Coverage Report

Created: 2025-07-04 06:49

/src/cpython/Objects/mimalloc/segment.c
Line
Count
Source (jump to first uncovered line)
1
/* ----------------------------------------------------------------------------
2
Copyright (c) 2018-2020, Microsoft Research, Daan Leijen
3
This is free software; you can redistribute it and/or modify it under the
4
terms of the MIT license. A copy of the license can be found in the file
5
"LICENSE" at the root of this distribution.
6
-----------------------------------------------------------------------------*/
7
#include "mimalloc.h"
8
#include "mimalloc/internal.h"
9
#include "mimalloc/atomic.h"
10
11
#include <string.h>  // memset
12
#include <stdio.h>
13
14
#define MI_PAGE_HUGE_ALIGN   (256*1024)
15
16
static void mi_segment_try_purge(mi_segment_t* segment, bool force, mi_stats_t* stats);
17
18
19
// -------------------------------------------------------------------
20
// commit mask
21
// -------------------------------------------------------------------
22
23
0
static bool mi_commit_mask_all_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {
24
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
25
0
    if ((commit->mask[i] & cm->mask[i]) != cm->mask[i]) return false;
26
0
  }
27
0
  return true;
28
0
}
29
30
0
static bool mi_commit_mask_any_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {
31
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
32
0
    if ((commit->mask[i] & cm->mask[i]) != 0) return true;
33
0
  }
34
0
  return false;
35
0
}
36
37
0
static void mi_commit_mask_create_intersect(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm, mi_commit_mask_t* res) {
38
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
39
0
    res->mask[i] = (commit->mask[i] & cm->mask[i]);
40
0
  }
41
0
}
42
43
0
static void mi_commit_mask_clear(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {
44
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
45
0
    res->mask[i] &= ~(cm->mask[i]);
46
0
  }
47
0
}
48
49
0
static void mi_commit_mask_set(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {
50
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
51
0
    res->mask[i] |= cm->mask[i];
52
0
  }
53
0
}
54
55
0
static void mi_commit_mask_create(size_t bitidx, size_t bitcount, mi_commit_mask_t* cm) {
56
0
  mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);
57
0
  mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);
58
0
  if (bitcount == MI_COMMIT_MASK_BITS) {
59
0
    mi_assert_internal(bitidx==0);
60
0
    mi_commit_mask_create_full(cm);
61
0
  }
62
0
  else if (bitcount == 0) {
63
0
    mi_commit_mask_create_empty(cm);
64
0
  }
65
0
  else {
66
0
    mi_commit_mask_create_empty(cm);
67
0
    size_t i = bitidx / MI_COMMIT_MASK_FIELD_BITS;
68
0
    size_t ofs = bitidx % MI_COMMIT_MASK_FIELD_BITS;
69
0
    while (bitcount > 0) {
70
0
      mi_assert_internal(i < MI_COMMIT_MASK_FIELD_COUNT);
71
0
      size_t avail = MI_COMMIT_MASK_FIELD_BITS - ofs;
72
0
      size_t count = (bitcount > avail ? avail : bitcount);
73
0
      size_t mask = (count >= MI_COMMIT_MASK_FIELD_BITS ? ~((size_t)0) : (((size_t)1 << count) - 1) << ofs);
74
0
      cm->mask[i] = mask;
75
0
      bitcount -= count;
76
0
      ofs = 0;
77
0
      i++;
78
0
    }
79
0
  }
80
0
}
81
82
0
size_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total) {
83
0
  mi_assert_internal((total%MI_COMMIT_MASK_BITS)==0);
84
0
  size_t count = 0;
85
0
  for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
86
0
    size_t mask = cm->mask[i];
87
0
    if (~mask == 0) {
88
0
      count += MI_COMMIT_MASK_FIELD_BITS;
89
0
    }
90
0
    else {
91
0
      for (; mask != 0; mask >>= 1) {  // todo: use popcount
92
0
        if ((mask&1)!=0) count++;
93
0
      }
94
0
    }
95
0
  }
96
  // we use total since for huge segments each commit bit may represent a larger size
97
0
  return ((total / MI_COMMIT_MASK_BITS) * count);
98
0
}
99
100
101
0
size_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx) {
102
0
  size_t i = (*idx) / MI_COMMIT_MASK_FIELD_BITS;
103
0
  size_t ofs = (*idx) % MI_COMMIT_MASK_FIELD_BITS;
104
0
  size_t mask = 0;
105
  // find first ones
106
0
  while (i < MI_COMMIT_MASK_FIELD_COUNT) {
107
0
    mask = cm->mask[i];
108
0
    mask >>= ofs;
109
0
    if (mask != 0) {
110
0
      while ((mask&1) == 0) {
111
0
        mask >>= 1;
112
0
        ofs++;
113
0
      }
114
0
      break;
115
0
    }
116
0
    i++;
117
0
    ofs = 0;
118
0
  }
119
0
  if (i >= MI_COMMIT_MASK_FIELD_COUNT) {
120
    // not found
121
0
    *idx = MI_COMMIT_MASK_BITS;
122
0
    return 0;
123
0
  }
124
0
  else {
125
    // found, count ones
126
0
    size_t count = 0;
127
0
    *idx = (i*MI_COMMIT_MASK_FIELD_BITS) + ofs;
128
0
    do {
129
0
      mi_assert_internal(ofs < MI_COMMIT_MASK_FIELD_BITS && (mask&1) == 1);
130
0
      do {
131
0
        count++;
132
0
        mask >>= 1;
133
0
      } while ((mask&1) == 1);
134
0
      if ((((*idx + count) % MI_COMMIT_MASK_FIELD_BITS) == 0)) {
135
0
        i++;
136
0
        if (i >= MI_COMMIT_MASK_FIELD_COUNT) break;
137
0
        mask = cm->mask[i];
138
0
        ofs = 0;
139
0
      }
140
0
    } while ((mask&1) == 1);
141
0
    mi_assert_internal(count > 0);
142
0
    return count;
143
0
  }
144
0
}
145
146
147
/* --------------------------------------------------------------------------------
148
  Segment allocation
149
150
  If a  thread ends, it "abandons" pages with used blocks
151
  and there is an abandoned segment list whose segments can
152
  be reclaimed by still running threads, much like work-stealing.
153
-------------------------------------------------------------------------------- */
154
155
156
/* -----------------------------------------------------------
157
   Slices
158
----------------------------------------------------------- */
159
160
161
0
static const mi_slice_t* mi_segment_slices_end(const mi_segment_t* segment) {
162
0
  return &segment->slices[segment->slice_entries];
163
0
}
164
165
0
static uint8_t* mi_slice_start(const mi_slice_t* slice) {
166
0
  mi_segment_t* segment = _mi_ptr_segment(slice);
167
0
  mi_assert_internal(slice >= segment->slices && slice < mi_segment_slices_end(segment));
168
0
  return ((uint8_t*)segment + ((slice - segment->slices)*MI_SEGMENT_SLICE_SIZE));
169
0
}
170
171
172
/* -----------------------------------------------------------
173
   Bins
174
----------------------------------------------------------- */
175
// Use bit scan forward to quickly find the first zero bit if it is available
176
177
0
static inline size_t mi_slice_bin8(size_t slice_count) {
178
0
  if (slice_count<=1) return slice_count;
179
0
  mi_assert_internal(slice_count <= MI_SLICES_PER_SEGMENT);
180
0
  slice_count--;
181
0
  size_t s = mi_bsr(slice_count);  // slice_count > 1
182
0
  if (s <= 2) return slice_count + 1;
183
0
  size_t bin = ((s << 2) | ((slice_count >> (s - 2))&0x03)) - 4;
184
0
  return bin;
185
0
}
186
187
0
static inline size_t mi_slice_bin(size_t slice_count) {
188
0
  mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_SEGMENT_SIZE);
189
0
  mi_assert_internal(mi_slice_bin8(MI_SLICES_PER_SEGMENT) <= MI_SEGMENT_BIN_MAX);
190
0
  size_t bin = mi_slice_bin8(slice_count);
191
0
  mi_assert_internal(bin <= MI_SEGMENT_BIN_MAX);
192
0
  return bin;
193
0
}
194
195
0
static inline size_t mi_slice_index(const mi_slice_t* slice) {
196
0
  mi_segment_t* segment = _mi_ptr_segment(slice);
197
0
  ptrdiff_t index = slice - segment->slices;
198
0
  mi_assert_internal(index >= 0 && index < (ptrdiff_t)segment->slice_entries);
199
0
  return index;
200
0
}
201
202
203
/* -----------------------------------------------------------
204
   Slice span queues
205
----------------------------------------------------------- */
206
207
0
static void mi_span_queue_push(mi_span_queue_t* sq, mi_slice_t* slice) {
208
  // todo: or push to the end?
209
0
  mi_assert_internal(slice->prev == NULL && slice->next==NULL);
210
0
  slice->prev = NULL; // paranoia
211
0
  slice->next = sq->first;
212
0
  sq->first = slice;
213
0
  if (slice->next != NULL) slice->next->prev = slice;
214
0
                     else sq->last = slice;
215
0
  slice->xblock_size = 0; // free
216
0
}
217
218
0
static mi_span_queue_t* mi_span_queue_for(size_t slice_count, mi_segments_tld_t* tld) {
219
0
  size_t bin = mi_slice_bin(slice_count);
220
0
  mi_span_queue_t* sq = &tld->spans[bin];
221
0
  mi_assert_internal(sq->slice_count >= slice_count);
222
0
  return sq;
223
0
}
224
225
0
static void mi_span_queue_delete(mi_span_queue_t* sq, mi_slice_t* slice) {
226
0
  mi_assert_internal(slice->xblock_size==0 && slice->slice_count>0 && slice->slice_offset==0);
227
  // should work too if the queue does not contain slice (which can happen during reclaim)
228
0
  if (slice->prev != NULL) slice->prev->next = slice->next;
229
0
  if (slice == sq->first) sq->first = slice->next;
230
0
  if (slice->next != NULL) slice->next->prev = slice->prev;
231
0
  if (slice == sq->last) sq->last = slice->prev;
232
0
  slice->prev = NULL;
233
0
  slice->next = NULL;
234
0
  slice->xblock_size = 1; // no more free
235
0
}
236
237
238
/* -----------------------------------------------------------
239
 Invariant checking
240
----------------------------------------------------------- */
241
242
0
static bool mi_slice_is_used(const mi_slice_t* slice) {
243
0
  return (slice->xblock_size > 0);
244
0
}
245
246
247
#if (MI_DEBUG>=3)
248
static bool mi_span_queue_contains(mi_span_queue_t* sq, mi_slice_t* slice) {
249
  for (mi_slice_t* s = sq->first; s != NULL; s = s->next) {
250
    if (s==slice) return true;
251
  }
252
  return false;
253
}
254
255
static bool mi_segment_is_valid(mi_segment_t* segment, mi_segments_tld_t* tld) {
256
  mi_assert_internal(segment != NULL);
257
  mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie);
