Path: blob/main/system/lib/mimalloc/src/theap.c
14369 views
/*----------------------------------------------------------------------------1Copyright (c) 2018-2025, Microsoft Research, Daan Leijen2This is free software; you can redistribute it and/or modify it under the3terms of the MIT license. A copy of the license can be found in the file4"LICENSE" at the root of this distribution.5-----------------------------------------------------------------------------*/67#include "mimalloc.h"8#include "mimalloc/internal.h"9#include "mimalloc/prim.h" // _mi_theap_default1011#if defined(_MSC_VER) && (_MSC_VER < 1920)12#pragma warning(disable:4204) // non-constant aggregate initializer13#endif1415/* -----------------------------------------------------------16Helpers17----------------------------------------------------------- */1819// return `true` if ok, `false` to break20typedef bool (theap_page_visitor_fun)(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2);2122// Visit all pages in a theap; returns `false` if break was called.23static bool mi_theap_visit_pages(mi_theap_t* theap, theap_page_visitor_fun* fn, bool include_full, void* arg1, void* arg2)24{25if (theap==NULL || theap->page_count==0) return 0;2627// visit all pages28#if MI_DEBUG>129size_t total = theap->page_count;30size_t count = 0;31#endif3233const size_t max_bin = (include_full ? MI_BIN_FULL : MI_BIN_FULL - 1);34for (size_t i = 0; i <= max_bin; i++) {35mi_page_queue_t* pq = &theap->pages[i];36mi_page_t* page = pq->first;37while(page != NULL) {38mi_page_t* next = page->next; // save next in case the page gets removed from the queue39mi_assert_internal(mi_page_theap(page) == theap);40#if MI_DEBUG>141count++;42#endif43if (!fn(theap, pq, page, arg1, arg2)) return false;44page = next; // and continue45}46}47mi_assert_internal(!include_full || count == total);48return true;49}505152#if MI_DEBUG>=253static bool mi_theap_page_is_valid(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {54MI_UNUSED(arg1);55MI_UNUSED(arg2);56MI_UNUSED(pq);57mi_assert_internal(mi_page_theap(page) == theap);58mi_assert_expensive(_mi_page_is_valid(page));59return true;60}61#endif62#if MI_DEBUG>=363static bool mi_theap_is_valid(mi_theap_t* theap) {64mi_assert_internal(theap!=NULL);65mi_theap_visit_pages(theap, &mi_theap_page_is_valid, true, NULL, NULL);66for (size_t bin = 0; bin < MI_BIN_COUNT; bin++) {67mi_assert_internal(_mi_page_queue_is_valid(theap, &theap->pages[bin]));68}69return true;70}71#endif7273747576/* -----------------------------------------------------------77"Collect" pages by migrating `local_free` and `thread_free`78lists and freeing empty pages. This is done when a thread79stops (and in that case abandons pages if there are still80blocks alive)81----------------------------------------------------------- */8283typedef enum mi_collect_e {84MI_NORMAL,85MI_FORCE,86MI_ABANDON87} mi_collect_t;888990static bool mi_theap_page_collect(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg_collect, void* arg2 ) {91MI_UNUSED(arg2);92MI_UNUSED(theap);93mi_assert_internal(mi_theap_page_is_valid(theap, pq, page, NULL, NULL));94mi_collect_t collect = *((mi_collect_t*)arg_collect);95_mi_page_free_collect(page, collect >= MI_FORCE);96if (mi_page_all_free(page)) {97// no more used blocks, possibly free the page.98if (collect >= MI_FORCE || page->retire_expire == 0) { // either forced/abandon, or not already retired99// note: this will potentially free retired pages as well.100_mi_page_free(page, pq);101}102}103else if (collect == MI_ABANDON) {104// still used blocks but the thread is done; abandon the page105_mi_page_abandon(page, pq);106}107return true; // don't break108}109110static void mi_theap_merge_stats(mi_theap_t* theap) {111mi_assert_internal(mi_theap_is_initialized(theap));112_mi_stats_merge_into(&_mi_theap_heap(theap)->stats, &theap->stats);113}114115static void mi_theap_collect_ex(mi_theap_t* theap, mi_collect_t collect)116{117if (theap==NULL || !mi_theap_is_initialized(theap)) return;118mi_assert_expensive(mi_theap_is_valid(theap));119120const bool force = (collect >= MI_FORCE);121_mi_deferred_free(theap, force);122123// python/cpython#112532: we may be called from a thread that is not the owner of the theap124// const bool is_main_thread = (_mi_is_main_thread() && theap->thread_id == _mi_thread_id());125126// collect retired pages127_mi_theap_collect_retired(theap, force);128129// collect all pages owned by this thread130mi_theap_visit_pages(theap, &mi_theap_page_collect, (collect!=MI_NORMAL), &collect, NULL); // dont normally visit full pages, see issue #1220131132// collect arenas (this is program wide so don't force purges on abandonment of threads)133//mi_atomic_storei64_release(&theap->tld->subproc->purge_expire, 1);134_mi_arenas_collect(collect == MI_FORCE /* force purge? */, collect >= MI_FORCE /* visit all? */, theap->tld);135136// merge statistics137mi_theap_merge_stats(theap);138}139140void _mi_theap_collect_abandon(mi_theap_t* theap) {141mi_theap_collect_ex(theap, MI_ABANDON);142}143144void mi_theap_collect(mi_theap_t* theap, bool force) mi_attr_noexcept {145mi_theap_collect_ex(theap, (force ? MI_FORCE : MI_NORMAL));146}147148void mi_collect(bool force) mi_attr_noexcept {149// cannot really collect process wide, just a theap..150mi_theap_collect(_mi_theap_default(), force);151}152153void mi_heap_collect(mi_heap_t* heap, bool force) {154// cannot really collect a heap, just a theap..155mi_theap_collect(mi_heap_theap(heap), force);156}157158/* -----------------------------------------------------------159Heap new160----------------------------------------------------------- */161162mi_theap_t* mi_theap_get_default(void) {163mi_theap_t* theap = _mi_theap_default();164if mi_unlikely(!mi_theap_is_initialized(theap)) {165mi_thread_init();166theap = _mi_theap_default();167mi_assert_internal(mi_theap_is_initialized(theap));168}169return theap;170}171172// todo: make order of parameters consistent (but would that break compat with CPython?)173void _mi_theap_init(mi_theap_t* theap, mi_heap_t* heap, mi_tld_t* tld)174{175mi_assert_internal(theap!=NULL);176mi_assert_internal(heap!=NULL);177mi_memid_t memid = theap->memid;178_mi_memcpy_aligned(theap, &_mi_theap_empty, sizeof(mi_theap_t));179theap->memid = memid;180theap->refcount = 1;181theap->tld = tld; // avoid reading the thread-local tld during initialization182mi_atomic_store_ptr_relaxed(mi_heap_t,&theap->heap,heap);183184_mi_theap_options_init(theap);185if (theap->tld->is_in_threadpool) {186// if we run as part of a thread pool it is better to not arbitrarily reclaim abandoned pages into our theap.187// this is checked in `free.c:mi_free_try_collect_mt`188// .. but abandoning is good in this case: halve the full page retain (possibly to 0)189// (so blocked threads do not hold on to too much memory)190if (theap->page_full_retain > 0) {191theap->page_full_retain = theap->page_full_retain / 4;192}193}194195// push on the thread local theaps list196mi_theap_t* head = NULL;197mi_lock(&theap->tld->theaps_lock) {198head = theap->tld->theaps;199theap->tprev = NULL;200theap->tnext = head;201if (head!=NULL) { head->tprev = theap; }202theap->tld->theaps = theap;203}204205// initialize random206if (head == NULL) { // first theap in this thread?207#if defined(_WIN32) && !defined(MI_SHARED_LIB)208_mi_random_init_weak(&theap->random); // prevent allocation failure during bcrypt dll initialization with static linking (issue #1185)209#else210_mi_random_init(&theap->random);211#endif212}213else {214_mi_random_split(&head->random, &theap->random);215}216theap->cookie = _mi_theap_random_next(theap) | 1;217_mi_theap_guarded_init(theap);218mi_subproc_stat_increase(_mi_subproc(),theaps,1);219220// push on the heap's theap list221mi_lock(&heap->theaps_lock) {222head = heap->theaps;223theap->hprev = NULL;224theap->hnext = head;225if (head!=NULL) { head->hprev = theap; }226heap->theaps = theap;227}228}229230mi_theap_t* _mi_theap_create(mi_heap_t* heap, mi_tld_t* tld) {231mi_assert_internal(tld!=NULL);232mi_assert_internal(heap!=NULL);233// allocate and initialize a theap234mi_memid_t memid;235mi_theap_t* theap;236//if (!_mi_is_heap_main(heap)) {237// theap = (mi_theap_t*)mi_heap_zalloc(mi_heap_main(),sizeof(mi_theap_t));238// memid = _mi_memid_create(MI_MEM_HEAP_MAIN);239// memid.initially_zero = memid.initially_committed = true;240//}241//else242if (heap->exclusive_arena == NULL) {243theap = (mi_theap_t*)_mi_meta_zalloc(sizeof(mi_theap_t), &memid);244}245else {246// theaps associated with a specific arena are allocated in that arena247// note: takes up at least one slice which is quite wasteful...248const size_t size = _mi_align_up(sizeof(mi_theap_t),MI_ARENA_MIN_OBJ_SIZE);249theap = (mi_theap_t*)_mi_arenas_alloc(heap, size, true, true, heap->exclusive_arena, tld->thread_seq, tld->numa_node, &memid);250mi_assert_internal(memid.mem.os.size >= size);251}252if (theap==NULL) {253_mi_error_message(ENOMEM, "unable to allocate theap meta-data\n");254return NULL;255}256theap->memid = memid;257_mi_theap_init(theap, heap, tld);258return theap;259}260261uintptr_t _mi_theap_random_next(mi_theap_t* theap) {262return _mi_random_next(&theap->random);263}264265static void mi_theap_free_mem(mi_theap_t* theap) {266if (theap!=NULL) {267mi_subproc_stat_decrease(_mi_subproc(),theaps,1);268// free the used memory269if (theap->memid.memkind == MI_MEM_HEAP_MAIN) { // note: for now unused as it would access theap_default stats in mi_free of the current theap270mi_assert_internal(_mi_is_heap_main(mi_heap_of(theap)));271mi_free(theap);272}273else if (theap->memid.memkind == MI_MEM_META) {274_mi_meta_free(theap, sizeof(*theap), theap->memid);275}276else {277_mi_arenas_free(theap, _mi_align_up(sizeof(*theap),MI_ARENA_MIN_OBJ_SIZE), theap->memid ); // issue #1168, avoid assertion failure278}279}280}281282void _mi_theap_incref(mi_theap_t* theap) {283if (theap!=NULL && theap->memid.memkind > MI_MEM_STATIC) {284mi_atomic_increment_acq_rel(&theap->refcount);285}286}287288void _mi_theap_decref(mi_theap_t* theap) {289if (theap!=NULL && theap->memid.memkind > MI_MEM_STATIC) {290if (mi_atomic_decrement_acq_rel(&theap->refcount) == 1) {291mi_theap_free_mem(theap);292}293}294}295296297// called from `mi_theap_delete` to free the internal theap resources.298bool _mi_theap_free(mi_theap_t* theap, bool acquire_heap_theaps_lock, bool acquire_tld_theaps_lock) {299mi_assert(theap != NULL);300if (theap==NULL) return true;301302mi_heap_t* const heap = mi_atomic_exchange_ptr_acq_rel(mi_heap_t, &theap->heap, NULL);303if (heap==NULL) {304// concurrent interaction, retry in an outer loop (as the other thread may be blocked on our lock)305return false;306}307else {308// merge stats to the owning heap309_mi_stats_merge_into(&heap->stats, &theap->stats);310311// remove ourselves from the heap theaps list312mi_lock_maybe(&heap->theaps_lock, acquire_heap_theaps_lock) {313if (theap->hnext != NULL) { theap->hnext->hprev = theap->hprev; }314if (theap->hprev != NULL) { theap->hprev->hnext = theap->hnext; }315else { mi_assert_internal(heap->theaps == theap); heap->theaps = theap->hnext; }316theap->hnext = theap->hprev = NULL;317}318319// remove ourselves from the thread local theaps list320mi_lock_maybe(&theap->tld->theaps_lock, acquire_tld_theaps_lock) {321if (theap->tnext != NULL) { theap->tnext->tprev = theap->tprev; }322if (theap->tprev != NULL) { theap->tprev->tnext = theap->tnext; }323else { mi_assert_internal(theap->tld->theaps == theap); theap->tld->theaps = theap->tnext; }324theap->tnext = theap->tprev = NULL;325}326theap->tld = NULL;327_mi_theap_decref(theap);328return true;329}330}331332333/* -----------------------------------------------------------334Heap destroy335----------------------------------------------------------- */336/*337338// zero out the page queues339static void mi_theap_reset_pages(mi_theap_t* theap) {340mi_assert_internal(theap != NULL);341mi_assert_internal(mi_theap_is_initialized(theap));342// TODO: copy full empty theap instead?343_mi_memset(&theap->pages_free_direct, 0, sizeof(theap->pages_free_direct));344_mi_memcpy_aligned(&theap->pages, &_mi_theap_empty.pages, sizeof(theap->pages));345// theap->thread_delayed_free = NULL;346theap->page_count = 0;347}348349static bool _mi_theap_page_destroy(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) {350MI_UNUSED(arg1);351MI_UNUSED(arg2);352MI_UNUSED(pq);353354// ensure no more thread_delayed_free will be added355//_mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);356357// stats358const size_t bsize = mi_page_block_size(page);359if (bsize > MI_LARGE_MAX_OBJ_SIZE) {360mi_theap_stat_decrease(theap, malloc_huge, bsize);361}362#if (MI_STAT>0)363_mi_page_free_collect(page, false); // update used count364const size_t inuse = page->used;365if (bsize <= MI_LARGE_MAX_OBJ_SIZE) {366mi_theap_stat_decrease(theap, malloc_normal, bsize * inuse);367#if (MI_STAT>1)368mi_theap_stat_decrease(theap, malloc_bins[_mi_bin(bsize)], inuse);369#endif370}371// mi_theap_stat_decrease(theap, malloc_requested, bsize * inuse); // todo: off for aligned blocks...372#endif373374/// pretend it is all free now375mi_assert_internal(mi_page_thread_free(page) == NULL);376page->used = 0;377378// and free the page379// mi_page_free(page,false);380page->next = NULL;381page->prev = NULL;382mi_page_set_theap(page, NULL);383_mi_arenas_page_free(page, theap);384385return true; // keep going386}387388void _mi_theap_destroy_pages(mi_theap_t* theap) {389mi_theap_visit_pages(theap, &_mi_theap_page_destroy, NULL, NULL);390mi_theap_reset_pages(theap);391}392393#if MI_TRACK_HEAP_DESTROY394static bool mi_cdecl mi_theap_track_block_free(const mi_theap_t* theap, const mi_theap_area_t* area, void* block, size_t block_size, void* arg) {395MI_UNUSED(theap); MI_UNUSED(area); MI_UNUSED(arg); MI_UNUSED(block_size);396mi_track_free_size(block,mi_usable_size(block));397return true;398}399#endif400401void mi_theap_destroy(mi_theap_t* theap) {402mi_assert(theap != NULL);403mi_assert(mi_theap_is_initialized(theap));404mi_assert(!theap->allow_page_reclaim);405mi_assert(!theap->allow_page_abandon);406mi_assert_expensive(mi_theap_is_valid(theap));407if (theap==NULL || !mi_theap_is_initialized(theap)) return;408#if MI_GUARDED409// _mi_warning_message("'mi_theap_destroy' called but MI_GUARDED is enabled -- using `mi_theap_delete` instead (theap at %p)\n", theap);410mi_theap_delete(theap);411return;412#else413if (theap->allow_page_reclaim) {414_mi_warning_message("'mi_theap_destroy' called but ignored as the theap was not created with 'allow_destroy' (theap at %p)\n", theap);415// don't free in case it may contain reclaimed pages,416mi_theap_delete(theap);417}418else {419// track all blocks as freed420#if MI_TRACK_HEAP_DESTROY421mi_theap_visit_blocks(theap, true, mi_theap_track_block_free, NULL);422#endif423// free all pages424_mi_theap_destroy_pages(theap);425mi_theap_free(theap,true);426}427#endif428}429430// forcefully destroy all theaps in the current thread431void _mi_theap_unsafe_destroy_all(mi_theap_t* theap) {432mi_assert_internal(theap != NULL);433if (theap == NULL) return;434mi_theap_t* curr = theap->tld->theaps;435while (curr != NULL) {436mi_theap_t* next = curr->next;437if (!curr->allow_page_reclaim) {438mi_theap_destroy(curr);439}440else {441_mi_theap_destroy_pages(curr);442}443curr = next;444}445}446*/447448/* -----------------------------------------------------------449Safe Heap delete450----------------------------------------------------------- */451452// Safe delete a theap without freeing any still allocated blocks in that theap.453void _mi_theap_delete(mi_theap_t* theap, bool acquire_tld_theaps_lock)454{455mi_assert(theap != NULL);456mi_assert(mi_theap_is_initialized(theap));457mi_assert_expensive(mi_theap_is_valid(theap));458if (theap==NULL || !mi_theap_is_initialized(theap)) return;459460// abandon all pages461_mi_theap_collect_abandon(theap);462463mi_assert_internal(theap->page_count==0);464_mi_theap_free(theap, true /* acquire heap->theaps_lock */, acquire_tld_theaps_lock);465}466467468469/* -----------------------------------------------------------470Load/unload theaps471----------------------------------------------------------- */472/*473void mi_theap_unload(mi_theap_t* theap) {474mi_assert(mi_theap_is_initialized(theap));475mi_assert_expensive(mi_theap_is_valid(theap));476if (theap==NULL || !mi_theap_is_initialized(theap)) return;477if (_mi_theap_heap(theap)->exclusive_arena == NULL) {478_mi_warning_message("cannot unload theaps that are not associated with an exclusive arena\n");479return;480}481482// abandon all pages so all thread'id in the pages are cleared483_mi_theap_collect_abandon(theap);484mi_assert_internal(theap->page_count==0);485486// remove from theap list487mi_theap_free(theap, false); // but don't actually free the memory488489// disassociate from the current thread-local and static state490theap->tld = NULL;491return;492}493494bool mi_theap_reload(mi_theap_t* theap, mi_arena_id_t arena_id) {495mi_assert(mi_theap_is_initialized(theap));496if (theap==NULL || !mi_theap_is_initialized(theap)) return false;497if (_mi_theap_heap(theap)->exclusive_arena == NULL) {498_mi_warning_message("cannot reload theaps that were not associated with an exclusive arena\n");499return false;500}501if (theap->tld != NULL) {502_mi_warning_message("cannot reload theaps that were not unloaded first\n");503return false;504}505mi_arena_t* arena = _mi_arena_from_id(arena_id);506if (_mi_theap_heap(theap)->exclusive_arena != arena) {507_mi_warning_message("trying to reload a theap at a different arena address: %p vs %p\n", _mi_theap_heap(theap)->exclusive_arena, arena);508return false;509}510511mi_assert_internal(theap->page_count==0);512513// re-associate with the current thread-local and static state514theap->tld = mi_theap_get_default()->tld;515516// reinit direct pages (as we may be in a different process)517mi_assert_internal(theap->page_count == 0);518for (size_t i = 0; i < MI_PAGES_DIRECT; i++) {519theap->pages_free_direct[i] = (mi_page_t*)&_mi_page_empty;520}521522// push on the thread local theaps list523theap->tnext = theap->tld->theaps;524theap->tld->theaps = theap;525return true;526}527*/528529530/* -----------------------------------------------------------531Visit all theap blocks and areas532Todo: enable visiting abandoned pages, and533enable visiting all blocks of all theaps across threads534----------------------------------------------------------- */535536void _mi_heap_area_init(mi_heap_area_t* area, mi_page_t* page) {537const size_t bsize = mi_page_block_size(page);538const size_t ubsize = mi_page_usable_block_size(page);539area->reserved = page->reserved * bsize;540area->committed = page->capacity * bsize;541area->blocks = mi_page_start(page);542area->used = page->used; // number of blocks in use (#553)543area->block_size = ubsize;544area->full_block_size = bsize;545area->reserved1 = page;546}547548static void mi_get_fast_divisor(size_t divisor, uint64_t* magic, size_t* shift) {549mi_assert_internal(divisor > 0 && divisor <= UINT32_MAX);550*shift = MI_SIZE_BITS - mi_clz(divisor - 1);551*magic = ((((uint64_t)1 << 32) * (((uint64_t)1 << *shift) - divisor)) / divisor + 1);552}553554static size_t mi_fast_divide(size_t n, uint64_t magic, size_t shift) {555mi_assert_internal(n <= UINT32_MAX);556const uint64_t hi = ((uint64_t)n * magic) >> 32;557return (size_t)((hi + n) >> shift);558}559560bool _mi_theap_area_visit_blocks(const mi_heap_area_t* area, mi_page_t* page, mi_block_visit_fun* visitor, void* arg) {561mi_assert(area != NULL);562if (area==NULL) return true;563mi_assert(page != NULL);564if (page == NULL) return true;565566_mi_page_free_collect(page,true); // collect both thread_delayed and local_free567mi_assert_internal(page->local_free == NULL);568if (page->used == 0) return true;569570size_t psize;571uint8_t* const pstart = mi_page_area(page, &psize);572mi_heap_t* const heap = mi_page_heap(page);573const size_t bsize = mi_page_block_size(page);574const size_t ubsize = mi_page_usable_block_size(page); // without padding575576// optimize page with one block577if (page->capacity == 1) {578mi_assert_internal(page->used == 1 && page->free == NULL);579return visitor(heap, area, pstart, ubsize, arg);580}581mi_assert(bsize <= UINT32_MAX);582583// optimize full pages584if (page->used == page->capacity) {585uint8_t* block = pstart;586for (size_t i = 0; i < page->capacity; i++) {587if (!visitor(heap, area, block, ubsize, arg)) return false;588block += bsize;589}590return true;591}592593// create a bitmap of free blocks.594#define MI_MAX_BLOCKS (MI_SMALL_PAGE_SIZE / sizeof(void*))595uintptr_t free_map[MI_MAX_BLOCKS / MI_INTPTR_BITS];596const uintptr_t bmapsize = _mi_divide_up(page->capacity, MI_INTPTR_BITS);597memset(free_map, 0, bmapsize * sizeof(intptr_t));598if (page->capacity % MI_INTPTR_BITS != 0) {599// mark left-over bits at the end as free600size_t shift = (page->capacity % MI_INTPTR_BITS);601uintptr_t mask = (UINTPTR_MAX << shift);602free_map[bmapsize - 1] = mask;603}604605// fast repeated division by the block size606uint64_t magic;607size_t shift;608mi_get_fast_divisor(bsize, &magic, &shift);609610#if MI_DEBUG>1611size_t free_count = 0;612#endif613for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {614#if MI_DEBUG>1615free_count++;616#endif617mi_assert_internal((uint8_t*)block >= pstart && (uint8_t*)block < (pstart + psize));618size_t offset = (uint8_t*)block - pstart;619mi_assert_internal(offset % bsize == 0);620mi_assert_internal(offset <= UINT32_MAX);621size_t blockidx = mi_fast_divide(offset, magic, shift);622mi_assert_internal(blockidx == offset / bsize);623mi_assert_internal(blockidx < MI_MAX_BLOCKS);624size_t bitidx = (blockidx / MI_INTPTR_BITS);625size_t bit = blockidx - (bitidx * MI_INTPTR_BITS);626free_map[bitidx] |= ((uintptr_t)1 << bit);627}628mi_assert_internal(page->capacity == (free_count + page->used));629630// walk through all blocks skipping the free ones631#if MI_DEBUG>1632size_t used_count = 0;633#endif634uint8_t* block = pstart;635for (size_t i = 0; i < bmapsize; i++) {636if (free_map[i] == 0) {637// every block is in use638for (size_t j = 0; j < MI_INTPTR_BITS; j++) {639#if MI_DEBUG>1640used_count++;641#endif642if (!visitor(heap, area, block, ubsize, arg)) return false;643block += bsize;644}645}646else {647// visit the used blocks in the mask648uintptr_t m = ~free_map[i];649while (m != 0) {650#if MI_DEBUG>1651used_count++;652#endif653size_t bitidx = mi_ctz(m);654if (!visitor(heap, area, block + (bitidx * bsize), ubsize, arg)) return false;655m &= m - 1; // clear least significant bit656}657block += bsize * MI_INTPTR_BITS;658}659}660mi_assert_internal(page->used == used_count);661return true;662}663664665666// Separate struct to keep `mi_page_t` out of the public interface667typedef struct mi_theap_area_ex_s {668mi_heap_area_t area;669mi_page_t* page;670} mi_theap_area_ex_t;671672typedef bool (mi_theap_area_visit_fun)(const mi_theap_t* theap, const mi_theap_area_ex_t* area, void* arg);673674static bool mi_theap_visit_areas_page(mi_theap_t* theap, mi_page_queue_t* pq, mi_page_t* page, void* vfun, void* arg) {675MI_UNUSED(theap);676MI_UNUSED(pq);677mi_theap_area_visit_fun* fun = (mi_theap_area_visit_fun*)vfun;678mi_theap_area_ex_t xarea;679xarea.page = page;680_mi_heap_area_init(&xarea.area, page);681return fun(theap, &xarea, arg);682}683684// Visit all theap pages as areas685static bool mi_theap_visit_areas(const mi_theap_t* theap, mi_theap_area_visit_fun* visitor, void* arg) {686if (visitor == NULL) return false;687return mi_theap_visit_pages((mi_theap_t*)theap, &mi_theap_visit_areas_page, true, (void*)(visitor), arg); // note: function pointer to void* :-{688}689690// Just to pass arguments691typedef struct mi_visit_blocks_args_s {692bool visit_blocks;693mi_block_visit_fun* visitor;694void* arg;695} mi_visit_blocks_args_t;696697static bool mi_theap_area_visitor(const mi_theap_t* theap, const mi_theap_area_ex_t* xarea, void* arg) {698mi_visit_blocks_args_t* args = (mi_visit_blocks_args_t*)arg;699if (!args->visitor(_mi_theap_heap(theap), &xarea->area, NULL, xarea->area.block_size, args->arg)) return false;700if (args->visit_blocks) {701return _mi_theap_area_visit_blocks(&xarea->area, xarea->page, args->visitor, args->arg);702}703else {704return true;705}706}707708// Visit all blocks in a theap709bool mi_theap_visit_blocks(const mi_theap_t* theap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {710mi_visit_blocks_args_t args = { visit_blocks, visitor, arg };711return mi_theap_visit_areas(theap, &mi_theap_area_visitor, &args);712}713714715716