py/gc: Allow the GC heap to be split over multiple memory areas.

This commit adds a new option MICROPY_GC_SPLIT_HEAP (disabled by default)
which, when enabled, allows the GC heap to be split over multiple memory
areas/regions.  The first area is added with gc_init() and subsequent areas
can be added with gc_add().  New areas can be added at runtime.  Areas are
stored internally as a linked list, and calls to gc_alloc() can be
satisfied from any area.

This feature has the following use-cases (among others):
- The ESP32 has a fragmented OS heap, so to use all (or more) of it the
  GC heap must be split.
- Other MCUs may have disjoint RAM regions and are now able to use them
  all for the GC heap.
- The user could explicitly increase the size of the GC heap.
- Support a dynamic heap while running on an OS, adding more heap when
  necessary.
This commit is contained in:
Ayke van Laethem 2018-01-24 02:09:58 +01:00 committed by Damien George
parent 5dbb822ca4
commit bcc827d695
4 changed files with 497 additions and 322 deletions

383
py/gc.c
View File

@ -74,17 +74,22 @@
#define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0) #define ATB_2_IS_FREE(a) (((a) & ATB_MASK_2) == 0)
#define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0) #define ATB_3_IS_FREE(a) (((a) & ATB_MASK_3) == 0)
#define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1))) #if MICROPY_GC_SPLIT_HEAP
#define ATB_GET_KIND(block) ((MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3) #define NEXT_AREA(area) (area->next)
#define ATB_ANY_TO_FREE(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0) #else
#define ATB_FREE_TO_HEAD(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0) #define NEXT_AREA(area) (NULL)
#define ATB_FREE_TO_TAIL(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0) #endif
#define ATB_HEAD_TO_MARK(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0)
#define ATB_MARK_TO_HEAD(block) do { MP_STATE_MEM(gc_alloc_table_start)[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0)
#define BLOCK_FROM_PTR(ptr) (((byte *)(ptr) - MP_STATE_MEM(gc_pool_start)) / BYTES_PER_BLOCK) #define BLOCK_SHIFT(block) (2 * ((block) & (BLOCKS_PER_ATB - 1)))
#define PTR_FROM_BLOCK(block) (((block) * BYTES_PER_BLOCK + (uintptr_t)MP_STATE_MEM(gc_pool_start))) #define ATB_GET_KIND(area, block) (((area)->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] >> BLOCK_SHIFT(block)) & 3)
#define ATB_FROM_BLOCK(bl) ((bl) / BLOCKS_PER_ATB) #define ATB_ANY_TO_FREE(area, block) do { area->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_MARK << BLOCK_SHIFT(block))); } while (0)
#define ATB_FREE_TO_HEAD(area, block) do { area->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_HEAD << BLOCK_SHIFT(block)); } while (0)
#define ATB_FREE_TO_TAIL(area, block) do { area->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_TAIL << BLOCK_SHIFT(block)); } while (0)
#define ATB_HEAD_TO_MARK(area, block) do { area->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] |= (AT_MARK << BLOCK_SHIFT(block)); } while (0)
#define ATB_MARK_TO_HEAD(area, block) do { area->gc_alloc_table_start[(block) / BLOCKS_PER_ATB] &= (~(AT_TAIL << BLOCK_SHIFT(block))); } while (0)
#define BLOCK_FROM_PTR(area, ptr) (((byte *)(ptr) - area->gc_pool_start) / BYTES_PER_BLOCK)
#define PTR_FROM_BLOCK(area, block) (((block) * BYTES_PER_BLOCK + (uintptr_t)area->gc_pool_start))
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
// FTB = finaliser table byte // FTB = finaliser table byte
@ -92,9 +97,9 @@
#define BLOCKS_PER_FTB (8) #define BLOCKS_PER_FTB (8)
#define FTB_GET(block) ((MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1) #define FTB_GET(area, block) ((area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] >> ((block) & 7)) & 1)
#define FTB_SET(block) do { MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0) #define FTB_SET(area, block) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] |= (1 << ((block) & 7)); } while (0)
#define FTB_CLEAR(block) do { MP_STATE_MEM(gc_finaliser_table_start)[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0) #define FTB_CLEAR(area, block) do { area->gc_finaliser_table_start[(block) / BLOCKS_PER_FTB] &= (~(1 << ((block) & 7))); } while (0)
#endif #endif
#if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL #if MICROPY_PY_THREAD && !MICROPY_PY_THREAD_GIL
@ -106,11 +111,7 @@
#endif #endif
// TODO waste less memory; currently requires that all entries in alloc_table have a corresponding block in pool // 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) { STATIC void gc_setup_area(mp_state_mem_area_t *area, void *start, void *end) {
// align end pointer on block boundary
end = (void *)((uintptr_t)end & (~(BYTES_PER_BLOCK - 1)));
DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte *)end - (byte *)start);
// calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes): // calculate parameters for GC (T=total, A=alloc table, F=finaliser table, P=pool; all in bytes):
// T = A + F + P // T = A + F + P
// F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB // F = A * BLOCKS_PER_ATB / BLOCKS_PER_FTB
@ -118,36 +119,52 @@ void gc_init(void *start, void *end) {
// => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK) // => T = A * (1 + BLOCKS_PER_ATB / BLOCKS_PER_FTB + BLOCKS_PER_ATB * BYTES_PER_BLOCK)
size_t total_byte_len = (byte *)end - (byte *)start; size_t total_byte_len = (byte *)end - (byte *)start;
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len * MP_BITS_PER_BYTE / (MP_BITS_PER_BYTE + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK); area->gc_alloc_table_byte_len = total_byte_len * MP_BITS_PER_BYTE / (MP_BITS_PER_BYTE + MP_BITS_PER_BYTE * BLOCKS_PER_ATB / BLOCKS_PER_FTB + MP_BITS_PER_BYTE * BLOCKS_PER_ATB * BYTES_PER_BLOCK);
#else #else
MP_STATE_MEM(gc_alloc_table_byte_len) = total_byte_len / (1 + MP_BITS_PER_BYTE / 2 * BYTES_PER_BLOCK); area->gc_alloc_table_byte_len = total_byte_len / (1 + MP_BITS_PER_BYTE / 2 * BYTES_PER_BLOCK);
#endif #endif
MP_STATE_MEM(gc_alloc_table_start) = (byte *)start; area->gc_alloc_table_start = (byte *)start;
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
size_t gc_finaliser_table_byte_len = (MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB; size_t gc_finaliser_table_byte_len = (area->gc_alloc_table_byte_len * BLOCKS_PER_ATB + BLOCKS_PER_FTB - 1) / BLOCKS_PER_FTB;
MP_STATE_MEM(gc_finaliser_table_start) = MP_STATE_MEM(gc_alloc_table_start) + MP_STATE_MEM(gc_alloc_table_byte_len); area->gc_finaliser_table_start = area->gc_alloc_table_start + area->gc_alloc_table_byte_len;
#endif #endif
size_t gc_pool_block_len = MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; size_t gc_pool_block_len = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB;
MP_STATE_MEM(gc_pool_start) = (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK; area->gc_pool_start = (byte *)end - gc_pool_block_len * BYTES_PER_BLOCK;
MP_STATE_MEM(gc_pool_end) = end; area->gc_pool_end = end;
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
assert(MP_STATE_MEM(gc_pool_start) >= MP_STATE_MEM(gc_finaliser_table_start) + gc_finaliser_table_byte_len); assert(area->gc_pool_start >= area->gc_finaliser_table_start + gc_finaliser_table_byte_len);
#endif #endif
// clear ATBs // clear ATBs
memset(MP_STATE_MEM(gc_alloc_table_start), 0, MP_STATE_MEM(gc_alloc_table_byte_len)); memset(area->gc_alloc_table_start, 0, area->gc_alloc_table_byte_len);
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
// clear FTBs // clear FTBs
memset(MP_STATE_MEM(gc_finaliser_table_start), 0, gc_finaliser_table_byte_len); memset(area->gc_finaliser_table_start, 0, gc_finaliser_table_byte_len);
#endif #endif
area->gc_last_free_atb_index = 0;
#if MICROPY_GC_SPLIT_HEAP
area->next = NULL;
#endif
}
void gc_init(void *start, void *end) {
// align end pointer on block boundary
end = (void *)((uintptr_t)end & (~(BYTES_PER_BLOCK - 1)));
DEBUG_printf("Initializing GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte *)end - (byte *)start);
gc_setup_area(&MP_STATE_MEM(area), start, end);
// set last free ATB index to start of heap // set last free ATB index to start of heap
MP_STATE_MEM(gc_last_free_atb_index) = 0; #if MICROPY_GC_SPLIT_HEAP
MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area);
#endif
// unlock the GC // unlock the GC
MP_STATE_THREAD(gc_lock_depth) = 0; MP_STATE_THREAD(gc_lock_depth) = 0;
@ -173,6 +190,29 @@ void gc_init(void *start, void *end) {
DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(gc_pool_start), gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len); DEBUG_printf(" pool at %p, length " UINT_FMT " bytes, " UINT_FMT " blocks\n", MP_STATE_MEM(gc_pool_start), gc_pool_block_len * BYTES_PER_BLOCK, gc_pool_block_len);
} }
#if MICROPY_GC_SPLIT_HEAP
void gc_add(void *start, void *end) {
// Place the area struct at the start of the area.
mp_state_mem_area_t *area = (mp_state_mem_area_t *)start;
start = (void *)((uintptr_t)start + sizeof(mp_state_mem_area_t));
end = (void *)((uintptr_t)end & (~(BYTES_PER_BLOCK - 1)));
DEBUG_printf("Adding GC heap: %p..%p = " UINT_FMT " bytes\n", start, end, (byte *)end - (byte *)start);
// Init this area
gc_setup_area(area, start, end);
// Find the last registered area in the linked list
mp_state_mem_area_t *prev_area = &MP_STATE_MEM(area);
while (prev_area->next != NULL) {
prev_area = prev_area->next;
}
// Add this area to the linked list
prev_area->next = area;
}
#endif
void gc_lock(void) { void gc_lock(void) {
// This does not need to be atomic or have the GC mutex because: // This does not need to be atomic or have the GC mutex because:
// - each thread has its own gc_lock_depth so there are no races between threads; // - each thread has its own gc_lock_depth so there are no races between threads;
@ -190,12 +230,20 @@ bool gc_is_locked(void) {
return MP_STATE_THREAD(gc_lock_depth) != 0; return MP_STATE_THREAD(gc_lock_depth) != 0;
} }
// ptr should be of type void* // Returns the area to which this pointer belongs, or NULL if it isn't
#define VERIFY_PTR(ptr) ( \ // allocated on the GC-managed heap.
((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) == 0 /* must be aligned on a block */ \ STATIC inline mp_state_mem_area_t *gc_get_ptr_area(const void *ptr) {
&& ptr >= (void *)MP_STATE_MEM(gc_pool_start) /* must be above start of pool */ \ if (((uintptr_t)(ptr) & (BYTES_PER_BLOCK - 1)) != 0) { // must be aligned on a block
&& ptr < (void *)MP_STATE_MEM(gc_pool_end) /* must be below end of pool */ \ return NULL;
) }
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
if (ptr >= (void *)area->gc_pool_start // must be above start of pool
&& ptr < (void *)area->gc_pool_end) { // must be below end of pool
return area;
}
}
return NULL;
}
#ifndef TRACE_MARK #ifndef TRACE_MARK
#if DEBUG_PRINT #if DEBUG_PRINT
@ -209,45 +257,64 @@ bool gc_is_locked(void) {
// children: mark the unmarked child blocks and put those newly marked // children: mark the unmarked child blocks and put those newly marked
// blocks on the stack. When all children have been checked, pop off the // blocks on the stack. When all children have been checked, pop off the
// topmost block on the stack and repeat with that one. // topmost block on the stack and repeat with that one.
STATIC void gc_mark_subtree(size_t block) { STATIC void gc_mark_subtree(mp_gc_stack_item_t item) {
// Start with the block passed in the argument. // Start with the item passed in the argument.
size_t sp = 0; size_t sp = 0;
for (;;) { for (;;) {
MICROPY_GC_HOOK_LOOP MICROPY_GC_HOOK_LOOP
#if MICROPY_GC_SPLIT_HEAP
mp_state_mem_area_t *area = item.area;
#else
mp_state_mem_area_t *area = &MP_STATE_MEM(area);
#endif
size_t block = item.block;
// work out number of consecutive blocks in the chain starting with this one // work out number of consecutive blocks in the chain starting with this one
size_t n_blocks = 0; size_t n_blocks = 0;
do { do {
n_blocks += 1; n_blocks += 1;
} while (ATB_GET_KIND(block + n_blocks) == AT_TAIL); } while (ATB_GET_KIND(area, block + n_blocks) == AT_TAIL);
// check this block's children // check this block's children
void **ptrs = (void **)PTR_FROM_BLOCK(block); void **ptrs = (void **)PTR_FROM_BLOCK(area, block);
for (size_t i = n_blocks * BYTES_PER_BLOCK / sizeof(void *); i > 0; i--, ptrs++) { for (size_t i = n_blocks * BYTES_PER_BLOCK / sizeof(void *); i > 0; i--, ptrs++) {
MICROPY_GC_HOOK_LOOP MICROPY_GC_HOOK_LOOP
void *ptr = *ptrs; void *ptr = *ptrs;
if (VERIFY_PTR(ptr)) { // If this is a heap pointer that hasn't been marked, mark it and push
// Mark and push this pointer // it's children to the stack.
size_t childblock = BLOCK_FROM_PTR(ptr); mp_state_mem_area_t *ptr_area = gc_get_ptr_area(ptr);
if (ATB_GET_KIND(childblock) == AT_HEAD) { if (!ptr_area) {
// an unmarked head, mark it, and push it on gc stack // Not a heap-allocated pointer (might even be random data).
TRACE_MARK(childblock, ptr); continue;
ATB_HEAD_TO_MARK(childblock); }
size_t ptr_block = BLOCK_FROM_PTR(ptr_area, ptr);
if (ATB_GET_KIND(ptr_area, ptr_block) != AT_HEAD) {
// This block is already marked.
continue;
}
// An unmarked head. Mark it, and push it on gc stack.
TRACE_MARK(ptr_block, ptr);
ATB_HEAD_TO_MARK(ptr_area, ptr_block);
if (sp < MICROPY_ALLOC_GC_STACK_SIZE) { if (sp < MICROPY_ALLOC_GC_STACK_SIZE) {
MP_STATE_MEM(gc_stack)[sp++] = childblock; #if MICROPY_GC_SPLIT_HEAP
mp_gc_stack_item_t ptr_item = {ptr_area, ptr_block};
#else
mp_gc_stack_item_t ptr_item = {ptr_block};
#endif
MP_STATE_MEM(gc_stack)[sp++] = ptr_item;
} else { } else {
MP_STATE_MEM(gc_stack_overflow) = 1; MP_STATE_MEM(gc_stack_overflow) = 1;
} }
} }
}
}
// Are there any blocks on the stack? // Are there any items on the stack?
if (sp == 0) { if (sp == 0) {
break; // No, stack is empty, we're done. break; // No, stack is empty, we're done.
} }
// pop the next block off the stack // pop the next item off the stack
block = MP_STATE_MEM(gc_stack)[--sp]; item = MP_STATE_MEM(gc_stack)[--sp];
} }
} }
@ -256,11 +323,20 @@ STATIC void gc_deal_with_stack_overflow(void) {
MP_STATE_MEM(gc_stack_overflow) = 0; MP_STATE_MEM(gc_stack_overflow) = 0;
// scan entire memory looking for blocks which have been marked but not their children // scan entire memory looking for blocks which have been marked but not their children
for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
for (size_t block = 0; block < area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
MICROPY_GC_HOOK_LOOP MICROPY_GC_HOOK_LOOP
// trace (again) if mark bit set // trace (again) if mark bit set
if (ATB_GET_KIND(block) == AT_MARK) { if (ATB_GET_KIND(area, block) == AT_MARK) {
gc_mark_subtree(block); #if MICROPY_GC_SPLIT_HEAP
mp_gc_stack_item_t item = {area, block};
#else
mp_gc_stack_item_t item = {block};
#endif
// *MP_STATE_MEM(gc_sp)++ = item;
// gc_drain_stack();
gc_mark_subtree(item);
}
} }
} }
} }
@ -272,13 +348,14 @@ STATIC void gc_sweep(void) {
#endif #endif
// free unmarked heads and their tails // free unmarked heads and their tails
int free_tail = 0; int free_tail = 0;
for (size_t block = 0; block < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; block++) { for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
for (size_t block = 0; block < area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; block++) {
MICROPY_GC_HOOK_LOOP MICROPY_GC_HOOK_LOOP
switch (ATB_GET_KIND(block)) { switch (ATB_GET_KIND(area, block)) {
case AT_HEAD: case AT_HEAD:
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
if (FTB_GET(block)) { if (FTB_GET(area, block)) {
mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(block); mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(area, block);
if (obj->type != NULL) { if (obj->type != NULL) {
// if the object has a type then see if it has a __del__ method // if the object has a type then see if it has a __del__ method
mp_obj_t dest[2]; mp_obj_t dest[2];
@ -295,11 +372,11 @@ STATIC void gc_sweep(void) {
} }
} }
// clear finaliser flag // clear finaliser flag
FTB_CLEAR(block); FTB_CLEAR(area, block);
} }
#endif #endif
free_tail = 1; free_tail = 1;
DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(block)); DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(area, block));
#if MICROPY_PY_GC_COLLECT_RETVAL #if MICROPY_PY_GC_COLLECT_RETVAL
MP_STATE_MEM(gc_collected)++; MP_STATE_MEM(gc_collected)++;
#endif #endif
@ -308,20 +385,21 @@ STATIC void gc_sweep(void) {
case AT_TAIL: case AT_TAIL:
if (free_tail) { if (free_tail) {
ATB_ANY_TO_FREE(block); ATB_ANY_TO_FREE(area, block);
#if CLEAR_ON_SWEEP #if CLEAR_ON_SWEEP
memset((void *)PTR_FROM_BLOCK(block), 0, BYTES_PER_BLOCK); memset((void *)PTR_FROM_BLOCK(area, block), 0, BYTES_PER_BLOCK);
#endif #endif
} }
break; break;
case AT_MARK: case AT_MARK:
ATB_MARK_TO_HEAD(block); ATB_MARK_TO_HEAD(area, block);
free_tail = 0; free_tail = 0;
break; break;
} }
} }
} }
}
void gc_collect_start(void) { void gc_collect_start(void) {
GC_ENTER(); GC_ENTER();
@ -360,13 +438,18 @@ void gc_collect_root(void **ptrs, size_t len) {
for (size_t i = 0; i < len; i++) { for (size_t i = 0; i < len; i++) {
MICROPY_GC_HOOK_LOOP MICROPY_GC_HOOK_LOOP
void *ptr = gc_get_ptr(ptrs, i); void *ptr = gc_get_ptr(ptrs, i);
if (VERIFY_PTR(ptr)) { mp_state_mem_area_t *area = gc_get_ptr_area(ptr);
size_t block = BLOCK_FROM_PTR(ptr); if (area) {
if (ATB_GET_KIND(block) == AT_HEAD) { size_t block = BLOCK_FROM_PTR(area, ptr);
if (ATB_GET_KIND(area, block) == AT_HEAD) {
// An unmarked head: mark it, and mark all its children // An unmarked head: mark it, and mark all its children
TRACE_MARK(block, ptr); ATB_HEAD_TO_MARK(area, block);
ATB_HEAD_TO_MARK(block); #if MICROPY_GC_SPLIT_HEAP
gc_mark_subtree(block); mp_gc_stack_item_t item = {area, block};
#else
mp_gc_stack_item_t item = {block};
#endif
gc_mark_subtree(item);
} }
} }
} }
@ -375,7 +458,12 @@ void gc_collect_root(void **ptrs, size_t len) {
void gc_collect_end(void) { void gc_collect_end(void) {
gc_deal_with_stack_overflow(); gc_deal_with_stack_overflow();
gc_sweep(); gc_sweep();
MP_STATE_MEM(gc_last_free_atb_index) = 0; #if MICROPY_GC_SPLIT_HEAP
MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area);
#endif
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
area->gc_last_free_atb_index = 0;
}
MP_STATE_THREAD(gc_lock_depth)--; MP_STATE_THREAD(gc_lock_depth)--;
GC_EXIT(); GC_EXIT();
} }
@ -389,16 +477,18 @@ void gc_sweep_all(void) {
void gc_info(gc_info_t *info) { void gc_info(gc_info_t *info) {
GC_ENTER(); GC_ENTER();
info->total = MP_STATE_MEM(gc_pool_end) - MP_STATE_MEM(gc_pool_start); info->total = 0;
info->used = 0; info->used = 0;
info->free = 0; info->free = 0;
info->max_free = 0; info->max_free = 0;
info->num_1block = 0; info->num_1block = 0;
info->num_2block = 0; info->num_2block = 0;
info->max_block = 0; info->max_block = 0;
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
bool finish = false; bool finish = false;
info->total += area->gc_pool_end - area->gc_pool_start;
for (size_t block = 0, len = 0, len_free = 0; !finish;) { for (size_t block = 0, len = 0, len_free = 0; !finish;) {
size_t kind = ATB_GET_KIND(block); size_t kind = ATB_GET_KIND(area, block);
switch (kind) { switch (kind) {
case AT_FREE: case AT_FREE:
info->free += 1; info->free += 1;
@ -422,10 +512,10 @@ void gc_info(gc_info_t *info) {
} }
block++; block++;
finish = (block == MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB); finish = (block == area->gc_alloc_table_byte_len * BLOCKS_PER_ATB);
// Get next block type if possible // Get next block type if possible
if (!finish) { if (!finish) {
kind = ATB_GET_KIND(block); kind = ATB_GET_KIND(area, block);
} }
if (finish || kind == AT_FREE || kind == AT_HEAD) { if (finish || kind == AT_FREE || kind == AT_HEAD) {
@ -445,6 +535,7 @@ void gc_info(gc_info_t *info) {
} }
} }
} }
}
info->used *= BYTES_PER_BLOCK; info->used *= BYTES_PER_BLOCK;
info->free *= BYTES_PER_BLOCK; info->free *= BYTES_PER_BLOCK;
@ -468,6 +559,7 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
GC_ENTER(); GC_ENTER();
mp_state_mem_area_t *area;
size_t i; size_t i;
size_t end_block; size_t end_block;
size_t start_block; size_t start_block;
@ -485,10 +577,17 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
for (;;) { for (;;) {
#if MICROPY_GC_SPLIT_HEAP
area = MP_STATE_MEM(gc_last_free_area);
#else
area = &MP_STATE_MEM(area);
#endif
// look for a run of n_blocks available blocks // look for a run of n_blocks available blocks
for (; area != NULL; area = NEXT_AREA(area), i = 0) {
n_free = 0; n_free = 0;
for (i = MP_STATE_MEM(gc_last_free_atb_index); i < MP_STATE_MEM(gc_alloc_table_byte_len); i++) { for (i = area->gc_last_free_atb_index; i < area->gc_alloc_table_byte_len; i++) {
byte a = MP_STATE_MEM(gc_alloc_table_start)[i]; byte a = area->gc_alloc_table_start[i];
// *FORMAT-OFF* // *FORMAT-OFF*
if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; } if (ATB_0_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 0; goto found; } } else { n_free = 0; }
if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; } if (ATB_1_IS_FREE(a)) { if (++n_free >= n_blocks) { i = i * BLOCKS_PER_ATB + 1; goto found; } } else { n_free = 0; }
@ -497,6 +596,16 @@ void *gc_alloc(size_t n_bytes, unsigned int alloc_flags) {
// *FORMAT-ON* // *FORMAT-ON*
} }
// No free blocks found on this heap. Mark this heap as
// filled, so we won't try to find free space here again until
// space is freed.
#if MICROPY_GC_SPLIT_HEAP
if (n_blocks == 1) {
area->gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB; // or (size_t)-1
}
#endif
}
GC_EXIT(); GC_EXIT();
// nothing found! // nothing found!
if (collected) { if (collected) {
@ -520,21 +629,24 @@ found:
// before this one. Also, whenever we free or shink a block we must check // before this one. Also, whenever we free or shink a block we must check
// if this index needs adjusting (see gc_realloc and gc_free). // if this index needs adjusting (see gc_realloc and gc_free).
if (n_free == 1) { if (n_free == 1) {
MP_STATE_MEM(gc_last_free_atb_index) = (i + 1) / BLOCKS_PER_ATB; #if MICROPY_GC_SPLIT_HEAP
MP_STATE_MEM(gc_last_free_area) = area;
#endif
area->gc_last_free_atb_index = (i + 1) / BLOCKS_PER_ATB;
} }
// mark first block as used head // mark first block as used head
ATB_FREE_TO_HEAD(start_block); ATB_FREE_TO_HEAD(area, start_block);
// mark rest of blocks as used tail // mark rest of blocks as used tail
// TODO for a run of many blocks can make this more efficient // TODO for a run of many blocks can make this more efficient
for (size_t bl = start_block + 1; bl <= end_block; bl++) { for (size_t bl = start_block + 1; bl <= end_block; bl++) {
ATB_FREE_TO_TAIL(bl); ATB_FREE_TO_TAIL(area, bl);
} }
// get pointer to first block // get pointer to first block
// we must create this pointer before unlocking the GC so a collection can find it // we must create this pointer before unlocking the GC so a collection can find it
void *ret_ptr = (void *)(MP_STATE_MEM(gc_pool_start) + start_block * BYTES_PER_BLOCK); void *ret_ptr = (void *)(area->gc_pool_start + start_block * BYTES_PER_BLOCK);
DEBUG_printf("gc_alloc(%p)\n", ret_ptr); DEBUG_printf("gc_alloc(%p)\n", ret_ptr);
#if MICROPY_GC_ALLOC_THRESHOLD #if MICROPY_GC_ALLOC_THRESHOLD
@ -561,7 +673,7 @@ found:
((mp_obj_base_t *)ret_ptr)->type = NULL; ((mp_obj_base_t *)ret_ptr)->type = NULL;
// set mp_obj flag only if it has a finaliser // set mp_obj flag only if it has a finaliser
GC_ENTER(); GC_ENTER();
FTB_SET(start_block); FTB_SET(area, start_block);
GC_EXIT(); GC_EXIT();
} }
#else #else
@ -598,27 +710,46 @@ void gc_free(void *ptr) {
DEBUG_printf("gc_free(%p)\n", ptr); DEBUG_printf("gc_free(%p)\n", ptr);
if (ptr == NULL) { if (ptr == NULL) {
// free(NULL) is a no-op
GC_EXIT(); GC_EXIT();
} else { return;
}
// get the GC block number corresponding to this pointer // get the GC block number corresponding to this pointer
assert(VERIFY_PTR(ptr)); mp_state_mem_area_t *area = gc_get_ptr_area(ptr);
size_t block = BLOCK_FROM_PTR(ptr); assert(area);
assert(ATB_GET_KIND(block) == AT_HEAD); size_t block = BLOCK_FROM_PTR(area, ptr);
assert(ATB_GET_KIND(area, block) == AT_HEAD);
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
FTB_CLEAR(block); FTB_CLEAR(area, block);
#endif
#if MICROPY_GC_SPLIT_HEAP
if (MP_STATE_MEM(gc_last_free_area) != area) {
// We freed something but it isn't the current area. Reset the
// last free area to the start for a rescan. Note that this won't
// give much of a performance hit, since areas that are completely
// filled will likely be skipped (the gc_last_free_atb_index
// points to the last block).
// The reason why this is necessary is because it is not possible
// to see which area came first (like it is possible to adjust
// gc_last_free_atb_index based on whether the freed block is
// before the last free block).
MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area);
}
#endif #endif
// set the last_free pointer to this block if it's earlier in the heap // set the last_free pointer to this block if it's earlier in the heap
if (block / BLOCKS_PER_ATB < MP_STATE_MEM(gc_last_free_atb_index)) { if (block / BLOCKS_PER_ATB < area->gc_last_free_atb_index) {
MP_STATE_MEM(gc_last_free_atb_index) = block / BLOCKS_PER_ATB; area->gc_last_free_atb_index = block / BLOCKS_PER_ATB;
} }
// free head and all of its tail blocks // free head and all of its tail blocks
do { do {
ATB_ANY_TO_FREE(block); ATB_ANY_TO_FREE(area, block);
block += 1; block += 1;
} while (ATB_GET_KIND(block) == AT_TAIL); } while (ATB_GET_KIND(area, block) == AT_TAIL);
GC_EXIT(); GC_EXIT();
@ -626,18 +757,18 @@ void gc_free(void *ptr) {
gc_dump_alloc_table(); gc_dump_alloc_table();
#endif #endif
} }
}
size_t gc_nbytes(const void *ptr) { size_t gc_nbytes(const void *ptr) {
GC_ENTER(); GC_ENTER();
if (VERIFY_PTR(ptr)) { mp_state_mem_area_t *area = gc_get_ptr_area(ptr);
size_t block = BLOCK_FROM_PTR(ptr); if (area) {
if (ATB_GET_KIND(block) == AT_HEAD) { size_t block = BLOCK_FROM_PTR(area, ptr);
if (ATB_GET_KIND(area, block) == AT_HEAD) {
// work out number of consecutive blocks in the chain starting with this on // work out number of consecutive blocks in the chain starting with this on
size_t n_blocks = 0; size_t n_blocks = 0;
do { do {
n_blocks += 1; n_blocks += 1;
} while (ATB_GET_KIND(block + n_blocks) == AT_TAIL); } while (ATB_GET_KIND(area, block + n_blocks) == AT_TAIL);
GC_EXIT(); GC_EXIT();
return n_blocks * BYTES_PER_BLOCK; return n_blocks * BYTES_PER_BLOCK;
} }
@ -698,9 +829,10 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
GC_ENTER(); GC_ENTER();
// get the GC block number corresponding to this pointer // get the GC block number corresponding to this pointer
assert(VERIFY_PTR(ptr)); mp_state_mem_area_t *area = gc_get_ptr_area(ptr);
size_t block = BLOCK_FROM_PTR(ptr); assert(area);
assert(ATB_GET_KIND(block) == AT_HEAD); size_t block = BLOCK_FROM_PTR(area, ptr);
assert(ATB_GET_KIND(area, block) == AT_HEAD);
// compute number of new blocks that are requested // compute number of new blocks that are requested
size_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK; size_t new_blocks = (n_bytes + BYTES_PER_BLOCK - 1) / BYTES_PER_BLOCK;
@ -713,9 +845,9 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
// efficiently shrink it (see below for shrinking code). // efficiently shrink it (see below for shrinking code).
size_t n_free = 0; size_t n_free = 0;
size_t n_blocks = 1; // counting HEAD block size_t n_blocks = 1; // counting HEAD block
size_t max_block = MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; size_t max_block = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB;
for (size_t bl = block + n_blocks; bl < max_block; bl++) { for (size_t bl = block + n_blocks; bl < max_block; bl++) {
byte block_type = ATB_GET_KIND(bl); byte block_type = ATB_GET_KIND(area, bl);
if (block_type == AT_TAIL) { if (block_type == AT_TAIL) {
n_blocks++; n_blocks++;
continue; continue;
@ -741,12 +873,19 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
if (new_blocks < n_blocks) { if (new_blocks < n_blocks) {
// free unneeded tail blocks // free unneeded tail blocks
for (size_t bl = block + new_blocks, count = n_blocks - new_blocks; count > 0; bl++, count--) { for (size_t bl = block + new_blocks, count = n_blocks - new_blocks; count > 0; bl++, count--) {
ATB_ANY_TO_FREE(bl); ATB_ANY_TO_FREE(area, bl);
} }
#if MICROPY_GC_SPLIT_HEAP
if (MP_STATE_MEM(gc_last_free_area) != area) {
// See comment in gc_free.
MP_STATE_MEM(gc_last_free_area) = &MP_STATE_MEM(area);
}
#endif
// set the last_free pointer to end of this block if it's earlier in the heap // set the last_free pointer to end of this block if it's earlier in the heap
if ((block + new_blocks) / BLOCKS_PER_ATB < MP_STATE_MEM(gc_last_free_atb_index)) { if ((block + new_blocks) / BLOCKS_PER_ATB < area->gc_last_free_atb_index) {
MP_STATE_MEM(gc_last_free_atb_index) = (block + new_blocks) / BLOCKS_PER_ATB; area->gc_last_free_atb_index = (block + new_blocks) / BLOCKS_PER_ATB;
} }
GC_EXIT(); GC_EXIT();
@ -762,8 +901,8 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
if (new_blocks <= n_blocks + n_free) { if (new_blocks <= n_blocks + n_free) {
// mark few more blocks as used tail // mark few more blocks as used tail
for (size_t bl = block + n_blocks; bl < block + new_blocks; bl++) { for (size_t bl = block + n_blocks; bl < block + new_blocks; bl++) {
assert(ATB_GET_KIND(bl) == AT_FREE); assert(ATB_GET_KIND(area, bl) == AT_FREE);
ATB_FREE_TO_TAIL(bl); ATB_FREE_TO_TAIL(area, bl);
} }
GC_EXIT(); GC_EXIT();
@ -784,7 +923,7 @@ void *gc_realloc(void *ptr_in, size_t n_bytes, bool allow_move) {
} }
#if MICROPY_ENABLE_FINALISER #if MICROPY_ENABLE_FINALISER
bool ftb_state = FTB_GET(block); bool ftb_state = FTB_GET(area, block);
#else #else
bool ftb_state = false; bool ftb_state = false;
#endif #endif
@ -823,25 +962,26 @@ void gc_dump_info(void) {
void gc_dump_alloc_table(void) { void gc_dump_alloc_table(void) {
GC_ENTER(); GC_ENTER();
static const size_t DUMP_BYTES_PER_LINE = 64; static const size_t DUMP_BYTES_PER_LINE = 64;
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
#if !EXTENSIVE_HEAP_PROFILING #if !EXTENSIVE_HEAP_PROFILING
// When comparing heap output we don't want to print the starting // When comparing heap output we don't want to print the starting
// pointer of the heap because it changes from run to run. // pointer of the heap because it changes from run to run.
mp_printf(&mp_plat_print, "GC memory layout; from %p:", MP_STATE_MEM(gc_pool_start)); mp_printf(&mp_plat_print, "GC memory layout; from %p:", area->gc_pool_start);
#endif #endif
for (size_t bl = 0; bl < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB; bl++) { for (size_t bl = 0; bl < area->gc_alloc_table_byte_len * BLOCKS_PER_ATB; bl++) {
if (bl % DUMP_BYTES_PER_LINE == 0) { if (bl % DUMP_BYTES_PER_LINE == 0) {
// a new line of blocks // a new line of blocks
{ {
// check if this line contains only free blocks // check if this line contains only free blocks
size_t bl2 = bl; size_t bl2 = bl;
while (bl2 < MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB && ATB_GET_KIND(bl2) == AT_FREE) { while (bl2 < area->gc_alloc_table_byte_len * BLOCKS_PER_ATB && ATB_GET_KIND(area, bl2) == AT_FREE) {
bl2++; bl2++;
} }
if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) { if (bl2 - bl >= 2 * DUMP_BYTES_PER_LINE) {
// there are at least 2 lines containing only free blocks, so abbreviate their printing // there are at least 2 lines containing only free blocks, so abbreviate their printing
mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE); mp_printf(&mp_plat_print, "\n (%u lines all free)", (uint)(bl2 - bl) / DUMP_BYTES_PER_LINE);
bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1)); bl = bl2 & (~(DUMP_BYTES_PER_LINE - 1));
if (bl >= MP_STATE_MEM(gc_alloc_table_byte_len) * BLOCKS_PER_ATB) { if (bl >= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB) {
// got to end of heap // got to end of heap
break; break;
} }
@ -849,11 +989,11 @@ void gc_dump_alloc_table(void) {
} }
// print header for new line of blocks // print header for new line of blocks
// (the cast to uint32_t is for 16-bit ports) // (the cast to uint32_t is for 16-bit ports)
// mp_printf(&mp_plat_print, "\n%05x: ", (uint)(PTR_FROM_BLOCK(bl) & (uint32_t)0xfffff)); // mp_printf(&mp_plat_print, "\n%05x: ", (uint)(PTR_FROM_BLOCK(area, bl) & (uint32_t)0xfffff));
mp_printf(&mp_plat_print, "\n%05x: ", (uint)((bl * BYTES_PER_BLOCK) & (uint32_t)0xfffff)); mp_printf(&mp_plat_print, "\n%05x: ", (uint)((bl * BYTES_PER_BLOCK) & (uint32_t)0xfffff));
} }
int c = ' '; int c = ' ';
switch (ATB_GET_KIND(bl)) { switch (ATB_GET_KIND(area, bl)) {
case AT_FREE: case AT_FREE:
c = '.'; c = '.';
break; break;
@ -864,7 +1004,7 @@ void gc_dump_alloc_table(void) {
mp_uint_t len = offsetof(mp_state_ctx_t, vm.stack_top) / sizeof(mp_uint_t); mp_uint_t len = offsetof(mp_state_ctx_t, vm.stack_top) / sizeof(mp_uint_t);
for (mp_uint_t i = 0; i < len; i++) { for (mp_uint_t i = 0; i < len; i++) {
mp_uint_t ptr = (mp_uint_t)ptrs[i]; mp_uint_t ptr = (mp_uint_t)ptrs[i];
if (VERIFY_PTR(ptr) && BLOCK_FROM_PTR(ptr) == bl) { if (gc_get_ptr_area(ptr) && BLOCK_FROM_PTR(ptr) == bl) {
c = 'B'; c = 'B';
break; break;
} }
@ -874,7 +1014,7 @@ void gc_dump_alloc_table(void) {
len = ((mp_uint_t)MP_STATE_THREAD(stack_top) - (mp_uint_t)&c) / sizeof(mp_uint_t); len = ((mp_uint_t)MP_STATE_THREAD(stack_top) - (mp_uint_t)&c) / sizeof(mp_uint_t);
for (mp_uint_t i = 0; i < len; i++) { for (mp_uint_t i = 0; i < len; i++) {
mp_uint_t ptr = (mp_uint_t)ptrs[i]; mp_uint_t ptr = (mp_uint_t)ptrs[i];
if (VERIFY_PTR(ptr) && BLOCK_FROM_PTR(ptr) == bl) { if (gc_get_ptr_area(ptr) && BLOCK_FROM_PTR(ptr) == bl) {
c = 'S'; c = 'S';
break; break;
} }
@ -885,7 +1025,7 @@ void gc_dump_alloc_table(void) {
*/ */
/* this prints the uPy object type of the head block */ /* this prints the uPy object type of the head block */
case AT_HEAD: { case AT_HEAD: {
void **ptr = (void **)(MP_STATE_MEM(gc_pool_start) + bl * BYTES_PER_BLOCK); void **ptr = (void **)(area->gc_pool_start + bl * BYTES_PER_BLOCK);
if (*ptr == &mp_type_tuple) { if (*ptr == &mp_type_tuple) {
c = 'T'; c = 'T';
} else if (*ptr == &mp_type_list) { } else if (*ptr == &mp_type_list) {
@ -920,13 +1060,13 @@ void gc_dump_alloc_table(void) {
// This code prints "Q" for qstr-pool data, and "q" for qstr-str // This code prints "Q" for qstr-pool data, and "q" for qstr-str
// data. It can be useful to see how qstrs are being allocated, // data. It can be useful to see how qstrs are being allocated,
// but is disabled by default because it is very slow. // but is disabled by default because it is very slow.
for (const qstr_pool_t *pool = MP_STATE_VM(last_pool); c == 'h' && pool != NULL; pool = pool->prev) { for (qstr_pool_t *pool = MP_STATE_VM(last_pool); c == 'h' && pool != NULL; pool = pool->prev) {
if ((const qstr_pool_t *)ptr == pool) { if ((qstr_pool_t *)ptr == pool) {
c = 'Q'; c = 'Q';
break; break;
} }
for (const char *const *q = pool->qstrs, *const *q_top = pool->qstrs + pool->len; q < q_top; q++) { for (const byte **q = pool->qstrs, **q_top = pool->qstrs + pool->len; q < q_top; q++) {
if ((const char *)ptr == *q) { if ((const byte *)ptr == *q) {
c = 'q'; c = 'q';
break; break;
} }
@ -946,6 +1086,7 @@ void gc_dump_alloc_table(void) {
mp_printf(&mp_plat_print, "%c", c); mp_printf(&mp_plat_print, "%c", c);
} }
mp_print_str(&mp_plat_print, "\n"); mp_print_str(&mp_plat_print, "\n");
}
GC_EXIT(); GC_EXIT();
} }

View File

@ -28,9 +28,15 @@
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h> #include <stddef.h>
#include "py/mpconfig.h"
void gc_init(void *start, void *end); void gc_init(void *start, void *end);
#if MICROPY_GC_SPLIT_HEAP
// Used to add additional memory areas to the heap.
void gc_add(void *start, void *end);
#endif
// These lock/unlock functions can be nested. // These lock/unlock functions can be nested.
// They can be used to prevent the GC from allocating/freeing. // They can be used to prevent the GC from allocating/freeing.
void gc_lock(void); void gc_lock(void);

View File

@ -606,6 +606,11 @@
#define MICROPY_ENABLE_GC (0) #define MICROPY_ENABLE_GC (0)
#endif #endif
// Whether the garbage-collected heap can be split over multiple memory areas.
#ifndef MICROPY_GC_SPLIT_HEAP
#define MICROPY_GC_SPLIT_HEAP (0)
#endif
// Hook to run code during time consuming garbage collector operations // Hook to run code during time consuming garbage collector operations
#ifndef MICROPY_GC_HOOK_LOOP #ifndef MICROPY_GC_HOOK_LOOP
#define MICROPY_GC_HOOK_LOOP #define MICROPY_GC_HOOK_LOOP

View File

@ -71,12 +71,11 @@ typedef struct _mp_sched_item_t {
mp_obj_t arg; mp_obj_t arg;
} mp_sched_item_t; } mp_sched_item_t;
// This structure hold information about the memory allocation system. // This structure holds information about a single contiguous area of
typedef struct _mp_state_mem_t { // memory reserved for the memory manager.
#if MICROPY_MEM_STATS typedef struct _mp_state_mem_area_t {
size_t total_bytes_allocated; #if MICROPY_GC_SPLIT_HEAP
size_t current_bytes_allocated; struct _mp_state_mem_area_t *next;
size_t peak_bytes_allocated;
#endif #endif
byte *gc_alloc_table_start; byte *gc_alloc_table_start;
@ -87,8 +86,30 @@ typedef struct _mp_state_mem_t {
byte *gc_pool_start; byte *gc_pool_start;
byte *gc_pool_end; byte *gc_pool_end;
size_t gc_last_free_atb_index;
} mp_state_mem_area_t;
// This structure holds a single stacked block and the area it is on. Used
// during garbage collection.
typedef struct {
#if MICROPY_GC_SPLIT_HEAP
mp_state_mem_area_t *area;
#endif
size_t block;
} mp_gc_stack_item_t;
// This structure hold information about the memory allocation system.
typedef struct _mp_state_mem_t {
#if MICROPY_MEM_STATS
size_t total_bytes_allocated;
size_t current_bytes_allocated;
size_t peak_bytes_allocated;
#endif
mp_state_mem_area_t area;
int gc_stack_overflow; int gc_stack_overflow;
MICROPY_GC_STACK_ENTRY_TYPE gc_stack[MICROPY_ALLOC_GC_STACK_SIZE]; mp_gc_stack_item_t gc_stack[MICROPY_ALLOC_GC_STACK_SIZE];
// This variable controls auto garbage collection. If set to 0 then the // This variable controls auto garbage collection. If set to 0 then the
// GC won't automatically run when gc_alloc can't find enough blocks. But // GC won't automatically run when gc_alloc can't find enough blocks. But
@ -100,7 +121,9 @@ typedef struct _mp_state_mem_t {
size_t gc_alloc_threshold; size_t gc_alloc_threshold;
#endif #endif
size_t gc_last_free_atb_index; #if MICROPY_GC_SPLIT_HEAP
mp_state_mem_area_t *gc_last_free_area;
#endif
#if MICROPY_PY_GC_COLLECT_RETVAL #if MICROPY_PY_GC_COLLECT_RETVAL
size_t gc_collected; size_t gc_collected;