Skip to content

Commit f8d4736

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 cdb7c37 commit f8d4736

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
@@ -156,6 +156,19 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
156156
sv.ssavalue_uses[sv.currpc] = saved_uses
157157
end
158158
end
159+
this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i]
160+
this_arginfo = ArgInfo(fargs, this_argtypes)
161+
162+
early_const_call_result = abstract_call_method_with_const_args_early(interp,
163+
f, match, this_arginfo, sv)
164+
if early_const_call_result !== nothing
165+
this_conditional = this_rt = early_const_call_result.rt
166+
(; effects, const_result) = early_const_call_result
167+
tristate_merge!(sv, effects)
168+
push!(const_results, const_result)
169+
any_const_result = true
170+
@goto call_computed
171+
end
159172

160173
result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, sv)
161174
this_conditional = ignorelimited(result.rt)
@@ -164,8 +177,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
164177
edge !== nothing && push!(edges, edge)
165178
# try constant propagation with argtypes for this match
166179
# this is in preparation for inlining, or improving the return result
167-
this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i]
168-
this_arginfo = ArgInfo(fargs, this_argtypes)
169180
const_call_result = abstract_call_method_with_const_args(interp, result,
170181
f, this_arginfo, match, sv)
171182
effects = result.edge_effects
@@ -185,6 +196,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f),
185196
push!(const_results, const_result)
186197
any_const_result |= const_result !== nothing
187198
end
199+
@label call_computed
188200
@assert !(this_conditional isa Conditional) "invalid lattice element returned from inter-procedural context"
189201
seen += 1
190202
rettype = tmerge(rettype, this_rt)
@@ -674,10 +686,11 @@ end
674686
function pure_eval_eligible(interp::AbstractInterpreter,
675687
@nospecialize(f), applicable::Vector{Any}, arginfo::ArgInfo, sv::InferenceState)
676688
# XXX we need to check that this pure function doesn't call any overlayed method
677-
return f !== nothing &&
678-
length(applicable) == 1 &&
679-
is_method_pure(applicable[1]::MethodMatch) &&
680-
is_all_const_arg(arginfo)
689+
f !== nothing || return false
690+
length(applicable) == 1 || return false
691+
match = applicable[1]::MethodMatch
692+
is_method_pure(match) || return false
693+
return is_all_const_arg(arginfo)
681694
end
682695

683696
function is_method_pure(method::Method, @nospecialize(sig), sparams::SimpleVector)
@@ -710,16 +723,17 @@ end
710723

711724
function concrete_eval_eligible(interp::AbstractInterpreter,
712725
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
713-
# disable concrete-evaluation since this function call is tainted by some overlayed
714-
# method and currently there is no direct way to execute overlayed methods
726+
# disable concrete-evaluation if this function call is tainted by some overlayed
727+
# method since currently there is no direct way to execute overlayed methods
715728
isoverlayed(method_table(interp)) && !is_nonoverlayed(result.edge_effects) && return false
716-
return f !== nothing &&
717-
result.edge !== nothing &&
718-
is_concrete_eval_eligible(result.edge_effects) &&
719-
is_all_const_arg(arginfo)
729+
f !== nothing || return false
730+
result.edge !== nothing || return false
731+
is_concrete_eval_eligible(result.edge_effects) || return false
732+
return is_all_const_arg(arginfo)
720733
end
721734

722-
function is_all_const_arg((; argtypes)::ArgInfo)
735+
is_all_const_arg((; argtypes)::ArgInfo) = is_all_const_arg(argtypes)
736+
function is_all_const_arg(argtypes::Vector{Any})
723737
for i = 2:length(argtypes)
724738
a = widenconditional(argtypes[i])
725739
isa(a, Const) || isconstType(a) || issingletontype(a) || return false
@@ -738,26 +752,61 @@ end
738752
function concrete_eval_call(interp::AbstractInterpreter,
739753
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::InferenceState)
740754
concrete_eval_eligible(interp, f, result, arginfo, sv) || return nothing
755+
return _concrete_eval_call(interp, f, arginfo, result.edge, sv)
756+
end
757+
758+
function _concrete_eval_call(interp::AbstractInterpreter,
759+
@nospecialize(f), arginfo::ArgInfo, edge::MethodInstance, sv::InferenceState)
741760
args = collect_const_args(arginfo)
742761
world = get_world_counter(interp)
743762
value = try
744763
Core._call_in_world_total(world, f, args...)
745764
catch
746765
# The evaulation threw. By :consistent-cy, we're guaranteed this would have happened at runtime
747-
return ConstCallResults(Union{}, ConstResult(result.edge, result.edge_effects), result.edge_effects)
766+
return ConstCallResults(Union{}, ConstResult(edge, EFFECTS_THROWS), EFFECTS_THROWS)
748767
end
749768
if is_inlineable_constant(value) || call_result_unused(sv)
750769
# If the constant is not inlineable, still do the const-prop, since the
751770
# code that led to the creation of the Const may be inlineable in the same
752771
# circumstance and may be optimizable.
753-
return ConstCallResults(Const(value), ConstResult(result.edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL)
772+
return ConstCallResults(Const(value), ConstResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL)
773+
end
774+
return nothing
775+
end
776+
777+
function early_concrete_eval_eligible(interp::AbstractInterpreter,
778+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
779+
# the effects for this match may not be derived yet, so disable concrete-evaluation
780+
# immediately when the interpreter can use overlayed methods
781+
isoverlayed(method_table(interp)) && return false
782+
f !== nothing || return false
783+
is_concrete_eval_eligible(decode_effects_override(match.method.purity)) || return false
784+
return is_all_const_arg(arginfo)
785+
end
786+
787+
function early_concrete_eval(interp::AbstractInterpreter,
788+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
789+
early_concrete_eval_eligible(interp, f, match, arginfo, sv) || return nothing
790+
edge = specialize_method(match.method, match.spec_types, match.sparams)
791+
edge === nothing && return nothing
792+
return _concrete_eval_call(interp, f, arginfo, edge, sv)
793+
end
794+
795+
function abstract_call_method_with_const_args_early(interp::AbstractInterpreter,
796+
@nospecialize(f), match::MethodMatch, arginfo::ArgInfo, sv::InferenceState)
797+
const_prop_enabled(interp, sv, match) || return nothing
798+
val = early_concrete_eval(interp, f, match, arginfo, sv)
799+
if val !== nothing
800+
add_backedge!(val.const_result.mi, sv)
801+
return val
754802
end
803+
# TODO early constant prop' for `@nospecialize`d methods?
755804
return nothing
756805
end
757806

758807
function const_prop_enabled(interp::AbstractInterpreter, sv::InferenceState, match::MethodMatch)
759808
if !InferenceParams(interp).ipo_constant_propagation
760-
add_remark!(interp, sv, "[constprop] Disabled by parameter")
809+
add_remark!(interp, sv, "[constprop] Disabled by inference parameter")
761810
return false
762811
end
763812
method = match.method
@@ -781,12 +830,10 @@ end
781830
function abstract_call_method_with_const_args(interp::AbstractInterpreter, result::MethodCallResult,
782831
@nospecialize(f), arginfo::ArgInfo, match::MethodMatch,
783832
sv::InferenceState)
784-
if !const_prop_enabled(interp, sv, match)
785-
return nothing
786-
end
833+
const_prop_enabled(interp, sv, match) || return nothing
787834
val = concrete_eval_call(interp, f, result, arginfo, sv)
788835
if val !== nothing
789-
add_backedge!(result.edge, sv)
836+
add_backedge!(val.const_result.mi, sv)
790837
return val
791838
end
792839
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)