Path: blob/main/system/lib/mimalloc/src/page-map.c
14369 views
/*----------------------------------------------------------------------------1Copyright (c) 2023-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 "bitmap.h"1011static void mi_page_map_cannot_commit(void) {12_mi_warning_message("unable to commit the allocation page-map on-demand\n" );13}1415#if MI_PAGE_MAP_FLAT1617// The page-map contains a byte for each 64kb slice in the address space.18// For an address `a` where `ofs = _mi_page_map[a >> 16]`:19// 0 = unused20// 1 = the slice at `a & ~0xFFFF` is a mimalloc page.21// 1 < ofs <= 127 = the slice is part of a page, starting at `(((a>>16) - ofs - 1) << 16)`.22//23// 1 byte per slice => 1 TiB address space needs a 2^14 * 2^16 = 16 MiB page map.24// A full 256 TiB address space (48 bit) needs a 4 GiB page map.25// A full 4 GiB address space (32 bit) needs only a 64 KiB page map.2627mi_decl_cache_align uint8_t* _mi_page_map = NULL;28static void* mi_page_map_max_address = NULL;29static mi_memid_t mi_page_map_memid;3031#define MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT MI_ARENA_SLICE_SIZE32static mi_bitmap_t* mi_page_map_commit; // one bit per committed 64 KiB entries3334mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count);3536bool _mi_page_map_init(void) {37size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);38if (vbits == 0) {39vbits = _mi_os_virtual_address_bits();40#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB41if (vbits >= 48) { vbits = 47; }42#endif43}44if (vbits < MI_ARENA_SLICE_SHIFT) {45vbits = MI_ARENA_SLICE_SHIFT;46}4748// Allocate the page map and commit bits49mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));50const size_t page_map_size = (MI_ZU(1) << (vbits - MI_ARENA_SLICE_SHIFT));51const bool commit = (page_map_size <= 1*MI_MiB || mi_option_is_enabled(mi_option_pagemap_commit)); // _mi_os_has_overcommit(); // commit on-access on Linux systems?52const size_t commit_bits = _mi_divide_up(page_map_size, MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);53const size_t bitmap_size = (commit ? 0 : mi_bitmap_size(commit_bits, NULL));54const size_t reserve_size = bitmap_size + page_map_size;55uint8_t* const base = (uint8_t*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);56if (base==NULL) {57_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);58return false;59}60if (mi_page_map_memid.initially_committed && !mi_page_map_memid.initially_zero) {61_mi_warning_message("internal: the page map was committed but not zero initialized!\n");62_mi_memzero_aligned(base, reserve_size);63}64if (bitmap_size > 0) {65mi_page_map_commit = (mi_bitmap_t*)base;66if (!_mi_os_commit(mi_page_map_commit, bitmap_size, NULL)) {67mi_page_map_cannot_commit();68return false;69}70mi_bitmap_init(mi_page_map_commit, commit_bits, true);71}72_mi_page_map = base + bitmap_size;7374// commit the first part so NULL pointers get resolved without an access violation75if (!commit) {76if (!mi_page_map_ensure_committed(0, 1)) {77mi_page_map_cannot_commit();78return false;79}80}81_mi_page_map[0] = 1; // so _mi_ptr_page(NULL) == NULL82mi_assert_internal(_mi_ptr_page(NULL)==NULL);83return true;84}8586void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {87mi_assert_internal(subproc != NULL);88mi_assert_internal(_mi_page_map != NULL);89if (_mi_page_map == NULL) return;90_mi_os_free_ex(mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);91_mi_page_map = NULL;92mi_page_map_commit = NULL;93mi_page_map_max_address = NULL;94mi_page_map_memid = _mi_memid_none();95}969798static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count) {99// is the page map area that contains the page address committed?100// we always set the commit bits so we can track what ranges are in-use.101// we only actually commit if the map wasn't committed fully already.102if (mi_page_map_commit != NULL) {103const size_t commit_idx = idx / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;104const size_t commit_idx_hi = (idx + slice_count - 1) / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;105for (size_t i = commit_idx; i <= commit_idx_hi; i++) { // per bit to avoid crossing over bitmap chunks106if (mi_bitmap_is_clear(mi_page_map_commit, i)) {107// this may race, in which case we do multiple commits (which is ok)108bool is_zero;109uint8_t* const start = _mi_page_map + (i * MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);110const size_t size = MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;111if (!_mi_os_commit(start, size, &is_zero)) {112mi_page_map_cannot_commit();113return false;114}115if (!is_zero && !mi_page_map_memid.initially_zero) { _mi_memzero(start, size); }116mi_bitmap_set(mi_page_map_commit, i);117}118}119}120#if MI_DEBUG > 0121_mi_page_map[idx] = 0;122_mi_page_map[idx+slice_count-1] = 0;123#endif124return true;125}126127128static size_t mi_page_map_get_idx(mi_page_t* page, uint8_t** page_start, size_t* slice_count) {129size_t page_size;130*page_start = mi_page_area(page, &page_size);131if (page_size > MI_LARGE_PAGE_SIZE) { page_size = MI_LARGE_PAGE_SIZE - MI_ARENA_SLICE_SIZE; } // furthest interior pointer132*slice_count = mi_slice_count_of_size(page_size) + ((*page_start - mi_page_slice_start(page))/MI_ARENA_SLICE_SIZE); // add for large aligned blocks133return _mi_page_map_index(page);134}135136bool _mi_page_map_register(mi_page_t* page) {137mi_assert_internal(page != NULL);138mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));139mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!140if mi_unlikely(_mi_page_map == NULL) {141if (!_mi_page_map_init()) return false;142}143mi_assert(_mi_page_map!=NULL);144uint8_t* page_start;145size_t slice_count;146const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);147148if (!mi_page_map_ensure_committed(idx, slice_count)) {149return false;150}151152// set the offsets153for (size_t i = 0; i < slice_count; i++) {154mi_assert_internal(i < 128);155_mi_page_map[idx + i] = (uint8_t)(i+1);156}157return true;158}159160void _mi_page_map_unregister(mi_page_t* page) {161mi_assert_internal(_mi_page_map != NULL);162// get index and count163uint8_t* page_start;164size_t slice_count;165const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);166// unset the offsets167_mi_memzero(_mi_page_map + idx, slice_count);168}169170void _mi_page_map_unregister_range(void* start, size_t size) {171const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);172const uintptr_t index = _mi_page_map_index(start);173// todo: scan the commit bits and clear only those ranges?174if (!mi_page_map_ensure_committed(index, slice_count)) { // we commit the range in total;175return;176}177_mi_memzero(&_mi_page_map[index], slice_count);178}179180181mi_page_t* _mi_safe_ptr_page(const void* p) {182if mi_unlikely(p >= mi_page_map_max_address) return NULL;183const uintptr_t idx = _mi_page_map_index(p);184if mi_unlikely(mi_page_map_commit != NULL && !mi_bitmap_is_set(mi_page_map_commit, idx/MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT)) return NULL;185const uintptr_t ofs = _mi_page_map[idx];186if mi_unlikely(ofs == 0) return NULL;187return (mi_page_t*)((((uintptr_t)p >> MI_ARENA_SLICE_SHIFT) - ofs + 1) << MI_ARENA_SLICE_SHIFT);188}189190mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {191return (_mi_safe_ptr_page(p) != NULL);192}193194#else195196// A 2-level page map197#define MI_PAGE_MAP_SUB_SIZE (MI_PAGE_MAP_SUB_COUNT * sizeof(mi_page_t*))198#define MI_PAGE_MAP_ENTRIES_PER_CBIT (MI_PAGE_MAP_COUNT < MI_BFIELD_BITS ? 1 : (MI_PAGE_MAP_COUNT / MI_BFIELD_BITS))199200mi_decl_cache_align _Atomic(mi_submap_t)* _mi_page_map;201static size_t mi_page_map_count;202static void* mi_page_map_max_address;203static mi_memid_t mi_page_map_memid;204static mi_lock_t mi_page_map_lock;205206// divide the main map in 64 (`MI_BFIELD_BITS`) parts commit those parts on demand207static _Atomic(mi_bfield_t) mi_page_map_commit;208209mi_decl_nodiscard static inline bool mi_page_map_is_committed(size_t idx, size_t* pbit_idx) {210mi_bfield_t commit = mi_atomic_load_relaxed(&mi_page_map_commit);211const size_t bit_idx = idx/MI_PAGE_MAP_ENTRIES_PER_CBIT;212mi_assert_internal(bit_idx < MI_BFIELD_BITS);213if (pbit_idx != NULL) { *pbit_idx = bit_idx; }214return ((commit & (MI_ZU(1) << bit_idx)) != 0);215}216217mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, mi_submap_t* submap) {218mi_assert_internal(submap!=NULL && *submap==NULL);219size_t bit_idx;220if mi_unlikely(!mi_page_map_is_committed(idx, &bit_idx)) {221uint8_t* start = (uint8_t*)&_mi_page_map[bit_idx * MI_PAGE_MAP_ENTRIES_PER_CBIT];222if (!_mi_os_commit(start, MI_PAGE_MAP_ENTRIES_PER_CBIT * sizeof(mi_submap_t), NULL)) {223mi_page_map_cannot_commit();224return false;225}226mi_atomic_or_acq_rel(&mi_page_map_commit, MI_ZU(1) << bit_idx);227}228*submap = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // acquire _mi_page_map_at(idx);229return true;230}231232// initialize the page map233bool _mi_page_map_init(void) {234size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);235if (vbits == 0) {236vbits = _mi_os_virtual_address_bits();237#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB238if (vbits >= 48) { vbits = 47; }239#endif240}241if (vbits < MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT) {242vbits = MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT;243}244245// Allocate the page map and commit bits246mi_assert(MI_MAX_VABITS >= vbits);247mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));248mi_page_map_count = (MI_ZU(1) << (vbits - MI_PAGE_MAP_SUB_SHIFT - MI_ARENA_SLICE_SHIFT));249mi_assert(mi_page_map_count <= MI_PAGE_MAP_COUNT);250const size_t os_page_size = _mi_os_page_size();251const size_t page_map_size = _mi_align_up( mi_page_map_count * sizeof(mi_page_t**), os_page_size);252const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;253const size_t reserve_size = page_map_size + submap_size;254#if MI_SECURE255const bool commit = true; // the whole page map is valid and we can reliably check any pointer256#else257const bool commit = page_map_size <= 64*MI_KiB ||258mi_option_is_enabled(mi_option_pagemap_commit) || _mi_os_has_overcommit();259#endif260_mi_page_map = (_Atomic(mi_page_t**)*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);261if (_mi_page_map==NULL) {262_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);263return false;264}265if (mi_page_map_memid.initially_committed && !mi_page_map_memid.initially_zero) {266_mi_warning_message("internal: the page map was committed but not zero initialized!\n");267_mi_memzero_aligned(_mi_page_map, page_map_size);268}269mi_atomic_store_release(&mi_page_map_commit, (mi_page_map_memid.initially_committed ? ~MI_ZU(0) : MI_ZU(0)));270271// ensure there is a submap for the NULL address272mi_page_t** const sub0 = (mi_page_t**)((uint8_t*)_mi_page_map + page_map_size); // we reserved a submap part at the end already273if (!mi_page_map_memid.initially_committed) {274if (!_mi_os_commit(sub0, submap_size, NULL)) { // commit full submap (issue #1087)275mi_page_map_cannot_commit();276return false;277}278}279if (!mi_page_map_memid.initially_zero) { // initialize low addresses with NULL280_mi_memzero_aligned(sub0, submap_size);281}282mi_submap_t nullsub = NULL;283if (!mi_page_map_ensure_committed(0,&nullsub)) {284mi_page_map_cannot_commit();285return false;286}287mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[0], sub0);288mi_lock_init(&mi_page_map_lock); // initialize late in case the lock init causes allocation289290mi_assert_internal(_mi_ptr_page(NULL)==NULL);291return true;292}293294295void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {296mi_assert_internal(subproc != NULL);297mi_assert_internal(_mi_page_map != NULL);298if (_mi_page_map == NULL) return;299mi_lock_done(&mi_page_map_lock);300for (size_t idx = 1; idx < mi_page_map_count; idx++) { // skip entry 0 (as we allocate that submap at the end of the page_map)301// free all sub-maps302if (mi_page_map_is_committed(idx, NULL)) {303mi_submap_t sub = _mi_page_map_at(idx);304if (sub != NULL) {305mi_memid_t memid = _mi_memid_create_os(sub, MI_PAGE_MAP_SUB_SIZE, true, false, false);306_mi_os_free_ex(memid.mem.os.base, memid.mem.os.size, true, memid, subproc);307mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[idx], NULL);308}309}310}311_mi_os_free_ex(_mi_page_map, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);312_mi_page_map = NULL;313mi_page_map_count = 0;314mi_page_map_memid = _mi_memid_none();315mi_page_map_max_address = NULL;316mi_atomic_store_release(&mi_page_map_commit, (mi_bfield_t)0);317}318319320mi_decl_nodiscard static bool mi_page_map_ensure_submap_at(size_t idx, mi_submap_t* submap) {321mi_assert_internal(submap!=NULL && *submap==NULL);322mi_submap_t sub = NULL;323if (!mi_page_map_ensure_committed(idx, &sub)) {324return false;325}326if mi_unlikely(sub == NULL) {327// sub map not yet allocated, alloc now328mi_lock(&mi_page_map_lock)329{330sub = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // reload331if (sub==NULL) // not yet allocated by another thread?332{333mi_memid_t memid;334const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;335sub = (mi_submap_t)_mi_os_zalloc(submap_size, &memid);336if (sub==NULL) {337_mi_warning_message("internal error: unable to extend the page map\n");338}339else {340mi_submap_t expect = NULL;341if (!mi_atomic_cas_ptr_strong_acq_rel(mi_page_t*, &_mi_page_map[idx], &expect, sub)) {342// another thread already allocated it.. free and continue343_mi_os_free(sub, submap_size, memid);344sub = expect;345}346}347}348}349if (sub==NULL) return false; // unable to allocate the submap..350}351mi_assert_internal(sub!=NULL);352*submap = sub;353return true;354}355356static bool mi_page_map_set_range_prim(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {357// is the page map area that contains the page address committed?358while (slice_count > 0) {359mi_submap_t sub = NULL;360if (!mi_page_map_ensure_submap_at(idx, &sub)) {361return false;362};363mi_assert_internal(sub!=NULL);364// set the offsets for the page365while (slice_count > 0 && sub_idx < MI_PAGE_MAP_SUB_COUNT) {366sub[sub_idx] = page;367slice_count--;368sub_idx++;369}370idx++; // potentially wrap around to the next idx371sub_idx = 0;372}373return true;374}375376static bool mi_page_map_set_range(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {377if mi_unlikely(!mi_page_map_set_range_prim(page,idx,sub_idx,slice_count)) {378// failed to commit, call again to reset the page pointer if needed379if (page!=NULL) {380mi_page_map_set_range_prim(NULL,idx,sub_idx,slice_count);381}382return false;383}384return true;385}386387static size_t mi_page_map_get_idx(mi_page_t* page, size_t* sub_idx, size_t* slice_count) {388size_t page_size;389uint8_t* page_start = mi_page_area(page, &page_size);390if (page_size > MI_LARGE_PAGE_SIZE) { page_size = MI_LARGE_PAGE_SIZE - MI_ARENA_SLICE_SIZE; } // furthest interior pointer391*slice_count = mi_slice_count_of_size(page_size) + ((page_start - mi_page_slice_start(page))/MI_ARENA_SLICE_SIZE); // add for large aligned blocks392return _mi_page_map_index(page_start, sub_idx);393}394395bool _mi_page_map_register(mi_page_t* page) {396mi_assert_internal(page != NULL);397mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));398mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!399if mi_unlikely(_mi_page_map == NULL) {400if (!_mi_page_map_init()) return false;401}402mi_assert(_mi_page_map!=NULL);403size_t slice_count;404size_t sub_idx;405const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);406return mi_page_map_set_range(page, idx, sub_idx, slice_count);407}408409void _mi_page_map_unregister(mi_page_t* page) {410mi_assert_internal(_mi_page_map != NULL);411mi_assert_internal(page != NULL);412mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));413if mi_unlikely(_mi_page_map == NULL) return;414// get index and count415size_t slice_count;416size_t sub_idx;417const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);418// unset the offsets419mi_page_map_set_range(NULL, idx, sub_idx, slice_count);420}421422void _mi_page_map_unregister_range(void* start, size_t size) {423if mi_unlikely(_mi_page_map == NULL) return;424const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);425size_t sub_idx;426const uintptr_t idx = _mi_page_map_index(start, &sub_idx);427mi_page_map_set_range(NULL, idx, sub_idx, slice_count); // todo: avoid committing if not already committed?428}429430// Return NULL for invalid pointers431mi_page_t* _mi_safe_ptr_page(const void* p) {432if (p==NULL) return NULL;433if mi_unlikely(p >= mi_page_map_max_address) return NULL;434size_t sub_idx;435const size_t idx = _mi_page_map_index(p,&sub_idx);436if mi_unlikely(!mi_page_map_is_committed(idx,NULL)) return NULL;437mi_page_t** const sub = _mi_page_map[idx];438if mi_unlikely(sub==NULL) return NULL;439return sub[sub_idx];440}441442mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {443return (_mi_safe_ptr_page(p) != NULL);444}445446#endif447448449