Path: blob/main/system/lib/mimalloc/src/options.c
6175 views
/* ----------------------------------------------------------------------------1Copyright (c) 2018-2021, 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#include "mimalloc.h"7#include "mimalloc/internal.h"8#include "mimalloc/atomic.h"9#include "mimalloc/prim.h" // mi_prim_out_stderr1011#include <stdio.h> // stdin/stdout12#include <stdlib.h> // abort13141516static long mi_max_error_count = 16; // stop outputting errors after this (use < 0 for no limit)17static long mi_max_warning_count = 16; // stop outputting warnings after this (use < 0 for no limit)1819static void mi_add_stderr_output(void);2021int mi_version(void) mi_attr_noexcept {22return MI_MALLOC_VERSION;23}242526// --------------------------------------------------------27// Options28// These can be accessed by multiple threads and may be29// concurrently initialized, but an initializing data race30// is ok since they resolve to the same value.31// --------------------------------------------------------32typedef enum mi_init_e {33UNINIT, // not yet initialized34DEFAULTED, // not found in the environment, use default value35INITIALIZED // found in environment or set explicitly36} mi_init_t;3738typedef struct mi_option_desc_s {39long value; // the value40mi_init_t init; // is it initialized yet? (from the environment)41mi_option_t option; // for debugging: the option index should match the option42const char* name; // option name without `mimalloc_` prefix43const char* legacy_name; // potential legacy option name44} mi_option_desc_t;4546#define MI_OPTION(opt) mi_option_##opt, #opt, NULL47#define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy4849static mi_option_desc_t options[_mi_option_last] =50{51// stable options52#if MI_DEBUG || defined(MI_SHOW_ERRORS)53{ 1, UNINIT, MI_OPTION(show_errors) },54#else55{ 0, UNINIT, MI_OPTION(show_errors) },56#endif57{ 0, UNINIT, MI_OPTION(show_stats) },58{ 0, UNINIT, MI_OPTION(verbose) },5960// the following options are experimental and not all combinations make sense.61{ 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (4MiB) (but see also `eager_commit_delay`)62{ 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)63{ 1, UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) }, // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)64{ 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's65{ 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages66{-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N67{ 0, UNINIT, MI_OPTION(reserve_os_memory) }, // reserve N KiB OS memory in advance (use `option_get_size`)68{ 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread69{ 0, UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free70{ 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_purge,abandoned_page_reset) }, // reset free page memory when a thread terminates71{ 0, UNINIT, MI_OPTION(deprecated_segment_reset) }, // reset segment memory on free (needs eager commit)72#if defined(__NetBSD__)73{ 0, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed74#else75{ 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)76#endif77{ 10, UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds78{ 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.79{ 0, UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)80{ 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose81{ 32, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output82{ 32, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output83{ 10, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. percentage of the abandoned segments to be reclaimed per try.84{ 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees!85#if (MI_INTPTR_SIZE>4)86{ 1024L*1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)87#else88{ 128L*1024L, UNINIT, MI_OPTION(arena_reserve) }, // =128MiB on 32-bit89#endif90{ 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's91{ 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },92{ 1, UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free93{ 0, UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)94{ 400, UNINIT, MI_OPTION(retry_on_oom) }, // windows only: retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries.95};9697static void mi_option_init(mi_option_desc_t* desc);9899static bool mi_option_has_size_in_kib(mi_option_t option) {100return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);101}102103void _mi_options_init(void) {104// called on process load; should not be called before the CRT is initialized!105// (e.g. do not call this from process_init as that may run before CRT initialization)106mi_add_stderr_output(); // now it safe to use stderr for output107for(int i = 0; i < _mi_option_last; i++ ) {108mi_option_t option = (mi_option_t)i;109long l = mi_option_get(option); MI_UNUSED(l); // initialize110// if (option != mi_option_verbose)111{112mi_option_desc_t* desc = &options[option];113_mi_verbose_message("option '%s': %ld %s\n", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? "KiB" : ""));114}115}116mi_max_error_count = mi_option_get(mi_option_max_errors);117mi_max_warning_count = mi_option_get(mi_option_max_warnings);118}119120mi_decl_nodiscard long mi_option_get(mi_option_t option) {121mi_assert(option >= 0 && option < _mi_option_last);122if (option < 0 || option >= _mi_option_last) return 0;123mi_option_desc_t* desc = &options[option];124mi_assert(desc->option == option); // index should match the option125if mi_unlikely(desc->init == UNINIT) {126mi_option_init(desc);127}128return desc->value;129}130131mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) {132long x = mi_option_get(option);133return (x < min ? min : (x > max ? max : x));134}135136mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {137mi_assert_internal(mi_option_has_size_in_kib(option));138const long x = mi_option_get(option);139size_t size = (x < 0 ? 0 : (size_t)x);140if (mi_option_has_size_in_kib(option)) {141size *= MI_KiB;142}143return size;144}145146void mi_option_set(mi_option_t option, long value) {147mi_assert(option >= 0 && option < _mi_option_last);148if (option < 0 || option >= _mi_option_last) return;149mi_option_desc_t* desc = &options[option];150mi_assert(desc->option == option); // index should match the option151desc->value = value;152desc->init = INITIALIZED;153}154155void mi_option_set_default(mi_option_t option, long value) {156mi_assert(option >= 0 && option < _mi_option_last);157if (option < 0 || option >= _mi_option_last) return;158mi_option_desc_t* desc = &options[option];159if (desc->init != INITIALIZED) {160desc->value = value;161}162}163164mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) {165return (mi_option_get(option) != 0);166}167168void mi_option_set_enabled(mi_option_t option, bool enable) {169mi_option_set(option, (enable ? 1 : 0));170}171172void mi_option_set_enabled_default(mi_option_t option, bool enable) {173mi_option_set_default(option, (enable ? 1 : 0));174}175176void mi_option_enable(mi_option_t option) {177mi_option_set_enabled(option,true);178}179180void mi_option_disable(mi_option_t option) {181mi_option_set_enabled(option,false);182}183184static void mi_cdecl mi_out_stderr(const char* msg, void* arg) {185MI_UNUSED(arg);186if (msg != NULL && msg[0] != 0) {187_mi_prim_out_stderr(msg);188}189}190191// Since an output function can be registered earliest in the `main`192// function we also buffer output that happens earlier. When193// an output function is registered it is called immediately with194// the output up to that point.195#ifndef MI_MAX_DELAY_OUTPUT196#define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024))197#endif198static char out_buf[MI_MAX_DELAY_OUTPUT+1];199static _Atomic(size_t) out_len;200201static void mi_cdecl mi_out_buf(const char* msg, void* arg) {202MI_UNUSED(arg);203if (msg==NULL) return;204if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return;205size_t n = _mi_strlen(msg);206if (n==0) return;207// claim space208size_t start = mi_atomic_add_acq_rel(&out_len, n);209if (start >= MI_MAX_DELAY_OUTPUT) return;210// check bound211if (start+n >= MI_MAX_DELAY_OUTPUT) {212n = MI_MAX_DELAY_OUTPUT-start-1;213}214_mi_memcpy(&out_buf[start], msg, n);215}216217static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {218if (out==NULL) return;219// claim (if `no_more_buf == true`, no more output will be added after this point)220size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));221// and output the current contents222if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;223out_buf[count] = 0;224out(out_buf,arg);225if (!no_more_buf) {226out_buf[count] = '\n'; // if continue with the buffer, insert a newline227}228}229230231// Once this module is loaded, switch to this routine232// which outputs to stderr and the delayed output buffer.233static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {234mi_out_stderr(msg,arg);235mi_out_buf(msg,arg);236}237238239240// --------------------------------------------------------241// Default output handler242// --------------------------------------------------------243244// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.245// For now, don't register output from multiple threads.246static mi_output_fun* volatile mi_out_default; // = NULL247static _Atomic(void*) mi_out_arg; // = NULL248249static mi_output_fun* mi_out_get_default(void** parg) {250if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }251mi_output_fun* out = mi_out_default;252return (out == NULL ? &mi_out_buf : out);253}254255void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {256mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer257mi_atomic_store_ptr_release(void,&mi_out_arg, arg);258if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now259}260261// add stderr to the delayed output after the module is loaded262static void mi_add_stderr_output(void) {263mi_assert_internal(mi_out_default == NULL);264mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr265mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output266}267268// --------------------------------------------------------269// Messages, all end up calling `_mi_fputs`.270// --------------------------------------------------------271static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors272static _Atomic(size_t) warning_count; // = 0; // when >= max_warning_count stop emitting warnings273274// When overriding malloc, we may recurse into mi_vfprintf if an allocation275// inside the C runtime causes another message.276// In some cases (like on macOS) the loader already allocates which277// calls into mimalloc; if we then access thread locals (like `recurse`)278// this may crash as the access may call _tlv_bootstrap that tries to279// (recursively) invoke malloc again to allocate space for the thread local280// variables on demand. This is why we use a _mi_preloading test on such281// platforms. However, C code generator may move the initial thread local address282// load before the `if` and we therefore split it out in a separate funcion.283static mi_decl_thread bool recurse = false;284285static mi_decl_noinline bool mi_recurse_enter_prim(void) {286if (recurse) return false;287recurse = true;288return true;289}290291static mi_decl_noinline void mi_recurse_exit_prim(void) {292recurse = false;293}294295static bool mi_recurse_enter(void) {296#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)297if (_mi_preloading()) return false;298#endif299return mi_recurse_enter_prim();300}301302static void mi_recurse_exit(void) {303#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)304if (_mi_preloading()) return;305#endif306mi_recurse_exit_prim();307}308309void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {310if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr?311if (!mi_recurse_enter()) return;312out = mi_out_get_default(&arg);313if (prefix != NULL) out(prefix, arg);314out(message, arg);315mi_recurse_exit();316}317else {318if (prefix != NULL) out(prefix, arg);319out(message, arg);320}321}322323// Define our own limited `fprintf` that avoids memory allocation.324// We do this using `_mi_vsnprintf` with a limited buffer.325static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {326char buf[512];327if (fmt==NULL) return;328if (!mi_recurse_enter()) return;329_mi_vsnprintf(buf, sizeof(buf)-1, fmt, args);330mi_recurse_exit();331_mi_fputs(out,arg,prefix,buf);332}333334void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {335va_list args;336va_start(args,fmt);337mi_vfprintf(out,arg,NULL,fmt,args);338va_end(args);339}340341static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {342if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {343char tprefix[64];344_mi_snprintf(tprefix, sizeof(tprefix), "%sthread 0x%tx: ", prefix, (uintptr_t)_mi_thread_id());345mi_vfprintf(out, arg, tprefix, fmt, args);346}347else {348mi_vfprintf(out, arg, prefix, fmt, args);349}350}351352void _mi_trace_message(const char* fmt, ...) {353if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher354va_list args;355va_start(args, fmt);356mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);357va_end(args);358}359360void _mi_verbose_message(const char* fmt, ...) {361if (!mi_option_is_enabled(mi_option_verbose)) return;362va_list args;363va_start(args,fmt);364mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args);365va_end(args);366}367368static void mi_show_error_message(const char* fmt, va_list args) {369if (!mi_option_is_enabled(mi_option_verbose)) {370if (!mi_option_is_enabled(mi_option_show_errors)) return;371if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return;372}373mi_vfprintf_thread(NULL, NULL, "mimalloc: error: ", fmt, args);374}375376void _mi_warning_message(const char* fmt, ...) {377if (!mi_option_is_enabled(mi_option_verbose)) {378if (!mi_option_is_enabled(mi_option_show_errors)) return;379if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return;380}381va_list args;382va_start(args,fmt);383mi_vfprintf_thread(NULL, NULL, "mimalloc: warning: ", fmt, args);384va_end(args);385}386387388#if MI_DEBUG389void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) {390_mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);391abort();392}393#endif394395// --------------------------------------------------------396// Errors397// --------------------------------------------------------398399static mi_error_fun* volatile mi_error_handler; // = NULL400static _Atomic(void*) mi_error_arg; // = NULL401402static void mi_error_default(int err) {403MI_UNUSED(err);404#if (MI_DEBUG>0)405if (err==EFAULT) {406#ifdef _MSC_VER407__debugbreak();408#endif409abort();410}411#endif412#if (MI_SECURE>0)413if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)414abort();415}416#endif417#if defined(MI_XMALLOC)418if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode419abort();420}421#endif422}423424void mi_register_error(mi_error_fun* fun, void* arg) {425mi_error_handler = fun; // can be NULL426mi_atomic_store_ptr_release(void,&mi_error_arg, arg);427}428429void _mi_error_message(int err, const char* fmt, ...) {430// show detailed error message431va_list args;432va_start(args, fmt);433mi_show_error_message(fmt, args);434va_end(args);435// and call the error handler which may abort (or return normally)436if (mi_error_handler != NULL) {437mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));438}439else {440mi_error_default(err);441}442}443444// --------------------------------------------------------445// Initialize options by checking the environment446// --------------------------------------------------------447448// TODO: implement ourselves to reduce dependencies on the C runtime449#include <stdlib.h> // strtol450#include <string.h> // strstr451452453static void mi_option_init(mi_option_desc_t* desc) {454// Read option value from the environment455char s[64 + 1];456char buf[64+1];457_mi_strlcpy(buf, "mimalloc_", sizeof(buf));458_mi_strlcat(buf, desc->name, sizeof(buf));459bool found = _mi_getenv(buf, s, sizeof(s));460if (!found && desc->legacy_name != NULL) {461_mi_strlcpy(buf, "mimalloc_", sizeof(buf));462_mi_strlcat(buf, desc->legacy_name, sizeof(buf));463found = _mi_getenv(buf, s, sizeof(s));464if (found) {465_mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name);466}467}468469if (found) {470size_t len = _mi_strnlen(s, sizeof(buf) - 1);471for (size_t i = 0; i < len; i++) {472buf[i] = _mi_toupper(s[i]);473}474buf[len] = 0;475if (buf[0] == 0 || strstr("1;TRUE;YES;ON", buf) != NULL) {476desc->value = 1;477desc->init = INITIALIZED;478}479else if (strstr("0;FALSE;NO;OFF", buf) != NULL) {480desc->value = 0;481desc->init = INITIALIZED;482}483else {484char* end = buf;485long value = strtol(buf, &end, 10);486if (mi_option_has_size_in_kib(desc->option)) {487// this option is interpreted in KiB to prevent overflow of `long` for large allocations488// (long is 32-bit on 64-bit windows, which allows for 4TiB max.)489size_t size = (value < 0 ? 0 : (size_t)value);490bool overflow = false;491if (*end == 'K') { end++; }492else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }493else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }494else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }495else { size = (size + MI_KiB - 1) / MI_KiB; }496if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB497else if (*end == 'B') { end++; } // Kb, Mb, Gb, Tb498if (overflow || size > MI_MAX_ALLOC_SIZE) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }499value = (size > LONG_MAX ? LONG_MAX : (long)size);500}501if (*end == 0) {502desc->value = value;503desc->init = INITIALIZED;504}505else {506// set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.507desc->init = DEFAULTED;508if (desc->option == mi_option_verbose && desc->value == 0) {509// if the 'mimalloc_verbose' env var has a bogus value we'd never know510// (since the value defaults to 'off') so in that case briefly enable verbose511desc->value = 1;512_mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);513desc->value = 0;514}515else {516_mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);517}518}519}520mi_assert_internal(desc->init != UNINIT);521}522else if (!_mi_preloading()) {523desc->init = DEFAULTED;524}525}526527528