Skip to content

Commit d3bf5db

Browse files
committed
inference: early const-prop' pass
We discussed that for certain methods like `:total`ly-declared method or `@nospecialize`d method we may want to only do constant propagation while disabling preceding only-type-level inference. This commit implements a simple infrastructure for such early constant propagation, and especially setups early concrete evaluation pass. This commit should recover the regression reported at <#44776 (comment)> while preserving the advantages and correctness improvements of the `@assume_effects`-based concrete evaluation enabled in the PR.
1 parent 2636722 commit d3bf5db

File tree

2 files changed

+72
-20
lines changed

2 files changed

+72
-20
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,19 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
158158
sv.ssavalue_uses[sv.currpc] = saved_uses
159159
end
160160
end
161+
this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i]
162+
this_arginfo = ArgInfo(fargs, this_argtypes)
163+
164+
early_const_call_result = abstract_call_method_with_const_args_early(interp,
165+
f, match, this_arginfo, sv)
166+
if early_const_call_result !== nothing
167+
this_conditional = this_rt = early_const_call_result.rt
168+
(; effects, const_result) = early_const_call_result
169+
tristate_merge!(sv, effects)
170+
push!(const_results, const_result)
171+
any_const_result = true
172+
@goto call_computed
173+
end
161174

162175
result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, sv)
163176
this_conditional = ignorelimited(result.rt)
@@ -166,8 +179,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
166179
edge !== nothing && push!(edges, edge)
167180
# try constant propagation with argtypes for this match
168181
# this is in preparation for inlining, or improving the return result
169-
this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i]
170-
this_arginfo = ArgInfo(fargs, this_argtypes)
171182
const_call_result = abstract_call_method_with_const_args(interp, result,
172183
f, this_arginfo, match, sv)
173184
effects = result.edge_effects
@@ -187,6 +198,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
187198
push!(const_results, const_result)
188199
any_const_result |= const_result !== nothing
189200
end
201+
@label call_computed
190202
@assert !(this_conditional isa Conditional) "invalid lattice element returned from inter-procedural context"
191203
seen += 1
192204
rettype = tmerge(rettype, this_rt)
@@ -682,10 +694,11 @@ end
682694
function pure_eval_eligible(interp::AbstractInterpreter,
683695
@nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState)
684696
# XXX we need to check that this pure function doesn't call any overlayed method
685-
return f !== nothing &&
686-
length(applicable) == 1 &&
687-
is_method_pure(applicable[1]::MethodMatch) &&
688-
is_all_const_arg(arginfo)
697+
f !== nothing || return false
698+
length(applicable) == 1 || return false
699+
match = applicable[1]::MethodMatch
700+
is_method_pure(match) || return false
701+
return is_all_const_arg(arginfo)
689702
end
690703

691704
function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector)
@@ -718,16 +731,17 @@ end
718731

