Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/mimalloc/options.c
Line
Count
Source
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>      // stdin/stdout
13
#include <stdlib.h>     // abort
14
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
// Some options can be set at build time for statically linked libraries
51
// (use `-DMI_EXTRA_CPPDEFS="opt1=val1;opt2=val2"`)
52
//
53
// This is useful if we cannot pass them as environment variables
54
// (and setting them programmatically would be too late)
55
56
#ifndef MI_DEFAULT_VERBOSE
57
#define MI_DEFAULT_VERBOSE 0
58
#endif
59
60
#ifndef MI_DEFAULT_EAGER_COMMIT
61
#define MI_DEFAULT_EAGER_COMMIT 1
62
#endif
63
64
#ifndef MI_DEFAULT_ARENA_EAGER_COMMIT
65
#define MI_DEFAULT_ARENA_EAGER_COMMIT 2
66
#endif
67
68
// in KiB
69
#ifndef MI_DEFAULT_ARENA_RESERVE
70
 #if (MI_INTPTR_SIZE>4)
71
  #define MI_DEFAULT_ARENA_RESERVE 1024L*1024L
72
 #else
73
  #define MI_DEFAULT_ARENA_RESERVE 128L*1024L
74
 #endif
75
#endif
76
77
#ifndef MI_DEFAULT_DISALLOW_ARENA_ALLOC
78
#define MI_DEFAULT_DISALLOW_ARENA_ALLOC 0
79
#endif
80
81
#ifndef MI_DEFAULT_ALLOW_LARGE_OS_PAGES
82
#define MI_DEFAULT_ALLOW_LARGE_OS_PAGES 0
83
#endif
84
85
#ifndef MI_DEFAULT_RESERVE_HUGE_OS_PAGES
86
#define MI_DEFAULT_RESERVE_HUGE_OS_PAGES 0
87
#endif
88
89
#ifndef MI_DEFAULT_RESERVE_OS_MEMORY
90
#define MI_DEFAULT_RESERVE_OS_MEMORY 0
91
#endif
92
93
#ifndef MI_DEFAULT_GUARDED_SAMPLE_RATE
94
#if MI_GUARDED && !MI_DEBUG
95
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 4000
96
#else
97
#define MI_DEFAULT_GUARDED_SAMPLE_RATE 0
98
#endif
99
#endif
100
101
102
#ifndef MI_DEFAULT_ALLOW_THP
103
#if defined(__ANDROID__)
104
#define MI_DEFAULT_ALLOW_THP  0
105
#else
106
#define MI_DEFAULT_ALLOW_THP  1
107
#endif
108
#endif
109
110
// Static options
111
static mi_option_desc_t options[_mi_option_last] =
112
{
113
  // stable options
114
  #if MI_DEBUG || defined(MI_SHOW_ERRORS)
115
  { 1, UNINIT, MI_OPTION(show_errors) },
116
  #else
117
  { 0, UNINIT, MI_OPTION(show_errors) },
118
  #endif
119
  { 0, UNINIT, MI_OPTION(show_stats) },
120
  { MI_DEFAULT_VERBOSE, UNINIT, MI_OPTION(verbose) },
121
122
  // some of the following options are experimental and not all combinations are allowed.
123
  { MI_DEFAULT_EAGER_COMMIT,
124
       UNINIT, MI_OPTION(eager_commit) },               // commit per segment directly (4MiB)  (but see also `eager_commit_delay`)
125
  { MI_DEFAULT_ARENA_EAGER_COMMIT,
126
       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)
127
  { 1, UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) },        // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit)
128
  { MI_DEFAULT_ALLOW_LARGE_OS_PAGES,
129
       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
130
  { MI_DEFAULT_RESERVE_HUGE_OS_PAGES,
131
       UNINIT, MI_OPTION(reserve_huge_os_pages) },      // per 1GiB huge pages
132
  {-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) },   // reserve huge pages at node N
133
  { MI_DEFAULT_RESERVE_OS_MEMORY,
134
       UNINIT, MI_OPTION(reserve_os_memory)     },      // reserve N KiB OS memory in advance (use `option_get_size`)
135
  { 0, UNINIT, MI_OPTION(deprecated_segment_cache) },   // cache N segments per thread
136
  { 0, UNINIT, MI_OPTION(deprecated_page_reset) },      // reset page memory on free
