Skip to content

Commit

Permalink
Flesh out API for malloc with buffered finalization
Browse files Browse the repository at this point in the history
This introduces a new malloc function `GC_buffered_finalize_malloc` which
ensures that unreachable objects are added to a 4KiB thread-local buffer to
be later finalized. Once the buffer is full, it's passed to a user supplied
closure.

The disclaim API in Boehm has no mechanism for detecting which
allocations in a free-list are simply empty and which require finalizing.
So in order to get around this we change mark procedure, using an extra bit
in the object header to determine whether or not an object has a
finalization closure.
  • Loading branch information
jacob-hughes committed Apr 9, 2024
1 parent 216d7de commit e78f4df
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 1 deletion.
63 changes: 63 additions & 0 deletions fnlz_mlc.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,67 @@ GC_API GC_ATTR_MALLOC void * GC_CALL GC_finalized_malloc(size_t lb,
return (word *)op + 1;
}

#ifdef BUFFERED_FINALIZATION

STATIC __thread void** finalizer_buffer_start;
STATIC __thread void** finalizer_buffer_next;
STATIC __thread void** finalizer_buffer_end;

STATIC GC_push_to_fin_q_proc push_proc;

void GC_new_buffer() {
GC_ASSERT(I_HOLD_LOCK());
finalizer_buffer_start = (void**) GC_os_get_mem(GC_page_size);
finalizer_buffer_next = finalizer_buffer_start;
if (NULL == finalizer_buffer_next)
ABORT("Insufficient memory for finalization queue.");

finalizer_buffer_end = (void*)finalizer_buffer_start + GC_page_size;
}

STATIC int GC_CALLBACK GC_push_to_fin_q(void *obj)
{
if (!GC_is_finalizer_queued_bit_set(obj))
return 0;

if (finalizer_buffer_next == finalizer_buffer_end) {
(push_proc)(finalizer_buffer_start);
GC_new_buffer();
}

*finalizer_buffer_next = obj;
finalizer_buffer_next++;
return 1;
}


GC_API GC_ATTR_MALLOC void * GC_CALL GC_buffered_finalize_malloc(size_t lb)
{
void *op;

GC_ASSERT(GC_fin_q_kind != 0);
op = GC_malloc_kind(lb, (int)GC_fin_q_kind);
if (EXPECT(NULL == op, FALSE))
return NULL;
GC_set_finalizer_queued_bit(op);
return (word *)op;
}


GC_API void GC_CALL GC_init_buffered_finalization(GC_push_to_fin_q_proc p)
{
GC_init(); /* In case it's not already done. */
LOCK();
GC_new_buffer();
GC_fin_q_kind = GC_new_kind_inner(GC_new_free_list_inner(),
GC_DS_LENGTH, TRUE, TRUE);
GC_ASSERT(GC_fin_q_kind != 0);

push_proc = p;
GC_register_disclaim_proc_inner(GC_fin_q_kind, GC_push_to_fin_q, TRUE);
UNLOCK();
}

#endif /* BUFFERED_FINALIZATION */

#endif /* ENABLE_DISCLAIM */
33 changes: 33 additions & 0 deletions include/gc/gc_disclaim.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,39 @@ GC_API GC_ATTR_MALLOC GC_ATTR_ALLOC_SIZE(1) void * GC_CALL
GC_finalized_malloc(size_t /* size */,
const struct GC_finalizer_closure * /* fc */) GC_ATTR_NONNULL(2);


#ifdef BUFFERED_FINALIZATION

typedef int (GC_CALLBACK * GC_push_to_fin_q_proc)(void ** /* buffer */);

/* This API is defined only if the library has been suitably compiled */
/* (i.e. with ENABLE_DISCLAIM defined). */

/* Prepare the object kind used by GC_buffered_finalize_malloc. Call */
/* it from your initialization code or, at least, at some point before */
/* finalized allocations. The function is thread-safe. */
GC_API void GC_CALL GC_init_buffered_finalization(GC_push_to_fin_q_proc);

/* Allocate an object which is to be finalized. */
/* This function assumes the first word in the allocated block will */
/* store the function pointer to the finalizer. */
/* Allocations of this kind are added to a buffer which, when full, is */
/* passed to a user supplied closure to invoke their finalizers. It is */
/* the responsibility of the user to ensure these objects are finalized.*/
/* This uses a dedicated object kind with a disclaim procedure, and is */
/* more efficient than GC_register_finalizer and friends. */
/* GC_init_buffered_finalization must be called before using this. */
/* The collector will not reclaim the object during the GC cycle where */
/* it was considered unreachable. In addition, objects reachable from */
/* the finalizer will be protected from collection until the finalizer */
/* has been run. */
/* The disclaim procedure is not invoked in the leak-finding mode. */
/* There is no debugging version of this allocation API. */
GC_API GC_ATTR_MALLOC GC_ATTR_ALLOC_SIZE(1) void * GC_CALL
GC_buffered_finalize_malloc(size_t);

#endif /* BUFFERED_FINALIZATION */

#ifdef __cplusplus
} /* extern "C" */
#endif
Expand Down
2 changes: 2 additions & 0 deletions include/private/gc_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,8 @@ struct _GC_arrays {
# ifdef ENABLE_DISCLAIM
# define GC_finalized_kind GC_arrays._finalized_kind
unsigned _finalized_kind;
# define GC_fin_q_kind GC_arrays._fin_q_kind
unsigned _fin_q_kind;
# endif
# define n_root_sets GC_arrays._n_root_sets
# define GC_excl_table_entries GC_arrays._excl_table_entries
Expand Down
12 changes: 11 additions & 1 deletion mark.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,19 @@ GC_INNER void GC_clear_hdr_marks(hdr *hhdr)
last_bit = FINAL_MARK_BIT((size_t)hhdr->hb_sz);
# endif

BZERO(hhdr -> hb_marks, sizeof(hhdr->hb_marks));
# ifdef BUFFERED_FINALIZATION
unsigned i;
size_t sz = (size_t)hhdr->hb_sz;
unsigned n_marks = (unsigned)FINAL_MARK_BIT(sz);

for (i = 0; i <= n_marks; i += (unsigned)MARK_BIT_OFFSET(sz)) {
hhdr -> hb_marks[i] &= ~1;
}
set_mark_bit_from_hdr(hhdr, last_bit);
hhdr -> hb_n_marks = 0;
#else
BZERO(hhdr -> hb_marks, sizeof(hhdr->hb_marks));
#endif
}

/* Set all mark bits in the header. Used for uncollectible blocks. */
Expand Down

0 comments on commit e78f4df

Please sign in to comment.