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