Skip to content

Commit

Permalink
Implement FinalizerEscape
Browse files Browse the repository at this point in the history
Co-authored-by: Shuhei Kadowaki <[email protected]>
  • Loading branch information
jpsamaroo and aviatesk committed Mar 1, 2022
1 parent 325f414 commit 6340ff0
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 22 deletions.
71 changes: 49 additions & 22 deletions base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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
Expand Down Expand Up @@ -54,6 +55,7 @@ A lattice for escape information, which holds the following properties:
* `pc ∈ x.ThrownEscape`: `x` may be thrown at the SSA statement at `pc`
* `-1 ∈ x.ThrownEscape`: `x` may be thrown at arbitrary points of this call frame (the top)
This information will be used by `escape_exception!` to propagate potential escapes via exception.
- `x.FinalizerEscape::Bool`: indicates `x` can escape through a finalizer
- `x.AliasInfo::Union{Bool,IndexableFields,IndexableElements,Unindexable}`: maintains all possible values
that can be aliased to fields or array elements of `x`:
* `x.AliasInfo === false` indicates the fields/elements of `x` aren't analyzed yet
Expand Down Expand Up @@ -88,13 +90,15 @@ struct EscapeInfo
Analyzed::Bool
ReturnEscape::Bool
ThrownEscape::LivenessSet
FinalizerEscape::Bool
AliasInfo #::Union{IndexableFields,IndexableElements,Unindexable,Bool}
Liveness::LivenessSet

function EscapeInfo(
Analyzed::Bool,
ReturnEscape::Bool,
ThrownEscape::LivenessSet,
FinalizerEscape::Bool,
AliasInfo#=::Union{IndexableFields,IndexableElements,Unindexable,Bool}=#,
Liveness::LivenessSet,
)
Expand All @@ -103,6 +107,7 @@ struct EscapeInfo
Analyzed,
ReturnEscape,
ThrownEscape,
FinalizerEscape,
AliasInfo,
Liveness,
)
Expand All @@ -115,13 +120,15 @@ struct EscapeInfo
Analyzed::Bool = x.Analyzed,
ReturnEscape::Bool = x.ReturnEscape,
ThrownEscape::LivenessSet = x.ThrownEscape,
FinalizerEscape::Bool = x.FinalizerEscape,
Liveness::LivenessSet = x.Liveness,
)
@nospecialize AliasInfo
return new(
Analyzed,
ReturnEscape,
ThrownEscape,
FinalizerEscape,
AliasInfo,
Liveness,
)
Expand All @@ -142,23 +149,26 @@ 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()

# Convenience names for some ⊑ₑ queries
has_no_escape(x::EscapeInfo) = !x.ReturnEscape && isempty(x.ThrownEscape) && 0 x.Liveness
has_no_escape(x::EscapeInfo) = !x.ReturnEscape && isempty(x.ThrownEscape) && !x.FinalizerEscape && 0 x.Liveness
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
Expand Down Expand Up @@ -214,6 +224,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
Expand Down Expand Up @@ -263,6 +274,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
Expand Down Expand Up @@ -374,6 +386,7 @@ x::EscapeInfo ⊔ₑ y::EscapeInfo = begin
x.Analyzed | y.Analyzed,
x.ReturnEscape | y.ReturnEscape,
ThrownEscape,
x.FinalizerEscape | y.FinalizerEscape,
AliasInfo,
Liveness,
)
Expand Down Expand Up @@ -547,19 +560,22 @@ end
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)
has_return_escape(x) && (EscapeBits |= ARG_RETURN_ESCAPE)
has_thrown_escape(x) && (EscapeBits |= ARG_THROWN_ESCAPE)
has_finalizer_escape(x) && (EscapeBits |= ARG_FINALIZER_ESCAPE)
return ArgEscapeInfo(EscapeBits)
end

const ARG_ALL_ESCAPE = 0x01 << 0
const ARG_RETURN_ESCAPE = 0x01 << 1
const ARG_THROWN_ESCAPE = 0x01 << 2
const ARG_ALL_ESCAPE = 0x01 << 0
const ARG_RETURN_ESCAPE = 0x01 << 1
const ARG_THROWN_ESCAPE = 0x01 << 2
const ARG_FINALIZER_ESCAPE = 0x01 << 3

has_no_escape(x::ArgEscapeInfo) = !has_all_escape(x) && !has_return_escape(x) && !has_thrown_escape(x)
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
has_no_escape(x::ArgEscapeInfo) = !has_all_escape(x) && !has_return_escape(x) && !has_thrown_escape(x) && !has_finalizer_escape(x)
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
has_finalizer_escape(x::ArgEscapeInfo) = x.EscapeBits & ARG_FINALIZER_ESCAPE 0

struct ArgAliasing
aidx::Int
Expand Down Expand Up @@ -1224,7 +1240,7 @@ function from_interprocedural(arginfo::ArgEscapeInfo, pc::Int)
# 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

# escape every argument `(args[6:length(args[3])])` and the name `args[1]`
Expand Down Expand Up @@ -1253,10 +1269,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)
Expand Down Expand Up @@ -1872,6 +1888,17 @@ 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) 8 || return add_fallback_changes!(astate, pc, args)
obj = args[7]
func = args[8]
# TODO: We need to analyze func
add_escape_change!(astate, obj, 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},
Expand Down
3 changes: 3 additions & 0 deletions test/compiler/EscapeAnalysis/EAUtils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,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
Expand Down

0 comments on commit 6340ff0

Please sign in to comment.