Skip to content

Commit e5ccc44

Browse files
committed
inference: always bail out const-prop' with non-const result limited (#52836)
Investigating into #52763, I've found that `AbstractInterpreters` which enables the `aggressive_constprop` option, such as `REPLInterpreter`, might perform const-prop' even if the result of a non-const call is `LimitedAccuracy`. This can lead to an unintended infinite loop with a custom aggressive const-prop' implementation. This commit restricts const-prop' for such cases where the non-const call result is limited to avoid the issue. This fix is conservative, but given that accurate inference is mostly impossible when there are unresolvable cycles (which is indicated by limited result), aggressive const-prop' isn't necessary for such cases, and I don't anticipate this leading to any obvious regression. fix #52763
1 parent 813bfac commit e5ccc44

File tree

2 files changed

+41
-26
lines changed

2 files changed

+41
-26
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -785,11 +785,7 @@ end
785785
function abstract_call_method_with_const_args(interp::AbstractInterpreter,
786786
result::MethodCallResult, @nospecialize(f), arginfo::ArgInfo, si::StmtInfo,
787787
match::MethodMatch, sv::AbsIntState, invokecall::Union{Nothing,InvokeCall}=nothing)
788-
if !const_prop_enabled(interp, sv, match)
789-
return nothing
790-
end
791-
if bail_out_const_call(interp, result, si)
792-
add_remark!(interp, sv, "[constprop] No more information to be gained")
788+
if !const_prop_enabled(interp, match, sv) || bail_out_const_call(interp, result, si, sv)
793789
return nothing
794790
end
795791
eligibility = concrete_eval_eligible(interp, f, result, arginfo, sv)
@@ -822,7 +818,7 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter,
822818
return const_prop_call(interp, mi, result, arginfo, sv, concrete_eval_result)
823819
end
824820

825-
function const_prop_enabled(interp::AbstractInterpreter, sv::AbsIntState, match::MethodMatch)
821+
function const_prop_enabled(interp::AbstractInterpreter, match::MethodMatch, sv::AbsIntState)
826822
if !InferenceParams(interp).ipo_constant_propagation
827823
add_remark!(interp, sv, "[constprop] Disabled by parameter")
828824
return false
@@ -834,9 +830,11 @@ function const_prop_enabled(interp::AbstractInterpreter, sv::AbsIntState, match:
834830
return true
835831
end
836832

837-
function bail_out_const_call(interp::AbstractInterpreter, result::MethodCallResult, si::StmtInfo)
833+
function bail_out_const_call(interp::AbstractInterpreter, result::MethodCallResult,
834+
si::StmtInfo, sv::AbsIntState)
838835
if is_removable_if_unused(result.effects)
839836
if isa(result.rt, Const) || call_result_unused(si)
837+
add_remark!(interp, sv, "[constprop] No more information to be gained (const)")
840838
return true
841839
end
842840
end
@@ -937,7 +935,10 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter,
937935
match::MethodMatch, sv::AbsIntState)
938936
method = match.method
939937
force = force_const_prop(interp, f, method)
940-
force || const_prop_entry_heuristic(interp, result, si, sv) || return nothing
938+
if !const_prop_entry_heuristic(interp, result, si, sv, force)
939+
# N.B. remarks are emitted within `const_prop_entry_heuristic`
940+
return nothing
941+
end
941942
nargs::Int = method.nargs
942943
method.isva && (nargs -= 1)
943944
length(arginfo.argtypes) < nargs && return nothing
@@ -964,8 +965,17 @@ function maybe_get_const_prop_profitable(interp::AbstractInterpreter,
964965
return mi
965966
end
966967

967-
function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult, si::StmtInfo, sv::AbsIntState)
968-
if call_result_unused(si) && result.edgecycle
968+
function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodCallResult,
969+
si::StmtInfo, sv::AbsIntState, force::Bool)
970+
if result.rt isa LimitedAccuracy
971+
# optimizations like inlining are disabled for limited frames,
972+
# thus there won't be much benefit in constant-prop' here
973+
# N.B. don't allow forced constprop' for safety (xref #52763)
974+
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (limited accuracy)")
975+
return false
976+
elseif force
977+
return true
978+
elseif call_result_unused(si) && result.edgecycle
969979
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (edgecycle with unused result)")
970980
return false
971981
end
@@ -978,27 +988,21 @@ function const_prop_entry_heuristic(interp::AbstractInterpreter, result::MethodC
978988
if rt === Bottom
979989
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (erroneous result)")
980990
return false
981-
else
982-
return true
983991
end
992+
return true
984993
elseif isa(rt, PartialStruct) || isa(rt, InterConditional) || isa(rt, InterMustAlias)
985994
# could be improved to `Const` or a more precise wrapper
986995
return true
987-
elseif isa(rt, LimitedAccuracy)
988-
# optimizations like inlining are disabled for limited frames,
989-
# thus there won't be much benefit in constant-prop' here
990-
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (limited accuracy)")
991-
return false
992-
else
993-
if isa(rt, Const)
994-
if !is_nothrow(result.effects)
995-
# Could still be improved to Bottom (or at least could see the effects improved)
996-
return true
997-
end
996+
elseif isa(rt, Const)
997+
if is_nothrow(result.effects)
998+
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (nothrow const)")
999+
return false
9981000
end
999-
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable result)")
1000-
return false
1001+
# Could still be improved to Bottom (or at least could see the effects improved)
1002+
return true
10011003
end
1004+
add_remark!(interp, sv, "[constprop] Disabled by entry heuristic (unimprovable result)")
1005+
return false
10021006
end
10031007

10041008
# determines heuristically whether if constant propagation can be worthwhile

test/compiler/inference.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4845,10 +4845,21 @@ g() = empty_nt_values(Base.inferencebarrier(Tuple{}))
48454845
# This is somewhat sensitive to the exact recursion level that inference is willing to do, but the intention
48464846
# is to test the case where inference limited a recursion, but then a forced constprop nevertheless managed
48474847
# to terminate the call.
4848+
@newinterp RecurseInterpreter
4849+
let CC = Core.Compiler
4850+
function CC.const_prop_entry_heuristic(interp::RecurseInterpreter, result::CC.MethodCallResult,
4851+
si::CC.StmtInfo, sv::CC.AbsIntState, force::Bool)
4852+
if result.rt isa CC.LimitedAccuracy
4853+
return force # allow forced constprop to recurse into unresolved cycles
4854+
end
4855+
return @invoke CC.const_prop_entry_heuristic(interp::CC.AbstractInterpreter, result::CC.MethodCallResult,
4856+
si::CC.StmtInfo, sv::CC.AbsIntState, force::Bool)
4857+
end
4858+
end
48484859
Base.@constprop :aggressive type_level_recurse1(x...) = x[1] == 2 ? 1 : (length(x) > 100 ? x : type_level_recurse2(x[1] + 1, x..., x...))
48494860
Base.@constprop :aggressive type_level_recurse2(x...) = type_level_recurse1(x...)
48504861
type_level_recurse_entry() = Val{type_level_recurse1(1)}()
4851-
@test Base.return_types(type_level_recurse_entry, ()) |> only == Val{1}
4862+
@test only(Base.return_types(type_level_recurse_entry, (); interp=RecurseInterpreter())) == Val{1}
48524863

48534864
# Test that inference doesn't give up if it can potentially refine effects,
48544865
# even if the return type is Any.

0 commit comments

Comments
 (0)