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-queue.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
  Definition of page queues for each block size
10
----------------------------------------------------------- */
11
12
#ifndef MI_IN_PAGE_C
13
#error "this file should be included from 'page.c'"
14
// include to help an IDE
15
#include "mimalloc.h"
16
#include "mimalloc/internal.h"
17
#include "mimalloc/atomic.h"
18
#endif
19
20
/* -----------------------------------------------------------
21
  Minimal alignment in machine words (i.e. `sizeof(void*)`)
22
----------------------------------------------------------- */
23
24
#if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE)
25
  #error "define alignment for more than 4x word size for this platform"
26
#elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE)
27
  #define MI_ALIGN4W   // 4 machine words minimal alignment
28
#elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE)
29
  #define MI_ALIGN2W   // 2 machine words minimal alignment
30
#else
31
  // ok, default alignment is 1 word
32
#endif
33
34
35
/* -----------------------------------------------------------
36
  Queue query
37
----------------------------------------------------------- */
38
39
40
0
static inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) {
41
0
  return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+sizeof(uintptr_t)));
42
0
}
43
44
0
static inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) {
45
0
  return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+(2*sizeof(uintptr_t))));
46
0
}
47
48
0
static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) {
49
0
  return (pq->block_size > MI_MEDIUM_OBJ_SIZE_MAX);
50
0
}
51
52
/* -----------------------------------------------------------
53
  Bins
54
----------------------------------------------------------- */
55
56
// Return the bin for a given field size.
57
// Returns MI_BIN_HUGE if the size is too large.
58
// We use `wsize` for the size in "machine word sizes",
59
// i.e. byte size == `wsize*sizeof(void*)`.
60
0
static inline size_t mi_bin(size_t size) {
61
0
  size_t wsize = _mi_wsize_from_size(size);
62
#if defined(MI_ALIGN4W)
63
  if mi_likely(wsize <= 4) {
64
    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes
65
  }
66
#elif defined(MI_ALIGN2W)
67
0
  if mi_likely(wsize <= 8) {
68
0
    return (wsize <= 1 ? 1 : (wsize+1)&~1); // round to double word sizes
69
0
  }
70
#else
71
  if mi_likely(wsize <= 8) {
72
    return (wsize == 0 ? 1 : wsize);
73
  }
74
#endif
75
0
  else if mi_unlikely(wsize > MI_MEDIUM_OBJ_WSIZE_MAX) {
76
0
    return MI_BIN_HUGE;
77
0
  }
78
0
  else {
79
    #if defined(MI_ALIGN4W)
80
    if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes
81
    #endif
82
0
    wsize--;
83
    // find the highest bit
84
0
    const size_t b = (MI_SIZE_BITS - 1 - mi_clz(wsize));  // note: wsize != 0
85
    // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation).
86
    // - adjust with 3 because we use do not round the first 8 sizes
87
    //   which each get an exact bin
88
0
    const size_t bin = ((b << 2) + ((wsize >> (b - 2)) & 0x03)) - 3;
89
0
    mi_assert_internal(bin > 0 && bin < MI_BIN_HUGE);
90
0
    return bin;
91
0
  }
92
0
}
93
94
95
96
/* -----------------------------------------------------------
97
  Queue of pages with free blocks
98
----------------------------------------------------------- */
99
100
0
size_t _mi_bin(size_t size) {
101
0
  return mi_bin(size);
102
0
}
103
104
0
size_t _mi_bin_size(size_t bin) {
105
0
  return _mi_heap_empty.pages[bin].block_size;
106
0
}
107
108
// Good size for allocation
109
0
size_t mi_good_size(size_t size) mi_attr_noexcept {
110
0
  if (size <= MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) {
111
0
    return _mi_bin_size(mi_bin(size + MI_PADDING_SIZE));
112
0
  }
113
0
  else if (size <= MI_MAX_ALLOC_SIZE - MI_PADDING_SIZE) {
114
0
    return _mi_align_up(size + MI_PADDING_SIZE,_mi_os_page_size());
115
0
  }
116
0
  else {
117
0
    return size;
118
0
  }
119
0
}
120
121
#if (MI_DEBUG>1)
122
static bool mi_page_queue_contains(mi_page_queue_t* queue, const mi_page_t* page) {
123
  mi_assert_internal(page != NULL);
124
  mi_page_t* list = queue->first;
125
  while (list != NULL) {
126
    mi_assert_internal(list->next == NULL || list->next->prev == list);
127
    mi_assert_internal(list->prev == NULL || list->prev->next == list);
128
    if (list == page) break;
129
    list = list->next;
130
  }
131
  return (list == page);
132
}
133
134
#endif
135
136
#if (MI_DEBUG>1)
137
static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t* pq) {
138
  return (pq >= &heap->pages[0] && pq <= &heap->pages[MI_BIN_FULL]);
139
}
140
#endif
141
142
0
static inline bool mi_page_is_large_or_huge(const mi_page_t* page) {
143
0
  return (mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_huge(page));
144
0
}
145
146
0
static size_t mi_page_bin(const mi_page_t* page) {
147
0
  const size_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page))));
