Skip to content

Commit ccc76ba

Browse files
committed
feat: implement garbage collection (mark/sweep)
1 parent 6f74a21 commit ccc76ba

5 files changed

Lines changed: 148 additions & 19 deletions

File tree

src/memory.c

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@
1313
#include <stdio.h>
1414
#endif
1515

16+
#define GC_HEAP_GROW_FACTOR 2
17+
1618
void* reallocate(void* pointer, size_t old_size, size_t new_size)
1719
{
20+
vm.bytes_allocated += new_size - old_size;
21+
1822
if (new_size > old_size)
1923
{
20-
#ifdef DEBUG_PRINT_CODE
24+
#ifdef DEBUG_STRESS_GC
2125
collect_garbage();
2226
#endif
27+
if (vm.bytes_allocated > vm.next_GC)
28+
{
29+
collect_garbage();
30+
}
2331
}
2432
if (new_size == 0)
2533
{
@@ -37,6 +45,8 @@ void mark_object(Obj* object)
3745
{
3846
if (object == NULL)
3947
return;
48+
if (object->is_marked)
49+
return;
4050

4151
#ifdef DEBUG_LOG_GC
4252
printf("%p mark ", (void*)object);
@@ -45,6 +55,16 @@ void mark_object(Obj* object)
4555
#endif
4656

4757
object->is_marked = true;
58+
59+
if (vm.gray_capacity < vm.gray_count + 1)
60+
{
61+
vm.gray_capacity = GROW_CAPACITY(vm.gray_capacity);
62+
vm.gray_stack = realloc(vm.gray_stack, sizeof(Obj*) * vm.gray_capacity);
63+
64+
if (vm.gray_stack == NULL)
65+
exit(1);
66+
}
67+
vm.gray_stack[vm.gray_count++] = object;
4868
}
4969

5070
void mark_value(Value value)
@@ -55,6 +75,45 @@ void mark_value(Value value)
5575
mark_object(AS_OBJ(value));
5676
}
5777

78+
static void mark_array(ValueArray* array)
79+
{
80+
for (int i = 0; i < array->count; i++)
81+
{
82+
mark_value(array->values[i]);
83+
}
84+
}
85+
86+
static void blacken_object(Obj* object)
87+
{
88+
#ifdef DEBUG_LOG_GC
89+
printf("%p blacken", (void*)object);
90+
print_value(OBJ_VAL(object));
91+
printf("\n");
92+
#endif // DEbUG_LOG_GC
93+
switch (object->type)
94+
{
95+
case OBJ_CLOSURE:
96+
ObjClosure* closure = (ObjClosure*)object;
97+
mark_object((Obj*)closure->function);
98+
for (int i = 0; i < closure->upvalue_count; i++)
99+
{
100+
mark_object((Obj*)closure->upvalues[i]);
101+
}
102+
break;
103+
case OBJ_FUNCTION:
104+
ObjFunction* function = (ObjFunction*)object;
105+
mark_object((Obj*)function->name);
106+
mark_array(&function->chunk.constants);
107+
break;
108+
case OBJ_UPVALUE:
109+
mark_value(((ObjUpvalue*)object)->closed);
110+
break;
111+
case OBJ_NATIVE:
112+
case OBJ_STRING:
113+
break;
114+
}
115+
}
116+
58117
static void free_object(Obj* object)
59118
{
60119

@@ -107,6 +166,8 @@ void free_objects()
107166
free_object(object);
108167
object = next;
109168
}
169+
170+
free(vm.gray_stack);
110171
}
111172

112173
static void mark_roots()
@@ -131,15 +192,58 @@ static void mark_roots()
131192
mark_compiler_roots();
132193
}
133194

