Coverage Report

Created: 2026-10-31 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpython/Objects/mimalloc/page.c
Line
Count
Source
1
/*----------------------------------------------------------------------------
2
Copyright (c) 2018-2024, 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
8
/* -----------------------------------------------------------
9
  The core of the allocator. Every segment contains
10
  pages of a certain block size. The main function
11
  exported is `mi_malloc_generic`.
12
----------------------------------------------------------- */
13
14
#include "mimalloc.h"
15
#include "mimalloc/internal.h"
16
#include "mimalloc/atomic.h"
17
18
/* -----------------------------------------------------------
19
  Definition of page queues for each block size
20
----------------------------------------------------------- */
21
22
#define MI_IN_PAGE_C
23
#include "page-queue.c"
24
#undef MI_IN_PAGE_C
25
26
27
/* -----------------------------------------------------------
28
  Page helpers
29
----------------------------------------------------------- */
30
31
// Index a block in a page
32
0
static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) {
33
0
  MI_UNUSED(page);
34
0
  mi_assert_internal(page != NULL);
35
0
  mi_assert_internal(i <= page->reserved);
36
0
  return (mi_block_t*)((uint8_t*)page_start + (i * block_size));
37
0
}
38
39
static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld);
40
static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld);
41
42
#if (MI_DEBUG>=3)
43
static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) {
44
  size_t count = 0;
45
  while (head != NULL) {
46
    mi_assert_internal(page == _mi_ptr_page(head));
47
    count++;
48
    head = mi_block_next(page, head);
49
  }
50
  return count;
51
}
52
53
/*
54
// Start of the page available memory
55
static inline uint8_t* mi_page_area(const mi_page_t* page) {
56
  return _mi_page_start(_mi_page_segment(page), page, NULL);
57
}
58
*/
59
60
static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) {
61
  size_t psize;
62
  uint8_t* page_area = _mi_segment_page_start(_mi_page_segment(page), page, &psize);
63
  mi_block_t* start = (mi_block_t*)page_area;
64
  mi_block_t* end   = (mi_block_t*)(page_area + psize);
65
  while(p != NULL) {
66
    if (p < start || p >= end) return false;
67
    p = mi_block_next(page, p);
68
  }
69
#if MI_DEBUG>3 // generally too expensive to check this
70
  if (page->free_is_zero) {
71
    const size_t ubsize = mi_page_usable_block_size(page);
72
    for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) {
73
      mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));
74
    }
75
  }
76
#endif
77
  return true;
78
}
79
80
static bool mi_page_is_valid_init(mi_page_t* page) {
81
  mi_assert_internal(mi_page_block_size(page) > 0);
82
83
  mi_assert_internal(page->used <= page->capacity);
84
  mi_assert_internal(page->capacity <= page->reserved);
85
86
  // [specbot P-NEW-1] block_size_shift must be consistent with block_size (L1, O(1))
87
  mi_assert_internal(page->block_size_shift == 0 || (mi_page_block_size(page) == ((size_t)1 << page->block_size_shift)));
88
  // [specbot P-NEW-2] capacity must be nonzero when blocks are in use (L1, O(1))
89
  mi_assert_internal(page->used == 0 || page->capacity > 0);
90
  // [specbot P-NEW-4] page_start must be non-null when capacity > 0 (L1, O(1))
91
  mi_assert_internal(page->capacity == 0 || mi_page_start(page) != NULL);
92
93
  uint8_t* start = mi_page_start(page);
94
  mi_assert_internal(start == _mi_segment_page_start(_mi_page_segment(page), page, NULL));
95
  mi_assert_internal(page->is_huge == (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE));
96
  //mi_assert_internal(start + page->capacity*page->block_size == page->top);
97
98
  mi_assert_internal(mi_page_list_is_valid(page,page->free));
99
  mi_assert_internal(mi_page_list_is_valid(page,page->local_free));
100
101
  // [specbot P-NEW-7] All free list blocks are aligned to block_size (L2, O(n))
102
  {
103
    size_t bsize = mi_page_block_size(page);
104
    uint8_t* pstart = mi_page_start(page);
105
    for (mi_block_t* b = page->free; b != NULL; b = mi_block_next(page, b)) {
106
      mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
107
    }
108
    for (mi_block_t* b = page->local_free; b != NULL; b = mi_block_next(page, b)) {
109
      mi_assert_internal(((uint8_t*)b - pstart) % bsize == 0);
110
    }
111
  }
112
113
  #if MI_DEBUG>3 // generally too expensive to check this
114
  if (page->free_is_zero) {
115
    const size_t ubsize = mi_page_usable_block_size(page);
116
    for(mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) {
117
      mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t)));
118
    }
119
  }
120
  #endif
121
122
  #if !MI_TRACK_ENABLED && !MI_TSAN
123
  mi_block_t* tfree = mi_page_thread_free(page);
124
  mi_assert_internal(mi_page_list_is_valid(page, tfree));
125
  //size_t tfree_count = mi_page_list_count(page, tfree);
126
  //mi_assert_internal(tfree_count <= page->thread_freed + 1);
127
  #endif
128
129
  size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free);
130
  mi_assert_internal(page->used + free_count == page->capacity);
131
132
  return true;
133
}
134
135
extern mi_decl_hidden bool _mi_process_is_initialized;             // has mi_process_init been called?
136
137
bool _mi_page_is_valid(mi_page_t* page) {
138
  mi_assert_internal(mi_page_is_valid_init(page));
139
  #if MI_SECURE
140
  mi_assert_internal(page->keys[0] != 0);
141
  #endif
142
  if (mi_page_heap(page)!=NULL) {
143
    mi_segment_t* segment = _mi_page_segment(page);
144
145
    // [specbot P-NEW-6] heap_tag must match owning heap's tag (L1, O(1))
146
    mi_assert_internal(page->heap_tag == mi_page_heap(page)->tag);
147
148
    mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id);
149
    #if MI_HUGE_PAGE_ABANDON
150
    if (segment->kind != MI_SEGMENT_HUGE)
151
    #endif
152
    {
153
      mi_page_queue_t* pq = mi_page_queue_of(page);
154
      mi_assert_internal(mi_page_queue_contains(pq, page));
155
      mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page));
