diff --git a/README.md b/README.md index 7fb9515..d1dd1e7 100644 --- a/README.md +++ b/README.md @@ -516,7 +516,7 @@ it is more ideal if it runs once and succeeding optimization passes incrementall Core.Compiler.EscapeAnalysis.analyze_escapes Core.Compiler.EscapeAnalysis.EscapeState Core.Compiler.EscapeAnalysis.EscapeInfo -Core.Compiler.EscapeAnalysis.cache_escapes! +Core.Compiler.EscapeAnalysis.ArgEscapeInfo ``` [^LatticeDesign]: Our type inference implementation takes the alternative approach, diff --git a/src/EAUtils.jl b/src/EAUtils.jl index 246fc13..6ea026b 100644 --- a/src/EAUtils.jl +++ b/src/EAUtils.jl @@ -9,6 +9,7 @@ const EA = EscapeAnalysis # entries # ------- +import Base: unwrap_unionall, rewrap_unionall import InteractiveUtils: gen_call_with_extracted_types_and_kwargs """ @@ -25,7 +26,6 @@ end """ code_escapes(f, argtypes=Tuple{}; [debuginfo::Symbol = :none], [optimize::Bool = true]) -> result::EscapeResult - code_escapes(tt::Type{<:Tuple}; [debuginfo::Symbol = :none], [optimize::Bool = true]) -> result::EscapeResult Runs the escape analysis on optimized IR of a generic function call with the given type signature. @@ -37,13 +37,20 @@ Runs the escape analysis on optimized IR of a generic function call with the giv - `debuginfo::Symbol = :none`: controls the amount of code metadata present in the output, possible options are `:none` or `:source`. """ -function code_escapes(@nospecialize(args...); +function code_escapes(@nospecialize(f), @nospecialize(types=Base.default_tt(f)); world::UInt = get_world_counter(), interp::Core.Compiler.AbstractInterpreter = Core.Compiler.NativeInterpreter(world), debuginfo::Symbol = :none, optimize::Bool = true) - interp = EscapeAnalyzer(interp, optimize) - results = code_typed(args...; optimize=true, world, interp) + ft = Core.Typeof(f) + if isa(types, Type) + u = unwrap_unionall(types) + tt = rewrap_unionall(Tuple{ft, u.parameters...}, types) + else + tt = Tuple{ft, types...} + end + interp = EscapeAnalyzer(interp, tt, optimize) + results = Base.code_typed_by_type(tt; optimize=true, world, interp) isone(length(results)) || throw(ArgumentError("`code_escapes` only supports single analysis result")) return EscapeResult(interp.ir, interp.state, interp.linfo, debuginfo===:source) end @@ -67,7 +74,7 @@ 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 +import .EA: analyze_escapes, ArgEscapeInfo, EscapeInfo, EscapeState, is_ipo_profitable # when working outside of Core.Compiler, # cache entire escape state for later inspection and debugging @@ -80,12 +87,13 @@ end mutable struct EscapeAnalyzer{State} <: AbstractInterpreter native::NativeInterpreter cache::IdDict{InferenceResult,EscapeCache} + entry_tt optimize::Bool ir::IRCode state::State linfo::MethodInstance - EscapeAnalyzer(native::NativeInterpreter, optimize::Bool) = - new{EscapeState}(native, IdDict{InferenceResult,EscapeCache}(), optimize) + EscapeAnalyzer(native::NativeInterpreter, @nospecialize(tt), optimize::Bool) = + new{EscapeState}(native, IdDict{InferenceResult,EscapeCache}(), tt, optimize) end CC.InferenceParams(interp::EscapeAnalyzer) = InferenceParams(interp.native) @@ -199,15 +207,19 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati @timeit "compact 1" ir = compact!(ir) nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end local state - try - @timeit "[IPO EA]" state = analyze_escapes(ir, nargs, false, getargescapes(interp)) - cache_escapes!(interp, caller, state, cccopy(ir)) - catch err - @error "error happened within [IPO EA], insepct `Main.ir` and `Main.nargs`" - @eval Main (ir = $ir; nargs = $nargs) - rethrow(err) + 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)) + cache_escapes!(interp, caller, state, cccopy(ir)) + end + catch err + @error "error happened within [IPO EA], insepct `Main.ir` and `Main.nargs`" + @eval Main (ir = $ir; nargs = $nargs) + rethrow(err) + end end - if !interp.optimize + if caller.linfo.specTypes === interp.entry_tt && !interp.optimize # return back the result interp.ir = cccopy(ir) interp.state = state @@ -216,14 +228,14 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati @timeit "Inlining" ir = ssa_inlining_pass!(ir, ir.linetable, sv.inlining, ci.propagate_inbounds) # @timeit "verify 2" verify_ir(ir) @timeit "compact 2" ir = compact!(ir) - try - @timeit "[Local EA]" state = analyze_escapes(ir, nargs, true, getargescapes(interp)) - catch err - @error "error happened within [Local EA], insepct `Main.ir` and `Main.nargs`" - @eval Main (ir = $ir; nargs = $nargs) - rethrow(err) - end - if interp.optimize + if caller.linfo.specTypes === interp.entry_tt && interp.optimize + try + @timeit "[Local EA]" state = analyze_escapes(ir, nargs, true, getargescapes(interp)) + catch err + @error "error happened within [Local EA], insepct `Main.ir` and `Main.nargs`" + @eval Main (ir = $ir; nargs = $nargs) + rethrow(err) + end # return back the result interp.ir = cccopy(ir) interp.state = state diff --git a/src/EscapeAnalysis.jl b/src/EscapeAnalysis.jl index f7469c9..51e50dd 100644 --- a/src/EscapeAnalysis.jl +++ b/src/EscapeAnalysis.jl @@ -2,7 +2,6 @@ baremodule EscapeAnalysis export analyze_escapes, - cache_escapes!, getaliases, isaliased, has_no_escape, @@ -26,7 +25,8 @@ import ._TOP_MOD: # Base definitions @label, @goto, !, !==, !=, ≠, +, -, *, ≤, <, ≥, >, &, |, include, error, missing, copy, 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 + pop!, push!, pushfirst!, empty!, delete!, max, min, enumerate, unwrap_unionall, + ismutabletype import Core.Compiler: # Core.Compiler specific definitions Bottom, InferenceResult, IRCode, Instruction, Signature, MethodResultPure, MethodMatchInfo, UnionSplitInfo, ConstCallInfo, InvokeCallInfo, @@ -600,6 +600,23 @@ function to_interprocedural(estate::EscapeState) return argescapes end +# checks if `ir` has any argument that is "interesting" in terms of their escapability +function is_ipo_profitable(ir::IRCode, nargs::Int) + for i = 1:nargs + t = unwrap_unionall(widenconst(ir.argtypes[i])) + t <: IO && return false # bail out IO-related functions + is_ipo_profitable_type(t) && return true + end + return false +end +function is_ipo_profitable_type(@nospecialize t) + if isa(t, Union) + return is_ipo_profitable_type(t.a) && is_ipo_profitable_type(t.b) + end + (t === String || t === Symbol) && return false + return ismutabletype(t) +end + abstract type Change end struct EscapeChange <: Change xidx::Int diff --git a/test/EscapeAnalysis.jl b/test/EscapeAnalysis.jl index 624dc64..09883c1 100644 --- a/test/EscapeAnalysis.jl +++ b/test/EscapeAnalysis.jl @@ -2335,92 +2335,92 @@ Base.@constprop :aggressive function conditional_escape!(cnd, x) end # MethodMatchInfo -- global cache -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return noescape(x) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x identity(x) return nothing end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return identity(x) end r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[Argument(2)], r) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return Ref(x) end r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[Argument(2)], r) end -let result = code_escapes((String,); optimize=false) do x - r = Ref{String}() +let result = code_escapes((SafeRef{String},); optimize=false) do x + r = Ref{SafeRef{String}}() r[] = x return r end r = only(findall(isreturn, result.ir.stmts.inst)) @test has_return_escape(result.state[Argument(2)], r) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x global_escape!(x) end @test has_all_escape(result.state[Argument(2)]) end # UnionSplitInfo -let result = code_escapes((Bool,String); optimize=false) do c, s +let result = code_escapes((Bool,Vector{Any}); optimize=false) do c, s x = c ? s : SafeRef(s) union_escape!(x) end @test has_all_escape(result.state[Argument(3)]) # s end -let result = code_escapes((Bool,String); optimize=false) do c, s +let result = code_escapes((Bool,Vector{Any}); optimize=false) do c, s x = c ? SafeRef(s) : SafeRefs(s, s) union_escape!(x) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end # ConstCallInfo -- local cache -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return conditional_escape!(false, x) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end # InvokeCallInfo -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return Base.@invoke noescape(x::Any) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x return Base.@invoke conditional_escape!(false::Any, x::Any) end @test has_no_escape(ignore_argescape(result.state[Argument(2)])) end # ThrownEscape via potential MethodError -identity_if_string(x::String) = nothing -let result = code_escapes((Any,); optimize=false) do x +identity_if_string(x::SafeRef) = nothing +let result = code_escapes((SafeRef{String},); optimize=false) do x identity_if_string(x) end i = only(findall(iscall((result.ir, identity_if_string)), result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) - @test has_thrown_escape(result.state[Argument(2)], i) + @test !has_thrown_escape(result.state[Argument(2)], i) @test !has_return_escape(result.state[Argument(2)], r) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((Union{SafeRef{String},Vector{String}},); optimize=false) do x identity_if_string(x) end i = only(findall(iscall((result.ir, identity_if_string)), result.ir.stmts.inst)) r = only(findall(isreturn, result.ir.stmts.inst)) - @test !has_thrown_escape(result.state[Argument(2)], i) + @test has_thrown_escape(result.state[Argument(2)], i) @test !has_return_escape(result.state[Argument(2)], r) end -let result = code_escapes((String,); optimize=false) do x +let result = code_escapes((SafeRef{String},); optimize=false) do x try identity_if_string(x) catch err @@ -2430,7 +2430,7 @@ let result = code_escapes((String,); optimize=false) do x end @test !has_all_escape(result.state[Argument(2)]) end -let result = code_escapes((Any,); optimize=false) do x +let result = code_escapes((Union{SafeRef{String},Vector{String}},); optimize=false) do x try identity_if_string(x) catch err @@ -2455,7 +2455,7 @@ end return true end target_modules = (EscapeAnalysis,) - interp = EscapeAnalysis.EAUtils.EscapeAnalyzer(Core.Compiler.NativeInterpreter(), true) + 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)} test_opt(sig;