258
  mi_assert_internal(segment->abandoned <= segment->used);
259
  mi_assert_internal(segment->thread_id == 0 || segment->thread_id == _mi_thread_id());
260
  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); // can only decommit committed blocks
261
  //mi_assert_internal(segment->segment_info_size % MI_SEGMENT_SLICE_SIZE == 0);
262
  mi_slice_t* slice = &segment->slices[0];
263
  const mi_slice_t* end = mi_segment_slices_end(segment);
264
  size_t used_count = 0;
265
  mi_span_queue_t* sq;
266
  while(slice < end) {
267
    mi_assert_internal(slice->slice_count > 0);
268
    mi_assert_internal(slice->slice_offset == 0);
269
    size_t index = mi_slice_index(slice);
270
    size_t maxindex = (index + slice->slice_count >= segment->slice_entries ? segment->slice_entries : index + slice->slice_count) - 1;
271
    if (mi_slice_is_used(slice)) { // a page in use, we need at least MAX_SLICE_OFFSET valid back offsets
272
      used_count++;
273
      for (size_t i = 0; i <= MI_MAX_SLICE_OFFSET && index + i <= maxindex; i++) {
274
        mi_assert_internal(segment->slices[index + i].slice_offset == i*sizeof(mi_slice_t));
275
        mi_assert_internal(i==0 || segment->slices[index + i].slice_count == 0);
276
        mi_assert_internal(i==0 || segment->slices[index + i].xblock_size == 1);
277
      }
278
      // and the last entry as well (for coalescing)
279
      const mi_slice_t* last = slice + slice->slice_count - 1;
280
      if (last > slice && last < mi_segment_slices_end(segment)) {
281
        mi_assert_internal(last->slice_offset == (slice->slice_count-1)*sizeof(mi_slice_t));
282
        mi_assert_internal(last->slice_count == 0);
283
        mi_assert_internal(last->xblock_size == 1);
284
      }
285
    }
286
    else {  // free range of slices; only last slice needs a valid back offset
287
      mi_slice_t* last = &segment->slices[maxindex];
288
      if (segment->kind != MI_SEGMENT_HUGE || slice->slice_count <= (segment->slice_entries - segment->segment_info_slices)) {
289
        mi_assert_internal((uint8_t*)slice == (uint8_t*)last - last->slice_offset);
290
      }
291
      mi_assert_internal(slice == last || last->slice_count == 0 );
292
      mi_assert_internal(last->xblock_size == 0 || (segment->kind==MI_SEGMENT_HUGE && last->xblock_size==1));
293
      if (segment->kind != MI_SEGMENT_HUGE && segment->thread_id != 0) { // segment is not huge or abandoned
294
        sq = mi_span_queue_for(slice->slice_count,tld);
295
        mi_assert_internal(mi_span_queue_contains(sq,slice));
296
      }
297
    }
298
    slice = &segment->slices[maxindex+1];
299
  }
300
  mi_assert_internal(slice == end);
301
  mi_assert_internal(used_count == segment->used + 1);
302
  return true;
303
}
304
#endif
305
306
/* -----------------------------------------------------------
307
 Segment size calculations
308
----------------------------------------------------------- */
309
310
0
static size_t mi_segment_info_size(mi_segment_t* segment) {
311
0
  return segment->segment_info_slices * MI_SEGMENT_SLICE_SIZE;
312
0
}
313
314
static uint8_t* _mi_segment_page_start_from_slice(const mi_segment_t* segment, const mi_slice_t* slice, size_t xblock_size, size_t* page_size)
315
0
{
316
0
  ptrdiff_t idx = slice - segment->slices;
317
0
  size_t psize = (size_t)slice->slice_count * MI_SEGMENT_SLICE_SIZE;
318
  // make the start not OS page aligned for smaller blocks to avoid page/cache effects
319
  // note: the offset must always be an xblock_size multiple since we assume small allocations
320
  // are aligned (see `mi_heap_malloc_aligned`).
321
0
  size_t start_offset = 0;
322
0
  if (xblock_size >= MI_INTPTR_SIZE) {
323
0
    if (xblock_size <= 64) { start_offset = 3*xblock_size; }
324
0
    else if (xblock_size <= 512) { start_offset = xblock_size; }
325
0
  }
326
0
  if (page_size != NULL) { *page_size = psize - start_offset; }
327
0
  return (uint8_t*)segment + ((idx*MI_SEGMENT_SLICE_SIZE) + start_offset);
328
0
}
329
330
// Start of the page available memory; can be used on uninitialized pages
331
uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size)
332
0
{
333
0
  const mi_slice_t* slice = mi_page_to_slice((mi_page_t*)page);
334
0
  uint8_t* p = _mi_segment_page_start_from_slice(segment, slice, page->xblock_size, page_size);
335
0
  mi_assert_internal(page->xblock_size > 0 || _mi_ptr_page(p) == page);
336
0
  mi_assert_internal(_mi_ptr_segment(p) == segment);
337
0
  return p;
338
0
}
339
340
341
0
static size_t mi_segment_calculate_slices(size_t required, size_t* pre_size, size_t* info_slices) {
342
0
  size_t page_size = _mi_os_page_size();
343
0
  size_t isize     = _mi_align_up(sizeof(mi_segment_t), page_size);
344
0
  size_t guardsize = 0;
345
346
0
  if (MI_SECURE>0) {
347
    // in secure mode, we set up a protected page in between the segment info
348
    // and the page data (and one at the end of the segment)
349
0
    guardsize = page_size;
350
0
    if (required > 0) {
351
0
      required = _mi_align_up(required, MI_SEGMENT_SLICE_SIZE) + page_size;
352
0
    }
353
0
  }
354
355
0
  if (pre_size != NULL) *pre_size = isize;
356
0
  isize = _mi_align_up(isize + guardsize, MI_SEGMENT_SLICE_SIZE);
357
0
  if (info_slices != NULL) *info_slices = isize / MI_SEGMENT_SLICE_SIZE;
358
0
  size_t segment_size = (required==0 ? MI_SEGMENT_SIZE : _mi_align_up( required + isize + guardsize, MI_SEGMENT_SLICE_SIZE) );
359
0
  mi_assert_internal(segment_size % MI_SEGMENT_SLICE_SIZE == 0);
360
0
  return (segment_size / MI_SEGMENT_SLICE_SIZE);
361
0
}
362
363
364
/* ----------------------------------------------------------------------------
365
Segment caches
366
We keep a small segment cache per thread to increase local
367
reuse and avoid setting/clearing guard pages in secure mode.
368
------------------------------------------------------------------------------- */
369
370
0
static void mi_segments_track_size(long segment_size, mi_segments_tld_t* tld) {
371
0
  if (segment_size>=0) _mi_stat_increase(&tld->stats->segments,1);
372
0
                  else _mi_stat_decrease(&tld->stats->segments,1);
373
0
  tld->count += (segment_size >= 0 ? 1 : -1);
374
0
  if (tld->count > tld->peak_count) tld->peak_count = tld->count;
375
0
  tld->current_size += segment_size;
376
0
  if (tld->current_size > tld->peak_size) tld->peak_size = tld->current_size;
377
0
}
378
379
0
static void mi_segment_os_free(mi_segment_t* segment, mi_segments_tld_t* tld) {
380
0
  segment->thread_id = 0;
381
0
  _mi_segment_map_freed_at(segment);
382
0
  mi_segments_track_size(-((long)mi_segment_size(segment)),tld);
383
0
  if (MI_SECURE>0) {
384
    // _mi_os_unprotect(segment, mi_segment_size(segment)); // ensure no more guard pages are set
385
    // unprotect the guard pages; we cannot just unprotect the whole segment size as part may be decommitted
386
0
    size_t os_pagesize = _mi_os_page_size();
387
0
    _mi_os_unprotect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);
388
0
    uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;
389
0
    _mi_os_unprotect(end, os_pagesize);
390
0
  }
391
392
  // purge delayed decommits now? (no, leave it to the arena)
393
  // mi_segment_try_purge(segment,true,tld->stats);
394
395
0
  const size_t size = mi_segment_size(segment);
396
0
  const size_t csize = _mi_commit_mask_committed_size(&segment->commit_mask, size);
397
398
0
  _mi_abandoned_await_readers(tld->abandoned);  // wait until safe to free
399
0
  _mi_arena_free(segment, mi_segment_size(segment), csize, segment->memid, tld->stats);
400
0
}
401
402
// called by threads that are terminating
403
0
void _mi_segment_thread_collect(mi_segments_tld_t* tld) {
404
0
  MI_UNUSED(tld);
405
  // nothing to do
406
0
}
407
408
409
/* -----------------------------------------------------------
410
   Commit/Decommit ranges
411
----------------------------------------------------------- */
412
413
0
static void mi_segment_commit_mask(mi_segment_t* segment, bool conservative, uint8_t* p, size_t size, uint8_t** start_p, size_t* full_size, mi_commit_mask_t* cm) {
414
0
  mi_assert_internal(_mi_ptr_segment(p + 1) == segment);
415
0
  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
416
0
  mi_commit_mask_create_empty(cm);
417
0
  if (size == 0 || size > MI_SEGMENT_SIZE || segment->kind == MI_SEGMENT_HUGE) return;
418
0
  const size_t segstart = mi_segment_info_size(segment);
419
0
  const size_t segsize = mi_segment_size(segment);
420
0
  if (p >= (uint8_t*)segment + segsize) return;
421
422
0
  size_t pstart = (p - (uint8_t*)segment);
423
0
  mi_assert_internal(pstart + size <= segsize);
424
425
0
  size_t start;
426
0
  size_t end;
427
0
  if (conservative) {
428
    // decommit conservative
429
0
    start = _mi_align_up(pstart, MI_COMMIT_SIZE);
430
0
    end   = _mi_align_down(pstart + size, MI_COMMIT_SIZE);
431
0
    mi_assert_internal(start >= segstart);
432
0
    mi_assert_internal(end <= segsize);
433
0
  }
434
0
  else {
435
    // commit liberal
436
0
    start = _mi_align_down(pstart, MI_MINIMAL_COMMIT_SIZE);
437
0
    end   = _mi_align_up(pstart + size, MI_MINIMAL_COMMIT_SIZE);
438
0
  }
439
0
  if (pstart >= segstart && start < segstart) {  // note: the mask is also calculated for an initial commit of the info area
440
0
    start = segstart;
441
0
  }
442
0
  if (end > segsize) {
443
0
    end = segsize;
444
0
  }
445
446
0
  mi_assert_internal(start <= pstart && (pstart + size) <= end);
447
0
  mi_assert_internal(start % MI_COMMIT_SIZE==0 && end % MI_COMMIT_SIZE == 0);
448
0
  *start_p   = (uint8_t*)segment + start;
449
0
  *full_size = (end > start ? end - start : 0);
450
0
  if (*full_size == 0) return;
451
452
0
  size_t bitidx = start / MI_COMMIT_SIZE;
453
0
  mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);
