blob: 53bcb069a953c781b2448d7b8db3faf00ff10062 [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 "qstr.h"
37#include "obj.h"
muxcc849f72014-04-05 15:49:03 +020038#include "runtime.h"
mux4f7e9f52014-04-03 23:55:12 +020039
Damien Georged3ebe482014-01-07 15:20:33 +000040#if MICROPY_ENABLE_GC
41
stijn9acb5e42014-06-18 12:29:03 +020042#if 0 // print debugging info
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020043#define DEBUG_PRINT (1)
Paul Sokolovsky44739e22014-02-16 18:11:42 +020044#define DEBUG_printf DEBUG_printf
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020045#else // don't print debugging info
Damien George41eb6082014-02-26 22:40:35 +000046#define DEBUG_printf(...) (void)0
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +020047#endif
48
Damiendcced922013-10-21 23:45:08 +010049#define WORDS_PER_BLOCK (4)
50#define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
51#define STACK_SIZE (64) // tunable; minimum is 1
52
Paul Sokolovsky520e2f52014-02-12 18:31:30 +020053STATIC byte *gc_alloc_table_start;
Damien George40f3c022014-07-03 13:25:24 +010054STATIC mp_uint_t gc_alloc_table_byte_len;
Damien George12bab722014-04-05 20:35:48 +010055#if MICROPY_ENABLE_FINALISER
56STATIC byte *gc_finaliser_table_start;
57#endif
Damien George40f3c022014-07-03 13:25:24 +010058STATIC mp_uint_t *gc_pool_start;
59STATIC mp_uint_t *gc_pool_end;
Damiendcced922013-10-21 23:45:08 +010060
Paul Sokolovsky520e2f52014-02-12 18:31:30 +020061STATIC int gc_stack_overflow;
Damien George40f3c022014-07-03 13:25:24 +010062STATIC mp_uint_t gc_stack[STACK_SIZE];
63STATIC mp_uint_t *gc_sp;
64STATIC mp_uint_t gc_lock_depth;
Damien Georged5e7f6e2014-08-22 18:17:02 +010065STATIC mp_uint_t gc_last_free_atb_index;
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
Damien George40f3c022014-07-03 13:25:24 +010097#define BLOCK_FROM_PTR(ptr) (((ptr) - (mp_uint_t)gc_pool_start) / BYTES_PER_BLOCK)
98#define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (mp_uint_t)gc_pool_start))
Damiendcced922013-10-21 23:45:08 +010099#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
Damien George40f3c022014-07-03 13:25:24 +0100115 end = (void*)((mp_uint_t)end & (~(BYTES_PER_BLOCK - 1)));
stijndef10ce2014-06-18 10:20:41 +0200116 DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte*)end - (byte*)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)
Damien George40f3c022014-07-03 13:25:24 +0100123 mp_uint_t total_byte_len = (byte*)end - (byte*)start;
Damien George12bab722014-04-05 20:35:48 +0100124#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
Damienbb5316b2013-10-22 21:12:29 +0100130 gc_alloc_table_start = (byte*)start;
mux4f7e9f52014-04-03 23:55:12 +0200131
Damien George12bab722014-04-05 20:35:48 +0100132#if MICROPY_ENABLE_FINALISER
Damien Georgea1d3ee32014-08-08 12:33:49 +0100133 mp_uint_t gc_finaliser_table_byte_len = (gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB;
Damien George12bab722014-04-05 20:35:48 +0100134 gc_finaliser_table_start = gc_alloc_table_start + gc_alloc_table_byte_len;
135#endif
mux4f7e9f52014-04-03 23:55:12 +0200136
Damien George40f3c022014-07-03 13:25:24 +0100137 mp_uint_t gc_pool_block_len = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
138 gc_pool_start = (mp_uint_t*)((byte*)end - gc_pool_block_len * BYTES_PER_BLOCK);
139 gc_pool_end = (mp_uint_t*)end;
Damienbb5316b2013-10-22 21:12:29 +0100140
Damien Georgea1d3ee32014-08-08 12:33:49 +0100141#if MICROPY_ENABLE_FINALISER
142 assert((byte*)gc_pool_start >= gc_finaliser_table_start + gc_finaliser_table_byte_len);
143#endif
144
Damienbb5316b2013-10-22 21:12:29 +0100145 // clear ATBs
146 memset(gc_alloc_table_start, 0, gc_alloc_table_byte_len);
147
Damien George12bab722014-04-05 20:35:48 +0100148#if MICROPY_ENABLE_FINALISER
149 // clear FTBs
150 memset(gc_finaliser_table_start, 0, gc_finaliser_table_byte_len);
151#endif
mux4f7e9f52014-04-03 23:55:12 +0200152
Damienbb5316b2013-10-22 21:12:29 +0100153 // allocate first block because gc_pool_start points there and it will never
154 // be freed, so allocating 1 block with null pointers will minimise memory loss
155 ATB_FREE_TO_HEAD(0);
156 for (int i = 0; i < WORDS_PER_BLOCK; i++) {
157 gc_pool_start[i] = 0;
158 }
159
Damien Georged5e7f6e2014-08-22 18:17:02 +0100160 // set last free ATB index to start of heap
161 gc_last_free_atb_index = 0;
162
Damien George12bab722014-04-05 20:35:48 +0100163 // unlock the GC
Damien George443e0182014-04-08 11:31:21 +0000164 gc_lock_depth = 0;
Damien George12bab722014-04-05 20:35:48 +0100165
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200166 DEBUG_printf("GC layout:\n");
Damien George12bab722014-04-05 20:35:48 +0100167 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);
168#if MICROPY_ENABLE_FINALISER
169 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);
170#endif
171 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 +0100172}
173
Damien George443e0182014-04-08 11:31:21 +0000174void gc_lock(void) {
175 gc_lock_depth++;
176}
177
178void gc_unlock(void) {
179 gc_lock_depth--;
180}
181
Dave Hylands2fe841d2014-06-30 22:49:21 -0700182bool gc_is_locked(void) {
183 return gc_lock_depth != 0;
184}
185
Damienfd8b6bc2013-10-22 20:26:36 +0100186#define VERIFY_PTR(ptr) ( \
187 (ptr & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \
Damien George40f3c022014-07-03 13:25:24 +0100188 && ptr >= (mp_uint_t)gc_pool_start /* must be above start of pool */ \
189 && ptr < (mp_uint_t)gc_pool_end /* must be below end of pool */ \
Damienfd8b6bc2013-10-22 20:26:36 +0100190 )
191
Damiendcced922013-10-21 23:45:08 +0100192#define VERIFY_MARK_AND_PUSH(ptr) \
193 do { \
Damienfd8b6bc2013-10-22 20:26:36 +0100194 if (VERIFY_PTR(ptr)) { \
Damien George40f3c022014-07-03 13:25:24 +0100195 mp_uint_t _block = BLOCK_FROM_PTR(ptr); \
Damiendcced922013-10-21 23:45:08 +0100196 if (ATB_GET_KIND(_block) == AT_HEAD) { \
197 /* an unmarked head, mark it, and push it on gc stack */ \
198 ATB_HEAD_TO_MARK(_block); \
199 if (gc_sp < &gc_stack[STACK_SIZE]) { \
200 *gc_sp++ = _block; \
201 } else { \
202 gc_stack_overflow = 1; \
203 } \
204 } \
205 } \
206 } while (0)
207
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200208STATIC void gc_drain_stack(void) {
Damiendcced922013-10-21 23:45:08 +0100209 while (gc_sp > gc_stack) {
210 // pop the next block off the stack
Damien George40f3c022014-07-03 13:25:24 +0100211 mp_uint_t block = *--gc_sp;
Damiendcced922013-10-21 23:45:08 +0100212
Damieneefcc792013-10-22 15:25:25 +0100213 // work out number of consecutive blocks in the chain starting with this one
Damien George40f3c022014-07-03 13:25:24 +0100214 mp_uint_t n_blocks = 0;
Damiendcced922013-10-21 23:45:08 +0100215 do {
216 n_blocks += 1;
217 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
218
219 // check this block's children
Damien George40f3c022014-07-03 13:25:24 +0100220 mp_uint_t *scan = (mp_uint_t*)PTR_FROM_BLOCK(block);
221 for (mp_uint_t i = n_blocks * WORDS_PER_BLOCK; i > 0; i--, scan++) {
222 mp_uint_t ptr2 = *scan;
Damiendcced922013-10-21 23:45:08 +0100223 VERIFY_MARK_AND_PUSH(ptr2);
224 }
225 }
226}
227
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200228STATIC void gc_deal_with_stack_overflow(void) {
Damiendcced922013-10-21 23:45:08 +0100229 while (gc_stack_overflow) {
230 gc_stack_overflow = 0;
231 gc_sp = gc_stack;
232
233 // scan entire memory looking for blocks which have been marked but not their children
Damien George40f3c022014-07-03 13:25:24 +0100234 for (mp_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
Damiendcced922013-10-21 23:45:08 +0100235 // trace (again) if mark bit set
236 if (ATB_GET_KIND(block) == AT_MARK) {
237 *gc_sp++ = block;
238 gc_drain_stack();
239 }
240 }
241 }
242}
243
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300244#if MICROPY_PY_GC_COLLECT_RETVAL
245uint gc_collected;
246#endif
247
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200248STATIC void gc_sweep(void) {
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300249 #if MICROPY_PY_GC_COLLECT_RETVAL
250 gc_collected = 0;
251 #endif
Damiendcced922013-10-21 23:45:08 +0100252 // free unmarked heads and their tails
253 int free_tail = 0;
Damien George40f3c022014-07-03 13:25:24 +0100254 for (mp_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
Damiendcced922013-10-21 23:45:08 +0100255 switch (ATB_GET_KIND(block)) {
256 case AT_HEAD:
Damien George12bab722014-04-05 20:35:48 +0100257#if MICROPY_ENABLE_FINALISER
258 if (FTB_GET(block)) {
259 mp_obj_t obj = (mp_obj_t)PTR_FROM_BLOCK(block);
260 if (((mp_obj_base_t*)obj)->type != MP_OBJ_NULL) {
261 // if the object has a type then see if it has a __del__ method
262 mp_obj_t dest[2];
263 mp_load_method_maybe(obj, MP_QSTR___del__, dest);
264 if (dest[0] != MP_OBJ_NULL) {
265 // load_method returned a method
266 mp_call_method_n_kw(0, 0, dest);
267 }
mux4f7e9f52014-04-03 23:55:12 +0200268 }
Damien George12bab722014-04-05 20:35:48 +0100269 // clear finaliser flag
270 FTB_CLEAR(block);
mux4f7e9f52014-04-03 23:55:12 +0200271 }
Damien George12bab722014-04-05 20:35:48 +0100272#endif
Damiendcced922013-10-21 23:45:08 +0100273 free_tail = 1;
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300274 #if MICROPY_PY_GC_COLLECT_RETVAL
275 gc_collected++;
276 #endif
Damiendcced922013-10-21 23:45:08 +0100277 // fall through to free the head
278
279 case AT_TAIL:
280 if (free_tail) {
stijnbbcea3f2014-06-16 10:44:29 +0200281 DEBUG_printf("gc_sweep(%p)\n",PTR_FROM_BLOCK(block));
Damiendcced922013-10-21 23:45:08 +0100282 ATB_ANY_TO_FREE(block);
283 }
284 break;
285
286 case AT_MARK:
287 ATB_MARK_TO_HEAD(block);
288 free_tail = 0;
289 break;
290 }
291 }
292}
293
Damien8b3a7c22013-10-23 20:20:17 +0100294void gc_collect_start(void) {
Damien George443e0182014-04-08 11:31:21 +0000295 gc_lock();
Damiendcced922013-10-21 23:45:08 +0100296 gc_stack_overflow = 0;
297 gc_sp = gc_stack;
298}
299
Damien George40f3c022014-07-03 13:25:24 +0100300void gc_collect_root(void **ptrs, mp_uint_t len) {
301 for (mp_uint_t i = 0; i < len; i++) {
302 mp_uint_t ptr = (mp_uint_t)ptrs[i];
Damiendcced922013-10-21 23:45:08 +0100303 VERIFY_MARK_AND_PUSH(ptr);
304 gc_drain_stack();
305 }
306}
307
Damien8b3a7c22013-10-23 20:20:17 +0100308void gc_collect_end(void) {
Damiendcced922013-10-21 23:45:08 +0100309 gc_deal_with_stack_overflow();
310 gc_sweep();
Damien Georged5e7f6e2014-08-22 18:17:02 +0100311 gc_last_free_atb_index = 0;
Damien George443e0182014-04-08 11:31:21 +0000312 gc_unlock();
Damieneefcc792013-10-22 15:25:25 +0100313}
Damiendcced922013-10-21 23:45:08 +0100314
Damieneefcc792013-10-22 15:25:25 +0100315void gc_info(gc_info_t *info) {
Damien George40f3c022014-07-03 13:25:24 +0100316 info->total = (gc_pool_end - gc_pool_start) * sizeof(mp_uint_t);
Damieneefcc792013-10-22 15:25:25 +0100317 info->used = 0;
318 info->free = 0;
319 info->num_1block = 0;
320 info->num_2block = 0;
321 info->max_block = 0;
Damien George40f3c022014-07-03 13:25:24 +0100322 for (mp_uint_t block = 0, len = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
323 mp_uint_t kind = ATB_GET_KIND(block);
Damieneefcc792013-10-22 15:25:25 +0100324 if (kind == AT_FREE || kind == AT_HEAD) {
325 if (len == 1) {
326 info->num_1block += 1;
327 } else if (len == 2) {
328 info->num_2block += 1;
329 }
330 if (len > info->max_block) {
331 info->max_block = len;
332 }
333 }
334 switch (kind) {
Damiendcced922013-10-21 23:45:08 +0100335 case AT_FREE:
Damieneefcc792013-10-22 15:25:25 +0100336 info->free += 1;
337 len = 0;
Damiendcced922013-10-21 23:45:08 +0100338 break;
339
340 case AT_HEAD:
Damieneefcc792013-10-22 15:25:25 +0100341 info->used += 1;
342 len = 1;
343 break;
344
Damiendcced922013-10-21 23:45:08 +0100345 case AT_TAIL:
Damieneefcc792013-10-22 15:25:25 +0100346 info->used += 1;
347 len += 1;
Damiendcced922013-10-21 23:45:08 +0100348 break;
349
350 case AT_MARK:
Damieneefcc792013-10-22 15:25:25 +0100351 // shouldn't happen
Damiendcced922013-10-21 23:45:08 +0100352 break;
353 }
354 }
355
Damieneefcc792013-10-22 15:25:25 +0100356 info->used *= BYTES_PER_BLOCK;
357 info->free *= BYTES_PER_BLOCK;
Damiendcced922013-10-21 23:45:08 +0100358}
359
Damien George40f3c022014-07-03 13:25:24 +0100360void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
361 mp_uint_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK;
stijndef10ce2014-06-18 10:20:41 +0200362 DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks);
Damiendcced922013-10-21 23:45:08 +0100363
Damien George443e0182014-04-08 11:31:21 +0000364 // check if GC is locked
365 if (gc_lock_depth > 0) {
366 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100367 }
368
Damiendcced922013-10-21 23:45:08 +0100369 // check for 0 allocation
370 if (n_blocks == 0) {
371 return NULL;
372 }
373
Damien George40f3c022014-07-03 13:25:24 +0100374 mp_uint_t i;
375 mp_uint_t end_block;
376 mp_uint_t start_block;
377 mp_uint_t n_free = 0;
Damiendcced922013-10-21 23:45:08 +0100378 int collected = 0;
379 for (;;) {
380
381 // look for a run of n_blocks available blocks
Damien Georged5e7f6e2014-08-22 18:17:02 +0100382 for (i = gc_last_free_atb_index; i < gc_alloc_table_byte_len; i++) {
383 byte a = gc_alloc_table_start[i];
384 if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
385 if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
386 if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; }
387 if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; }
388 }
Damiendcced922013-10-21 23:45:08 +0100389
390 // nothing found!
391 if (collected) {
392 return NULL;
393 }
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200394 DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes);
Damiendcced922013-10-21 23:45:08 +0100395 gc_collect();
396 collected = 1;
397 }
398
399 // found, ending at block i inclusive
400found:
401 // get starting and end blocks, both inclusive
402 end_block = i;
403 start_block = i - n_free + 1;
404
Damien Georgeb796e3d2014-08-28 10:18:40 +0100405 // Set last free ATB index to block after last block we found, for start of
406 // next scan. To reduce fragmentation, we only do this if we were looking
407 // for a single free block, which guarantees that there are no free blocks
408 // before this one.
409 if (n_free == 1) {
410 gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
411 }
Damien Georged5e7f6e2014-08-22 18:17:02 +0100412
Damiendcced922013-10-21 23:45:08 +0100413 // mark first block as used head
414 ATB_FREE_TO_HEAD(start_block);
415
416 // mark rest of blocks as used tail
417 // TODO for a run of many blocks can make this more efficient
Damien George40f3c022014-07-03 13:25:24 +0100418 for (mp_uint_t bl = start_block + 1; bl <= end_block; bl++) {
Damiendcced922013-10-21 23:45:08 +0100419 ATB_FREE_TO_TAIL(bl);
420 }
421
Damien George12bab722014-04-05 20:35:48 +0100422 // get pointer to first block
Damien Georgec0376942014-06-13 22:33:31 +0100423 void *ret_ptr = (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK);
stijndef10ce2014-06-18 10:20:41 +0200424 DEBUG_printf("gc_alloc(%p)\n", ret_ptr);
muxcc849f72014-04-05 15:49:03 +0200425
Damien George32bef312014-04-26 22:23:42 +0100426 // zero out the additional bytes of the newly allocated blocks
Damien Georgedaab6512014-04-25 23:37:55 +0100427 // This is needed because the blocks may have previously held pointers
428 // to the heap and will not be set to something else if the caller
429 // doesn't actually use the entire block. As such they will continue
430 // to point to the heap and may prevent other blocks from being reclaimed.
Damien Georgec0376942014-06-13 22:33:31 +0100431 memset((byte*)ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100432
Damien George12bab722014-04-05 20:35:48 +0100433#if MICROPY_ENABLE_FINALISER
434 if (has_finaliser) {
Damien George32bef312014-04-26 22:23:42 +0100435 // clear type pointer in case it is never set
436 ((mp_obj_base_t*)ret_ptr)->type = MP_OBJ_NULL;
Damien George12bab722014-04-05 20:35:48 +0100437 // set mp_obj flag only if it has a finaliser
438 FTB_SET(start_block);
439 }
440#endif
441
442 return ret_ptr;
Damiendcced922013-10-21 23:45:08 +0100443}
444
Damien George12bab722014-04-05 20:35:48 +0100445/*
Damien George40f3c022014-07-03 13:25:24 +0100446void *gc_alloc(mp_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200447 return _gc_alloc(n_bytes, false);
448}
449
Damien George40f3c022014-07-03 13:25:24 +0100450void *gc_alloc_with_finaliser(mp_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200451 return _gc_alloc(n_bytes, true);
452}
Damien George12bab722014-04-05 20:35:48 +0100453*/
mux4f7e9f52014-04-03 23:55:12 +0200454
Damienfd8b6bc2013-10-22 20:26:36 +0100455// force the freeing of a piece of memory
456void gc_free(void *ptr_in) {
Damien George443e0182014-04-08 11:31:21 +0000457 if (gc_lock_depth > 0) {
458 // TODO how to deal with this error?
459 return;
Damien George12bab722014-04-05 20:35:48 +0100460 }
461
Damien George40f3c022014-07-03 13:25:24 +0100462 mp_uint_t ptr = (mp_uint_t)ptr_in;
stijnbbcea3f2014-06-16 10:44:29 +0200463 DEBUG_printf("gc_free(%p)\n", ptr);
Damienfd8b6bc2013-10-22 20:26:36 +0100464
465 if (VERIFY_PTR(ptr)) {
Damien George40f3c022014-07-03 13:25:24 +0100466 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Damienfd8b6bc2013-10-22 20:26:36 +0100467 if (ATB_GET_KIND(block) == AT_HEAD) {
468 // free head and all of its tail blocks
469 do {
470 ATB_ANY_TO_FREE(block);
471 block += 1;
472 } while (ATB_GET_KIND(block) == AT_TAIL);
473 }
474 }
475}
476
Damien George40f3c022014-07-03 13:25:24 +0100477mp_uint_t gc_nbytes(void *ptr_in) {
478 mp_uint_t ptr = (mp_uint_t)ptr_in;
Damiendcced922013-10-21 23:45:08 +0100479
Damienfd8b6bc2013-10-22 20:26:36 +0100480 if (VERIFY_PTR(ptr)) {
Damien George40f3c022014-07-03 13:25:24 +0100481 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Damiendcced922013-10-21 23:45:08 +0100482 if (ATB_GET_KIND(block) == AT_HEAD) {
483 // work out number of consecutive blocks in the chain starting with this on
Damien George40f3c022014-07-03 13:25:24 +0100484 mp_uint_t n_blocks = 0;
Damiendcced922013-10-21 23:45:08 +0100485 do {
486 n_blocks += 1;
487 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
488 return n_blocks * BYTES_PER_BLOCK;
489 }
490 }
491
492 // invalid pointer
493 return 0;
494}
495
mux87826762014-03-12 21:00:23 +0200496#if 0
Damien George443e0182014-04-08 11:31:21 +0000497// old, simple realloc that didn't expand memory in place
Damien George40f3c022014-07-03 13:25:24 +0100498void *gc_realloc(void *ptr, mp_uint_t n_bytes) {
499 mp_uint_t n_existing = gc_nbytes(ptr);
Damien George6fc765c2014-03-07 00:21:51 +0000500 if (n_bytes <= n_existing) {
501 return ptr;
502 } else {
Damien George410f3072014-04-25 11:44:53 +0000503 bool has_finaliser;
504 if (ptr == NULL) {
505 has_finaliser = false;
506 } else {
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300507#if MICROPY_ENABLE_FINALISER
Damien George40f3c022014-07-03 13:25:24 +0100508 has_finaliser = FTB_GET(BLOCK_FROM_PTR((mp_uint_t)ptr));
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300509#else
Damien George410f3072014-04-25 11:44:53 +0000510 has_finaliser = false;
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300511#endif
Damien George410f3072014-04-25 11:44:53 +0000512 }
513 void *ptr2 = gc_alloc(n_bytes, has_finaliser);
Damien George6fc765c2014-03-07 00:21:51 +0000514 if (ptr2 == NULL) {
515 return ptr2;
516 }
517 memcpy(ptr2, ptr, n_existing);
518 gc_free(ptr);
519 return ptr2;
520 }
521}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300522
523#else // Alternative gc_realloc impl
Damien George443e0182014-04-08 11:31:21 +0000524
Damien George40f3c022014-07-03 13:25:24 +0100525void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
Damien George443e0182014-04-08 11:31:21 +0000526 if (gc_lock_depth > 0) {
527 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100528 }
529
Damien Georgedde739d2014-04-20 18:16:25 +0100530 // check for pure allocation
muxfbaa1472014-03-05 23:23:04 +0200531 if (ptr_in == NULL) {
Damien George12bab722014-04-05 20:35:48 +0100532 return gc_alloc(n_bytes, false);
Damiendcced922013-10-21 23:45:08 +0100533 }
muxfbaa1472014-03-05 23:23:04 +0200534
Damien George40f3c022014-07-03 13:25:24 +0100535 mp_uint_t ptr = (mp_uint_t)ptr_in;
Damien Georgedde739d2014-04-20 18:16:25 +0100536
537 // sanity check the ptr
538 if (!VERIFY_PTR(ptr)) {
539 return NULL;
540 }
541
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300542 // get first block
Damien George40f3c022014-07-03 13:25:24 +0100543 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300544
Damien Georgedde739d2014-04-20 18:16:25 +0100545 // sanity check the ptr is pointing to the head of a block
546 if (ATB_GET_KIND(block) != AT_HEAD) {
547 return NULL;
muxfbaa1472014-03-05 23:23:04 +0200548 }
549
Damien Georgedde739d2014-04-20 18:16:25 +0100550 // compute number of new blocks that are requested
Damien George40f3c022014-07-03 13:25:24 +0100551 mp_uint_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK;
Damien Georgedde739d2014-04-20 18:16:25 +0100552
553 // get the number of consecutive tail blocks and
554 // the number of free blocks after last tail block
555 // stop if we reach (or are at) end of heap
Damien George40f3c022014-07-03 13:25:24 +0100556 mp_uint_t n_free = 0;
557 mp_uint_t n_blocks = 1; // counting HEAD block
558 mp_uint_t max_block = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
Damien Georgedde739d2014-04-20 18:16:25 +0100559 while (block + n_blocks + n_free < max_block) {
560 if (n_blocks + n_free >= new_blocks) {
561 // stop as soon as we find enough blocks for n_bytes
562 break;
563 }
564 byte block_type = ATB_GET_KIND(block + n_blocks + n_free);
565 switch (block_type) {
566 case AT_FREE: n_free++; continue;
567 case AT_TAIL: n_blocks++; continue;
568 case AT_MARK: assert(0);
569 }
570 break;
571 }
572
573 // return original ptr if it already has the requested number of blocks
574 if (new_blocks == n_blocks) {
575 return ptr_in;
576 }
577
578 // check if we can shrink the allocated area
579 if (new_blocks < n_blocks) {
580 // free unneeded tail blocks
Damien George40f3c022014-07-03 13:25:24 +0100581 for (mp_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
Damien Georgedde739d2014-04-20 18:16:25 +0100582 ATB_ANY_TO_FREE(bl);
583 }
584 return ptr_in;
585 }
586
587 // check if we can expand in place
588 if (new_blocks <= n_blocks + n_free) {
589 // mark few more blocks as used tail
Damien George40f3c022014-07-03 13:25:24 +0100590 for (mp_uint_t bl = block + n_blocks; bl < block + new_blocks; bl++) {
Damien Georgedde739d2014-04-20 18:16:25 +0100591 assert(ATB_GET_KIND(bl) == AT_FREE);
592 ATB_FREE_TO_TAIL(bl);
593 }
Damien Georgedaab6512014-04-25 23:37:55 +0100594
Damien George32bef312014-04-26 22:23:42 +0100595 // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
stijnf33385f2014-06-12 17:42:20 +0200596 memset((byte*)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100597
Damien Georgedde739d2014-04-20 18:16:25 +0100598 return ptr_in;
599 }
600
601 // can't resize inplace; try to find a new contiguous chain
602 void *ptr_out = gc_alloc(n_bytes,
603#if MICROPY_ENABLE_FINALISER
604 FTB_GET(block)
605#else
606 false
607#endif
608 );
609
610 // check that the alloc succeeded
611 if (ptr_out == NULL) {
612 return NULL;
613 }
614
stijnbbcea3f2014-06-16 10:44:29 +0200615 DEBUG_printf("gc_realloc(%p -> %p)\n", ptr_in, ptr_out);
Damien Georgedde739d2014-04-20 18:16:25 +0100616 memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK);
617 gc_free(ptr_in);
618 return ptr_out;
Damiendcced922013-10-21 23:45:08 +0100619}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300620#endif // Alternative gc_realloc impl
mux87826762014-03-12 21:00:23 +0200621
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200622void gc_dump_info() {
623 gc_info_t info;
624 gc_info(&info);
625 printf("GC: total: " UINT_FMT ", used: " UINT_FMT ", free: " UINT_FMT "\n", info.total, info.used, info.free);
626 printf(" No. of 1-blocks: " UINT_FMT ", 2-blocks: " UINT_FMT ", max blk sz: " UINT_FMT "\n",
627 info.num_1block, info.num_2block, info.max_block);
628}
629
Damien Georgece1162a2014-02-26 22:55:59 +0000630void gc_dump_alloc_table(void) {
Damien Georgedaab6512014-04-25 23:37:55 +0100631 printf("GC memory layout; from %p:", gc_pool_start);
Damien George40f3c022014-07-03 13:25:24 +0100632 for (mp_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
Damien Georgece1162a2014-02-26 22:55:59 +0000633 if (bl % 64 == 0) {
634 printf("\n%04x: ", (uint)bl);
Damieneefcc792013-10-22 15:25:25 +0100635 }
Damien Georgece1162a2014-02-26 22:55:59 +0000636 int c = ' ';
637 switch (ATB_GET_KIND(bl)) {
638 case AT_FREE: c = '.'; break;
639 case AT_HEAD: c = 'h'; break;
Damien Georgedaab6512014-04-25 23:37:55 +0100640 /* this prints the uPy object type of the head block
641 case AT_HEAD: {
Damien George40f3c022014-07-03 13:25:24 +0100642 mp_uint_t *ptr = gc_pool_start + bl * WORDS_PER_BLOCK;
643 if (*ptr == (mp_uint_t)&mp_type_tuple) { c = 'T'; }
644 else if (*ptr == (mp_uint_t)&mp_type_list) { c = 'L'; }
645 else if (*ptr == (mp_uint_t)&mp_type_dict) { c = 'D'; }
646 else if (*ptr == (mp_uint_t)&mp_type_float) { c = 'F'; }
647 else if (*ptr == (mp_uint_t)&mp_type_fun_bc) { c = 'B'; }
Damien Georgedaab6512014-04-25 23:37:55 +0100648 else { c = 'h'; }
649 break;
650 }
651 */
Damien Georgece1162a2014-02-26 22:55:59 +0000652 case AT_TAIL: c = 't'; break;
653 case AT_MARK: c = 'm'; break;
654 }
655 printf("%c", c);
Damieneefcc792013-10-22 15:25:25 +0100656 }
Damien Georgece1162a2014-02-26 22:55:59 +0000657 printf("\n");
Damieneefcc792013-10-22 15:25:25 +0100658}
659
Damien Georgece1162a2014-02-26 22:55:59 +0000660#if DEBUG_PRINT
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200661void gc_test(void) {
Damien George40f3c022014-07-03 13:25:24 +0100662 mp_uint_t len = 500;
663 mp_uint_t *heap = malloc(len);
664 gc_init(heap, heap + len / sizeof(mp_uint_t));
Damiendcced922013-10-21 23:45:08 +0100665 void *ptrs[100];
666 {
Damien George40f3c022014-07-03 13:25:24 +0100667 mp_uint_t **p = gc_alloc(16, false);
Damien George12bab722014-04-05 20:35:48 +0100668 p[0] = gc_alloc(64, false);
669 p[1] = gc_alloc(1, false);
670 p[2] = gc_alloc(1, false);
671 p[3] = gc_alloc(1, false);
Damien George40f3c022014-07-03 13:25:24 +0100672 mp_uint_t ***p2 = gc_alloc(16, false);
Damiendcced922013-10-21 23:45:08 +0100673 p2[0] = p;
674 p2[1] = p;
675 ptrs[0] = p2;
676 }
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200677 for (int i = 0; i < 25; i+=2) {
Damien George40f3c022014-07-03 13:25:24 +0100678 mp_uint_t *p = gc_alloc(i, false);
Damiendcced922013-10-21 23:45:08 +0100679 printf("p=%p\n", p);
680 if (i & 3) {
681 //ptrs[i] = p;
682 }
683 }
684
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200685 printf("Before GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000686 gc_dump_alloc_table();
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200687 printf("Starting GC...\n");
688 gc_collect_start();
689 gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void*));
690 gc_collect_end();
691 printf("After GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000692 gc_dump_alloc_table();
Damiendcced922013-10-21 23:45:08 +0100693}
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200694#endif
Damien Georged3ebe482014-01-07 15:20:33 +0000695
696#endif // MICROPY_ENABLE_GC