Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/mimalloc/src/options.c
6175 views
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
int mi_version(void) mi_attr_noexcept {
23
return MI_MALLOC_VERSION;
24
}
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) }, // reserve N KiB OS memory in advance (use `option_get_size`)
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_LEGACY(disallow_os_alloc,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
{ 32, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output
83
{ 32, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output
84
{ 10, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. percentage of the abandoned segments to be reclaimed 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 (=1GiB) (use `option_get_size`)
88
#else
89
{ 128L*1024L, UNINIT, MI_OPTION(arena_reserve) }, // =128MiB on 32-bit
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
{ 1, UNINIT, MI_OPTION(abandoned_reclaim_on_free) },// reclaim an abandoned segment on a free
94
{ 0, UNINIT, MI_OPTION(disallow_arena_alloc) }, // 1 = do not use arena's for allocation (except if using specific arena id's)
95
{ 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.
96
};
97
98
static void mi_option_init(mi_option_desc_t* desc);
99
100
static bool mi_option_has_size_in_kib(mi_option_t option) {
101
return (option == mi_option_reserve_os_memory || option == mi_option_arena_reserve);
102
}
103
104
void _mi_options_init(void) {
105
// called on process load; should not be called before the CRT is initialized!
106
// (e.g. do not call this from process_init as that may run before CRT initialization)
107
mi_add_stderr_output(); // now it safe to use stderr for output
108
for(int i = 0; i < _mi_option_last; i++ ) {
109
mi_option_t option = (mi_option_t)i;
110
long l = mi_option_get(option); MI_UNUSED(l); // initialize
111
// if (option != mi_option_verbose)
112
{
113
mi_option_desc_t* desc = &options[option];
114
_mi_verbose_message("option '%s': %ld %s\n", desc->name, desc->value, (mi_option_has_size_in_kib(option) ? "KiB" : ""));
115
}
116
}
117
mi_max_error_count = mi_option_get(mi_option_max_errors);
118
mi_max_warning_count = mi_option_get(mi_option_max_warnings);
119
}
120
121
mi_decl_nodiscard long mi_option_get(mi_option_t option) {
122
mi_assert(option >= 0 && option < _mi_option_last);
123
if (option < 0 || option >= _mi_option_last) return 0;
124
mi_option_desc_t* desc = &options[option];
125
mi_assert(desc->option == option); // index should match the option
126
if mi_unlikely(desc->init == UNINIT) {
127
mi_option_init(desc);
128
}
129
return desc->value;
130
}
131
132
mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) {
133
long x = mi_option_get(option);
134
return (x < min ? min : (x > max ? max : x));
135
}
136
137
mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) {
138
mi_assert_internal(mi_option_has_size_in_kib(option));
139
const long x = mi_option_get(option);
140
size_t size = (x < 0 ? 0 : (size_t)x);
141
if (mi_option_has_size_in_kib(option)) {
142
size *= MI_KiB;
143
}
144
return size;
145
}
146
147
void mi_option_set(mi_option_t option, long value) {
148
mi_assert(option >= 0 && option < _mi_option_last);
149
if (option < 0 || option >= _mi_option_last) return;
150
mi_option_desc_t* desc = &options[option];
151
mi_assert(desc->option == option); // index should match the option
152
desc->value = value;
153
desc->init = INITIALIZED;
154
}
155
156
void mi_option_set_default(mi_option_t option, long value) {
157
mi_assert(option >= 0 && option < _mi_option_last);
158
if (option < 0 || option >= _mi_option_last) return;
159
mi_option_desc_t* desc = &options[option];
160
if (desc->init != INITIALIZED) {
161
desc->value = value;
162
}
163
}
164
165
mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) {
166
return (mi_option_get(option) != 0);
167
}
168
169
void mi_option_set_enabled(mi_option_t option, bool enable) {
170
mi_option_set(option, (enable ? 1 : 0));
171
}
172
173
void mi_option_set_enabled_default(mi_option_t option, bool enable) {
174
mi_option_set_default(option, (enable ? 1 : 0));
175
}
176
177
void mi_option_enable(mi_option_t option) {
178
mi_option_set_enabled(option,true);
179
}
180
181
void mi_option_disable(mi_option_t option) {
182
mi_option_set_enabled(option,false);
183
}
184
185
static void mi_cdecl mi_out_stderr(const char* msg, void* arg) {
186
MI_UNUSED(arg);
187
if (msg != NULL && msg[0] != 0) {
188
_mi_prim_out_stderr(msg);
189
}
190
}
191
192
// Since an output function can be registered earliest in the `main`
193
// function we also buffer output that happens earlier. When
194
// an output function is registered it is called immediately with
195
// the output up to that point.
196
#ifndef MI_MAX_DELAY_OUTPUT
197
#define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024))
198
#endif
199
static char out_buf[MI_MAX_DELAY_OUTPUT+1];
200
static _Atomic(size_t) out_len;
201
202
static void mi_cdecl mi_out_buf(const char* msg, void* arg) {
203
MI_UNUSED(arg);
204
if (msg==NULL) return;
205
if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return;
206
size_t n = _mi_strlen(msg);
207
if (n==0) return;
208
// claim space
209
size_t start = mi_atomic_add_acq_rel(&out_len, n);
210
if (start >= MI_MAX_DELAY_OUTPUT) return;
211
// check bound
212
if (start+n >= MI_MAX_DELAY_OUTPUT) {
213
n = MI_MAX_DELAY_OUTPUT-start-1;
214
}
215
_mi_memcpy(&out_buf[start], msg, n);
216
}
217
218
static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) {
219
if (out==NULL) return;
220
// claim (if `no_more_buf == true`, no more output will be added after this point)
221
size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1));
222
// and output the current contents
223
if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT;
224
out_buf[count] = 0;
225
out(out_buf,arg);
226
if (!no_more_buf) {
227
out_buf[count] = '\n'; // if continue with the buffer, insert a newline
228
}
229
}
230
231
232
// Once this module is loaded, switch to this routine
233
// which outputs to stderr and the delayed output buffer.
234
static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) {
235
mi_out_stderr(msg,arg);
236
mi_out_buf(msg,arg);
237
}
238
239
240
241
// --------------------------------------------------------
242
// Default output handler
243
// --------------------------------------------------------
244
245
// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t.
246
// For now, don't register output from multiple threads.
247
static mi_output_fun* volatile mi_out_default; // = NULL
248
static _Atomic(void*) mi_out_arg; // = NULL
249
250
static mi_output_fun* mi_out_get_default(void** parg) {
251
if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); }
252
mi_output_fun* out = mi_out_default;
253
return (out == NULL ? &mi_out_buf : out);
254
}
255
256
void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept {
257
mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer
258
mi_atomic_store_ptr_release(void,&mi_out_arg, arg);
259
if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now
260
}
261
262
// add stderr to the delayed output after the module is loaded
263
static void mi_add_stderr_output(void) {
264
mi_assert_internal(mi_out_default == NULL);
265
mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr
266
mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output
267
}
268
269
// --------------------------------------------------------
270
// Messages, all end up calling `_mi_fputs`.
271
// --------------------------------------------------------
272
static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors
273
static _Atomic(size_t) warning_count; // = 0; // when >= max_warning_count stop emitting warnings
274
275
// When overriding malloc, we may recurse into mi_vfprintf if an allocation
276
// inside the C runtime causes another message.
277
// In some cases (like on macOS) the loader already allocates which
278
// calls into mimalloc; if we then access thread locals (like `recurse`)
279
// this may crash as the access may call _tlv_bootstrap that tries to
280
// (recursively) invoke malloc again to allocate space for the thread local
281
// variables on demand. This is why we use a _mi_preloading test on such
282
// platforms. However, C code generator may move the initial thread local address
283
// load before the `if` and we therefore split it out in a separate funcion.
284
static mi_decl_thread bool recurse = false;
285
286
static mi_decl_noinline bool mi_recurse_enter_prim(void) {
287
if (recurse) return false;
288
recurse = true;
289
return true;
290
}
291
292
static mi_decl_noinline void mi_recurse_exit_prim(void) {
293
recurse = false;
294
}
295
296
static bool mi_recurse_enter(void) {
297
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
298
if (_mi_preloading()) return false;
299
#endif
300
return mi_recurse_enter_prim();
301
}
302
303
static void mi_recurse_exit(void) {
304
#if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD)
305
if (_mi_preloading()) return;
306
#endif
307
mi_recurse_exit_prim();
308
}
309
310
void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) {
311
if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr?
312
if (!mi_recurse_enter()) return;
313
out = mi_out_get_default(&arg);
314
if (prefix != NULL) out(prefix, arg);
315
out(message, arg);
316
mi_recurse_exit();
317
}
318
else {
319
if (prefix != NULL) out(prefix, arg);
320
out(message, arg);
321
}
322
}
323
324
// Define our own limited `fprintf` that avoids memory allocation.
325
// We do this using `_mi_vsnprintf` with a limited buffer.
326
static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) {
327
char buf[512];
328
if (fmt==NULL) return;
329
if (!mi_recurse_enter()) return;
330
_mi_vsnprintf(buf, sizeof(buf)-1, fmt, args);
331
mi_recurse_exit();
332
_mi_fputs(out,arg,prefix,buf);
333
}
334
335
void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) {
336
va_list args;
337
va_start(args,fmt);
338
mi_vfprintf(out,arg,NULL,fmt,args);
339
va_end(args);
340
}
341
342
static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) {
343
if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) {
344
char tprefix[64];
345
_mi_snprintf(tprefix, sizeof(tprefix), "%sthread 0x%tx: ", prefix, (uintptr_t)_mi_thread_id());
346
mi_vfprintf(out, arg, tprefix, fmt, args);
347
}
348
else {
349
mi_vfprintf(out, arg, prefix, fmt, args);
350
}
351
}
352
353
void _mi_trace_message(const char* fmt, ...) {
354
if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher
355
va_list args;
356
va_start(args, fmt);
357
mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args);
358
va_end(args);
359
}
360
361
void _mi_verbose_message(const char* fmt, ...) {
362
if (!mi_option_is_enabled(mi_option_verbose)) return;
363
va_list args;
364
va_start(args,fmt);
365
mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args);
366
va_end(args);
367
}
368
369
static void mi_show_error_message(const char* fmt, va_list args) {
370
if (!mi_option_is_enabled(mi_option_verbose)) {
371
if (!mi_option_is_enabled(mi_option_show_errors)) return;
372
if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return;
373
}
374
mi_vfprintf_thread(NULL, NULL, "mimalloc: error: ", fmt, args);
375
}
376
377
void _mi_warning_message(const char* fmt, ...) {
378
if (!mi_option_is_enabled(mi_option_verbose)) {
379
if (!mi_option_is_enabled(mi_option_show_errors)) return;
380
if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return;
381
}
382
va_list args;
383
va_start(args,fmt);
384
mi_vfprintf_thread(NULL, NULL, "mimalloc: warning: ", fmt, args);
385
va_end(args);
386
}
387
388
389
#if MI_DEBUG
390
void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) {
391
_mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion);
392
abort();
393
}
394
#endif
395
396
// --------------------------------------------------------
397
// Errors
398
// --------------------------------------------------------
399
400
static mi_error_fun* volatile mi_error_handler; // = NULL
401
static _Atomic(void*) mi_error_arg; // = NULL
402
403
static void mi_error_default(int err) {
404
MI_UNUSED(err);
405
#if (MI_DEBUG>0)
406
if (err==EFAULT) {
407
#ifdef _MSC_VER
408
__debugbreak();
409
#endif
410
abort();
411
}
412
#endif
413
#if (MI_SECURE>0)
414
if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data)
415
abort();
416
}
417
#endif
418
#if defined(MI_XMALLOC)
419
if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode
420
abort();
421
}
422
#endif
423
}
424
425
void mi_register_error(mi_error_fun* fun, void* arg) {
426
mi_error_handler = fun; // can be NULL
427
mi_atomic_store_ptr_release(void,&mi_error_arg, arg);
428
}
429
430
void _mi_error_message(int err, const char* fmt, ...) {
431
// show detailed error message
432
va_list args;
433
va_start(args, fmt);
434
mi_show_error_message(fmt, args);
435
va_end(args);
436
// and call the error handler which may abort (or return normally)
437
if (mi_error_handler != NULL) {
438
mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg));
439
}
440
else {
441
mi_error_default(err);
442
}
443
}
444
445
// --------------------------------------------------------
446
// Initialize options by checking the environment
447
// --------------------------------------------------------
448
449
// TODO: implement ourselves to reduce dependencies on the C runtime
450
#include <stdlib.h> // strtol
451
#include <string.h> // strstr
452
453
454
static void mi_option_init(mi_option_desc_t* desc) {
455
// Read option value from the environment
456
char s[64 + 1];
457
char buf[64+1];
458
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
459
_mi_strlcat(buf, desc->name, sizeof(buf));
460
bool found = _mi_getenv(buf, s, sizeof(s));
461
if (!found && desc->legacy_name != NULL) {
462
_mi_strlcpy(buf, "mimalloc_", sizeof(buf));
463
_mi_strlcat(buf, desc->legacy_name, sizeof(buf));
464
found = _mi_getenv(buf, s, sizeof(s));
465
if (found) {
466
_mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name);
467
}
468
}
469
470
if (found) {
471
size_t len = _mi_strnlen(s, sizeof(buf) - 1);
472
for (size_t i = 0; i < len; i++) {
473
buf[i] = _mi_toupper(s[i]);
474
}
475
buf[len] = 0;
476
if (buf[0] == 0 || strstr("1;TRUE;YES;ON", buf) != NULL) {
477
desc->value = 1;
478
desc->init = INITIALIZED;
479
}
480
else if (strstr("0;FALSE;NO;OFF", buf) != NULL) {
481
desc->value = 0;
482
desc->init = INITIALIZED;
483
}
484
else {
485
char* end = buf;
486
long value = strtol(buf, &end, 10);
487
if (mi_option_has_size_in_kib(desc->option)) {
488
// this option is interpreted in KiB to prevent overflow of `long` for large allocations
489
// (long is 32-bit on 64-bit windows, which allows for 4TiB max.)
490
size_t size = (value < 0 ? 0 : (size_t)value);
491
bool overflow = false;
492
if (*end == 'K') { end++; }
493
else if (*end == 'M') { overflow = mi_mul_overflow(size,MI_KiB,&size); end++; }
494
else if (*end == 'G') { overflow = mi_mul_overflow(size,MI_MiB,&size); end++; }
495
else if (*end == 'T') { overflow = mi_mul_overflow(size,MI_GiB,&size); end++; }
496
else { size = (size + MI_KiB - 1) / MI_KiB; }
497
if (end[0] == 'I' && end[1] == 'B') { end += 2; } // KiB, MiB, GiB, TiB
498
else if (*end == 'B') { end++; } // Kb, Mb, Gb, Tb
499
if (overflow || size > MI_MAX_ALLOC_SIZE) { size = (MI_MAX_ALLOC_SIZE / MI_KiB); }
500
value = (size > LONG_MAX ? LONG_MAX : (long)size);
501
}
502
if (*end == 0) {
503
desc->value = value;
504
desc->init = INITIALIZED;
505
}
506
else {
507
// set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose.
508
desc->init = DEFAULTED;
509
if (desc->option == mi_option_verbose && desc->value == 0) {
510
// if the 'mimalloc_verbose' env var has a bogus value we'd never know
511
// (since the value defaults to 'off') so in that case briefly enable verbose
512
desc->value = 1;
513
_mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);
514
desc->value = 0;
515
}
516
else {
517
_mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name);
518
}
519
}
520
}
521
mi_assert_internal(desc->init != UNINIT);
522
}
523
else if (!_mi_preloading()) {
524
desc->init = DEFAULTED;
525
}
526
}
527
528