Skip to content

Commit

Permalink
Refactoring to be considered before adding MMTk (#55608)
Browse files Browse the repository at this point in the history
This PR contains some refactoring of common functions that were moved to
`gc-common.c` and should be shared between MMTk and Julia's stock GC.
  • Loading branch information
udesou authored Oct 19, 2024
1 parent b0c1525 commit 877de98
Show file tree
Hide file tree
Showing 14 changed files with 402 additions and 370 deletions.
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ ifeq ($(USE_SYSTEM_LIBUV),0)
UV_HEADERS += uv.h
UV_HEADERS += uv/*.h
endif
PUBLIC_HEADERS := $(BUILDDIR)/julia_version.h $(wildcard $(SRCDIR)/support/*.h) $(addprefix $(SRCDIR)/,work-stealing-queue.h gc-interface.h gc-tls.h julia.h julia_assert.h julia_threads.h julia_fasttls.h julia_locks.h julia_atomics.h jloptions.h)
PUBLIC_HEADERS := $(BUILDDIR)/julia_version.h $(wildcard $(SRCDIR)/support/*.h) $(addprefix $(SRCDIR)/,work-stealing-queue.h gc-interface.h gc-tls.h gc-tls-common.h julia.h julia_assert.h julia_threads.h julia_fasttls.h julia_locks.h julia_atomics.h jloptions.h)
ifeq ($(OS),WINNT)
PUBLIC_HEADERS += $(addprefix $(SRCDIR)/,win32_ucontext.h)
endif
Expand Down
205 changes: 205 additions & 0 deletions src/gc-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ extern "C" {

jl_gc_num_t gc_num = {0};

JL_DLLEXPORT uint64_t jl_gc_total_hrtime(void)
{
return gc_num.total_time;
}

// =========================================================================== //
// GC Callbacks
// =========================================================================== //
Expand Down Expand Up @@ -485,10 +490,210 @@ JL_DLLEXPORT void jl_finalize(jl_value_t *o)
int gc_n_threads;
jl_ptls_t* gc_all_tls_states;

// =========================================================================== //
// Allocation
// =========================================================================== //

JL_DLLEXPORT void * jl_gc_alloc_typed(jl_ptls_t ptls, size_t sz, void *ty)
{
return jl_gc_alloc(ptls, sz, ty);
}

JL_DLLEXPORT jl_value_t *jl_gc_allocobj(size_t sz)
{
jl_ptls_t ptls = jl_current_task->ptls;
return jl_gc_alloc(ptls, sz, NULL);
}

// allocation wrappers that save the size of allocations, to allow using
// jl_gc_counted_* functions with a libc-compatible API.

JL_DLLEXPORT void *jl_malloc(size_t sz)
{
int64_t *p = (int64_t *)jl_gc_counted_malloc(sz + JL_SMALL_BYTE_ALIGNMENT);
if (p == NULL)
return NULL;
p[0] = sz;
return (void *)(p + 2); // assumes JL_SMALL_BYTE_ALIGNMENT == 16
}

//_unchecked_calloc does not check for potential overflow of nm*sz
STATIC_INLINE void *_unchecked_calloc(size_t nm, size_t sz) {
size_t nmsz = nm*sz;
int64_t *p = (int64_t *)jl_gc_counted_calloc(nmsz + JL_SMALL_BYTE_ALIGNMENT, 1);
if (p == NULL)
return NULL;
p[0] = nmsz;
return (void *)(p + 2); // assumes JL_SMALL_BYTE_ALIGNMENT == 16
}

JL_DLLEXPORT void *jl_calloc(size_t nm, size_t sz)
{
if (nm > SSIZE_MAX/sz - JL_SMALL_BYTE_ALIGNMENT)
return NULL;
return _unchecked_calloc(nm, sz);
}

JL_DLLEXPORT void jl_free(void *p)
{
if (p != NULL) {
int64_t *pp = (int64_t *)p - 2;
size_t sz = pp[0];
jl_gc_counted_free_with_size(pp, sz + JL_SMALL_BYTE_ALIGNMENT);
}
}

JL_DLLEXPORT void *jl_realloc(void *p, size_t sz)
{
int64_t *pp;
size_t szold;
if (p == NULL) {
pp = NULL;
szold = 0;
}
else {
pp = (int64_t *)p - 2;
szold = pp[0] + JL_SMALL_BYTE_ALIGNMENT;
}
int64_t *pnew = (int64_t *)jl_gc_counted_realloc_with_old_size(pp, szold, sz + JL_SMALL_BYTE_ALIGNMENT);
if (pnew == NULL)
return NULL;
pnew[0] = sz;
return (void *)(pnew + 2); // assumes JL_SMALL_BYTE_ALIGNMENT == 16
}

// allocator entry points

JL_DLLEXPORT jl_value_t *(jl_gc_alloc)(jl_ptls_t ptls, size_t sz, void *ty)
{
return jl_gc_alloc_(ptls, sz, ty);
}

// =========================================================================== //
// Generic Memory
// =========================================================================== //

size_t jl_genericmemory_nbytes(jl_genericmemory_t *m) JL_NOTSAFEPOINT
{
const jl_datatype_layout_t *layout = ((jl_datatype_t*)jl_typetagof(m))->layout;
size_t sz = layout->size * m->length;
if (layout->flags.arrayelem_isunion)
// account for isbits Union array selector bytes
sz += m->length;
return sz;
}

// tracking Memorys with malloc'd storage
void jl_gc_track_malloced_genericmemory(jl_ptls_t ptls, jl_genericmemory_t *m, int isaligned){
// This is **NOT** a GC safe point.
mallocmemory_t *ma;
if (ptls->gc_tls_common.heap.mafreelist == NULL) {
ma = (mallocmemory_t*)malloc_s(sizeof(mallocmemory_t));
}
else {
ma = ptls->gc_tls_common.heap.mafreelist;
ptls->gc_tls_common.heap.mafreelist = ma->next;
}
ma->a = (jl_genericmemory_t*)((uintptr_t)m | !!isaligned);
ma->next = ptls->gc_tls_common.heap.mallocarrays;
ptls->gc_tls_common.heap.mallocarrays = ma;
}

// =========================================================================== //
// GC Debug
// =========================================================================== //

int gc_slot_to_fieldidx(void *obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT
{
int nf = (int)jl_datatype_nfields(vt);
for (int i = 1; i < nf; i++) {
if (slot < (void*)((char*)obj + jl_field_offset(vt, i)))
return i - 1;
}
return nf - 1;
}

int gc_slot_to_arrayidx(void *obj, void *_slot) JL_NOTSAFEPOINT
{
char *slot = (char*)_slot;
jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(obj);
char *start = NULL;
size_t len = 0;
size_t elsize = sizeof(void*);
if (vt == jl_module_type) {
jl_module_t *m = (jl_module_t*)obj;
start = (char*)m->usings.items;
len = module_usings_length(m);
elsize = sizeof(struct _jl_module_using);
}
else if (vt == jl_simplevector_type) {
start = (char*)jl_svec_data(obj);
len = jl_svec_len(obj);
}
if (slot < start || slot >= start + elsize * len)
return -1;
return (slot - start) / elsize;
}

// =========================================================================== //
// GC Control
// =========================================================================== //

JL_DLLEXPORT uint32_t jl_get_gc_disable_counter(void) {
return jl_atomic_load_acquire(&jl_gc_disable_counter);
}

JL_DLLEXPORT int jl_gc_is_enabled(void)
{
jl_ptls_t ptls = jl_current_task->ptls;
return !ptls->disable_gc;
}

int gc_logging_enabled = 0;

JL_DLLEXPORT void jl_enable_gc_logging(int enable) {
gc_logging_enabled = enable;
}

JL_DLLEXPORT int jl_is_gc_logging_enabled(void) {
return gc_logging_enabled;
}


// collector entry point and control
_Atomic(uint32_t) jl_gc_disable_counter = 1;

JL_DLLEXPORT int jl_gc_enable(int on)
{
jl_ptls_t ptls = jl_current_task->ptls;
int prev = !ptls->disable_gc;
ptls->disable_gc = (on == 0);
if (on && !prev) {
// disable -> enable
if (jl_atomic_fetch_add(&jl_gc_disable_counter, -1) == 1) {
gc_num.allocd += gc_num.deferred_alloc;
gc_num.deferred_alloc = 0;
}
}
else if (prev && !on) {
// enable -> disable
jl_atomic_fetch_add(&jl_gc_disable_counter, 1);
// check if the GC is running and wait for it to finish
jl_gc_safepoint_(ptls);
}
return prev;
}

// =========================================================================== //
// MISC
// =========================================================================== //

JL_DLLEXPORT jl_weakref_t *jl_gc_new_weakref(jl_value_t *value)
{
jl_ptls_t ptls = jl_current_task->ptls;
return jl_gc_new_weakref_th(ptls, value);
}

const uint64_t _jl_buff_tag[3] = {0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull, 0x4eadc0004eadc000ull}; // aka 0xHEADER00
JL_DLLEXPORT uintptr_t jl_get_buff_tag(void) JL_NOTSAFEPOINT
{
Expand Down
12 changes: 12 additions & 0 deletions src/gc-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ extern jl_gc_callback_list_t *gc_cblist_notify_gc_pressure;
// malloc wrappers, aligned allocation
// =========================================================================== //

// data structure for tracking malloc'd genericmemory.
typedef struct _mallocmemory_t {
jl_genericmemory_t *a; // lowest bit is tagged if this is aligned memory
struct _mallocmemory_t *next;
} mallocmemory_t;

#if defined(_OS_WINDOWS_)
STATIC_INLINE void *jl_malloc_aligned(size_t sz, size_t align)
{
Expand Down Expand Up @@ -173,4 +179,10 @@ JL_DLLEXPORT void jl_finalize_th(jl_task_t *ct, jl_value_t *o);
extern int gc_n_threads;
extern jl_ptls_t* gc_all_tls_states;

// =========================================================================== //
// Logging
// =========================================================================== //

extern int gc_logging_enabled;

#endif // JL_GC_COMMON_H
42 changes: 0 additions & 42 deletions src/gc-debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -1105,48 +1105,6 @@ void gc_count_pool(void)
jl_safe_printf("************************\n");
}

int gc_slot_to_fieldidx(void *obj, void *slot, jl_datatype_t *vt) JL_NOTSAFEPOINT
{
int nf = (int)jl_datatype_nfields(vt);
for (int i = 1; i < nf; i++) {
if (slot < (void*)((char*)obj + jl_field_offset(vt, i)))
return i - 1;
}
return nf - 1;
}

int gc_slot_to_arrayidx(void *obj, void *_slot) JL_NOTSAFEPOINT
{
char *slot = (char*)_slot;
jl_datatype_t *vt = (jl_datatype_t*)jl_typeof(obj);
char *start = NULL;
size_t len = 0;
size_t elsize = sizeof(void*);
if (vt == jl_module_type) {
jl_module_t *m = (jl_module_t*)obj;
start = (char*)m->usings.items;
len = module_usings_length(m);
elsize = sizeof(struct _jl_module_using);
}
else if (vt == jl_simplevector_type) {
start = (char*)jl_svec_data(obj);
len = jl_svec_len(obj);
}
if (slot < start || slot >= start + elsize * len)
return -1;
return (slot - start) / elsize;
}

static int gc_logging_enabled = 0;

JL_DLLEXPORT void jl_enable_gc_logging(int enable) {
gc_logging_enabled = enable;
}

JL_DLLEXPORT int jl_is_gc_logging_enabled(void) {
return gc_logging_enabled;
}

void _report_gc_finished(uint64_t pause, uint64_t freed, int full, int recollect, int64_t live_bytes) JL_NOTSAFEPOINT {
if (!gc_logging_enabled) {
return;
Expand Down
37 changes: 9 additions & 28 deletions src/gc-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ JL_DLLEXPORT void jl_gc_set_max_memory(uint64_t max_mem);
// should run a collection cycle again (e.g. a full mark right after a full sweep to ensure
// we do a full heap traversal).
JL_DLLEXPORT void jl_gc_collect(jl_gc_collection_t collection);
// Returns whether the thread with `tid` is a collector thread
JL_DLLEXPORT int gc_is_collector_thread(int tid) JL_NOTSAFEPOINT;

// ========================================================================= //
// Metrics
Expand Down Expand Up @@ -130,6 +132,13 @@ JL_DLLEXPORT uint64_t jl_gc_total_hrtime(void);
// Allocation
// ========================================================================= //

// On GCC, this function is inlined when sz is constant (see julia_internal.h)
// In general, this function should implement allocation and should use the specific GC's logic
// to decide whether to allocate a small or a large object. Finally, note that this function
// **must** also set the type of the returning object to be `ty`. The type `ty` may also be used to record
// an allocation of that type in the allocation profiler.
struct _jl_value_t *jl_gc_alloc_(struct _jl_tls_states_t * ptls, size_t sz, void *ty);

// Allocates small objects and increments Julia allocation counterst. Size of the object
// header must be included in the object size. The (possibly unused in some implementations)
// offset to the arena in which we're allocating is passed in the second parameter, and the
Expand Down Expand Up @@ -157,26 +166,6 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz);
JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz);
// Wrapper around Libc realloc that updates Julia allocation counters.
JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size_t sz);
// Wrapper around Libc malloc that allocates a memory region with a few additional machine
// words before the actual payload that are used to record the size of the requested
// allocation. Also updates Julia allocation counters. The function returns a pointer to the
// payload as a result of the allocation.
JL_DLLEXPORT void *jl_malloc(size_t sz);
// Wrapper around Libc calloc that allocates a memory region with a few additional machine
// words before the actual payload that are used to record the size of the requested
// allocation. Also updates Julia allocation counters. The function returns a pointer to the
// payload as a result of the allocation.
JL_DLLEXPORT void *jl_calloc(size_t nm, size_t sz);
// Wrapper around Libc free that takes a pointer to the payload of a memory region allocated
// with jl_malloc or jl_calloc, and uses the size information stored in the first machine
// words of the memory buffer update Julia allocation counters, and then frees the
// corresponding memory buffer.
JL_DLLEXPORT void jl_free(void *p);
// Wrapper around Libc realloc that takes a memory region allocated with jl_malloc or
// jl_calloc, and uses the size information stored in the first machine words of the memory
// buffer to update Julia allocation counters, reallocating the corresponding memory buffer
// in the end.
JL_DLLEXPORT void *jl_realloc(void *p, size_t sz);
// Wrapper around Libc malloc that's used to dynamically allocate memory for Arrays and
// Strings. It increments Julia allocation counters and should check whether we're close to
// the Julia heap target, and therefore, whether we should run a collection. Note that this
Expand All @@ -190,14 +179,6 @@ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz);
// thread-local allocator of the thread referenced by the first jl_ptls_t argument.
JL_DLLEXPORT struct _jl_weakref_t *jl_gc_new_weakref_th(struct _jl_tls_states_t *ptls,
struct _jl_value_t *value);
// Allocates a new weak-reference, assigns its value and increments Julia allocation
// counters. If thread-local allocators are used, then this function should allocate in the
// thread-local allocator of the current thread.
JL_DLLEXPORT struct _jl_weakref_t *jl_gc_new_weakref(struct _jl_value_t *value);
// Allocates an object whose size is specified by the function argument and increments Julia
// allocation counters. If thread-local allocators are used, then this function should
// allocate in the thread-local allocator of the current thread.
JL_DLLEXPORT struct _jl_value_t *jl_gc_allocobj(size_t sz);
// Permanently allocates a memory slot of the size specified by the first parameter. This
// block of memory is allocated in an immortal region that is never swept. The second
// parameter specifies whether the memory should be filled with zeros. The third and fourth
Expand Down
Loading

0 comments on commit 877de98

Please sign in to comment.