From 12bab72d93498958ddef26c1a2dd7c69a9f9cf8d Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 5 Apr 2014 20:35:48 +0100 Subject: [PATCH] Improve GC finalisation code; add option to disable it. --- py/gc.c | 150 +++++++++++++++++++++++++++++------------- py/gc.h | 3 +- py/malloc.c | 13 ++-- py/misc.h | 8 ++- py/mpconfig.h | 5 ++ stm/file.c | 2 +- stm/malloc0.c | 3 +- stm/mpconfigport.h | 1 + stmhal/file.c | 3 +- stmhal/malloc0.c | 3 +- stmhal/mpconfigport.h | 1 + unix/mpconfigport.h | 1 + 12 files changed, 135 insertions(+), 58 deletions(-) diff --git a/py/gc.c b/py/gc.c index 1eb859cbf7..0f137b963f 100644 --- a/py/gc.c +++ b/py/gc.c @@ -27,15 +27,17 @@ typedef unsigned char byte; #define STACK_SIZE (64) // tunable; minimum is 1 STATIC byte *gc_alloc_table_start; -STATIC byte *gc_mpobj_table_start; STATIC machine_uint_t gc_alloc_table_byte_len; -STATIC machine_uint_t gc_mpobj_table_byte_len; +#if MICROPY_ENABLE_FINALISER +STATIC byte *gc_finaliser_table_start; +#endif STATIC machine_uint_t *gc_pool_start; STATIC machine_uint_t *gc_pool_end; STATIC int gc_stack_overflow; STATIC machine_uint_t gc_stack[STACK_SIZE]; STATIC machine_uint_t *gc_sp; +STATIC bool gc_lock; // ATB = allocation table byte // 0b00 = FREE -- free block @@ -67,38 +69,58 @@ STATIC machine_uint_t *gc_sp; #define ATB_HEAD_TO_MARK(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0) #define ATB_MARK_TO_HEAD(block) do { gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0) -#define ATB_SET_MPOBJ(block) do { gc_mpobj_table_start[(block) / 8] |= (1<<(block%8)); } while (0) -#define ATB_CLR_MPOBJ(block) do { gc_mpobj_table_start[(block) / 8] &= (~(1<<(block%8))); } while (0) -#define ATB_IS_MPOBJ(block) ((gc_mpobj_table_start[(block) / 8]>>(block%8))&0x01) - #define BLOCK_FROM_PTR(ptr) (((ptr) - (machine_uint_t)gc_pool_start) / BYTES_PER_BLOCK) #define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (machine_uint_t)gc_pool_start)) #define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB) +#if MICROPY_ENABLE_FINALISER +// FTB = finaliser table byte +// if set, then the corresponding block may have a finaliser + +#define BLOCKS_PER_FTB (8) + +#define FTB_GET(block) ((gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1) +#define FTB_SET(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0) +#define FTB_CLEAR(block) do { gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0) +#endif + // TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool void gc_init(void *start, void *end) { // align end pointer on block boundary end = (void*)((machine_uint_t)end & (~(BYTES_PER_BLOCK - 1))); - DEBUG_printf("Initializing GC heap: %p-%p\n", start, end); + DEBUG_printf("Initializing GC heap: %p..%p = %ld bytes\n", start, end, end - start); + + // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes): + // T = A + F + P + // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB + // P = A * BLOCKS_PER_ATB * BYTES_PER_BLOCK + // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) + machine_uint_t total_byte_len = end - start; +#if MICROPY_ENABLE_FINALISER + 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); +#else + gc_alloc_table_byte_len = total_byte_len / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); +#endif + - // calculate parameters for GC - machine_uint_t total_word_len = (machine_uint_t*)end - (machine_uint_t*)start; - gc_alloc_table_byte_len = total_word_len * BYTES_PER_WORD / (1 + BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); gc_alloc_table_start = (byte*)start; - gc_mpobj_table_byte_len = (gc_alloc_table_byte_len * BITS_PER_BYTE / 2)/8; - gc_mpobj_table_start = gc_alloc_table_start+gc_alloc_table_byte_len; +#if MICROPY_ENABLE_FINALISER + machine_uint_t gc_finaliser_table_byte_len = (gc_alloc_table_byte_len * BLOCKS_PER_ATB) / BLOCKS_PER_FTB; + gc_finaliser_table_start = gc_alloc_table_start + gc_alloc_table_byte_len; +#endif - machine_uint_t gc_pool_block_len = (gc_alloc_table_byte_len * BITS_PER_BYTE / 2) -(gc_mpobj_table_byte_len / BYTES_PER_BLOCK); - machine_uint_t gc_pool_word_len = gc_pool_block_len * WORDS_PER_BLOCK; - gc_pool_start = (machine_uint_t*)end - gc_pool_word_len; + machine_uint_t gc_pool_block_len = gc_alloc_table_byte_len * BLOCKS_PER_ATB; + gc_pool_start = end - gc_pool_block_len * BYTES_PER_BLOCK; gc_pool_end = end; // clear ATBs memset(gc_alloc_table_start, 0, gc_alloc_table_byte_len); - // clear MPOBJ flags - memset(gc_mpobj_table_start, 0, gc_mpobj_table_byte_len); +#if MICROPY_ENABLE_FINALISER + // clear FTBs + memset(gc_finaliser_table_start, 0, gc_finaliser_table_byte_len); +#endif // allocate first block because gc_pool_start points there and it will never // be freed, so allocating 1 block with null pointers will minimise memory loss @@ -107,9 +129,15 @@ void gc_init(void *start, void *end) { gc_pool_start[i] = 0; } + // unlock the GC + gc_lock = false; + DEBUG_printf("GC layout:\n"); - DEBUG_printf(" alloc table at %p, length " UINT_FMT " bytes\n", gc_alloc_table_start, gc_alloc_table_byte_len); - DEBUG_printf(" pool at %p, length " UINT_FMT " blocks = " UINT_FMT " words = " UINT_FMT " bytes\n", gc_pool_start, gc_pool_block_len, gc_pool_word_len, gc_pool_word_len * BYTES_PER_WORD); + 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); +#if MICROPY_ENABLE_FINALISER + 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); +#endif + 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); } #define VERIFY_PTR(ptr) ( \ @@ -176,16 +204,22 @@ STATIC void gc_sweep(void) { for (machine_uint_t block = 0; block < gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) { switch (ATB_GET_KIND(block)) { case AT_HEAD: - if (ATB_IS_MPOBJ(block)) { - mp_obj_t dest[2]; - mp_load_method((mp_obj_t*)PTR_FROM_BLOCK(block), MP_QSTR___del__, dest); - // load_method returned a method - if (dest[1] != MP_OBJ_NULL) { - mp_call_method_n_kw(0, 0, dest); +#if MICROPY_ENABLE_FINALISER + if (FTB_GET(block)) { + mp_obj_t obj = (mp_obj_t)PTR_FROM_BLOCK(block); + if (((mp_obj_base_t*)obj)->type != MP_OBJ_NULL) { + // if the object has a type then see if it has a __del__ method + mp_obj_t dest[2]; + mp_load_method_maybe(obj, MP_QSTR___del__, dest); + if (dest[0] != MP_OBJ_NULL) { + // load_method returned a method + mp_call_method_n_kw(0, 0, dest); + } } - // clear mpobj flag - ATB_CLR_MPOBJ(block); + // clear finaliser flag + FTB_CLEAR(block); } +#endif free_tail = 1; // fall through to free the head @@ -204,6 +238,7 @@ STATIC void gc_sweep(void) { } void gc_collect_start(void) { + gc_lock = true; gc_stack_overflow = 0; gc_sp = gc_stack; } @@ -219,6 +254,7 @@ void gc_collect_root(void **ptrs, machine_uint_t len) { void gc_collect_end(void) { gc_deal_with_stack_overflow(); gc_sweep(); + gc_lock = false; } void gc_info(gc_info_t *info) { @@ -266,10 +302,14 @@ void gc_info(gc_info_t *info) { info->free *= BYTES_PER_BLOCK; } -void *_gc_alloc(machine_uint_t n_bytes, bool is_mpobj) { +void *gc_alloc(machine_uint_t n_bytes, bool has_finaliser) { machine_uint_t n_blocks = ((n_bytes + BYTES_PER_BLOCK - 1) & (~(BYTES_PER_BLOCK - 1))) / BYTES_PER_BLOCK; DEBUG_printf("gc_alloc(" UINT_FMT " bytes -> " UINT_FMT " blocks)\n", n_bytes, n_blocks); + if (gc_lock) { + // TODO + } + // check for 0 allocation if (n_blocks == 0) { return NULL; @@ -315,25 +355,37 @@ found: ATB_FREE_TO_TAIL(bl); } - if (is_mpobj) { - // set mp_obj flag only if it has del - ATB_SET_MPOBJ(start_block); - } + // get pointer to first block + void *ret_ptr = (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK); - // return pointer to first block - return (void*)(gc_pool_start + start_block * WORDS_PER_BLOCK); +#if MICROPY_ENABLE_FINALISER + if (has_finaliser) { + // clear type pointer in case it is never set + ((mp_obj_base_t*)ret_ptr)->type = MP_OBJ_NULL; + // set mp_obj flag only if it has a finaliser + FTB_SET(start_block); + } +#endif + + return ret_ptr; } +/* void *gc_alloc(machine_uint_t n_bytes) { return _gc_alloc(n_bytes, false); } -void *gc_alloc_mp_obj(machine_uint_t n_bytes) { +void *gc_alloc_with_finaliser(machine_uint_t n_bytes) { return _gc_alloc(n_bytes, true); } +*/ // force the freeing of a piece of memory void gc_free(void *ptr_in) { + if (gc_lock) { + // TODO + } + machine_uint_t ptr = (machine_uint_t)ptr_in; if (VERIFY_PTR(ptr)) { @@ -386,12 +438,16 @@ void *gc_realloc(void *ptr, machine_uint_t n_bytes) { } #else void *gc_realloc(void *ptr_in, machine_uint_t n_bytes) { + if (gc_lock) { + // TODO + } + void *ptr_out = NULL; machine_uint_t block = 0; machine_uint_t ptr = (machine_uint_t)ptr_in; if (ptr_in == NULL) { - return gc_alloc(n_bytes); + return gc_alloc(n_bytes, false); } if (VERIFY_PTR(ptr) // verify pointer @@ -444,7 +500,13 @@ void *gc_realloc(void *ptr_in, machine_uint_t n_bytes) { ptr_out = ptr_in; // try to find a new contiguous chain - } else if ((ptr_out = gc_alloc(n_bytes)) != NULL) { + } else if ((ptr_out = gc_alloc(n_bytes, +#if MICROPY_ENABLE_FINALISER + FTB_GET(block) +#else + false +#endif + )) != NULL) { DEBUG_printf("gc_realloc: allocating new block\n"); memcpy(ptr_out, ptr_in, n_existing); gc_free(ptr_in); @@ -489,18 +551,18 @@ void gc_test(void) { gc_init(heap, heap + len / sizeof(machine_uint_t)); void *ptrs[100]; { - machine_uint_t **p = gc_alloc(16); - p[0] = gc_alloc(64); - p[1] = gc_alloc(1); - p[2] = gc_alloc(1); - p[3] = gc_alloc(1); - machine_uint_t ***p2 = gc_alloc(16); + machine_uint_t **p = gc_alloc(16, false); + p[0] = gc_alloc(64, false); + p[1] = gc_alloc(1, false); + p[2] = gc_alloc(1, false); + p[3] = gc_alloc(1, false); + machine_uint_t ***p2 = gc_alloc(16, false); p2[0] = p; p2[1] = p; ptrs[0] = p2; } for (int i = 0; i < 25; i+=2) { - machine_uint_t *p = gc_alloc(i); + machine_uint_t *p = gc_alloc(i, false); printf("p=%p\n", p); if (i & 3) { //ptrs[i] = p; diff --git a/py/gc.h b/py/gc.h index f8aeb393a8..dd6f60dbd7 100644 --- a/py/gc.h +++ b/py/gc.h @@ -3,8 +3,7 @@ void gc_collect_start(void); void gc_collect_root(void **ptrs, machine_uint_t len); void gc_collect_end(void); void gc_collect(void); -void *gc_alloc(machine_uint_t n_bytes); -void *gc_alloc_mp_obj(machine_uint_t n_bytes); +void *gc_alloc(machine_uint_t n_bytes, bool has_finaliser); void gc_free(void *ptr); machine_uint_t gc_nbytes(void *ptr); void *gc_realloc(void *ptr, machine_uint_t n_bytes); diff --git a/py/malloc.c b/py/malloc.c index a07d360020..45e939b6c2 100644 --- a/py/malloc.c +++ b/py/malloc.c @@ -30,8 +30,8 @@ STATIC int peak_bytes_allocated = 0; #undef malloc #undef free #undef realloc -#define malloc gc_alloc -#define malloc_mp_obj gc_alloc_mp_obj +#define malloc(b) gc_alloc((b), false) +#define malloc_with_finaliser(b) gc_alloc((b), true) #define free gc_free #define realloc gc_realloc #endif // MICROPY_ENABLE_GC @@ -53,14 +53,14 @@ void *m_malloc(int num_bytes) { return ptr; } -void *m_malloc_mp_obj(int num_bytes) { +#if MICROPY_ENABLE_FINALISER +void *m_malloc_with_finaliser(int num_bytes) { if (num_bytes == 0) { return NULL; } - void *ptr = malloc_mp_obj(num_bytes); + void *ptr = malloc_with_finaliser(num_bytes); if (ptr == NULL) { - printf("could not allocate memory, allocating %d bytes\n", num_bytes); - return NULL; + return m_malloc_fail(num_bytes); } #if MICROPY_MEM_STATS total_bytes_allocated += num_bytes; @@ -70,6 +70,7 @@ void *m_malloc_mp_obj(int num_bytes) { DEBUG_printf("malloc %d : %p\n", num_bytes, ptr); return ptr; } +#endif void *m_malloc0(int num_bytes) { void *ptr = m_malloc(num_bytes); diff --git a/py/misc.h b/py/misc.h index df9a465e0f..4112a6f0da 100644 --- a/py/misc.h +++ b/py/misc.h @@ -27,14 +27,18 @@ typedef unsigned int uint; #define m_new0(type, num) ((type*)(m_malloc0(sizeof(type) * (num)))) #define m_new_obj(type) (m_new(type, 1)) #define m_new_obj_var(obj_type, var_type, var_num) ((obj_type*)m_malloc(sizeof(obj_type) + sizeof(var_type) * (var_num))) -#define m_new_mp_obj(type)((type*)(m_malloc_mp_obj(sizeof(type)))) +#if MICROPY_ENABLE_FINALISER +#define m_new_obj_with_finaliser(type) ((type*)(m_malloc_with_finaliser(sizeof(type)))) +#else +#define m_new_obj_with_finaliser(type) m_new_obj(type) +#endif #define m_renew(type, ptr, old_num, new_num) ((type*)(m_realloc((ptr), sizeof(type) * (old_num), sizeof(type) * (new_num)))) #define m_del(type, ptr, num) m_free(ptr, sizeof(type) * (num)) #define m_del_obj(type, ptr) (m_del(type, ptr, 1)) #define m_del_var(obj_type, var_type, var_num, ptr) (m_free(ptr, sizeof(obj_type) + sizeof(var_type) * (var_num))) void *m_malloc(int num_bytes); -void *m_malloc_mp_obj(int num_bytes); +void *m_malloc_with_finaliser(int num_bytes); void *m_malloc0(int num_bytes); void *m_realloc(void *ptr, int old_num_bytes, int new_num_bytes); void m_free(void *ptr, int num_bytes); diff --git a/py/mpconfig.h b/py/mpconfig.h index 65577f06ca..22724c84e4 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -55,6 +55,11 @@ #define MICROPY_ENABLE_GC (0) #endif +// Whether to enable finalisers in the garbage collector (ie call __del__) +#ifndef MICROPY_ENABLE_GC_FINALISER +#define MICROPY_ENABLE_GC_FINALISER (0) +#endif + // Whether to include REPL helper function #ifndef MICROPY_ENABLE_REPL_HELPERS #define MICROPY_ENABLE_REPL_HELPERS (0) diff --git a/stm/file.c b/stm/file.c index 8abb45b4e4..44210d59d3 100644 --- a/stm/file.c +++ b/stm/file.c @@ -71,7 +71,7 @@ static const mp_obj_type_t file_obj_type = { mp_obj_t pyb_io_open(mp_obj_t o_filename, mp_obj_t o_mode) { const char *filename = mp_obj_str_get_str(o_filename); const char *mode = mp_obj_str_get_str(o_mode); - pyb_file_obj_t *self = m_new_mp_obj(pyb_file_obj_t); + pyb_file_obj_t *self = m_new_obj_with_finaliser(pyb_file_obj_t); self->base.type = &file_obj_type; if (mode[0] == 'r') { // open for reading diff --git a/stm/malloc0.c b/stm/malloc0.c index 85a643f72d..510fa0d740 100644 --- a/stm/malloc0.c +++ b/stm/malloc0.c @@ -1,5 +1,6 @@ +#include #include -#include "std.h" +#include "misc.h" #include "mpconfig.h" #include "gc.h" diff --git a/stm/mpconfigport.h b/stm/mpconfigport.h index 4c8338be53..c832f43883 100644 --- a/stm/mpconfigport.h +++ b/stm/mpconfigport.h @@ -5,6 +5,7 @@ #define MICROPY_EMIT_THUMB (1) #define MICROPY_EMIT_INLINE_THUMB (1) #define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) #define MICROPY_ENABLE_REPL_HELPERS (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) diff --git a/stmhal/file.c b/stmhal/file.c index a66b59b29d..a78c58d4f9 100644 --- a/stmhal/file.c +++ b/stmhal/file.c @@ -53,6 +53,7 @@ STATIC const mp_map_elem_t file_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_readline), (mp_obj_t)&mp_stream_unbuffered_readline_obj}, { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&file_obj_close_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR___del__), (mp_obj_t)&file_obj_close_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR___enter__), (mp_obj_t)&mp_identity_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR___exit__), (mp_obj_t)&file_obj___exit___obj }, }; @@ -81,7 +82,7 @@ STATIC mp_obj_t file_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, cons if (n_args > 1) { mode = mp_obj_str_get_str(args[1]); } - pyb_file_obj_t *self = m_new_obj(pyb_file_obj_t); + pyb_file_obj_t *self = m_new_obj_with_finaliser(pyb_file_obj_t); self->base.type = &file_obj_type; if (mode[0] == 'r') { // open for reading diff --git a/stmhal/malloc0.c b/stmhal/malloc0.c index 85a643f72d..510fa0d740 100644 --- a/stmhal/malloc0.c +++ b/stmhal/malloc0.c @@ -1,5 +1,6 @@ +#include #include -#include "std.h" +#include "misc.h" #include "mpconfig.h" #include "gc.h" diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h index b187c43bbe..0bb11cae5a 100644 --- a/stmhal/mpconfigport.h +++ b/stmhal/mpconfigport.h @@ -5,6 +5,7 @@ #define MICROPY_EMIT_THUMB (1) #define MICROPY_EMIT_INLINE_THUMB (1) #define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) #define MICROPY_ENABLE_REPL_HELPERS (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h index 254d14d98f..b8ee020f1e 100644 --- a/unix/mpconfigport.h +++ b/unix/mpconfigport.h @@ -4,6 +4,7 @@ #define MICROPY_EMIT_THUMB (0) #define MICROPY_EMIT_INLINE_THUMB (0) #define MICROPY_ENABLE_GC (1) +#define MICROPY_ENABLE_FINALISER (1) #define MICROPY_MEM_STATS (1) #define MICROPY_DEBUG_PRINTERS (1) #define MICROPY_ENABLE_REPL_HELPERS (1)