156
      mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq));
157
    }
158
  }
159
  return true;
160
}
161
#endif
162
163
0
void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {
164
0
  size_t ticks = 0;
165
0
  while (!_mi_page_try_use_delayed_free(page, delay, override_never)) {
166
0
    mi_atomic_yield_sleep(&ticks,10);
167
0
  }
168
0
}
169
170
0
bool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) {
171
0
  mi_thread_free_t tfreex;
172
0
  mi_delayed_t     old_delay;
173
0
  mi_thread_free_t tfree;
174
0
  size_t yield_count = 0;
175
0
  do {
176
0
    tfree = mi_atomic_load_acquire(&page->xthread_free); // note: must acquire as we can break/repeat this loop and not do a CAS;
177
0
    tfreex = mi_tf_set_delayed(tfree, delay);
178
0
    old_delay = mi_tf_delayed(tfree);
179
0
    if mi_unlikely(old_delay == MI_DELAYED_FREEING) {
180
0
      if (yield_count >= 4) return false;  // give up after 4 tries
181
0
      yield_count++;
182
0
      mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done.
183
      // tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail
184
0
    }
185
0
    else if (delay == old_delay) {
186
0
      break; // avoid atomic operation if already equal
187
0
    }
188
0
    else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) {
189
0
      break; // leave never-delayed flag set
190
0
    }
191
0
  } while ((old_delay == MI_DELAYED_FREEING) ||
192
0
           !mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex));
193
194
0
  return true; // success
195
0
}
196
197
/* -----------------------------------------------------------
198
  Page collect the `local_free` and `thread_free` lists
199
----------------------------------------------------------- */
200
201
// Collect the local `thread_free` list using an atomic exchange.
202
// Note: The exchange must be done atomically as this is used right after
203
// moving to the full list in `mi_page_collect_ex` and we need to
204
// ensure that there was no race where the page became unfull just before the move.
205
static void _mi_page_thread_free_collect(mi_page_t* page)
206
0
{
207
0
  mi_block_t* head;
208
0
  mi_thread_free_t tfreex;
209
0
  mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free);
210
0
  do {
211
0
    head = mi_tf_block(tfree);
212
0
    tfreex = mi_tf_set_block(tfree,NULL);
213
0
  } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex));
214
215
  // return if the list is empty
216
0
  if (head == NULL) return;
217
218
  // find the tail -- also to get a proper count (without data races)
219
0
  size_t max_count = page->capacity; // cannot collect more than capacity
220
0
  size_t count = 1;
221
0
  mi_block_t* tail = head;
222
0
  mi_block_t* next;
223
0
  while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) {
224
0
    count++;
225
0
    tail = next;
226
0
  }
227
  // if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free)
228
0
  if mi_unlikely(count > max_count) {
229
0
    _mi_error_message(EFAULT, "corrupted thread-free list (possibly due to a cross-thread double free)\n");
230
0
    return; // the thread-free items cannot be freed
231
0
  }
232
  // if `count > page->used` there was another kind memory corruption (either in the page meta-data or in the linked list)
233
0
  else if mi_unlikely(count > page->used) {
234
0
    _mi_error_message(EFAULT, "corrupted meta-data in thread-free list\n");
235
0
    return; // the thread-free items cannot be freed
236
0
  }
237
238
  // and append the current local free list
239
0
  mi_block_set_next(page,tail, page->local_free);
240
0
  page->local_free = head;
241
242
  // update counts now
243
0
  mi_assert_internal(count <= UINT16_MAX);
244
0
  mi_assert_internal(page->used >= (uint16_t)count);
245
0
  page->used = page->used - (uint16_t)count;
246
247
0
  if (page->used == 0) {
248
    // CPython: the page may have had a QSBR goal set from a previous point when it
249
    // was all-free. That goal is no longer valid because the page was
250
    // allocated from and then freed again by other threads.
251
0
    _PyMem_mi_page_clear_qsbr(page);
252
0
  }
253
0
}
254
255
0
void _mi_page_free_collect(mi_page_t* page, bool force) {
256
0
  mi_assert_internal(page!=NULL);
257
258
  // collect the thread free list
259
0
  if (force || mi_page_thread_free(page) != NULL) {  // quick test to avoid an atomic operation
260
0
    _mi_page_thread_free_collect(page);
261
0
  }
262
263
  // and the local free list
264
0
  if (page->local_free != NULL) {
265
0
    if mi_likely(page->free == NULL) {
266
      // usual case
267
0
      page->free = page->local_free;
268
0
      page->local_free = NULL;
269
0
      page->free_is_zero = false;
270
0
    }
271
0
    else if (force) {
272
      // append -- only on shutdown (force) as this is a linear operation
273
0
      mi_block_t* tail = page->local_free;
274
0
      mi_block_t* next;
275
0
      while ((next = mi_block_next(page, tail)) != NULL) {
276
0
        tail = next;
277
0
      }
278
0
      mi_block_set_next(page, tail, page->free);
279
0
      page->free = page->local_free;
280
0
      page->local_free = NULL;
281
0
      page->free_is_zero = false;
282
0
    }
283
0
  }
284
285
0
  mi_assert_internal(!force || page->local_free == NULL);
286
0
}
287
288
289
290
/* -----------------------------------------------------------
291
  Page fresh and retire
292
----------------------------------------------------------- */
293
294
// called from segments when reclaiming abandoned pages
295
0
void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {
296
0
  mi_assert_expensive(mi_page_is_valid_init(page));
297
298
0
  mi_assert_internal(mi_page_heap(page) == heap);
299
0
  mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE);
300
  #if MI_HUGE_PAGE_ABANDON
301
  mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
302
  #endif
303
304
  // TODO: push on full queue immediately if it is full?
305
0
  mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page));
