4646#define DEBUG_printf (...) (void)0
4747#endif
4848
49+ // make this 1 to dump the heap each time it changes
50+ #define EXTENSIVE_HEAP_PROFILING (0)
51+
4952#define WORDS_PER_BLOCK (4)
5053#define BYTES_PER_BLOCK (WORDS_PER_BLOCK * BYTES_PER_WORD)
5154#define STACK_SIZE (64) // tunable; minimum is 1
@@ -405,7 +408,8 @@ void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
405408 // Set last free ATB index to block after last block we found, for start of
406409 // next scan. To reduce fragmentation, we only do this if we were looking
407410 // for a single free block, which guarantees that there are no free blocks
408- // before this one.
411+ // before this one. Also, whenever we free or shink a block we must check
412+ // if this index needs adjusting (see gc_realloc and gc_free).
409413 if (n_free == 1 ) {
410414 gc_last_free_atb_index = (i + 1 ) / BLOCKS_PER_ATB ;
411415 }
@@ -439,6 +443,10 @@ void *gc_alloc(mp_uint_t n_bytes, bool has_finaliser) {
439443 }
440444#endif
441445
446+ #if EXTENSIVE_HEAP_PROFILING
447+ gc_dump_alloc_table ();
448+ #endif
449+
442450 return ret_ptr ;
443451}
444452
@@ -465,11 +473,20 @@ void gc_free(void *ptr_in) {
465473 if (VERIFY_PTR (ptr )) {
466474 mp_uint_t block = BLOCK_FROM_PTR (ptr );
467475 if (ATB_GET_KIND (block ) == AT_HEAD ) {
476+ // set the last_free pointer to this block if it's earlier in the heap
477+ if (block / BLOCKS_PER_ATB < gc_last_free_atb_index ) {
478+ gc_last_free_atb_index = block / BLOCKS_PER_ATB ;
479+ }
480+
468481 // free head and all of its tail blocks
469482 do {
470483 ATB_ANY_TO_FREE (block );
471484 block += 1 ;
472485 } while (ATB_GET_KIND (block ) == AT_TAIL );
486+
487+ #if EXTENSIVE_HEAP_PROFILING
488+ gc_dump_alloc_table ();
489+ #endif
473490 }
474491 }
475492}
@@ -581,6 +598,16 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
581598 for (mp_uint_t bl = block + new_blocks ; ATB_GET_KIND (bl ) == AT_TAIL ; bl ++ ) {
582599 ATB_ANY_TO_FREE (bl );
583600 }
601+
602+ // set the last_free pointer to end of this block if it's earlier in the heap
603+ if ((block + new_blocks ) / BLOCKS_PER_ATB < gc_last_free_atb_index ) {
604+ gc_last_free_atb_index = (block + new_blocks ) / BLOCKS_PER_ATB ;
605+ }
606+
607+ #if EXTENSIVE_HEAP_PROFILING
608+ gc_dump_alloc_table ();
609+ #endif
610+
584611 return ptr_in ;
585612 }
586613
@@ -595,6 +622,10 @@ void *gc_realloc(void *ptr_in, mp_uint_t n_bytes) {
595622 // zero out the additional bytes of the newly allocated blocks (see comment above in gc_alloc)
596623 memset ((byte * )ptr_in + n_bytes , 0 , new_blocks * BYTES_PER_BLOCK - n_bytes );
597624
625+ #if EXTENSIVE_HEAP_PROFILING
626+ gc_dump_alloc_table ();
627+ #endif
628+
598629 return ptr_in ;
599630 }
600631
@@ -628,9 +659,34 @@ void gc_dump_info() {
628659}
629660
630661void gc_dump_alloc_table (void ) {
662+ static const mp_uint_t DUMP_BYTES_PER_LINE = 64 ;
663+ #if !EXTENSIVE_HEAP_PROFILING
664+ // When comparing heap output we don't want to print the starting
665+ // pointer of the heap because it changes from run to run.
631666 printf ("GC memory layout; from %p:" , gc_pool_start );
667+ #endif
632668 for (mp_uint_t bl = 0 ; bl < gc_alloc_table_byte_len * BLOCKS_PER_ATB ; bl ++ ) {
633- if (bl % 64 == 0 ) {
669+ if (bl % DUMP_BYTES_PER_LINE == 0 ) {
670+ // a new line of blocks
671+ #if EXTENSIVE_HEAP_PROFILING
672+ {
673+ // check if this line contains only free blocks
674+ bool only_free_blocks = true;
675+ for (mp_uint_t bl2 = bl ; bl2 < gc_alloc_table_byte_len * BLOCKS_PER_ATB && bl2 < bl + DUMP_BYTES_PER_LINE ; bl2 ++ ) {
676+ if (ATB_GET_KIND (bl2 ) != AT_FREE ) {
677+
678+ only_free_blocks = false;
679+ break ;
680+ }
681+ }
682+ if (only_free_blocks ) {
683+ // line contains only free blocks, so skip printing it
684+ bl += DUMP_BYTES_PER_LINE - 1 ;
685+ continue ;
686+ }
687+ }
688+ #endif
689+ // print header for new line of blocks
634690 printf ("\n%04x: " , (uint )bl );
635691 }
636692 int c = ' ' ;
0 commit comments