454
455
0
  size_t bitcount = *full_size / MI_COMMIT_SIZE; // can be 0
456
0
  if (bitidx + bitcount > MI_COMMIT_MASK_BITS) {
457
0
    _mi_warning_message("commit mask overflow: idx=%zu count=%zu start=%zx end=%zx p=0x%p size=%zu fullsize=%zu\n", bitidx, bitcount, start, end, p, size, *full_size);
458
0
  }
459
0
  mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);
460
0
  mi_commit_mask_create(bitidx, bitcount, cm);
461
0
}
462
463
0
static bool mi_segment_commit(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
464
0
  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));
465
466
  // commit liberal
467
0
  uint8_t* start = NULL;
468
0
  size_t   full_size = 0;
469
0
  mi_commit_mask_t mask;
470
0
  mi_segment_commit_mask(segment, false /* conservative? */, p, size, &start, &full_size, &mask);
471
0
  if (mi_commit_mask_is_empty(&mask) || full_size == 0) return true;
472
473
0
  if (!mi_commit_mask_all_set(&segment->commit_mask, &mask)) {
474
    // committing
475
0
    bool is_zero = false;
476
0
    mi_commit_mask_t cmask;
477
0
    mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);
478
0
    _mi_stat_decrease(&_mi_stats_main.committed, _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for overlap
479
0
    if (!_mi_os_commit(start, full_size, &is_zero, stats)) return false;
480
0
    mi_commit_mask_set(&segment->commit_mask, &mask);
481
0
  }
482
483
  // increase purge expiration when using part of delayed purges -- we assume more allocations are coming soon.
484
0
  if (mi_commit_mask_any_set(&segment->purge_mask, &mask)) {
485
0
    segment->purge_expire = _mi_clock_now() + mi_option_get(mi_option_purge_delay);
486
0
  }
487
488
  // always clear any delayed purges in our range (as they are either committed now)
489
0
  mi_commit_mask_clear(&segment->purge_mask, &mask);
490
0
  return true;
491
0
}
492
493
0
static bool mi_segment_ensure_committed(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
494
0
  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));
495
  // note: assumes commit_mask is always full for huge segments as otherwise the commit mask bits can overflow
496
0
  if (mi_commit_mask_is_full(&segment->commit_mask) && mi_commit_mask_is_empty(&segment->purge_mask)) return true; // fully committed
497
0
  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
498
0
  return mi_segment_commit(segment, p, size, stats);
499
0
}
500
501
0
static bool mi_segment_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
502
0
  mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));
503
0
  if (!segment->allow_purge) return true;
504
505
  // purge conservative
506
0
  uint8_t* start = NULL;
507
0
  size_t   full_size = 0;
508
0
  mi_commit_mask_t mask;
509
0
  mi_segment_commit_mask(segment, true /* conservative? */, p, size, &start, &full_size, &mask);
510
0
  if (mi_commit_mask_is_empty(&mask) || full_size==0) return true;
511
512
0
  if (mi_commit_mask_any_set(&segment->commit_mask, &mask)) {
513
    // purging
514
0
    mi_assert_internal((void*)start != (void*)segment);
515
0
    mi_assert_internal(segment->allow_decommit);
516
0
    const bool decommitted = _mi_os_purge(start, full_size, stats);  // reset or decommit
517
0
    if (decommitted) {
518
0
      mi_commit_mask_t cmask;
519
0
      mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);
520
0
      _mi_stat_increase(&_mi_stats_main.committed, full_size - _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for double counting
521
0
      mi_commit_mask_clear(&segment->commit_mask, &mask);
522
0
    }
523
0
  }
524
525
  // always clear any scheduled purges in our range
526
0
  mi_commit_mask_clear(&segment->purge_mask, &mask);
527
0
  return true;
528
0
}
529
530
0
static void mi_segment_schedule_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
531
0
  if (!segment->allow_purge) return;
532
533
0
  if (mi_option_get(mi_option_purge_delay) == 0) {
534
0
    mi_segment_purge(segment, p, size, stats);
535
0
  }
536
0
  else {
537
    // register for future purge in the purge mask
538
0
    uint8_t* start = NULL;
539
0
    size_t   full_size = 0;
540
0
    mi_commit_mask_t mask;
541
0
    mi_segment_commit_mask(segment, true /*conservative*/, p, size, &start, &full_size, &mask);
542
0
    if (mi_commit_mask_is_empty(&mask) || full_size==0) return;
543
544
    // update delayed commit
545
0
    mi_assert_internal(segment->purge_expire > 0 || mi_commit_mask_is_empty(&segment->purge_mask));
546
0
    mi_commit_mask_t cmask;
547
0
    mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);  // only purge what is committed; span_free may try to decommit more
548
0
    mi_commit_mask_set(&segment->purge_mask, &cmask);
549
0
    mi_msecs_t now = _mi_clock_now();
550
0
    if (segment->purge_expire == 0) {
551
      // no previous purgess, initialize now
552
0
      segment->purge_expire = now + mi_option_get(mi_option_purge_delay);
553
0
    }
554
0
    else if (segment->purge_expire <= now) {
555
      // previous purge mask already expired
556
0
      if (segment->purge_expire + mi_option_get(mi_option_purge_extend_delay) <= now) {
557
0
        mi_segment_try_purge(segment, true, stats);
558
0
      }
559
0
      else {
560
0
        segment->purge_expire = now + mi_option_get(mi_option_purge_extend_delay); // (mi_option_get(mi_option_purge_delay) / 8); // wait a tiny bit longer in case there is a series of free's
561
0
      }
562
0
    }
563
0
    else {
564
      // previous purge mask is not yet expired, increase the expiration by a bit.
565
0
      segment->purge_expire += mi_option_get(mi_option_purge_extend_delay);
566
0
    }
567
0
  }
568
0
}
569
570
0
static void mi_segment_try_purge(mi_segment_t* segment, bool force, mi_stats_t* stats) {
571
0
  if (!segment->allow_purge || mi_commit_mask_is_empty(&segment->purge_mask)) return;
572
0
  mi_msecs_t now = _mi_clock_now();
573
0
  if (!force && now < segment->purge_expire) return;
574
575
0
  mi_commit_mask_t mask = segment->purge_mask;
576
0
  segment->purge_expire = 0;
577
0
  mi_commit_mask_create_empty(&segment->purge_mask);
578
579
0
  size_t idx;
580
0
  size_t count;
581
0
  mi_commit_mask_foreach(&mask, idx, count) {
582
    // if found, decommit that sequence
583
0
    if (count > 0) {
584
0
      uint8_t* p = (uint8_t*)segment + (idx*MI_COMMIT_SIZE);
585
0
      size_t size = count * MI_COMMIT_SIZE;
586
0
      mi_segment_purge(segment, p, size, stats);
587
0
    }
588
0
  }
589
0
  mi_commit_mask_foreach_end()
590
0
  mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));