306
0
  mi_page_queue_push(heap, pq, page);
307
0
  _PyMem_mi_page_reclaimed(page);
308
0
  mi_assert_expensive(_mi_page_is_valid(page));
309
0
}
310
311
// allocate a fresh page from a segment
312
0
static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size, size_t page_alignment) {
313
0
  #if !MI_HUGE_PAGE_ABANDON
314
0
  mi_assert_internal(pq != NULL);
315
0
  mi_assert_internal(mi_heap_contains_queue(heap, pq));
316
0
  mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size);
317
0
  #endif
318
0
  mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments);
319
0
  if (page == NULL) {
320
    // this may be out-of-memory, or an abandoned page was reclaimed (and in our queue)
321
0
    return NULL;
322
0
  }
323
  #if MI_HUGE_PAGE_ABANDON
324
  mi_assert_internal(pq==NULL || _mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
325
  #endif
326
0
  mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
327
0
  mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size);
328
  // a fresh page was found, initialize it
329
0
  const size_t full_block_size = (pq == NULL || mi_page_is_huge(page) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc
330
0
  mi_assert_internal(full_block_size >= block_size);
331
0
  mi_page_init(heap, page, full_block_size, heap->tld);
332
0
  _mi_stat_increase(&heap->tld->stats.pages, 1);
333
0
  _mi_stat_increase(&heap->tld->stats.page_bins[_mi_page_stats_bin(page)], 1);
334
0
  if (pq != NULL) { mi_page_queue_push(heap, pq, page); }
335
0
  mi_assert_expensive(_mi_page_is_valid(page));
336
0
  return page;
337
0
}
338
339
// Get a fresh page to use
340
0
static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) {
341
0
  mi_assert_internal(mi_heap_contains_queue(heap, pq));
342
0
  mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size, 0);
343
0
  if (page==NULL) return NULL;
344
0
  mi_assert_internal(pq->block_size==mi_page_block_size(page));
345
0
  mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page)));
346
0
  return page;
347
0
}
348
349
/* -----------------------------------------------------------
350
   Do any delayed frees
351
   (put there by other threads if they deallocated in a full page)
352
----------------------------------------------------------- */
353
0
void _mi_heap_delayed_free_all(mi_heap_t* heap) {
354
0
  size_t ticks = 0;
355
0
  while (!_mi_heap_delayed_free_partial(heap)) {
356
0
    mi_atomic_yield_sleep(&ticks,10);
357
0
  }
358
0
}
359
360
// returns true if all delayed frees were processed
361
0
bool _mi_heap_delayed_free_partial(mi_heap_t* heap) {
362
  // take over the list (note: no atomic exchange since it is often NULL)
363
0
  mi_block_t* block = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);
364
0
  while (block != NULL && !mi_atomic_cas_ptr_weak_acq_rel(mi_block_t, &heap->thread_delayed_free, &block, NULL)) { /* nothing */ };
365
0
  bool all_freed = true;
366
367
  // and free them all
368
0
  while(block != NULL) {
369
0
    mi_block_t* next = mi_block_nextx(heap,block, heap->keys);
370
    // use internal free instead of regular one to keep stats etc correct
371
0
    if (!_mi_free_delayed_block(block)) {
372
      // we might already start delayed freeing while another thread has not yet
373
      // reset the delayed_freeing flag; in that case delay it further by reinserting the current block
374
      // into the delayed free list
375
0
      all_freed = false;
376
0
      mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free);
377
0
      do {
378
0
        mi_block_set_nextx(heap, block, dfree, heap->keys);
379
0
      } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block));
380
0
    }
381
0
    block = next;
382
0
  }
383
0
  return all_freed;
384
0
}
385
386
/* -----------------------------------------------------------
387
  Unfull, abandon, free and retire
388
----------------------------------------------------------- */
389
390
// Move a page from the full list back to a regular list
391
0
void _mi_page_unfull(mi_page_t* page) {
392
0
  mi_assert_internal(page != NULL);
393
0
  mi_assert_expensive(_mi_page_is_valid(page));
394
0
  mi_assert_internal(mi_page_is_in_full(page));
395
0
  if (!mi_page_is_in_full(page)) return;
396
397
0
  mi_heap_t* heap = mi_page_heap(page);
398
0
  mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL];
399
0
  mi_page_set_in_full(page, false); // to get the right queue
400
0
  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
401
0
  mi_page_set_in_full(page, true);
402
0
  mi_page_queue_enqueue_from_full(pq, pqfull, page);
403
0
}
404
405
0
static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) {
406
0
  mi_assert_internal(pq == mi_page_queue_of(page));
407
0
  mi_assert_internal(!mi_page_immediate_available(page));
408
0
  mi_assert_internal(!mi_page_is_in_full(page));
409
410
0
  if (mi_page_is_in_full(page)) return;
411
0
  mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page);
412
0
  _mi_page_free_collect(page,false);  // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set
413
0
}
414
415
416
// Abandon a page with used blocks at the end of a thread.
417
// Note: only call if it is ensured that no references exist from
418
// the `page->heap->thread_delayed_free` into this page.
419
// Currently only called through `mi_heap_collect_ex` which ensures this.
420
0
void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) {
421
0
  mi_assert_internal(page != NULL);
422
0
  mi_assert_expensive(_mi_page_is_valid(page));
423
0
  mi_assert_internal(pq == mi_page_queue_of(page));
424
0
  mi_assert_internal(mi_page_heap(page) != NULL);
425
426
0
  mi_heap_t* pheap = mi_page_heap(page);
427
428
#ifdef Py_GIL_DISABLED
429
  if (page->qsbr_node.next != NULL) {
430
    // CPython: remove from the QSBR queue, but keep the goal
431
    llist_remove(&page->qsbr_node);
432
  }
433
#endif
434
435
  // remove from our page list
436
0
  mi_segments_tld_t* segments_tld = &pheap->tld->segments;
437
0
  mi_page_queue_remove(pq, page);
438
439
  // page is no longer associated with our heap
440
0
  mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
441
0
  mi_page_set_heap(page, NULL);
442
443
#if (MI_DEBUG>1) && !MI_TRACK_ENABLED
444
  // check there are no references left..
445
  for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) {
446
    mi_assert_internal(_mi_ptr_page(block) != page);
447
  }
