From 0416934e3f8d0e128596603f6641d27789f557b7 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Fri, 4 Feb 2022 19:28:27 +0900 Subject: [PATCH] better IPO handling --- src/EAUtils.jl | 20 ++++++------- src/EscapeAnalysis.jl | 67 ++++++++++++++++++++---------------------- test/EscapeAnalysis.jl | 6 ++-- 3 files changed, 45 insertions(+), 48 deletions(-) diff --git a/src/EAUtils.jl b/src/EAUtils.jl index 0c8be0b..dff49ca 100644 --- a/src/EAUtils.jl +++ b/src/EAUtils.jl @@ -74,12 +74,12 @@ import .CC: InferenceResult, OptimizationState, IRCode, copy as cccopy, @timeit, convert_to_ircode, slot2reg, compact!, ssa_inlining_pass!, sroa_pass!, adce_pass!, type_lift_pass!, JLOptions, verify_ir, verify_linetable -import .EA: analyze_escapes, ArgEscapeInfo, EscapeInfo, EscapeState, is_ipo_profitable +import .EA: analyze_escapes, ArgEscapeCache, EscapeInfo, EscapeState, is_ipo_profitable # when working outside of Core.Compiler, # cache entire escape state for later inspection and debugging struct EscapeCache - argescapes::Vector{ArgEscapeInfo} + cache::ArgEscapeCache state::EscapeState # preserved just for debugging purpose ir::IRCode # preserved just for debugging purpose end @@ -182,20 +182,20 @@ and then caches it into a global cache for later interprocedural propagation. """ function cache_escapes!(interp::EscapeAnalyzer, caller::InferenceResult, estate::EscapeState, cacheir::IRCode) - argescapes = EscapeAnalysis.to_interprocedural(estate) - cache = EscapeCache(argescapes, estate, cacheir) - interp.cache[caller] = cache - return argescapes + cache = ArgEscapeCache(estate) + ecache = EscapeCache(cache, estate, cacheir) + interp.cache[caller] = ecache + return cache end -function getargescapes(interp::EscapeAnalyzer) +function get_escape_cache(interp::EscapeAnalyzer) return function (linfo::Union{InferenceResult,MethodInstance}) if isa(linfo, InferenceResult) ecache = get(interp.cache, linfo, nothing) else ecache = get(GLOBAL_ESCAPE_CACHE, linfo, nothing) end - return ecache !== nothing ? ecache.argescapes : nothing + return ecache !== nothing ? ecache.cache : nothing end end @@ -210,7 +210,7 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati if is_ipo_profitable(ir, nargs) || caller.linfo.specTypes === interp.entry_tt try @timeit "[IPO EA]" begin - state = analyze_escapes(ir, nargs, false, getargescapes(interp)) + state = analyze_escapes(ir, nargs, false, get_escape_cache(interp)) cache_escapes!(interp, caller, state, cccopy(ir)) end catch err @@ -230,7 +230,7 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati @timeit "compact 2" ir = compact!(ir) if caller.linfo.specTypes === interp.entry_tt && interp.optimize try - @timeit "[Local EA]" state = analyze_escapes(ir, nargs, true, getargescapes(interp)) + @timeit "[Local EA]" state = analyze_escapes(ir, nargs, true, get_escape_cache(interp)) catch err @error "error happened within [Local EA], insepct `Main.ir` and `Main.nargs`" @eval Main (ir = $ir; nargs = $nargs) diff --git a/src/EscapeAnalysis.jl b/src/EscapeAnalysis.jl index b1ff94f..35cee5b 100644 --- a/src/EscapeAnalysis.jl +++ b/src/EscapeAnalysis.jl @@ -22,7 +22,7 @@ import Core: import ._TOP_MOD: # Base definitions @__MODULE__, @eval, @assert, @specialize, @nospecialize, @inbounds, @inline, @noinline, @label, @goto, !, !==, !=, ≠, +, -, *, ≤, <, ≥, >, &, |, <<, include, error, missing, copy, - Vector, BitSet, IdDict, IdSet, UnitRange, Csize_t, Callable, ∪, ⊆, ∩, :, ∈, ∉, + Vector, BitSet, IdDict, IdSet, UnitRange, Csize_t, Callable, ∪, ⊆, ∩, :, ∈, ∉, =>, in, length, get, first, last, haskey, keys, get!, isempty, isassigned, pop!, push!, pushfirst!, empty!, delete!, max, min, enumerate, unwrap_unionall, ismutabletype @@ -538,22 +538,15 @@ isaliased(x::Union{Argument,SSAValue}, y::Union{Argument,SSAValue}, estate::Esca isaliased(xidx::Int, yidx::Int, estate::EscapeState) = in_same_set(estate.aliasset, xidx, yidx) -""" - ArgEscapeInfo(x::EscapeInfo) -> x′::ArgEscapeInfo - -The data structure for caching `x::EscapeInfo` for interprocedural propagation, -which is slightly more efficient than the original `x::EscapeInfo` object. -""" struct ArgEscapeInfo EscapeBits::UInt8 - ArgAliasing::Union{Nothing,Vector{Int}} end -function ArgEscapeInfo(x::EscapeInfo, ArgAliasing::Union{Nothing,Vector{Int}}) - x === ⊤ && return ArgEscapeInfo(ARG_ALL_ESCAPE, nothing) +function ArgEscapeInfo(x::EscapeInfo) + x === ⊤ && return ArgEscapeInfo(ARG_ALL_ESCAPE) EscapeBits = 0x00 has_return_escape(x) && (EscapeBits |= ARG_RETURN_ESCAPE) has_thrown_escape(x) && (EscapeBits |= ARG_THROWN_ESCAPE) - return ArgEscapeInfo(EscapeBits, ArgAliasing) + return ArgEscapeInfo(EscapeBits) end const ARG_ALL_ESCAPE = 0x01 << 0 @@ -565,24 +558,31 @@ has_all_escape(x::ArgEscapeInfo) = x.EscapeBits & ARG_ALL_ESCAPE ≠ 0 has_return_escape(x::ArgEscapeInfo) = x.EscapeBits & ARG_RETURN_ESCAPE ≠ 0 has_thrown_escape(x::ArgEscapeInfo) = x.EscapeBits & ARG_THROWN_ESCAPE ≠ 0 -function to_interprocedural(estate::EscapeState) +struct ArgAliasing + aidx::Int + bidx::Int +end + +struct ArgEscapeCache + argescapes::Vector{ArgEscapeInfo} + argaliases::Vector{ArgAliasing} +end + +function ArgEscapeCache(estate::EscapeState) nargs = estate.nargs argescapes = Vector{ArgEscapeInfo}(undef, nargs) + argaliases = ArgAliasing[] for i = 1:nargs info = estate.escapes[i] @assert info.AliasInfo === true - ArgAliasing = nothing + argescapes[i] = ArgEscapeInfo(info) for j = (i+1):nargs if isaliased(i, j, estate) - if ArgAliasing === nothing - ArgAliasing = Int[] - end - push!(ArgAliasing, j) + push!(argaliases, ArgAliasing(i, j)) end end - argescapes[i] = ArgEscapeInfo(info, ArgAliasing) end - return argescapes + return ArgEscapeCache(argescapes, argaliases) end # checks if `ir` has any argument that is "interesting" in terms of their escapability @@ -625,7 +625,7 @@ struct AnalysisState{T<:Callable} ir::IRCode estate::EscapeState changes::Changes - getargescapes::T + get_escape_cache::T end function getinst(ir::IRCode, idx::Int) @@ -638,22 +638,22 @@ function getinst(ir::IRCode, idx::Int) end """ - analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, getargescapes::Callable) + analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape_cache::Callable) -> estate::EscapeState Analyzes escape information in `ir`: - `nargs`: the number of actual arguments of the analyzed call - `call_resolved`: if interprocedural calls are already resolved by `ssa_inlining_pass!` -- `getargescapes(::Union{InferenceResult,MethodInstance})`: retrieves argument escape cache +- `get_escape_cache(::Union{InferenceResult,MethodInstance})`: retrieves argument escape cache """ -function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, getargescapes::T) where T<:Callable +function analyze_escapes(ir::IRCode, nargs::Int, call_resolved::Bool, get_escape_cache::T) where T<:Callable stmts = ir.stmts nstmts = length(stmts) + length(ir.new_nodes.stmts) tryregions, arrayinfo, callinfo = compute_frameinfo(ir, call_resolved) estate = EscapeState(nargs, nstmts, arrayinfo) changes = Changes() # keeps changes that happen at current statement - astate = AnalysisState(ir, estate, changes, getargescapes) + astate = AnalysisState(ir, estate, changes, get_escape_cache) local debug_itr_counter = 0 while true @@ -1227,15 +1227,15 @@ escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) = function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}, linfo::Linfo, first_idx::Int, last_idx::Int = length(args)) if isa(linfo, InferenceResult) - argescapes = astate.getargescapes(linfo) + cache = astate.get_escape_cache(linfo) linfo = linfo.linfo else - argescapes = astate.getargescapes(linfo) + cache = astate.get_escape_cache(linfo) end - if argescapes === nothing + if cache === nothing return add_conservative_changes!(astate, pc, args, 2) else - argescapes = argescapes::Vector{ArgEscapeInfo} + cache = cache::ArgEscapeCache end ret = SSAValue(pc) retinfo = astate.estate[ret] # escape information imposed on the call statement @@ -1248,7 +1248,7 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}, # COMBAK will this be invalid once we take alias information into account? i = nargs end - arginfo = argescapes[i] + arginfo = cache.argescapes[i] info = from_interprocedural(arginfo, retinfo, pc) if has_return_escape(arginfo) # if this argument can be "returned", in addition to propagating @@ -1261,12 +1261,9 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}, # the escape information imposed on this call argument within the callee add_escape_change!(astate, arg, info) end - ArgAliasing = arginfo.ArgAliasing - if ArgAliasing !== nothing - for j in ArgAliasing - add_alias_change!(astate, arg, args[j-(first_idx-1)]) - end - end + end + for (; aidx, bidx) in cache.argaliases + add_alias_change!(astate, args[aidx-(first_idx-1)], args[bidx-(first_idx-1)]) end # we should disable the alias analysis on this newly introduced object add_escape_change!(astate, ret, EscapeInfo(retinfo, true)) diff --git a/test/EscapeAnalysis.jl b/test/EscapeAnalysis.jl index 47d8214..e781d9d 100644 --- a/test/EscapeAnalysis.jl +++ b/test/EscapeAnalysis.jl @@ -2455,8 +2455,8 @@ end end target_modules = (EscapeAnalysis,) interp = EscapeAnalysis.EAUtils.EscapeAnalyzer(Core.Compiler.NativeInterpreter(), Tuple{}, true) - getargescapes = EscapeAnalysis.EAUtils.getargescapes(interp) - sig = Tuple{typeof(analyze_escapes), Core.Compiler.IRCode, Int, Bool, typeof(getargescapes)} + get_escape_cache = EscapeAnalysis.EAUtils.get_escape_cache(interp) + sig = Tuple{typeof(analyze_escapes), Core.Compiler.IRCode, Int, Bool, typeof(get_escape_cache)} test_opt(sig; function_filter, target_modules, @@ -2466,7 +2466,7 @@ end sig = m.sig Base._methods_by_ftype(sig, 1, Base.get_world_counter()) === false && continue types = collect(sig.parameters) - types[2] = EscapeAnalysis.AnalysisState{typeof(getargescapes)} + types[2] = EscapeAnalysis.AnalysisState{typeof(get_escape_cache)} sig = Tuple{types...} test_opt(sig; function_filter,