Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 45e0ec5

Browse files
committed
mimalloc: changes to support separate heaps
These are changes to support separate heaps for Python objects, Python objects with GC header, and non Python objects.
1 parent 3d3a227 commit 45e0ec5

7 files changed

Lines changed: 98 additions & 16 deletions

File tree

‎Include/mimalloc/mimalloc-internal.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t*
8181
void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld);
8282
uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t block_size, size_t* page_size, size_t* pre_size); // page start for any page
8383
void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block);
84+
mi_segment_t* _mi_segment_abandoned(void);
85+
mi_segment_t* _mi_segment_abandoned_visited(void);
8486

8587
void _mi_segment_thread_collect(mi_segments_tld_t* tld);
8688
void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld);

‎Include/mimalloc/mimalloc-types.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ terms of the MIT license. A copy of the license can be found in the file
5858
// Encoded free lists allow detection of corrupted free lists
5959
// and can detect buffer overflows, modify after free, and double `free`s.
6060
#if (MI_SECURE>=3 || MI_DEBUG>=1 || defined(MI_PADDING))
61-
#define MI_ENCODE_FREELIST 1
61+
// TODO(sgross): Don't encode free-list because it breaks the constraint that
62+
// freed blocks do not have the LSB of the first word set.
63+
//#define MI_ENCODE_FREELIST 1
6264
#endif
6365

6466