591
0
}
592
593
594
/* -----------------------------------------------------------
595
   Span free
596
----------------------------------------------------------- */
597
598
0
static bool mi_segment_is_abandoned(mi_segment_t* segment) {
599
0
  return (segment->thread_id == 0);
600
0
}
601
602
// note: can be called on abandoned segments
603
0
static void mi_segment_span_free(mi_segment_t* segment, size_t slice_index, size_t slice_count, bool allow_purge, mi_segments_tld_t* tld) {
604
0
  mi_assert_internal(slice_index < segment->slice_entries);
605
0
  mi_span_queue_t* sq = (segment->kind == MI_SEGMENT_HUGE || mi_segment_is_abandoned(segment)
606
0
                          ? NULL : mi_span_queue_for(slice_count,tld));
607
0
  if (slice_count==0) slice_count = 1;
608
0
  mi_assert_internal(slice_index + slice_count - 1 < segment->slice_entries);
609
610
  // set first and last slice (the intermediates can be undetermined)
611
0
  mi_slice_t* slice = &segment->slices[slice_index];
612
0
  slice->slice_count = (uint32_t)slice_count;
613
0
  mi_assert_internal(slice->slice_count == slice_count); // no overflow?
614
0
  slice->slice_offset = 0;
615
0
  if (slice_count > 1) {
616
0
    mi_slice_t* last = &segment->slices[slice_index + slice_count - 1];
617
0
    last->slice_count = 0;
618
0
    last->slice_offset = (uint32_t)(sizeof(mi_page_t)*(slice_count - 1));
619
0
    last->xblock_size = 0;
620
0
  }
621
622
  // perhaps decommit
623
0
  if (allow_purge) {
624
0
    mi_segment_schedule_purge(segment, mi_slice_start(slice), slice_count * MI_SEGMENT_SLICE_SIZE, tld->stats);
625
0
  }
626
627
  // and push it on the free page queue (if it was not a huge page)
628
0
  if (sq != NULL) mi_span_queue_push( sq, slice );
629
0
             else slice->xblock_size = 0; // mark huge page as free anyways
630
0
}
631
632
/*
633
// called from reclaim to add existing free spans
634
static void mi_segment_span_add_free(mi_slice_t* slice, mi_segments_tld_t* tld) {
635
  mi_segment_t* segment = _mi_ptr_segment(slice);
636
  mi_assert_internal(slice->xblock_size==0 && slice->slice_count>0 && slice->slice_offset==0);
637
  size_t slice_index = mi_slice_index(slice);
638
  mi_segment_span_free(segment,slice_index,slice->slice_count,tld);
639
}
640
*/
641
642
0
static void mi_segment_span_remove_from_queue(mi_slice_t* slice, mi_segments_tld_t* tld) {
643
0
  mi_assert_internal(slice->slice_count > 0 && slice->slice_offset==0 && slice->xblock_size==0);
644
0
  mi_assert_internal(_mi_ptr_segment(slice)->kind != MI_SEGMENT_HUGE);
645
0
  mi_span_queue_t* sq = mi_span_queue_for(slice->slice_count, tld);
646
0
  mi_span_queue_delete(sq, slice);
647
0
}
648
649
// note: can be called on abandoned segments
650
0
static mi_slice_t* mi_segment_span_free_coalesce(mi_slice_t* slice, mi_segments_tld_t* tld) {
651
0
  mi_assert_internal(slice != NULL && slice->slice_count > 0 && slice->slice_offset == 0);
652
0
  mi_segment_t* segment = _mi_ptr_segment(slice);
653
0
  bool is_abandoned = mi_segment_is_abandoned(segment);
654
655
  // for huge pages, just mark as free but don't add to the queues
656
0
  if (segment->kind == MI_SEGMENT_HUGE) {
657
    // issue #691: segment->used can be 0 if the huge page block was freed while abandoned (reclaim will get here in that case)
658
0
    mi_assert_internal((segment->used==0 && slice->xblock_size==0) || segment->used == 1);  // decreased right after this call in `mi_segment_page_clear`
659
0
    slice->xblock_size = 0;  // mark as free anyways
660
    // we should mark the last slice `xblock_size=0` now to maintain invariants but we skip it to
661
    // avoid a possible cache miss (and the segment is about to be freed)
662
0
    return slice;
663
0
  }
664
665
  // otherwise coalesce the span and add to the free span queues
666
0
  size_t slice_count = slice->slice_count;
667
0
  mi_slice_t* next = slice + slice->slice_count;
668
0
  mi_assert_internal(next <= mi_segment_slices_end(segment));
669
0
  if (next < mi_segment_slices_end(segment) && next->xblock_size==0) {
670
    // free next block -- remove it from free and merge
671
0
    mi_assert_internal(next->slice_count > 0 && next->slice_offset==0);
672
0
    slice_count += next->slice_count; // extend
673
0
    if (!is_abandoned) { mi_segment_span_remove_from_queue(next, tld); }
674
0
  }
675
0
  if (slice > segment->slices) {
676
0
    mi_slice_t* prev = mi_slice_first(slice - 1);
677
0
    mi_assert_internal(prev >= segment->slices);
678
0
    if (prev->xblock_size==0) {
679
      // free previous slice -- remove it from free and merge
680
0
      mi_assert_internal(prev->slice_count > 0 && prev->slice_offset==0);
681
0
      slice_count += prev->slice_count;
682
0
      if (!is_abandoned) { mi_segment_span_remove_from_queue(prev, tld); }
683
0
      slice = prev;
684
0
    }
685
0
  }
686
687
  // and add the new free page
688
0
  mi_segment_span_free(segment, mi_slice_index(slice), slice_count, true, tld);
689
0
  return slice;
690
0
}
691
692
693
694
/* -----------------------------------------------------------
695
   Page allocation
696
----------------------------------------------------------- */
697
698
// Note: may still return NULL if committing the memory failed
699
0
static mi_page_t* mi_segment_span_allocate(mi_segment_t* segment, size_t slice_index, size_t slice_count, mi_segments_tld_t* tld) {
700
0
  mi_assert_internal(slice_index < segment->slice_entries);
701
0
  mi_slice_t* const slice = &segment->slices[slice_index];
702
0
  mi_assert_internal(slice->xblock_size==0 || slice->xblock_size==1);
703
704
  // commit before changing the slice data
705
0
  if (!mi_segment_ensure_committed(segment, _mi_segment_page_start_from_slice(segment, slice, 0, NULL), slice_count * MI_SEGMENT_SLICE_SIZE, tld->stats)) {
706
0
    return NULL;  // commit failed!
707
0
  }
708
709
  // convert the slices to a page
710
0
  slice->slice_offset = 0;
711
0
  slice->slice_count = (uint32_t)slice_count;
712
0
  mi_assert_internal(slice->slice_count == slice_count);
713
0
  const size_t bsize = slice_count * MI_SEGMENT_SLICE_SIZE;
714
0
  slice->xblock_size = (uint32_t)(bsize >= MI_HUGE_BLOCK_SIZE ? MI_HUGE_BLOCK_SIZE : bsize);
715
0
  mi_page_t*  page = mi_slice_to_page(slice);
716
0
  mi_assert_internal(mi_page_block_size(page) == bsize);
717
718
  // set slice back pointers for the first MI_MAX_SLICE_OFFSET entries
719
0
  size_t extra = slice_count-1;
720
0
  if (extra > MI_MAX_SLICE_OFFSET) extra = MI_MAX_SLICE_OFFSET;
721
0
  if (slice_index + extra >= segment->slice_entries) extra = segment->slice_entries - slice_index - 1;  // huge objects may have more slices than available entries in the segment->slices
722
723
0
  mi_slice_t* slice_next = slice + 1;
724
0
  for (size_t i = 1; i <= extra; i++, slice_next++) {
725
0
    slice_next->slice_offset = (uint32_t)(sizeof(mi_slice_t)*i);
726
0
    slice_next->slice_count = 0;
727
0
    slice_next->xblock_size = 1;
728
0
  }
729
730
  // and also for the last one (if not set already) (the last one is needed for coalescing and for large alignments)
731
  // note: the cast is needed for ubsan since the index can be larger than MI_SLICES_PER_SEGMENT for huge allocations (see #543)
732
0
  mi_slice_t* last = slice + slice_count - 1;
733
0
  mi_slice_t* end = (mi_slice_t*)mi_segment_slices_end(segment);
734
0
  if (last > end) last = end;
735
0
  if (last > slice) {
736
0
    last->slice_offset = (uint32_t)(sizeof(mi_slice_t) * (last - slice));
737
0
    last->slice_count = 0;
738
0
    last->xblock_size = 1;
739
0
  }
740
741
  // and initialize the page
742
0
  page->is_committed = true;
743
0
  segment->used++;
744
0
  return page;
745
0
}
746
747
0
static void mi_segment_slice_split(mi_segment_t* segment, mi_slice_t* slice, size_t slice_count, mi_segments_tld_t* tld) {
748
0
  mi_assert_internal(_mi_ptr_segment(slice) == segment);
749
0
  mi_assert_internal(slice->slice_count >= slice_count);
750
0
  mi_assert_internal(slice->xblock_size > 0); // no more in free queue
751
0
  if (slice->slice_count <= slice_count) return;
752
0
  mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
753
0
  size_t next_index = mi_slice_index(slice) + slice_count;
754
0
  size_t next_count = slice->slice_count - slice_count;
755
0
  mi_segment_span_free(segment, next_index, next_count, false /* don't purge left-over part */, tld);
756
0
  slice->slice_count = (uint32_t)slice_count;
757
0
}
758
759
0
static mi_page_t* mi_segments_page_find_and_allocate(size_t slice_count, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld) {
760
0
  mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_LARGE_OBJ_SIZE_MAX);
761
  // search from best fit up
762
0
  mi_span_queue_t* sq = mi_span_queue_for(slice_count, tld);
763
0
  if (slice_count == 0) slice_count = 1;
764
0
  while (sq <= &tld->spans[MI_SEGMENT_BIN_MAX]) {
765
0
    for (mi_slice_t* slice = sq->first; slice != NULL; slice = slice->next) {
766
0
      if (slice->slice_count >= slice_count) {
767
        // found one
768
0
        mi_segment_t* segment = _mi_ptr_segment(slice);
769
0
        if (_mi_arena_memid_is_suitable(segment->memid, req_arena_id)) {
770
          // found a suitable page span
771
0
          mi_span_queue_delete(sq, slice);
772
773
0
          if (slice->slice_count > slice_count) {
774
0
            mi_segment_slice_split(segment, slice, slice_count, tld);
775
0
          }
776
0
          mi_assert_internal(slice != NULL && slice->slice_count == slice_count && slice->xblock_size > 0);
777
0
          mi_page_t* page = mi_segment_span_allocate(segment, mi_slice_index(slice), slice->slice_count, tld);
778
0
          if (page == NULL) {
779
            // commit failed; return NULL but first restore the slice
780
0
            mi_segment_span_free_coalesce(slice, tld);
781
0
            return NULL;
782
0
          }
783
0
          return page;
784
0
        }
785
0
      }
786
0
    }
787
0
    sq++;
788
0
  }
789
  // could not find a page..
790
0
  return NULL;
791
0
}
792
793
794
/* -----------------------------------------------------------
795
   Segment allocation
796
----------------------------------------------------------- */
797
798
static mi_segment_t* mi_segment_os_alloc( size_t required, size_t page_alignment, bool eager_delayed, mi_arena_id_t req_arena_id,
799
                                          size_t* psegment_slices, size_t* ppre_size, size_t* pinfo_slices,
800
                                          bool commit, mi_segments_tld_t* tld, mi_os_tld_t* os_tld)
801
802
0
{
803
0
  mi_memid_t memid;
804
0
  bool   allow_large = (!eager_delayed && (MI_SECURE == 0)); // only allow large OS pages once we are no longer lazy
805
0
  size_t align_offset = 0;
806
0
  size_t alignment = MI_SEGMENT_ALIGN;
807
808
0
  if (page_alignment > 0) {
809
    // mi_assert_internal(huge_page != NULL);
810
0
    mi_assert_internal(page_alignment >= MI_SEGMENT_ALIGN);
811
0
    alignment = page_alignment;
812
0
    const size_t info_size = (*pinfo_slices) * MI_SEGMENT_SLICE_SIZE;
813
0
    align_offset = _mi_align_up( info_size, MI_SEGMENT_ALIGN );
814
0
    const size_t extra = align_offset - info_size;
815
    // recalculate due to potential guard pages
816
0
    *psegment_slices = mi_segment_calculate_slices(required + extra, ppre_size, pinfo_slices);
817
818
    // mi_page_t.slice_count type is uint32_t
819
0
    if (*psegment_slices > (size_t)UINT32_MAX) return NULL;
820
0
  }
821
822
0
  const size_t segment_size = (*psegment_slices) * MI_SEGMENT_SLICE_SIZE;
823
0
  mi_segment_t* segment = (mi_segment_t*)_mi_arena_alloc_aligned(segment_size, alignment, align_offset, commit, allow_large, req_arena_id, &memid, os_tld);
824
0
  if (segment == NULL) {
825
0
    return NULL;  // failed to allocate
826
0
  }
827
828
  // ensure metadata part of the segment is committed
829
0
  mi_commit_mask_t commit_mask;
830
0
  if (memid.initially_committed) {
831
0
    mi_commit_mask_create_full(&commit_mask);
832
0
  }
833
0
  else {
834
    // at least commit the info slices
835
0
    const size_t commit_needed = _mi_divide_up((*pinfo_slices)*MI_SEGMENT_SLICE_SIZE, MI_COMMIT_SIZE);
836
0
    mi_assert_internal(commit_needed>0);
837
0
    mi_commit_mask_create(0, commit_needed, &commit_mask);
838
0
    mi_assert_internal(commit_needed*MI_COMMIT_SIZE >= (*pinfo_slices)*MI_SEGMENT_SLICE_SIZE);
839
0
    if (!_mi_os_commit(segment, commit_needed*MI_COMMIT_SIZE, NULL, tld->stats)) {
840
0
      _mi_arena_free(segment,segment_size,0,memid,tld->stats);
841
0
      return NULL;
842
0
    }
843
0
  }
844
0
  mi_assert_internal(segment != NULL && (uintptr_t)segment % MI_SEGMENT_SIZE == 0);
845
846
0
  segment->memid = memid;
847
0
  segment->allow_decommit = !memid.is_pinned;
848
0
  segment->allow_purge = segment->allow_decommit && (mi_option_get(mi_option_purge_delay) >= 0);
849
0
  segment->segment_size = segment_size;
850
0
  segment->commit_mask = commit_mask;
851
0
  segment->purge_expire = 0;
852
0
  mi_commit_mask_create_empty(&segment->purge_mask);
853
0
  mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);  // tsan
