Work on GC
This commit is contained in:
257
src/gc.c
257
src/gc.c
@ -8,127 +8,6 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void free_object_process_stack(ObjectProcessStack *restrict stack) {
|
||||
for (size_t i = 0; i < stack->num_blocks; ++i) {
|
||||
lisp_free(stack->blocks[i]);
|
||||
}
|
||||
lisp_free(stack->blocks);
|
||||
}
|
||||
|
||||
// Ensure STACK can hold at least NEW_ENTS more entries
|
||||
static void ensure_object_process_stack_size(ObjectProcessStack *restrict stack,
|
||||
size_t new_ents) {
|
||||
size_t total_spaces = stack->num_blocks * OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
size_t ents_left = total_spaces - stack->num_objs;
|
||||
if (ents_left < new_ents) {
|
||||
// grow the stack
|
||||
size_t need_to_alloc =
|
||||
(new_ents - ents_left) / OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
if ((new_ents - ents_left) % OBJECT_PROCESS_STACK_BLOCK_SIZE != 0) {
|
||||
++need_to_alloc;
|
||||
}
|
||||
stack->blocks = lisp_realloc(stack->blocks,
|
||||
sizeof(struct ObjectProcessStackBlock *)
|
||||
* (stack->num_blocks + need_to_alloc));
|
||||
for (size_t i = 0; i < need_to_alloc; ++i) {
|
||||
stack->blocks[i + stack->num_blocks] =
|
||||
lisp_malloc(sizeof(struct ObjectProcessStackBlock));
|
||||
}
|
||||
stack->num_blocks += need_to_alloc;
|
||||
}
|
||||
}
|
||||
|
||||
// this does NOT check if there is actually space to push the object, so make
|
||||
// sure to expand the stack with the above function before pushing
|
||||
static ALWAYS_INLINE void
|
||||
add_to_object_process_stack(ObjectProcessStack *restrict stack, void *obj) {
|
||||
if (OBJECTP(obj)) {
|
||||
size_t block_idx = stack->num_objs / OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
size_t small_idx = stack->num_objs % OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
stack->blocks[block_idx]->objs[small_idx] = obj;
|
||||
++stack->num_objs;
|
||||
}
|
||||
}
|
||||
|
||||
void object_process_stack_push_object(ObjectProcessStack *restrict stack,
|
||||
void *obj) {
|
||||
if (OBJECTP(obj)) {
|
||||
ensure_object_process_stack_size(stack, 1);
|
||||
add_to_object_process_stack(stack, obj);
|
||||
}
|
||||
}
|
||||
|
||||
void object_process_stack_push_held_objects(ObjectProcessStack *restrict stack,
|
||||
void *obj) {
|
||||
if (!OBJECTP(obj)) {
|
||||
return;
|
||||
}
|
||||
switch (((LispObject *) obj)->type) {
|
||||
case TYPE_CONS:
|
||||
ensure_object_process_stack_size(stack, 2);
|
||||
add_to_object_process_stack(stack, XCAR(obj));
|
||||
add_to_object_process_stack(stack, XCDR(obj));
|
||||
break;
|
||||
case TYPE_SYMBOL: {
|
||||
LispSymbol *sym = obj;
|
||||
ensure_object_process_stack_size(stack, 4);
|
||||
add_to_object_process_stack(stack, sym->name);
|
||||
add_to_object_process_stack(stack, sym->value);
|
||||
add_to_object_process_stack(stack, sym->function);
|
||||
add_to_object_process_stack(stack, sym->plist);
|
||||
break;
|
||||
}
|
||||
case TYPE_VECTOR: {
|
||||
LispVector *vec = obj;
|
||||
for (size_t i = 0; i < vec->length; ++i) {
|
||||
if (OBJECTP(vec->data[i])) {
|
||||
ensure_object_process_stack_size(stack, 1);
|
||||
}
|
||||
add_to_object_process_stack(stack, vec->data[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_FUNCTION: {
|
||||
LispFunction *fobj = obj;
|
||||
ensure_object_process_stack_size(stack, 6);
|
||||
add_to_object_process_stack(stack, fobj->name);
|
||||
add_to_object_process_stack(stack, fobj->docstr);
|
||||
add_to_object_process_stack(stack, fobj->args.req);
|
||||
add_to_object_process_stack(stack, fobj->args.opt);
|
||||
add_to_object_process_stack(stack, fobj->args.kw);
|
||||
add_to_object_process_stack(stack, fobj->args.rest);
|
||||
// TODO when other function types are added, record their held
|
||||
// references
|
||||
break;
|
||||
}
|
||||
case TYPE_HASH_TABLE: {
|
||||
HT_FOREACH_INDEX(obj, i) {
|
||||
if (OBJECTP(HASH_KEY(obj, i)) || OBJECTP(HASH_VALUE(obj, i))) {
|
||||
ensure_object_process_stack_size(stack, 2);
|
||||
}
|
||||
add_to_object_process_stack(stack, HASH_KEY(obj, i));
|
||||
add_to_object_process_stack(stack, HASH_VALUE(obj, i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_STRING:
|
||||
// holds no references break;
|
||||
break;
|
||||
case TYPE_FIXNUM:
|
||||
case TYPE_FLOAT:
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void *object_process_stack_pop(ObjectProcessStack *restrict stack) {
|
||||
assert(stack->num_objs > 0);
|
||||
--stack->num_objs;
|
||||
size_t block_idx = stack->num_objs / OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
size_t small_idx = stack->num_objs % OBJECT_PROCESS_STACK_BLOCK_SIZE;
|
||||
return stack->blocks[block_idx]->objs[small_idx];
|
||||
}
|
||||
|
||||
bool lisp_doing_gc;
|
||||
struct timespec total_gc_time;
|
||||
size_t total_gc_count;
|
||||
@ -279,60 +158,96 @@ static void free_object(LispVal *val) {
|
||||
lisp_release_object(val);
|
||||
}
|
||||
|
||||
static void mark_object_recurse(LispGCStats *restrict stats, LispVal *val) {
|
||||
static inline void make_grey_if_while(LispVal *val) {
|
||||
if (OBJECTP(val) && OBJECT_GC_SET_P(val, GC_WHITE)) {
|
||||
gc_move_to_set(val, GC_GREY);
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_object(LispVal *val) {
|
||||
if (!OBJECTP(val) || OBJECT_GC_SET_P(val, GC_BLACK)) {
|
||||
return;
|
||||
}
|
||||
ObjectProcessStack stack;
|
||||
init_object_process_stack(&stack);
|
||||
object_process_stack_push_object(&stack, val);
|
||||
while (!OBJECT_PROCESS_STACK_EMPTY_P(&stack)) {
|
||||
LispVal *cur = object_process_stack_pop(&stack);
|
||||
if (!OBJECT_GC_SET_P(cur, GC_BLACK)) {
|
||||
if (!OBJECT_STATIC_P(val)) {
|
||||
++stats->non_statics_kept;
|
||||
}
|
||||
gc_move_to_set(cur, GC_BLACK);
|
||||
object_process_stack_push_held_objects(&stack, cur);
|
||||
switch (((LispObject *) val)->type) {
|
||||
case TYPE_CONS:
|
||||
make_grey_if_while(((LispCons *) val)->car);
|
||||
make_grey_if_while(((LispCons *) val)->cdr);
|
||||
break;
|
||||
case TYPE_SYMBOL: {
|
||||
LispSymbol *sym = val;
|
||||
make_grey_if_while(sym->name);
|
||||
make_grey_if_while(sym->value);
|
||||
make_grey_if_while(sym->function);
|
||||
make_grey_if_while(sym->plist);
|
||||
break;
|
||||
}
|
||||
case TYPE_VECTOR: {
|
||||
LispVector *vec = val;
|
||||
for (size_t i = 0; i < vec->length; ++i) {
|
||||
make_grey_if_while(vec->data[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
free_object_process_stack(&stack);
|
||||
case TYPE_HASH_TABLE: {
|
||||
HT_FOREACH_INDEX(val, i) {
|
||||
make_grey_if_while(HASH_KEY(val, i));
|
||||
make_grey_if_while(HASH_VALUE(val, i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TYPE_FUNCTION: {
|
||||
LispFunction *fobj = val;
|
||||
make_grey_if_while(fobj->name);
|
||||
make_grey_if_while(fobj->docstr);
|
||||
make_grey_if_while(fobj->args.req);
|
||||
make_grey_if_while(fobj->args.opt);
|
||||
make_grey_if_while(fobj->args.kw);
|
||||
make_grey_if_while(fobj->args.rest);
|
||||
break;
|
||||
}
|
||||
case TYPE_STRING:
|
||||
// no held refs
|
||||
break;
|
||||
case TYPE_FIXNUM:
|
||||
case TYPE_FLOAT:
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
gc_move_to_set(val, GC_BLACK);
|
||||
}
|
||||
|
||||
static void mark_statics(LispGCStats *restrict stats) {
|
||||
static void mark_statics(void) {
|
||||
for (struct GCObjectList *node = static_objects; node; node = node->next) {
|
||||
mark_object_recurse(stats, node->obj);
|
||||
mark_object(node->obj);
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_stack_local_refs(LispGCStats *restrict stats,
|
||||
struct LocalReferences *restrict refs) {
|
||||
static void mark_stack_local_refs(struct LocalReferences *restrict refs) {
|
||||
size_t full_blocks = refs->num_refs / LOCAL_REFERENCES_BLOCK_LENGTH;
|
||||
size_t last_block_len = refs->num_refs % LOCAL_REFERENCES_BLOCK_LENGTH;
|
||||
for (size_t i = 0; i < full_blocks; ++i) {
|
||||
for (size_t j = 0; j < LOCAL_REFERENCES_BLOCK_LENGTH; ++j) {
|
||||
mark_object_recurse(stats, refs->blocks[i]->refs[j]);
|
||||
mark_object(refs->blocks[i]->refs[j]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < last_block_len; ++i) {
|
||||
mark_object_recurse(stats, refs->blocks[full_blocks]->refs[i]);
|
||||
mark_object(refs->blocks[full_blocks]->refs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_stack_frame(LispGCStats *restrict stats,
|
||||
struct StackFrame *frame) {
|
||||
mark_object_recurse(stats, frame->name);
|
||||
mark_object_recurse(stats, frame->args);
|
||||
mark_object_recurse(stats, frame->fobj);
|
||||
mark_object_recurse(stats, frame->lexenv);
|
||||
mark_stack_local_refs(stats, &frame->local_refs);
|
||||
static void mark_stack_frame(struct StackFrame *frame) {
|
||||
mark_object(frame->name);
|
||||
mark_object(frame->args);
|
||||
mark_object(frame->fobj);
|
||||
mark_object(frame->lexenv);
|
||||
mark_stack_local_refs(&frame->local_refs);
|
||||
}
|
||||
|
||||
static void mark_and_compact_the_stack(LispGCStats *restrict stats) {
|
||||
mark_object_recurse(stats, the_stack.nogc_retval);
|
||||
static void mark_and_compact_the_stack(void) {
|
||||
mark_object(the_stack.nogc_retval);
|
||||
size_t i;
|
||||
for (i = 0; i < the_stack.depth; ++i) {
|
||||
mark_stack_frame(stats, &the_stack.frames[i]);
|
||||
mark_stack_frame(&the_stack.frames[i]);
|
||||
}
|
||||
for (; i < the_stack.first_clear_local_refs; ++i) {
|
||||
compact_stack_frame(&the_stack.frames[i]);
|
||||
@ -340,9 +255,14 @@ static void mark_and_compact_the_stack(LispGCStats *restrict stats) {
|
||||
the_stack.first_clear_local_refs = the_stack.depth;
|
||||
}
|
||||
|
||||
static void gc_sweep_objects(LispGCStats *restrict stats) {
|
||||
static void mark_grey_objects(void) {
|
||||
while (grey_objects) {
|
||||
mark_object(grey_objects->obj);
|
||||
}
|
||||
}
|
||||
|
||||
static void gc_sweep_objects(void) {
|
||||
while (white_objects) {
|
||||
++stats->objects_cleaned;
|
||||
free_object(white_objects->obj);
|
||||
}
|
||||
}
|
||||
@ -365,34 +285,25 @@ static void swap_white_black_sets(void) {
|
||||
GC_BLACK = tmp_id;
|
||||
}
|
||||
|
||||
void lisp_gc_now(LispGCStats *restrict stats) {
|
||||
void lisp_gc_now(struct timespec *restrict time_took) {
|
||||
lisp_doing_gc = true;
|
||||
LispGCStats backup_stats;
|
||||
if (!stats) {
|
||||
stats = &backup_stats;
|
||||
}
|
||||
stats->objects_cleaned = 0;
|
||||
stats->non_statics_kept = 0;
|
||||
struct timespec start_time;
|
||||
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
|
||||
mark_statics(stats);
|
||||
mark_object_recurse(stats, obarray);
|
||||
mark_and_compact_the_stack(stats);
|
||||
gc_sweep_objects(stats);
|
||||
mark_statics();
|
||||
mark_object(obarray);
|
||||
mark_and_compact_the_stack();
|
||||
mark_grey_objects();
|
||||
gc_sweep_objects();
|
||||
maybe_free_some_object_list_nodes();
|
||||
swap_white_black_sets();
|
||||
struct timespec end_time;
|
||||
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
|
||||
sub_timespecs(&end_time, &start_time, &stats->ellapsed_time);
|
||||
add_timespecs(&stats->ellapsed_time, &total_gc_time, &total_gc_time);
|
||||
struct timespec backup_time_took;
|
||||
if (!time_took) {
|
||||
time_took = &backup_time_took;
|
||||
}
|
||||
sub_timespecs(&end_time, &start_time, time_took);
|
||||
add_timespecs(time_took, &total_gc_time, &total_gc_time);
|
||||
++total_gc_count;
|
||||
lisp_doing_gc = false;
|
||||
}
|
||||
|
||||
void debug_print_gc_stats(FILE *stream, const LispGCStats *stats) {
|
||||
fprintf(stream, "Non-Statics Kept: %zu\n", stats->non_statics_kept);
|
||||
fprintf(stream, "Objects Cleaned: %zu\n", stats->objects_cleaned);
|
||||
double time = stats->ellapsed_time.tv_sec * 1000
|
||||
+ (stats->ellapsed_time.tv_nsec / 1000000.0);
|
||||
fprintf(stream, "Time Ellapsed (ms): %f\n", time);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user