Home | History | Annotate | Line # | Download | only in unit
      1 #include "test/jemalloc_test.h"
      2 
      3 #include "jemalloc/internal/psset.h"
      4 
      5 #define PAGESLAB_ADDR ((void *)(1234 * HUGEPAGE))
      6 #define PAGESLAB_AGE 5678
      7 
      8 #define ALLOC_ARENA_IND 111
      9 #define ALLOC_ESN 222
     10 
     11 static void
     12 edata_init_test(edata_t *edata) {
     13 	memset(edata, 0, sizeof(*edata));
     14 	edata_arena_ind_set(edata, ALLOC_ARENA_IND);
     15 	edata_esn_set(edata, ALLOC_ESN);
     16 }
     17 
     18 static void
     19 test_psset_fake_purge(hpdata_t *ps) {
     20 	hpdata_purge_state_t purge_state;
     21 	hpdata_alloc_allowed_set(ps, false);
     22 	hpdata_purge_begin(ps, &purge_state);
     23 	void *addr;
     24 	size_t size;
     25 	while (hpdata_purge_next(ps, &purge_state, &addr, &size)) {
     26 	}
     27 	hpdata_purge_end(ps, &purge_state);
     28 	hpdata_alloc_allowed_set(ps, true);
     29 }
     30 
     31 static void
     32 test_psset_alloc_new(psset_t *psset, hpdata_t *ps, edata_t *r_edata,
     33     size_t size) {
     34 	hpdata_assert_empty(ps);
     35 
     36 	test_psset_fake_purge(ps);
     37 
     38 	psset_insert(psset, ps);
     39 	psset_update_begin(psset, ps);
     40 
     41         void *addr = hpdata_reserve_alloc(ps, size);
     42         edata_init(r_edata, edata_arena_ind_get(r_edata), addr, size,
     43 	    /* slab */ false, SC_NSIZES, /* sn */ 0, extent_state_active,
     44             /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA,
     45             EXTENT_NOT_HEAD);
     46         edata_ps_set(r_edata, ps);
     47 	psset_update_end(psset, ps);
     48 }
     49 
     50 static bool
     51 test_psset_alloc_reuse(psset_t *psset, edata_t *r_edata, size_t size) {
     52 	hpdata_t *ps = psset_pick_alloc(psset, size);
     53 	if (ps == NULL) {
     54 		return true;
     55 	}
     56 	psset_update_begin(psset, ps);
     57 	void *addr = hpdata_reserve_alloc(ps, size);
     58 	edata_init(r_edata, edata_arena_ind_get(r_edata), addr, size,
     59 	    /* slab */ false, SC_NSIZES, /* sn */ 0, extent_state_active,
     60 	    /* zeroed */ false, /* committed */ true, EXTENT_PAI_HPA,
     61 	    EXTENT_NOT_HEAD);
     62 	edata_ps_set(r_edata, ps);
     63 	psset_update_end(psset, ps);
     64 	return false;
     65 }
     66 
     67 static hpdata_t *
     68 test_psset_dalloc(psset_t *psset, edata_t *edata) {
     69 	hpdata_t *ps = edata_ps_get(edata);
     70 	psset_update_begin(psset, ps);
     71 	hpdata_unreserve(ps, edata_addr_get(edata), edata_size_get(edata));
     72 	psset_update_end(psset, ps);
     73 	if (hpdata_empty(ps)) {
     74 		psset_remove(psset, ps);
     75 		return ps;
     76 	} else {
     77 		return NULL;
     78 	}
     79 }
     80 
     81 static void
     82 edata_expect(edata_t *edata, size_t page_offset, size_t page_cnt) {
     83 	/*
     84 	 * Note that allocations should get the arena ind of their home
     85 	 * arena, *not* the arena ind of the pageslab allocator.
     86 	 */
     87 	expect_u_eq(ALLOC_ARENA_IND, edata_arena_ind_get(edata),
     88 	    "Arena ind changed");
     89 	expect_ptr_eq(
     90 	    (void *)((uintptr_t)PAGESLAB_ADDR + (page_offset << LG_PAGE)),
     91 	    edata_addr_get(edata), "Didn't allocate in order");
     92 	expect_zu_eq(page_cnt << LG_PAGE, edata_size_get(edata), "");
     93 	expect_false(edata_slab_get(edata), "");
     94 	expect_u_eq(SC_NSIZES, edata_szind_get_maybe_invalid(edata),
     95 	    "");
     96 	expect_u64_eq(0, edata_sn_get(edata), "");
     97 	expect_d_eq(edata_state_get(edata), extent_state_active, "");
     98 	expect_false(edata_zeroed_get(edata), "");
     99 	expect_true(edata_committed_get(edata), "");
    100 	expect_d_eq(EXTENT_PAI_HPA, edata_pai_get(edata), "");
    101 	expect_false(edata_is_head_get(edata), "");
    102 }
    103 
    104 TEST_BEGIN(test_empty) {
    105 	bool err;
    106 	hpdata_t pageslab;
    107 	hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
    108 
    109 	edata_t alloc;
    110 	edata_init_test(&alloc);
    111 
    112 	psset_t psset;
    113 	psset_init(&psset);
    114 
    115 	/* Empty psset should return fail allocations. */
    116 	err = test_psset_alloc_reuse(&psset, &alloc, PAGE);
    117 	expect_true(err, "Empty psset succeeded in an allocation.");
    118 }
    119 TEST_END
    120 
    121 TEST_BEGIN(test_fill) {
    122 	bool err;
    123 
    124 	hpdata_t pageslab;
    125 	hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
    126 
    127 	edata_t alloc[HUGEPAGE_PAGES];
    128 
    129 	psset_t psset;
    130 	psset_init(&psset);
    131 
    132 	edata_init_test(&alloc[0]);
    133 	test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
    134 	for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
    135 		edata_init_test(&alloc[i]);
    136 		err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
    137 		expect_false(err, "Nonempty psset failed page allocation.");
    138 	}
    139 
    140 	for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
    141 		edata_t *edata = &alloc[i];
    142 		edata_expect(edata, i, 1);
    143 	}
    144 
    145 	/* The pageslab, and thus psset, should now have no allocations. */
    146 	edata_t extra_alloc;
    147 	edata_init_test(&extra_alloc);
    148 	err = test_psset_alloc_reuse(&psset, &extra_alloc, PAGE);
    149 	expect_true(err, "Alloc succeeded even though psset should be empty");
    150 }
    151 TEST_END
    152 
    153 TEST_BEGIN(test_reuse) {
    154 	bool err;
    155 	hpdata_t *ps;
    156 
    157 	hpdata_t pageslab;
    158 	hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
    159 
    160 	edata_t alloc[HUGEPAGE_PAGES];
    161 
    162 	psset_t psset;
    163 	psset_init(&psset);
    164 
    165 	edata_init_test(&alloc[0]);
    166 	test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
    167 	for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
    168 		edata_init_test(&alloc[i]);
    169 		err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
    170 		expect_false(err, "Nonempty psset failed page allocation.");
    171 	}
    172 
    173 	/* Free odd indices. */
    174 	for (size_t i = 0; i < HUGEPAGE_PAGES; i ++) {
    175 		if (i % 2 == 0) {
    176 			continue;
    177 		}
    178 		ps = test_psset_dalloc(&psset, &alloc[i]);
    179 		expect_ptr_null(ps, "Nonempty pageslab evicted");
    180 	}
    181 	/* Realloc into them. */
    182 	for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
    183 		if (i % 2 == 0) {
    184 			continue;
    185 		}
    186 		err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
    187 		expect_false(err, "Nonempty psset failed page allocation.");
    188 		edata_expect(&alloc[i], i, 1);
    189 	}
    190 	/* Now, free the pages at indices 0 or 1 mod 2. */
    191 	for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
    192 		if (i % 4 > 1) {
    193 			continue;
    194 		}
    195 		ps = test_psset_dalloc(&psset, &alloc[i]);
    196 		expect_ptr_null(ps, "Nonempty pageslab evicted");
    197 	}
    198 	/* And realloc 2-page allocations into them. */
    199 	for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
    200 		if (i % 4 != 0) {
    201 			continue;
    202 		}
    203 		err = test_psset_alloc_reuse(&psset, &alloc[i], 2 * PAGE);
    204 		expect_false(err, "Nonempty psset failed page allocation.");
    205 		edata_expect(&alloc[i], i, 2);
    206 	}
    207 	/* Free all the 2-page allocations. */
    208 	for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
    209 		if (i % 4 != 0) {
    210 			continue;
    211 		}
    212 		ps = test_psset_dalloc(&psset, &alloc[i]);
    213 		expect_ptr_null(ps, "Nonempty pageslab evicted");
    214 	}
    215 	/*
    216 	 * Free up a 1-page hole next to a 2-page hole, but somewhere in the
    217 	 * middle of the pageslab.  Index 11 should be right before such a hole
    218 	 * (since 12 % 4 == 0).
    219 	 */
    220 	size_t index_of_3 = 11;
    221 	ps = test_psset_dalloc(&psset, &alloc[index_of_3]);
    222 	expect_ptr_null(ps, "Nonempty pageslab evicted");
    223 	err = test_psset_alloc_reuse(&psset, &alloc[index_of_3], 3 * PAGE);
    224 	expect_false(err, "Should have been able to find alloc.");
    225 	edata_expect(&alloc[index_of_3], index_of_3, 3);
    226 
    227 	/*
    228 	 * Free up a 4-page hole at the end.  Recall that the pages at offsets 0
    229 	 * and 1 mod 4 were freed above, so we just have to free the last
    230 	 * allocations.
    231 	 */
    232 	ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]);
    233 	expect_ptr_null(ps, "Nonempty pageslab evicted");
    234 	ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 2]);
    235 	expect_ptr_null(ps, "Nonempty pageslab evicted");
    236 
    237 	/* Make sure we can satisfy an allocation at the very end of a slab. */
    238 	size_t index_of_4 = HUGEPAGE_PAGES - 4;
    239 	err = test_psset_alloc_reuse(&psset, &alloc[index_of_4], 4 * PAGE);
    240 	expect_false(err, "Should have been able to find alloc.");
    241 	edata_expect(&alloc[index_of_4], index_of_4, 4);
    242 }
    243 TEST_END
    244 
    245 TEST_BEGIN(test_evict) {
    246 	bool err;
    247 	hpdata_t *ps;
    248 
    249 	hpdata_t pageslab;
    250 	hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
    251 
    252 	edata_t alloc[HUGEPAGE_PAGES];
    253 
    254 	psset_t psset;
    255 	psset_init(&psset);
    256 
    257 	/* Alloc the whole slab. */
    258 	edata_init_test(&alloc[0]);
    259 	test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
    260 	for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
    261 		edata_init_test(&alloc[i]);
    262 		err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
    263 		expect_false(err, "Unxpected allocation failure");
    264 	}
    265 
    266 	/* Dealloc the whole slab, going forwards. */
    267 	for (size_t i = 0; i < HUGEPAGE_PAGES - 1; i++) {
    268 		ps = test_psset_dalloc(&psset, &alloc[i]);
    269 		expect_ptr_null(ps, "Nonempty pageslab evicted");
    270 	}
    271 	ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]);
    272 	expect_ptr_eq(&pageslab, ps, "Empty pageslab not evicted.");
    273 
    274 	err = test_psset_alloc_reuse(&psset, &alloc[0], PAGE);
    275 	expect_true(err, "psset should be empty.");
    276 }
    277 TEST_END
    278 
    279 TEST_BEGIN(test_multi_pageslab) {
    280 	bool err;
    281 	hpdata_t *ps;
    282 
    283 	hpdata_t pageslab[2];
    284 	hpdata_init(&pageslab[0], PAGESLAB_ADDR, PAGESLAB_AGE);
    285 	hpdata_init(&pageslab[1],
    286 	    (void *)((uintptr_t)PAGESLAB_ADDR + HUGEPAGE),
    287 	    PAGESLAB_AGE + 1);
    288 
    289 	edata_t alloc[2][HUGEPAGE_PAGES];
    290 
    291 	psset_t psset;
    292 	psset_init(&psset);
    293 
    294 	/* Insert both slabs. */
    295 	edata_init_test(&alloc[0][0]);
    296 	test_psset_alloc_new(&psset, &pageslab[0], &alloc[0][0], PAGE);
    297 	edata_init_test(&alloc[1][0]);
    298 	test_psset_alloc_new(&psset, &pageslab[1], &alloc[1][0], PAGE);
    299 
    300 	/* Fill them both up; make sure we do so in first-fit order. */
    301 	for (size_t i = 0; i < 2; i++) {
    302 		for (size_t j = 1; j < HUGEPAGE_PAGES; j++) {
    303 			edata_init_test(&alloc[i][j]);
    304 			err = test_psset_alloc_reuse(&psset, &alloc[i][j], PAGE);
    305 			expect_false(err,
    306 			    "Nonempty psset failed page allocation.");
    307 			assert_ptr_eq(&pageslab[i], edata_ps_get(&alloc[i][j]),
    308 			    "Didn't pick pageslabs in first-fit");
    309 		}
    310 	}
    311 
    312 	/*
    313 	 * Free up a 2-page hole in the earlier slab, and a 1-page one in the
    314 	 * later one.  We should still pick the later one.
    315 	 */
    316 	ps = test_psset_dalloc(&psset, &alloc[0][0]);
    317 	expect_ptr_null(ps, "Unexpected eviction");
    318 	ps = test_psset_dalloc(&psset, &alloc[0][1]);
    319 	expect_ptr_null(ps, "Unexpected eviction");
    320 	ps = test_psset_dalloc(&psset, &alloc[1][0]);
    321 	expect_ptr_null(ps, "Unexpected eviction");
    322 	err = test_psset_alloc_reuse(&psset, &alloc[0][0], PAGE);
    323 	expect_ptr_eq(&pageslab[1], edata_ps_get(&alloc[0][0]),
    324 	    "Should have picked the fuller pageslab");
    325 
    326 	/*
    327 	 * Now both slabs have 1-page holes. Free up a second one in the later
    328 	 * slab.
    329 	 */
    330 	ps = test_psset_dalloc(&psset, &alloc[1][1]);
    331 	expect_ptr_null(ps, "Unexpected eviction");
    332 
    333 	/*
    334 	 * We should be able to allocate a 2-page object, even though an earlier
    335 	 * size class is nonempty.
    336 	 */
    337 	err = test_psset_alloc_reuse(&psset, &alloc[1][0], 2 * PAGE);
    338 	expect_false(err, "Allocation should have succeeded");
    339 }
    340 TEST_END
    341 
    342 static void
    343 stats_expect_empty(psset_bin_stats_t *stats) {
    344 	assert_zu_eq(0, stats->npageslabs,
    345 	    "Supposedly empty bin had positive npageslabs");
    346 	expect_zu_eq(0, stats->nactive, "Unexpected nonempty bin"
    347 	    "Supposedly empty bin had positive nactive");
    348 }
    349 
    350 static void
    351 stats_expect(psset_t *psset, size_t nactive) {
    352 	if (nactive == HUGEPAGE_PAGES) {
    353 		expect_zu_eq(1, psset->stats.full_slabs[0].npageslabs,
    354 		    "Expected a full slab");
    355 		expect_zu_eq(HUGEPAGE_PAGES,
    356 		    psset->stats.full_slabs[0].nactive,
    357 		    "Should have exactly filled the bin");
    358 	} else {
    359 		stats_expect_empty(&psset->stats.full_slabs[0]);
    360 	}
    361 	size_t ninactive = HUGEPAGE_PAGES - nactive;
    362 	pszind_t nonempty_pind = PSSET_NPSIZES;
    363 	if (ninactive != 0 && ninactive < HUGEPAGE_PAGES) {
    364 		nonempty_pind = sz_psz2ind(sz_psz_quantize_floor(
    365 		    ninactive << LG_PAGE));
    366 	}
    367 	for (pszind_t i = 0; i < PSSET_NPSIZES; i++) {
    368 		if (i == nonempty_pind) {
    369 			assert_zu_eq(1,
    370 			    psset->stats.nonfull_slabs[i][0].npageslabs,
    371 			    "Should have found a slab");
    372 			expect_zu_eq(nactive,
    373 			    psset->stats.nonfull_slabs[i][0].nactive,
    374 			    "Mismatch in active pages");
    375 		} else {
    376 			stats_expect_empty(&psset->stats.nonfull_slabs[i][0]);
    377 		}
    378 	}
    379 	expect_zu_eq(nactive, psset_nactive(psset), "");
    380 }
    381 
    382 TEST_BEGIN(test_stats) {
    383 	bool err;
    384 
    385 	hpdata_t pageslab;
    386 	hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
    387 
    388 	edata_t alloc[HUGEPAGE_PAGES];
    389 
    390 	psset_t psset;
    391 	psset_init(&psset);
    392 	stats_expect(&psset, 0);
    393 
    394 	edata_init_test(&alloc[0]);
    395 	test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
    396 	for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
    397 		stats_expect(&psset, i);
    398 		edata_init_test(&alloc[i]);
    399 		err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
    400 		expect_false(err, "Nonempty psset failed page allocation.");
    401 	}
    402 	stats_expect(&psset, HUGEPAGE_PAGES);
    403 	hpdata_t *ps;
    404 	for (ssize_t i = HUGEPAGE_PAGES - 1; i >= 0; i--) {
    405 		ps = test_psset_dalloc(&psset, &alloc[i]);
    406 		expect_true((ps == NULL) == (i != 0),
    407 		    "test_psset_dalloc should only evict a slab on the last "
    408 		    "free");
    409 		stats_expect(&psset, i);
    410 	}
    411 
    412 	test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
    413 	stats_expect(&psset, 1);
    414 	psset_update_begin(&psset, &pageslab);
    415 	stats_expect(&psset, 0);
    416 	psset_update_end(&psset, &pageslab);
    417 	stats_expect(&psset, 1);
    418 }
    419 TEST_END
    420 
    421 /*
    422  * Fills in and inserts two pageslabs, with the first better than the second,
    423  * and each fully allocated (into the allocations in allocs and worse_allocs,
    424  * each of which should be HUGEPAGE_PAGES long), except for a single free page
    425  * at the end.
    426  *
    427  * (There's nothing magic about these numbers; it's just useful to share the
    428  * setup between the oldest fit and the insert/remove test).
    429  */
    430 static void
    431 init_test_pageslabs(psset_t *psset, hpdata_t *pageslab,
    432     hpdata_t *worse_pageslab, edata_t *alloc, edata_t *worse_alloc) {
    433 	bool err;
    434 
    435 	hpdata_init(pageslab, (void *)(10 * HUGEPAGE), PAGESLAB_AGE);
    436 	/*
    437 	 * This pageslab would be better from an address-first-fit POV, but
    438 	 * worse from an age POV.
    439 	 */
    440 	hpdata_init(worse_pageslab, (void *)(9 * HUGEPAGE), PAGESLAB_AGE + 1);
    441 
    442 	psset_init(psset);
    443 
    444 	edata_init_test(&alloc[0]);
    445 	test_psset_alloc_new(psset, pageslab, &alloc[0], PAGE);
    446 	for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
    447 		edata_init_test(&alloc[i]);
    448 		err = test_psset_alloc_reuse(psset, &alloc[i], PAGE);
    449 		expect_false(err, "Nonempty psset failed page allocation.");
    450 		expect_ptr_eq(pageslab, edata_ps_get(&alloc[i]),
    451 		    "Allocated from the wrong pageslab");
    452 	}
    453 
    454 	edata_init_test(&worse_alloc[0]);
    455 	test_psset_alloc_new(psset, worse_pageslab, &worse_alloc[0], PAGE);
    456 	expect_ptr_eq(worse_pageslab, edata_ps_get(&worse_alloc[0]),
    457 	    "Allocated from the wrong pageslab");
    458 	/*
    459 	 * Make the two pssets otherwise indistinguishable; all full except for
    460 	 * a single page.
    461 	 */
    462 	for (size_t i = 1; i < HUGEPAGE_PAGES - 1; i++) {
    463 		edata_init_test(&worse_alloc[i]);
    464 		err = test_psset_alloc_reuse(psset, &alloc[i], PAGE);
    465 		expect_false(err, "Nonempty psset failed page allocation.");
    466 		expect_ptr_eq(worse_pageslab, edata_ps_get(&alloc[i]),
    467 		    "Allocated from the wrong pageslab");
    468 	}
    469 
    470 	/* Deallocate the last page from the older pageslab. */
    471 	hpdata_t *evicted = test_psset_dalloc(psset,
    472 	    &alloc[HUGEPAGE_PAGES - 1]);
    473 	expect_ptr_null(evicted, "Unexpected eviction");
    474 }
    475 
    476 TEST_BEGIN(test_oldest_fit) {
    477 	bool err;
    478 	edata_t alloc[HUGEPAGE_PAGES];
    479 	edata_t worse_alloc[HUGEPAGE_PAGES];
    480 
    481 	hpdata_t pageslab;
    482 	hpdata_t worse_pageslab;
    483 
    484 	psset_t psset;
    485 
    486 	init_test_pageslabs(&psset, &pageslab, &worse_pageslab, alloc,
    487 	    worse_alloc);
    488 
    489 	/* The edata should come from the better pageslab. */
    490 	edata_t test_edata;
    491 	edata_init_test(&test_edata);
    492 	err = test_psset_alloc_reuse(&psset, &test_edata, PAGE);
    493 	expect_false(err, "Nonempty psset failed page allocation");
    494 	expect_ptr_eq(&pageslab, edata_ps_get(&test_edata),
    495 	    "Allocated from the wrong pageslab");
    496 }
    497 TEST_END
    498 
    499 TEST_BEGIN(test_insert_remove) {
    500 	bool err;
    501 	hpdata_t *ps;
    502 	edata_t alloc[HUGEPAGE_PAGES];
    503 	edata_t worse_alloc[HUGEPAGE_PAGES];
    504 
    505 	hpdata_t pageslab;
    506 	hpdata_t worse_pageslab;
    507 
    508 	psset_t psset;
    509 
    510 	init_test_pageslabs(&psset, &pageslab, &worse_pageslab, alloc,
    511 	    worse_alloc);
    512 
    513 	/* Remove better; should still be able to alloc from worse. */
    514 	psset_update_begin(&psset, &pageslab);
    515 	err = test_psset_alloc_reuse(&psset, &worse_alloc[HUGEPAGE_PAGES - 1],
    516 	    PAGE);
    517 	expect_false(err, "Removal should still leave an empty page");
    518 	expect_ptr_eq(&worse_pageslab,
    519 	    edata_ps_get(&worse_alloc[HUGEPAGE_PAGES - 1]),
    520 	    "Allocated out of wrong ps");
    521 
    522 	/*
    523 	 * After deallocating the previous alloc and reinserting better, it
    524 	 * should be preferred for future allocations.
    525 	 */
    526 	ps = test_psset_dalloc(&psset, &worse_alloc[HUGEPAGE_PAGES - 1]);
    527 	expect_ptr_null(ps, "Incorrect eviction of nonempty pageslab");
    528 	psset_update_end(&psset, &pageslab);
    529 	err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE);
    530 	expect_false(err, "psset should be nonempty");
    531 	expect_ptr_eq(&pageslab, edata_ps_get(&alloc[HUGEPAGE_PAGES - 1]),
    532 	    "Removal/reinsertion shouldn't change ordering");
    533 	/*
    534 	 * After deallocating and removing both, allocations should fail.
    535 	 */
    536 	ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]);
    537 	expect_ptr_null(ps, "Incorrect eviction");
    538 	psset_update_begin(&psset, &pageslab);
    539 	psset_update_begin(&psset, &worse_pageslab);
    540 	err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE);
    541 	expect_true(err, "psset should be empty, but an alloc succeeded");
    542 }
    543 TEST_END
    544 
    545 TEST_BEGIN(test_purge_prefers_nonhuge) {
    546 	/*
    547 	 * All else being equal, we should prefer purging non-huge pages over
    548 	 * huge ones for non-empty extents.
    549 	 */
    550 
    551 	/* Nothing magic about this constant. */
    552 	enum {
    553 		NHP = 23,
    554 	};
    555 	hpdata_t *hpdata;
    556 
    557 	psset_t psset;
    558 	psset_init(&psset);
    559 
    560 	hpdata_t hpdata_huge[NHP];
    561 	uintptr_t huge_begin = (uintptr_t)&hpdata_huge[0];
    562 	uintptr_t huge_end = (uintptr_t)&hpdata_huge[NHP];
    563 	hpdata_t hpdata_nonhuge[NHP];
    564 	uintptr_t nonhuge_begin = (uintptr_t)&hpdata_nonhuge[0];
    565 	uintptr_t nonhuge_end = (uintptr_t)&hpdata_nonhuge[NHP];
    566 
    567 	for (size_t i = 0; i < NHP; i++) {
    568 		hpdata_init(&hpdata_huge[i], (void *)((10 + i) * HUGEPAGE),
    569 		    123 + i);
    570 		psset_insert(&psset, &hpdata_huge[i]);
    571 
    572 		hpdata_init(&hpdata_nonhuge[i],
    573 		    (void *)((10 + NHP + i) * HUGEPAGE),
    574 		    456 + i);
    575 		psset_insert(&psset, &hpdata_nonhuge[i]);
    576 
    577 	}
    578 	for (int i = 0; i < 2 * NHP; i++) {
    579 		hpdata = psset_pick_alloc(&psset, HUGEPAGE * 3 / 4);
    580 		psset_update_begin(&psset, hpdata);
    581 		void *ptr;
    582 		ptr = hpdata_reserve_alloc(hpdata, HUGEPAGE * 3 / 4);
    583 		/* Ignore the first alloc, which will stick around. */
    584 		(void)ptr;
    585 		/*
    586 		 * The second alloc is to dirty the pages; free it immediately
    587 		 * after allocating.
    588 		 */
    589 		ptr = hpdata_reserve_alloc(hpdata, HUGEPAGE / 4);
    590 		hpdata_unreserve(hpdata, ptr, HUGEPAGE / 4);
    591 
    592 		if (huge_begin <= (uintptr_t)hpdata
    593 		    && (uintptr_t)hpdata < huge_end) {
    594 			hpdata_hugify(hpdata);
    595 		}
    596 
    597 		hpdata_purge_allowed_set(hpdata, true);
    598 		psset_update_end(&psset, hpdata);
    599 	}
    600 
    601 	/*
    602 	 * We've got a bunch of 1/8th dirty hpdatas.  It should give us all the
    603 	 * non-huge ones to purge, then all the huge ones, then refuse to purge
    604 	 * further.
    605 	 */
    606 	for (int i = 0; i < NHP; i++) {
    607 		hpdata = psset_pick_purge(&psset);
    608 		assert_true(nonhuge_begin <= (uintptr_t)hpdata
    609 		    && (uintptr_t)hpdata < nonhuge_end, "");
    610 		psset_update_begin(&psset, hpdata);
    611 		test_psset_fake_purge(hpdata);
    612 		hpdata_purge_allowed_set(hpdata, false);
    613 		psset_update_end(&psset, hpdata);
    614 	}
    615 	for (int i = 0; i < NHP; i++) {
    616 		hpdata = psset_pick_purge(&psset);
    617 		expect_true(huge_begin <= (uintptr_t)hpdata
    618 		    && (uintptr_t)hpdata < huge_end, "");
    619 		psset_update_begin(&psset, hpdata);
    620 		hpdata_dehugify(hpdata);
    621 		test_psset_fake_purge(hpdata);
    622 		hpdata_purge_allowed_set(hpdata, false);
    623 		psset_update_end(&psset, hpdata);
    624 	}
    625 }
    626 TEST_END
    627 
    628 TEST_BEGIN(test_purge_prefers_empty) {
    629 	void *ptr;
    630 
    631 	psset_t psset;
    632 	psset_init(&psset);
    633 
    634 	hpdata_t hpdata_empty;
    635 	hpdata_t hpdata_nonempty;
    636 	hpdata_init(&hpdata_empty, (void *)(10 * HUGEPAGE), 123);
    637 	psset_insert(&psset, &hpdata_empty);
    638 	hpdata_init(&hpdata_nonempty, (void *)(11 * HUGEPAGE), 456);
    639 	psset_insert(&psset, &hpdata_nonempty);
    640 
    641 	psset_update_begin(&psset, &hpdata_empty);
    642 	ptr = hpdata_reserve_alloc(&hpdata_empty, PAGE);
    643 	expect_ptr_eq(hpdata_addr_get(&hpdata_empty), ptr, "");
    644 	hpdata_unreserve(&hpdata_empty, ptr, PAGE);
    645 	hpdata_purge_allowed_set(&hpdata_empty, true);
    646 	psset_update_end(&psset, &hpdata_empty);
    647 
    648 	psset_update_begin(&psset, &hpdata_nonempty);
    649 	ptr = hpdata_reserve_alloc(&hpdata_nonempty, 10 * PAGE);
    650 	expect_ptr_eq(hpdata_addr_get(&hpdata_nonempty), ptr, "");
    651 	hpdata_unreserve(&hpdata_nonempty, ptr, 9 * PAGE);
    652 	hpdata_purge_allowed_set(&hpdata_nonempty, true);
    653 	psset_update_end(&psset, &hpdata_nonempty);
    654 
    655 	/*
    656 	 * The nonempty slab has 9 dirty pages, while the empty one has only 1.
    657 	 * We should still pick the empty one for purging.
    658 	 */
    659 	hpdata_t *to_purge = psset_pick_purge(&psset);
    660 	expect_ptr_eq(&hpdata_empty, to_purge, "");
    661 }
    662 TEST_END
    663 
    664 TEST_BEGIN(test_purge_prefers_empty_huge) {
    665 	void *ptr;
    666 
    667 	psset_t psset;
    668 	psset_init(&psset);
    669 
    670 	enum {NHP = 10 };
    671 
    672 	hpdata_t hpdata_huge[NHP];
    673 	hpdata_t hpdata_nonhuge[NHP];
    674 
    675 	uintptr_t cur_addr = 100 * HUGEPAGE;
    676 	uint64_t cur_age = 123;
    677 	for (int i = 0; i < NHP; i++) {
    678 		hpdata_init(&hpdata_huge[i], (void *)cur_addr, cur_age);
    679 		cur_addr += HUGEPAGE;
    680 		cur_age++;
    681 		psset_insert(&psset, &hpdata_huge[i]);
    682 
    683 		hpdata_init(&hpdata_nonhuge[i], (void *)cur_addr, cur_age);
    684 		cur_addr += HUGEPAGE;
    685 		cur_age++;
    686 		psset_insert(&psset, &hpdata_nonhuge[i]);
    687 
    688 		/*
    689 		 * Make the hpdata_huge[i] fully dirty, empty, purgable, and
    690 		 * huge.
    691 		 */
    692 		psset_update_begin(&psset, &hpdata_huge[i]);
    693 		ptr = hpdata_reserve_alloc(&hpdata_huge[i], HUGEPAGE);
    694 		expect_ptr_eq(hpdata_addr_get(&hpdata_huge[i]), ptr, "");
    695 		hpdata_hugify(&hpdata_huge[i]);
    696 		hpdata_unreserve(&hpdata_huge[i], ptr, HUGEPAGE);
    697 		hpdata_purge_allowed_set(&hpdata_huge[i], true);
    698 		psset_update_end(&psset, &hpdata_huge[i]);
    699 
    700 		/*
    701 		 * Make hpdata_nonhuge[i] fully dirty, empty, purgable, and
    702 		 * non-huge.
    703 		 */
    704 		psset_update_begin(&psset, &hpdata_nonhuge[i]);
    705 		ptr = hpdata_reserve_alloc(&hpdata_nonhuge[i], HUGEPAGE);
    706 		expect_ptr_eq(hpdata_addr_get(&hpdata_nonhuge[i]), ptr, "");
    707 		hpdata_unreserve(&hpdata_nonhuge[i], ptr, HUGEPAGE);
    708 		hpdata_purge_allowed_set(&hpdata_nonhuge[i], true);
    709 		psset_update_end(&psset, &hpdata_nonhuge[i]);
    710 	}
    711 
    712 	/*
    713 	 * We have a bunch of empty slabs, half huge, half nonhuge, inserted in
    714 	 * alternating order.  We should pop all the huge ones before popping
    715 	 * any of the non-huge ones for purging.
    716 	 */
    717 	for (int i = 0; i < NHP; i++) {
    718 		hpdata_t *to_purge = psset_pick_purge(&psset);
    719 		expect_ptr_eq(&hpdata_huge[i], to_purge, "");
    720 		psset_update_begin(&psset, to_purge);
    721 		hpdata_purge_allowed_set(to_purge, false);
    722 		psset_update_end(&psset, to_purge);
    723 	}
    724 	for (int i = 0; i < NHP; i++) {
    725 		hpdata_t *to_purge = psset_pick_purge(&psset);
    726 		expect_ptr_eq(&hpdata_nonhuge[i], to_purge, "");
    727 		psset_update_begin(&psset, to_purge);
    728 		hpdata_purge_allowed_set(to_purge, false);
    729 		psset_update_end(&psset, to_purge);
    730 	}
    731 }
    732 TEST_END
    733 
    734 int
    735 main(void) {
    736 	return test_no_reentrancy(
    737 	    test_empty,
    738 	    test_fill,
    739 	    test_reuse,
    740 	    test_evict,
    741 	    test_multi_pageslab,
    742 	    test_stats,
    743 	    test_oldest_fit,
    744 	    test_insert_remove,
    745 	    test_purge_prefers_nonhuge,
    746 	    test_purge_prefers_empty,
    747 	    test_purge_prefers_empty_huge);
    748 }
    749