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 | |
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 | |
22 | static long mi_max_error_count = 16; // stop outputting errors after this (use < 0 for no limit) |
23 | static long mi_max_warning_count = 16; // stop outputting warnings after this (use < 0 for no limit) |
24 | |
25 | static void mi_add_stderr_output(void); |
26 | |
27 | int 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 | // -------------------------------------------------------- |
41 | typedef 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 | |
47 | typedef 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 | |
58 | static 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 | |
100 | static void mi_option_init(mi_option_desc_t* desc); |
101 | |
102 | void _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 | |
118 | mi_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 | |
129 | mi_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 | |
134 | void 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 | |
143 | void 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 | |
152 | mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) { |
153 | return (mi_option_get(option) != 0); |
154 | } |
155 | |
156 | void mi_option_set_enabled(mi_option_t option, bool enable) { |
157 | mi_option_set(option, (enable ? 1 : 0)); |
158 | } |
159 | |
160 | void mi_option_set_enabled_default(mi_option_t option, bool enable) { |
161 | mi_option_set_default(option, (enable ? 1 : 0)); |
162 | } |
163 | |
164 | void mi_option_enable(mi_option_t option) { |
165 | mi_option_set_enabled(option,true); |
166 | } |
167 | |
168 | void mi_option_disable(mi_option_t option) { |
169 | mi_option_set_enabled(option,false); |
170 | } |
171 | |
172 | |
173 | static 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 |
203 | static char out_buf[MI_MAX_DELAY_OUTPUT+1]; |
204 | static _Atomic(size_t) out_len; |
205 | |
206 | static 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 | |
222 | static 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. |
238 | static 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. |
251 | static mi_output_fun* volatile mi_out_default; // = NULL |
252 | static _Atomic(void*) mi_out_arg; // = NULL |
253 | |
254 | static 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 | |
260 | void 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 |
267 | static 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 | // -------------------------------------------------------- |
276 | static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors |
277 | static _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. |
288 | static mi_decl_thread bool recurse = false; |
289 | |
290 | static mi_decl_noinline bool mi_recurse_enter_prim(void) { |
291 | if (recurse) return false; |
292 | recurse = true; |
293 | return true; |
294 | } |
295 | |
296 | static mi_decl_noinline void mi_recurse_exit_prim(void) { |
297 | recurse = false; |
298 | } |
299 | |
300 | static 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 | |
307 | static 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 | |
314 | void _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. |
330 | static 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 | |
339 | void _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 | |
346 | static 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 | |
357 | void _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 | |
365 | void _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 | |
373 | static 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 | |
381 | void _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 |
394 | void _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 | |
404 | static mi_error_fun* volatile mi_error_handler; // = NULL |
405 | static _Atomic(void*) mi_error_arg; // = NULL |
406 | |
407 | static 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 | |
429 | void 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 | |
434 | void _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 | |
453 | static 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 | |
464 | static 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 |
476 | static 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 |
483 | static 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> |
496 | static 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> |
506 | static char** mi_get_environ(void) { |
507 | return (*_NSGetEnviron()); |
508 | } |
509 | #else |
510 | extern char** environ; |
511 | static char** mi_get_environ(void) { |
512 | return environ; |
513 | } |
514 | #endif |
515 | static 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 |
534 | static 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 | |
560 | static 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 | |