@@ -29,6 +29,9 @@ The arena allocation needs to be thread safe and we use an atomic bitmap to allo
2929#error "The page_t.page_ma_offset field is not large enough to cover a full arena"
3030#endif
3131
32+ static mi_bitmap_t * mi_arena_pages_abandoned (mi_arena_pages_t * arena_pages , size_t bin );
33+ static mi_bitmap_t * mi_arena_pages_abandoned_ensure (mi_arena_t * arena , mi_arena_pages_t * arena_pages , size_t bin );
34+
3235/* -----------------------------------------------------------
3336 Arena id's
3437----------------------------------------------------------- */
@@ -726,9 +729,9 @@ static mi_page_t* mi_arenas_page_try_find_abandoned(mi_theap_t* theap, size_t sl
726729 mi_forall_suitable_arenas (heap , req_arena , tseq , match_numa , any_numa , allow_large , arena )
727730 {
728731 mi_arena_pages_t * const arena_pages = mi_heap_arena_pages (heap , arena );
729- if (arena_pages != NULL ) {
732+ mi_bitmap_t * const bitmap = (arena_pages != NULL ? mi_arena_pages_abandoned (arena_pages , bin ) : NULL );
733+ if (bitmap != NULL ) {
730734 size_t slice_index ;
731- mi_bitmap_t * const bitmap = arena_pages -> pages_abandoned [bin ];
732735
733736 if (mi_bitmap_try_find_and_claim (bitmap , tseq , & slice_index , & mi_arena_try_claim_abandoned , arena )) {
734737 // found an abandoned page of the right size
@@ -1119,7 +1122,7 @@ static void mi_arenas_page_free_prim(mi_page_t* page, mi_subproc_t* subproc, mi_
11191122 const size_t bin = _mi_bin (mi_page_block_size (page ));
11201123 mi_assert_internal (mi_bbitmap_is_clearN (arena -> slices_free , slice_index , slice_count ));
11211124 mi_assert_internal (mi_page_slice_committed (page ) > 0 || mi_bitmap_is_setN (arena -> slices_committed , slice_index , slice_count ));
1122- mi_assert_internal (bin >= MI_ARENA_BIN_COUNT || mi_bitmap_is_clearN (arena_pages -> pages_abandoned [ bin ] , slice_index , 1 ));
1125+ mi_assert_internal (bin >= MI_ARENA_BIN_COUNT || mi_arena_pages_abandoned ( arena_pages , bin ) == NULL || mi_bitmap_is_clearN (mi_arena_pages_abandoned ( arena_pages , bin ) , slice_index , 1 ));
11231126 // note: we cannot check for `!mi_page_is_abandoned_and_mapped` since that may
11241127 // be (temporarily) not true if the free happens while trying to reclaim
11251128 // see `mi_arena_try_claim_abandoned`
@@ -1224,13 +1227,18 @@ void _mi_arenas_page_abandon(mi_page_t* page, mi_theap_t* current_theapx) {
12241227 mi_assert_internal (mi_page_slice_committed (page ) > 0 || mi_bitmap_is_setN (arena -> slices_committed , slice_index , slice_count ));
12251228 mi_assert_internal (mi_bitmap_is_setN (arena -> slices_dirty , slice_index , slice_count ));
12261229
1227- mi_page_set_abandoned_mapped (page );
1228- const bool was_clear = mi_bitmap_set (arena_pages -> pages_abandoned [bin ], slice_index );
1229- MI_UNUSED (was_clear ); mi_assert_internal (was_clear );
1230- mi_atomic_increment_relaxed (& heap -> abandoned_count [bin ]);
1231- mi_theapx_stat_increase (heap , current_theapx , pages_abandoned , 1 );
1232- mi_abandoned_page_unown (page , current_theapx );
1233- return ;
1230+ // If the bin's bitmap cannot be allocated the page is abandoned unmapped, like a full
1231+ // page: it is still reachable through `pages` and is reclaimed once a block in it is freed.
1232+ mi_bitmap_t * const bitmap = mi_arena_pages_abandoned_ensure (arena , arena_pages , bin );
1233+ if mi_likely (bitmap != NULL ) {
1234+ mi_page_set_abandoned_mapped (page );
1235+ const bool was_clear = mi_bitmap_set (bitmap , slice_index );
1236+ MI_UNUSED (was_clear ); mi_assert_internal (was_clear );
1237+ mi_atomic_increment_relaxed (& heap -> abandoned_count [bin ]);
1238+ mi_theapx_stat_increase (heap , current_theapx , pages_abandoned , 1 );
1239+ mi_abandoned_page_unown (page , current_theapx );
1240+ return ;
1241+ }
12341242 }
12351243 }
12361244 // otherwise,
@@ -1298,7 +1306,9 @@ void _mi_arenas_page_unabandon(mi_page_t* page, mi_theap_t* current_theapx) {
12981306 mi_assert_internal (mi_page_slice_committed (page ) > 0 || mi_bitmap_is_setN (arena -> slices_committed , slice_index , slice_count ));
12991307
13001308 // this busy waits until a concurrent reader (from alloc_abandoned) is done
1301- mi_bitmap_clear_once_set (arena -> subproc , arena_pages -> pages_abandoned [bin ], slice_index );
1309+ mi_bitmap_t * const bitmap = mi_arena_pages_abandoned (arena_pages , bin );
1310+ mi_assert_internal (bitmap != NULL ); // a mapped page was set in it
1311+ mi_bitmap_clear_once_set (arena -> subproc , bitmap , slice_index );
13021312 mi_page_clear_abandoned_mapped (page );
13031313 mi_atomic_decrement_relaxed (& heap -> abandoned_count [bin ]);
13041314 }
@@ -1384,7 +1394,8 @@ void _mi_arenas_purge_abandoned_holes(mi_heap_t* heap, mi_tld_t* tld) {
13841394 // singleton bins have no abandoned bitmap (upstream ad1bcdbf, to shrink arena meta).
13851395 for (size_t bin = 0 ; bin < MI_ARENA_BIN_COUNT ; bin ++ ) {
13861396 if (mi_atomic_load_relaxed (& heap -> abandoned_count [bin ]) == 0 ) continue ;
1387- mi_bitmap_t * const bitmap = arena_pages -> pages_abandoned [bin ];
1397+ mi_bitmap_t * const bitmap = mi_arena_pages_abandoned (arena_pages , bin );
1398+ if (bitmap == NULL ) continue ;
13881399 mi_purge_holes_arg_t parg = { bitmap , tld };
13891400 (void )_mi_bitmap_forall_set (bitmap , & mi_arena_page_purge_holes_at , arena , & parg );
13901401 }
@@ -1427,7 +1438,8 @@ void _mi_arenas_holes_report(mi_heap_t* heap, mi_holes_report_t* rep) {
14271438 if (arena_pages != NULL ) {
14281439 for (size_t bin = 0 ; bin < MI_ARENA_BIN_COUNT ; bin ++ ) { // see above: not MI_BIN_COUNT
14291440 if (mi_atomic_load_relaxed (& heap -> abandoned_count [bin ]) == 0 ) continue ;
1430- mi_arena_holes_report_arg_t ra = { arena_pages -> pages_abandoned [bin ], rep };
1441+ mi_arena_holes_report_arg_t ra = { mi_arena_pages_abandoned (arena_pages , bin ), rep };
1442+ if (ra .bitmap == NULL ) continue ;
14311443 (void )_mi_bitmap_forall_set (ra .bitmap , & mi_arena_page_holes_report_at , arena , & ra );
14321444 }
14331445 }
@@ -1651,8 +1663,7 @@ static size_t mi_arena_pages_size(size_t slice_count, size_t* bitmap_base) {
16511663 if (slice_count == 0 ) slice_count = MI_BCHUNK_BITS ;
16521664 mi_assert_internal ((slice_count % MI_BCHUNK_BITS ) == 0 );
16531665 const size_t base_size = _mi_align_up (sizeof (mi_arena_pages_t ), MI_BCHUNK_SIZE );
1654- const size_t bitmaps_count = 1 + MI_ARENA_BIN_COUNT ; // pages, and abandoned
1655- const size_t bitmaps_size = bitmaps_count * mi_bitmap_size (slice_count , NULL );
1666+ const size_t bitmaps_size = mi_bitmap_size (slice_count , NULL ); // pages (the abandoned bitmaps are allocated on demand)
16561667 const size_t size = base_size + bitmaps_size ;
16571668 if (bitmap_base != NULL ) * bitmap_base = base_size ;
16581669 return size ;
@@ -1662,7 +1673,7 @@ static size_t mi_arena_info_slices_needed(size_t slice_count, size_t* bitmap_bas
16621673 if (slice_count == 0 ) slice_count = MI_BCHUNK_BITS ;
16631674 mi_assert_internal ((slice_count % MI_BCHUNK_BITS ) == 0 );
16641675 const size_t base_size = _mi_align_up (sizeof (mi_arena_t ), MI_BCHUNK_SIZE );
1665- const size_t bitmaps_count = 4 + MI_ARENA_BIN_COUNT ; // commit, dirty, purge, pages, and abandoned
1676+ const size_t bitmaps_count = 4 ; // commit, dirty, purge, and pages (the abandoned bitmaps are allocated on demand)
16661677 const size_t bitmaps_size = bitmaps_count * mi_bitmap_size (slice_count , NULL ) + mi_bbitmap_size (slice_count , NULL ); // + free
16671678 #if MI_PAGE_META_IS_SEPARATED
16681679 const size_t pages_size = slice_count * sizeof (mi_page_t );
@@ -1701,12 +1712,55 @@ static mi_arena_pages_t* mi_arena_pages_alloc(mi_arena_t* arena) {
17011712 uint8_t * base = (uint8_t * )arena_pages + bitmap_base ;
17021713 mi_assert_internal (_mi_is_aligned (base , MI_BCHUNK_SIZE ));
17031714 arena_pages -> pages = mi_arena_bitmap_init (slice_count , & base );
1704- for (size_t i = 0 ; i < MI_ARENA_BIN_COUNT ; i ++ ) {
1705- arena_pages -> pages_abandoned [i ] = mi_arena_bitmap_init (slice_count , & base );
1706- }
1715+ // `pages_abandoned[]` stays NULL (the allocation is zeroed) until a page of that bin is abandoned.
17071716 return arena_pages ;
17081717}
17091718
1719+ // The abandoned-pages bitmap of `bin`, or NULL if no page of that bin was ever abandoned in
1720+ // this (heap, arena) pair. A NULL bitmap reads as all-clear.
1721+ static mi_bitmap_t * mi_arena_pages_abandoned (mi_arena_pages_t * arena_pages , size_t bin ) {
1722+ mi_assert_internal (bin < MI_ARENA_BIN_COUNT );
1723+ return mi_atomic_load_ptr_acquire (mi_bitmap_t , & arena_pages -> pages_abandoned [bin ]);
1724+ }
1725+
1726+ // The abandoned-pages bitmap of `bin`, allocated on first use. Allocated from the subproc
1727+ // meta-data heap so this is safe on the abandon paths (a thread tearing down its theaps, a
1728+ // foreign free re-abandoning a page), where allocating from a regular heap is not. Publishing
1729+ // is a CAS so no lock is needed: a loser frees its copy and uses the winner's. Returns NULL only
1730+ // if the allocation failed.
1731+ static mi_bitmap_t * mi_arena_pages_abandoned_ensure (mi_arena_t * arena , mi_arena_pages_t * arena_pages , size_t bin ) {
1732+ mi_bitmap_t * bitmap = mi_arena_pages_abandoned (arena_pages , bin );
1733+ if mi_likely (bitmap != NULL ) return bitmap ;
1734+ const size_t slice_count = arena -> slice_count ;
1735+ const size_t size = mi_bitmap_size (slice_count , NULL );
1736+ mi_bitmap_t * fresh = (mi_bitmap_t * )_mi_meta_zalloc_aligned (arena -> subproc , size , MI_BCHUNK_SIZE , NULL );
1737+ if (fresh == NULL ) return NULL ;
1738+ mi_bitmap_init (fresh , slice_count , true /* already zero */ );
1739+ mi_bitmap_t * expected = NULL ;
1740+ if (mi_atomic_cas_ptr_strong_acq_rel (mi_bitmap_t , & arena_pages -> pages_abandoned [bin ], & expected , fresh )) {
1741+ return fresh ;
1742+ }
1743+ // another thread published one first
1744+ _mi_free_subproc_safe (fresh );
1745+ mi_assert_internal (expected != NULL );
1746+ return expected ;
1747+ }
1748+
1749+ // Release the on-demand abandoned bitmaps of a heap's arena pages (the `pages` bitmap is part of
1750+ // the `arena_pages` allocation itself).
1751+ static void mi_arena_pages_free_abandoned (mi_arena_pages_t * arena_pages ) {
1752+ for (size_t bin = 0 ; bin < MI_ARENA_BIN_COUNT ; bin ++ ) {
1753+ mi_bitmap_t * bitmap = mi_atomic_exchange_ptr_acq_rel (mi_bitmap_t , & arena_pages -> pages_abandoned [bin ], NULL );
1754+ if (bitmap != NULL ) { _mi_free_subproc_safe (bitmap ); }
1755+ }
1756+ }
1757+
1758+ void _mi_arena_pages_free (mi_arena_pages_t * arena_pages ) {
1759+ if (arena_pages == NULL ) return ;
1760+ mi_arena_pages_free_abandoned (arena_pages );
1761+ _mi_free_subproc_safe (arena_pages );
1762+ }
1763+
17101764static mi_arena_t * mi_arena_initialize (mi_subproc_t * subproc , void * start ,
17111765 size_t slice_count , mi_arena_t * parent , size_t total_size ,
17121766 int numa_node , bool exclusive ,
@@ -1799,7 +1853,7 @@ static mi_arena_t* mi_arena_initialize(mi_subproc_t* subproc, void* start,
17991853 arena -> slices_purge = mi_arena_bitmap_init (slice_count , & base );
18001854 arena -> pages_main .pages = mi_arena_bitmap_init (slice_count , & base );
18011855 for (size_t i = 0 ; i < MI_ARENA_BIN_COUNT ; i ++ ) {
1802- arena -> pages_main .pages_abandoned [i ] = mi_arena_bitmap_init ( slice_count , & base );
1856+ mi_atomic_store_ptr_relaxed ( mi_bitmap_t , & arena -> pages_main .pages_abandoned [i ], NULL ); // allocated on first abandon
18031857 }
18041858 #if MI_PAGE_META_IS_SEPARATED
18051859 arena -> pages_meta = (mi_page_t * )base ;
@@ -2690,7 +2744,9 @@ bool _mi_heap_visit_blocks(mi_heap_t* heap, bool abandoned_only, bool visit_bloc
26902744 for (size_t bin = 0 ; ok && bin < MI_ARENA_BIN_COUNT ; bin ++ ) {
26912745 // todo: if we had a single abandoned page map as well, this can be faster.
26922746 if (mi_atomic_load_relaxed (& heap -> abandoned_count [bin ]) > 0 ) {
2693- ok = _mi_bitmap_forall_set (arena_pages -> pages_abandoned [bin ], & mi_heap_visit_page_at , arena , & visit_info );
2747+ mi_bitmap_t * const bitmap = mi_arena_pages_abandoned (arena_pages , bin );
2748+ if (bitmap == NULL ) continue ;
2749+ ok = _mi_bitmap_forall_set (bitmap , & mi_heap_visit_page_at , arena , & visit_info );
26942750 }
26952751 }
26962752 }
0 commit comments