blob: 452736b6df82abfbc090397580f5243f6ebd3baa [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)
stijnf33385f2014-06-12 17:42:20 +0200123 machine_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
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;
stijnf33385f2014-06-12 17:42:20 +0200139 gc_pool_start = (machine_uint_t*)((byte*)end - gc_pool_block_len * BYTES_PER_BLOCK);
140 gc_pool_end = (machine_uint_t*)end;
Damienbb5316b2013-10-22 21:12:29 +0100141
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 Sokolovsky755a55f2014-06-05 22:48:02 +0300234#if MICROPY_PY_GC_COLLECT_RETVAL
235uint gc_collected;
236#endif
237
Paul Sokolovsky520e2f52014-02-12 18:31:30 +0200238STATIC void gc_sweep(void) {
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300239 #if MICROPY_PY_GC_COLLECT_RETVAL
240 gc_collected = 0;
241 #endif
Damiendcced922013-10-21 23:45:08 +0100242 // free unmarked heads and their tails
243 int free_tail = 0;
244 for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
245 switch (ATB_GET_KIND(block)) {
246 case AT_HEAD:
Damien George12bab722014-04-05 20:35:48 +0100247#if MICROPY_ENABLE_FINALISER
248 if (FTB_GET(block)) {
249 mp_obj_t obj = (mp_obj_t)PTR_FROM_BLOCK(block);
250 if (((mp_obj_base_t*)obj)->type != MP_OBJ_NULL) {
251 // if the object has a type then see if it has a __del__ method
252 mp_obj_t dest[2];
253 mp_load_method_maybe(obj, MP_QSTR___del__, dest);
254 if (dest[0] != MP_OBJ_NULL) {
255 // load_method returned a method
256 mp_call_method_n_kw(0, 0, dest);
257 }
mux4f7e9f52014-04-03 23:55:12 +0200258 }
Damien George12bab722014-04-05 20:35:48 +0100259 // clear finaliser flag
260 FTB_CLEAR(block);
mux4f7e9f52014-04-03 23:55:12 +0200261 }
Damien George12bab722014-04-05 20:35:48 +0100262#endif
Damiendcced922013-10-21 23:45:08 +0100263 free_tail = 1;
Paul Sokolovsky755a55f2014-06-05 22:48:02 +0300264 #if MICROPY_PY_GC_COLLECT_RETVAL
265 gc_collected++;
266 #endif
Damiendcced922013-10-21 23:45:08 +0100267 // fall through to free the head
268
269 case AT_TAIL:
270 if (free_tail) {
271 ATB_ANY_TO_FREE(block);
272 }
273 break;
274
275 case AT_MARK:
276 ATB_MARK_TO_HEAD(block);
277 free_tail = 0;
278 break;
279 }
280 }
281}
282
Damien8b3a7c22013-10-23 20:20:17 +0100283void gc_collect_start(void) {
Damien George443e0182014-04-08 11:31:21 +0000284 gc_lock();
Damiendcced922013-10-21 23:45:08 +0100285 gc_stack_overflow = 0;
286 gc_sp = gc_stack;
287}
288
289void gc_collect_root(void **ptrs, machine_uint_t len) {
290 for (machine_uint_t i = 0; i < len; i++) {
291 machine_uint_t ptr = (machine_uint_t)ptrs[i];
292 VERIFY_MARK_AND_PUSH(ptr);
293 gc_drain_stack();
294 }
295}
296
Damien8b3a7c22013-10-23 20:20:17 +0100297void gc_collect_end(void) {
Damiendcced922013-10-21 23:45:08 +0100298 gc_deal_with_stack_overflow();
299 gc_sweep();
Damien George443e0182014-04-08 11:31:21 +0000300 gc_unlock();
Damieneefcc792013-10-22 15:25:25 +0100301}
Damiendcced922013-10-21 23:45:08 +0100302
Damieneefcc792013-10-22 15:25:25 +0100303void gc_info(gc_info_t *info) {
304 info->total = (gc_pool_end - gc_pool_start) * sizeof(machine_uint_t);
305 info->used = 0;
306 info->free = 0;
307 info->num_1block = 0;
308 info->num_2block = 0;
309 info->max_block = 0;
310 for (machine_uint_t block = 0, len = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
311 machine_uint_t kind = ATB_GET_KIND(block);
312 if (kind == AT_FREE || kind == AT_HEAD) {
313 if (len == 1) {
314 info->num_1block += 1;
315 } else if (len == 2) {
316 info->num_2block += 1;
317 }
318 if (len > info->max_block) {
319 info->max_block = len;
320 }
321 }
322 switch (kind) {
Damiendcced922013-10-21 23:45:08 +0100323 case AT_FREE:
Damieneefcc792013-10-22 15:25:25 +0100324 info->free += 1;
325 len = 0;
Damiendcced922013-10-21 23:45:08 +0100326 break;
327
328 case AT_HEAD:
Damieneefcc792013-10-22 15:25:25 +0100329 info->used += 1;
330 len = 1;
331 break;
332
Damiendcced922013-10-21 23:45:08 +0100333 case AT_TAIL:
Damieneefcc792013-10-22 15:25:25 +0100334 info->used += 1;
335 len += 1;
Damiendcced922013-10-21 23:45:08 +0100336 break;
337
338 case AT_MARK:
Damieneefcc792013-10-22 15:25:25 +0100339 // shouldn't happen
Damiendcced922013-10-21 23:45:08 +0100340 break;
341 }
342 }
343
Damieneefcc792013-10-22 15:25:25 +0100344 info->used *= BYTES_PER_BLOCK;
345 info->free *= BYTES_PER_BLOCK;
Damiendcced922013-10-21 23:45:08 +0100346}
347
Damien George12bab722014-04-05 20:35:48 +0100348void *gc_alloc(machine_uint_t n_bytes, bool has_finaliser) {
Damiendcced922013-10-21 23:45:08 +0100349 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 +0000350 DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks);
Damiendcced922013-10-21 23:45:08 +0100351
Damien George443e0182014-04-08 11:31:21 +0000352 // check if GC is locked
353 if (gc_lock_depth > 0) {
354 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100355 }
356
Damiendcced922013-10-21 23:45:08 +0100357 // check for 0 allocation
358 if (n_blocks == 0) {
359 return NULL;
360 }
361
362 machine_uint_t i;
363 machine_uint_t end_block;
364 machine_uint_t start_block;
365 machine_uint_t n_free = 0;
366 int collected = 0;
367 for (;;) {
368
369 // look for a run of n_blocks available blocks
370 for (i = 0; i < gc_alloc_table_byte_len; i++) {
371 byte a = gc_alloc_table_start[i];
372 if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
373 if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
374 if (ATB_2_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 2; goto found; } } else { n_free = 0; }
375 if (ATB_3_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 3; goto found; } } else { n_free = 0; }
376 }
377
378 // nothing found!
379 if (collected) {
380 return NULL;
381 }
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200382 DEBUG_printf("gc_alloc(" UINT_FMT "): no free mem, triggering GC\n", n_bytes);
Damiendcced922013-10-21 23:45:08 +0100383 gc_collect();
384 collected = 1;
385 }
386
387 // found, ending at block i inclusive
388found:
389 // get starting and end blocks, both inclusive
390 end_block = i;
391 start_block = i - n_free + 1;
392
393 // mark first block as used head
394 ATB_FREE_TO_HEAD(start_block);
395
396 // mark rest of blocks as used tail
397 // TODO for a run of many blocks can make this more efficient
398 for (machine_uint_t bl = start_block + 1; bl <= end_block; bl++) {
399 ATB_FREE_TO_TAIL(bl);
400 }
401
Damien George12bab722014-04-05 20:35:48 +0100402 // get pointer to first block
stijnf33385f2014-06-12 17:42:20 +0200403 byte *ret_ptr = (byte*)(gc_pool_start + start_block * WORDS_PER_BLOCK);
muxcc849f72014-04-05 15:49:03 +0200404
Damien George32bef312014-04-26 22:23:42 +0100405 // zero out the additional bytes of the newly allocated blocks
Damien Georgedaab6512014-04-25 23:37:55 +0100406 // This is needed because the blocks may have previously held pointers
407 // to the heap and will not be set to something else if the caller
408 // doesn't actually use the entire block. As such they will continue
409 // to point to the heap and may prevent other blocks from being reclaimed.
Damien George32bef312014-04-26 22:23:42 +0100410 memset(ret_ptr + n_bytes, 0, (end_block - start_block + 1) * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100411
Damien George12bab722014-04-05 20:35:48 +0100412#if MICROPY_ENABLE_FINALISER
413 if (has_finaliser) {
Damien George32bef312014-04-26 22:23:42 +0100414 // clear type pointer in case it is never set
415 ((mp_obj_base_t*)ret_ptr)->type = MP_OBJ_NULL;
Damien George12bab722014-04-05 20:35:48 +0100416 // set mp_obj flag only if it has a finaliser
417 FTB_SET(start_block);
418 }
419#endif
420
421 return ret_ptr;
Damiendcced922013-10-21 23:45:08 +0100422}
423
Damien George12bab722014-04-05 20:35:48 +0100424/*
mux4f7e9f52014-04-03 23:55:12 +0200425void *gc_alloc(machine_uint_t n_bytes) {
426 return _gc_alloc(n_bytes, false);
427}
428
Damien George12bab722014-04-05 20:35:48 +0100429void *gc_alloc_with_finaliser(machine_uint_t n_bytes) {
mux4f7e9f52014-04-03 23:55:12 +0200430 return _gc_alloc(n_bytes, true);
431}
Damien George12bab722014-04-05 20:35:48 +0100432*/
mux4f7e9f52014-04-03 23:55:12 +0200433
Damienfd8b6bc2013-10-22 20:26:36 +0100434// force the freeing of a piece of memory
435void gc_free(void *ptr_in) {
Damien George443e0182014-04-08 11:31:21 +0000436 if (gc_lock_depth > 0) {
437 // TODO how to deal with this error?
438 return;
Damien George12bab722014-04-05 20:35:48 +0100439 }
440
Damienfd8b6bc2013-10-22 20:26:36 +0100441 machine_uint_t ptr = (machine_uint_t)ptr_in;
442
443 if (VERIFY_PTR(ptr)) {
444 machine_uint_t block = BLOCK_FROM_PTR(ptr);
445 if (ATB_GET_KIND(block) == AT_HEAD) {
446 // free head and all of its tail blocks
447 do {
448 ATB_ANY_TO_FREE(block);
449 block += 1;
450 } while (ATB_GET_KIND(block) == AT_TAIL);
451 }
452 }
453}
454
Damiendcced922013-10-21 23:45:08 +0100455machine_uint_t gc_nbytes(void *ptr_in) {
456 machine_uint_t ptr = (machine_uint_t)ptr_in;
457
Damienfd8b6bc2013-10-22 20:26:36 +0100458 if (VERIFY_PTR(ptr)) {
Damiendcced922013-10-21 23:45:08 +0100459 machine_uint_t block = BLOCK_FROM_PTR(ptr);
460 if (ATB_GET_KIND(block) == AT_HEAD) {
461 // work out number of consecutive blocks in the chain starting with this on
462 machine_uint_t n_blocks = 0;
463 do {
464 n_blocks += 1;
465 } while (ATB_GET_KIND(block + n_blocks) == AT_TAIL);
466 return n_blocks * BYTES_PER_BLOCK;
467 }
468 }
469
470 // invalid pointer
471 return 0;
472}
473
mux87826762014-03-12 21:00:23 +0200474#if 0
Damien George443e0182014-04-08 11:31:21 +0000475// old, simple realloc that didn't expand memory in place
Damien George6fc765c2014-03-07 00:21:51 +0000476void *gc_realloc(void *ptr, machine_uint_t n_bytes) {
477 machine_uint_t n_existing = gc_nbytes(ptr);
478 if (n_bytes <= n_existing) {
479 return ptr;
480 } else {
Damien George410f3072014-04-25 11:44:53 +0000481 bool has_finaliser;
482 if (ptr == NULL) {
483 has_finaliser = false;
484 } else {
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300485#if MICROPY_ENABLE_FINALISER
Damien George410f3072014-04-25 11:44:53 +0000486 has_finaliser = FTB_GET(BLOCK_FROM_PTR((machine_uint_t)ptr));
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300487#else
Damien George410f3072014-04-25 11:44:53 +0000488 has_finaliser = false;
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300489#endif
Damien George410f3072014-04-25 11:44:53 +0000490 }
491 void *ptr2 = gc_alloc(n_bytes, has_finaliser);
Damien George6fc765c2014-03-07 00:21:51 +0000492 if (ptr2 == NULL) {
493 return ptr2;
494 }
495 memcpy(ptr2, ptr, n_existing);
496 gc_free(ptr);
497 return ptr2;
498 }
499}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300500
501#else // Alternative gc_realloc impl
Damien George443e0182014-04-08 11:31:21 +0000502
muxfbaa1472014-03-05 23:23:04 +0200503void *gc_realloc(void *ptr_in, machine_uint_t n_bytes) {
Damien George443e0182014-04-08 11:31:21 +0000504 if (gc_lock_depth > 0) {
505 return NULL;
Damien George12bab722014-04-05 20:35:48 +0100506 }
507
Damien Georgedde739d2014-04-20 18:16:25 +0100508 // check for pure allocation
muxfbaa1472014-03-05 23:23:04 +0200509 if (ptr_in == NULL) {
Damien George12bab722014-04-05 20:35:48 +0100510 return gc_alloc(n_bytes, false);
Damiendcced922013-10-21 23:45:08 +0100511 }
muxfbaa1472014-03-05 23:23:04 +0200512
Damien Georgedde739d2014-04-20 18:16:25 +0100513 machine_uint_t ptr = (machine_uint_t)ptr_in;
514
515 // sanity check the ptr
516 if (!VERIFY_PTR(ptr)) {
517 return NULL;
518 }
519
Paul Sokolovskyc86889d2014-04-20 11:45:16 +0300520 // get first block
521 machine_uint_t block = BLOCK_FROM_PTR(ptr);
522
Damien Georgedde739d2014-04-20 18:16:25 +0100523 // sanity check the ptr is pointing to the head of a block
524 if (ATB_GET_KIND(block) != AT_HEAD) {
525 return NULL;
muxfbaa1472014-03-05 23:23:04 +0200526 }
527
Damien Georgedde739d2014-04-20 18:16:25 +0100528 // compute number of new blocks that are requested
Paul Sokolovsky5b991ae2014-04-20 20:46:39 +0300529 machine_uint_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK;
Damien Georgedde739d2014-04-20 18:16:25 +0100530
531 // get the number of consecutive tail blocks and
532 // the number of free blocks after last tail block
533 // stop if we reach (or are at) end of heap
534 machine_uint_t n_free = 0;
535 machine_uint_t n_blocks = 1; // counting HEAD block
536 machine_uint_t max_block = gc_alloc_table_byte_len * BLOCKS_PER_ATB;
537 while (block + n_blocks + n_free < max_block) {
538 if (n_blocks + n_free >= new_blocks) {
539 // stop as soon as we find enough blocks for n_bytes
540 break;
541 }
542 byte block_type = ATB_GET_KIND(block + n_blocks + n_free);
543 switch (block_type) {
544 case AT_FREE: n_free++; continue;
545 case AT_TAIL: n_blocks++; continue;
546 case AT_MARK: assert(0);
547 }
548 break;
549 }
550
551 // return original ptr if it already has the requested number of blocks
552 if (new_blocks == n_blocks) {
553 return ptr_in;
554 }
555
556 // check if we can shrink the allocated area
557 if (new_blocks < n_blocks) {
558 // free unneeded tail blocks
559 for (machine_uint_t bl = block + new_blocks; ATB_GET_KIND(bl) == AT_TAIL; bl++) {
560 ATB_ANY_TO_FREE(bl);
561 }
562 return ptr_in;
563 }
564
565 // check if we can expand in place
566 if (new_blocks <= n_blocks + n_free) {
567 // mark few more blocks as used tail
568 for (machine_uint_t bl = block + n_blocks; bl < block + new_blocks; bl++) {
569 assert(ATB_GET_KIND(bl) == AT_FREE);
570 ATB_FREE_TO_TAIL(bl);
571 }
Damien Georgedaab6512014-04-25 23:37:55 +0100572
Damien George32bef312014-04-26 22:23:42 +0100573 // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
stijnf33385f2014-06-12 17:42:20 +0200574 memset((byte*)ptr_in + n_bytes, 0, new_blocks * BYTES_PER_BLOCK - n_bytes);
Damien Georgedaab6512014-04-25 23:37:55 +0100575
Damien Georgedde739d2014-04-20 18:16:25 +0100576 return ptr_in;
577 }
578
579 // can't resize inplace; try to find a new contiguous chain
580 void *ptr_out = gc_alloc(n_bytes,
581#if MICROPY_ENABLE_FINALISER
582 FTB_GET(block)
583#else
584 false
585#endif
586 );
587
588 // check that the alloc succeeded
589 if (ptr_out == NULL) {
590 return NULL;
591 }
592
593 DEBUG_printf("gc_realloc: allocating new block\n");
594 memcpy(ptr_out, ptr_in, n_blocks * BYTES_PER_BLOCK);
595 gc_free(ptr_in);
596 return ptr_out;
Damiendcced922013-10-21 23:45:08 +0100597}
Paul Sokolovskyed162b52014-04-20 11:43:38 +0300598#endif // Alternative gc_realloc impl
mux87826762014-03-12 21:00:23 +0200599
Paul Sokolovsky723a6ed2014-02-11 18:01:38 +0200600void gc_dump_info() {
601 gc_info_t info;
602 gc_info(&info);
603 printf("GC: total: " UINT_FMT ", used: " UINT_FMT ", free: " UINT_FMT "\n", info.total, info.used, info.free);
604 printf(" No. of 1-blocks: " UINT_FMT ", 2-blocks: " UINT_FMT ", max blk sz: " UINT_FMT "\n",
605 info.num_1block, info.num_2block, info.max_block);
606}
607
Damien Georgece1162a2014-02-26 22:55:59 +0000608void gc_dump_alloc_table(void) {
Damien Georgedaab6512014-04-25 23:37:55 +0100609 printf("GC memory layout; from %p:", gc_pool_start);
Damieneefcc792013-10-22 15:25:25 +0100610 for (machine_uint_t bl = 0; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
Damien Georgece1162a2014-02-26 22:55:59 +0000611 if (bl % 64 == 0) {
612 printf("\n%04x: ", (uint)bl);
Damieneefcc792013-10-22 15:25:25 +0100613 }
Damien Georgece1162a2014-02-26 22:55:59 +0000614 int c = ' ';
615 switch (ATB_GET_KIND(bl)) {
616 case AT_FREE: c = '.'; break;
617 case AT_HEAD: c = 'h'; break;
Damien Georgedaab6512014-04-25 23:37:55 +0100618 /* this prints the uPy object type of the head block
619 case AT_HEAD: {
620 machine_uint_t *ptr = gc_pool_start + bl * WORDS_PER_BLOCK;
621 if (*ptr == (machine_uint_t)&mp_type_tuple) { c = 'T'; }
622 else if (*ptr == (machine_uint_t)&mp_type_list) { c = 'L'; }
623 else if (*ptr == (machine_uint_t)&mp_type_dict) { c = 'D'; }
624 else if (*ptr == (machine_uint_t)&mp_type_float) { c = 'F'; }
625 else if (*ptr == (machine_uint_t)&mp_type_fun_bc) { c = 'B'; }
626 else { c = 'h'; }
627 break;
628 }
629 */
Damien Georgece1162a2014-02-26 22:55:59 +0000630 case AT_TAIL: c = 't'; break;
631 case AT_MARK: c = 'm'; break;
632 }
633 printf("%c", c);
Damieneefcc792013-10-22 15:25:25 +0100634 }
Damien Georgece1162a2014-02-26 22:55:59 +0000635 printf("\n");
Damieneefcc792013-10-22 15:25:25 +0100636}
637
Damien Georgece1162a2014-02-26 22:55:59 +0000638#if DEBUG_PRINT
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200639void gc_test(void) {
640 machine_uint_t len = 500;
Damiendcced922013-10-21 23:45:08 +0100641 machine_uint_t *heap = malloc(len);
642 gc_init(heap, heap + len / sizeof(machine_uint_t));
643 void *ptrs[100];
644 {
Damien George12bab722014-04-05 20:35:48 +0100645 machine_uint_t **p = gc_alloc(16, false);
646 p[0] = gc_alloc(64, false);
647 p[1] = gc_alloc(1, false);
648 p[2] = gc_alloc(1, false);
649 p[3] = gc_alloc(1, false);
650 machine_uint_t ***p2 = gc_alloc(16, false);
Damiendcced922013-10-21 23:45:08 +0100651 p2[0] = p;
652 p2[1] = p;
653 ptrs[0] = p2;
654 }
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200655 for (int i = 0; i < 25; i+=2) {
Damien George12bab722014-04-05 20:35:48 +0100656 machine_uint_t *p = gc_alloc(i, false);
Damiendcced922013-10-21 23:45:08 +0100657 printf("p=%p\n", p);
658 if (i & 3) {
659 //ptrs[i] = p;
660 }
661 }
662
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200663 printf("Before GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000664 gc_dump_alloc_table();
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200665 printf("Starting GC...\n");
666 gc_collect_start();
667 gc_collect_root(ptrs, sizeof(ptrs) / sizeof(void*));
668 gc_collect_end();
669 printf("After GC:\n");
Damien Georgece1162a2014-02-26 22:55:59 +0000670 gc_dump_alloc_table();
Damiendcced922013-10-21 23:45:08 +0100671}
Paul Sokolovskyaf19cbd2014-02-10 21:45:54 +0200672#endif
Damien Georged3ebe482014-01-07 15:20:33 +0000673
674#endif // MICROPY_ENABLE_GC