xref: /netbsd-src/external/bsd/jemalloc/dist/src/extent.c (revision 05ba679349628d820dcd51abf35b604155f35450)
1 #include "jemalloc/internal/jemalloc_preamble.h"
2 #include "jemalloc/internal/jemalloc_internal_includes.h"
3 
4 #include "jemalloc/internal/assert.h"
5 #include "jemalloc/internal/emap.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/mutex.h"
10 
11 /******************************************************************************/
12 /* Data. */
13 
14 size_t opt_lg_extent_max_active_fit = LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT;
15 
16 static bool extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
17     size_t offset, size_t length, bool growing_retained);
18 static bool extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks,
19     edata_t *edata, size_t offset, size_t length, bool growing_retained);
20 static bool extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks,
21     edata_t *edata, size_t offset, size_t length, bool growing_retained);
22 static edata_t *extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
23     edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks);
24 static bool extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
25     edata_t *a, edata_t *b, bool holding_core_locks);
26 
27 /* Used exclusively for gdump triggering. */
28 static atomic_zu_t curpages;
29 static atomic_zu_t highpages;
30 
31 /******************************************************************************/
32 /*
33  * Function prototypes for static functions that are referenced prior to
34  * definition.
35  */
36 
37 static void extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata);
38 static edata_t *extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
39     ecache_t *ecache, edata_t *expand_edata, size_t usize, size_t alignment,
40     bool zero, bool *commit, bool growing_retained, bool guarded);
41 static edata_t *extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
42     ecache_t *ecache, edata_t *edata, bool *coalesced);
43 static edata_t *extent_alloc_retained(tsdn_t *tsdn, pac_t *pac,
44     ehooks_t *ehooks, edata_t *expand_edata, size_t size, size_t alignment,
45     bool zero, bool *commit, bool guarded);
46 
47 /******************************************************************************/
48 
49 size_t
50 extent_sn_next(pac_t *pac) {
51 	return atomic_fetch_add_zu(&pac->extent_sn_next, 1, ATOMIC_RELAXED);
52 }
53 
54 static inline bool
55 extent_may_force_decay(pac_t *pac) {
56 	return !(pac_decay_ms_get(pac, extent_state_dirty) == -1
57 	    || pac_decay_ms_get(pac, extent_state_muzzy) == -1);
58 }
59 
60 static bool
61 extent_try_delayed_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
62     ecache_t *ecache, edata_t *edata) {
63 	emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active);
64 
65 	bool coalesced;
66 	edata = extent_try_coalesce(tsdn, pac, ehooks, ecache,
67 	    edata, &coalesced);
68 	emap_update_edata_state(tsdn, pac->emap, edata, ecache->state);
69 
70 	if (!coalesced) {
71 		return true;
72 	}
73 	eset_insert(&ecache->eset, edata);
74 	return false;
75 }
76 
77 edata_t *
78 ecache_alloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
79     edata_t *expand_edata, size_t size, size_t alignment, bool zero,
80     bool guarded) {
81 	assert(size != 0);
82 	assert(alignment != 0);
83 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
84 	    WITNESS_RANK_CORE, 0);
85 
86 	bool commit = true;
87 	edata_t *edata = extent_recycle(tsdn, pac, ehooks, ecache, expand_edata,
88 	    size, alignment, zero, &commit, false, guarded);
89 	assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC);
90 	assert(edata == NULL || edata_guarded_get(edata) == guarded);
91 	return edata;
92 }
93 
94 edata_t *
95 ecache_alloc_grow(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
96     edata_t *expand_edata, size_t size, size_t alignment, bool zero,
97     bool guarded) {
98 	assert(size != 0);
99 	assert(alignment != 0);
100 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
101 	    WITNESS_RANK_CORE, 0);
102 
103 	bool commit = true;
104 	edata_t *edata = extent_alloc_retained(tsdn, pac, ehooks, expand_edata,
105 	    size, alignment, zero, &commit, guarded);
106 	if (edata == NULL) {
107 		if (opt_retain && expand_edata != NULL) {
108 			/*
109 			 * When retain is enabled and trying to expand, we do
110 			 * not attempt extent_alloc_wrapper which does mmap that
111 			 * is very unlikely to succeed (unless it happens to be
112 			 * at the end).
113 			 */
114 			return NULL;
115 		}
116 		if (guarded) {
117 			/*
118 			 * Means no cached guarded extents available (and no
119 			 * grow_retained was attempted).  The pac_alloc flow
120 			 * will alloc regular extents to make new guarded ones.
121 			 */
122 			return NULL;
123 		}
124 		void *new_addr = (expand_edata == NULL) ? NULL :
125 		    edata_past_get(expand_edata);
126 		edata = extent_alloc_wrapper(tsdn, pac, ehooks, new_addr,
127 		    size, alignment, zero, &commit,
128 		    /* growing_retained */ false);
129 	}
130 
131 	assert(edata == NULL || edata_pai_get(edata) == EXTENT_PAI_PAC);
132 	return edata;
133 }
134 
135 void
136 ecache_dalloc(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
137     edata_t *edata) {
138 	assert(edata_base_get(edata) != NULL);
139 	assert(edata_size_get(edata) != 0);
140 	assert(edata_pai_get(edata) == EXTENT_PAI_PAC);
141 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
142 	    WITNESS_RANK_CORE, 0);
143 
144 	edata_addr_set(edata, edata_base_get(edata));
145 	edata_zeroed_set(edata, false);
146 
147 	extent_record(tsdn, pac, ehooks, ecache, edata);
148 }
149 
150 edata_t *
151 ecache_evict(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
152     ecache_t *ecache, size_t npages_min) {
153 	malloc_mutex_lock(tsdn, &ecache->mtx);
154 
155 	/*
156 	 * Get the LRU coalesced extent, if any.  If coalescing was delayed,
157 	 * the loop will iterate until the LRU extent is fully coalesced.
158 	 */
159 	edata_t *edata;
160 	while (true) {
161 		/* Get the LRU extent, if any. */
162 		eset_t *eset = &ecache->eset;
163 		edata = edata_list_inactive_first(&eset->lru);
164 		if (edata == NULL) {
165 			/*
166 			 * Next check if there are guarded extents.  They are
167 			 * more expensive to purge (since they are not
168 			 * mergeable), thus in favor of caching them longer.
169 			 */
170 			eset = &ecache->guarded_eset;
171 			edata = edata_list_inactive_first(&eset->lru);
172 			if (edata == NULL) {
173 				goto label_return;
174 			}
175 		}
176 		/* Check the eviction limit. */
177 		size_t extents_npages = ecache_npages_get(ecache);
178 		if (extents_npages <= npages_min) {
179 			edata = NULL;
180 			goto label_return;
181 		}
182 		eset_remove(eset, edata);
183 		if (!ecache->delay_coalesce || edata_guarded_get(edata)) {
184 			break;
185 		}
186 		/* Try to coalesce. */
187 		if (extent_try_delayed_coalesce(tsdn, pac, ehooks, ecache,
188 		    edata)) {
189 			break;
190 		}
191 		/*
192 		 * The LRU extent was just coalesced and the result placed in
193 		 * the LRU at its neighbor's position.  Start over.
194 		 */
195 	}
196 
197 	/*
198 	 * Either mark the extent active or deregister it to protect against
199 	 * concurrent operations.
200 	 */
201 	switch (ecache->state) {
202 	case extent_state_active:
203 		not_reached();
204 	case extent_state_dirty:
205 	case extent_state_muzzy:
206 		emap_update_edata_state(tsdn, pac->emap, edata,
207 		    extent_state_active);
208 		break;
209 	case extent_state_retained:
210 		extent_deregister(tsdn, pac, edata);
211 		break;
212 	default:
213 		not_reached();
214 	}
215 
216 label_return:
217 	malloc_mutex_unlock(tsdn, &ecache->mtx);
218 	return edata;
219 }
220 
221 /*
222  * This can only happen when we fail to allocate a new extent struct (which
223  * indicates OOM), e.g. when trying to split an existing extent.
224  */
225 static void
226 extents_abandon_vm(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
227     edata_t *edata, bool growing_retained) {
228 	size_t sz = edata_size_get(edata);
229 	if (config_stats) {
230 		atomic_fetch_add_zu(&pac->stats->abandoned_vm, sz,
231 		    ATOMIC_RELAXED);
232 	}
233 	/*
234 	 * Leak extent after making sure its pages have already been purged, so
235 	 * that this is only a virtual memory leak.
236 	 */
237 	if (ecache->state == extent_state_dirty) {
238 		if (extent_purge_lazy_impl(tsdn, ehooks, edata, 0, sz,
239 		    growing_retained)) {
240 			extent_purge_forced_impl(tsdn, ehooks, edata, 0,
241 			    edata_size_get(edata), growing_retained);
242 		}
243 	}
244 	edata_cache_put(tsdn, pac->edata_cache, edata);
245 }
246 
247 static void
248 extent_deactivate_locked_impl(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache,
249     edata_t *edata) {
250 	malloc_mutex_assert_owner(tsdn, &ecache->mtx);
251 	assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache));
252 
253 	emap_update_edata_state(tsdn, pac->emap, edata, ecache->state);
254 	eset_t *eset = edata_guarded_get(edata) ? &ecache->guarded_eset :
255 	    &ecache->eset;
256 	eset_insert(eset, edata);
257 }
258 
259 static void
260 extent_deactivate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache,
261     edata_t *edata) {
262 	assert(edata_state_get(edata) == extent_state_active);
263 	extent_deactivate_locked_impl(tsdn, pac, ecache, edata);
264 }
265 
266 static void
267 extent_deactivate_check_state_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache,
268     edata_t *edata, extent_state_t expected_state) {
269 	assert(edata_state_get(edata) == expected_state);
270 	extent_deactivate_locked_impl(tsdn, pac, ecache, edata);
271 }
272 
273 static void
274 extent_activate_locked(tsdn_t *tsdn, pac_t *pac, ecache_t *ecache, eset_t *eset,
275     edata_t *edata) {
276 	assert(edata_arena_ind_get(edata) == ecache_ind_get(ecache));
277 	assert(edata_state_get(edata) == ecache->state ||
278 	    edata_state_get(edata) == extent_state_merging);
279 
280 	eset_remove(eset, edata);
281 	emap_update_edata_state(tsdn, pac->emap, edata, extent_state_active);
282 }
283 
284 void
285 extent_gdump_add(tsdn_t *tsdn, const edata_t *edata) {
286 	cassert(config_prof);
287 	/* prof_gdump() requirement. */
288 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
289 	    WITNESS_RANK_CORE, 0);
290 
291 	if (opt_prof && edata_state_get(edata) == extent_state_active) {
292 		size_t nadd = edata_size_get(edata) >> LG_PAGE;
293 		size_t cur = atomic_fetch_add_zu(&curpages, nadd,
294 		    ATOMIC_RELAXED) + nadd;
295 		size_t high = atomic_load_zu(&highpages, ATOMIC_RELAXED);
296 		while (cur > high && !atomic_compare_exchange_weak_zu(
297 		    &highpages, &high, cur, ATOMIC_RELAXED, ATOMIC_RELAXED)) {
298 			/*
299 			 * Don't refresh cur, because it may have decreased
300 			 * since this thread lost the highpages update race.
301 			 * Note that high is updated in case of CAS failure.
302 			 */
303 		}
304 		if (cur > high && prof_gdump_get_unlocked()) {
305 			prof_gdump(tsdn);
306 		}
307 	}
308 }
309 
310 static void
311 extent_gdump_sub(tsdn_t *tsdn, const edata_t *edata) {
312 	cassert(config_prof);
313 
314 	if (opt_prof && edata_state_get(edata) == extent_state_active) {
315 		size_t nsub = edata_size_get(edata) >> LG_PAGE;
316 		assert(atomic_load_zu(&curpages, ATOMIC_RELAXED) >= nsub);
317 		atomic_fetch_sub_zu(&curpages, nsub, ATOMIC_RELAXED);
318 	}
319 }
320 
321 static bool
322 extent_register_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata, bool gdump_add) {
323 	assert(edata_state_get(edata) == extent_state_active);
324 	/*
325 	 * No locking needed, as the edata must be in active state, which
326 	 * prevents other threads from accessing the edata.
327 	 */
328 	if (emap_register_boundary(tsdn, pac->emap, edata, SC_NSIZES,
329 	    /* slab */ false)) {
330 		return true;
331 	}
332 
333 	if (config_prof && gdump_add) {
334 		extent_gdump_add(tsdn, edata);
335 	}
336 
337 	return false;
338 }
339 
340 static bool
341 extent_register(tsdn_t *tsdn, pac_t *pac, edata_t *edata) {
342 	return extent_register_impl(tsdn, pac, edata, true);
343 }
344 
345 static bool
346 extent_register_no_gdump_add(tsdn_t *tsdn, pac_t *pac, edata_t *edata) {
347 	return extent_register_impl(tsdn, pac, edata, false);
348 }
349 
350 static void
351 extent_reregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) {
352 	bool err = extent_register(tsdn, pac, edata);
353 	assert(!err);
354 }
355 
356 /*
357  * Removes all pointers to the given extent from the global rtree.
358  */
359 static void
360 extent_deregister_impl(tsdn_t *tsdn, pac_t *pac, edata_t *edata,
361     bool gdump) {
362 	emap_deregister_boundary(tsdn, pac->emap, edata);
363 
364 	if (config_prof && gdump) {
365 		extent_gdump_sub(tsdn, edata);
366 	}
367 }
368 
369 static void
370 extent_deregister(tsdn_t *tsdn, pac_t *pac, edata_t *edata) {
371 	extent_deregister_impl(tsdn, pac, edata, true);
372 }
373 
374 static void
375 extent_deregister_no_gdump_sub(tsdn_t *tsdn, pac_t *pac,
376     edata_t *edata) {
377 	extent_deregister_impl(tsdn, pac, edata, false);
378 }
379 
380 /*
381  * Tries to find and remove an extent from ecache that can be used for the
382  * given allocation request.
383  */
384 static edata_t *
385 extent_recycle_extract(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
386     ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment,
387     bool guarded) {
388 	malloc_mutex_assert_owner(tsdn, &ecache->mtx);
389 	assert(alignment > 0);
390 	if (config_debug && expand_edata != NULL) {
391 		/*
392 		 * Non-NULL expand_edata indicates in-place expanding realloc.
393 		 * new_addr must either refer to a non-existing extent, or to
394 		 * the base of an extant extent, since only active slabs support
395 		 * interior lookups (which of course cannot be recycled).
396 		 */
397 		void *new_addr = edata_past_get(expand_edata);
398 		assert(PAGE_ADDR2BASE(new_addr) == new_addr);
399 		assert(alignment <= PAGE);
400 	}
401 
402 	edata_t *edata;
403 	eset_t *eset = guarded ? &ecache->guarded_eset : &ecache->eset;
404 	if (expand_edata != NULL) {
405 		edata = emap_try_acquire_edata_neighbor_expand(tsdn, pac->emap,
406 		    expand_edata, EXTENT_PAI_PAC, ecache->state);
407 		if (edata != NULL) {
408 			extent_assert_can_expand(expand_edata, edata);
409 			if (edata_size_get(edata) < size) {
410 				emap_release_edata(tsdn, pac->emap, edata,
411 				    ecache->state);
412 				edata = NULL;
413 			}
414 		}
415 	} else {
416 		/*
417 		 * A large extent might be broken up from its original size to
418 		 * some small size to satisfy a small request.  When that small
419 		 * request is freed, though, it won't merge back with the larger
420 		 * extent if delayed coalescing is on.  The large extent can
421 		 * then no longer satify a request for its original size.  To
422 		 * limit this effect, when delayed coalescing is enabled, we
423 		 * put a cap on how big an extent we can split for a request.
424 		 */
425 		unsigned lg_max_fit = ecache->delay_coalesce
426 		    ? (unsigned)opt_lg_extent_max_active_fit : SC_PTR_BITS;
427 
428 		/*
429 		 * If split and merge are not allowed (Windows w/o retain), try
430 		 * exact fit only.
431 		 *
432 		 * For simplicity purposes, splitting guarded extents is not
433 		 * supported.  Hence, we do only exact fit for guarded
434 		 * allocations.
435 		 */
436 		bool exact_only = (!maps_coalesce && !opt_retain) || guarded;
437 		edata = eset_fit(eset, size, alignment, exact_only,
438 		    lg_max_fit);
439 	}
440 	if (edata == NULL) {
441 		return NULL;
442 	}
443 	assert(!guarded || edata_guarded_get(edata));
444 	extent_activate_locked(tsdn, pac, ecache, eset, edata);
445 
446 	return edata;
447 }
448 
449 /*
450  * Given an allocation request and an extent guaranteed to be able to satisfy
451  * it, this splits off lead and trail extents, leaving edata pointing to an
452  * extent satisfying the allocation.
453  * This function doesn't put lead or trail into any ecache; it's the caller's
454  * job to ensure that they can be reused.
455  */
456 typedef enum {
457 	/*
458 	 * Split successfully.  lead, edata, and trail, are modified to extents
459 	 * describing the ranges before, in, and after the given allocation.
460 	 */
461 	extent_split_interior_ok,
462 	/*
463 	 * The extent can't satisfy the given allocation request.  None of the
464 	 * input edata_t *s are touched.
465 	 */
466 	extent_split_interior_cant_alloc,
467 	/*
468 	 * In a potentially invalid state.  Must leak (if *to_leak is non-NULL),
469 	 * and salvage what's still salvageable (if *to_salvage is non-NULL).
470 	 * None of lead, edata, or trail are valid.
471 	 */
472 	extent_split_interior_error
473 } extent_split_interior_result_t;
474 
475 static extent_split_interior_result_t
476 extent_split_interior(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
477     /* The result of splitting, in case of success. */
478     edata_t **edata, edata_t **lead, edata_t **trail,
479     /* The mess to clean up, in case of error. */
480     edata_t **to_leak, edata_t **to_salvage,
481     edata_t *expand_edata, size_t size, size_t alignment) {
482 	size_t leadsize = ALIGNMENT_CEILING((uintptr_t)edata_base_get(*edata),
483 	    PAGE_CEILING(alignment)) - (uintptr_t)edata_base_get(*edata);
484 	assert(expand_edata == NULL || leadsize == 0);
485 	if (edata_size_get(*edata) < leadsize + size) {
486 		return extent_split_interior_cant_alloc;
487 	}
488 	size_t trailsize = edata_size_get(*edata) - leadsize - size;
489 
490 	*lead = NULL;
491 	*trail = NULL;
492 	*to_leak = NULL;
493 	*to_salvage = NULL;
494 
495 	/* Split the lead. */
496 	if (leadsize != 0) {
497 		assert(!edata_guarded_get(*edata));
498 		*lead = *edata;
499 		*edata = extent_split_impl(tsdn, pac, ehooks, *lead, leadsize,
500 		    size + trailsize, /* holding_core_locks*/ true);
501 		if (*edata == NULL) {
502 			*to_leak = *lead;
503 			*lead = NULL;
504 			return extent_split_interior_error;
505 		}
506 	}
507 
508 	/* Split the trail. */
509 	if (trailsize != 0) {
510 		assert(!edata_guarded_get(*edata));
511 		*trail = extent_split_impl(tsdn, pac, ehooks, *edata, size,
512 		    trailsize, /* holding_core_locks */ true);
513 		if (*trail == NULL) {
514 			*to_leak = *edata;
515 			*to_salvage = *lead;
516 			*lead = NULL;
517 			*edata = NULL;
518 			return extent_split_interior_error;
519 		}
520 	}
521 
522 	return extent_split_interior_ok;
523 }
524 
525 /*
526  * This fulfills the indicated allocation request out of the given extent (which
527  * the caller should have ensured was big enough).  If there's any unused space
528  * before or after the resulting allocation, that space is given its own extent
529  * and put back into ecache.
530  */
531 static edata_t *
532 extent_recycle_split(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
533     ecache_t *ecache, edata_t *expand_edata, size_t size, size_t alignment,
534     edata_t *edata, bool growing_retained) {
535 	assert(!edata_guarded_get(edata) || size == edata_size_get(edata));
536 	malloc_mutex_assert_owner(tsdn, &ecache->mtx);
537 
538 	edata_t *lead;
539 	edata_t *trail;
540 	edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL);
541 	edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL);
542 
543 	extent_split_interior_result_t result = extent_split_interior(
544 	    tsdn, pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage,
545 	    expand_edata, size, alignment);
546 
547 	if (!maps_coalesce && result != extent_split_interior_ok
548 	    && !opt_retain) {
549 		/*
550 		 * Split isn't supported (implies Windows w/o retain).  Avoid
551 		 * leaking the extent.
552 		 */
553 		assert(to_leak != NULL && lead == NULL && trail == NULL);
554 		extent_deactivate_locked(tsdn, pac, ecache, to_leak);
555 		return NULL;
556 	}
557 
558 	if (result == extent_split_interior_ok) {
559 		if (lead != NULL) {
560 			extent_deactivate_locked(tsdn, pac, ecache, lead);
561 		}
562 		if (trail != NULL) {
563 			extent_deactivate_locked(tsdn, pac, ecache, trail);
564 		}
565 		return edata;
566 	} else {
567 		/*
568 		 * We should have picked an extent that was large enough to
569 		 * fulfill our allocation request.
570 		 */
571 		assert(result == extent_split_interior_error);
572 		if (to_salvage != NULL) {
573 			extent_deregister(tsdn, pac, to_salvage);
574 		}
575 		if (to_leak != NULL) {
576 			extent_deregister_no_gdump_sub(tsdn, pac, to_leak);
577 			/*
578 			 * May go down the purge path (which assume no ecache
579 			 * locks).  Only happens with OOM caused split failures.
580 			 */
581 			malloc_mutex_unlock(tsdn, &ecache->mtx);
582 			extents_abandon_vm(tsdn, pac, ehooks, ecache, to_leak,
583 			    growing_retained);
584 			malloc_mutex_lock(tsdn, &ecache->mtx);
585 		}
586 		return NULL;
587 	}
588 	unreachable();
589 }
590 
591 /*
592  * Tries to satisfy the given allocation request by reusing one of the extents
593  * in the given ecache_t.
594  */
595 static edata_t *
596 extent_recycle(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
597     edata_t *expand_edata, size_t size, size_t alignment, bool zero,
598     bool *commit, bool growing_retained, bool guarded) {
599 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
600 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
601 	assert(!guarded || expand_edata == NULL);
602 	assert(!guarded || alignment <= PAGE);
603 
604 	malloc_mutex_lock(tsdn, &ecache->mtx);
605 
606 	edata_t *edata = extent_recycle_extract(tsdn, pac, ehooks, ecache,
607 	    expand_edata, size, alignment, guarded);
608 	if (edata == NULL) {
609 		malloc_mutex_unlock(tsdn, &ecache->mtx);
610 		return NULL;
611 	}
612 
613 	edata = extent_recycle_split(tsdn, pac, ehooks, ecache, expand_edata,
614 	    size, alignment, edata, growing_retained);
615 	malloc_mutex_unlock(tsdn, &ecache->mtx);
616 	if (edata == NULL) {
617 		return NULL;
618 	}
619 
620 	assert(edata_state_get(edata) == extent_state_active);
621 	if (extent_commit_zero(tsdn, ehooks, edata, *commit, zero,
622 	    growing_retained)) {
623 		extent_record(tsdn, pac, ehooks, ecache, edata);
624 		return NULL;
625 	}
626 	if (edata_committed_get(edata)) {
627 		/*
628 		 * This reverses the purpose of this variable - previously it
629 		 * was treated as an input parameter, now it turns into an
630 		 * output parameter, reporting if the edata has actually been
631 		 * committed.
632 		 */
633 		*commit = true;
634 	}
635 	return edata;
636 }
637 
638 /*
639  * If virtual memory is retained, create increasingly larger extents from which
640  * to split requested extents in order to limit the total number of disjoint
641  * virtual memory ranges retained by each shard.
642  */
643 static edata_t *
644 extent_grow_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
645     size_t size, size_t alignment, bool zero, bool *commit) {
646 	malloc_mutex_assert_owner(tsdn, &pac->grow_mtx);
647 
648 	size_t alloc_size_min = size + PAGE_CEILING(alignment) - PAGE;
649 	/* Beware size_t wrap-around. */
650 	if (alloc_size_min < size) {
651 		goto label_err;
652 	}
653 	/*
654 	 * Find the next extent size in the series that would be large enough to
655 	 * satisfy this request.
656 	 */
657 	size_t alloc_size;
658 	pszind_t exp_grow_skip;
659 	bool err = exp_grow_size_prepare(&pac->exp_grow, alloc_size_min,
660 	    &alloc_size, &exp_grow_skip);
661 	if (err) {
662 		goto label_err;
663 	}
664 
665 	edata_t *edata = edata_cache_get(tsdn, pac->edata_cache);
666 	if (edata == NULL) {
667 		goto label_err;
668 	}
669 	bool zeroed = false;
670 	bool committed = false;
671 
672 	void *ptr = ehooks_alloc(tsdn, ehooks, NULL, alloc_size, PAGE, &zeroed,
673 	    &committed);
674 
675 	if (ptr == NULL) {
676 		edata_cache_put(tsdn, pac->edata_cache, edata);
677 		goto label_err;
678 	}
679 
680 	edata_init(edata, ecache_ind_get(&pac->ecache_retained), ptr,
681 	    alloc_size, false, SC_NSIZES, extent_sn_next(pac),
682 	    extent_state_active, zeroed, committed, EXTENT_PAI_PAC,
683 	    EXTENT_IS_HEAD);
684 
685 	if (extent_register_no_gdump_add(tsdn, pac, edata)) {
686 		edata_cache_put(tsdn, pac->edata_cache, edata);
687 		goto label_err;
688 	}
689 
690 	if (edata_committed_get(edata)) {
691 		*commit = true;
692 	}
693 
694 	edata_t *lead;
695 	edata_t *trail;
696 	edata_t *to_leak JEMALLOC_CC_SILENCE_INIT(NULL);
697 	edata_t *to_salvage JEMALLOC_CC_SILENCE_INIT(NULL);
698 
699 	extent_split_interior_result_t result = extent_split_interior(tsdn,
700 	    pac, ehooks, &edata, &lead, &trail, &to_leak, &to_salvage, NULL,
701 	    size, alignment);
702 
703 	if (result == extent_split_interior_ok) {
704 		if (lead != NULL) {
705 			extent_record(tsdn, pac, ehooks, &pac->ecache_retained,
706 			    lead);
707 		}
708 		if (trail != NULL) {
709 			extent_record(tsdn, pac, ehooks, &pac->ecache_retained,
710 			    trail);
711 		}
712 	} else {
713 		/*
714 		 * We should have allocated a sufficiently large extent; the
715 		 * cant_alloc case should not occur.
716 		 */
717 		assert(result == extent_split_interior_error);
718 		if (to_salvage != NULL) {
719 			if (config_prof) {
720 				extent_gdump_add(tsdn, to_salvage);
721 			}
722 			extent_record(tsdn, pac, ehooks, &pac->ecache_retained,
723 			    to_salvage);
724 		}
725 		if (to_leak != NULL) {
726 			extent_deregister_no_gdump_sub(tsdn, pac, to_leak);
727 			extents_abandon_vm(tsdn, pac, ehooks,
728 			    &pac->ecache_retained, to_leak, true);
729 		}
730 		goto label_err;
731 	}
732 
733 	if (*commit && !edata_committed_get(edata)) {
734 		if (extent_commit_impl(tsdn, ehooks, edata, 0,
735 		    edata_size_get(edata), true)) {
736 			extent_record(tsdn, pac, ehooks,
737 			    &pac->ecache_retained, edata);
738 			goto label_err;
739 		}
740 		/* A successful commit should return zeroed memory. */
741 		if (config_debug) {
742 			void *addr = edata_addr_get(edata);
743 			size_t *p = (size_t *)(uintptr_t)addr;
744 			/* Check the first page only. */
745 			for (size_t i = 0; i < PAGE / sizeof(size_t); i++) {
746 				assert(p[i] == 0);
747 			}
748 		}
749 	}
750 
751 	/*
752 	 * Increment extent_grow_next if doing so wouldn't exceed the allowed
753 	 * range.
754 	 */
755 	/* All opportunities for failure are past. */
756 	exp_grow_size_commit(&pac->exp_grow, exp_grow_skip);
757 	malloc_mutex_unlock(tsdn, &pac->grow_mtx);
758 
759 	if (config_prof) {
760 		/* Adjust gdump stats now that extent is final size. */
761 		extent_gdump_add(tsdn, edata);
762 	}
763 	if (zero && !edata_zeroed_get(edata)) {
764 		ehooks_zero(tsdn, ehooks, edata_base_get(edata),
765 		    edata_size_get(edata));
766 	}
767 	return edata;
768 label_err:
769 	malloc_mutex_unlock(tsdn, &pac->grow_mtx);
770 	return NULL;
771 }
772 
773 static edata_t *
774 extent_alloc_retained(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
775     edata_t *expand_edata, size_t size, size_t alignment, bool zero,
776     bool *commit, bool guarded) {
777 	assert(size != 0);
778 	assert(alignment != 0);
779 
780 	malloc_mutex_lock(tsdn, &pac->grow_mtx);
781 
782 	edata_t *edata = extent_recycle(tsdn, pac, ehooks,
783 	    &pac->ecache_retained, expand_edata, size, alignment, zero, commit,
784 	    /* growing_retained */ true, guarded);
785 	if (edata != NULL) {
786 		malloc_mutex_unlock(tsdn, &pac->grow_mtx);
787 		if (config_prof) {
788 			extent_gdump_add(tsdn, edata);
789 		}
790 	} else if (opt_retain && expand_edata == NULL && !guarded) {
791 		edata = extent_grow_retained(tsdn, pac, ehooks, size,
792 		    alignment, zero, commit);
793 		/* extent_grow_retained() always releases pac->grow_mtx. */
794 	} else {
795 		malloc_mutex_unlock(tsdn, &pac->grow_mtx);
796 	}
797 	malloc_mutex_assert_not_owner(tsdn, &pac->grow_mtx);
798 
799 	return edata;
800 }
801 
802 static bool
803 extent_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
804     edata_t *inner, edata_t *outer, bool forward) {
805 	extent_assert_can_coalesce(inner, outer);
806 	eset_remove(&ecache->eset, outer);
807 
808 	bool err = extent_merge_impl(tsdn, pac, ehooks,
809 	    forward ? inner : outer, forward ? outer : inner,
810 	    /* holding_core_locks */ true);
811 	if (err) {
812 		extent_deactivate_check_state_locked(tsdn, pac, ecache, outer,
813 		    extent_state_merging);
814 	}
815 
816 	return err;
817 }
818 
819 static edata_t *
820 extent_try_coalesce_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
821     ecache_t *ecache, edata_t *edata, bool *coalesced) {
822 	assert(!edata_guarded_get(edata));
823 	/*
824 	 * We avoid checking / locking inactive neighbors for large size
825 	 * classes, since they are eagerly coalesced on deallocation which can
826 	 * cause lock contention.
827 	 */
828 	/*
829 	 * Continue attempting to coalesce until failure, to protect against
830 	 * races with other threads that are thwarted by this one.
831 	 */
832 	bool again;
833 	do {
834 		again = false;
835 
836 		/* Try to coalesce forward. */
837 		edata_t *next = emap_try_acquire_edata_neighbor(tsdn, pac->emap,
838 		    edata, EXTENT_PAI_PAC, ecache->state, /* forward */ true);
839 		if (next != NULL) {
840 			if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata,
841 			    next, true)) {
842 				if (ecache->delay_coalesce) {
843 					/* Do minimal coalescing. */
844 					*coalesced = true;
845 					return edata;
846 				}
847 				again = true;
848 			}
849 		}
850 
851 		/* Try to coalesce backward. */
852 		edata_t *prev = emap_try_acquire_edata_neighbor(tsdn, pac->emap,
853 		    edata, EXTENT_PAI_PAC, ecache->state, /* forward */ false);
854 		if (prev != NULL) {
855 			if (!extent_coalesce(tsdn, pac, ehooks, ecache, edata,
856 			    prev, false)) {
857 				edata = prev;
858 				if (ecache->delay_coalesce) {
859 					/* Do minimal coalescing. */
860 					*coalesced = true;
861 					return edata;
862 				}
863 				again = true;
864 			}
865 		}
866 	} while (again);
867 
868 	if (ecache->delay_coalesce) {
869 		*coalesced = false;
870 	}
871 	return edata;
872 }
873 
874 static edata_t *
875 extent_try_coalesce(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
876     ecache_t *ecache, edata_t *edata, bool *coalesced) {
877 	return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata,
878 	    coalesced);
879 }
880 
881 static edata_t *
882 extent_try_coalesce_large(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
883     ecache_t *ecache, edata_t *edata, bool *coalesced) {
884 	return extent_try_coalesce_impl(tsdn, pac, ehooks, ecache, edata,
885 	    coalesced);
886 }
887 
888 /* Purge a single extent to retained / unmapped directly. */
889 static void
890 extent_maximally_purge(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
891     edata_t *edata) {
892 	size_t extent_size = edata_size_get(edata);
893 	extent_dalloc_wrapper(tsdn, pac, ehooks, edata);
894 	if (config_stats) {
895 		/* Update stats accordingly. */
896 		LOCKEDINT_MTX_LOCK(tsdn, *pac->stats_mtx);
897 		locked_inc_u64(tsdn,
898 		    LOCKEDINT_MTX(*pac->stats_mtx),
899 		    &pac->stats->decay_dirty.nmadvise, 1);
900 		locked_inc_u64(tsdn,
901 		    LOCKEDINT_MTX(*pac->stats_mtx),
902 		    &pac->stats->decay_dirty.purged,
903 		    extent_size >> LG_PAGE);
904 		LOCKEDINT_MTX_UNLOCK(tsdn, *pac->stats_mtx);
905 		atomic_fetch_sub_zu(&pac->stats->pac_mapped, extent_size,
906 		    ATOMIC_RELAXED);
907 	}
908 }
909 
910 /*
911  * Does the metadata management portions of putting an unused extent into the
912  * given ecache_t (coalesces and inserts into the eset).
913  */
914 void
915 extent_record(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, ecache_t *ecache,
916     edata_t *edata) {
917 	assert((ecache->state != extent_state_dirty &&
918 	    ecache->state != extent_state_muzzy) ||
919 	    !edata_zeroed_get(edata));
920 
921 	malloc_mutex_lock(tsdn, &ecache->mtx);
922 
923 	emap_assert_mapped(tsdn, pac->emap, edata);
924 
925 	if (edata_guarded_get(edata)) {
926 		goto label_skip_coalesce;
927 	}
928 	if (!ecache->delay_coalesce) {
929 		edata = extent_try_coalesce(tsdn, pac, ehooks, ecache, edata,
930 		    NULL);
931 	} else if (edata_size_get(edata) >= SC_LARGE_MINCLASS) {
932 		assert(ecache == &pac->ecache_dirty);
933 		/* Always coalesce large extents eagerly. */
934 		bool coalesced;
935 		do {
936 			assert(edata_state_get(edata) == extent_state_active);
937 			edata = extent_try_coalesce_large(tsdn, pac, ehooks,
938 			    ecache, edata, &coalesced);
939 		} while (coalesced);
940 		if (edata_size_get(edata) >=
941 		    atomic_load_zu(&pac->oversize_threshold, ATOMIC_RELAXED)
942 		    && extent_may_force_decay(pac)) {
943 			/* Shortcut to purge the oversize extent eagerly. */
944 			malloc_mutex_unlock(tsdn, &ecache->mtx);
945 			extent_maximally_purge(tsdn, pac, ehooks, edata);
946 			return;
947 		}
948 	}
949 label_skip_coalesce:
950 	extent_deactivate_locked(tsdn, pac, ecache, edata);
951 
952 	malloc_mutex_unlock(tsdn, &ecache->mtx);
953 }
954 
955 void
956 extent_dalloc_gap(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
957     edata_t *edata) {
958 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
959 	    WITNESS_RANK_CORE, 0);
960 
961 	if (extent_register(tsdn, pac, edata)) {
962 		edata_cache_put(tsdn, pac->edata_cache, edata);
963 		return;
964 	}
965 	extent_dalloc_wrapper(tsdn, pac, ehooks, edata);
966 }
967 
968 static bool
969 extent_dalloc_wrapper_try(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
970     edata_t *edata) {
971 	bool err;
972 
973 	assert(edata_base_get(edata) != NULL);
974 	assert(edata_size_get(edata) != 0);
975 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
976 	    WITNESS_RANK_CORE, 0);
977 
978 	edata_addr_set(edata, edata_base_get(edata));
979 
980 	/* Try to deallocate. */
981 	err = ehooks_dalloc(tsdn, ehooks, edata_base_get(edata),
982 	    edata_size_get(edata), edata_committed_get(edata));
983 
984 	if (!err) {
985 		edata_cache_put(tsdn, pac->edata_cache, edata);
986 	}
987 
988 	return err;
989 }
990 
991 edata_t *
992 extent_alloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
993     void *new_addr, size_t size, size_t alignment, bool zero, bool *commit,
994     bool growing_retained) {
995 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
996 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
997 
998 	edata_t *edata = edata_cache_get(tsdn, pac->edata_cache);
999 	if (edata == NULL) {
1000 		return NULL;
1001 	}
1002 	size_t palignment = ALIGNMENT_CEILING(alignment, PAGE);
1003 	void *addr = ehooks_alloc(tsdn, ehooks, new_addr, size, palignment,
1004 	    &zero, commit);
1005 	if (addr == NULL) {
1006 		edata_cache_put(tsdn, pac->edata_cache, edata);
1007 		return NULL;
1008 	}
1009 	edata_init(edata, ecache_ind_get(&pac->ecache_dirty), addr,
1010 	    size, /* slab */ false, SC_NSIZES, extent_sn_next(pac),
1011 	    extent_state_active, zero, *commit, EXTENT_PAI_PAC,
1012 	    opt_retain ? EXTENT_IS_HEAD : EXTENT_NOT_HEAD);
1013 	/*
1014 	 * Retained memory is not counted towards gdump.  Only if an extent is
1015 	 * allocated as a separate mapping, i.e. growing_retained is false, then
1016 	 * gdump should be updated.
1017 	 */
1018 	bool gdump_add = !growing_retained;
1019 	if (extent_register_impl(tsdn, pac, edata, gdump_add)) {
1020 		edata_cache_put(tsdn, pac->edata_cache, edata);
1021 		return NULL;
1022 	}
1023 
1024 	return edata;
1025 }
1026 
1027 void
1028 extent_dalloc_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
1029     edata_t *edata) {
1030 	assert(edata_pai_get(edata) == EXTENT_PAI_PAC);
1031 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1032 	    WITNESS_RANK_CORE, 0);
1033 
1034 	/* Avoid calling the default extent_dalloc unless have to. */
1035 	if (!ehooks_dalloc_will_fail(ehooks)) {
1036 		/* Remove guard pages for dalloc / unmap. */
1037 		if (edata_guarded_get(edata)) {
1038 			assert(ehooks_are_default(ehooks));
1039 			san_unguard_pages_two_sided(tsdn, ehooks, edata,
1040 			    pac->emap);
1041 		}
1042 		/*
1043 		 * Deregister first to avoid a race with other allocating
1044 		 * threads, and reregister if deallocation fails.
1045 		 */
1046 		extent_deregister(tsdn, pac, edata);
1047 		if (!extent_dalloc_wrapper_try(tsdn, pac, ehooks, edata)) {
1048 			return;
1049 		}
1050 		extent_reregister(tsdn, pac, edata);
1051 	}
1052 
1053 	/* Try to decommit; purge if that fails. */
1054 	bool zeroed;
1055 	if (!edata_committed_get(edata)) {
1056 		zeroed = true;
1057 	} else if (!extent_decommit_wrapper(tsdn, ehooks, edata, 0,
1058 	    edata_size_get(edata))) {
1059 		zeroed = true;
1060 	} else if (!ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata),
1061 	    edata_size_get(edata), 0, edata_size_get(edata))) {
1062 		zeroed = true;
1063 	} else if (edata_state_get(edata) == extent_state_muzzy ||
1064 	    !ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata),
1065 	    edata_size_get(edata), 0, edata_size_get(edata))) {
1066 		zeroed = false;
1067 	} else {
1068 		zeroed = false;
1069 	}
1070 	edata_zeroed_set(edata, zeroed);
1071 
1072 	if (config_prof) {
1073 		extent_gdump_sub(tsdn, edata);
1074 	}
1075 
1076 	extent_record(tsdn, pac, ehooks, &pac->ecache_retained, edata);
1077 }
1078 
1079 void
1080 extent_destroy_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
1081     edata_t *edata) {
1082 	assert(edata_base_get(edata) != NULL);
1083 	assert(edata_size_get(edata) != 0);
1084 	extent_state_t state = edata_state_get(edata);
1085 	assert(state == extent_state_retained || state == extent_state_active);
1086 	assert(emap_edata_is_acquired(tsdn, pac->emap, edata));
1087 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1088 	    WITNESS_RANK_CORE, 0);
1089 
1090 	if (edata_guarded_get(edata)) {
1091 		assert(opt_retain);
1092 		san_unguard_pages_pre_destroy(tsdn, ehooks, edata, pac->emap);
1093 	}
1094 	edata_addr_set(edata, edata_base_get(edata));
1095 
1096 	/* Try to destroy; silently fail otherwise. */
1097 	ehooks_destroy(tsdn, ehooks, edata_base_get(edata),
1098 	    edata_size_get(edata), edata_committed_get(edata));
1099 
1100 	edata_cache_put(tsdn, pac->edata_cache, edata);
1101 }
1102 
1103 static bool
1104 extent_commit_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1105     size_t offset, size_t length, bool growing_retained) {
1106 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1107 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1108 	bool err = ehooks_commit(tsdn, ehooks, edata_base_get(edata),
1109 	    edata_size_get(edata), offset, length);
1110 	edata_committed_set(edata, edata_committed_get(edata) || !err);
1111 	return err;
1112 }
1113 
1114 bool
1115 extent_commit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1116     size_t offset, size_t length) {
1117 	return extent_commit_impl(tsdn, ehooks, edata, offset, length,
1118 	    /* growing_retained */ false);
1119 }
1120 
1121 bool
1122 extent_decommit_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1123     size_t offset, size_t length) {
1124 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1125 	    WITNESS_RANK_CORE, 0);
1126 	bool err = ehooks_decommit(tsdn, ehooks, edata_base_get(edata),
1127 	    edata_size_get(edata), offset, length);
1128 	edata_committed_set(edata, edata_committed_get(edata) && err);
1129 	return err;
1130 }
1131 
1132 static bool
1133 extent_purge_lazy_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1134     size_t offset, size_t length, bool growing_retained) {
1135 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1136 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1137 	bool err = ehooks_purge_lazy(tsdn, ehooks, edata_base_get(edata),
1138 	    edata_size_get(edata), offset, length);
1139 	return err;
1140 }
1141 
1142 bool
1143 extent_purge_lazy_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1144     size_t offset, size_t length) {
1145 	return extent_purge_lazy_impl(tsdn, ehooks, edata, offset,
1146 	    length, false);
1147 }
1148 
1149 static bool
1150 extent_purge_forced_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1151     size_t offset, size_t length, bool growing_retained) {
1152 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1153 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1154 	bool err = ehooks_purge_forced(tsdn, ehooks, edata_base_get(edata),
1155 	    edata_size_get(edata), offset, length);
1156 	return err;
1157 }
1158 
1159 bool
1160 extent_purge_forced_wrapper(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1161     size_t offset, size_t length) {
1162 	return extent_purge_forced_impl(tsdn, ehooks, edata, offset, length,
1163 	    false);
1164 }
1165 
1166 /*
1167  * Accepts the extent to split, and the characteristics of each side of the
1168  * split.  The 'a' parameters go with the 'lead' of the resulting pair of
1169  * extents (the lower addressed portion of the split), and the 'b' parameters go
1170  * with the trail (the higher addressed portion).  This makes 'extent' the lead,
1171  * and returns the trail (except in case of error).
1172  */
1173 static edata_t *
1174 extent_split_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
1175     edata_t *edata, size_t size_a, size_t size_b, bool holding_core_locks) {
1176 	assert(edata_size_get(edata) == size_a + size_b);
1177 	/* Only the shrink path may split w/o holding core locks. */
1178 	if (holding_core_locks) {
1179 		witness_assert_positive_depth_to_rank(
1180 		    tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE);
1181 	} else {
1182 		witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1183 		    WITNESS_RANK_CORE, 0);
1184 	}
1185 
1186 	if (ehooks_split_will_fail(ehooks)) {
1187 		return NULL;
1188 	}
1189 
1190 	edata_t *trail = edata_cache_get(tsdn, pac->edata_cache);
1191 	if (trail == NULL) {
1192 		goto label_error_a;
1193 	}
1194 
1195 	edata_init(trail, edata_arena_ind_get(edata),
1196 	    (void *)((uintptr_t)edata_base_get(edata) + size_a), size_b,
1197 	    /* slab */ false, SC_NSIZES, edata_sn_get(edata),
1198 	    edata_state_get(edata), edata_zeroed_get(edata),
1199 	    edata_committed_get(edata), EXTENT_PAI_PAC, EXTENT_NOT_HEAD);
1200 	emap_prepare_t prepare;
1201 	bool err = emap_split_prepare(tsdn, pac->emap, &prepare, edata,
1202 	    size_a, trail, size_b);
1203 	if (err) {
1204 		goto label_error_b;
1205 	}
1206 
1207 	/*
1208 	 * No need to acquire trail or edata, because: 1) trail was new (just
1209 	 * allocated); and 2) edata is either an active allocation (the shrink
1210 	 * path), or in an acquired state (extracted from the ecache on the
1211 	 * extent_recycle_split path).
1212 	 */
1213 	assert(emap_edata_is_acquired(tsdn, pac->emap, edata));
1214 	assert(emap_edata_is_acquired(tsdn, pac->emap, trail));
1215 
1216 	err = ehooks_split(tsdn, ehooks, edata_base_get(edata), size_a + size_b,
1217 	    size_a, size_b, edata_committed_get(edata));
1218 
1219 	if (err) {
1220 		goto label_error_b;
1221 	}
1222 
1223 	edata_size_set(edata, size_a);
1224 	emap_split_commit(tsdn, pac->emap, &prepare, edata, size_a, trail,
1225 	    size_b);
1226 
1227 	return trail;
1228 label_error_b:
1229 	edata_cache_put(tsdn, pac->edata_cache, trail);
1230 label_error_a:
1231 	return NULL;
1232 }
1233 
1234 edata_t *
1235 extent_split_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *edata,
1236     size_t size_a, size_t size_b, bool holding_core_locks) {
1237 	return extent_split_impl(tsdn, pac, ehooks, edata, size_a, size_b,
1238 	    holding_core_locks);
1239 }
1240 
1241 static bool
1242 extent_merge_impl(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks, edata_t *a,
1243     edata_t *b, bool holding_core_locks) {
1244 	/* Only the expanding path may merge w/o holding ecache locks. */
1245 	if (holding_core_locks) {
1246 		witness_assert_positive_depth_to_rank(
1247 		    tsdn_witness_tsdp_get(tsdn), WITNESS_RANK_CORE);
1248 	} else {
1249 		witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1250 		    WITNESS_RANK_CORE, 0);
1251 	}
1252 
1253 	assert(edata_base_get(a) < edata_base_get(b));
1254 	assert(edata_arena_ind_get(a) == edata_arena_ind_get(b));
1255 	assert(edata_arena_ind_get(a) == ehooks_ind_get(ehooks));
1256 	emap_assert_mapped(tsdn, pac->emap, a);
1257 	emap_assert_mapped(tsdn, pac->emap, b);
1258 
1259 	bool err = ehooks_merge(tsdn, ehooks, edata_base_get(a),
1260 	    edata_size_get(a), edata_base_get(b), edata_size_get(b),
1261 	    edata_committed_get(a));
1262 
1263 	if (err) {
1264 		return true;
1265 	}
1266 
1267 	/*
1268 	 * The rtree writes must happen while all the relevant elements are
1269 	 * owned, so the following code uses decomposed helper functions rather
1270 	 * than extent_{,de}register() to do things in the right order.
1271 	 */
1272 	emap_prepare_t prepare;
1273 	emap_merge_prepare(tsdn, pac->emap, &prepare, a, b);
1274 
1275 	assert(edata_state_get(a) == extent_state_active ||
1276 	    edata_state_get(a) == extent_state_merging);
1277 	edata_state_set(a, extent_state_active);
1278 	edata_size_set(a, edata_size_get(a) + edata_size_get(b));
1279 	edata_sn_set(a, (edata_sn_get(a) < edata_sn_get(b)) ?
1280 	    edata_sn_get(a) : edata_sn_get(b));
1281 	edata_zeroed_set(a, edata_zeroed_get(a) && edata_zeroed_get(b));
1282 
1283 	emap_merge_commit(tsdn, pac->emap, &prepare, a, b);
1284 
1285 	edata_cache_put(tsdn, pac->edata_cache, b);
1286 
1287 	return false;
1288 }
1289 
1290 bool
1291 extent_merge_wrapper(tsdn_t *tsdn, pac_t *pac, ehooks_t *ehooks,
1292     edata_t *a, edata_t *b) {
1293 	return extent_merge_impl(tsdn, pac, ehooks, a, b,
1294 	    /* holding_core_locks */ false);
1295 }
1296 
1297 bool
1298 extent_commit_zero(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
1299     bool commit, bool zero, bool growing_retained) {
1300 	witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
1301 	    WITNESS_RANK_CORE, growing_retained ? 1 : 0);
1302 
1303 	if (commit && !edata_committed_get(edata)) {
1304 		if (extent_commit_impl(tsdn, ehooks, edata, 0,
1305 		    edata_size_get(edata), growing_retained)) {
1306 			return true;
1307 		}
1308 	}
1309 	if (zero && !edata_zeroed_get(edata)) {
1310 		void *addr = edata_base_get(edata);
1311 		size_t size = edata_size_get(edata);
1312 		ehooks_zero(tsdn, ehooks, addr, size);
1313 	}
1314 	return false;
1315 }
1316 
1317 bool
1318 extent_boot(void) {
1319 	assert(sizeof(slab_data_t) >= sizeof(e_prof_info_t));
1320 
1321 	if (have_dss) {
1322 		extent_dss_boot();
1323 	}
1324 
1325 	return false;
1326 }
1327