From f2bd35abee95900a86d4a91064f114ef9024ee35 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Wed, 1 Nov 2023 18:55:22 +0900 Subject: [PATCH] define it as `VolatileInferenceResult` --- base/compiler/abstractinterpretation.jl | 37 ++++------- base/compiler/ssair/inlining.jl | 86 ++++++++++++------------- base/compiler/stmtinfo.jl | 8 ++- base/compiler/typeinfer.jl | 17 +++-- test/compiler/inline.jl | 17 +++++ 5 files changed, 87 insertions(+), 78 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index caf86db2243e40..40f8243abbc901 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -59,12 +59,12 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), splitsigs = switchtupleunion(sig) for sig_n in splitsigs result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, si, sv) - (; rt, edge, effects, inferred_src) = result + (; rt, edge, effects, volatile_inf_result) = result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, si, match, sv) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing if const_call_result.rt ⊑ₚ rt rt = const_call_result.rt @@ -90,7 +90,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_rt = widenwrappedconditional(this_rt) else result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv) - (; rt, edge, effects, inferred_src) = result + (; rt, edge, effects, volatile_inf_result) = result this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) # try constant propagation with argtypes for this match @@ -99,7 +99,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, result, f, this_arginfo, si, match, sv) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing this_const_conditional = ignorelimited(const_call_result.rt) this_const_rt = widenwrappedconditional(const_call_result.rt) @@ -119,11 +119,6 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), const_results = fill!(Vector{Union{Nothing,ConstResult}}(undef, napplicable), nothing) end const_results[i] = const_result - elseif inferred_src !== nothing - if const_results === nothing - const_results = fill!(Vector{Union{Nothing,ConstResult}}(undef, napplicable), nothing) - end - const_results[i] = InferredResult(inferred_src) end edge === nothing || push!(edges, edge) end @@ -626,7 +621,7 @@ function abstract_call_method(interp::AbstractInterpreter, sparams = recomputed[2]::SimpleVector end - (; rt, edge, effects, inferred_src) = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = edgelimited = true @@ -650,7 +645,7 @@ function abstract_call_method(interp::AbstractInterpreter, end end - return MethodCallResult(rt, edgecycle, edgelimited, edge, effects, inferred_src) + return MethodCallResult(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) end function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, @@ -753,14 +748,14 @@ struct MethodCallResult edgelimited::Bool edge::Union{Nothing,MethodInstance} effects::Effects - inferred_src::Union{Nothing,CodeInfo} + volatile_inf_result::Union{Nothing,VolatileInferenceResult} function MethodCallResult(@nospecialize(rt), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, effects::Effects, - inferred_src::Union{Nothing,CodeInfo}=nothing) - return new(rt, edgecycle, edgelimited, edge, effects, inferred_src) + volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) + return new(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) end end @@ -1952,7 +1947,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector ti = tienv[1]; env = tienv[2]::SimpleVector result = abstract_call_method(interp, method, ti, env, false, si, sv) - (; rt, edge, effects, inferred_src) = result + (; rt, edge, effects, volatile_inf_result) = result match = MethodMatch(ti, env, method, argtype <: method.sig) res = nothing sig = match.spec_types @@ -1969,15 +1964,12 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn invokecall = InvokeCall(types, lookupsig) const_call_result = abstract_call_method_with_const_args(interp, result, f, arginfo, si, match, sv, invokecall) - const_result = nothing + const_result = volatile_inf_result if const_call_result !== nothing if ⊑(𝕃ₚ, const_call_result.rt, rt) (; rt, effects, const_result, edge) = const_call_result end end - if const_result === nothing && inferred_src !== nothing - const_result = InferredResult(inferred_src) - end rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) @@ -2101,13 +2093,13 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, closure::PartialOpaque, arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState, check::Bool=true) sig = argtypes_to_type(arginfo.argtypes) result = abstract_call_method(interp, closure.source::Method, sig, Core.svec(), false, si, sv) - (; rt, edge, effects, inferred_src) = result + (; rt, edge, effects, volatile_inf_result) = result tt = closure.typ sigT = (unwrap_unionall(tt)::DataType).parameters[1] match = MethodMatch(sig, Core.svec(), closure.source, sig <: rewrap_unionall(sigT, tt)) 𝕃ₚ = ipo_lattice(interp) ⊑ₚ = ⊑(𝕃ₚ) - const_result = nothing + const_result = volatile_inf_result if !result.edgecycle const_call_result = abstract_call_method_with_const_args(interp, result, nothing, arginfo, si, match, sv) @@ -2125,9 +2117,6 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, effects = Effects(effects; nothrow=false) end end - if const_result === nothing && inferred_src !== nothing - const_result = InferredResult(inferred_src) - end rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 5ddce03f5dadf6..360ed1b5307b41 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -842,10 +842,10 @@ function compileable_specialization(match::MethodMatch, effects::Effects, return compileable_specialization(mi, effects, et, info; compilesig_invokes) end -struct CachedResult +struct InferredResult src::Any effects::Effects - CachedResult(@nospecialize(src), effects::Effects) = new(src, effects) + InferredResult(@nospecialize(src), effects::Effects) = new(src, effects) end @inline function get_cached_result(state::InliningState, mi::MethodInstance) code = get(code_cache(state), mi, nothing) @@ -853,41 +853,46 @@ end if use_const_api(code) # in this case function can be inlined to a constant return ConstantCase(quoted(code.rettype_const)) - else - src = @atomic :monotonic code.inferred end + src = @atomic :monotonic code.inferred effects = decode_effects(code.ipo_purity_bits) - return CachedResult(src, effects) + return InferredResult(src, effects) + end + return InferredResult(nothing, Effects()) +end +@inline function get_local_result(inf_result::InferenceResult) + effects = inf_result.ipo_effects + if is_foldable_nothrow(effects) + res = inf_result.result + if isa(res, Const) && is_inlineable_constant(res.val) + # use constant calling convention + return ConstantCase(quoted(res.val)) + end end - return CachedResult(nothing, Effects()) + return InferredResult(inf_result.src, effects) end # the general resolver for usual and const-prop'ed calls -function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceResult}, +function resolve_todo(mi::MethodInstance, result::Union{Nothing,InferenceResult,VolatileInferenceResult}, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - invokesig::Union{Nothing,Vector{Any}}=nothing, - inferred_result::Union{Nothing,InferredResult}=nothing) + invokesig::Union{Nothing,Vector{Any}}=nothing) et = InliningEdgeTracker(state, invokesig) + preserve_local_sources = true if isa(result, InferenceResult) - src = result.src - effects = result.ipo_effects - if is_foldable_nothrow(effects) - res = result.result - if isa(res, Const) && is_inlineable_constant(res.val) - # use constant calling convention - add_inlining_backedge!(et, mi) - return ConstantCase(quoted(res.val)) - end - end + inferred_result = get_local_result(result) + elseif isa(result, VolatileInferenceResult) + # use this volatile inference result destructively + inferred_result = get_local_result(result.inf_result) + preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources else - cached_result = get_cached_result(state, mi) - if cached_result isa ConstantCase - add_inlining_backedge!(et, mi) - return cached_result - end - (; src, effects) = cached_result + inferred_result = get_cached_result(state, mi) + end + if inferred_result isa ConstantCase + add_inlining_backedge!(et, mi) + return inferred_result end + (; src, effects) = inferred_result # the duplicated check might have been done already within `analyze_method!`, but still # we need it here too since we may come here directly using a constant-prop' result @@ -901,17 +906,7 @@ function resolve_todo(mi::MethodInstance, result::Union{MethodMatch,InferenceRes compilesig_invokes=OptimizationParams(state.interp).compilesig_invokes) add_inlining_backedge!(et, mi) - - if src isa String && inferred_result !== nothing - # if the inferred source for this globally-cached method is available, - # use it destructively as it will never be used again - src = inferred_result.inferred_src - preserve_local_sources = OptimizationParams(state.interp).preserve_local_sources - else - preserve_local_sources = true - end ir = retrieve_ir_for_inlining(mi, src, preserve_local_sources) - return InliningTodo(mi, ir, effects) end @@ -957,7 +952,7 @@ end function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; allow_typevars::Bool, invokesig::Union{Nothing,Vector{Any}}=nothing, - inferred_result::Union{Nothing,InferredResult}=nothing) + volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) method = match.method spec_types = match.spec_types @@ -986,7 +981,7 @@ function analyze_method!(match::MethodMatch, argtypes::Vector{Any}, # Get the specialization for this method signature # (later we will decide what to do with it) mi = specialize_method(match) - return resolve_todo(mi, match, argtypes, info, flag, state; invokesig, inferred_result) + return resolve_todo(mi, volatile_inf_result, argtypes, info, flag, state; invokesig) end function retrieve_ir_for_inlining(mi::MethodInstance, src::String, ::Bool=true) @@ -1226,8 +1221,8 @@ function handle_invoke_call!(todo::Vector{Pair{Int,Any}}, return nothing end end - inferred_result = result isa InferredResult ? result : nothing - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig, inferred_result) + volatile_inf_result = result isa VolatileInferenceResult ? result : nothing + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars=false, invokesig, volatile_inf_result) end end handle_single_case!(todo, ir, idx, stmt, item, true) @@ -1367,8 +1362,8 @@ function handle_any_const_result!(cases::Vector{InliningCase}, if isa(result, ConstPropResult) return handle_const_prop_result!(cases, result, argtypes, info, flag, state; allow_abstract, allow_typevars) else - @assert result === nothing || result isa InferredResult - return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars, inferred_result = result) + @assert result === nothing || result isa VolatileInferenceResult + return handle_match!(cases, match, argtypes, info, flag, state; allow_abstract, allow_typevars, volatile_inf_result = result) end end @@ -1499,14 +1494,14 @@ end function handle_match!(cases::Vector{InliningCase}, match::MethodMatch, argtypes::Vector{Any}, @nospecialize(info::CallInfo), flag::UInt32, state::InliningState; - allow_abstract::Bool, allow_typevars::Bool, inferred_result::Union{Nothing,InferredResult}) + allow_abstract::Bool, allow_typevars::Bool, volatile_inf_result::Union{Nothing,VolatileInferenceResult}) spec_types = match.spec_types allow_abstract || isdispatchtuple(spec_types) || return false # We may see duplicated dispatch signatures here when a signature gets widened # during abstract interpretation: for the purpose of inlining, we can just skip # processing this dispatch candidate (unless unmatched type parameters are present) !allow_typevars && any(case::InliningCase->case.sig === spec_types, cases) && return true - item = analyze_method!(match, argtypes, info, flag, state; allow_typevars, inferred_result) + item = analyze_method!(match, argtypes, info, flag, state; allow_typevars, volatile_inf_result) item === nothing && return false push!(cases, InliningCase(spec_types, item)) return true @@ -1613,8 +1608,9 @@ function handle_opaque_closure_call!(todo::Vector{Pair{Int,Any}}, if isa(result, SemiConcreteResult) item = semiconcrete_result_item(result, info, flag, state) else - @assert result === nothing || result isa InferredResult - item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false, inferred_result=result) + @assert result === nothing || result isa VolatileInferenceResult + volatile_inf_result = result + item = analyze_method!(info.match, sig.argtypes, info, flag, state; allow_typevars=false, volatile_inf_result) end end handle_single_case!(todo, ir, idx, stmt, item) diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index c36f8b05836cfb..d634c88cdf8e89 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -76,8 +76,12 @@ struct SemiConcreteResult <: ConstResult effects::Effects end -struct InferredResult <: ConstResult - inferred_src::CodeInfo +# XXX Technically this does not represent a result of constant inference, but rather that of +# regular edge inference. It might be more appropriate to rename `ConstResult` and +# `ConstCallInfo` to better reflect the fact that they represent either of local or +# volatile inference result. +struct VolatileInferenceResult <: ConstResult + inf_result::InferenceResult end """ diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 593a34f07d205f..0e81f450259937 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -800,12 +800,12 @@ struct EdgeCallResult rt #::Type edge::Union{Nothing,MethodInstance} effects::Effects - inferred_src::Union{Nothing,CodeInfo} + volatile_inf_result::Union{Nothing,VolatileInferenceResult} function EdgeCallResult(@nospecialize(rt), edge::Union{Nothing,MethodInstance}, effects::Effects, - inferred_src::Union{Nothing,CodeInfo} = nothing) - return new(rt, edge, effects, inferred_src) + volatile_inf_result::Union{Nothing,VolatileInferenceResult} = nothing) + return new(rt, edge, effects, volatile_inf_result) end end @@ -813,9 +813,10 @@ end function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize(atype), sparams::SimpleVector, caller::AbsIntState) mi = specialize_method(method, atype, sparams)::MethodInstance code = get(code_cache(interp), mi, nothing) + force_inline = is_stmt_inline(get_curr_ssaflag(caller)) if code isa CodeInstance # return existing rettype if the code is already inferred inferred = @atomic :monotonic code.inferred - if inferred === nothing && is_stmt_inline(get_curr_ssaflag(caller)) + if inferred === nothing && force_inline # we already inferred this edge before and decided to discard the inferred code, # nevertheless we re-infer it here again and keep it around in the local cache # since the inliner will request to use it later @@ -826,7 +827,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize update_valid_age!(caller, WorldRange(min_world(code), max_world(code))) return EdgeCallResult(rt, mi, effects) end - elseif is_stmt_inline(get_curr_ssaflag(caller)) + elseif force_inline # if this fresh is going to be inlined, we cache it locally too so that the inliner # can see it later even in a case when the inferred source is discarded from the global cache cache_mode = GLOBAL_CACHE_MODE | LOCAL_CACHE_MODE @@ -863,8 +864,10 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize isinferred = is_inferred(frame) edge = isinferred ? mi : nothing effects = isinferred ? frame.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects - inferred_src = isinferred && is_inlineable(frame.src) ? frame.src : nothing - return EdgeCallResult(frame.bestguess, edge, effects, inferred_src) + volatile_inf_result = isinferred && let inferred_src = result.src + isa(inferred_src, CodeInfo) && (is_inlineable(inferred_src) || force_inline) + end ? VolatileInferenceResult(result) : nothing + return EdgeCallResult(frame.bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle return EdgeCallResult(Any, nothing, Effects()) diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 9ef2fdbc34f816..4bb343a6a17240 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -507,6 +507,17 @@ end Base.@constprop :aggressive noinlined_constprop_implicit(a) = a+g force_inline_constprop_implicit() = @inline noinlined_constprop_implicit(0) + function force_inline_constprop_cached1() + r1 = noinlined_constprop_implicit(0) + r2 = @inline noinlined_constprop_implicit(0) + return (r1, r2) + end + function force_inline_constprop_cached2() + r1 = @inline noinlined_constprop_implicit(0) + r2 = noinlined_constprop_implicit(0) + return (r1, r2) + end + @inline Base.@constprop :aggressive inlined_constprop_explicit(a) = a+g force_noinline_constprop_explicit() = @noinline inlined_constprop_explicit(0) @inline Base.@constprop :aggressive inlined_constprop_implicit(a) = a+g @@ -557,6 +568,12 @@ end let code = get_code(M.force_inline_constprop_implicit) @test all(!isinvoke(:noinlined_constprop_implicit), code) end + let code = get_code(M.force_inline_constprop_cached1) + @test count(isinvoke(:noinlined_constprop_implicit), code) == 1 + end + let code = get_code(M.force_inline_constprop_cached2) + @test count(isinvoke(:noinlined_constprop_implicit), code) == 1 + end let code = get_code(M.force_noinline_constprop_explicit) @test any(isinvoke(:inlined_constprop_explicit), code)