Skip to content

Commit

Permalink
Make finalizer a built-in (#45423)
Browse files Browse the repository at this point in the history
* Make finalizer a built-in

Split out from #45272. This is prepratory work towards adding
optimization passes that recognize this builtin. This PR adds
`Core.finalizer` with essentially the same interface as
`Base.finalizer`, but without the error checking or raw-C-pointer
feature. In future commits, the Core.finalizer interface will
likely expand slightly, but Base.finalizer will remain unchanged
and is the supported interface for this functionality.

* Update base/docs/basedocs.jl

Co-authored-by: Shuhei Kadowaki <[email protected]>

* Apply suggestions from code review

Co-authored-by: Ian Atol <[email protected]>

Co-authored-by: Shuhei Kadowaki <[email protected]>
Co-authored-by: Ian Atol <[email protected]>
  • Loading branch information
3 people authored May 24, 2022
1 parent 86f5501 commit 8512dd2
Show file tree
Hide file tree
Showing 9 changed files with 55 additions and 15 deletions.
1 change: 1 addition & 0 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 15 additions & 10 deletions base/gcutils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down
9 changes: 9 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
6 changes: 3 additions & 3 deletions src/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 8512dd2

Please sign in to comment.