/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 | } |