1#define JEMALLOC_EXTENT_C_
2#include "jemalloc/internal/jemalloc_preamble.h"
3#include "jemalloc/internal/jemalloc_internal_includes.h"
4
5#include "jemalloc/internal/assert.h"
6#include "jemalloc/internal/extent_dss.h"
7#include "jemalloc/internal/extent_mmap.h"
8#include "jemalloc/internal/ph.h"
9#include "jemalloc/internal/rtree.h"
10#include "jemalloc/internal/mutex.h"
11#include "jemalloc/internal/mutex_pool.h"
12
13/******************************************************************************/
14/* Data. */
15
16rtree_t		extents_rtree;
17/* Keyed by the address of the extent_t being protected. */
18mutex_pool_t	extent_mutex_pool;
19
20size_t opt_lg_extent_max_active_fit = LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT;
21
22static const bitmap_info_t extents_bitmap_info =
23    BITMAP_INFO_INITIALIZER(NPSIZES+1);
24
25static void *extent_alloc_default(extent_hooks_t *extent_hooks, void *new_addr,
26    size_t size, size_t alignment, bool *zero, bool *commit,
27    unsigned arena_ind);
28static bool extent_dalloc_default(extent_hooks_t *extent_hooks, void *addr,
29    size_t size, bool committed, unsigned arena_ind);
30static void extent_destroy_default(extent_hooks_t *extent_hooks, void *addr,
31    size_t size, bool committed, unsigned arena_ind);
32static bool extent_commit_default(extent_hooks_t *extent_hooks, void *addr,
33    size_t size, size_t offset, size_t length, unsigned arena_ind);
34static bool extent_commit_impl(tsdn_t *tsdn, arena_t *arena,
35    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
36    size_t length, bool growing_retained);
37static bool extent_decommit_default(extent_hooks_t *extent_hooks,
38    void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind);
39#ifdef PAGES_CAN_PURGE_LAZY
40static bool extent_purge_lazy_default(extent_hooks_t *extent_hooks, void *addr,
41    size_t size, size_t offset, size_t length, unsigned arena_ind);
42#endif
43static bool extent_purge_lazy_impl(tsdn_t *tsdn, arena_t *arena,
44    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
45    size_t length, bool growing_retained);
46#ifdef PAGES_CAN_PURGE_FORCED
47static bool extent_purge_forced_default(extent_hooks_t *extent_hooks,
48    void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind);
49#endif
50static bool extent_purge_forced_impl(tsdn_t *tsdn, arena_t *arena,
51    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
52    size_t length, bool growing_retained);
53#ifdef JEMALLOC_MAPS_COALESCE
54static bool extent_split_default(extent_hooks_t *extent_hooks, void *addr,
55    size_t size, size_t size_a, size_t size_b, bool committed,
56    unsigned arena_ind);
57#endif
58static extent_t *extent_split_impl(tsdn_t *tsdn, arena_t *arena,
59    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
60    szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b,
61    bool growing_retained);
62#ifdef JEMALLOC_MAPS_COALESCE
63static bool extent_merge_default(extent_hooks_t *extent_hooks, void *addr_a,
64    size_t size_a, void *addr_b, size_t size_b, bool committed,
65    unsigned arena_ind);
66#endif
67static bool extent_merge_impl(tsdn_t *tsdn, arena_t *arena,
68    extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b,
69    bool growing_retained);
70
71const extent_hooks_t	extent_hooks_default = {
72	extent_alloc_default,
73	extent_dalloc_default,
74	extent_destroy_default,
75	extent_commit_default,
76	extent_decommit_default
77#ifdef PAGES_CAN_PURGE_LAZY
78	,
79	extent_purge_lazy_default
80#else
81	,
82	NULL
83#endif
84#ifdef PAGES_CAN_PURGE_FORCED
85	,
86	extent_purge_forced_default
87#else
88	,
89	NULL
90#endif
91#ifdef JEMALLOC_MAPS_COALESCE
92	,
93	extent_split_default,
94	extent_merge_default
95#endif
96};
97
98/* Used exclusively for gdump triggering. */
99static atomic_zu_t curpages;
100static atomic_zu_t highpages;
101
102/******************************************************************************/
103/*
104 * Function prototypes for static functions that are referenced prior to
105 * definition.
106 */
107
108static void extent_deregister(tsdn_t *tsdn, extent_t *extent);
109static extent_t *extent_recycle(tsdn_t *tsdn, arena_t *arena,
110    extent_hooks_t **r_extent_hooks, extents_t *extents, void *new_addr,
111    size_t usize, size_t pad, size_t alignment, bool slab, szind_t szind,
112    bool *zero, bool *commit, bool growing_retained);
113static extent_t *extent_try_coalesce(tsdn_t *tsdn, arena_t *arena,
114    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
115    extent_t *extent, bool *coalesced, bool growing_retained);
116static void extent_record(tsdn_t *tsdn, arena_t *arena,
117    extent_hooks_t **r_extent_hooks, extents_t *extents, extent_t *extent,
118    bool growing_retained);
119
120/******************************************************************************/
121
122ph_gen(UNUSED, extent_avail_, extent_tree_t, extent_t, ph_link,
123    extent_esnead_comp)
124
125typedef enum {
126	lock_result_success,
127	lock_result_failure,
128	lock_result_no_extent
129} lock_result_t;
130
131static lock_result_t
132extent_rtree_leaf_elm_try_lock(tsdn_t *tsdn, rtree_leaf_elm_t *elm,
133    extent_t **result) {
134	extent_t *extent1 = rtree_leaf_elm_extent_read(tsdn, &extents_rtree,
135	    elm, true);
136
137	if (extent1 == NULL) {
138		return lock_result_no_extent;
139	}
140	/*
141	 * It's possible that the extent changed out from under us, and with it
142	 * the leaf->extent mapping.  We have to recheck while holding the lock.
143	 */
144	extent_lock(tsdn, extent1);
145	extent_t *extent2 = rtree_leaf_elm_extent_read(tsdn,
146	    &extents_rtree, elm, true);
147
148	if (extent1 == extent2) {
149		*result = extent1;
150		return lock_result_success;
151	} else {
152		extent_unlock(tsdn, extent1);
153		return lock_result_failure;
154	}
155}
156
157/*
158 * Returns a pool-locked extent_t * if there's one associated with the given
159 * address, and NULL otherwise.
160 */
161static extent_t *
162extent_lock_from_addr(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx, void *addr) {
163	extent_t *ret = NULL;
164	rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &extents_rtree,
165	    rtree_ctx, (uintptr_t)addr, false, false);
166	if (elm == NULL) {
167		return NULL;
168	}
169	lock_result_t lock_result;
170	do {
171		lock_result = extent_rtree_leaf_elm_try_lock(tsdn, elm, &ret);
172	} while (lock_result == lock_result_failure);
173	return ret;
174}
175
176extent_t *
177extent_alloc(tsdn_t *tsdn, arena_t *arena) {
178	malloc_mutex_lock(tsdn, &arena->extent_avail_mtx);
179	extent_t *extent = extent_avail_first(&arena->extent_avail);
180	if (extent == NULL) {
181		malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
182		return base_alloc_extent(tsdn, arena->base);
183	}
184	extent_avail_remove(&arena->extent_avail, extent);
185	malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
186	return extent;
187}
188
189void
190extent_dalloc(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
191	malloc_mutex_lock(tsdn, &arena->extent_avail_mtx);
192	extent_avail_insert(&arena->extent_avail, extent);
193	malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
194}
195
196extent_hooks_t *
197extent_hooks_get(arena_t *arena) {
198	return base_extent_hooks_get(arena->base);
199}
200
201extent_hooks_t *
202extent_hooks_set(tsd_t *tsd, arena_t *arena, extent_hooks_t *extent_hooks) {
203	background_thread_info_t *info;
204	if (have_background_thread) {
205		info = arena_background_thread_info_get(arena);
206		malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
207	}
208	extent_hooks_t *ret = base_extent_hooks_set(arena->base, extent_hooks);
209	if (have_background_thread) {
210		malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
211	}
212
213	return ret;
214}
215
216static void
217extent_hooks_assure_initialized(arena_t *arena,
218    extent_hooks_t **r_extent_hooks) {
219	if (*r_extent_hooks == EXTENT_HOOKS_INITIALIZER) {
220		*r_extent_hooks = extent_hooks_get(arena);
221	}
222}
223
224#ifndef JEMALLOC_JET
225static
226#endif
227size_t
228extent_size_quantize_floor(size_t size) {
229	size_t ret;
230	pszind_t pind;
231
232	assert(size > 0);
233	assert((size & PAGE_MASK) == 0);
234
235	pind = sz_psz2ind(size - sz_large_pad + 1);
236	if (pind == 0) {
237		/*
238		 * Avoid underflow.  This short-circuit would also do the right
239		 * thing for all sizes in the range for which there are
240		 * PAGE-spaced size classes, but it's simplest to just handle
241		 * the one case that would cause erroneous results.
242		 */
243		return size;
244	}
245	ret = sz_pind2sz(pind - 1) + sz_large_pad;
246	assert(ret <= size);
247	return ret;
248}
249
250#ifndef JEMALLOC_JET
251static
252#endif
253size_t
254extent_size_quantize_ceil(size_t size) {
255	size_t ret;
256
257	assert(size > 0);
258	assert(size - sz_large_pad <= LARGE_MAXCLASS);
259	assert((size & PAGE_MASK) == 0);
260
261	ret = extent_size_quantize_floor(size);
262	if (ret < size) {
263		/*
264		 * Skip a quantization that may have an adequately large extent,
265		 * because under-sized extents may be mixed in.  This only
266		 * happens when an unusual size is requested, i.e. for aligned
267		 * allocation, and is just one of several places where linear
268		 * search would potentially find sufficiently aligned available
269		 * memory somewhere lower.
270		 */
271		ret = sz_pind2sz(sz_psz2ind(ret - sz_large_pad + 1)) +
272		    sz_large_pad;
273	}
274	return ret;
275}
276
277/* Generate pairing heap functions. */
278ph_gen(, extent_heap_, extent_heap_t, extent_t, ph_link, extent_snad_comp)
279
280bool
281extents_init(tsdn_t *tsdn, extents_t *extents, extent_state_t state,
282    bool delay_coalesce) {
283	if (malloc_mutex_init(&extents->mtx, "extents", WITNESS_RANK_EXTENTS,
284	    malloc_mutex_rank_exclusive)) {
285		return true;
286	}
287	for (unsigned i = 0; i < NPSIZES+1; i++) {
288		extent_heap_new(&extents->heaps[i]);
289	}
290	bitmap_init(extents->bitmap, &extents_bitmap_info, true);
291	extent_list_init(&extents->lru);
292	atomic_store_zu(&extents->npages, 0, ATOMIC_RELAXED);
293	extents->state = state;
294	extents->delay_coalesce = delay_coalesce;
295	return false;
296}
297
298extent_state_t
299extents_state_get(const extents_t *extents) {
300	return extents->state;
301}
302
303size_t
304extents_npages_get(extents_t *extents) {
305	return atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
306}
307
308static void
309extents_insert_locked(tsdn_t *tsdn, extents_t *extents, extent_t *extent) {
310	malloc_mutex_assert_owner(tsdn, &extents->mtx);
311	assert(extent_state_get(extent) == extents->state);
312
313	size_t size = extent_size_get(extent);
314	size_t psz = extent_size_quantize_floor(size);
315	pszind_t pind = sz_psz2ind(psz);
316	if (extent_heap_empty(&extents->heaps[pind])) {
317		bitmap_unset(extents->bitmap, &extents_bitmap_info,
318		    (size_t)pind);
319	}
320	extent_heap_insert(&extents->heaps[pind], extent);
321	extent_list_append(&extents->lru, extent);
322	size_t npages = size >> LG_PAGE;
323	/*
324	 * All modifications to npages hold the mutex (as asserted above), so we
325	 * don't need an atomic fetch-add; we can get by with a load followed by
326	 * a store.
327	 */
328	size_t cur_extents_npages =
329	    atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
330	atomic_store_zu(&extents->npages, cur_extents_npages + npages,
331	    ATOMIC_RELAXED);
332}
333
334static void
335extents_remove_locked(tsdn_t *tsdn, extents_t *extents, extent_t *extent) {
336	malloc_mutex_assert_owner(tsdn, &extents->mtx);
337	assert(extent_state_get(extent) == extents->state);
338
339	size_t size = extent_size_get(extent);
340	size_t psz = extent_size_quantize_floor(size);
341	pszind_t pind = sz_psz2ind(psz);
342	extent_heap_remove(&extents->heaps[pind], extent);
343	if (extent_heap_empty(&extents->heaps[pind])) {
344		bitmap_set(extents->bitmap, &extents_bitmap_info,
345		    (size_t)pind);
346	}
347	extent_list_remove(&extents->lru, extent);
348	size_t npages = size >> LG_PAGE;
349	/*
350	 * As in extents_insert_locked, we hold extents->mtx and so don't need
351	 * atomic operations for updating extents->npages.
352	 */
353	size_t cur_extents_npages =
354	    atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
355	assert(cur_extents_npages >= npages);
356	atomic_store_zu(&extents->npages,
357	    cur_extents_npages - (size >> LG_PAGE), ATOMIC_RELAXED);
358}
359
360/*
361 * Find an extent with size [min_size, max_size) to satisfy the alignment
362 * requirement.  For each size, try only the first extent in the heap.
363 */
364static extent_t *
365extents_fit_alignment(extents_t *extents, size_t min_size, size_t max_size,
366    size_t alignment) {
367        pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(min_size));
368        pszind_t pind_max = sz_psz2ind(extent_size_quantize_ceil(max_size));
369
370	for (pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap,
371	    &extents_bitmap_info, (size_t)pind); i < pind_max; i =
372	    (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
373	    (size_t)i+1)) {
374		assert(i < NPSIZES);
375		assert(!extent_heap_empty(&extents->heaps[i]));
376		extent_t *extent = extent_heap_first(&extents->heaps[i]);
377		uintptr_t base = (uintptr_t)extent_base_get(extent);
378		size_t candidate_size = extent_size_get(extent);
379		assert(candidate_size >= min_size);
380
381		uintptr_t next_align = ALIGNMENT_CEILING((uintptr_t)base,
382		    PAGE_CEILING(alignment));
383		if (base > next_align || base + candidate_size <= next_align) {
384			/* Overflow or not crossing the next alignment. */
385			continue;
386		}
387
388		size_t leadsize = next_align - base;
389		if (candidate_size - leadsize >= min_size) {
390			return extent;
391		}
392	}
393
394	return NULL;
395}
396
397/* Do any-best-fit extent selection, i.e. select any extent that best fits. */
398static extent_t *
399extents_best_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
400    size_t size) {
401	pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(size));
402	pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
403	    (size_t)pind);
404	if (i < NPSIZES+1) {
405		/*
406		 * In order to reduce fragmentation, avoid reusing and splitting
407		 * large extents for much smaller sizes.
408		 */
409		if ((sz_pind2sz(i) >> opt_lg_extent_max_active_fit) > size) {
410			return NULL;
411		}
412		assert(!extent_heap_empty(&extents->heaps[i]));
413		extent_t *extent = extent_heap_first(&extents->heaps[i]);
414		assert(extent_size_get(extent) >= size);
415		return extent;
416	}
417
418	return NULL;
419}
420
421/*
422 * Do first-fit extent selection, i.e. select the oldest/lowest extent that is
423 * large enough.
424 */
425static extent_t *
426extents_first_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
427    size_t size) {
428	extent_t *ret = NULL;
429
430	pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(size));
431	for (pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap,
432	    &extents_bitmap_info, (size_t)pind); i < NPSIZES+1; i =
433	    (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
434	    (size_t)i+1)) {
435		assert(!extent_heap_empty(&extents->heaps[i]));
436		extent_t *extent = extent_heap_first(&extents->heaps[i]);
437		assert(extent_size_get(extent) >= size);
438		if (ret == NULL || extent_snad_comp(extent, ret) < 0) {
439			ret = extent;
440		}
441		if (i == NPSIZES) {
442			break;
443		}
444		assert(i < NPSIZES);
445	}
446
447	return ret;
448}
449
450/*
451 * Do {best,first}-fit extent selection, where the selection policy choice is
452 * based on extents->delay_coalesce.  Best-fit selection requires less
453 * searching, but its layout policy is less stable and may cause higher virtual
454 * memory fragmentation as a side effect.
455 */
456static extent_t *
457extents_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
458    size_t esize, size_t alignment) {
459	malloc_mutex_assert_owner(tsdn, &extents->mtx);
460
461	size_t max_size = esize + PAGE_CEILING(alignment) - PAGE;
462	/* Beware size_t wrap-around. */
463	if (max_size < esize) {
464		return NULL;
465	}
466
467	extent_t *extent = extents->delay_coalesce ?
468	    extents_best_fit_locked(tsdn, arena, extents, max_size) :
469	    extents_first_fit_locked(tsdn, arena, extents, max_size);
470
471	if (alignment > PAGE && extent == NULL) {
472		/*
473		 * max_size guarantees the alignment requirement but is rather
474		 * pessimistic.  Next we try to satisfy the aligned allocation
475		 * with sizes in [esize, max_size).
476		 */
477		extent = extents_fit_alignment(extents, esize, max_size,
478		    alignment);
479	}
480
481	return extent;
482}
483
484static bool
485extent_try_delayed_coalesce(tsdn_t *tsdn, arena_t *arena,
486    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
487    extent_t *extent) {
488	extent_state_set(extent, extent_state_active);
489	bool coalesced;
490	extent = extent_try_coalesce(tsdn, arena, r_extent_hooks, rtree_ctx,
491	    extents, extent, &coalesced, false);
492	extent_state_set(extent, extents_state_get(extents));
493
494	if (!coalesced) {
495		return true;
496	}
497	extents_insert_locked(tsdn, extents, extent);
498	return false;
499}
500
501extent_t *
502extents_alloc(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
503    extents_t *extents, void *new_addr, size_t size, size_t pad,
504    size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
505	assert(size + pad != 0);
506	assert(alignment != 0);
507	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
508	    WITNESS_RANK_CORE, 0);
509
510	extent_t *extent = extent_recycle(tsdn, arena, r_extent_hooks, extents,
511	    new_addr, size, pad, alignment, slab, szind, zero, commit, false);
512	assert(extent == NULL || extent_dumpable_get(extent));
513	return extent;
514}
515
516void
517extents_dalloc(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
518    extents_t *extents, extent_t *extent) {
519	assert(extent_base_get(extent) != NULL);
520	assert(extent_size_get(extent) != 0);
521	assert(extent_dumpable_get(extent));
522	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
523	    WITNESS_RANK_CORE, 0);
524
525	extent_addr_set(extent, extent_base_get(extent));
526	extent_zeroed_set(extent, false);
527
528	extent_record(tsdn, arena, r_extent_hooks, extents, extent, false);
529}
530
531extent_t *
532extents_evict(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
533    extents_t *extents, size_t npages_min) {
534	rtree_ctx_t rtree_ctx_fallback;
535	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
536
537	malloc_mutex_lock(tsdn, &extents->mtx);
538
539	/*
540	 * Get the LRU coalesced extent, if any.  If coalescing was delayed,
541	 * the loop will iterate until the LRU extent is fully coalesced.
542	 */
543	extent_t *extent;
544	while (true) {
545		/* Get the LRU extent, if any. */
546		extent = extent_list_first(&extents->lru);
547		if (extent == NULL) {
548			goto label_return;
549		}
550		/* Check the eviction limit. */
551		size_t extents_npages = atomic_load_zu(&extents->npages,
552		    ATOMIC_RELAXED);
553		if (extents_npages <= npages_min) {
554			extent = NULL;
555			goto label_return;
556		}
557		extents_remove_locked(tsdn, extents, extent);
558		if (!extents->delay_coalesce) {
559			break;
560		}
561		/* Try to coalesce. */
562		if (extent_try_delayed_coalesce(tsdn, arena, r_extent_hooks,
563		    rtree_ctx, extents, extent)) {
564			break;
565		}
566		/*
567		 * The LRU extent was just coalesced and the result placed in
568		 * the LRU at its neighbor's position.  Start over.
569		 */
570	}
571
572	/*
573	 * Either mark the extent active or deregister it to protect against
574	 * concurrent operations.
575	 */
576	switch (extents_state_get(extents)) {
577	case extent_state_active:
578		not_reached();
579	case extent_state_dirty:
580	case extent_state_muzzy:
581		extent_state_set(extent, extent_state_active);
582		break;
583	case extent_state_retained:
584		extent_deregister(tsdn, extent);
585		break;
586	default:
587		not_reached();
588	}
589
590label_return:
591	malloc_mutex_unlock(tsdn, &extents->mtx);
592	return extent;
593}
594
595static void
596extents_leak(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
597    extents_t *extents, extent_t *extent, bool growing_retained) {
598	/*
599	 * Leak extent after making sure its pages have already been purged, so
600	 * that this is only a virtual memory leak.
601	 */
602	if (extents_state_get(extents) == extent_state_dirty) {
603		if (extent_purge_lazy_impl(tsdn, arena, r_extent_hooks,
604		    extent, 0, extent_size_get(extent), growing_retained)) {
605			extent_purge_forced_impl(tsdn, arena, r_extent_hooks,
606			    extent, 0, extent_size_get(extent),
607			    growing_retained);
608		}
609	}
610	extent_dalloc(tsdn, arena, extent);
611}
612
613void
614extents_prefork(tsdn_t *tsdn, extents_t *extents) {
615	malloc_mutex_prefork(tsdn, &extents->mtx);
616}
617
618void
619extents_postfork_parent(tsdn_t *tsdn, extents_t *extents) {
620	malloc_mutex_postfork_parent(tsdn, &extents->mtx);
621}
622
623void
624extents_postfork_child(tsdn_t *tsdn, extents_t *extents) {
625	malloc_mutex_postfork_child(tsdn, &extents->mtx);
626}
627
628static void
629extent_deactivate_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
630    extent_t *extent) {
631	assert(extent_arena_get(extent) == arena);
632	assert(extent_state_get(extent) == extent_state_active);
633
634	extent_state_set(extent, extents_state_get(extents));
635	extents_insert_locked(tsdn, extents, extent);
636}
637
638static void
639extent_deactivate(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
640    extent_t *extent) {
641	malloc_mutex_lock(tsdn, &extents->mtx);
642	extent_deactivate_locked(tsdn, arena, extents, extent);
643	malloc_mutex_unlock(tsdn, &extents->mtx);
644}
645
646static void
647extent_activate_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
648    extent_t *extent) {
649	assert(extent_arena_get(extent) == arena);
650	assert(extent_state_get(extent) == extents_state_get(extents));
651
652	extents_remove_locked(tsdn, extents, extent);
653	extent_state_set(extent, extent_state_active);
654}
655
656static bool
657extent_rtree_leaf_elms_lookup(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx,
658    const extent_t *extent, bool dependent, bool init_missing,
659    rtree_leaf_elm_t **r_elm_a, rtree_leaf_elm_t **r_elm_b) {
660	*r_elm_a = rtree_leaf_elm_lookup(tsdn, &extents_rtree, rtree_ctx,
661	    (uintptr_t)extent_base_get(extent), dependent, init_missing);
662	if (!dependent && *r_elm_a == NULL) {
663		return true;
664	}
665	assert(*r_elm_a != NULL);
666
667	*r_elm_b = rtree_leaf_elm_lookup(tsdn, &extents_rtree, rtree_ctx,
668	    (uintptr_t)extent_last_get(extent), dependent, init_missing);
669	if (!dependent && *r_elm_b == NULL) {
670		return true;
671	}
672	assert(*r_elm_b != NULL);
673
674	return false;
675}
676
677static void
678extent_rtree_write_acquired(tsdn_t *tsdn, rtree_leaf_elm_t *elm_a,
679    rtree_leaf_elm_t *elm_b, extent_t *extent, szind_t szind, bool slab) {
680	rtree_leaf_elm_write(tsdn, &extents_rtree, elm_a, extent, szind, slab);
681	if (elm_b != NULL) {
682		rtree_leaf_elm_write(tsdn, &extents_rtree, elm_b, extent, szind,
683		    slab);
684	}
685}
686
687static void
688extent_interior_register(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx, extent_t *extent,
689    szind_t szind) {
690	assert(extent_slab_get(extent));
691
692	/* Register interior. */
693	for (size_t i = 1; i < (extent_size_get(extent) >> LG_PAGE) - 1; i++) {
694		rtree_write(tsdn, &extents_rtree, rtree_ctx,
695		    (uintptr_t)extent_base_get(extent) + (uintptr_t)(i <<
696		    LG_PAGE), extent, szind, true);
697	}
698}
699
700static void
701extent_gdump_add(tsdn_t *tsdn, const extent_t *extent) {
702	cassert(config_prof);
703	/* prof_gdump() requirement. */
704	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
705	    WITNESS_RANK_CORE, 0);
706
707	if (opt_prof && extent_state_get(extent) == extent_state_active) {
708		size_t nadd = extent_size_get(extent) >> LG_PAGE;
709		size_t cur = atomic_fetch_add_zu(&curpages, nadd,
710		    ATOMIC_RELAXED) + nadd;
711		size_t high = atomic_load_zu(&highpages, ATOMIC_RELAXED);
712		while (cur > high && !atomic_compare_exchange_weak_zu(
713		    &highpages, &high, cur, ATOMIC_RELAXED, ATOMIC_RELAXED)) {
714			/*
715			 * Don't refresh cur, because it may have decreased
716			 * since this thread lost the highpages update race.
717			 * Note that high is updated in case of CAS failure.
718			 */
719		}
720		if (cur > high && prof_gdump_get_unlocked()) {
721			prof_gdump(tsdn);
722		}
723	}
724}
725
726static void
727extent_gdump_sub(tsdn_t *tsdn, const extent_t *extent) {
728	cassert(config_prof);
729
730	if (opt_prof && extent_state_get(extent) == extent_state_active) {
731		size_t nsub = extent_size_get(extent) >> LG_PAGE;
732		assert(atomic_load_zu(&curpages, ATOMIC_RELAXED) >= nsub);
733		atomic_fetch_sub_zu(&curpages, nsub, ATOMIC_RELAXED);
734	}
735}
736
737static bool
738extent_register_impl(tsdn_t *tsdn, extent_t *extent, bool gdump_add) {
739	rtree_ctx_t rtree_ctx_fallback;
740	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
741	rtree_leaf_elm_t *elm_a, *elm_b;
742
743	/*
744	 * We need to hold the lock to protect against a concurrent coalesce
745	 * operation that sees us in a partial state.
746	 */
747	extent_lock(tsdn, extent);
748
749	if (extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, extent, false, true,
750	    &elm_a, &elm_b)) {
751		return true;
752	}
753
754	szind_t szind = extent_szind_get_maybe_invalid(extent);
755	bool slab = extent_slab_get(extent);
756	extent_rtree_write_acquired(tsdn, elm_a, elm_b, extent, szind, slab);
757	if (slab) {
758		extent_interior_register(tsdn, rtree_ctx, extent, szind);
759	}
760
761	extent_unlock(tsdn, extent);
762
763	if (config_prof && gdump_add) {
764		extent_gdump_add(tsdn, extent);
765	}
766
767	return false;
768}
769
770static bool
771extent_register(tsdn_t *tsdn, extent_t *extent) {
772	return extent_register_impl(tsdn, extent, true);
773}
774
775static bool
776extent_register_no_gdump_add(tsdn_t *tsdn, extent_t *extent) {
777	return extent_register_impl(tsdn, extent, false);
778}
779
780static void
781extent_reregister(tsdn_t *tsdn, extent_t *extent) {
782	bool err = extent_register(tsdn, extent);
783	assert(!err);
784}
785
786/*
787 * Removes all pointers to the given extent from the global rtree indices for
788 * its interior.  This is relevant for slab extents, for which we need to do
789 * metadata lookups at places other than the head of the extent.  We deregister
790 * on the interior, then, when an extent moves from being an active slab to an
791 * inactive state.
792 */
793static void
794extent_interior_deregister(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx,
795    extent_t *extent) {
796	size_t i;
797
798	assert(extent_slab_get(extent));
799
800	for (i = 1; i < (extent_size_get(extent) >> LG_PAGE) - 1; i++) {
801		rtree_clear(tsdn, &extents_rtree, rtree_ctx,
802		    (uintptr_t)extent_base_get(extent) + (uintptr_t)(i <<
803		    LG_PAGE));
804	}
805}
806
807/*
808 * Removes all pointers to the given extent from the global rtree.
809 */
810static void
811extent_deregister_impl(tsdn_t *tsdn, extent_t *extent, bool gdump) {
812	rtree_ctx_t rtree_ctx_fallback;
813	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
814	rtree_leaf_elm_t *elm_a, *elm_b;
815	extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, extent, true, false,
816	    &elm_a, &elm_b);
817
818	extent_lock(tsdn, extent);
819
820	extent_rtree_write_acquired(tsdn, elm_a, elm_b, NULL, NSIZES, false);
821	if (extent_slab_get(extent)) {
822		extent_interior_deregister(tsdn, rtree_ctx, extent);
823		extent_slab_set(extent, false);
824	}
825
826	extent_unlock(tsdn, extent);
827
828	if (config_prof && gdump) {
829		extent_gdump_sub(tsdn, extent);
830	}
831}
832
833static void
834extent_deregister(tsdn_t *tsdn, extent_t *extent) {
835	extent_deregister_impl(tsdn, extent, true);
836}
837
838static void
839extent_deregister_no_gdump_sub(tsdn_t *tsdn, extent_t *extent) {
840	extent_deregister_impl(tsdn, extent, false);
841}
842
843/*
844 * Tries to find and remove an extent from extents that can be used for the
845 * given allocation request.
846 */
847static extent_t *
848extent_recycle_extract(tsdn_t *tsdn, arena_t *arena,
849    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
850    void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
851    bool growing_retained) {
852	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
853	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
854	assert(alignment > 0);
855	if (config_debug && new_addr != NULL) {
856		/*
857		 * Non-NULL new_addr has two use cases:
858		 *
859		 *   1) Recycle a known-extant extent, e.g. during purging.
860		 *   2) Perform in-place expanding reallocation.
861		 *
862		 * Regardless of use case, new_addr must either refer to a
863		 * non-existing extent, or to the base of an extant extent,
864		 * since only active slabs support interior lookups (which of
865		 * course cannot be recycled).
866		 */
867		assert(PAGE_ADDR2BASE(new_addr) == new_addr);
868		assert(pad == 0);
869		assert(alignment <= PAGE);
870	}
871
872	size_t esize = size + pad;
873	malloc_mutex_lock(tsdn, &extents->mtx);
874	extent_hooks_assure_initialized(arena, r_extent_hooks);
875	extent_t *extent;
876	if (new_addr != NULL) {
877		extent = extent_lock_from_addr(tsdn, rtree_ctx, new_addr);
878		if (extent != NULL) {
879			/*
880			 * We might null-out extent to report an error, but we
881			 * still need to unlock the associated mutex after.
882			 */
883			extent_t *unlock_extent = extent;
884			assert(extent_base_get(extent) == new_addr);
885			if (extent_arena_get(extent) != arena ||
886			    extent_size_get(extent) < esize ||
887			    extent_state_get(extent) !=
888			    extents_state_get(extents)) {
889				extent = NULL;
890			}
891			extent_unlock(tsdn, unlock_extent);
892		}
893	} else {
894		extent = extents_fit_locked(tsdn, arena, extents, esize,
895		    alignment);
896	}
897	if (extent == NULL) {
898		malloc_mutex_unlock(tsdn, &extents->mtx);
899		return NULL;
900	}
901
902	extent_activate_locked(tsdn, arena, extents, extent);
903	malloc_mutex_unlock(tsdn, &extents->mtx);
904
905	return extent;
906}
907
908/*
909 * Given an allocation request and an extent guaranteed to be able to satisfy
910 * it, this splits off lead and trail extents, leaving extent pointing to an
911 * extent satisfying the allocation.
912 * This function doesn't put lead or trail into any extents_t; it's the caller's
913 * job to ensure that they can be reused.
914 */
915typedef enum {
916	/*
917	 * Split successfully.  lead, extent, and trail, are modified to extents
918	 * describing the ranges before, in, and after the given allocation.
919	 */
920	extent_split_interior_ok,
921	/*
922	 * The extent can't satisfy the given allocation request.  None of the
923	 * input extent_t *s are touched.
924	 */
925	extent_split_interior_cant_alloc,
926	/*
927	 * In a potentially invalid state.  Must leak (if *to_leak is non-NULL),
928	 * and salvage what's still salvageable (if *to_salvage is non-NULL).
929	 * None of lead, extent, or trail are valid.
930	 */
931	extent_split_interior_error
932} extent_split_interior_result_t;
933
934static extent_split_interior_result_t
935extent_split_interior(tsdn_t *tsdn, arena_t *arena,
936    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx,
937    /* The result of splitting, in case of success. */
938    extent_t **extent, extent_t **lead, extent_t **trail,
939    /* The mess to clean up, in case of error. */
940    extent_t **to_leak, extent_t **to_salvage,
941    void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
942    szind_t szind, bool growing_retained) {
943	size_t esize = size + pad;
944	size_t leadsize = ALIGNMENT_CEILING((uintptr_t)extent_base_get(*extent),
945	    PAGE_CEILING(alignment)) - (uintptr_t)extent_base_get(*extent);
946	assert(new_addr == NULL || leadsize == 0);
947	if (extent_size_get(*extent) < leadsize + esize) {
948		return extent_split_interior_cant_alloc;
949	}
950	size_t trailsize = extent_size_get(*extent) - leadsize - esize;
951
952	*lead = NULL;
953	*trail = NULL;
954	*to_leak = NULL;
955	*to_salvage = NULL;
956
957	/* Split the lead. */
958	if (leadsize != 0) {
959		*lead = *extent;
960		*extent = extent_split_impl(tsdn, arena, r_extent_hooks,
961		    *lead, leadsize, NSIZES, false, esize + trailsize, szind,
962		    slab, growing_retained);
963		if (*extent == NULL) {
964			*to_leak = *lead;
965			*lead = NULL;
966			return extent_split_interior_error;
967		}
968	}
969
970	/* Split the trail. */
971	if (trailsize != 0) {
972		*trail = extent_split_impl(tsdn, arena, r_extent_hooks, *extent,
973		    esize, szind, slab, trailsize, NSIZES, false,
974		    growing_retained);
975		if (*trail == NULL) {
976			*to_leak = *extent;
977			*to_salvage = *lead;
978			*lead = NULL;
979			*extent = NULL;
980			return extent_split_interior_error;
981		}
982	}
983
984	if (leadsize == 0 && trailsize == 0) {
985		/*
986		 * Splitting causes szind to be set as a side effect, but no
987		 * splitting occurred.
988		 */
989		extent_szind_set(*extent, szind);
990		if (szind != NSIZES) {
991			rtree_szind_slab_update(tsdn, &extents_rtree, rtree_ctx,
992			    (uintptr_t)extent_addr_get(*extent), szind, slab);
993			if (slab && extent_size_get(*extent) > PAGE) {
994				rtree_szind_slab_update(tsdn, &extents_rtree,
995				    rtree_ctx,
996				    (uintptr_t)extent_past_get(*extent) -
997				    (uintptr_t)PAGE, szind, slab);
998			}
999		}
1000	}
1001
1002	return extent_split_interior_ok;
1003}
1004
1005/*
1006 * This fulfills the indicated allocation request out of the given extent (which
1007 * the caller should have ensured was big enough).  If there's any unused space
1008 * before or after the resulting allocation, that space is given its own extent
1009 * and put back into extents.
1010 */
1011static extent_t *
1012extent_recycle_split(tsdn_t *tsdn, arena_t *arena,
1013    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
1014    void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
1015    szind_t szind, extent_t *extent, bool growing_retained) {
1016	extent_t *lead;
1017	extent_t *trail;
1018	extent_t *to_leak;
1019	extent_t *to_salvage;
1020
1021	extent_split_interior_result_t result = extent_split_interior(
1022	    tsdn, arena, r_extent_hooks, rtree_ctx, &extent, &lead, &trail,
1023	    &to_leak, &to_salvage, new_addr, size, pad, alignment, slab, szind,
1024	    growing_retained);
1025
1026	if (result == extent_split_interior_ok) {
1027		if (lead != NULL) {
1028			extent_deactivate(tsdn, arena, extents, lead);
1029		}
1030		if (trail != NULL) {
1031			extent_deactivate(tsdn, arena, extents, trail);
1032		}
1033		return extent;
1034	} else {
1035		/*
1036		 * We should have picked an extent that was large enough to
1037		 * fulfill our allocation request.
1038		 */
1039		assert(result == extent_split_interior_error);
1040		if (to_salvage != NULL) {
1041			extent_deregister(tsdn, to_salvage);
1042		}
1043		if (to_leak != NULL) {
1044			void *leak = extent_base_get(to_leak);
1045			extent_deregister_no_gdump_sub(tsdn, to_leak);
1046			extents_leak(tsdn, arena, r_extent_hooks, extents,
1047			    to_leak, growing_retained);
1048			assert(extent_lock_from_addr(tsdn, rtree_ctx, leak)
1049			    == NULL);
1050		}
1051		return NULL;
1052	}
1053	unreachable();
1054}
1055
1056/*
1057 * Tries to satisfy the given allocation request by reusing one of the extents
1058 * in the given extents_t.
1059 */
1060static extent_t *
1061extent_recycle(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
1062    extents_t *extents, void *new_addr, size_t size, size_t pad,
1063    size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit,
1064    bool growing_retained) {
1065	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1066	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1067	assert(new_addr == NULL || !slab);
1068	assert(pad == 0 || !slab);
1069	assert(!*zero || !slab);
1070
1071	rtree_ctx_t rtree_ctx_fallback;
1072	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
1073
1074	extent_t *extent = extent_recycle_extract(tsdn, arena, r_extent_hooks,
1075	    rtree_ctx, extents, new_addr, size, pad, alignment, slab,
1076	    growing_retained);
1077	if (extent == NULL) {
1078		return NULL;
1079	}
1080
1081	extent = extent_recycle_split(tsdn, arena, r_extent_hooks, rtree_ctx,
1082	    extents, new_addr, size, pad, alignment, slab, szind, extent,
1083	    growing_retained);
1084	if (extent == NULL) {
1085		return NULL;
1086	}
1087
1088	if (*commit && !extent_committed_get(extent)) {
1089		if (extent_commit_impl(tsdn, arena, r_extent_hooks, extent,
1090		    0, extent_size_get(extent), growing_retained)) {
1091			extent_record(tsdn, arena, r_extent_hooks, extents,
1092			    extent, growing_retained);
1093			return NULL;
1094		}
1095		extent_zeroed_set(extent, true);
1096	}
1097
1098	if (extent_committed_get(extent)) {
1099		*commit = true;
1100	}
1101	if (extent_zeroed_get(extent)) {
1102		*zero = true;
1103	}
1104
1105	if (pad != 0) {
1106		extent_addr_randomize(tsdn, extent, alignment);
1107	}
1108	assert(extent_state_get(extent) == extent_state_active);
1109	if (slab) {
1110		extent_slab_set(extent, slab);
1111		extent_interior_register(tsdn, rtree_ctx, extent, szind);
1112	}
1113
1114	if (*zero) {
1115		void *addr = extent_base_get(extent);
1116		size_t size = extent_size_get(extent);
1117		if (!extent_zeroed_get(extent)) {
1118			if (pages_purge_forced(addr, size)) {
1119				memset(addr, 0, size);
1120			}
1121		} else if (config_debug) {
1122			size_t *p = (size_t *)(uintptr_t)addr;
1123			for (size_t i = 0; i < size / sizeof(size_t); i++) {
1124				assert(p[i] == 0);
1125			}
1126		}
1127	}
1128	return extent;
1129}
1130
1131/*
1132 * If the caller specifies (!*zero), it is still possible to receive zeroed
1133 * memory, in which case *zero is toggled to true.  arena_extent_alloc() takes
1134 * advantage of this to avoid demanding zeroed extents, but taking advantage of
1135 * them if they are returned.
1136 */
1137static void *
1138extent_alloc_core(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size,
1139    size_t alignment, bool *zero, bool *commit, dss_prec_t dss_prec) {
1140	void *ret;
1141
1142	assert(size != 0);
1143	assert(alignment != 0);
1144
1145	/* "primary" dss. */
1146	if (have_dss && dss_prec == dss_prec_primary && (ret =
1147	    extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
1148	    commit)) != NULL) {
1149		return ret;
1150	}
1151	/* mmap. */
1152	if ((ret = extent_alloc_mmap(new_addr, size, alignment, zero, commit))
1153	    != NULL) {
1154		return ret;
1155	}
1156	/* "secondary" dss. */
1157	if (have_dss && dss_prec == dss_prec_secondary && (ret =
1158	    extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
1159	    commit)) != NULL) {
1160		return ret;
1161	}
1162
1163	/* All strategies for allocation failed. */
1164	return NULL;
1165}
1166
1167static void *
1168extent_alloc_default_impl(tsdn_t *tsdn, arena_t *arena, void *new_addr,
1169    size_t size, size_t alignment, bool *zero, bool *commit) {
1170	void *ret = extent_alloc_core(tsdn, arena, new_addr, size, alignment, zero,
1171	    commit, (dss_prec_t)atomic_load_u(&arena->dss_prec,
1172	    ATOMIC_RELAXED));
1173	if (have_madvise_huge && ret) {
1174		pages_set_thp_state(ret, size);
1175	}
1176	return ret;
1177}
1178
1179static void *
1180extent_alloc_default(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
1181    size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
1182	tsdn_t *tsdn;
1183	arena_t *arena;
1184
1185	tsdn = tsdn_fetch();
1186	arena = arena_get(tsdn, arena_ind, false);
1187	/*
1188	 * The arena we're allocating on behalf of must have been initialized
1189	 * already.
1190	 */
1191	assert(arena != NULL);
1192
1193	return extent_alloc_default_impl(tsdn, arena, new_addr, size,
1194	    alignment, zero, commit);
1195}
1196
1197static void
1198extent_hook_pre_reentrancy(tsdn_t *tsdn, arena_t *arena) {
1199	tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
1200	if (arena == arena_get(tsd_tsdn(tsd), 0, false)) {
1201		/*
1202		 * The only legitimate case of customized extent hooks for a0 is
1203		 * hooks with no allocation activities.  One such example is to
1204		 * place metadata on pre-allocated resources such as huge pages.
1205		 * In that case, rely on reentrancy_level checks to catch
1206		 * infinite recursions.
1207		 */
1208		pre_reentrancy(tsd, NULL);
1209	} else {
1210		pre_reentrancy(tsd, arena);
1211	}
1212}
1213
1214static void
1215extent_hook_post_reentrancy(tsdn_t *tsdn) {
1216	tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
1217	post_reentrancy(tsd);
1218}
1219
1220/*
1221 * If virtual memory is retained, create increasingly larger extents from which
1222 * to split requested extents in order to limit the total number of disjoint
1223 * virtual memory ranges retained by each arena.
1224 */
1225static extent_t *
1226extent_grow_retained(tsdn_t *tsdn, arena_t *arena,
1227    extent_hooks_t **r_extent_hooks, size_t size, size_t pad, size_t alignment,
1228    bool slab, szind_t szind, bool *zero, bool *commit) {
1229	malloc_mutex_assert_owner(tsdn, &arena->extent_grow_mtx);
1230	assert(pad == 0 || !slab);
1231	assert(!*zero || !slab);
1232
1233	size_t esize = size + pad;
1234	size_t alloc_size_min = esize + PAGE_CEILING(alignment) - PAGE;
1235	/* Beware size_t wrap-around. */
1236	if (alloc_size_min < esize) {
1237		goto label_err;
1238	}
1239	/*
1240	 * Find the next extent size in the series that would be large enough to
1241	 * satisfy this request.
1242	 */
1243	pszind_t egn_skip = 0;
1244	size_t alloc_size = sz_pind2sz(arena->extent_grow_next + egn_skip);
1245	while (alloc_size < alloc_size_min) {
1246		egn_skip++;
1247		if (arena->extent_grow_next + egn_skip == NPSIZES) {
1248			/* Outside legal range. */
1249			goto label_err;
1250		}
1251		assert(arena->extent_grow_next + egn_skip < NPSIZES);
1252		alloc_size = sz_pind2sz(arena->extent_grow_next + egn_skip);
1253	}
1254
1255	extent_t *extent = extent_alloc(tsdn, arena);
1256	if (extent == NULL) {
1257		goto label_err;
1258	}
1259	bool zeroed = false;
1260	bool committed = false;
1261
1262	void *ptr;
1263	if (*r_extent_hooks == &extent_hooks_default) {
1264		ptr = extent_alloc_default_impl(tsdn, arena, NULL,
1265		    alloc_size, PAGE, &zeroed, &committed);
1266	} else {
1267		extent_hook_pre_reentrancy(tsdn, arena);
1268		ptr = (*r_extent_hooks)->alloc(*r_extent_hooks, NULL,
1269		    alloc_size, PAGE, &zeroed, &committed,
1270		    arena_ind_get(arena));
1271		extent_hook_post_reentrancy(tsdn);
1272	}
1273
1274	extent_init(extent, arena, ptr, alloc_size, false, NSIZES,
1275	    arena_extent_sn_next(arena), extent_state_active, zeroed,
1276	    committed, true);
1277	if (ptr == NULL) {
1278		extent_dalloc(tsdn, arena, extent);
1279		goto label_err;
1280	}
1281
1282	if (extent_register_no_gdump_add(tsdn, extent)) {
1283		extents_leak(tsdn, arena, r_extent_hooks,
1284		    &arena->extents_retained, extent, true);
1285		goto label_err;
1286	}
1287
1288	if (extent_zeroed_get(extent) && extent_committed_get(extent)) {
1289		*zero = true;
1290	}
1291	if (extent_committed_get(extent)) {
1292		*commit = true;
1293	}
1294
1295	rtree_ctx_t rtree_ctx_fallback;
1296	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
1297
1298	extent_t *lead;
1299	extent_t *trail;
1300	extent_t *to_leak;
1301	extent_t *to_salvage;
1302	extent_split_interior_result_t result = extent_split_interior(
1303	    tsdn, arena, r_extent_hooks, rtree_ctx, &extent, &lead, &trail,
1304	    &to_leak, &to_salvage, NULL, size, pad, alignment, slab, szind,
1305	    true);
1306
1307	if (result == extent_split_interior_ok) {
1308		if (lead != NULL) {
1309			extent_record(tsdn, arena, r_extent_hooks,
1310			    &arena->extents_retained, lead, true);
1311		}
1312		if (trail != NULL) {
1313			extent_record(tsdn, arena, r_extent_hooks,
1314			    &arena->extents_retained, trail, true);
1315		}
1316	} else {
1317		/*
1318		 * We should have allocated a sufficiently large extent; the
1319		 * cant_alloc case should not occur.
1320		 */
1321		assert(result == extent_split_interior_error);
1322		if (to_salvage != NULL) {
1323			if (config_prof) {
1324				extent_gdump_add(tsdn, to_salvage);
1325			}
1326			extent_record(tsdn, arena, r_extent_hooks,
1327			    &arena->extents_retained, to_salvage, true);
1328		}
1329		if (to_leak != NULL) {
1330			extent_deregister_no_gdump_sub(tsdn, to_leak);
1331			extents_leak(tsdn, arena, r_extent_hooks,
1332			    &arena->extents_retained, to_leak, true);
1333		}
1334		goto label_err;
1335	}
1336
1337	if (*commit && !extent_committed_get(extent)) {
1338		if (extent_commit_impl(tsdn, arena, r_extent_hooks, extent, 0,
1339		    extent_size_get(extent), true)) {
1340			extent_record(tsdn, arena, r_extent_hooks,
1341			    &arena->extents_retained, extent, true);
1342			goto label_err;
1343		}
1344		extent_zeroed_set(extent, true);
1345	}
1346
1347	/*
1348	 * Increment extent_grow_next if doing so wouldn't exceed the allowed
1349	 * range.
1350	 */
1351	if (arena->extent_grow_next + egn_skip + 1 <=
1352	    arena->retain_grow_limit) {
1353		arena->extent_grow_next += egn_skip + 1;
1354	} else {
1355		arena->extent_grow_next = arena->retain_grow_limit;
1356	}
1357	/* All opportunities for failure are past. */
1358	malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
1359
1360	if (config_prof) {
1361		/* Adjust gdump stats now that extent is final size. */
1362		extent_gdump_add(tsdn, extent);
1363	}
1364	if (pad != 0) {
1365		extent_addr_randomize(tsdn, extent, alignment);
1366	}
1367	if (slab) {
1368		rtree_ctx_t rtree_ctx_fallback;
1369		rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn,
1370		    &rtree_ctx_fallback);
1371
1372		extent_slab_set(extent, true);
1373		extent_interior_register(tsdn, rtree_ctx, extent, szind);
1374	}
1375	if (*zero && !extent_zeroed_get(extent)) {
1376		void *addr = extent_base_get(extent);
1377		size_t size = extent_size_get(extent);
1378		if (pages_purge_forced(addr, size)) {
1379			memset(addr, 0, size);
1380		}
1381	}
1382
1383	return extent;
1384label_err:
1385	malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
1386	return NULL;
1387}
1388
1389static extent_t *
1390extent_alloc_retained(tsdn_t *tsdn, arena_t *arena,
1391    extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
1392    size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
1393	assert(size != 0);
1394	assert(alignment != 0);
1395
1396	malloc_mutex_lock(tsdn, &arena->extent_grow_mtx);
1397
1398	extent_t *extent = extent_recycle(tsdn, arena, r_extent_hooks,
1399	    &arena->extents_retained, new_addr, size, pad, alignment, slab,
1400	    szind, zero, commit, true);
1401	if (extent != NULL) {
1402		malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
1403		if (config_prof) {
1404			extent_gdump_add(tsdn, extent);
1405		}
1406	} else if (opt_retain && new_addr == NULL) {
1407		extent = extent_grow_retained(tsdn, arena, r_extent_hooks, size,
1408		    pad, alignment, slab, szind, zero, commit);
1409		/* extent_grow_retained() always releases extent_grow_mtx. */
1410	} else {
1411		malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
1412	}
1413	malloc_mutex_assert_not_owner(tsdn, &arena->extent_grow_mtx);
1414
1415	return extent;
1416}
1417
1418static extent_t *
1419extent_alloc_wrapper_hard(tsdn_t *tsdn, arena_t *arena,
1420    extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
1421    size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
1422	size_t esize = size + pad;
1423	extent_t *extent = extent_alloc(tsdn, arena);
1424	if (extent == NULL) {
1425		return NULL;
1426	}
1427	void *addr;
1428	if (*r_extent_hooks == &extent_hooks_default) {
1429		/* Call directly to propagate tsdn. */
1430		addr = extent_alloc_default_impl(tsdn, arena, new_addr, esize,
1431		    alignment, zero, commit);
1432	} else {
1433		extent_hook_pre_reentrancy(tsdn, arena);
1434		addr = (*r_extent_hooks)->alloc(*r_extent_hooks, new_addr,
1435		    esize, alignment, zero, commit, arena_ind_get(arena));
1436		extent_hook_post_reentrancy(tsdn);
1437	}
1438	if (addr == NULL) {
1439		extent_dalloc(tsdn, arena, extent);
1440		return NULL;
1441	}
1442	extent_init(extent, arena, addr, esize, slab, szind,
1443	    arena_extent_sn_next(arena), extent_state_active, *zero, *commit,
1444	    true);
1445	if (pad != 0) {
1446		extent_addr_randomize(tsdn, extent, alignment);
1447	}
1448	if (extent_register(tsdn, extent)) {
1449		extents_leak(tsdn, arena, r_extent_hooks,
1450		    &arena->extents_retained, extent, false);
1451		return NULL;
1452	}
1453
1454	return extent;
1455}
1456
1457extent_t *
1458extent_alloc_wrapper(tsdn_t *tsdn, arena_t *arena,
1459    extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
1460    size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
1461	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1462	    WITNESS_RANK_CORE, 0);
1463
1464	extent_hooks_assure_initialized(arena, r_extent_hooks);
1465
1466	extent_t *extent = extent_alloc_retained(tsdn, arena, r_extent_hooks,
1467	    new_addr, size, pad, alignment, slab, szind, zero, commit);
1468	if (extent == NULL) {
1469		if (opt_retain && new_addr != NULL) {
1470			/*
1471			 * When retain is enabled and new_addr is set, we do not
1472			 * attempt extent_alloc_wrapper_hard which does mmap
1473			 * that is very unlikely to succeed (unless it happens
1474			 * to be at the end).
1475			 */
1476			return NULL;
1477		}
1478		extent = extent_alloc_wrapper_hard(tsdn, arena, r_extent_hooks,
1479		    new_addr, size, pad, alignment, slab, szind, zero, commit);
1480	}
1481
1482	assert(extent == NULL || extent_dumpable_get(extent));
1483	return extent;
1484}
1485
1486static bool
1487extent_can_coalesce(arena_t *arena, extents_t *extents, const extent_t *inner,
1488    const extent_t *outer) {
1489	assert(extent_arena_get(inner) == arena);
1490	if (extent_arena_get(outer) != arena) {
1491		return false;
1492	}
1493
1494	assert(extent_state_get(inner) == extent_state_active);
1495	if (extent_state_get(outer) != extents->state) {
1496		return false;
1497	}
1498
1499	if (extent_committed_get(inner) != extent_committed_get(outer)) {
1500		return false;
1501	}
1502
1503	return true;
1504}
1505
1506static bool
1507extent_coalesce(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
1508    extents_t *extents, extent_t *inner, extent_t *outer, bool forward,
1509    bool growing_retained) {
1510	assert(extent_can_coalesce(arena, extents, inner, outer));
1511
1512	extent_activate_locked(tsdn, arena, extents, outer);
1513
1514	malloc_mutex_unlock(tsdn, &extents->mtx);
1515	bool err = extent_merge_impl(tsdn, arena, r_extent_hooks,
1516	    forward ? inner : outer, forward ? outer : inner, growing_retained);
1517	malloc_mutex_lock(tsdn, &extents->mtx);
1518
1519	if (err) {
1520		extent_deactivate_locked(tsdn, arena, extents, outer);
1521	}
1522
1523	return err;
1524}
1525
1526static extent_t *
1527extent_try_coalesce(tsdn_t *tsdn, arena_t *arena,
1528    extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
1529    extent_t *extent, bool *coalesced, bool growing_retained) {
1530	/*
1531	 * Continue attempting to coalesce until failure, to protect against
1532	 * races with other threads that are thwarted by this one.
1533	 */
1534	bool again;
1535	do {
1536		again = false;
1537
1538		/* Try to coalesce forward. */
1539		extent_t *next = extent_lock_from_addr(tsdn, rtree_ctx,
1540		    extent_past_get(extent));
1541		if (next != NULL) {
1542			/*
1543			 * extents->mtx only protects against races for
1544			 * like-state extents, so call extent_can_coalesce()
1545			 * before releasing next's pool lock.
1546			 */
1547			bool can_coalesce = extent_can_coalesce(arena, extents,
1548			    extent, next);
1549
1550			extent_unlock(tsdn, next);
1551
1552			if (can_coalesce && !extent_coalesce(tsdn, arena,
1553			    r_extent_hooks, extents, extent, next, true,
1554			    growing_retained)) {
1555				if (extents->delay_coalesce) {
1556					/* Do minimal coalescing. */
1557					*coalesced = true;
1558					return extent;
1559				}
1560				again = true;
1561			}
1562		}
1563
1564		/* Try to coalesce backward. */
1565		extent_t *prev = extent_lock_from_addr(tsdn, rtree_ctx,
1566		    extent_before_get(extent));
1567		if (prev != NULL) {
1568			bool can_coalesce = extent_can_coalesce(arena, extents,
1569			    extent, prev);
1570			extent_unlock(tsdn, prev);
1571
1572			if (can_coalesce && !extent_coalesce(tsdn, arena,
1573			    r_extent_hooks, extents, extent, prev, false,
1574			    growing_retained)) {
1575				extent = prev;
1576				if (extents->delay_coalesce) {
1577					/* Do minimal coalescing. */
1578					*coalesced = true;
1579					return extent;
1580				}
1581				again = true;
1582			}
1583		}
1584	} while (again);
1585
1586	if (extents->delay_coalesce) {
1587		*coalesced = false;
1588	}
1589	return extent;
1590}
1591
1592/*
1593 * Does the metadata management portions of putting an unused extent into the
1594 * given extents_t (coalesces, deregisters slab interiors, the heap operations).
1595 */
1596static void
1597extent_record(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
1598    extents_t *extents, extent_t *extent, bool growing_retained) {
1599	rtree_ctx_t rtree_ctx_fallback;
1600	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
1601
1602	assert((extents_state_get(extents) != extent_state_dirty &&
1603	    extents_state_get(extents) != extent_state_muzzy) ||
1604	    !extent_zeroed_get(extent));
1605
1606	malloc_mutex_lock(tsdn, &extents->mtx);
1607	extent_hooks_assure_initialized(arena, r_extent_hooks);
1608
1609	extent_szind_set(extent, NSIZES);
1610	if (extent_slab_get(extent)) {
1611		extent_interior_deregister(tsdn, rtree_ctx, extent);
1612		extent_slab_set(extent, false);
1613	}
1614
1615	assert(rtree_extent_read(tsdn, &extents_rtree, rtree_ctx,
1616	    (uintptr_t)extent_base_get(extent), true) == extent);
1617
1618	if (!extents->delay_coalesce) {
1619		extent = extent_try_coalesce(tsdn, arena, r_extent_hooks,
1620		    rtree_ctx, extents, extent, NULL, growing_retained);
1621	} else if (extent_size_get(extent) >= LARGE_MINCLASS) {
1622		/* Always coalesce large extents eagerly. */
1623		bool coalesced;
1624		size_t prev_size;
1625		do {
1626			prev_size = extent_size_get(extent);
1627			assert(extent_state_get(extent) == extent_state_active);
1628			extent = extent_try_coalesce(tsdn, arena,
1629			    r_extent_hooks, rtree_ctx, extents, extent,
1630			    &coalesced, growing_retained);
1631		} while (coalesced &&
1632		    extent_size_get(extent) >= prev_size + LARGE_MINCLASS);
1633	}
1634	extent_deactivate_locked(tsdn, arena, extents, extent);
1635
1636	malloc_mutex_unlock(tsdn, &extents->mtx);
1637}
1638
1639void
1640extent_dalloc_gap(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
1641	extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
1642
1643	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1644	    WITNESS_RANK_CORE, 0);
1645
1646	if (extent_register(tsdn, extent)) {
1647		extents_leak(tsdn, arena, &extent_hooks,
1648		    &arena->extents_retained, extent, false);
1649		return;
1650	}
1651	extent_dalloc_wrapper(tsdn, arena, &extent_hooks, extent);
1652}
1653
1654static bool
1655extent_dalloc_default_impl(void *addr, size_t size) {
1656	if (!have_dss || !extent_in_dss(addr)) {
1657		return extent_dalloc_mmap(addr, size);
1658	}
1659	return true;
1660}
1661
1662static bool
1663extent_dalloc_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1664    bool committed, unsigned arena_ind) {
1665	return extent_dalloc_default_impl(addr, size);
1666}
1667
1668static bool
1669extent_dalloc_wrapper_try(tsdn_t *tsdn, arena_t *arena,
1670    extent_hooks_t **r_extent_hooks, extent_t *extent) {
1671	bool err;
1672
1673	assert(extent_base_get(extent) != NULL);
1674	assert(extent_size_get(extent) != 0);
1675	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1676	    WITNESS_RANK_CORE, 0);
1677
1678	extent_addr_set(extent, extent_base_get(extent));
1679
1680	extent_hooks_assure_initialized(arena, r_extent_hooks);
1681	/* Try to deallocate. */
1682	if (*r_extent_hooks == &extent_hooks_default) {
1683		/* Call directly to propagate tsdn. */
1684		err = extent_dalloc_default_impl(extent_base_get(extent),
1685		    extent_size_get(extent));
1686	} else {
1687		extent_hook_pre_reentrancy(tsdn, arena);
1688		err = ((*r_extent_hooks)->dalloc == NULL ||
1689		    (*r_extent_hooks)->dalloc(*r_extent_hooks,
1690		    extent_base_get(extent), extent_size_get(extent),
1691		    extent_committed_get(extent), arena_ind_get(arena)));
1692		extent_hook_post_reentrancy(tsdn);
1693	}
1694
1695	if (!err) {
1696		extent_dalloc(tsdn, arena, extent);
1697	}
1698
1699	return err;
1700}
1701
1702void
1703extent_dalloc_wrapper(tsdn_t *tsdn, arena_t *arena,
1704    extent_hooks_t **r_extent_hooks, extent_t *extent) {
1705	assert(extent_dumpable_get(extent));
1706	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1707	    WITNESS_RANK_CORE, 0);
1708
1709	/*
1710	 * Deregister first to avoid a race with other allocating threads, and
1711	 * reregister if deallocation fails.
1712	 */
1713	extent_deregister(tsdn, extent);
1714	if (!extent_dalloc_wrapper_try(tsdn, arena, r_extent_hooks, extent)) {
1715		return;
1716	}
1717
1718	extent_reregister(tsdn, extent);
1719	if (*r_extent_hooks != &extent_hooks_default) {
1720		extent_hook_pre_reentrancy(tsdn, arena);
1721	}
1722	/* Try to decommit; purge if that fails. */
1723	bool zeroed;
1724	if (!extent_committed_get(extent)) {
1725		zeroed = true;
1726	} else if (!extent_decommit_wrapper(tsdn, arena, r_extent_hooks, extent,
1727	    0, extent_size_get(extent))) {
1728		zeroed = true;
1729	} else if ((*r_extent_hooks)->purge_forced != NULL &&
1730	    !(*r_extent_hooks)->purge_forced(*r_extent_hooks,
1731	    extent_base_get(extent), extent_size_get(extent), 0,
1732	    extent_size_get(extent), arena_ind_get(arena))) {
1733		zeroed = true;
1734	} else if (extent_state_get(extent) == extent_state_muzzy ||
1735	    ((*r_extent_hooks)->purge_lazy != NULL &&
1736	    !(*r_extent_hooks)->purge_lazy(*r_extent_hooks,
1737	    extent_base_get(extent), extent_size_get(extent), 0,
1738	    extent_size_get(extent), arena_ind_get(arena)))) {
1739		zeroed = false;
1740	} else {
1741		zeroed = false;
1742	}
1743	if (*r_extent_hooks != &extent_hooks_default) {
1744		extent_hook_post_reentrancy(tsdn);
1745	}
1746	extent_zeroed_set(extent, zeroed);
1747
1748	if (config_prof) {
1749		extent_gdump_sub(tsdn, extent);
1750	}
1751
1752	extent_record(tsdn, arena, r_extent_hooks, &arena->extents_retained,
1753	    extent, false);
1754}
1755
1756static void
1757extent_destroy_default_impl(void *addr, size_t size) {
1758	if (!have_dss || !extent_in_dss(addr)) {
1759		pages_unmap(addr, size);
1760	}
1761}
1762
1763static void
1764extent_destroy_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1765    bool committed, unsigned arena_ind) {
1766	extent_destroy_default_impl(addr, size);
1767}
1768
1769void
1770extent_destroy_wrapper(tsdn_t *tsdn, arena_t *arena,
1771    extent_hooks_t **r_extent_hooks, extent_t *extent) {
1772	assert(extent_base_get(extent) != NULL);
1773	assert(extent_size_get(extent) != 0);
1774	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1775	    WITNESS_RANK_CORE, 0);
1776
1777	/* Deregister first to avoid a race with other allocating threads. */
1778	extent_deregister(tsdn, extent);
1779
1780	extent_addr_set(extent, extent_base_get(extent));
1781
1782	extent_hooks_assure_initialized(arena, r_extent_hooks);
1783	/* Try to destroy; silently fail otherwise. */
1784	if (*r_extent_hooks == &extent_hooks_default) {
1785		/* Call directly to propagate tsdn. */
1786		extent_destroy_default_impl(extent_base_get(extent),
1787		    extent_size_get(extent));
1788	} else if ((*r_extent_hooks)->destroy != NULL) {
1789		extent_hook_pre_reentrancy(tsdn, arena);
1790		(*r_extent_hooks)->destroy(*r_extent_hooks,
1791		    extent_base_get(extent), extent_size_get(extent),
1792		    extent_committed_get(extent), arena_ind_get(arena));
1793		extent_hook_post_reentrancy(tsdn);
1794	}
1795
1796	extent_dalloc(tsdn, arena, extent);
1797}
1798
1799static bool
1800extent_commit_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1801    size_t offset, size_t length, unsigned arena_ind) {
1802	return pages_commit((void *)((uintptr_t)addr + (uintptr_t)offset),
1803	    length);
1804}
1805
1806static bool
1807extent_commit_impl(tsdn_t *tsdn, arena_t *arena,
1808    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1809    size_t length, bool growing_retained) {
1810	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1811	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1812
1813	extent_hooks_assure_initialized(arena, r_extent_hooks);
1814	if (*r_extent_hooks != &extent_hooks_default) {
1815		extent_hook_pre_reentrancy(tsdn, arena);
1816	}
1817	bool err = ((*r_extent_hooks)->commit == NULL ||
1818	    (*r_extent_hooks)->commit(*r_extent_hooks, extent_base_get(extent),
1819	    extent_size_get(extent), offset, length, arena_ind_get(arena)));
1820	if (*r_extent_hooks != &extent_hooks_default) {
1821		extent_hook_post_reentrancy(tsdn);
1822	}
1823	extent_committed_set(extent, extent_committed_get(extent) || !err);
1824	return err;
1825}
1826
1827bool
1828extent_commit_wrapper(tsdn_t *tsdn, arena_t *arena,
1829    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1830    size_t length) {
1831	return extent_commit_impl(tsdn, arena, r_extent_hooks, extent, offset,
1832	    length, false);
1833}
1834
1835static bool
1836extent_decommit_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1837    size_t offset, size_t length, unsigned arena_ind) {
1838	return pages_decommit((void *)((uintptr_t)addr + (uintptr_t)offset),
1839	    length);
1840}
1841
1842bool
1843extent_decommit_wrapper(tsdn_t *tsdn, arena_t *arena,
1844    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1845    size_t length) {
1846	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1847	    WITNESS_RANK_CORE, 0);
1848
1849	extent_hooks_assure_initialized(arena, r_extent_hooks);
1850
1851	if (*r_extent_hooks != &extent_hooks_default) {
1852		extent_hook_pre_reentrancy(tsdn, arena);
1853	}
1854	bool err = ((*r_extent_hooks)->decommit == NULL ||
1855	    (*r_extent_hooks)->decommit(*r_extent_hooks,
1856	    extent_base_get(extent), extent_size_get(extent), offset, length,
1857	    arena_ind_get(arena)));
1858	if (*r_extent_hooks != &extent_hooks_default) {
1859		extent_hook_post_reentrancy(tsdn);
1860	}
1861	extent_committed_set(extent, extent_committed_get(extent) && err);
1862	return err;
1863}
1864
1865#ifdef PAGES_CAN_PURGE_LAZY
1866static bool
1867extent_purge_lazy_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1868    size_t offset, size_t length, unsigned arena_ind) {
1869	assert(addr != NULL);
1870	assert((offset & PAGE_MASK) == 0);
1871	assert(length != 0);
1872	assert((length & PAGE_MASK) == 0);
1873
1874	return pages_purge_lazy((void *)((uintptr_t)addr + (uintptr_t)offset),
1875	    length);
1876}
1877#endif
1878
1879static bool
1880extent_purge_lazy_impl(tsdn_t *tsdn, arena_t *arena,
1881    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1882    size_t length, bool growing_retained) {
1883	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1884	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1885
1886	extent_hooks_assure_initialized(arena, r_extent_hooks);
1887
1888	if ((*r_extent_hooks)->purge_lazy == NULL) {
1889		return true;
1890	}
1891	if (*r_extent_hooks != &extent_hooks_default) {
1892		extent_hook_pre_reentrancy(tsdn, arena);
1893	}
1894	bool err = (*r_extent_hooks)->purge_lazy(*r_extent_hooks,
1895	    extent_base_get(extent), extent_size_get(extent), offset, length,
1896	    arena_ind_get(arena));
1897	if (*r_extent_hooks != &extent_hooks_default) {
1898		extent_hook_post_reentrancy(tsdn);
1899	}
1900
1901	return err;
1902}
1903
1904bool
1905extent_purge_lazy_wrapper(tsdn_t *tsdn, arena_t *arena,
1906    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1907    size_t length) {
1908	return extent_purge_lazy_impl(tsdn, arena, r_extent_hooks, extent,
1909	    offset, length, false);
1910}
1911
1912#ifdef PAGES_CAN_PURGE_FORCED
1913static bool
1914extent_purge_forced_default(extent_hooks_t *extent_hooks, void *addr,
1915    size_t size, size_t offset, size_t length, unsigned arena_ind) {
1916	assert(addr != NULL);
1917	assert((offset & PAGE_MASK) == 0);
1918	assert(length != 0);
1919	assert((length & PAGE_MASK) == 0);
1920
1921	return pages_purge_forced((void *)((uintptr_t)addr +
1922	    (uintptr_t)offset), length);
1923}
1924#endif
1925
1926static bool
1927extent_purge_forced_impl(tsdn_t *tsdn, arena_t *arena,
1928    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1929    size_t length, bool growing_retained) {
1930	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1931	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1932
1933	extent_hooks_assure_initialized(arena, r_extent_hooks);
1934
1935	if ((*r_extent_hooks)->purge_forced == NULL) {
1936		return true;
1937	}
1938	if (*r_extent_hooks != &extent_hooks_default) {
1939		extent_hook_pre_reentrancy(tsdn, arena);
1940	}
1941	bool err = (*r_extent_hooks)->purge_forced(*r_extent_hooks,
1942	    extent_base_get(extent), extent_size_get(extent), offset, length,
1943	    arena_ind_get(arena));
1944	if (*r_extent_hooks != &extent_hooks_default) {
1945		extent_hook_post_reentrancy(tsdn);
1946	}
1947	return err;
1948}
1949
1950bool
1951extent_purge_forced_wrapper(tsdn_t *tsdn, arena_t *arena,
1952    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
1953    size_t length) {
1954	return extent_purge_forced_impl(tsdn, arena, r_extent_hooks, extent,
1955	    offset, length, false);
1956}
1957
1958#ifdef JEMALLOC_MAPS_COALESCE
1959static bool
1960extent_split_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
1961    size_t size_a, size_t size_b, bool committed, unsigned arena_ind) {
1962	return !maps_coalesce;
1963}
1964#endif
1965
1966/*
1967 * Accepts the extent to split, and the characteristics of each side of the
1968 * split.  The 'a' parameters go with the 'lead' of the resulting pair of
1969 * extents (the lower addressed portion of the split), and the 'b' parameters go
1970 * with the trail (the higher addressed portion).  This makes 'extent' the lead,
1971 * and returns the trail (except in case of error).
1972 */
1973static extent_t *
1974extent_split_impl(tsdn_t *tsdn, arena_t *arena,
1975    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
1976    szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b,
1977    bool growing_retained) {
1978	assert(extent_size_get(extent) == size_a + size_b);
1979	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1980	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1981
1982	extent_hooks_assure_initialized(arena, r_extent_hooks);
1983
1984	if ((*r_extent_hooks)->split == NULL) {
1985		return NULL;
1986	}
1987
1988	extent_t *trail = extent_alloc(tsdn, arena);
1989	if (trail == NULL) {
1990		goto label_error_a;
1991	}
1992
1993	extent_init(trail, arena, (void *)((uintptr_t)extent_base_get(extent) +
1994	    size_a), size_b, slab_b, szind_b, extent_sn_get(extent),
1995	    extent_state_get(extent), extent_zeroed_get(extent),
1996	    extent_committed_get(extent), extent_dumpable_get(extent));
1997
1998	rtree_ctx_t rtree_ctx_fallback;
1999	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
2000	rtree_leaf_elm_t *lead_elm_a, *lead_elm_b;
2001	{
2002		extent_t lead;
2003
2004		extent_init(&lead, arena, extent_addr_get(extent), size_a,
2005		    slab_a, szind_a, extent_sn_get(extent),
2006		    extent_state_get(extent), extent_zeroed_get(extent),
2007		    extent_committed_get(extent), extent_dumpable_get(extent));
2008
2009		extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, &lead, false,
2010		    true, &lead_elm_a, &lead_elm_b);
2011	}
2012	rtree_leaf_elm_t *trail_elm_a, *trail_elm_b;
2013	extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, trail, false, true,
2014	    &trail_elm_a, &trail_elm_b);
2015
2016	if (lead_elm_a == NULL || lead_elm_b == NULL || trail_elm_a == NULL
2017	    || trail_elm_b == NULL) {
2018		goto label_error_b;
2019	}
2020
2021	extent_lock2(tsdn, extent, trail);
2022
2023	if (*r_extent_hooks != &extent_hooks_default) {
2024		extent_hook_pre_reentrancy(tsdn, arena);
2025	}
2026	bool err = (*r_extent_hooks)->split(*r_extent_hooks, extent_base_get(extent),
2027	    size_a + size_b, size_a, size_b, extent_committed_get(extent),
2028	    arena_ind_get(arena));
2029	if (*r_extent_hooks != &extent_hooks_default) {
2030		extent_hook_post_reentrancy(tsdn);
2031	}
2032	if (err) {
2033		goto label_error_c;
2034	}
2035
2036	extent_size_set(extent, size_a);
2037	extent_szind_set(extent, szind_a);
2038
2039	extent_rtree_write_acquired(tsdn, lead_elm_a, lead_elm_b, extent,
2040	    szind_a, slab_a);
2041	extent_rtree_write_acquired(tsdn, trail_elm_a, trail_elm_b, trail,
2042	    szind_b, slab_b);
2043
2044	extent_unlock2(tsdn, extent, trail);
2045
2046	return trail;
2047label_error_c:
2048	extent_unlock2(tsdn, extent, trail);
2049label_error_b:
2050	extent_dalloc(tsdn, arena, trail);
2051label_error_a:
2052	return NULL;
2053}
2054
2055extent_t *
2056extent_split_wrapper(tsdn_t *tsdn, arena_t *arena,
2057    extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
2058    szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b) {
2059	return extent_split_impl(tsdn, arena, r_extent_hooks, extent, size_a,
2060	    szind_a, slab_a, size_b, szind_b, slab_b, false);
2061}
2062
2063static bool
2064extent_merge_default_impl(void *addr_a, void *addr_b) {
2065	if (!maps_coalesce) {
2066		return true;
2067	}
2068	if (have_dss && !extent_dss_mergeable(addr_a, addr_b)) {
2069		return true;
2070	}
2071
2072	return false;
2073}
2074
2075#ifdef JEMALLOC_MAPS_COALESCE
2076static bool
2077extent_merge_default(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
2078    void *addr_b, size_t size_b, bool committed, unsigned arena_ind) {
2079	return extent_merge_default_impl(addr_a, addr_b);
2080}
2081#endif
2082
2083static bool
2084extent_merge_impl(tsdn_t *tsdn, arena_t *arena,
2085    extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b,
2086    bool growing_retained) {
2087	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
2088	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
2089
2090	extent_hooks_assure_initialized(arena, r_extent_hooks);
2091
2092	if ((*r_extent_hooks)->merge == NULL) {
2093		return true;
2094	}
2095
2096	bool err;
2097	if (*r_extent_hooks == &extent_hooks_default) {
2098		/* Call directly to propagate tsdn. */
2099		err = extent_merge_default_impl(extent_base_get(a),
2100		    extent_base_get(b));
2101	} else {
2102		extent_hook_pre_reentrancy(tsdn, arena);
2103		err = (*r_extent_hooks)->merge(*r_extent_hooks,
2104		    extent_base_get(a), extent_size_get(a), extent_base_get(b),
2105		    extent_size_get(b), extent_committed_get(a),
2106		    arena_ind_get(arena));
2107		extent_hook_post_reentrancy(tsdn);
2108	}
2109
2110	if (err) {
2111		return true;
2112	}
2113
2114	/*
2115	 * The rtree writes must happen while all the relevant elements are
2116	 * owned, so the following code uses decomposed helper functions rather
2117	 * than extent_{,de}register() to do things in the right order.
2118	 */
2119	rtree_ctx_t rtree_ctx_fallback;
2120	rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
2121	rtree_leaf_elm_t *a_elm_a, *a_elm_b, *b_elm_a, *b_elm_b;
2122	extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, a, true, false, &a_elm_a,
2123	    &a_elm_b);
2124	extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, b, true, false, &b_elm_a,
2125	    &b_elm_b);
2126
2127	extent_lock2(tsdn, a, b);
2128
2129	if (a_elm_b != NULL) {
2130		rtree_leaf_elm_write(tsdn, &extents_rtree, a_elm_b, NULL,
2131		    NSIZES, false);
2132	}
2133	if (b_elm_b != NULL) {
2134		rtree_leaf_elm_write(tsdn, &extents_rtree, b_elm_a, NULL,
2135		    NSIZES, false);
2136	} else {
2137		b_elm_b = b_elm_a;
2138	}
2139
2140	extent_size_set(a, extent_size_get(a) + extent_size_get(b));
2141	extent_szind_set(a, NSIZES);
2142	extent_sn_set(a, (extent_sn_get(a) < extent_sn_get(b)) ?
2143	    extent_sn_get(a) : extent_sn_get(b));
2144	extent_zeroed_set(a, extent_zeroed_get(a) && extent_zeroed_get(b));
2145
2146	extent_rtree_write_acquired(tsdn, a_elm_a, b_elm_b, a, NSIZES, false);
2147
2148	extent_unlock2(tsdn, a, b);
2149
2150	extent_dalloc(tsdn, extent_arena_get(b), b);
2151
2152	return false;
2153}
2154
2155bool
2156extent_merge_wrapper(tsdn_t *tsdn, arena_t *arena,
2157    extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b) {
2158	return extent_merge_impl(tsdn, arena, r_extent_hooks, a, b, false);
2159}
2160
2161bool
2162extent_boot(void) {
2163	if (rtree_new(&extents_rtree, true)) {
2164		return true;
2165	}
2166
2167	if (mutex_pool_init(&extent_mutex_pool, "extent_mutex_pool",
2168	    WITNESS_RANK_EXTENT_POOL)) {
2169		return true;
2170	}
2171
2172	if (have_dss) {
2173		extent_dss_boot();
2174	}
2175
2176	return false;
2177}
2178