137
  { 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_purge,abandoned_page_reset) },       // reset free page memory when a thread terminates
138
  { 0, UNINIT, MI_OPTION(deprecated_segment_reset) },   // reset segment memory on free (needs eager commit)
139
#if defined(__NetBSD__)
140
  { 0, UNINIT, MI_OPTION(eager_commit_delay) },         // the first N segments per thread are not eagerly committed
141
#else
142
  { 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)
143
#endif
144
  { 10,  UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) },  // purge delay in milli-seconds
145
  { 0,   UNINIT, MI_OPTION(use_numa_nodes) },           // 0 = use available numa nodes, otherwise use at most N nodes.
146
  { 0,   UNINIT, MI_OPTION_LEGACY(disallow_os_alloc,limit_os_alloc) },           // 1 = do not use OS memory for allocation (but only reserved arenas)
147
  { 100, UNINIT, MI_OPTION(os_tag) },                   // only apple specific for now but might serve more or less related purpose
148
  { 32,  UNINIT, MI_OPTION(max_errors) },               // maximum errors that are output
149
  { 32,  UNINIT, MI_OPTION(max_warnings) },             // maximum warnings that are output
150
  { 10,  UNINIT, MI_OPTION(max_segment_reclaim)},       // max. percentage of the abandoned segments to be reclaimed per try.
151
  { 0,   UNINIT, MI_OPTION(destroy_on_exit)},           // release all OS memory on process exit; careful with dangling pointer or after-exit frees!
152
  { MI_DEFAULT_ARENA_RESERVE, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time (=1GiB) (use `option_get_size`)
153
  { 10,  UNINIT, MI_OPTION(arena_purge_mult) },         // purge delay multiplier for arena's
154
  { 1,   UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) },