148
0
  mi_assert_internal(bin <= MI_BIN_FULL);
149
0
  return bin;
150
0
}
151
152
// returns the page bin without using MI_BIN_FULL for statistics
153
0
size_t _mi_page_stats_bin(const mi_page_t* page) {
154
0
  const size_t bin = (mi_page_is_huge(page) ? MI_BIN_HUGE : mi_bin(mi_page_block_size(page)));
155
0
  mi_assert_internal(bin <= MI_BIN_HUGE);
156
0
  return bin;
157
0
}
158
159
0
static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) {
160
0
  mi_assert_internal(heap!=NULL);
161
0
  const size_t bin = mi_page_bin(page);
162
0
  mi_page_queue_t* pq = &heap->pages[bin];
163
0
  mi_assert_internal((mi_page_block_size(page) == pq->block_size) ||
164
0
                       (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(pq)) ||
165
0
                         (mi_page_is_in_full(page) && mi_page_queue_is_full(pq)));
166
0
  return pq;
167
0
}
168
169
0
static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) {
170
0
  mi_heap_t* heap = mi_page_heap(page);
171
0
  mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page);
172
0
  mi_assert_expensive(mi_page_queue_contains(pq, page));
173
0
  return pq;
174
0
}
175
176
// The current small page array is for efficiency and for each
177
// small size (up to 256) it points directly to the page for that
178
// size without having to compute the bin. This means when the
179
// current free page queue is updated for a small bin, we need to update a
180
// range of entries in `_mi_page_small_free`.
181
0
static inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) {
182
0
  mi_assert_internal(mi_heap_contains_queue(heap,pq));
183
0
  const size_t size = pq->block_size;
184
0
  if (size > MI_SMALL_SIZE_MAX) return;
185
186
0
  mi_page_t* page = pq->first;
187
0
  if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty;
188
189
  // find index in the right direct page array
190
0
  const size_t idx = _mi_wsize_from_size(size);
191
0
  mi_page_t** const pages_free = heap->pages_free_direct;
192
0
  if (pages_free[idx] == page) return;  // already set
193
194
  // find start slot
195
0
  size_t start;
196
0
  if (idx<=1) {
197
0
    start = 0;
198
0
  }
199
0
  else {
200
    // find previous size; due to minimal alignment upto 3 previous bins may need to be skipped
201
0
    mi_assert_internal(pq > &heap->pages[0]); // since idx > 1
202
0
    size_t bin = mi_bin(size);
203
0
    const mi_page_queue_t* prev = pq - 1;
204
0
    while( bin == mi_bin(prev->block_size) && prev > &heap->pages[0]) {
205
0
      prev--;
206
0
    }
207
0
    start = 1 + _mi_wsize_from_size(prev->block_size);
208
0
    if (start > idx) start = idx;
209
0
  }
210
211
  // set size range to the right page
212
0
  mi_assert(start <= idx);
213
0
  for (size_t sz = start; sz <= idx; sz++) {
214
0
    pages_free[sz] = page;
215
0
  }
216
0
}
217
218
/*
219
static bool mi_page_queue_is_empty(mi_page_queue_t* queue) {
220
  return (queue->first == NULL);
221
}
222
*/
223
224
0
static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) {
225
0
  mi_assert_internal(page != NULL);
226
0
  mi_assert_expensive(mi_page_queue_contains(queue, page));
227
0
  mi_assert_internal(mi_page_block_size(page) == queue->block_size ||
228
0
                      (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||
229
0
                        (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
230
0
  mi_heap_t* heap = mi_page_heap(page);
231
232
0
  if (page->prev != NULL) page->prev->next = page->next;
233
0
  if (page->next != NULL) page->next->prev = page->prev;
234
0
  if (page == queue->last)  queue->last = page->prev;
235
0
  if (page == queue->first) {
236
0
    queue->first = page->next;
237
    // update first
238
0
    mi_assert_internal(mi_heap_contains_queue(heap, queue));
239
0
    mi_heap_queue_first_update(heap,queue);
240
0
  }
241
0
  heap->page_count--;
242
0
  page->next = NULL;
243
0
  page->prev = NULL;
244
  // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), NULL);
245
0
  mi_page_set_in_full(page,false);
246
0
}
247
248
249
#if MI_DEBUG >= 3
250
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue);
251
#endif
252
253
0
static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
254
0
  mi_assert_internal(mi_page_heap(page) == heap);
255
0
  mi_assert_internal(!mi_page_queue_contains(queue, page));
256
  #if MI_HUGE_PAGE_ABANDON
257
  mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE);
258
  #endif
259
0
  mi_assert_internal(mi_page_block_size(page) == queue->block_size ||
260
0
                      (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(queue)) ||
261
0
                        (mi_page_is_in_full(page) && mi_page_queue_is_full(queue)));
262
263
0
  mi_page_set_in_full(page, mi_page_queue_is_full(queue));
264
  // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), heap);
265
0
  page->next = queue->first;
266
0
  page->prev = NULL;
267
0
  if (queue->first != NULL) {
268
0
    mi_assert_internal(queue->first->prev == NULL);
269
0
    queue->first->prev = page;
270
0
    queue->first = page;
271
0
  }
272
0
  else {
273
0
    queue->first = queue->last = page;
274
0
  }
275
276
  // update direct
277
0
  mi_heap_queue_first_update(heap, queue);
278
0
  heap->page_count++;
279
280
  // [specbot Q-NEW-1] first/last must be both null or both non-null (L1, O(1))
281
0
  mi_assert_internal((queue->first == NULL) == (queue->last == NULL));
282
  // [specbot Q-NEW-2] last element must have no next (L1, O(1))
283
0
  mi_assert_internal(queue->last == NULL || queue->last->next == NULL);
284
0
  mi_assert_expensive(mi_page_queue_is_consistent(queue));
285
0
}
286
287
#if MI_DEBUG >= 3
288
// [specbot Q-NEW-3] Verify doubly-linked list forward/backward consistency (L2, O(n))
289
static bool mi_page_queue_is_consistent(const mi_page_queue_t* queue) {
290
  if (queue->first == NULL) return (queue->last == NULL);
291
  if (queue->last == NULL) return false;
292
  // forward: first -> ... -> last
293
  const mi_page_t* p = queue->first;
294
  const mi_page_t* prev = NULL;
295
  size_t count = 0;
296
  while (p != NULL) {
297
    mi_assert_internal(p->prev == prev);
298
    prev = p;
299
    p = p->next;
300
    count++;
301
    if (count > 100000) return false; // cycle guard
302
  }
303
  mi_assert_internal(prev == queue->last);
304
  return true;
305
}
306
#endif
307
308
0
static void mi_page_queue_move_to_front(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) {
309
0
  mi_assert_internal(mi_page_heap(page) == heap);
310
0
  mi_assert_internal(mi_page_queue_contains(queue, page));
311
0
  if (queue->first == page) return;
312
0
  mi_page_queue_remove(queue, page);
313
0
  mi_page_queue_push(heap, queue, page);
314
0
  mi_assert_internal(queue->first == page);
315
0
}
316
317
0
static void mi_page_queue_enqueue_from_ex(mi_page_queue_t* to, mi_page_queue_t* from, bool enqueue_at_end, mi_page_t* page) {
318
0
  mi_assert_internal(page != NULL);
319
0
  mi_assert_expensive(mi_page_queue_contains(from, page));
320
0
  mi_assert_expensive(!mi_page_queue_contains(to, page));
321
0
  const size_t bsize = mi_page_block_size(page);
322
0
  MI_UNUSED(bsize);
323
0
  mi_assert_internal((bsize == to->block_size && bsize == from->block_size) ||
324
0
                     (bsize == to->block_size && mi_page_queue_is_full(from)) ||
325
0
                     (bsize == from->block_size && mi_page_queue_is_full(to)) ||
326
0
                     (mi_page_is_large_or_huge(page) && mi_page_queue_is_huge(to)) ||
327
0
                     (mi_page_is_large_or_huge(page) && mi_page_queue_is_full(to)));
328
329
0
  mi_heap_t* heap = mi_page_heap(page);
330
331
  // delete from `from`
332
0
  if (page->prev != NULL) page->prev->next = page->next;
333
0
  if (page->next != NULL) page->next->prev = page->prev;
334
0
  if (page == from->last)  from->last = page->prev;
335
0
  if (page == from->first) {
336
0
    from->first = page->next;
337
    // update first
338
0
    mi_assert_internal(mi_heap_contains_queue(heap, from));
339
0
    mi_heap_queue_first_update(heap, from);
340
0
  }
341
342
  // insert into `to`
343
0
  if (enqueue_at_end) {
344
    // enqueue at the end
345
0
    page->prev = to->last;
346
0
    page->next = NULL;
347
0
    if (to->last != NULL) {
348
0
      mi_assert_internal(heap == mi_page_heap(to->last));
349
0
      to->last->next = page;
350
0
      to->last = page;
351
0
    }
352
0
    else {
353
0
      to->first = page;
354
0
      to->last = page;
355
0
      mi_heap_queue_first_update(heap, to);
356
0
    }
357
0
  }
358
0
  else {
359
0
    if (to->first != NULL) {
360
      // enqueue at 2nd place
361
0
      mi_assert_internal(heap == mi_page_heap(to->first));
362
0
      mi_page_t* next = to->first->next;
363
0
      page->prev = to->first;
364
0
      page->next = next;
365
0
      to->first->next = page;
366
0
      if (next != NULL) {
367
0
        next->prev = page;
368
0
      }
369
0
      else {
370
0
        to->last = page;
371
0
      }
372
0
    }
373
0
    else {
374
      // enqueue at the head (singleton list)
375
0
      page->prev = NULL;
376
0
      page->next = NULL;
377
0
      to->first = page;
378
0
      to->last = page;
379
0
      mi_heap_queue_first_update(heap, to);
380
0
    }
381
0
  }
382
383
0
  mi_page_set_in_full(page, mi_page_queue_is_full(to));
384
0
}
385
386
0
static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
387
0
  mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end */, page);
