blob: 7d4cac6e74df3dee6958778ad9c9c4d198bb3d9a [file] [log] [blame]
Damiendcced922013-10-21 23:45:08 +01001#include <stdio.h>
2#include <stdlib.h>
3#include <stdint.h>
4#include <string.h>
5
Damiend99b0522013-12-21 18:17:45 +00006#include "mpconfig.h"
Damiendcced922013-10-21 23:45:08 +01007#include "gc.h"
8
9// a machine word is big enough to hold a pointer
10/*
11#define BYTES_PER_WORD (8)
12typedef unsigned long machine_uint_t;
13*/
14typedef unsigned char byte;
15
16#define BITS_PER_BYTE (8)
17#define BITS_PER_WORD (BITS_PER_BYTE * BYTES_PER_WORD)
18#define WORDS_PER_BLOCK (4)
19#define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
20#define STACK_SIZE (64) // tunable; minimum is 1
21
22static byte *gc_alloc_table_start;
Damiendcced922013-10-21 23:45:08 +010023static machine_uint_t gc_alloc_table_byte_len;
24static machine_uint_t *gc_pool_start;
25static machine_uint_t *gc_pool_end;
26
27static int gc_stack_overflow;
28static machine_uint_t gc_stack[STACK_SIZE];
29static machine_uint_t *gc_sp;
30
Damiendcced922013-10-21 23:45:08 +010031// ATB = allocation table byte
32// 0b00 = FREE -- free block
33// 0b01 = HEAD -- head of a chain of blocks
34// 0b10 = TAIL -- in the tail of a chain of blocks
35// 0b11 = MARK -- marked head block
36
37#define AT_FREE (0)
38#define AT_HEAD (1)
39#define AT_TAIL (2)
40#define AT_MARK (3)
41
42#define BLOCKS_PER_ATB (4)
43#define ATB_MASK_0 (0x03)
44#define ATB_MASK_1 (0x0c)
45#define ATB_MASK_2 (0x30)
46#define ATB_MASK_3 (0xc0)
47
48#define ATB_0_IS_FREE(a) (((a) & ATB_MASK_0) == 0)
49#define ATB_1_IS_FREE(a) (((a) & ATB_MASK_1) == 0)
50#define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0)
51#define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0)
52
53#define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1)))
54#define ATB_GET_KIND(block) ((gc_alloc_table_start[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3)
55#define ATB_ANY_TO_FREE(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0)
56#define ATB_FREE_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0)
57#define ATB_FREE_TO_TAIL(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0)
58#define ATB_HEAD_TO_MARK(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0)
59#define ATB_MARK_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0)
60
Damiendcced922013-10-21 23:45:08 +010061#define BLOCK_FROM_PTR(ptr) (((ptr) - (machine_uint_t)gc_pool_start) / BYTES_PER_BLOCK)
62#define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (machine_uint_t)gc_pool_start))
63#define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB)
64
Damienbb5316b2013-10-22 21:12:29 +010065// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool
66void gc_init(void *start, void *end) {
67 // align end pointer on block boundary
68 end = (void*)((machine_uint_t)end & (~(BYTES_PER_BLOCK - 1)));
69
70 // calculate parameters for GC
71 machine_uint_t total_word_len = (machine_uint_t*)end - (machine_uint_t*)start;
72 gc_alloc_table_byte_len = total_word_len * BYTES_PER_WORD / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK);
73 gc_alloc_table_start = (byte*)start;
74 machine_uint_t gc_pool_block_len = gc_alloc_table_byte_len * BITS_PER_BYTE / 2;
75 machine_uint_t gc_pool_word_len = gc_pool_block_len * WORDS_PER_BLOCK;
76 gc_pool_start = (machine_uint_t*)end - gc_pool_word_len;
77 gc_pool_end = end;
78
79 // clear ATBs
80 memset(gc_alloc_table_start, 0, gc_alloc_table_byte_len);
81
82 // allocate first block because gc_pool_start points there and it will never
83 // be freed, so allocating 1 block with null pointers will minimise memory loss
84 ATB_FREE_TO_HEAD(0);
85 for (int i = 0; i < WORDS_PER_BLOCK; i++) {
86 gc_pool_start[i] = 0;
87 }
88
89 /*
90 printf("GC layout:\n");
91 printf(" alloc table at %p, length %u bytes\n", gc_alloc_table_start, gc_alloc_table_byte_len);
92 printf(" pool at %p, length %u blocks = %u words = %u bytes\n", gc_pool_start, gc_pool_block_len, gc_pool_word_len, gc_pool_word_len * BYTES_PER_WORD);
93 */
94}
95
Damienfd8b6bc2013-10-22 20:26:36 +010096#define VERIFY_PTR(ptr) ( \
97 (ptr & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \
98 && ptr >= (machine_uint_t)gc_pool_start /* must be above start of pool */ \
99 && ptr < (machine_uint_t)gc_pool_end /* must be below end of pool */ \
100 )
101
Damiendcced922013-10-21 23:45:08 +0100102#define VERIFY_MARK_AND_PUSH(ptr) \
103 do { \
Damienfd8b6bc2013-10-22 20:26:36 +0100104 if (VERIFY_PTR(ptr)) { \
Damiendcced922013-10-21 23:45:08 +0100105 machine_uint_t _block = BLOCK_FROM_PTR(ptr); \
106 if (ATB_GET_KIND(_block) == AT_HEAD) { \
107 /* an unmarked head, mark it, and push it on gc stack */ \
108 ATB_HEAD_TO_MARK(_block); \
109 if (gc_sp < &gc_stack[STACK_SIZE]) { \
110 *gc_sp++ = _block; \
111 } else { \
112 gc_stack_overflow = 1; \
113 } \
114 } \
115 } \
116 } while (0)
117
Damien8b3a7c22013-10-23 20:20:17 +0100118static void gc_drain_stack(void) {
Damiendcced922013-10-21 23:45:08 +0100119 while (gc_sp > gc_stack) {
120 // pop the next block off the stack
121 machine_uint_t block = *--gc_sp;
122
Damieneefcc792013-10-22 15:25:25 +0100123 // work out number of consecutive blocks in the chain starting with this one
Damiendcced922013-10-21 23:45:08 +0100124 machine_uint_t n_blocks = 0;
125 do {
126 n_blocks += 1;
127 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
128
129 // check this block's children
130 machine_uint_t *scan = (machine_uint_t*)PTR_FROM_BLOCK(block);
131 for (machine_uint_t i = n_blocks * WORDS_PER_BLOCK; i > 0; i--, scan++) {
132 machine_uint_t ptr2 = *scan;
133 VERIFY_MARK_AND_PUSH(ptr2);
134 }
135 }
136}
137
Damien8b3a7c22013-10-23 20:20:17 +0100138static void gc_deal_with_stack_overflow(void) {
Damiendcced922013-10-21 23:45:08 +0100139 while (gc_stack_overflow) {
140 gc_stack_overflow = 0;
141 gc_sp = gc_stack;
142
143 // scan entire memory looking for blocks which have been marked but not their children
144 for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
145 // trace (again) if mark bit set
146 if (ATB_GET_KIND(block) == AT_MARK) {
147 *gc_sp++ = block;
148 gc_drain_stack();
149 }
150 }
151 }
152}
153
Damien8b3a7c22013-10-23 20:20:17 +0100154static void gc_sweep(void) {
Damiendcced922013-10-21 23:45:08 +0100155 // free unmarked heads and their tails
156 int free_tail = 0;
157 for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
158 switch (ATB_GET_KIND(block)) {
159 case AT_HEAD:
160 free_tail = 1;
161 // fall through to free the head
162
163 case AT_TAIL:
164 if (free_tail) {
165 ATB_ANY_TO_FREE(block);
166 }
167 break;
168
169 case AT_MARK:
170 ATB_MARK_TO_HEAD(block);
171 free_tail = 0;
172 break;
173 }
174 }
175}
176
Damien8b3a7c22013-10-23 20:20:17 +0100177void gc_collect_start(void) {
Damiendcced922013-10-21 23:45:08 +0100178 gc_stack_overflow = 0;
179 gc_sp = gc_stack;
180}
181
182void gc_collect_root(void **ptrs, machine_uint_t len) {
183 for (machine_uint_t i = 0; i < len; i++) {
184 machine_uint_t ptr = (machine_uint_t)ptrs[i];
185 VERIFY_MARK_AND_PUSH(ptr);
186 gc_drain_stack();
187 }
188}
189
Damien8b3a7c22013-10-23 20:20:17 +0100190void gc_collect_end(void) {
Damiendcced922013-10-21 23:45:08 +0100191 gc_deal_with_stack_overflow();
192 gc_sweep();
Damieneefcc792013-10-22 15:25:25 +0100193}
Damiendcced922013-10-21 23:45:08 +0100194
Damieneefcc792013-10-22 15:25:25 +0100195void gc_info(gc_info_t *info) {
196 info->total = (gc_pool_end - gc_pool_start) * sizeof(machine_uint_t);
197 info->used = 0;
198 info->free = 0;
199 info->num_1block = 0;
200 info->num_2block = 0;
201 info->max_block = 0;
202 for (machine_uint_t block = 0, len = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
203 machine_uint_t kind = ATB_GET_KIND(block);
204 if (kind == AT_FREE || kind == AT_HEAD) {
205 if (len == 1) {
206 info->num_1block += 1;
207 } else if (len == 2) {
208 info->num_2block += 1;
209 }
210 if (len > info->max_block) {
211 info->max_block = len;
212 }
213 }
214 switch (kind) {
Damiendcced922013-10-21 23:45:08 +0100215 case AT_FREE:
Damieneefcc792013-10-22 15:25:25 +0100216 info->free += 1;
217 len = 0;
Damiendcced922013-10-21 23:45:08 +0100218 break;
219
220 case AT_HEAD:
Damieneefcc792013-10-22 15:25:25 +0100221 info->used += 1;
222 len = 1;
223 break;
224
Damiendcced922013-10-21 23:45:08 +0100225 case AT_TAIL:
Damieneefcc792013-10-22 15:25:25 +0100226 info->used += 1;
227 len += 1;
Damiendcced922013-10-21 23:45:08 +0100228 break;
229
230 case AT_MARK:
Damieneefcc792013-10-22 15:25:25 +0100231 // shouldn't happen
Damiendcced922013-10-21 23:45:08 +0100232 break;
233 }
234 }
235
Damieneefcc792013-10-22 15:25:25 +0100236 info->used *= BYTES_PER_BLOCK;
237 info->free *= BYTES_PER_BLOCK;
Damiendcced922013-10-21 23:45:08 +0100238}
239
240void *gc_alloc(machine_uint_t n_bytes) {
241 machine_uint_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK;
242 //printf("gc_alloc(%u bytes -> %u blocks)\n", n_bytes, n_blocks);
243
244 // check for 0 allocation
245 if (n_blocks == 0) {
246 return NULL;
247 }
248
249 machine_uint_t i;
250 machine_uint_t end_block;
251 machine_uint_t start_block;
252 machine_uint_t n_free = 0;
253 int collected = 0;
254 for (;;) {
255
256 // look for a run of n_blocks available blocks
257 for (i = 0; i < gc_alloc_table_byte_len; i++) {
258 byte a = gc_alloc_table_start[i];
259 if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
260 if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
261 if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; }
262 if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; }
263 }
264
265 // nothing found!
266 if (collected) {
267 return NULL;
268 }
269 gc_collect();
270 collected = 1;
271 }
272
273 // found, ending at block i inclusive
274found:
275 // get starting and end blocks, both inclusive
276 end_block = i;
277 start_block = i - n_free + 1;
278
279 // mark first block as used head
280 ATB_FREE_TO_HEAD(start_block);
281
282 // mark rest of blocks as used tail
283 // TODO for a run of many blocks can make this more efficient
284 for (machine_uint_t bl = start_block + 1; bl <= end_block; bl++) {
285 ATB_FREE_TO_TAIL(bl);
286 }
287
288 // return pointer to first block
289 return (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK);
290}
291
Damienfd8b6bc2013-10-22 20:26:36 +0100292// force the freeing of a piece of memory
293void gc_free(void *ptr_in) {
294 machine_uint_t ptr = (machine_uint_t)ptr_in;
295
296 if (VERIFY_PTR(ptr)) {
297 machine_uint_t block = BLOCK_FROM_PTR(ptr);
298 if (ATB_GET_KIND(block) == AT_HEAD) {
299 // free head and all of its tail blocks
300 do {
301 ATB_ANY_TO_FREE(block);
302 block += 1;
303 } while (ATB_GET_KIND(block) == AT_TAIL);
304 }
305 }
306}
307
Damiendcced922013-10-21 23:45:08 +0100308machine_uint_t gc_nbytes(void *ptr_in) {
309 machine_uint_t ptr = (machine_uint_t)ptr_in;
310
Damienfd8b6bc2013-10-22 20:26:36 +0100311 if (VERIFY_PTR(ptr)) {
Damiendcced922013-10-21 23:45:08 +0100312 machine_uint_t block = BLOCK_FROM_PTR(ptr);
313 if (ATB_GET_KIND(block) == AT_HEAD) {
314 // work out number of consecutive blocks in the chain starting with this on
315 machine_uint_t n_blocks = 0;
316 do {
317 n_blocks += 1;
318 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
319 return n_blocks * BYTES_PER_BLOCK;
320 }
321 }
322
323 // invalid pointer
324 return 0;
325}
326
327void *gc_realloc(void *ptr, machine_uint_t n_bytes) {
328 machine_uint_t n_existing = gc_nbytes(ptr);
329 if (n_bytes <= n_existing) {
330 return ptr;
331 } else {
Damiend2c1a732013-10-23 21:03:27 +0100332 // TODO check if we can grow inplace
Damiendcced922013-10-21 23:45:08 +0100333 void *ptr2 = gc_alloc(n_bytes);
334 memcpy(ptr2, ptr, n_existing);
Damiend2c1a732013-10-23 21:03:27 +0100335 gc_free(ptr);
Damiendcced922013-10-21 23:45:08 +0100336 return ptr2;
337 }
338}
339
340/*
Damien8b3a7c22013-10-23 20:20:17 +0100341static void gc_dump_at(void) {
Damieneefcc792013-10-22 15:25:25 +0100342 for (machine_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
343 printf("block % 6u ", bl);
344 switch (ATB_GET_KIND(bl)) {
345 case AT_FREE: printf("FREE"); break;
346 case AT_HEAD: printf("HEAD"); break;
347 case AT_TAIL: printf("TAIL"); break;
348 default: printf("MARK"); break;
349 }
350 printf("\n");
351 }
352}
353
Damien8b3a7c22013-10-23 20:20:17 +0100354int main(void) {
Damiendcced922013-10-21 23:45:08 +0100355 machine_uint_t len = 1000;
356 machine_uint_t *heap = malloc(len);
357 gc_init(heap, heap + len / sizeof(machine_uint_t));
358 void *ptrs[100];
359 {
360 machine_uint_t *p = gc_alloc(16);
361 p[0] = gc_alloc(64);
362 p[1] = gc_alloc(1);
363 p[2] = gc_alloc(1);
364 p[3] = gc_alloc(1);
365 machine_uint_t *p2 = gc_alloc(16);
366 p2[0] = p;
367 p2[1] = p;
368 ptrs[0] = p2;
369 }
370 for (int i = 0; i < 50; i+=2) {
371 machine_uint_t *p = gc_alloc(i);
372 printf("p=%p\n", p);
373 if (i & 3) {
374 //ptrs[i] = p;
375 }
376 }
377
378 gc_dump_at();
379 gc_collect(ptrs, sizeof(ptrs) / sizeof(void*));
380 gc_dump_at();
381}
382*/