blob: 87c458f82c0de1e3eb2bc7d322034c2f80abf23f [file] [log] [blame]
Damien George04b91472014-05-03 23:27:38 +01001/*
2 * This file is part of the Micro Python project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2013, 2014 Damien P. George
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 * THE SOFTWARE.
25 */
26
Paul Sokolovskyc86889d2014-04-20 11:45:16 +030027#include <assert.h>
Damiendcced922013-10-21 23:45:08 +010028#include <stdio.h>
Damiendcced922013-10-21 23:45:08 +010029#include <string.h>
mux4f7e9f52014-04-03 23:55:12 +020030#include <stdbool.h>
Damiendcced922013-10-21 23:45:08 +010031
Damiend99b0522013-12-21 18:17:45 +000032#include "mpconfig.h"
Paul Sokolovskye807fa82014-04-02 20:36:32 +030033#include "misc.h"
Damiendcced922013-10-21 23:45:08 +010034#include "gc.h"
35
mux4f7e9f52014-04-03 23:55:12 +020036#include "misc.h"
37#include "qstr.h"
38#include "obj.h"
muxcc849f72014-04-05 15:49:03 +020039#include "runtime.h"
mux4f7e9f52014-04-03 23:55:12 +020040
Damien Georged3ebe482014-01-07 15:20:33 +000041#if MICROPY_ENABLE_GC
42
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020043#if 0 // print debugging info
44#define DEBUG_PRINT (1)
Paul Sokolovsky44739e22014-02-16 18:11:42 +020045#define DEBUG_printf DEBUG_printf
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020046#else // don't print debugging info
Damien George41eb6082014-02-26 22:40:35 +000047#define DEBUG_printf(...) (void)0
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020048#endif
49
Damiendcced922013-10-21 23:45:08 +010050#define WORDS_PER_BLOCK (4)
51#define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
52#define STACK_SIZE (64) // tunable; minimum is 1
53
Paul Sokolovsky520e2f52014-02-12 18:31:30 +020054STATIC byte *gc_alloc_table_start;
55STATIC machine_uint_t gc_alloc_table_byte_len;
Damien George12bab722014-04-05 20:35:48 +010056#if MICROPY_ENABLE_FINALISER
57STATIC byte *gc_finaliser_table_start;
58#endif
Paul Sokolovsky520e2f52014-02-12 18:31:30 +020059STATIC machine_uint_t *gc_pool_start;
60STATIC machine_uint_t *gc_pool_end;
Damiendcced922013-10-21 23:45:08 +010061
Paul Sokolovsky520e2f52014-02-12 18:31:30 +020062STATIC int gc_stack_overflow;
63STATIC machine_uint_t gc_stack[STACK_SIZE];
64STATIC machine_uint_t *gc_sp;
Damien George443e0182014-04-08 11:31:21 +000065STATIC machine_uint_t gc_lock_depth;
Damiendcced922013-10-21 23:45:08 +010066
Damiendcced922013-10-21 23:45:08 +010067// ATB = allocation table byte
68// 0b00 = FREE -- free block
69// 0b01 = HEAD -- head of a chain of blocks
70// 0b10 = TAIL -- in the tail of a chain of blocks
71// 0b11 = MARK -- marked head block
72
73#define AT_FREE (0)
74#define AT_HEAD (1)
75#define AT_TAIL (2)
76#define AT_MARK (3)
77
78#define BLOCKS_PER_ATB (4)
79#define ATB_MASK_0 (0x03)
80#define ATB_MASK_1 (0x0c)
81#define ATB_MASK_2 (0x30)
82#define ATB_MASK_3 (0xc0)
83
84#define ATB_0_IS_FREE(a) (((a) & ATB_MASK_0) == 0)
85#define ATB_1_IS_FREE(a) (((a) & ATB_MASK_1) == 0)
86#define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0)
87#define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0)
88
89#define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1)))
90#define ATB_GET_KIND(block) ((gc_alloc_table_start[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3)
91#define ATB_ANY_TO_FREE(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0)
92#define ATB_FREE_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0)
93#define ATB_FREE_TO_TAIL(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0)
94#define ATB_HEAD_TO_MARK(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0)
95#define ATB_MARK_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0)
96
Damiendcced922013-10-21 23:45:08 +010097#define BLOCK_FROM_PTR(ptr) (((ptr) - (machine_uint_t)gc_pool_start) / BYTES_PER_BLOCK)
98#define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (machine_uint_t)gc_pool_start))
99#define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB)
100
Damien George12bab722014-04-05 20:35:48 +0100101#if MICROPY_ENABLE_FINALISER
102// FTB = finaliser table byte
103// if set, then the corresponding block may have a finaliser
104
105#define BLOCKS_PER_FTB (8)
106
107#define FTB_GET(block) ((gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1)
108#define FTB_SET(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0)
109#define FTB_CLEAR(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0)
110#endif
111
Damienbb5316b2013-10-22 21:12:29 +0100112// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool
113void gc_init(void *start, void *end) {
114 // align end pointer on block boundary
115 end = (void*)((machine_uint_t)end & (~(BYTES_PER_BLOCK - 1)));
Damien George0fb80c32014-05-10 18:16:21 +0100116 DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, end - start);
Damienbb5316b2013-10-22 21:12:29 +0100117
Damien George12bab722014-04-05 20:35:48 +0100118 // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes):
119 // T = A + F + P
120 // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB
121 // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK
122 // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
123 machine_uint_t total_byte_len = end - start;
124#if MICROPY_ENABLE_FINALISER
125 gc_alloc_table_byte_len = total_byte_len * BITS_PER_BYTE / (BITS_PER_BYTE + BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK);
126#else
127 gc_alloc_table_byte_len = total_byte_len / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK);
128#endif
129
130
Damienbb5316b2013-10-22 21:12:29 +0100131 gc_alloc_table_start = (byte*)start;
mux4f7e9f52014-04-03 23:55:12 +0200132
Damien George12bab722014-04-05 20:35:48 +0100133#if MICROPY_ENABLE_FINALISER
134 machine_uint_t gc_finaliser_table_byte_len = (gc_alloc_table_byte_len * BLOCKS_PER_ATB) / BLOCKS_PER_FTB;
135 gc_finaliser_table_start = gc_alloc_table_start + gc_alloc_table_byte_len;
136#endif
mux4f7e9f52014-04-03 23:55:12 +0200137
Damien George12bab722014-04-05 20:35:48 +0100138 machine_uint_t gc_pool_block_len = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
139 gc_pool_start = end - gc_pool_block_len * BYTES_PER_BLOCK;
Damienbb5316b2013-10-22 21:12:29 +0100140 gc_pool_end = end;
141
142 // clear ATBs
143 memset(gc_alloc_table_start, 0, gc_alloc_table_byte_len);
144
Damien George12bab722014-04-05 20:35:48 +0100145#if MICROPY_ENABLE_FINALISER
146 // clear FTBs
147 memset(gc_finaliser_table_start, 0, gc_finaliser_table_byte_len);
148#endif
mux4f7e9f52014-04-03 23:55:12 +0200149
Damienbb5316b2013-10-22 21:12:29 +0100150 // allocate first block because gc_pool_start points there and it will never
151 // be freed, so allocating 1 block with null pointers will minimise memory loss
152 ATB_FREE_TO_HEAD(0);
153 for (int i = 0; i < WORDS_PER_BLOCK; i++) {
154 gc_pool_start[i] = 0;
155 }
156
Damien George12bab722014-04-05 20:35:48 +0100157 // unlock the GC
Damien George443e0182014-04-08 11:31:21 +0000158 gc_lock_depth = 0;
Damien George12bab722014-04-05 20:35:48 +0100159
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200160 DEBUG_printf("GC layout:\n");
Damien George12bab722014-04-05 20:35:48 +0100161 DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", gc_alloc_table_start, gc_alloc_table_byte_len, gc_alloc_table_byte_len * BLOCKS_PER_ATB);
162#if MICROPY_ENABLE_FINALISER
163 DEBUG_printf(" finaliser table at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", gc_finaliser_table_start, gc_finaliser_table_byte_len, gc_finaliser_table_byte_len * BLOCKS_PER_FTB);
164#endif
165 DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", gc_pool_start, gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len);
Damienbb5316b2013-10-22 21:12:29 +0100166}
167
Damien George443e0182014-04-08 11:31:21 +0000168void gc_lock(void) {
169 gc_lock_depth++;
170}
171
172void gc_unlock(void) {
173 gc_lock_depth--;
174}
175
Damienfd8b6bc2013-10-22 20:26:36 +0100176#define VERIFY_PTR(ptr) ( \
177 (ptr & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \
178 && ptr >= (machine_uint_t)gc_pool_start /* must be above start of pool */ \
179 && ptr < (machine_uint_t)gc_pool_end /* must be below end of pool */ \
180 )
181
Damiendcced922013-10-21 23:45:08 +0100182#define VERIFY_MARK_AND_PUSH(ptr) \
183 do { \
Damienfd8b6bc2013-10-22 20:26:36 +0100184 if (VERIFY_PTR(ptr)) { \
Damiendcced922013-10-21 23:45:08 +0100185 machine_uint_t _block = BLOCK_FROM_PTR(ptr); \
186 if (ATB_GET_KIND(_block) == AT_HEAD) { \
187 /* an unmarked head, mark it, and push it on gc stack */ \
188 ATB_HEAD_TO_MARK(_block); \
189 if (gc_sp < &gc_stack[STACK_SIZE]) { \
190 *gc_sp++ = _block; \
191 } else { \
192 gc_stack_overflow = 1; \
193 } \
194 } \
195 } \
196 } while (0)
197
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200198STATIC void gc_drain_stack(void) {
Damiendcced922013-10-21 23:45:08 +0100199 while (gc_sp > gc_stack) {
200 // pop the next block off the stack
201 machine_uint_t block = *--gc_sp;
202
Damieneefcc792013-10-22 15:25:25 +0100203 // work out number of consecutive blocks in the chain starting with this one
Damiendcced922013-10-21 23:45:08 +0100204 machine_uint_t n_blocks = 0;
205 do {
206 n_blocks += 1;
207 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
208
209 // check this block's children
210 machine_uint_t *scan = (machine_uint_t*)PTR_FROM_BLOCK(block);
211 for (machine_uint_t i = n_blocks * WORDS_PER_BLOCK; i > 0; i--, scan++) {
212 machine_uint_t ptr2 = *scan;
213 VERIFY_MARK_AND_PUSH(ptr2);
214 }
215 }
216}
217
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200218STATIC void gc_deal_with_stack_overflow(void) {
Damiendcced922013-10-21 23:45:08 +0100219 while (gc_stack_overflow) {
220 gc_stack_overflow = 0;
221 gc_sp = gc_stack;
222
223 // scan entire memory looking for blocks which have been marked but not their children
224 for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
225 // trace (again) if mark bit set
226 if (ATB_GET_KIND(block) == AT_MARK) {
227 *gc_sp++ = block;
228 gc_drain_stack();
229 }
230 }
231 }
232}
233
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200234STATIC void gc_sweep(void) {
Damiendcced922013-10-21 23:45:08 +0100235 // free unmarked heads and their tails
236 int free_tail = 0;
237 for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
238 switch (ATB_GET_KIND(block)) {
239 case AT_HEAD:
Damien George12bab722014-04-05 20:35:48 +0100240#if MICROPY_ENABLE_FINALISER
241 if (FTB_GET(block)) {
242 mp_obj_t obj = (mp_obj_t)PTR_FROM_BLOCK(block);
243 if (((mp_obj_base_t*)obj)->type != MP_OBJ_NULL) {
244 // if the object has a type then see if it has a __del__ method
245 mp_obj_t dest[2];
246 mp_load_method_maybe(obj, MP_QSTR___del__, dest);
247 if (dest[0] != MP_OBJ_NULL) {
248 // load_method returned a method
249 mp_call_method_n_kw(0, 0, dest);
250 }
mux4f7e9f52014-04-03 23:55:12 +0200251 }
Damien George12bab722014-04-05 20:35:48 +0100252 // clear finaliser flag
253 FTB_CLEAR(block);
mux4f7e9f52014-04-03 23:55:12 +0200254 }
Damien George12bab722014-04-05 20:35:48 +0100255#endif
Damiendcced922013-10-21 23:45:08 +0100256 free_tail = 1;
257 // fall through to free the head
258
259 case AT_TAIL:
260 if (free_tail) {
261 ATB_ANY_TO_FREE(block);
262 }
263 break;
264
265 case AT_MARK:
266 ATB_MARK_TO_HEAD(block);
267 free_tail = 0;
268 break;
269 }
270 }
271}
272
Damien8b3a7c22013-10-23 20:20:17 +0100273void gc_collect_start(void) {
Damien George443e0182014-04-08 11:31:21 +0000274 gc_lock();
Damiendcced922013-10-21 23:45:08 +0100275 gc_stack_overflow = 0;
276 gc_sp = gc_stack;
277}
278
279void gc_collect_root(void **ptrs, machine_uint_t len) {
280 for (machine_uint_t i = 0; i < len; i++) {
281 machine_uint_t ptr = (machine_uint_t)ptrs[i];
282 VERIFY_MARK_AND_PUSH(ptr);
283 gc_drain_stack();
284 }
285}
286
Damien8b3a7c22013-10-23 20:20:17 +0100287void gc_collect_end(void) {
Damiendcced922013-10-21 23:45:08 +0100288 gc_deal_with_stack_overflow();
289 gc_sweep();
Damien George443e0182014-04-08 11:31:21 +0000290 gc_unlock();
Damieneefcc792013-10-22 15:25:25 +0100291}
Damiendcced922013-10-21 23:45:08 +0100292
Damieneefcc792013-10-22 15:25:25 +0100293void gc_info(gc_info_t *info) {
294 info->total = (gc_pool_end - gc_pool_start) * sizeof(machine_uint_t);
295 info->used = 0;
296 info->free = 0;
297 info->num_1block = 0;
298 info->num_2block = 0;
299 info->max_block = 0;
300 for (machine_uint_t block = 0, len = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
301 machine_uint_t kind = ATB_GET_KIND(block);
302 if (kind == AT_FREE || kind == AT_HEAD) {
303 if (len == 1) {
304 info->num_1block += 1;
305 } else if (len == 2) {
306 info->num_2block += 1;
307 }
308 if (len > info->max_block) {
309 info->max_block = len;
310 }
311 }
312 switch (kind) {
Damiendcced922013-10-21 23:45:08 +0100313 case AT_FREE:
Damieneefcc792013-10-22 15:25:25 +0100314 info->free += 1;
315 len = 0;
Damiendcced922013-10-21 23:45:08 +0100316 break;
317
318 case AT_HEAD:
Damieneefcc792013-10-22 15:25:25 +0100319 info->used += 1;
320 len = 1;
321 break;
322
Damiendcced922013-10-21 23:45:08 +0100323 case AT_TAIL:
Damieneefcc792013-10-22 15:25:25 +0100324 info->used += 1;
325 len += 1;
Damiendcced922013-10-21 23:45:08 +0100326 break;
327
328 case AT_MARK:
Damieneefcc792013-10-22 15:25:25 +0100329 // shouldn't happen
Damiendcced922013-10-21 23:45:08 +0100330 break;
331 }
332 }
333
Damieneefcc792013-10-22 15:25:25 +0100334 info->used *= BYTES_PER_BLOCK;
335 info->free *= BYTES_PER_BLOCK;
Damiendcced922013-10-21 23:45:08 +0100336}
337
Damien George12bab722014-04-05 20:35:48 +0100338void *gc_alloc(machine_uint_t n_bytes, bool has_finaliser) {
Damiendcced922013-10-21 23:45:08 +0100339 machine_uint_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK;
Damien Georgece1162a2014-02-26 22:55:59 +0000340 DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks);
Damiendcced922013-10-21 23:45:08 +0100341
Damien George443e0182014-04-08 11:31:21 +0000342 // check if GC is locked
343 if (gc_lock_depth > 0) {
344 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100345 }
346
Damiendcced922013-10-21 23:45:08 +0100347 // check for 0 allocation
348 if (n_blocks == 0) {
349 return NULL;
350 }
351
352 machine_uint_t i;
353 machine_uint_t end_block;
354 machine_uint_t start_block;
355 machine_uint_t n_free = 0;
356 int collected = 0;
357 for (;;) {
358
359 // look for a run of n_blocks available blocks
360 for (i = 0; i < gc_alloc_table_byte_len; i++) {
361 byte a = gc_alloc_table_start[i];
362 if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
363 if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
364 if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; }
365 if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; }
366 }
367
368 // nothing found!
369 if (collected) {
370 return NULL;
371 }
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200372 DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes);
Damiendcced922013-10-21 23:45:08 +0100373 gc_collect();
374 collected = 1;
375 }
376
377 // found, ending at block i inclusive
378found:
379 // get starting and end blocks, both inclusive
380 end_block = i;
381 start_block = i - n_free + 1;
382
383 // mark first block as used head
384 ATB_FREE_TO_HEAD(start_block);
385
386 // mark rest of blocks as used tail
387 // TODO for a run of many blocks can make this more efficient
388 for (machine_uint_t bl = start_block + 1; bl <= end_block; bl++) {
389 ATB_FREE_TO_TAIL(bl);
390 }
391
Damien George12bab722014-04-05 20:35:48 +0100392 // get pointer to first block
393 void *ret_ptr = (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK);
muxcc849f72014-04-05 15:49:03 +0200394
Damien George32bef312014-04-26 22:23:42 +0100395 // zero out the additional bytes of the newly allocated blocks
Damien Georgedaab6512014-04-25 23:37:55 +0100396 // This is needed because the blocks may have previously held pointers
397 // to the heap and will not be set to something else if the caller
398 // doesn't actually use the entire block. As such they will continue
399 // to point to the heap and may prevent other blocks from being reclaimed.
Damien George32bef312014-04-26 22:23:42 +0100400 memset(ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100401
Damien George12bab722014-04-05 20:35:48 +0100402#if MICROPY_ENABLE_FINALISER
403 if (has_finaliser) {
Damien George32bef312014-04-26 22:23:42 +0100404 // clear type pointer in case it is never set
405 ((mp_obj_base_t*)ret_ptr)->type = MP_OBJ_NULL;
Damien George12bab722014-04-05 20:35:48 +0100406 // set mp_obj flag only if it has a finaliser
407 FTB_SET(start_block);
408 }
409#endif
410
411 return ret_ptr;
Damiendcced922013-10-21 23:45:08 +0100412}
413
Damien George12bab722014-04-05 20:35:48 +0100414/*
mux4f7e9f52014-04-03 23:55:12 +0200415void *gc_alloc(machine_uint_t n_bytes) {
416 return _gc_alloc(n_bytes, false);
417}
418
Damien George12bab722014-04-05 20:35:48 +0100419void *gc_alloc_with_finaliser(machine_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200420 return _gc_alloc(n_bytes, true);
421}
Damien George12bab722014-04-05 20:35:48 +0100422*/
mux4f7e9f52014-04-03 23:55:12 +0200423
Damienfd8b6bc2013-10-22 20:26:36 +0100424// force the freeing of a piece of memory
425void gc_free(void *ptr_in) {
Damien George443e0182014-04-08 11:31:21 +0000426 if (gc_lock_depth > 0) {
427 // TODO how to deal with this error?
428 return;
Damien George12bab722014-04-05 20:35:48 +0100429 }
430
Damienfd8b6bc2013-10-22 20:26:36 +0100431 machine_uint_t ptr = (machine_uint_t)ptr_in;
432
433 if (VERIFY_PTR(ptr)) {
434 machine_uint_t block = BLOCK_FROM_PTR(ptr);
435 if (ATB_GET_KIND(block) == AT_HEAD) {
436 // free head and all of its tail blocks
437 do {
438 ATB_ANY_TO_FREE(block);
439 block += 1;
440 } while (ATB_GET_KIND(block) == AT_TAIL);
441 }
442 }
443}
444
Damiendcced922013-10-21 23:45:08 +0100445machine_uint_t gc_nbytes(void *ptr_in) {
446 machine_uint_t ptr = (machine_uint_t)ptr_in;
447
Damienfd8b6bc2013-10-22 20:26:36 +0100448 if (VERIFY_PTR(ptr)) {
Damiendcced922013-10-21 23:45:08 +0100449 machine_uint_t block = BLOCK_FROM_PTR(ptr);
450 if (ATB_GET_KIND(block) == AT_HEAD) {
451 // work out number of consecutive blocks in the chain starting with this on
452 machine_uint_t n_blocks = 0;
453 do {
454 n_blocks += 1;
455 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
456 return n_blocks * BYTES_PER_BLOCK;
457 }
458 }
459
460 // invalid pointer
461 return 0;
462}
463
mux87826762014-03-12 21:00:23 +0200464#if 0
Damien George443e0182014-04-08 11:31:21 +0000465// old, simple realloc that didn't expand memory in place
Damien George6fc765c2014-03-07 00:21:51 +0000466void *gc_realloc(void *ptr, machine_uint_t n_bytes) {
467 machine_uint_t n_existing = gc_nbytes(ptr);
468 if (n_bytes <= n_existing) {
469 return ptr;
470 } else {
Damien George410f3072014-04-25 11:44:53 +0000471 bool has_finaliser;
472 if (ptr == NULL) {
473 has_finaliser = false;
474 } else {
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300475#if MICROPY_ENABLE_FINALISER
Damien George410f3072014-04-25 11:44:53 +0000476 has_finaliser = FTB_GET(BLOCK_FROM_PTR((machine_uint_t)ptr));
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300477#else
Damien George410f3072014-04-25 11:44:53 +0000478 has_finaliser = false;
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300479#endif
Damien George410f3072014-04-25 11:44:53 +0000480 }
481 void *ptr2 = gc_alloc(n_bytes, has_finaliser);
Damien George6fc765c2014-03-07 00:21:51 +0000482 if (ptr2 == NULL) {
483 return ptr2;
484 }
485 memcpy(ptr2, ptr, n_existing);
486 gc_free(ptr);
487 return ptr2;
488 }
489}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300490
491#else // Alternative gc_realloc impl
Damien George443e0182014-04-08 11:31:21 +0000492
muxfbaa1472014-03-05 23:23:04 +0200493void *gc_realloc(void *ptr_in, machine_uint_t n_bytes) {
Damien George443e0182014-04-08 11:31:21 +0000494 if (gc_lock_depth > 0) {
495 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100496 }
497
Damien Georgedde739d2014-04-20 18:16:25 +0100498 // check for pure allocation
muxfbaa1472014-03-05 23:23:04 +0200499 if (ptr_in == NULL) {
Damien George12bab722014-04-05 20:35:48 +0100500 return gc_alloc(n_bytes, false);
Damiendcced922013-10-21 23:45:08 +0100501 }
muxfbaa1472014-03-05 23:23:04 +0200502
Damien Georgedde739d2014-04-20 18:16:25 +0100503 machine_uint_t ptr = (machine_uint_t)ptr_in;
504
505 // sanity check the ptr
506 if (!VERIFY_PTR(ptr)) {
507 return NULL;
508 }
509
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300510 // get first block
511 machine_uint_t block = BLOCK_FROM_PTR(ptr);
512
Damien Georgedde739d2014-04-20 18:16:25 +0100513 // sanity check the ptr is pointing to the head of a block
514 if (ATB_GET_KIND(block) != AT_HEAD) {
515 return NULL;
muxfbaa1472014-03-05 23:23:04 +0200516 }
517
Damien Georgedde739d2014-04-20 18:16:25 +0100518 // compute number of new blocks that are requested
Paul Sokolovsky5b991ae2014-04-20 20:46:39 +0300519 machine_uint_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK;
Damien Georgedde739d2014-04-20 18:16:25 +0100520
521 // get the number of consecutive tail blocks and
522 // the number of free blocks after last tail block
523 // stop if we reach (or are at) end of heap
524 machine_uint_t n_free = 0;
525 machine_uint_t n_blocks = 1; // counting HEAD block
526 machine_uint_t max_block = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
527 while (block + n_blocks + n_free < max_block) {
528 if (n_blocks + n_free >= new_blocks) {
529 // stop as soon as we find enough blocks for n_bytes
530 break;
531 }
532 byte block_type = ATB_GET_KIND(block + n_blocks + n_free);
533 switch (block_type) {
534 case AT_FREE: n_free++; continue;
535 case AT_TAIL: n_blocks++; continue;
536 case AT_MARK: assert(0);
537 }
538 break;
539 }
540
541 // return original ptr if it already has the requested number of blocks
542 if (new_blocks == n_blocks) {
543 return ptr_in;
544 }
545
546 // check if we can shrink the allocated area
547 if (new_blocks < n_blocks) {
548 // free unneeded tail blocks
549 for (machine_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
550 ATB_ANY_TO_FREE(bl);
551 }
552 return ptr_in;
553 }
554
555 // check if we can expand in place
556 if (new_blocks <= n_blocks + n_free) {
557 // mark few more blocks as used tail
558 for (machine_uint_t bl = block + n_blocks; bl < block + new_blocks; bl++) {
559 assert(ATB_GET_KIND(bl) == AT_FREE);
560 ATB_FREE_TO_TAIL(bl);
561 }
Damien Georgedaab6512014-04-25 23:37:55 +0100562
Damien George32bef312014-04-26 22:23:42 +0100563 // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
564 memset(ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100565
Damien Georgedde739d2014-04-20 18:16:25 +0100566 return ptr_in;
567 }
568
569 // can't resize inplace; try to find a new contiguous chain
570 void *ptr_out = gc_alloc(n_bytes,
571#if MICROPY_ENABLE_FINALISER
572 FTB_GET(block)
573#else
574 false
575#endif
576 );
577
578 // check that the alloc succeeded
579 if (ptr_out == NULL) {
580 return NULL;
581 }
582
583 DEBUG_printf("gc_realloc: allocating new block\n");
584 memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK);
585 gc_free(ptr_in);
586 return ptr_out;
Damiendcced922013-10-21 23:45:08 +0100587}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300588#endif // Alternative gc_realloc impl
mux87826762014-03-12 21:00:23 +0200589
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200590void gc_dump_info() {
591 gc_info_t info;
592 gc_info(&info);
593 printf("GC: total: " UINT_FMT ", used: " UINT_FMT ", free: " UINT_FMT "\n", info.total, info.used, info.free);
594 printf(" No. of 1-blocks: " UINT_FMT ", 2-blocks: " UINT_FMT ", max blk sz: " UINT_FMT "\n",
595 info.num_1block, info.num_2block, info.max_block);
596}
597
Damien Georgece1162a2014-02-26 22:55:59 +0000598void gc_dump_alloc_table(void) {
Damien Georgedaab6512014-04-25 23:37:55 +0100599 printf("GC memory layout; from %p:", gc_pool_start);
Damieneefcc792013-10-22 15:25:25 +0100600 for (machine_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
Damien Georgece1162a2014-02-26 22:55:59 +0000601 if (bl % 64 == 0) {
602 printf("\n%04x: ", (uint)bl);
Damieneefcc792013-10-22 15:25:25 +0100603 }
Damien Georgece1162a2014-02-26 22:55:59 +0000604 int c = ' ';
605 switch (ATB_GET_KIND(bl)) {
606 case AT_FREE: c = '.'; break;
607 case AT_HEAD: c = 'h'; break;
Damien Georgedaab6512014-04-25 23:37:55 +0100608 /* this prints the uPy object type of the head block
609 case AT_HEAD: {
610 machine_uint_t *ptr = gc_pool_start + bl * WORDS_PER_BLOCK;
611 if (*ptr == (machine_uint_t)&mp_type_tuple) { c = 'T'; }
612 else if (*ptr == (machine_uint_t)&mp_type_list) { c = 'L'; }
613 else if (*ptr == (machine_uint_t)&mp_type_dict) { c = 'D'; }
614 else if (*ptr == (machine_uint_t)&mp_type_float) { c = 'F'; }
615 else if (*ptr == (machine_uint_t)&mp_type_fun_bc) { c = 'B'; }
616 else { c = 'h'; }
617 break;
618 }
619 */
Damien Georgece1162a2014-02-26 22:55:59 +0000620 case AT_TAIL: c = 't'; break;
621 case AT_MARK: c = 'm'; break;
622 }
623 printf("%c", c);
Damieneefcc792013-10-22 15:25:25 +0100624 }
Damien Georgece1162a2014-02-26 22:55:59 +0000625 printf("\n");
Damieneefcc792013-10-22 15:25:25 +0100626}
627
Damien Georgece1162a2014-02-26 22:55:59 +0000628#if DEBUG_PRINT
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200629void gc_test(void) {
630 machine_uint_t len = 500;
Damiendcced922013-10-21 23:45:08 +0100631 machine_uint_t *heap = malloc(len);
632 gc_init(heap, heap + len / sizeof(machine_uint_t));
633 void *ptrs[100];
634 {
Damien George12bab722014-04-05 20:35:48 +0100635 machine_uint_t **p = gc_alloc(16, false);
636 p[0] = gc_alloc(64, false);
637 p[1] = gc_alloc(1, false);
638 p[2] = gc_alloc(1, false);
639 p[3] = gc_alloc(1, false);
640 machine_uint_t ***p2 = gc_alloc(16, false);
Damiendcced922013-10-21 23:45:08 +0100641 p2[0] = p;
642 p2[1] = p;
643 ptrs[0] = p2;
644 }
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200645 for (int i = 0; i < 25; i+=2) {
Damien George12bab722014-04-05 20:35:48 +0100646 machine_uint_t *p = gc_alloc(i, false);
Damiendcced922013-10-21 23:45:08 +0100647 printf("p=%p\n", p);
648 if (i & 3) {
649 //ptrs[i] = p;
650 }
651 }
652
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200653 printf("Before GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000654 gc_dump_alloc_table();
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200655 printf("Starting GC...\n");
656 gc_collect_start();
657 gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void*));
658 gc_collect_end();
659 printf("After GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000660 gc_dump_alloc_table();
Damiendcced922013-10-21 23:45:08 +0100661}
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200662#endif
Damien Georged3ebe482014-01-07 15:20:33 +0000663
664#endif // MICROPY_ENABLE_GC