diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 87df43ec92224..c6c34a30fe229 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -559,6 +559,7 @@ add_tfunc(atomic_pointerswap, 3, 3, (a, v, order) -> (@nospecialize; pointer_elt add_tfunc(atomic_pointermodify, 4, 4, atomic_pointermodify_tfunc, 5) add_tfunc(atomic_pointerreplace, 5, 5, atomic_pointerreplace_tfunc, 5) add_tfunc(donotdelete, 0, INT_INF, (@nospecialize args...)->Nothing, 0) +add_tfunc(Core.finalizer, 2, 2, (@nospecialize args...)->Nothing, 5) # more accurate typeof_tfunc for vararg tuples abstract only in length function typeof_concrete_vararg(t::DataType) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 74f804ca67627..10d1901003005 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -3068,4 +3068,25 @@ end """ Base.donotdelete +""" + Core.finalizer(f, o) + +This builtin is an implementation detail of [`Base.finalizer`](@ref) and end-users +should use the latter instead. + +# Differences from `Base.finalizer` + +The interface of `Core.finalizer` is essentially the same as `Base.finalizer`, +but there are a number of small differences. They are documented here for +completeness only and (unlike `Base.finalizer`) have no stability guarantees. + +The current differences are: + - `Core.finalizer` does not check for mutability of `o`. Attempting to register + a finalizer for an immutable object is undefined behavior. + - The value `f` must be a Julia object. `Core.finalizer` does not support a + raw C function pointer. + - `Core.finalizer` returns `nothing` rather than `o`. +""" +Core.finalizer + end diff --git a/base/gcutils.jl b/base/gcutils.jl index d17301a1be9b0..0e5d4c16e550a 100644 --- a/base/gcutils.jl +++ b/base/gcutils.jl @@ -4,17 +4,27 @@ ==(w::WeakRef, v) = isequal(w.value, v) ==(w, v::WeakRef) = isequal(w, v.value) +# Used by `Base.finalizer` to validate mutability of an object being finalized. +function _check_mutable(@nospecialize(o)) @noinline + if !ismutable(o) + error("objects of type ", typeof(o), " cannot be finalized") + end +end + """ finalizer(f, x) Register a function `f(x)` to be called when there are no program-accessible references to -`x`, and return `x`. The type of `x` must be a `mutable struct`, otherwise the behavior of -this function is unpredictable. +`x`, and return `x`. The type of `x` must be a `mutable struct`, otherwise the function +will throw. `f` must not cause a task switch, which excludes most I/O operations such as `println`. Using the `@async` macro (to defer context switching to outside of the finalizer) or `ccall` to directly invoke IO functions in C may be helpful for debugging purposes. +Note that there is no guaranteed world age for the execution of `f`. It may be +called in the world age in which the finalizer was registered or any later world age. + # Examples ```julia finalizer(my_mutable_struct) do x @@ -42,18 +52,13 @@ end ``` """ function finalizer(@nospecialize(f), @nospecialize(o)) - if !ismutable(o) - error("objects of type ", typeof(o), " cannot be finalized") - end - ccall(:jl_gc_add_finalizer_th, Cvoid, (Ptr{Cvoid}, Any, Any), - Core.getptls(), o, f) + _check_mutable(o) + Core.finalizer(f, o) return o end function finalizer(f::Ptr{Cvoid}, o::T) where T @inline - if !ismutable(o) - error("objects of type ", typeof(o), " cannot be finalized") - end + _check_mutable(o) ccall(:jl_gc_add_ptr_finalizer, Cvoid, (Ptr{Cvoid}, Any, Ptr{Cvoid}), Core.getptls(), o, f) return o diff --git a/src/builtin_proto.h b/src/builtin_proto.h index c820751ab56e2..6a2b930e17186 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -57,6 +57,7 @@ DECLARE_BUILTIN(_typevar); DECLARE_BUILTIN(donotdelete); DECLARE_BUILTIN(getglobal); DECLARE_BUILTIN(setglobal); +DECLARE_BUILTIN(finalizer); JL_CALLABLE(jl_f_invoke_kwsorter); #ifdef DEFINE_BUILTIN_GLOBALS @@ -73,6 +74,7 @@ JL_CALLABLE(jl_f_get_binding_type); JL_CALLABLE(jl_f_set_binding_type); JL_CALLABLE(jl_f_donotdelete); JL_CALLABLE(jl_f_setglobal); +JL_CALLABLE(jl_f_finalizer); #ifdef __cplusplus } diff --git a/src/builtins.c b/src/builtins.c index 904c691bdab98..26363491846f9 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1600,6 +1600,14 @@ JL_CALLABLE(jl_f_donotdelete) return jl_nothing; } +JL_CALLABLE(jl_f_finalizer) +{ + JL_NARGS(finalizer, 2, 2); + jl_task_t *ct = jl_current_task; + jl_gc_add_finalizer_(ct->ptls, args[1], args[0]); + return jl_nothing; +} + static int equiv_field_types(jl_value_t *old, jl_value_t *ft) { size_t nf = jl_svec_len(ft); @@ -1970,6 +1978,7 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody); add_builtin_func("_equiv_typedef", jl_f__equiv_typedef); jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete); + add_builtin_func("finalizer", jl_f_finalizer); // builtin types add_builtin("Any", (jl_value_t*)jl_any_type); diff --git a/src/codegen.cpp b/src/codegen.cpp index 6fcea6c8901c7..8376fec4b1a30 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1126,7 +1126,8 @@ static const auto &builtin_func_map() { { jl_f_arrayset_addr, new JuliaFunction{XSTR(jl_f_arrayset), get_func_sig, get_func_attrs} }, { jl_f_arraysize_addr, new JuliaFunction{XSTR(jl_f_arraysize), 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_donotdelete_addr, new JuliaFunction{XSTR(jl_f_donotdelete), get_donotdelete_sig, get_donotdelete_func_attrs} }, + { jl_f_finalizer_addr, new JuliaFunction{XSTR(jl_f_finalizer), get_func_sig, get_func_attrs} } }; return builtins; } diff --git a/src/gc.c b/src/gc.c index 8d917a5f52cc4..56e8191cc4453 100644 --- a/src/gc.c +++ b/src/gc.c @@ -488,7 +488,7 @@ void jl_gc_run_all_finalizers(jl_task_t *ct) run_finalizers(ct); } -static void gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT +void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT { assert(jl_atomic_load_relaxed(&ptls->gc_state) == 0); arraylist_t *a = &ptls->finalizers; @@ -518,7 +518,7 @@ static void gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT JL_DLLEXPORT void jl_gc_add_ptr_finalizer(jl_ptls_t ptls, jl_value_t *v, void *f) JL_NOTSAFEPOINT { - gc_add_finalizer_(ptls, (void*)(((uintptr_t)v) | 1), f); + jl_gc_add_finalizer_(ptls, (void*)(((uintptr_t)v) | 1), f); } JL_DLLEXPORT void jl_gc_add_finalizer_th(jl_ptls_t ptls, jl_value_t *v, jl_function_t *f) JL_NOTSAFEPOINT @@ -527,7 +527,7 @@ JL_DLLEXPORT void jl_gc_add_finalizer_th(jl_ptls_t ptls, jl_value_t *v, jl_funct jl_gc_add_ptr_finalizer(ptls, v, jl_unbox_voidpointer(f)); } else { - gc_add_finalizer_(ptls, v, f); + jl_gc_add_finalizer_(ptls, v, f); } } diff --git a/src/julia_internal.h b/src/julia_internal.h index b2115749685ef..327086e8fe98a 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -466,6 +466,7 @@ void jl_gc_track_malloced_array(jl_ptls_t ptls, jl_array_t *a) JL_NOTSAFEPOINT; void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT; void jl_gc_run_all_finalizers(jl_task_t *ct); void jl_release_task_stack(jl_ptls_t ptls, jl_task_t *task); +void jl_gc_add_finalizer_(jl_ptls_t ptls, void *v, void *f) JL_NOTSAFEPOINT; // Set GC memory trigger in bytes for greedy memory collecting void jl_gc_set_max_memory(uint64_t max_mem); diff --git a/src/staticdata.c b/src/staticdata.c index 27fbb0fb336cf..8a3d4132c42f5 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -314,7 +314,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, &jl_f_set_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, - &jl_f_getglobal, &jl_f_setglobal, + &jl_f_getglobal, &jl_f_setglobal, &jl_f_finalizer, NULL }; typedef struct {