@@ -207,6 +209,7 @@ typedef struct mi_page_s {
207209
uint8_t is_reset:1; // `true` if the page memory was reset
208210
uint8_t is_committed:1; // `true` if the page virtual memory is committed
209211
uint8_t is_zero_init:1; // `true` if the page was zero initialized
212+
unsigned int tag:4;
210213

211214
// layout like this to optimize access in `mi_malloc` and `mi_free`
212215
uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear`
@@ -331,6 +334,8 @@ struct mi_heap_s {
331334
size_t page_count; // total number of pages in the `pages` queues.
332335
mi_heap_t* next; // list of heaps per thread
333336
bool no_reclaim; // `true` if this heap should not reclaim abandoned pages
337+
unsigned char tag;
338+
bool visited; // used by gcmodule.c
334339
};
335340

336341

@@ -471,6 +476,8 @@ struct mi_tld_s {
471476
bool recurse; // true if deferred was called; used to prevent infinite recursion.
472477
mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted)
473478
mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates)
479+
mi_heap_t* heap_obj;
480+
mi_heap_t* heap_gc;
474481
mi_segments_tld_t segments; // segment tld
475482
mi_os_tld_t os; // os tld
476483
mi_stats_t stats; // statistics

‎Include/mimalloc/mimalloc.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,8 @@ mi_decl_export void mi_heap_destroy(mi_heap_t* heap);
178178
mi_decl_export mi_heap_t* mi_heap_set_default(mi_heap_t* heap);
179179
mi_decl_export mi_heap_t* mi_heap_get_default(void);
180180
mi_decl_export mi_heap_t* mi_heap_get_backing(void);
181+
mi_decl_export mi_heap_t* mi_heap_get_obj(void);
182+
mi_decl_export mi_heap_t* mi_heap_get_gc(void);
181183
mi_decl_export void mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept;
182184

183185
mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2);
@@ -228,6 +230,13 @@ mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned(mi_heap_t* heap,
228230
mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(3,4);
229231

230232

233+
typedef enum mi_heap_tag_e {
234+
mi_heap_tag_default,
235+
mi_heap_tag_obj,
236+
mi_heap_tag_gc,
237+
_mi_heap_tag_last
238+
} mi_heap_tag_t;
239+
231240
// ------------------------------------------------------
232241
// Analysis
233242
// ------------------------------------------------------

‎Objects/mimalloc/heap.c‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ mi_heap_t* mi_heap_get_backing(void) {
189189
return bheap;
190190
}
191191

192+
mi_heap_t* mi_heap_get_obj(void) {
193+
mi_heap_t* def = mi_heap_get_default();
194+
return def->tld->heap_obj;
195+
}
196+
197+
mi_heap_t* mi_heap_get_gc(void) {
198+
mi_heap_t* def = mi_heap_get_default();
199+
return def->tld->heap_gc;
200+
}
201+
192202
mi_heap_t* mi_heap_new(void) {
193203
mi_heap_t* bheap = mi_heap_get_backing();
194204
mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode?
@@ -329,7 +339,7 @@ void mi_heap_destroy(mi_heap_t* heap) {
329339
----------------------------------------------------------- */
330340

331341
// Tranfer the pages from one heap to the other
332-
static void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) {
342+
void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) {
333343
mi_assert_internal(heap!=NULL);
334344
if (from==NULL || from->page_count == 0) return;
335345

‎Objects/mimalloc/init.c‎

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ terms of the MIT license. A copy of the license can be found in the file
1313
// Empty page used to initialize the small free pages array
1414
const mi_page_t _mi_page_empty = {
1515
0, false, false, false, false,
16+
0, // tag
1617
0, // capacity
1718
0, // reserved capacity
1819
{ 0 }, // flags
@@ -98,6 +99,8 @@ const mi_heap_t _mi_heap_empty = {
9899
{ {0}, {0}, 0 },
99100
0, // page count
100101
NULL, // next
102+
false,
103+
0,
101104
false
102105
};
103106

@@ -110,9 +113,15 @@ mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty;
110113

111114
extern mi_heap_t _mi_heap_main;
112115

116+
// Initialized in mi_heap_init
117+
static mi_heap_t _mi_heap_main_obj;
118+
static mi_heap_t _mi_heap_main_gc;
119+
113120
static mi_tld_t tld_main = {
114121
0, false,
115122
&_mi_heap_main, &_mi_heap_main,
123+
&_mi_heap_main_obj,
124+
&_mi_heap_main_gc,
116125
{ { NULL, NULL }, {NULL ,NULL}, {NULL ,NULL, 0},
117126
0, 0, 0, 0, 0, 0, NULL,
118127
tld_main_stats, tld_main_os
@@ -163,9 +172,22 @@ mi_heap_t* _mi_heap_main_get(void) {
163172
// note: in x64 in release build `sizeof(mi_thread_data_t)` is under 4KiB (= OS page size).
164173
typedef struct mi_thread_data_s {
165174
mi_heap_t heap; // must come first due to cast in `_mi_heap_done`
175+
mi_heap_t heap_obj;
176+
mi_heap_t heap_gc;
166177
mi_tld_t tld;
167178
} mi_thread_data_t;
168179

180+
static void _mi_heap_init_ex(mi_heap_t* heap, mi_tld_t* tld, int tag) {
181+
memcpy(heap, &_mi_heap_empty, sizeof(*heap));
182+
heap->thread_id = _mi_thread_id();
183+
_mi_random_init(&heap->random);
184+
heap->cookie = _mi_heap_random_next(heap) | 1;
185+
heap->keys[0] = _mi_heap_random_next(heap);
186+
heap->keys[1] = _mi_heap_random_next(heap);
187+
heap->tld = tld;
188+
heap->tag = tag;
189+
}
190+
169191
// Initialize the thread local default heap, called from `mi_thread_init`
170192
static bool _mi_heap_init(void) {
171193
if (mi_heap_is_initialized(mi_get_default_heap())) return true;
@@ -185,24 +207,23 @@ static bool _mi_heap_init(void) {
185207
}
186208
// OS allocated so already zero initialized
187209
mi_tld_t* tld = &td->tld;
188-
mi_heap_t* heap = &td->heap;
189-
memcpy(heap, &_mi_heap_empty, sizeof(*heap));
190-
heap->thread_id = _mi_thread_id();
191-
_mi_random_init(&heap->random);
192-
heap->cookie = _mi_heap_random_next(heap) | 1;
193-
heap->keys[0] = _mi_heap_random_next(heap);
194-
heap->keys[1] = _mi_heap_random_next(heap);
195-
heap->tld = tld;
196-
tld->heap_backing = heap;
197-
tld->heaps = heap;
210+
_mi_heap_init_ex(&td->heap, &td->tld, mi_heap_tag_default);
211+
_mi_heap_init_ex(&td->heap_obj, &td->tld, mi_heap_tag_obj);
212+
_mi_heap_init_ex(&td->heap_gc, &td->tld, mi_heap_tag_gc);
213+
tld->heap_backing = &td->heap;
214+
tld->heaps = &td->heap;
215+
tld->heap_obj = &td->heap_obj;
216+
tld->heap_gc = &td->heap_gc;
198217
tld->segments.stats = &tld->stats;
199218
tld->segments.os = &tld->os;
200219
tld->os.stats = &tld->stats;
201-
_mi_heap_set_default_direct(heap);
220+
_mi_heap_set_default_direct(&td->heap);
202221
}
203222
return false;
204223
}
205224

225+
void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from);
226+
206227
// Free the thread local default heap (called from `mi_thread_done`)
207228
static bool _mi_heap_done(mi_heap_t* heap) {
208229
if (!mi_heap_is_initialized(heap)) return true;
@@ -230,6 +251,8 @@ static bool _mi_heap_done(mi_heap_t* heap) {
230251

231252
// collect if not the main thread
232253
if (heap != &_mi_heap_main) {
254+
mi_heap_absorb(heap, heap->tld->heap_obj);
255+
mi_heap_absorb(heap, heap->tld->heap_gc);
233256
_mi_heap_collect_abandon(heap);
234257
}
235258

@@ -461,6 +484,8 @@ void mi_process_init(void) mi_attr_noexcept {
461484
_mi_verbose_message("process init: 0x%zx\n", _mi_thread_id());
462485
_mi_os_init();
463486
mi_heap_main_init();
487+
_mi_heap_init_ex(&_mi_heap_main_obj, &tld_main, mi_heap_tag_obj);
488+
_mi_heap_init_ex(&_mi_heap_main_gc, &tld_main, mi_heap_tag_gc);
464489
#if (MI_DEBUG)
465490
_mi_verbose_message("debug level : %d\n", MI_DEBUG);
466491
#endif

‎Objects/mimalloc/page.c‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) {
234234
mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE);
235235
mi_assert_internal(_mi_page_segment(page)->page_kind != MI_PAGE_HUGE);
236236
mi_assert_internal(!page->is_reset);
237+
mi_assert_internal(page->tag == heap->tag);
237238
// TODO: push on full queue immediately if it is full?
238239
mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page));
239240
mi_page_queue_push(heap, pq, page);
@@ -613,6 +614,7 @@ static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi
613614
page->keys[1] = _mi_heap_random_next(heap);
614615
#endif
615616
page->is_zero = page->is_zero_init;
617+
page->tag = heap->tag;
616618

617619
mi_assert_internal(page->capacity == 0);
618620
mi_assert_internal(page->free == NULL);

‎Objects/mimalloc/segment.c‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,16 @@ static mi_segment_t* mi_abandoned_pop(void) {
989989
Abandon segment/page
990990
----------------------------------------------------------- */
991991

992+
extern mi_segment_t* _mi_segment_abandoned(void) {
993+
mi_tagged_segment_t ts = mi_atomic_read(&abandoned);
994+
mi_segment_t *segment = mi_tagged_segment_ptr(ts);
995+
return segment;
996+
}
997+
998+
extern mi_segment_t* _mi_segment_abandoned_visited(void) {
999+
return mi_atomic_read_ptr(mi_segment_t, &abandoned_visited);
1000+
}
1001+
9921002
static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) {
9931003
mi_assert_internal(segment->used == segment->abandoned);
9941004
mi_assert_internal(segment->used > 0);
@@ -1066,6 +1076,21 @@ static bool mi_segment_check_free(mi_segment_t* segment, size_t block_size, bool
10661076
return has_page;
10671077
}
10681078

1079+
static mi_heap_t* mi_heap_from_tag(mi_heap_t* base, unsigned int tag)
1080+
{
1081+
if (tag == base->tag) {
1082+
return base;
1083+
} else if (tag == mi_heap_tag_default) {
1084+
return base->tld->heap_backing;
1085+
} else if (tag == mi_heap_tag_obj) {
1086+
return base->tld->heap_obj;
1087+
} else if (tag == mi_heap_tag_gc) {
1088+
return base->tld->heap_gc;
1089+
} else {
1090+
_mi_error_message(EINVAL, "unknown page tag: %u\n", tag);
1091+
return NULL;
1092+
}
1093+
}
10691094

10701095
// Reclaim a segment; returns NULL if the segment was freed
10711096
// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full.
@@ -1083,6 +1108,7 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
10831108
for (size_t i = 0; i < segment->capacity; i++) {
10841109
mi_page_t* page = &segment->pages[i];
10851110
if (page->segment_in_use) {
1111+
mi_heap_t* target_heap = mi_heap_from_tag(heap, page->tag);
10861112
mi_assert_internal(!page->is_reset);
10871113
mi_assert_internal(page->is_committed);
10881114
mi_assert_internal(mi_page_not_in_queue(page, tld));
@@ -1092,7 +1118,7 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
10921118
mi_assert(page->next == NULL);
10931119
_mi_stat_decrease(&tld->stats->pages_abandoned, 1);
10941120
// set the heap again and allow heap thread delayed free again.
1095-
mi_page_set_heap(page, heap);
1121+
mi_page_set_heap(page, target_heap);
10961122
_mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set)
10971123
// TODO: should we not collect again given that we just collected in `check_free`?
10981124
_mi_page_free_collect(page, false); // ensure used count is up to date
@@ -1102,8 +1128,9 @@ static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap,
11021128
}
11031129
else {
11041130
// otherwise reclaim it into the heap
1105-
_mi_page_reclaim(heap, page);
1106-
if (requested_block_size == page->xblock_size && mi_page_has_any_available(page)) {
1131+
_mi_page_reclaim(target_heap, page);
1132+
if (heap == target_heap &&
1133+
requested_block_size == page->xblock_size && mi_page_has_any_available(page)) {
11071134
if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; }
11081135
}
11091136
}

0 commit comments

Comments
 (0)