From 976f85b8762dfcbb65794238be74e5326d781d19 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 3 Feb 2022 15:09:48 +0900 Subject: [PATCH 1/4] fix --- README.md | 2 +- src/EscapeAnalysis.jl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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/EscapeAnalysis.jl b/src/EscapeAnalysis.jl index 23d4562..71502b8 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, From 6d35b627190dc18744283abd64b4a8f67acf7fde Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 3 Feb 2022 16:49:19 +0900 Subject: [PATCH 2/4] avoid some copies in `EAUtils` --- src/EAUtils.jl | 24 ++++++++++++++++-------- test/EscapeAnalysis.jl | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/EAUtils.jl b/src/EAUtils.jl index 071bedb..eb89d1b 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 @@ -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) @@ -207,7 +215,7 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati @eval Main (ir = $ir; nargs = $nargs) rethrow(err) end - if !interp.optimize + if caller.linfo.specTypes === interp.entry_tt && !interp.optimize # return back the result interp.ir = cccopy(ir) interp.state = state @@ -223,7 +231,7 @@ function run_passes_with_ea(interp::EscapeAnalyzer, ci::CodeInfo, sv::Optimizati @eval Main (ir = $ir; nargs = $nargs) rethrow(err) end - if interp.optimize + if caller.linfo.specTypes === interp.entry_tt && interp.optimize # return back the result interp.ir = cccopy(ir) interp.state = state diff --git a/test/EscapeAnalysis.jl b/test/EscapeAnalysis.jl index 9d5068c..627d233 100644 --- a/test/EscapeAnalysis.jl +++ b/test/EscapeAnalysis.jl @@ -2454,7 +2454,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; From 55bbaa1f75c1ef37bd5bf1558a0120abfce80b5f Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 3 Feb 2022 17:18:33 +0900 Subject: [PATCH 3/4] run `IPO EA` only when any of arguments is mutable (#92) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This heuristic might be a bit too strict, but our test suite runs successfully with it. It seems to cut off much allocations: > master ``` ❯ julia -e 'using EscapeAnalysis; code_escapes(sin, (Int,)); @time code_escapes(Base.init_stdio, (Ptr{Cvoid},)); @time code_escapes(println, (QuoteNode,))' 5.618181 seconds (27.55 M allocations: 1.472 GiB, 21.72% gc time, 95.96% compilation time) 0.549823 seconds (2.78 M allocations: 188.157 MiB, 15.12% gc time, 99.99% compilation time) ``` > this PR ``` ❯ julia -e 'using EscapeAnalysis; code_escapes(sin, (Int,)); @time code_escapes(Base.init_stdio, (Ptr{Cvoid},)); @time code_escapes(println, (QuoteNode,))' 4.700614 seconds (25.27 M allocations: 1.311 GiB, 14.22% gc time, 94.48% compilation time) 0.603580 seconds (2.50 M allocations: 147.972 MiB, 36.40% gc time, 99.99% compilation time) ``` --- src/EAUtils.jl | 20 ++++++++++++-------- src/EscapeAnalysis.jl | 20 +++++++++++++++++++- test/EscapeAnalysis.jl | 38 +++++++++++++++++++------------------- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/EAUtils.jl b/src/EAUtils.jl index eb89d1b..d698f3b 100644 --- a/src/EAUtils.jl +++ b/src/EAUtils.jl @@ -74,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 @@ -207,13 +207,17 @@ 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 caller.linfo.specTypes === interp.entry_tt && !interp.optimize # return back the result diff --git a/src/EscapeAnalysis.jl b/src/EscapeAnalysis.jl index 71502b8..4efda68 100644 --- a/src/EscapeAnalysis.jl +++ b/src/EscapeAnalysis.jl @@ -24,7 +24,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, @@ -586,6 +587,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 627d233..47d8214 100644 --- a/test/EscapeAnalysis.jl +++ b/test/EscapeAnalysis.jl @@ -2334,92 +2334,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 @@ -2429,7 +2429,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 From 31fb431806c5a55fb3cad8f8f43633eab4c525c3 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Thu, 3 Feb 2022 19:39:54 +0900 Subject: [PATCH 4/4] run `Local EA` only for entry frames --- src/EAUtils.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/EAUtils.jl b/src/EAUtils.jl index d698f3b..0c8be0b 100644 --- a/src/EAUtils.jl +++ b/src/EAUtils.jl @@ -228,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 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