xref: /netbsd-src/external/bsd/jemalloc/dist/test/unit/psset.c (revision 7bdf38e5b7a28439665f2fdeff81e36913eef7dd)
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