| /* ---------------------------------------------------------------------------- | |
| Copyright (c) 2018-2026, Microsoft Research, Daan Leijen | |
| This is free software; you can redistribute it and/or modify it under the | |
| terms of the MIT license. A copy of the license can be found in the file | |
| "LICENSE" at the root of this distribution. | |
| -----------------------------------------------------------------------------*/ | |
| #if defined(__GNUC__) && !defined(__clang__) | |
| #pragma GCC diagnostic ignored "-Walloc-size-larger-than=" | |
| #endif | |
| /* | |
| Testing allocators is difficult as bugs may only surface after particular | |
| allocation patterns. The main approach to testing _mimalloc_ is therefore | |
| to have extensive internal invariant checking (see `page_is_valid` in `alloc/page.c` | |
| for example), which is enabled in debug mode with `-DMI_DEBUG_FULL=ON`. | |
| The main testing is then to run `mimalloc-bench` [1] using full invariant checking | |
| to catch any potential problems over a wide range of intensive allocation bench | |
| marks. | |
| However, this does not test well for the entire API surface. In this test file | |
| we therefore test the API over various inputs. Please add more tests :-) | |
| [1] https://github.com/daanx/mimalloc-bench | |
| */ | |
| #include <assert.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <errno.h> | |
| #ifdef __cplusplus | |
| #include <new> | |
| #include <vector> | |
| #endif | |
| #include "mimalloc.h" | |
| // #include "mimalloc/internal.h" | |
| #include "mimalloc/types.h" // for MI_DEBUG and MI_PAGE_MAX_OVERALLOC_ALIGN | |
| #include "testhelper.h" | |
| // --------------------------------------------------------------------------- | |
| // Test functions | |
| // --------------------------------------------------------------------------- | |
| bool test_theap1(void); | |
| bool test_theap2(void); | |
| bool test_theap_arena_destroy(void); | |
| bool test_theap_arena_delete(void); | |
| bool test_stl_allocator1(void); | |
| bool test_stl_allocator2(void); | |
| bool test_stl_theap_allocator1(void); | |
| bool test_stl_theap_allocator2(void); | |
| bool test_stl_theap_allocator3(void); | |
| bool test_stl_theap_allocator4(void); | |
| static bool test_zero_aligned_first(void); | |
| static bool test_aligned_all(void); | |
| static bool test_heap_destroy_xthread_free(void); | |
| static bool test_heap_destroy_stale_theap(void); | |
| #ifdef __cplusplus | |
| static bool test_new_first(void); | |
| #endif | |
| #if !MI_QSBR | |
| static void mi_cdecl qsbr_record_error(int err, void* arg) { | |
| *(int*)arg = err; | |
| } | |
| #endif | |
| static bool mem_has_vals(const uint8_t* p, size_t size, uint8_t val) { | |
| if (p==NULL) return false; | |
| for (size_t i = 0; i < size; ++i) { | |
| if (p[i] != val) return false; | |
| } | |
| return true; | |
| } | |
| static bool mem_is_zero(const void* p, size_t size) { | |
| return mem_has_vals((const uint8_t*)p,size,0); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Main testing | |
| // --------------------------------------------------------------------------- | |
| int main(void) { | |
| mi_option_disable(mi_option_verbose); | |
| // --------------------------------------------------- | |
| // Malloc | |
| // --------------------------------------------------- | |
| CHECK_BODY("malloc-zero") { | |
| void* p = mi_malloc(0); | |
| result = (p != NULL); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("malloc-nomem1") { | |
| result = (mi_malloc((size_t)PTRDIFF_MAX + (size_t)1) == NULL); | |
| }; | |
| CHECK_BODY("malloc-free-null") { | |
| mi_free(NULL); | |
| }; | |
| #if MI_INTPTR_BITS > 32 | |
| CHECK_BODY("malloc-free-invalid-low") { | |
| mi_cfree((void*)(MI_ZU(0x0000000003990080))); // issue #1087 | |
| }; | |
| #endif | |
| CHECK_BODY("calloc-overflow") { | |
| // use (size_t)&mi_calloc to get some number without triggering compiler warnings | |
| result = (mi_calloc((size_t)&mi_calloc | 1024,SIZE_MAX/1000) == NULL); // `| 1024` as on wasm function pointers are small indices | |
| }; | |
| CHECK_BODY("malloc-large") { // see PR #544. | |
| void* p = mi_malloc(67108872); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("malloc-size") { | |
| void* p = mi_malloc(42); | |
| result = (mi_malloc_size(p) >= 42 && mi_malloc_usable_size(p) >= 42); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("calloc0") { | |
| void* p = mi_calloc(0,1000); | |
| const size_t usable = mi_usable_size(p); | |
| result = (usable <= 16); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("mi_urealloc_invalid") { | |
| void* p = mi_malloc(64); | |
| size_t pre, post; | |
| void* q = mi_urealloc((char*)p + 3, 40, &pre, &post); | |
| mi_free(p); | |
| result = (q==NULL || q==(uint8_t*)p+3); | |
| } | |
| // --------------------------------------------------- | |
| // Extended | |
| // --------------------------------------------------- | |
| CHECK_BODY("posix_memalign1") { | |
| void* p = &p; | |
| int err = mi_posix_memalign(&p, sizeof(void*), 32); | |
| result = ((err==0 && (uintptr_t)p % sizeof(void*) == 0) || p==&p); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("posix_memalign_no_align") { | |
| void* p = &p; | |
| int err = mi_posix_memalign(&p, 3, 32); | |
| result = (err==EINVAL && p==&p); | |
| }; | |
| CHECK_BODY("posix_memalign_zero") { | |
| void* p = &p; | |
| int err = mi_posix_memalign(&p, sizeof(void*), 0); | |
| mi_free(p); | |
| result = (err==0); | |
| }; | |
| CHECK_BODY("posix_memalign_nopow2") { | |
| void* p = &p; | |
| int err = mi_posix_memalign(&p, 3*sizeof(void*), 32); | |
| result = (err==EINVAL && p==&p); | |
| }; | |
| CHECK_BODY("posix_memalign_nomem") { | |
| void* p = &p; | |
| int err = mi_posix_memalign(&p, sizeof(void*), SIZE_MAX); | |
| result = (err==ENOMEM && p==&p); | |
| }; | |
| // --------------------------------------------------- | |
| // Aligned API | |
| // --------------------------------------------------- | |
| CHECK_BODY("malloc-aligned1") { | |
| void* p = mi_malloc_aligned(32,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p); | |
| }; | |
| CHECK_BODY("malloc-aligned2") { | |
| void* p = mi_malloc_aligned(48,32); result = (p != NULL && (uintptr_t)(p) % 32 == 0); mi_free(p); | |
| }; | |
| CHECK_BODY("malloc-aligned3") { | |
| void* p1 = mi_malloc_aligned(48,32); bool result1 = (p1 != NULL && (uintptr_t)(p1) % 32 == 0); | |
| void* p2 = mi_malloc_aligned(48,32); bool result2 = (p2 != NULL && (uintptr_t)(p2) % 32 == 0); | |
| mi_free(p2); | |
| mi_free(p1); | |
| result = (result1&&result2); | |
| }; | |
| CHECK_BODY("malloc-aligned4") { | |
| void* p; | |
| bool ok = true; | |
| for (int i = 0; i < 8 && ok; i++) { | |
| p = mi_malloc_aligned(8, 16); | |
| ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p); | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("free-size-aligned-overaligned") { // issue #1400 | |
| const size_t size = 8; | |
| const size_t alignment = 16 * 1024; | |
| void* p[200]; | |
| bool ok = true; | |
| for (size_t i = 0; i < 200; i++) { | |
| p[i] = mi_malloc_aligned(size, alignment); | |
| ok = ok && (p[i] != NULL) && ((uintptr_t)p[i] % alignment == 0); | |
| } | |
| for (size_t i = 0; i < 200; i++) { | |
| mi_free_size_aligned(p[i], size, alignment); | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("malloc-aligned5") { | |
| void* p = mi_malloc_aligned(4097,4096); | |
| size_t usable = mi_usable_size(p); | |
| result = (usable >= 4097 && usable < 16000); | |
| fprintf(stderr, "malloc_aligned5: usable size: %zi. ", usable); | |
| mi_free(p); | |
| }; | |
| /* | |
| CHECK_BODY("malloc-aligned6") { | |
| bool ok = true; | |
| for (size_t align = 1; align <= MI_PAGE_MAX_OVERALLOC_ALIGN && ok; align *= 2) { | |
| void* ps[8]; | |
| for (int i = 0; i < 8 && ok; i++) { | |
| ps[i] = mi_malloc_aligned(align*13 // size | |
| , align); | |
| if (ps[i] == NULL || (uintptr_t)(ps[i]) % align != 0) { | |
| ok = false; | |
| } | |
| } | |
| for (int i = 0; i < 8 && ok; i++) { | |
| mi_free(ps[i]); | |
| } | |
| } | |
| result = ok; | |
| }; | |
| */ | |
| CHECK_BODY("malloc-aligned7") { | |
| void* p = mi_malloc_aligned(1024,MI_PAGE_MAX_OVERALLOC_ALIGN); | |
| mi_free(p); | |
| result = ((uintptr_t)p % MI_PAGE_MAX_OVERALLOC_ALIGN) == 0; | |
| }; | |
| CHECK_BODY("malloc-aligned8") { | |
| bool ok = true; | |
| for (int i = 0; i < 5 && ok; i++) { | |
| int n = (1 << i); | |
| void* p = mi_malloc_aligned(1024, n * MI_PAGE_MAX_OVERALLOC_ALIGN); | |
| ok = ((uintptr_t)p % (n*MI_PAGE_MAX_OVERALLOC_ALIGN)) == 0; | |
| mi_free(p); | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("malloc-aligned9") { // test large alignments | |
| bool ok = true; | |
| void* p[8]; | |
| const int max_align_shift = | |
| #if SIZE_MAX > UINT32_MAX | |
| 28 /* up to 64 MiB alignment */ | |
| #else | |
| 20 | |
| #endif | |
| ; | |
| size_t sizes[8] = { 8, 512, 1024 * 1024, MI_PAGE_MAX_OVERALLOC_ALIGN, MI_PAGE_MAX_OVERALLOC_ALIGN + 1, 2 * MI_PAGE_MAX_OVERALLOC_ALIGN, 8 * MI_PAGE_MAX_OVERALLOC_ALIGN, 0 }; | |
| for (int i = 0; i < max_align_shift && ok; i++) { | |
| int align = (1 << i); | |
| for (int j = 0; j < 8 && ok; j++) { | |
| p[j] = mi_zalloc_aligned(sizes[j], align); | |
| ok = ((uintptr_t)p[j] % align) == 0; | |
| } | |
| for (int j = 0; j < 8; j++) { | |
| mi_free(p[j]); | |
| } | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("malloc-aligned9a") { // test large alignments | |
| void* p = mi_zalloc_aligned(1024 * 1024, 2); | |
| mi_free(p); | |
| p = mi_zalloc_aligned(1024 * 1024, 2); | |
| mi_free(p); | |
| result = true; | |
| }; | |
| CHECK_BODY("malloc-aligned10") { | |
| bool ok = true; | |
| void* p[10+1]; | |
| int align; | |
| int j; | |
| for(j = 0, align = 1; j <= 10 && ok; align *= 2, j++ ) { | |
| p[j] = mi_malloc_aligned(43 + align, align); | |
| ok = ((uintptr_t)p[j] % align) == 0; | |
| } | |
| for ( ; j > 0; j--) { | |
| mi_free(p[j-1]); | |
| } | |
| result = ok; | |
| } | |
| //CHECK_BODY("malloc_aligned11") { | |
| // mi_theap_t* theap = mi_theap_new(); | |
| // void* p = mi_theap_malloc_aligned(theap, 33554426, 8); | |
| // result = mi_theap_contains_block(theap, p); | |
| // mi_theap_destroy(theap); | |
| //} | |
| CHECK_BODY("mimalloc-aligned12") { | |
| void* p = mi_malloc_aligned(0x100, 0x100); | |
| result = (((uintptr_t)p % 0x100) == 0); // #602 | |
| mi_free(p); | |
| } | |
| CHECK_BODY("mimalloc-aligned13") { | |
| bool ok = true; | |
| for( size_t size = 1; size <= (MI_SMALL_SIZE_MAX * 2) && ok; size++ ) { | |
| for(size_t align = 1; align <= size && ok; align *= 2 ) { | |
| void* p[10]; | |
| for(int i = 0; i < 10 && ok; i++) { | |
| p[i] = mi_malloc_aligned(size,align);; | |
| ok = (p[i] != NULL && ((uintptr_t)(p[i]) % align) == 0); | |
| } | |
| for(int i = 0; i < 10 && ok; i++) { | |
| mi_free(p[i]); | |
| } | |
| } | |
| } | |
| result = ok; | |
| } | |
| CHECK_BODY("mimalloc-size-aligned14") { | |
| bool ok = true; | |
| for( size_t size = 1; size <= (MI_SMALL_SIZE_MAX * 2) && ok; size++ ) { | |
| for(size_t align = 1; align <= 16*size && ok; align *= 2) { | |
| void* p[10]; | |
| for(int i = 0; i < 10 && ok; i++) { | |
| p[i] = mi_malloc_aligned(size,align);; | |
| ok = (p[i] != NULL && ((uintptr_t)(p[i]) % align) == 0); | |
| } | |
| for(int i = 0; i < 10 && ok; i++) { | |
| mi_free_size_aligned(p[i],size,align); | |
| } | |
| } | |
| } | |
| result = ok; | |
| } | |
| CHECK_BODY("mimalloc-aligned15") { | |
| result = test_aligned_all(); | |
| } | |
| CHECK_BODY("malloc-aligned-at1") { | |
| void* p = mi_malloc_aligned_at(48,32,0); result = (p != NULL && ((uintptr_t)(p) + 0) % 32 == 0); mi_free(p); | |
| }; | |
| CHECK_BODY("malloc-aligned-at2") { | |
| void* p = mi_malloc_aligned_at(50,32,8); result = (p != NULL && ((uintptr_t)(p) + 8) % 32 == 0); mi_free(p); | |
| }; | |
| CHECK_BODY("memalign1") { | |
| void* p; | |
| bool ok = true; | |
| for (int i = 0; i < 8 && ok; i++) { | |
| p = mi_memalign(16,8); | |
| ok = (p != NULL && (uintptr_t)(p) % 16 == 0); mi_free(p); | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("zalloc-csize-large") { | |
| // mi_theap_zalloc_csize must zero its large branch too: it used to | |
| // delegate sizes above MI_SMALL_SIZE_MAX to the plain malloc path. | |
| mi_theap_t* theap = mi_theap_get_default(); | |
| size_t size = MI_SMALL_SIZE_MAX + 64; | |
| bool ok = true; | |
| for (int round = 0; round < 16 && ok; round++) { | |
| uint8_t* junk = (uint8_t*)mi_theap_malloc(theap, size); | |
| memset(junk, 0xAB, size); | |
| mi_free(junk); | |
| uint8_t* z = (uint8_t*)mi_theap_zalloc_csize(theap, size); | |
| ok = mem_is_zero(z, size); | |
| mi_free(z); | |
| } | |
| result = ok; | |
| }; | |
| CHECK_BODY("zalloc-aligned-small1") { | |
| size_t zalloc_size = MI_SMALL_SIZE_MAX / 2; | |
| uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2); | |
| result = mem_is_zero(p, zalloc_size); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("rezalloc_aligned-small1") { | |
| size_t zalloc_size = MI_SMALL_SIZE_MAX / 2; | |
| uint8_t* p = (uint8_t*)mi_zalloc_aligned(zalloc_size, MI_MAX_ALIGN_SIZE * 2); | |
| result = mem_is_zero(p, zalloc_size); | |
| zalloc_size *= 3; | |
| p = (uint8_t*)mi_rezalloc_aligned(p, zalloc_size, MI_MAX_ALIGN_SIZE * 2); | |
| result = result && mem_is_zero(p, zalloc_size); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("rezalloc_aligned_zeros") { // issue #763 | |
| size_t alignment = 1024; | |
| size_t n = 1024 * 6; | |
| void* ptr = mi_zalloc_aligned(n, alignment); | |
| assert(mem_is_zero(ptr,n)); | |
| memset(ptr,123,n/2); | |
| ptr = mi_rezalloc_aligned(ptr, n/2, alignment); | |
| assert(mem_has_vals((uint8_t*)ptr,n/2,123)); | |
| ptr = mi_rezalloc_aligned(ptr, n, alignment); | |
| assert(mem_has_vals((uint8_t*)ptr,n/2,123)); | |
| result = mem_is_zero((uint8_t*)ptr + n/2, n/2); | |
| mi_free(ptr); | |
| } | |
| #if !MI_QSBR | |
| CHECK_BODY("heap-qsbr-unavailable") { | |
| int err = 0; | |
| mi_register_error(&qsbr_record_error, &err); | |
| mi_heap_t* heap = mi_heap_new_qsbr(); | |
| mi_register_error(NULL, NULL); | |
| result = (heap == NULL && err == ENOSYS); | |
| if (heap != NULL) { mi_heap_destroy(heap); } | |
| } | |
| #endif | |
| CHECK_BODY("heap_aligned1") { | |
| mi_heap_t* heap = mi_heap_new(); | |
| const size_t alignment = 128 * 1024; // 128 KiB | |
| const size_t buffer_size = 1 * 1024 * 1024; // 1 MiB | |
| void* buffer = mi_heap_malloc_aligned(heap, buffer_size, alignment); | |
| bool nonnull = (buffer != NULL); | |
| assert(nonnull); | |
| const bool is_aligned = ((uintptr_t)buffer % alignment) == 0; | |
| assert(is_aligned); | |
| mi_free(buffer); | |
| mi_heap_destroy(heap); | |
| result = (nonnull && is_aligned); | |
| } | |
| // --------------------------------------------------- | |
| // Reallocation | |
| // --------------------------------------------------- | |
| CHECK_BODY("realloc-null") { | |
| void* p = mi_realloc(NULL,4); | |
| result = (p != NULL); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("realloc-null-sizezero") { | |
| void* p = mi_realloc(NULL,0); // <https://en.cppreference.com/w/c/memory/realloc> "If ptr is NULL, the behavior is the same as calling malloc(new_size)." | |
| result = (p != NULL); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("realloc-sizezero") { | |
| void* p = mi_malloc(4); | |
| void* q = mi_realloc(p, 0); | |
| result = (q != NULL); | |
| mi_free(q); | |
| }; | |
| CHECK_BODY("reallocarray-null-sizezero") { | |
| void* p = mi_reallocarray(NULL,0,16); // issue #574 | |
| result = (p != NULL && errno == 0); | |
| mi_free(p); | |
| }; | |
| CHECK_BODY("reallocarray-overflow") { | |
| uint8_t* p = (uint8_t*)mi_malloc(32); | |
| if (p == NULL) { result = false; } | |
| else { | |
| memset(p, 0x5A, 32); | |
| errno = 0; | |
| void* q = mi_reallocarray(p, SIZE_MAX / 2 + 1, 2); | |
| result = (q == NULL && errno == EOVERFLOW && mem_has_vals(p, 32, 0x5A)); | |
| mi_free(q == NULL ? p : q); | |
| } | |
| }; | |
| CHECK_BODY("reallocarr-grow") { | |
| void* p = mi_malloc(32); | |
| if (p == NULL) { result = false; } | |
| else { | |
| memset(p, 0x5A, 32); | |
| int err = mi_reallocarr(&p, 64, 2); | |
| result = (err == 0 && mi_usable_size(p) >= 128 && mem_has_vals((uint8_t*)p, 32, 0x5A)); | |
| mi_free(p); | |
| } | |
| }; | |
| CHECK_BODY("reallocarr-zero-count") { | |
| void* p = mi_malloc(32); | |
| if (p == NULL) { result = false; } | |
| else { | |
| int err = mi_reallocarr(&p, 0, 2); | |
| result = (err == 0 && p == NULL); | |
| mi_free(p); | |
| } | |
| }; | |
| CHECK_BODY("reallocarr-overflow") { | |
| void* p = mi_malloc(32); | |
| if (p == NULL) { result = false; } | |
| else { | |
| memset(p, 0x5A, 32); | |
| void* original = p; | |
| int err = mi_reallocarr(&p, SIZE_MAX / 2 + 1, 2); | |
| result = (err == EOVERFLOW && p == original && mem_has_vals((uint8_t*)p, 32, 0x5A)); | |
| mi_free(original); | |
| } | |
| }; | |
| CHECK_BODY("realloc-guarded") { // issue #1304 | |
| void* shared_ptr = NULL; | |
| for (int iterations = 0; iterations < 64; ++iterations) { | |
| for (int i = 0; i < 1024; ++i) { | |
| shared_ptr = mi_realloc(shared_ptr, i * 64); | |
| } | |
| } | |
| mi_free(shared_ptr); | |
| } | |
| // --------------------------------------------------- | |
| // Small allocations | |
| // --------------------------------------------------- | |
| CHECK_BODY("free_small1") { | |
| for(size_t n = 1; n < MI_SMALL_SIZE_MAX; n *=2) { | |
| const size_t size = n*sizeof(int); | |
| int* p = (int*)mi_zalloc(size); | |
| p[n-1] = 42; | |
| mi_free_size(p,size); | |
| } | |
| } | |
| CHECK_BODY("free_small2") { | |
| for(size_t n = 1; n < MI_SMALL_SIZE_MAX; n *=2) { | |
| const size_t size = n*sizeof(int); | |
| int* p = (int*)mi_zalloc(size); | |
| p[n-1] = 42; | |
| p = (int*)mi_rezalloc(p, size + MI_SMALL_SIZE_MAX); | |
| mi_free_size(p,size + MI_SMALL_SIZE_MAX); | |
| } | |
| } | |
| CHECK_BODY("free_small3") { | |
| for(size_t n = 1; n < MI_SMALL_SIZE_MAX/sizeof(int); n *=2) { | |
| const size_t size = n*sizeof(int); | |
| int* p = (int*)mi_zalloc_small(size); | |
| p[n-1] = 42; | |
| mi_free_small(p); | |
| } | |
| } | |
| CHECK_BODY("free_small4") { | |
| for(size_t n = 1; n < MI_SMALL_SIZE_MAX/sizeof(int); n *=2) { | |
| const size_t size = n*sizeof(int); | |
| int* p = (int*)mi_zalloc_small(size); | |
| p[n-1] = 42; | |
| mi_free(p); | |
| } | |
| } | |
| // --------------------------------------------------- | |
| // Returned block sizes | |
| // --------------------------------------------------- | |
| CHECK_BODY("umalloc1") { | |
| for(size_t size = 1; size <= 32*MI_MiB; size *= 2 ) { | |
| size_t bsize; | |
| void* p = mi_umalloc(size,&bsize); | |
| assert(bsize >= size); | |
| size_t pre_size; | |
| size_t post_size; | |
| p = mi_urealloc(p, size + 1024, &pre_size, &post_size); | |
| assert(pre_size == bsize); | |
| assert(post_size >= size + 1024); | |
| size_t fsize; | |
| mi_ufree(p,&fsize); | |
| assert(fsize == post_size); | |
| } | |
| } | |
| #if (MI_SIZE_SIZE > 4) | |
| CHECK_BODY("arena_reserve") { | |
| result = (0==mi_reserve_os_memory(16*MI_GiB,false,true)); | |
| } | |
| #if TEST_ARENAS // normally disabled as it takes long and consumes a lot of memory | |
| CHECK_BODY("arena_reserve_child") { | |
| // With the following setup, the arena size is 24 GiB and the total | |
| // allocation requires over 16 GiB. mimalloc arena internally creates | |
| // child arenas once the arena size is over 16 GiB so this test will | |
| // trigger the creation and access to child arenas to ensure the arena | |
| // traversal and space accounting works as expected. | |
| const size_t arena_size = 24 * MI_GiB; | |
| const size_t allocation_size = 63 * MI_MiB; | |
| const size_t allocation_count = 272; | |
| const size_t alignment = mi_arena_min_alignment(); | |
| mi_arena_id_t arena_id = NULL; | |
| bool ok = (0==mi_reserve_os_memory_ex(arena_size,false /* commit */,false /* allow large */, | |
| true /* exclusive */, &arena_id)); | |
| if (!ok) { | |
| fprintf(stderr, "failed to register the 24 GiB arena\n"); | |
| ok = true; // don't fail the test on small machines | |
| } | |
| else { | |
| mi_heap_t* heap = mi_heap_new_in_arena(arena_id); | |
| for (size_t i = 0; i < allocation_count && ok; i++) { | |
| void* p = mi_heap_malloc(heap, allocation_size); | |
| if (p == NULL) { | |
| fprintf(stderr, "allocation %zu failed after %zu MiB; child arena was not used\n", i, i * allocation_size / MI_MiB); | |
| ok = false; | |
| } | |
| if (!mi_arena_contains(arena_id, p)) { | |
| fprintf(stderr, "allocation %zu came from outside the requested arena\n", i); | |
| ok = false; | |
| } | |
| } | |
| fprintf(stderr, "allocated %zu MiB from the parent arena and its children\n", allocation_count * allocation_size / MI_MiB); | |
| mi_heap_destroy(heap); | |
| } | |
| // mi_arena_destroy(arena_id); | |
| result = ok; | |
| } | |
| #endif | |
| #endif | |
| // --------------------------------------------------- | |
| // C++ | |
| // --------------------------------------------------- | |
| #if defined(__cplusplus) && !defined(_MSC_VER) | |
| CHECK_BODY("c++ new-handler") { | |
| std::set_new_handler([]{ throw std::bad_alloc(); }); | |
| void* p = mi_new_nothrow(SIZE_MAX/2); | |
| result = (p==NULL); | |
| } | |
| CHECK_BODY("c++ new handler2") { | |
| try { | |
| void* p = mi_new_n(SIZE_MAX/2, 4); | |
| (void)(p); | |
| result = false; | |
| } | |
| catch(const std::bad_alloc&) { | |
| result = true; | |
| } | |
| } | |
| #endif | |
| // --------------------------------------------------- | |
| // Heaps | |
| // --------------------------------------------------- | |
| CHECK_BODY("heap-os1") { | |
| // @zoxc opus bug #2. | |
| mi_heap_t* h = mi_heap_new(); | |
| void* p = mi_heap_malloc_aligned(h, 1<<20, 2<<20); // forced OS allocation | |
| mi_heap_delete(h); | |
| mi_free(p); // SIGSEGV | |
| } | |
| CHECK_BODY("heap-os2") { | |
| // @zoxc opus bug #3. | |
| mi_collect(true); | |
| mi_stats_t_decl(stats0); | |
| mi_stats_get(&stats0); | |
| mi_heap_t* h = mi_heap_new(); | |
| long failed = 0; | |
| for(int i = 0; i < 10; i++) { | |
| int* p = (int*)mi_heap_malloc_aligned(h, 1<<20, 2<<20); // forced OS allocation | |
| if (p==NULL) { | |
| failed++; | |
| } | |
| else { | |
| p[0] = 42; | |
| } | |
| } | |
| mi_heap_destroy(h); | |
| mi_collect(true); | |
| mi_stats_t_decl(stats1); | |
| mi_stats_get(&stats1); | |
| result = (stats0.pages.current == stats1.pages.current); | |
| if (!result) { | |
| fprintf(stderr, "heap-os2: pages: %ld != %ld (failed: %ld)\n", (long)stats0.pages.current, (long)stats1.pages.current, failed); | |
| } | |
| } | |
| CHECK_BODY("heap-destroy-os-pages") { // regular OS-backed owned pages must not be orphaned on heap destroy | |
| mi_collect(true); | |
| mi_stats_t_decl(stats0); | |
| mi_stats_get(&stats0); | |
| mi_option_set(mi_option_disallow_arena_alloc, 1); // force all pages to be OS allocated | |
| mi_heap_t* h = mi_heap_new(); | |
| for (int i = 0; i < 1000; i++) { (void)mi_heap_malloc(h, 48); } // owned OS-backed pages with live blocks | |
| mi_heap_destroy(h); | |
| mi_option_set(mi_option_disallow_arena_alloc, 0); | |
| mi_collect(true); | |
| mi_stats_t_decl(stats1); | |
| mi_stats_get(&stats1); | |
| result = (stats0.pages.current == stats1.pages.current); // orphaned if destroy missed the owned OS pages | |
| } | |
| #define NHEAPS (1000) | |
| CHECK_BODY("heap-many") { // check creating many heaps and threadlocals, see issue #1358 | |
| mi_heap_t* heaps[NHEAPS]; | |
| for (size_t i = 0; i < NHEAPS; i++) { | |
| heaps[i] = mi_heap_new(); | |
| if (heaps[i] == NULL) { result = false; break; }; | |
| if (mi_heap_malloc(heaps[i], 32) == NULL) { result = false; break; } | |
| } | |
| for (size_t i = 0; i < NHEAPS; i++) { | |
| mi_heap_destroy(heaps[i]); | |
| } | |
| } | |
| CHECK_THREADS_BODY("heap-destroy-xthread-free") { // destroy a heap with live blocks and pending cross-thread frees | |
| result = test_heap_destroy_xthread_free(); | |
| } | |
| CHECK_THREADS_BODY("heap-destroy-stale-theap") { // a thread's cached theap must not survive the destroy of its heap | |
| result = test_heap_destroy_stale_theap(); | |
| } | |
| //CHECK("theap_destroy", test_theap1()); | |
| //CHECK("theap_delete", test_theap2()); | |
| //CHECK("theap_arena_destroy", test_theap_arena_destroy()); | |
| //CHECK("theap_arena_delete", test_theap_arena_delete()); | |
| // --------------------------------------------------- | |
| // Threads | |
| // --------------------------------------------------- | |
| CHECK_THREADS_BODY("thread_zero_aligned_first") { | |
| result = mi_run_on_thread(&test_zero_aligned_first); | |
| } | |
| #ifdef __cplusplus | |
| CHECK_THREADS_BODY("thread_new_first") { | |
| result = mi_run_on_thread(&test_new_first); | |
| } | |
| #endif | |
| //mi_stats_print(NULL); | |
| // --------------------------------------------------- | |
| // various | |
| // --------------------------------------------------- | |
| #if !defined(MI_TRACK_ASAN) // realpath may leak with ASAN enabled (as the ASAN allocator intercepts it) | |
| CHECK_BODY("realpath") { | |
| char* s = mi_realpath( ".", NULL ); | |
| // printf("realpath: %s\n",s); | |
| mi_free(s); | |
| }; | |
| #endif | |
| CHECK("stl_allocator1", test_stl_allocator1()); | |
| CHECK("stl_allocator2", test_stl_allocator2()); | |
| //CHECK("stl_theap_allocator1", test_stl_theap_allocator1()); | |
| //CHECK("stl_theap_allocator2", test_stl_theap_allocator2()); | |
| //CHECK("stl_theap_allocator3", test_stl_theap_allocator3()); | |
| //CHECK("stl_theap_allocator4", test_stl_theap_allocator4()); | |
| // --------------------------------------------------- | |
| // Done | |
| // ---------------------------------------------------[] | |
| return print_test_summary(); | |
| } | |
| // --------------------------------------------------- | |
| // Larger test functions | |
| // --------------------------------------------------- | |
| /* | |
| bool test_theap1(void) { | |
| mi_theap_t* theap = mi_theap_new(); | |
| int* p1 = mi_theap_malloc_tp(theap,int); | |
| int* p2 = mi_theap_malloc_tp(theap,int); | |
| *p1 = *p2 = 43; | |
| mi_theap_destroy(theap); | |
| return true; | |
| } | |
| bool test_theap2(void) { | |
| mi_theap_t* theap = mi_theap_new(); | |
| int* p1 = mi_theap_malloc_tp(theap,int); | |
| int* p2 = mi_theap_malloc_tp(theap,int); | |
| mi_theap_delete(theap); | |
| *p1 = 42; | |
| mi_free(p1); | |
| mi_free(p2); | |
| return true; | |
| } | |
| bool test_theap_arena_destroy(void) { | |
| mi_arena_id_t arena_id = NULL; | |
| if (mi_reserve_os_memory_ex(64 * 1024 * 1024, true, false, true, &arena_id) != 0) { | |
| return false; | |
| } | |
| mi_theap_t* theap = mi_theap_new_ex(0, true, arena_id); | |
| if (theap == NULL) { | |
| return false; | |
| } | |
| mi_theap_destroy(theap); | |
| return true; | |
| } | |
| bool test_theap_arena_delete(void) { | |
| mi_arena_id_t arena_id = NULL; | |
| if (mi_reserve_os_memory_ex(64 * 1024 * 1024, true, false, true, &arena_id) != 0) { | |
| return false; | |
| } | |
| mi_theap_t* theap = mi_theap_new_ex(0, true, arena_id); | |
| if (theap == NULL) { | |
| return false; | |
| } | |
| mi_theap_delete(theap); | |
| return true; | |
| } | |
| */ | |
| bool test_stl_allocator1(void) { | |
| #ifdef __cplusplus | |
| std::vector<int, mi_stl_allocator<int> > vec; | |
| vec.push_back(1); | |
| vec.pop_back(); | |
| return vec.size() == 0; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| struct some_struct { int i; int j; double z; }; | |
| bool test_stl_allocator2(void) { | |
| #ifdef __cplusplus | |
| std::vector<some_struct, mi_stl_allocator<some_struct> > vec; | |
| vec.push_back(some_struct()); | |
| vec.pop_back(); | |
| return vec.size() == 0; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| /* | |
| bool test_stl_theap_allocator1(void) { | |
| #ifdef __cplusplus | |
| std::vector<some_struct, mi_theap_stl_allocator<some_struct> > vec; | |
| vec.push_back(some_struct()); | |
| vec.pop_back(); | |
| return vec.size() == 0; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| bool test_stl_theap_allocator2(void) { | |
| #ifdef __cplusplus | |
| std::vector<some_struct, mi_theap_destroy_stl_allocator<some_struct> > vec; | |
| vec.push_back(some_struct()); | |
| vec.pop_back(); | |
| return vec.size() == 0; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| bool test_stl_theap_allocator3(void) { | |
| #ifdef __cplusplus | |
| mi_theap_t* theap = mi_theap_new(); | |
| bool good = false; | |
| { | |
| mi_theap_stl_allocator<some_struct> myAlloc(theap); | |
| std::vector<some_struct, mi_theap_stl_allocator<some_struct> > vec(myAlloc); | |
| vec.push_back(some_struct()); | |
| vec.pop_back(); | |
| good = vec.size() == 0; | |
| } | |
| mi_theap_delete(theap); | |
| return good; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| bool test_stl_theap_allocator4(void) { | |
| #ifdef __cplusplus | |
| mi_theap_t* theap = mi_theap_new(); | |
| bool good = false; | |
| { | |
| mi_theap_destroy_stl_allocator<some_struct> myAlloc(theap); | |
| std::vector<some_struct, mi_theap_destroy_stl_allocator<some_struct> > vec(myAlloc); | |
| vec.push_back(some_struct()); | |
| vec.pop_back(); | |
| good = vec.size() == 0; | |
| } | |
| mi_theap_destroy(theap); | |
| return good; | |
| #else | |
| return true; | |
| #endif | |
| } | |
| */ | |
| // --------------------------------------------------------------------------- | |
| // Test a zero size aligned allocation as the very first allocation of a fresh thread. | |
| // --------------------------------------------------------------------------- | |
| static bool test_zero_aligned_first(void) { | |
| void* p = mi_malloc_aligned(0, 16); // must be the first mimalloc call on this thread | |
| bool res = (p != NULL && (uintptr_t)(p) % 16 == 0); | |
| mi_free(p); | |
| p = mi_zalloc_aligned(0, 32); | |
| res = res && (p != NULL && (uintptr_t)(p) % 32 == 0); | |
| mi_free(p); | |
| return res; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Aligned allocation for all size classes and alignments up to 4KiB (including huge blocks). | |
| // Keeps several blocks live so alignment is checked beyond the first block in a page. | |
| // --------------------------------------------------------------------------- | |
| static bool test_aligned_check(size_t size, size_t align, bool zero) { | |
| void* p[8]; | |
| bool ok = true; | |
| for (int i = 0; i < 8; i++) { | |
| p[i] = (zero ? mi_zalloc_aligned(size, align) : mi_malloc_aligned(size, align)); | |
| if (p[i] == NULL || ((uintptr_t)p[i] % align) != 0 || mi_usable_size(p[i]) < size || (zero && !mem_is_zero(p[i], size))) { | |
| ok = false; | |
| } | |
| if (p[i] != NULL) { memset(p[i], 0xAB, size); } | |
| } | |
| for (int i = 0; i < 8; i++) { | |
| mi_free(p[i]); | |
| } | |
| return ok; | |
| } | |
| static bool test_aligned_all(void) { | |
| bool ok = true; | |
| for (size_t size = 0; size <= 300*1024 && ok; size += (size < 16*1024 ? 8 : 512)) { | |
| for (size_t align = 8; align <= 4096 && ok; align *= 2) { | |
| ok = test_aligned_check(size, align, ((size/8) & 1) != 0); | |
| } | |
| } | |
| for (size_t size = 4*1024*1024 - 4096; size <= 4*1024*1024 + 3*4096 && ok; size += 1024) { | |
| for (size_t align = 8; align <= 4096 && ok; align *= 2) { | |
| ok = test_aligned_check(size, align, false); | |
| } | |
| } | |
| return ok; | |
| } | |
| #ifdef __cplusplus | |
| static bool test_new_first(void) { | |
| char* p = (char*)mi_new(20); | |
| bool res = (p != NULL && (uintptr_t)(p) % 16 == 0); | |
| mi_free_csize(p,20); | |
| return res; | |
| } | |
| #endif | |
| // --------------------------------------------------------------------------- | |
| // Heap destroy with blocks that were freed by another thread (and not yet collected). | |
| // The destroyed pages still have live blocks; the pending thread-free block must | |
| // not be reported as "corrupted meta-data in thread-free list". | |
| // --------------------------------------------------------------------------- | |
| static void* xthread_free_block; | |
| static long xthread_efault_count; | |
| static bool xthread_free_fun(void) { | |
| mi_free(xthread_free_block); // pushes onto the thread-free list of a page owned by the main thread | |
| return true; | |
| } | |
| static void xthread_count_efault(int err, void* arg) { | |
| (void)arg; | |
| if (err == EFAULT) { xthread_efault_count++; } | |
| } | |
| static bool test_heap_destroy_xthread_free(void) { | |
| mi_heap_t* heap = mi_heap_new(); | |
| if (heap == NULL) return false; | |
| void* live = mi_heap_malloc(heap, 32); // stays live, so the page is destroyed rather than freed | |
| xthread_free_block = mi_heap_malloc(heap, 32); | |
| if (live == NULL || xthread_free_block == NULL) return false; | |
| if (!mi_run_on_thread(&xthread_free_fun)) return false; | |
| xthread_efault_count = 0; | |
| mi_register_error(&xthread_count_efault, NULL); // the default handler aborts in debug mode | |
| mi_heap_destroy(heap); | |
| mi_register_error(NULL, NULL); | |
| return (xthread_efault_count == 0); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // A thread that last allocated from a heap keeps that theap in its `_mi_theap_cached`. | |
| // If another thread destroys the heap and a new heap is allocated at the same address, | |
| // the stale cached theap must not be used for the new heap (it has `tld==NULL` and its | |
| // pages were destroyed). | |
| // --------------------------------------------------------------------------- | |
| typedef struct test_gate_s { | |
| int step; | |
| #ifdef _WIN32 | |
| CRITICAL_SECTION lock; | |
| CONDITION_VARIABLE cond; | |
| #else | |
| pthread_mutex_t lock; | |
| pthread_cond_t cond; | |
| #endif | |
| } test_gate_t; | |
| #ifdef _WIN32 | |
| static void test_gate_init(test_gate_t* g) { g->step = 0; InitializeCriticalSection(&g->lock); InitializeConditionVariable(&g->cond); } | |
| static void test_gate_done(test_gate_t* g) { DeleteCriticalSection(&g->lock); } | |
| static void test_gate_set(test_gate_t* g, int step) { | |
| EnterCriticalSection(&g->lock); g->step = step; LeaveCriticalSection(&g->lock); | |
| WakeAllConditionVariable(&g->cond); | |
| } | |
| static void test_gate_wait(test_gate_t* g, int step) { | |
| EnterCriticalSection(&g->lock); | |
| while (g->step < step) { SleepConditionVariableCS(&g->cond, &g->lock, INFINITE); } | |
| LeaveCriticalSection(&g->lock); | |
| } | |
| typedef HANDLE test_thread_t; | |
| static void stale_theap_worker(void); | |
| static DWORD WINAPI test_thread_entry(LPVOID arg) { (void)arg; stale_theap_worker(); return 0; } | |
| static bool test_thread_start(test_thread_t* t) { | |
| *t = CreateThread(NULL, 0, &test_thread_entry, NULL, 0, NULL); | |
| return (*t != NULL); | |
| } | |
| static void test_thread_join(test_thread_t t) { WaitForSingleObject(t, INFINITE); CloseHandle(t); } | |
| #else | |
| static void test_gate_init(test_gate_t* g) { g->step = 0; pthread_mutex_init(&g->lock, NULL); pthread_cond_init(&g->cond, NULL); } | |
| static void test_gate_done(test_gate_t* g) { pthread_cond_destroy(&g->cond); pthread_mutex_destroy(&g->lock); } | |
| static void test_gate_set(test_gate_t* g, int step) { | |
| pthread_mutex_lock(&g->lock); g->step = step; pthread_mutex_unlock(&g->lock); | |
| pthread_cond_broadcast(&g->cond); | |
| } | |
| static void test_gate_wait(test_gate_t* g, int step) { | |
| pthread_mutex_lock(&g->lock); | |
| while (g->step < step) { pthread_cond_wait(&g->cond, &g->lock); } | |
| pthread_mutex_unlock(&g->lock); | |
| } | |
| typedef pthread_t test_thread_t; | |
| static void stale_theap_worker(void); | |
| static void* test_thread_entry(void* arg) { (void)arg; stale_theap_worker(); return NULL; } | |
| static bool test_thread_start(test_thread_t* t) { | |
| return (pthread_create(t, NULL, &test_thread_entry, NULL) == 0); | |
| } | |
| static void test_thread_join(test_thread_t t) { pthread_join(t, NULL); } | |
| #endif | |
| static test_gate_t stale_gate; | |
| static mi_heap_t* stale_heap; // published through the gate | |
| static bool stale_result; | |
| static void stale_theap_worker(void) { | |
| void* p = mi_heap_malloc(stale_heap, 16); // creates this thread's theap of `stale_heap` (may leave the main theap cached) | |
| mi_free(p); | |
| p = mi_heap_malloc(stale_heap, 16); // now `_mi_theap_cached` is this thread's theap of `stale_heap` | |
| test_gate_set(&stale_gate, 1); | |
| test_gate_wait(&stale_gate, 2); // meanwhile: heap destroyed, new heap (likely) at the same address | |
| void* q = mi_heap_malloc(stale_heap, 16); // must not use the cached theap of the destroyed heap | |
| stale_result = (p != NULL && q != NULL && mi_heap_contains(stale_heap, q)); | |
| mi_free(q); | |
| } | |
| #define STALE_MAX_HEAPS (1000) | |
| static bool test_heap_destroy_stale_theap(void) { | |
| test_gate_init(&stale_gate); | |
| mi_heap_t* const destroyed = mi_heap_new(); | |
| if (destroyed == NULL) return false; | |
| stale_heap = destroyed; | |
| stale_result = false; | |
| test_thread_t thread; | |
| if (!test_thread_start(&thread)) return false; | |
| test_gate_wait(&stale_gate, 1); | |
| mi_heap_destroy(destroyed); | |
| // allocate heaps until one reuses the address of the destroyed heap | |
| mi_heap_t* heaps[STALE_MAX_HEAPS]; | |
| size_t count = 0; | |
| bool reused = false; | |
| while (count < STALE_MAX_HEAPS) { | |
| mi_heap_t* const heap = mi_heap_new(); | |
| if (heap == NULL) break; | |
| heaps[count++] = heap; | |
| if (heap == destroyed) { reused = true; break; } | |
| } | |
| if (count == 0) return false; | |
| stale_heap = heaps[count-1]; | |
| test_gate_set(&stale_gate, 2); | |
| test_thread_join(thread); | |
| for (size_t i = 0; i < count; i++) { mi_heap_destroy(heaps[i]); } | |
| test_gate_done(&stale_gate); | |
| if (!reused) { fprintf(stderr, "(heap address not reused, inconclusive) "); } | |
| return stale_result; | |
| } |