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

This commit fixes the regression in the following example:
```julia
julia> stritr() = iterate(("1", '2'), 1);

julia> @time stritr();
  0.000001 seconds (2 allocations: 64 bytes) # on master
  0.000000 seconds                           # on 1.9
```

The problem is that currently we don't inline result of concrete-eval
when its result is not inlineable, although const-prop' or semi-concrete
eval may be able to optimize and inline the method body.
To improve the situation, this commit simply allows
`abstract_call_method_with_const_args` to continue to const-prop' when
concrete-eval returned non-inlineable result.
  • Loading branch information
aviatesk committed Aug 2, 2023
1 parent 3c64bc3 commit ed01198
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 16 deletions.
57 changes: 41 additions & 16 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -767,11 +767,21 @@ 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
function ConstCallResults(
result::ConstCallResults,
@nospecialize(rt = result.rt);
const_result::ConstResult = result.const_result,
effects::Effects = result.effects,
edge::MethodInstance = result.edge)
return new(rt, const_result, effects, edge)
end
end

function abstract_call_method_with_const_args(interp::AbstractInterpreter,
Expand All @@ -785,24 +795,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 @@ -881,7 +900,7 @@ function collect_const_args(argtypes::Vector{Any}, start::Int)
end

function concrete_eval_call(interp::AbstractInterpreter,
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState;
@nospecialize(f), result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState,
invokecall::Union{InvokeCall,Nothing}=nothing)
args = collect_const_args(arginfo, #=start=#2)
if invokecall !== nothing
Expand All @@ -895,7 +914,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 @@ -1153,16 +1172,17 @@ function semi_concrete_eval_call(interp::AbstractInterpreter,
# that are newly resovled by irinterp
# state = InliningState(interp)
# ir = ssa_inlining_pass!(irsv.ir, state, propagate_inbounds(irsv))
new_effects = Effects(result.effects; nothrow)
return ConstCallResults(rt, SemiConcreteResult(mi, ir, new_effects), new_effects, mi)
effects = Effects(result.effects; nothrow)
return ConstCallResults(rt, SemiConcreteResult(mi, ir, effects), effects, mi)
end
end
end
return nothing
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 @@ -1185,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
11 changes: 11 additions & 0 deletions test/compiler/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5099,3 +5099,14 @@ end
refine_partial_struct2(42, s)
end |> only === String
# JET.test_call(s::AbstractString->Base._string(s, 'c'))

Base.@assume_effects :foldable function continue_const_prop(i, j)
chars = map(Char, i:j)
String(chars)
end
@test Base.return_types() do
length(continue_const_prop(1, 5)) == 5 ? :ok : nothing
end |> only === Symbol
@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 @@ -2065,3 +2065,17 @@ end
# https://github.com/JuliaLang/julia/issues/50612
f50612(x) = UInt32(x)
@test all(!isinvoke(:UInt32),get_code(f50612,Tuple{Char}))

# 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 ed01198

Please sign in to comment.