From 9fa0a09509964478a4087a294489a07e8ee083d7 Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Sat, 24 Oct 2009 17:55:56 -0500 Subject: [PATCH] Initial check-in. --- Makefile | 15 ++ gc.c | 424 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 439 insertions(+) create mode 100644 Makefile create mode 100644 gc.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..198b3a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CFLAGS = -std=c99 -O2 -DNDEBUG -fprofile-generate + +all: gc + +.PHONY: all clean + +ifneq (,$(wildcard *.gcda *.gcno)) +CFLAGS += -fprofile-use +dummy := $(shell rm -f gc) +endif + +clean: + -rm -f gc gc.gcda gc.gcno + +gc: gc.c diff --git a/gc.c b/gc.c new file mode 100644 index 0000000..688de74 --- /dev/null +++ b/gc.c @@ -0,0 +1,424 @@ +#include + +#include +#include +#include +#include +#include +#include + +#ifndef NDEBUG +# define debug(printf_args) ((void)printf printf_args) +#else +# define debug(printf_args) ((void)0) +#endif + +typedef uintptr_t value_t; + +/* NIL: 00000000 00000000 00000000 00000000 */ +/* Pair: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa00 (where aa... >= 1024) */ +/* Object: aaaaaaaa aaaaaaaa aaaaaaaa aaaaaa10 */ +/* Fixnum: snnnnnnn nnnnnnnn nnnnnnnn nnnnnnn1 */ + +#define NIL ((value_t)0) + +/* Special values (1 <= n < 1024) */ +/* These correspond to pairs within the first page of memory */ +#define SPECIAL_VALUE(n) ((value_t)(4*n)) +#define MAX_SPECIAL SPECIAL_VALUE(1023) + +#define BROKEN_HEART SPECIAL_VALUE(1) + +typedef struct pair +{ + value_t car; + value_t cdr; +} pair_t; + +typedef struct object +{ + value_t tag; + union { + value_t values[0]; + char bytes[0]; + } payload; +} object_t; + +typedef struct gc_root +{ + value_t value; + struct gc_root *prev; + struct gc_root *next; +} gc_root_t; + +static inline bool is_pair(value_t v) +{ + return ((v & 0x3) == 0) && (v > MAX_SPECIAL); +} + +static inline bool is_object(value_t v) +{ + return ((v & 0x1) == 0) && (v > MAX_SPECIAL); +} + +static inline bool is_fixnum(value_t v) +{ + return (v & 1) != 0; +} + +static inline value_t to_fixnum(intptr_t n) +{ + return (value_t)(n << 1) | 1; +} + +static inline intptr_t from_fixnum(value_t n) +{ + return ((intptr_t)n) >> 1; +} + +static inline value_t pair_value(pair_t *p) +{ + return (value_t)p; +} + +static inline value_t object_value(object_t *obj) +{ + return (value_t)obj | 2; +} + +object_t *get_object(value_t v); +pair_t *get_pair(value_t pair); +value_t get_cdr(value_t pair); +void replace_car(value_t pair, value_t car); +void replace_cdr(value_t pair, value_t cdr); +value_t cons(value_t car, value_t cdr); + +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); + +/***********************************************/ + +/* Pairs are a type of object, but the value representation is different */ +object_t *get_object(value_t v) +{ + if (is_object(v)) + return (object_t*)(v & ~(value_t)3); + else + abort(); +} + +pair_t *get_pair(value_t v) +{ + if (is_pair(v)) + return (pair_t*)v; + else + abort(); +} + +static int cons_counter = 0; + +value_t cons(value_t car, value_t cdr) +{ + gc_root_t car_root, cdr_root; + pair_t *p; + + register_gc_root(&car_root, car); + register_gc_root(&cdr_root, cdr); + + p = gc_alloc(sizeof(pair_t)); + p->car = car_root.value; + p->cdr = cdr_root.value; + + unregister_gc_root(&car_root); + unregister_gc_root(&cdr_root); + + ++cons_counter; + + return pair_value(p); +} + +/* Equal to the max. active set size. */ +#define GC_RANGE_SIZE (256*1024*1024) + +/* GC starts after this much space has been allocated. Increases by 5% each time. */ +#define GC_INIT_SIZE 1024 + +/* Alignment must ensure each object has enough room to hold a pair (BH . new_addr) */ +#define GC_ALIGNMENT ((size_t)(sizeof(pair_t))) + +static pair_t gc_ranges[2][GC_RANGE_SIZE / sizeof(pair_t)]; +static char *gc_free_ptr = (char*)&gc_ranges[0][0]; +static size_t gc_free_space = GC_INIT_SIZE; +static size_t gc_soft_limit = GC_INIT_SIZE; +static int current_gc_range = 0; +static gc_root_t gc_root_list = { NIL, &gc_root_list, &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 inline size_t gc_align(size_t nbytes) +{ + return ((nbytes + GC_ALIGNMENT - 1) & ~(GC_ALIGNMENT - 1)); +} + +static int gc_range_of(void *object) +{ + if (((uintptr_t)object >= (uintptr_t)gc_ranges[0]) && + ((uintptr_t)object < (uintptr_t)gc_ranges[1])) + return 0; + if (((uintptr_t)object >= (uintptr_t)gc_ranges[1]) && + ((uintptr_t)object < (uintptr_t)gc_ranges[2])) + return 1; + return -1; +} + +void register_gc_root(gc_root_t *root, value_t v) +{ + 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; +} + +static void out_of_memory(void) __attribute__ ((noreturn)); +static void out_of_memory(void) +{ + fprintf(stderr, "Out of memory!\n"); + abort(); +} + +void *gc_alloc(size_t nbytes) +{ + nbytes = gc_align(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; +} + +static void transfer_object(value_t *value) +{ + if (is_object(*value)) + { + object_t *obj = get_object(*value); + value_t new_value; + + assert(gc_range_of(obj) != current_gc_range); + + if (obj->tag == BROKEN_HEART) + { + /* Object has already been moved; just update the reference */ + new_value = obj->payload.values[0]; + } + else + { + switch (obj->tag) + { +#if 0 + case TYPE_TAG_VECTOR: + break; +#endif + default: /* pair or compatible */ + { + pair_t *p = (pair_t*)gc_alloc(sizeof(pair_t)); + const pair_t *q = (const pair_t*)obj; + *p = *q; + + //debug(("Moved pair from 0x%0.8X to 0x%0.8X.\n", q, p)); + + /* Keep the original tag bits (pair or object) */ +#if 0 + if (is_pair(*value)) + new_value = pair_value(p); + else + new_value = object_value((object_t*)p); +#else + new_value = pair_value(p) | (*value & 2); +#endif + } + break; + } + + obj->tag = BROKEN_HEART; + obj->payload.values[0] = new_value; + } + +#if 0 +#ifndef NDEBUG + if (is_pair(new_value)) + { + if (gc_range_of(get_pair(new_value)) != current_gc_range) + { + debug(("Invalid address after transfer: 0x%0.8X. Current GC: %d.\n", + get_pair(new_value), current_gc_range)); + abort(); + } + } +#endif +#endif + + *value = new_value; + } +} + +static inline size_t transfer_children(object_t *obj) +{ + switch (obj->tag) + { +#if 0 /* Don't support other kinds of GC objects yet */ + case TYPE_TAG_VECTOR: + { + return sizeof(*obj) + + sizeof(value_t) * from_fixnum(obj->payload.values[0]); + } +#endif + default: + { + /* Object is a pair or compatible type (e.g. box) */ + pair_t *p = (pair_t*)obj; + transfer_object(&p->car); + transfer_object(&p->cdr); + return sizeof(pair_t); + } + } +} + +void collect_garbage(size_t min_free) +{ + static bool collecting = false; + + gc_root_t *root; + char *object_ptr; + + //debug(("Collecting garbage...\n")); + + /* Recursive calls to collector should never occur */ + if (collecting) + { + debug(("Ran out of memory while collecting garbage!\n")); + abort(); + } + else + collecting = true; + + /* Swap ranges; new "current" range is initially empty, old one is full */ + current_gc_range = 1 - current_gc_range; + gc_free_ptr = (char*)&gc_ranges[current_gc_range][0]; + gc_free_space = gc_soft_limit; + object_ptr = gc_free_ptr; + + /* Transfer GC roots (if necessary) */ + root = gc_root_list.next; + + while (root != &gc_root_list) + { + 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)); + + { + size_t bytes_used = gc_soft_limit - gc_free_space; + size_t min_limit = bytes_used + min_free; + size_t new_limit = (3 * min_limit) / 2; + + if (new_limit > GC_RANGE_SIZE) + new_limit = GC_RANGE_SIZE; + else if (new_limit < GC_INIT_SIZE) + new_limit = GC_INIT_SIZE; + + gc_free_space += (new_limit - gc_soft_limit); + gc_soft_limit = new_limit; + } + + if (gc_free_space < min_free) + { + out_of_memory(); + } + + /* Done collecting. */ + collecting = false; +} + +int main(int argc, char **argv) +{ + struct timeval start_time; + gc_root_t list_root; + int count = 0; + + gettimeofday(&start_time, NULL); + srand((unsigned int)start_time.tv_usec); + + register_gc_root(&list_root, NIL); + + while (1) + { + int r = rand() & 0xffff; + + if (r == 0) + list_root.value = to_fixnum(rand()); + else if (r & 1) + list_root.value = cons(to_fixnum(rand()), list_root.value); + else if (r & 2) + list_root.value = cons(list_root.value, to_fixnum(rand())); + else if (r & 4) + { + list_root.value = cons(list_root.value, cons(to_fixnum(-1), NIL)); + get_pair(get_pair(list_root.value)->cdr)->cdr = list_root.value; + } + + if (cons_counter >= 1000000) + { + struct timeval end_time; + double seconds; + gettimeofday(&end_time, NULL); + + seconds = (end_time.tv_sec - start_time.tv_sec) + + 0.000001 * (end_time.tv_usec - start_time.tv_usec); + + printf("%d conses took %.6f seconds.\n", cons_counter, seconds); + + gettimeofday(&start_time, NULL); + cons_counter = 0; + + if (++count == 50) + break; + } + } + + unregister_gc_root(&list_root); + + return 0; +} + +/* vim:set sw=2 expandtab: */