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