blob: c923ebf921dc1158bee6419df789667fd1fa60a5 [file] [edit]
/* ----------------------------------------------------------------------------
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_options_print();
mi_option_disable(mi_option_verbose);
CHECK_BODY("pointer-address-and-memtag") {
uint8_t area[128];
for (size_t tag = 0; tag < 16; tag++) {
void* p = mi_ptr_with_memtag(area, tag);
void* q = mi_ptr_with_addr(p, mi_addr(area) + 32);
result = result && mi_addr(p) == mi_addr(area) && mi_addr(q) == mi_addr(area + 32);
#if MI_MTE
result = result && mi_ptr_memtag(p) == tag;
#else
result = result && mi_ptr_memtag(p) == 0;
#endif
result = result && mi_ptr_memtag(q) == mi_ptr_memtag(p);
result = result && mi_ptr_memtag_meta(q) == area + 32;
}
#if MI_MTE
// Ignored pointer bits are not logical memory-tag bits.
void* p = (void*)((uintptr_t)mi_ptr_with_memtag(area, 5) | (~MI_PTR_ADDR_MASK & ~MI_PTR_MEMTAG_MASK));
result = result && mi_addr(p) == mi_addr(area) && mi_ptr_memtag(p) == 5;
result = result && mi_ptr_with_memtag(p, 0xF5) == mi_ptr_with_memtag(area, 5);
#endif
};
CHECK_BODY("pointer-pack-rederive") {
uint8_t area[128];
for (size_t tag = 0; tag < 16; tag++) {
void* p = mi_ptr_with_memtag(mi_ptr_bounded(area + 32, 16), tag);
void* auth = mi_ptr_with_memtag(area, tag ^ 1);
void* unpacked = mi_ptr_unpack(auth, mi_ptr_pack(p));
void* derived = mi_ptr_rederive(auth, p);
result = result && mi_addr(unpacked) == mi_addr(p) && mi_addr(derived) == mi_addr(p);
result = result && mi_ptr_memtag(unpacked) == mi_ptr_memtag(p) && mi_ptr_memtag(derived) == mi_ptr_memtag(p);
// Recover authority before moving outside the user's bounds.
void* base = mi_ptr_with_addr(derived, mi_addr(area));
result = result && mi_addr(base) == mi_addr(area) && mi_ptr_memtag(base) == mi_ptr_memtag(p);
#if MI_CHERI
result = result && __builtin_cheri_tag_get(unpacked) && __builtin_cheri_tag_get(base);
result = result && __builtin_cheri_base_get(unpacked) == __builtin_cheri_base_get(auth);
result = result && __builtin_cheri_length_get(unpacked) == __builtin_cheri_length_get(auth);
result = result && __builtin_cheri_base_get(base) == __builtin_cheri_base_get(auth);
result = result && __builtin_cheri_length_get(base) == __builtin_cheri_length_get(auth);
#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 | 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;
}