155
  { 0,   UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free
156
  { MI_DEFAULT_DISALLOW_ARENA_ALLOC,   UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
157
  { 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.
158
#if defined(MI_VISIT_ABANDONED)
159
  { 1,   INITIALIZED, MI_OPTION(visit_abandoned) },     // allow visiting heap blocks in abandoned segments; requires taking locks during reclaim.
160
#else
161
  { 0,   UNINIT, MI_OPTION(visit_abandoned) },
162
#endif
163
  { 0,   UNINIT, MI_OPTION(guarded_min) },              // only used when building with MI_GUARDED: minimal rounded object size for guarded objects
164
  { MI_GiB, UNINIT, MI_OPTION(guarded_max) },           // only used when building with MI_GUARDED: maximal rounded object size for guarded objects
165
  { 0,   UNINIT, MI_OPTION(guarded_precise) },          // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0)
166
  { MI_DEFAULT_GUARDED_SAMPLE_RATE,
167
         UNINIT, MI_OPTION(guarded_sample_rate)},       // 1 out of N allocations in the min/max range will be guarded (=4000)
168
  { 0,   UNINIT, MI_OPTION(guarded_sample_seed)},
169
  { 0,   UNINIT, MI_OPTION(target_segments_per_thread) }, // abandon segments beyond this point, or 0 to disable.
170
  { 10000, UNINIT, MI_OPTION(generic_collect) },          // collect heaps every N (=10000) generic allocation calls
171
  { MI_DEFAULT_ALLOW_THP,
172
         UNINIT, MI_OPTION(allow_thp) }                 // allow transparent huge pages?
173
};
174
175
static void mi_option_init(mi_option_desc_t* desc);
176
177
0
static bool mi_option_has_size_in_kib(mi_option_t option) {
178
0
  return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);
179
0
}
180
181
37
void _mi_options_init(void) {
182
  // called on process load
183
37
  mi_add_stderr_output(); // now it safe to use stderr for output
184
1.44k
  for(int i = 0; i < _mi_option_last; i++ ) {
185
1.40k
    mi_option_t option = (mi_option_t)i;
186
1.40k
    long l = mi_option_get(option); MI_UNUSED(l); // initialize
187
1.40k
  }
188
37
  mi_max_error_count = mi_option_get(mi_option_max_errors);
189
37
  mi_max_warning_count = mi_option_get(mi_option_max_warnings);
190
  #if MI_GUARDED
191
  if (mi_option_get(mi_option_guarded_sample_rate) > 0) {
192
    if (mi_option_is_enabled(mi_option_allow_large_os_pages)) {
193
      mi_option_disable(mi_option_allow_large_os_pages);
194
      _mi_warning_message("option 'allow_large_os_pages' is disabled to allow for guarded objects\n");
195
    }
196
  }
197
  #endif
198
37
  if (mi_option_is_enabled(mi_option_verbose)) { mi_options_print(); }
199
37
}
200
201
#define mi_stringifyx(str)  #str                // and stringify
202
#define mi_stringify(str)   mi_stringifyx(str)  // expand
203
204
void mi_options_print(void) mi_attr_noexcept
205
0
{
206
  // show version
207
0
  const int vermajor = MI_MALLOC_VERSION/10000;
208
0
  const int verminor = (MI_MALLOC_VERSION%10000)/100;
209
0
  const int verpatch = (MI_MALLOC_VERSION%100);
210
0
  _mi_message("v%i.%i.%i%s%s\n", vermajor, verminor, verpatch,
211
      #if defined(MI_CMAKE_BUILD_TYPE)
212
      ", " mi_stringify(MI_CMAKE_BUILD_TYPE)
213
      #else
214
0
      ""
215
0
      #endif
216
0
      ,
217
      #if defined(MI_GIT_DESCRIBE)
218
      ", git " mi_stringify(MI_GIT_DESCRIBE)
219
      #else
220
0
      ""
221
0
      #endif
222
0
      );
223
224
  // show options
225
0
  for (int i = 0; i < _mi_option_last; i++) {
226
0
    mi_option_t option = (mi_option_t)i;
227
0
    long l = mi_option_get(option); MI_UNUSED(l); // possibly initialize
228
0
    mi_option_desc_t* desc = &options[option];
229
0
    _mi_message("option '%s': %ld %s\n", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? "KiB" : ""));
230
0
  }
231
232
  // show build configuration
233
0
  _mi_message("debug level : %d\n", MI_DEBUG );
234
0
  _mi_message("secure level: %d\n", MI_SECURE );
235
0
  _mi_message("mem tracking: %s\n", MI_TRACK_TOOL);
236
  #if MI_GUARDED
237
  _mi_message("guarded build: %s\n", mi_option_get(mi_option_guarded_sample_rate) != 0 ? "enabled" : "disabled");
238
  #endif
239
  #if MI_TSAN
240
  _mi_message("thread santizer enabled\n");
241
  #endif
242
0
}
243
244
0
long _mi_option_get_fast(mi_option_t option) {
245
0
  mi_assert(option >= 0 && option < _mi_option_last);
246
0
  mi_option_desc_t* desc = &options[option];
247
0
  mi_assert(desc->option == option);  // index should match the option
248
  //mi_assert(desc->init != UNINIT);
249
0
  return desc->value;
250
0
}
251
252
253
1.66k
mi_decl_nodiscard long mi_option_get(mi_option_t option) {
254
1.66k
  mi_assert(option >= 0 && option < _mi_option_last);
255
1.66k
  if (option < 0 || option >= _mi_option_last) return 0;
256
1.66k
  mi_option_desc_t* desc = &options[option];
257
1.66k
  mi_assert(desc->option == option);  // index should match the option
258
1.66k
  if mi_unlikely(desc->init == UNINIT) {
259
1.40k
    mi_option_init(desc);
260
1.40k
  }
261
1.66k
  return desc->value;
262
1.66k
}
263
264
0
mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) {
265
0
  long x = mi_option_get(option);
266
0
  return (x < min ? min : (x > max ? max : x));
267
0
}
268
269
0
mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {
270
0
  const long x = mi_option_get(option);
271
0
  size_t size = (x < 0 ? 0 : (size_t)x);
272
0
  if (mi_option_has_size_in_kib(option)) {
273
0
    if (mi_mul_overflow(size, MI_KiB, &size)) {
274
0
      size = MI_MAX_ALLOC_SIZE;
275
0
    }
276
0
  }
277
0
  return size;
278
0
}
279
280
0
void mi_option_set(mi_option_t option, long value) {
281
0
  mi_assert(option >= 0 && option < _mi_option_last);
282
0
  if (option < 0 || option >= _mi_option_last) return;
283
0
  mi_option_desc_t* desc = &options[option];
284
0
  mi_assert(desc->option == option);  // index should match the option
285
0
  desc->value = value;
286
0
  desc->init = INITIALIZED;
287
  // ensure min/max range; be careful to not recurse.
288
0
  if (desc->option == mi_option_guarded_min && _mi_option_get_fast(mi_option_guarded_max) < value) {
289
0
    mi_option_set(mi_option_guarded_max, value);
290
0
  }
291
0
  else if (desc->option == mi_option_guarded_max && _mi_option_get_fast(mi_option_guarded_min) > value) {
292
0
    mi_option_set(mi_option_guarded_min, value);
293
0
  }
294
0
}
295
296
0
void mi_option_set_default(mi_option_t option, long value) {
297
0
  mi_assert(option >= 0 && option < _mi_option_last);
298
0
  if (option < 0 || option >= _mi_option_last) return;
299
0
  mi_option_desc_t* desc = &options[option];
300
0
  if (desc->init != INITIALIZED) {
301
0
    desc->value = value;
302
0
  }
303
0
}
304
305
185
mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) {
306
185
  return (mi_option_get(option) != 0);
307
185
}
308
309
0
void mi_option_set_enabled(mi_option_t option, bool enable) {
310
0
  mi_option_set(option, (enable ? 1 : 0));
311
0
}
312
313
0
void mi_option_set_enabled_default(mi_option_t option, bool enable) {
314
0
  mi_option_set_default(option, (enable ? 1 : 0));
315
0
}
316
317
0
void mi_option_enable(mi_option_t option) {
318
0
  mi_option_set_enabled(option,true);
319
0
}
320
321
0
void mi_option_disable(mi_option_t option) {
322
0
  mi_option_set_enabled(option,false);
323
0
}
324
325
37
static void mi_cdecl mi_out_stderr(const char* msg, void* arg) {
326
37
  MI_UNUSED(arg);
327
37
  if (msg != NULL && msg[0] != 0) {
328
0
    _mi_prim_out_stderr(msg);
329
0
  }
330
37
}
331
332
// Since an output function can be registered earliest in the `main`
333
// function we also buffer output that happens earlier. When
334
// an output function is registered it is called immediately with
335
// the output up to that point.
336
#ifndef MI_MAX_DELAY_OUTPUT
337
37
#define MI_MAX_DELAY_OUTPUT ((size_t)(16*1024))
338
#endif
339
static char out_buf[MI_MAX_DELAY_OUTPUT+1];
340
static _Atomic(size_t) out_len;
341
static mi_lock_t out_buf_lock = MI_LOCK_INITIALIZER;
342
343
0
static void mi_cdecl mi_out_buf(const char* msg, void* arg) {
344
0
  MI_UNUSED(arg);
345
0
  if (msg==NULL) return;
346
0
  if (mi_atomic_load_acquire(&out_len)>=MI_MAX_DELAY_OUTPUT) return;
347
0
  size_t n = _mi_strlen(msg);
348
0
  if (n==0 || n >= MI_MAX_DELAY_OUTPUT) return;
349
  // copy msg into the buffer
350
0
  mi_lock(&out_buf_lock) {
351
0
    const size_t start = mi_atomic_add_acq_rel(&out_len, n);
352
0
    if (start < MI_MAX_DELAY_OUTPUT) {
353
      // check bound
354
0
      if (start+n >= MI_MAX_DELAY_OUTPUT) {
355
0
        n = MI_MAX_DELAY_OUTPUT-start-1;
356
0
      }
357
0
      _mi_memcpy(&out_buf[start], msg, n);
358
0
    }
359
0
  }
360
0
}
361
362
37
static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {
363
37
  if (out==NULL) return;
364
  // claim (if `no_more_buf == true`, no more output will be added after this point)
365
37
  mi_lock(&out_buf_lock) {
366
37
    size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));
