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