├── .gitignore ├── COPYRIGHT ├── Makefile ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | markandsweep 3 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | This uses the MIT License: 2 | 3 | Copyright (c) 2013 Robert Nystrom 4 | 5 | Permission is hereby granted, free of charge, to 6 | any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), 8 | to deal in the Software without restriction, 9 | including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, 11 | sublicense, and/or sell copies of the Software, 12 | and to permit persons to whom the Software is 13 | furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission 17 | notice shall be included in all copies or 18 | substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT 21 | WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 23 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 24 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 25 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR 28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 | THE SOFTWARE. 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN=markandsweep 2 | 3 | .PHONY : clean 4 | 5 | $(BIN) : main.c 6 | $(CC) -ggdb -std=gnu99 main.c -o $(BIN) 7 | 8 | clean : 9 | rm -f $(BIN) *~ 10 | 11 | run : $(BIN) 12 | valgrind --leak-check=yes ./$(BIN) 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the sample code for [this article](http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/). 2 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define STACK_MAX 256 5 | #define INIT_OBJ_NUM_MAX 8 6 | 7 | typedef enum { 8 | OBJ_INT, 9 | OBJ_PAIR 10 | } ObjectType; 11 | 12 | typedef struct sObject { 13 | ObjectType type; 14 | unsigned char marked; 15 | 16 | /* The next object in the linked list of heap allocated objects. */ 17 | struct sObject* next; 18 | 19 | union { 20 | /* OBJ_INT */ 21 | int value; 22 | 23 | /* OBJ_PAIR */ 24 | struct { 25 | struct sObject* head; 26 | struct sObject* tail; 27 | }; 28 | }; 29 | } Object; 30 | 31 | typedef struct { 32 | Object* stack[STACK_MAX]; 33 | int stackSize; 34 | 35 | /* The first object in the linked list of all objects on the heap. */ 36 | Object* firstObject; 37 | 38 | /* The total number of currently allocated objects. */ 39 | int numObjects; 40 | 41 | /* The number of objects required to trigger a GC. */ 42 | int maxObjects; 43 | } VM; 44 | 45 | void assert(int condition, const char* message) { 46 | if (!condition) { 47 | printf("%s\n", message); 48 | exit(1); 49 | } 50 | } 51 | 52 | VM* newVM() { 53 | VM* vm = malloc(sizeof(VM)); 54 | vm->stackSize = 0; 55 | vm->firstObject = NULL; 56 | vm->numObjects = 0; 57 | vm->maxObjects = INIT_OBJ_NUM_MAX; 58 | return vm; 59 | } 60 | 61 | 62 | void push(VM* vm, Object* value) { 63 | assert(vm->stackSize < STACK_MAX, "Stack overflow!"); 64 | vm->stack[vm->stackSize++] = value; 65 | } 66 | 67 | 68 | Object* pop(VM* vm) { 69 | assert(vm->stackSize > 0, "Stack underflow!"); 70 | return vm->stack[--vm->stackSize]; 71 | } 72 | 73 | void mark(Object* object) { 74 | /* If already marked, we're done. Check this first to avoid recursing 75 | on cycles in the object graph. */ 76 | if (object->marked) return; 77 | 78 | object->marked = 1; 79 | 80 | if (object->type == OBJ_PAIR) { 81 | mark(object->head); 82 | mark(object->tail); 83 | } 84 | } 85 | 86 | void markAll(VM* vm) 87 | { 88 | for (int i = 0; i < vm->stackSize; i++) { 89 | mark(vm->stack[i]); 90 | } 91 | } 92 | 93 | void sweep(VM* vm) 94 | { 95 | Object** object = &vm->firstObject; 96 | while (*object) { 97 | if (!(*object)->marked) { 98 | /* This object wasn't reached, so remove it from the list and free it. */ 99 | Object* unreached = *object; 100 | 101 | *object = unreached->next; 102 | free(unreached); 103 | 104 | vm->numObjects--; 105 | } else { 106 | /* This object was reached, so unmark it (for the next GC) and move on to 107 | the next. */ 108 | (*object)->marked = 0; 109 | object = &(*object)->next; 110 | } 111 | } 112 | } 113 | 114 | void gc(VM* vm) { 115 | int numObjects = vm->numObjects; 116 | 117 | markAll(vm); 118 | sweep(vm); 119 | 120 | vm->maxObjects = vm->numObjects == 0 ? INIT_OBJ_NUM_MAX : vm->numObjects * 2; 121 | 122 | printf("Collected %d objects, %d remaining.\n", numObjects - vm->numObjects, 123 | vm->numObjects); 124 | } 125 | 126 | Object* newObject(VM* vm, ObjectType type) { 127 | if (vm->numObjects == vm->maxObjects) gc(vm); 128 | 129 | Object* object = malloc(sizeof(Object)); 130 | object->type = type; 131 | object->next = vm->firstObject; 132 | vm->firstObject = object; 133 | object->marked = 0; 134 | 135 | vm->numObjects++; 136 | 137 | return object; 138 | } 139 | 140 | void pushInt(VM* vm, int intValue) { 141 | Object* object = newObject(vm, OBJ_INT); 142 | object->value = intValue; 143 | 144 | push(vm, object); 145 | } 146 | 147 | Object* pushPair(VM* vm) { 148 | Object* object = newObject(vm, OBJ_PAIR); 149 | object->tail = pop(vm); 150 | object->head = pop(vm); 151 | 152 | push(vm, object); 153 | return object; 154 | } 155 | 156 | void objectPrint(Object* object) { 157 | switch (object->type) { 158 | case OBJ_INT: 159 | printf("%d", object->value); 160 | break; 161 | 162 | case OBJ_PAIR: 163 | printf("("); 164 | objectPrint(object->head); 165 | printf(", "); 166 | objectPrint(object->tail); 167 | printf(")"); 168 | break; 169 | } 170 | } 171 | 172 | void freeVM(VM *vm) { 173 | vm->stackSize = 0; 174 | gc(vm); 175 | free(vm); 176 | } 177 | 178 | void test1() { 179 | printf("Test 1: Objects on stack are preserved.\n"); 180 | VM* vm = newVM(); 181 | pushInt(vm, 1); 182 | pushInt(vm, 2); 183 | 184 | gc(vm); 185 | assert(vm->numObjects == 2, "Should have preserved objects."); 186 | freeVM(vm); 187 | } 188 | 189 | void test2() { 190 | printf("Test 2: Unreached objects are collected.\n"); 191 | VM* vm = newVM(); 192 | pushInt(vm, 1); 193 | pushInt(vm, 2); 194 | pop(vm); 195 | pop(vm); 196 | 197 | gc(vm); 198 | assert(vm->numObjects == 0, "Should have collected objects."); 199 | freeVM(vm); 200 | } 201 | 202 | void test3() { 203 | printf("Test 3: Reach nested objects.\n"); 204 | VM* vm = newVM(); 205 | pushInt(vm, 1); 206 | pushInt(vm, 2); 207 | pushPair(vm); 208 | pushInt(vm, 3); 209 | pushInt(vm, 4); 210 | pushPair(vm); 211 | pushPair(vm); 212 | 213 | gc(vm); 214 | assert(vm->numObjects == 7, "Should have reached objects."); 215 | freeVM(vm); 216 | } 217 | 218 | void test4() { 219 | printf("Test 4: Handle cycles.\n"); 220 | VM* vm = newVM(); 221 | pushInt(vm, 1); 222 | pushInt(vm, 2); 223 | Object* a = pushPair(vm); 224 | pushInt(vm, 3); 225 | pushInt(vm, 4); 226 | Object* b = pushPair(vm); 227 | 228 | /* Set up a cycle, and also make 2 and 4 unreachable and collectible. */ 229 | a->tail = b; 230 | b->tail = a; 231 | 232 | gc(vm); 233 | assert(vm->numObjects == 4, "Should have collected objects."); 234 | freeVM(vm); 235 | } 236 | 237 | void perfTest() { 238 | printf("Performance Test.\n"); 239 | VM* vm = newVM(); 240 | 241 | for (int i = 0; i < 1000; i++) { 242 | for (int j = 0; j < 20; j++) { 243 | pushInt(vm, i); 244 | } 245 | 246 | for (int k = 0; k < 20; k++) { 247 | pop(vm); 248 | } 249 | } 250 | freeVM(vm); 251 | } 252 | 253 | int main(int argc, const char * argv[]) { 254 | test1(); 255 | test2(); 256 | test3(); 257 | test4(); 258 | perfTest(); 259 | 260 | return 0; 261 | } 262 | --------------------------------------------------------------------------------