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
mi_commit_mask_all_set(const mi_commit_mask_t * commit,const mi_commit_mask_t * cm)23 static bool mi_commit_mask_all_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {
24 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
25 if ((commit->mask[i] & cm->mask[i]) != cm->mask[i]) return false;
26 }
27 return true;
28 }
29
mi_commit_mask_any_set(const mi_commit_mask_t * commit,const mi_commit_mask_t * cm)30 static bool mi_commit_mask_any_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) {
31 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
32 if ((commit->mask[i] & cm->mask[i]) != 0) return true;
33 }
34 return false;
35 }
36
mi_commit_mask_create_intersect(const mi_commit_mask_t * commit,const mi_commit_mask_t * cm,mi_commit_mask_t * res)37 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 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
39 res->mask[i] = (commit->mask[i] & cm->mask[i]);
40 }
41 }
42
mi_commit_mask_clear(mi_commit_mask_t * res,const mi_commit_mask_t * cm)43 static void mi_commit_mask_clear(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {
44 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
45 res->mask[i] &= ~(cm->mask[i]);
46 }
47 }
48
mi_commit_mask_set(mi_commit_mask_t * res,const mi_commit_mask_t * cm)49 static void mi_commit_mask_set(mi_commit_mask_t* res, const mi_commit_mask_t* cm) {
50 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
51 res->mask[i] |= cm->mask[i];
52 }
53 }
54
mi_commit_mask_create(size_t bitidx,size_t bitcount,mi_commit_mask_t * cm)55 static void mi_commit_mask_create(size_t bitidx, size_t bitcount, mi_commit_mask_t* cm) {
56 mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);
57 mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);
58 if (bitcount == MI_COMMIT_MASK_BITS) {
59 mi_assert_internal(bitidx==0);
60 mi_commit_mask_create_full(cm);
61 }
62 else if (bitcount == 0) {
63 mi_commit_mask_create_empty(cm);
64 }
65 else {
66 mi_commit_mask_create_empty(cm);
67 size_t i = bitidx / MI_COMMIT_MASK_FIELD_BITS;
68 size_t ofs = bitidx % MI_COMMIT_MASK_FIELD_BITS;
69 while (bitcount > 0) {
70 mi_assert_internal(i < MI_COMMIT_MASK_FIELD_COUNT);
71 size_t avail = MI_COMMIT_MASK_FIELD_BITS - ofs;
72 size_t count = (bitcount > avail ? avail : bitcount);
73 size_t mask = (count >= MI_COMMIT_MASK_FIELD_BITS ? ~((size_t)0) : (((size_t)1 << count) - 1) << ofs);
74 cm->mask[i] = mask;
75 bitcount -= count;
76 ofs = 0;
77 i++;
78 }
79 }
80 }
81
_mi_commit_mask_committed_size(const mi_commit_mask_t * cm,size_t total)82 size_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total) {
83 mi_assert_internal((total%MI_COMMIT_MASK_BITS)==0);
84 size_t count = 0;
85 for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) {
86 size_t mask = cm->mask[i];
87 if (~mask == 0) {
88 count += MI_COMMIT_MASK_FIELD_BITS;
89 }
90 else {
91 for (; mask != 0; mask >>= 1) { // todo: use popcount
92 if ((mask&1)!=0) count++;
93 }
94 }
95 }
96 // we use total since for huge segments each commit bit may represent a larger size
97 return ((total / MI_COMMIT_MASK_BITS) * count);
98 }
99
100
_mi_commit_mask_next_run(const mi_commit_mask_t * cm,size_t * idx)101 size_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx) {
102 size_t i = (*idx) / MI_COMMIT_MASK_FIELD_BITS;
103 size_t ofs = (*idx) % MI_COMMIT_MASK_FIELD_BITS;
104 size_t mask = 0;
105 // find first ones
106 while (i < MI_COMMIT_MASK_FIELD_COUNT) {
107 mask = cm->mask[i];
108 mask >>= ofs;
109 if (mask != 0) {
110 while ((mask&1) == 0) {
111 mask >>= 1;
112 ofs++;
113 }
114 break;
115 }
116 i++;
117 ofs = 0;
118 }
119 if (i >= MI_COMMIT_MASK_FIELD_COUNT) {
120 // not found
121 *idx = MI_COMMIT_MASK_BITS;
122 return 0;
123 }
124 else {
125 // found, count ones
126 size_t count = 0;
127 *idx = (i*MI_COMMIT_MASK_FIELD_BITS) + ofs;
128 do {
129 mi_assert_internal(ofs < MI_COMMIT_MASK_FIELD_BITS && (mask&1) == 1);
130 do {
131 count++;
132 mask >>= 1;
133 } while ((mask&1) == 1);
134 if ((((*idx + count) % MI_COMMIT_MASK_FIELD_BITS) == 0)) {
135 i++;
136 if (i >= MI_COMMIT_MASK_FIELD_COUNT) break;
137 mask = cm->mask[i];
138 ofs = 0;
139 }
140 } while ((mask&1) == 1);
141 mi_assert_internal(count > 0);
142 return count;
143 }
144 }
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
mi_segment_slices_end(const mi_segment_t * segment)161 static const mi_slice_t* mi_segment_slices_end(const mi_segment_t* segment) {
162 return &segment->slices[segment->slice_entries];
163 }
164
mi_slice_start(const mi_slice_t * slice)165 static uint8_t* mi_slice_start(const mi_slice_t* slice) {
166 mi_segment_t* segment = _mi_ptr_segment(slice);
167 mi_assert_internal(slice >= segment->slices && slice < mi_segment_slices_end(segment));
168 return ((uint8_t*)segment + ((slice - segment->slices)*MI_SEGMENT_SLICE_SIZE));
169 }
170
171
172 /* -----------------------------------------------------------
173 Bins
174 ----------------------------------------------------------- */
175 // Use bit scan forward to quickly find the first zero bit if it is available
176
mi_slice_bin8(size_t slice_count)177 static inline size_t mi_slice_bin8(size_t slice_count) {
178 if (slice_count<=1) return slice_count;
179 mi_assert_internal(slice_count <= MI_SLICES_PER_SEGMENT);
180 slice_count--;
181 size_t s = mi_bsr(slice_count); // slice_count > 1
182 if (s <= 2) return slice_count + 1;
183 size_t bin = ((s << 2) | ((slice_count >> (s - 2))&0x03)) - 4;
184 return bin;
185 }
186
mi_slice_bin(size_t slice_count)187 static inline size_t mi_slice_bin(size_t slice_count) {
188 mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_SEGMENT_SIZE);
189 mi_assert_internal(mi_slice_bin8(MI_SLICES_PER_SEGMENT) <= MI_SEGMENT_BIN_MAX);
190 size_t bin = mi_slice_bin8(slice_count);
191 mi_assert_internal(bin <= MI_SEGMENT_BIN_MAX);
192 return bin;
193 }
194
mi_slice_index(const mi_slice_t * slice)195 static inline size_t mi_slice_index(const mi_slice_t* slice) {
196 mi_segment_t* segment = _mi_ptr_segment(slice);
197 ptrdiff_t index = slice - segment->slices;
198 mi_assert_internal(index >= 0 && index < (ptrdiff_t)segment->slice_entries);
199 return index;
200 }
201
202
203 /* -----------------------------------------------------------
204 Slice span queues
205 ----------------------------------------------------------- */
206
mi_span_queue_push(mi_span_queue_t * sq,mi_slice_t * slice)207 static void mi_span_queue_push(mi_span_queue_t* sq, mi_slice_t* slice) {
208 // todo: or push to the end?
209 mi_assert_internal(slice->prev == NULL && slice->next==NULL);
210 slice->prev = NULL; // paranoia
211 slice->next = sq->first;
212 sq->first = slice;
213 if (slice->next != NULL) slice->next->prev = slice;
214 else sq->last = slice;
215 slice->xblock_size = 0; // free
216 }
217
mi_span_queue_for(size_t slice_count,mi_segments_tld_t * tld)218 static mi_span_queue_t* mi_span_queue_for(size_t slice_count, mi_segments_tld_t* tld) {
219 size_t bin = mi_slice_bin(slice_count);
220 mi_span_queue_t* sq = &tld->spans[bin];
221 mi_assert_internal(sq->slice_count >= slice_count);
222 return sq;
223 }
224
mi_span_queue_delete(mi_span_queue_t * sq,mi_slice_t * slice)225 static void mi_span_queue_delete(mi_span_queue_t* sq, mi_slice_t* slice) {
226 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 if (slice->prev != NULL) slice->prev->next = slice->next;
229 if (slice == sq->first) sq->first = slice->next;
230 if (slice->next != NULL) slice->next->prev = slice->prev;
231 if (slice == sq->last) sq->last = slice->prev;
232 slice->prev = NULL;
233 slice->next = NULL;
234 slice->xblock_size = 1; // no more free
235 }
236
237
238 /* -----------------------------------------------------------
239 Invariant checking
240 ----------------------------------------------------------- */
241
mi_slice_is_used(const mi_slice_t * slice)242 static bool mi_slice_is_used(const mi_slice_t* slice) {
243 return (slice->xblock_size > 0);
244 }
245
246
247 #if (MI_DEBUG>=3)
mi_span_queue_contains(mi_span_queue_t * sq,mi_slice_t * slice)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
mi_segment_is_valid(mi_segment_t * segment,mi_segments_tld_t * tld)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
mi_segment_info_size(mi_segment_t * segment)310 static size_t mi_segment_info_size(mi_segment_t* segment) {
311 return segment->segment_info_slices * MI_SEGMENT_SLICE_SIZE;
312 }
313
_mi_segment_page_start_from_slice(const mi_segment_t * segment,const mi_slice_t * slice,size_t xblock_size,size_t * page_size)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 {
316 ptrdiff_t idx = slice - segment->slices;
317 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 size_t start_offset = 0;
322 if (xblock_size >= MI_INTPTR_SIZE) {
323 if (xblock_size <= 64) { start_offset = 3*xblock_size; }
324 else if (xblock_size <= 512) { start_offset = xblock_size; }
325 }
326 if (page_size != NULL) { *page_size = psize - start_offset; }
327 return (uint8_t*)segment + ((idx*MI_SEGMENT_SLICE_SIZE) + start_offset);
328 }
329
330 // Start of the page available memory; can be used on uninitialized pages
_mi_segment_page_start(const mi_segment_t * segment,const mi_page_t * page,size_t * page_size)331 uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size)
332 {
333 const mi_slice_t* slice = mi_page_to_slice((mi_page_t*)page);
334 uint8_t* p = _mi_segment_page_start_from_slice(segment, slice, page->xblock_size, page_size);
335 mi_assert_internal(page->xblock_size > 0 || _mi_ptr_page(p) == page);
336 mi_assert_internal(_mi_ptr_segment(p) == segment);
337 return p;
338 }
339
340
mi_segment_calculate_slices(size_t required,size_t * pre_size,size_t * info_slices)341 static size_t mi_segment_calculate_slices(size_t required, size_t* pre_size, size_t* info_slices) {
342 size_t page_size = _mi_os_page_size();
343 size_t isize = _mi_align_up(sizeof(mi_segment_t), page_size);
344 size_t guardsize = 0;
345
346 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 guardsize = page_size;
350 if (required > 0) {
351 required = _mi_align_up(required, MI_SEGMENT_SLICE_SIZE) + page_size;
352 }
353 }
354
355 if (pre_size != NULL) *pre_size = isize;
356 isize = _mi_align_up(isize + guardsize, MI_SEGMENT_SLICE_SIZE);
357 if (info_slices != NULL) *info_slices = isize / MI_SEGMENT_SLICE_SIZE;
358 size_t segment_size = (required==0 ? MI_SEGMENT_SIZE : _mi_align_up( required + isize + guardsize, MI_SEGMENT_SLICE_SIZE) );
359 mi_assert_internal(segment_size % MI_SEGMENT_SLICE_SIZE == 0);
360 return (segment_size / MI_SEGMENT_SLICE_SIZE);
361 }
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
mi_segments_track_size(long segment_size,mi_segments_tld_t * tld)370 static void mi_segments_track_size(long segment_size, mi_segments_tld_t* tld) {
371 if (segment_size>=0) _mi_stat_increase(&tld->stats->segments,1);
372 else _mi_stat_decrease(&tld->stats->segments,1);
373 tld->count += (segment_size >= 0 ? 1 : -1);
374 if (tld->count > tld->peak_count) tld->peak_count = tld->count;
375 tld->current_size += segment_size;
376 if (tld->current_size > tld->peak_size) tld->peak_size = tld->current_size;
377 }
378
mi_segment_os_free(mi_segment_t * segment,mi_segments_tld_t * tld)379 static void mi_segment_os_free(mi_segment_t* segment, mi_segments_tld_t* tld) {
380 segment->thread_id = 0;
381 _mi_segment_map_freed_at(segment);
382 mi_segments_track_size(-((long)mi_segment_size(segment)),tld);
383 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 size_t os_pagesize = _mi_os_page_size();
387 _mi_os_unprotect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);
388 uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;
389 _mi_os_unprotect(end, os_pagesize);
390 }
391
392 // purge delayed decommits now? (no, leave it to the arena)
393 // mi_segment_try_purge(segment,true,tld->stats);
394
395 const size_t size = mi_segment_size(segment);
396 const size_t csize = _mi_commit_mask_committed_size(&segment->commit_mask, size);
397
398 _mi_abandoned_await_readers(tld->abandoned); // wait until safe to free
399 _mi_arena_free(segment, mi_segment_size(segment), csize, segment->memid, tld->stats);
400 }
401
402 // called by threads that are terminating
_mi_segment_thread_collect(mi_segments_tld_t * tld)403 void _mi_segment_thread_collect(mi_segments_tld_t* tld) {
404 MI_UNUSED(tld);
405 // nothing to do
406 }
407
408
409 /* -----------------------------------------------------------
410 Commit/Decommit ranges
411 ----------------------------------------------------------- */
412
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)413 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 mi_assert_internal(_mi_ptr_segment(p + 1) == segment);
415 mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
416 mi_commit_mask_create_empty(cm);
417 if (size == 0 || size > MI_SEGMENT_SIZE || segment->kind == MI_SEGMENT_HUGE) return;
418 const size_t segstart = mi_segment_info_size(segment);
419 const size_t segsize = mi_segment_size(segment);
420 if (p >= (uint8_t*)segment + segsize) return;
421
422 size_t pstart = (p - (uint8_t*)segment);
423 mi_assert_internal(pstart + size <= segsize);
424
425 size_t start;
426 size_t end;
427 if (conservative) {
428 // decommit conservative
429 start = _mi_align_up(pstart, MI_COMMIT_SIZE);
430 end = _mi_align_down(pstart + size, MI_COMMIT_SIZE);
431 mi_assert_internal(start >= segstart);
432 mi_assert_internal(end <= segsize);
433 }
434 else {
435 // commit liberal
436 start = _mi_align_down(pstart, MI_MINIMAL_COMMIT_SIZE);
437 end = _mi_align_up(pstart + size, MI_MINIMAL_COMMIT_SIZE);
438 }
439 if (pstart >= segstart && start < segstart) { // note: the mask is also calculated for an initial commit of the info area
440 start = segstart;
441 }
442 if (end > segsize) {
443 end = segsize;
444 }
445
446 mi_assert_internal(start <= pstart && (pstart + size) <= end);
447 mi_assert_internal(start % MI_COMMIT_SIZE==0 && end % MI_COMMIT_SIZE == 0);
448 *start_p = (uint8_t*)segment + start;
449 *full_size = (end > start ? end - start : 0);
450 if (*full_size == 0) return;
451
452 size_t bitidx = start / MI_COMMIT_SIZE;
453 mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS);
454
455 size_t bitcount = *full_size / MI_COMMIT_SIZE; // can be 0
456 if (bitidx + bitcount > MI_COMMIT_MASK_BITS) {
457 _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 }
459 mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS);
460 mi_commit_mask_create(bitidx, bitcount, cm);
461 }
462
mi_segment_commit(mi_segment_t * segment,uint8_t * p,size_t size,mi_stats_t * stats)463 static bool mi_segment_commit(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
464 mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));
465
466 // commit liberal
467 uint8_t* start = NULL;
468 size_t full_size = 0;
469 mi_commit_mask_t mask;
470 mi_segment_commit_mask(segment, false /* conservative? */, p, size, &start, &full_size, &mask);
471 if (mi_commit_mask_is_empty(&mask) || full_size == 0) return true;
472
473 if (!mi_commit_mask_all_set(&segment->commit_mask, &mask)) {
474 // committing
475 bool is_zero = false;
476 mi_commit_mask_t cmask;
477 mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);
478 _mi_stat_decrease(&_mi_stats_main.committed, _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for overlap
479 if (!_mi_os_commit(start, full_size, &is_zero, stats)) return false;
480 mi_commit_mask_set(&segment->commit_mask, &mask);
481 }
482
483 // increase purge expiration when using part of delayed purges -- we assume more allocations are coming soon.
484 if (mi_commit_mask_any_set(&segment->purge_mask, &mask)) {
485 segment->purge_expire = _mi_clock_now() + mi_option_get(mi_option_purge_delay);
486 }
487
488 // always clear any delayed purges in our range (as they are either committed now)
489 mi_commit_mask_clear(&segment->purge_mask, &mask);
490 return true;
491 }
492
mi_segment_ensure_committed(mi_segment_t * segment,uint8_t * p,size_t size,mi_stats_t * stats)493 static bool mi_segment_ensure_committed(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
494 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 if (mi_commit_mask_is_full(&segment->commit_mask) && mi_commit_mask_is_empty(&segment->purge_mask)) return true; // fully committed
497 mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
498 return mi_segment_commit(segment, p, size, stats);
499 }
500
mi_segment_purge(mi_segment_t * segment,uint8_t * p,size_t size,mi_stats_t * stats)501 static bool mi_segment_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
502 mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask));
503 if (!segment->allow_purge) return true;
504
505 // purge conservative
506 uint8_t* start = NULL;
507 size_t full_size = 0;
508 mi_commit_mask_t mask;
509 mi_segment_commit_mask(segment, true /* conservative? */, p, size, &start, &full_size, &mask);
510 if (mi_commit_mask_is_empty(&mask) || full_size==0) return true;
511
512 if (mi_commit_mask_any_set(&segment->commit_mask, &mask)) {
513 // purging
514 mi_assert_internal((void*)start != (void*)segment);
515 mi_assert_internal(segment->allow_decommit);
516 const bool decommitted = _mi_os_purge(start, full_size, stats); // reset or decommit
517 if (decommitted) {
518 mi_commit_mask_t cmask;
519 mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask);
520 _mi_stat_increase(&_mi_stats_main.committed, full_size - _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for double counting
521 mi_commit_mask_clear(&segment->commit_mask, &mask);
522 }
523 }
524
525 // always clear any scheduled purges in our range
526 mi_commit_mask_clear(&segment->purge_mask, &mask);
527 return true;
528 }
529
mi_segment_schedule_purge(mi_segment_t * segment,uint8_t * p,size_t size,mi_stats_t * stats)530 static void mi_segment_schedule_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) {
531 if (!segment->allow_purge) return;
532
533 if (mi_option_get(mi_option_purge_delay) == 0) {
534 mi_segment_purge(segment, p, size, stats);
535 }
536 else {
537 // register for future purge in the purge mask
538 uint8_t* start = NULL;
539 size_t full_size = 0;
540 mi_commit_mask_t mask;
541 mi_segment_commit_mask(segment, true /*conservative*/, p, size, &start, &full_size, &mask);
542 if (mi_commit_mask_is_empty(&mask) || full_size==0) return;
543
544 // update delayed commit
545 mi_assert_internal(segment->purge_expire > 0 || mi_commit_mask_is_empty(&segment->purge_mask));
546 mi_commit_mask_t cmask;
547 mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask); // only purge what is committed; span_free may try to decommit more
548 mi_commit_mask_set(&segment->purge_mask, &cmask);
549 mi_msecs_t now = _mi_clock_now();
550 if (segment->purge_expire == 0) {
551 // no previous purgess, initialize now
552 segment->purge_expire = now + mi_option_get(mi_option_purge_delay);
553 }
554 else if (segment->purge_expire <= now) {
555 // previous purge mask already expired
556 if (segment->purge_expire + mi_option_get(mi_option_purge_extend_delay) <= now) {
557 mi_segment_try_purge(segment, true, stats);
558 }
559 else {
560 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 }
562 }
563 else {
564 // previous purge mask is not yet expired, increase the expiration by a bit.
565 segment->purge_expire += mi_option_get(mi_option_purge_extend_delay);
566 }
567 }
568 }
569
mi_segment_try_purge(mi_segment_t * segment,bool force,mi_stats_t * stats)570 static void mi_segment_try_purge(mi_segment_t* segment, bool force, mi_stats_t* stats) {
571 if (!segment->allow_purge || mi_commit_mask_is_empty(&segment->purge_mask)) return;
572 mi_msecs_t now = _mi_clock_now();
573 if (!force && now < segment->purge_expire) return;
574
575 mi_commit_mask_t mask = segment->purge_mask;
576 segment->purge_expire = 0;
577 mi_commit_mask_create_empty(&segment->purge_mask);
578
579 size_t idx;
580 size_t count;
581 mi_commit_mask_foreach(&mask, idx, count) {
582 // if found, decommit that sequence
583 if (count > 0) {
584 uint8_t* p = (uint8_t*)segment + (idx*MI_COMMIT_SIZE);
585 size_t size = count * MI_COMMIT_SIZE;
586 mi_segment_purge(segment, p, size, stats);
587 }
588 }
589 mi_commit_mask_foreach_end()
590 mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));
591 }
592
593
594 /* -----------------------------------------------------------
595 Span free
596 ----------------------------------------------------------- */
597
mi_segment_is_abandoned(mi_segment_t * segment)598 static bool mi_segment_is_abandoned(mi_segment_t* segment) {
599 return (segment->thread_id == 0);
600 }
601
602 // note: can be called on abandoned segments
mi_segment_span_free(mi_segment_t * segment,size_t slice_index,size_t slice_count,bool allow_purge,mi_segments_tld_t * tld)603 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 mi_assert_internal(slice_index < segment->slice_entries);
605 mi_span_queue_t* sq = (segment->kind == MI_SEGMENT_HUGE || mi_segment_is_abandoned(segment)
606 ? NULL : mi_span_queue_for(slice_count,tld));
607 if (slice_count==0) slice_count = 1;
608 mi_assert_internal(slice_index + slice_count - 1 < segment->slice_entries);
609
610 // set first and last slice (the intermediates can be undetermined)
611 mi_slice_t* slice = &segment->slices[slice_index];
612 slice->slice_count = (uint32_t)slice_count;
613 mi_assert_internal(slice->slice_count == slice_count); // no overflow?
614 slice->slice_offset = 0;
615 if (slice_count > 1) {
616 mi_slice_t* last = &segment->slices[slice_index + slice_count - 1];
617 last->slice_count = 0;
618 last->slice_offset = (uint32_t)(sizeof(mi_page_t)*(slice_count - 1));
619 last->xblock_size = 0;
620 }
621
622 // perhaps decommit
623 if (allow_purge) {
624 mi_segment_schedule_purge(segment, mi_slice_start(slice), slice_count * MI_SEGMENT_SLICE_SIZE, tld->stats);
625 }
626
627 // and push it on the free page queue (if it was not a huge page)
628 if (sq != NULL) mi_span_queue_push( sq, slice );
629 else slice->xblock_size = 0; // mark huge page as free anyways
630 }
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
mi_segment_span_remove_from_queue(mi_slice_t * slice,mi_segments_tld_t * tld)642 static void mi_segment_span_remove_from_queue(mi_slice_t* slice, mi_segments_tld_t* tld) {
643 mi_assert_internal(slice->slice_count > 0 && slice->slice_offset==0 && slice->xblock_size==0);
644 mi_assert_internal(_mi_ptr_segment(slice)->kind != MI_SEGMENT_HUGE);
645 mi_span_queue_t* sq = mi_span_queue_for(slice->slice_count, tld);
646 mi_span_queue_delete(sq, slice);
647 }
648
649 // note: can be called on abandoned segments
mi_segment_span_free_coalesce(mi_slice_t * slice,mi_segments_tld_t * tld)650 static mi_slice_t* mi_segment_span_free_coalesce(mi_slice_t* slice, mi_segments_tld_t* tld) {
651 mi_assert_internal(slice != NULL && slice->slice_count > 0 && slice->slice_offset == 0);
652 mi_segment_t* segment = _mi_ptr_segment(slice);
653 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 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 mi_assert_internal((segment->used==0 && slice->xblock_size==0) || segment->used == 1); // decreased right after this call in `mi_segment_page_clear`
659 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 return slice;
663 }
664
665 // otherwise coalesce the span and add to the free span queues
666 size_t slice_count = slice->slice_count;
667 mi_slice_t* next = slice + slice->slice_count;
668 mi_assert_internal(next <= mi_segment_slices_end(segment));
669 if (next < mi_segment_slices_end(segment) && next->xblock_size==0) {
670 // free next block -- remove it from free and merge
671 mi_assert_internal(next->slice_count > 0 && next->slice_offset==0);
672 slice_count += next->slice_count; // extend
673 if (!is_abandoned) { mi_segment_span_remove_from_queue(next, tld); }
674 }
675 if (slice > segment->slices) {
676 mi_slice_t* prev = mi_slice_first(slice - 1);
677 mi_assert_internal(prev >= segment->slices);
678 if (prev->xblock_size==0) {
679 // free previous slice -- remove it from free and merge
680 mi_assert_internal(prev->slice_count > 0 && prev->slice_offset==0);
681 slice_count += prev->slice_count;
682 if (!is_abandoned) { mi_segment_span_remove_from_queue(prev, tld); }
683 slice = prev;
684 }
685 }
686
687 // and add the new free page
688 mi_segment_span_free(segment, mi_slice_index(slice), slice_count, true, tld);
689 return slice;
690 }
691
692
693
694 /* -----------------------------------------------------------
695 Page allocation
696 ----------------------------------------------------------- */
697
698 // Note: may still return NULL if committing the memory failed
mi_segment_span_allocate(mi_segment_t * segment,size_t slice_index,size_t slice_count,mi_segments_tld_t * tld)699 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 mi_assert_internal(slice_index < segment->slice_entries);
701 mi_slice_t* const slice = &segment->slices[slice_index];
702 mi_assert_internal(slice->xblock_size==0 || slice->xblock_size==1);
703
704 // commit before changing the slice data
705 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 return NULL; // commit failed!
707 }
708
709 // convert the slices to a page
710 slice->slice_offset = 0;
711 slice->slice_count = (uint32_t)slice_count;
712 mi_assert_internal(slice->slice_count == slice_count);
713 const size_t bsize = slice_count * MI_SEGMENT_SLICE_SIZE;
714 slice->xblock_size = (uint32_t)(bsize >= MI_HUGE_BLOCK_SIZE ? MI_HUGE_BLOCK_SIZE : bsize);
715 mi_page_t* page = mi_slice_to_page(slice);
716 mi_assert_internal(mi_page_block_size(page) == bsize);
717
718 // set slice back pointers for the first MI_MAX_SLICE_OFFSET entries
719 size_t extra = slice_count-1;
720 if (extra > MI_MAX_SLICE_OFFSET) extra = MI_MAX_SLICE_OFFSET;
721 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
722
723 mi_slice_t* slice_next = slice + 1;
724 for (size_t i = 1; i <= extra; i++, slice_next++) {
725 slice_next->slice_offset = (uint32_t)(sizeof(mi_slice_t)*i);
726 slice_next->slice_count = 0;
727 slice_next->xblock_size = 1;
728 }
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 mi_slice_t* last = slice + slice_count - 1;
733 mi_slice_t* end = (mi_slice_t*)mi_segment_slices_end(segment);
734 if (last > end) last = end;
735 if (last > slice) {
736 last->slice_offset = (uint32_t)(sizeof(mi_slice_t) * (last - slice));
737 last->slice_count = 0;
738 last->xblock_size = 1;
739 }
740
741 // and initialize the page
742 page->is_committed = true;
743 segment->used++;
744 return page;
745 }
746
mi_segment_slice_split(mi_segment_t * segment,mi_slice_t * slice,size_t slice_count,mi_segments_tld_t * tld)747 static void mi_segment_slice_split(mi_segment_t* segment, mi_slice_t* slice, size_t slice_count, mi_segments_tld_t* tld) {
748 mi_assert_internal(_mi_ptr_segment(slice) == segment);
749 mi_assert_internal(slice->slice_count >= slice_count);
750 mi_assert_internal(slice->xblock_size > 0); // no more in free queue
751 if (slice->slice_count <= slice_count) return;
752 mi_assert_internal(segment->kind != MI_SEGMENT_HUGE);
753 size_t next_index = mi_slice_index(slice) + slice_count;
754 size_t next_count = slice->slice_count - slice_count;
755 mi_segment_span_free(segment, next_index, next_count, false /* don't purge left-over part */, tld);
756 slice->slice_count = (uint32_t)slice_count;
757 }
758
mi_segments_page_find_and_allocate(size_t slice_count,mi_arena_id_t req_arena_id,mi_segments_tld_t * tld)759 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 mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_LARGE_OBJ_SIZE_MAX);
761 // search from best fit up
762 mi_span_queue_t* sq = mi_span_queue_for(slice_count, tld);
763 if (slice_count == 0) slice_count = 1;
764 while (sq <= &tld->spans[MI_SEGMENT_BIN_MAX]) {
765 for (mi_slice_t* slice = sq->first; slice != NULL; slice = slice->next) {
766 if (slice->slice_count >= slice_count) {
767 // found one
768 mi_segment_t* segment = _mi_ptr_segment(slice);
769 if (_mi_arena_memid_is_suitable(segment->memid, req_arena_id)) {
770 // found a suitable page span
771 mi_span_queue_delete(sq, slice);
772
773 if (slice->slice_count > slice_count) {
774 mi_segment_slice_split(segment, slice, slice_count, tld);
775 }
776 mi_assert_internal(slice != NULL && slice->slice_count == slice_count && slice->xblock_size > 0);
777 mi_page_t* page = mi_segment_span_allocate(segment, mi_slice_index(slice), slice->slice_count, tld);
778 if (page == NULL) {
779 // commit failed; return NULL but first restore the slice
780 mi_segment_span_free_coalesce(slice, tld);
781 return NULL;
782 }
783 return page;
784 }
785 }
786 }
787 sq++;
788 }
789 // could not find a page..
790 return NULL;
791 }
792
793
794 /* -----------------------------------------------------------
795 Segment allocation
796 ----------------------------------------------------------- */
797
mi_segment_os_alloc(size_t required,size_t page_alignment,bool eager_delayed,mi_arena_id_t req_arena_id,size_t * psegment_slices,size_t * ppre_size,size_t * pinfo_slices,bool commit,mi_segments_tld_t * tld,mi_os_tld_t * os_tld)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 {
803 mi_memid_t memid;
804 bool allow_large = (!eager_delayed && (MI_SECURE == 0)); // only allow large OS pages once we are no longer lazy
805 size_t align_offset = 0;
806 size_t alignment = MI_SEGMENT_ALIGN;
807
808 if (page_alignment > 0) {
809 // mi_assert_internal(huge_page != NULL);
810 mi_assert_internal(page_alignment >= MI_SEGMENT_ALIGN);
811 alignment = page_alignment;
812 const size_t info_size = (*pinfo_slices) * MI_SEGMENT_SLICE_SIZE;
813 align_offset = _mi_align_up( info_size, MI_SEGMENT_ALIGN );
814 const size_t extra = align_offset - info_size;
815 // recalculate due to potential guard pages
816 *psegment_slices = mi_segment_calculate_slices(required + extra, ppre_size, pinfo_slices);
817
818 // mi_page_t.slice_count type is uint32_t
819 if (*psegment_slices > (size_t)UINT32_MAX) return NULL;
820 }
821
822 const size_t segment_size = (*psegment_slices) * MI_SEGMENT_SLICE_SIZE;
823 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 if (segment == NULL) {
825 return NULL; // failed to allocate
826 }
827
828 // ensure metadata part of the segment is committed
829 mi_commit_mask_t commit_mask;
830 if (memid.initially_committed) {
831 mi_commit_mask_create_full(&commit_mask);
832 }
833 else {
834 // at least commit the info slices
835 const size_t commit_needed = _mi_divide_up((*pinfo_slices)*MI_SEGMENT_SLICE_SIZE, MI_COMMIT_SIZE);
836 mi_assert_internal(commit_needed>0);
837 mi_commit_mask_create(0, commit_needed, &commit_mask);
838 mi_assert_internal(commit_needed*MI_COMMIT_SIZE >= (*pinfo_slices)*MI_SEGMENT_SLICE_SIZE);
839 if (!_mi_os_commit(segment, commit_needed*MI_COMMIT_SIZE, NULL, tld->stats)) {
840 _mi_arena_free(segment,segment_size,0,memid,tld->stats);
841 return NULL;
842 }
843 }
844 mi_assert_internal(segment != NULL && (uintptr_t)segment % MI_SEGMENT_SIZE == 0);
845
846 segment->memid = memid;
847 segment->allow_decommit = !memid.is_pinned;
848 segment->allow_purge = segment->allow_decommit && (mi_option_get(mi_option_purge_delay) >= 0);
849 segment->segment_size = segment_size;
850 segment->commit_mask = commit_mask;
851 segment->purge_expire = 0;
852 mi_commit_mask_create_empty(&segment->purge_mask);
853 mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL); // tsan
854
855 mi_segments_track_size((long)(segment_size), tld);
856 _mi_segment_map_allocated_at(segment);
857 return segment;
858 }
859
860
861 // Allocate a segment from the OS aligned to `MI_SEGMENT_SIZE` .
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)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 {
864 mi_assert_internal((required==0 && huge_page==NULL) || (required>0 && huge_page != NULL));
865
866 // calculate needed sizes first
867 size_t info_slices;
868 size_t pre_size;
869 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 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 const bool eager_delay = (// !_mi_os_has_overcommit() && // never delay on overcommit systems
876 _mi_current_thread_count() > 1 && // do not delay for the first N threads
877 tld->count < (size_t)mi_option_get(mi_option_eager_commit_delay));
878 const bool eager = !eager_delay && mi_option_is_enabled(mi_option_eager_commit);
879 bool commit = eager || (required > 0);
880
881 // Allocate the segment from the OS
882 mi_segment_t* segment = mi_segment_os_alloc(required, page_alignment, eager_delay, req_arena_id,
883 &segment_slices, &pre_size, &info_slices, commit, tld, os_tld);
884 if (segment == NULL) return NULL;
885
886 // zero the segment info? -- not always needed as it may be zero initialized from the OS
887 if (!segment->memid.initially_zero) {
888 ptrdiff_t ofs = offsetof(mi_segment_t, next);
889 size_t prefix = offsetof(mi_segment_t, slices) - ofs;
890 size_t zsize = prefix + (sizeof(mi_slice_t) * (segment_slices + 1)); // one more
891 _mi_memzero((uint8_t*)segment + ofs, zsize);
892 }
893
894 // initialize the rest of the segment info
895 const size_t slice_entries = (segment_slices > MI_SLICES_PER_SEGMENT ? MI_SLICES_PER_SEGMENT : segment_slices);
896 segment->segment_slices = segment_slices;
897 segment->segment_info_slices = info_slices;
898 segment->thread_id = _mi_thread_id();
899 segment->cookie = _mi_ptr_cookie(segment);
900 segment->slice_entries = slice_entries;
901 segment->kind = (required == 0 ? MI_SEGMENT_NORMAL : MI_SEGMENT_HUGE);
902
903 // _mi_memzero(segment->slices, sizeof(mi_slice_t)*(info_slices+1));
904 _mi_stat_increase(&tld->stats->page_committed, mi_segment_info_size(segment));
905
906 // set up guard pages
907 size_t guard_slices = 0;
908 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 size_t os_pagesize = _mi_os_page_size();
912 mi_assert_internal(mi_segment_info_size(segment) - os_pagesize >= pre_size);
913 _mi_os_protect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize);
914 uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize;
915 mi_segment_ensure_committed(segment, end, os_pagesize, tld->stats);
916 _mi_os_protect(end, os_pagesize);
917 if (slice_entries == segment_slices) segment->slice_entries--; // don't use the last slice :-(
918 guard_slices = 1;
919 }
920
921 // reserve first slices for segment info
922 mi_page_t* page0 = mi_segment_span_allocate(segment, 0, info_slices, tld);
923 mi_assert_internal(page0!=NULL); if (page0==NULL) return NULL; // cannot fail as we always commit in advance
924 mi_assert_internal(segment->used == 1);
925 segment->used = 0; // don't count our internal slices towards usage
926
927 // initialize initial free pages
928 if (segment->kind == MI_SEGMENT_NORMAL) { // not a huge page
929 mi_assert_internal(huge_page==NULL);
930 mi_segment_span_free(segment, info_slices, segment->slice_entries - info_slices, false /* don't purge */, tld);
931 }
932 else {
933 mi_assert_internal(huge_page!=NULL);
934 mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask));
935 mi_assert_internal(mi_commit_mask_is_full(&segment->commit_mask));
936 *huge_page = mi_segment_span_allocate(segment, info_slices, segment_slices - info_slices - guard_slices, tld);
937 mi_assert_internal(*huge_page != NULL); // cannot fail as we commit in advance
938 }
939
940 mi_assert_expensive(mi_segment_is_valid(segment,tld));
941 return segment;
942 }
943
944
mi_segment_free(mi_segment_t * segment,bool force,mi_segments_tld_t * tld)945 static void mi_segment_free(mi_segment_t* segment, bool force, mi_segments_tld_t* tld) {
946 MI_UNUSED(force);
947 mi_assert_internal(segment != NULL);
948 mi_assert_internal(segment->next == NULL);
949 mi_assert_internal(segment->used == 0);
950
951 // Remove the free pages
952 mi_slice_t* slice = &segment->slices[0];
953 const mi_slice_t* end = mi_segment_slices_end(segment);
954 #if MI_DEBUG>1
955 size_t page_count = 0;
956 #endif
957 while (slice < end) {
958 mi_assert_internal(slice->slice_count > 0);
959 mi_assert_internal(slice->slice_offset == 0);
960 mi_assert_internal(mi_slice_index(slice)==0 || slice->xblock_size == 0); // no more used pages ..
961 if (slice->xblock_size == 0 && segment->kind != MI_SEGMENT_HUGE) {
962 mi_segment_span_remove_from_queue(slice, tld);
963 }
964 #if MI_DEBUG>1
965 page_count++;
966 #endif
967 slice = slice + slice->slice_count;
968 }
969 mi_assert_internal(page_count == 2); // first page is allocated by the segment itself
970
971 // stats
972 _mi_stat_decrease(&tld->stats->page_committed, mi_segment_info_size(segment));
973
974 // return it to the OS
975 mi_segment_os_free(segment, tld);
976 }
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
mi_segment_page_clear(mi_page_t * page,mi_segments_tld_t * tld)986 static mi_slice_t* mi_segment_page_clear(mi_page_t* page, mi_segments_tld_t* tld) {
987 mi_assert_internal(page->xblock_size > 0);
988 mi_assert_internal(mi_page_all_free(page));
989 mi_segment_t* segment = _mi_ptr_segment(page);
990 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 size_t inuse = page->capacity * mi_page_block_size(page);
997 _mi_stat_decrease(&tld->stats->page_committed, inuse);
998 _mi_stat_decrease(&tld->stats->pages, 1);
999
1000 // reset the page memory to reduce memory pressure?
1001 if (segment->allow_decommit && mi_option_is_enabled(mi_option_deprecated_page_reset)) {
1002 size_t psize;
1003 uint8_t* start = _mi_page_start(segment, page, &psize);
1004 _mi_os_reset(start, psize, tld->stats);
1005 }
1006
1007 // zero the page data, but not the segment fields
1008 page->is_zero_init = false;
1009 ptrdiff_t ofs = offsetof(mi_page_t, capacity);
1010 _mi_memzero((uint8_t*)page + ofs, sizeof(*page) - ofs);
1011 page->xblock_size = 1;
1012
1013 // and free it
1014 mi_slice_t* slice = mi_segment_span_free_coalesce(mi_page_to_slice(page), tld);
1015 segment->used--;
1016 // cannot assert segment valid as it is called during reclaim
1017 // mi_assert_expensive(mi_segment_is_valid(segment, tld));
1018 return slice;
1019 }
1020
_mi_segment_page_free(mi_page_t * page,bool force,mi_segments_tld_t * tld)1021 void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld)
1022 {
1023 mi_assert(page != NULL);
1024
1025 mi_segment_t* segment = _mi_page_segment(page);
1026 mi_assert_expensive(mi_segment_is_valid(segment,tld));
1027
1028 // mark it as free now
1029 mi_segment_page_clear(page, tld);
1030 mi_assert_expensive(mi_segment_is_valid(segment, tld));
1031
1032 if (segment->used == 0) {
1033 // no more used pages; remove from the free list and free the segment
1034 mi_segment_free(segment, force, tld);
1035 }
1036 else if (segment->used == segment->abandoned) {
1037 // only abandoned pages; remove from free list and abandon
1038 mi_segment_abandon(segment,tld);
1039 }
1040 }
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 #define MI_TAGGED_MASK MI_SEGMENT_MASK
1072
mi_tagged_segment_ptr(mi_tagged_segment_t ts)1073 static mi_segment_t* mi_tagged_segment_ptr(mi_tagged_segment_t ts) {
1074 return (mi_segment_t*)(ts & ~MI_TAGGED_MASK);
1075 }
1076
mi_tagged_segment(mi_segment_t * segment,mi_tagged_segment_t ts)1077 static mi_tagged_segment_t mi_tagged_segment(mi_segment_t* segment, mi_tagged_segment_t ts) {
1078 mi_assert_internal(((uintptr_t)segment & MI_TAGGED_MASK) == 0);
1079 uintptr_t tag = ((ts & MI_TAGGED_MASK) + 1) & MI_TAGGED_MASK;
1080 return ((uintptr_t)segment | tag);
1081 }
1082
1083 mi_abandoned_pool_t _mi_abandoned_default;
1084
1085 // Push on the visited list
mi_abandoned_visited_push(mi_abandoned_pool_t * pool,mi_segment_t * segment)1086 static void mi_abandoned_visited_push(mi_abandoned_pool_t *pool, mi_segment_t* segment) {
1087 mi_assert_internal(segment->thread_id == 0);
1088 mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t,&segment->abandoned_next) == NULL);
1089 mi_assert_internal(segment->next == NULL);
1090 mi_assert_internal(segment->used > 0);
1091 mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &pool->abandoned_visited);
1092 do {
1093 mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, anext);
1094 } while (!mi_atomic_cas_ptr_weak_release(mi_segment_t, &pool->abandoned_visited, &anext, segment));
1095 mi_atomic_increment_relaxed(&pool->abandoned_visited_count);
1096 }
1097
1098 // Move the visited list to the abandoned list.
mi_abandoned_visited_revisit(mi_abandoned_pool_t * pool)1099 static bool mi_abandoned_visited_revisit(mi_abandoned_pool_t *pool)
1100 {
1101 // quick check if the visited list is empty
1102 if (mi_atomic_load_ptr_relaxed(mi_segment_t, &pool->abandoned_visited) == NULL) return false;
1103
1104 // grab the whole visited list
1105 mi_segment_t* first = mi_atomic_exchange_ptr_acq_rel(mi_segment_t, &pool->abandoned_visited, NULL);
1106 if (first == NULL) return false;
1107
1108 // first try to swap directly if the abandoned list happens to be NULL
1109 mi_tagged_segment_t afirst;
1110 mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1111 if (mi_tagged_segment_ptr(ts)==NULL) {
1112 size_t count = mi_atomic_load_relaxed(&pool->abandoned_visited_count);
1113 afirst = mi_tagged_segment(first, ts);
1114 if (mi_atomic_cas_strong_acq_rel(&pool->abandoned, &ts, afirst)) {
1115 mi_atomic_add_relaxed(&pool->abandoned_count, count);
1116 mi_atomic_sub_relaxed(&pool->abandoned_visited_count, count);
1117 return true;
1118 }
1119 }
1120
1121 // find the last element of the visited list: O(n)
1122 mi_segment_t* last = first;
1123 mi_segment_t* next;
1124 while ((next = mi_atomic_load_ptr_relaxed(mi_segment_t, &last->abandoned_next)) != NULL) {
1125 last = next;
1126 }
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 mi_tagged_segment_t anext = mi_atomic_load_relaxed(&pool->abandoned);
1131 size_t count;
1132 do {
1133 count = mi_atomic_load_relaxed(&pool->abandoned_visited_count);
1134 mi_atomic_store_ptr_release(mi_segment_t, &last->abandoned_next, mi_tagged_segment_ptr(anext));
1135 afirst = mi_tagged_segment(first, anext);
1136 } while (!mi_atomic_cas_weak_release(&pool->abandoned, &anext, afirst));
1137 mi_atomic_add_relaxed(&pool->abandoned_count, count);
1138 mi_atomic_sub_relaxed(&pool->abandoned_visited_count, count);
1139 return true;
1140 }
1141
1142 // Push on the abandoned list.
mi_abandoned_push(mi_abandoned_pool_t * pool,mi_segment_t * segment)1143 static void mi_abandoned_push(mi_abandoned_pool_t* pool, mi_segment_t* segment) {
1144 mi_assert_internal(segment->thread_id == 0);
1145 mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1146 mi_assert_internal(segment->next == NULL);
1147 mi_assert_internal(segment->used > 0);
1148 mi_tagged_segment_t next;
1149 mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1150 do {
1151 mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, mi_tagged_segment_ptr(ts));
1152 next = mi_tagged_segment(segment, ts);
1153 } while (!mi_atomic_cas_weak_release(&pool->abandoned, &ts, next));
1154 mi_atomic_increment_relaxed(&pool->abandoned_count);
1155 }
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
_mi_abandoned_await_readers(mi_abandoned_pool_t * pool)1159 void _mi_abandoned_await_readers(mi_abandoned_pool_t* pool) {
1160 size_t n;
1161 do {
1162 n = mi_atomic_load_acquire(&pool->abandoned_readers);
1163 if (n != 0) mi_atomic_yield();
1164 } while (n != 0);
1165 }
1166
1167 // Pop from the abandoned list
mi_abandoned_pop(mi_abandoned_pool_t * pool)1168 static mi_segment_t* mi_abandoned_pop(mi_abandoned_pool_t* pool) {
1169 mi_segment_t* segment;
1170 // Check efficiently if it is empty (or if the visited list needs to be moved)
1171 mi_tagged_segment_t ts = mi_atomic_load_relaxed(&pool->abandoned);
1172 segment = mi_tagged_segment_ptr(ts);
1173 if mi_likely(segment == NULL) {
1174 if mi_likely(!mi_abandoned_visited_revisit(pool)) { // try to swap in the visited list on NULL
1175 return NULL;
1176 }
1177 }
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 mi_atomic_increment_relaxed(&pool->abandoned_readers); // ensure no segment gets decommitted
1184 mi_tagged_segment_t next = 0;
1185 ts = mi_atomic_load_acquire(&pool->abandoned);
1186 do {
1187 segment = mi_tagged_segment_ptr(ts);
1188 if (segment != NULL) {
1189 mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next);
1190 next = mi_tagged_segment(anext, ts); // note: reads the segment's `abandoned_next` field so should not be decommitted
1191 }
1192 } while (segment != NULL && !mi_atomic_cas_weak_acq_rel(&pool->abandoned, &ts, next));
1193 mi_atomic_decrement_relaxed(&pool->abandoned_readers); // release reader lock
1194 if (segment != NULL) {
1195 mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
1196 mi_atomic_decrement_relaxed(&pool->abandoned_count);
1197 }
1198 return segment;
1199 }
1200
1201 /* -----------------------------------------------------------
1202 Abandon segment/page
1203 ----------------------------------------------------------- */
1204
mi_segment_abandon(mi_segment_t * segment,mi_segments_tld_t * tld)1205 static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
1206 mi_assert_internal(segment->used == segment->abandoned);
1207 mi_assert_internal(segment->used > 0);
1208 mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1209 mi_assert_internal(segment->abandoned_visits == 0);
1210 mi_assert_expensive(mi_segment_is_valid(segment,tld));
1211
1212 // remove the free pages from the free page queues
1213 mi_slice_t* slice = &segment->slices[0];
1214 const mi_slice_t* end = mi_segment_slices_end(segment);
1215 while (slice < end) {
1216 mi_assert_internal(slice->slice_count > 0);
1217 mi_assert_internal(slice->slice_offset == 0);
1218 if (slice->xblock_size == 0) { // a free page
1219 mi_segment_span_remove_from_queue(slice,tld);
1220 slice->xblock_size = 0; // but keep it free
1221 }
1222 slice = slice + slice->slice_count;
1223 }
1224
1225 // perform delayed decommits (forcing is much slower on mstress)
1226 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 _mi_stat_increase(&tld->stats->segments_abandoned, 1);
1230 mi_segments_track_size(-((long)mi_segment_size(segment)), tld);
1231 segment->thread_id = 0;
1232 mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL);
1233 segment->abandoned_visits = 1; // from 0 to 1 to signify it is abandoned
1234 mi_abandoned_push(tld->abandoned, segment);
1235 }
1236
_mi_segment_page_abandon(mi_page_t * page,mi_segments_tld_t * tld)1237 void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) {
1238 mi_assert(page != NULL);
1239 mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
1240 mi_assert_internal(mi_page_heap(page) == NULL);
1241 mi_segment_t* segment = _mi_page_segment(page);
1242
1243 mi_assert_expensive(mi_segment_is_valid(segment,tld));
1244 segment->abandoned++;
1245
1246 _mi_stat_increase(&tld->stats->pages_abandoned, 1);
1247 mi_assert_internal(segment->abandoned <= segment->used);
1248 if (segment->used == segment->abandoned) {
1249 // all pages are abandoned, abandon the entire segment
1250 mi_segment_abandon(segment, tld);
1251 }
1252 }
1253
1254 /* -----------------------------------------------------------
1255 Reclaim abandoned pages
1256 ----------------------------------------------------------- */
1257
mi_slices_start_iterate(mi_segment_t * segment,const mi_slice_t ** end)1258 static mi_slice_t* mi_slices_start_iterate(mi_segment_t* segment, const mi_slice_t** end) {
1259 mi_slice_t* slice = &segment->slices[0];
1260 *end = mi_segment_slices_end(segment);
1261 mi_assert_internal(slice->slice_count>0 && slice->xblock_size>0); // segment allocated page
1262 slice = slice + slice->slice_count; // skip the first segment allocated page
1263 return slice;
1264 }
1265
1266 // Possibly free pages and check if free space is available
mi_segment_check_free(mi_segment_t * segment,size_t slices_needed,size_t block_size,mi_segments_tld_t * tld)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 {
1269 mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE);
1270 mi_assert_internal(mi_segment_is_abandoned(segment));
1271 bool has_page = false;
1272
1273 // for all slices
1274 const mi_slice_t* end;
1275 mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1276 while (slice < end) {
1277 mi_assert_internal(slice->slice_count > 0);
1278 mi_assert_internal(slice->slice_offset == 0);
1279 if (mi_slice_is_used(slice)) { // used page
1280 // ensure used count is up to date and collect potential concurrent frees
1281 mi_page_t* const page = mi_slice_to_page(slice);
1282 _mi_page_free_collect(page, false);
1283 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 mi_assert_internal(page->next == NULL && page->prev==NULL);
1286 _mi_stat_decrease(&tld->stats->pages_abandoned, 1);
1287 #ifdef Py_GIL_DISABLED
1288 page->qsbr_goal = 0;
1289 #endif
1290 segment->abandoned--;
1291 slice = mi_segment_page_clear(page, tld); // re-assign slice due to coalesce!
1292 mi_assert_internal(!mi_slice_is_used(slice));
1293 if (slice->slice_count >= slices_needed) {
1294 has_page = true;
1295 }
1296 }
1297 else {
1298 if (page->xblock_size == block_size && mi_page_has_any_available(page)) {
1299 // a page has available free blocks of the right size
1300 has_page = true;
1301 }
1302 }
1303 }
1304 else {
1305 // empty span
1306 if (slice->slice_count >= slices_needed) {
1307 has_page = true;
1308 }
1309 }
1310 slice = slice + slice->slice_count;
1311 }
1312 return has_page;
1313 }
1314
mi_heap_by_tag(mi_heap_t * heap,uint8_t tag)1315 static mi_heap_t* mi_heap_by_tag(mi_heap_t* heap, uint8_t tag) {
1316 if (heap->tag == tag) {
1317 return heap;
1318 }
1319 for (mi_heap_t *curr = heap->tld->heaps; curr != NULL; curr = curr->next) {
1320 if (curr->tag == tag) {
1321 return curr;
1322 }
1323 }
1324 return NULL;
1325 }
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.
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)1329 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 mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL);
1331 mi_assert_expensive(mi_segment_is_valid(segment, tld));
1332 if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; }
1333
1334 segment->thread_id = _mi_thread_id();
1335 segment->abandoned_visits = 0;
1336 mi_segments_track_size((long)mi_segment_size(segment), tld);
1337 mi_assert_internal(segment->next == NULL);
1338 _mi_stat_decrease(&tld->stats->segments_abandoned, 1);
1339
1340 // for all slices
1341 const mi_slice_t* end;
1342 mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1343 while (slice < end) {
1344 mi_assert_internal(slice->slice_count > 0);
1345 mi_assert_internal(slice->slice_offset == 0);
1346 if (mi_slice_is_used(slice)) {
1347 // in use: reclaim the page in our heap
1348 mi_page_t* page = mi_slice_to_page(slice);
1349 mi_heap_t* target_heap = mi_heap_by_tag(heap, page->tag);
1350 mi_assert_internal(page->is_committed);
1351 mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
1352 mi_assert_internal(mi_page_heap(page) == NULL);
1353 mi_assert_internal(page->next == NULL && page->prev==NULL);
1354 _mi_stat_decrease(&tld->stats->pages_abandoned, 1);
1355 segment->abandoned--;
1356 // set the heap again and allow delayed free again
1357 mi_page_set_heap(page, target_heap);
1358 _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set)
1359 _mi_page_free_collect(page, false); // ensure used count is up to date
1360 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 slice = mi_segment_page_clear(page, tld); // set slice again due to coalesceing
1366 }
1367 else {
1368 // otherwise reclaim it into the heap
1369 _mi_page_reclaim(target_heap, page);
1370 if (requested_block_size == page->xblock_size && mi_page_has_any_available(page) &&
1371 requested_block_size <= MI_MEDIUM_OBJ_SIZE_MAX && heap == target_heap) {
1372 if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; }
1373 }
1374 }
1375 }
1376 else {
1377 // the span is free, add it to our page queues
1378 slice = mi_segment_span_free_coalesce(slice, tld); // set slice again due to coalesceing
1379 }
1380 mi_assert_internal(slice->slice_count>0 && slice->slice_offset==0);
1381 slice = slice + slice->slice_count;
1382 }
1383
1384 mi_assert(segment->abandoned == 0);
1385 if (segment->used == 0) { // due to page_clear
1386 mi_assert_internal(right_page_reclaimed == NULL || !(*right_page_reclaimed));
1387 mi_segment_free(segment, false, tld);
1388 return NULL;
1389 }
1390 else {
1391 return segment;
1392 }
1393 }
1394
1395
_mi_abandoned_reclaim_all(mi_heap_t * heap,mi_segments_tld_t * tld)1396 void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) {
1397 mi_segment_t* segment;
1398 while ((segment = mi_abandoned_pop(tld->abandoned)) != NULL) {
1399 mi_segment_reclaim(segment, heap, 0, NULL, tld);
1400 }
1401 }
1402
mi_segment_try_reclaim(mi_heap_t * heap,size_t needed_slices,size_t block_size,bool * reclaimed,mi_segments_tld_t * tld)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 {
1405 *reclaimed = false;
1406 mi_segment_t* segment;
1407 long max_tries = mi_option_get_clamp(mi_option_max_segment_reclaim, 8, 1024); // limit the work to bound allocation times
1408 while ((max_tries-- > 0) && ((segment = mi_abandoned_pop(tld->abandoned)) != NULL)) {
1409 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 bool is_suitable = _mi_heap_memid_is_suitable(heap, segment->memid);
1413 bool has_page = mi_segment_check_free(segment,needed_slices,block_size,tld); // try to free up pages (due to concurrent frees)
1414 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 mi_segment_reclaim(segment, heap, 0, NULL, tld);
1421 }
1422 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 return mi_segment_reclaim(segment, heap, block_size, reclaimed, tld);
1427 }
1428 else if (segment->abandoned_visits > 3 && is_suitable) {
1429 // always reclaim on 3rd visit to limit the abandoned queue length.
1430 mi_segment_reclaim(segment, heap, 0, NULL, tld);
1431 }
1432 else {
1433 // otherwise, push on the visited list so it gets not looked at too quickly again
1434 mi_segment_try_purge(segment, true /* force? */, tld->stats); // force purge if needed as we may not visit soon again
1435 mi_abandoned_visited_push(tld->abandoned, segment);
1436 }
1437 }
1438 return NULL;
1439 }
1440
1441
_mi_abandoned_collect(mi_heap_t * heap,bool force,mi_segments_tld_t * tld)1442 void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld)
1443 {
1444 mi_segment_t* segment;
1445 mi_abandoned_pool_t* pool = tld->abandoned;
1446 int max_tries = (force ? 16*1024 : 1024); // limit latency
1447 if (force) {
1448 mi_abandoned_visited_revisit(pool);
1449 }
1450 while ((max_tries-- > 0) && ((segment = mi_abandoned_pop(pool)) != NULL)) {
1451 mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees)
1452 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 mi_segment_reclaim(segment, heap, 0, NULL, tld);
1457 }
1458 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 mi_segment_try_purge(segment, force, tld->stats);
1462 mi_abandoned_visited_push(pool, segment);
1463 }
1464 }
1465 }
1466
1467 /* -----------------------------------------------------------
1468 Reclaim or allocate
1469 ----------------------------------------------------------- */
1470
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)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 {
1473 mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE);
1474 mi_assert_internal(block_size <= MI_LARGE_OBJ_SIZE_MAX);
1475
1476 // 1. try to reclaim an abandoned segment
1477 bool reclaimed;
1478 mi_segment_t* segment = mi_segment_try_reclaim(heap, needed_slices, block_size, &reclaimed, tld);
1479 if (reclaimed) {
1480 // reclaimed the right page right into the heap
1481 mi_assert_internal(segment != NULL);
1482 return NULL; // pretend out-of-memory as the page will be in the page queue of the heap with available blocks
1483 }
1484 else if (segment != NULL) {
1485 // reclaimed a segment with a large enough empty span in it
1486 return segment;
1487 }
1488 // 2. otherwise allocate a fresh segment
1489 return mi_segment_alloc(0, 0, heap->arena_id, tld, os_tld, NULL);
1490 }
1491
1492
1493 /* -----------------------------------------------------------
1494 Page allocation
1495 ----------------------------------------------------------- */
1496
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)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 {
1499 mi_assert_internal(required <= MI_LARGE_OBJ_SIZE_MAX && page_kind <= MI_PAGE_LARGE);
1500
1501 // find a free page
1502 size_t page_size = _mi_align_up(required, (required > MI_MEDIUM_PAGE_SIZE ? MI_MEDIUM_PAGE_SIZE : MI_SEGMENT_SLICE_SIZE));
1503 size_t slices_needed = page_size / MI_SEGMENT_SLICE_SIZE;
1504 mi_assert_internal(slices_needed * MI_SEGMENT_SLICE_SIZE == page_size);
1505 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 if (page==NULL) {
1507 // no free page, allocate a new segment and try again
1508 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 return NULL;
1511 }
1512 else {
1513 // otherwise try again
1514 return mi_segments_page_alloc(heap, page_kind, required, block_size, tld, os_tld);
1515 }
1516 }
1517 mi_assert_internal(page != NULL && page->slice_count*MI_SEGMENT_SLICE_SIZE == page_size);
1518 mi_assert_internal(_mi_ptr_segment(page)->thread_id == _mi_thread_id());
1519 mi_segment_try_purge(_mi_ptr_segment(page), false, tld->stats);
1520 return page;
1521 }
1522
1523
1524
1525 /* -----------------------------------------------------------
1526 Huge page allocation
1527 ----------------------------------------------------------- */
1528
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)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 {
1531 mi_page_t* page = NULL;
1532 mi_segment_t* segment = mi_segment_alloc(size,page_alignment,req_arena_id,tld,os_tld,&page);
1533 if (segment == NULL || page==NULL) return NULL;
1534 mi_assert_internal(segment->used==1);
1535 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 size_t psize;
1543 uint8_t* start = _mi_segment_page_start(segment, page, &psize);
1544 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 if (page_alignment > 0 && segment->allow_decommit) {
1548 uint8_t* aligned_p = (uint8_t*)_mi_align_up((uintptr_t)start, page_alignment);
1549 mi_assert_internal(_mi_is_aligned(aligned_p, page_alignment));
1550 mi_assert_internal(psize - (aligned_p - start) >= size);
1551 uint8_t* decommit_start = start + sizeof(mi_block_t); // for the free list
1552 ptrdiff_t decommit_size = aligned_p - decommit_start;
1553 _mi_os_reset(decommit_start, decommit_size, &_mi_stats_main); // note: cannot use segment_decommit on huge segments
1554 }
1555
1556 return page;
1557 }
1558
1559 #if MI_HUGE_PAGE_ABANDON
1560 // free huge block from another thread
_mi_segment_huge_page_free(mi_segment_t * segment,mi_page_t * page,mi_block_t * block)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
_mi_segment_huge_page_reset(mi_segment_t * segment,mi_page_t * page,mi_block_t * block)1589 void _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) {
1590 MI_UNUSED(page);
1591 mi_assert_internal(segment->kind == MI_SEGMENT_HUGE);
1592 mi_assert_internal(segment == _mi_page_segment(page));
1593 mi_assert_internal(page->used == 1); // this is called just before the free
1594 mi_assert_internal(page->free == NULL);
1595 if (segment->allow_decommit) {
1596 size_t csize = mi_usable_size(block);
1597 if (csize > sizeof(mi_block_t)) {
1598 csize = csize - sizeof(mi_block_t);
1599 uint8_t* p = (uint8_t*)block + sizeof(mi_block_t);
1600 _mi_os_reset(p, csize, &_mi_stats_main); // note: cannot use segment_decommit on huge segments
1601 }
1602 }
1603 }
1604 #endif
1605
1606 /* -----------------------------------------------------------
1607 Page allocation and free
1608 ----------------------------------------------------------- */
_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)1609 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 mi_page_t* page;
1611 if mi_unlikely(page_alignment > MI_ALIGNMENT_MAX) {
1612 mi_assert_internal(_mi_is_power_of_two(page_alignment));
1613 mi_assert_internal(page_alignment >= MI_SEGMENT_SIZE);
1614 if (page_alignment < MI_SEGMENT_SIZE) { page_alignment = MI_SEGMENT_SIZE; }
1615 page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld);
1616 }
1617 else if (block_size <= MI_SMALL_OBJ_SIZE_MAX) {
1618 page = mi_segments_page_alloc(heap,MI_PAGE_SMALL,block_size,block_size,tld,os_tld);
1619 }
1620 else if (block_size <= MI_MEDIUM_OBJ_SIZE_MAX) {
1621 page = mi_segments_page_alloc(heap,MI_PAGE_MEDIUM,MI_MEDIUM_PAGE_SIZE,block_size,tld, os_tld);
1622 }
1623 else if (block_size <= MI_LARGE_OBJ_SIZE_MAX) {
1624 page = mi_segments_page_alloc(heap,MI_PAGE_LARGE,block_size,block_size,tld, os_tld);
1625 }
1626 else {
1627 page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld);
1628 }
1629 mi_assert_internal(page == NULL || _mi_heap_memid_is_suitable(heap, _mi_page_segment(page)->memid));
1630 mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld));
1631 return page;
1632 }
1633
1634 /* -----------------------------------------------------------
1635 Visit blocks in abandoned segments
1636 ----------------------------------------------------------- */
1637
mi_segment_visit_page(mi_segment_t * segment,mi_page_t * page,bool visit_blocks,mi_block_visit_fun * visitor,void * arg)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 {
1640 mi_heap_area_t area;
1641 _mi_heap_area_init(&area, page);
1642 if (!visitor(NULL, &area, NULL, area.block_size, arg)) return false;
1643 if (visit_blocks) {
1644 return _mi_heap_area_visit_blocks(&area, page, visitor, arg);
1645 }
1646 else {
1647 return true;
1648 }
1649 }
1650
mi_segment_visit_pages(mi_segment_t * segment,uint8_t page_tag,bool visit_blocks,mi_block_visit_fun * visitor,void * arg)1651 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 const mi_slice_t* end;
1653 mi_slice_t* slice = mi_slices_start_iterate(segment, &end);
1654 while (slice < end) {
1655 if (mi_slice_is_used(slice)) {
1656 mi_page_t* const page = mi_slice_to_page(slice);
1657 if (page->tag == page_tag) {
1658 if (!mi_segment_visit_page(segment, page, visit_blocks, visitor, arg)) return false;
1659 }
1660 }
1661 slice = slice + slice->slice_count;
1662 }
1663 return true;
1664 }
1665
1666 // Visit all blocks in a abandoned segments
_mi_abandoned_pool_visit_blocks(mi_abandoned_pool_t * pool,uint8_t page_tag,bool visit_blocks,mi_block_visit_fun * visitor,void * arg)1667 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 mi_segment_t* segment = mi_tagged_segment_ptr(pool->abandoned);
1670 while (segment != NULL) {
1671 if (!mi_segment_visit_pages(segment, page_tag, visit_blocks, visitor, arg)) return false;
1672 segment = segment->abandoned_next;
1673 }
1674
1675 segment = pool->abandoned_visited;
1676 while (segment != NULL) {
1677 if (!mi_segment_visit_pages(segment, page_tag, visit_blocks, visitor, arg)) return false;
1678 segment = segment->abandoned_next;
1679 }
1680
1681 return true;
1682 }
1683