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