367
    // and output the current contents
368
37
    if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;
369
37
    out_buf[count] = 0;
370
37
    out(out_buf,arg);
371
37
    if (!no_more_buf) {
372
37
      out_buf[count] = '\n'; // if continue with the buffer, insert a newline
373
37
    }
374
37
  }
375
37
}
376
377
378
// Once this module is loaded, switch to this routine
379
// which outputs to stderr and the delayed output buffer.
380
0
static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {
381
0
  mi_out_stderr(msg,arg);
382
0
  mi_out_buf(msg,arg);
383
0
}
384
385
386
387
// --------------------------------------------------------
388
// Default output handler
389
// --------------------------------------------------------
390
391
// The program should only install a single output handler from a single thread
392
// since otherwise the argument and output function may not match.
393
static _Atomic(void*) mi_out_default; // = // is `mi_output_fun*` (but some platforms don't support atomic function pointers)
394
static _Atomic(void*) mi_out_arg; // = NULL
395
396
0
static mi_output_fun* mi_out_get_default(void** parg) {
397
0
  mi_output_fun* const out = (mi_output_fun*)mi_atomic_load_ptr_acquire(void,&mi_out_default);
398
0
  if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }
399
0
  return (out == NULL ? &mi_out_buf : out);
400
0
}
401
402
0
void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {
403
0
  mi_atomic_store_ptr_release(void,&mi_out_default, (void*)(out == NULL ? &mi_out_stderr : out)); // stop using the delayed output buffer
404
0
  mi_atomic_store_ptr_release(void,&mi_out_arg, arg);
405
0
  if (out!=NULL) { mi_out_buf_flush(out,true,arg); }        // output all the delayed output now
406
0
}
407
408
// add stderr to the delayed output after the module is loaded
409
37
static void mi_add_stderr_output(void) {
410
37
  mi_assert_internal(mi_out_default == NULL);
411
37
  mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
412
37
  mi_atomic_store_ptr_release(void,&mi_out_default,(void*)&mi_out_buf_stderr);  // and add stderr to the delayed output
413
37
  mi_atomic_store_ptr_release(void,&mi_out_arg,NULL);
414
37
}
415
416
// --------------------------------------------------------
417
// Messages, all end up calling `_mi_fputs`.
418
// --------------------------------------------------------
419
static _Atomic(size_t) error_count;   // = 0;  // when >= max_error_count stop emitting errors
420
static _Atomic(size_t) warning_count; // = 0;  // when >= max_warning_count stop emitting warnings
421
422
// When overriding malloc, we may recurse into mi_vfprintf if an allocation
423
// inside the C runtime causes another message.
424
// In some cases (like on macOS) the loader already allocates which
425
// calls into mimalloc; if we then access thread locals (like `recurse`)
426
// this may crash as the access may call _tlv_bootstrap that tries to
427
// (recursively) invoke malloc again to allocate space for the thread local
428
// variables on demand. This is why we use a _mi_preloading test on such
429
// platforms. However, C code generator may move the initial thread local address
430
// load before the `if` and we therefore split it out in a separate function.
431
static mi_decl_thread bool recurse = false;
432
433
0
static mi_decl_noinline bool mi_recurse_enter_prim(void) {
434
0
  if (recurse) return false;
435
0
  recurse = true;
436
0
  return true;
437
0
}
438
439
0
static mi_decl_noinline void mi_recurse_exit_prim(void) {
440
0
  recurse = false;
441
0
}
442
443
0
static bool mi_recurse_enter(void) {
444
  #if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)
445
  if (_mi_preloading()) return false;
446
  #endif
447
0
  return mi_recurse_enter_prim();
448
0
}
449
450
0
static void mi_recurse_exit(void) {
451
  #if defined(__APPLE__) || defined(__ANDROID__) || defined(MI_TLS_RECURSE_GUARD)
452
  if (_mi_preloading()) return;
453
  #endif
454
0
  mi_recurse_exit_prim();
455
0
}
456
457
0
void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {
458
0
  if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr?
459
0
    if (!mi_recurse_enter()) return;
460
0
    out = mi_out_get_default(&arg);
461
0
    if (prefix != NULL) out(prefix, arg);
462
0
    out(message, arg);
463
0
    mi_recurse_exit();
464
0
  }
