| /* ---------------------------------------------------------------------------- |
| 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 |
| // load before the `if` and we therefore split it out in a separate funcion. |
| 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; |
| } |
| } |