854
855
0
  mi_segments_track_size((long)(segment_size), tld);
856
0
  _mi_segment_map_allocated_at(segment);
857
0
  return segment;
858
0
}
859
860
861
// Allocate a segment from the OS aligned to `MI_SEGMENT_SIZE` .
862
static mi_segment_t* mi_segment_alloc(size_t required, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld, mi_os_tld_t* os_tld, mi_page_t** huge_page)
863
0
{
864
0
  mi_assert_internal((required==0 && huge_page==NULL) || (required>0 && huge_page != NULL));
865
866
  // calculate needed sizes first
867
0
  size_t info_slices;
868
0
  size_t pre_size;
869
0
  size_t segment_slices = mi_segment_calculate_slices(required, &pre_size, &info_slices);
870
871
  // mi_page_t.slice_count type is uint32_t
872
0
  if (segment_slices > (size_t)UINT32_MAX) return NULL;
873
874
  // Commit eagerly only if not the first N lazy segments (to reduce impact of many threads that allocate just a little)
875
0
  const bool eager_delay = (// !_mi_os_has_overcommit() &&             // never delay on overcommit systems
876
0
                            _mi_current_thread_count() > 1 &&       // do not delay for the first N threads
877
0
                            tld->count < (size_t)mi_option_get(mi_option_eager_commit_delay));
878
0
  const bool eager = !eager_delay && mi_option_is_enabled(mi_option_eager_commit);
879
0
  bool commit = eager || (required > 0);
880
881
  // Allocate the segment from the OS
882
0
  mi_segment_t* segment = mi_segment_os_alloc(required, page_alignment, eager_delay, req_arena_id,
883
0
                                              &segment_slices, &pre_size, &info_slices, commit, tld, os_tld);
884
0
  if (segment == NULL) return NULL;
885
886
  // zero the segment info? -- not always needed as it may be zero initialized from the OS
887
0
  if (!segment->memid.initially_zero) {
888
0
    ptrdiff_t ofs    = offsetof(mi_segment_t, next);
889
0
    size_t    prefix = offsetof(mi_segment_t, slices) - ofs;
890
0
    size_t    zsize  = prefix + (sizeof(mi_slice_t) * (segment_slices + 1)); // one more
891
0
    _mi_memzero((uint8_t*)segment + ofs, zsize);
892
0
  }
893
894
  // initialize the rest of the segment info
895
0
  const size_t slice_entries = (segment_slices > MI_SLICES_PER_SEGMENT ? MI_SLICES_PER_SEGMENT : segment_slices);
896
0
  segment->segment_slices = segment_slices;
897
0
  segment->segment_info_slices = info_slices;
898
0
  segment->thread_id = _mi_thread_id();
899
0
  segment->cookie = _mi_ptr_cookie(segment);
900
0
  segment->slice_entries = slice_entries;
901
0
  segment->kind = (required == 0 ? MI_SEGMENT_NORMAL : MI_SEGMENT_HUGE);
902
903
  // _mi_memzero(segment->slices, sizeof(mi_slice_t)*(info_slices+1));
904
0
  _mi_stat_increase(&tld->stats->page_committed, mi_segment_info_size(segment));
905
906
  // set up guard pages
907
0
  size_t guard_slices = 0;
908
0
  if (MI_SECURE>0) {
909
    // in secure mode, we set up a protected page in between the segment info
910
    // and the page data, and at the end of the segment.
911
0
    size_t os_pagesize = _mi_os_page_size();
912
0
    mi_assert_internal(mi_segment_info_size(segment) - os_pagesize >= pre_size);
913
0
    _mi_os_protect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);
914
0
    uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;
915
0
    mi_segment_ensure_committed(segment, end, os_pagesize, tld->stats);
916
0
    _mi_os_protect(end, os_pagesize);
917
0
    if (slice_entries == segment_slices) segment->slice_entries--; // don't use the last slice :-(
918
0
    guard_slices = 1;
919
0
  }
920
921
  // reserve first slices for segment info
922
0
  mi_page_t* page0 = mi_segment_span_allocate(segment, 0, info_slices, tld);
923
0
  mi_assert_internal(page0!=NULL); if (page0==NULL) return NULL; // cannot fail as we always commit in advance
924
0
  mi_assert_internal(segment->used == 1);
925
0
  segment->used = 0; // don't count our internal slices towards usage
926
927
  // initialize initial free pages
928
0
  if (segment->kind == MI_SEGMENT_NORMAL) { // not a huge page
929
0
    mi_assert_internal(huge_page==NULL);
930
0
    mi_segment_span_free(segment, info_slices, segment->slice_entries - info_slices, false /* don't purge */, tld);
931
0
  }
932
0
  else {
933
0
    mi_assert_internal(huge_page!=NULL);
934
0
    mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));
935
0
    mi_assert_internal(mi_commit_mask_is_full(&segment->commit_mask));
936
0
    *huge_page = mi_segment_span_allocate(segment, info_slices, segment_slices - info_slices - guard_slices, tld);
937
0
    mi_assert_internal(*huge_page != NULL); // cannot fail as we commit in advance
938
0
  }
939
940
0
  mi_assert_expensive(mi_segment_is_valid(segment,tld));
941
0
  return segment;
942
0
}
943
944
945
0
static void mi_segment_free(mi_segment_t* segment, bool force, mi_segments_tld_t* tld) {
946
0
  MI_UNUSED(force);
947
0
  mi_assert_internal(segment != NULL);
948
0
  mi_assert_internal(segment->next == NULL);
949
0
  mi_assert_internal(segment->used == 0);
950
951
  // Remove the free pages
952
0
  mi_slice_t* slice = &segment->slices[0];
953
0
  const mi_slice_t* end = mi_segment_slices_end(segment);
954
  #if MI_DEBUG>1
955
  size_t page_count = 0;
956
  #endif
957
0
  while (slice < end) {
958
0
    mi_assert_internal(slice->slice_count > 0);
959
0
    mi_assert_internal(slice->slice_offset == 0);
960
0
    mi_assert_internal(mi_slice_index(slice)==0 || slice->xblock_size == 0); // no more used pages ..
961
0
    if (slice->xblock_size == 0 && segment->kind != MI_SEGMENT_HUGE) {
962
0
      mi_segment_span_remove_from_queue(slice, tld);
963
0
    }
964
    #if MI_DEBUG>1
965
    page_count++;
966
    #endif
967
0
    slice = slice + slice->slice_count;
968
0
  }
969
0
  mi_assert_internal(page_count == 2); // first page is allocated by the segment itself
970
971
  // stats
972
0
  _mi_stat_decrease(&tld->stats->page_committed, mi_segment_info_size(segment));
973
974
  // return it to the OS
975
0
  mi_segment_os_free(segment, tld);
976
0
}
977
978
979
/* -----------------------------------------------------------
980
   Page Free
981
----------------------------------------------------------- */
982
983
static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld);
984
985
// note: can be called on abandoned pages
986
0
static mi_slice_t* mi_segment_page_clear(mi_page_t* page, mi_segments_tld_t* tld) {
987
0
  mi_assert_internal(page->xblock_size > 0);
988
0
  mi_assert_internal(mi_page_all_free(page));
989
0
  mi_segment_t* segment = _mi_ptr_segment(page);
990
0
  mi_assert_internal(segment->used > 0);
991
#ifdef Py_GIL_DISABLED
992
  mi_assert_internal(page->qsbr_goal == 0);
993
  mi_assert_internal(page->qsbr_node.next == NULL);
994
#endif
995
996
0
  size_t inuse = page->capacity * mi_page_block_size(page);
997
0
  _mi_stat_decrease(&tld->stats->page_committed, inuse);
998
0
  _mi_stat_decrease(&tld->stats->pages, 1);
999
1000
  // reset the page memory to reduce memory pressure?
1001
0
  if (segment->allow_decommit && mi_option_is_enabled(mi_option_deprecated_page_reset)) {
1002
0
    size_t psize;
1003
0
    uint8_t* start = _mi_page_start(segment, page, &psize);
1004
0
    _mi_os_reset(start, psize, tld->stats);
1005
0
  }
1006
1007
  // zero the page data, but not the segment fields
1008
0
  page->is_zero_init = false;
1009
0
  ptrdiff_t ofs = offsetof(mi_page_t, capacity);
1010
0
  _mi_memzero((uint8_t*)page + ofs, sizeof(*page) - ofs);
1011
0
  page->xblock_size = 1;
1012
1013
  // and free it
1014
0
  mi_slice_t* slice = mi_segment_span_free_coalesce(mi_page_to_slice(page), tld);
1015
0
  segment->used--;
1016
  // cannot assert segment valid as it is called during reclaim
1017
  // mi_assert_expensive(mi_segment_is_valid(segment, tld));
1018
0
  return slice;
1019
0
}
1020
1021
void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld)
1022
0
{
1023
0
  mi_assert(page != NULL);
1024
1025
0
  mi_segment_t* segment = _mi_page_segment(page);
1026
0
  mi_assert_expensive(mi_segment_is_valid(segment,tld));
1027
1028
  // mark it as free now
1029
0
  mi_segment_page_clear(page, tld);
1030
0
  mi_assert_expensive(mi_segment_is_valid(segment, tld));
1031
1032
0
  if (segment->used == 0) {
1033
    // no more used pages; remove from the free list and free the segment
1034
0
    mi_segment_free(segment, force, tld);
1035
0
  }
1036
0
  else if (segment->used == segment->abandoned) {
1037
    // only abandoned pages; remove from free list and abandon
1038
0
    mi_segment_abandon(segment,tld);
1039
0
  }
1040
0
}
1041
1042
1043
/* -----------------------------------------------------------
1044
Abandonment
1045
1046
When threads terminate, they can leave segments with
1047
live blocks (reachable through other threads). Such segments
1048
are "abandoned" and will be reclaimed by other threads to
1049
reuse their pages and/or free them eventually
1050
1051
We maintain a global list of abandoned segments that are
1052
reclaimed on demand. Since this is shared among threads
1053
the implementation needs to avoid the A-B-A problem on
1054
popping abandoned segments: <https://en.wikipedia.org/wiki/ABA_problem>
1055
We use tagged pointers to avoid accidentally identifying
1056
reused segments, much like stamped references in Java.
1057
Secondly, we maintain a reader counter to avoid resetting
1058
or decommitting segments that have a pending read operation.
1059
1060
Note: the current implementation is one possible design;
1061
another way might be to keep track of abandoned segments
1062
in the arenas/segment_cache's. This would have the advantage of keeping
1063
all concurrent code in one place and not needing to deal
1064
with ABA issues. The drawback is that it is unclear how to
1065
scan abandoned segments efficiently in that case as they
1066
would be spread among all other segments in the arenas.
1067
----------------------------------------------------------- */
1068
1069
// Use the bottom 20-bits (on 64-bit) of the aligned segment pointers
1070
// to put in a tag that increments on update to avoid the A-B-A problem.
1071
0
#define MI_TAGGED_MASK   MI_SEGMENT_MASK
1072
1073
0
static mi_segment_t* mi_tagged_segment_ptr(mi_tagged_segment_t ts) {
1074
0
  return (mi_segment_t*)(ts & ~MI_TAGGED_MASK);
1075
0
}
1076
1077
0
static mi_tagged_segment_t mi_tagged_segment(mi_segment_t* segment, mi_tagged_segment_t ts) {
1078
0
  mi_assert_internal(((uintptr_t)segment & MI_TAGGED_MASK) == 0);
1079
0
  uintptr_t tag = ((ts & MI_TAGGED_MASK) + 1) & MI_TAGGED_MASK;
1080
0
  return ((uintptr_t)segment | tag);
1081
0
}
1082
1083
mi_abandoned_pool_t _mi_abandoned_default;
1084
1085
// Push on the visited list
1086
0
static void mi_abandoned_visited_push(mi_abandoned_pool_t *pool, mi_segment_t* segment) {
1087
0
  mi_assert_internal(segment->thread_id == 0);
1088
0
  mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t,&segment->abandoned_next) == NULL);
