blob: 802c1b1edc5831adaffba1472b0a767d25f2c6df [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;
Damiendcced922013-10-21 23:45:08 +010065
Damiendcced922013-10-21 23:45:08 +010066// ATB = allocation table byte
67// 0b00 = FREE -- free block
68// 0b01 = HEAD -- head of a chain of blocks
69// 0b10 = TAIL -- in the tail of a chain of blocks
70// 0b11 = MARK -- marked head block
71
72#define AT_FREE (0)
73#define AT_HEAD (1)
74#define AT_TAIL (2)
75#define AT_MARK (3)
76
77#define BLOCKS_PER_ATB (4)
78#define ATB_MASK_0 (0x03)
79#define ATB_MASK_1 (0x0c)
80#define ATB_MASK_2 (0x30)
81#define ATB_MASK_3 (0xc0)
82
83#define ATB_0_IS_FREE(a) (((a) & ATB_MASK_0) == 0)
84#define ATB_1_IS_FREE(a) (((a) & ATB_MASK_1) == 0)
85#define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0)
86#define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0)
87
88#define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1)))
89#define ATB_GET_KIND(block) ((gc_alloc_table_start[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3)
90#define ATB_ANY_TO_FREE(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0)
91#define ATB_FREE_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0)
92#define ATB_FREE_TO_TAIL(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0)
93#define ATB_HEAD_TO_MARK(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0)
94#define ATB_MARK_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0)
95
Damien George40f3c022014-07-03 13:25:24 +010096#define BLOCK_FROM_PTR(ptr) (((ptr) - (mp_uint_t)gc_pool_start) / BYTES_PER_BLOCK)
97#define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (mp_uint_t)gc_pool_start))
Damiendcced922013-10-21 23:45:08 +010098#define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB)
99
Damien George12bab722014-04-05 20:35:48 +0100100#if MICROPY_ENABLE_FINALISER
101// FTB = finaliser table byte
102// if set, then the corresponding block may have a finaliser
103
104#define BLOCKS_PER_FTB (8)
105
106#define FTB_GET(block) ((gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1)
107#define FTB_SET(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0)
108#define FTB_CLEAR(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0)
109#endif
110
Damienbb5316b2013-10-22 21:12:29 +0100111// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool
112void gc_init(void *start, void *end) {
113 // align end pointer on block boundary
Damien George40f3c022014-07-03 13:25:24 +0100114 end = (void*)((mp_uint_t)end & (~(BYTES_PER_BLOCK - 1)));
stijndef10ce2014-06-18 10:20:41 +0200115 DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte*)end - (byte*)start);
Damienbb5316b2013-10-22 21:12:29 +0100116
Damien George12bab722014-04-05 20:35:48 +0100117 // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes):
118 // T = A + F + P
119 // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB
120 // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK
121 // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
Damien George40f3c022014-07-03 13:25:24 +0100122 mp_uint_t total_byte_len = (byte*)end - (byte*)start;
Damien George12bab722014-04-05 20:35:48 +0100123#if MICROPY_ENABLE_FINALISER
124 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);
125#else
126 gc_alloc_table_byte_len = total_byte_len / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK);
127#endif
128
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 George40f3c022014-07-03 13:25:24 +0100133 mp_uint_t gc_finaliser_table_byte_len = (gc_alloc_table_byte_len * BLOCKS_PER_ATB) / 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
141 // clear ATBs
142 memset(gc_alloc_table_start, 0, gc_alloc_table_byte_len);
143
Damien George12bab722014-04-05 20:35:48 +0100144#if MICROPY_ENABLE_FINALISER
145 // clear FTBs
146 memset(gc_finaliser_table_start, 0, gc_finaliser_table_byte_len);
147#endif
mux4f7e9f52014-04-03 23:55:12 +0200148
Damienbb5316b2013-10-22 21:12:29 +0100149 // allocate first block because gc_pool_start points there and it will never
150 // be freed, so allocating 1 block with null pointers will minimise memory loss
151 ATB_FREE_TO_HEAD(0);
152 for (int i = 0; i < WORDS_PER_BLOCK; i++) {
153 gc_pool_start[i] = 0;
154 }
155
Damien George12bab722014-04-05 20:35:48 +0100156 // unlock the GC
Damien George443e0182014-04-08 11:31:21 +0000157 gc_lock_depth = 0;
Damien George12bab722014-04-05 20:35:48 +0100158
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200159 DEBUG_printf("GC layout:\n");
Damien George12bab722014-04-05 20:35:48 +0100160 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);
161#if MICROPY_ENABLE_FINALISER
162 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);
163#endif
164 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 +0100165}
166
Damien George443e0182014-04-08 11:31:21 +0000167void gc_lock(void) {
168 gc_lock_depth++;
169}
170
171void gc_unlock(void) {
172 gc_lock_depth--;
173}
174
Dave Hylands2fe841d2014-06-30 22:49:21 -0700175bool gc_is_locked(void) {
176 return gc_lock_depth != 0;
177}
178
Damienfd8b6bc2013-10-22 20:26:36 +0100179#define VERIFY_PTR(ptr) ( \
180 (ptr & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \
Damien George40f3c022014-07-03 13:25:24 +0100181 && ptr >= (mp_uint_t)gc_pool_start /* must be above start of pool */ \
182 && ptr < (mp_uint_t)gc_pool_end /* must be below end of pool */ \
Damienfd8b6bc2013-10-22 20:26:36 +0100183 )
184
Damiendcced922013-10-21 23:45:08 +0100185#define VERIFY_MARK_AND_PUSH(ptr) \
186 do { \
Damienfd8b6bc2013-10-22 20:26:36 +0100187 if (VERIFY_PTR(ptr)) { \
Damien George40f3c022014-07-03 13:25:24 +0100188 mp_uint_t _block = BLOCK_FROM_PTR(ptr); \
Damiendcced922013-10-21 23:45:08 +0100189 if (ATB_GET_KIND(_block) == AT_HEAD) { \
190 /* an unmarked head, mark it, and push it on gc stack */ \
191 ATB_HEAD_TO_MARK(_block); \
192 if (gc_sp < &gc_stack[STACK_SIZE]) { \
193 *gc_sp++ = _block; \
194 } else { \
195 gc_stack_overflow = 1; \
196 } \
197 } \
198 } \
199 } while (0)
200
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200201STATIC void gc_drain_stack(void) {
Damiendcced922013-10-21 23:45:08 +0100202 while (gc_sp > gc_stack) {
203 // pop the next block off the stack
Damien George40f3c022014-07-03 13:25:24 +0100204 mp_uint_t block = *--gc_sp;
Damiendcced922013-10-21 23:45:08 +0100205
Damieneefcc792013-10-22 15:25:25 +0100206 // work out number of consecutive blocks in the chain starting with this one
Damien George40f3c022014-07-03 13:25:24 +0100207 mp_uint_t n_blocks = 0;
Damiendcced922013-10-21 23:45:08 +0100208 do {
209 n_blocks += 1;
210 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
211
212 // check this block's children
Damien George40f3c022014-07-03 13:25:24 +0100213 mp_uint_t *scan = (mp_uint_t*)PTR_FROM_BLOCK(block);
214 for (mp_uint_t i = n_blocks * WORDS_PER_BLOCK; i > 0; i--, scan++) {
215 mp_uint_t ptr2 = *scan;
Damiendcced922013-10-21 23:45:08 +0100216 VERIFY_MARK_AND_PUSH(ptr2);
217 }
218 }
219}
220
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200221STATIC void gc_deal_with_stack_overflow(void) {
Damiendcced922013-10-21 23:45:08 +0100222 while (gc_stack_overflow) {
223 gc_stack_overflow = 0;
224 gc_sp = gc_stack;
225
226 // scan entire memory looking for blocks which have been marked but not their children
Damien George40f3c022014-07-03 13:25:24 +0100227 for (mp_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
Damiendcced922013-10-21 23:45:08 +0100228 // trace (again) if mark bit set
229 if (ATB_GET_KIND(block) == AT_MARK) {
230 *gc_sp++ = block;
231 gc_drain_stack();
232 }
233 }
234 }
235}
236
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300237#if MICROPY_PY_GC_COLLECT_RETVAL
238uint gc_collected;
239#endif
240
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200241STATIC void gc_sweep(void) {
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300242 #if MICROPY_PY_GC_COLLECT_RETVAL
243 gc_collected = 0;
244 #endif
Damiendcced922013-10-21 23:45:08 +0100245 // free unmarked heads and their tails
246 int free_tail = 0;
Damien George40f3c022014-07-03 13:25:24 +0100247 for (mp_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
Damiendcced922013-10-21 23:45:08 +0100248 switch (ATB_GET_KIND(block)) {
249 case AT_HEAD:
Damien George12bab722014-04-05 20:35:48 +0100250#if MICROPY_ENABLE_FINALISER
251 if (FTB_GET(block)) {
252 mp_obj_t obj = (mp_obj_t)PTR_FROM_BLOCK(block);
253 if (((mp_obj_base_t*)obj)->type != MP_OBJ_NULL) {
254 // if the object has a type then see if it has a __del__ method
255 mp_obj_t dest[2];
256 mp_load_method_maybe(obj, MP_QSTR___del__, dest);
257 if (dest[0] != MP_OBJ_NULL) {
258 // load_method returned a method
259 mp_call_method_n_kw(0, 0, dest);
260 }
mux4f7e9f52014-04-03 23:55:12 +0200261 }
Damien George12bab722014-04-05 20:35:48 +0100262 // clear finaliser flag
263 FTB_CLEAR(block);
mux4f7e9f52014-04-03 23:55:12 +0200264 }
Damien George12bab722014-04-05 20:35:48 +0100265#endif
Damiendcced922013-10-21 23:45:08 +0100266 free_tail = 1;
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300267 #if MICROPY_PY_GC_COLLECT_RETVAL
268 gc_collected++;
269 #endif
Damiendcced922013-10-21 23:45:08 +0100270 // fall through to free the head
271
272 case AT_TAIL:
273 if (free_tail) {
stijnbbcea3f2014-06-16 10:44:29 +0200274 DEBUG_printf("gc_sweep(%p)\n",PTR_FROM_BLOCK(block));
Damiendcced922013-10-21 23:45:08 +0100275 ATB_ANY_TO_FREE(block);
276 }
277 break;
278
279 case AT_MARK:
280 ATB_MARK_TO_HEAD(block);
281 free_tail = 0;
282 break;
283 }
284 }
285}
286
Damien8b3a7c22013-10-23 20:20:17 +0100287void gc_collect_start(void) {
Damien George443e0182014-04-08 11:31:21 +0000288 gc_lock();
Damiendcced922013-10-21 23:45:08 +0100289 gc_stack_overflow = 0;
290 gc_sp = gc_stack;
291}
292
Damien George40f3c022014-07-03 13:25:24 +0100293void gc_collect_root(void **ptrs, mp_uint_t len) {
294 for (mp_uint_t i = 0; i < len; i++) {
295 mp_uint_t ptr = (mp_uint_t)ptrs[i];
Damiendcced922013-10-21 23:45:08 +0100296 VERIFY_MARK_AND_PUSH(ptr);
297 gc_drain_stack();
298 }
299}
300
Damien8b3a7c22013-10-23 20:20:17 +0100301void gc_collect_end(void) {
Damiendcced922013-10-21 23:45:08 +0100302 gc_deal_with_stack_overflow();
303 gc_sweep();
Damien George443e0182014-04-08 11:31:21 +0000304 gc_unlock();
Damieneefcc792013-10-22 15:25:25 +0100305}
Damiendcced922013-10-21 23:45:08 +0100306
Damieneefcc792013-10-22 15:25:25 +0100307void gc_info(gc_info_t *info) {
Damien George40f3c022014-07-03 13:25:24 +0100308 info->total = (gc_pool_end - gc_pool_start) * sizeof(mp_uint_t);
Damieneefcc792013-10-22 15:25:25 +0100309 info->used = 0;
310 info->free = 0;
311 info->num_1block = 0;
312 info->num_2block = 0;
313 info->max_block = 0;
Damien George40f3c022014-07-03 13:25:24 +0100314 for (mp_uint_t block = 0, len = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
315 mp_uint_t kind = ATB_GET_KIND(block);
Damieneefcc792013-10-22 15:25:25 +0100316 if (kind == AT_FREE || kind == AT_HEAD) {
317 if (len == 1) {
318 info->num_1block += 1;
319 } else if (len == 2) {
320 info->num_2block += 1;
321 }
322 if (len > info->max_block) {
323 info->max_block = len;
324 }
325 }
326 switch (kind) {
Damiendcced922013-10-21 23:45:08 +0100327 case AT_FREE:
Damieneefcc792013-10-22 15:25:25 +0100328 info->free += 1;
329 len = 0;
Damiendcced922013-10-21 23:45:08 +0100330 break;
331
332 case AT_HEAD:
Damieneefcc792013-10-22 15:25:25 +0100333 info->used += 1;
334 len = 1;
335 break;
336
Damiendcced922013-10-21 23:45:08 +0100337 case AT_TAIL:
Damieneefcc792013-10-22 15:25:25 +0100338 info->used += 1;
339 len += 1;
Damiendcced922013-10-21 23:45:08 +0100340 break;
341
342 case AT_MARK:
Damieneefcc792013-10-22 15:25:25 +0100343 // shouldn't happen
Damiendcced922013-10-21 23:45:08 +0100344 break;
345 }
346 }
347
Damieneefcc792013-10-22 15:25:25 +0100348 info->used *= BYTES_PER_BLOCK;
349 info->free *= BYTES_PER_BLOCK;
Damiendcced922013-10-21 23:45:08 +0100350}
351
Damien George40f3c022014-07-03 13:25:24 +0100352void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
353 mp_uint_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK;
stijndef10ce2014-06-18 10:20:41 +0200354 DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks);
Damiendcced922013-10-21 23:45:08 +0100355
Damien George443e0182014-04-08 11:31:21 +0000356 // check if GC is locked
357 if (gc_lock_depth > 0) {
358 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100359 }
360
Damiendcced922013-10-21 23:45:08 +0100361 // check for 0 allocation
362 if (n_blocks == 0) {
363 return NULL;
364 }
365
Damien George40f3c022014-07-03 13:25:24 +0100366 mp_uint_t i;
367 mp_uint_t end_block;
368 mp_uint_t start_block;
369 mp_uint_t n_free = 0;
Damiendcced922013-10-21 23:45:08 +0100370 int collected = 0;
371 for (;;) {
372
373 // look for a run of n_blocks available blocks
374 for (i = 0; i < gc_alloc_table_byte_len; i++) {
375 byte a = gc_alloc_table_start[i];
376 if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
377 if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
378 if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; }
379 if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; }
380 }
381
382 // nothing found!
383 if (collected) {
384 return NULL;
385 }
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200386 DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes);
Damiendcced922013-10-21 23:45:08 +0100387 gc_collect();
388 collected = 1;
389 }
390
391 // found, ending at block i inclusive
392found:
393 // get starting and end blocks, both inclusive
394 end_block = i;
395 start_block = i - n_free + 1;
396
397 // mark first block as used head
398 ATB_FREE_TO_HEAD(start_block);
399
400 // mark rest of blocks as used tail
401 // TODO for a run of many blocks can make this more efficient
Damien George40f3c022014-07-03 13:25:24 +0100402 for (mp_uint_t bl = start_block + 1; bl <= end_block; bl++) {
Damiendcced922013-10-21 23:45:08 +0100403 ATB_FREE_TO_TAIL(bl);
404 }
405
Damien George12bab722014-04-05 20:35:48 +0100406 // get pointer to first block
Damien Georgec0376942014-06-13 22:33:31 +0100407 void *ret_ptr = (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK);
stijndef10ce2014-06-18 10:20:41 +0200408 DEBUG_printf("gc_alloc(%p)\n", ret_ptr);
muxcc849f72014-04-05 15:49:03 +0200409
Damien George32bef312014-04-26 22:23:42 +0100410 // zero out the additional bytes of the newly allocated blocks
Damien Georgedaab6512014-04-25 23:37:55 +0100411 // This is needed because the blocks may have previously held pointers
412 // to the heap and will not be set to something else if the caller
413 // doesn't actually use the entire block. As such they will continue
414 // to point to the heap and may prevent other blocks from being reclaimed.
Damien Georgec0376942014-06-13 22:33:31 +0100415 memset((byte*)ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100416
Damien George12bab722014-04-05 20:35:48 +0100417#if MICROPY_ENABLE_FINALISER
418 if (has_finaliser) {
Damien George32bef312014-04-26 22:23:42 +0100419 // clear type pointer in case it is never set
420 ((mp_obj_base_t*)ret_ptr)->type = MP_OBJ_NULL;
Damien George12bab722014-04-05 20:35:48 +0100421 // set mp_obj flag only if it has a finaliser
422 FTB_SET(start_block);
423 }
424#endif
425
426 return ret_ptr;
Damiendcced922013-10-21 23:45:08 +0100427}
428
Damien George12bab722014-04-05 20:35:48 +0100429/*
Damien George40f3c022014-07-03 13:25:24 +0100430void *gc_alloc(mp_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200431 return _gc_alloc(n_bytes, false);
432}
433
Damien George40f3c022014-07-03 13:25:24 +0100434void *gc_alloc_with_finaliser(mp_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200435 return _gc_alloc(n_bytes, true);
436}
Damien George12bab722014-04-05 20:35:48 +0100437*/
mux4f7e9f52014-04-03 23:55:12 +0200438
Damienfd8b6bc2013-10-22 20:26:36 +0100439// force the freeing of a piece of memory
440void gc_free(void *ptr_in) {
Damien George443e0182014-04-08 11:31:21 +0000441 if (gc_lock_depth > 0) {
442 // TODO how to deal with this error?
443 return;
Damien George12bab722014-04-05 20:35:48 +0100444 }
445
Damien George40f3c022014-07-03 13:25:24 +0100446 mp_uint_t ptr = (mp_uint_t)ptr_in;
stijnbbcea3f2014-06-16 10:44:29 +0200447 DEBUG_printf("gc_free(%p)\n", ptr);
Damienfd8b6bc2013-10-22 20:26:36 +0100448
449 if (VERIFY_PTR(ptr)) {
Damien George40f3c022014-07-03 13:25:24 +0100450 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Damienfd8b6bc2013-10-22 20:26:36 +0100451 if (ATB_GET_KIND(block) == AT_HEAD) {
452 // free head and all of its tail blocks
453 do {
454 ATB_ANY_TO_FREE(block);
455 block += 1;
456 } while (ATB_GET_KIND(block) == AT_TAIL);
457 }
458 }
459}
460
Damien George40f3c022014-07-03 13:25:24 +0100461mp_uint_t gc_nbytes(void *ptr_in) {
462 mp_uint_t ptr = (mp_uint_t)ptr_in;
Damiendcced922013-10-21 23:45:08 +0100463
Damienfd8b6bc2013-10-22 20:26:36 +0100464 if (VERIFY_PTR(ptr)) {
Damien George40f3c022014-07-03 13:25:24 +0100465 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Damiendcced922013-10-21 23:45:08 +0100466 if (ATB_GET_KIND(block) == AT_HEAD) {
467 // work out number of consecutive blocks in the chain starting with this on
Damien George40f3c022014-07-03 13:25:24 +0100468 mp_uint_t n_blocks = 0;
Damiendcced922013-10-21 23:45:08 +0100469 do {
470 n_blocks += 1;
471 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
472 return n_blocks * BYTES_PER_BLOCK;
473 }
474 }
475
476 // invalid pointer
477 return 0;
478}
479
mux87826762014-03-12 21:00:23 +0200480#if 0
Damien George443e0182014-04-08 11:31:21 +0000481// old, simple realloc that didn't expand memory in place
Damien George40f3c022014-07-03 13:25:24 +0100482void *gc_realloc(void *ptr, mp_uint_t n_bytes) {
483 mp_uint_t n_existing = gc_nbytes(ptr);
Damien George6fc765c2014-03-07 00:21:51 +0000484 if (n_bytes <= n_existing) {
485 return ptr;
486 } else {
Damien George410f3072014-04-25 11:44:53 +0000487 bool has_finaliser;
488 if (ptr == NULL) {
489 has_finaliser = false;
490 } else {
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300491#if MICROPY_ENABLE_FINALISER
Damien George40f3c022014-07-03 13:25:24 +0100492 has_finaliser = FTB_GET(BLOCK_FROM_PTR((mp_uint_t)ptr));
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300493#else
Damien George410f3072014-04-25 11:44:53 +0000494 has_finaliser = false;
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300495#endif
Damien George410f3072014-04-25 11:44:53 +0000496 }
497 void *ptr2 = gc_alloc(n_bytes, has_finaliser);
Damien George6fc765c2014-03-07 00:21:51 +0000498 if (ptr2 == NULL) {
499 return ptr2;
500 }
501 memcpy(ptr2, ptr, n_existing);
502 gc_free(ptr);
503 return ptr2;
504 }
505}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300506
507#else // Alternative gc_realloc impl
Damien George443e0182014-04-08 11:31:21 +0000508
Damien George40f3c022014-07-03 13:25:24 +0100509void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
Damien George443e0182014-04-08 11:31:21 +0000510 if (gc_lock_depth > 0) {
511 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100512 }
513
Damien Georgedde739d2014-04-20 18:16:25 +0100514 // check for pure allocation
muxfbaa1472014-03-05 23:23:04 +0200515 if (ptr_in == NULL) {
Damien George12bab722014-04-05 20:35:48 +0100516 return gc_alloc(n_bytes, false);
Damiendcced922013-10-21 23:45:08 +0100517 }
muxfbaa1472014-03-05 23:23:04 +0200518
Damien George40f3c022014-07-03 13:25:24 +0100519 mp_uint_t ptr = (mp_uint_t)ptr_in;
Damien Georgedde739d2014-04-20 18:16:25 +0100520
521 // sanity check the ptr
522 if (!VERIFY_PTR(ptr)) {
523 return NULL;
524 }
525
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300526 // get first block
Damien George40f3c022014-07-03 13:25:24 +0100527 mp_uint_t block = BLOCK_FROM_PTR(ptr);
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300528
Damien Georgedde739d2014-04-20 18:16:25 +0100529 // sanity check the ptr is pointing to the head of a block
530 if (ATB_GET_KIND(block) != AT_HEAD) {
531 return NULL;
muxfbaa1472014-03-05 23:23:04 +0200532 }
533
Damien Georgedde739d2014-04-20 18:16:25 +0100534 // compute number of new blocks that are requested
Damien George40f3c022014-07-03 13:25:24 +0100535 mp_uint_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK;
Damien Georgedde739d2014-04-20 18:16:25 +0100536
537 // get the number of consecutive tail blocks and
538 // the number of free blocks after last tail block
539 // stop if we reach (or are at) end of heap
Damien George40f3c022014-07-03 13:25:24 +0100540 mp_uint_t n_free = 0;
541 mp_uint_t n_blocks = 1; // counting HEAD block
542 mp_uint_t max_block = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
Damien Georgedde739d2014-04-20 18:16:25 +0100543 while (block + n_blocks + n_free < max_block) {
544 if (n_blocks + n_free >= new_blocks) {
545 // stop as soon as we find enough blocks for n_bytes
546 break;
547 }
548 byte block_type = ATB_GET_KIND(block + n_blocks + n_free);
549 switch (block_type) {
550 case AT_FREE: n_free++; continue;
551 case AT_TAIL: n_blocks++; continue;
552 case AT_MARK: assert(0);
553 }
554 break;
555 }
556
557 // return original ptr if it already has the requested number of blocks
558 if (new_blocks == n_blocks) {
559 return ptr_in;
560 }
561
562 // check if we can shrink the allocated area
563 if (new_blocks < n_blocks) {
564 // free unneeded tail blocks
Damien George40f3c022014-07-03 13:25:24 +0100565 for (mp_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
Damien Georgedde739d2014-04-20 18:16:25 +0100566 ATB_ANY_TO_FREE(bl);
567 }
568 return ptr_in;
569 }
570
571 // check if we can expand in place
572 if (new_blocks <= n_blocks + n_free) {
573 // mark few more blocks as used tail
Damien George40f3c022014-07-03 13:25:24 +0100574 for (mp_uint_t bl = block + n_blocks; bl < block + new_blocks; bl++) {
Damien Georgedde739d2014-04-20 18:16:25 +0100575 assert(ATB_GET_KIND(bl) == AT_FREE);
576 ATB_FREE_TO_TAIL(bl);
577 }
Damien Georgedaab6512014-04-25 23:37:55 +0100578
Damien George32bef312014-04-26 22:23:42 +0100579 // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
stijnf33385f2014-06-12 17:42:20 +0200580 memset((byte*)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100581
Damien Georgedde739d2014-04-20 18:16:25 +0100582 return ptr_in;
583 }
584
585 // can't resize inplace; try to find a new contiguous chain
586 void *ptr_out = gc_alloc(n_bytes,
587#if MICROPY_ENABLE_FINALISER
588 FTB_GET(block)
589#else
590 false
591#endif
592 );
593
594 // check that the alloc succeeded
595 if (ptr_out == NULL) {
596 return NULL;
597 }
598
stijnbbcea3f2014-06-16 10:44:29 +0200599 DEBUG_printf("gc_realloc(%p -> %p)\n", ptr_in, ptr_out);
Damien Georgedde739d2014-04-20 18:16:25 +0100600 memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK);
601 gc_free(ptr_in);
602 return ptr_out;
Damiendcced922013-10-21 23:45:08 +0100603}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300604#endif // Alternative gc_realloc impl
mux87826762014-03-12 21:00:23 +0200605
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200606void gc_dump_info() {
607 gc_info_t info;
608 gc_info(&info);
609 printf("GC: total: " UINT_FMT ", used: " UINT_FMT ", free: " UINT_FMT "\n", info.total, info.used, info.free);
610 printf(" No. of 1-blocks: " UINT_FMT ", 2-blocks: " UINT_FMT ", max blk sz: " UINT_FMT "\n",
611 info.num_1block, info.num_2block, info.max_block);
612}
613
Damien Georgece1162a2014-02-26 22:55:59 +0000614void gc_dump_alloc_table(void) {
Damien Georgedaab6512014-04-25 23:37:55 +0100615 printf("GC memory layout; from %p:", gc_pool_start);
Damien George40f3c022014-07-03 13:25:24 +0100616 for (mp_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
Damien Georgece1162a2014-02-26 22:55:59 +0000617 if (bl % 64 == 0) {
618 printf("\n%04x: ", (uint)bl);
Damieneefcc792013-10-22 15:25:25 +0100619 }
Damien Georgece1162a2014-02-26 22:55:59 +0000620 int c = ' ';
621 switch (ATB_GET_KIND(bl)) {
622 case AT_FREE: c = '.'; break;
623 case AT_HEAD: c = 'h'; break;
Damien Georgedaab6512014-04-25 23:37:55 +0100624 /* this prints the uPy object type of the head block
625 case AT_HEAD: {
Damien George40f3c022014-07-03 13:25:24 +0100626 mp_uint_t *ptr = gc_pool_start + bl * WORDS_PER_BLOCK;
627 if (*ptr == (mp_uint_t)&mp_type_tuple) { c = 'T'; }
628 else if (*ptr == (mp_uint_t)&mp_type_list) { c = 'L'; }
629 else if (*ptr == (mp_uint_t)&mp_type_dict) { c = 'D'; }
630 else if (*ptr == (mp_uint_t)&mp_type_float) { c = 'F'; }
631 else if (*ptr == (mp_uint_t)&mp_type_fun_bc) { c = 'B'; }
Damien Georgedaab6512014-04-25 23:37:55 +0100632 else { c = 'h'; }
633 break;
634 }
635 */
Damien Georgece1162a2014-02-26 22:55:59 +0000636 case AT_TAIL: c = 't'; break;
637 case AT_MARK: c = 'm'; break;
638 }
639 printf("%c", c);
Damieneefcc792013-10-22 15:25:25 +0100640 }
Damien Georgece1162a2014-02-26 22:55:59 +0000641 printf("\n");
Damieneefcc792013-10-22 15:25:25 +0100642}
643
Damien Georgece1162a2014-02-26 22:55:59 +0000644#if DEBUG_PRINT
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200645void gc_test(void) {
Damien George40f3c022014-07-03 13:25:24 +0100646 mp_uint_t len = 500;
647 mp_uint_t *heap = malloc(len);
648 gc_init(heap, heap + len / sizeof(mp_uint_t));
Damiendcced922013-10-21 23:45:08 +0100649 void *ptrs[100];
650 {
Damien George40f3c022014-07-03 13:25:24 +0100651 mp_uint_t **p = gc_alloc(16, false);
Damien George12bab722014-04-05 20:35:48 +0100652 p[0] = gc_alloc(64, false);
653 p[1] = gc_alloc(1, false);
654 p[2] = gc_alloc(1, false);
655 p[3] = gc_alloc(1, false);
Damien George40f3c022014-07-03 13:25:24 +0100656 mp_uint_t ***p2 = gc_alloc(16, false);
Damiendcced922013-10-21 23:45:08 +0100657 p2[0] = p;
658 p2[1] = p;
659 ptrs[0] = p2;
660 }
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200661 for (int i = 0; i < 25; i+=2) {
Damien George40f3c022014-07-03 13:25:24 +0100662 mp_uint_t *p = gc_alloc(i, false);
Damiendcced922013-10-21 23:45:08 +0100663 printf("p=%p\n", p);
664 if (i & 3) {
665 //ptrs[i] = p;
666 }
667 }
668
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200669 printf("Before GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000670 gc_dump_alloc_table();
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200671 printf("Starting GC...\n");
672 gc_collect_start();
673 gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void*));
674 gc_collect_end();
675 printf("After GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000676 gc_dump_alloc_table();
Damiendcced922013-10-21 23:45:08 +0100677}
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200678#endif
Damien Georged3ebe482014-01-07 15:20:33 +0000679
680#endif // MICROPY_ENABLE_GC