#include "test/jemalloc_test.h" static void do_fill_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t ncached_max, cache_bin_sz_t nfill_attempt, cache_bin_sz_t nfill_succeed) { bool success; void *ptr; assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill_attempt); cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill_attempt); for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) { arr.ptr[i] = &ptrs[i]; } cache_bin_finish_fill(bin, info, &arr, nfill_succeed); expect_true(cache_bin_ncached_get_local(bin, info) == nfill_succeed, ""); cache_bin_low_water_set(bin); for (cache_bin_sz_t i = 0; i < nfill_succeed; i++) { ptr = cache_bin_alloc(bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, (void *)&ptrs[i], "Should pop in order filled"); expect_true(cache_bin_low_water_get(bin, info) == nfill_succeed - i - 1, ""); } expect_true(cache_bin_ncached_get_local(bin, info) == 0, ""); expect_true(cache_bin_low_water_get(bin, info) == 0, ""); } static void do_flush_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, cache_bin_sz_t nflush) { bool success; assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); for (cache_bin_sz_t i = 0; i < nfill; i++) { success = cache_bin_dalloc_easy(bin, &ptrs[i]); expect_true(success, ""); } CACHE_BIN_PTR_ARRAY_DECLARE(arr, nflush); cache_bin_init_ptr_array_for_flush(bin, info, &arr, nflush); for (cache_bin_sz_t i = 0; i < nflush; i++) { expect_ptr_eq(arr.ptr[i], &ptrs[nflush - i - 1], ""); } cache_bin_finish_flush(bin, info, &arr, nflush); expect_true(cache_bin_ncached_get_local(bin, info) == nfill - nflush, ""); while (cache_bin_ncached_get_local(bin, info) > 0) { cache_bin_alloc(bin, &success); } } static void do_batch_alloc_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, size_t batch) { assert_true(cache_bin_ncached_get_local(bin, info) == 0, ""); CACHE_BIN_PTR_ARRAY_DECLARE(arr, nfill); cache_bin_init_ptr_array_for_fill(bin, info, &arr, nfill); for (cache_bin_sz_t i = 0; i < nfill; i++) { arr.ptr[i] = &ptrs[i]; } cache_bin_finish_fill(bin, info, &arr, nfill); assert_true(cache_bin_ncached_get_local(bin, info) == nfill, ""); cache_bin_low_water_set(bin); void **out = malloc((batch + 1) * sizeof(void *)); size_t n = cache_bin_alloc_batch(bin, batch, out); assert_true(n == ((size_t)nfill < batch ? (size_t)nfill : batch), ""); for (cache_bin_sz_t i = 0; i < (cache_bin_sz_t)n; i++) { expect_ptr_eq(out[i], &ptrs[i], ""); } expect_true(cache_bin_low_water_get(bin, info) == nfill - (cache_bin_sz_t)n, ""); while (cache_bin_ncached_get_local(bin, info) > 0) { bool success; cache_bin_alloc(bin, &success); } free(out); } static void test_bin_init(cache_bin_t *bin, cache_bin_info_t *info) { size_t size; size_t alignment; cache_bin_info_compute_alloc(info, 1, &size, &alignment); void *mem = mallocx(size, MALLOCX_ALIGN(alignment)); assert_ptr_not_null(mem, "Unexpected mallocx failure"); size_t cur_offset = 0; cache_bin_preincrement(info, 1, mem, &cur_offset); cache_bin_init(bin, info, mem, &cur_offset); cache_bin_postincrement(info, 1, mem, &cur_offset); assert_zu_eq(cur_offset, size, "Should use all requested memory"); } TEST_BEGIN(test_cache_bin) { const int ncached_max = 100; bool success; void *ptr; cache_bin_info_t info; cache_bin_info_init(&info, ncached_max); cache_bin_t bin; test_bin_init(&bin, &info); /* Initialize to empty; should then have 0 elements. */ expect_d_eq(ncached_max, cache_bin_info_ncached_max(&info), ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, ""); expect_true(cache_bin_low_water_get(&bin, &info) == 0, ""); ptr = cache_bin_alloc_easy(&bin, &success); expect_false(success, "Shouldn't successfully allocate when empty"); expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure"); ptr = cache_bin_alloc(&bin, &success); expect_false(success, "Shouldn't successfully allocate when empty"); expect_ptr_null(ptr, "Shouldn't get a non-null pointer on failure"); /* * We allocate one more item than ncached_max, so we can test cache bin * exhaustion. */ void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0); assert_ptr_not_null(ptrs, "Unexpected mallocx failure"); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_ncached_get_local(&bin, &info) == i, ""); success = cache_bin_dalloc_easy(&bin, &ptrs[i]); expect_true(success, "Should be able to dalloc into a non-full cache bin."); expect_true(cache_bin_low_water_get(&bin, &info) == 0, "Pushes and pops shouldn't change low water of zero."); } expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max, ""); success = cache_bin_dalloc_easy(&bin, &ptrs[ncached_max]); expect_false(success, "Shouldn't be able to dalloc into a full bin."); cache_bin_low_water_set(&bin); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i, ""); /* * This should fail -- the easy variant can't change the low * water mark. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i, ""); /* This should succeed, though. */ ptr = cache_bin_alloc(&bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, &ptrs[ncached_max - i - 1], "Alloc should pop in stack order"); expect_true(cache_bin_low_water_get(&bin, &info) == ncached_max - i - 1, ""); expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max - i - 1, ""); } /* Now we're empty -- all alloc attempts should fail. */ expect_true(cache_bin_ncached_get_local(&bin, &info) == 0, ""); ptr = cache_bin_alloc_easy(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); ptr = cache_bin_alloc(&bin, &success); expect_ptr_null(ptr, ""); expect_false(success, ""); for (cache_bin_sz_t i = 0; i < ncached_max / 2; i++) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } cache_bin_low_water_set(&bin); for (cache_bin_sz_t i = ncached_max / 2; i < ncached_max; i++) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } expect_true(cache_bin_ncached_get_local(&bin, &info) == ncached_max, ""); for (cache_bin_sz_t i = ncached_max - 1; i >= ncached_max / 2; i--) { /* * Size is bigger than low water -- the reduced version should * succeed. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_true(success, ""); expect_ptr_eq(ptr, &ptrs[i], ""); } /* But now, we've hit low-water. */ ptr = cache_bin_alloc_easy(&bin, &success); expect_false(success, ""); expect_ptr_null(ptr, ""); /* We're going to test filling -- we must be empty to start. */ while (cache_bin_ncached_get_local(&bin, &info)) { cache_bin_alloc(&bin, &success); expect_true(success, ""); } /* Test fill. */ /* Try to fill all, succeed fully. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, ncached_max); /* Try to fill all, succeed partially. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, ncached_max / 2); /* Try to fill all, fail completely. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max, 0); /* Try to fill some, succeed fully. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, ncached_max / 2); /* Try to fill some, succeed partially. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, ncached_max / 4); /* Try to fill some, fail completely. */ do_fill_test(&bin, &info, ptrs, ncached_max, ncached_max / 2, 0); do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max); do_flush_test(&bin, &info, ptrs, ncached_max, ncached_max / 2); do_flush_test(&bin, &info, ptrs, ncached_max, 0); do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_flush_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_flush_test(&bin, &info, ptrs, ncached_max / 2, 0); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max * 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, ncached_max / 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 1); do_batch_alloc_test(&bin, &info, ptrs, ncached_max, 0); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 2); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 1); do_batch_alloc_test(&bin, &info, ptrs, ncached_max / 2, 0); do_batch_alloc_test(&bin, &info, ptrs, 2, ncached_max); do_batch_alloc_test(&bin, &info, ptrs, 2, 2); do_batch_alloc_test(&bin, &info, ptrs, 2, 1); do_batch_alloc_test(&bin, &info, ptrs, 2, 0); do_batch_alloc_test(&bin, &info, ptrs, 1, 2); do_batch_alloc_test(&bin, &info, ptrs, 1, 1); do_batch_alloc_test(&bin, &info, ptrs, 1, 0); do_batch_alloc_test(&bin, &info, ptrs, 0, 2); do_batch_alloc_test(&bin, &info, ptrs, 0, 1); do_batch_alloc_test(&bin, &info, ptrs, 0, 0); free(ptrs); } TEST_END static void do_flush_stashed_test(cache_bin_t *bin, cache_bin_info_t *info, void **ptrs, cache_bin_sz_t nfill, cache_bin_sz_t nstash) { expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Bin not empty"); expect_true(cache_bin_nstashed_get_local(bin, info) == 0, "Bin not empty"); expect_true(nfill + nstash <= info->ncached_max, "Exceeded max"); bool ret; /* Fill */ for (cache_bin_sz_t i = 0; i < nfill; i++) { ret = cache_bin_dalloc_easy(bin, &ptrs[i]); expect_true(ret, "Unexpected fill failure"); } expect_true(cache_bin_ncached_get_local(bin, info) == nfill, "Wrong cached count"); /* Stash */ for (cache_bin_sz_t i = 0; i < nstash; i++) { ret = cache_bin_stash(bin, &ptrs[i + nfill]); expect_true(ret, "Unexpected stash failure"); } expect_true(cache_bin_nstashed_get_local(bin, info) == nstash, "Wrong stashed count"); if (nfill + nstash == info->ncached_max) { ret = cache_bin_dalloc_easy(bin, &ptrs[0]); expect_false(ret, "Should not dalloc into a full bin"); ret = cache_bin_stash(bin, &ptrs[0]); expect_false(ret, "Should not stash into a full bin"); } /* Alloc filled ones */ for (cache_bin_sz_t i = 0; i < nfill; i++) { void *ptr = cache_bin_alloc(bin, &ret); expect_true(ret, "Unexpected alloc failure"); /* Verify it's not from the stashed range. */ expect_true((uintptr_t)ptr < (uintptr_t)&ptrs[nfill], "Should not alloc stashed ptrs"); } expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Wrong cached count"); expect_true(cache_bin_nstashed_get_local(bin, info) == nstash, "Wrong stashed count"); cache_bin_alloc(bin, &ret); expect_false(ret, "Should not alloc stashed"); /* Clear stashed ones */ cache_bin_finish_flush_stashed(bin, info); expect_true(cache_bin_ncached_get_local(bin, info) == 0, "Wrong cached count"); expect_true(cache_bin_nstashed_get_local(bin, info) == 0, "Wrong stashed count"); cache_bin_alloc(bin, &ret); expect_false(ret, "Should not alloc from empty bin"); } TEST_BEGIN(test_cache_bin_stash) { const int ncached_max = 100; cache_bin_t bin; cache_bin_info_t info; cache_bin_info_init(&info, ncached_max); test_bin_init(&bin, &info); /* * The content of this array is not accessed; instead the interior * addresses are used to insert / stash into the bins as test pointers. */ void **ptrs = mallocx(sizeof(void *) * (ncached_max + 1), 0); assert_ptr_not_null(ptrs, "Unexpected mallocx failure"); bool ret; for (cache_bin_sz_t i = 0; i < ncached_max; i++) { expect_true(cache_bin_ncached_get_local(&bin, &info) == (i / 2 + i % 2), "Wrong ncached value"); expect_true(cache_bin_nstashed_get_local(&bin, &info) == i / 2, "Wrong nstashed value"); if (i % 2 == 0) { cache_bin_dalloc_easy(&bin, &ptrs[i]); } else { ret = cache_bin_stash(&bin, &ptrs[i]); expect_true(ret, "Should be able to stash into a " "non-full cache bin"); } } ret = cache_bin_dalloc_easy(&bin, &ptrs[0]); expect_false(ret, "Should not dalloc into a full cache bin"); ret = cache_bin_stash(&bin, &ptrs[0]); expect_false(ret, "Should not stash into a full cache bin"); for (cache_bin_sz_t i = 0; i < ncached_max; i++) { void *ptr = cache_bin_alloc(&bin, &ret); if (i < ncached_max / 2) { expect_true(ret, "Should be able to alloc"); uintptr_t diff = ((uintptr_t)ptr - (uintptr_t)&ptrs[0]) / sizeof(void *); expect_true(diff % 2 == 0, "Should be able to alloc"); } else { expect_false(ret, "Should not alloc stashed"); expect_true(cache_bin_nstashed_get_local(&bin, &info) == ncached_max / 2, "Wrong nstashed value"); } } test_bin_init(&bin, &info); do_flush_stashed_test(&bin, &info, ptrs, ncached_max, 0); do_flush_stashed_test(&bin, &info, ptrs, 0, ncached_max); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 2); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 2); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 2, ncached_max / 4); do_flush_stashed_test(&bin, &info, ptrs, ncached_max / 4, ncached_max / 4); } TEST_END int main(void) { return test(test_cache_bin, test_cache_bin_stash); }