/* ---------------------------------------------------------------------------- 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 `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 #include #include #include #ifdef __cplusplus #include #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 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); #if 1 #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(std::bad_alloc) { result = true; } } #endif #endif // --------------------------------------------------- // 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,SIZE_MAX/1000) == NULL); }; CHECK_BODY("malloc-large") { // see PR #544. void* p = mi_malloc(67108872); 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, 32, &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("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]); } /* if (ok && align <= size && ((size + MI_PADDING_SIZE) & (align-1)) == 0) { size_t bsize = mi_good_size(size); ok = (align <= bsize && (bsize & (align-1)) == 0); } */ } } result = ok; } 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-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); } // --------------------------------------------------- // 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); // "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("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); } } } // --------------------------------------------------- // 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); } } // --------------------------------------------------- // 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_INTPTR_SIZE > 4) CHECK_BODY("arena_reserve") { result = (0==mi_reserve_os_memory(16*MI_GiB,false,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); } } #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("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_BODY("zero_aligned_first") { result = mi_run_on_thread(&test_zero_aligned_first); } //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 > 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 > 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 > 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 > 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 myAlloc(theap); std::vector > 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 myAlloc(theap); std::vector > 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; }