edata_init_test(&alloc[0]);
test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
edata_init_test(&alloc[i]);
err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
}
for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
edata_t *edata = &alloc[i];
edata_expect(edata, i, 1);
}
/* The pageslab, and thus psset, should now have no allocations. */
edata_t extra_alloc;
edata_init_test(&extra_alloc);
err = test_psset_alloc_reuse(&psset, &extra_alloc, PAGE);
expect_true(err, "Alloc succeeded even though psset should be empty");
}
TEST_END
edata_init_test(&alloc[0]);
test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
edata_init_test(&alloc[i]);
err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
}
/* Free odd indices. */
for (size_t i = 0; i < HUGEPAGE_PAGES; i ++) {
if (i % 2 == 0) {
continue;
}
ps = test_psset_dalloc(&psset, &alloc[i]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
}
/* Realloc into them. */
for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
if (i % 2 == 0) {
continue;
}
err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
edata_expect(&alloc[i], i, 1);
}
/* Now, free the pages at indices 0 or 1 mod 2. */
for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
if (i % 4 > 1) {
continue;
}
ps = test_psset_dalloc(&psset, &alloc[i]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
}
/* And realloc 2-page allocations into them. */
for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
if (i % 4 != 0) {
continue;
}
err = test_psset_alloc_reuse(&psset, &alloc[i], 2 * PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
edata_expect(&alloc[i], i, 2);
}
/* Free all the 2-page allocations. */
for (size_t i = 0; i < HUGEPAGE_PAGES; i++) {
if (i % 4 != 0) {
continue;
}
ps = test_psset_dalloc(&psset, &alloc[i]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
}
/*
* Free up a 1-page hole next to a 2-page hole, but somewhere in the
* middle of the pageslab. Index 11 should be right before such a hole
* (since 12 % 4 == 0).
*/
size_t index_of_3 = 11;
ps = test_psset_dalloc(&psset, &alloc[index_of_3]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
err = test_psset_alloc_reuse(&psset, &alloc[index_of_3], 3 * PAGE);
expect_false(err, "Should have been able to find alloc.");
edata_expect(&alloc[index_of_3], index_of_3, 3);
/*
* Free up a 4-page hole at the end. Recall that the pages at offsets 0
* and 1 mod 4 were freed above, so we just have to free the last
* allocations.
*/
ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 2]);
expect_ptr_null(ps, "Nonempty pageslab evicted");
/* Make sure we can satisfy an allocation at the very end of a slab. */
size_t index_of_4 = HUGEPAGE_PAGES - 4;
err = test_psset_alloc_reuse(&psset, &alloc[index_of_4], 4 * PAGE);
expect_false(err, "Should have been able to find alloc.");
edata_expect(&alloc[index_of_4], index_of_4, 4);
}
TEST_END
/* Fill them both up; make sure we do so in first-fit order. */
for (size_t i = 0; i < 2; i++) {
for (size_t j = 1; j < HUGEPAGE_PAGES; j++) {
edata_init_test(&alloc[i][j]);
err = test_psset_alloc_reuse(&psset, &alloc[i][j], PAGE);
expect_false(err,
"Nonempty psset failed page allocation.");
assert_ptr_eq(&pageslab[i], edata_ps_get(&alloc[i][j]),
"Didn't pick pageslabs in first-fit");
}
}
/*
* Free up a 2-page hole in the earlier slab, and a 1-page one in the
* later one. We should still pick the later one.
*/
ps = test_psset_dalloc(&psset, &alloc[0][0]);
expect_ptr_null(ps, "Unexpected eviction");
ps = test_psset_dalloc(&psset, &alloc[0][1]);
expect_ptr_null(ps, "Unexpected eviction");
ps = test_psset_dalloc(&psset, &alloc[1][0]);
expect_ptr_null(ps, "Unexpected eviction");
err = test_psset_alloc_reuse(&psset, &alloc[0][0], PAGE);
expect_ptr_eq(&pageslab[1], edata_ps_get(&alloc[0][0]),
"Should have picked the fuller pageslab");
/*
* Now both slabs have 1-page holes. Free up a second one in the later
* slab.
*/
ps = test_psset_dalloc(&psset, &alloc[1][1]);
expect_ptr_null(ps, "Unexpected eviction");
/*
* We should be able to allocate a 2-page object, even though an earlier
* size class is nonempty.
*/
err = test_psset_alloc_reuse(&psset, &alloc[1][0], 2 * PAGE);
expect_false(err, "Allocation should have succeeded");
}
TEST_END
static void
stats_expect_empty(psset_bin_stats_t *stats) {
assert_zu_eq(0, stats->npageslabs,
"Supposedly empty bin had positive npageslabs");
expect_zu_eq(0, stats->nactive, "Unexpected nonempty bin"
"Supposedly empty bin had positive nactive");
}
static void
stats_expect(psset_t *psset, size_t nactive) {
if (nactive == HUGEPAGE_PAGES) {
expect_zu_eq(1, psset->stats.full_slabs[0].npageslabs,
"Expected a full slab");
expect_zu_eq(HUGEPAGE_PAGES,
psset->stats.full_slabs[0].nactive,
"Should have exactly filled the bin");
} else {
stats_expect_empty(&psset->stats.full_slabs[0]);
}
size_t ninactive = HUGEPAGE_PAGES - nactive;
pszind_t nonempty_pind = PSSET_NPSIZES;
if (ninactive != 0 && ninactive < HUGEPAGE_PAGES) {
nonempty_pind = sz_psz2ind(sz_psz_quantize_floor(
ninactive << LG_PAGE));
}
for (pszind_t i = 0; i < PSSET_NPSIZES; i++) {
if (i == nonempty_pind) {
assert_zu_eq(1,
psset->stats.nonfull_slabs[i][0].npageslabs,
"Should have found a slab");
expect_zu_eq(nactive,
psset->stats.nonfull_slabs[i][0].nactive,
"Mismatch in active pages");
} else {
stats_expect_empty(&psset->stats.nonfull_slabs[i][0]);
}
}
expect_zu_eq(nactive, psset_nactive(psset), "");
}
/*
* Fills in and inserts two pageslabs, with the first better than the second,
* and each fully allocated (into the allocations in allocs and worse_allocs,
* each of which should be HUGEPAGE_PAGES long), except for a single free page
* at the end.
*
* (There's nothing magic about these numbers; it's just useful to share the
* setup between the oldest fit and the insert/remove test).
*/
static void
init_test_pageslabs(psset_t *psset, hpdata_t *pageslab,
hpdata_t *worse_pageslab, edata_t *alloc, edata_t *worse_alloc) {
bool err;
hpdata_init(pageslab, (void *)(10 * HUGEPAGE), PAGESLAB_AGE);
/*
* This pageslab would be better from an address-first-fit POV, but
* worse from an age POV.
*/
hpdata_init(worse_pageslab, (void *)(9 * HUGEPAGE), PAGESLAB_AGE + 1);
psset_init(psset);
edata_init_test(&alloc[0]);
test_psset_alloc_new(psset, pageslab, &alloc[0], PAGE);
for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
edata_init_test(&alloc[i]);
err = test_psset_alloc_reuse(psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
expect_ptr_eq(pageslab, edata_ps_get(&alloc[i]),
"Allocated from the wrong pageslab");
}
edata_init_test(&worse_alloc[0]);
test_psset_alloc_new(psset, worse_pageslab, &worse_alloc[0], PAGE);
expect_ptr_eq(worse_pageslab, edata_ps_get(&worse_alloc[0]),
"Allocated from the wrong pageslab");
/*
* Make the two pssets otherwise indistinguishable; all full except for
* a single page.
*/
for (size_t i = 1; i < HUGEPAGE_PAGES - 1; i++) {
edata_init_test(&worse_alloc[i]);
err = test_psset_alloc_reuse(psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
expect_ptr_eq(worse_pageslab, edata_ps_get(&alloc[i]),
"Allocated from the wrong pageslab");
}
/* Deallocate the last page from the older pageslab. */
hpdata_t *evicted = test_psset_dalloc(psset,
&alloc[HUGEPAGE_PAGES - 1]);
expect_ptr_null(evicted, "Unexpected eviction");
}
/* The edata should come from the better pageslab. */
edata_t test_edata;
edata_init_test(&test_edata);
err = test_psset_alloc_reuse(&psset, &test_edata, PAGE);
expect_false(err, "Nonempty psset failed page allocation");
expect_ptr_eq(&pageslab, edata_ps_get(&test_edata),
"Allocated from the wrong pageslab");
}
TEST_END
/* Remove better; should still be able to alloc from worse. */
psset_update_begin(&psset, &pageslab);
err = test_psset_alloc_reuse(&psset, &worse_alloc[HUGEPAGE_PAGES - 1],
PAGE);
expect_false(err, "Removal should still leave an empty page");
expect_ptr_eq(&worse_pageslab,
edata_ps_get(&worse_alloc[HUGEPAGE_PAGES - 1]),
"Allocated out of wrong ps");
/*
* After deallocating the previous alloc and reinserting better, it
* should be preferred for future allocations.
*/
ps = test_psset_dalloc(&psset, &worse_alloc[HUGEPAGE_PAGES - 1]);
expect_ptr_null(ps, "Incorrect eviction of nonempty pageslab");
psset_update_end(&psset, &pageslab);
err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE);
expect_false(err, "psset should be nonempty");
expect_ptr_eq(&pageslab, edata_ps_get(&alloc[HUGEPAGE_PAGES - 1]),
"Removal/reinsertion shouldn't change ordering");
/*
* After deallocating and removing both, allocations should fail.
*/
ps = test_psset_dalloc(&psset, &alloc[HUGEPAGE_PAGES - 1]);
expect_ptr_null(ps, "Incorrect eviction");
psset_update_begin(&psset, &pageslab);
psset_update_begin(&psset, &worse_pageslab);
err = test_psset_alloc_reuse(&psset, &alloc[HUGEPAGE_PAGES - 1], PAGE);
expect_true(err, "psset should be empty, but an alloc succeeded");
}
TEST_END
TEST_BEGIN(test_purge_prefers_nonhuge) {
/*
* All else being equal, we should prefer purging non-huge pages over
* huge ones for non-empty extents.
*/
/* Nothing magic about this constant. */
enum {
NHP = 23,
};
hpdata_t *hpdata;
/*
* We've got a bunch of 1/8th dirty hpdatas. It should give us all the
* non-huge ones to purge, then all the huge ones, then refuse to purge
* further.
*/
for (int i = 0; i < NHP; i++) {
hpdata = psset_pick_purge(&psset);
assert_true(nonhuge_begin <= (uintptr_t)hpdata
&& (uintptr_t)hpdata < nonhuge_end, "");
psset_update_begin(&psset, hpdata);
test_psset_fake_purge(hpdata);
hpdata_purge_allowed_set(hpdata, false);
psset_update_end(&psset, hpdata);
}
for (int i = 0; i < NHP; i++) {
hpdata = psset_pick_purge(&psset);
expect_true(huge_begin <= (uintptr_t)hpdata
&& (uintptr_t)hpdata < huge_end, "");
psset_update_begin(&psset, hpdata);
hpdata_dehugify(hpdata);
test_psset_fake_purge(hpdata);
hpdata_purge_allowed_set(hpdata, false);
psset_update_end(&psset, hpdata);
}
}
TEST_END
/*
* The nonempty slab has 9 dirty pages, while the empty one has only 1.
* We should still pick the empty one for purging.
*/
hpdata_t *to_purge = psset_pick_purge(&psset);
expect_ptr_eq(&hpdata_empty, to_purge, "");
}
TEST_END
/*
* We have a bunch of empty slabs, half huge, half nonhuge, inserted in
* alternating order. We should pop all the huge ones before popping
* any of the non-huge ones for purging.
*/
for (int i = 0; i < NHP; i++) {
hpdata_t *to_purge = psset_pick_purge(&psset);
expect_ptr_eq(&hpdata_huge[i], to_purge, "");
psset_update_begin(&psset, to_purge);
hpdata_purge_allowed_set(to_purge, false);
psset_update_end(&psset, to_purge);
}
for (int i = 0; i < NHP; i++) {
hpdata_t *to_purge = psset_pick_purge(&psset);
expect_ptr_eq(&hpdata_nonhuge[i], to_purge, "");
psset_update_begin(&psset, to_purge);
hpdata_purge_allowed_set(to_purge, false);
psset_update_end(&psset, to_purge);
}
}
TEST_END