Skip to content

Commit 42deb59

Browse files
authored
Merge f9b6863 into dd31084
2 parents dd31084 + f9b6863 commit 42deb59

File tree

7 files changed

+140
-45
lines changed

7 files changed

+140
-45
lines changed

base/compiler/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState)
648648
if !is_effect_free(sv.result.ipo_effects) && sv.all_effect_free && !isempty(sv.ea_analysis_pending)
649649
ir = sv.ir
650650
nargs = let def = sv.result.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end
651-
estate = EscapeAnalysis.analyze_escapes(ir, nargs, optimizer_lattice(interp), GetNativeEscapeCache(interp))
651+
estate = EscapeAnalysis.analyze_escapes(ir, nargs, optimizer_lattice(interp), get_escape_cache(interp))
652652
argescapes = EscapeAnalysis.ArgEscapeCache(estate)
653653
stack_analysis_result!(sv.result, argescapes)
654654
validate_mutable_arg_escapes!(estate, sv)

base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,17 @@ import ._TOP_MOD: ==, getindex, setindex!
1818
using Core: MethodMatch, SimpleVector, ifelse, sizeof
1919
using Core.IR
2020
using ._TOP_MOD: # Base definitions
21-
@__MODULE__, @assert, @eval, @goto, @inbounds, @inline, @label, @noinline,
21+
@__MODULE__, @assert, @eval, @goto, @inbounds, @inline, @label, @noinline, @show,
2222
@nospecialize, @specialize, BitSet, Callable, Csize_t, IdDict, IdSet, UnitRange, Vector,
2323
copy, delete!, empty!, enumerate, error, first, get, get!, haskey, in, isassigned,
2424
isempty, ismutabletype, keys, last, length, max, min, missing, pop!, push!, pushfirst!,
2525
unwrap_unionall, !, !=, !==, &, *, +, -, :, <, <<, =>, >, |, , , , , , , ,
2626
using Core.Compiler: # Core.Compiler specific definitions
27-
Bottom, IRCode, IR_FLAG_NOTHROW, InferenceResult, SimpleInferenceLattice,
27+
AbstractLattice, Bottom, IRCode, IR_FLAG_NOTHROW, InferenceResult, SimpleInferenceLattice,
2828
argextype, fieldcount_noerror, hasintersect, has_flag, intrinsic_nothrow,
29-
is_meta_expr_head, isbitstype, isexpr, println, setfield!_nothrow, singleton_type,
30-
try_compute_field, try_compute_fieldidx, widenconst, , AbstractLattice
29+
is_meta_expr_head, is_mutation_free_argtype, isbitstype, isexpr, println,
30+
setfield!_nothrow, singleton_type, try_compute_field, try_compute_fieldidx, widenconst,
31+
3132

3233
include(x) = _TOP_MOD.include(@__MODULE__, x)
3334
if _TOP_MOD === Core.Compiler
@@ -657,11 +658,13 @@ function analyze_escapes(ir::IRCode, nargs::Int, 𝕃ₒ::AbstractLattice, get_e
657658
# `escape_exception!` conservatively propagates `AllEscape` anyway,
658659
# and so escape information imposed on `:the_exception` isn't computed
659660
continue
661+
elseif head === :gc_preserve_begin
662+
# GC preserve is handled by `escape_gc_preserve!`
663+
elseif head === :gc_preserve_end
664+
escape_gc_preserve!(astate, pc, stmt.args)
660665
elseif head === :static_parameter || # this exists statically, not interested in its escape
661-
head === :copyast || # XXX can this account for some escapes?
662-
head === :isdefined || # just returns `Bool`, nothing accounts for any escapes
663-
head === :gc_preserve_begin || # `GC.@preserve` expressions themselves won't be used anywhere
664-
head === :gc_preserve_end # `GC.@preserve` expressions themselves won't be used anywhere
666+
head === :copyast || # XXX escape something?
667+
head === :isdefined # just returns `Bool`, nothing accounts for any escapes
665668
continue
666669
else
667670
add_conservative_changes!(astate, pc, stmt.args)
@@ -1062,17 +1065,27 @@ end
10621065
function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any})
10631066
mi = first(args)::MethodInstance
10641067
first_idx, last_idx = 2, length(args)
1068+
add_liveness_changes!(astate, pc, args, first_idx, last_idx)
10651069
# TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available
10661070
cache = astate.get_escape_cache(mi)
1071+
ret = SSAValue(pc)
10671072
if cache isa Bool
10681073
if cache
1069-
return nothing # guaranteed to have no escape
1074+
# This method call is very simple and has good effects, so there's no need to
1075+
# escape its arguments. However, since the arguments might be returned, we need
1076+
# to consider the possibility of aliasing between them and the return value.
1077+
for argidx = first_idx:last_idx
1078+
arg = args[argidx]
1079+
if !is_mutation_free_argtype(argextype(arg, astate.ir))
1080+
add_alias_change!(astate, ret, arg)
1081+
end
1082+
end
1083+
return nothing
10701084
else
10711085
return add_conservative_changes!(astate, pc, args, 2)
10721086
end
10731087
end
10741088
cache = cache::ArgEscapeCache
1075-
ret = SSAValue(pc)
10761089
retinfo = astate.estate[ret] # escape information imposed on the call statement
10771090
method = mi.def::Method
10781091
nargs = Int(method.nargs)
@@ -1160,6 +1173,17 @@ function escape_foreigncall!(astate::AnalysisState, pc::Int, args::Vector{Any})
11601173
end
11611174
end
11621175

