Skip to content

Commit c2a1692

Browse files
committed
pythongh-112532: Tag mimalloc heaps and pages
Mimalloc pages are data structures that contain contiguous allocations of the same block size. Note that they are distinct from operating system pages. Mimalloc pages are contained in segments. When a thread exits, it abandons any segments and contained pages that have live allocations. These segments and pages may be later reclaimed by another thread. To support GC and certain thread-safety guarantees in free-threaded builds, we want pages to only be reclaimed by the corresponding heap in the claimant thread. For example, we want pages containing GC objects to only be claimed by GC heaps. This allows heaps and pages to be tagged with an integer tag that is used to ensure that abandoned pages are only claimed by heaps with the same tag. Heaps can be initialized with a tag (0-15); any page allocated by that heap copies the corresponding tag.
1 parent 5e1916b commit c2a1692

File tree

7 files changed

+35
-16
lines changed

7 files changed

+35
-16
lines changed

Include/internal/mimalloc/mimalloc/internal.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ size_t _mi_bin_size(uint8_t bin); // for stats
155155
uint8_t _mi_bin(size_t size); // for stats
156156

157157
// "heap.c"
158-
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id);
158+
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool no_reclaim, uint8_t tag);
159159
void _mi_heap_destroy_pages(mi_heap_t* heap);
160160
void _mi_heap_collect_abandon(mi_heap_t* heap);
161161
void _mi_heap_set_default_direct(mi_heap_t* heap);

Include/internal/mimalloc/mimalloc/types.h