1089
0
  mi_assert_internal(segment->next == NULL);
1090
0
  mi_assert_internal(segment->used > 0);
1091
0
  mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &pool->abandoned_visited);
1092
0
  do {
1093
0
    mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, anext);
1094
0
  } while (!mi_atomic_cas_ptr_weak_release(mi_segment_t, &pool->abandoned_visited, &anext, segment));
1095
0
  mi_atomic_increment_relaxed(&pool->abandoned_visited_count);
1096
0
}
1097
1098
// Move the visited list to the abandoned list.
1099
static bool mi_abandoned_visited_revisit(mi_abandoned_pool_t *pool)
1100
0
{
1101
  // quick check if the visited list is empty
1102
0
  if (mi_atomic_load_ptr_relaxed(mi_segment_t, &pool->abandoned_visited) == NULL) return false;
1103
1104
  // grab the whole visited list
1105
0
  mi_segment_t* first = mi_atomic_exchange_ptr_acq_rel(mi_segment_t, &pool->abandoned_visited, NULL);
1106
0
  if (first == NULL) return false;
1107
1108
  // first try to swap directly if the abandoned list happens to be NULL
1109
0
  mi_tagged_segment_t afirst;
1110
0
  mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1111
0
  if (mi_tagged_segment_ptr(ts)==NULL) {
1112
0
    size_t count = mi_atomic_load_relaxed(&pool->abandoned_visited_count);
1113
0
    afirst = mi_tagged_segment(first, ts);
1114
0
    if (mi_atomic_cas_strong_acq_rel(&pool->abandoned, &ts, afirst)) {
1115
0
      mi_atomic_add_relaxed(&pool->abandoned_count, count);
1116
0
      mi_atomic_sub_relaxed(&pool->abandoned_visited_count, count);
1117
0
      return true;
1118
0
    }
1119
0
  }
1120
1121
  // find the last element of the visited list: O(n)
1122
0
  mi_segment_t* last = first;
1123
0
  mi_segment_t* next;
1124
0
  while ((next = mi_atomic_load_ptr_relaxed(mi_segment_t, &last->abandoned_next)) != NULL) {
1125
0
    last = next;
1126
0
  }
1127
1128
  // and atomically prepend to the abandoned list
1129
  // (no need to increase the readers as we don't access the abandoned segments)
1130
0
  mi_tagged_segment_t anext = mi_atomic_load_relaxed(&pool->abandoned);
1131
0
  size_t count;
1132
0
  do {
1133
0
    count = mi_atomic_load_relaxed(&pool->abandoned_visited_count);
1134
0
    mi_atomic_store_ptr_release(mi_segment_t, &last->abandoned_next, mi_tagged_segment_ptr(anext));
1135
0
    afirst = mi_tagged_segment(first, anext);
1136
0
  } while (!mi_atomic_cas_weak_release(&pool->abandoned, &anext, afirst));
1137
0
  mi_atomic_add_relaxed(&pool->abandoned_count, count);
1138
0
  mi_atomic_sub_relaxed(&pool->abandoned_visited_count, count);
1139
0
  return true;
1140
0
}
1141
1142
// Push on the abandoned list.
1143
0
static void mi_abandoned_push(mi_abandoned_pool_t* pool, mi_segment_t* segment) {
1144
0
  mi_assert_internal(segment->thread_id == 0);
1145
0
  mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1146
0
  mi_assert_internal(segment->next == NULL);
1147
0
  mi_assert_internal(segment->used > 0);
1148
0
  mi_tagged_segment_t next;
1149
0
  mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1150
0
  do {
1151
0
    mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, mi_tagged_segment_ptr(ts));
1152
0
    next = mi_tagged_segment(segment, ts);
1153
0
  } while (!mi_atomic_cas_weak_release(&pool->abandoned, &ts, next));
1154
0
  mi_atomic_increment_relaxed(&pool->abandoned_count);
1155
0
}
1156
1157
// Wait until there are no more pending reads on segments that used to be in the abandoned list
1158
// called for example from `arena.c` before decommitting
1159
0
void _mi_abandoned_await_readers(mi_abandoned_pool_t* pool) {
1160
0
  size_t n;
1161
0
  do {
1162
0
    n = mi_atomic_load_acquire(&pool->abandoned_readers);
1163
0
    if (n != 0) mi_atomic_yield();
1164
0
  } while (n != 0);
1165
0
}
1166
1167
// Pop from the abandoned list
1168
0
static mi_segment_t* mi_abandoned_pop(mi_abandoned_pool_t* pool) {
1169
0
  mi_segment_t* segment;
1170
  // Check efficiently if it is empty (or if the visited list needs to be moved)
1171
0
  mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1172
0
  segment = mi_tagged_segment_ptr(ts);
1173
0
  if mi_likely(segment == NULL) {
1174
0
    if mi_likely(!mi_abandoned_visited_revisit(pool)) { // try to swap in the visited list on NULL
1175
0
      return NULL;
1176
0
    }
1177
0
  }
1178
1179
  // Do a pop. We use a reader count to prevent
1180
  // a segment to be decommitted while a read is still pending,
1181
  // and a tagged pointer to prevent A-B-A link corruption.
1182
  // (this is called from `region.c:_mi_mem_free` for example)
1183
0
  mi_atomic_increment_relaxed(&pool->abandoned_readers);  // ensure no segment gets decommitted
1184
0
  mi_tagged_segment_t next = 0;
1185
0
  ts = mi_atomic_load_acquire(&pool->abandoned);
1186
0
  do {
1187
0
    segment = mi_tagged_segment_ptr(ts);
1188
0
    if (segment != NULL) {
1189
0
      mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next);
1190
0
      next = mi_tagged_segment(anext, ts); // note: reads the segment's `abandoned_next` field so should not be decommitted
1191
0
    }
1192
0
  } while (segment != NULL && !mi_atomic_cas_weak_acq_rel(&pool->abandoned, &ts, next));
1193
0
  mi_atomic_decrement_relaxed(&pool->abandoned_readers);  // release reader lock
1194
0
  if (segment != NULL) {
1195
0
    mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
1196
0
    mi_atomic_decrement_relaxed(&pool->abandoned_count);
1197
0
  }
1198
0
  return segment;
1199
0
}
1200
1201
/* -----------------------------------------------------------
1202
   Abandon segment/page
1203
----------------------------------------------------------- */
1204
1205
0
static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
1206
0
  mi_assert_internal(segment->used == segment->abandoned);
1207
0
  mi_assert_internal(segment->used > 0);
1208
0
  mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1209
0
  mi_assert_internal(segment->abandoned_visits == 0);
1210
0
  mi_assert_expensive(mi_segment_is_valid(segment,tld));
1211
1212
  // remove the free pages from the free page queues
1213
0
  mi_slice_t* slice = &segment->slices[0];
1214
0
  const mi_slice_t* end = mi_segment_slices_end(segment);
1215
0
  while (slice < end) {
1216
0
    mi_assert_internal(slice->slice_count > 0);
1217
0
    mi_assert_internal(slice->slice_offset == 0);
1218
0
    if (slice->xblock_size == 0) { // a free page
1219
0
      mi_segment_span_remove_from_queue(slice,tld);
1220
0
      slice->xblock_size = 0; // but keep it free
1221
0
    }
1222
0
    slice = slice + slice->slice_count;
1223
0
  }
1224
1225
  // perform delayed decommits (forcing is much slower on mstress)
1226
0
  mi_segment_try_purge(segment, mi_option_is_enabled(mi_option_abandoned_page_purge) /* force? */, tld->stats);
1227
1228
  // all pages in the segment are abandoned; add it to the abandoned list
1229
0
  _mi_stat_increase(&tld->stats->segments_abandoned, 1);
1230
0
  mi_segments_track_size(-((long)mi_segment_size(segment)), tld);
1231
0
  segment->thread_id = 0;
1232
0
  mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
1233
0
  segment->abandoned_visits = 1;   // from 0 to 1 to signify it is abandoned
1234
0
  mi_abandoned_push(tld->abandoned, segment);
1235
0
}
1236
1237
0
void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) {
1238
0
  mi_assert(page != NULL);
1239
0
  mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
1240
0
  mi_assert_internal(mi_page_heap(page) == NULL);
1241
0
  mi_segment_t* segment = _mi_page_segment(page);
1242
1243
0
  mi_assert_expensive(mi_segment_is_valid(segment,tld));
1244
0
  segment->abandoned++;
1245
1246
0
  _mi_stat_increase(&tld->stats->pages_abandoned, 1);
1247
0
  mi_assert_internal(segment->abandoned <= segment->used);
1248
0
  if (segment->used == segment->abandoned) {
1249
    // all pages are abandoned, abandon the entire segment
1250
0
    mi_segment_abandon(segment, tld);
1251
0
  }
1252
0
}
1253
1254
/* -----------------------------------------------------------
1255
  Reclaim abandoned pages
1256
----------------------------------------------------------- */
1257
1258
0
static mi_slice_t* mi_slices_start_iterate(mi_segment_t* segment, const mi_slice_t** end) {
1259
0
  mi_slice_t* slice = &segment->slices[0];
1260
0
  *end = mi_segment_slices_end(segment);
1261
0
  mi_assert_internal(slice->slice_count>0 && slice->xblock_size>0); // segment allocated page
1262
0
  slice = slice + slice->slice_count; // skip the first segment allocated page
1263
0
  return slice;
1264
0
}
1265
1266
// Possibly free pages and check if free space is available
1267
static bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, size_t block_size, mi_segments_tld_t* tld)
1268
0
{
1269
0
  mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE);
