Path: blob/main/system/lib/mimalloc/src/alloc.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-----------------------------------------------------------------------------*/6#ifndef _DEFAULT_SOURCE7#define _DEFAULT_SOURCE // for realpath() on Linux8#endif910#include "mimalloc.h"11#include "mimalloc/internal.h"12#include "mimalloc/atomic.h"13#include "mimalloc/prim.h" // _mi_prim_thread_id()1415#include <string.h> // memset, strlen (for mi_strdup)16#include <stdlib.h> // malloc, abort1718#define MI_IN_ALLOC_C19#include "alloc-override.c"20#include "free.c"21#undef MI_IN_ALLOC_C2223// ------------------------------------------------------24// Allocation25// ------------------------------------------------------2627// Fast allocation in a page: just pop from the free list.28// Fall back to generic allocation only if the list is empty.29// Note: in release mode the (inlined) routine is about 7 instructions with a single test.30extern inline void* _mi_page_malloc_zero(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero) mi_attr_noexcept31{32mi_assert_internal(page->block_size == 0 /* empty heap */ || mi_page_block_size(page) >= size);33mi_block_t* const block = page->free;34if mi_unlikely(block == NULL) {35return _mi_malloc_generic(heap, size, zero, 0);36}37mi_assert_internal(block != NULL && _mi_ptr_page(block) == page);38// pop from the free list39page->free = mi_block_next(page, block);40page->used++;41mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page);42#if MI_DEBUG>343if (page->free_is_zero) {44mi_assert_expensive(mi_mem_is_zero(block+1,size - sizeof(*block)));45}46#endif4748// allow use of the block internally49// note: when tracking we need to avoid ever touching the MI_PADDING since50// that is tracked by valgrind etc. as non-accessible (through the red-zone, see `mimalloc/track.h`)51mi_track_mem_undefined(block, mi_page_usable_block_size(page));5253// zero the block? note: we need to zero the full block size (issue #63)54if mi_unlikely(zero) {55mi_assert_internal(page->block_size != 0); // do not call with zero'ing for huge blocks (see _mi_malloc_generic)56mi_assert_internal(page->block_size >= MI_PADDING_SIZE);57if (page->free_is_zero) {58block->next = 0;59mi_track_mem_defined(block, page->block_size - MI_PADDING_SIZE);60}61else {62_mi_memzero_aligned(block, page->block_size - MI_PADDING_SIZE);63}64}6566#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN67if (!zero && !mi_page_is_huge(page)) {68memset(block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page));69}70#elif (MI_SECURE!=0)71if (!zero) { block->next = 0; } // don't leak internal data72#endif7374#if (MI_STAT>0)75const size_t bsize = mi_page_usable_block_size(page);76if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) {77mi_heap_stat_increase(heap, normal, bsize);78mi_heap_stat_counter_increase(heap, normal_count, 1);79#if (MI_STAT>1)80const size_t bin = _mi_bin(bsize);81mi_heap_stat_increase(heap, normal_bins[bin], 1);82#endif83}84#endif8586#if MI_PADDING // && !MI_TRACK_ENABLED87mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page));88ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE));89#if (MI_DEBUG>=2)90mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta));91#endif92mi_track_mem_defined(padding,sizeof(mi_padding_t)); // note: re-enable since mi_page_usable_block_size may set noaccess93padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys));94padding->delta = (uint32_t)(delta);95#if MI_PADDING_CHECK96if (!mi_page_is_huge(page)) {97uint8_t* fill = (uint8_t*)padding - delta;98const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes99for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; }100}101#endif102#endif103104return block;105}106107// extra entries for improved efficiency in `alloc-aligned.c`.108extern void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {109return _mi_page_malloc_zero(heap,page,size,false);110}111extern void* _mi_page_malloc_zeroed(mi_heap_t* heap, mi_page_t* page, size_t size) mi_attr_noexcept {112return _mi_page_malloc_zero(heap,page,size,true);113}114115static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {116mi_assert(heap != NULL);117#if MI_DEBUG118const uintptr_t tid = _mi_thread_id();119mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local120#endif121mi_assert(size <= MI_SMALL_SIZE_MAX);122#if (MI_PADDING)123if (size == 0) { size = sizeof(void*); }124#endif125126mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE);127void* const p = _mi_page_malloc_zero(heap, page, size + MI_PADDING_SIZE, zero);128mi_track_malloc(p,size,zero);129130#if MI_STAT>1131if (p != NULL) {132if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); }133mi_heap_stat_increase(heap, malloc, mi_usable_size(p));134}135#endif136#if MI_DEBUG>3137if (p != NULL && zero) {138mi_assert_expensive(mi_mem_is_zero(p, size));139}140#endif141return p;142}143144// allocate a small block145mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept {146return mi_heap_malloc_small_zero(heap, size, false);147}148149mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept {150return mi_heap_malloc_small(mi_prim_get_default_heap(), size);151}152153// The main allocation function154extern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept {155if mi_likely(size <= MI_SMALL_SIZE_MAX) {156mi_assert_internal(huge_alignment == 0);157return mi_heap_malloc_small_zero(heap, size, zero);158}159else {160mi_assert(heap!=NULL);161mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local162void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment); // note: size can overflow but it is detected in malloc_generic163mi_track_malloc(p,size,zero);164#if MI_STAT>1165if (p != NULL) {166if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); }167mi_heap_stat_increase(heap, malloc, mi_usable_size(p));168}169#endif170#if MI_DEBUG>3171if (p != NULL && zero) {172mi_assert_expensive(mi_mem_is_zero(p, size));173}174#endif175return p;176}177}178179extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept {180return _mi_heap_malloc_zero_ex(heap, size, zero, 0);181}182183mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {184return _mi_heap_malloc_zero(heap, size, false);185}186187mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept {188return mi_heap_malloc(mi_prim_get_default_heap(), size);189}190191// zero initialized small block192mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept {193return mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, true);194}195196mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept {197return _mi_heap_malloc_zero(heap, size, true);198}199200mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept {201return mi_heap_zalloc(mi_prim_get_default_heap(),size);202}203204205mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {206size_t total;207if (mi_count_size_overflow(count,size,&total)) return NULL;208return mi_heap_zalloc(heap,total);209}210211mi_decl_nodiscard mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept {212return mi_heap_calloc(mi_prim_get_default_heap(),count,size);213}214215// Uninitialized `calloc`216mi_decl_nodiscard extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept {217size_t total;218if (mi_count_size_overflow(count, size, &total)) return NULL;219return mi_heap_malloc(heap, total);220}221222mi_decl_nodiscard mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept {223return mi_heap_mallocn(mi_prim_get_default_heap(),count,size);224}225226// Expand (or shrink) in place (or fail)227void* mi_expand(void* p, size_t newsize) mi_attr_noexcept {228#if MI_PADDING229// we do not shrink/expand with padding enabled230MI_UNUSED(p); MI_UNUSED(newsize);231return NULL;232#else233if (p == NULL) return NULL;234const size_t size = _mi_usable_size(p,"mi_expand");235if (newsize > size) return NULL;236return p; // it fits237#endif238}239240void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept {241// if p == NULL then behave as malloc.242// else if size == 0 then reallocate to a zero-sized block (and don't return NULL, just as mi_malloc(0)).243// (this means that returning NULL always indicates an error, and `p` will not have been freed in that case.)244const size_t size = _mi_usable_size(p,"mi_realloc"); // also works if p == NULL (with size 0)245if mi_unlikely(newsize <= size && newsize >= (size / 2) && newsize > 0) { // note: newsize must be > 0 or otherwise we return NULL for realloc(NULL,0)246mi_assert_internal(p!=NULL);247// todo: do not track as the usable size is still the same in the free; adjust potential padding?248// mi_track_resize(p,size,newsize)249// if (newsize < size) { mi_track_mem_noaccess((uint8_t*)p + newsize, size - newsize); }250return p; // reallocation still fits and not more than 50% waste251}252void* newp = mi_heap_malloc(heap,newsize);253if mi_likely(newp != NULL) {254if (zero && newsize > size) {255// also set last word in the previous allocation to zero to ensure any padding is zero-initialized256const size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0);257_mi_memzero((uint8_t*)newp + start, newsize - start);258}259else if (newsize == 0) {260((uint8_t*)newp)[0] = 0; // work around for applications that expect zero-reallocation to be zero initialized (issue #725)261}262if mi_likely(p != NULL) {263const size_t copysize = (newsize > size ? size : newsize);264mi_track_mem_defined(p,copysize); // _mi_useable_size may be too large for byte precise memory tracking..265_mi_memcpy(newp, p, copysize);266mi_free(p); // only free the original pointer if successful267}268}269return newp;270}271272mi_decl_nodiscard void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {273return _mi_heap_realloc_zero(heap, p, newsize, false);274}275276mi_decl_nodiscard void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {277size_t total;278if (mi_count_size_overflow(count, size, &total)) return NULL;279return mi_heap_realloc(heap, p, total);280}281282283// Reallocate but free `p` on errors284mi_decl_nodiscard void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {285void* newp = mi_heap_realloc(heap, p, newsize);286if (newp==NULL && p!=NULL) mi_free(p);287return newp;288}289290mi_decl_nodiscard void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept {291return _mi_heap_realloc_zero(heap, p, newsize, true);292}293294mi_decl_nodiscard void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept {295size_t total;296if (mi_count_size_overflow(count, size, &total)) return NULL;297return mi_heap_rezalloc(heap, p, total);298}299300301mi_decl_nodiscard void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept {302return mi_heap_realloc(mi_prim_get_default_heap(),p,newsize);303}304305mi_decl_nodiscard void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept {306return mi_heap_reallocn(mi_prim_get_default_heap(),p,count,size);307}308309// Reallocate but free `p` on errors310mi_decl_nodiscard void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept {311return mi_heap_reallocf(mi_prim_get_default_heap(),p,newsize);312}313314mi_decl_nodiscard void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept {315return mi_heap_rezalloc(mi_prim_get_default_heap(), p, newsize);316}317318mi_decl_nodiscard void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept {319return mi_heap_recalloc(mi_prim_get_default_heap(), p, count, size);320}321322323324// ------------------------------------------------------325// strdup, strndup, and realpath326// ------------------------------------------------------327328// `strdup` using mi_malloc329mi_decl_nodiscard mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept {330if (s == NULL) return NULL;331size_t len = _mi_strlen(s);332char* t = (char*)mi_heap_malloc(heap,len+1);333if (t == NULL) return NULL;334_mi_memcpy(t, s, len);335t[len] = 0;336return t;337}338339mi_decl_nodiscard mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept {340return mi_heap_strdup(mi_prim_get_default_heap(), s);341}342343// `strndup` using mi_malloc344mi_decl_nodiscard mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept {345if (s == NULL) return NULL;346const size_t len = _mi_strnlen(s,n); // len <= n347char* t = (char*)mi_heap_malloc(heap, len+1);348if (t == NULL) return NULL;349_mi_memcpy(t, s, len);350t[len] = 0;351return t;352}353354mi_decl_nodiscard mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept {355return mi_heap_strndup(mi_prim_get_default_heap(),s,n);356}357358#ifndef __wasi__359// `realpath` using mi_malloc360#ifdef _WIN32361#ifndef PATH_MAX362#define PATH_MAX MAX_PATH363#endif364#include <windows.h>365mi_decl_nodiscard mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {366// todo: use GetFullPathNameW to allow longer file names367char buf[PATH_MAX];368DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL);369if (res == 0) {370errno = GetLastError(); return NULL;371}372else if (res > PATH_MAX) {373errno = EINVAL; return NULL;374}375else if (resolved_name != NULL) {376return resolved_name;377}378else {379return mi_heap_strndup(heap, buf, PATH_MAX);380}381}382#else383/*384#include <unistd.h> // pathconf385static size_t mi_path_max(void) {386static size_t path_max = 0;387if (path_max <= 0) {388long m = pathconf("/",_PC_PATH_MAX);389if (m <= 0) path_max = 4096; // guess390else if (m < 256) path_max = 256; // at least 256391else path_max = m;392}393return path_max;394}395*/396char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept {397if (resolved_name != NULL) {398return realpath(fname,resolved_name);399}400else {401char* rname = realpath(fname, NULL);402if (rname == NULL) return NULL;403char* result = mi_heap_strdup(heap, rname);404mi_cfree(rname); // use checked free (which may be redirected to our free but that's ok)405// note: with ASAN realpath is intercepted and mi_cfree may leak the returned pointer :-(406return result;407}408/*409const size_t n = mi_path_max();410char* buf = (char*)mi_malloc(n+1);411if (buf == NULL) {412errno = ENOMEM;413return NULL;414}415char* rname = realpath(fname,buf);416char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL`417mi_free(buf);418return result;419}420*/421}422#endif423424mi_decl_nodiscard mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept {425return mi_heap_realpath(mi_prim_get_default_heap(),fname,resolved_name);426}427#endif428429/*-------------------------------------------------------430C++ new and new_aligned431The standard requires calling into `get_new_handler` and432throwing the bad_alloc exception on failure. If we compile433with a C++ compiler we can implement this precisely. If we434use a C compiler we cannot throw a `bad_alloc` exception435but we call `exit` instead (i.e. not returning).436-------------------------------------------------------*/437438#ifdef __cplusplus439#include <new>440static bool mi_try_new_handler(bool nothrow) {441#if defined(_MSC_VER) || (__cplusplus >= 201103L)442std::new_handler h = std::get_new_handler();443#else444std::new_handler h = std::set_new_handler();445std::set_new_handler(h);446#endif447if (h==NULL) {448_mi_error_message(ENOMEM, "out of memory in 'new'");449#if defined(_CPPUNWIND) || defined(__cpp_exceptions) // exceptions are not always enabled450if (!nothrow) {451throw std::bad_alloc();452}453#else454MI_UNUSED(nothrow);455#endif456return false;457}458else {459h();460return true;461}462}463#else464typedef void (*std_new_handler_t)(void);465466#if (defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER))) // exclude clang-cl, see issue #631467std_new_handler_t __attribute__((weak)) _ZSt15get_new_handlerv(void) {468return NULL;469}470static std_new_handler_t mi_get_new_handler(void) {471return _ZSt15get_new_handlerv();472}473#else474// note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`.475static std_new_handler_t mi_get_new_handler() {476return NULL;477}478#endif479480static bool mi_try_new_handler(bool nothrow) {481std_new_handler_t h = mi_get_new_handler();482if (h==NULL) {483_mi_error_message(ENOMEM, "out of memory in 'new'");484if (!nothrow) {485abort(); // cannot throw in plain C, use abort486}487return false;488}489else {490h();491return true;492}493}494#endif495496mi_decl_export mi_decl_noinline void* mi_heap_try_new(mi_heap_t* heap, size_t size, bool nothrow ) {497void* p = NULL;498while(p == NULL && mi_try_new_handler(nothrow)) {499p = mi_heap_malloc(heap,size);500}501return p;502}503504static mi_decl_noinline void* mi_try_new(size_t size, bool nothrow) {505return mi_heap_try_new(mi_prim_get_default_heap(), size, nothrow);506}507508509mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) {510void* p = mi_heap_malloc(heap,size);511if mi_unlikely(p == NULL) return mi_heap_try_new(heap, size, false);512return p;513}514515mi_decl_nodiscard mi_decl_restrict void* mi_new(size_t size) {516return mi_heap_alloc_new(mi_prim_get_default_heap(), size);517}518519520mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) {521size_t total;522if mi_unlikely(mi_count_size_overflow(count, size, &total)) {523mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc524return NULL;525}526else {527return mi_heap_alloc_new(heap,total);528}529}530531mi_decl_nodiscard mi_decl_restrict void* mi_new_n(size_t count, size_t size) {532return mi_heap_alloc_new_n(mi_prim_get_default_heap(), size, count);533}534535536mi_decl_nodiscard mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept {537void* p = mi_malloc(size);538if mi_unlikely(p == NULL) return mi_try_new(size, true);539return p;540}541542mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) {543void* p;544do {545p = mi_malloc_aligned(size, alignment);546}547while(p == NULL && mi_try_new_handler(false));548return p;549}550551mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept {552void* p;553do {554p = mi_malloc_aligned(size, alignment);555}556while(p == NULL && mi_try_new_handler(true));557return p;558}559560mi_decl_nodiscard void* mi_new_realloc(void* p, size_t newsize) {561void* q;562do {563q = mi_realloc(p, newsize);564} while (q == NULL && mi_try_new_handler(false));565return q;566}567568mi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) {569size_t total;570if mi_unlikely(mi_count_size_overflow(newcount, size, &total)) {571mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc572return NULL;573}574else {575return mi_new_realloc(p, total);576}577}578579// ------------------------------------------------------580// ensure explicit external inline definitions are emitted!581// ------------------------------------------------------582583#ifdef __cplusplus584void* _mi_externs[] = {585(void*)&_mi_page_malloc,586(void*)&_mi_heap_malloc_zero,587(void*)&_mi_heap_malloc_zero_ex,588(void*)&mi_malloc,589(void*)&mi_malloc_small,590(void*)&mi_zalloc_small,591(void*)&mi_heap_malloc,592(void*)&mi_heap_zalloc,593(void*)&mi_heap_malloc_small,594// (void*)&mi_heap_alloc_new,595// (void*)&mi_heap_alloc_new_n596};597#endif598599600