448
#endif
449
450
  // and abandon it
451
0
  mi_assert_internal(mi_page_heap(page) == NULL);
452
0
  _mi_segment_page_abandon(page,segments_tld);
453
0
}
454
455
// force abandon a page
456
0
void _mi_page_force_abandon(mi_page_t* page) {
457
0
  mi_heap_t* heap = mi_page_heap(page);
458
  // mark page as not using delayed free
459
0
  _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false);
460
461
  // ensure this page is no longer in the heap delayed free list
462
0
  _mi_heap_delayed_free_all(heap);
463
  // We can still access the page meta-info even if it is freed as we ensure
464
  // in `mi_segment_force_abandon` that the segment is not freed (yet)
465
0
  if (page->capacity == 0) return; // it may have been freed now
466
467
  // and now unlink it from the page queue and abandon (or free)
468
0
  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
469
0
  if (mi_page_all_free(page)) {
470
    // CPython: keep the page if it is still waiting for QSBR.
471
0
    if (!_PyMem_mi_page_maybe_free(page, pq, false)) {
472
0
      pq = mi_heap_page_queue_of(heap, page);
473
0
      _mi_page_abandon(page, pq);
474
0
    }
475
0
  }
476
0
  else {
477
0
    _mi_page_abandon(page, pq);
478
0
  }
479
0
}
480
481
482
// Free a page with no more free blocks
483
0
void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) {
484
0
  mi_assert_internal(page != NULL);
485
0
  mi_assert_expensive(_mi_page_is_valid(page));
486
0
  mi_assert_internal(pq == mi_page_queue_of(page));
487
0
  mi_assert_internal(mi_page_all_free(page));
488
0
  mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING);
489
490
  // no more aligned blocks in here
491
0
  mi_page_set_has_aligned(page, false);
492
493
0
  mi_heap_t* heap = mi_page_heap(page);
494
495
#ifdef Py_GIL_DISABLED
496
  mi_assert_internal(page->qsbr_goal == 0);
497
  mi_assert_internal(page->qsbr_node.next == NULL);
498
#endif
499
500
  // remove from the page list
501
  // (no need to do _mi_heap_delayed_free first as all blocks are already free)
502
0
  mi_segments_tld_t* segments_tld = &heap->tld->segments;
503
0
  mi_page_queue_remove(pq, page);
504
505
  // and free it
506
0
  mi_page_set_heap(page,NULL);
507
0
  _mi_segment_page_free(page, force, segments_tld);
508
0
}
509
510
0
#define MI_RETIRE_CYCLES      (16)
511
512
// Retire a page with no more used blocks
513
// Important to not retire too quickly though as new
514
// allocations might coming.
515
// Note: called from `mi_free` and benchmarks often
516
// trigger this due to freeing everything and then
517
// allocating again so careful when changing this.
518
0
void _mi_page_retire(mi_page_t* page) mi_attr_noexcept {
519
0
  mi_assert_internal(page != NULL);
520
0
  mi_assert_expensive(_mi_page_is_valid(page));
521
0
  mi_assert_internal(mi_page_all_free(page));
522
523
0
  mi_page_set_has_aligned(page, false);
524
525
  // CPython: any previous QSBR goals are no longer valid because we reused the page
526
0
  _PyMem_mi_page_clear_qsbr(page);
527
528
  // don't retire too often..
529
  // (or we end up retiring and re-allocating most of the time)
530
  // NOTE: refine this more: we should not retire if this
531
  // is the only page left with free blocks. It is not clear
532
  // how to check this efficiently though...
533
  // for now, we don't retire if it is the only page left of this size class.
534
0
  mi_page_queue_t* pq = mi_page_queue_of(page);
535
0
  #if MI_RETIRE_CYCLES > 0
536
0
  const size_t bsize = mi_page_block_size(page);
537
0
  if mi_likely( /* bsize < MI_MAX_RETIRE_SIZE && */ !mi_page_queue_is_special(pq)) {  // not full or huge queue?
538
0
    if (pq->last==page && pq->first==page) { // the only page in the queue?
539
0
      mi_stat_counter_increase(_mi_stats_main.pages_retire,1);
540
0
      page->retire_expire = (bsize <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4);
541
0
      mi_heap_t* heap = mi_page_heap(page);
542
0
      mi_assert_internal(pq >= heap->pages);
543
0
      const size_t index = pq - heap->pages;
544
0
      mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE);
545
0
      if (index < heap->page_retired_min) heap->page_retired_min = index;
546
0
      if (index > heap->page_retired_max) heap->page_retired_max = index;
547
0
      mi_assert_internal(mi_page_all_free(page));
548
0
      return; // don't free after all
549
0
    }
550
0
  }
551
0
  #endif
552
0
  _PyMem_mi_page_maybe_free(page, pq, false);
553
0
}
554
555
// free retired pages: we don't need to look at the entire queues
556
// since we only retire pages that are at the head position in a queue.
557
0
void _mi_heap_collect_retired(mi_heap_t* heap, bool force) {
558
0
  size_t min = MI_BIN_FULL;
559
0
  size_t max = 0;
560
0
  for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) {
561
0
    mi_page_queue_t* pq   = &heap->pages[bin];
562
0
    mi_page_t*       page = pq->first;
563
0
    if (page != NULL && page->retire_expire != 0) {
564
0
      if (mi_page_all_free(page)) {
565
0
        page->retire_expire--;
566
0
        if (force || page->retire_expire == 0) {
567
#ifdef Py_GIL_DISABLED
568
          mi_assert_internal(page->qsbr_goal == 0);
569
#endif
570
0
          _PyMem_mi_page_maybe_free(page, pq, force);
571
0
        }
572
0
        else {
573
          // keep retired, update min/max
574
0
          if (bin < min) min = bin;
575
0
          if (bin > max) max = bin;
576
0
        }
577
0
      }
578
0
      else {
579
0
        page->retire_expire = 0;
580
0
      }
581
0
    }
582
0
  }
