2023-10-30 12:43:11 -03:00
/* ----------------------------------------------------------------------------
Copyright ( c ) 2018 - 2021 , 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" // mi_prim_out_stderr
# include <stdio.h> // FILE
# include <stdlib.h> // abort
# include <stdarg.h>
static long mi_max_error_count = 16 ; // stop outputting errors after this (use < 0 for no limit)
static long mi_max_warning_count = 16 ; // stop outputting warnings after this (use < 0 for no limit)
static void mi_add_stderr_output ( void ) ;
int mi_version ( void ) mi_attr_noexcept {
return MI_MALLOC_VERSION ;
}
// --------------------------------------------------------
// Options
// These can be accessed by multiple threads and may be
// concurrently initialized, but an initializing data race
// is ok since they resolve to the same value.
// --------------------------------------------------------
typedef enum mi_init_e {
UNINIT , // not yet initialized
DEFAULTED , // not found in the environment, use default value
INITIALIZED // found in environment or set explicitly
} mi_init_t ;
typedef struct mi_option_desc_s {
long value ; // the value
mi_init_t init ; // is it initialized yet? (from the environment)
mi_option_t option ; // for debugging: the option index should match the option
const char * name ; // option name without `mimalloc_` prefix
const char * legacy_name ; // potential legacy option name
} mi_option_desc_t ;
# define MI_OPTION(opt) mi_option_##opt, #opt, NULL
# define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy
static mi_option_desc_t options [ _mi_option_last ] =
{
// stable options
# if MI_DEBUG || defined(MI_SHOW_ERRORS)
{ 1 , UNINIT , MI_OPTION ( show_errors ) } ,
# else
{ 0 , UNINIT , MI_OPTION ( show_errors ) } ,
# endif
{ 0 , UNINIT , MI_OPTION ( show_stats ) } ,
{ 0 , UNINIT , MI_OPTION ( verbose ) } ,
// the following options are experimental and not all combinations make sense.
{ 1 , UNINIT , MI_OPTION ( eager_commit ) } , // commit per segment directly (4MiB) (but see also `eager_commit_delay`)
{ 2 , UNINIT , MI_OPTION_LEGACY ( arena_eager_commit , eager_region_commit ) } , // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux)
{ 1 , UNINIT , MI_OPTION_LEGACY ( purge_decommits , reset_decommits ) } , // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)
{ 0 , UNINIT , MI_OPTION_LEGACY ( allow_large_os_pages , large_os_pages ) } , // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
{ 0 , UNINIT , MI_OPTION ( reserve_huge_os_pages ) } , // per 1GiB huge pages
{ - 1 , UNINIT , MI_OPTION ( reserve_huge_os_pages_at ) } , // reserve huge pages at node N
{ 0 , UNINIT , MI_OPTION ( reserve_os_memory ) } ,
{ 0 , UNINIT , MI_OPTION ( deprecated_segment_cache ) } , // cache N segments per thread
{ 0 , UNINIT , MI_OPTION ( deprecated_page_reset ) } , // reset page memory on free
{ 0 , UNINIT , MI_OPTION_LEGACY ( abandoned_page_purge , abandoned_page_reset ) } , // reset free page memory when a thread terminates
{ 0 , UNINIT , MI_OPTION ( deprecated_segment_reset ) } , // reset segment memory on free (needs eager commit)
# if defined(__NetBSD__)
{ 0 , UNINIT , MI_OPTION ( eager_commit_delay ) } , // the first N segments per thread are not eagerly committed
# else
{ 1 , UNINIT , MI_OPTION ( eager_commit_delay ) } , // the first N segments per thread are not eagerly committed (but per page in the segment on demand)
# endif
{ 10 , UNINIT , MI_OPTION_LEGACY ( purge_delay , reset_delay ) } , // purge delay in milli-seconds
{ 0 , UNINIT , MI_OPTION ( use_numa_nodes ) } , // 0 = use available numa nodes, otherwise use at most N nodes.
{ 0 , UNINIT , MI_OPTION ( limit_os_alloc ) } , // 1 = do not use OS memory for allocation (but only reserved arenas)
{ 100 , UNINIT , MI_OPTION ( os_tag ) } , // only apple specific for now but might serve more or less related purpose
{ 16 , UNINIT , MI_OPTION ( max_errors ) } , // maximum errors that are output
{ 16 , UNINIT , MI_OPTION ( max_warnings ) } , // maximum warnings that are output
{ 8 , UNINIT , MI_OPTION ( max_segment_reclaim ) } , // max. number of segment reclaims from the abandoned segments per try.
{ 0 , UNINIT , MI_OPTION ( destroy_on_exit ) } , // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
# if (MI_INTPTR_SIZE>4)
{ 1024L * 1024L , UNINIT , MI_OPTION ( arena_reserve ) } , // reserve memory N KiB at a time
# else
{ 128L * 1024L , UNINIT , MI_OPTION ( arena_reserve ) } ,
# endif
{ 10 , UNINIT , MI_OPTION ( arena_purge_mult ) } , // purge delay multiplier for arena's
{ 1 , UNINIT , MI_OPTION_LEGACY ( purge_extend_delay , decommit_extend_delay ) } ,
} ;
static void mi_option_init ( mi_option_desc_t * desc ) ;
void _mi_options_init ( void ) {
// called on process load; should not be called before the CRT is initialized!
// (e.g. do not call this from process_init as that may run before CRT initialization)
mi_add_stderr_output ( ) ; // now it safe to use stderr for output
for ( int i = 0 ; i < _mi_option_last ; i + + ) {
mi_option_t option = ( mi_option_t ) i ;
long l = mi_option_get ( option ) ; MI_UNUSED ( l ) ; // initialize
// if (option != mi_option_verbose)
{
mi_option_desc_t * desc = & options [ option ] ;
_mi_verbose_message ( " option '%s': %ld \n " , desc - > name , desc - > value ) ;
}
}
mi_max_error_count = mi_option_get ( mi_option_max_errors ) ;
mi_max_warning_count = mi_option_get ( mi_option_max_warnings ) ;
}
mi_decl_nodiscard long mi_option_get ( mi_option_t option ) {
mi_assert ( option > = 0 & & option < _mi_option_last ) ;
if ( option < 0 | | option > = _mi_option_last ) return 0 ;
mi_option_desc_t * desc = & options [ option ] ;
mi_assert ( desc - > option = = option ) ; // index should match the option
if mi_unlikely ( desc - > init = = UNINIT ) {
mi_option_init ( desc ) ;
}
return desc - > value ;
}
mi_decl_nodiscard long mi_option_get_clamp ( mi_option_t option , long min , long max ) {
long x = mi_option_get ( option ) ;
return ( x < min ? min : ( x > max ? max : x ) ) ;
}
mi_decl_nodiscard size_t mi_option_get_size ( mi_option_t option ) {
mi_assert_internal ( option = = mi_option_reserve_os_memory | | option = = mi_option_arena_reserve ) ;
long x = mi_option_get ( option ) ;
return ( x < 0 ? 0 : ( size_t ) x * MI_KiB ) ;
}
void mi_option_set ( mi_option_t option , long value ) {
mi_assert ( option > = 0 & & option < _mi_option_last ) ;
if ( option < 0 | | option > = _mi_option_last ) return ;
mi_option_desc_t * desc = & options [ option ] ;
mi_assert ( desc - > option = = option ) ; // index should match the option
desc - > value = value ;
desc - > init = INITIALIZED ;
}
void mi_option_set_default ( mi_option_t option , long value ) {
mi_assert ( option > = 0 & & option < _mi_option_last ) ;
if ( option < 0 | | option > = _mi_option_last ) return ;
mi_option_desc_t * desc = & options [ option ] ;
if ( desc - > init ! = INITIALIZED ) {
desc - > value = value ;
}
}
mi_decl_nodiscard bool mi_option_is_enabled ( mi_option_t option ) {
return ( mi_option_get ( option ) ! = 0 ) ;
}
void mi_option_set_enabled ( mi_option_t option , bool enable ) {
mi_option_set ( option , ( enable ? 1 : 0 ) ) ;
}
void mi_option_set_enabled_default ( mi_option_t option , bool enable ) {
mi_option_set_default ( option , ( enable ? 1 : 0 ) ) ;
}
void mi_option_enable ( mi_option_t option ) {
mi_option_set_enabled ( option , true ) ;
}
void mi_option_disable ( mi_option_t option ) {
mi_option_set_enabled ( option , false ) ;
}
static void mi_cdecl mi_out_stderr ( const char * msg , void * arg ) {
MI_UNUSED ( arg ) ;
if ( msg ! = NULL & & msg [ 0 ] ! = 0 ) {
_mi_prim_out_stderr ( msg ) ;
}
}
// Since an output function can be registered earliest in the `main`
// function we also buffer output that happens earlier. When
// an output function is registered it is called immediately with
// the output up to that point.
# ifndef MI_MAX_DELAY_OUTPUT
# define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024))
# endif
static char out_buf [ MI_MAX_DELAY_OUTPUT + 1 ] ;
static _Atomic ( size_t ) out_len ;
static void mi_cdecl mi_out_buf ( const char * msg , void * arg ) {
MI_UNUSED ( arg ) ;
if ( msg = = NULL ) return ;
if ( mi_atomic_load_relaxed ( & out_len ) > = MI_MAX_DELAY_OUTPUT ) return ;
size_t n = _mi_strlen ( msg ) ;
if ( n = = 0 ) return ;
// claim space
size_t start = mi_atomic_add_acq_rel ( & out_len , n ) ;
if ( start > = MI_MAX_DELAY_OUTPUT ) return ;
// check bound
if ( start + n > = MI_MAX_DELAY_OUTPUT ) {
n = MI_MAX_DELAY_OUTPUT - start - 1 ;
}
_mi_memcpy ( & out_buf [ start ] , msg , n ) ;
}
static void mi_out_buf_flush ( mi_output_fun * out , bool no_more_buf , void * arg ) {
if ( out = = NULL ) return ;
// claim (if `no_more_buf == true`, no more output will be added after this point)
size_t count = mi_atomic_add_acq_rel ( & out_len , ( no_more_buf ? MI_MAX_DELAY_OUTPUT : 1 ) ) ;
// and output the current contents
if ( count > MI_MAX_DELAY_OUTPUT ) count = MI_MAX_DELAY_OUTPUT ;
out_buf [ count ] = 0 ;
out ( out_buf , arg ) ;
if ( ! no_more_buf ) {
out_buf [ count ] = ' \n ' ; // if continue with the buffer, insert a newline
}
}
// Once this module is loaded, switch to this routine
// which outputs to stderr and the delayed output buffer.
static void mi_cdecl mi_out_buf_stderr ( const char * msg , void * arg ) {
mi_out_stderr ( msg , arg ) ;
mi_out_buf ( msg , arg ) ;
}
// --------------------------------------------------------
// Default output handler
// --------------------------------------------------------
// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.
// For now, don't register output from multiple threads.
static mi_output_fun * volatile mi_out_default ; // = NULL
static _Atomic ( void * ) mi_out_arg ; // = NULL
static mi_output_fun * mi_out_get_default ( void * * parg ) {
if ( parg ! = NULL ) { * parg = mi_atomic_load_ptr_acquire ( void , & mi_out_arg ) ; }
mi_output_fun * out = mi_out_default ;
return ( out = = NULL ? & mi_out_buf : out ) ;
}
void mi_register_output ( mi_output_fun * out , void * arg ) mi_attr_noexcept {
mi_out_default = ( out = = NULL ? & mi_out_stderr : out ) ; // stop using the delayed output buffer
mi_atomic_store_ptr_release ( void , & mi_out_arg , arg ) ;
if ( out ! = NULL ) mi_out_buf_flush ( out , true , arg ) ; // output all the delayed output now
}
// add stderr to the delayed output after the module is loaded
static void mi_add_stderr_output ( void ) {
mi_assert_internal ( mi_out_default = = NULL ) ;
mi_out_buf_flush ( & mi_out_stderr , false , NULL ) ; // flush current contents to stderr
mi_out_default = & mi_out_buf_stderr ; // and add stderr to the delayed output
}
// --------------------------------------------------------
// Messages, all end up calling `_mi_fputs`.
// --------------------------------------------------------
static _Atomic ( size_t ) error_count ; // = 0; // when >= max_error_count stop emitting errors
static _Atomic ( size_t ) warning_count ; // = 0; // when >= max_warning_count stop emitting warnings
// When overriding malloc, we may recurse into mi_vfprintf if an allocation
// inside the C runtime causes another message.
// In some cases (like on macOS) the loader already allocates which
// calls into mimalloc; if we then access thread locals (like `recurse`)
// this may crash as the access may call _tlv_bootstrap that tries to
// (recursively) invoke malloc again to allocate space for the thread local
// variables on demand. This is why we use a _mi_preloading test on such
// platforms. However, C code generator may move the initial thread local address
2024-08-28 08:41:04 -03:00
// load before the `if` and we therefore split it out in a separate function.
2023-10-30 12:43:11 -03:00
static mi_decl_thread bool recurse = false ;
static mi_decl_noinline bool mi_recurse_enter_prim ( void ) {
if ( recurse ) return false ;
recurse = true ;
return true ;
}
static mi_decl_noinline void mi_recurse_exit_prim ( void ) {
recurse = false ;
}
static bool mi_recurse_enter ( void ) {
# if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
if ( _mi_preloading ( ) ) return false ;
# endif
return mi_recurse_enter_prim ( ) ;
}
static void mi_recurse_exit ( void ) {
# if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
if ( _mi_preloading ( ) ) return ;
# endif
mi_recurse_exit_prim ( ) ;
}
void _mi_fputs ( mi_output_fun * out , void * arg , const char * prefix , const char * message ) {
if ( out = = NULL | | ( void * ) out = = ( void * ) stdout | | ( void * ) out = = ( void * ) stderr ) { // TODO: use mi_out_stderr for stderr?
if ( ! mi_recurse_enter ( ) ) return ;
out = mi_out_get_default ( & arg ) ;
if ( prefix ! = NULL ) out ( prefix , arg ) ;
out ( message , arg ) ;
mi_recurse_exit ( ) ;
}
else {
if ( prefix ! = NULL ) out ( prefix , arg ) ;
out ( message , arg ) ;
}
}
// Define our own limited `fprintf` that avoids memory allocation.
// We do this using `snprintf` with a limited buffer.
static void mi_vfprintf ( mi_output_fun * out , void * arg , const char * prefix , const char * fmt , va_list args ) {
char buf [ 512 ] ;
if ( fmt = = NULL ) return ;
if ( ! mi_recurse_enter ( ) ) return ;
vsnprintf ( buf , sizeof ( buf ) - 1 , fmt , args ) ;
mi_recurse_exit ( ) ;
_mi_fputs ( out , arg , prefix , buf ) ;
}
void _mi_fprintf ( mi_output_fun * out , void * arg , const char * fmt , . . . ) {
va_list args ;
va_start ( args , fmt ) ;
mi_vfprintf ( out , arg , NULL , fmt , args ) ;
va_end ( args ) ;
}
static void mi_vfprintf_thread ( mi_output_fun * out , void * arg , const char * prefix , const char * fmt , va_list args ) {
if ( prefix ! = NULL & & _mi_strnlen ( prefix , 33 ) < = 32 & & ! _mi_is_main_thread ( ) ) {
char tprefix [ 64 ] ;
snprintf ( tprefix , sizeof ( tprefix ) , " %sthread 0x%llx: " , prefix , ( unsigned long long ) _mi_thread_id ( ) ) ;
mi_vfprintf ( out , arg , tprefix , fmt , args ) ;
}
else {
mi_vfprintf ( out , arg , prefix , fmt , args ) ;
}
}
void _mi_trace_message ( const char * fmt , . . . ) {
if ( mi_option_get ( mi_option_verbose ) < = 1 ) return ; // only with verbose level 2 or higher
va_list args ;
va_start ( args , fmt ) ;
mi_vfprintf_thread ( NULL , NULL , " mimalloc: " , fmt , args ) ;
va_end ( args ) ;
}
void _mi_verbose_message ( const char * fmt , . . . ) {
if ( ! mi_option_is_enabled ( mi_option_verbose ) ) return ;
va_list args ;
va_start ( args , fmt ) ;
mi_vfprintf ( NULL , NULL , " mimalloc: " , fmt , args ) ;
va_end ( args ) ;
}
static void mi_show_error_message ( const char * fmt , va_list args ) {
if ( ! mi_option_is_enabled ( mi_option_verbose ) ) {
if ( ! mi_option_is_enabled ( mi_option_show_errors ) ) return ;
if ( mi_max_error_count > = 0 & & ( long ) mi_atomic_increment_acq_rel ( & error_count ) > mi_max_error_count ) return ;
}
mi_vfprintf_thread ( NULL , NULL , " mimalloc: error: " , fmt , args ) ;
}
void _mi_warning_message ( const char * fmt , . . . ) {
if ( ! mi_option_is_enabled ( mi_option_verbose ) ) {
if ( ! mi_option_is_enabled ( mi_option_show_errors ) ) return ;
if ( mi_max_warning_count > = 0 & & ( long ) mi_atomic_increment_acq_rel ( & warning_count ) > mi_max_warning_count ) return ;
}
va_list args ;
va_start ( args , fmt ) ;
mi_vfprintf_thread ( NULL , NULL , " mimalloc: warning: " , fmt , args ) ;
va_end ( args ) ;
}
# if MI_DEBUG
void _mi_assert_fail ( const char * assertion , const char * fname , unsigned line , const char * func ) {
_mi_fprintf ( NULL , NULL , " mimalloc: assertion failed: at \" %s \" :%u, %s \n assertion: \" %s \" \n " , fname , line , ( func = = NULL ? " " : func ) , assertion ) ;
abort ( ) ;
}
# endif
// --------------------------------------------------------
// Errors
// --------------------------------------------------------
static mi_error_fun * volatile mi_error_handler ; // = NULL
static _Atomic ( void * ) mi_error_arg ; // = NULL
static void mi_error_default ( int err ) {
MI_UNUSED ( err ) ;
# if (MI_DEBUG>0)
if ( err = = EFAULT ) {
# ifdef _MSC_VER
__debugbreak ( ) ;
# endif
abort ( ) ;
}
# endif
# if (MI_SECURE>0)
if ( err = = EFAULT ) { // abort on serious errors in secure mode (corrupted meta-data)
abort ( ) ;
}
# endif
# if defined(MI_XMALLOC)
if ( err = = ENOMEM | | err = = EOVERFLOW ) { // abort on memory allocation fails in xmalloc mode
abort ( ) ;
}
# endif
}
void mi_register_error ( mi_error_fun * fun , void * arg ) {
mi_error_handler = fun ; // can be NULL
mi_atomic_store_ptr_release ( void , & mi_error_arg , arg ) ;
}
void _mi_error_message ( int err , const char * fmt , . . . ) {
// show detailed error message
va_list args ;
va_start ( args , fmt ) ;
mi_show_error_message ( fmt , args ) ;
va_end ( args ) ;
// and call the error handler which may abort (or return normally)
if ( mi_error_handler ! = NULL ) {
mi_error_handler ( err , mi_atomic_load_ptr_acquire ( void , & mi_error_arg ) ) ;
}
else {
mi_error_default ( err ) ;
}
}
// --------------------------------------------------------
// Initialize options by checking the environment
// --------------------------------------------------------
char _mi_toupper ( char c ) {
if ( c > = ' a ' & & c < = ' z ' ) return ( c - ' a ' + ' A ' ) ;
else return c ;
}
int _mi_strnicmp ( const char * s , const char * t , size_t n ) {
if ( n = = 0 ) return 0 ;
for ( ; * s ! = 0 & & * t ! = 0 & & n > 0 ; s + + , t + + , n - - ) {
if ( _mi_toupper ( * s ) ! = _mi_toupper ( * t ) ) break ;
}
return ( n = = 0 ? 0 : * s - * t ) ;
}
void _mi_strlcpy ( char * dest , const char * src , size_t dest_size ) {
if ( dest = = NULL | | src = = NULL | | dest_size = = 0 ) return ;
// copy until end of src, or when dest is (almost) full
while ( * src ! = 0 & & dest_size > 1 ) {
* dest + + = * src + + ;
dest_size - - ;
}
// always zero terminate
* dest = 0 ;
}
void _mi_strlcat ( char * dest , const char * src , size_t dest_size ) {
if ( dest = = NULL | | src = = NULL | | dest_size = = 0 ) return ;
// find end of string in the dest buffer
while ( * dest ! = 0 & & dest_size > 1 ) {
dest + + ;
dest_size - - ;
}
// and catenate
_mi_strlcpy ( dest , src , dest_size ) ;
}
size_t _mi_strlen ( const char * s ) {
if ( s = = NULL ) return 0 ;
size_t len = 0 ;
while ( s [ len ] ! = 0 ) { len + + ; }
return len ;
}
size_t _mi_strnlen ( const char * s , size_t max_len ) {
if ( s = = NULL ) return 0 ;
size_t len = 0 ;
while ( s [ len ] ! = 0 & & len < max_len ) { len + + ; }
return len ;
}
# ifdef MI_NO_GETENV
static bool mi_getenv ( const char * name , char * result , size_t result_size ) {
MI_UNUSED ( name ) ;
MI_UNUSED ( result ) ;
MI_UNUSED ( result_size ) ;
return false ;
}
# else
static bool mi_getenv ( const char * name , char * result , size_t result_size ) {
if ( name = = NULL | | result = = NULL | | result_size < 64 ) return false ;
return _mi_prim_getenv ( name , result , result_size ) ;
}
# endif
// TODO: implement ourselves to reduce dependencies on the C runtime
# include <stdlib.h> // strtol
# include <string.h> // strstr
static void mi_option_init ( mi_option_desc_t * desc ) {
// Read option value from the environment
char s [ 64 + 1 ] ;
char buf [ 64 + 1 ] ;
_mi_strlcpy ( buf , " mimalloc_ " , sizeof ( buf ) ) ;
_mi_strlcat ( buf , desc - > name , sizeof ( buf ) ) ;
bool found = mi_getenv ( buf , s , sizeof ( s ) ) ;
if ( ! found & & desc - > legacy_name ! = NULL ) {
_mi_strlcpy ( buf , " mimalloc_ " , sizeof ( buf ) ) ;
_mi_strlcat ( buf , desc - > legacy_name , sizeof ( buf ) ) ;
found = mi_getenv ( buf , s , sizeof ( s ) ) ;
if ( found ) {
_mi_warning_message ( " environment option \" mimalloc_%s \" is deprecated -- use \" mimalloc_%s \" instead. \n " , desc - > legacy_name , desc - > name ) ;
}
}
if ( found ) {
size_t len = _mi_strnlen ( s , sizeof ( buf ) - 1 ) ;
for ( size_t i = 0 ; i < len ; i + + ) {
buf [ i ] = _mi_toupper ( s [ i ] ) ;
}
buf [ len ] = 0 ;
if ( buf [ 0 ] = = 0 | | strstr ( " 1;TRUE;YES;ON " , buf ) ! = NULL ) {
desc - > value = 1 ;
desc - > init = INITIALIZED ;
}
else if ( strstr ( " 0;FALSE;NO;OFF " , buf ) ! = NULL ) {
desc - > value = 0 ;
desc - > init = INITIALIZED ;
}
else {
char * end = buf ;
long value = strtol ( buf , & end , 10 ) ;
if ( desc - > option = = mi_option_reserve_os_memory | | desc - > option = = mi_option_arena_reserve ) {
// this option is interpreted in KiB to prevent overflow of `long`
if ( * end = = ' K ' ) { end + + ; }
else if ( * end = = ' M ' ) { value * = MI_KiB ; end + + ; }
else if ( * end = = ' G ' ) { value * = MI_MiB ; end + + ; }
else { value = ( value + MI_KiB - 1 ) / MI_KiB ; }
if ( end [ 0 ] = = ' I ' & & end [ 1 ] = = ' B ' ) { end + = 2 ; }
else if ( * end = = ' B ' ) { end + + ; }
}
if ( * end = = 0 ) {
desc - > value = value ;
desc - > init = INITIALIZED ;
}
else {
// set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.
desc - > init = DEFAULTED ;
if ( desc - > option = = mi_option_verbose & & desc - > value = = 0 ) {
// if the 'mimalloc_verbose' env var has a bogus value we'd never know
// (since the value defaults to 'off') so in that case briefly enable verbose
desc - > value = 1 ;
_mi_warning_message ( " environment option mimalloc_%s has an invalid value. \n " , desc - > name ) ;
desc - > value = 0 ;
}
else {
_mi_warning_message ( " environment option mimalloc_%s has an invalid value. \n " , desc - > name ) ;
}
}
}
mi_assert_internal ( desc - > init ! = UNINIT ) ;
}
else if ( ! _mi_preloading ( ) ) {
desc - > init = DEFAULTED ;
}
}