Skip to content

Commit 8a2ff2c

Browse files
projectgusdpgeorge
authored andcommitted
py/gc: Split out running finalizers to a separate pass.
Currently a finalizer may run and access memory which has already been freed. (This happens mostly during gc_sweep_all() but could happen during any garbage collection pass.) Includes some speed improvement tweaks to skip empty FTB blocks. These help compensate for the inherent slowdown of having to walk the heap twice. Signed-off-by: Angus Gratton <[email protected]>
1 parent d642cce commit 8a2ff2c

File tree

1 file changed

+43
-24
lines changed

1 file changed

+43
-24
lines changed

py/gc.c

+43-24
Original file line numberDiff line numberDiff line change
@@ -477,29 +477,20 @@ static void gc_deal_with_stack_overflow(void) {
477477
}
478478
}
479479

480-
static void gc_sweep(void) {
481-
#if MICROPY_PY_GC_COLLECT_RETVAL
482-
MP_STATE_MEM(gc_collected) = 0;
483-
#endif
484-
// free unmarked heads and their tails
485-
int free_tail = 0;
486-
#if MICROPY_GC_SPLIT_HEAP_AUTO
487-
mp_state_mem_area_t *prev_area = NULL;
488-
#endif
489-
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
490-
size_t end_block = area->gc_alloc_table_byte_len * BLOCKS_PER_ATB;
491-
if (area->gc_last_used_block < end_block) {
492-
end_block = area->gc_last_used_block + 1;
493-
}
494-
495-
size_t last_used_block = 0;
496-
497-
for (size_t block = 0; block < end_block; block++) {
498-
MICROPY_GC_HOOK_LOOP(block);
499-
switch (ATB_GET_KIND(area, block)) {
500-
case AT_HEAD:
501-
#if MICROPY_ENABLE_FINALISER
502-
if (FTB_GET(area, block)) {
480+
// Run finalisers for all to-be-freed blocks
481+
static void gc_sweep_run_finalisers(void) {
482+
#if MICROPY_ENABLE_FINALISER
483+
for (const mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
484+
assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB);
485+
// Small speed optimisation: skip over empty FTB blocks
486+
size_t ftb_end = area->gc_last_used_block / BLOCKS_PER_FTB; // index is inclusive
487+
for (size_t ftb_idx = 0; ftb_idx <= ftb_end; ftb_idx++) {
488+
byte ftb = area->gc_finaliser_table_start[ftb_idx];
489+
size_t block = ftb_idx * BLOCKS_PER_FTB;
490+
while (ftb) {
491+
MICROPY_GC_HOOK_LOOP(block);
492+
if (ftb & 1) { // FTB_GET(area, block) shortcut
493+
if (ATB_GET_KIND(area, block) == AT_HEAD) {
503494
mp_obj_base_t *obj = (mp_obj_base_t *)PTR_FROM_BLOCK(area, block);
504495
if (obj->type != NULL) {
505496
// if the object has a type then see if it has a __del__ method
@@ -519,7 +510,35 @@ static void gc_sweep(void) {
519510
// clear finaliser flag
520511
FTB_CLEAR(area, block);
521512
}
522-
#endif
513+
}
514+
ftb >>= 1;
515+
block++;
516+
}
517+
}
518+
}
519+
#endif // MICROPY_ENABLE_FINALISER
520+
}
521+
522+
static void gc_sweep(void) {
523+
#if MICROPY_PY_GC_COLLECT_RETVAL
524+
MP_STATE_MEM(gc_collected) = 0;
525+
#endif
526+
// free unmarked heads and their tails
527+
int free_tail = 0;
528+
#if MICROPY_GC_SPLIT_HEAP_AUTO
529+
mp_state_mem_area_t *prev_area = NULL;
530+
#endif
531+
532+
gc_sweep_run_finalisers();
533+
534+
for (mp_state_mem_area_t *area = &MP_STATE_MEM(area); area != NULL; area = NEXT_AREA(area)) {
535+
size_t last_used_block = 0;
536+
assert(area->gc_last_used_block <= area->gc_alloc_table_byte_len * BLOCKS_PER_ATB);
537+
538+
for (size_t block = 0; block <= area->gc_last_used_block; block++) {
539+
MICROPY_GC_HOOK_LOOP(block);
540+
switch (ATB_GET_KIND(area, block)) {
541+
case AT_HEAD:
523542
free_tail = 1;
524543
DEBUG_printf("gc_sweep(%p)\n", (void *)PTR_FROM_BLOCK(area, block));
525544
#if MICROPY_PY_GC_COLLECT_RETVAL

0 commit comments

Comments
 (0)