583
0
  heap->page_retired_min = min;
584
0
  heap->page_retired_max = max;
585
0
}
586
587
588
/* -----------------------------------------------------------
589
  Initialize the initial free list in a page.
590
  In secure mode we initialize a randomized list by
591
  alternating between slices.
592
----------------------------------------------------------- */
593
594
0
#define MI_MAX_SLICE_SHIFT  (6)   // at most 64 slices
595
#define MI_MAX_SLICES       (1UL << MI_MAX_SLICE_SHIFT)
596
0
#define MI_MIN_SLICES       (2)
597
598
0
static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) {
599
0
  MI_UNUSED(stats);
600
0
  #if (MI_SECURE < 2)
601
0
  mi_assert_internal(page->free == NULL);
602
0
  mi_assert_internal(page->local_free == NULL);
603
0
  #endif
604
0
  mi_assert_internal(page->capacity + extend <= page->reserved);
605
0
  mi_assert_internal(bsize == mi_page_block_size(page));
606
0
  void* const page_area = mi_page_start(page);
607
608
  // initialize a randomized free list
609
  // set up `slice_count` slices to alternate between
610
0
  size_t shift = MI_MAX_SLICE_SHIFT;
611
0
  while ((extend >> shift) == 0) {
612
0
    shift--;
613
0
  }
614
0
  const size_t slice_count = (size_t)1U << shift;
615
0
  const size_t slice_extend = extend / slice_count;
616
0
  mi_assert_internal(slice_extend >= 1);
617
0
  mi_block_t* blocks[MI_MAX_SLICES];   // current start of the slice
618
0
  size_t      counts[MI_MAX_SLICES];   // available objects in the slice
619
0
  for (size_t i = 0; i < slice_count; i++) {
620
0
    blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend);
621
0
    counts[i] = slice_extend;
622
0
  }
623
0
  counts[slice_count-1] += (extend % slice_count);  // final slice holds the modulus too (todo: distribute evenly?)
624
625
  // and initialize the free list by randomly threading through them
626
  // set up first element
627
0
  const uintptr_t r = _mi_heap_random_next(heap);
628
0
  size_t current = r % slice_count;
629
0
  counts[current]--;
630
0
  mi_block_t* const free_start = blocks[current];
631
  // and iterate through the rest; use `random_shuffle` for performance
632
0
  uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0
633
0
  for (size_t i = 1; i < extend; i++) {
634
    // call random_shuffle only every INTPTR_SIZE rounds
635
0
    const size_t round = i%MI_INTPTR_SIZE;
636
0
    if (round == 0) rnd = _mi_random_shuffle(rnd);
637
    // select a random next slice index
638
0
    size_t next = ((rnd >> 8*round) & (slice_count-1));
639
0
    while (counts[next]==0) {                            // ensure it still has space
640
0
      next++;
641
0
      if (next==slice_count) next = 0;
642
0
    }
643
    // and link the current block to it
644
0
    counts[next]--;
645
0
    mi_block_t* const block = blocks[current];
646
0
    blocks[current] = (mi_block_t*)((uint8_t*)block + bsize);  // bump to the following block
647
0
    mi_block_set_next(page, block, blocks[next]);   // and set next; note: we may have `current == next`
648
0
    current = next;
649
0
  }
650
  // prepend to the free list (usually NULL)
651
0
  mi_block_set_next(page, blocks[current], page->free);  // end of the list
652
0
  page->free = free_start;
653
0
}
654
655
static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats)
656
0
{
657
0
  MI_UNUSED(stats);
658
0
  #if (MI_SECURE < 2)
659
0
  mi_assert_internal(page->free == NULL);
660
0
  mi_assert_internal(page->local_free == NULL);
661
0
  #endif
662
0
  mi_assert_internal(page->capacity + extend <= page->reserved);
663
0
  mi_assert_internal(bsize == mi_page_block_size(page));
664
0
  void* const page_area = mi_page_start(page);
665
666
0
  mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity);
667
668
  // initialize a sequential free list
669
0
  mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1);
670
0
  mi_block_t* block = start;
671
0
  while(block <= last) {
672
0
    mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize);
673
0
    mi_block_set_next(page,block,next);
674
0
    block = next;
675
0
  }
676
  // prepend to free list (usually `NULL`)
677
0
  mi_block_set_next(page, last, page->free);
678
0
  page->free = start;
679
0
}
680
681
/* -----------------------------------------------------------
682
  Page initialize and extend the capacity
683
----------------------------------------------------------- */
684
685
0
#define MI_MAX_EXTEND_SIZE    (4*1024)      // heuristic, one OS page seems to work well.
686
#if (MI_SECURE>=2)
687
#define MI_MIN_EXTEND         (8*MI_SECURE) // extend at least by this many blocks
688
#else
689
0
#define MI_MIN_EXTEND         (4)
690
#endif
691
692
// Extend the capacity (up to reserved) by initializing a free list
693
// We do at most `MI_MAX_EXTEND` to avoid touching too much memory
694
// Note: we also experimented with "bump" allocation on the first
695
// allocations but this did not speed up any benchmark (due to an
696
// extra test in malloc? or cache effects?)
697
0
static bool mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) {
698
0
  mi_assert_expensive(mi_page_is_valid_init(page));
699
0
  #if (MI_SECURE < 2)
700
0
  mi_assert(page->free == NULL);
701
0
  mi_assert(page->local_free == NULL);
702
0
  if (page->free != NULL) return true;
703
0
  #endif
704
0
  if (page->capacity >= page->reserved) return true;
705
706
0
  mi_stat_counter_increase(tld->stats.pages_extended, 1);
707
708
  // calculate the extend count
709
0
  const size_t bsize = mi_page_block_size(page);
710
0
  size_t extend = page->reserved - page->capacity;
711
0
  mi_assert_internal(extend > 0);
712
713
0
  size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/bsize);
