Path: blob/main/system/lib/mimalloc/src/page.c
6175 views
/*----------------------------------------------------------------------------1Copyright (c) 2018-2024, 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/* -----------------------------------------------------------8The core of the allocator. Every segment contains9pages of a certain block size. The main function10exported is `mi_malloc_generic`.11----------------------------------------------------------- */1213#include "mimalloc.h"14#include "mimalloc/internal.h"15#include "mimalloc/atomic.h"1617/* -----------------------------------------------------------18Definition of page queues for each block size19----------------------------------------------------------- */2021#define MI_IN_PAGE_C22#include "page-queue.c"23#undef MI_IN_PAGE_C242526/* -----------------------------------------------------------27Page helpers28----------------------------------------------------------- */2930// Index a block in a page31static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) {32MI_UNUSED(page);33mi_assert_internal(page != NULL);34mi_assert_internal(i <= page->reserved);35return (mi_block_t*)((uint8_t*)page_start + (i * block_size));36}3738static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld);39static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld);4041#if (MI_DEBUG>=3)42static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) {43size_t count = 0;44while (head != NULL) {45mi_assert_internal(page == _mi_ptr_page(head));46count++;47head = mi_block_next(page, head);48}49return count;50}5152/*53// Start of the page available memory54static inline uint8_t* mi_page_area(const mi_page_t* page) {55return _mi_page_start(_mi_page_segment(page), page, NULL);56}57*/5859static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {60size_t psize;61uint8_t* page_area = _mi_segment_page_start(_mi_page_segment(page), page, &psize);62mi_block_t* start = (mi_block_t*)page_area;63mi_block_t* end = (mi_block_t*)(page_area + psize);64while(p != NULL) {65if (p < start || p >= end) return false;66p = mi_block_next(page, p);67}68#if MI_DEBUG>3 // generally too expensive to check this69if (page->free_is_zero) {70const size_t ubsize = mi_page_usable_block_size(page);71for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {72mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));73}74}75#endif76return true;77}7879static bool mi_page_is_valid_init(mi_page_t* page) {80mi_assert_internal(mi_page_block_size(page) > 0);81mi_assert_internal(page->used <= page->capacity);82mi_assert_internal(page->capacity <= page->reserved);8384uint8_t* start = mi_page_start(page);85mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL));86mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE));87//mi_assert_internal(start + page->capacity*page->block_size == page->top);8889mi_assert_internal(mi_page_list_is_valid(page,page->free));90mi_assert_internal(mi_page_list_is_valid(page,page->local_free));9192#if MI_DEBUG>3 // generally too expensive to check this93if (page->free_is_zero) {94const size_t ubsize = mi_page_usable_block_size(page);95for(mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) {96mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));97}98}99#endif100101#if !MI_TRACK_ENABLED && !MI_TSAN102mi_block_t* tfree = mi_page_thread_free(page);103mi_assert_internal(mi_page_list_is_valid(page, tfree));104//size_t tfree_count = mi_page_list_count(page, tfree);105//mi_assert_internal(tfree_count <= page->thread_freed + 1);106#endif107108size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free);109mi_assert_internal(page->used + free_count == page->capacity);110111return true;112}113114extern bool _mi_process_is_initialized; // has mi_process_init been called?115116bool _mi_page_is_valid(mi_page_t* page) {117mi_assert_internal(mi_page_is_valid_init(page));118#if MI_SECURE119mi_assert_internal(page->keys[0] != 0);120#endif121if (mi_page_heap(page)!=NULL) {122mi_segment_t* segment = _mi_page_segment(page);123124mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);125#if MI_HUGE_PAGE_ABANDON126if (segment->kind != MI_SEGMENT_HUGE)127#endif128{129mi_page_queue_t* pq = mi_page_queue_of(page);130mi_assert_internal(mi_page_queue_contains(pq, page));131mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page));132mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq));133}134}135return true;136}137#endif138139void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {140while (!_mi_page_try_use_delayed_free(page, delay, override_never)) {141mi_atomic_yield();142}143}144145bool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {146mi_thread_free_t tfreex;147mi_delayed_t old_delay;148mi_thread_free_t tfree;149size_t yield_count = 0;150do {151tfree = mi_atomic_load_acquire(&page->xthread_free); // note: must acquire as we can break/repeat this loop and not do a CAS;152tfreex = mi_tf_set_delayed(tfree, delay);153old_delay = mi_tf_delayed(tfree);154if mi_unlikely(old_delay == MI_DELAYED_FREEING) {155if (yield_count >= 4) return false; // give up after 4 tries156yield_count++;157mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done.158// tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail159}160else if (delay == old_delay) {161break; // avoid atomic operation if already equal162}163else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) {164break; // leave never-delayed flag set165}166} while ((old_delay == MI_DELAYED_FREEING) ||167!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));168169return true; // success170}171172/* -----------------------------------------------------------173Page collect the `local_free` and `thread_free` lists174----------------------------------------------------------- */175176// Collect the local `thread_free` list using an atomic exchange.177// Note: The exchange must be done atomically as this is used right after178// moving to the full list in `mi_page_collect_ex` and we need to179// ensure that there was no race where the page became unfull just before the move.180static void _mi_page_thread_free_collect(mi_page_t* page)181{182mi_block_t* head;183mi_thread_free_t tfreex;184mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free);185do {186head = mi_tf_block(tfree);187tfreex = mi_tf_set_block(tfree,NULL);188} while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex));189190// return if the list is empty191if (head == NULL) return;192193// find the tail -- also to get a proper count (without data races)194size_t max_count = page->capacity; // cannot collect more than capacity195size_t count = 1;196mi_block_t* tail = head;197mi_block_t* next;198while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) {199count++;200tail = next;201}202// if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free)203if (count > max_count) {204_mi_error_message(EFAULT, "corrupted thread-free list\n");205return; // the thread-free items cannot be freed206}207208// and append the current local free list209mi_block_set_next(page,tail, page->local_free);210page->local_free = head;211212// update counts now213page->used -= (uint16_t)count;214}215216void _mi_page_free_collect(mi_page_t* page, bool force) {217mi_assert_internal(page!=NULL);218219// collect the thread free list220if (force || mi_page_thread_free(page) != NULL) { // quick test to avoid an atomic operation221_mi_page_thread_free_collect(page);222}223224// and the local free list225if (page->local_free != NULL) {226if mi_likely(page->free == NULL) {227// usual case228page->free = page->local_free;229page->local_free = NULL;230page->free_is_zero = false;231}232else if (force) {233// append -- only on shutdown (force) as this is a linear operation234mi_block_t* tail = page->local_free;235mi_block_t* next;236while ((next = mi_block_next(page, tail)) != NULL) {237tail = next;238}239mi_block_set_next(page, tail, page->free);240page->free = page->local_free;241page->local_free = NULL;242page->free_is_zero = false;243}244}245246mi_assert_internal(!force || page->local_free == NULL);247}248249250251/* -----------------------------------------------------------252Page fresh and retire253----------------------------------------------------------- */254255// called from segments when reclaiming abandoned pages256void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {257mi_assert_expensive(mi_page_is_valid_init(page));258259mi_assert_internal(mi_page_heap(page) == heap);260mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE);261#if MI_HUGE_PAGE_ABANDON262mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);263#endif264265// TODO: push on full queue immediately if it is full?266mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page));267mi_page_queue_push(heap, pq, page);268mi_assert_expensive(_mi_page_is_valid(page));269}270271// allocate a fresh page from a segment272static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size, size_t page_alignment) {273#if !MI_HUGE_PAGE_ABANDON274mi_assert_internal(pq != NULL);275mi_assert_internal(mi_heap_contains_queue(heap, pq));276mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size);277#endif278mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments, &heap->tld->os);279if (page == NULL) {280// this may be out-of-memory, or an abandoned page was reclaimed (and in our queue)281return NULL;282}283#if MI_HUGE_PAGE_ABANDON284mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE);285#endif286mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE);287mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size);288// a fresh page was found, initialize it289const size_t full_block_size = (pq == NULL || mi_page_is_huge(page) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc290mi_assert_internal(full_block_size >= block_size);291mi_page_init(heap, page, full_block_size, heap->tld);292mi_heap_stat_increase(heap, pages, 1);293if (pq != NULL) { mi_page_queue_push(heap, pq, page); }294mi_assert_expensive(_mi_page_is_valid(page));295return page;296}297298// Get a fresh page to use299static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) {300mi_assert_internal(mi_heap_contains_queue(heap, pq));301mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size, 0);302if (page==NULL) return NULL;303mi_assert_internal(pq->block_size==mi_page_block_size(page));304mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page)));305return page;306}307308/* -----------------------------------------------------------309Do any delayed frees310(put there by other threads if they deallocated in a full page)311----------------------------------------------------------- */312void _mi_heap_delayed_free_all(mi_heap_t* heap) {313while (!_mi_heap_delayed_free_partial(heap)) {314mi_atomic_yield();315}316}317318// returns true if all delayed frees were processed319bool _mi_heap_delayed_free_partial(mi_heap_t* heap) {320// take over the list (note: no atomic exchange since it is often NULL)321mi_block_t* block = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);322while (block != NULL && !mi_atomic_cas_ptr_weak_acq_rel(mi_block_t, &heap->thread_delayed_free, &block, NULL)) { /* nothing */ };323bool all_freed = true;324325// and free them all326while(block != NULL) {327mi_block_t* next = mi_block_nextx(heap,block, heap->keys);328// use internal free instead of regular one to keep stats etc correct329if (!_mi_free_delayed_block(block)) {330// we might already start delayed freeing while another thread has not yet331// reset the delayed_freeing flag; in that case delay it further by reinserting the current block332// into the delayed free list333all_freed = false;334mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);335do {336mi_block_set_nextx(heap, block, dfree, heap->keys);337} while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block));338}339block = next;340}341return all_freed;342}343344/* -----------------------------------------------------------345Unfull, abandon, free and retire346----------------------------------------------------------- */347348// Move a page from the full list back to a regular list349void _mi_page_unfull(mi_page_t* page) {350mi_assert_internal(page != NULL);351mi_assert_expensive(_mi_page_is_valid(page));352mi_assert_internal(mi_page_is_in_full(page));353if (!mi_page_is_in_full(page)) return;354355mi_heap_t* heap = mi_page_heap(page);356mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL];357mi_page_set_in_full(page, false); // to get the right queue358mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);359mi_page_set_in_full(page, true);360mi_page_queue_enqueue_from(pq, pqfull, page);361}362363static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {364mi_assert_internal(pq == mi_page_queue_of(page));365mi_assert_internal(!mi_page_immediate_available(page));366mi_assert_internal(!mi_page_is_in_full(page));367368if (mi_page_is_in_full(page)) return;369mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page);370_mi_page_free_collect(page,false); // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set371}372373374// Abandon a page with used blocks at the end of a thread.375// Note: only call if it is ensured that no references exist from376// the `page->heap->thread_delayed_free` into this page.377// Currently only called through `mi_heap_collect_ex` which ensures this.378void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {379mi_assert_internal(page != NULL);380mi_assert_expensive(_mi_page_is_valid(page));381mi_assert_internal(pq == mi_page_queue_of(page));382mi_assert_internal(mi_page_heap(page) != NULL);383384mi_heap_t* pheap = mi_page_heap(page);385386// remove from our page list387mi_segments_tld_t* segments_tld = &pheap->tld->segments;388mi_page_queue_remove(pq, page);389390// page is no longer associated with our heap391mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);392mi_page_set_heap(page, NULL);393394#if (MI_DEBUG>1) && !MI_TRACK_ENABLED395// check there are no references left..396for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) {397mi_assert_internal(_mi_ptr_page(block) != page);398}399#endif400401// and abandon it402mi_assert_internal(mi_page_heap(page) == NULL);403_mi_segment_page_abandon(page,segments_tld);404}405406407// Free a page with no more free blocks408void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {409mi_assert_internal(page != NULL);410mi_assert_expensive(_mi_page_is_valid(page));411mi_assert_internal(pq == mi_page_queue_of(page));412mi_assert_internal(mi_page_all_free(page));413mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING);414415// no more aligned blocks in here416mi_page_set_has_aligned(page, false);417418mi_heap_t* heap = mi_page_heap(page);419420// remove from the page list421// (no need to do _mi_heap_delayed_free first as all blocks are already free)422mi_segments_tld_t* segments_tld = &heap->tld->segments;423mi_page_queue_remove(pq, page);424425// and free it426mi_page_set_heap(page,NULL);427_mi_segment_page_free(page, force, segments_tld);428}429430#define MI_MAX_RETIRE_SIZE MI_MEDIUM_OBJ_SIZE_MAX // should be less than size for MI_BIN_HUGE431#define MI_RETIRE_CYCLES (16)432433// Retire a page with no more used blocks434// Important to not retire too quickly though as new435// allocations might coming.436// Note: called from `mi_free` and benchmarks often437// trigger this due to freeing everything and then438// allocating again so careful when changing this.439void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {440mi_assert_internal(page != NULL);441mi_assert_expensive(_mi_page_is_valid(page));442mi_assert_internal(mi_page_all_free(page));443444mi_page_set_has_aligned(page, false);445446// don't retire too often..447// (or we end up retiring and re-allocating most of the time)448// NOTE: refine this more: we should not retire if this449// is the only page left with free blocks. It is not clear450// how to check this efficiently though...451// for now, we don't retire if it is the only page left of this size class.452mi_page_queue_t* pq = mi_page_queue_of(page);453const size_t bsize = mi_page_block_size(page);454if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) { // not full or huge queue?455if (pq->last==page && pq->first==page) { // the only page in the queue?456mi_stat_counter_increase(_mi_stats_main.page_no_retire,1);457page->retire_expire = (bsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);458mi_heap_t* heap = mi_page_heap(page);459mi_assert_internal(pq >= heap->pages);460const size_t index = pq - heap->pages;461mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE);462if (index < heap->page_retired_min) heap->page_retired_min = index;463if (index > heap->page_retired_max) heap->page_retired_max = index;464mi_assert_internal(mi_page_all_free(page));465return; // don't free after all466}467}468_mi_page_free(page, pq, false);469}470471// free retired pages: we don't need to look at the entire queues472// since we only retire pages that are at the head position in a queue.473void _mi_heap_collect_retired(mi_heap_t* heap, bool force) {474size_t min = MI_BIN_FULL;475size_t max = 0;476for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) {477mi_page_queue_t* pq = &heap->pages[bin];478mi_page_t* page = pq->first;479if (page != NULL && page->retire_expire != 0) {480if (mi_page_all_free(page)) {481page->retire_expire--;482if (force || page->retire_expire == 0) {483_mi_page_free(pq->first, pq, force);484}485else {486// keep retired, update min/max487if (bin < min) min = bin;488if (bin > max) max = bin;489}490}491else {492page->retire_expire = 0;493}494}495}496heap->page_retired_min = min;497heap->page_retired_max = max;498}499500501/* -----------------------------------------------------------502Initialize the initial free list in a page.503In secure mode we initialize a randomized list by504alternating between slices.505----------------------------------------------------------- */506507#define MI_MAX_SLICE_SHIFT (6) // at most 64 slices508#define MI_MAX_SLICES (1UL << MI_MAX_SLICE_SHIFT)509#define MI_MIN_SLICES (2)510511static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) {512MI_UNUSED(stats);513#if (MI_SECURE<=2)514mi_assert_internal(page->free == NULL);515mi_assert_internal(page->local_free == NULL);516#endif517mi_assert_internal(page->capacity + extend <= page->reserved);518mi_assert_internal(bsize == mi_page_block_size(page));519void* const page_area = mi_page_start(page);520521// initialize a randomized free list522// set up `slice_count` slices to alternate between523size_t shift = MI_MAX_SLICE_SHIFT;524while ((extend >> shift) == 0) {525shift--;526}527const size_t slice_count = (size_t)1U << shift;528const size_t slice_extend = extend / slice_count;529mi_assert_internal(slice_extend >= 1);530mi_block_t* blocks[MI_MAX_SLICES]; // current start of the slice531size_t counts[MI_MAX_SLICES]; // available objects in the slice532for (size_t i = 0; i < slice_count; i++) {533blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend);534counts[i] = slice_extend;535}536counts[slice_count-1] += (extend % slice_count); // final slice holds the modulus too (todo: distribute evenly?)537538// and initialize the free list by randomly threading through them539// set up first element540const uintptr_t r = _mi_heap_random_next(heap);541size_t current = r % slice_count;542counts[current]--;543mi_block_t* const free_start = blocks[current];544// and iterate through the rest; use `random_shuffle` for performance545uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0546for (size_t i = 1; i < extend; i++) {547// call random_shuffle only every INTPTR_SIZE rounds548const size_t round = i%MI_INTPTR_SIZE;549if (round == 0) rnd = _mi_random_shuffle(rnd);550// select a random next slice index551size_t next = ((rnd >> 8*round) & (slice_count-1));552while (counts[next]==0) { // ensure it still has space553next++;554if (next==slice_count) next = 0;555}556// and link the current block to it557counts[next]--;558mi_block_t* const block = blocks[current];559blocks[current] = (mi_block_t*)((uint8_t*)block + bsize); // bump to the following block560mi_block_set_next(page, block, blocks[next]); // and set next; note: we may have `current == next`561current = next;562}563// prepend to the free list (usually NULL)564mi_block_set_next(page, blocks[current], page->free); // end of the list565page->free = free_start;566}567568static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats)569{570MI_UNUSED(stats);571#if (MI_SECURE <= 2)572mi_assert_internal(page->free == NULL);573mi_assert_internal(page->local_free == NULL);574#endif575mi_assert_internal(page->capacity + extend <= page->reserved);576mi_assert_internal(bsize == mi_page_block_size(page));577void* const page_area = mi_page_start(page);578579mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity);580581// initialize a sequential free list582mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1);583mi_block_t* block = start;584while(block <= last) {585mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize);586mi_block_set_next(page,block,next);587block = next;588}589// prepend to free list (usually `NULL`)590mi_block_set_next(page, last, page->free);591page->free = start;592}593594/* -----------------------------------------------------------595Page initialize and extend the capacity596----------------------------------------------------------- */597598#define MI_MAX_EXTEND_SIZE (4*1024) // heuristic, one OS page seems to work well.599#if (MI_SECURE>0)600#define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many601#else602#define MI_MIN_EXTEND (4)603#endif604605// Extend the capacity (up to reserved) by initializing a free list606// We do at most `MI_MAX_EXTEND` to avoid touching too much memory607// Note: we also experimented with "bump" allocation on the first608// allocations but this did not speed up any benchmark (due to an609// extra test in malloc? or cache effects?)610static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {611MI_UNUSED(tld);612mi_assert_expensive(mi_page_is_valid_init(page));613#if (MI_SECURE<=2)614mi_assert(page->free == NULL);615mi_assert(page->local_free == NULL);616if (page->free != NULL) return;617#endif618if (page->capacity >= page->reserved) return;619620mi_stat_counter_increase(tld->stats.pages_extended, 1);621622// calculate the extend count623const size_t bsize = mi_page_block_size(page);624size_t extend = page->reserved - page->capacity;625mi_assert_internal(extend > 0);626627size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/bsize);628if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; }629mi_assert_internal(max_extend > 0);630631if (extend > max_extend) {632// ensure we don't touch memory beyond the page to reduce page commit.633// the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%.634extend = max_extend;635}636637mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved);638mi_assert_internal(extend < (1UL<<16));639640// and append the extend the free list641if (extend < MI_MIN_SLICES || MI_SECURE==0) { //!mi_option_is_enabled(mi_option_secure)) {642mi_page_free_list_extend(page, bsize, extend, &tld->stats );643}644else {645mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats);646}647// enable the new free list648page->capacity += (uint16_t)extend;649mi_stat_increase(tld->stats.page_committed, extend * bsize);650mi_assert_expensive(mi_page_is_valid_init(page));651}652653// Initialize a fresh page654static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) {655mi_assert(page != NULL);656mi_segment_t* segment = _mi_page_segment(page);657mi_assert(segment != NULL);658mi_assert_internal(block_size > 0);659// set fields660mi_page_set_heap(page, heap);661page->block_size = block_size;662size_t page_size;663page->page_start = _mi_segment_page_start(segment, page, &page_size);664mi_track_mem_noaccess(page->page_start,page_size);665mi_assert_internal(mi_page_block_size(page) <= page_size);666mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE);667mi_assert_internal(page_size / block_size < (1L<<16));668page->reserved = (uint16_t)(page_size / block_size);669mi_assert_internal(page->reserved > 0);670#if (MI_PADDING || MI_ENCODE_FREELIST)671page->keys[0] = _mi_heap_random_next(heap);672page->keys[1] = _mi_heap_random_next(heap);673#endif674page->free_is_zero = page->is_zero_init;675#if MI_DEBUG>2676if (page->is_zero_init) {677mi_track_mem_defined(page->page_start, page_size);678mi_assert_expensive(mi_mem_is_zero(page->page_start, page_size));679}680#endif681mi_assert_internal(page->is_committed);682if (block_size > 0 && _mi_is_power_of_two(block_size)) {683page->block_size_shift = (uint8_t)(mi_ctz((uintptr_t)block_size));684}685else {686page->block_size_shift = 0;687}688689mi_assert_internal(page->capacity == 0);690mi_assert_internal(page->free == NULL);691mi_assert_internal(page->used == 0);692mi_assert_internal(page->xthread_free == 0);693mi_assert_internal(page->next == NULL);694mi_assert_internal(page->prev == NULL);695mi_assert_internal(page->retire_expire == 0);696mi_assert_internal(!mi_page_has_aligned(page));697#if (MI_PADDING || MI_ENCODE_FREELIST)698mi_assert_internal(page->keys[0] != 0);699mi_assert_internal(page->keys[1] != 0);700#endif701mi_assert_internal(page->block_size_shift == 0 || (block_size == ((size_t)1 << page->block_size_shift)));702mi_assert_expensive(mi_page_is_valid_init(page));703704// initialize an initial free list705mi_page_extend_free(heap,page,tld);706mi_assert(mi_page_immediate_available(page));707}708709710/* -----------------------------------------------------------711Find pages with free blocks712-------------------------------------------------------------*/713714// Find a page with free blocks of `page->block_size`.715static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try)716{717// search through the pages in "next fit" order718#if MI_STAT719size_t count = 0;720#endif721mi_page_t* page = pq->first;722while (page != NULL)723{724mi_page_t* next = page->next; // remember next725#if MI_STAT726count++;727#endif728729// 0. collect freed blocks by us and other threads730_mi_page_free_collect(page, false);731732// 1. if the page contains free blocks, we are done733if (mi_page_immediate_available(page)) {734break; // pick this one735}736737// 2. Try to extend738if (page->capacity < page->reserved) {739mi_page_extend_free(heap, page, heap->tld);740mi_assert_internal(mi_page_immediate_available(page));741break;742}743744// 3. If the page is completely full, move it to the `mi_pages_full`745// queue so we don't visit long-lived pages too often.746mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));747mi_page_to_full(page, pq);748749page = next;750} // for each page751752mi_heap_stat_counter_increase(heap, searches, count);753754if (page == NULL) {755_mi_heap_collect_retired(heap, false); // perhaps make a page available?756page = mi_page_fresh(heap, pq);757if (page == NULL && first_try) {758// out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again759page = mi_page_queue_find_free_ex(heap, pq, false);760}761}762else {763mi_assert(pq->first == page);764page->retire_expire = 0;765}766mi_assert_internal(page == NULL || mi_page_immediate_available(page));767return page;768}769770771772// Find a page with free blocks of `size`.773static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {774mi_page_queue_t* pq = mi_page_queue(heap,size);775mi_page_t* page = pq->first;776if (page != NULL) {777#if (MI_SECURE>=3) // in secure mode, we extend half the time to increase randomness778if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) {779mi_page_extend_free(heap, page, heap->tld);780mi_assert_internal(mi_page_immediate_available(page));781}782else783#endif784{785_mi_page_free_collect(page,false);786}787788if (mi_page_immediate_available(page)) {789page->retire_expire = 0;790return page; // fast path791}792}793return mi_page_queue_find_free_ex(heap, pq, true);794}795796797/* -----------------------------------------------------------798Users can register a deferred free function called799when the `free` list is empty. Since the `local_free`800is separate this is deterministically called after801a certain number of allocations.802----------------------------------------------------------- */803804static mi_deferred_free_fun* volatile deferred_free = NULL;805static _Atomic(void*) deferred_arg; // = NULL806807void _mi_deferred_free(mi_heap_t* heap, bool force) {808heap->tld->heartbeat++;809if (deferred_free != NULL && !heap->tld->recurse) {810heap->tld->recurse = true;811deferred_free(force, heap->tld->heartbeat, mi_atomic_load_ptr_relaxed(void,&deferred_arg));812heap->tld->recurse = false;813}814}815816void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept {817deferred_free = fn;818mi_atomic_store_ptr_release(void,&deferred_arg, arg);819}820821822/* -----------------------------------------------------------823General allocation824----------------------------------------------------------- */825826// Large and huge page allocation.827// Huge pages contain just one block, and the segment contains just that page (as `MI_SEGMENT_HUGE`).828// Huge pages are also use if the requested alignment is very large (> MI_BLOCK_ALIGNMENT_MAX)829// so their size is not always `> MI_LARGE_OBJ_SIZE_MAX`.830static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) {831size_t block_size = _mi_os_good_alloc_size(size);832mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0);833bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX || page_alignment > 0);834#if MI_HUGE_PAGE_ABANDON835mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size));836#else837mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_LARGE_OBJ_SIZE_MAX+1 : block_size);838mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq));839#endif840mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment);841if (page != NULL) {842mi_assert_internal(mi_page_immediate_available(page));843844if (is_huge) {845mi_assert_internal(mi_page_is_huge(page));846mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE);847mi_assert_internal(_mi_page_segment(page)->used==1);848#if MI_HUGE_PAGE_ABANDON849mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue850mi_page_set_heap(page, NULL);851#endif852}853else {854mi_assert_internal(!mi_page_is_huge(page));855}856857const size_t bsize = mi_page_usable_block_size(page); // note: not `mi_page_block_size` to account for padding858if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {859mi_heap_stat_increase(heap, large, bsize);860mi_heap_stat_counter_increase(heap, large_count, 1);861}862else {863mi_heap_stat_increase(heap, huge, bsize);864mi_heap_stat_counter_increase(heap, huge_count, 1);865}866}867return page;868}869870871// Allocate a page872// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.873static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignment) mi_attr_noexcept {874// huge allocation?875const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size`876if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) {877if mi_unlikely(req_size > MI_MAX_ALLOC_SIZE) {878_mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size);879return NULL;880}881else {882return mi_large_huge_page_alloc(heap,size,huge_alignment);883}884}885else {886// otherwise find a page with free blocks in our size segregated queues887#if MI_PADDING888mi_assert_internal(size >= MI_PADDING_SIZE);889#endif890return mi_find_free_page(heap, size);891}892}893894// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed.895// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.896// The `huge_alignment` is normally 0 but is set to a multiple of MI_SEGMENT_SIZE for897// very large requested alignments in which case we use a huge segment.898void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept899{900mi_assert_internal(heap != NULL);901902// initialize if necessary903if mi_unlikely(!mi_heap_is_initialized(heap)) {904heap = mi_heap_get_default(); // calls mi_thread_init905if mi_unlikely(!mi_heap_is_initialized(heap)) { return NULL; }906}907mi_assert_internal(mi_heap_is_initialized(heap));908909// call potential deferred free routines910_mi_deferred_free(heap, false);911912// free delayed frees from other threads (but skip contended ones)913_mi_heap_delayed_free_partial(heap);914915// find (or allocate) a page of the right size916mi_page_t* page = mi_find_page(heap, size, huge_alignment);917if mi_unlikely(page == NULL) { // first time out of memory, try to collect and retry the allocation once more918mi_heap_collect(heap, true /* force */);919page = mi_find_page(heap, size, huge_alignment);920}921922if mi_unlikely(page == NULL) { // out of memory923const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size`924_mi_error_message(ENOMEM, "unable to allocate memory (%zu bytes)\n", req_size);925return NULL;926}927928mi_assert_internal(mi_page_immediate_available(page));929mi_assert_internal(mi_page_block_size(page) >= size);930931// and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc)932if mi_unlikely(zero && page->block_size == 0) {933// note: we cannot call _mi_page_malloc with zeroing for huge blocks; we zero it afterwards in that case.934void* p = _mi_page_malloc(heap, page, size);935mi_assert_internal(p != NULL);936_mi_memzero_aligned(p, mi_page_usable_block_size(page));937return p;938}939else {940return _mi_page_malloc_zero(heap, page, size, zero);941}942}943944945