├── .gitignore ├── 2space ├── 2space.c ├── allocate.h ├── copy.c └── copy.h ├── Makefile ├── README.txt ├── cgc ├── allocate.h └── cgc.c ├── common ├── default.h ├── mem.c ├── mem.h ├── node.h └── tree.c ├── gc ├── allocate.h ├── gc.c ├── mark_and_sweep.c └── mark_and_sweep.h ├── gen2space └── gen2space.c ├── gengc └── gengc.c ├── manual ├── allocate.h └── manual.c └── refcount ├── allocate.h └── refcount.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.dSYM 2 | *~ 3 | 4 | # Generated exeuctables: 5 | tree-2space 6 | tree-cgc 7 | tree-gc 8 | tree-gen2space 9 | tree-gengc 10 | tree-manual 11 | tree-refcount 12 | -------------------------------------------------------------------------------- /2space/2space.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../common/node.h" 5 | #include "../common/mem.h" 6 | #include "copy.h" 7 | 8 | static uintptr_t prev_size = 1024; 9 | 10 | void collect_garbage() 11 | { 12 | /* Set up new space */ 13 | size_t new_size = (space_end - space_start + sizeof(struct gc_node)); 14 | if (new_size < (prev_size * 2)) 15 | new_size = prev_size * 2; 16 | 17 | void *new_space = raw_malloc(new_size); 18 | struct gc_node *alloc_pos = new_space; 19 | 20 | /* Copy into new space */ 21 | copy_from_roots(&alloc_pos); 22 | 23 | /* Clean up old space */ 24 | if (space_start != 0) 25 | raw_free((void *)space_start, space_end - space_start); 26 | 27 | /* Continue allocating into new space */ 28 | space_start = (uintptr_t)new_space; 29 | space_end = space_start + new_size; 30 | space_next = (uintptr_t)alloc_pos; 31 | 32 | /* Remember size after collection to be used as a guide for the next 33 | space's size: */ 34 | prev_size = space_next - space_start; 35 | } 36 | 37 | /* Called by copy_from_roots(): */ 38 | int is_allocated(void *p) 39 | { 40 | /* The pointer `p` refers to an allocated object 41 | if it's in the allocation range: */ 42 | return ((uintptr_t)p >= space_start 43 | && (uintptr_t)p < space_end); 44 | } 45 | -------------------------------------------------------------------------------- /2space/allocate.h: -------------------------------------------------------------------------------- 1 | 2 | #define PUSH_STACK_POINTER(var) root_addrs[num_roots++] = &var 3 | #define POP_STACK_POINTER(var) --num_roots 4 | 5 | /* Since a GC moves allocated objects, we need variable addresses o be 6 | registered, not just the value of each variable */ 7 | extern struct node **root_addrs[]; 8 | extern int num_roots; 9 | 10 | struct node *allocate(); 11 | -------------------------------------------------------------------------------- /2space/copy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../common/mem.h" 7 | #include "../common/node.h" 8 | #include "allocate.h" 9 | #include "copy.h" 10 | 11 | /* Current allocation region: */ 12 | uintptr_t space_start, space_end; 13 | /* Current allocation pointer: */ 14 | uintptr_t space_next; 15 | 16 | struct node *allocate() 17 | { 18 | if (space_next + sizeof(struct gc_node) > space_end) 19 | collect_garbage(); 20 | 21 | struct gc_node *p = (struct gc_node *)space_next; 22 | space_next += sizeof(struct gc_node); 23 | 24 | p->forwarded = 0; 25 | 26 | return &p->n; 27 | } 28 | 29 | /* Registered _addresses_ of local variables that hold pointers, so 30 | that we can update each local variable: */ 31 | struct node **root_addrs[128]; 32 | int num_roots; 33 | 34 | /* First argument is object to mark/copy, second argument is the 35 | address of an allocation pointer into the new space */ 36 | static void copy_or_forward(struct node **addr, 37 | struct gc_node **alloc_pos_ptr) 38 | { 39 | if (is_allocated(*addr)) { 40 | struct gc_node *nh; 41 | nh = NODE_TO_GC(*addr); 42 | 43 | if (nh->forwarded) { 44 | /* Object already copied */ 45 | *addr = nh->forward_to; 46 | } else { 47 | /* Allocate a copy: */ 48 | struct gc_node *nh2; 49 | nh2 = *alloc_pos_ptr; 50 | (*alloc_pos_ptr)++; 51 | memcpy(nh2, nh, sizeof(struct gc_node)); 52 | 53 | /* Mark old location as copied and set forwarding pointer */ 54 | nh->forwarded = 1; 55 | nh->forward_to = &nh2->n; 56 | *addr = &nh2->n; 57 | } 58 | } 59 | } 60 | 61 | static void traverse_copied(struct gc_node *traverse_ptr, 62 | struct gc_node **alloc_pos_ptr) 63 | { 64 | /* Iterate through new space to copy : */ 65 | while (traverse_ptr < *alloc_pos_ptr) { 66 | copy_or_forward(&traverse_ptr->n.left, alloc_pos_ptr); 67 | copy_or_forward(&traverse_ptr->n.right, alloc_pos_ptr); 68 | traverse_ptr++; 69 | } 70 | } 71 | 72 | /* Argument is the address of a pointer into the new space: */ 73 | void copy_from_roots(struct gc_node **alloc_pos_ptr) 74 | { 75 | struct gc_node *init_ptr = *alloc_pos_ptr; 76 | 77 | for (int i = 0; i < num_roots; i++) { 78 | copy_or_forward(root_addrs[i], alloc_pos_ptr); 79 | } 80 | 81 | traverse_copied(init_ptr, alloc_pos_ptr); 82 | } 83 | -------------------------------------------------------------------------------- /2space/copy.h: -------------------------------------------------------------------------------- 1 | /* During a GC, we need to know whether an object has been forwarded 2 | or not: */ 3 | struct gc_node { 4 | int forwarded; 5 | union { 6 | struct node n; 7 | struct node *forward_to; 8 | }; 9 | }; 10 | 11 | #define NODE_TO_GC(p) ((struct gc_node *)((char *)(p) - offsetof(struct gc_node, n))) 12 | 13 | extern struct node **root_addrs[]; 14 | extern int num_roots; 15 | 16 | /* Allocation region and current allocation pointer: */ 17 | extern uintptr_t space_start, space_end, space_next; 18 | 19 | /* Called by garbage_collect(): */ 20 | void copy_from_roots(struct gc_node **alloc_pos_ptr); 21 | 22 | /* Called by copy_from_roots(): */ 23 | int is_allocated(void *p); 24 | 25 | /* Called by allocate(): */ 26 | void collect_garbage(); 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | BINS = tree-manual tree-refcount tree-gc tree-cgc tree-gengc tree-2space tree-gen2space 3 | 4 | all: $(BINS) 5 | 6 | clean: 7 | rm -rf $(BINS) *.dSYM *~ */*~ 8 | 9 | COMMON_C = common/tree.c common/mem.c 10 | COMMON_DEP = $(COMMON_C) common/node.h common/mem.h 11 | 12 | CFLAGS = -O2 -g -std=c99 13 | 14 | tree-manual: $(COMMON_DEP) manual/allocate.h manual/manual.c 15 | $(CC) $(CFLAGS) -o tree-manual -Imanual $(COMMON_C) manual/manual.c 16 | 17 | tree-refcount: $(COMMON_DEP) refcount/allocate.h refcount/refcount.c 18 | $(CC) $(CFLAGS) -o tree-refcount -Irefcount $(COMMON_C) refcount/refcount.c 19 | 20 | tree-gc: $(COMMON_DEP) gc/allocate.h gc/gc.c gc/mark_and_sweep.c gc/mark_and_sweep.h 21 | $(CC) $(CFLAGS) -o tree-gc -Igc $(COMMON_C) gc/gc.c gc/mark_and_sweep.c 22 | 23 | tree-cgc: $(COMMON_DEP) cgc/allocate.h gc/allocate.h cgc/cgc.c gc/mark_and_sweep.c gc/mark_and_sweep.h 24 | $(CC) $(CFLAGS) -o tree-cgc -Icgc $(COMMON_C) cgc/cgc.c gc/mark_and_sweep.c 25 | 26 | tree-gengc: $(COMMON_DEP) gc/allocate.h gengc/gengc.c gc/mark_and_sweep.c gc/mark_and_sweep.h 27 | $(CC) $(CFLAGS) -o tree-gengc -Igc $(COMMON_C) gengc/gengc.c gc/mark_and_sweep.c 28 | 29 | tree-2space: $(COMMON_DEP) 2space/allocate.h 2space/2space.c 2space/copy.h 2space/copy.c 30 | $(CC) $(CFLAGS) -o tree-2space -I2space $(COMMON_C) 2space/2space.c 2space/copy.c 31 | 32 | tree-gen2space: $(COMMON_DEP) 2space/allocate.h gen2space/gen2space.c 2space/copy.h 2space/copy.c 33 | $(CC) $(CFLAGS) -o tree-gen2space -I2space $(COMMON_C) gen2space/gen2space.c 2space/copy.c 34 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | This code demonstrates basic implementation techniques for garbage 2 | collection. In subdirectories: 3 | 4 | * manual = manual allocation and deallocation (i.e., not GC) 5 | 6 | * refcount = reference counting (also not GC) 7 | 8 | * gc = basic mark-and-sweep for a cooperative C program 9 | 10 | * cgc = conservative mark-and-sweep for an uncooperative C program 11 | 12 | * gengc = generational mark-and-sweep 13 | 14 | * 2space = 2-space copying GC; allocated objects move during a GC 15 | 16 | * gen2space = generational 2-space copying GC 17 | 18 | The "common/tree.c" program provides a synthetic workload of 19 | allocating a binary tree up to some depth, optionally creating and 20 | discarding extra trees along the way. The makfile combines that 21 | workload with each of the memory-management implementations. 22 | 23 | The "common/mem.c" library provides a wrapper for malloc() and free() 24 | so that peak memory can be reported at the end of the test workload. 25 | Different techniques can have substatially different peak memory 26 | sizes. 27 | 28 | 29 | For simplicity all allocations are for a single datatype, `struct 30 | node` as declared in "common/mem.h". The conservative collector does 31 | not support interior pointers (i.e., pointers that are not to the 32 | start of the object). As a further simplification (for generational 33 | GCs), allocated objects refer only to objects allocated that were 34 | earlier. 35 | 36 | 37 | Videos that walk though parts of the implementation: 38 | 39 | https://www.youtube.com/playlist?list=PLbdXd8eufjyVCrWF-obj8_BbyU5vEF8Jw 40 | -------------------------------------------------------------------------------- /cgc/allocate.h: -------------------------------------------------------------------------------- 1 | 2 | /* Conservative GC means that the program doesn't have to 3 | cooperate, so all macros are defaults */ 4 | 5 | struct node *allocate(); 6 | 7 | /* Let CGC take over main, so that it can track 8 | stack positions. */ 9 | #define main original_main 10 | 11 | -------------------------------------------------------------------------------- /cgc/cgc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../common/node.h" 6 | #include "../gc/allocate.h" 7 | #include "../gc/mark_and_sweep.h" 8 | 9 | extern int original_main(int argc, char **argv); 10 | 11 | /* The "noinline" attributes here ensure that stack frames 12 | are pushed as these functions are called: */ 13 | static void find_roots(jmp_buf env) __attribute__ ((noinline)); 14 | static int call_original_main(int argc, char **argv) __attribute__ ((noinline)); 15 | 16 | static void *stack_init; 17 | 18 | void collect_garbage() 19 | { 20 | /* Using setjmp() with a stack-allocated `env` ensures that any 21 | callee-saved register values are moved onto the stack. Pass 22 | `&env` to fint_roots() just to make sure it stays on the stack.*/ 23 | jmp_buf env; 24 | if (setjmp(env)) { 25 | /* Register a stack location as a root when it seems to have a 26 | pointer value: */ 27 | find_roots(env); 28 | /* Normal GC: */ 29 | mark_and_sweep_from_roots(); 30 | } 31 | } 32 | 33 | int main(int argc, char **argv) 34 | { 35 | /* Record the original stack position */ 36 | stack_init = &argc; 37 | 38 | return call_original_main(argc, argv); 39 | } 40 | 41 | int call_original_main(int argc, char **argv) 42 | { 43 | /* Now that a frame has been pushed, stack variables 44 | during original_main() are definitely shallower 45 | than `stack_int` */ 46 | return original_main(argc, argv); 47 | } 48 | 49 | static int looks_like_allocated(struct gc_node *p) 50 | { 51 | /* Walk through chunks, checking the pointer range */ 52 | for (struct gc_chunk *c = chunks; c; c = c->next) { 53 | if ((p >= c->mem) 54 | && (p <= (c->mem + NODES_PER_CHUNK))) { 55 | /* looks like a pointer; make sure it matches a `gc_node` 56 | start position */ 57 | uintptr_t delta = ((char *)p - (char *)c->mem); 58 | if ((delta % sizeof(struct gc_node)) == offsetof(struct gc_node, n)) 59 | return 1; 60 | } 61 | } 62 | 63 | return 0; 64 | } 65 | 66 | static void find_roots(jmp_buf env) 67 | { 68 | num_roots = 0; 69 | 70 | /* Since a frame was pushed to call find_roots, 71 | stack variables during original_main() are definitely 72 | deeper than `stack_now` */ 73 | void **stack_now = (void **)&stack_now; 74 | 75 | /* Scan the stack for pointers */ 76 | while ((void *)stack_now < stack_init) { 77 | if (looks_like_allocated(*stack_now)) 78 | root_addrs[num_roots++] = (struct node **)stack_now; 79 | stack_now++; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /common/default.h: -------------------------------------------------------------------------------- 1 | 2 | /* Does a client need to explicitly deallocate? */ 3 | #ifndef MANUAL_DEALLOCATE 4 | # define MANUAL_DEALLOCATE 0 5 | #endif 6 | 7 | /* A client uses PUSH_STACK_POINTER() and POP_STACK_POINTER() to 8 | report when local variables become live and go away, 9 | respectively. */ 10 | #ifndef PUSH_STACK_POINTER 11 | # define PUSH_STACK_POINTER(var) /* nothing */ 12 | # define POP_STACK_POINTER(var) /* nothing */ 13 | #endif 14 | 15 | /* Use SET_NODE() to assign to a variable or field, in case the 16 | allocator needs to know about asignments. Use UNSET_NODE() 17 | when a variable stops using a pointer because it goes out 18 | of scope, and use UNSET_RETURN_NODE() for a variable that is 19 | going out of scope but holds a pointer result (which must 20 | be immediately claimed by a caller). */ 21 | #ifndef SET_NODE 22 | # define SET_NODE(var, val) var = val 23 | # define UNSET_NODE(var) /* nothing */ 24 | # define UNSET_RETURN_NODE(var) /* nothing */ 25 | #endif 26 | -------------------------------------------------------------------------------- /common/mem.c: -------------------------------------------------------------------------------- 1 | /* Provide wrappers for malloc() and free() that track current and 2 | peak memory use. */ 3 | 4 | #include 5 | #include "mem.h" 6 | 7 | size_t mem_use, peak_mem_use; 8 | 9 | void *raw_malloc(size_t s) 10 | { 11 | mem_use += s; 12 | if (mem_use > peak_mem_use) 13 | peak_mem_use = mem_use; 14 | return malloc(s); 15 | } 16 | 17 | void raw_free(void *p, size_t s) 18 | { 19 | mem_use -= s; 20 | free(p); 21 | } 22 | -------------------------------------------------------------------------------- /common/mem.h: -------------------------------------------------------------------------------- 1 | extern size_t mem_use, peak_mem_use; 2 | 3 | void *raw_malloc(size_t s); 4 | void raw_free(void *p, size_t s); 5 | -------------------------------------------------------------------------------- /common/node.h: -------------------------------------------------------------------------------- 1 | 2 | /* For simplicitly, all allocations will 3 | allocate a `struct node`: */ 4 | 5 | struct node { 6 | struct node *left; 7 | struct node *right; 8 | }; 9 | -------------------------------------------------------------------------------- /common/tree.c: -------------------------------------------------------------------------------- 1 | /* A main program that to be compiled and linked with different 2 | allocators. */ 3 | 4 | #include 5 | #include 6 | #include "node.h" 7 | #include "mem.h" 8 | 9 | /* This header will be allocator-specific, as selected by 10 | `cc -I...`: */ 11 | #include "allocate.h" 12 | 13 | /* Defaults for macros not defined by "allocate.h" */ 14 | #include "default.h" 15 | 16 | static void destroy_tree(struct node *n); 17 | 18 | static int create_garbage; /* a flag for make_tree() */ 19 | 20 | /* Synthetic workload for the allocator: create a binary treee at the 21 | given depth. If `create_garbage` is true, create and discard trees 22 | along the way. */ 23 | static struct node *make_tree(int depth) 24 | { 25 | if (depth == 0) 26 | return NULL; 27 | else { 28 | if (create_garbage) { 29 | /* create and destroy a junk branch */ 30 | struct node *n = NULL; 31 | PUSH_STACK_POINTER(n); 32 | SET_NODE(n, make_tree(depth-1)); 33 | destroy_tree(n); 34 | UNSET_NODE(n); 35 | POP_STACK_POINTER(n); 36 | } 37 | 38 | /* Left subtree */ 39 | struct node *l = NULL; 40 | PUSH_STACK_POINTER(l); 41 | SET_NODE(l, make_tree(depth-1)); 42 | 43 | /* Right subtree */ 44 | struct node *r = NULL; 45 | PUSH_STACK_POINTER(r); 46 | SET_NODE(r, make_tree(depth-1)); 47 | 48 | /* New node */ 49 | struct node *t = NULL; 50 | PUSH_STACK_POINTER(t); 51 | SET_NODE(t, allocate()); 52 | SET_NODE(t->left, l); 53 | UNSET_NODE(l); 54 | SET_NODE(t->right, r); 55 | UNSET_NODE(r); 56 | 57 | UNSET_RETURN_NODE(t); 58 | 59 | POP_STACK_POINTER(t); 60 | POP_STACK_POINTER(r); 61 | POP_STACK_POINTER(l); 62 | 63 | return t; 64 | } 65 | } 66 | 67 | /* To free the memory of a tree, when explicit deallocation is 68 | needed: */ 69 | static void destroy_tree(struct node *n) 70 | { 71 | #if MANUAL_DEALLOCATE 72 | if (n != NULL) { 73 | destroy_tree(n->left); 74 | destroy_tree(n->right); 75 | deallocate(n); 76 | } 77 | #endif 78 | } 79 | 80 | /* Count tree nodes, which we use as a sanity check on the result 81 | trees: */ 82 | static int count(struct node *n) 83 | { 84 | if (n == NULL) 85 | return 0; 86 | else 87 | return 1 + count(n->left) + count(n->right); 88 | } 89 | 90 | /* This program makes some trees, counts their nodes, 91 | reports peak memory use as recorded in "mem.c", and 92 | exits: */ 93 | int main(int argc, char **argv) 94 | { 95 | /* create one tree without intermediate garbage: */ 96 | struct node *init_n = make_tree(10); 97 | PUSH_STACK_POINTER(init_n); 98 | 99 | /* create a larger tree with lots of intermediate garbage: */ 100 | create_garbage = 1; 101 | struct node *n = NULL; 102 | PUSH_STACK_POINTER(n); 103 | SET_NODE(n, make_tree(16)); 104 | 105 | /* check the trees: */ 106 | printf("%d\n", count(n) + count(init_n)); 107 | 108 | destroy_tree(n); 109 | destroy_tree(init_n); 110 | 111 | UNSET_NODE(n); 112 | UNSET_NODE(init_n); 113 | 114 | POP_STACK_POINTER(n); 115 | POP_STACK_POINTER(init_n); 116 | 117 | printf("Peak memory use: %ld\n", peak_mem_use); 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /gc/allocate.h: -------------------------------------------------------------------------------- 1 | 2 | #define PUSH_STACK_POINTER(var) root_addrs[num_roots++] = &var; 3 | #define POP_STACK_POINTER(var) --num_roots 4 | 5 | extern struct node **root_addrs[]; 6 | extern int num_roots; 7 | 8 | struct node *allocate(); 9 | -------------------------------------------------------------------------------- /gc/gc.c: -------------------------------------------------------------------------------- 1 | #include "../common/node.h" 2 | #include "allocate.h" 3 | #include "mark_and_sweep.h" 4 | 5 | void collect_garbage() 6 | { 7 | mark_and_sweep_from_roots(); 8 | } 9 | -------------------------------------------------------------------------------- /gc/mark_and_sweep.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../common/node.h" 5 | #include "../common/mem.h" 6 | #include "allocate.h" 7 | #include "mark_and_sweep.h" 8 | 9 | static void get_more_memory(); 10 | struct gc_node *free_list = NULL; 11 | 12 | struct node *allocate() 13 | { 14 | struct gc_node *nh; 15 | 16 | if (!free_list) { 17 | collect_garbage(); 18 | if (!free_list) 19 | get_more_memory(); 20 | } 21 | 22 | nh = free_list; 23 | free_list = free_list->next_free; 24 | 25 | return &nh->n; 26 | } 27 | 28 | /* Hold roots registered by the main program: */ 29 | int num_roots = 0; 30 | struct node **root_addrs[128]; 31 | 32 | /* Allocation statistics: */ 33 | int num_chunks, num_chunked_nodes, num_free_nodes; 34 | 35 | /* A linked list of alloction chunks: */ 36 | struct gc_chunk *chunks = NULL; 37 | 38 | /* When a GC doesn't free up any memory, allocate another chunk */ 39 | static void get_more_memory() 40 | { 41 | // printf("Allocate more\n"); 42 | 43 | struct gc_chunk *c = raw_malloc(sizeof(struct gc_chunk)); 44 | c->next = chunks; 45 | chunks = c; 46 | num_chunks++; 47 | 48 | c->mem = raw_malloc(NODES_PER_CHUNK * sizeof(struct gc_node)); 49 | 50 | /* Add everything in the new chunk to the free list: */ 51 | for (int i = 0; i < NODES_PER_CHUNK; i++) { 52 | struct gc_node *nh = c->mem + i; 53 | nh->next_free = free_list; 54 | free_list = nh; 55 | } 56 | 57 | num_chunked_nodes += NODES_PER_CHUNK; 58 | num_free_nodes += NODES_PER_CHUNK; 59 | } 60 | 61 | /* A helper for mark_and_sweep_from_roots() */ 62 | static void mark(struct node *n) 63 | { 64 | if (n != NULL) { 65 | struct gc_node *nh; 66 | nh = NODE_TO_GC(n); 67 | 68 | if (!nh->marked) { 69 | nh->marked = 1; 70 | 71 | /* Recur to keep referenced objects live: */ 72 | mark(nh->n.left); 73 | mark(nh->n.right); 74 | } 75 | } 76 | } 77 | 78 | /* Also a helper for mark_and_sweep_from_roots() */ 79 | static void reset_all_marks() 80 | { 81 | for (struct gc_chunk *c = chunks; c; c = c->next) { 82 | for (int i = 0; i < NODES_PER_CHUNK; i++) { 83 | c->mem[i].marked = 0; 84 | } 85 | } 86 | } 87 | 88 | static void sweep() 89 | { 90 | free_list = NULL; 91 | for (struct gc_chunk *c = chunks; c; c = c->next) { 92 | c->num_marked_nodes = 0; 93 | for (int i = 0; i < NODES_PER_CHUNK; i++) { 94 | if (!c->mem[i].marked) { 95 | c->mem[i].next_free = free_list; 96 | free_list = &c->mem[i]; 97 | num_free_nodes++; 98 | } else 99 | c->num_marked_nodes++; 100 | } 101 | } 102 | } 103 | 104 | /* A garbage_collect() function calls this one */ 105 | void mark_and_sweep_from_roots() 106 | { 107 | // printf("Collect garbage\n"); 108 | 109 | reset_all_marks(); 110 | 111 | /* mark (i.e., find all referenced) */ 112 | for (int i = 0; i < num_roots; i++) 113 | mark(*(root_addrs[i])); 114 | 115 | /* sweep (i.e., add unmarked to the free list) */ 116 | sweep(); 117 | } 118 | -------------------------------------------------------------------------------- /gc/mark_and_sweep.h: -------------------------------------------------------------------------------- 1 | /* Every allocated object needs a header for 2 | the GC to use: */ 3 | struct gc_node { 4 | union { 5 | int marked; 6 | struct gc_node *next_free; 7 | }; 8 | struct node n; 9 | }; 10 | 11 | #define NODE_TO_GC(p) ((struct gc_node *)((char *)(p) - offsetof(struct gc_node, n))) 12 | 13 | /* List of available nodes for allocation: */ 14 | extern struct gc_node *free_list; 15 | 16 | extern int num_free_nodes; 17 | 18 | /* An allocation chunk is a block of memory 19 | where we allocate nodes; we keep a linked 20 | list of chunks */ 21 | 22 | #define NODES_PER_CHUNK (32 * 1024) 23 | 24 | struct gc_chunk { 25 | struct gc_chunk *next; 26 | struct gc_node *mem; 27 | int num_marked_nodes; 28 | }; 29 | 30 | extern struct gc_chunk *chunks; 31 | extern int num_chunks, num_chunked_nodes; 32 | 33 | /* Called from allocate() */ 34 | void collect_garbage(); 35 | 36 | /* To be called by garbage_collect(): */ 37 | void mark_and_sweep_from_roots(); 38 | -------------------------------------------------------------------------------- /gen2space/gen2space.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../common/node.h" 5 | #include "../common/mem.h" 6 | #include "copy.h" 7 | #include "allocate.h" 8 | 9 | #define GEN0_SIZE (4 * 1024) 10 | 11 | /* Old-generation region and allocation pointer: */ 12 | static uintptr_t gen1_space_start, gen1_space_end, gen1_space_next; 13 | 14 | static uintptr_t gen1_prev_size = 1024; 15 | 16 | /* Tells is_allocated whether to consider references to the old 17 | generation: */ 18 | static int is_major; 19 | 20 | void collect_garbage() 21 | { 22 | size_t need_size = space_end - space_start; 23 | if (need_size < (gen1_space_end - gen1_space_next)) { 24 | /* Minor collection, since we have room in the gen1 space */ 25 | struct gc_node *alloc_pos; 26 | alloc_pos = (struct gc_node *)gen1_space_next; 27 | 28 | /* A minor collection (still) relies on having no references from 29 | old object to new objects; this is not a general-purpose GC. If 30 | such references were allowed, we'd have to add them as 31 | roots. */ 32 | 33 | is_major = 0; 34 | copy_from_roots(&alloc_pos); 35 | 36 | /* Record updated allocation pointer */ 37 | gen1_space_next = (uintptr_t)alloc_pos; 38 | } else { 39 | /* Major collection; make a space big enough for both the old and 40 | new generation: */ 41 | size_t new_size = need_size + (gen1_space_next - gen1_space_start); 42 | if (new_size < (gen1_prev_size * 2)) 43 | new_size = gen1_prev_size * 2; 44 | 45 | struct gc_node *alloc_pos; 46 | void *new_space = raw_malloc(new_size); 47 | alloc_pos = (struct gc_node *)new_space; 48 | 49 | is_major = 1; 50 | copy_from_roots(&alloc_pos); 51 | 52 | /* Clean up old space */ 53 | if (gen1_space_start != 0) 54 | raw_free((void *)gen1_space_start, gen1_space_end - gen1_space_start); 55 | 56 | /* Continue allocating into new old-generation space */ 57 | gen1_space_start = (uintptr_t)new_space; 58 | gen1_space_end = gen1_space_start + new_size; 59 | gen1_space_next = (uintptr_t)alloc_pos; 60 | 61 | gen1_prev_size = gen1_space_next - gen1_space_start; 62 | } 63 | 64 | /* Create a fresh new-generation space: */ 65 | void *new_space = raw_malloc(GEN0_SIZE); 66 | 67 | /* Clean up old new-generation space */ 68 | if (space_start != 0) 69 | raw_free((void *)space_start, GEN0_SIZE); 70 | 71 | /* Continue allocating into new space */ 72 | space_start = (uintptr_t)new_space; 73 | space_end = space_start + GEN0_SIZE; 74 | space_next = space_start; 75 | } 76 | 77 | /* used by collect_from_roots: */ 78 | int is_allocated(void *p) 79 | { 80 | return (((uintptr_t)p >= space_start 81 | && (uintptr_t)p < space_end) 82 | || (is_major 83 | /* During a major collection, also treat references 84 | into the old generation's space as allocated: */ 85 | && ((uintptr_t)p >= gen1_space_start 86 | && (uintptr_t)p < gen1_space_end))); 87 | } 88 | -------------------------------------------------------------------------------- /gengc/gengc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../common/node.h" 5 | #include "../common/mem.h" 6 | #include "../gc/allocate.h" 7 | #include "../gc/mark_and_sweep.h" 8 | 9 | /* After a minor collection, move surving chunks into this 10 | list, which will be ignored until a major collection: */ 11 | struct gc_chunk *gen1_chunks; 12 | 13 | int num_gen1_chunks, last_major_gen1_chunks; 14 | 15 | void collect_garbage() 16 | { 17 | if (!chunks) return; 18 | 19 | /* minor collection: */ 20 | mark_and_sweep_from_roots(); 21 | 22 | /* The minor collection above relies on having no references from 23 | old object to new objects; this is not a general-purpose GC. If 24 | such references were allowed, we'd have to add them as roots. */ 25 | 26 | /* move non-empty chunks to old generation: */ 27 | if (chunks) { 28 | struct gc_chunk *prev_c = NULL, *next; 29 | 30 | for (struct gc_chunk *c = chunks; c; c = next) { 31 | next = c->next; 32 | if (c->num_marked_nodes == 0) { 33 | /* This chunk is empty, so free it */ 34 | raw_free(c->mem, NODES_PER_CHUNK * sizeof(struct gc_node)); 35 | raw_free(c, sizeof(struct gc_chunk)); 36 | if (!prev_c) 37 | chunks = next; 38 | } else { 39 | /* Keep this chunk, now as an old-generation chunk */ 40 | num_gen1_chunks++; 41 | if (prev_c) 42 | prev_c->next = c; 43 | prev_c = c; 44 | } 45 | } 46 | 47 | if (prev_c) { 48 | prev_c->next = gen1_chunks; 49 | gen1_chunks = chunks; 50 | } 51 | 52 | chunks = NULL; 53 | free_list = NULL; 54 | } 55 | 56 | if (num_gen1_chunks >= (last_major_gen1_chunks * 2)) { 57 | /* perform major collection by moving all old-generation 58 | chunks back to the "new" generation, and GC again */ 59 | // printf("Major GC\n"); 60 | last_major_gen1_chunks = num_gen1_chunks; 61 | chunks = gen1_chunks; 62 | gen1_chunks = NULL; 63 | num_gen1_chunks = 0; 64 | collect_garbage(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /manual/allocate.h: -------------------------------------------------------------------------------- 1 | 2 | #define MANUAL_DEALLOCATE 1 3 | 4 | struct node *allocate(); 5 | void deallocate(struct node *p); 6 | -------------------------------------------------------------------------------- /manual/manual.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../common/node.h" 3 | #include "../common/mem.h" 4 | #include "allocate.h" 5 | 6 | struct node *allocate() 7 | { 8 | return raw_malloc(sizeof(struct node)); 9 | } 10 | 11 | void deallocate(struct node *p) 12 | { 13 | return raw_free(p, sizeof(struct node)); 14 | } 15 | -------------------------------------------------------------------------------- /refcount/allocate.h: -------------------------------------------------------------------------------- 1 | 2 | /* For a variable that is going out of scope: */ 3 | #define UNSET_NODE(var) refcount_dec(var) 4 | 5 | /* For a variable that holds the return value: */ 6 | #define UNSET_RETURN_NODE(var) refcount_dec_no_free(var) 7 | 8 | /* Before setting a variable field, drop the refcount on the old 9 | value. After setting a variable or field, increment the refcount on 10 | the new value. */ 11 | #define SET_NODE(var, val) (refcount_dec(var), refcount_inc(var = val)) 12 | 13 | struct node *allocate(); 14 | void refcount_inc(struct node *p); 15 | void refcount_dec(struct node *p); 16 | void refcount_dec_no_free(struct node *p); 17 | -------------------------------------------------------------------------------- /refcount/refcount.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../common/node.h" 4 | #include "../common/mem.h" 5 | #include "allocate.h" 6 | 7 | /* Allocate a counter field with each node: */ 8 | struct rc_node { 9 | int count; 10 | struct node n; 11 | }; 12 | 13 | #define NODE_TO_RC(p) ((struct rc_node *)((char *)(p) - offsetof(struct rc_node, n))) 14 | 15 | struct node *allocate() 16 | { 17 | /* A node is alloctaed with a refcount of 0, so the 18 | client program is responsible for immediately 19 | incrementing the reference count. */ 20 | struct rc_node *rn = raw_malloc(sizeof(struct rc_node)); 21 | rn->count = 0; 22 | rn->n.left = NULL; 23 | rn->n.right = NULL; 24 | return &rn->n; 25 | } 26 | 27 | void refcount_inc(struct node *p) 28 | { 29 | if (p) { 30 | /* Increment the reference count */ 31 | NODE_TO_RC(p)->count++; 32 | } 33 | } 34 | 35 | void refcount_dec(struct node *p) 36 | { 37 | if (p) { 38 | /* Decrement the reference count, and free if it does to zero */ 39 | struct rc_node *rn = NODE_TO_RC(p); 40 | if (--rn->count == 0) { 41 | refcount_dec(rn->n.left); 42 | refcount_dec(rn->n.right); 43 | raw_free(rn, sizeof(struct rc_node)); 44 | } 45 | } 46 | } 47 | 48 | void refcount_dec_no_free(struct node *p) 49 | { 50 | if (p) { 51 | /* Decrement the reference count without freeing; used 52 | to return an allocated object from a function, so 53 | that the caller can assume ownership. */ 54 | --NODE_TO_RC(p)->count; 55 | } 56 | } 57 | --------------------------------------------------------------------------------