Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/system/lib/mimalloc/src/page-map.c
14369 views
1
/*----------------------------------------------------------------------------
2
Copyright (c) 2023-2025, 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
8
#include "mimalloc.h"
9
#include "mimalloc/internal.h"
10
#include "bitmap.h"
11
12
static void mi_page_map_cannot_commit(void) {
13
_mi_warning_message("unable to commit the allocation page-map on-demand\n" );
14
}
15
16
#if MI_PAGE_MAP_FLAT
17
18
// The page-map contains a byte for each 64kb slice in the address space.
19
// For an address `a` where `ofs = _mi_page_map[a >> 16]`:
20
// 0 = unused
21
// 1 = the slice at `a & ~0xFFFF` is a mimalloc page.
22
// 1 < ofs <= 127 = the slice is part of a page, starting at `(((a>>16) - ofs - 1) << 16)`.
23
//
24
// 1 byte per slice => 1 TiB address space needs a 2^14 * 2^16 = 16 MiB page map.
25
// A full 256 TiB address space (48 bit) needs a 4 GiB page map.
26
// A full 4 GiB address space (32 bit) needs only a 64 KiB page map.
27
28
mi_decl_cache_align uint8_t* _mi_page_map = NULL;
29
static void* mi_page_map_max_address = NULL;
30
static mi_memid_t mi_page_map_memid;
31
32
#define MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT MI_ARENA_SLICE_SIZE
33
static mi_bitmap_t* mi_page_map_commit; // one bit per committed 64 KiB entries
34
35
mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count);
36
37
bool _mi_page_map_init(void) {
38
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);
39
if (vbits == 0) {
40
vbits = _mi_os_virtual_address_bits();
41
#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB
42
if (vbits >= 48) { vbits = 47; }
43
#endif
44
}
45
if (vbits < MI_ARENA_SLICE_SHIFT) {
46
vbits = MI_ARENA_SLICE_SHIFT;
47
}
48
49
// Allocate the page map and commit bits
50
mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));
51
const size_t page_map_size = (MI_ZU(1) << (vbits - MI_ARENA_SLICE_SHIFT));
52
const bool commit = (page_map_size <= 1*MI_MiB || mi_option_is_enabled(mi_option_pagemap_commit)); // _mi_os_has_overcommit(); // commit on-access on Linux systems?
53
const size_t commit_bits = _mi_divide_up(page_map_size, MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);
54
const size_t bitmap_size = (commit ? 0 : mi_bitmap_size(commit_bits, NULL));
55
const size_t reserve_size = bitmap_size + page_map_size;
56
uint8_t* const base = (uint8_t*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);
57
if (base==NULL) {
58
_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);
59
return false;
60
}
61
if (mi_page_map_memid.initially_committed && !mi_page_map_memid.initially_zero) {
62
_mi_warning_message("internal: the page map was committed but not zero initialized!\n");
63
_mi_memzero_aligned(base, reserve_size);
64
}
65
if (bitmap_size > 0) {
66
mi_page_map_commit = (mi_bitmap_t*)base;
67
if (!_mi_os_commit(mi_page_map_commit, bitmap_size, NULL)) {
68
mi_page_map_cannot_commit();
69
return false;
70
}
71
mi_bitmap_init(mi_page_map_commit, commit_bits, true);
72
}
73
_mi_page_map = base + bitmap_size;
74
75
// commit the first part so NULL pointers get resolved without an access violation
76
if (!commit) {
77
if (!mi_page_map_ensure_committed(0, 1)) {
78
mi_page_map_cannot_commit();
79
return false;
80
}
81
}
82
_mi_page_map[0] = 1; // so _mi_ptr_page(NULL) == NULL
83
mi_assert_internal(_mi_ptr_page(NULL)==NULL);
84
return true;
85
}
86
87
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
88
mi_assert_internal(subproc != NULL);
89
mi_assert_internal(_mi_page_map != NULL);
90
if (_mi_page_map == NULL) return;
91
_mi_os_free_ex(mi_page_map_memid.mem.os.base, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
92
_mi_page_map = NULL;
93
mi_page_map_commit = NULL;
94
mi_page_map_max_address = NULL;
95
mi_page_map_memid = _mi_memid_none();
96
}
97
98
99
static bool mi_page_map_ensure_committed(size_t idx, size_t slice_count) {
100
// is the page map area that contains the page address committed?
101
// we always set the commit bits so we can track what ranges are in-use.
102
// we only actually commit if the map wasn't committed fully already.
103
if (mi_page_map_commit != NULL) {
104
const size_t commit_idx = idx / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
105
const size_t commit_idx_hi = (idx + slice_count - 1) / MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
106
for (size_t i = commit_idx; i <= commit_idx_hi; i++) { // per bit to avoid crossing over bitmap chunks
107
if (mi_bitmap_is_clear(mi_page_map_commit, i)) {
108
// this may race, in which case we do multiple commits (which is ok)
109
bool is_zero;
110
uint8_t* const start = _mi_page_map + (i * MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT);
111
const size_t size = MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT;
112
if (!_mi_os_commit(start, size, &is_zero)) {
113
mi_page_map_cannot_commit();
114
return false;
115
}
116
if (!is_zero && !mi_page_map_memid.initially_zero) { _mi_memzero(start, size); }
117
mi_bitmap_set(mi_page_map_commit, i);
118
}
119
}
120
}
121
#if MI_DEBUG > 0
122
_mi_page_map[idx] = 0;
123
_mi_page_map[idx+slice_count-1] = 0;
124
#endif
125
return true;
126
}
127
128
129
static size_t mi_page_map_get_idx(mi_page_t* page, uint8_t** page_start, size_t* slice_count) {
130
size_t page_size;
131
*page_start = mi_page_area(page, &page_size);
132
if (page_size > MI_LARGE_PAGE_SIZE) { page_size = MI_LARGE_PAGE_SIZE - MI_ARENA_SLICE_SIZE; } // furthest interior pointer
133
*slice_count = mi_slice_count_of_size(page_size) + ((*page_start - mi_page_slice_start(page))/MI_ARENA_SLICE_SIZE); // add for large aligned blocks
134
return _mi_page_map_index(page);
135
}
136
137
bool _mi_page_map_register(mi_page_t* page) {
138
mi_assert_internal(page != NULL);
139
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
140
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
141
if mi_unlikely(_mi_page_map == NULL) {
142
if (!_mi_page_map_init()) return false;
143
}
144
mi_assert(_mi_page_map!=NULL);
145
uint8_t* page_start;
146
size_t slice_count;
147
const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);
148
149
if (!mi_page_map_ensure_committed(idx, slice_count)) {
150
return false;
151
}
152
153
// set the offsets
154
for (size_t i = 0; i < slice_count; i++) {
155
mi_assert_internal(i < 128);
156
_mi_page_map[idx + i] = (uint8_t)(i+1);
157
}
158
return true;
159
}
160
161
void _mi_page_map_unregister(mi_page_t* page) {
162
mi_assert_internal(_mi_page_map != NULL);
163
// get index and count
164
uint8_t* page_start;
165
size_t slice_count;
166
const size_t idx = mi_page_map_get_idx(page, &page_start, &slice_count);
167
// unset the offsets
168
_mi_memzero(_mi_page_map + idx, slice_count);
169
}
170
171
void _mi_page_map_unregister_range(void* start, size_t size) {
172
const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);
173
const uintptr_t index = _mi_page_map_index(start);
174
// todo: scan the commit bits and clear only those ranges?
175
if (!mi_page_map_ensure_committed(index, slice_count)) { // we commit the range in total;
176
return;
177
}
178
_mi_memzero(&_mi_page_map[index], slice_count);
179
}
180
181
182
mi_page_t* _mi_safe_ptr_page(const void* p) {
183
if mi_unlikely(p >= mi_page_map_max_address) return NULL;
184
const uintptr_t idx = _mi_page_map_index(p);
185
if mi_unlikely(mi_page_map_commit != NULL && !mi_bitmap_is_set(mi_page_map_commit, idx/MI_PAGE_MAP_ENTRIES_PER_COMMIT_BIT)) return NULL;
186
const uintptr_t ofs = _mi_page_map[idx];
187
if mi_unlikely(ofs == 0) return NULL;
188
return (mi_page_t*)((((uintptr_t)p >> MI_ARENA_SLICE_SHIFT) - ofs + 1) << MI_ARENA_SLICE_SHIFT);
189
}
190
191
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {
192
return (_mi_safe_ptr_page(p) != NULL);
193
}
194
195
#else
196
197
// A 2-level page map
198
#define MI_PAGE_MAP_SUB_SIZE (MI_PAGE_MAP_SUB_COUNT * sizeof(mi_page_t*))
199
#define MI_PAGE_MAP_ENTRIES_PER_CBIT (MI_PAGE_MAP_COUNT < MI_BFIELD_BITS ? 1 : (MI_PAGE_MAP_COUNT / MI_BFIELD_BITS))
200
201
mi_decl_cache_align _Atomic(mi_submap_t)* _mi_page_map;
202
static size_t mi_page_map_count;
203
static void* mi_page_map_max_address;
204
static mi_memid_t mi_page_map_memid;
205
static mi_lock_t mi_page_map_lock;
206
207
// divide the main map in 64 (`MI_BFIELD_BITS`) parts commit those parts on demand
208
static _Atomic(mi_bfield_t) mi_page_map_commit;
209
210
mi_decl_nodiscard static inline bool mi_page_map_is_committed(size_t idx, size_t* pbit_idx) {
211
mi_bfield_t commit = mi_atomic_load_relaxed(&mi_page_map_commit);
212
const size_t bit_idx = idx/MI_PAGE_MAP_ENTRIES_PER_CBIT;
213
mi_assert_internal(bit_idx < MI_BFIELD_BITS);
214
if (pbit_idx != NULL) { *pbit_idx = bit_idx; }
215
return ((commit & (MI_ZU(1) << bit_idx)) != 0);
216
}
217
218
mi_decl_nodiscard static bool mi_page_map_ensure_committed(size_t idx, mi_submap_t* submap) {
219
mi_assert_internal(submap!=NULL && *submap==NULL);
220
size_t bit_idx;
221
if mi_unlikely(!mi_page_map_is_committed(idx, &bit_idx)) {
222
uint8_t* start = (uint8_t*)&_mi_page_map[bit_idx * MI_PAGE_MAP_ENTRIES_PER_CBIT];
223
if (!_mi_os_commit(start, MI_PAGE_MAP_ENTRIES_PER_CBIT * sizeof(mi_submap_t), NULL)) {
224
mi_page_map_cannot_commit();
225
return false;
226
}
227
mi_atomic_or_acq_rel(&mi_page_map_commit, MI_ZU(1) << bit_idx);
228
}
229
*submap = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // acquire _mi_page_map_at(idx);
230
return true;
231
}
232
233
// initialize the page map
234
bool _mi_page_map_init(void) {
235
size_t vbits = (size_t)mi_option_get_clamp(mi_option_max_vabits, 0, MI_SIZE_BITS);
236
if (vbits == 0) {
237
vbits = _mi_os_virtual_address_bits();
238
#if MI_ARCH_X64 // canonical address is limited to the first 128 TiB
239
if (vbits >= 48) { vbits = 47; }
240
#endif
241
}
242
if (vbits < MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT) {
243
vbits = MI_PAGE_MAP_SUB_SHIFT + MI_ARENA_SLICE_SHIFT;
244
}
245
246
// Allocate the page map and commit bits
247
mi_assert(MI_MAX_VABITS >= vbits);
248
mi_page_map_max_address = (void*)(vbits >= MI_SIZE_BITS ? (SIZE_MAX - MI_ARENA_SLICE_SIZE + 1) : (MI_PU(1) << vbits));
249
mi_page_map_count = (MI_ZU(1) << (vbits - MI_PAGE_MAP_SUB_SHIFT - MI_ARENA_SLICE_SHIFT));
250
mi_assert(mi_page_map_count <= MI_PAGE_MAP_COUNT);
251
const size_t os_page_size = _mi_os_page_size();
252
const size_t page_map_size = _mi_align_up( mi_page_map_count * sizeof(mi_page_t**), os_page_size);
253
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
254
const size_t reserve_size = page_map_size + submap_size;
255
#if MI_SECURE
256
const bool commit = true; // the whole page map is valid and we can reliably check any pointer
257
#else
258
const bool commit = page_map_size <= 64*MI_KiB ||
259
mi_option_is_enabled(mi_option_pagemap_commit) || _mi_os_has_overcommit();
260
#endif
261
_mi_page_map = (_Atomic(mi_page_t**)*)_mi_os_alloc_aligned(reserve_size, 1, commit, true /* allow large */, &mi_page_map_memid);
262
if (_mi_page_map==NULL) {
263
_mi_error_message(ENOMEM, "unable to reserve virtual memory for the page map (%zu KiB)\n", page_map_size / MI_KiB);
264
return false;
265
}
266
if (mi_page_map_memid.initially_committed && !mi_page_map_memid.initially_zero) {
267
_mi_warning_message("internal: the page map was committed but not zero initialized!\n");
268
_mi_memzero_aligned(_mi_page_map, page_map_size);
269
}
270
mi_atomic_store_release(&mi_page_map_commit, (mi_page_map_memid.initially_committed ? ~MI_ZU(0) : MI_ZU(0)));
271
272
// ensure there is a submap for the NULL address
273
mi_page_t** const sub0 = (mi_page_t**)((uint8_t*)_mi_page_map + page_map_size); // we reserved a submap part at the end already
274
if (!mi_page_map_memid.initially_committed) {
275
if (!_mi_os_commit(sub0, submap_size, NULL)) { // commit full submap (issue #1087)
276
mi_page_map_cannot_commit();
277
return false;
278
}
279
}
280
if (!mi_page_map_memid.initially_zero) { // initialize low addresses with NULL
281
_mi_memzero_aligned(sub0, submap_size);
282
}
283
mi_submap_t nullsub = NULL;
284
if (!mi_page_map_ensure_committed(0,&nullsub)) {
285
mi_page_map_cannot_commit();
286
return false;
287
}
288
mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[0], sub0);
289
mi_lock_init(&mi_page_map_lock); // initialize late in case the lock init causes allocation
290
291
mi_assert_internal(_mi_ptr_page(NULL)==NULL);
292
return true;
293
}
294
295
296
void _mi_page_map_unsafe_destroy(mi_subproc_t* subproc) {
297
mi_assert_internal(subproc != NULL);
298
mi_assert_internal(_mi_page_map != NULL);
299
if (_mi_page_map == NULL) return;
300
mi_lock_done(&mi_page_map_lock);
301
for (size_t idx = 1; idx < mi_page_map_count; idx++) { // skip entry 0 (as we allocate that submap at the end of the page_map)
302
// free all sub-maps
303
if (mi_page_map_is_committed(idx, NULL)) {
304
mi_submap_t sub = _mi_page_map_at(idx);
305
if (sub != NULL) {
306
mi_memid_t memid = _mi_memid_create_os(sub, MI_PAGE_MAP_SUB_SIZE, true, false, false);
307
_mi_os_free_ex(memid.mem.os.base, memid.mem.os.size, true, memid, subproc);
308
mi_atomic_store_ptr_release(mi_page_t*, &_mi_page_map[idx], NULL);
309
}
310
}
311
}
312
_mi_os_free_ex(_mi_page_map, mi_page_map_memid.mem.os.size, true, mi_page_map_memid, subproc);
313
_mi_page_map = NULL;
314
mi_page_map_count = 0;
315
mi_page_map_memid = _mi_memid_none();
316
mi_page_map_max_address = NULL;
317
mi_atomic_store_release(&mi_page_map_commit, (mi_bfield_t)0);
318
}
319
320
321
mi_decl_nodiscard static bool mi_page_map_ensure_submap_at(size_t idx, mi_submap_t* submap) {
322
mi_assert_internal(submap!=NULL && *submap==NULL);
323
mi_submap_t sub = NULL;
324
if (!mi_page_map_ensure_committed(idx, &sub)) {
325
return false;
326
}
327
if mi_unlikely(sub == NULL) {
328
// sub map not yet allocated, alloc now
329
mi_lock(&mi_page_map_lock)
330
{
331
sub = mi_atomic_load_ptr_acquire(mi_page_t*, &_mi_page_map[idx]); // reload
332
if (sub==NULL) // not yet allocated by another thread?
333
{
334
mi_memid_t memid;
335
const size_t submap_size = MI_PAGE_MAP_SUB_SIZE;
336
sub = (mi_submap_t)_mi_os_zalloc(submap_size, &memid);
337
if (sub==NULL) {
338
_mi_warning_message("internal error: unable to extend the page map\n");
339
}
340
else {
341
mi_submap_t expect = NULL;
342
if (!mi_atomic_cas_ptr_strong_acq_rel(mi_page_t*, &_mi_page_map[idx], &expect, sub)) {
343
// another thread already allocated it.. free and continue
344
_mi_os_free(sub, submap_size, memid);
345
sub = expect;
346
}
347
}
348
}
349
}
350
if (sub==NULL) return false; // unable to allocate the submap..
351
}
352
mi_assert_internal(sub!=NULL);
353
*submap = sub;
354
return true;
355
}
356
357
static bool mi_page_map_set_range_prim(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
358
// is the page map area that contains the page address committed?
359
while (slice_count > 0) {
360
mi_submap_t sub = NULL;
361
if (!mi_page_map_ensure_submap_at(idx, &sub)) {
362
return false;
363
};
364
mi_assert_internal(sub!=NULL);
365
// set the offsets for the page
366
while (slice_count > 0 && sub_idx < MI_PAGE_MAP_SUB_COUNT) {
367
sub[sub_idx] = page;
368
slice_count--;
369
sub_idx++;
370
}
371
idx++; // potentially wrap around to the next idx
372
sub_idx = 0;
373
}
374
return true;
375
}
376
377
static bool mi_page_map_set_range(mi_page_t* page, size_t idx, size_t sub_idx, size_t slice_count) {
378
if mi_unlikely(!mi_page_map_set_range_prim(page,idx,sub_idx,slice_count)) {
379
// failed to commit, call again to reset the page pointer if needed
380
if (page!=NULL) {
381
mi_page_map_set_range_prim(NULL,idx,sub_idx,slice_count);
382
}
383
return false;
384
}
385
return true;
386
}
387
388
static size_t mi_page_map_get_idx(mi_page_t* page, size_t* sub_idx, size_t* slice_count) {
389
size_t page_size;
390
uint8_t* page_start = mi_page_area(page, &page_size);
391
if (page_size > MI_LARGE_PAGE_SIZE) { page_size = MI_LARGE_PAGE_SIZE - MI_ARENA_SLICE_SIZE; } // furthest interior pointer
392
*slice_count = mi_slice_count_of_size(page_size) + ((page_start - mi_page_slice_start(page))/MI_ARENA_SLICE_SIZE); // add for large aligned blocks
393
return _mi_page_map_index(page_start, sub_idx);
394
}
395
396
bool _mi_page_map_register(mi_page_t* page) {
397
mi_assert_internal(page != NULL);
398
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
399
mi_assert_internal(_mi_page_map != NULL); // should be initialized before multi-thread access!
400
if mi_unlikely(_mi_page_map == NULL) {
401
if (!_mi_page_map_init()) return false;
402
}
403
mi_assert(_mi_page_map!=NULL);
404
size_t slice_count;
405
size_t sub_idx;
406
const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);
407
return mi_page_map_set_range(page, idx, sub_idx, slice_count);
408
}
409
410
void _mi_page_map_unregister(mi_page_t* page) {
411
mi_assert_internal(_mi_page_map != NULL);
412
mi_assert_internal(page != NULL);
413
mi_assert_internal(_mi_is_aligned(mi_page_slice_start(page), MI_PAGE_ALIGN));
414
if mi_unlikely(_mi_page_map == NULL) return;
415
// get index and count
416
size_t slice_count;
417
size_t sub_idx;
418
const size_t idx = mi_page_map_get_idx(page, &sub_idx, &slice_count);
419
// unset the offsets
420
mi_page_map_set_range(NULL, idx, sub_idx, slice_count);
421
}
422
423
void _mi_page_map_unregister_range(void* start, size_t size) {
424
if mi_unlikely(_mi_page_map == NULL) return;
425
const size_t slice_count = _mi_divide_up(size, MI_ARENA_SLICE_SIZE);
426
size_t sub_idx;
427
const uintptr_t idx = _mi_page_map_index(start, &sub_idx);
428
mi_page_map_set_range(NULL, idx, sub_idx, slice_count); // todo: avoid committing if not already committed?
429
}
430
431
// Return NULL for invalid pointers
432
mi_page_t* _mi_safe_ptr_page(const void* p) {
433
if (p==NULL) return NULL;
434
if mi_unlikely(p >= mi_page_map_max_address) return NULL;
435
size_t sub_idx;
436
const size_t idx = _mi_page_map_index(p,&sub_idx);
437
if mi_unlikely(!mi_page_map_is_committed(idx,NULL)) return NULL;
438
mi_page_t** const sub = _mi_page_map[idx];
439
if mi_unlikely(sub==NULL) return NULL;
440
return sub[sub_idx];
441
}
442
443
mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept {
444
return (_mi_safe_ptr_page(p) != NULL);
445
}
446
447
#endif
448
449