Coverage Report

Created: 2026-10-31 06:21

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