/src/cpython/Objects/mimalloc/init.c
Line | Count | Source |
1 | | /* ---------------------------------------------------------------------------- |
2 | | Copyright (c) 2018-2022, 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/prim.h" |
10 | | |
11 | | #include <string.h> // memcpy, memset |
12 | | #include <stdlib.h> // atexit |
13 | | |
14 | | |
15 | | // Empty page used to initialize the small free pages array |
16 | | const mi_page_t _mi_page_empty; // CPython: zero-initialized since mi_page_t has extra fields |
17 | | |
18 | | #define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) |
19 | | |
20 | | #if (MI_SMALL_WSIZE_MAX==128) |
21 | | #if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8) |
22 | | #define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } |
23 | | #elif (MI_PADDING>0) |
24 | | #define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } |
25 | | #else |
26 | | #define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() } |
27 | | #endif |
28 | | #else |
29 | | #error "define right initialization sizes corresponding to MI_SMALL_WSIZE_MAX" |
30 | | #endif |
31 | | |
32 | | // Empty page queues for every bin |
33 | | #define QNULL(sz) { NULL, NULL, (sz)*sizeof(uintptr_t) } |
34 | | #define MI_PAGE_QUEUES_EMPTY \ |
35 | | { QNULL(1), \ |
36 | | QNULL( 1), QNULL( 2), QNULL( 3), QNULL( 4), QNULL( 5), QNULL( 6), QNULL( 7), QNULL( 8), /* 8 */ \ |
37 | | QNULL( 10), QNULL( 12), QNULL( 14), QNULL( 16), QNULL( 20), QNULL( 24), QNULL( 28), QNULL( 32), /* 16 */ \ |
38 | | QNULL( 40), QNULL( 48), QNULL( 56), QNULL( 64), QNULL( 80), QNULL( 96), QNULL( 112), QNULL( 128), /* 24 */ \ |
39 | | QNULL( 160), QNULL( 192), QNULL( 224), QNULL( 256), QNULL( 320), QNULL( 384), QNULL( 448), QNULL( 512), /* 32 */ \ |
40 | | QNULL( 640), QNULL( 768), QNULL( 896), QNULL( 1024), QNULL( 1280), QNULL( 1536), QNULL( 1792), QNULL( 2048), /* 40 */ \ |
41 | | QNULL( 2560), QNULL( 3072), QNULL( 3584), QNULL( 4096), QNULL( 5120), QNULL( 6144), QNULL( 7168), QNULL( 8192), /* 48 */ \ |
42 | | QNULL( 10240), QNULL( 12288), QNULL( 14336), QNULL( 16384), QNULL( 20480), QNULL( 24576), QNULL( 28672), QNULL( 32768), /* 56 */ \ |
43 | | QNULL( 40960), QNULL( 49152), QNULL( 57344), QNULL( 65536), QNULL( 81920), QNULL( 98304), QNULL(114688), QNULL(131072), /* 64 */ \ |
44 | | QNULL(163840), QNULL(196608), QNULL(229376), QNULL(262144), QNULL(327680), QNULL(393216), QNULL(458752), QNULL(524288), /* 72 */ \ |
45 | | QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \ |
46 | | QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 2) /* Full queue */ } |
47 | | |
48 | | #define MI_STAT_COUNT_NULL() {0,0,0} |
49 | | |
50 | | // Empty statistics |
51 | | #define MI_STATS_NULL \ |
52 | | MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ |
53 | | { 0 }, { 0 }, \ |
54 | | MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ |
55 | | MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ |
56 | | { 0 }, { 0 }, { 0 }, { 0 }, \ |
57 | | { 0 }, { 0 }, { 0 }, { 0 }, \ |
58 | | \ |
59 | | { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, \ |
60 | | MI_INIT4(MI_STAT_COUNT_NULL), \ |
61 | | { 0 }, { 0 }, { 0 }, { 0 }, \ |
62 | | \ |
63 | | { MI_INIT4(MI_STAT_COUNT_NULL) }, \ |
64 | | { { 0 }, { 0 }, { 0 }, { 0 } }, \ |
65 | | \ |
66 | | { MI_INIT74(MI_STAT_COUNT_NULL) }, \ |
67 | | { MI_INIT74(MI_STAT_COUNT_NULL) } |
68 | | |
69 | | |
70 | | // Empty slice span queues for every bin |
71 | | #define SQNULL(sz) { NULL, NULL, sz } |
72 | | #define MI_SEGMENT_SPAN_QUEUES_EMPTY \ |
73 | | { SQNULL(1), \ |
74 | | SQNULL( 1), SQNULL( 2), SQNULL( 3), SQNULL( 4), SQNULL( 5), SQNULL( 6), SQNULL( 7), SQNULL( 10), /* 8 */ \ |
75 | | SQNULL( 12), SQNULL( 14), SQNULL( 16), SQNULL( 20), SQNULL( 24), SQNULL( 28), SQNULL( 32), SQNULL( 40), /* 16 */ \ |
76 | | SQNULL( 48), SQNULL( 56), SQNULL( 64), SQNULL( 80), SQNULL( 96), SQNULL( 112), SQNULL( 128), SQNULL( 160), /* 24 */ \ |
77 | | SQNULL( 192), SQNULL( 224), SQNULL( 256), SQNULL( 320), SQNULL( 384), SQNULL( 448), SQNULL( 512), SQNULL( 640), /* 32 */ \ |
78 | | SQNULL( 768), SQNULL( 896), SQNULL( 1024) /* 35 */ } |
79 | | |
80 | | |
81 | | // -------------------------------------------------------- |
82 | | // Statically allocate an empty heap as the initial |
83 | | // thread local value for the default heap, |
84 | | // and statically allocate the backing heap for the main |
85 | | // thread so it can function without doing any allocation |
86 | | // itself (as accessing a thread local for the first time |
87 | | // may lead to allocation itself on some platforms) |
88 | | // -------------------------------------------------------- |
89 | | |
90 | | mi_decl_cache_align const mi_heap_t _mi_heap_empty = { |
91 | | NULL, |
92 | | MI_ATOMIC_VAR_INIT(NULL), |
93 | | 0, // tid |
94 | | 0, // cookie |
95 | | 0, // arena id |
96 | | { 0, 0 }, // keys |
97 | | { {0}, {0}, 0, true }, // random |
98 | | 0, // page count |
99 | | MI_BIN_FULL, 0, // page retired min/max |
100 | | 0, // pages_full_size |
101 | | 0, 0, // generic count |
102 | | NULL, // next |
103 | | false, // can reclaim |
104 | | 0, // tag |
105 | | 0, // debug_offset (CPython) |
106 | | false, // page_use_qsbr (CPython) |
107 | | #if MI_GUARDED |
108 | | 0, 0, 0, 1, // rate is 0 and count is 1 so we never write to it (see `internal.h:mi_heap_malloc_use_guarded`) |
109 | | #endif |
110 | | MI_SMALL_PAGES_EMPTY, |
111 | | MI_PAGE_QUEUES_EMPTY |
112 | | }; |
113 | | |
114 | | static mi_decl_cache_align mi_subproc_t mi_subproc_default; |
115 | | |
116 | | #define tld_empty_stats ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats))) |
117 | | |
118 | | mi_decl_cache_align static const mi_tld_t tld_empty = { |
119 | | 0, |
120 | | false, |
121 | | NULL, NULL, |
122 | | { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, tld_empty_stats }, // segments |
123 | | { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL } // stats |
124 | | }; |
125 | | |
126 | 222 | mi_threadid_t _mi_thread_id(void) mi_attr_noexcept { |
127 | 222 | mi_threadid_t tid = _mi_prim_thread_id(); |
128 | 222 | mi_assert_internal( (tid & 0x03) == 0 ); // mimalloc reserves the bottom 2 bits |
129 | 222 | return tid; |
130 | 222 | } |
131 | | |
132 | | // the thread-local default heap for allocation |
133 | | mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty; |
134 | | |
135 | | extern mi_decl_hidden mi_heap_t _mi_heap_main; |
136 | | |
137 | | static mi_decl_cache_align mi_tld_t tld_main = { |
138 | | 0, false, |
139 | | &_mi_heap_main, & _mi_heap_main, |
140 | | { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, 0, &mi_subproc_default, &tld_main.stats }, // segments |
141 | | { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL } // stats |
142 | | }; |
143 | | |
144 | | mi_decl_cache_align mi_heap_t _mi_heap_main = { |
145 | | &tld_main, |
146 | | MI_ATOMIC_VAR_INIT(NULL), |
147 | | 0, // thread id |
148 | | 0, // initial cookie |
149 | | 0, // arena id |
150 | | { 0, 0 }, // the key of the main heap can be fixed (unlike page keys that need to be secure!) |
151 | | { {0x846ca68b}, {0}, 0, true }, // random |
152 | | 0, // page count |
153 | | MI_BIN_FULL, 0, // page retired min/max |
154 | | 0, // pages_full_size |
155 | | 0, 0, // generic count |
156 | | NULL, // next heap |
157 | | false, // can reclaim |
158 | | 0, // tag |
159 | | 0, // debug_offset (CPython) |
160 | | false, // page_use_qsbr (CPython) |
161 | | #if MI_GUARDED |
162 | | 0, 0, 0, 0, |
163 | | #endif |
164 | | MI_SMALL_PAGES_EMPTY, |
165 | | MI_PAGE_QUEUES_EMPTY |
166 | | }; |
167 | | |
168 | | bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`. |
169 | | |
170 | | mi_stats_t _mi_stats_main = { sizeof(mi_stats_t), MI_STAT_VERSION, MI_STATS_NULL }; |
171 | | |
172 | | #if MI_GUARDED |
173 | | mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) { |
174 | | heap->guarded_sample_rate = sample_rate; |
175 | | heap->guarded_sample_count = sample_rate; // count down samples |
176 | | if (heap->guarded_sample_rate > 1) { |
177 | | if (seed == 0) { |
178 | | seed = _mi_heap_random_next(heap); |
179 | | } |
180 | | heap->guarded_sample_count = (seed % heap->guarded_sample_rate) + 1; // start at random count between 1 and `sample_rate` |
181 | | } |
182 | | } |
183 | | |
184 | | mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) { |
185 | | heap->guarded_size_min = min; |
186 | | heap->guarded_size_max = (min > max ? min : max); |
187 | | } |
188 | | |
189 | | void _mi_heap_guarded_init(mi_heap_t* heap) { |
190 | | mi_heap_guarded_set_sample_rate(heap, |
191 | | (size_t)mi_option_get_clamp(mi_option_guarded_sample_rate, 0, LONG_MAX), |
192 | | (size_t)mi_option_get(mi_option_guarded_sample_seed)); |
193 | | mi_heap_guarded_set_size_bound(heap, |
194 | | (size_t)mi_option_get_clamp(mi_option_guarded_min, 0, LONG_MAX), |
195 | | (size_t)mi_option_get_clamp(mi_option_guarded_max, 0, LONG_MAX) ); |
196 | | } |
197 | | #else |
198 | 0 | mi_decl_export void mi_heap_guarded_set_sample_rate(mi_heap_t* heap, size_t sample_rate, size_t seed) { |
199 | 0 | MI_UNUSED(heap); MI_UNUSED(sample_rate); MI_UNUSED(seed); |
200 | 0 | } |
201 | | |
202 | 0 | mi_decl_export void mi_heap_guarded_set_size_bound(mi_heap_t* heap, size_t min, size_t max) { |
203 | 0 | MI_UNUSED(heap); MI_UNUSED(min); MI_UNUSED(max); |
204 | 0 | } |
205 | 37 | void _mi_heap_guarded_init(mi_heap_t* heap) { |
206 | 37 | MI_UNUSED(heap); |
207 | 37 | } |
208 | | #endif |
209 | | |
210 | | |
211 | 185 | static void mi_heap_main_init(void) { |
212 | 185 | if (_mi_heap_main.cookie == 0) { |
213 | 37 | _mi_heap_main.thread_id = _mi_thread_id(); |
214 | 37 | _mi_heap_main.cookie = 1; |
215 | | #if defined(_WIN32) && !defined(MI_SHARED_LIB) |
216 | | _mi_random_init_weak(&_mi_heap_main.random); // prevent allocation failure during bcrypt dll initialization with static linking |
217 | | #else |
218 | 37 | _mi_random_init(&_mi_heap_main.random); |
219 | 37 | #endif |
220 | 37 | _mi_heap_main.cookie = _mi_heap_random_next(&_mi_heap_main); |
221 | 37 | _mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main); |
222 | 37 | _mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main); |
223 | 37 | mi_lock_init(&mi_subproc_default.abandoned_os_lock); |
224 | 37 | mi_lock_init(&mi_subproc_default.abandoned_os_visit_lock); |
225 | 37 | _mi_heap_guarded_init(&_mi_heap_main); |
226 | 37 | } |
227 | 185 | } |
228 | | |
229 | 0 | mi_heap_t* _mi_heap_main_get(void) { |
230 | 0 | mi_heap_main_init(); |
231 | 0 | return &_mi_heap_main; |
232 | 0 | } |
233 | | |
234 | | /* ----------------------------------------------------------- |
235 | | Sub process |
236 | | ----------------------------------------------------------- */ |
237 | | |
238 | 0 | mi_subproc_id_t mi_subproc_main(void) { |
239 | 0 | return NULL; |
240 | 0 | } |
241 | | |
242 | 0 | mi_subproc_id_t mi_subproc_new(void) { |
243 | 0 | mi_memid_t memid = _mi_memid_none(); |
244 | 0 | mi_subproc_t* subproc = (mi_subproc_t*)_mi_arena_meta_zalloc(sizeof(mi_subproc_t), &memid); |
245 | 0 | if (subproc == NULL) return NULL; |
246 | 0 | subproc->memid = memid; |
247 | 0 | subproc->abandoned_os_list = NULL; |
248 | 0 | mi_lock_init(&subproc->abandoned_os_lock); |
249 | 0 | mi_lock_init(&subproc->abandoned_os_visit_lock); |
250 | 0 | return subproc; |
251 | 0 | } |
252 | | |
253 | 0 | mi_subproc_t* _mi_subproc_from_id(mi_subproc_id_t subproc_id) { |
254 | 0 | return (subproc_id == NULL ? &mi_subproc_default : (mi_subproc_t*)subproc_id); |
255 | 0 | } |
256 | | |
257 | 0 | void mi_subproc_delete(mi_subproc_id_t subproc_id) { |
258 | 0 | if (subproc_id == NULL) return; |
259 | 0 | mi_subproc_t* subproc = _mi_subproc_from_id(subproc_id); |
260 | 0 | if (subproc==NULL) return; |
261 | | // check if there are no abandoned segments still.. |
262 | 0 | bool safe_to_delete = false; |
263 | 0 | mi_lock(&subproc->abandoned_os_lock) { |
264 | 0 | if (subproc->abandoned_os_list == NULL) { |
265 | 0 | safe_to_delete = true; |
266 | 0 | } |
267 | 0 | } |
268 | 0 | if (!safe_to_delete) return; |
269 | | // safe to release |
270 | | // todo: should we refcount subprocesses? |
271 | 0 | mi_lock_done(&subproc->abandoned_os_lock); |
272 | 0 | mi_lock_done(&subproc->abandoned_os_visit_lock); |
273 | 0 | _mi_arena_meta_free(subproc, subproc->memid, sizeof(mi_subproc_t)); |
274 | 0 | } |
275 | | |
276 | 0 | void mi_subproc_add_current_thread(mi_subproc_id_t subproc_id) { |
277 | 0 | mi_heap_t* heap = mi_heap_get_default(); |
278 | 0 | if (heap == NULL) return; |
279 | 0 | mi_assert(heap->tld->segments.subproc == &mi_subproc_default); |
280 | 0 | if (heap->tld->segments.subproc != &mi_subproc_default) return; |
281 | 0 | heap->tld->segments.subproc = _mi_subproc_from_id(subproc_id); |
282 | 0 | } |
283 | | |
284 | | |
285 | | |
286 | | /* ----------------------------------------------------------- |
287 | | Initialization and freeing of the thread local heaps |
288 | | ----------------------------------------------------------- */ |
289 | | |
290 | | // note: in x64 in release build `sizeof(mi_thread_data_t)` is under 4KiB (= OS page size). |
291 | | typedef struct mi_thread_data_s { |
292 | | mi_heap_t heap; // must come first due to cast in `_mi_heap_done` |
293 | | mi_tld_t tld; |
294 | | mi_memid_t memid; // must come last due to zero'ing |
295 | | } mi_thread_data_t; |
296 | | |
297 | | |
298 | | // Thread meta-data is allocated directly from the OS. For |
299 | | // some programs that do not use thread pools and allocate and |
300 | | // destroy many OS threads, this may causes too much overhead |
301 | | // per thread so we maintain a small cache of recently freed metadata. |
302 | | |
303 | 0 | #define TD_CACHE_SIZE (32) |
304 | | static _Atomic(mi_thread_data_t*) td_cache[TD_CACHE_SIZE]; |
305 | | |
306 | 0 | static mi_thread_data_t* mi_thread_data_zalloc(void) { |
307 | | // try to find thread metadata in the cache |
308 | 0 | mi_thread_data_t* td = NULL; |
309 | 0 | for (int i = 0; i < TD_CACHE_SIZE; i++) { |
310 | 0 | td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); |
311 | 0 | if (td != NULL) { |
312 | | // found cached allocation, try use it |
313 | 0 | td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL); |
314 | 0 | if (td != NULL) { |
315 | 0 | _mi_memzero(td, offsetof(mi_thread_data_t,memid)); |
316 | 0 | return td; |
317 | 0 | } |
318 | 0 | } |
319 | 0 | } |
320 | | |
321 | | // if that fails, allocate as meta data |
322 | 0 | mi_memid_t memid; |
323 | 0 | td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid); |
324 | 0 | if (td == NULL) { |
325 | | // if this fails, try once more. (issue #257) |
326 | 0 | td = (mi_thread_data_t*)_mi_os_zalloc(sizeof(mi_thread_data_t), &memid); |
327 | 0 | if (td == NULL) { |
328 | | // really out of memory |
329 | 0 | _mi_error_message(ENOMEM, "unable to allocate thread local heap metadata (%zu bytes)\n", sizeof(mi_thread_data_t)); |
330 | 0 | return NULL; |
331 | 0 | } |
332 | 0 | } |
333 | 0 | td->memid = memid; |
334 | 0 | return td; |
335 | 0 | } |
336 | | |
337 | 0 | static void mi_thread_data_free( mi_thread_data_t* tdfree ) { |
338 | | // try to add the thread metadata to the cache |
339 | 0 | for (int i = 0; i < TD_CACHE_SIZE; i++) { |
340 | 0 | mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); |
341 | 0 | if (td == NULL) { |
342 | 0 | mi_thread_data_t* expected = NULL; |
343 | 0 | if (mi_atomic_cas_ptr_weak_acq_rel(mi_thread_data_t, &td_cache[i], &expected, tdfree)) { |
344 | 0 | return; |
345 | 0 | } |
346 | 0 | } |
347 | 0 | } |
348 | | // if that fails, just free it directly |
349 | 0 | _mi_os_free(tdfree, sizeof(mi_thread_data_t), tdfree->memid); |
350 | 0 | } |
351 | | |
352 | 0 | void _mi_thread_data_collect(void) { |
353 | | // free all thread metadata from the cache |
354 | 0 | for (int i = 0; i < TD_CACHE_SIZE; i++) { |
355 | 0 | mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); |
356 | 0 | if (td != NULL) { |
357 | 0 | td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL); |
358 | 0 | if (td != NULL) { |
359 | 0 | _mi_os_free(td, sizeof(mi_thread_data_t), td->memid); |
360 | 0 | } |
361 | 0 | } |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | // Initialize the thread local default heap, called from `mi_thread_init` |
366 | 74 | static bool _mi_thread_heap_init(void) { |
367 | 74 | if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true; |
368 | 0 | if (_mi_is_main_thread()) { |
369 | | // mi_assert_internal(_mi_heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization |
370 | | // the main heap is statically allocated |
371 | 0 | mi_heap_main_init(); |
372 | 0 | _mi_heap_set_default_direct(&_mi_heap_main); |
373 | | //mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_prim_get_default_heap()); |
374 | 0 | } |
375 | 0 | else { |
376 | | // use `_mi_os_alloc` to allocate directly from the OS |
377 | 0 | mi_thread_data_t* td = mi_thread_data_zalloc(); |
378 | 0 | if (td == NULL) return false; |
379 | | |
380 | 0 | mi_tld_t* tld = &td->tld; |
381 | 0 | mi_heap_t* heap = &td->heap; |
382 | 0 | _mi_tld_init(tld, heap); // must be before `_mi_heap_init` |
383 | 0 | _mi_heap_init(heap, tld, _mi_arena_id_none(), false /* can reclaim */, 0 /* default tag */); |
384 | 0 | _mi_heap_set_default_direct(heap); |
385 | 0 | } |
386 | 0 | return false; |
387 | 0 | } |
388 | | |
389 | | // initialize thread local data |
390 | 0 | void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) { |
391 | 0 | _mi_memcpy_aligned(tld, &tld_empty, sizeof(mi_tld_t)); |
392 | 0 | tld->heap_backing = bheap; |
393 | 0 | tld->heaps = NULL; |
394 | 0 | tld->segments.subproc = &mi_subproc_default; |
395 | 0 | tld->segments.stats = &tld->stats; |
396 | 0 | } |
397 | | |
398 | | // Free the thread local default heap (called from `mi_thread_done`) |
399 | 0 | static bool _mi_thread_heap_done(mi_heap_t* heap) { |
400 | 0 | if (!mi_heap_is_initialized(heap)) return true; |
401 | | |
402 | | // reset default heap |
403 | 0 | _mi_heap_set_default_direct(_mi_is_main_thread() ? &_mi_heap_main : (mi_heap_t*)&_mi_heap_empty); |
404 | | |
405 | | // switch to backing heap |
406 | 0 | heap = heap->tld->heap_backing; |
407 | 0 | if (!mi_heap_is_initialized(heap)) return false; |
408 | | |
409 | | // delete all non-backing heaps in this thread |
410 | 0 | mi_heap_t* curr = heap->tld->heaps; |
411 | 0 | while (curr != NULL) { |
412 | 0 | mi_heap_t* next = curr->next; // save `next` as `curr` will be freed |
413 | 0 | if (curr != heap) { |
414 | 0 | mi_assert_internal(!mi_heap_is_backing(curr)); |
415 | 0 | mi_heap_delete(curr); |
416 | 0 | } |
417 | 0 | curr = next; |
418 | 0 | } |
419 | 0 | mi_assert_internal(heap->tld->heaps == heap && heap->next == NULL); |
420 | 0 | mi_assert_internal(mi_heap_is_backing(heap)); |
421 | | |
422 | | // collect if not the main thread |
423 | 0 | if (heap != &_mi_heap_main) { |
424 | 0 | _mi_heap_collect_abandon(heap); |
425 | 0 | } |
426 | | |
427 | | // merge stats |
428 | 0 | _mi_stats_done(&heap->tld->stats); |
429 | | |
430 | | // free if not the main thread |
431 | 0 | if (heap != &_mi_heap_main) { |
432 | | // the following assertion does not always hold for huge segments as those are always treated |
433 | | // as abandoned: one may allocate it in one thread, but deallocate in another in which case |
434 | | // the count can be too large or negative. todo: perhaps not count huge segments? see issue #363 |
435 | | // mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id()); |
436 | 0 | mi_thread_data_free((mi_thread_data_t*)heap); |
437 | 0 | } |
438 | 0 | else { |
439 | | #if 0 |
440 | | // never free the main thread even in debug mode; if a dll is linked statically with mimalloc, |
441 | | // there may still be delete/free calls after the mi_fls_done is called. Issue #207 |
442 | | _mi_heap_destroy_pages(heap); |
443 | | mi_assert_internal(heap->tld->heap_backing == &_mi_heap_main); |
444 | | #endif |
445 | 0 | } |
446 | 0 | return false; |
447 | 0 | } |
448 | | |
449 | | |
450 | | |
451 | | // -------------------------------------------------------- |
452 | | // Try to run `mi_thread_done()` automatically so any memory |
453 | | // owned by the thread but not yet released can be abandoned |
454 | | // and re-owned by another thread. |
455 | | // |
456 | | // 1. windows dynamic library: |
457 | | // call from DllMain on DLL_THREAD_DETACH |
458 | | // 2. windows static library: |
459 | | // use `FlsAlloc` to call a destructor when the thread is done |
460 | | // 3. unix, pthreads: |
461 | | // use a pthread key to call a destructor when a pthread is done |
462 | | // |
463 | | // In the last two cases we also need to call `mi_process_init` |
464 | | // to set up the thread local keys. |
465 | | // -------------------------------------------------------- |
466 | | |
467 | | // Set up handlers so `mi_thread_done` is called automatically |
468 | 74 | static void mi_process_setup_auto_thread_done(void) { |
469 | 74 | mi_atomic_do_once { |
470 | 37 | _mi_prim_thread_init_auto_done(); |
471 | 37 | _mi_heap_set_default_direct(&_mi_heap_main); |
472 | 37 | } |
473 | 74 | } |
474 | | |
475 | | |
476 | 0 | bool _mi_is_main_thread(void) { |
477 | 0 | return (_mi_heap_main.thread_id==0 || _mi_heap_main.thread_id == _mi_thread_id()); |
478 | 0 | } |
479 | | |
480 | | static _Atomic(size_t) thread_count = MI_ATOMIC_VAR_INIT(1); |
481 | | |
482 | 0 | size_t _mi_current_thread_count(void) { |
483 | 0 | return mi_atomic_load_relaxed(&thread_count); |
484 | 0 | } |
485 | | |
486 | | // This is called from the `mi_malloc_generic` |
487 | | void mi_thread_init(void) mi_attr_noexcept |
488 | 74 | { |
489 | | // ensure our process has started already |
490 | 74 | mi_process_init(); |
491 | | |
492 | | // initialize the thread local default heap |
493 | | // (this will call `_mi_heap_set_default_direct` and thus set the |
494 | | // fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called) |
495 | 74 | if (_mi_thread_heap_init()) return; // returns true if already initialized |
496 | | |
497 | 0 | _mi_stat_increase(&_mi_stats_main.threads, 1); |
498 | 0 | mi_atomic_increment_relaxed(&thread_count); |
499 | | //_mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id()); |
500 | 0 | } |
501 | | |
502 | 0 | void mi_thread_done(void) mi_attr_noexcept { |
503 | 0 | _mi_thread_done(NULL); |
504 | 0 | } |
505 | | |
506 | | void _mi_thread_done(mi_heap_t* heap) |
507 | 0 | { |
508 | | // calling with NULL implies using the default heap |
509 | 0 | if (heap == NULL) { |
510 | 0 | heap = mi_prim_get_default_heap(); |
511 | 0 | if (heap == NULL) return; |
512 | 0 | } |
513 | | |
514 | | // prevent re-entrancy through heap_done/heap_set_default_direct (issue #699) |
515 | 0 | if (!mi_heap_is_initialized(heap)) { |
516 | 0 | return; |
517 | 0 | } |
518 | | |
519 | | // adjust stats |
520 | 0 | mi_atomic_decrement_relaxed(&thread_count); |
521 | 0 | _mi_stat_decrease(&_mi_stats_main.threads, 1); |
522 | | |
523 | | // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps... |
524 | 0 | if (heap->thread_id != _mi_thread_id()) return; |
525 | | |
526 | | // abandon the thread local heap |
527 | 0 | if (_mi_thread_heap_done(heap)) return; // returns true if already ran |
528 | 0 | } |
529 | | |
530 | 37 | void _mi_heap_set_default_direct(mi_heap_t* heap) { |
531 | 37 | mi_assert_internal(heap != NULL); |
532 | | #if defined(MI_TLS_SLOT) |
533 | | mi_prim_tls_slot_set(MI_TLS_SLOT,heap); |
534 | | #elif defined(MI_TLS_PTHREAD_SLOT_OFS) |
535 | | *mi_prim_tls_pthread_heap_slot() = heap; |
536 | | #elif defined(MI_TLS_PTHREAD) |
537 | | // we use _mi_heap_default_key |
538 | | #else |
539 | 37 | _mi_heap_default = heap; |
540 | 37 | #endif |
541 | | |
542 | | // ensure the default heap is passed to `_mi_thread_done` |
543 | | // setting to a non-NULL value also ensures `mi_thread_done` is called. |
544 | 37 | _mi_prim_thread_associate_default_heap(heap); |
545 | 37 | } |
546 | | |
547 | 0 | void mi_thread_set_in_threadpool(void) mi_attr_noexcept { |
548 | | // nothing |
549 | 0 | } |
550 | | |
551 | | // -------------------------------------------------------- |
552 | | // Run functions on process init/done, and thread init/done |
553 | | // -------------------------------------------------------- |
554 | | static bool os_preloading = true; // true until this module is initialized |
555 | | |
556 | | // Returns true if this module has not been initialized; Don't use C runtime routines until it returns false. |
557 | 0 | bool mi_decl_noinline _mi_preloading(void) { |
558 | 0 | return os_preloading; |
559 | 0 | } |
560 | | |
561 | | // Returns true if mimalloc was redirected |
562 | 0 | mi_decl_nodiscard bool mi_is_redirected(void) mi_attr_noexcept { |
563 | 0 | return _mi_is_redirected(); |
564 | 0 | } |
565 | | |
566 | | // Called once by the process loader from `src/prim/prim.c` |
567 | 37 | void _mi_auto_process_init(void) { |
568 | 37 | mi_heap_main_init(); |
569 | | #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD) |
570 | | volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true; |
571 | | if (dummy == NULL) return; // use dummy or otherwise the access may get optimized away (issue #697) |
572 | | #endif |
573 | 37 | os_preloading = false; |
574 | 37 | mi_assert_internal(_mi_is_main_thread()); |
575 | 37 | _mi_options_init(); |
576 | 37 | mi_process_setup_auto_thread_done(); |
577 | 37 | mi_process_init(); |
578 | 37 | if (_mi_is_redirected()) _mi_verbose_message("malloc is redirected.\n"); |
579 | | |
580 | | // show message from the redirector (if present) |
581 | 37 | const char* msg = NULL; |
582 | 37 | _mi_allocator_init(&msg); |
583 | 37 | if (msg != NULL && (mi_option_is_enabled(mi_option_verbose) || mi_option_is_enabled(mi_option_show_errors))) { |
584 | 0 | _mi_fputs(NULL,NULL,NULL,msg); |
585 | 0 | } |
586 | | |
587 | | // reseed random |
588 | 37 | _mi_random_reinit_if_weak(&_mi_heap_main.random); |
589 | 37 | } |
590 | | |
591 | | #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64)) |
592 | | #include <intrin.h> |
593 | | mi_decl_cache_align bool _mi_cpu_has_fsrm = false; |
594 | | mi_decl_cache_align bool _mi_cpu_has_erms = false; |
595 | | |
596 | | static void mi_detect_cpu_features(void) { |
597 | | // FSRM for fast short rep movsb/stosb support (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017)) |
598 | | // EMRS for fast enhanced rep movsb/stosb support |
599 | | int32_t cpu_info[4]; |
600 | | __cpuid(cpu_info, 7); |
601 | | _mi_cpu_has_fsrm = ((cpu_info[3] & (1 << 4)) != 0); // bit 4 of EDX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features> |
602 | | _mi_cpu_has_erms = ((cpu_info[1] & (1 << 9)) != 0); // bit 9 of EBX : see <https://en.wikipedia.org/wiki/CPUID#EAX=7,_ECX=0:_Extended_Features> |
603 | | } |
604 | | #else |
605 | 37 | static void mi_detect_cpu_features(void) { |
606 | | // nothing |
607 | 37 | } |
608 | | #endif |
609 | | |
610 | | // Initialize the process; called by thread_init or the process loader |
611 | 37 | static void mi_process_init_once(void) mi_attr_noexcept { |
612 | 37 | _mi_process_is_initialized = true; |
613 | 37 | _mi_verbose_message("process init: 0x%zx\n", _mi_thread_id()); |
614 | 37 | mi_process_setup_auto_thread_done(); |
615 | | |
616 | 37 | mi_detect_cpu_features(); |
617 | 37 | _mi_os_init(); |
618 | 37 | mi_heap_main_init(); |
619 | 37 | mi_thread_init(); |
620 | | |
621 | | #if defined(_WIN32) |
622 | | // On windows, when building as a static lib the FLS cleanup happens to early for the main thread. |
623 | | // To avoid this, set the FLS value for the main thread to NULL so the fls cleanup |
624 | | // will not call _mi_thread_done on the (still executing) main thread. See issue #508. |
625 | | _mi_prim_thread_associate_default_heap(NULL); |
626 | | #endif |
627 | | |
628 | 37 | mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL) |
629 | 37 | mi_track_init(); |
630 | | |
631 | 37 | if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { |
632 | 0 | size_t pages = mi_option_get_clamp(mi_option_reserve_huge_os_pages, 0, 128*1024); |
633 | 0 | int reserve_at = (int)mi_option_get_clamp(mi_option_reserve_huge_os_pages_at, -1, INT_MAX); |
634 | 0 | if (reserve_at != -1) { |
635 | 0 | mi_reserve_huge_os_pages_at(pages, reserve_at, pages*500); |
636 | 0 | } else { |
637 | 0 | mi_reserve_huge_os_pages_interleave(pages, 0, pages*500); |
638 | 0 | } |
639 | 0 | } |
640 | 37 | if (mi_option_is_enabled(mi_option_reserve_os_memory)) { |
641 | 0 | long ksize = mi_option_get(mi_option_reserve_os_memory); |
642 | 0 | if (ksize > 0) { |
643 | 0 | mi_reserve_os_memory((size_t)ksize*MI_KiB, true /* commit? */, true /* allow large pages? */); |
644 | 0 | } |
645 | 0 | } |
646 | 37 | } |
647 | | |
648 | | // Initialize the process; called by thread_init or the process loader |
649 | 111 | void mi_process_init(void) mi_attr_noexcept { |
650 | 111 | #if _MSC_VER < 1920 |
651 | 111 | mi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main |
652 | 111 | #endif |
653 | 111 | mi_atomic_do_once { |
654 | 37 | mi_process_init_once(); |
655 | 37 | } |
656 | 111 | } |
657 | | |
658 | | |
659 | | // Called when the process is done |
660 | 0 | static void mi_process_done_once(void) { |
661 | | // only shutdown if we were initialized |
662 | 0 | if (!_mi_process_is_initialized) return; |
663 | | // ensure we are called once |
664 | 0 | static bool process_done = false; |
665 | 0 | if (process_done) return; |
666 | 0 | process_done = true; |
667 | | |
668 | | // get the default heap so we don't need to acces thread locals anymore |
669 | 0 | mi_heap_t* heap = mi_prim_get_default_heap(); // use prim to not initialize any heap |
670 | 0 | mi_assert_internal(heap != NULL); |
671 | | |
672 | | // release any thread specific resources and ensure _mi_thread_done is called on all but the main thread |
673 | 0 | _mi_prim_thread_done_auto_done(); |
674 | | |
675 | |
|
676 | 0 | #ifndef MI_SKIP_COLLECT_ON_EXIT |
677 | 0 | #if (MI_DEBUG || !defined(MI_SHARED_LIB)) |
678 | | // free all memory if possible on process exit. This is not needed for a stand-alone process |
679 | | // but should be done if mimalloc is statically linked into another shared library which |
680 | | // is repeatedly loaded/unloaded, see issue #281. |
681 | 0 | mi_heap_collect(heap, true /* force */ ); |
682 | 0 | #endif |
683 | 0 | #endif |
684 | | |
685 | | // done with tracking tools |
686 | 0 | mi_track_done(); |
687 | | |
688 | | // Forcefully release all retained memory; this can be dangerous in general if overriding regular malloc/free |
689 | | // since after process_done there might still be other code running that calls `free` (like at_exit routines, |
690 | | // or C-runtime termination code. |
691 | 0 | if (mi_option_is_enabled(mi_option_destroy_on_exit)) { |
692 | 0 | mi_heap_collect(heap, true /* force */); |
693 | 0 | _mi_heap_unsafe_destroy_all(heap); // forcefully release all memory held by all heaps (of this thread only!) |
694 | 0 | _mi_arena_unsafe_destroy_all(); |
695 | 0 | _mi_segment_map_unsafe_destroy(); |
696 | 0 | } |
697 | |
|
698 | 0 | if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) { |
699 | 0 | mi_stats_print(NULL); |
700 | 0 | } |
701 | 0 | _mi_allocator_done(); |
702 | 0 | _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); |
703 | 0 | os_preloading = true; // don't call the C runtime anymore |
704 | 0 | } |
705 | | |
706 | | |
707 | | // Called when the process is done (cdecl as it is used with `at_exit` on some platforms) |
708 | 0 | void mi_cdecl mi_process_done(void) mi_attr_noexcept { |
709 | 0 | mi_atomic_do_once { |
710 | 0 | mi_process_done_once(); |
711 | 0 | } |
712 | 0 | } |
713 | | |
714 | 0 | void mi_cdecl _mi_auto_process_done(void) mi_attr_noexcept { |
715 | 0 | if (_mi_option_get_fast(mi_option_destroy_on_exit)>1) return; |
716 | 0 | mi_process_done(); |
717 | 0 | } |