714
0
  if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; }
715
0
  mi_assert_internal(max_extend > 0);
716
717
0
  if (extend > max_extend) {
718
    // ensure we don't touch memory beyond the page to reduce page commit.
719
    // the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%.
720
0
    extend = max_extend;
721
0
  }
722
723
0
  mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved);
724
0
  mi_assert_internal(extend < (1UL<<16));
725
726
  // and append the extend the free list
727
0
  if (extend < MI_MIN_SLICES || MI_SECURE<2) { //!mi_option_is_enabled(mi_option_secure)) {
728
0
    mi_page_free_list_extend(page, bsize, extend, &tld->stats );
729
0
  }
730
0
  else {
731
0
    mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats);
732
0
  }
733
  // enable the new free list
734
0
  page->capacity += (uint16_t)extend;
735
0
  _mi_stat_increase(&tld->stats.page_committed, extend * bsize);
736
0
  mi_assert_expensive(mi_page_is_valid_init(page));
737
0
  return true;
738
0
}
739
740
// Initialize a fresh page
741
0
static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) {
742
0
  mi_assert(page != NULL);
743
0
  mi_segment_t* segment = _mi_page_segment(page);
744
0
  mi_assert(segment != NULL);
745
0
  mi_assert_internal(block_size > 0);
746
  // set fields
747
0
  mi_page_set_heap(page, heap);
748
0
  page->use_qsbr = heap->page_use_qsbr;        // CPython
749
0
  page->debug_offset = heap->debug_offset;     // CPython
750
0
  page->block_size = block_size;
751
0
  size_t page_size;
752
0
  page->page_start = _mi_segment_page_start(segment, page, &page_size);
753
0
  mi_track_mem_noaccess(page->page_start,page_size);
754
0
  mi_assert_internal(mi_page_block_size(page) <= page_size);
755
0
  mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE);
756
0
  mi_assert_internal(page_size / block_size < (1L<<16));
757
0
  page->reserved = (uint16_t)(page_size / block_size);
758
0
  mi_assert_internal(page->reserved > 0);
759
  #if (MI_PADDING || MI_ENCODE_FREELIST)
760
  page->keys[0] = _mi_heap_random_next(heap);
761
  page->keys[1] = _mi_heap_random_next(heap);
762
  #endif
763
0
  page->free_is_zero = page->is_zero_init;
764
  #if MI_DEBUG>2
765
  if (page->is_zero_init) {
766
    mi_track_mem_defined(page->page_start, page_size);
767
    mi_assert_expensive(mi_mem_is_zero(page->page_start, page_size));
768
  }
769
  #endif
770
0
  mi_assert_internal(page->is_committed);
771
0
  if (block_size > 0 && _mi_is_power_of_two(block_size)) {
772
0
    page->block_size_shift = (uint8_t)(mi_ctz((uintptr_t)block_size));
773
0
  }
774
0
  else {
775
0
    page->block_size_shift = 0;
776
0
  }
777
778
0
  mi_assert_internal(page->capacity == 0);
779
0
  mi_assert_internal(page->free == NULL);
780
0
  mi_assert_internal(page->used == 0);
781
0
  mi_assert_internal(page->xthread_free == 0);
782
0
  mi_assert_internal(page->next == NULL);
783
0
  mi_assert_internal(page->prev == NULL);
784
#ifdef Py_GIL_DISABLED
785
  mi_assert_internal(page->qsbr_goal == 0);
786
  mi_assert_internal(page->qsbr_node.next == NULL);
787
#endif
788
0
  mi_assert_internal(page->retire_expire == 0);
789
0
  mi_assert_internal(!mi_page_has_aligned(page));
790
  #if (MI_PADDING || MI_ENCODE_FREELIST)
791
  mi_assert_internal(page->keys[0] != 0);
792
  mi_assert_internal(page->keys[1] != 0);
793
  #endif
794
0
  mi_assert_internal(page->block_size_shift == 0 || (block_size == ((size_t)1 << page->block_size_shift)));
795
0
  mi_assert_expensive(mi_page_is_valid_init(page));
796
797
  // initialize an initial free list
798
0
  if (mi_page_extend_free(heap,page,tld)) {
799
0
    mi_assert(mi_page_immediate_available(page));
800
0
  }
801
0
  return;
802
0
}
803
804
805
/* -----------------------------------------------------------
806
  Find pages with free blocks
807
-------------------------------------------------------------*/
808
809
// search for a best next page to use for at most N pages (often cut short if immediate blocks are available)
810
0
#define MI_MAX_CANDIDATE_SEARCH  (4)
811
812
// is the page not yet used up to its reserved space?
813
0
static bool mi_page_is_expandable(const mi_page_t* page) {
814
0
  mi_assert_internal(page != NULL);
815
0
  mi_assert_internal(page->capacity <= page->reserved);
816
0
  return (page->capacity < page->reserved);
817
0
}
818
819
820
// Find a page with free blocks of `page->block_size`.
821
static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try)
822
0
{
823
  // search through the pages in "next fit" order
824
  #if MI_STAT
825
  size_t count = 0;
826
  #endif
827
0
  size_t candidate_count = 0;        // we reset this on the first candidate to limit the search
828
0
  mi_page_t* page_candidate = NULL;  // a page with free space
829
0
  mi_page_t* page = pq->first;
830
831
0
  while (page != NULL)
832
0
  {
833
0
    mi_page_t* next = page->next; // remember next
834
    #if MI_STAT
835
    count++;
836
    #endif
837
0
    candidate_count++;
838
839
    // collect freed blocks by us and other threads
840
0
    _mi_page_free_collect(page, false);
841
842
0
  #if MI_MAX_CANDIDATE_SEARCH > 1
843
    // search up to N pages for a best candidate
844
845
    // is the local free list non-empty?
846
0
    const bool immediate_available = mi_page_immediate_available(page);
847
848
    // if the page is completely full, move it to the `mi_pages_full`
849
    // queue so we don't visit long-lived pages too often.
850
0
    if (!immediate_available && !mi_page_is_expandable(page)) {
851
0
      mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
852
0
      mi_page_to_full(page, pq);
853
0
    }
854
0
    else {
855
      // the page has free space, make it a candidate
856
      // we prefer non-expandable pages with high usage as candidates (to reduce commit, and increase chances of free-ing up pages)
857
0
      if (page_candidate == NULL) {
858
0
        page_candidate = page;
859
0
        candidate_count = 0;
860
0
      }
861
      // prefer to reuse fuller pages (in the hope the less used page gets freed)
862
0
      else if (page->used >= page_candidate->used && !mi_page_is_mostly_used(page) && !mi_page_is_expandable(page)) {
863
0
        page_candidate = page;
864
0
      }
865
      // if we find a non-expandable candidate, or searched for N pages, return with the best candidate
866
0
      if (immediate_available || candidate_count > MI_MAX_CANDIDATE_SEARCH) {
867
0
        mi_assert_internal(page_candidate!=NULL);
868
0
        break;
869
0
      }
870
0
    }
871
  #else
872
    // first-fit algorithm
873
    // If the page contains free blocks, we are done
874
    if (mi_page_immediate_available(page) || mi_page_is_expandable(page)) {
875
      break;  // pick this one
876
    }
877
878
    // If the page is completely full, move it to the `mi_pages_full`
879
    // queue so we don't visit long-lived pages too often.
880
    mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page));
