diff --git a/src/EAUtils.jl b/src/EAUtils.jl index 6f9dbba..2d2606a 100644 --- a/src/EAUtils.jl +++ b/src/EAUtils.jl @@ -328,6 +328,9 @@ function get_name_color(x::EscapeInfo, symbol::Bool = false) elseif EA.has_return_escape(x) name = (getname(EA.ReturnEscape), "↑") color = EA.has_thrown_escape(x) ? :yellow : :blue + elseif EA.has_finalizer_escape(x) + name = (getname(EA.FinalizerEscape), "&") + color = EA.has_thrown_escape(x) ? :yellow : :blue else name = (nothing, "*") color = EA.has_thrown_escape(x) ? :yellow : :bold diff --git a/src/EscapeAnalysis.jl b/src/EscapeAnalysis.jl index be36f5f..b169d7e 100644 --- a/src/EscapeAnalysis.jl +++ b/src/EscapeAnalysis.jl @@ -9,6 +9,7 @@ export has_arg_escape, has_return_escape, has_thrown_escape, + has_finalizer_escape, has_all_escape const _TOP_MOD = ccall(:jl_base_relative_to, Any, (Any,), EscapeAnalysis)::Module @@ -122,6 +123,7 @@ struct EscapeInfo Analyzed::Bool ReturnEscape::Bool ThrownEscape::LivenessSet + FinalizerEscape::Bool AliasInfo #::Union{IndexableFields,IndexableElements,Unindexable,Bool} Liveness::LivenessSet # TODO: ArgEscape::Int @@ -130,6 +132,7 @@ struct EscapeInfo Analyzed::Bool, ReturnEscape::Bool, ThrownEscape::LivenessSet, + FinalizerEscape::Bool, AliasInfo#=::Union{IndexableFields,IndexableElements,Unindexable,Bool}=#, Liveness::LivenessSet, ) @@ -138,6 +141,7 @@ struct EscapeInfo Analyzed, ReturnEscape, ThrownEscape, + FinalizerEscape, AliasInfo, Liveness, ) @@ -150,6 +154,7 @@ struct EscapeInfo Analyzed::Bool = x.Analyzed, ReturnEscape::Bool = x.ReturnEscape, ThrownEscape::LivenessSet = x.ThrownEscape, + FinalizerEscape::Bool = x.FinalizerEscape, Liveness::LivenessSet = x.Liveness, ) @nospecialize AliasInfo @@ -157,6 +162,7 @@ struct EscapeInfo Analyzed, ReturnEscape, ThrownEscape, + FinalizerEscape, AliasInfo, Liveness, ) @@ -177,13 +183,14 @@ const TOP_LIVENESS = LivenessSet(-1:0) const ARG_LIVENESS = LivenessSet(0) # the constructors -NotAnalyzed() = EscapeInfo(false, false, BOT_THROWN_ESCAPE, false, BOT_LIVENESS) # not formally part of the lattice -NoEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, false, BOT_LIVENESS) -ArgEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, true, ARG_LIVENESS) -ReturnEscape(pc::Int) = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, LivenessSet(pc)) -AllReturnEscape() = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, TOP_LIVENESS) -ThrownEscape(pc::Int) = EscapeInfo(true, false, LivenessSet(pc), false, BOT_LIVENESS) -AllEscape() = EscapeInfo(true, true, TOP_THROWN_ESCAPE, true, TOP_LIVENESS) +NotAnalyzed() = EscapeInfo(false, false, BOT_THROWN_ESCAPE, false, false, BOT_LIVENESS) # not formally part of the lattice +NoEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, false, false, BOT_LIVENESS) +ArgEscape() = EscapeInfo(true, false, BOT_THROWN_ESCAPE, false, true, ARG_LIVENESS) +ReturnEscape(pc::Int) = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, false, LivenessSet(pc)) +AllReturnEscape() = EscapeInfo(true, true, BOT_THROWN_ESCAPE, false, false, TOP_LIVENESS) +ThrownEscape(pc::Int) = EscapeInfo(true, false, LivenessSet(pc), false, false, BOT_LIVENESS) +FinalizerEscape(pc::Int) = EscapeInfo(true, false, BOT_THROWN_ESCAPE, true, false, LivenessSet(pc)) +AllEscape() = EscapeInfo(true, true, TOP_THROWN_ESCAPE, true, true, TOP_LIVENESS) const ⊥, ⊤ = NotAnalyzed(), AllEscape() @@ -193,7 +200,9 @@ has_arg_escape(x::EscapeInfo) = 0 in x.Liveness has_return_escape(x::EscapeInfo) = x.ReturnEscape has_return_escape(x::EscapeInfo, pc::Int) = x.ReturnEscape && (-1 ∈ x.Liveness || pc in x.Liveness) has_thrown_escape(x::EscapeInfo) = !isempty(x.ThrownEscape) -has_thrown_escape(x::EscapeInfo, pc::Int) = -1 ∈ x.ThrownEscape || pc in x.ThrownEscape +has_thrown_escape(x::EscapeInfo, pc::Int) = -1 ∈ x.ThrownEscape || pc in x.ThrownEscape +has_finalizer_escape(x::EscapeInfo) = x.FinalizerEscape +has_finalizer_escape(x::EscapeInfo, pc::Int) = x.FinalizerEscape && (-1 ∈ x.Liveness || pc in x.Liveness) has_all_escape(x::EscapeInfo) = ⊤ ⊑ₑ x # utility lattice constructors @@ -217,6 +226,7 @@ x::EscapeInfo == y::EscapeInfo = begin else xt == yt || return false end + x.FinalizerEscape === y.FinalizerEscape || return false xa, ya = x.AliasInfo, y.AliasInfo if isa(xa, Bool) xa === ya || return false @@ -267,6 +277,7 @@ x::EscapeInfo ⊑ₑ y::EscapeInfo = begin elseif yt !== TOP_THROWN_ESCAPE xt ⊆ yt || return false end + x.FinalizerEscape ≤ y.FinalizerEscape || return false xa, ya = x.AliasInfo, y.AliasInfo if isa(xa, Bool) xa && ya !== true && return false @@ -442,6 +453,7 @@ x::EscapeInfo ⊔ₑ y::EscapeInfo = begin x.Analyzed | y.Analyzed, x.ReturnEscape | y.ReturnEscape, ThrownEscape, + x.FinalizerEscape | y.FinalizerEscape, AliasInfo, Liveness, ) @@ -559,13 +571,14 @@ struct ArgEscapeInfo AllEscape::Bool ReturnEscape::Bool ThrownEscape::Bool + FinalizerEscape::Bool ArgAliasing::Union{Nothing,Vector{Int}} end function ArgEscapeInfo(x::EscapeInfo, ArgAliasing::Union{Nothing,Vector{Int}}) - x === ⊤ && return ArgEscapeInfo(true, true, true, nothing) + x === ⊤ && return ArgEscapeInfo(true, true, true, true, nothing) ThrownEscape = isempty(x.ThrownEscape) ? false : true - return ArgEscapeInfo(false, x.ReturnEscape, ThrownEscape, ArgAliasing) + return ArgEscapeInfo(false, x.ReturnEscape, ThrownEscape, x.FinalizerEscape, ArgAliasing) end # when working outside of Core.Compiler, cache as much as information for later inspection and debugging @@ -1296,7 +1309,7 @@ function from_interprocedural(arginfo::ArgEscapeInfo, retinfo::EscapeInfo, pc::I # it might be okay from the SROA point of view, since we can't remove the allocation # as far as it's passed to a callee anyway, but still we may want some field analysis # for e.g. stack allocation or some other IPO optimizations - #=AliasInfo=#true, #=Liveness=#LivenessSet(pc)) + #=FinalizerEscape=#false, #=AliasInfo=#true, #=Liveness=#LivenessSet(pc)) end @noinline function unexpected_assignment!(ir::IRCode, pc::Int) @@ -1435,10 +1448,10 @@ function escape_foreigncall!(astate::AnalysisState, pc::Int, args::Vector{Any}) elseif is_array_isassigned(nn) escape_array_isassigned!(astate, pc, args) return + elseif is_finalizer(nn) + escape_finalizer!(astate, pc, args) + return end - # if nn === :jl_gc_add_finalizer_th - # # TODO add `FinalizerEscape` ? - # end end # NOTE array allocations might have been proven as nothrow (https://github.com/JuliaLang/julia/pull/43565) nothrow = is_effect_free(astate.ir, pc) @@ -1991,6 +2004,22 @@ function array_isassigned_nothrow(args::Vector{Any}, src::IRCode) return true end +is_finalizer(name::Symbol) = name === :jl_gc_add_finalizer_th + +function escape_finalizer!(astate::AnalysisState, pc::Int, args::Vector{Any}) + length(args) ≥ 6 || return add_fallback_changes!(astate, pc, args) + println("Finalizer escape!") + println(pc) + for arg in args + println(arg) + end + value = args[8] + func = args[9] + # TODO: We need to analyze func + add_escape_change!(astate, value, FinalizerEscape(pc)) + add_liveness_changes!(astate, pc, args, 6) +end + # # COMBAK do we want to enable this (and also backport this to Base for array allocations?) # import Core.Compiler: Cint, svec # function validate_foreigncall_args(args::Vector{Any},