1176+
function escape_gc_preserve!(astate::AnalysisState, pc::Int, args::Vector{Any})
1177+
@assert length(args) == 1 "invalid :gc_preserve_end"
1178+
val = args[1]
1179+
@assert val isa SSAValue "invalid :gc_preserve_end"
1180+
beginstmt = astate.ir[val][:stmt]
1181+
@assert isexpr(beginstmt, :gc_preserve_begin) "invalid :gc_preserve_end"
1182+
beginargs = beginstmt.args
1183+
# COMBAK we might need to add liveness for all statements from `:gc_preserve_begin` to `:gc_preserve_end`
1184+
add_liveness_changes!(astate, pc, beginargs)
1185+
end
1186+
11631187
normalize(@nospecialize x) = isa(x, QuoteNode) ? x.value : x
11641188

11651189
function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
@@ -1185,20 +1209,12 @@ function escape_call!(astate::AnalysisState, pc::Int, args::Vector{Any})
11851209
if result === missing
11861210
# if this call hasn't been handled by any of pre-defined handlers, escape it conservatively
11871211
add_conservative_changes!(astate, pc, args)
1188-
return
11891212
elseif result === true
11901213
add_liveness_changes!(astate, pc, args, 2)
1191-
return # ThrownEscape is already checked
1214+
elseif is_nothrow(astate.ir, pc)
1215+
add_liveness_changes!(astate, pc, args, 2)
11921216
else
1193-
# we escape statements with the `ThrownEscape` property using the effect-freeness
1194-
# computed by `stmt_effect_flags` invoked within inlining
1195-
# TODO throwness ≠ "effect-free-ness"
1196-
if is_nothrow(astate.ir, pc)
1197-
add_liveness_changes!(astate, pc, args, 2)
1198-
else
1199-
add_fallback_changes!(astate, pc, args, 2)
1200-
end
1201-
return
1217+
add_fallback_changes!(astate, pc, args, 2)
12021218
end
12031219
end
12041220

@@ -1526,4 +1542,12 @@ function escape_array_copy!(astate::AnalysisState, pc::Int, args::Vector{Any})
15261542
add_liveness_changes!(astate, pc, args, 6)
15271543
end
15281544

1545+
function escape_builtin!(::typeof(Core.finalizer), astate::AnalysisState, pc::Int, args::Vector{Any})
1546+
if length(args) 3
1547+
obj = args[3]
1548+
add_liveness_change!(astate, obj, pc) # TODO setup a proper FinalizerEscape?
1549+
end
1550+
return false
1551+
end
1552+
15291553
end # baremodule EscapeAnalysis

