Change to incremental GC
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user