881
    mi_page_to_full(page, pq);
882
  #endif
883
884
0
    page = next;
885
0
  } // for each page
886
887
0
  mi_heap_stat_counter_increase(heap, page_searches, count);
888
0
  mi_heap_stat_counter_increase(heap, page_searches_count, 1);
889
890
  // set the page to the best candidate
891
0
  if (page_candidate != NULL) {
892
0
    page = page_candidate;
893
0
  }
894
0
  if (page != NULL) {
895
0
    if (!mi_page_immediate_available(page)) {
896
0
      mi_assert_internal(mi_page_is_expandable(page));
897
0
      if (!mi_page_extend_free(heap, page, heap->tld)) {
898
0
        page = NULL; // failed to extend
899
0
      }
900
0
    }
901
0
    mi_assert_internal(page == NULL || mi_page_immediate_available(page));
902
0
  }
903
904
0
  if (page == NULL) {
905
0
    _PyMem_mi_heap_collect_qsbr(heap); // CPython: some pages might be safe to free now
906
0
    _mi_heap_collect_retired(heap, false); // perhaps make a page available?
907
0
    page = mi_page_fresh(heap, pq);
908
0
    if (page == NULL && first_try) {
909
      // out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again
910
0
      page = mi_page_queue_find_free_ex(heap, pq, false);
911
0
    }
912
0
  }
913
0
  else {
914
    // move the page to the front of the queue
915
0
    mi_page_queue_move_to_front(heap, pq, page);
916
0
    page->retire_expire = 0;
917
0
    _PyMem_mi_page_clear_qsbr(page);
918
    // _mi_heap_collect_retired(heap, false); // update retire counts; note: increases rss on MemoryLoad bench so don't do this
919
0
  }
920
0
  mi_assert_internal(page == NULL || mi_page_immediate_available(page));
921
922
923
0
  return page;
924
0
}
925
926
927
928
// Find a page with free blocks of `size`.
929
0
static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) {
930
0
  mi_page_queue_t* pq = mi_page_queue(heap, size);
931
932
  // check the first page: we even do this with candidate search or otherwise we re-search every time
933
0
  mi_page_t* page = pq->first;
934
0
  if (page != NULL) {
935
   #if (MI_SECURE>=2) // in secure mode, we extend half the time to increase randomness
936
    if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) {
937
      mi_page_extend_free(heap, page, heap->tld);
938
      mi_assert_internal(mi_page_immediate_available(page));
939
    }
940
    else
941
   #endif
942
0
    {
943
0
      _mi_page_free_collect(page,false);
944
0
    }
945
946
0
    if (mi_page_immediate_available(page)) {
947
0
      page->retire_expire = 0;
948
0
      _PyMem_mi_page_clear_qsbr(page);
949
0
      return page; // fast path
950
0
    }
951
0
  }
952
953
0
  return mi_page_queue_find_free_ex(heap, pq, true);
954
0
}
955
956
957
/* -----------------------------------------------------------
958
  Users can register a deferred free function called
959
  when the `free` list is empty. Since the `local_free`
960
  is separate this is deterministically called after
961
  a certain number of allocations.
962
----------------------------------------------------------- */
963
964
// The program should only install a single deferred free handler before doing allocation.
965
static _Atomic(void*) deferred_free; // is `mi_deferred_free_fun*` (but some platforms don't support atomic function pointers)
966
static _Atomic(void*) deferred_arg;
967
968
0
void _mi_deferred_free(mi_heap_t* heap, bool force) {
969
0
  heap->tld->heartbeat++;
970
0
  mi_deferred_free_fun* const fun = (mi_deferred_free_fun*)mi_atomic_load_ptr_acquire(void,&deferred_free);
971
0
  if (fun != NULL && !heap->tld->recurse) {
972
0
    heap->tld->recurse = true;
973
0
    void* const arg = mi_atomic_load_ptr_acquire(void,&deferred_arg);
974
0
    fun(force, heap->tld->heartbeat, arg);
975
0
    heap->tld->recurse = false;
976
0
  }
977
0
}
978
979
0
void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept {
980
0
  mi_atomic_store_ptr_release(void,&deferred_arg, arg);
981
0
  mi_atomic_store_ptr_release(void,&deferred_free, (void*)fn);
982
0
}
983
984
985
/* -----------------------------------------------------------
986
  General allocation
987
----------------------------------------------------------- */
988
989
// Large and huge page allocation.
990
// Huge pages contain just one block, and the segment contains just that page (as `MI_SEGMENT_HUGE`).
991
// Huge pages are also use if the requested alignment is very large (> MI_BLOCK_ALIGNMENT_MAX)
992
// so their size is not always `> MI_LARGE_OBJ_SIZE_MAX`.
993
0
static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) {
994
0
  size_t block_size = _mi_os_good_alloc_size(size);
995
0
  mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0);