465
0
  else {
466
0
    if (prefix != NULL) out(prefix, arg);
467
0
    out(message, arg);
468
0
  }
469
0
}
470
471
// Define our own limited `fprintf` that avoids memory allocation.
472
// We do this using `_mi_vsnprintf` with a limited buffer.
473
0
static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {
474
0
  char buf[512];
475
0
  if (fmt==NULL) return;
476
0
  if (!mi_recurse_enter()) return;
477
0
  _mi_vsnprintf(buf, sizeof(buf)-1, fmt, args);
478
0
  mi_recurse_exit();
479
0
  _mi_fputs(out,arg,prefix,buf);
480
0
}
481
482
0
void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {
483
0
  va_list args;
484
0
  va_start(args,fmt);
485
0
  mi_vfprintf(out,arg,NULL,fmt,args);
486
0
  va_end(args);
487
0
}
488
489
0
static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {
490
0
  if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {
491
0
    char tprefix[64];
492
0
    _mi_snprintf(tprefix, sizeof(tprefix), "%sthread 0x%tx: ", prefix, (uintptr_t)_mi_thread_id());
493
0
    mi_vfprintf(out, arg, tprefix, fmt, args);
494
0
  }
495
0
  else {
496
0
    mi_vfprintf(out, arg, prefix, fmt, args);
497
0
  }
498
0
}
499
500
0
void _mi_message(const char* fmt, ...) {
501
0
  va_list args;
502
0
  va_start(args, fmt);
503
0
  mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
504
0
  va_end(args);
505
0
}
506
507
0
void _mi_trace_message(const char* fmt, ...) {
508
0
  if (mi_option_get(mi_option_verbose) <= 1) return;  // only with verbose level 2 or higher
509
0
  va_list args;
510
0
  va_start(args, fmt);
511
0
  mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
512
0
  va_end(args);
513
0
}
514
515
37
void _mi_verbose_message(const char* fmt, ...) {
516
37
  if (!mi_option_is_enabled(mi_option_verbose)) return;
517
0
  va_list args;
518
0
  va_start(args,fmt);
519
0
  mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args);
