Skip to content

Commit 241dc56

Browse files
committed
optimizer: early finalize insertion
Currently, in the finalizer inlining pass, if not all the code between the finalizer registration and the end of the object’s lifetime (i.e., where the finalizer would be inlined) is marked as `:nothrow`, it simply bails out. However, even in such cases, we can insert a `finalize` call at the end of the object’s lifetime, allowing us to call the finalizer early if no exceptions occur. This commit implements this optimization. To do so, it also moves `finalize` to `Core`, so the compiler can handle it directly.
1 parent 7986e17 commit 241dc56

File tree

10 files changed

+51
-23
lines changed

10 files changed

+51
-23
lines changed

base/Base.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ end
271271

272272
BUILDROOT::String = ""
273273

274-
baremodule BuildSettings
275-
end
274+
baremodule BuildSettings end
276275

277276
let i = 1
278277
global BUILDROOT

base/boot.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export
214214
nfields, throw, tuple, ===, isdefined, eval,
215215
# access to globals
216216
getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!,
217-
# ifelse, sizeof # not exported, to avoid conflicting with Base
217+
# ifelse, sizeof, finalize # not exported, to avoid conflicting with Base
218218
# type reflection
219219
<:, typeof, isa, typeassert,
220220
# method reflection

base/compiler/ssair/passes.jl

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,19 +1625,18 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int,
16251625
foreach(note_defuse!, defuse.defs)
16261626
insert_bb != 0 || return nothing # verify post-dominator of all uses exists
16271627

1628+
# Figure out the exact statement where we're going to inline the finalizer.
1629+
loc = insert_idx === nothing ? first(ir.cfg.blocks[insert_bb].stmts) : insert_idx::Int
1630+
attach_after = insert_idx !== nothing
1631+
flag = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL
1632+
finalizer_stmt = ir[SSAValue(finalizer_idx)][:stmt]
1633+
16281634
if !OptimizationParams(inlining.interp).assume_fatal_throw
16291635
# Collect all reachable blocks between the finalizer registration and the
16301636
# insertion point
16311637
blocks = reachable_blocks(ir.cfg, finalizer_bb, insert_bb)
16321638

16331639
# Check #3
1634-
function check_range_nothrow(s::Int, e::Int)
1635-
return all(s:e) do sidx::Int
1636-
sidx == finalizer_idx && return true
1637-
sidx == alloc_idx && return true
1638-
return is_nothrow(ir, SSAValue(sidx))
1639-
end
1640-
end
16411640
for bb in blocks
16421641
range = ir.cfg.blocks[bb].stmts
16431642
s, e = first(range), last(range)
@@ -1648,18 +1647,23 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int,
16481647
if bb == finalizer_bb
16491648
s = finalizer_idx
16501649
end
1651-
check_range_nothrow(s, e) || return nothing
1650+
all(s:e) do sidx::Int
1651+
sidx == finalizer_idx && return true
1652+
sidx == alloc_idx && return true
1653+
return is_nothrow(ir, SSAValue(sidx))
1654+
end && continue
1655+
1656+
# A exception may be thrown between the finalizer registration and the point
1657+
# where the object’s lifetime ends (`insert_idx`): In such cases, we can’t
1658+
# remove the finalizer registration, but we can still insert a `Core.finalize`
1659+
# call at `insert_idx` while leaving the registration intact.
1660+
newinst = add_flag(NewInstruction(Expr(:call, GlobalRef(Core, :finalize), finalizer_stmt.args[3]), Nothing), flag)
1661+
insert_node!(ir, loc, newinst, attach_after)
1662+
return nothing
16521663
end
16531664
end
16541665

1655-
# Ok, legality check complete. Figure out the exact statement where we're
1656-
# going to inline the finalizer.
1657-
loc = insert_idx === nothing ? first(ir.cfg.blocks[insert_bb].stmts) : insert_idx::Int
1658-
attach_after = insert_idx !== nothing
1659-
1660-
finalizer_stmt = ir[SSAValue(finalizer_idx)][:stmt]
16611666
argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]]
1662-
flag = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL
16631667
if length(finalizer_stmt.args) >= 4
16641668
inline = finalizer_stmt.args[4]
16651669
if inline === nothing

base/compiler/tfuncs.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ add_tfunc(donotdelete, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->No
743743
end
744744
add_tfunc(compilerbarrier, 2, 2, compilerbarrier_tfunc, 5)
745745
add_tfunc(Core.finalizer, 2, 4, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 5)
746+
add_tfunc(Core.finalize, 1, 1, @nospecs((𝕃::AbstractLattice, o)->Nothing), 100)
746747

747748
@nospecs function compilerbarrier_nothrow(setting, val)
748749
return isa(setting, Const) && contains_is((:type, :const, :conditional), setting.val)
@@ -2288,8 +2289,11 @@ function _builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f::Builtin), argt
22882289
return true
22892290
elseif f === Core.finalizer
22902291
2 <= na <= 4 || return false
2291-
# Core.finalizer does no error checking - that's done in Base.finalizer
2292+
# `Core.finalizer` does no error checking - that's done in Base.finalizer
22922293
return true
2294+
elseif f === Core.finalize
2295+
na == 2 || return false
2296+
return true # `Core.finalize` does no error checking
22932297
elseif f === Core.compilerbarrier
22942298
na == 2 || return false
22952299
return compilerbarrier_nothrow(argtypes[1], nothing)