+2
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ typedef struct mi_page_s {
311311
uint32_t slice_offset; // distance from the actual page data slice (0 if a page)
312312
uint8_t is_committed : 1; // `true` if the page virtual memory is committed
313313
uint8_t is_zero_init : 1; // `true` if the page was initially zero initialized
314+
uint8_t tag : 4; // tag from the owning heap
314315

315316
// layout like this to optimize access in `mi_malloc` and `mi_free`
316317
uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear`
@@ -551,6 +552,7 @@ struct mi_heap_s {
551552
size_t page_retired_max; // largest retired index into the `pages` array.
552553
mi_heap_t* next; // list of heaps per thread
553554
bool no_reclaim; // `true` if this heap should not reclaim abandoned pages
555+
uint8_t tag; // custom identifier for this heap
554556
};
555557

556558

Objects/mimalloc/heap.c

+8-6
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ mi_heap_t* mi_heap_get_backing(void) {
209209
return bheap;
210210
}
211211

212-
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id)
212+
void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id, bool no_reclaim, uint8_t tag)
213213
{
214214
_mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t));
215215
heap->tld = tld;
@@ -224,17 +224,19 @@ void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, mi_arena_id_t arena_id)
224224
heap->cookie = _mi_heap_random_next(heap) | 1;
225225
heap->keys[0] = _mi_heap_random_next(heap);
226226
heap->keys[1] = _mi_heap_random_next(heap);
227+
heap->no_reclaim = no_reclaim;
228+
heap->tag = tag;
229+
// push on the thread local heaps list
230+
heap->next = heap->tld->heaps;
231+
heap->tld->heaps = heap;
227232
}
228233

229234
mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) {
230235
mi_heap_t* bheap = mi_heap_get_backing();
231236
mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode?
232237
if (heap == NULL) return NULL;
233-
_mi_heap_init_ex(heap, bheap->tld, arena_id);
234-
heap->no_reclaim = true; // don't reclaim abandoned pages or otherwise destroy is unsafe
235-
// push on the thread local heaps list
236-
heap->next = heap->tld->heaps;
237-
heap->tld->heaps = heap;
238+
// don't reclaim abandoned pages or otherwise destroy is unsafe
239+
_mi_heap_init_ex(heap, bheap->tld, arena_id, true, 0);
238240
return heap;
239241
}
240242

Objects/mimalloc/init.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ terms of the MIT license. A copy of the license can be found in the file
1414

1515
// Empty page used to initialize the small free pages array
1616
const mi_page_t _mi_page_empty = {
17-
0, false, false, false,
17+
0, false, false, false, 0,
1818
0, // capacity
1919
0, // reserved capacity
2020
{ 0 }, // flags
@@ -121,7 +121,8 @@ mi_decl_cache_align const mi_heap_t _mi_heap_empty = {
121121
0, // page count
122122
MI_BIN_FULL, 0, // page retired min/max
123123
NULL, // next
124-
false
124+
false,
125+
0
125126
};
126127

127128
#define tld_empty_stats ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats)))
@@ -298,7 +299,7 @@ static bool _mi_heap_init(void) {
298299
if (td == NULL) return false;
299300

300301
_mi_tld_init(&td->tld, &td->heap);
301-
_mi_heap_init_ex(&td->heap, &td->tld, _mi_arena_id_none());
302+
_mi_heap_init_ex(&td->heap, &td->tld, _mi_arena_id_none(), false, 0);
302303
_mi_heap_set_default_direct(&td->heap);
303304
}
304305
return false;
@@ -311,7 +312,6 @@ void _mi_tld_init(mi_tld_t* tld, mi_heap_t* bheap) {
311312
tld->segments.abandoned = &_mi_abandoned_default;
312313
tld->os.stats = &tld->stats;
313314
tld->heap_backing = bheap;
314-
tld->heaps = bheap;
315315
}
316316

317317
// Free the thread local default heap (called from `mi_thread_done`)

Objects/mimalloc/page.c

+1
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
660660
mi_assert_internal(block_size > 0);
661661
// set fields
662662
mi_page_set_heap(page, heap);
663+
page->tag = heap->tag;
663664
page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start
664665
size_t page_size;
665666
const void* page_start = _mi_segment_page_start(segment, page, &page_size);

Objects/mimalloc/segment.c

+17-3
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,18 @@ static bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, s
12991299
return has_page;
13001300
}
13011301

1302+
static mi_heap_t* mi_heap_by_tag(mi_heap_t* heap, uint8_t tag) {
1303+
if (heap->tag == tag) {
1304+
return heap;
1305+
}
1306+
for (mi_heap_t *curr = heap->tld->heaps; curr != NULL; curr = curr->next) {
1307+
if (curr->tag == tag) {
1308+
return curr;
1309+
}
1310+
}
1311+
return NULL;
1312+
}
1313+
13021314
// Reclaim an abandoned segment; returns NULL if the segment was freed
13031315
// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.
13041316
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) {
@@ -1321,14 +1333,15 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
13211333
if (mi_slice_is_used(slice)) {
13221334
// in use: reclaim the page in our heap
13231335
mi_page_t* page = mi_slice_to_page(slice);
1336+
mi_heap_t* target_heap = mi_heap_by_tag(heap, page->tag);
13241337
mi_assert_internal(page->is_committed);
13251338
mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE);
13261339
mi_assert_internal(mi_page_heap(page) == NULL);
13271340
mi_assert_internal(page->next == NULL && page->prev==NULL);
13281341
_mi_stat_decrease(&tld->stats->pages_abandoned, 1);
13291342
segment->abandoned--;
13301343
// set the heap again and allow delayed free again
1331-
mi_page_set_heap(page, heap);
1344+
mi_page_set_heap(page, target_heap);
13321345
_mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set)
13331346
_mi_page_free_collect(page, false); // ensure used count is up to date
13341347
if (mi_page_all_free(page)) {
@@ -1337,8 +1350,9 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
13371350
}
13381351
else {
13391352
// otherwise reclaim it into the heap
1340-
_mi_page_reclaim(heap, page);
1341-
if (requested_block_size == page->xblock_size && mi_page_has_any_available(page)) {
1353+
_mi_page_reclaim(target_heap, page);
1354+
if (requested_block_size == page->xblock_size && mi_page_has_any_available(page) &&
1355+
heap == target_heap) {
13421356
if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; }
13431357
}
13441358
}

Python/pystate.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -2539,8 +2539,8 @@ tstate_mimalloc_bind(PyThreadState *tstate)
25392539
tld->segments.abandoned = &tstate->interp->mimalloc.abandoned_pool;
25402540

25412541
// Initialize each heap
2542-
for (Py_ssize_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) {
2543-
_mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none());
2542+
for (size_t i = 0; i < _Py_MIMALLOC_HEAP_COUNT; i++) {
2543+
_mi_heap_init_ex(&mts->heaps[i], tld, _mi_arena_id_none(), false, i);
25442544
}
25452545

25462546
// By default, object allocations use _Py_MIMALLOC_HEAP_OBJECT.

0 commit comments

Comments
 (0)