-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AbstractInterpreter
: make it easier to overload pure/concrete-eval (#…
- Loading branch information
Showing
2 changed files
with
88 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,13 +65,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), | |
const_results = Union{InferenceResult,Nothing,ConstResult}[] | ||
multiple_matches = napplicable > 1 | ||
|
||
if f !== nothing && napplicable == 1 && is_method_pure(applicable[1]::MethodMatch) | ||
val = pure_eval_call(f, argtypes) | ||
if val !== nothing | ||
# TODO: add some sort of edge(s) | ||
return CallMeta(val, MethodResultPure(info)) | ||
end | ||
end | ||
val = pure_eval_call(interp, f, applicable, arginfo, sv) | ||
val !== nothing && return CallMeta(val, MethodResultPure(info)) # TODO: add some sort of edge(s) | ||
|
||
fargs = arginfo.fargs | ||
for i in 1:napplicable | ||
|
@@ -619,27 +614,85 @@ struct MethodCallResult | |
end | ||
end | ||
|
||
function pure_eval_eligible(interp::AbstractInterpreter, | ||
@nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState) | ||
return !isoverlayed(method_table(interp, sv)) && | ||
f !== nothing && | ||
length(applicable) == 1 && | ||
is_method_pure(applicable[1]::MethodMatch) && | ||
is_all_const_arg(arginfo) | ||
end | ||
|
||
function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector) | ||
if isdefined(method, :generator) | ||
method.generator.expand_early || return false | ||
mi = specialize_method(method, sig, sparams) | ||
isa(mi, MethodInstance) || return false | ||
staged = get_staged(mi) | ||
(staged isa CodeInfo && (staged::CodeInfo).pure) || return false | ||
return true | ||
end | ||
return method.pure | ||
end | ||
is_method_pure(match::MethodMatch) = is_method_pure(match.method, match.spec_types, match.sparams) | ||
|
||
function pure_eval_call(interp::AbstractInterpreter, | ||
@nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState) | ||
pure_eval_eligible(interp, f, applicable, arginfo, sv) || return nothing | ||
return _pure_eval_call(f, arginfo) | ||
end | ||
function _pure_eval_call(@nospecialize(f), arginfo::ArgInfo) | ||
args = collect_const_args(arginfo) | ||
try | ||
value = Core._apply_pure(f, args) | ||
return Const(value) | ||
catch | ||
return nothing | ||
end | ||
end | ||
|
||
function concrete_eval_eligible(interp::AbstractInterpreter, | ||
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) | ||
return !isoverlayed(method_table(interp, sv)) && | ||
f !== nothing && | ||
result.edge !== nothing && | ||
is_total_or_error(result.edge_effects) && | ||
is_all_const_arg(arginfo) | ||
end | ||
|
||
function is_all_const_arg((; argtypes)::ArgInfo) | ||
for a in argtypes | ||
if !isa(a, Const) && !isconstType(a) && !issingletontype(a) | ||
return false | ||
end | ||
for i = 2:length(argtypes) | ||
a = widenconditional(argtypes[i]) | ||
isa(a, Const) || isconstType(a) || issingletontype(a) || return false | ||
end | ||
return true | ||
end | ||
|
||
function concrete_eval_const_proven_total_or_error(interp::AbstractInterpreter, | ||
@nospecialize(f), (; argtypes)::ArgInfo, _::InferenceState) | ||
args = Any[ (a = widenconditional(argtypes[i]); | ||
isa(a, Const) ? a.val : | ||
isconstType(a) ? (a::DataType).parameters[1] : | ||
(a::DataType).instance) for i in 2:length(argtypes) ] | ||
function collect_const_args((; argtypes)::ArgInfo) | ||
return Any[ let a = widenconditional(argtypes[i]) | ||
isa(a, Const) ? a.val : | ||
isconstType(a) ? (a::DataType).parameters[1] : | ||
(a::DataType).instance | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
vtjnash
Member
|
||
end for i in 2:length(argtypes) ] | ||
end | ||
|
||
function concrete_eval_call(interp::AbstractInterpreter, | ||
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState) | ||
concrete_eval_eligible(interp, f, result, arginfo, sv) || return nothing | ||
args = collect_const_args(arginfo) | ||
try | ||
value = Core._call_in_world_total(get_world_counter(interp), f, args...) | ||
return Const(value) | ||
catch e | ||
return nothing | ||
if is_inlineable_constant(value) || call_result_unused(sv) | ||
# If the constant is not inlineable, still do the const-prop, since the | ||
# code that led to the creation of the Const may be inlineable in the same | ||
# circumstance and may be optimizable. | ||
return ConstCallResults(Const(value), ConstResult(result.edge, value), EFFECTS_TOTAL) | ||
end | ||
catch | ||
# The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime | ||
return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) | ||
end | ||
return nothing | ||
end | ||
|
||
function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch) | ||
|
@@ -671,19 +724,10 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter, resul | |
if !const_prop_enabled(interp, sv, match) | ||
return nothing | ||
end | ||
if f !== nothing && result.edge !== nothing && is_total_or_error(result.edge_effects) && is_all_const_arg(arginfo) | ||
rt = concrete_eval_const_proven_total_or_error(interp, f, arginfo, sv) | ||
val = concrete_eval_call(interp, f, result, arginfo, sv) | ||
if val !== nothing | ||
add_backedge!(result.edge, sv) | ||
if rt === nothing | ||
# The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime | ||
return ConstCallResults(Union{}, ConstResult(result.edge), result.edge_effects) | ||
end | ||
if is_inlineable_constant(rt.val) || call_result_unused(sv) | ||
# If the constant is not inlineable, still do the const-prop, since the | ||
# code that led to the creation of the Const may be inlineable in the same | ||
# circumstance and may be optimizable. | ||
return ConstCallResults(rt, ConstResult(result.edge, rt.val), EFFECTS_TOTAL) | ||
end | ||
return val | ||
end | ||
mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv) | ||
mi === nothing && return nothing | ||
|
@@ -1218,36 +1262,6 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, sv:: | |
return CallMeta(res, retinfo) | ||
end | ||
|
||
function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector) | ||
if isdefined(method, :generator) | ||
method.generator.expand_early || return false | ||
mi = specialize_method(method, sig, sparams) | ||
isa(mi, MethodInstance) || return false | ||
staged = get_staged(mi) | ||
(staged isa CodeInfo && (staged::CodeInfo).pure) || return false | ||
return true | ||
end | ||
return method.pure | ||
end | ||
is_method_pure(match::MethodMatch) = is_method_pure(match.method, match.spec_types, match.sparams) | ||
|
||
function pure_eval_call(@nospecialize(f), argtypes::Vector{Any}) | ||
for i = 2:length(argtypes) | ||
a = widenconditional(argtypes[i]) | ||
if !(isa(a, Const) || isconstType(a)) | ||
return nothing | ||
end | ||
end | ||
args = Any[ (a = widenconditional(argtypes[i]); | ||
isa(a, Const) ? a.val : (a::DataType).parameters[1]) for i in 2:length(argtypes) ] | ||
try | ||
value = Core._apply_pure(f, args) | ||
return Const(value) | ||
catch | ||
return nothing | ||
end | ||
end | ||
|
||
function argtype_by_index(argtypes::Vector{Any}, i::Int) | ||
n = length(argtypes) | ||
na = argtypes[n] | ||
|
@@ -1586,8 +1600,10 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), | |
elseif max_methods > 1 && istopfunction(f, :copyto!) | ||
max_methods = 1 | ||
elseif la == 3 && istopfunction(f, :typejoin) | ||
val = pure_eval_call(f, argtypes) | ||
return CallMeta(val === nothing ? Type : val, MethodResultPure()) | ||
if is_all_const_arg(arginfo) | ||
val = _pure_eval_call(f, arginfo) | ||
return CallMeta(val === nothing ? Type : val, MethodResultPure()) | ||
end | ||
end | ||
atype = argtypes_to_type(argtypes) | ||
return abstract_call_gf_by_type(interp, f, arginfo, atype, sv, max_methods) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
While working on #46010 I've discovered a case in which
a.instance
is undefined.argtypes
isand this is triggered from this test. The problematic argument is
Type{Tuple{Any}}
.Any thoughts about how to fix this? The problem might well be me choosing to call
collect_const_args
on that particular signature, so maybe there is some kind of validation I need to do first? Basically I'm just trying to strip off typelattice annotations likeConst
and restore the regular signature. Perhaps I should just do that manually. But it seemed worth reporting the issue, just in case this is a bug.