1/* ----------------------------------------------------------------------------
2Copyright (c) 2018-2021, Microsoft Research, Daan Leijen
3This is free software; you can redistribute it and/or modify it under the
4terms of the MIT license. A copy of the license can be found in the file
5"LICENSE" at the root of this distribution.
6-----------------------------------------------------------------------------*/
7#include "mimalloc.h"
8#include "mimalloc-internal.h"
9#include "mimalloc-atomic.h"
10
11#include <stdio.h>
12#include <stdlib.h> // strtol
13#include <string.h> // strncpy, strncat, strlen, strstr
14#include <ctype.h> // toupper
15#include <stdarg.h>
16
17#ifdef _MSC_VER
18#pragma warning(disable:4996) // strncpy, strncat
19#endif
20
21
22static long mi_max_error_count = 16; // stop outputting errors after this (use < 0 for no limit)
23static long mi_max_warning_count = 16; // stop outputting warnings after this (use < 0 for no limit)
24
25static void mi_add_stderr_output(void);
26
27int mi_version(void) mi_attr_noexcept {
28 return MI_MALLOC_VERSION;
29}
30
31#ifdef _WIN32
32#include <conio.h>
33#endif
34
35// --------------------------------------------------------
36// Options
37// These can be accessed by multiple threads and may be
38// concurrently initialized, but an initializing data race
39// is ok since they resolve to the same value.
40// --------------------------------------------------------
41typedef enum mi_init_e {
42 UNINIT, // not yet initialized
43 DEFAULTED, // not found in the environment, use default value
44 INITIALIZED // found in environment or set explicitly
45} mi_init_t;
46
47typedef struct mi_option_desc_s {
48 long value; // the value
49 mi_init_t init; // is it initialized yet? (from the environment)
50 mi_option_t option; // for debugging: the option index should match the option
51 const char* name; // option name without `mimalloc_` prefix
52 const char* legacy_name; // potential legacy v1.x option name
53} mi_option_desc_t;
54
55#define MI_OPTION(opt) mi_option_##opt, #opt, NULL
56#define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy
57
58static mi_option_desc_t options[_mi_option_last] =
59{
60 // stable options
61 #if MI_DEBUG || defined(MI_SHOW_ERRORS)
62 { 1, UNINIT, MI_OPTION(show_errors) },
63 #else
64 { 0, UNINIT, MI_OPTION(show_errors) },
65 #endif
66 { 0, UNINIT, MI_OPTION(show_stats) },
67 { 0, UNINIT, MI_OPTION(verbose) },
68
69 // Some of the following options are experimental and not all combinations are valid. Use with care.
70 { 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (8MiB) (but see also `eager_commit_delay`)
71 { 0, UNINIT, MI_OPTION(deprecated_eager_region_commit) },
72 { 0, UNINIT, MI_OPTION(deprecated_reset_decommits) },
73 { 0, UNINIT, MI_OPTION(large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's
74 { 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages
75 { -1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N
76 { 0, UNINIT, MI_OPTION(reserve_os_memory) },
77 { 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread
78 { 0, UNINIT, MI_OPTION(page_reset) }, // reset page memory on free
79 { 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_decommit, abandoned_page_reset) },// decommit free page memory when a thread terminates
80 { 0, UNINIT, MI_OPTION(deprecated_segment_reset) },
81 #if defined(__NetBSD__)
82 { 0, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed
83 #elif defined(_WIN32)
84 { 4, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed (but per page in the segment on demand)
85 #else
86 { 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)
87 #endif
88 { 25, UNINIT, MI_OPTION_LEGACY(decommit_delay, reset_delay) }, // page decommit delay in milli-seconds
89 { 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes.
90 { 0, UNINIT, MI_OPTION(limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas)
91 { 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose
92 { 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
93 { 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
94 { 8, UNINIT, MI_OPTION(max_segment_reclaim)},// max. number of segment reclaims from the abandoned segments per try.
95 { 1, UNINIT, MI_OPTION(allow_decommit) }, // decommit slices when no longer used (after decommit_delay milli-seconds)
96 { 500, UNINIT, MI_OPTION(segment_decommit_delay) }, // decommit delay in milli-seconds for freed segments
97 { 2, UNINIT, MI_OPTION(decommit_extend_delay) }
98};
99
100static void mi_option_init(mi_option_desc_t* desc);
101
102void _mi_options_init(void) {
103 // called on process load; should not be called before the CRT is initialized!
104 // (e.g. do not call this from process_init as that may run before CRT initialization)
105 mi_add_stderr_output(); // now it safe to use stderr for output
106 for(int i = 0; i < _mi_option_last; i++ ) {
107 mi_option_t option = (mi_option_t)i;
108 long l = mi_option_get(option); MI_UNUSED(l); // initialize
109 if (option != mi_option_verbose) {
110 mi_option_desc_t* desc = &options[option];
111 _mi_verbose_message("option '%s': %ld\n", desc->name, desc->value);
112 }
113 }
114 mi_max_error_count = mi_option_get(mi_option_max_errors);
115 mi_max_warning_count = mi_option_get(mi_option_max_warnings);
116}
117
118mi_decl_nodiscard long mi_option_get(mi_option_t option) {
119 mi_assert(option >= 0 && option < _mi_option_last);
120 if (option < 0 || option >= _mi_option_last) return 0;
121 mi_option_desc_t* desc = &options[option];
122 mi_assert(desc->option == option); // index should match the option
123 if mi_unlikely(desc->init == UNINIT) {
124 mi_option_init(desc);
125 }
126 return desc->value;
127}
128
129mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) {
130 long x = mi_option_get(option);
131 return (x < min ? min : (x > max ? max : x));
132}
133
134void mi_option_set(mi_option_t option, long value) {
135 mi_assert(option >= 0 && option < _mi_option_last);
136 if (option < 0 || option >= _mi_option_last) return;
137 mi_option_desc_t* desc = &options[option];
138 mi_assert(desc->option == option); // index should match the option
139 desc->value = value;
140 desc->init = INITIALIZED;
141}
142
143void mi_option_set_default(mi_option_t option, long value) {
144 mi_assert(option >= 0 && option < _mi_option_last);
145 if (option < 0 || option >= _mi_option_last) return;
146 mi_option_desc_t* desc = &options[option];
147 if (desc->init != INITIALIZED) {
148 desc->value = value;
149 }
150}
151
152mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) {
153 return (mi_option_get(option) != 0);
154}
155
156void mi_option_set_enabled(mi_option_t option, bool enable) {
157 mi_option_set(option, (enable ? 1 : 0));
158}
159
160void mi_option_set_enabled_default(mi_option_t option, bool enable) {
161 mi_option_set_default(option, (enable ? 1 : 0));
162}
163
164void mi_option_enable(mi_option_t option) {
165 mi_option_set_enabled(option,true);
166}
167
168void mi_option_disable(mi_option_t option) {
169 mi_option_set_enabled(option,false);
170}
171
172
173static void mi_cdecl mi_out_stderr(const char* msg, void* arg) {
174 MI_UNUSED(arg);
175 if (msg == NULL) return;
176 #ifdef _WIN32
177 // on windows with redirection, the C runtime cannot handle locale dependent output
178 // after the main thread closes so we use direct console output.
179 if (!_mi_preloading()) {
180 // _cputs(msg); // _cputs cannot be used at is aborts if it fails to lock the console
181 static HANDLE hcon = INVALID_HANDLE_VALUE;
182 if (hcon == INVALID_HANDLE_VALUE) {
183 hcon = GetStdHandle(STD_ERROR_HANDLE);
184 }
185 const size_t len = strlen(msg);
186 if (hcon != INVALID_HANDLE_VALUE && len > 0 && len < UINT32_MAX) {
187 DWORD written = 0;
188 WriteConsoleA(hcon, msg, (DWORD)len, &written, NULL);
189 }
190 }
191 #else
192 fputs(msg, stderr);
193 #endif
194}
195
196// Since an output function can be registered earliest in the `main`
197// function we also buffer output that happens earlier. When
198// an output function is registered it is called immediately with
199// the output up to that point.
200#ifndef MI_MAX_DELAY_OUTPUT
201#define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024))
202#endif
203static char out_buf[MI_MAX_DELAY_OUTPUT+1];
204static _Atomic(size_t) out_len;
205
206static void mi_cdecl mi_out_buf(const char* msg, void* arg) {
207 MI_UNUSED(arg);
208 if (msg==NULL) return;
209 if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return;
210 size_t n = strlen(msg);
211 if (n==0) return;
212 // claim space
213 size_t start = mi_atomic_add_acq_rel(&out_len, n);
214 if (start >= MI_MAX_DELAY_OUTPUT) return;
215 // check bound
216 if (start+n >= MI_MAX_DELAY_OUTPUT) {
217 n = MI_MAX_DELAY_OUTPUT-start-1;
218 }
219 _mi_memcpy(&out_buf[start], msg, n);
220}
221
222static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {
223 if (out==NULL) return;
224 // claim (if `no_more_buf == true`, no more output will be added after this point)
225 size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));
226 // and output the current contents
227 if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;
228 out_buf[count] = 0;
229 out(out_buf,arg);
230 if (!no_more_buf) {
231 out_buf[count] = '\n'; // if continue with the buffer, insert a newline
232 }
233}
234
235
236// Once this module is loaded, switch to this routine
237// which outputs to stderr and the delayed output buffer.
238static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {
239 mi_out_stderr(msg,arg);
240 mi_out_buf(msg,arg);
241}
242
243
244
245// --------------------------------------------------------
246// Default output handler
247// --------------------------------------------------------
248
249// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.
250// For now, don't register output from multiple threads.
251static mi_output_fun* volatile mi_out_default; // = NULL
252static _Atomic(void*) mi_out_arg; // = NULL
253
254static mi_output_fun* mi_out_get_default(void** parg) {
255 if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }
256 mi_output_fun* out = mi_out_default;
257 return (out == NULL ? &mi_out_buf : out);
258}
259
260void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {
261 mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer
262 mi_atomic_store_ptr_release(void,&mi_out_arg, arg);
263 if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now
264}
265
266// add stderr to the delayed output after the module is loaded
267static void mi_add_stderr_output() {
268 mi_assert_internal(mi_out_default == NULL);
269 mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
270 mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output
271}
272
273// --------------------------------------------------------
274// Messages, all end up calling `_mi_fputs`.
275// --------------------------------------------------------
276static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors
277static _Atomic(size_t) warning_count; // = 0; // when >= max_warning_count stop emitting warnings
278
279// When overriding malloc, we may recurse into mi_vfprintf if an allocation
280// inside the C runtime causes another message.
281// In some cases (like on macOS) the loader already allocates which
282// calls into mimalloc; if we then access thread locals (like `recurse`)
283// this may crash as the access may call _tlv_bootstrap that tries to
284// (recursively) invoke malloc again to allocate space for the thread local
285// variables on demand. This is why we use a _mi_preloading test on such
286// platforms. However, C code generator may move the initial thread local address
287// load before the `if` and we therefore split it out in a separate funcion.
288static mi_decl_thread bool recurse = false;
289
290static mi_decl_noinline bool mi_recurse_enter_prim(void) {
291 if (recurse) return false;
292 recurse = true;
293 return true;
294}
295
296static mi_decl_noinline void mi_recurse_exit_prim(void) {
297 recurse = false;
298}
299
300static bool mi_recurse_enter(void) {
301 #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
302 if (_mi_preloading()) return true;
303 #endif
304 return mi_recurse_enter_prim();
305}
306
307static void mi_recurse_exit(void) {
308 #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
309 if (_mi_preloading()) return;
310 #endif
311 mi_recurse_exit_prim();
312}
313
314void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {
315 if (out==NULL || (FILE*)out==stdout || (FILE*)out==stderr) { // TODO: use mi_out_stderr for stderr?
316 if (!mi_recurse_enter()) return;
317 out = mi_out_get_default(&arg);
318 if (prefix != NULL) out(prefix, arg);
319 out(message, arg);
320 mi_recurse_exit();
321 }
322 else {
323 if (prefix != NULL) out(prefix, arg);
324 out(message, arg);
325 }
326}
327
328// Define our own limited `fprintf` that avoids memory allocation.
329// We do this using `snprintf` with a limited buffer.
330static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {
331 char buf[512];
332 if (fmt==NULL) return;
333 if (!mi_recurse_enter()) return;
334 vsnprintf(buf,sizeof(buf)-1,fmt,args);
335 mi_recurse_exit();
336 _mi_fputs(out,arg,prefix,buf);
337}
338
339void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {
340 va_list args;
341 va_start(args,fmt);
342 mi_vfprintf(out,arg,NULL,fmt,args);
343 va_end(args);
344}
345
346static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {
347 if (prefix != NULL && strlen(prefix) <= 32 && !_mi_is_main_thread()) {
348 char tprefix[64];
349 snprintf(tprefix, sizeof(tprefix), "%sthread 0x%zx: ", prefix, _mi_thread_id());
350 mi_vfprintf(out, arg, tprefix, fmt, args);
351 }
352 else {
353 mi_vfprintf(out, arg, prefix, fmt, args);
354 }
355}
356
357void _mi_trace_message(const char* fmt, ...) {
358 if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher
359 va_list args;
360 va_start(args, fmt);
361 mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
362 va_end(args);
363}
364
365void _mi_verbose_message(const char* fmt, ...) {
366 if (!mi_option_is_enabled(mi_option_verbose)) return;
367 va_list args;
368 va_start(args,fmt);
369 mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args);
370 va_end(args);
371}
372
373static void mi_show_error_message(const char* fmt, va_list args) {
374 if (!mi_option_is_enabled(mi_option_verbose)) {
375 if (!mi_option_is_enabled(mi_option_show_errors)) return;
376 if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return;
377 }
378 mi_vfprintf_thread(NULL, NULL, "mimalloc: error: ", fmt, args);
379}
380
381void _mi_warning_message(const char* fmt, ...) {
382 if (!mi_option_is_enabled(mi_option_verbose)) {
383 if (!mi_option_is_enabled(mi_option_show_errors)) return;
384 if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return;
385 }
386 va_list args;
387 va_start(args,fmt);
388 mi_vfprintf_thread(NULL, NULL, "mimalloc: warning: ", fmt, args);
389 va_end(args);
390}
391
392
393#if MI_DEBUG
394void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) {
395 _mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);
396 abort();
397}
398#endif
399
400// --------------------------------------------------------
401// Errors
402// --------------------------------------------------------
403
404static mi_error_fun* volatile mi_error_handler; // = NULL
405static _Atomic(void*) mi_error_arg; // = NULL
406
407static void mi_error_default(int err) {
408 MI_UNUSED(err);
409#if (MI_DEBUG>0)
410 if (err==EFAULT) {
411 #ifdef _MSC_VER
412 __debugbreak();
413 #endif
414 abort();
415 }
416#endif
417#if (MI_SECURE>0)
418 if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
419 abort();
420 }
421#endif
422#if defined(MI_XMALLOC)
423 if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode
424 abort();
425 }
426#endif
427}
428
429void mi_register_error(mi_error_fun* fun, void* arg) {
430 mi_error_handler = fun; // can be NULL
431 mi_atomic_store_ptr_release(void,&mi_error_arg, arg);
432}
433
434void _mi_error_message(int err, const char* fmt, ...) {
435 // show detailed error message
436 va_list args;
437 va_start(args, fmt);
438 mi_show_error_message(fmt, args);
439 va_end(args);
440 // and call the error handler which may abort (or return normally)
441 if (mi_error_handler != NULL) {
442 mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));
443 }
444 else {
445 mi_error_default(err);
446 }
447}
448
449// --------------------------------------------------------
450// Initialize options by checking the environment
451// --------------------------------------------------------
452
453static void mi_strlcpy(char* dest, const char* src, size_t dest_size) {
454 if (dest==NULL || src==NULL || dest_size == 0) return;
455 // copy until end of src, or when dest is (almost) full
456 while (*src != 0 && dest_size > 1) {
457 *dest++ = *src++;
458 dest_size--;
459 }
460 // always zero terminate
461 *dest = 0;
462}
463
464static void mi_strlcat(char* dest, const char* src, size_t dest_size) {
465 if (dest==NULL || src==NULL || dest_size == 0) return;
466 // find end of string in the dest buffer
467 while (*dest != 0 && dest_size > 1) {
468 dest++;
469 dest_size--;
470 }
471 // and catenate
472 mi_strlcpy(dest, src, dest_size);
473}
474
475#ifdef MI_NO_GETENV
476static bool mi_getenv(const char* name, char* result, size_t result_size) {
477 MI_UNUSED(name);
478 MI_UNUSED(result);
479 MI_UNUSED(result_size);
480 return false;
481}
482#else
483static inline int mi_strnicmp(const char* s, const char* t, size_t n) {
484 if (n==0) return 0;
485 for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) {
486 if (toupper(*s) != toupper(*t)) break;
487 }
488 return (n==0 ? 0 : *s - *t);
489}
490#if defined _WIN32
491// On Windows use GetEnvironmentVariable instead of getenv to work
492// reliably even when this is invoked before the C runtime is initialized.
493// i.e. when `_mi_preloading() == true`.
494// Note: on windows, environment names are not case sensitive.
495#include <windows.h>
496static bool mi_getenv(const char* name, char* result, size_t result_size) {
497 result[0] = 0;
498 size_t len = GetEnvironmentVariableA(name, result, (DWORD)result_size);
499 return (len > 0 && len < result_size);
500}
501#elif !defined(MI_USE_ENVIRON) || (MI_USE_ENVIRON!=0)
502// On Posix systemsr use `environ` to acces environment variables
503// even before the C runtime is initialized.
504#if defined(__APPLE__) && defined(__has_include) && __has_include(<crt_externs.h>)
505#include <crt_externs.h>
506static char** mi_get_environ(void) {
507 return (*_NSGetEnviron());
508}
509#else
510extern char** environ;
511static char** mi_get_environ(void) {
512 return environ;
513}
514#endif
515static bool mi_getenv(const char* name, char* result, size_t result_size) {
516 if (name==NULL) return false;
517 const size_t len = strlen(name);
518 if (len == 0) return false;
519 char** env = mi_get_environ();
520 if (env == NULL) return false;
521 // compare up to 256 entries
522 for (int i = 0; i < 256 && env[i] != NULL; i++) {
523 const char* s = env[i];
524 if (mi_strnicmp(name, s, len) == 0 && s[len] == '=') { // case insensitive
525 // found it
526 mi_strlcpy(result, s + len + 1, result_size);
527 return true;
528 }
529 }
530 return false;
531}
532#else
533// fallback: use standard C `getenv` but this cannot be used while initializing the C runtime
534static bool mi_getenv(const char* name, char* result, size_t result_size) {
535 // cannot call getenv() when still initializing the C runtime.
536 if (_mi_preloading()) return false;
537 const char* s = getenv(name);
538 if (s == NULL) {
539 // we check the upper case name too.
540 char buf[64+1];
541 size_t len = strlen(name);
542 if (len >= sizeof(buf)) len = sizeof(buf) - 1;
543 for (size_t i = 0; i < len; i++) {
544 buf[i] = toupper(name[i]);
545 }
546 buf[len] = 0;
547 s = getenv(buf);
548 }
549 if (s != NULL && strlen(s) < result_size) {
550 mi_strlcpy(result, s, result_size);
551 return true;
552 }
553 else {
554 return false;
555 }
556}
557#endif // !MI_USE_ENVIRON
558#endif // !MI_NO_GETENV
559
560static void mi_option_init(mi_option_desc_t* desc) {
561 // Read option value from the environment
562 char s[64+1];
563 char buf[64+1];
564 mi_strlcpy(buf, "mimalloc_", sizeof(buf));
565 mi_strlcat(buf, desc->name, sizeof(buf));
566 bool found = mi_getenv(buf,s,sizeof(s));
567 if (!found && desc->legacy_name != NULL) {
568 mi_strlcpy(buf, "mimalloc_", sizeof(buf));
569 mi_strlcat(buf, desc->legacy_name, sizeof(buf));
570 found = mi_getenv(buf,s,sizeof(s));
571 if (found) {
572 _mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name );
573 }
574 }
575
576 if (found) {
577 size_t len = strlen(s);
578 if (len >= sizeof(buf)) len = sizeof(buf) - 1;
579 for (size_t i = 0; i < len; i++) {
580 buf[i] = (char)toupper(s[i]);
581 }
582 buf[len] = 0;
583 if (buf[0]==0 || strstr("1;TRUE;YES;ON", buf) != NULL) {
584 desc->value = 1;
585 desc->init = INITIALIZED;
586 }
587 else if (strstr("0;FALSE;NO;OFF", buf) != NULL) {
588 desc->value = 0;
589 desc->init = INITIALIZED;
590 }
591 else {
592 char* end = buf;
593 long value = strtol(buf, &end, 10);
594 if (desc->option == mi_option_reserve_os_memory) {
595 // this option is interpreted in KiB to prevent overflow of `long`
596 if (*end == 'K') { end++; }
597 else if (*end == 'M') { value *= MI_KiB; end++; }
598 else if (*end == 'G') { value *= MI_MiB; end++; }
599 else { value = (value + MI_KiB - 1) / MI_KiB; }
600 if (end[0] == 'I' && end[1] == 'B') { end += 2; }
601 else if (*end == 'B') { end++; }
602 }
603 if (*end == 0) {
604 desc->value = value;
605 desc->init = INITIALIZED;
606 }
607 else {
608 // set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.
609 desc->init = DEFAULTED;
610 if (desc->option == mi_option_verbose && desc->value == 0) {
611 // if the 'mimalloc_verbose' env var has a bogus value we'd never know
612 // (since the value defaults to 'off') so in that case briefly enable verbose
613 desc->value = 1;
614 _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name );
615 desc->value = 0;
616 }
617 else {
618 _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name );
619 }
620 }
621 }
622 mi_assert_internal(desc->init != UNINIT);
623 }
624 else if (!_mi_preloading()) {
625 desc->init = DEFAULTED;
626 }
627}
628