switching GC from semi-space copying to mark-sweep

This commit is contained in:
2020-12-31 17:48:47 +00:00
parent fbb7ce853a
commit 11e831bbcc
15 changed files with 826 additions and 348 deletions

439
lib/gc.c
View File

@ -26,6 +26,11 @@
#include "hcl-prv.h"
#if defined(HCL_PROFILE_VM)
#include <sys/time.h>
#include <sys/resource.h> /* getrusage */
#endif
static struct
{
hcl_oow_t len;
@ -84,7 +89,7 @@ static void compact_symbol_table (hcl_t* hcl, hcl_oop_t _nil)
}
HCL_ASSERT (hcl, hcl->symtab->bucket->slot[index] != _nil);
for (i = 0, x = index, y = index; i < bucket_size; i++)
{
y = (y + 1) % bucket_size;
@ -118,7 +123,7 @@ static void compact_symbol_table (hcl_t* hcl, hcl_oop_t _nil)
hcl->symtab->tally = HCL_SMOOI_TO_OOP(tally);
}
static HCL_INLINE hcl_oow_t get_payload_bytes (hcl_t* hcl, hcl_oop_t oop)
hcl_oow_t hcl_getobjpayloadbytes (hcl_t* hcl, hcl_oop_t oop)
{
hcl_oow_t nbytes_aligned;
@ -154,86 +159,88 @@ static HCL_INLINE hcl_oow_t get_payload_bytes (hcl_t* hcl, hcl_oop_t oop)
return nbytes_aligned;
}
hcl_oop_t hcl_moveoop (hcl_t* hcl, hcl_oop_t oop)
/* ----------------------------------------------------------------------- */
#if 0
static HCL_INLINE void gc_ms_mark (hcl_t* hcl, hcl_oop_t oop)
{
hcl_oow_t i, sz;
#if defined(HCL_SUPPORT_GC_DURING_IGNITION)
if (!oop) return oop;
if (!oop) return;
#endif
if (!HCL_OOP_IS_POINTER(oop)) return oop;
if (HCL_OBJ_GET_FLAGS_NGC(oop)) return oop; /* non-GC object */
if (!HCL_OOP_IS_POINTER(oop)) return;
if (HCL_OBJ_GET_FLAGS_MOVED(oop)) return; /* already marked */
if (HCL_OBJ_GET_FLAGS_MOVED(oop))
HCL_OBJ_SET_FLAGS_MOVED(oop, 1); /* mark */
gc_ms_mark (hcl, (hcl_oop_t)HCL_OBJ_GET_CLASS(oop)); /* TODO: remove recursion */
if (HCL_OBJ_GET_FLAGS_TYPE(oop) == HCL_OBJ_TYPE_OOP)
{
/* this object has migrated to the new heap.
* the class field has been updated to the new object
* in the 'else' block below. i can simply return it
* without further migration. */
return HCL_OBJ_GET_CLASS(oop);
}
else
{
hcl_oow_t nbytes_aligned;
hcl_oop_t tmp;
hcl_oow_t size, i;
nbytes_aligned = get_payload_bytes(hcl, oop);
/* is it really better to use a flag bit in the header to
* determine that it is an instance of process? */
if (HCL_UNLIKELY(HCL_OBJ_GET_FLAGS_PROC(oop)))
{
/* the stack in a process object doesn't need to be
* scanned in full. the slots above the stack pointer
* are garbages. */
size = HCL_PROCESS_NAMED_INSTVARS + HCL_OOP_TO_SMOOI(((hcl_oop_process_t)oop)->sp) + 1;
HCL_ASSERT (hcl, size <= HCL_OBJ_GET_SIZE(oop));
}
else
{
size = HCL_OBJ_GET_SIZE(oop);
}
/* allocate space in the new heap */
tmp = (hcl_oop_t)hcl_allocheapmem(hcl, hcl->newheap, HCL_SIZEOF(hcl_obj_t) + nbytes_aligned);
/* allocation here must not fail because
* i'm allocating the new space in a new heap for
* moving an existing object in the current heap.
*
* assuming the new heap is as large as the old heap,
* and garbage collection doesn't allocate more objects
* than in the old heap, it must not fail. */
HCL_ASSERT (hcl, tmp != HCL_NULL);
/* copy the payload to the new object */
HCL_MEMCPY (tmp, oop, HCL_SIZEOF(hcl_obj_t) + nbytes_aligned);
/* mark the old object that it has migrated to the new heap */
HCL_OBJ_SET_FLAGS_MOVED(oop, 1);
/* let the class field of the old object point to the new
* object allocated in the new heap. it is returned in
* the 'if' block at the top of this function. */
HCL_OBJ_SET_CLASS (oop, tmp);
/* return the new object */
return tmp;
for (i = 0; i < size; i++)
{
hcl_oop_t tmp = HCL_OBJ_GET_OOP_VAL(oop, i);
if (HCL_OOP_IS_POINTER(tmp)) gc_ms_mark (hcl, tmp); /* TODO: no resursion */
}
}
}
static hcl_uint8_t* scan_new_heap (hcl_t* hcl, hcl_uint8_t* ptr)
#else
static HCL_INLINE void gc_ms_mark_object (hcl_t* hcl, hcl_oop_t oop)
{
while (ptr < hcl->newheap->ptr)
#if defined(HCL_SUPPORT_GC_DURING_IGNITION)
if (!oop) return;
#endif
if (!HCL_OOP_IS_POINTER(oop) || HCL_OBJ_GET_FLAGS_MOVED(oop)) return; /* non-pointer or already marked */
HCL_OBJ_SET_FLAGS_MOVED(oop, 1); /* mark */
HCL_ASSERT (hcl, hcl->gci.stack.len < hcl->gci.stack.capa);
hcl->gci.stack.ptr[hcl->gci.stack.len++] = oop; /* push */
if (hcl->gci.stack.len > hcl->gci.stack.max) hcl->gci.stack.max = hcl->gci.stack.len;
}
static HCL_INLINE void gc_ms_scan_stack (hcl_t* hcl)
{
hcl_oop_t oop;
while (hcl->gci.stack.len > 0)
{
hcl_oow_t i;
hcl_oow_t nbytes_aligned;
hcl_oop_t oop, tmp;
oop = hcl->gci.stack.ptr[--hcl->gci.stack.len];
oop = (hcl_oop_t)ptr;
HCL_ASSERT (hcl, HCL_OOP_IS_POINTER(oop));
nbytes_aligned = get_payload_bytes(hcl, oop);
tmp = hcl_moveoop(hcl, HCL_OBJ_GET_CLASS(oop));
HCL_OBJ_SET_CLASS (oop, tmp);
gc_ms_mark_object (hcl, (hcl_oop_t)HCL_OBJ_GET_CLASS(oop));
if (HCL_OBJ_GET_FLAGS_TYPE(oop) == HCL_OBJ_TYPE_OOP)
{
hcl_oop_oop_t xtmp;
hcl_oow_t size;
hcl_oow_t size, i;
/* is it really better to use a flag bit in the header to
* determine that it is an instance of process? */
/* if (HCL_UNLIKELY(HCL_OBJ_GET_FLAGS_PROC(oop))) */
if (HCL_OBJ_GET_FLAGS_BRAND(oop) == HCL_BRAND_PROCESS)
{
/* the stack in a process object doesn't need to be
* scanned in full. the slots above the stack pointer
* are garbages. */
size = HCL_PROCESS_NAMED_INSTVARS +
HCL_OOP_TO_SMOOI(((hcl_oop_process_t)oop)->sp) + 1;
size = HCL_PROCESS_NAMED_INSTVARS + HCL_OOP_TO_SMOOI(((hcl_oop_process_t)oop)->sp) + 1;
HCL_ASSERT (hcl, size <= HCL_OBJ_GET_SIZE(oop));
}
else
@ -241,21 +248,285 @@ static hcl_uint8_t* scan_new_heap (hcl_t* hcl, hcl_uint8_t* ptr)
size = HCL_OBJ_GET_SIZE(oop);
}
xtmp = (hcl_oop_oop_t)oop;
for (i = 0; i < size; i++)
{
if (HCL_OOP_IS_POINTER(xtmp->slot[i]))
xtmp->slot[i] = hcl_moveoop (hcl, xtmp->slot[i]);
gc_ms_mark_object (hcl, HCL_OBJ_GET_OOP_VAL(oop, i));
}
}
}
}
static HCL_INLINE void gc_ms_mark (hcl_t* hcl, hcl_oop_t oop)
{
gc_ms_mark_object (hcl, oop);
gc_ms_scan_stack (hcl);
}
#endif
static HCL_INLINE void gc_ms_mark_roots (hcl_t* hcl)
{
hcl_oow_t i;
#if defined(ENABLE_GCFIN)
hcl_oow_t gcfin_count;
#endif
hcl_cb_t* cb;
#if defined(HCL_PROFILE_VM)
struct rusage ru;
hcl_ntime_t rut;
getrusage(RUSAGE_SELF, &ru);
HCL_INIT_NTIME (&rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
#endif
if (hcl->processor && hcl->processor->active)
{
HCL_ASSERT (hcl, (hcl_oop_t)hcl->processor != hcl->_nil);
HCL_ASSERT (hcl, (hcl_oop_t)hcl->processor->active != hcl->_nil);
/* commit the stack pointer to the active process because
* gc needs the correct stack pointer for a process object */
hcl->processor->active->sp = HCL_SMOOI_TO_OOP(hcl->sp);
}
gc_ms_mark (hcl, hcl->_nil);
gc_ms_mark (hcl, hcl->_true);
gc_ms_mark (hcl, hcl->_false);
for (i = 0; i < HCL_COUNTOF(syminfo); i++)
{
gc_ms_mark (hcl, *(hcl_oop_t*)((hcl_uint8_t*)hcl + syminfo[i].offset));
}
gc_ms_mark (hcl, (hcl_oop_t)hcl->sysdic);
gc_ms_mark (hcl, (hcl_oop_t)hcl->processor);
gc_ms_mark (hcl, (hcl_oop_t)hcl->nil_process);
for (i = 0; i < hcl->code.lit.len; i++)
{
/* the literal array ia a NGC object. but the literal objects
* pointed by the elements of this array must be gabage-collected. */
gc_ms_mark (hcl, ((hcl_oop_oop_t)hcl->code.lit.arr)->slot[i]);
}
gc_ms_mark (hcl, hcl->p.e);
for (i = 0; i < hcl->sem_list_count; i++)
{
gc_ms_mark (hcl, (hcl_oop_t)hcl->sem_list[i]);
}
for (i = 0; i < hcl->sem_heap_count; i++)
{
gc_ms_mark (hcl, (hcl_oop_t)hcl->sem_heap[i]);
}
for (i = 0; i < hcl->sem_io_tuple_count; i++)
{
if (hcl->sem_io_tuple[i].sem[HCL_SEMAPHORE_IO_TYPE_INPUT])
gc_ms_mark (hcl, (hcl_oop_t)hcl->sem_io_tuple[i].sem[HCL_SEMAPHORE_IO_TYPE_INPUT]);
if (hcl->sem_io_tuple[i].sem[HCL_SEMAPHORE_IO_TYPE_OUTPUT])
gc_ms_mark (hcl, (hcl_oop_t)hcl->sem_io_tuple[i].sem[HCL_SEMAPHORE_IO_TYPE_OUTPUT]);
}
#if defined(ENABLE_GCFIN)
gc_ms_mark (hcl, (hcl_oop_t)hcl->sem_gcfin);
#endif
for (i = 0; i < hcl->proc_map_capa; i++)
{
gc_ms_mark (hcl, hcl->proc_map[i]);
}
for (i = 0; i < hcl->volat_count; i++)
{
gc_ms_mark (hcl, *hcl->volat_stack[i]);
}
if (hcl->initial_context) gc_ms_mark (hcl, (hcl_oop_t)hcl->initial_context);
if (hcl->active_context) gc_ms_mark (hcl, (hcl_oop_t)hcl->active_context);
if (hcl->initial_function) gc_ms_mark (hcl, (hcl_oop_t)hcl->initial_function);
if (hcl->active_function) gc_ms_mark (hcl, (hcl_oop_t)hcl->active_function);
if (hcl->last_retv) gc_ms_mark (hcl, hcl->last_retv);
/*hcl_rbt_walk (&hcl->modtab, call_module_gc, hcl); */
for (cb = hcl->cblist; cb; cb = cb->next)
{
if (cb->gc) cb->gc (hcl);
}
#if defined(ENABLE_GCFIN)
gcfin_count = move_finalizable_objects(hcl); /* mark finalizable objects */
#endif
if (hcl->symtab)
{
compact_symbol_table (hcl, hcl->_nil); /* delete symbol table entries that are not marked */
#if 0
gc_ms_mark (hcl, (hcl_oop_t)hcl->symtab); /* mark the symbol table */
#else
HCL_OBJ_SET_FLAGS_MOVED(hcl->symtab, 1); /* mark */
HCL_OBJ_SET_FLAGS_MOVED(hcl->symtab->bucket, 1); /* mark */
#endif
}
#if defined(ENABLE_GCFIN)
if (gcfin_count > 0) hcl->sem_gcfin_sigreq = 1;
#endif
if (hcl->active_function) hcl->active_code = HCL_FUNCTION_GET_CODE_BYTE(hcl->active_function); /* update hcl->active_code */
#if defined(HCL_PROFILE_VM)
getrusage(RUSAGE_SELF, &ru);
HCL_SUB_NTIME_SNS (&rut, &rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
HCL_SUB_NTIME (&hcl->gci.stat.mark, &hcl->gci.stat.mark, &rut); /* do subtraction because rut is negative */
#endif
}
void hcl_gc_ms_sweep_lazy (hcl_t* hcl, hcl_oow_t allocsize)
{
hcl_gchdr_t* curr, * next, * prev;
hcl_oop_t obj;
hcl_oow_t freed_size;
#if defined(HCL_PROFILE_VM)
struct rusage ru;
hcl_ntime_t rut;
getrusage(RUSAGE_SELF, &ru);
HCL_INIT_NTIME (&rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
#endif
if (!hcl->gci.ls.curr) goto done;
freed_size = 0;
prev = hcl->gci.ls.prev;
curr = hcl->gci.ls.curr;
while (curr)
{
next = curr->next;
obj = (hcl_oop_t)(curr + 1);
if (HCL_OBJ_GET_FLAGS_MOVED(obj)) /* if marked */
{
HCL_OBJ_SET_FLAGS_MOVED (obj, 0); /* unmark */
prev = curr;
}
else
{
hcl_oow_t objsize;
if (prev) prev->next = next;
else hcl->gci.b = next;
objsize = HCL_SIZEOF(hcl_obj_t) + hcl_getobjpayloadbytes(hcl, obj);
freed_size += objsize;
hcl->gci.bsz -= objsize;
hcl_freeheapmem (hcl, hcl->heap, curr); /* destroy */
/*if (freed_size > allocsize)*/ /* TODO: can it secure large enough space? */
if (objsize == allocsize)
{
hcl->gci.ls.prev = prev;
hcl->gci.ls.curr = next; /* let the next lazy sweeping begin at this point */
goto done;
}
}
ptr = ptr + HCL_SIZEOF(hcl_obj_t) + nbytes_aligned;
curr = next;
}
/* return the pointer to the beginning of the free space in the heap */
return ptr;
hcl->gci.ls.curr = HCL_NULL;
done:
#if defined(HCL_PROFILE_VM)
getrusage(RUSAGE_SELF, &ru);
HCL_SUB_NTIME_SNS (&rut, &rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
HCL_SUB_NTIME (&hcl->gci.stat.sweep, &hcl->gci.stat.sweep, &rut); /* do subtraction because rut is negative */
#endif
return;
}
static HCL_INLINE void gc_ms_sweep (hcl_t* hcl)
{
hcl_gchdr_t* curr, * next, * prev;
hcl_oop_t obj;
#if defined(HCL_PROFILE_VM)
struct rusage ru;
hcl_ntime_t rut;
getrusage(RUSAGE_SELF, &ru);
HCL_INIT_NTIME (&rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
#endif
prev = HCL_NULL;
curr = hcl->gci.b;
while (curr)
{
next = curr->next;
obj = (hcl_oop_t)(curr + 1);
if (HCL_OBJ_GET_FLAGS_MOVED(obj)) /* if marked */
{
HCL_OBJ_SET_FLAGS_MOVED (obj, 0); /* unmark */
prev = curr;
}
else
{
if (prev) prev->next = next;
else hcl->gci.b = next;
hcl->gci.bsz -= HCL_SIZEOF(hcl_obj_t) + hcl_getobjpayloadbytes(hcl, obj);
hcl_freeheapmem (hcl, hcl->heap, curr); /* destroy */
}
curr = next;
}
hcl->gci.ls.curr = HCL_NULL;
#if defined(HCL_PROFILE_VM)
getrusage(RUSAGE_SELF, &ru);
HCL_SUB_NTIME_SNS (&rut, &rut, ru.ru_utime.tv_sec, HCL_USEC_TO_NSEC(ru.ru_utime.tv_usec));
HCL_SUB_NTIME (&hcl->gci.stat.sweep, &hcl->gci.stat.sweep, &rut); /* do subtraction because rut is negative */
#endif
}
void hcl_gc (hcl_t* hcl, int full)
{
if (hcl->gci.lazy_sweep) hcl_gc_ms_sweep_lazy (hcl, HCL_TYPE_MAX(hcl_oow_t));
HCL_LOG1 (hcl, HCL_LOG_GC | HCL_LOG_INFO, "Starting GC (mark-sweep) - gci.bsz = %zu\n", hcl->gci.bsz);
hcl->gci.stack.len = 0;
/*hcl->gci.stack.max = 0;*/
gc_ms_mark_roots (hcl);
if (!full && hcl->gci.lazy_sweep)
{
/* set the lazy sweeping point to the head of the allocated blocks.
* hawk_allocbytes() updates hcl->gci.ls.prev if it is called while
* hcl->gci.ls.curr stays at hcl->gci.b */
hcl->gci.ls.prev = HCL_NULL;
hcl->gci.ls.curr = hcl->gci.b;
}
else
{
gc_ms_sweep (hcl);
}
HCL_LOG2 (hcl, HCL_LOG_GC | HCL_LOG_INFO, "Finished GC (mark-sweep) - gci.bsz = %zu, gci.stack.max %zu\n", hcl->gci.bsz, hcl->gci.stack.max);
}
hcl_oop_t hcl_moveoop (hcl_t* hcl, hcl_oop_t oop)
{
if (oop) gc_ms_mark (hcl, oop);
return oop;
}
#if 0
void hcl_gc (hcl_t* hcl)
{
/*
@ -343,9 +614,9 @@ void hcl_gc (hcl_t* hcl)
hcl->proc_map[i] = hcl_moveoop(hcl, hcl->proc_map[i]);
}
for (i = 0; i < hcl->tmp_count; i++)
for (i = 0; i < hcl->volat_count; i++)
{
*hcl->tmp_stack[i] = hcl_moveoop(hcl, *hcl->tmp_stack[i]);
*hcl->volat_stack[i] = hcl_moveoop(hcl, *hcl->volat_stack[i]);
}
if (hcl->initial_context)
@ -417,26 +688,26 @@ void hcl_gc (hcl_t* hcl)
"Finished GC curheap base %p ptr %p newheap base %p ptr %p\n",
hcl->curheap->base, hcl->curheap->ptr, hcl->newheap->base, hcl->newheap->ptr);
}
#endif
void hcl_pushtmp (hcl_t* hcl, hcl_oop_t* oop_ptr)
void hcl_pushvolat (hcl_t* hcl, hcl_oop_t* oop_ptr)
{
/* if you have too many temporaries pushed, something must be wrong.
* change your code not to exceede the stack limit */
HCL_ASSERT (hcl, hcl->tmp_count < HCL_COUNTOF(hcl->tmp_stack));
hcl->tmp_stack[hcl->tmp_count++] = oop_ptr;
HCL_ASSERT (hcl, hcl->volat_count < HCL_COUNTOF(hcl->volat_stack));
hcl->volat_stack[hcl->volat_count++] = oop_ptr;
}
void hcl_poptmp (hcl_t* hcl)
void hcl_popvolat (hcl_t* hcl)
{
HCL_ASSERT (hcl, hcl->tmp_count > 0);
hcl->tmp_count--;
HCL_ASSERT (hcl, hcl->volat_count > 0);
hcl->volat_count--;
}
void hcl_poptmps (hcl_t* hcl, hcl_oow_t count)
void hcl_popvolats (hcl_t* hcl, hcl_oow_t count)
{
HCL_ASSERT (hcl, hcl->tmp_count >= count);
hcl->tmp_count -= count;
HCL_ASSERT (hcl, hcl->volat_count >= count);
hcl->volat_count -= count;
}
@ -447,11 +718,11 @@ hcl_oop_t hcl_shallowcopy (hcl_t* hcl, hcl_oop_t oop)
hcl_oop_t z;
hcl_oow_t total_bytes;
total_bytes = HCL_SIZEOF(hcl_obj_t) + get_payload_bytes(hcl, oop);
total_bytes = HCL_SIZEOF(hcl_obj_t) + hcl_getobjpayloadbytes(hcl, oop);
hcl_pushtmp (hcl, &oop);
hcl_pushvolat (hcl, &oop);
z = (hcl_oop_t)hcl_allocbytes (hcl, total_bytes);
hcl_poptmp(hcl);
hcl_popvolat(hcl);
HCL_MEMCPY (z, oop, total_bytes);
return z;
@ -526,12 +797,15 @@ int hcl_ignite (hcl_t* hcl)
hcl->processor->total_count = HCL_SMOOI_TO_OOP(0);
hcl->processor->runnable.count = HCL_SMOOI_TO_OOP(0);
hcl->processor->suspended.count = HCL_SMOOI_TO_OOP(0);
/* commit the sp field of the initial active context to hcl->sp */
hcl->sp = HCL_OOP_TO_SMOOI(hcl->processor->active->sp);
}
/* TODO: move code.bc.ptr creation to hcl_init? */
if (!hcl->code.bc.ptr)
{
hcl->code.bc.ptr = (hcl_oop_byte_t)hcl_allocmem(hcl, HCL_SIZEOF(*hcl->code.bc.ptr) * HCL_BC_BUFFER_INIT); /* TODO: set a proper intial size */
hcl->code.bc.ptr = (hcl_oob_t*)hcl_allocmem(hcl, HCL_SIZEOF(*hcl->code.bc.ptr) * HCL_BC_BUFFER_INIT); /* TODO: set a proper intial size */
if (HCL_UNLIKELY(!hcl->code.bc.ptr)) return -1;
HCL_ASSERT (hcl, hcl->code.bc.len == 0);
hcl->code.bc.capa = HCL_BC_BUFFER_INIT;
@ -546,6 +820,5 @@ int hcl_ignite (hcl_t* hcl)
}
hcl->p.e = hcl->_nil;
return 0;
}