719732
function concrete_eval_eligible(interp::AbstractInterpreter,
720733
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
721-
# disable concrete-evaluation since this function call is tainted by some overlayed
722-
# method and currently there is no direct way to execute overlayed methods
734+
# disable concrete-evaluation if this function call is tainted by some overlayed
735+
# method since currently there is no direct way to execute overlayed methods
723736
isoverlayed(method_table(interp)) && !is_nonoverlayed(result.edge_effects) && return false
724-
return f !== nothing &&
725-
result.edge !== nothing &&
726-
is_concrete_eval_eligible(result.edge_effects) &&
727-
is_all_const_arg(arginfo)
737+
f !== nothing || return false
738+
result.edge !== nothing || return false
739+
is_concrete_eval_eligible(result.edge_effects) || return false
740+
return is_all_const_arg(arginfo)
728741
end
729742

730-
function is_all_const_arg((; argtypes)::ArgInfo)
743+
is_all_const_arg((; argtypes)::ArgInfo) = is_all_const_arg(argtypes)
744+
function is_all_const_arg(argtypes::Vector{Any})
731745
for i = 2:length(argtypes)
732746
a = widenconditional(argtypes[i])
733747
isa(a, Const) || isconstType(a) || issingletontype(a) || return false
@@ -746,26 +760,61 @@ end
746760
function concrete_eval_call(interp::AbstractInterpreter,
747761
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
748762
concrete_eval_eligible(interp, f, result, arginfo, sv) || return nothing
763+
return _concrete_eval_call(interp, f, arginfo, result.edge, sv)
764+
end
765+
766+
function _concrete_eval_call(interp::AbstractInterpreter,
767+
@nospecialize(f), arginfo::ArgInfo, edge::MethodInstance, sv::InferenceState)
749768
args = collect_const_args(arginfo)
750769
world = get_world_counter(interp)
751770
value = try
752771
Core._call_in_world_total(world, f, args...)
753772
catch
754773
# The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime
755-
return ConstCallResults(Union{}, ConcreteResult(result.edge, result.edge_effects), result.edge_effects)
774+
return ConstCallResults(Union{}, ConcreteResult(edge, EFFECTS_THROWS), EFFECTS_THROWS)
756775
end
757776
if is_inlineable_constant(value) || call_result_unused(sv)
758777
# If the constant is not inlineable, still do the const-prop, since the
759778
# code that led to the creation of the Const may be inlineable in the same
760779
# circumstance and may be optimizable.
761-
return ConstCallResults(Const(value), ConcreteResult(result.edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL)
780+
return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL)
762781
end
763782
return nothing
764783
end
765784

785+
function early_concrete_eval_eligible(interp::AbstractInterpreter,
786+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
787+
# the effects for this match may not be derived yet, so disable concrete-evaluation
788+
# immediately when the interpreter can use overlayed methods
789+
isoverlayed(method_table(interp)) && return false
790+
f !== nothing || return false
791+
is_concrete_eval_eligible(decode_effects_override(match.method.purity)) || return false
792+
return is_all_const_arg(arginfo)
793+
end
794+
795+
function early_concrete_eval(interp::AbstractInterpreter,
796+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
797+
early_concrete_eval_eligible(interp, f, match, arginfo, sv) || return nothing
798+
edge = specialize_method(match.method, match.spec_types, match.sparams)
799+
edge === nothing && return nothing
800+
return _concrete_eval_call(interp, f, arginfo, edge, sv)
801+
end
802+
803+
function abstract_call_method_with_const_args_early(interp::AbstractInterpreter,
804+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
805+
const_prop_enabled(interp, sv, match) || return nothing
806+
val = early_concrete_eval(interp, f, match, arginfo, sv)
807+
if val !== nothing
808+
add_backedge!(val.const_result.mi, sv)
809+
return val
810+
end
811+
# TODO early constant prop' for `@nospecialize`d methods?
812+
return nothing
813+
end
814+
766815
function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch)
767816
if !InferenceParams(interp).ipo_constant_propagation
768-
add_remark!(interp, sv, "[constprop] Disabled by parameter")
817+
add_remark!(interp, sv, "[constprop] Disabled by inference parameter")
769818
return false
770819
end
771820
method = match.method
@@ -789,12 +838,10 @@ end
789838
function abstract_call_method_with_const_args(interp::AbstractInterpreter, result::MethodCallResult,
790839
@nospecialize(f), arginfo::ArgInfo, match::MethodMatch,
791840
sv::InferenceState)
792-
if !const_prop_enabled(interp, sv, match)
793-
return nothing
794-
end
841+
const_prop_enabled(interp, sv, match) || return nothing
795842
val = concrete_eval_call(interp, f, result, arginfo, sv)
796843
if val !== nothing
797-
add_backedge!(result.edge, sv)
844+
add_backedge!(val.const_result.mi, sv)
798845
return val
799846
end
800847
mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, match, sv)

base/compiler/types.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ function decode_effects_override(e::UInt8)
185185
(e & 0x10) != 0x00)
186186
end
187187

188+
is_concrete_eval_eligible(eo::EffectsOverride) =
189+
eo.consistent &&
190+
eo.effect_free &&
191+
eo.terminates_globally
192+
188193
"""
189194
InferenceResult
190195

0 commit comments

Comments
 (0)