1270
0
  mi_assert_internal(mi_segment_is_abandoned(segment));
1271
0
  bool has_page = false;
1272
1273
  // for all slices
1274
0
  const mi_slice_t* end;
1275
0
  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1276
0
  while (slice < end) {
1277
0
    mi_assert_internal(slice->slice_count > 0);
1278
0
    mi_assert_internal(slice->slice_offset == 0);
1279
0
    if (mi_slice_is_used(slice)) { // used page
1280
      // ensure used count is up to date and collect potential concurrent frees
1281
0
      mi_page_t* const page = mi_slice_to_page(slice);
1282
0
      _mi_page_free_collect(page, false);
1283
0
      if (mi_page_all_free(page) && _PyMem_mi_page_is_safe_to_free(page)) {
1284
        // if this page is all free now, free it without adding to any queues (yet)
1285
0
        mi_assert_internal(page->next == NULL && page->prev==NULL);
1286
0
        _mi_stat_decrease(&tld->stats->pages_abandoned, 1);
1287
#ifdef Py_GIL_DISABLED
1288
        page->qsbr_goal = 0;
1289
#endif
1290
0
        segment->abandoned--;
1291
0
        slice = mi_segment_page_clear(page, tld); // re-assign slice due to coalesce!
1292
0
        mi_assert_internal(!mi_slice_is_used(slice));
1293
0
        if (slice->slice_count >= slices_needed) {
1294
0
          has_page = true;
1295
0
        }
1296
0
      }
1297
0
      else {
1298
0
        if (page->xblock_size == block_size && mi_page_has_any_available(page)) {
1299
          // a page has available free blocks of the right size
1300
0
          has_page = true;
1301
0
        }
1302
0
      }
1303
0
    }
1304
0
    else {
1305
      // empty span
1306
0
      if (slice->slice_count >= slices_needed) {
1307
0
        has_page = true;
1308
0
      }
1309
0
    }
1310
0
    slice = slice + slice->slice_count;
1311
0
  }
1312
0
  return has_page;
1313
0
}
1314
1315
0
static mi_heap_t* mi_heap_by_tag(mi_heap_t* heap, uint8_t tag) {
1316
0
  if (heap->tag == tag) {
1317
0
    return heap;
1318
0
  }
1319
0
  for (mi_heap_t *curr = heap->tld->heaps; curr != NULL; curr = curr->next) {
1320
0
    if (curr->tag == tag) {
1321
0
      return curr;
1322
0
    }
1323
0
  }
1324
0
  return NULL;
1325
0
}
1326
1327
// Reclaim an abandoned segment; returns NULL if the segment was freed
1328
// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.
1329
0
static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) {
1330
0
  mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1331
0
  mi_assert_expensive(mi_segment_is_valid(segment, tld));
1332
0
  if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; }
1333
1334
0
  segment->thread_id = _mi_thread_id();
1335
0
  segment->abandoned_visits = 0;
1336
0
  mi_segments_track_size((long)mi_segment_size(segment), tld);
1337
0
  mi_assert_internal(segment->next == NULL);
1338
0
  _mi_stat_decrease(&tld->stats->segments_abandoned, 1);
1339
1340
  // for all slices
1341
0
  const mi_slice_t* end;
1342
0
  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1343
0
  while (slice < end) {
1344
0
    mi_assert_internal(slice->slice_count > 0);
1345
0
    mi_assert_internal(slice->slice_offset == 0);
1346
0
    if (mi_slice_is_used(slice)) {
1347
      // in use: reclaim the page in our heap
1348
0
      mi_page_t* page = mi_slice_to_page(slice);
1349
0
      mi_heap_t* target_heap = mi_heap_by_tag(heap, page->tag);
1350
0
      mi_assert_internal(page->is_committed);
1351
0
      mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
1352
0
      mi_assert_internal(mi_page_heap(page) == NULL);
1353
0
      mi_assert_internal(page->next == NULL && page->prev==NULL);
1354
0
      _mi_stat_decrease(&tld->stats->pages_abandoned, 1);
1355
0
      segment->abandoned--;
1356
      // set the heap again and allow delayed free again
1357
0
      mi_page_set_heap(page, target_heap);
1358
0
      _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set)
1359
0
      _mi_page_free_collect(page, false); // ensure used count is up to date
1360
0
      if (mi_page_all_free(page) && _PyMem_mi_page_is_safe_to_free(page)) {
1361
        // if everything free by now, free the page
1362
#ifdef Py_GIL_DISABLED
1363
        page->qsbr_goal = 0;
1364
#endif
1365
0
        slice = mi_segment_page_clear(page, tld);   // set slice again due to coalesceing
1366
0
      }
1367
0
      else {
1368
        // otherwise reclaim it into the heap
1369
0
        _mi_page_reclaim(target_heap, page);
1370
0
        if (requested_block_size == page->xblock_size && mi_page_has_any_available(page) &&
1371
0
            requested_block_size <= MI_MEDIUM_OBJ_SIZE_MAX && heap == target_heap) {
1372
0
          if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; }
1373
0
        }
1374
0
      }
1375
0
    }
1376
0
    else {
1377
      // the span is free, add it to our page queues
1378
0
      slice = mi_segment_span_free_coalesce(slice, tld); // set slice again due to coalesceing
1379
0
    }
1380
0
    mi_assert_internal(slice->slice_count>0 && slice->slice_offset==0);
1381
0
    slice = slice + slice->slice_count;
1382
0
  }
1383
1384
0
  mi_assert(segment->abandoned == 0);
1385
0
  if (segment->used == 0) {  // due to page_clear
1386
0
    mi_assert_internal(right_page_reclaimed == NULL || !(*right_page_reclaimed));
1387
0
    mi_segment_free(segment, false, tld);
1388
0
    return NULL;
1389
0
  }
1390
0
  else {
1391
0
    return segment;
1392
0
  }
1393
0
}
1394
1395
1396
0
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) {
1397
0
  mi_segment_t* segment;
1398
0
  while ((segment = mi_abandoned_pop(tld->abandoned)) != NULL) {
1399
0
    mi_segment_reclaim(segment, heap, 0, NULL, tld);
1400
0
  }
1401
0
}
1402
1403
static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slices, size_t block_size, bool* reclaimed, mi_segments_tld_t* tld)
1404
0
{
1405
0
  *reclaimed = false;
1406
0
  mi_segment_t* segment;
1407
0
  long max_tries = mi_option_get_clamp(mi_option_max_segment_reclaim, 8, 1024);     // limit the work to bound allocation times
1408
0
  while ((max_tries-- > 0) && ((segment = mi_abandoned_pop(tld->abandoned)) != NULL)) {
1409
0
    segment->abandoned_visits++;
1410
    // todo: an arena exclusive heap will potentially visit many abandoned unsuitable segments
1411
    // and push them into the visited list and use many tries. Perhaps we can skip non-suitable ones in a better way?
1412
0
    bool is_suitable = _mi_heap_memid_is_suitable(heap, segment->memid);
1413
0
    bool has_page = mi_segment_check_free(segment,needed_slices,block_size,tld); // try to free up pages (due to concurrent frees)
1414
0
    if (segment->used == 0) {
1415
      // free the segment (by forced reclaim) to make it available to other threads.
1416
      // note1: we prefer to free a segment as that might lead to reclaiming another
1417
      // segment that is still partially used.
1418
      // note2: we could in principle optimize this by skipping reclaim and directly
1419
      // freeing but that would violate some invariants temporarily)
1420
0
      mi_segment_reclaim(segment, heap, 0, NULL, tld);
1421
0
    }
1422
0
    else if (has_page && is_suitable) {
1423
      // found a large enough free span, or a page of the right block_size with free space
1424
      // we return the result of reclaim (which is usually `segment`) as it might free
1425
      // the segment due to concurrent frees (in which case `NULL` is returned).
1426
0
      return mi_segment_reclaim(segment, heap, block_size, reclaimed, tld);
1427
0
    }
1428
0
    else if (segment->abandoned_visits > 3 && is_suitable) {
1429
      // always reclaim on 3rd visit to limit the abandoned queue length.
1430
0
      mi_segment_reclaim(segment, heap, 0, NULL, tld);
1431
0
    }
1432
0
    else {
1433
      // otherwise, push on the visited list so it gets not looked at too quickly again
1434
0
      mi_segment_try_purge(segment, true /* force? */, tld->stats); // force purge if needed as we may not visit soon again
1435
0
      mi_abandoned_visited_push(tld->abandoned, segment);
1436
0
    }
1437
0
  }
1438
0
  return NULL;
1439
0
}
1440
1441
1442
void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)
1443
0
{
1444
0
  mi_segment_t* segment;
1445
0
  mi_abandoned_pool_t* pool = tld->abandoned;
1446
0
  int max_tries = (force ? 16*1024 : 1024); // limit latency
1447
0
  if (force) {
1448
0
    mi_abandoned_visited_revisit(pool);
1449
0
  }
1450
0
  while ((max_tries-- > 0) && ((segment = mi_abandoned_pop(pool)) != NULL)) {
1451
0
    mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees)
1452
0
    if (segment->used == 0) {
1453
      // free the segment (by forced reclaim) to make it available to other threads.
1454
      // note: we could in principle optimize this by skipping reclaim and directly
1455
      // freeing but that would violate some invariants temporarily)
1456
0
      mi_segment_reclaim(segment, heap, 0, NULL, tld);
1457
0
    }
1458
0
    else {
1459
      // otherwise, purge if needed and push on the visited list
1460
      // note: forced purge can be expensive if many threads are destroyed/created as in mstress.
1461
0
      mi_segment_try_purge(segment, force, tld->stats);
1462
0
      mi_abandoned_visited_push(pool, segment);
1463
0
    }
1464
0
  }
1465
0
}
1466
1467
/* -----------------------------------------------------------
1468
   Reclaim or allocate
1469
----------------------------------------------------------- */
1470
1471
static mi_segment_t* mi_segment_reclaim_or_alloc(mi_heap_t* heap, size_t needed_slices, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld)
1472
0
{
1473
0
  mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE);
1474
0
  mi_assert_internal(block_size <= MI_LARGE_OBJ_SIZE_MAX);
1475
1476
  // 1. try to reclaim an abandoned segment
1477
0
  bool reclaimed;
1478
0
  mi_segment_t* segment = mi_segment_try_reclaim(heap, needed_slices, block_size, &reclaimed, tld);
1479
0
  if (reclaimed) {
1480
    // reclaimed the right page right into the heap
1481
0
    mi_assert_internal(segment != NULL);
1482
0
    return NULL; // pretend out-of-memory as the page will be in the page queue of the heap with available blocks
1483
0
  }
