From 34d172c9038a9b7cee78e42bb7768b93fbb8ed70 Mon Sep 17 00:00:00 2001 From: News Date: Tue, 22 Sep 2026 22:33:51 +0900 Subject: [PATCH 1/3] fix(windows): pair aligned delete with its allocator Signed-off-by: News --- src/foundation/mem_override_win.c | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/foundation/mem_override_win.c b/src/foundation/mem_override_win.c index e18b09d11..bb86a2e31 100644 --- a/src/foundation/mem_override_win.c +++ b/src/foundation/mem_override_win.c @@ -56,6 +56,7 @@ void *__real_malloc(size_t size); void *__real_calloc(size_t count, size_t size); void *__real_realloc(void *block, size_t size); void __real_free(void *block); +void __real__aligned_free(void *block); char *__real_strdup(const char *text); size_t __real__msize(void *block); /* Wrappers must call __real_* for anything that is itself wrapped: a plain @@ -155,9 +156,58 @@ void __wrap__aligned_free(void *block) { mi_free(block); return; } - __real_free(block); + /* A CRT aligned block has its own bookkeeping header. Plain free is not + * its matching deallocator. */ + __real__aligned_free(block); } +#if defined(CBM_MEM_GLOBAL_OVERRIDE) && CBM_MEM_GLOBAL_OVERRIDE +/* LLVM-MinGW's static C++ runtime calls _aligned_malloc through the linker's + * wrapper but its aligned operator delete can call the CRT import pointer + * __imp__aligned_free directly. --wrap=_aligned_free does not rewrite that + * import-pointer reference. Provide the six C++ ABI delete entry points so + * every aligned new/delete pair reaches the same owner-aware deallocator. */ +void cbm_cxx_aligned_delete(void *block) __asm__("_ZdlPvSt11align_val_t"); +void cbm_cxx_aligned_delete(void *block) { + __wrap__aligned_free(block); +} + +void cbm_cxx_aligned_delete_nothrow(void *block, + const void *tag) __asm__("_ZdlPvSt11align_val_tRKSt9nothrow_t"); +void cbm_cxx_aligned_delete_nothrow(void *block, const void *tag) { + (void)tag; + __wrap__aligned_free(block); +} + +void cbm_cxx_aligned_delete_sized(void *block, size_t size, + size_t alignment) __asm__("_ZdlPvySt11align_val_t"); +void cbm_cxx_aligned_delete_sized(void *block, size_t size, size_t alignment) { + (void)size; + (void)alignment; + __wrap__aligned_free(block); +} + +void cbm_cxx_aligned_array_delete(void *block) __asm__("_ZdaPvSt11align_val_t"); +void cbm_cxx_aligned_array_delete(void *block) { + __wrap__aligned_free(block); +} + +void cbm_cxx_aligned_array_delete_nothrow(void *block, const void *tag) __asm__( + "_ZdaPvSt11align_val_tRKSt9nothrow_t"); +void cbm_cxx_aligned_array_delete_nothrow(void *block, const void *tag) { + (void)tag; + __wrap__aligned_free(block); +} + +void cbm_cxx_aligned_array_delete_sized(void *block, size_t size, + size_t alignment) __asm__("_ZdaPvySt11align_val_t"); +void cbm_cxx_aligned_array_delete_sized(void *block, size_t size, size_t alignment) { + (void)size; + (void)alignment; + __wrap__aligned_free(block); +} +#endif + void *__wrap_realloc(void *block, size_t size) { if (!block) { void *fresh = mi_malloc(size); From 9dca8d2fede860605943ee8dbd57ba6b927ae41b Mon Sep 17 00:00:00 2001 From: News Date: Sat, 26 Sep 2026 20:38:47 +0900 Subject: [PATCH 2/3] fix(windows): limit allocator fix to CRT aligned blocks Signed-off-by: News --- src/foundation/mem_override_win.c | 47 ------------------------------- 1 file changed, 47 deletions(-) diff --git a/src/foundation/mem_override_win.c b/src/foundation/mem_override_win.c index bb86a2e31..a33b697d2 100644 --- a/src/foundation/mem_override_win.c +++ b/src/foundation/mem_override_win.c @@ -161,53 +161,6 @@ void __wrap__aligned_free(void *block) { __real__aligned_free(block); } -#if defined(CBM_MEM_GLOBAL_OVERRIDE) && CBM_MEM_GLOBAL_OVERRIDE -/* LLVM-MinGW's static C++ runtime calls _aligned_malloc through the linker's - * wrapper but its aligned operator delete can call the CRT import pointer - * __imp__aligned_free directly. --wrap=_aligned_free does not rewrite that - * import-pointer reference. Provide the six C++ ABI delete entry points so - * every aligned new/delete pair reaches the same owner-aware deallocator. */ -void cbm_cxx_aligned_delete(void *block) __asm__("_ZdlPvSt11align_val_t"); -void cbm_cxx_aligned_delete(void *block) { - __wrap__aligned_free(block); -} - -void cbm_cxx_aligned_delete_nothrow(void *block, - const void *tag) __asm__("_ZdlPvSt11align_val_tRKSt9nothrow_t"); -void cbm_cxx_aligned_delete_nothrow(void *block, const void *tag) { - (void)tag; - __wrap__aligned_free(block); -} - -void cbm_cxx_aligned_delete_sized(void *block, size_t size, - size_t alignment) __asm__("_ZdlPvySt11align_val_t"); -void cbm_cxx_aligned_delete_sized(void *block, size_t size, size_t alignment) { - (void)size; - (void)alignment; - __wrap__aligned_free(block); -} - -void cbm_cxx_aligned_array_delete(void *block) __asm__("_ZdaPvSt11align_val_t"); -void cbm_cxx_aligned_array_delete(void *block) { - __wrap__aligned_free(block); -} - -void cbm_cxx_aligned_array_delete_nothrow(void *block, const void *tag) __asm__( - "_ZdaPvSt11align_val_tRKSt9nothrow_t"); -void cbm_cxx_aligned_array_delete_nothrow(void *block, const void *tag) { - (void)tag; - __wrap__aligned_free(block); -} - -void cbm_cxx_aligned_array_delete_sized(void *block, size_t size, - size_t alignment) __asm__("_ZdaPvySt11align_val_t"); -void cbm_cxx_aligned_array_delete_sized(void *block, size_t size, size_t alignment) { - (void)size; - (void)alignment; - __wrap__aligned_free(block); -} -#endif - void *__wrap_realloc(void *block, size_t size) { if (!block) { void *fresh = mi_malloc(size); From 71ec2e6e225ebc3662026b58c720e9491f796ebe Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 20 Oct 2026 19:31:47 +0200 Subject: [PATCH 3/3] test(windows): pin the CRT aligned-block route of the aligned-free wrapper __wrap__aligned_free hands a block mimalloc did not make back to the CRT. A block from the CRT's _aligned_malloc starts past the CRT's bookkeeping header, so its matching deallocator is _aligned_free; plain free receives an interior pointer. This branch routes such blocks to __real__aligned_free. The new Windows-only mem test allocates through __real__aligned_malloc (the CRT's own allocator under --wrap), asserts mimalloc does not own the block, frees it through the wrapper by name, and checks errno and HeapValidate on the process heap. Freeing the interior pointer with plain free is a bad-free report under ASan; on an uninstrumented heap the free fails with errno set or the process terminates. Signed-off-by: Martin Vogel --- tests/test_mem.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_mem.c b/tests/test_mem.c index 11b3e2c2c..072476f6a 100644 --- a/tests/test_mem.c +++ b/tests/test_mem.c @@ -30,6 +30,7 @@ #define WIN32_LEAN_AND_MEAN #endif #include +#include #endif /* ASan detection — mimalloc MI_OVERRIDE=0 under ASan, mi_process_info @@ -353,6 +354,34 @@ TEST(mem_rss_reflects_external_resident_memory) { PASS(); } +#ifdef _WIN32 +/* A block the CRT's _aligned_malloc made starts past the CRT's bookkeeping + * header, so the aligned-free wrapper must return it through _aligned_free. + * Plain free receives an interior pointer: ASan reports a bad free, and the + * Windows heap either rejects the free (errno) or terminates the process. + * --wrap makes __real__aligned_malloc the CRT's own allocator, so the block is + * genuinely foreign to mimalloc. The wrapper is called by name: the routing + * inside it is what this pins, independent of which references the linker + * rewrites. */ +void *__real__aligned_malloc(size_t size, size_t alignment); +void __wrap__aligned_free(void *block); + +TEST(mem_win_aligned_free_returns_crt_block_to_crt) { + enum { CRT_BLOCK_SIZE = 256, CRT_BLOCK_ALIGNMENT = 64 }; + unsigned char *block = __real__aligned_malloc(CRT_BLOCK_SIZE, CRT_BLOCK_ALIGNMENT); + ASSERT_NOT_NULL(block); + /* Without a CRT-owned block the wrapper takes its mimalloc path instead. */ + ASSERT_FALSE(mi_is_in_heap_region(block)); + ASSERT_EQ((uintptr_t)block % CRT_BLOCK_ALIGNMENT, 0); + memset(block, 0xA5, CRT_BLOCK_SIZE); + errno = 0; + __wrap__aligned_free(block); + ASSERT_EQ(errno, 0); + ASSERT_TRUE(HeapValidate(GetProcessHeap(), 0, NULL)); + PASS(); +} +#endif + TEST(mem_collect_rss_still_positive) { cbm_mem_init(0.5); cbm_mem_collect(); @@ -1744,6 +1773,10 @@ SUITE(mem) { RUN_TEST(mem_rss_reflects_external_resident_memory); RUN_TEST(mem_collect_no_crash); RUN_TEST(mem_collect_rss_still_positive); +#ifdef _WIN32 + /* Windows allocator wrappers */ + RUN_TEST(mem_win_aligned_free_returns_crt_block_to_crt); +#endif /* Memory pressure simulation */ RUN_TEST(mem_progressive_alloc_rss_increases); RUN_TEST(mem_free_and_collect_no_crash);