base/compiler/ssair/passes.jl

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,13 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
13001300
# Inlining performs legality checks on the finalizer to determine
13011301
# whether or not we may inline it. If so, it appends extra arguments
13021302
# at the end of the intrinsic. Detect that here.
1303-
length(stmt.args) == 5 || continue
1303+
if length(stmt.args) == 4 && stmt.args[4] === nothing
1304+
# constant case
1305+
elseif length(stmt.args) == 5 && stmt.args[4] isa Bool && stmt.args[5] isa MethodInstance
1306+
# inlining case
1307+
else
1308+
continue
1309+
end
13041310
end
13051311
is_finalizer = true
13061312
elseif isexpr(stmt, :foreigncall)
@@ -1685,7 +1691,32 @@ end
16851691
function sroa_mutables!(ir::IRCode, defuses::IdDict{Int,Tuple{SPCSet,SSADefUse}}, used_ssas::Vector{Int}, lazydomtree::LazyDomtree, inlining::Union{Nothing,InliningState})
16861692
𝕃ₒ = inlining === nothing ? SimpleInferenceLattice.instance : optimizer_lattice(inlining.interp)
16871693
lazypostdomtree = LazyPostDomtree(ir)
1694+
function find_finalizer_idx(defuse::SSADefUse)
1695+
finalizer_idx = nothing
1696+
for use in defuse.uses
1697+
if use.kind === :finalizer
1698+
# For now: Only allow one finalizer per allocation
1699+
finalizer_idx !== nothing && return false
1700+
finalizer_idx = use.idx
1701+
end
1702+
end
1703+
if finalizer_idx === nothing
1704+
return true
1705+
elseif inlining === nothing
1706+
return false
1707+
end
1708+
return finalizer_idx
1709+
end
16881710
for (defidx, (intermediaries, defuse)) in defuses
1711+
# Find the type for this allocation
1712+
defexpr = ir[SSAValue(defidx)][:stmt]
1713+
isexpr(defexpr, :new) || continue
1714+
typ = unwrap_unionall(ir.stmts[defidx][:type])
1715+
# Could still end up here if we tried to setfield! on an immutable, which would
1716+
# error at runtime, but is not illegal to have in the IR.
1717+
typ = widenconst(typ)
1718+
ismutabletype(typ) || continue
1719+
typ = typ::DataType
16891720
# Check if there are any uses we did not account for. If so, the variable
16901721
# escapes and we cannot eliminate the allocation. This works, because we're guaranteed
16911722
# not to include any intermediaries that have dead uses. As a result, missing uses will only ever
@@ -1696,29 +1727,33 @@ function sroa_mutables!(ir::IRCode, defuses::IdDict{Int,Tuple{SPCSet,SSADefUse}}
16961727
nuses += used_ssas[iidx]
16971728
end
16981729
nuses_total = used_ssas[defidx] + nuses - length(intermediaries)
1699-
nleaves == nuses_total || continue
1700-
# Find the type for this allocation
1701-
defexpr = ir[SSAValue(defidx)][:stmt]
1702-
isexpr(defexpr, :new) || continue
1703-
typ = unwrap_unionall(ir.stmts[defidx][:type])
1704-
# Could still end up here if we tried to setfield! on an immutable, which would
1705-
# error at runtime, but is not illegal to have in the IR.
1706-
typ = widenconst(typ)
1707-
ismutabletype(typ) || continue
1708-
typ = typ::DataType
1709-
# First check for any finalizer calls
1710-
finalizer_idx = nothing
1711-
for use in defuse.uses
1712-
if use.kind === :finalizer
1713-
# For now: Only allow one finalizer per allocation
1714-
finalizer_idx !== nothing && @goto skip
1715-
finalizer_idx = use.idx
1730+
if nleaves nuses_total
1731+
finalizer_idx = find_finalizer_idx(defuse)
1732+
if finalizer_idx isa Int
1733+
nargs = length(ir.argtypes) # TODO
1734+
estate = EscapeAnalysis.analyze_escapes(ir, nargs, 𝕃ₒ, get_escape_cache(inlining.interp))
1735+
einfo = estate[SSAValue(defidx)]
1736+
if EscapeAnalysis.has_no_escape(einfo)
1737+
already = BitSet(use.idx for use in defuse.uses)
1738+
for idx = einfo.Liveness
1739+
if idx already
1740+
push!(defuse.uses, SSAUse(:EALiveness, idx))
1741+
end
1742+
end
1743+
try_resolve_finalizer!(ir, defidx, finalizer_idx, defuse, inlining::InliningState,
1744+
lazydomtree, lazypostdomtree, ir[SSAValue(finalizer_idx)][:info])
1745+
end
17161746
end
1717-
end
1718-
if finalizer_idx !== nothing && inlining !== nothing
1719-
try_resolve_finalizer!(ir, defidx, finalizer_idx, defuse, inlining,
1720-
lazydomtree, lazypostdomtree, ir[SSAValue(finalizer_idx)][:info])
17211747
continue
1748+
else
1749+
finalizer_idx = find_finalizer_idx(defuse)
1750+
if finalizer_idx isa Int
1751+
try_resolve_finalizer!(ir, defidx, finalizer_idx, defuse, inlining::InliningState,
1752+
lazydomtree, lazypostdomtree, ir[SSAValue(finalizer_idx)][:info])
1753+
continue
1754+
elseif !finalizer_idx
1755+
continue
1756+
end
17221757
end
17231758
# Partition defuses by field
17241759
fielddefuse = SSADefUse[SSADefUse() for _ = 1:fieldcount(typ)]

base/compiler/types.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ typeinf_lattice(::AbstractInterpreter) = InferenceLattice(BaseInferenceLattice.i
452452
ipo_lattice(::AbstractInterpreter) = InferenceLattice(IPOResultLattice.instance)
453453
optimizer_lattice(::AbstractInterpreter) = SimpleInferenceLattice.instance
454454

455+
get_escape_cache(interp::AbstractInterpreter) = GetNativeEscapeCache(interp)
456+
455457
abstract type CallInfo end
456458

457459
@nospecialize

test/compiler/EscapeAnalysis/EAUtils.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ CC.OptimizationParams(interp::EscapeAnalyzer) = interp.opt_params
115115
CC.get_inference_world(interp::EscapeAnalyzer) = interp.world
116116
CC.get_inference_cache(interp::EscapeAnalyzer) = interp.inf_cache
117117
CC.cache_owner(::EscapeAnalyzer) = EAToken()
118+
CC.get_escape_cache(interp::EscapeAnalyzer) = GetEscapeCache(interp)
118119

119120
function CC.ipo_dataflow_analysis!(interp::EscapeAnalyzer, ir::IRCode, caller::InferenceResult)
120121
# run EA on all frames that have been optimized

test/compiler/codegen.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ if opt_level > 0
266266
@test occursin("ret $Iptr %\"x::$(Int)\"", load_dummy_ref_ir)
267267
end
268268

269-
# Issue 22770
269+
# Issue JuliaLang/julia#22770
270270
let was_gced = false
271271
@noinline make_tuple(x) = tuple(x)
272272
@noinline use(x) = ccall(:jl_breakpoint, Cvoid, ())

test/compiler/inline.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2223,3 +2223,36 @@ let src = code_typed1(bar_split_error, Tuple{})
22232223
@test count(iscall((src, foo_split)), src.code) == 0
22242224
@test count(iscall((src, Core.throw_methoderror)), src.code) > 0
22252225
end
2226+
2227+
# finalizer inlining with EA
2228+
mutable struct ForeignBuffer{T}
2229+
const ptr::Ptr{T}
2230+
end
2231+
mutable struct ForeignBufferChecker
2232+
@atomic finalized::Bool
2233+
end
2234+
const foreign_buffer_checker = ForeignBufferChecker(false)
2235+
function foreign_alloc(::Type{T}, length) where T
2236+
ptr = Libc.malloc(sizeof(T) * length)
2237+
ptr = Base.unsafe_convert(Ptr{T}, ptr)
2238+
obj = ForeignBuffer{T}(ptr)
2239+
return finalizer(obj) do obj
2240+
Base.@assume_effects :notaskstate :nothrow
2241+
@atomic foreign_buffer_checker.finalized = true
2242+
Libc.free(obj.ptr)
2243+
end
2244+
end
2245+
function f_EA_finalizer(N::Int)
2246+
workspace = foreign_alloc(Float64, N)
2247+
GC.@preserve workspace begin
2248+
(;ptr) = workspace
2249+
Base.@assume_effects :nothrow @noinline println(devnull, "ptr = ", ptr)
2250+
end
2251+
end
2252+
let src = code_typed1(f_EA_finalizer, (Int,))
2253+
@test count(iscall((src, Core.finalizer)), src.code) == 0
2254+
end
2255+
let;Base.Experimental.@force_compile
2256+
f_EA_finalizer(42000)
2257+
@test foreign_buffer_checker.finalized
2258+
end

0 commit comments

Comments
 (0)