1484
0
  else if (segment != NULL) {
1485
    // reclaimed a segment with a large enough empty span in it
1486
0
    return segment;
1487
0
  }
1488
  // 2. otherwise allocate a fresh segment
1489
0
  return mi_segment_alloc(0, 0, heap->arena_id, tld, os_tld, NULL);
1490
0
}
1491
1492
1493
/* -----------------------------------------------------------
1494
   Page allocation
1495
----------------------------------------------------------- */
1496
1497
static mi_page_t* mi_segments_page_alloc(mi_heap_t* heap, mi_page_kind_t page_kind, size_t required, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld)
1498
0
{
1499
0
  mi_assert_internal(required <= MI_LARGE_OBJ_SIZE_MAX && page_kind <= MI_PAGE_LARGE);
1500
1501
  // find a free page
1502
0
  size_t page_size = _mi_align_up(required, (required > MI_MEDIUM_PAGE_SIZE ? MI_MEDIUM_PAGE_SIZE : MI_SEGMENT_SLICE_SIZE));
1503
0
  size_t slices_needed = page_size / MI_SEGMENT_SLICE_SIZE;
1504
0
  mi_assert_internal(slices_needed * MI_SEGMENT_SLICE_SIZE == page_size);
1505
0
  mi_page_t* page = mi_segments_page_find_and_allocate(slices_needed, heap->arena_id, tld); //(required <= MI_SMALL_SIZE_MAX ? 0 : slices_needed), tld);
1506
0
  if (page==NULL) {
1507
    // no free page, allocate a new segment and try again
1508
0
    if (mi_segment_reclaim_or_alloc(heap, slices_needed, block_size, tld, os_tld) == NULL) {
1509
      // OOM or reclaimed a good page in the heap
1510
0
      return NULL;
1511
0
    }
1512
0
    else {
1513
      // otherwise try again
1514
0
      return mi_segments_page_alloc(heap, page_kind, required, block_size, tld, os_tld);
1515
0
    }
1516
0
  }
1517
0
  mi_assert_internal(page != NULL && page->slice_count*MI_SEGMENT_SLICE_SIZE == page_size);
1518
0
  mi_assert_internal(_mi_ptr_segment(page)->thread_id == _mi_thread_id());
1519
0
  mi_segment_try_purge(_mi_ptr_segment(page), false, tld->stats);
1520
0
  return page;
1521
0
}
1522
1523
1524
1525
/* -----------------------------------------------------------
1526
   Huge page allocation
1527
----------------------------------------------------------- */
1528
1529
static mi_page_t* mi_segment_huge_page_alloc(size_t size, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld, mi_os_tld_t* os_tld)
1530
0
{
1531
0
  mi_page_t* page = NULL;
1532
0
  mi_segment_t* segment = mi_segment_alloc(size,page_alignment,req_arena_id,tld,os_tld,&page);
1533
0
  if (segment == NULL || page==NULL) return NULL;
1534
0
  mi_assert_internal(segment->used==1);
1535
0
  mi_assert_internal(mi_page_block_size(page) >= size);
1536
  #if MI_HUGE_PAGE_ABANDON
1537
  segment->thread_id = 0; // huge segments are immediately abandoned
1538
  #endif
1539
1540
  // for huge pages we initialize the xblock_size as we may
1541
  // overallocate to accommodate large alignments.
1542
0
  size_t psize;
1543
0
  uint8_t* start = _mi_segment_page_start(segment, page, &psize);
1544
0
  page->xblock_size = (psize > MI_HUGE_BLOCK_SIZE ? MI_HUGE_BLOCK_SIZE : (uint32_t)psize);
1545
1546
  // decommit the part of the prefix of a page that will not be used; this can be quite large (close to MI_SEGMENT_SIZE)
1547
0
  if (page_alignment > 0 && segment->allow_decommit) {
1548
0
    uint8_t* aligned_p = (uint8_t*)_mi_align_up((uintptr_t)start, page_alignment);
1549
0
    mi_assert_internal(_mi_is_aligned(aligned_p, page_alignment));
1550
0
    mi_assert_internal(psize - (aligned_p - start) >= size);
1551
0
    uint8_t* decommit_start = start + sizeof(mi_block_t);              // for the free list
1552
0
    ptrdiff_t decommit_size = aligned_p - decommit_start;
1553
0
    _mi_os_reset(decommit_start, decommit_size, &_mi_stats_main);   // note: cannot use segment_decommit on huge segments
1554
0
  }
1555
1556
0
  return page;
1557
0
}
1558
1559
#if MI_HUGE_PAGE_ABANDON
1560
// free huge block from another thread
1561
void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) {
1562
  // huge page segments are always abandoned and can be freed immediately by any thread
1563
  mi_assert_internal(segment->kind==MI_SEGMENT_HUGE);
1564
  mi_assert_internal(segment == _mi_page_segment(page));
1565
  mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id)==0);
1566
1567
  // claim it and free
1568
  mi_heap_t* heap = mi_heap_get_default(); // issue #221; don't use the internal get_default_heap as we need to ensure the thread is initialized.
1569
  // paranoia: if this it the last reference, the cas should always succeed
1570
  size_t expected_tid = 0;
1571
  if (mi_atomic_cas_strong_acq_rel(&segment->thread_id, &expected_tid, heap->thread_id)) {
1572
    mi_block_set_next(page, block, page->free);
1573
    page->free = block;
1574
    page->used--;
1575
    page->is_zero = false;
1576
    mi_assert(page->used == 0);
1577
    mi_tld_t* tld = heap->tld;
1578
    _mi_segment_page_free(page, true, &tld->segments);
1579
  }
1580
#if (MI_DEBUG!=0)
1581
  else {
1582
    mi_assert_internal(false);
1583
  }
1584
#endif
1585
}
1586
1587
#else
1588
// reset memory of a huge block from another thread
1589
0
void _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) {
1590
0
  MI_UNUSED(page);
1591
0
  mi_assert_internal(segment->kind == MI_SEGMENT_HUGE);
1592
0
  mi_assert_internal(segment == _mi_page_segment(page));
1593
0
  mi_assert_internal(page->used == 1); // this is called just before the free
1594
0
  mi_assert_internal(page->free == NULL);
1595
0
  if (segment->allow_decommit) {
1596
0
    size_t csize = mi_usable_size(block);
1597
0
    if (csize > sizeof(mi_block_t)) {
1598
0
      csize = csize - sizeof(mi_block_t);
1599
0
      uint8_t* p = (uint8_t*)block + sizeof(mi_block_t);
1600
0
      _mi_os_reset(p, csize, &_mi_stats_main);  // note: cannot use segment_decommit on huge segments
1601
0
    }
1602
0
  }
1603
0
}
1604
#endif
1605
1606
/* -----------------------------------------------------------
1607
   Page allocation and free
1608
----------------------------------------------------------- */
1609
0
mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) {
1610
0
  mi_page_t* page;
1611
0
  if mi_unlikely(page_alignment > MI_ALIGNMENT_MAX) {
1612
0
    mi_assert_internal(_mi_is_power_of_two(page_alignment));
1613
0
    mi_assert_internal(page_alignment >= MI_SEGMENT_SIZE);
1614
0
    if (page_alignment < MI_SEGMENT_SIZE) { page_alignment = MI_SEGMENT_SIZE; }
1615
0
    page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld);
1616
0
  }
1617
0
  else if (block_size <= MI_SMALL_OBJ_SIZE_MAX) {
1618
0
    page = mi_segments_page_alloc(heap,MI_PAGE_SMALL,block_size,block_size,tld,os_tld);
1619
0
  }
1620
0
  else if (block_size <= MI_MEDIUM_OBJ_SIZE_MAX) {
1621
0
    page = mi_segments_page_alloc(heap,MI_PAGE_MEDIUM,MI_MEDIUM_PAGE_SIZE,block_size,tld, os_tld);
1622
0
  }
1623
0
  else if (block_size <= MI_LARGE_OBJ_SIZE_MAX) {
1624
0
    page = mi_segments_page_alloc(heap,MI_PAGE_LARGE,block_size,block_size,tld, os_tld);
1625
0
  }
1626
0
  else {
1627
0
    page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld);
1628
0
  }
1629
0
  mi_assert_internal(page == NULL || _mi_heap_memid_is_suitable(heap, _mi_page_segment(page)->memid));
1630
0
  mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld));
1631
0
  return page;
1632
0
}
1633
1634
/* -----------------------------------------------------------
1635
   Visit blocks in abandoned segments
1636
----------------------------------------------------------- */
1637
1638
static bool mi_segment_visit_page(mi_segment_t* segment, mi_page_t* page, bool visit_blocks, mi_block_visit_fun* visitor, void* arg)
1639
0
{
1640
0
  mi_heap_area_t area;
1641
0
  _mi_heap_area_init(&area, page);
1642
0
  if (!visitor(NULL, &area, NULL, area.block_size, arg)) return false;
1643
0
  if (visit_blocks) {
1644
0
    return _mi_heap_area_visit_blocks(&area, page, visitor, arg);
1645
0
  }
1646
0
  else {
1647
0
    return true;
1648
0
  }
1649
0
}
1650
1651
0
static bool mi_segment_visit_pages(mi_segment_t* segment, uint8_t page_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {
1652
0
  const mi_slice_t* end;
1653
0
  mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1654
0
  while (slice < end) {
1655
0
    if (mi_slice_is_used(slice)) {
1656
0
      mi_page_t* const page = mi_slice_to_page(slice);
1657
0
      if (page->tag == page_tag) {
1658
0
        if (!mi_segment_visit_page(segment, page, visit_blocks, visitor, arg)) return false;
1659
0
      }
1660
0
    }
1661
0
    slice = slice + slice->slice_count;
1662
0
  }
1663
0
  return true;
1664
0
}
1665
1666
// Visit all blocks in a abandoned segments
1667
0
bool _mi_abandoned_pool_visit_blocks(mi_abandoned_pool_t* pool, uint8_t page_tag, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) {
1668
  // Note: this is not safe in any other thread is abandoning or claiming segments from the pool
1669
0
  mi_segment_t* segment = mi_tagged_segment_ptr(pool->abandoned);
1670
0
  while (segment != NULL) {
1671
0
    if (!mi_segment_visit_pages(segment, page_tag, visit_blocks, visitor, arg)) return false;
1672
0
    segment = segment->abandoned_next;
1673
0
  }
1674
1675
0
  segment = pool->abandoned_visited;
1676
0
  while (segment != NULL) {
1677
0
    if (!mi_segment_visit_pages(segment, page_tag, visit_blocks, visitor, arg)) return false;
1678
0
    segment = segment->abandoned_next;
1679
0
  }
1680
1681
0
  return true;
1682
0
}