From 18ba92d6c7cb4888cb5dc64021bc6773c88073e6 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Sat, 5 Oct 2024 02:46:11 +0900 Subject: [PATCH] optimizer: early `finalize` insertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- base/Base.jl | 3 +- base/boot.jl | 2 +- base/compiler/ssair/passes.jl | 34 ++++++++------- base/compiler/tfuncs.jl | 6 ++- base/gcutils.jl | 4 +- src/builtin_proto.h | 1 + src/builtins.c | 8 ++++ src/codegen.cpp | 81 ++++++++++++++++++----------------- src/staticdata.c | 2 +- test/compiler/inline.jl | 13 ++++++ 10 files changed, 91 insertions(+), 63 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 84e10ca788ba2e..57dcebe01e7282 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -271,8 +271,7 @@ end BUILDROOT::String = "" -baremodule BuildSettings -end +baremodule BuildSettings end let i = 1 global BUILDROOT diff --git a/base/boot.jl b/base/boot.jl index 608e273d4b514f..b291d249e004d7 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -214,7 +214,7 @@ export nfields, throw, tuple, ===, isdefined, eval, # access to globals getglobal, setglobal!, swapglobal!, modifyglobal!, replaceglobal!, setglobalonce!, - # ifelse, sizeof # not exported, to avoid conflicting with Base + # ifelse, sizeof, finalize # not exported, to avoid conflicting with Base # type reflection <:, typeof, isa, typeassert, # method reflection diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 0e2272524a0ed2..80c7cf60e6814f 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -1625,19 +1625,18 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int, foreach(note_defuse!, defuse.defs) insert_bb != 0 || return nothing # verify post-dominator of all uses exists + # Figure out the exact statement where we're going to inline the finalizer. + loc = insert_idx === nothing ? first(ir.cfg.blocks[insert_bb].stmts) : insert_idx::Int + attach_after = insert_idx !== nothing + flag = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL + finalizer_stmt = ir[SSAValue(finalizer_idx)][:stmt] + if !OptimizationParams(inlining.interp).assume_fatal_throw # Collect all reachable blocks between the finalizer registration and the # insertion point blocks = reachable_blocks(ir.cfg, finalizer_bb, insert_bb) # Check #3 - function check_range_nothrow(s::Int, e::Int) - return all(s:e) do sidx::Int - sidx == finalizer_idx && return true - sidx == alloc_idx && return true - return is_nothrow(ir, SSAValue(sidx)) - end - end for bb in blocks range = ir.cfg.blocks[bb].stmts s, e = first(range), last(range) @@ -1648,18 +1647,23 @@ function try_resolve_finalizer!(ir::IRCode, alloc_idx::Int, finalizer_idx::Int, if bb == finalizer_bb s = finalizer_idx end - check_range_nothrow(s, e) || return nothing + all(s:e) do sidx::Int + sidx == finalizer_idx && return true + sidx == alloc_idx && return true + return is_nothrow(ir, SSAValue(sidx)) + end && continue + + # An exception may be thrown between the finalizer registration and the point + # where the object’s lifetime ends (`insert_idx`): In such cases, we can’t + # remove the finalizer registration, but we can still insert a `Core.finalize` + # call at `insert_idx` while leaving the registration intact. + newinst = add_flag(NewInstruction(Expr(:call, GlobalRef(Core, :finalize), finalizer_stmt.args[3]), Nothing), flag) + insert_node!(ir, loc, newinst, attach_after) + return nothing end end - # Ok, legality check complete. Figure out the exact statement where we're - # going to inline the finalizer. - loc = insert_idx === nothing ? first(ir.cfg.blocks[insert_bb].stmts) : insert_idx::Int - attach_after = insert_idx !== nothing - - finalizer_stmt = ir[SSAValue(finalizer_idx)][:stmt] argexprs = Any[finalizer_stmt.args[2], finalizer_stmt.args[3]] - flag = info isa FinalizerInfo ? flags_for_effects(info.effects) : IR_FLAG_NULL if length(finalizer_stmt.args) >= 4 inline = finalizer_stmt.args[4] if inline === nothing diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index a6b7e53c6f3201..403184aa1057ff 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -743,6 +743,7 @@ add_tfunc(donotdelete, 0, INT_INF, @nospecs((𝕃::AbstractLattice, args...)->No end add_tfunc(compilerbarrier, 2, 2, compilerbarrier_tfunc, 5) add_tfunc(Core.finalizer, 2, 4, @nospecs((𝕃::AbstractLattice, args...)->Nothing), 5) +add_tfunc(Core.finalize, 1, 1, @nospecs((𝕃::AbstractLattice, o)->Nothing), 100) @nospecs function compilerbarrier_nothrow(setting, val) return isa(setting, Const) && contains_is((:type, :const, :conditional), setting.val) @@ -2288,8 +2289,11 @@ function _builtin_nothrow(𝕃::AbstractLattice, @nospecialize(f::Builtin), argt return true elseif f === Core.finalizer 2 <= na <= 4 || return false - # Core.finalizer does no error checking - that's done in Base.finalizer + # `Core.finalizer` does no error checking - that's done in Base.finalizer return true + elseif f === Core.finalize + na == 2 || return false + return true # `Core.finalize` does no error checking elseif f === Core.compilerbarrier na == 2 || return false return compilerbarrier_nothrow(argtypes[1], nothing) diff --git a/base/gcutils.jl b/base/gcutils.jl index 84a184537ffc05..8a905dd8ad205b 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -1,6 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license - """ WeakRef(x) @@ -99,8 +98,7 @@ end Immediately run finalizers registered for object `x`. """ -finalize(@nospecialize(o)) = ccall(:jl_finalize_th, Cvoid, (Any, Any,), - current_task(), o) +finalize(@nospecialize(o)) = Core.finalize(o) """ Base.GC diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 7fbd5557586757..6c263d218a7868 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -37,6 +37,7 @@ DECLARE_BUILTIN(compilerbarrier); DECLARE_BUILTIN(current_scope); DECLARE_BUILTIN(donotdelete); DECLARE_BUILTIN(fieldtype); +DECLARE_BUILTIN(finalize); DECLARE_BUILTIN(finalizer); DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(getglobal); diff --git a/src/builtins.c b/src/builtins.c index 96c4cec0f50876..5f37b563ba901c 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -2010,6 +2010,13 @@ JL_CALLABLE(jl_f_finalizer) return jl_nothing; } +JL_CALLABLE(jl_f_finalize) +{ + JL_NARGS(finalize, 1, 1); + jl_finalize(args[0]); + return jl_nothing; +} + JL_CALLABLE(jl_f__compute_sparams) { JL_NARGSV(_compute_sparams, 1); @@ -2442,6 +2449,7 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete); jl_builtin_compilerbarrier = add_builtin_func("compilerbarrier", jl_f_compilerbarrier); add_builtin_func("finalizer", jl_f_finalizer); + add_builtin_func("finalize", jl_f_finalize); add_builtin_func("_compute_sparams", jl_f__compute_sparams); add_builtin_func("_svec_ref", jl_f__svec_ref); jl_builtin_current_scope = add_builtin_func("current_scope", jl_f_current_scope); diff --git a/src/codegen.cpp b/src/codegen.cpp index bcda527416676f..8db2efad05b67d 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1607,47 +1607,48 @@ static const auto jlintrinsic_func = new JuliaFunction<>{XSTR(jl_f_intrinsic_cal static const auto &builtin_func_map() { static std::map*> builtins = { - { jl_f_is_addr, new JuliaFunction<>{XSTR(jl_f_is), get_func_sig, get_func_attrs} }, - { jl_f_typeof_addr, new JuliaFunction<>{XSTR(jl_f_typeof), get_func_sig, get_func_attrs} }, - { jl_f_sizeof_addr, new JuliaFunction<>{XSTR(jl_f_sizeof), get_func_sig, get_func_attrs} }, - { jl_f_issubtype_addr, new JuliaFunction<>{XSTR(jl_f_issubtype), get_func_sig, get_func_attrs} }, - { jl_f_isa_addr, new JuliaFunction<>{XSTR(jl_f_isa), get_func_sig, get_func_attrs} }, - { jl_f_typeassert_addr, new JuliaFunction<>{XSTR(jl_f_typeassert), get_func_sig, get_func_attrs} }, - { jl_f_ifelse_addr, new JuliaFunction<>{XSTR(jl_f_ifelse), get_func_sig, get_func_attrs} }, - { jl_f__apply_iterate_addr, new JuliaFunction<>{XSTR(jl_f__apply_iterate), get_func_sig, get_func_attrs} }, - { jl_f__apply_pure_addr, new JuliaFunction<>{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} }, - { jl_f__call_latest_addr, new JuliaFunction<>{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} }, - { jl_f__call_in_world_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} }, + { jl_f_is_addr, new JuliaFunction<>{XSTR(jl_f_is), get_func_sig, get_func_attrs} }, + { jl_f_typeof_addr, new JuliaFunction<>{XSTR(jl_f_typeof), get_func_sig, get_func_attrs} }, + { jl_f_sizeof_addr, new JuliaFunction<>{XSTR(jl_f_sizeof), get_func_sig, get_func_attrs} }, + { jl_f_issubtype_addr, new JuliaFunction<>{XSTR(jl_f_issubtype), get_func_sig, get_func_attrs} }, + { jl_f_isa_addr, new JuliaFunction<>{XSTR(jl_f_isa), get_func_sig, get_func_attrs} }, + { jl_f_typeassert_addr, new JuliaFunction<>{XSTR(jl_f_typeassert), get_func_sig, get_func_attrs} }, + { jl_f_ifelse_addr, new JuliaFunction<>{XSTR(jl_f_ifelse), get_func_sig, get_func_attrs} }, + { jl_f__apply_iterate_addr, new JuliaFunction<>{XSTR(jl_f__apply_iterate), get_func_sig, get_func_attrs} }, + { jl_f__apply_pure_addr, new JuliaFunction<>{XSTR(jl_f__apply_pure), get_func_sig, get_func_attrs} }, + { jl_f__call_latest_addr, new JuliaFunction<>{XSTR(jl_f__call_latest), get_func_sig, get_func_attrs} }, + { jl_f__call_in_world_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world), get_func_sig, get_func_attrs} }, { jl_f__call_in_world_total_addr, new JuliaFunction<>{XSTR(jl_f__call_in_world_total), get_func_sig, get_func_attrs} }, - { jl_f_throw_addr, new JuliaFunction<>{XSTR(jl_f_throw), get_func_sig, get_func_attrs} }, - { jl_f_throw_methoderror_addr, new JuliaFunction<>{XSTR(jl_f_throw_methoderror), get_func_sig, get_func_attrs} }, - { jl_f_tuple_addr, jltuple_func }, - { jl_f_svec_addr, new JuliaFunction<>{XSTR(jl_f_svec), get_func_sig, get_func_attrs} }, - { jl_f_applicable_addr, new JuliaFunction<>{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} }, - { jl_f_invoke_addr, new JuliaFunction<>{XSTR(jl_f_invoke), get_func_sig, get_func_attrs} }, - { jl_f_isdefined_addr, new JuliaFunction<>{XSTR(jl_f_isdefined), get_func_sig, get_func_attrs} }, - { jl_f_getfield_addr, new JuliaFunction<>{XSTR(jl_f_getfield), get_func_sig, get_func_attrs} }, - { jl_f_setfield_addr, new JuliaFunction<>{XSTR(jl_f_setfield), get_func_sig, get_func_attrs} }, - { jl_f_swapfield_addr, new JuliaFunction<>{XSTR(jl_f_swapfield), get_func_sig, get_func_attrs} }, - { jl_f_modifyfield_addr, new JuliaFunction<>{XSTR(jl_f_modifyfield), get_func_sig, get_func_attrs} }, - { jl_f_fieldtype_addr, new JuliaFunction<>{XSTR(jl_f_fieldtype), get_func_sig, get_func_attrs} }, - { jl_f_nfields_addr, new JuliaFunction<>{XSTR(jl_f_nfields), get_func_sig, get_func_attrs} }, - { jl_f__expr_addr, new JuliaFunction<>{XSTR(jl_f__expr), get_func_sig, get_func_attrs} }, - { jl_f__typevar_addr, new JuliaFunction<>{XSTR(jl_f__typevar), get_func_sig, get_func_attrs} }, - { jl_f_memoryref_addr, new JuliaFunction<>{XSTR(jl_f_memoryref), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefoffset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefoffset), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefset), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefswap_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefswap), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefreplace_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefreplace), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefmodify_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefmodify), get_func_sig, get_func_attrs} }, - { jl_f_memoryrefsetonce_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefsetonce), get_func_sig, get_func_attrs} }, - { jl_f_memoryref_isassigned_addr,new JuliaFunction<>{XSTR(jl_f_memoryref_isassigned), get_func_sig, get_func_attrs} }, - { jl_f_apply_type_addr, new JuliaFunction<>{XSTR(jl_f_apply_type), get_func_sig, get_func_attrs} }, - { jl_f_donotdelete_addr, new JuliaFunction<>{XSTR(jl_f_donotdelete), get_donotdelete_sig, get_donotdelete_func_attrs} }, - { jl_f_compilerbarrier_addr, new JuliaFunction<>{XSTR(jl_f_compilerbarrier), get_func_sig, get_func_attrs} }, - { jl_f_finalizer_addr, new JuliaFunction<>{XSTR(jl_f_finalizer), get_func_sig, get_func_attrs} }, - { jl_f__svec_ref_addr, new JuliaFunction<>{XSTR(jl_f__svec_ref), get_func_sig, get_func_attrs} }, - { jl_f_current_scope_addr, new JuliaFunction<>{XSTR(jl_f_current_scope), get_func_sig, get_func_attrs} }, + { jl_f_throw_addr, new JuliaFunction<>{XSTR(jl_f_throw), get_func_sig, get_func_attrs} }, + { jl_f_throw_methoderror_addr, new JuliaFunction<>{XSTR(jl_f_throw_methoderror), get_func_sig, get_func_attrs} }, + { jl_f_tuple_addr, jltuple_func }, + { jl_f_svec_addr, new JuliaFunction<>{XSTR(jl_f_svec), get_func_sig, get_func_attrs} }, + { jl_f_applicable_addr, new JuliaFunction<>{XSTR(jl_f_applicable), get_func_sig, get_func_attrs} }, + { jl_f_invoke_addr, new JuliaFunction<>{XSTR(jl_f_invoke), get_func_sig, get_func_attrs} }, + { jl_f_isdefined_addr, new JuliaFunction<>{XSTR(jl_f_isdefined), get_func_sig, get_func_attrs} }, + { jl_f_getfield_addr, new JuliaFunction<>{XSTR(jl_f_getfield), get_func_sig, get_func_attrs} }, + { jl_f_setfield_addr, new JuliaFunction<>{XSTR(jl_f_setfield), get_func_sig, get_func_attrs} }, + { jl_f_swapfield_addr, new JuliaFunction<>{XSTR(jl_f_swapfield), get_func_sig, get_func_attrs} }, + { jl_f_modifyfield_addr, new JuliaFunction<>{XSTR(jl_f_modifyfield), get_func_sig, get_func_attrs} }, + { jl_f_fieldtype_addr, new JuliaFunction<>{XSTR(jl_f_fieldtype), get_func_sig, get_func_attrs} }, + { jl_f_nfields_addr, new JuliaFunction<>{XSTR(jl_f_nfields), get_func_sig, get_func_attrs} }, + { jl_f__expr_addr, new JuliaFunction<>{XSTR(jl_f__expr), get_func_sig, get_func_attrs} }, + { jl_f__typevar_addr, new JuliaFunction<>{XSTR(jl_f__typevar), get_func_sig, get_func_attrs} }, + { jl_f_memoryref_addr, new JuliaFunction<>{XSTR(jl_f_memoryref), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefoffset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefoffset), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefset_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefset), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefswap_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefswap), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefreplace_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefreplace), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefmodify_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefmodify), get_func_sig, get_func_attrs} }, + { jl_f_memoryrefsetonce_addr, new JuliaFunction<>{XSTR(jl_f_memoryrefsetonce), get_func_sig, get_func_attrs} }, + { jl_f_memoryref_isassigned_addr, new JuliaFunction<>{XSTR(jl_f_memoryref_isassigned), get_func_sig, get_func_attrs} }, + { jl_f_apply_type_addr, new JuliaFunction<>{XSTR(jl_f_apply_type), get_func_sig, get_func_attrs} }, + { jl_f_donotdelete_addr, new JuliaFunction<>{XSTR(jl_f_donotdelete), get_donotdelete_sig, get_donotdelete_func_attrs} }, + { jl_f_compilerbarrier_addr, new JuliaFunction<>{XSTR(jl_f_compilerbarrier), get_func_sig, get_func_attrs} }, + { jl_f_finalizer_addr, new JuliaFunction<>{XSTR(jl_f_finalizer), get_func_sig, get_func_attrs} }, + { jl_f_finalize_addr, new JuliaFunction<>{XSTR(jl_f_finalize), get_func_sig, get_func_attrs} }, + { jl_f__svec_ref_addr, new JuliaFunction<>{XSTR(jl_f__svec_ref), get_func_sig, get_func_attrs} }, + { jl_f_current_scope_addr, new JuliaFunction<>{XSTR(jl_f_current_scope), get_func_sig, get_func_attrs} }, }; return builtins; } diff --git a/src/staticdata.c b/src/staticdata.c index 0a8cbe6db7c67b..47df1dae295f27 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -514,7 +514,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, - &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, + &jl_f_finalizer, &jl_f_finalize, &jl_f__compute_sparams, &jl_f__svec_ref, &jl_f_current_scope, NULL }; diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index 2de6d9950d4e45..f5500fb0e72d11 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -1622,6 +1622,19 @@ let src = code_typed1((Int,)) do x @test count(iscall((src, setfield!)), src.code) == 1 end +# early `finalize` insertion +let src = code_typed1((Int,)) do x + xs = finalizer(Ref(x)) do obj + Base.@assume_effects :nothrow :notaskstate + Core.println("finalizing: ", objectid(obj)) + end + @show xs[] + return xs[] + end + @test count(iscall((src, Core.finalizer)), src.code) == 1 + @test count(iscall((src, Core.finalize)), src.code) == 1 +end + # optimize `[push!|pushfirst!](::Vector{Any}, x...)` @testset "optimize `$f(::Vector{Any}, x...)`" for f = Any[push!, pushfirst!] @eval begin