2023-10-30 12:43:11 -03:00
/* ----------------------------------------------------------------------------
Copyright ( c ) 2018 - 2023 , Microsoft Research , Daan Leijen
This is free software ; you can redistribute it and / or modify it under the
terms of the MIT license . A copy of the license can be found in the file
" LICENSE " at the root of this distribution .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# include "mimalloc.h"
# include "mimalloc/internal.h"
# include "mimalloc/atomic.h"
# include "mimalloc/prim.h"
/* -----------------------------------------------------------
Initialization .
On windows initializes support for aligned allocation and
large OS pages ( if MIMALLOC_LARGE_OS_PAGES is true ) .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static mi_os_mem_config_t mi_os_mem_config = {
4096 , // page size
0 , // large page size (usually 2MiB)
4096 , // allocation granularity
true , // has overcommit? (if true we use MAP_NORESERVE on mmap systems)
false , // must free whole? (on mmap systems we can free anywhere in a mapped range, but on Windows we must free the entire span)
true // has virtual reserve? (if true we can reserve virtual address space without using commit or physical memory)
} ;
bool _mi_os_has_overcommit ( void ) {
return mi_os_mem_config . has_overcommit ;
}
bool _mi_os_has_virtual_reserve ( void ) {
return mi_os_mem_config . has_virtual_reserve ;
}
// OS (small) page size
size_t _mi_os_page_size ( void ) {
return mi_os_mem_config . page_size ;
}
// if large OS pages are supported (2 or 4MiB), then return the size, otherwise return the small page size (4KiB)
size_t _mi_os_large_page_size ( void ) {
return ( mi_os_mem_config . large_page_size ! = 0 ? mi_os_mem_config . large_page_size : _mi_os_page_size ( ) ) ;
}
bool _mi_os_use_large_page ( size_t size , size_t alignment ) {
// if we have access, check the size and alignment requirements
if ( mi_os_mem_config . large_page_size = = 0 | | ! mi_option_is_enabled ( mi_option_allow_large_os_pages ) ) return false ;
return ( ( size % mi_os_mem_config . large_page_size ) = = 0 & & ( alignment % mi_os_mem_config . large_page_size ) = = 0 ) ;
}
// round to a good OS allocation size (bounded by max 12.5% waste)
size_t _mi_os_good_alloc_size ( size_t size ) {
size_t align_size ;
if ( size < 512 * MI_KiB ) align_size = _mi_os_page_size ( ) ;
else if ( size < 2 * MI_MiB ) align_size = 64 * MI_KiB ;
else if ( size < 8 * MI_MiB ) align_size = 256 * MI_KiB ;
else if ( size < 32 * MI_MiB ) align_size = 1 * MI_MiB ;
else align_size = 4 * MI_MiB ;
if mi_unlikely ( size > = ( SIZE_MAX - align_size ) ) return size ; // possible overflow?
return _mi_align_up ( size , align_size ) ;
}
void _mi_os_init ( void ) {
_mi_prim_mem_init ( & mi_os_mem_config ) ;
}
/* -----------------------------------------------------------
Util
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
bool _mi_os_decommit ( void * addr , size_t size , mi_stats_t * stats ) ;
bool _mi_os_commit ( void * addr , size_t size , bool * is_zero , mi_stats_t * tld_stats ) ;
static void * mi_align_up_ptr ( void * p , size_t alignment ) {
return ( void * ) _mi_align_up ( ( uintptr_t ) p , alignment ) ;
}
static void * mi_align_down_ptr ( void * p , size_t alignment ) {
return ( void * ) _mi_align_down ( ( uintptr_t ) p , alignment ) ;
}
/* -----------------------------------------------------------
aligned hinting
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// On 64-bit systems, we can do efficient aligned allocation by using
// the 2TiB to 30TiB area to allocate those.
# if (MI_INTPTR_SIZE >= 8)
static mi_decl_cache_align _Atomic ( uintptr_t ) aligned_base ;
// Return a MI_SEGMENT_SIZE aligned address that is probably available.
// If this returns NULL, the OS will determine the address but on some OS's that may not be
// properly aligned which can be more costly as it needs to be adjusted afterwards.
// For a size > 1GiB this always returns NULL in order to guarantee good ASLR randomization;
// (otherwise an initial large allocation of say 2TiB has a 50% chance to include (known) addresses
// in the middle of the 2TiB - 6TiB address range (see issue #372))
# define MI_HINT_BASE ((uintptr_t)2 << 40) // 2TiB start
# define MI_HINT_AREA ((uintptr_t)4 << 40) // upto 6TiB (since before win8 there is "only" 8TiB available to processes)
# define MI_HINT_MAX ((uintptr_t)30 << 40) // wrap after 30TiB (area after 32TiB is used for huge OS pages)
void * _mi_os_get_aligned_hint ( size_t try_alignment , size_t size )
{
if ( try_alignment < = 1 | | try_alignment > MI_SEGMENT_SIZE ) return NULL ;
size = _mi_align_up ( size , MI_SEGMENT_SIZE ) ;
if ( size > 1 * MI_GiB ) return NULL ; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<30) = 1/4096.
# if (MI_SECURE>0)
size + = MI_SEGMENT_SIZE ; // put in `MI_SEGMENT_SIZE` virtual gaps between hinted blocks; this splits VLA's but increases guarded areas.
# endif
uintptr_t hint = mi_atomic_add_acq_rel ( & aligned_base , size ) ;
if ( hint = = 0 | | hint > MI_HINT_MAX ) { // wrap or initialize
uintptr_t init = MI_HINT_BASE ;
# if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of aligned allocations unless in debug mode
2024-08-17 17:04:08 -03:00
mi_heap_t * heap = mi_prim_get_default_heap ( ) ;
// gh-123022: default heap may not be initialized in CPython in background threads
if ( mi_heap_is_initialized ( heap ) ) {
uintptr_t r = _mi_heap_random_next ( heap ) ;
init = init + ( ( MI_SEGMENT_SIZE * ( ( r > > 17 ) & 0xFFFFF ) ) % MI_HINT_AREA ) ; // (randomly 20 bits)*4MiB == 0 to 4TiB
}
2023-10-30 12:43:11 -03:00
# endif
uintptr_t expected = hint + size ;
mi_atomic_cas_strong_acq_rel ( & aligned_base , & expected , init ) ;
hint = mi_atomic_add_acq_rel ( & aligned_base , size ) ; // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all
}
if ( hint % try_alignment ! = 0 ) return NULL ;
return ( void * ) hint ;
}
# else
void * _mi_os_get_aligned_hint ( size_t try_alignment , size_t size ) {
MI_UNUSED ( try_alignment ) ; MI_UNUSED ( size ) ;
return NULL ;
}
# endif
/* -----------------------------------------------------------
Free memory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static void mi_os_free_huge_os_pages ( void * p , size_t size , mi_stats_t * stats ) ;
static void mi_os_prim_free ( void * addr , size_t size , bool still_committed , mi_stats_t * tld_stats ) {
MI_UNUSED ( tld_stats ) ;
mi_assert_internal ( ( size % _mi_os_page_size ( ) ) = = 0 ) ;
if ( addr = = NULL | | size = = 0 ) return ; // || _mi_os_is_huge_reserved(addr)
int err = _mi_prim_free ( addr , size ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p) \n " , err , err , size , addr ) ;
}
mi_stats_t * stats = & _mi_stats_main ;
if ( still_committed ) { _mi_stat_decrease ( & stats - > committed , size ) ; }
_mi_stat_decrease ( & stats - > reserved , size ) ;
}
void _mi_os_free_ex ( void * addr , size_t size , bool still_committed , mi_memid_t memid , mi_stats_t * tld_stats ) {
if ( mi_memkind_is_os ( memid . memkind ) ) {
size_t csize = _mi_os_good_alloc_size ( size ) ;
void * base = addr ;
// different base? (due to alignment)
if ( memid . mem . os . base ! = NULL ) {
mi_assert ( memid . mem . os . base < = addr ) ;
mi_assert ( ( uint8_t * ) memid . mem . os . base + memid . mem . os . alignment > = ( uint8_t * ) addr ) ;
base = memid . mem . os . base ;
csize + = ( ( uint8_t * ) addr - ( uint8_t * ) memid . mem . os . base ) ;
}
// free it
if ( memid . memkind = = MI_MEM_OS_HUGE ) {
mi_assert ( memid . is_pinned ) ;
mi_os_free_huge_os_pages ( base , csize , tld_stats ) ;
}
else {
mi_os_prim_free ( base , csize , still_committed , tld_stats ) ;
}
}
else {
// nothing to do
mi_assert ( memid . memkind < MI_MEM_OS ) ;
}
}
void _mi_os_free ( void * p , size_t size , mi_memid_t memid , mi_stats_t * tld_stats ) {
_mi_os_free_ex ( p , size , true , memid , tld_stats ) ;
}
/* -----------------------------------------------------------
Primitive allocation from the OS .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned.
static void * mi_os_prim_alloc ( size_t size , size_t try_alignment , bool commit , bool allow_large , bool * is_large , bool * is_zero , mi_stats_t * stats ) {
mi_assert_internal ( size > 0 & & ( size % _mi_os_page_size ( ) ) = = 0 ) ;
mi_assert_internal ( is_zero ! = NULL ) ;
mi_assert_internal ( is_large ! = NULL ) ;
if ( size = = 0 ) return NULL ;
if ( ! commit ) { allow_large = false ; }
if ( try_alignment = = 0 ) { try_alignment = 1 ; } // avoid 0 to ensure there will be no divide by zero when aligning
* is_zero = false ;
void * p = NULL ;
int err = _mi_prim_alloc ( size , try_alignment , commit , allow_large , is_large , is_zero , & p ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " unable to allocate OS memory (error: %d (0x%x), size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d) \n " , err , err , size , try_alignment , commit , allow_large ) ;
}
mi_stat_counter_increase ( stats - > mmap_calls , 1 ) ;
if ( p ! = NULL ) {
_mi_stat_increase ( & stats - > reserved , size ) ;
if ( commit ) {
_mi_stat_increase ( & stats - > committed , size ) ;
// seems needed for asan (or `mimalloc-test-api` fails)
# ifdef MI_TRACK_ASAN
if ( * is_zero ) { mi_track_mem_defined ( p , size ) ; }
else { mi_track_mem_undefined ( p , size ) ; }
# endif
}
}
return p ;
}
// Primitive aligned allocation from the OS.
// This function guarantees the allocated memory is aligned.
static void * mi_os_prim_alloc_aligned ( size_t size , size_t alignment , bool commit , bool allow_large , bool * is_large , bool * is_zero , void * * base , mi_stats_t * stats ) {
mi_assert_internal ( alignment > = _mi_os_page_size ( ) & & ( ( alignment & ( alignment - 1 ) ) = = 0 ) ) ;
mi_assert_internal ( size > 0 & & ( size % _mi_os_page_size ( ) ) = = 0 ) ;
mi_assert_internal ( is_large ! = NULL ) ;
mi_assert_internal ( is_zero ! = NULL ) ;
mi_assert_internal ( base ! = NULL ) ;
if ( ! commit ) allow_large = false ;
if ( ! ( alignment > = _mi_os_page_size ( ) & & ( ( alignment & ( alignment - 1 ) ) = = 0 ) ) ) return NULL ;
size = _mi_align_up ( size , _mi_os_page_size ( ) ) ;
// try first with a hint (this will be aligned directly on Win 10+ or BSD)
void * p = mi_os_prim_alloc ( size , alignment , commit , allow_large , is_large , is_zero , stats ) ;
if ( p = = NULL ) return NULL ;
// aligned already?
if ( ( ( uintptr_t ) p % alignment ) = = 0 ) {
* base = p ;
}
else {
// if not aligned, free it, overallocate, and unmap around it
// NOTE(sgross): this warning causes issues in Python tests
// _mi_warning_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit);
mi_os_prim_free ( p , size , commit , stats ) ;
if ( size > = ( SIZE_MAX - alignment ) ) return NULL ; // overflow
const size_t over_size = size + alignment ;
if ( mi_os_mem_config . must_free_whole ) { // win32 virtualAlloc cannot free parts of an allocate block
// over-allocate uncommitted (virtual) memory
p = mi_os_prim_alloc ( over_size , 1 /*alignment*/ , false /* commit? */ , false /* allow_large */ , is_large , is_zero , stats ) ;
if ( p = = NULL ) return NULL ;
// set p to the aligned part in the full region
// note: this is dangerous on Windows as VirtualFree needs the actual base pointer
// this is handled though by having the `base` field in the memid's
* base = p ; // remember the base
p = mi_align_up_ptr ( p , alignment ) ;
// explicitly commit only the aligned part
if ( commit ) {
_mi_os_commit ( p , size , NULL , stats ) ;
}
}
else { // mmap can free inside an allocation
// overallocate...
p = mi_os_prim_alloc ( over_size , 1 , commit , false , is_large , is_zero , stats ) ;
if ( p = = NULL ) return NULL ;
// and selectively unmap parts around the over-allocated area. (noop on sbrk)
void * aligned_p = mi_align_up_ptr ( p , alignment ) ;
size_t pre_size = ( uint8_t * ) aligned_p - ( uint8_t * ) p ;
size_t mid_size = _mi_align_up ( size , _mi_os_page_size ( ) ) ;
size_t post_size = over_size - pre_size - mid_size ;
mi_assert_internal ( pre_size < over_size & & post_size < over_size & & mid_size > = size ) ;
if ( pre_size > 0 ) { mi_os_prim_free ( p , pre_size , commit , stats ) ; }
if ( post_size > 0 ) { mi_os_prim_free ( ( uint8_t * ) aligned_p + mid_size , post_size , commit , stats ) ; }
// we can return the aligned pointer on `mmap` (and sbrk) systems
p = aligned_p ;
* base = aligned_p ; // since we freed the pre part, `*base == p`.
}
}
mi_assert_internal ( p = = NULL | | ( p ! = NULL & & * base ! = NULL & & ( ( uintptr_t ) p % alignment ) = = 0 ) ) ;
return p ;
}
/* -----------------------------------------------------------
OS API : alloc and alloc_aligned
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void * _mi_os_alloc ( size_t size , mi_memid_t * memid , mi_stats_t * tld_stats ) {
MI_UNUSED ( tld_stats ) ;
* memid = _mi_memid_none ( ) ;
mi_stats_t * stats = & _mi_stats_main ;
if ( size = = 0 ) return NULL ;
size = _mi_os_good_alloc_size ( size ) ;
bool os_is_large = false ;
bool os_is_zero = false ;
void * p = mi_os_prim_alloc ( size , 0 , true , false , & os_is_large , & os_is_zero , stats ) ;
if ( p ! = NULL ) {
* memid = _mi_memid_create_os ( true , os_is_zero , os_is_large ) ;
}
return p ;
}
void * _mi_os_alloc_aligned ( size_t size , size_t alignment , bool commit , bool allow_large , mi_memid_t * memid , mi_stats_t * tld_stats )
{
MI_UNUSED ( & _mi_os_get_aligned_hint ) ; // suppress unused warnings
MI_UNUSED ( tld_stats ) ;
* memid = _mi_memid_none ( ) ;
if ( size = = 0 ) return NULL ;
size = _mi_os_good_alloc_size ( size ) ;
alignment = _mi_align_up ( alignment , _mi_os_page_size ( ) ) ;
bool os_is_large = false ;
bool os_is_zero = false ;
void * os_base = NULL ;
void * p = mi_os_prim_alloc_aligned ( size , alignment , commit , allow_large , & os_is_large , & os_is_zero , & os_base , & _mi_stats_main /*tld->stats*/ ) ;
if ( p ! = NULL ) {
* memid = _mi_memid_create_os ( commit , os_is_zero , os_is_large ) ;
memid - > mem . os . base = os_base ;
memid - > mem . os . alignment = alignment ;
}
return p ;
}
/* -----------------------------------------------------------
OS aligned allocation with an offset . This is used
for large alignments > MI_ALIGNMENT_MAX . We use a large mimalloc
page where the object can be aligned at an offset from the start of the segment .
As we may need to overallocate , we need to free such pointers using ` mi_free_aligned `
to use the actual start of the memory region .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
void * _mi_os_alloc_aligned_at_offset ( size_t size , size_t alignment , size_t offset , bool commit , bool allow_large , mi_memid_t * memid , mi_stats_t * tld_stats ) {
mi_assert ( offset < = MI_SEGMENT_SIZE ) ;
mi_assert ( offset < = size ) ;
mi_assert ( ( alignment % _mi_os_page_size ( ) ) = = 0 ) ;
* memid = _mi_memid_none ( ) ;
if ( offset > MI_SEGMENT_SIZE ) return NULL ;
if ( offset = = 0 ) {
// regular aligned allocation
return _mi_os_alloc_aligned ( size , alignment , commit , allow_large , memid , tld_stats ) ;
}
else {
// overallocate to align at an offset
const size_t extra = _mi_align_up ( offset , alignment ) - offset ;
const size_t oversize = size + extra ;
void * const start = _mi_os_alloc_aligned ( oversize , alignment , commit , allow_large , memid , tld_stats ) ;
if ( start = = NULL ) return NULL ;
void * const p = ( uint8_t * ) start + extra ;
mi_assert ( _mi_is_aligned ( ( uint8_t * ) p + offset , alignment ) ) ;
// decommit the overallocation at the start
if ( commit & & extra > _mi_os_page_size ( ) ) {
_mi_os_decommit ( start , extra , tld_stats ) ;
}
return p ;
}
}
/* -----------------------------------------------------------
OS memory API : reset , commit , decommit , protect , unprotect .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// OS page align within a given area, either conservative (pages inside the area only),
// or not (straddling pages outside the area is possible)
static void * mi_os_page_align_areax ( bool conservative , void * addr , size_t size , size_t * newsize ) {
mi_assert ( addr ! = NULL & & size > 0 ) ;
if ( newsize ! = NULL ) * newsize = 0 ;
if ( size = = 0 | | addr = = NULL ) return NULL ;
// page align conservatively within the range
void * start = ( conservative ? mi_align_up_ptr ( addr , _mi_os_page_size ( ) )
: mi_align_down_ptr ( addr , _mi_os_page_size ( ) ) ) ;
void * end = ( conservative ? mi_align_down_ptr ( ( uint8_t * ) addr + size , _mi_os_page_size ( ) )
: mi_align_up_ptr ( ( uint8_t * ) addr + size , _mi_os_page_size ( ) ) ) ;
ptrdiff_t diff = ( uint8_t * ) end - ( uint8_t * ) start ;
if ( diff < = 0 ) return NULL ;
mi_assert_internal ( ( conservative & & ( size_t ) diff < = size ) | | ( ! conservative & & ( size_t ) diff > = size ) ) ;
if ( newsize ! = NULL ) * newsize = ( size_t ) diff ;
return start ;
}
static void * mi_os_page_align_area_conservative ( void * addr , size_t size , size_t * newsize ) {
return mi_os_page_align_areax ( true , addr , size , newsize ) ;
}
bool _mi_os_commit ( void * addr , size_t size , bool * is_zero , mi_stats_t * tld_stats ) {
MI_UNUSED ( tld_stats ) ;
mi_stats_t * stats = & _mi_stats_main ;
if ( is_zero ! = NULL ) { * is_zero = false ; }
_mi_stat_increase ( & stats - > committed , size ) ; // use size for precise commit vs. decommit
_mi_stat_counter_increase ( & stats - > commit_calls , 1 ) ;
// page align range
size_t csize ;
void * start = mi_os_page_align_areax ( false /* conservative? */ , addr , size , & csize ) ;
if ( csize = = 0 ) return true ;
// commit
bool os_is_zero = false ;
int err = _mi_prim_commit ( start , csize , & os_is_zero ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " cannot commit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes) \n " , err , err , start , csize ) ;
return false ;
}
if ( os_is_zero & & is_zero ! = NULL ) {
* is_zero = true ;
mi_assert_expensive ( mi_mem_is_zero ( start , csize ) ) ;
}
// note: the following seems required for asan (otherwise `mimalloc-test-stress` fails)
# ifdef MI_TRACK_ASAN
if ( os_is_zero ) { mi_track_mem_defined ( start , csize ) ; }
else { mi_track_mem_undefined ( start , csize ) ; }
# endif
return true ;
}
static bool mi_os_decommit_ex ( void * addr , size_t size , bool * needs_recommit , mi_stats_t * tld_stats ) {
MI_UNUSED ( tld_stats ) ;
mi_stats_t * stats = & _mi_stats_main ;
mi_assert_internal ( needs_recommit ! = NULL ) ;
_mi_stat_decrease ( & stats - > committed , size ) ;
// page align
size_t csize ;
void * start = mi_os_page_align_area_conservative ( addr , size , & csize ) ;
if ( csize = = 0 ) return true ;
// decommit
* needs_recommit = true ;
int err = _mi_prim_decommit ( start , csize , needs_recommit ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " cannot decommit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes) \n " , err , err , start , csize ) ;
}
mi_assert_internal ( err = = 0 ) ;
return ( err = = 0 ) ;
}
bool _mi_os_decommit ( void * addr , size_t size , mi_stats_t * tld_stats ) {
bool needs_recommit ;
return mi_os_decommit_ex ( addr , size , & needs_recommit , tld_stats ) ;
}
// Signal to the OS that the address range is no longer in use
// but may be used later again. This will release physical memory
// pages and reduce swapping while keeping the memory committed.
// We page align to a conservative area inside the range to reset.
bool _mi_os_reset ( void * addr , size_t size , mi_stats_t * stats ) {
// page align conservatively within the range
size_t csize ;
void * start = mi_os_page_align_area_conservative ( addr , size , & csize ) ;
if ( csize = = 0 ) return true ; // || _mi_os_is_huge_reserved(addr)
_mi_stat_increase ( & stats - > reset , csize ) ;
_mi_stat_counter_increase ( & stats - > reset_calls , 1 ) ;
# if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN
memset ( start , 0 , csize ) ; // pretend it is eagerly reset
# endif
int err = _mi_prim_reset ( start , csize ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " cannot reset OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes) \n " , err , err , start , csize ) ;
}
return ( err = = 0 ) ;
}
// either resets or decommits memory, returns true if the memory needs
// to be recommitted if it is to be re-used later on.
bool _mi_os_purge_ex ( void * p , size_t size , bool allow_reset , mi_stats_t * stats )
{
if ( mi_option_get ( mi_option_purge_delay ) < 0 ) return false ; // is purging allowed?
_mi_stat_counter_increase ( & stats - > purge_calls , 1 ) ;
_mi_stat_increase ( & stats - > purged , size ) ;
if ( mi_option_is_enabled ( mi_option_purge_decommits ) & & // should decommit?
! _mi_preloading ( ) ) // don't decommit during preloading (unsafe)
{
bool needs_recommit = true ;
mi_os_decommit_ex ( p , size , & needs_recommit , stats ) ;
return needs_recommit ;
}
else {
if ( allow_reset ) { // this can sometimes be not allowed if the range is not fully committed
_mi_os_reset ( p , size , stats ) ;
}
return false ; // needs no recommit
}
}
// either resets or decommits memory, returns true if the memory needs
// to be recommitted if it is to be re-used later on.
bool _mi_os_purge ( void * p , size_t size , mi_stats_t * stats ) {
return _mi_os_purge_ex ( p , size , true , stats ) ;
}
// Protect a region in memory to be not accessible.
static bool mi_os_protectx ( void * addr , size_t size , bool protect ) {
// page align conservatively within the range
size_t csize = 0 ;
void * start = mi_os_page_align_area_conservative ( addr , size , & csize ) ;
if ( csize = = 0 ) return false ;
/*
if ( _mi_os_is_huge_reserved ( addr ) ) {
_mi_warning_message ( " cannot mprotect memory allocated in huge OS pages \n " ) ;
}
*/
int err = _mi_prim_protect ( start , csize , protect ) ;
if ( err ! = 0 ) {
_mi_warning_message ( " cannot %s OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes) \n " , ( protect ? " protect " : " unprotect " ) , err , err , start , csize ) ;
}
return ( err = = 0 ) ;
}
bool _mi_os_protect ( void * addr , size_t size ) {
return mi_os_protectx ( addr , size , true ) ;
}
bool _mi_os_unprotect ( void * addr , size_t size ) {
return mi_os_protectx ( addr , size , false ) ;
}
/* ----------------------------------------------------------------------------
Support for allocating huge OS pages ( 1 Gib ) that are reserved up - front
and possibly associated with a specific NUMA node . ( use ` numa_node > = 0 ` )
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# define MI_HUGE_OS_PAGE_SIZE (MI_GiB)
# if (MI_INTPTR_SIZE >= 8)
// To ensure proper alignment, use our own area for huge OS pages
static mi_decl_cache_align _Atomic ( uintptr_t ) mi_huge_start ; // = 0
// Claim an aligned address range for huge pages
static uint8_t * mi_os_claim_huge_pages ( size_t pages , size_t * total_size ) {
if ( total_size ! = NULL ) * total_size = 0 ;
const size_t size = pages * MI_HUGE_OS_PAGE_SIZE ;
uintptr_t start = 0 ;
uintptr_t end = 0 ;
uintptr_t huge_start = mi_atomic_load_relaxed ( & mi_huge_start ) ;
do {
start = huge_start ;
if ( start = = 0 ) {
// Initialize the start address after the 32TiB area
start = ( ( uintptr_t ) 32 < < 40 ) ; // 32TiB virtual start address
# if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode
2024-08-17 17:04:08 -03:00
mi_heap_t * heap = mi_prim_get_default_heap ( ) ;
// gh-123022: default heap may not be initialized in CPython in background threads
if ( mi_heap_is_initialized ( heap ) ) {
uintptr_t r = _mi_heap_random_next ( heap ) ;
start = start + ( ( uintptr_t ) MI_HUGE_OS_PAGE_SIZE * ( ( r > > 17 ) & 0x0FFF ) ) ; // (randomly 12bits)*1GiB == between 0 to 4TiB
}
2023-10-30 12:43:11 -03:00
# endif
}
end = start + size ;
mi_assert_internal ( end % MI_SEGMENT_SIZE = = 0 ) ;
} while ( ! mi_atomic_cas_strong_acq_rel ( & mi_huge_start , & huge_start , end ) ) ;
if ( total_size ! = NULL ) * total_size = size ;
return ( uint8_t * ) start ;
}
# else
static uint8_t * mi_os_claim_huge_pages ( size_t pages , size_t * total_size ) {
MI_UNUSED ( pages ) ;
if ( total_size ! = NULL ) * total_size = 0 ;
return NULL ;
}
# endif
// Allocate MI_SEGMENT_SIZE aligned huge pages
void * _mi_os_alloc_huge_os_pages ( size_t pages , int numa_node , mi_msecs_t max_msecs , size_t * pages_reserved , size_t * psize , mi_memid_t * memid ) {
* memid = _mi_memid_none ( ) ;
if ( psize ! = NULL ) * psize = 0 ;
if ( pages_reserved ! = NULL ) * pages_reserved = 0 ;
size_t size = 0 ;
uint8_t * start = mi_os_claim_huge_pages ( pages , & size ) ;
if ( start = = NULL ) return NULL ; // or 32-bit systems
// Allocate one page at the time but try to place them contiguously
// We allocate one page at the time to be able to abort if it takes too long
// or to at least allocate as many as available on the system.
mi_msecs_t start_t = _mi_clock_start ( ) ;
size_t page = 0 ;
bool all_zero = true ;
while ( page < pages ) {
// allocate a page
bool is_zero = false ;
void * addr = start + ( page * MI_HUGE_OS_PAGE_SIZE ) ;
void * p = NULL ;
int err = _mi_prim_alloc_huge_os_pages ( addr , MI_HUGE_OS_PAGE_SIZE , numa_node , & is_zero , & p ) ;
if ( ! is_zero ) { all_zero = false ; }
if ( err ! = 0 ) {
_mi_warning_message ( " unable to allocate huge OS page (error: %d (0x%x), address: %p, size: %zx bytes) \n " , err , err , addr , MI_HUGE_OS_PAGE_SIZE ) ;
break ;
}
// Did we succeed at a contiguous address?
if ( p ! = addr ) {
// no success, issue a warning and break
if ( p ! = NULL ) {
_mi_warning_message ( " could not allocate contiguous huge OS page %zu at %p \n " , page , addr ) ;
mi_os_prim_free ( p , MI_HUGE_OS_PAGE_SIZE , true , & _mi_stats_main ) ;
}
break ;
}
// success, record it
page + + ; // increase before timeout check (see issue #711)
_mi_stat_increase ( & _mi_stats_main . committed , MI_HUGE_OS_PAGE_SIZE ) ;
_mi_stat_increase ( & _mi_stats_main . reserved , MI_HUGE_OS_PAGE_SIZE ) ;
// check for timeout
if ( max_msecs > 0 ) {
mi_msecs_t elapsed = _mi_clock_end ( start_t ) ;
if ( page > = 1 ) {
mi_msecs_t estimate = ( ( elapsed / ( page + 1 ) ) * pages ) ;
if ( estimate > 2 * max_msecs ) { // seems like we are going to timeout, break
elapsed = max_msecs + 1 ;
}
}
if ( elapsed > max_msecs ) {
_mi_warning_message ( " huge OS page allocation timed out (after allocating %zu page(s)) \n " , page ) ;
break ;
}
}
}
mi_assert_internal ( page * MI_HUGE_OS_PAGE_SIZE < = size ) ;
if ( pages_reserved ! = NULL ) { * pages_reserved = page ; }
if ( psize ! = NULL ) { * psize = page * MI_HUGE_OS_PAGE_SIZE ; }
if ( page ! = 0 ) {
mi_assert ( start ! = NULL ) ;
* memid = _mi_memid_create_os ( true /* is committed */ , all_zero , true /* is_large */ ) ;
memid - > memkind = MI_MEM_OS_HUGE ;
mi_assert ( memid - > is_pinned ) ;
# ifdef MI_TRACK_ASAN
if ( all_zero ) { mi_track_mem_defined ( start , size ) ; }
# endif
}
return ( page = = 0 ? NULL : start ) ;
}
// free every huge page in a range individually (as we allocated per page)
// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems.
static void mi_os_free_huge_os_pages ( void * p , size_t size , mi_stats_t * stats ) {
if ( p = = NULL | | size = = 0 ) return ;
uint8_t * base = ( uint8_t * ) p ;
while ( size > = MI_HUGE_OS_PAGE_SIZE ) {
mi_os_prim_free ( base , MI_HUGE_OS_PAGE_SIZE , true , stats ) ;
size - = MI_HUGE_OS_PAGE_SIZE ;
base + = MI_HUGE_OS_PAGE_SIZE ;
}
}
/* ----------------------------------------------------------------------------
Support NUMA aware allocation
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
_Atomic ( size_t ) _mi_numa_node_count ; // = 0 // cache the node count
size_t _mi_os_numa_node_count_get ( void ) {
size_t count = mi_atomic_load_acquire ( & _mi_numa_node_count ) ;
if ( count < = 0 ) {
long ncount = mi_option_get ( mi_option_use_numa_nodes ) ; // given explicitly?
if ( ncount > 0 ) {
count = ( size_t ) ncount ;
}
else {
count = _mi_prim_numa_node_count ( ) ; // or detect dynamically
if ( count = = 0 ) count = 1 ;
}
mi_atomic_store_release ( & _mi_numa_node_count , count ) ; // save it
_mi_verbose_message ( " using %zd numa regions \n " , count ) ;
}
return count ;
}
int _mi_os_numa_node_get ( mi_os_tld_t * tld ) {
MI_UNUSED ( tld ) ;
size_t numa_count = _mi_os_numa_node_count ( ) ;
if ( numa_count < = 1 ) return 0 ; // optimize on single numa node systems: always node 0
// never more than the node count and >= 0
size_t numa_node = _mi_prim_numa_node ( ) ;
if ( numa_node > = numa_count ) { numa_node = numa_node % numa_count ; }
return ( int ) numa_node ;
}