Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}