Summary
Building with -DMI_SECURE=2 and assertions enabled (MI_DEBUG / no NDEBUG) aborts in mi_page_extend_free with:
mimalloc: assertion failed: .../src/page.c: mi_page_extend_free
assertion: "page->free == NULL"
This looks like an internal inconsistency between two #if MI_SECURE gates, not application heap corruption. Levels 0/1 never take the offending call; levels ≥3 compile out the assert. Observed on Windows and Linux with a recent v3.3.x-theap pin; the same structural clash is still present on current dev (classic mi_heap_t API).
Conflicting sites (theap / v3.3-style page.c)
After a successful mi_page_free_quick_collect (page already has free blocks), secure≥2 randomly extends:
// mi_find_free_page fast path
if (page != NULL && mi_page_free_quick_collect(page)) {
#if (MI_SECURE>=2) // extend half the time to increase randomness
if (page->capacity < page->reserved && ((_mi_theap_random_next(theap) & 1) == 1)) {
(void)mi_page_extend_free(theap, page);
}
#endif
return page;
}
But mi_page_extend_free still requires an empty free list when secure<3:
static bool mi_page_extend_free(...) {
#if (MI_SECURE<3)
mi_assert(page->free == NULL);
mi_assert(page->local_free == NULL);
if (page->free != NULL) return true;
#endif
...
}
So exactly MI_SECURE == 2: random-extend-on-hot-page is enabled, while page->free == NULL is still asserted. With asserts off, the early return true would skip the extend (silent no-op of the randomness feature); with asserts on, it aborts.
Upstream dev (classic heap)
Same pattern in mi_find_free_page (MI_SECURE>=2 random mi_page_extend_free) vs assert gated by MI_SECURE<=2 in mi_page_extend_free.
Suggested fixes (either)
- Do not call
mi_page_extend_free from the fast path when page->free != NULL / when the page is already immediately available; or
- Allow prepend at secure=2 and align the assert gate with the call site (e.g. only assert empty free list when secure<2), matching how secure≥3 already allows a non-empty free list during extend.
Reproduction notes
Any assert-enabled build with -DMI_SECURE=2 that allocates enough to hit the fast path coin-flip should trip this (allocation-heavy C++ workload on Windows clang-cl and Linux clang-22). Happy to try a minimal standalone repro if useful.
Summary
Building with
-DMI_SECURE=2and assertions enabled (MI_DEBUG/ noNDEBUG) aborts inmi_page_extend_freewith:This looks like an internal inconsistency between two
#if MI_SECUREgates, not application heap corruption. Levels 0/1 never take the offending call; levels ≥3 compile out the assert. Observed on Windows and Linux with a recent v3.3.x-theap pin; the same structural clash is still present on currentdev(classicmi_heap_tAPI).Conflicting sites (theap / v3.3-style
page.c)After a successful
mi_page_free_quick_collect(page already has free blocks), secure≥2 randomly extends:But
mi_page_extend_freestill requires an empty free list when secure<3:So exactly
MI_SECURE == 2: random-extend-on-hot-page is enabled, whilepage->free == NULLis still asserted. With asserts off, the earlyreturn truewould skip the extend (silent no-op of the randomness feature); with asserts on, it aborts.Upstream
dev(classic heap)Same pattern in
mi_find_free_page(MI_SECURE>=2randommi_page_extend_free) vs assert gated byMI_SECURE<=2inmi_page_extend_free.Suggested fixes (either)
mi_page_extend_freefrom the fast path whenpage->free != NULL/ when the page is already immediately available; orReproduction notes
Any assert-enabled build with
-DMI_SECURE=2that allocates enough to hit the fast path coin-flip should trip this (allocation-heavy C++ workload on Windows clang-cl and Linux clang-22). Happy to try a minimal standalone repro if useful.