base/gcutils.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
43
"""
54
WeakRef(x)
65
@@ -99,8 +98,7 @@ end
9998
10099
Immediately run finalizers registered for object `x`.
101100
"""
102-
finalize(@nospecialize(o)) = ccall(:jl_finalize_th, Cvoid, (Any, Any,),
103-
current_task(), o)
101+
finalize(@nospecialize(o)) = Core.finalize(o)
104102

105103
"""
106104
Base.GC

src/builtin_proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ DECLARE_BUILTIN(compilerbarrier);
3737
DECLARE_BUILTIN(current_scope);
3838
DECLARE_BUILTIN(donotdelete);
3939
DECLARE_BUILTIN(fieldtype);
40+
DECLARE_BUILTIN(finalize);
4041
DECLARE_BUILTIN(finalizer);
4142
DECLARE_BUILTIN(getfield);
4243
DECLARE_BUILTIN(getglobal);

src/builtins.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,13 @@ JL_CALLABLE(jl_f_throw_methoderror)
588588
return jl_nothing;
589589
}
590590

591+
JL_CALLABLE(jl_f_finalize)
592+
{
593+
JL_NARGS(finalize, 1, 1);
594+
jl_finalize(args[0]);
595+
return jl_nothing;
596+
}
597+
591598
JL_CALLABLE(jl_f_ifelse)
592599
{
593600
JL_NARGS(ifelse, 3, 3);
@@ -2446,6 +2453,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
24462453
add_builtin_func("_svec_ref", jl_f__svec_ref);
24472454
jl_builtin_current_scope = add_builtin_func("current_scope", jl_f_current_scope);
24482455
add_builtin_func("throw_methoderror", jl_f_throw_methoderror);
2456+
add_builtin_func("finalize", jl_f_finalize);
24492457

24502458
// builtin types
24512459
add_builtin("Any", (jl_value_t*)jl_any_type);

src/codegen.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,7 @@ static const auto &builtin_func_map() {
16211621
{ jl_f__call_in_world_total_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} },
16221622
{ jl_f_throw_addr, new JuliaFunction<>{XSTR(jl_f_throw), get_func_sig, get_func_attrs} },
16231623
{ jl_f_throw_methoderror_addr, new JuliaFunction<>{XSTR(jl_f_throw_methoderror), get_func_sig, get_func_attrs} },
1624+
{ jl_f_finalize_addr, new JuliaFunction<>{XSTR(jl_f_finalize), get_func_sig, get_func_attrs} },
16241625
{ jl_f_tuple_addr, jltuple_func },
16251626
{ jl_f_svec_addr, new JuliaFunction<>{XSTR(jl_f_svec), get_func_sig, get_func_attrs} },
16261627
{ jl_f_applicable_addr, new JuliaFunction<>{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} },

src/staticdata.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ static htable_t relocatable_ext_cis;
501501
// (reverse of fptr_to_id)
502502
// This is a manually constructed dual of the fvars array, which would be produced by codegen for Julia code, for C.
503503
static const jl_fptr_args_t id_to_fptrs[] = {
504-
&jl_f_throw, &jl_f_throw_methoderror, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa,
504+
&jl_f_throw, &jl_f_throw_methoderror, &jl_f_finalize, &jl_f_is, &jl_f_typeof, &jl_f_issubtype, &jl_f_isa,
505505
&jl_f_typeassert, &jl_f__apply_iterate, &jl_f__apply_pure,
506506
&jl_f__call_latest, &jl_f__call_in_world, &jl_f__call_in_world_total, &jl_f_isdefined,
507507
&jl_f_tuple, &jl_f_svec, &jl_f_intrinsic_call,

test/compiler/inline.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,19 @@ let
15961596
@test get_finalization_count() == 1000
15971597
end
15981598

1599+
# early `finalize` insertion
1600+
let src = code_typed1((Int,)) do x
1601+
xs = finalizer(Ref(x)) do obj
1602+
Base.@assume_effects :nothrow :notaskstate
1603+
Core.println("finalizing: ", objectid(obj))
1604+
end
1605+
@show xs[]
1606+
return xs[]
1607+
end
1608+
@test count(iscall((src, Core.finalizer)), src.code) == 1
1609+
@test count(iscall((src, Core.finalize)), src.code) == 1
1610+
end
1611+
15991612
# optimize `[push!|pushfirst!](::Vector{Any}, x...)`
16001613
@testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!]
16011614
@eval begin

0 commit comments

Comments
 (0)