388
0
}
389
390
0
static void mi_page_queue_enqueue_from_full(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) {
391
  // note: we could insert at the front to increase reuse, but it slows down certain benchmarks (like `alloc-test`)
392
0
  mi_page_queue_enqueue_from_ex(to, from, true /* enqueue at the end of the `to` queue? */, page);
393
0
}
394
395
// Only called from `mi_heap_absorb`.
396
0
size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) {
397
0
  mi_assert_internal(mi_heap_contains_queue(heap,pq));
398
0
  mi_assert_internal(pq->block_size == append->block_size);
399
400
0
  if (append->first==NULL) return 0;
401
402
  // set append pages to new heap and count
403
0
  size_t count = 0;
404
0
  for (mi_page_t* page = append->first; page != NULL; page = page->next) {
405
    // inline `mi_page_set_heap` to avoid wrong assertion during absorption;
406
    // in this case it is ok to be delayed freeing since both "to" and "from" heap are still alive.
407
0
    mi_atomic_store_release(&page->xheap, (uintptr_t)heap);
408
    // set the flag to delayed free (not overriding NEVER_DELAYED_FREE) which has as a
409
    // side effect that it spins until any DELAYED_FREEING is finished. This ensures
410
    // that after appending only the new heap will be used for delayed free operations.
411
0
    _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false);
412
0
    count++;
413
0
  }
414
415
0
  if (pq->last==NULL) {
416
    // take over afresh
417
0
    mi_assert_internal(pq->first==NULL);
418
0
    pq->first = append->first;
419
0
    pq->last = append->last;
420
0
    mi_heap_queue_first_update(heap, pq);
421
0
  }
422
0
  else {
423
    // append to end
424
0
    mi_assert_internal(pq->last!=NULL);
425
0
    mi_assert_internal(append->first!=NULL);
426
0
    pq->last->next = append->first;
427
0
    append->first->prev = pq->last;
428
0
    pq->last = append->last;
429
0
  }
430
0
  return count;
431
0
}