Coverage Report

Created: 2025-07-04 06:49

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