diff --git a/gc.c b/gc.c index ae2f3ad..45194d0 100644 --- a/gc.c +++ b/gc.c @@ -11,11 +11,54 @@ gc_stats_t gc_stats; /* Helper macros to reduce duplication */ -#define VECTOR_BYTES(nelem) (sizeof(vector_t) + (sizeof(value_t) * (nelem))) -#define BYTESTR_BYTES(size) (sizeof(byte_string_t) + (size)) +#define VECTOR_BYTES(nelem) (sizeof(vector_t) + (sizeof(value_t) * (nelem))) +#define BYTESTR_BYTES(size) (sizeof(byte_string_t) + (size)) +#define STRUCT_BYTES(nslots) VECTOR_BYTES(nslots) -/* Alignment must ensure each object has enough room to hold a pair (BH . new_addr) */ -#define GC_ALIGNMENT ((size_t)(sizeof(pair_t))) +/* Alignment must ensure each object has enough room to hold a forwarding object */ +#define GC_ALIGNMENT ((size_t)(sizeof(object_t))) + +/****************************************************************************/ + +static char *gc_ranges[2]; +static size_t gc_min_size; +static size_t gc_max_size; +static size_t gc_soft_limit; +static bool gc_enabled; + +static int gc_current_range; +static char *gc_free_ptr; +static char *gc_range_end; + +static gc_root_t gc_root_list = { + .value = NIL, + .prev = &gc_root_list, + .next = &gc_root_list +}; + +void register_gc_root(gc_root_t *root, value_t v) +{ + root->value = v; + root->next = &gc_root_list; + gc_root_list.prev->next = root; + root->prev = gc_root_list.prev; + gc_root_list.prev = root; +} + +void unregister_gc_root(gc_root_t *root) +{ + assert(root && root->prev && root->next); /* Uninitialized */ + assert((root->prev != root) && (root->next != root)); /* Already removed */ + + /* Cut the given root out of the list */ + root->prev->next = root->next; + root->next->prev = root->prev; + + /* Remove dead references to root list; protects against double-removal */ + root->prev = root->next = root; +} + +/****************************************************************************/ object_t *get_object(value_t v) { @@ -124,6 +167,40 @@ byte_string_t *get_byte_string(value_t v) abort(); } +value_t make_struct(value_t type, size_t nslots) +{ + gc_root_t type_root; + struct_t *s; + + assert(nslots >= 1); + + /* Ensure that there is always a slot for the type */ + if (nslots < 1) + nslots = 1; + + register_gc_root(&type_root, type); + + s = (struct_t*)gc_alloc(STRUCT_BYTES(nslots)); + s->tag = TYPE_TAG_VECTOR; + s->nslots = nslots; + s->slots[0] = type_root.value; + + for (int i = 1; i < nslots; ++i) + s->slots[i] = NIL; + + unregister_gc_root(&type_root); + + return object_value(s); +} + +struct_t *get_struct(value_t v) +{ + if (is_struct(v)) + return _get_struct(v); + else + abort(); +} + intptr_t get_fixnum(value_t v) { if (is_fixnum(v)) @@ -134,25 +211,11 @@ intptr_t get_fixnum(value_t v) /****************************************************************************/ -static char *gc_ranges[2]; -static size_t gc_min_size; -static size_t gc_max_size; -static size_t gc_soft_limit; - -static int gc_current_range; -static size_t gc_free_space; -static char *gc_free_ptr; - -static gc_root_t gc_root_list = { - .value = NIL, - .prev = &gc_root_list, - .next = &gc_root_list -}; - static inline size_t gc_align(size_t nbytes) __attribute__ ((const)); static int gc_range_of(void *object) __attribute__ ((const)); static void transfer_object(value_t *value); static size_t transfer_children(object_t *object); +static void _collect_garbage(size_t min_free); static inline size_t gc_align(size_t nbytes) { @@ -170,6 +233,11 @@ static int gc_range_of(void *object) return -1; } +static inline size_t gc_free_space(void) +{ + return gc_range_end - gc_free_ptr; +} + void gc_init(size_t min_size, size_t max_size) { assert(min_size <= max_size); @@ -185,186 +253,180 @@ void gc_init(size_t min_size, size_t max_size) gc_min_size = min_size; gc_max_size = max_size; gc_soft_limit = gc_min_size; - gc_free_space = gc_soft_limit; + gc_range_end = gc_free_ptr + gc_soft_limit; gc_stats.collections = 0; gc_stats.total_ticks = 0; gc_stats.high_water = 0; + + gc_enabled = true; } -void register_gc_root(gc_root_t *root, value_t v) +/* Preconditions: nbytes pre-aligned a la gc_align(), and space exists. */ +static inline void *_gc_alloc(size_t nbytes) { - root->value = v; - root->prev = &gc_root_list; - root->next = gc_root_list.next; - root->next->prev = root; - gc_root_list.next = root; -} - -void unregister_gc_root(gc_root_t *root) -{ - assert(root && root->prev && root->next); /* Uninitialized */ - assert((root->prev != root) && (root->next != root)); /* Already removed */ - - /* Cut the given root out of the list */ - root->prev->next = root->next; - root->next->prev = root->prev; - - /* Remove dead references to root list; protects against double-removal */ - root->prev = root->next = root; + void *p = gc_free_ptr; + gc_free_ptr += nbytes; + return p; } void *gc_alloc(size_t nbytes) { nbytes = gc_align(nbytes); - if (nbytes > gc_free_space) - collect_garbage(nbytes); + if (nbytes > gc_free_space()) + _collect_garbage(nbytes); - void *p = gc_free_ptr; - gc_free_ptr += nbytes; - gc_free_space -= nbytes; - //debug(("Found %d bytes at %#.8p.\n", nbytes, p)); - return p; + return _gc_alloc(nbytes); } +/* Precondition: *value refers to an object (or pair). */ static void transfer_object(value_t *value) { - if (is_object(*value)) + object_t *obj; + size_t nbytes; + void *newobj; + value_t new_value; + + assert(gc_range_of(obj) != gc_current_range); + assert(is_object(*value)); + + obj = _get_object(*value); + + if (obj->tag == BROKEN_HEART) { - object_t *obj = get_object(*value); - value_t new_value; - - assert(gc_range_of(obj) != gc_current_range); - - if (obj->tag == BROKEN_HEART) - { - /* Object has already been moved; just update the reference */ - new_value = obj->payload.values[0]; - } - else - { - size_t nbytes; - - switch (obj->tag) - { - case TYPE_TAG_VECTOR: - nbytes = VECTOR_BYTES(((const vector_t*)obj)->size); - break; - case TYPE_TAG_BYTESTR: - nbytes = BYTESTR_BYTES(((const byte_string_t*)obj)->size); - break; - case TYPE_TAG_BOX: - default: /* pair */ - nbytes = sizeof(pair_t); - break; - } - - { - void *newobj = gc_alloc(nbytes); - memcpy(newobj, obj, nbytes); - - /* Keep the original tag bits (pair or object) */ - new_value = object_value(newobj) | (*value & 2); - } - - obj->tag = BROKEN_HEART; - obj->payload.values[0] = new_value; - } - - *value = new_value; + /* Object has already been moved; just update the reference */ + *value = obj->forward; + return; } + + switch (obj->tag) + { + case TYPE_TAG_VECTOR: + case TYPE_TAG_STRUCT: + nbytes = VECTOR_BYTES(((const vector_t*)obj)->size); + break; + case TYPE_TAG_BYTESTR: + nbytes = BYTESTR_BYTES(((const byte_string_t*)obj)->size); + break; + case TYPE_TAG_BOX: + default: /* pair */ + nbytes = sizeof(pair_t); + break; + } + + newobj = _gc_alloc(gc_align(nbytes)); + + memcpy(newobj, obj, nbytes); + + /* Keep the original tag bits (pair or object) */ + new_value = object_value(newobj) | (*value & 2); + + obj->tag = BROKEN_HEART; + obj->forward = new_value; + + *value = new_value; } -static inline size_t transfer_children(object_t *obj) +/* Also works on structs, which share the same layout */ +static size_t transfer_vector(vector_t *vec) +{ + for (size_t i = 0; i < vec->size; ++i) + { + if (is_object(vec->elements[i])) + transfer_object(&vec->elements[i]); + } + + return VECTOR_BYTES(vec->size); +} + +static size_t transfer_pair(pair_t *p) +{ + if (is_object(p->car)) + transfer_object(&p->car); + + if (is_object(p->cdr)) + transfer_object(&p->cdr); + + return sizeof(pair_t); +} + +static size_t transfer_children(object_t *obj) { switch (obj->tag) { case TYPE_TAG_VECTOR: - { - vector_t *vec = (vector_t*)obj; - - for (size_t i = 0; i < vec->size; ++i) - { - transfer_object(&vec->elements[i]); - } - - return VECTOR_BYTES(vec->size); - } + case TYPE_TAG_STRUCT: + return transfer_vector((vector_t*)obj); case TYPE_TAG_BYTESTR: - { - return BYTESTR_BYTES(((const byte_string_t*)obj)->size); - } + return BYTESTR_BYTES(((const byte_string_t*)obj)->size); case TYPE_TAG_BOX: default: /* pair */ - { - pair_t *p = (pair_t*)obj; - transfer_object(&p->car); - transfer_object(&p->cdr); - return sizeof(pair_t); - } + return transfer_pair((pair_t*)obj); } } -void collect_garbage(size_t min_free) +static void _collect_garbage(size_t min_free) { - static bool collecting = false; - gc_root_t *root; char *object_ptr; - //debug(("Collecting garbage...\n")); - - ++gc_stats.collections; - gc_stats.total_ticks -= clock(); - - /* Recursive calls to collector should never occur */ - if (collecting) + if (gc_enabled) { - debug(("Ran out of memory while collecting garbage!\n")); - abort(); - } - else - collecting = true; + gc_stats.total_ticks -= clock(); + ++gc_stats.collections; - /* Swap ranges; new "current" range is initially empty, old one is full */ - gc_current_range = 1 - gc_current_range; - gc_free_ptr = (char*)&gc_ranges[gc_current_range][0]; - gc_free_space = gc_soft_limit; - object_ptr = gc_free_ptr; + //debug(("Collecting garbage...\n")); - /* Transfer GC roots (if necessary) */ - root = gc_root_list.next; - - while (root != &gc_root_list) - { - transfer_object(&root->value); - root = root->next; + /* Swap ranges; new "current" range is initially empty, old one is full */ + gc_current_range = 1 - gc_current_range; + gc_free_ptr = gc_ranges[gc_current_range]; + gc_range_end = gc_free_ptr + gc_soft_limit; + object_ptr = gc_free_ptr; + + /* Transfer GC roots (if necessary) */ + root = gc_root_list.next; + + while (root != &gc_root_list) + { + if (is_object(root->value)) + transfer_object(&root->value); + root = root->next; + } + + /* Keep transferring until no more objects in the new range refer to the old one */ + while (object_ptr < gc_free_ptr) + { + object_ptr += gc_align(transfer_children((object_t*)object_ptr)); + } + + //debug(("Finished collection with %d bytes to spare (out of %d bytes).\n", gc_free_space(), gc_soft_limit)); + + gc_stats.total_ticks += clock(); } - /* Keep transferring until no more objects in the new range refer to the old one */ - while (object_ptr < gc_free_ptr) { - object_ptr += gc_align(transfer_children((object_t*)object_ptr)); - } - - //debug(("Finished collection with %d bytes to spare (out of %d bytes).\n", gc_free_space, gc_soft_limit)); - - { - size_t bytes_used = gc_soft_limit - gc_free_space; + size_t bytes_used = gc_free_ptr - gc_ranges[gc_current_range]; size_t min_limit = bytes_used + min_free; size_t new_limit = (5 * min_limit) / 3; if (new_limit > gc_max_size) new_limit = gc_max_size; +#if 0 else if (new_limit < gc_min_size) new_limit = gc_min_size; - gc_free_space = (gc_free_space + new_limit) - gc_soft_limit; gc_soft_limit = new_limit; +#else + if (new_limit > gc_soft_limit) + gc_soft_limit = new_limit; +#endif } - if (gc_free_space < min_free) + /* Update end of range to reflect new limit */ + gc_range_end = gc_ranges[gc_current_range] + gc_soft_limit; + + if (gc_free_space() < min_free) { out_of_memory(); } @@ -373,11 +435,21 @@ void collect_garbage(size_t min_free) { gc_stats.high_water = gc_soft_limit; } +} - /* Done collecting. */ - collecting = false; +void collect_garbage(size_t min_free) +{ + bool was_enabled = gc_enabled; + gc_enabled = true; + _collect_garbage(min_free); + gc_enabled = was_enabled; +} - gc_stats.total_ticks += clock(); +bool set_gc_enabled(bool enable) +{ + bool was_enabled = gc_enabled; + gc_enabled = enable; + return was_enabled; } /* vim:set sw=2 expandtab: */ diff --git a/gc.h b/gc.h index 9a7f9d3..157993e 100644 --- a/gc.h +++ b/gc.h @@ -30,42 +30,49 @@ typedef uintptr_t value_t; #define TYPE_TAG_BOX SPECIAL_VALUE(1) #define TYPE_TAG_VECTOR SPECIAL_VALUE(2) #define TYPE_TAG_BYTESTR SPECIAL_VALUE(3) +#define TYPE_TAG_STRUCT SPECIAL_VALUE(4) typedef struct object { value_t tag; - union { - value_t values[0]; - char bytes[0]; - } payload; + value_t forward; /* only if tag == BROKEN_HEART */ } object_t; -typedef struct box -{ - value_t tag; - value_t value; -} box_t; - +/* CAR is anything *other* than a valid type tag or BROKEN_HEART. */ typedef struct pair { value_t car; value_t cdr; } pair_t; +typedef struct box +{ + value_t tag; /* TYPE_TAG_BOX */ + value_t value; +} box_t; + typedef struct vector { - value_t tag; + value_t tag; /* TYPE_TAG_VECTOR */ size_t size; value_t elements[0]; } vector_t; typedef struct byte_string { - value_t tag; + value_t tag; /* TYPE_TAG_BYTESTR */ size_t size; uint8_t bytes[0]; } byte_string_t; +/* Equivalent to vector_t */ +typedef struct structure +{ + value_t tag; /* TYPE_TAG_STRUCT */ + size_t nslots; /* Includes slots[0], the struct subtype */ + value_t slots[0]; +} struct_t; + typedef struct gc_root { value_t value; @@ -96,6 +103,10 @@ vector_t *get_vector(value_t v); value_t make_byte_string(size_t size, int default_value); byte_string_t *get_byte_string(value_t v); +/* Precondition: slots >= 1; this includes the struct type tag, slots[0]. */ +value_t make_struct(value_t type, size_t slots); +struct_t *get_struct(value_t v); + intptr_t get_fixnum(value_t v); /****************************************************************************/ @@ -176,6 +187,21 @@ static inline size_t byte_string_size(value_t v) return get_byte_string(v)->size; } +static inline bool is_struct(value_t v) +{ + return is_object(v) && (_get_object(v)->tag == TYPE_TAG_STRUCT); +} + +static inline struct_t *_get_struct(value_t v) +{ + return (struct_t*)_get_object(v); +} + +static inline size_t struct_type(value_t v) +{ + return get_struct(v)->slots[0]; +} + static inline bool is_fixnum(value_t v) { return (v & 1) != 0; @@ -196,6 +222,7 @@ void register_gc_root(gc_root_t *root, value_t v); void unregister_gc_root(gc_root_t *root); void *gc_alloc(size_t nbytes); void collect_garbage(size_t min_free); +bool set_gc_enabled(bool enable); /* To be provided by the main application */ void out_of_memory(void) __attribute__ ((noreturn)); diff --git a/gc_test.c b/gc_test.c index d752a82..eed5bf1 100644 --- a/gc_test.c +++ b/gc_test.c @@ -28,7 +28,7 @@ int main(int argc, char **argv) while (1) { - int r = rand() & 0xffff; + int r = rand() & 0x3fff; if (r == 0) list_root.value = make_fixnum(rand()); @@ -48,29 +48,29 @@ int main(int argc, char **argv) case 3: list_root.value = cons(list_root.value, cons(make_fixnum(-1), NIL)); get_pair(get_pair(list_root.value)->cdr)->cdr = list_root.value; - ++count; break; case 4: case 5: case 6: case 7: { - value_t vec = make_vector(4, NIL); - _get_vector(vec)->elements[r & 3] = list_root.value; - list_root.value = vec; + value_t s = make_struct(NIL, 5); + _get_struct(s)->slots[1+(r & 3)] = list_root.value; + list_root.value = s; } break; } } - ++count; - if (count >= 10000000) + if (++count >= 10000000) { - fprintf(stderr, "%0.3f usec / GC; max. limit was %u bytes; %0.3f seconds spent in %d GCs.\n", - (1000000 * (gc_stats.total_ticks / (double)CLOCKS_PER_SEC)) / gc_stats.collections, - gc_stats.high_water, - gc_stats.total_ticks / (double)CLOCKS_PER_SEC, - gc_stats.collections); + const double total_time = gc_stats.total_ticks / (double)CLOCKS_PER_SEC; + + fprintf(stderr, "%0.3f sec / %d GCs => %0.3f usec/GC; peak was %u bytes.\n", + total_time, + gc_stats.collections, + (1000000 * total_time) / gc_stats.collections, + gc_stats.high_water); gc_stats.collections = 0; gc_stats.total_ticks = 0;