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 static void *
      4  1.1  christos pinned_extent_alloc(extent_hooks_t *extent_hooks, void *new_addr,
      5  1.1  christos     size_t size, size_t alignment, bool *zero, bool *commit,
      6  1.1  christos     unsigned arena_ind) {
      7  1.1  christos 	void *ret = ehooks_default_extent_hooks.alloc(
      8  1.1  christos 	    (extent_hooks_t *)&ehooks_default_extent_hooks,
      9  1.1  christos 	    new_addr, size, alignment, zero, commit, arena_ind);
     10  1.1  christos 	if (ret == NULL) {
     11  1.1  christos 		return NULL;
     12  1.1  christos 	}
     13  1.1  christos 	if (!*commit) {
     14  1.1  christos 		if (ehooks_default_extent_hooks.commit != NULL &&
     15  1.1  christos 		    ehooks_default_extent_hooks.commit(
     16  1.1  christos 		    (extent_hooks_t *)&ehooks_default_extent_hooks, ret, size,
     17  1.1  christos 		    0, size, arena_ind)) {
     18  1.1  christos 			ehooks_default_extent_hooks.dalloc(
     19  1.1  christos 			    (extent_hooks_t *)&ehooks_default_extent_hooks, ret,
     20  1.1  christos 			    size, *commit, arena_ind);
     21  1.1  christos 			return NULL;
     22  1.1  christos 		}
     23  1.1  christos 		*commit = true;
     24  1.1  christos 	}
     25  1.1  christos 	return (void *)((uintptr_t)ret | EXTENT_ALLOC_FLAG_PINNED);
     26  1.1  christos }
     27  1.1  christos 
     28  1.1  christos static unsigned pinned_split_calls;
     29  1.1  christos static unsigned pinned_destroy_calls;
     30  1.1  christos static size_t   pinned_destroy_bytes;
     31  1.1  christos 
     32  1.1  christos static bool
     33  1.1  christos pinned_extent_split(extent_hooks_t *extent_hooks, void *addr, size_t size,
     34  1.1  christos     size_t size_a, size_t size_b, bool committed, unsigned arena_ind) {
     35  1.1  christos 	pinned_split_calls++;
     36  1.1  christos 	return ehooks_default_extent_hooks.split(
     37  1.1  christos 	    (extent_hooks_t *)&ehooks_default_extent_hooks, addr, size, size_a,
     38  1.1  christos 	    size_b, committed, arena_ind);
     39  1.1  christos }
     40  1.1  christos 
     41  1.1  christos static bool
     42  1.1  christos pinned_extent_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
     43  1.1  christos     void *addr_b, size_t size_b, bool committed, unsigned arena_ind) {
     44  1.1  christos 	return ehooks_default_extent_hooks.merge(
     45  1.1  christos 	    (extent_hooks_t *)&ehooks_default_extent_hooks, addr_a, size_a,
     46  1.1  christos 	    addr_b, size_b, committed, arena_ind);
     47  1.1  christos }
     48  1.1  christos 
     49  1.1  christos static void
     50  1.1  christos pinned_extent_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size,
     51  1.1  christos     bool committed, unsigned arena_ind) {
     52  1.1  christos 	pinned_destroy_calls++;
     53  1.1  christos 	pinned_destroy_bytes += size;
     54  1.1  christos 	ehooks_default_extent_hooks.destroy(
     55  1.1  christos 	    (extent_hooks_t *)&ehooks_default_extent_hooks, addr, size,
     56  1.1  christos 	    committed, arena_ind);
     57  1.1  christos }
     58  1.1  christos 
     59  1.1  christos static void
     60  1.1  christos pinned_hooks_reset(void) {
     61  1.1  christos 	pinned_split_calls = 0;
     62  1.1  christos 	pinned_destroy_calls = 0;
     63  1.1  christos 	pinned_destroy_bytes = 0;
     64  1.1  christos }
     65  1.1  christos 
     66  1.1  christos static extent_hooks_t pinned_hooks = {
     67  1.1  christos 	pinned_extent_alloc,
     68  1.1  christos 	NULL, /* dalloc  force retain */
     69  1.1  christos 	pinned_extent_destroy,
     70  1.1  christos 	NULL, /* commit */
     71  1.1  christos 	NULL, /* decommit */
     72  1.1  christos 	NULL, /* purge_lazy */
     73  1.1  christos 	NULL, /* purge_forced */
     74  1.1  christos 	pinned_extent_split,
     75  1.1  christos 	pinned_extent_merge
     76  1.1  christos };
     77  1.1  christos 
     78  1.1  christos static size_t
     79  1.1  christos get_arena_mapped(unsigned arena_ind) {
     80  1.1  christos 	uint64_t epoch = 1;
     81  1.1  christos 	size_t   epoch_sz = sizeof(epoch);
     82  1.1  christos 	expect_d_eq(0, mallctl("epoch", &epoch, &epoch_sz, &epoch,
     83  1.1  christos 	    sizeof(epoch)), "epoch failed");
     84  1.1  christos 	size_t mapped;
     85  1.1  christos 	size_t mapped_sz = sizeof(mapped);
     86  1.1  christos 	char   buf[64];
     87  1.1  christos 	snprintf(buf, sizeof(buf), "stats.arenas.%u.mapped", arena_ind);
     88  1.1  christos 	expect_d_eq(0, mallctl(buf, &mapped, &mapped_sz, NULL, 0),
     89  1.1  christos 	    "stats.arenas.<i>.mapped read failed");
     90  1.1  christos 	return mapped;
     91  1.1  christos }
     92  1.1  christos 
     93  1.1  christos /*
     94  1.1  christos  * Non-dependent emap lookup: returns the edata for addr, or NULL if the
     95  1.1  christos  * rtree leaf does not exist (safe for addresses that jemalloc may never
     96  1.1  christos  * have mapped, e.g. after arena destroy).
     97  1.1  christos  */
     98  1.1  christos static edata_t *
     99  1.1  christos emap_edata_try_lookup(const void *ptr) {
    100  1.1  christos 	emap_full_alloc_ctx_t ctx;
    101  1.1  christos 	bool err = emap_full_alloc_ctx_try_lookup(TSDN_NULL,
    102  1.1  christos 	    &arena_emap_global, ptr, &ctx);
    103  1.1  christos 	if (err) {
    104  1.1  christos 		return NULL;
    105  1.1  christos 	}
    106  1.1  christos 	return ctx.edata;
    107  1.1  christos }
    108  1.1  christos 
    109  1.1  christos /*
    110  1.1  christos  * Find the edata covering addr by walking the emap from addr in PAGE
    111  1.1  christos  * strides. Returns NULL if no covering edata is found within max_bytes.
    112  1.1  christos  */
    113  1.1  christos static edata_t *
    114  1.1  christos find_covering_edata(const void *addr, size_t max_bytes) {
    115  1.1  christos 	uintptr_t a = (uintptr_t)addr;
    116  1.1  christos 	edata_t  *back = NULL;
    117  1.1  christos 	for (size_t off = 0; off <= max_bytes; off += PAGE) {
    118  1.1  christos 		back = emap_edata_try_lookup((void *)(a - off));
    119  1.1  christos 		if (back != NULL) {
    120  1.1  christos 			break;
    121  1.1  christos 		}
    122  1.1  christos 	}
    123  1.1  christos 	if (back == NULL) {
    124  1.1  christos 		return NULL;
    125  1.1  christos 	}
    126  1.1  christos 	edata_t *fwd = NULL;
    127  1.1  christos 	for (size_t off = 0; off <= max_bytes; off += PAGE) {
    128  1.1  christos 		fwd = emap_edata_try_lookup((void *)(a + off));
    129  1.1  christos 		if (fwd != NULL) {
    130  1.1  christos 			break;
    131  1.1  christos 		}
    132  1.1  christos 	}
    133  1.1  christos 	return (back == fwd) ? back : NULL;
    134  1.1  christos }
    135  1.1  christos 
    136  1.1  christos TEST_BEGIN(test_pinned_stats) {
    137  1.1  christos 	test_skip_if(!config_stats);
    138  1.1  christos 	pinned_hooks_reset();
    139  1.1  christos 
    140  1.1  christos 	unsigned arena_ind;
    141  1.1  christos 	size_t sz = sizeof(arena_ind);
    142  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    143  1.1  christos 
    144  1.1  christos 	/* Create arena with pinned hooks. */
    145  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    146  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    147  1.1  christos 	    "arena creation failed");
    148  1.1  christos 
    149  1.1  christos 	/* Allocate and free to populate ecache_pinned. */
    150  1.1  christos 	void *p = mallocx(PAGE * 4, MALLOCX_ARENA(arena_ind)
    151  1.1  christos 	    | MALLOCX_TCACHE_NONE);
    152  1.1  christos 	expect_ptr_not_null(p, "alloc failed");
    153  1.1  christos 	dallocx(p, MALLOCX_TCACHE_NONE);
    154  1.1  christos 
    155  1.1  christos 	/* Refresh stats. */
    156  1.1  christos 	uint64_t epoch = 1;
    157  1.1  christos 	sz = sizeof(epoch);
    158  1.1  christos 	expect_d_eq(0, mallctl("epoch", &epoch, &sz, &epoch, sizeof(epoch)),
    159  1.1  christos 	    "epoch failed");
    160  1.1  christos 
    161  1.1  christos 	/* Read total pinned stat. */
    162  1.1  christos 	char buf[128];
    163  1.1  christos 	size_t pinned_total;
    164  1.1  christos 	sz = sizeof(pinned_total);
    165  1.1  christos 	snprintf(buf, sizeof(buf), "stats.arenas.%u.pinned", arena_ind);
    166  1.1  christos 	expect_d_eq(0, mallctl(buf, &pinned_total, &sz, NULL, 0),
    167  1.1  christos 	    "stats.arenas.<i>.pinned read failed");
    168  1.1  christos 	expect_zu_gt(pinned_total, 0,
    169  1.1  christos 	    "pinned total should be > 0 after free to pinned arena");
    170  1.1  christos 
    171  1.1  christos 	/* Pinned bytes are part of stats.mapped (unlike retained). */
    172  1.1  christos 	expect_zu_ge(get_arena_mapped(arena_ind), pinned_total,
    173  1.1  christos 	    "stats.mapped should include pinned bytes");
    174  1.1  christos 
    175  1.1  christos 	/* Destroy the arena. */
    176  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    177  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    178  1.1  christos 	    "arena destroy failed");
    179  1.1  christos }
    180  1.1  christos TEST_END
    181  1.1  christos 
    182  1.1  christos TEST_BEGIN(test_pinned_shrink) {
    183  1.1  christos 	test_skip_if(ehooks_default_split_impl());
    184  1.1  christos 	pinned_hooks_reset();
    185  1.1  christos 
    186  1.1  christos 	unsigned arena_ind;
    187  1.1  christos 	size_t sz = sizeof(arena_ind);
    188  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    189  1.1  christos 
    190  1.1  christos 	/* Create arena with pinned hooks. */
    191  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    192  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    193  1.1  christos 	    "arena creation failed");
    194  1.1  christos 
    195  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    196  1.1  christos 	void *ptr = mallocx(SC_LARGE_MINCLASS + PAGE, flags);
    197  1.1  christos 	expect_ptr_not_null(ptr, "alloc failed");
    198  1.1  christos 	unsigned split_calls_before = pinned_split_calls;
    199  1.1  christos 	void *shrunk = rallocx(ptr, SC_LARGE_MINCLASS, flags);
    200  1.1  christos 	expect_ptr_not_null(shrunk, "shrink failed");
    201  1.1  christos 	expect_u_gt(pinned_split_calls, split_calls_before,
    202  1.1  christos 	    "shrink should invoke the split hook");
    203  1.1  christos 	dallocx(shrunk, MALLOCX_TCACHE_NONE);
    204  1.1  christos 
    205  1.1  christos 	char buf[64];
    206  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    207  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    208  1.1  christos 	    "arena destroy failed");
    209  1.1  christos }
    210  1.1  christos TEST_END
    211  1.1  christos 
    212  1.1  christos TEST_BEGIN(test_pinned_remnant_lock) {
    213  1.1  christos 	test_skip_if(!opt_retain);
    214  1.1  christos 	pinned_hooks_reset();
    215  1.1  christos 	unsigned arena_ind;
    216  1.1  christos 	size_t sz = sizeof(arena_ind);
    217  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    218  1.1  christos 
    219  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    220  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    221  1.1  christos 	    "arena creation failed");
    222  1.1  christos 
    223  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    224  1.1  christos 
    225  1.1  christos 	void *p1 = mallocx(SC_LARGE_MINCLASS, flags);
    226  1.1  christos 	expect_ptr_not_null(p1, "first alloc failed");
    227  1.1  christos 
    228  1.1  christos 	tsd_t *tsd = tsd_fetch();
    229  1.1  christos 	arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false);
    230  1.1  christos 	expect_ptr_not_null(arena, "arena_get failed");
    231  1.1  christos 	expect_zu_gt(ecache_npages_get(&arena->pa_shard.pac.ecache_pinned),
    232  1.1  christos 	    0, "grow remnant should be cached in ecache_pinned");
    233  1.1  christos 
    234  1.1  christos 	dallocx(p1, MALLOCX_TCACHE_NONE);
    235  1.1  christos 
    236  1.1  christos 	char buf[64];
    237  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    238  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    239  1.1  christos 	    "arena destroy failed");
    240  1.1  christos }
    241  1.1  christos TEST_END
    242  1.1  christos 
    243  1.1  christos TEST_BEGIN(test_pinned_reuse) {
    244  1.1  christos 	pinned_hooks_reset();
    245  1.1  christos 	unsigned arena_ind;
    246  1.1  christos 	size_t sz = sizeof(arena_ind);
    247  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    248  1.1  christos 
    249  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    250  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    251  1.1  christos 	    "arena creation failed");
    252  1.1  christos 
    253  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    254  1.1  christos 
    255  1.1  christos 	void *p1 = mallocx(PAGE * 2, flags);
    256  1.1  christos 	expect_ptr_not_null(p1, "first alloc failed");
    257  1.1  christos 	dallocx(p1, MALLOCX_TCACHE_NONE);
    258  1.1  christos 
    259  1.1  christos 	void *p2 = mallocx(PAGE * 2, flags);
    260  1.1  christos 	expect_ptr_not_null(p2, "reuse alloc failed");
    261  1.1  christos 	expect_ptr_eq(p1, p2,
    262  1.1  christos 	    "pinned extent should be reused at the same address");
    263  1.1  christos 	dallocx(p2, MALLOCX_TCACHE_NONE);
    264  1.1  christos 
    265  1.1  christos 	/* Destroy the arena. */
    266  1.1  christos 	char buf[64];
    267  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    268  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    269  1.1  christos 	    "arena destroy failed");
    270  1.1  christos }
    271  1.1  christos TEST_END
    272  1.1  christos 
    273  1.1  christos TEST_BEGIN(test_pinned_realloc) {
    274  1.1  christos 	test_skip_if(ehooks_default_split_impl());
    275  1.1  christos 	pinned_hooks_reset();
    276  1.1  christos 	unsigned arena_ind;
    277  1.1  christos 	size_t sz = sizeof(arena_ind);
    278  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    279  1.1  christos 
    280  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    281  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    282  1.1  christos 	    "arena creation failed");
    283  1.1  christos 
    284  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    285  1.1  christos 
    286  1.1  christos 	/*
    287  1.1  christos 	 * Reallocs within the large-class range shrink/grow in place; the
    288  1.1  christos 	 * pointer stays at the original address rather than moving to a
    289  1.1  christos 	 * fresh extent (important since pinned memory is finite).  All sizes
    290  1.1  christos 	 * are multiples of SC_LARGE_MINCLASS so they bypass the slab path
    291  1.1  christos 	 * regardless of LG_PAGE / SC_LG_NGROUP.
    292  1.1  christos 	 */
    293  1.1  christos 	void *p = mallocx(SC_LARGE_MINCLASS * 7, flags);
    294  1.1  christos 	expect_ptr_not_null(p, "initial alloc failed");
    295  1.1  christos 	void *original = p;
    296  1.1  christos 
    297  1.1  christos 	p = rallocx(p, SC_LARGE_MINCLASS * 3, flags);
    298  1.1  christos 	expect_ptr_not_null(p, "shrink failed");
    299  1.1  christos 	expect_ptr_eq(p, original, "shrink should preserve address");
    300  1.1  christos 
    301  1.1  christos 	p = rallocx(p, SC_LARGE_MINCLASS * 6, flags);
    302  1.1  christos 	expect_ptr_not_null(p, "regrow failed");
    303  1.1  christos 	expect_ptr_eq(p, original, "regrow should preserve address");
    304  1.1  christos 
    305  1.1  christos 	dallocx(p, MALLOCX_TCACHE_NONE);
    306  1.1  christos 
    307  1.1  christos 	char buf[64];
    308  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    309  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    310  1.1  christos 	    "arena destroy failed");
    311  1.1  christos }
    312  1.1  christos TEST_END
    313  1.1  christos 
    314  1.1  christos TEST_BEGIN(test_pinned_reset) {
    315  1.1  christos 	pinned_hooks_reset();
    316  1.1  christos 	unsigned arena_ind;
    317  1.1  christos 	size_t sz = sizeof(arena_ind);
    318  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    319  1.1  christos 
    320  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    321  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    322  1.1  christos 	    "arena creation failed");
    323  1.1  christos 
    324  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    325  1.1  christos 
    326  1.1  christos 	/* Allocate several pinned extents, leave them live. */
    327  1.1  christos 	void *ptrs[4];
    328  1.1  christos 	for (int i = 0; i < 4; i++) {
    329  1.1  christos 		ptrs[i] = mallocx(SC_LARGE_MINCLASS * (i + 1), flags);
    330  1.1  christos 		expect_ptr_not_null(ptrs[i], "alloc %d failed", i);
    331  1.1  christos 	}
    332  1.1  christos 
    333  1.1  christos 	/*
    334  1.1  christos 	 * arena.<i>.reset returns live allocations to the caches without
    335  1.1  christos 	 * destroying the arena.  No destroy hook calls should fire (pinned
    336  1.1  christos 	 * extents stay in ecache_pinned), and the arena should still be
    337  1.1  christos 	 * usable for further allocations afterward.
    338  1.1  christos 	 */
    339  1.1  christos 	unsigned destroy_calls_before = pinned_destroy_calls;
    340  1.1  christos 	char buf[64];
    341  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.reset", arena_ind);
    342  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    343  1.1  christos 	    "arena reset failed");
    344  1.1  christos 	expect_u_eq(pinned_destroy_calls, destroy_calls_before,
    345  1.1  christos 	    "reset should not invoke the destroy hook");
    346  1.1  christos 
    347  1.1  christos 	tsd_t *tsd = tsd_fetch();
    348  1.1  christos 	arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false);
    349  1.1  christos 	expect_ptr_not_null(arena, "arena_get failed");
    350  1.1  christos 	expect_zu_gt(ecache_npages_get(&arena->pa_shard.pac.ecache_pinned), 0,
    351  1.1  christos 	    "pinned ecache should hold the reset extents");
    352  1.1  christos 
    353  1.1  christos 	/* Arena is still usable: alloc and free again. */
    354  1.1  christos 	void *p = mallocx(SC_LARGE_MINCLASS, flags);
    355  1.1  christos 	expect_ptr_not_null(p, "post-reset alloc failed");
    356  1.1  christos 	dallocx(p, MALLOCX_TCACHE_NONE);
    357  1.1  christos 
    358  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    359  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    360  1.1  christos 	    "arena destroy failed");
    361  1.1  christos }
    362  1.1  christos TEST_END
    363  1.1  christos 
    364  1.1  christos TEST_BEGIN(test_pinned_destroy) {
    365  1.1  christos 	pinned_hooks_reset();
    366  1.1  christos 
    367  1.1  christos 	unsigned arena_ind;
    368  1.1  christos 	size_t sz = sizeof(arena_ind);
    369  1.1  christos 	extent_hooks_t *hooks_ptr = &pinned_hooks;
    370  1.1  christos 	expect_d_eq(0, mallctl("arenas.create", &arena_ind, &sz,
    371  1.1  christos 	    &hooks_ptr, sizeof(hooks_ptr)),
    372  1.1  christos 	    "arena creation failed");
    373  1.1  christos 
    374  1.1  christos 	int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
    375  1.1  christos 
    376  1.1  christos 	size_t mapped_initial = config_stats ? get_arena_mapped(arena_ind) : 0;
    377  1.1  christos 
    378  1.1  christos 	void *ptrs[4];
    379  1.1  christos 	size_t sizes[4] = {PAGE * 4, SC_LARGE_MINCLASS,
    380  1.1  christos 	    SC_LARGE_MINCLASS + PAGE, SC_LARGE_MINCLASS * 2};
    381  1.1  christos 	size_t total = 0;
    382  1.1  christos 	for (int i = 0; i < 4; i++) {
    383  1.1  christos 		ptrs[i] = mallocx(sizes[i], flags);
    384  1.1  christos 		expect_ptr_not_null(ptrs[i], "alloc %d failed", i);
    385  1.1  christos 		total += sizes[i];
    386  1.1  christos 	}
    387  1.1  christos 
    388  1.1  christos 	if (config_stats) {
    389  1.1  christos 		expect_zu_ge(get_arena_mapped(arena_ind) - mapped_initial,
    390  1.1  christos 		    total,
    391  1.1  christos 		    "mapped should grow by at least total after pinned allocs");
    392  1.1  christos 	}
    393  1.1  christos 
    394  1.1  christos 	/*
    395  1.1  christos 	 * Stress alloc/dalloc/realloc churn before the final teardown.  Mix
    396  1.1  christos 	 * sizes (some at the slab boundary, some multi-large-class), and on
    397  1.1  christos 	 * platforms that support splitting, alternate shrink and regrow on
    398  1.1  christos 	 * the same extent to stress the in-place realloc accounting paths.
    399  1.1  christos 	 */
    400  1.1  christos 	bool can_split = !ehooks_default_split_impl();
    401  1.1  christos 	for (int round = 0; round < 64; round++) {
    402  1.1  christos 		size_t s = SC_LARGE_MINCLASS + PAGE * (round % 4);
    403  1.1  christos 		void *p = mallocx(s, flags);
    404  1.1  christos 		expect_ptr_not_null(p, "churn alloc %d failed", round);
    405  1.1  christos 		if (can_split && (round & 1)) {
    406  1.1  christos 			void *shrunk = rallocx(p, SC_LARGE_MINCLASS, flags);
    407  1.1  christos 			expect_ptr_not_null(shrunk, "churn shrink %d failed",
    408  1.1  christos 			    round);
    409  1.1  christos 			void *regrown = rallocx(shrunk, SC_LARGE_MINCLASS * 3,
    410  1.1  christos 			    flags);
    411  1.1  christos 			expect_ptr_not_null(regrown, "churn regrow %d failed",
    412  1.1  christos 			    round);
    413  1.1  christos 			dallocx(regrown, MALLOCX_TCACHE_NONE);
    414  1.1  christos 		} else {
    415  1.1  christos 			void *p2 = rallocx(p, SC_LARGE_MINCLASS * 2, flags);
    416  1.1  christos 			expect_ptr_not_null(p2, "churn realloc %d failed",
    417  1.1  christos 			    round);
    418  1.1  christos 			dallocx(p2, MALLOCX_TCACHE_NONE);
    419  1.1  christos 		}
    420  1.1  christos 	}
    421  1.1  christos 	/* Free and re-alloc one of the original pointers to exercise reuse. */
    422  1.1  christos 	dallocx(ptrs[1], MALLOCX_TCACHE_NONE);
    423  1.1  christos 	ptrs[1] = mallocx(sizes[1], flags);
    424  1.1  christos 	expect_ptr_not_null(ptrs[1], "reuse alloc failed");
    425  1.1  christos 
    426  1.1  christos 	for (int i = 0; i < 4; i++) {
    427  1.1  christos 		dallocx(ptrs[i], MALLOCX_TCACHE_NONE);
    428  1.1  christos 	}
    429  1.1  christos 
    430  1.1  christos 	if (config_stats) {
    431  1.1  christos 		expect_zu_ge(get_arena_mapped(arena_ind) - mapped_initial,
    432  1.1  christos 		    total,
    433  1.1  christos 		    "pinned bytes should remain in mapped after dalloc "
    434  1.1  christos 		    "(no decay for pinned)");
    435  1.1  christos 	}
    436  1.1  christos 
    437  1.1  christos 	tsd_t *tsd = tsd_fetch();
    438  1.1  christos 	tsdn_t *tsdn = tsd_tsdn(tsd);
    439  1.1  christos 	arena_t *arena = arena_get(tsdn, arena_ind, false);
    440  1.1  christos 	expect_ptr_not_null(arena, "arena_get failed");
    441  1.1  christos 	size_t pinned_bytes =
    442  1.1  christos 	    ecache_npages_get(&arena->pa_shard.pac.ecache_pinned) << LG_PAGE;
    443  1.1  christos 	expect_zu_gt(pinned_bytes, 0,
    444  1.1  christos 	    "pinned ecache should contain the freed extents");
    445  1.1  christos 
    446  1.1  christos 	/*
    447  1.1  christos 	 * Pinned extents stay registered in the emap after dalloc.  Coalescing
    448  1.1  christos 	 * may have made any individual ptrs[i] interior to a larger merged
    449  1.1  christos 	 * extent, so use find_covering_edata to walk to the merged extent's
    450  1.1  christos 	 * base/last-page; each must resolve to a pinned-state edata.  The
    451  1.1  christos 	 * search bound is the total bytes currently in ecache_pinned, which
    452  1.1  christos 	 * upper-bounds the size of any covering merged extent.
    453  1.1  christos 	 */
    454  1.1  christos 	for (int i = 0; i < 4; i++) {
    455  1.1  christos 		edata_t *covering = find_covering_edata(ptrs[i],
    456  1.1  christos 		    pinned_bytes);
    457  1.1  christos 		expect_ptr_not_null(covering,
    458  1.1  christos 		    "freed pinned extent ptrs[%d] should still be reachable "
    459  1.1  christos 		    "in the emap", i);
    460  1.1  christos 		expect_d_eq(edata_state_get(covering), extent_state_pinned,
    461  1.1  christos 		    "covering extent for ptrs[%d] should be in pinned state",
    462  1.1  christos 		    i);
    463  1.1  christos 	}
    464  1.1  christos 
    465  1.1  christos 	char buf[64];
    466  1.1  christos 	snprintf(buf, sizeof(buf), "arena.%u.destroy", arena_ind);
    467  1.1  christos 	unsigned destroy_calls_before = pinned_destroy_calls;
    468  1.1  christos 	size_t destroy_bytes_before = pinned_destroy_bytes;
    469  1.1  christos 	expect_d_eq(0, mallctl(buf, NULL, NULL, NULL, 0),
    470  1.1  christos 	    "arena destroy failed");
    471  1.1  christos 	expect_u_gt(pinned_destroy_calls, destroy_calls_before,
    472  1.1  christos 	    "arena destroy should invoke the destroy hook");
    473  1.1  christos 	expect_zu_ge(pinned_destroy_bytes - destroy_bytes_before, total,
    474  1.1  christos 	    "destroy hook should be called with at least the allocated total "
    475  1.1  christos 	    "(coalesced fragments returned to OS)");
    476  1.1  christos 	if (maps_coalesce) {
    477  1.1  christos 		expect_u_lt(pinned_destroy_calls - destroy_calls_before, 4,
    478  1.1  christos 		    "destroy calls should be < #allocs after coalesce");
    479  1.1  christos 	}
    480  1.1  christos 	for (int i = 0; i < 4; i++) {
    481  1.1  christos 		expect_ptr_null(find_covering_edata(ptrs[i], pinned_bytes),
    482  1.1  christos 		    "arena destroy should clear the emap entry for ptrs[%d]",
    483  1.1  christos 		    i);
    484  1.1  christos 	}
    485  1.1  christos }
    486  1.1  christos TEST_END
    487  1.1  christos 
    488  1.1  christos int
    489  1.1  christos main(void) {
    490  1.1  christos 	return test_no_reentrancy(
    491  1.1  christos 	    test_pinned_remnant_lock,
    492  1.1  christos 	    test_pinned_reuse,
    493  1.1  christos 	    test_pinned_realloc,
    494  1.1  christos 	    test_pinned_stats,
    495  1.1  christos 	    test_pinned_shrink,
    496  1.1  christos 	    test_pinned_reset,
    497  1.1  christos 	    test_pinned_destroy);
    498  1.1  christos }
    499