520
0
  va_end(args);
521
0
}
522
523
0
static void mi_show_error_message(const char* fmt, va_list args) {
524
0
  if (!mi_option_is_enabled(mi_option_verbose)) {
525
0
    if (!mi_option_is_enabled(mi_option_show_errors)) return;
526
0
    if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return;
527
0
  }
528
0
  mi_vfprintf_thread(NULL, NULL, "mimalloc: error: ", fmt, args);
529
0
}
530
531
0
void _mi_warning_message(const char* fmt, ...) {
532
0
  if (!mi_option_is_enabled(mi_option_verbose)) {
533
0
    if (!mi_option_is_enabled(mi_option_show_errors)) return;
534
0
    if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return;
535
0
  }
536
0
  va_list args;
537
0
  va_start(args,fmt);
538
0
  mi_vfprintf_thread(NULL, NULL, "mimalloc: warning: ", fmt, args);
539
0
  va_end(args);
540
0
}
541
542
543
#if MI_DEBUG
544
mi_decl_noreturn mi_decl_cold void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) mi_attr_noexcept {
545
  _mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n  assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);
546
  abort();
547
}
548
#endif
549
550
// --------------------------------------------------------
551
// Errors
552
// --------------------------------------------------------
553
554
static mi_error_fun* volatile  mi_error_handler; // = NULL
555
static _Atomic(void*) mi_error_arg;     // = NULL
556
557
0
static void mi_error_default(int err) {
558
0
  MI_UNUSED(err);
559
  #if (MI_DEBUG>0)
560
    if (err==EFAULT) {
561
      #ifdef _MSC_VER
562
      __debugbreak();
563
      #endif
564
      abort();
565
    }
566
  #endif
567
  #if (MI_SECURE>0)
568
    if (err==EFAULT) {  // abort on serious errors in secure mode (corrupted meta-data)
569
      abort();
570
    }
571
  #endif
572
  #if defined(MI_XMALLOC)
573
    if (err==ENOMEM || err==EOVERFLOW || err==EINVAL) { // abort on memory allocation fails in xmalloc mode
574
      abort();
575
    }
576
  #endif
577
0
  if (errno==0) {
578
0
    errno = (err==EINVAL ? EINVAL : ENOMEM /* compatibility */ );
579
0
  }
580
0
}
581
582
0
void mi_register_error(mi_error_fun* fun, void* arg) {
583
0
  mi_error_handler = fun;  // can be NULL
584
0
  mi_atomic_store_ptr_release(void,&mi_error_arg, arg);
585
0
}
586
587
0
void _mi_error_message(int err, const char* fmt, ...) {
588
  // show detailed error message
589
0
  va_list args;
590
0
  va_start(args, fmt);
591
0
  mi_show_error_message(fmt, args);
592
0
  va_end(args);
593
  // and call the error handler which may abort (or return normally, potentially setting errno)
594
0
  if (mi_error_handler != NULL) {
595
0
    mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));
