1 1.1 christos #include "test/jemalloc_test.h" 2 1.1 christos 3 1.1 christos #include "jemalloc/internal/hpa.h" 4 1.1 christos #include "jemalloc/internal/nstime.h" 5 1.1 christos 6 1.1 christos #define SHARD_IND 111 7 1.1 christos 8 1.1 christos #define ALLOC_MAX (HUGEPAGE / 4) 9 1.1 christos 10 1.1 christos typedef struct test_data_s test_data_t; 11 1.1 christos struct test_data_s { 12 1.1 christos /* 13 1.1 christos * Must be the first member -- we convert back and forth between the 14 1.1 christos * test_data_t and the hpa_shard_t; 15 1.1 christos */ 16 1.1 christos hpa_shard_t shard; 17 1.1 christos hpa_central_t central; 18 1.1 christos base_t *base; 19 1.1 christos edata_cache_t shard_edata_cache; 20 1.1 christos 21 1.1 christos emap_t emap; 22 1.1 christos }; 23 1.1 christos 24 1.1 christos static hpa_shard_opts_t test_hpa_shard_opts_default = { 25 1.1 christos /* slab_max_alloc */ 26 1.1 christos ALLOC_MAX, 27 1.1 christos /* hugification threshold */ 28 1.1 christos HUGEPAGE, 29 1.1 christos /* dirty_mult */ 30 1.1 christos FXP_INIT_PERCENT(25), 31 1.1 christos /* deferral_allowed */ 32 1.1 christos false, 33 1.1 christos /* hugify_delay_ms */ 34 1.1 christos 10 * 1000, 35 1.1 christos }; 36 1.1 christos 37 1.1 christos static hpa_shard_t * 38 1.1 christos create_test_data(hpa_hooks_t *hooks, hpa_shard_opts_t *opts) { 39 1.1 christos bool err; 40 1.1 christos base_t *base = base_new(TSDN_NULL, /* ind */ SHARD_IND, 41 1.1 christos &ehooks_default_extent_hooks, /* metadata_use_hooks */ true); 42 1.1 christos assert_ptr_not_null(base, ""); 43 1.1 christos 44 1.1 christos test_data_t *test_data = malloc(sizeof(test_data_t)); 45 1.1 christos assert_ptr_not_null(test_data, ""); 46 1.1 christos 47 1.1 christos test_data->base = base; 48 1.1 christos 49 1.1 christos err = edata_cache_init(&test_data->shard_edata_cache, base); 50 1.1 christos assert_false(err, ""); 51 1.1 christos 52 1.1 christos err = emap_init(&test_data->emap, test_data->base, /* zeroed */ false); 53 1.1 christos assert_false(err, ""); 54 1.1 christos 55 1.1 christos err = hpa_central_init(&test_data->central, test_data->base, hooks); 56 1.1 christos assert_false(err, ""); 57 1.1 christos 58 1.1 christos err = hpa_shard_init(&test_data->shard, &test_data->central, 59 1.1 christos &test_data->emap, test_data->base, &test_data->shard_edata_cache, 60 1.1 christos SHARD_IND, opts); 61 1.1 christos assert_false(err, ""); 62 1.1 christos 63 1.1 christos return (hpa_shard_t *)test_data; 64 1.1 christos } 65 1.1 christos 66 1.1 christos static void 67 1.1 christos destroy_test_data(hpa_shard_t *shard) { 68 1.1 christos test_data_t *test_data = (test_data_t *)shard; 69 1.1 christos base_delete(TSDN_NULL, test_data->base); 70 1.1 christos free(test_data); 71 1.1 christos } 72 1.1 christos 73 1.1 christos TEST_BEGIN(test_alloc_max) { 74 1.1 christos test_skip_if(!hpa_supported()); 75 1.1 christos 76 1.1 christos hpa_shard_t *shard = create_test_data(&hpa_hooks_default, 77 1.1 christos &test_hpa_shard_opts_default); 78 1.1 christos tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); 79 1.1 christos 80 1.1 christos edata_t *edata; 81 1.1 christos 82 1.1 christos /* Small max */ 83 1.1 christos bool deferred_work_generated = false; 84 1.1 christos edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX, PAGE, false, false, 85 1.1 christos false, &deferred_work_generated); 86 1.1 christos expect_ptr_not_null(edata, "Allocation of small max failed"); 87 1.1 christos edata = pai_alloc(tsdn, &shard->pai, ALLOC_MAX + PAGE, PAGE, false, 88 1.1 christos false, false, &deferred_work_generated); 89 1.1 christos expect_ptr_null(edata, "Allocation of larger than small max succeeded"); 90 1.1 christos 91 1.1 christos destroy_test_data(shard); 92 1.1 christos } 93 1.1 christos TEST_END 94 1.1 christos 95 1.1 christos typedef struct mem_contents_s mem_contents_t; 96 1.1 christos struct mem_contents_s { 97 1.1 christos uintptr_t my_addr; 98 1.1 christos size_t size; 99 1.1 christos edata_t *my_edata; 100 1.1 christos rb_node(mem_contents_t) link; 101 1.1 christos }; 102 1.1 christos 103 1.1 christos static int 104 1.1 christos mem_contents_cmp(const mem_contents_t *a, const mem_contents_t *b) { 105 1.1 christos return (a->my_addr > b->my_addr) - (a->my_addr < b->my_addr); 106 1.1 christos } 107 1.1 christos 108 1.1 christos typedef rb_tree(mem_contents_t) mem_tree_t; 109 1.1 christos rb_gen(static, mem_tree_, mem_tree_t, mem_contents_t, link, 110 1.1 christos mem_contents_cmp); 111 1.1 christos 112 1.1 christos static void 113 1.1 christos node_assert_ordered(mem_contents_t *a, mem_contents_t *b) { 114 1.1 christos assert_zu_lt(a->my_addr, a->my_addr + a->size, "Overflow"); 115 1.1 christos assert_zu_le(a->my_addr + a->size, b->my_addr, ""); 116 1.1 christos } 117 1.1 christos 118 1.1 christos static void 119 1.1 christos node_check(mem_tree_t *tree, mem_contents_t *contents) { 120 1.1 christos edata_t *edata = contents->my_edata; 121 1.1 christos assert_ptr_eq(contents, (void *)contents->my_addr, ""); 122 1.1 christos assert_ptr_eq(contents, edata_base_get(edata), ""); 123 1.1 christos assert_zu_eq(contents->size, edata_size_get(edata), ""); 124 1.1 christos assert_ptr_eq(contents->my_edata, edata, ""); 125 1.1 christos 126 1.1 christos mem_contents_t *next = mem_tree_next(tree, contents); 127 1.1 christos if (next != NULL) { 128 1.1 christos node_assert_ordered(contents, next); 129 1.1 christos } 130 1.1 christos mem_contents_t *prev = mem_tree_prev(tree, contents); 131 1.1 christos if (prev != NULL) { 132 1.1 christos node_assert_ordered(prev, contents); 133 1.1 christos } 134 1.1 christos } 135 1.1 christos 136 1.1 christos static void 137 1.1 christos node_insert(mem_tree_t *tree, edata_t *edata, size_t npages) { 138 1.1 christos mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata); 139 1.1 christos contents->my_addr = (uintptr_t)edata_base_get(edata); 140 1.1 christos contents->size = edata_size_get(edata); 141 1.1 christos contents->my_edata = edata; 142 1.1 christos mem_tree_insert(tree, contents); 143 1.1 christos node_check(tree, contents); 144 1.1 christos } 145 1.1 christos 146 1.1 christos static void 147 1.1 christos node_remove(mem_tree_t *tree, edata_t *edata) { 148 1.1 christos mem_contents_t *contents = (mem_contents_t *)edata_base_get(edata); 149 1.1 christos node_check(tree, contents); 150 1.1 christos mem_tree_remove(tree, contents); 151 1.1 christos } 152 1.1 christos 153 1.1 christos TEST_BEGIN(test_stress) { 154 1.1 christos test_skip_if(!hpa_supported()); 155 1.1 christos 156 1.1 christos hpa_shard_t *shard = create_test_data(&hpa_hooks_default, 157 1.1 christos &test_hpa_shard_opts_default); 158 1.1 christos 159 1.1 christos tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); 160 1.1 christos 161 1.1 christos const size_t nlive_edatas_max = 500; 162 1.1 christos size_t nlive_edatas = 0; 163 1.1 christos edata_t **live_edatas = calloc(nlive_edatas_max, sizeof(edata_t *)); 164 1.1 christos /* 165 1.1 christos * Nothing special about this constant; we're only fixing it for 166 1.1 christos * consistency across runs. 167 1.1 christos */ 168 1.1 christos size_t prng_state = (size_t)0x76999ffb014df07c; 169 1.1 christos 170 1.1 christos mem_tree_t tree; 171 1.1 christos mem_tree_new(&tree); 172 1.1 christos 173 1.1 christos bool deferred_work_generated = false; 174 1.1 christos 175 1.1 christos for (size_t i = 0; i < 100 * 1000; i++) { 176 1.1 christos size_t operation = prng_range_zu(&prng_state, 2); 177 1.1 christos if (operation == 0) { 178 1.1 christos /* Alloc */ 179 1.1 christos if (nlive_edatas == nlive_edatas_max) { 180 1.1 christos continue; 181 1.1 christos } 182 1.1 christos 183 1.1 christos /* 184 1.1 christos * We make sure to get an even balance of small and 185 1.1 christos * large allocations. 186 1.1 christos */ 187 1.1 christos size_t npages_min = 1; 188 1.1 christos size_t npages_max = ALLOC_MAX / PAGE; 189 1.1 christos size_t npages = npages_min + prng_range_zu(&prng_state, 190 1.1 christos npages_max - npages_min); 191 1.1 christos edata_t *edata = pai_alloc(tsdn, &shard->pai, 192 1.1 christos npages * PAGE, PAGE, false, false, false, 193 1.1 christos &deferred_work_generated); 194 1.1 christos assert_ptr_not_null(edata, 195 1.1 christos "Unexpected allocation failure"); 196 1.1 christos live_edatas[nlive_edatas] = edata; 197 1.1 christos nlive_edatas++; 198 1.1 christos node_insert(&tree, edata, npages); 199 1.1 christos } else { 200 1.1 christos /* Free. */ 201 1.1 christos if (nlive_edatas == 0) { 202 1.1 christos continue; 203 1.1 christos } 204 1.1 christos size_t victim = prng_range_zu(&prng_state, nlive_edatas); 205 1.1 christos edata_t *to_free = live_edatas[victim]; 206 1.1 christos live_edatas[victim] = live_edatas[nlive_edatas - 1]; 207 1.1 christos nlive_edatas--; 208 1.1 christos node_remove(&tree, to_free); 209 1.1 christos pai_dalloc(tsdn, &shard->pai, to_free, 210 1.1 christos &deferred_work_generated); 211 1.1 christos } 212 1.1 christos } 213 1.1 christos 214 1.1 christos size_t ntreenodes = 0; 215 1.1 christos for (mem_contents_t *contents = mem_tree_first(&tree); contents != NULL; 216 1.1 christos contents = mem_tree_next(&tree, contents)) { 217 1.1 christos ntreenodes++; 218 1.1 christos node_check(&tree, contents); 219 1.1 christos } 220 1.1 christos expect_zu_eq(ntreenodes, nlive_edatas, ""); 221 1.1 christos 222 1.1 christos /* 223 1.1 christos * Test hpa_shard_destroy, which requires as a precondition that all its 224 1.1 christos * extents have been deallocated. 225 1.1 christos */ 226 1.1 christos for (size_t i = 0; i < nlive_edatas; i++) { 227 1.1 christos edata_t *to_free = live_edatas[i]; 228 1.1 christos node_remove(&tree, to_free); 229 1.1 christos pai_dalloc(tsdn, &shard->pai, to_free, 230 1.1 christos &deferred_work_generated); 231 1.1 christos } 232 1.1 christos hpa_shard_destroy(tsdn, shard); 233 1.1 christos 234 1.1 christos free(live_edatas); 235 1.1 christos destroy_test_data(shard); 236 1.1 christos } 237 1.1 christos TEST_END 238 1.1 christos 239 1.1 christos static void 240 1.1 christos expect_contiguous(edata_t **edatas, size_t nedatas) { 241 1.1 christos for (size_t i = 0; i < nedatas; i++) { 242 1.1 christos size_t expected = (size_t)edata_base_get(edatas[0]) 243 1.1 christos + i * PAGE; 244 1.1 christos expect_zu_eq(expected, (size_t)edata_base_get(edatas[i]), 245 1.1 christos "Mismatch at index %zu", i); 246 1.1 christos } 247 1.1 christos } 248 1.1 christos 249 1.1 christos TEST_BEGIN(test_alloc_dalloc_batch) { 250 1.1 christos test_skip_if(!hpa_supported()); 251 1.1 christos 252 1.1 christos hpa_shard_t *shard = create_test_data(&hpa_hooks_default, 253 1.1 christos &test_hpa_shard_opts_default); 254 1.1 christos tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); 255 1.1 christos 256 1.1 christos bool deferred_work_generated = false; 257 1.1 christos 258 1.1 christos enum {NALLOCS = 8}; 259 1.1 christos 260 1.1 christos edata_t *allocs[NALLOCS]; 261 1.1 christos /* 262 1.1 christos * Allocate a mix of ways; first half from regular alloc, second half 263 1.1 christos * from alloc_batch. 264 1.1 christos */ 265 1.1 christos for (size_t i = 0; i < NALLOCS / 2; i++) { 266 1.1 christos allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, 267 1.1 christos /* zero */ false, /* guarded */ false, 268 1.1 christos /* frequent_reuse */ false, &deferred_work_generated); 269 1.1 christos expect_ptr_not_null(allocs[i], "Unexpected alloc failure"); 270 1.1 christos } 271 1.1 christos edata_list_active_t allocs_list; 272 1.1 christos edata_list_active_init(&allocs_list); 273 1.1 christos size_t nsuccess = pai_alloc_batch(tsdn, &shard->pai, PAGE, NALLOCS / 2, 274 1.1 christos &allocs_list, &deferred_work_generated); 275 1.1 christos expect_zu_eq(NALLOCS / 2, nsuccess, "Unexpected oom"); 276 1.1 christos for (size_t i = NALLOCS / 2; i < NALLOCS; i++) { 277 1.1 christos allocs[i] = edata_list_active_first(&allocs_list); 278 1.1 christos edata_list_active_remove(&allocs_list, allocs[i]); 279 1.1 christos } 280 1.1 christos 281 1.1 christos /* 282 1.1 christos * Should have allocated them contiguously, despite the differing 283 1.1 christos * methods used. 284 1.1 christos */ 285 1.1 christos void *orig_base = edata_base_get(allocs[0]); 286 1.1 christos expect_contiguous(allocs, NALLOCS); 287 1.1 christos 288 1.1 christos /* 289 1.1 christos * Batch dalloc the first half, individually deallocate the second half. 290 1.1 christos */ 291 1.1 christos for (size_t i = 0; i < NALLOCS / 2; i++) { 292 1.1 christos edata_list_active_append(&allocs_list, allocs[i]); 293 1.1 christos } 294 1.1 christos pai_dalloc_batch(tsdn, &shard->pai, &allocs_list, 295 1.1 christos &deferred_work_generated); 296 1.1 christos for (size_t i = NALLOCS / 2; i < NALLOCS; i++) { 297 1.1 christos pai_dalloc(tsdn, &shard->pai, allocs[i], 298 1.1 christos &deferred_work_generated); 299 1.1 christos } 300 1.1 christos 301 1.1 christos /* Reallocate (individually), and ensure reuse and contiguity. */ 302 1.1 christos for (size_t i = 0; i < NALLOCS; i++) { 303 1.1 christos allocs[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, 304 1.1 christos /* zero */ false, /* guarded */ false, /* frequent_reuse */ 305 1.1 christos false, &deferred_work_generated); 306 1.1 christos expect_ptr_not_null(allocs[i], "Unexpected alloc failure."); 307 1.1 christos } 308 1.1 christos void *new_base = edata_base_get(allocs[0]); 309 1.1 christos expect_ptr_eq(orig_base, new_base, 310 1.1 christos "Failed to reuse the allocated memory."); 311 1.1 christos expect_contiguous(allocs, NALLOCS); 312 1.1 christos 313 1.1 christos destroy_test_data(shard); 314 1.1 christos } 315 1.1 christos TEST_END 316 1.1 christos 317 1.1 christos static uintptr_t defer_bump_ptr = HUGEPAGE * 123; 318 1.1 christos static void * 319 1.1 christos defer_test_map(size_t size) { 320 1.1 christos void *result = (void *)defer_bump_ptr; 321 1.1 christos defer_bump_ptr += size; 322 1.1 christos return result; 323 1.1 christos } 324 1.1 christos 325 1.1 christos static void 326 1.1 christos defer_test_unmap(void *ptr, size_t size) { 327 1.1 christos (void)ptr; 328 1.1 christos (void)size; 329 1.1 christos } 330 1.1 christos 331 1.1 christos static bool defer_purge_called = false; 332 1.1 christos static void 333 1.1 christos defer_test_purge(void *ptr, size_t size) { 334 1.1 christos (void)ptr; 335 1.1 christos (void)size; 336 1.1 christos defer_purge_called = true; 337 1.1 christos } 338 1.1 christos 339 1.1 christos static bool defer_hugify_called = false; 340 1.1 christos static void 341 1.1 christos defer_test_hugify(void *ptr, size_t size) { 342 1.1 christos defer_hugify_called = true; 343 1.1 christos } 344 1.1 christos 345 1.1 christos static bool defer_dehugify_called = false; 346 1.1 christos static void 347 1.1 christos defer_test_dehugify(void *ptr, size_t size) { 348 1.1 christos defer_dehugify_called = true; 349 1.1 christos } 350 1.1 christos 351 1.1 christos static nstime_t defer_curtime; 352 1.1 christos static void 353 1.1 christos defer_test_curtime(nstime_t *r_time, bool first_reading) { 354 1.1 christos *r_time = defer_curtime; 355 1.1 christos } 356 1.1 christos 357 1.1 christos static uint64_t 358 1.1 christos defer_test_ms_since(nstime_t *past_time) { 359 1.1 christos return (nstime_ns(&defer_curtime) - nstime_ns(past_time)) / 1000 / 1000; 360 1.1 christos } 361 1.1 christos 362 1.1 christos TEST_BEGIN(test_defer_time) { 363 1.1 christos test_skip_if(!hpa_supported()); 364 1.1 christos 365 1.1 christos hpa_hooks_t hooks; 366 1.1 christos hooks.map = &defer_test_map; 367 1.1 christos hooks.unmap = &defer_test_unmap; 368 1.1 christos hooks.purge = &defer_test_purge; 369 1.1 christos hooks.hugify = &defer_test_hugify; 370 1.1 christos hooks.dehugify = &defer_test_dehugify; 371 1.1 christos hooks.curtime = &defer_test_curtime; 372 1.1 christos hooks.ms_since = &defer_test_ms_since; 373 1.1 christos 374 1.1 christos hpa_shard_opts_t opts = test_hpa_shard_opts_default; 375 1.1 christos opts.deferral_allowed = true; 376 1.1 christos 377 1.1 christos hpa_shard_t *shard = create_test_data(&hooks, &opts); 378 1.1 christos 379 1.1 christos bool deferred_work_generated = false; 380 1.1 christos 381 1.1 christos nstime_init(&defer_curtime, 0); 382 1.1 christos tsdn_t *tsdn = tsd_tsdn(tsd_fetch()); 383 1.1 christos edata_t *edatas[HUGEPAGE_PAGES]; 384 1.1 christos for (int i = 0; i < (int)HUGEPAGE_PAGES; i++) { 385 1.1 christos edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false, 386 1.1 christos false, false, &deferred_work_generated); 387 1.1 christos expect_ptr_not_null(edatas[i], "Unexpected null edata"); 388 1.1 christos } 389 1.1 christos hpa_shard_do_deferred_work(tsdn, shard); 390 1.1 christos expect_false(defer_hugify_called, "Hugified too early"); 391 1.1 christos 392 1.1 christos /* Hugification delay is set to 10 seconds in options. */ 393 1.1 christos nstime_init2(&defer_curtime, 11, 0); 394 1.1 christos hpa_shard_do_deferred_work(tsdn, shard); 395 1.1 christos expect_true(defer_hugify_called, "Failed to hugify"); 396 1.1 christos 397 1.1 christos defer_hugify_called = false; 398 1.1 christos 399 1.1 christos /* Purge. Recall that dirty_mult is .25. */ 400 1.1 christos for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) { 401 1.1 christos pai_dalloc(tsdn, &shard->pai, edatas[i], 402 1.1 christos &deferred_work_generated); 403 1.1 christos } 404 1.1 christos 405 1.1 christos hpa_shard_do_deferred_work(tsdn, shard); 406 1.1 christos 407 1.1 christos expect_false(defer_hugify_called, "Hugified too early"); 408 1.1 christos expect_true(defer_dehugify_called, "Should have dehugified"); 409 1.1 christos expect_true(defer_purge_called, "Should have purged"); 410 1.1 christos defer_hugify_called = false; 411 1.1 christos defer_dehugify_called = false; 412 1.1 christos defer_purge_called = false; 413 1.1 christos 414 1.1 christos /* 415 1.1 christos * Refill the page. We now meet the hugification threshold; we should 416 1.1 christos * be marked for pending hugify. 417 1.1 christos */ 418 1.1 christos for (int i = 0; i < (int)HUGEPAGE_PAGES / 2; i++) { 419 1.1 christos edatas[i] = pai_alloc(tsdn, &shard->pai, PAGE, PAGE, false, 420 1.1 christos false, false, &deferred_work_generated); 421 1.1 christos expect_ptr_not_null(edatas[i], "Unexpected null edata"); 422 1.1 christos } 423 1.1 christos /* 424 1.1 christos * We would be ineligible for hugification, had we not already met the 425 1.1 christos * threshold before dipping below it. 426 1.1 christos */ 427 1.1 christos pai_dalloc(tsdn, &shard->pai, edatas[0], 428 1.1 christos &deferred_work_generated); 429 1.1 christos /* Wait for the threshold again. */ 430 1.1 christos nstime_init2(&defer_curtime, 22, 0); 431 1.1 christos hpa_shard_do_deferred_work(tsdn, shard); 432 1.1 christos expect_true(defer_hugify_called, "Hugified too early"); 433 1.1 christos expect_false(defer_dehugify_called, "Unexpected dehugify"); 434 1.1 christos expect_false(defer_purge_called, "Unexpected purge"); 435 1.1 christos 436 1.1 christos destroy_test_data(shard); 437 1.1 christos } 438 1.1 christos TEST_END 439 1.1 christos 440 1.1 christos int 441 1.1 christos main(void) { 442 1.1 christos /* 443 1.1 christos * These trigger unused-function warnings on CI runs, even if declared 444 1.1 christos * with static inline. 445 1.1 christos */ 446 1.1 christos (void)mem_tree_empty; 447 1.1 christos (void)mem_tree_last; 448 1.1 christos (void)mem_tree_search; 449 1.1 christos (void)mem_tree_nsearch; 450 1.1 christos (void)mem_tree_psearch; 451 1.1 christos (void)mem_tree_iter; 452 1.1 christos (void)mem_tree_reverse_iter; 453 1.1 christos (void)mem_tree_destroy; 454 1.1 christos return test_no_reentrancy( 455 1.1 christos test_alloc_max, 456 1.1 christos test_stress, 457 1.1 christos test_alloc_dalloc_batch, 458 1.1 christos test_defer_time); 459 1.1 christos } 460