Change to incremental GC

This commit is contained in:
2026-02-28 09:11:21 -08:00
parent a76e6a335d
commit 45f6d7a53d
8 changed files with 176 additions and 61 deletions
+143 -54
View File
@@ -10,7 +10,7 @@
bool lisp_doing_gc;
struct timespec total_gc_time;
size_t total_gc_count;
size_t lisp_gc_count;
struct GCObjectList {
LispVal *obj;
@@ -31,6 +31,23 @@ ObjectGCSet GC_BLACK = 0;
ObjectGCSet GC_GREY = 1;
ObjectGCSet GC_WHITE = 2;
enum IncrementalGCSetp {
GC_STEP_STATICS,
GC_STEP_STACK,
GC_STEP_HEAP,
GC_STEP_FREE,
};
struct IncrementalGCState {
enum IncrementalGCSetp step;
struct GCObjectList *next_static;
};
static struct IncrementalGCState incremental_state = {
.step = GC_STEP_STATICS,
.next_static = NULL,
};
static ALWAYS_INLINE struct GCObjectList **HEAD_FOR_SET(ObjectGCSet set) {
if (set == GC_BLACK) {
return &black_objects;
@@ -92,6 +109,9 @@ void lisp_gc_register_static_object(void *val) {
}
node->obj = obj;
static_objects = node;
// reset incremental GC to ensure we scan the new static
incremental_state.step = GC_STEP_STATICS;
incremental_state.next_static = static_objects;
}
static void unregister_object_node(LispObject *obj) {
@@ -124,7 +144,14 @@ void gc_move_to_set(void *val, ObjectGCSet new_set) {
}
}
void gc_mark_stack_for_rescan(void) {
if (incremental_state.step > GC_STEP_STACK) {
incremental_state.step = GC_STEP_STACK;
}
}
static void free_object(LispVal *val) {
assert(!OBJECT_HAS_LOCAL_REFERENCE_P(val));
switch (((LispObject *) val)->type) {
case TYPE_HASH_TABLE: {
LispHashTable *ht = val;
@@ -158,7 +185,7 @@ static void free_object(LispVal *val) {
lisp_release_object(val);
}
static inline void make_grey_if_while(LispVal *val) {
static inline void make_grey_if_white(LispVal *val) {
if (OBJECTP(val) && OBJECT_GC_SET_P(val, GC_WHITE)) {
gc_move_to_set(val, GC_GREY);
}
@@ -171,39 +198,39 @@ static void mark_object(LispVal *val) {
}
switch (((LispObject *) val)->type) {
case TYPE_CONS:
make_grey_if_while(((LispCons *) val)->car);
make_grey_if_while(((LispCons *) val)->cdr);
make_grey_if_white(((LispCons *) val)->car);
make_grey_if_white(((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);
make_grey_if_white(sym->name);
make_grey_if_white(sym->value);
make_grey_if_white(sym->function);
make_grey_if_white(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]);
make_grey_if_white(vec->data[i]);
}
break;
}
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));
make_grey_if_white(HASH_KEY(val, i));
make_grey_if_white(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);
make_grey_if_white(fobj->name);
make_grey_if_white(fobj->docstr);
make_grey_if_white(fobj->args.req);
make_grey_if_white(fobj->args.opt);
make_grey_if_white(fobj->args.kw);
make_grey_if_white(fobj->args.rest);
break;
}
case TYPE_STRING:
@@ -217,13 +244,32 @@ static void mark_object(LispVal *val) {
gc_move_to_set(val, GC_BLACK);
}
static void mark_statics(void) {
for (struct GCObjectList *node = static_objects; node; node = node->next) {
static inline size_t saturating_dec(size_t *restrict limit, size_t amount) {
if (amount >= *limit) {
*limit = 0;
} else {
*limit -= amount;
}
return *limit;
}
static void mark_statics(size_t *restrict limit) {
struct GCObjectList *node = incremental_state.next_static;
while (node && saturating_dec(limit, 1)) {
mark_object(node->obj);
node = node->next;
}
// we processed the whole list, move to the next step
if (!node) {
incremental_state.next_static = static_objects;
incremental_state.step = GC_STEP_STACK;
}
}
static void mark_stack_local_refs(struct LocalReferences *restrict refs) {
// This mark_stack_local_refs and mark_stack_frame mark the whole frame,
// ignoring limit. However, they update limit with how many objects the marked.
static void mark_stack_local_refs(struct LocalReferences *restrict refs,
size_t *restrict limit) {
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) {
@@ -234,46 +280,51 @@ static void mark_stack_local_refs(struct LocalReferences *restrict refs) {
for (size_t i = 0; i < last_block_len; ++i) {
mark_object(refs->blocks[full_blocks]->refs[i]);
}
saturating_dec(limit, refs->num_refs);
}
static void mark_stack_frame(struct StackFrame *frame) {
static void mark_stack_frame(struct StackFrame *frame, size_t *restrict limit) {
mark_object(frame->name);
mark_object(frame->args);
mark_object(frame->fobj);
mark_object(frame->lexenv);
mark_stack_local_refs(&frame->local_refs);
saturating_dec(limit, 4);
mark_stack_local_refs(&frame->local_refs, limit);
}
static void mark_and_compact_the_stack(void) {
mark_object(the_stack.nogc_retval);
static void mark_and_compact_the_stack(size_t *restrict limit) {
if ((*limit)--) {
mark_object(the_stack.nogc_retval);
}
size_t i;
for (i = 0; i < the_stack.depth; ++i) {
mark_stack_frame(&the_stack.frames[i]);
for (i = 0; i < the_stack.depth && *limit; ++i) {
if (!the_stack.frames[i].marked) {
mark_stack_frame(&the_stack.frames[i], limit);
the_stack.frames[i].marked = true;
}
}
for (; i < the_stack.first_clear_local_refs; ++i) {
compact_stack_frame(&the_stack.frames[i]);
if (i == the_stack.depth) {
for (; i < the_stack.first_clear_local_refs; ++i) {
compact_stack_frame(&the_stack.frames[i]);
}
the_stack.first_clear_local_refs = the_stack.depth;
// move to the next step
incremental_state.step = GC_STEP_HEAP;
}
the_stack.first_clear_local_refs = the_stack.depth;
}
static void mark_grey_objects(void) {
while (grey_objects) {
static void unmark_the_stack(void) {
for (size_t i = 0; i < the_stack.depth; ++i) {
the_stack.frames[i].marked = false;
}
}
static void mark_grey_objects(size_t *restrict limit) {
while (grey_objects && saturating_dec(limit, 1)) {
mark_object(grey_objects->obj);
}
}
static void gc_sweep_objects(void) {
while (white_objects) {
free_object(white_objects->obj);
}
}
static void maybe_free_some_object_list_nodes(void) {
while (free_objects_list_count > FREE_OBJECTS_LIST_LIMIT) {
struct GCObjectList *to_free = free_objects_list;
free_objects_list = free_objects_list->next;
lisp_free(to_free);
--free_objects_list_count;
if (!grey_objects) {
incremental_state.step = GC_STEP_FREE;
}
}
@@ -286,17 +337,55 @@ static void swap_white_black_sets(void) {
GC_BLACK = tmp_id;
}
void lisp_gc_now(struct timespec *restrict time_took) {
static void maybe_free_some_object_list_nodes(void) {
while (free_objects_list_count > FREE_OBJECTS_LIST_LIMIT) {
struct GCObjectList *to_free = free_objects_list;
free_objects_list = free_objects_list->next;
lisp_free(to_free);
--free_objects_list_count;
}
}
static void gc_sweep_objects(size_t *restrict limit) {
while (white_objects && saturating_dec(limit, 1)) {
free_object(white_objects->obj);
}
// reset the gc
if (!white_objects) {
swap_white_black_sets();
maybe_free_some_object_list_nodes();
unmark_the_stack();
incremental_state.step = GC_STEP_STATICS;
}
}
void lisp_gc_yield(struct timespec *restrict time_took, bool full) {
lisp_doing_gc = true;
struct timespec start_time;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
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();
size_t limit = full ? SIZE_MAX : LISP_GC_INCREMENTAL_COUNT;
while (limit) {
// there are more grey objects, mark them before we sweep
if (incremental_state.step == GC_STEP_FREE && grey_objects) {
incremental_state.step = GC_STEP_HEAP;
}
switch (incremental_state.step) {
case GC_STEP_STATICS:
mark_statics(&limit);
break;
case GC_STEP_STACK:
mark_and_compact_the_stack(&limit);
break;
case GC_STEP_HEAP:
mark_grey_objects(&limit);
break;
case GC_STEP_FREE:
gc_sweep_objects(&limit);
// force being done
limit = 0;
break;
}
}
struct timespec end_time;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
struct timespec backup_time_took;
@@ -305,8 +394,8 @@ void lisp_gc_now(struct timespec *restrict 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;
++lisp_gc_count;
}
void lisp_gc_teardown(void) {