195+
static void trace_references()
196+
{
197+
while (vm.gray_count > 0)
198+
{
199+
Obj* object = vm.gray_stack[--vm.gray_count];
200+
blacken_object(object);
201+
}
202+
}
203+
204+
static void sweep()
205+
{
206+
Obj* previous = NULL;
207+
Obj* object = vm.objects;
208+
209+
while (object != NULL)
210+
{
211+
if (object->is_marked)
212+
{
213+
object->is_marked = false;
214+
previous = object;
215+
object = object->next;
216+
}
217+
else
218+
{
219+
Obj* unreached = object;
220+
object = object->next;
221+
if (previous != NULL)
222+
previous->next = object;
223+
else
224+
vm.objects = object;
225+
free_object(unreached);
226+
}
227+
}
228+
}
229+
134230
void collect_garbage()
135231
{
136232
#ifdef DEBUG_LOG_GC
137233
printf("-- GC Begins \n");
234+
size_t before = vm.bytes_allocated;
138235
#endif
139236

140237
mark_roots();
238+
trace_references();
239+
table_remove_white(&vm.strings);
240+
sweep();
241+
242+
vm.next_GC = vm.bytes_allocated * GC_HEAP_GROW_FACTOR;
141243

142244
#ifdef DEBUG_LOG_GC
143245
printf("-- GC Ends \n");
246+
printf(" collected %ld bytes (from %ld to %ld) next at %ld\n",
247+
before - vm.bytes_allocated, before, vm.bytes_allocated, vm.next_GC);
144248
#endif
145249
}

src/table.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ ObjString* table_find_string(Table* table, const char* chars, int length,
162162
}
163163
}
164164

165+
void table_remove_white(Table* table)
166+
{
167+
for (int i = 0; i < table->count; i++)
168+
{
169+
Entry* entry = &table->entries[i];
170+
if (entry->key != NULL && !entry->key->obj.is_marked)
171+
{
172+
table_delete(table, entry->key);
173+
}
174+
}
175+
}
176+
165177
void mark_table(Table* table)
166178
{
167179
for (int i = 0; i < table->capacity; i++)

src/table.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ bool table_set(Table* table, ObjString* key, Value value);
2424
bool table_delete(Table* table, ObjString* key);
2525
void table_add_all(Table* from, Table* to);
2626
ObjString* table_find_string(Table* table, const char* chars, int length, u32 hash);
27+
void table_remove_white(Table* table);
2728
void mark_table(Table* table);
2829

2930
#endif

src/vm.c

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ void init_VM()
6363
init_table(&vm.globals);
6464
init_table(&vm.strings);
6565

66+
vm.bytes_allocated = 0;
67+
vm.next_GC = 1024 * 1024;
68+
69+
vm.gray_capacity = 0;
70+
vm.gray_count = 0;
71+
vm.gray_stack = NULL;
72+
6673
define_native("clock", clock_native);
6774
}
6875

@@ -199,20 +206,20 @@ static InterpretResult run()
199206

200207
#define READ_BYTE() (*frame->ip++)
201208
#define READ_SHORT() (frame->ip += 2, (u16)(frame->ip[-2] << 8) | frame->ip[-1])
202-
#define READ_CONSTANT() \
209+
#define READ_CONSTANT() \
203210
(frame->closure->function->chunk.constants.values[READ_BYTE()])
204211
#define READ_STRING() AS_STRING(READ_CONSTANT())
205-
#define BINARY_OP(value_type, op) \
206-
do \
207-
{ \
208-
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) \
209-
{ \
210-
runtime_error("Operands must be numbers "); \
211-
return INTERPRET_RUNTIME_ERROR; \
212-
} \
213-
double b = AS_NUMBER(pop()); \
214-
double a = AS_NUMBER(pop()); \
215-
push(value_type(a op b)); \
212+
#define BINARY_OP(value_type, op) \
213+
do \
214+
{ \
215+
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) \
216+
{ \
217+
runtime_error("Operands must be numbers "); \
218+
return INTERPRET_RUNTIME_ERROR; \
219+
} \
220+
double b = AS_NUMBER(pop()); \
221+
double a = AS_NUMBER(pop()); \
222+
push(value_type(a op b)); \
216223
} while (false)
217224

218225
for (;;)

src/vm.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ typedef struct
1919

2020
typedef struct
2121
{
22-
CallFrame frames[FRAMES_MAX];
23-
int frame_count;
24-
Value stack[STACK_MAX];
25-
Value* stack_top;
26-
Table globals;
27-
Table strings; // for string interning just like (string pool in java)
22+
CallFrame frames[FRAMES_MAX];
23+
int frame_count;
24+
Value stack[STACK_MAX];
25+
Value* stack_top;
26+
Table globals;
27+
Table strings; // for string interning just like (string pool in java)
2828
ObjUpvalue* open_upvalues;
29+
size_t bytes_allocated;
30+
size_t next_GC;
2931
Obj* objects;
32+
int gray_capacity;
33+
int gray_count;
34+
Obj** gray_stack;
3035
} VM;
3136

3237
typedef enum

0 commit comments

Comments
 (0)