Skip to content

Commit

Permalink
inference: continue const-prop' when concrete-eval returns non-inline…
Browse files Browse the repository at this point in the history
…able (#50618)
  • Loading branch information
aviatesk committed Aug 5, 2023
1 parent 9e9f67d commit 80ccc37
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 13 deletions.
43 changes: 30 additions & 13 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -773,11 +773,13 @@ struct ConstCallResults
const_result::ConstResult
effects::Effects
edge::MethodInstance
ConstCallResults(@nospecialize(rt),
const_result::ConstResult,
effects::Effects,
edge::MethodInstance) =
new(rt, const_result, effects, edge)
function ConstCallResults(
@nospecialize(rt),
const_result::ConstResult,
effects::Effects,
edge::MethodInstance)
return new(rt, const_result, effects, edge)
end
end

function abstract_call_method_with_const_args(interp::AbstractInterpreter,
Expand All @@ -791,24 +793,33 @@ function abstract_call_method_with_const_args(interp::AbstractInterpreter,
return nothing
end
eligibility = concrete_eval_eligible(interp, f, result, arginfo, sv)
concrete_eval_result = nothing
if eligibility === :concrete_eval
return concrete_eval_call(interp, f, result, arginfo, sv, invokecall)
concrete_eval_result = concrete_eval_call(interp, f, result, arginfo, sv, invokecall)
# if we don't inline the result of this concrete evaluation,
# give const-prop' a chance to inline a better method body
if !may_optimize(interp) || (
may_inline_concrete_result(concrete_eval_result.const_result::ConcreteResult) ||
concrete_eval_result.rt === Bottom) # unless this call deterministically throws and thus is non-inlineable
return concrete_eval_result
end
# TODO allow semi-concrete interp for this call?
end
mi = maybe_get_const_prop_profitable(interp, result, f, arginfo, si, match, sv)
mi === nothing && return nothing
mi === nothing && return concrete_eval_result
if is_constprop_recursed(result, mi, sv)
add_remark!(interp, sv, "[constprop] Edge cycle encountered")
return nothing
end
# try semi-concrete evaluation
if eligibility === :semi_concrete_eval
res = semi_concrete_eval_call(interp, mi, result, arginfo, sv)
if res !== nothing
return res
irinterp_result = semi_concrete_eval_call(interp, mi, result, arginfo, sv)
if irinterp_result !== nothing
return irinterp_result
end
end
# try constant prop'
return const_prop_call(interp, mi, result, arginfo, sv)
return const_prop_call(interp, mi, result, arginfo, sv, concrete_eval_result)
end

function const_prop_enabled(interp::AbstractInterpreter, sv::AbsIntState, match::MethodMatch)
Expand Down Expand Up @@ -900,7 +911,7 @@ function concrete_eval_call(interp::AbstractInterpreter,
Core._call_in_world_total(world, f, args...)
catch
# The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime
return ConstCallResults(Union{}, ConcreteResult(edge, result.effects), result.effects, edge)
return ConstCallResults(Bottom, ConcreteResult(edge, result.effects), result.effects, edge)
end
return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge)
end
Expand Down Expand Up @@ -1170,7 +1181,8 @@ function semi_concrete_eval_call(interp::AbstractInterpreter,
end

function const_prop_call(interp::AbstractInterpreter,
mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState)
mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState,
concrete_eval_result::Union{Nothing,ConstCallResults}=nothing)
inf_cache = get_inference_cache(interp)
𝕃ᵢ = typeinf_lattice(interp)
inf_result = cache_lookup(𝕃ᵢ, mi, arginfo.argtypes, inf_cache)
Expand All @@ -1193,6 +1205,11 @@ function const_prop_call(interp::AbstractInterpreter,
return nothing
end
@assert inf_result.result !== nothing
if concrete_eval_result !== nothing
# override return type and effects with concrete evaluation result if available
inf_result.result = concrete_eval_result.rt
inf_result.ipo_effects = concrete_eval_result.effects
end
else
# found the cache for this constant prop'
if inf_result.result === nothing
Expand Down
13 changes: 13 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5030,3 +5030,16 @@ h_issue50544(x::T) where T = g_issue50544(f_issue50544(T, 1), f_issue50544(T, 2,
let x = Issue50544((1, Issue50544((2.0, 'x'))))
@test only(Base.return_types(h_issue50544, (typeof(x),))) == Type{Issue50544{Tuple{Int,Float64}}}
end

# override const-prop' return type with the concrete-eval result
# if concrete-eval returns non-inlineable constant
Base.@assume_effects :foldable function continue_const_prop(i, j)
chars = map(Char, i:j)
String(chars)
end
@test Base.return_types() do
Val(length(continue_const_prop(1, 5)))
end |> only === Val{5}
@test fully_eliminated() do
length(continue_const_prop(1, 5))
end
14 changes: 14 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2082,3 +2082,17 @@ end
z = erase_before_inlining(true, y)
return length(z), length(z)
end

# continue const-prop' when concrete-eval result is too big
const THE_BIG_TUPLE_2 = ntuple(identity, 1024)
return_the_big_tuple2(a) = (a, THE_BIG_TUPLE_2)
let src = code_typed1() do
return return_the_big_tuple2(42)[2]
end
@test count(isinvoke(:return_the_big_tuple2), src.code) == 0
end
let src = code_typed1() do
return iterate(("1", '2'), 1)
end
@test count(isinvoke(:iterate), src.code) == 0
end

0 comments on commit 80ccc37

Please sign in to comment.