996
0
  bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX || page_alignment > 0);
997
  #if MI_HUGE_PAGE_ABANDON
998
  mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size));
999
  #else
1000
0
  mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_LARGE_OBJ_SIZE_MAX+1 : block_size);
1001
0
  mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq));
1002
0
  #endif
1003
0
  mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment);
1004
0
  if (page != NULL) {
1005
0
    mi_assert_internal(mi_page_immediate_available(page));
1006
1007
0
    if (is_huge) {
1008
0
      mi_assert_internal(mi_page_is_huge(page));
1009
0
      mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE);
1010
0
      mi_assert_internal(_mi_page_segment(page)->used==1);
1011
      #if MI_HUGE_PAGE_ABANDON
1012
      mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue
1013
      mi_page_set_heap(page, NULL);
1014
      #endif
1015
0
    }
1016
0
    else {
1017
0
      mi_assert_internal(!mi_page_is_huge(page));
1018
0
    }
1019
1020
0
    const size_t bsize = mi_page_usable_block_size(page);  // note: not `mi_page_block_size` to account for padding
1021
    /*if (bsize <= MI_LARGE_OBJ_SIZE_MAX) {
1022
      mi_heap_stat_increase(heap, malloc_large, bsize);
1023
      mi_heap_stat_counter_increase(heap, malloc_large_count, 1);
1024
    }
1025
    else */
1026
0
    {
1027
0
      _mi_stat_increase(&heap->tld->stats.malloc_huge, bsize);
1028
0
      _mi_stat_counter_increase(&heap->tld->stats.malloc_huge_count, 1);
1029
0
    }
1030
0
  }
1031
0
  return page;
1032
0
}
1033
1034
1035
// Allocate a page
1036
// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.
1037
0
static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignment) mi_attr_noexcept {
1038
  // huge allocation?
1039
0
  const size_t req_size = size - MI_PADDING_SIZE;  // correct for padding_size in case of an overflow on `size`
1040
0
  if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) {
1041
0
    if mi_unlikely(req_size > MI_MAX_ALLOC_SIZE) {
1042
0
      _mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size);
1043
0
      return NULL;
1044
0
    }
1045
0
    else {
1046
0
      _PyMem_mi_heap_collect_qsbr(heap);  // CPython
1047
0
      return mi_large_huge_page_alloc(heap,size,huge_alignment);
1048
0
    }
1049
0
  }
1050
0
  else {
1051
    // otherwise find a page with free blocks in our size segregated queues
1052
    #if MI_PADDING
1053
    mi_assert_internal(size >= MI_PADDING_SIZE);
1054
    #endif
1055
0
    return mi_find_free_page(heap, size);
1056
0
  }
1057
0
}
1058
1059
// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed.
1060
// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed.
1061
// The `huge_alignment` is normally 0 but is set to a multiple of MI_SLICE_SIZE for
1062
// very large requested alignments in which case we use a huge singleton page.
1063
void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment, mi_page_t** ppage) mi_attr_noexcept
1064
0
{
1065
0
  mi_assert_internal(heap != NULL);
1066
1067
  // initialize if necessary
1068
0
  if mi_unlikely(!mi_heap_is_initialized(heap)) {
1069
0
    heap = mi_heap_get_default(); // calls mi_thread_init
1070
0
    if mi_unlikely(!mi_heap_is_initialized(heap)) { return NULL; }
1071
0
  }
1072
0
  mi_assert_internal(mi_heap_is_initialized(heap));
1073
1074
  // do administrative tasks every N generic mallocs
1075
0
  if mi_unlikely(++heap->generic_count >= 100) {
1076
0
    heap->generic_collect_count += heap->generic_count;
1077
0
    heap->generic_count = 0;
1078
    // call potential deferred free routines
1079
0
    _mi_deferred_free(heap, false);
1080
1081
    // free delayed frees from other threads (but skip contended ones)
1082
0
    _mi_heap_delayed_free_partial(heap);
1083
1084
    // collect every once in a while (10000 by default)
1085
0
    const long generic_collect = mi_option_get_clamp(mi_option_generic_collect, 1, 1000000L);
1086
0
    if (heap->generic_collect_count >= generic_collect) {
1087
0
      heap->generic_collect_count = 0;
1088
0
      mi_heap_collect(heap, false /* force? */);
1089
0
    }
1090
0
  }
1091
1092
  // find (or allocate) a page of the right size
1093
0
  mi_page_t* page = mi_find_page(heap, size, huge_alignment);
1094
0
  if mi_unlikely(page == NULL) { // first time out of memory, try to collect and retry the allocation once more
1095
0
    mi_heap_collect(heap, true /* force */);
1096
0
    page = mi_find_page(heap, size, huge_alignment);
1097
0
  }
1098
1099
0
  if mi_unlikely(page == NULL) { // out of memory
1100
0
    const size_t req_size = size - MI_PADDING_SIZE;  // correct for padding_size in case of an overflow on `size`
1101
0
    _mi_error_message(ENOMEM, "unable to allocate memory (%zu bytes)\n", req_size);
1102
0
    return NULL;
1103
0
  }
1104
1105
0
  mi_assert_internal(mi_page_immediate_available(page));
1106
0
  mi_assert_internal(mi_page_block_size(page) >= size);
1107
1108
  // and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc)
1109
0
  void* const p = _mi_page_malloc_zero(heap, page, size, zero, ppage);
1110
0
  mi_assert_internal(p != NULL);
1111
1112
  // move singleton pages to the full queue
1113
0
  if (page->reserved == page->used) {
1114
0
    mi_page_to_full(page, mi_page_queue_of(page));
1115
0
  }
1116
0
  return p;
1117
0
}