596
0
  }
597
0
  else {
598
0
    mi_error_default(err);
599
0
  }
600
0
}
601
602
// --------------------------------------------------------
603
// Initialize options by checking the environment
604
// --------------------------------------------------------
605
606
// TODO: implement ourselves to reduce dependencies on the C runtime
607
#include <stdlib.h> // strtol
608
#include <string.h> // strstr
609
610
611
1.40k
static void mi_option_init(mi_option_desc_t* desc) {
612
  // Read option value from the environment
613
1.40k
  char s[64 + 1];
614
1.40k
  char buf[64+1];
615
1.40k
  _mi_strlcpy(buf, "mimalloc_", sizeof(buf));
616
1.40k
  _mi_strlcat(buf, desc->name, sizeof(buf));
617
1.40k
  int err = _mi_getenv(buf, s, sizeof(s));
618
1.40k
  if (err==ENOENT && desc->legacy_name != NULL) {
619
259
    _mi_strlcpy(buf, "mimalloc_", sizeof(buf));
620
259
    _mi_strlcat(buf, desc->legacy_name, sizeof(buf));
621
259
    err = _mi_getenv(buf, s, sizeof(s));
622
259
    if (err==0) {
623
0
      _mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name);
624
0
    }
625
259
  }
626
627
1.40k
  if (err==0) {
628
0
    size_t len = _mi_strnlen(s, sizeof(buf) - 1);
629
0
    for (size_t i = 0; i < len; i++) {
630
0
      buf[i] = _mi_toupper(s[i]);
631
0
    }
632
0
    buf[len] = 0;
633
0
    if (buf[0] == 0 || _mi_streq(buf,"1") || _mi_streq(buf,"TRUE") || _mi_streq(buf,"YES") || _mi_streq(buf,"ON")) {
634
0
      desc->value = 1;
635
0
      desc->init = INITIALIZED;
636
0
    }
637
0
    else if (_mi_streq(buf,"0") || _mi_streq(buf,"FALSE") || _mi_streq(buf,"NO") || _mi_streq(buf,"OFF")) {
638
0
      desc->value = 0;
639
0
      desc->init = INITIALIZED;
640
0
    }
641
0
    else {
642
0
      char* end = buf;
643
0
      errno = 0;
644
0
      long value = strtol(buf, &end, 10);
645
0
      if (errno==0 && mi_option_has_size_in_kib(desc->option)) {
646
        // this option is interpreted in KiB to prevent overflow of `long` for large allocations
647
        // (long is 32-bit on 64-bit windows, which allows for 4TiB max.)
648
0
        size_t size = (value < 0 ? 0 : (size_t)value);
649
0
        bool overflow = false;
650
0
        if (*end == 'K') { end++; }
651
0
        else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }
652
0
        else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }
653
0
        else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }
654
0
        else { size = (size + MI_KiB - 1) / MI_KiB; }
655
0
        if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB
656
0
        else if (*end == 'B') { end++; }                  // Kb, Mb, Gb, Tb
657
0
        if (overflow || size > (MI_MAX_ALLOC_SIZE / MI_KiB)) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }
658
0
        value = (size > LONG_MAX ? LONG_MAX : (long)size);
659
0
      }
660
0
      if (errno==0 && *end == 0) {
661
0
        mi_option_set(desc->option, value);
662
0
      }
663
0
      else {
664
        // set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.
665
0
        desc->init = DEFAULTED;
666
0
        if (desc->option == mi_option_verbose && desc->value == 0) {
667
          // if the 'mimalloc_verbose' env var has a bogus value we'd never know
668
          // (since the value defaults to 'off') so in that case briefly enable verbose
669
0
          desc->value = 1;
670
0
          _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);
671
0
          desc->value = 0;
672
0
        }
673
0
        else {
674
0
          _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);
675
0
        }
676
0
      }
677
0
    }
678
0
    mi_assert_internal(desc->init != UNINIT);
679
0
  }
680
1.40k
  else if (err==ENOENT) {
681
1.40k
    desc->init = DEFAULTED;
682
1.40k
  }
683
  // and on another error, keep unitialized to try again (can happen during preloading if getenv is not available)
684
1.40k
}