Skip to content

Commit 0c338b7

Browse files
committed
re-infer source when it's not cached
1 parent ea67453 commit 0c338b7

File tree

5 files changed

+35
-38
lines changed

5 files changed

+35
-38
lines changed

base/compiler/abstractinterpretation.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,7 @@ function const_prop_methodinstance_heuristic(interp::AbstractInterpreter, match:
712712
cache_inf = code.inferred
713713
if !(cache_inf === nothing)
714714
# TODO maybe we want to respect callsite `@inline`/`@noinline` annotations here ?
715-
cache_inlineable = inlining_policy(interp)(cache_inf, nothing, match) !== nothing
715+
cache_inlineable = inlining_policy(interp, cache_inf, 0x00, match) !== nothing
716716
end
717717
end
718718
if !cache_inlineable

base/compiler/optimize.jl

+12-14
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ function push!(et::EdgeTracker, ci::CodeInstance)
2121
push!(et, ci.def)
2222
end
2323

24-
struct InliningState{S <: Union{EdgeTracker, Nothing}, T, P, U}
24+
struct InliningState{S <: Union{EdgeTracker, Nothing}, T, I<:AbstractInterpreter}
2525
params::OptimizationParams
2626
et::S
2727
mi_cache::T
28-
inf_cache::U
29-
policy::P
28+
interp::I
3029
end
3130

32-
function default_inlining_policy(@nospecialize(src), stmt_flag::Union{Nothing,UInt8}, match::Union{MethodMatch,InferenceResult})
31+
function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_flag::UInt8, match::Union{MethodMatch,InferenceResult})
3332
if isa(src, CodeInfo) || isa(src, Vector{UInt8})
3433
src_inferred = ccall(:jl_ir_flag_inferred, Bool, (Any,), src)
3534
src_inlineable = is_stmt_inline(stmt_flag) || ccall(:jl_ir_flag_inlineable, Bool, (Any,), src)
@@ -38,9 +37,12 @@ function default_inlining_policy(@nospecialize(src), stmt_flag::Union{Nothing,UI
3837
return (is_stmt_inline(stmt_flag) || src.src.inlineable) ? src.ir : nothing
3938
elseif src === nothing && is_stmt_inline(stmt_flag) && isa(match, MethodMatch)
4039
# when the source isn't available at this moment, try to re-infer and inline it
41-
# HACK in order to avoid cycles here, we disable inlining and makes sure the following inference never comes here
42-
# TODO sort out `AbstractInterpreter` interface to handle this well, and also inference should try to keep the source if the statement will be inlined
43-
interp = NativeInterpreter(; opt_params = OptimizationParams(; inlining = false))
40+
# NOTE we can make inference try to keep the source if the call is going to be inlined,
41+
# but then inlining will depend on local state of inference and so the first entry
42+
# and the succeeding ones may generate different code; rather we always re-infer
43+
# the source to avoid the problem while it's obviously not most efficient
44+
# HACK disable inlining for the re-inference to avoid cycles by making sure the following inference never comes here again
45+
interp = NativeInterpreter(get_world_counter(interp); opt_params = OptimizationParams(; inlining = false))
4446
src, rt = typeinf_code(interp, match.method, match.spec_types, match.sparams, true)
4547
return src
4648
end
@@ -64,8 +66,7 @@ mutable struct OptimizationState
6466
inlining = InliningState(params,
6567
EdgeTracker(s_edges, frame.valid_worlds),
6668
WorldView(code_cache(interp), frame.world),
67-
get_inference_cache(interp),
68-
inlining_policy(interp))
69+
interp)
6970
return new(frame.linfo,
7071
frame.src, nothing, frame.stmt_info, frame.mod,
7172
frame.sptypes, frame.slottypes, false,
@@ -94,8 +95,7 @@ mutable struct OptimizationState
9495
inlining = InliningState(params,
9596
nothing,
9697
WorldView(code_cache(interp), get_world_counter()),
97-
get_inference_cache(interp),
98-
inlining_policy(interp))
98+
interp)
9999
return new(linfo,
100100
src, nothing, stmt_info, mod,
101101
sptypes_from_meth_instance(linfo), slottypes, false,
@@ -187,10 +187,8 @@ function isinlineable(m::Method, me::OptimizationState, params::OptimizationPara
187187
return inlineable
188188
end
189189

190-
is_stmt_inline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_INLINE != 0
191-
is_stmt_inline(::Nothing) = false
190+
is_stmt_inline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_INLINE != 0
192191
is_stmt_noinline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_NOINLINE != 0
193-
is_stmt_noinline(::Nothing) = false # not used for now
194192

195193
# These affect control flow within the function (so may not be removed
196194
# if there is no usage within the function), but don't affect the purity

base/compiler/ssair/inlining.jl

+6-22
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
725725
(; match) = todo.spec::DelayedInliningSpec
726726

727727
#XXX: update_valid_age!(min_valid[1], max_valid[1], sv)
728-
isconst, src, argtypes = false, nothing, nothing
728+
isconst, src = false, nothing
729729
if isa(match, InferenceResult)
730730
let inferred_src = match.src
731731
if isa(inferred_src, Const)
@@ -737,9 +737,6 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
737737
isconst, src = false, inferred_src
738738
end
739739
end
740-
if is_stmt_inline(flag)
741-
argtypes = match.argtypes
742-
end
743740
else
744741
linfo = get(state.mi_cache, todo.mi, nothing)
745742
if linfo isa CodeInstance
@@ -752,9 +749,6 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
752749
else
753750
isconst, src = false, linfo
754751
end
755-
if is_stmt_inline(flag)
756-
argtypes = collect(todo.mi.specTypes.parameters)::Vector{Any}
757-
end
758752
end
759753

760754
et = state.et
@@ -764,18 +758,7 @@ function resolve_todo(todo::InliningTodo, state::InliningState, flag::UInt8)
764758
return ConstantCase(src)
765759
end
766760

767-
if argtypes !== nothing && src === nothing
768-
inf_cache = state.inf_cache
769-
inf_result = cache_lookup(todo.mi, argtypes, inf_cache)
770-
if isa(inf_result, InferenceResult)
771-
src = inf_result.src
772-
if isa(src, OptimizationState)
773-
src = src.src
774-
end
775-
end
776-
end
777-
778-
src = state.policy(src, flag, match)
761+
src = inlining_policy(state.interp, src, flag, match)
779762

780763
if src === nothing
781764
return compileable_specialization(et, match)
@@ -1420,7 +1403,8 @@ end
14201403
function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::Expr, params::OptimizationParams)
14211404
f, ft, atypes = sig.f, sig.ft, sig.atypes
14221405
typ = ir.stmts[idx][:type]
1423-
if params.inlining && length(atypes) == 3 && istopfunction(f, :!==)
1406+
isinlining = params.inlining
1407+
if isinlining && length(atypes) == 3 && istopfunction(f, :!==)
14241408
# special-case inliner for !== that precedes _methods_by_ftype union splitting
14251409
# and that works, even though inference generally avoids inferring the `!==` Method
14261410
if isa(typ, Const)
@@ -1432,7 +1416,7 @@ function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::E
14321416
not_call = Expr(:call, GlobalRef(Core.Intrinsics, :not_int), cmp_call_ssa)
14331417
ir[SSAValue(idx)] = not_call
14341418
return true
1435-
elseif params.inlining && length(atypes) == 3 && istopfunction(f, :(>:))
1419+
elseif isinlining && length(atypes) == 3 && istopfunction(f, :(>:))
14361420
# special-case inliner for issupertype
14371421
# that works, even though inference generally avoids inferring the `>:` Method
14381422
if isa(typ, Const) && _builtin_nothrow(<:, Any[atypes[3], atypes[2]], typ)
@@ -1442,7 +1426,7 @@ function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::E
14421426
subtype_call = Expr(:call, GlobalRef(Core, :(<:)), stmt.args[3], stmt.args[2])
14431427
ir[SSAValue(idx)] = subtype_call
14441428
return true
1445-
elseif params.inlining && f === TypeVar && 2 <= length(atypes) <= 4 && (atypes[2] Symbol)
1429+
elseif isinlining && f === TypeVar && 2 <= length(atypes) <= 4 && (atypes[2] Symbol)
14461430
ir[SSAValue(idx)] = Expr(:call, GlobalRef(Core, :_typevar), stmt.args[2],
14471431
length(stmt.args) < 4 ? Bottom : stmt.args[3],
14481432
length(stmt.args) == 2 ? Any : stmt.args[end])

base/compiler/types.jl

-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ may_discard_trees(::AbstractInterpreter) = true
218218
verbose_stmt_info(::AbstractInterpreter) = false
219219

220220
method_table(interp::AbstractInterpreter) = InternalMethodTable(get_world_counter(interp))
221-
inlining_policy(::AbstractInterpreter) = default_inlining_policy
222221

223222
"""
224223
By default `AbstractInterpreter` implements the following inference bail out logic:

test/compiler/inline.jl

+16
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,19 @@ end
539539
end
540540
end
541541

542+
# test inlining of un-cached callsites
543+
542544
import Core.Compiler: isType
545+
543546
limited(a) = @noinline(isType(a)) ? @inline(limited(a.parameters[1])) : rand(a)
547+
548+
function multilimited(a)
549+
if @noinline(isType(a))
550+
return @inline(multilimited(a.parameters[1]))
551+
else
552+
return rand(Bool) ? rand(a) : @inline(multilimited(a))
553+
end
554+
end
544555
end
545556

546557
let ci = code_typed1(m.force_inline_explicit, (Int,))
@@ -593,6 +604,11 @@ end
593604
let ci = code_typed1(m.limited, (Any,))
594605
@test count(x->isinvoke(x, :isType), ci.code) == 2
595606
end
607+
# check that inlining for recursive callsites doesn't depend on inference local cache
608+
let ci1 = code_typed1(m.multilimited, (Any,))
609+
ci2 = code_typed1(m.multilimited, (Any,))
610+
@test ci1.code == ci2.code
611+
end
596612
end
597613

598614
# Issue #41299 - inlining deletes error check in :>

0 commit comments

Comments
 (0)