Loading...
src/xzone_malloc/xzone_segment.c /dev/null libmalloc-792.80.2
--- /dev/null
+++ libmalloc/libmalloc-792.80.2/src/xzone_malloc/xzone_segment.c
@@ -0,0 +1,3270 @@
+/* ----------------------------------------------------------------------------
+Copyright (c) 2018-2022, Microsoft Research, Daan Leijen
+Copyright © 2025 Apple Inc.
+This is free software; you can redistribute it and/or modify it under the
+terms of the MIT license. A copy of the license can be found in the file
+"LICENSE" in the same directory as this file.
+-----------------------------------------------------------------------------*/
+
+#include "../internal.h"
+
+#if CONFIG_XZONE_MALLOC
+
+static void
+xzm_madvise(xzm_malloc_zone_t zone, uint8_t *start, size_t size);
+
+static void
+_xzm_segment_group_segment_deallocate(xzm_segment_group_t sg,
+		xzm_segment_t segment, bool free_from_table);
+
+#pragma mark segment map
+
+// mimalloc: _mi_segment_map_allocated_at
+static void
+_xzm_segment_table_allocated_at(xzm_main_malloc_zone_t main, void *data,
+		xzm_segment_t metadata, bool normal)
+{
+	xzm_debug_assert((uintptr_t)data % XZM_SEGMENT_SIZE == 0);
+
+	void *segment_end = _xzm_segment_end(metadata);
+	xzm_debug_assert((uintptr_t)data < (uintptr_t)segment_end);
+
+	xzm_segment_table_entry_s entry_val =
+			_xzm_segment_to_segment_table_entry(metadata, normal);
+
+	while (data < segment_end) {
+#if CONFIG_EXTERNAL_METADATA_LARGE
+		// If this allocation is in a new 64GB granule, allocate a new leaf
+		// table to store the metadata pointers in
+		size_t ext_idx = 0;
+		__assert_only size_t index = _xzm_segment_table_index_of(data, &ext_idx);
+		xzm_debug_assert(index < XZM_SEGMENT_TABLE_ENTRIES);
+		xzm_debug_assert(ext_idx < XZM_EXTENDED_SEGMENT_TABLE_ENTRIES);
+
+		if (ext_idx != 0) {
+			xzm_extended_segment_table_entry_s *ext_addr =
+					&main->xzmz_extended_segment_table[ext_idx];
+			xzm_extended_segment_table_entry_s ext_entry = { 0 };
+			ext_entry = os_atomic_load(ext_addr, relaxed);
+			if (ext_entry.xeste_val == 0) {
+				// Need to allocate a new segment table since this pointer is in
+				// a new segment table (64GB span)
+				_malloc_lock_lock(&main->xzmz_extended_segment_table_lock);
+				// Load the table entry again to see if another thread populated
+				// it while we were acquiring the lock
+				ext_entry = os_atomic_load(ext_addr, relaxed);
+				if (ext_entry.xeste_val == 0) {
+					xzm_metapool_t mp;
+					mp = &main->xzmz_metapools[XZM_METAPOOL_SEGMENT_TABLE];
+					void *leaf_table = xzm_metapool_alloc(mp);
+					xzm_assert(leaf_table);
+					xzm_debug_assert(((uintptr_t)leaf_table /
+							XZM_SEGMENT_TABLE_ALIGN) <= UINT32_MAX);
+					ext_entry.xeste_val = (uint32_t)((uintptr_t)leaf_table /
+							XZM_SEGMENT_TABLE_ALIGN);
+					os_atomic_store(ext_addr, ext_entry, relaxed);
+				}
+				_malloc_lock_unlock(&main->xzmz_extended_segment_table_lock);
+			}
+		}
+#endif // CONFIG_EXTERNAL_METADATA_LARGE
+
+		xzm_segment_table_entry_s *entry;
+		entry = _xzm_ptr_to_table_entry(data, main);
+		xzm_debug_assert(entry != NULL);
+
+		xzm_debug_assert(entry->xste_val == 0);
+
+		// Store-release to publish the segment and chunk initializations
+		// TODO: document all paired dependency/acquire loads
+		os_atomic_store(entry, entry_val, release);
+
+		data = (void *)((uintptr_t)data + XZM_SEGMENT_SIZE);
+	}
+}
+
+// mimalloc: _mi_segment_map_freed_at
+static void
+_xzm_segment_table_freed_at(xzm_main_malloc_zone_t main, void *data,
+		xzm_segment_t metadata, __assert_only bool full_segment)
+{
+	void *end = _xzm_segment_end(metadata);
+	xzm_debug_assert(!full_segment ||
+			_xzm_segment_start(metadata) == data);
+	while (data < end) {
+		xzm_segment_table_entry_s *entry;
+		entry = _xzm_ptr_to_table_entry(data, main);
+		xzm_debug_assert(entry != NULL);
+		xzm_debug_assert(_xzm_segment_to_segment_table_entry(metadata, false).xste_val ==
+				entry->xste_val);
+		xzm_segment_table_entry_s null_entry;
+		null_entry = _xzm_segment_to_segment_table_entry(NULL, false);
+		os_atomic_store(entry, null_entry, relaxed);
+
+		data = (void *)((uintptr_t)data + XZM_SEGMENT_SIZE);
+	}
+}
+
+#pragma mark vm reclaim
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+
+static struct xzm_reclaim_buffer_s xzm_reclaim_buffer;
+
+static bool
+_xzm_reclaim_id_cache_is_empty(xzm_reclaim_id_cache_t cache)
+{
+	return cache->ric_head == 0;
+}
+
+static uint64_t
+_xzm_reclaim_id_cache_pop(xzm_reclaim_id_cache_t cache)
+{
+	xzm_debug_assert(!_xzm_reclaim_id_cache_is_empty(cache));
+	uint64_t id = cache->ric_ids[--cache->ric_head];
+	xzm_debug_assert(id != VM_RECLAIM_ID_NULL);
+	return id;
+}
+
+static void
+_xzm_reclaim_id_cache_push(xzm_reclaim_id_cache_t cache, mach_vm_reclaim_id_t id)
+{
+	xzm_assert(cache->ric_head < cache->ric_len);
+	xzm_debug_assert(id != VM_RECLAIM_ID_NULL);
+	cache->ric_ids[cache->ric_head++] = id;
+}
+
+static void
+_xzm_reclaim_id_cache_init(xzm_reclaim_buffer_t buffer)
+{
+	xzm_reclaim_id_cache_t id_cache = &buffer->xrb_id_cache;
+	mach_vm_reclaim_count_t max_buffer_count;
+	mach_vm_reclaim_error_t kr = mach_vm_reclaim_ring_capacity(
+			buffer->xrb_ringbuffer, &max_buffer_count);
+	xzm_assert(kr == VM_RECLAIM_SUCCESS);
+	size_t min_id_cache_size =
+			max_buffer_count * sizeof(mach_vm_reclaim_id_t);
+	size_t id_cache_size = round_page(min_id_cache_size);
+	if (id_cache->ric_ids == NULL ||
+			id_cache->ric_len < max_buffer_count) {
+		mach_vm_reclaim_id_t *ids = (mach_vm_reclaim_id_t *)
+				mvm_allocate_pages(id_cache_size, 0, MALLOC_ABORT_ON_ERROR,
+				VM_MEMORY_MALLOC);
+		if (id_cache->ric_ids != NULL) {
+			// Deallocate the old cache
+			mvm_deallocate_pages((void *)(id_cache->ric_ids),
+					id_cache->ric_len * sizeof(mach_vm_reclaim_id_t),
+					MALLOC_ABORT_ON_ERROR);
+		}
+		id_cache->ric_ids = ids;
+		id_cache->ric_len = id_cache_size / sizeof(mach_vm_reclaim_id_t);
+	}
+	id_cache->ric_head = 0;
+	xzm_debug_assert(id_cache->ric_len >= max_buffer_count);
+}
+
+bool
+xzm_reclaim_init(xzm_main_malloc_zone_t main,
+		mach_vm_reclaim_count_t initial_count, mach_vm_reclaim_count_t max_count)
+{
+	// Pick a sane minimum number of entries and let vm_reclaim round up
+	// to a page boundary. The intention is for the initial size to be
+	// one page.
+	mach_vm_reclaim_count_t buffer_capacity =
+			mach_vm_reclaim_round_capacity(initial_count);
+	mach_vm_reclaim_count_t max_buffer_capacity =
+			mach_vm_reclaim_round_capacity(max_count);
+	xzm_reclaim_buffer.xrb_id_cache.ric_len = 0;
+	xzm_reclaim_buffer.xrb_id_cache.ric_ids = NULL;
+	_malloc_lock_init(&xzm_reclaim_buffer.xrb_lock);
+	mach_vm_reclaim_error_t err = mach_vm_reclaim_ring_allocate(
+			&xzm_reclaim_buffer.xrb_ringbuffer, buffer_capacity,
+			max_buffer_capacity);
+	if (err == VM_RECLAIM_SUCCESS) {
+		xzm_reclaim_buffer.xrb_len = buffer_capacity;
+		main->xzmz_reclaim_buffer = &xzm_reclaim_buffer;
+		_xzm_reclaim_id_cache_init(&xzm_reclaim_buffer);
+	} else {
+		malloc_report(ASL_LEVEL_ERR,
+				"xzm: failed to initialize deferred "
+				"reclamation buffer [%d] %s\n",
+				err_get_code(err), mach_error_string(err));
+	}
+	return (err == VM_RECLAIM_SUCCESS);
+}
+
+static mach_vm_reclaim_state_t
+_xzm_reclaim_mark_used_locked(xzm_reclaim_buffer_t buffer,
+		mach_vm_reclaim_id_t id, uint8_t *addr, size_t size, bool reusable,
+		bool *update_accounting_out)
+{
+	mach_vm_reclaim_error_t err;
+	mach_vm_reclaim_state_t state;
+
+	xzm_debug_assert(size <= UINT32_MAX);
+	mach_vm_reclaim_action_t behavior = reusable ?
+			VM_RECLAIM_FREE : VM_RECLAIM_DEALLOCATE;
+
+	err = mach_vm_reclaim_try_cancel(buffer->xrb_ringbuffer, id,
+			(mach_vm_address_t)addr, (mach_vm_size_t)size,
+			behavior, &state, update_accounting_out);
+	xzm_assert(err == VM_RECLAIM_SUCCESS);
+
+	if (state == VM_RECLAIM_UNRECLAIMED) {
+		_xzm_reclaim_id_cache_push(&buffer->xrb_id_cache, id);
+	}
+
+	return state;
+}
+
+static mach_vm_reclaim_state_t
+_xzm_reclaim_mark_used(xzm_reclaim_buffer_t buffer, mach_vm_reclaim_id_t id,
+		uint8_t *addr, size_t size, bool reusable)
+{
+	bool update_accounting = false;
+
+	_malloc_lock_lock(&buffer->xrb_lock);
+
+	mach_vm_reclaim_state_t state = _xzm_reclaim_mark_used_locked(buffer, id,
+			addr, size, reusable, &update_accounting);
+
+	_malloc_lock_unlock(&buffer->xrb_lock);
+
+	if (update_accounting) {
+		__assert_only mach_vm_reclaim_error_t err =
+				mach_vm_reclaim_update_kernel_accounting(buffer->xrb_ringbuffer);
+		xzm_debug_assert(err == VM_RECLAIM_SUCCESS);
+	}
+
+	return state;
+}
+
+static bool
+_xzm_reclaim_is_reusable(xzm_reclaim_buffer_t buffer, mach_vm_reclaim_id_t reclaim_id, bool deallocate)
+{
+	mach_vm_reclaim_error_t err;
+	mach_vm_reclaim_state_t state;
+	err = mach_vm_reclaim_query_state(buffer->xrb_ringbuffer, reclaim_id,
+			deallocate ? VM_RECLAIM_DEALLOCATE : VM_RECLAIM_FREE, &state);
+	xzm_assert(err == VM_RECLAIM_SUCCESS);
+	return mach_vm_reclaim_is_reusable(state);
+}
+
+uint64_t
+xzm_reclaim_mark_free_locked(xzm_reclaim_buffer_t buffer, uint8_t *addr,
+		size_t size, bool reusable, bool *update_accounting_out)
+{
+	mach_vm_reclaim_error_t kr;
+	mach_vm_reclaim_id_t id;
+	mach_vm_address_t vm_addr = (mach_vm_address_t)addr;
+	uint32_t vm_size = (uint32_t)size;
+	xzm_debug_assert(size <= UINT32_MAX);
+	xzm_debug_assert(vm_addr % XZM_SEGMENT_SLICE_SIZE == 0);
+	xzm_debug_assert(vm_size % XZM_SEGMENT_SLICE_SIZE == 0);
+#ifdef DEBUG
+	_malloc_lock_assert_owner(&buffer->xrb_lock);
+#endif // DEBUG
+
+	mach_vm_reclaim_action_t behavior = reusable ?
+			VM_RECLAIM_FREE : VM_RECLAIM_DEALLOCATE;
+
+	while (!_xzm_reclaim_id_cache_is_empty(&buffer->xrb_id_cache)) {
+		id = _xzm_reclaim_id_cache_pop(&buffer->xrb_id_cache);
+		kr = mach_vm_reclaim_try_enter(
+				buffer->xrb_ringbuffer,
+				vm_addr, vm_size, behavior, &id,
+				update_accounting_out);
+		xzm_assert(kr == VM_RECLAIM_SUCCESS);
+		if (id != VM_RECLAIM_ID_NULL) {
+			goto done;
+		}
+	}
+	do {
+		id = VM_RECLAIM_ID_NULL;
+		kr = mach_vm_reclaim_try_enter(buffer->xrb_ringbuffer, vm_addr, vm_size,
+				behavior, &id, update_accounting_out);
+		xzm_assert(kr == VM_RECLAIM_SUCCESS);
+		if (id == VM_RECLAIM_ID_NULL) {
+			// If the ringbuffer is full, reap all of its contents and resize
+			xzm_reclaim_sync_and_resize(buffer);
+		}
+	} while (id == VM_RECLAIM_ID_NULL);
+
+done:
+	return id;
+}
+
+static uint64_t
+_xzm_reclaim_mark_free(xzm_reclaim_buffer_t buffer, uint8_t *addr, size_t size,
+		bool reusable)
+{
+	uint64_t id;
+	bool should_update_kernel_accounting = false;
+
+	_malloc_lock_lock(&buffer->xrb_lock);
+
+	id = xzm_reclaim_mark_free_locked(buffer, addr, size, reusable,
+			&should_update_kernel_accounting);
+
+	_malloc_lock_unlock(&buffer->xrb_lock);
+
+	if (should_update_kernel_accounting) {
+		__assert_only mach_vm_reclaim_error_t kr =
+				mach_vm_reclaim_update_kernel_accounting(buffer->xrb_ringbuffer);
+		xzm_debug_assert(kr == VM_RECLAIM_SUCCESS);
+	}
+	return id;
+}
+
+static bool
+xzm_reclaim_mark_smaller(xzm_reclaim_buffer_t buffer, uint64_t *front_id,
+		uint64_t *back_id, uint8_t *front_start, size_t front_free_size,
+		size_t used_size, size_t back_free_size, bool deferred, bool pristine,
+		bool reusable)
+{
+	const size_t span_size = front_free_size + used_size + back_free_size;
+	xzm_debug_assert(span_size <= UINT32_MAX);
+
+	bool should_update_used = false;
+	bool should_update_free_front = false, should_update_free_back = false;
+
+	_malloc_lock_lock(&buffer->xrb_lock);
+
+	bool usable = true;
+	mach_vm_reclaim_state_t state;
+	if (deferred) {
+		xzm_debug_assert(*front_id != VM_RECLAIM_ID_NULL);
+		// Mark the entire span as used
+		state = _xzm_reclaim_mark_used_locked(buffer, *front_id, front_start,
+				span_size, reusable, &should_update_used);
+		usable = mach_vm_reclaim_is_reusable(state);
+		if (usable) {
+			*front_id = VM_RECLAIM_ID_NULL;
+		}
+	}
+	if (usable) {
+		if (front_free_size && !pristine) {
+			// Mark the front as free. Note that it already has a reclaim id
+			xzm_debug_assert(*front_id == VM_RECLAIM_ID_NULL);
+			*front_id = xzm_reclaim_mark_free_locked(buffer, front_start,
+					front_free_size, reusable, &should_update_free_front);
+		}
+
+		if (back_free_size) {
+			xzm_debug_assert(back_id);
+			if (!pristine) {
+				// Mark the back as free
+				uint8_t *back_start = front_start + front_free_size + used_size;
+				*back_id = xzm_reclaim_mark_free_locked(buffer, back_start,
+						back_free_size, reusable, &should_update_free_back);
+			} else {
+				// Initialize the reclaim id now, because when the span metadata
+				// is updated, it cannot overwrite any reclaim id we set
+				*back_id = VM_RECLAIM_ID_NULL;
+			}
+		}
+	}
+
+	_malloc_lock_unlock(&buffer->xrb_lock);
+
+	if (should_update_used || should_update_free_front ||
+			should_update_free_back) {
+		mach_vm_reclaim_update_kernel_accounting(buffer->xrb_ringbuffer);
+	}
+
+	return usable;
+}
+
+void
+xzm_reclaim_force_sync(xzm_reclaim_buffer_t buffer)
+{
+	// This function is called in a loop when reclaim_mark_used fails while
+	// trying to free a span in the reclaim buffer.
+	mach_vm_reclaim_count_t capacity;
+	__assert_only mach_vm_reclaim_error_t err;
+	err = mach_vm_reclaim_ring_capacity(buffer->xrb_ringbuffer, &capacity);
+	xzm_assert(err == VM_RECLAIM_SUCCESS);
+	err = mach_vm_reclaim_ring_flush(buffer->xrb_ringbuffer, capacity);
+	xzm_assert(err == VM_RECLAIM_SUCCESS);
+}
+
+void
+xzm_reclaim_sync_and_resize(xzm_reclaim_buffer_t buffer)
+{
+	mach_vm_reclaim_error_t kr;
+	mach_vm_reclaim_count_t count;
+	kr = mach_vm_reclaim_ring_capacity(buffer->xrb_ringbuffer, &count);
+	xzm_assert(kr == VM_RECLAIM_SUCCESS);
+	mach_vm_reclaim_count_t new_count =
+			mach_vm_reclaim_round_capacity(2 * count);
+
+	kr = mach_vm_reclaim_ring_resize(buffer->xrb_ringbuffer, new_count);
+	if (kr == VM_RECLAIM_SUCCESS) {
+		_xzm_reclaim_id_cache_init(buffer);
+	} else {
+		// Must explicitly flush if the resize operation failed
+		xzm_reclaim_force_sync(buffer);
+	}
+}
+
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+#pragma mark range group
+
+OS_OPTIONS(xzm_range_group_alloc_flags, uint32_t,
+	XZM_RANGE_GROUP_ALLOC_FLAGS_HUGE = 1 << 0,
+	XZM_RANGE_GROUP_ALLOC_FLAGS_PURGEABLE = 1 << 1,
+#if CONFIG_MTE
+	XZM_RANGE_GROUP_ALLOC_FLAGS_MTE = 1 << 2,
+#endif
+);
+
+static int
+_xzm_range_group_vm_tag_for_segment(size_t size, bool huge)
+{
+	// Note: although there is already a VM_MEMORY_MALLOC_HUGE tag, which has
+	// been there since prehistory, we'll use LARGE for huge segments to ensure
+	// that any special handling from the kernel or other tools works exactly as
+	// before (e.g. VM_MEMORY_MALLOC_HUGE is not included in
+	// vm_memory_malloc_no_cow_mask)
+	//
+	// We use VM_MEMORY_MALLOC_SMALL for normal segment allocations so that they
+	// are easily distinguisable from metadata allocations purely by tag.
+	return huge ? VM_MEMORY_MALLOC_LARGE : VM_MEMORY_MALLOC_SMALL;
+}
+
+static void * __alloc_size(2)
+_xzm_range_group_alloc_mvm_segment(xzm_main_malloc_zone_t main, size_t size,
+		size_t align, plat_map_t *map, xzm_range_group_alloc_flags_t rga_flags)
+{
+	bool huge = (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_HUGE);
+	bool purgeable = (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_PURGEABLE);
+
+	uint32_t flags = 0;
+	if (os_unlikely(purgeable)) {
+		flags |= MALLOC_PURGEABLE;
+	}
+
+#if XZM_NARROW_BUCKETING
+	// If we're doing narrow bucketing, and we ourselves aren't enabling
+	// VM user ranges, but we've detected that VM user ranges are active in the
+	// address space (<-> entropic_base is set), we want to pass DISABLE_ASLR to
+	// skip the mvm-layer ASLR, which would cause our allocations to be placed
+	// at the opposite end of the heap range from other pure data allocations
+	// and use an additional PTE
+	if (main->xzmz_narrow_bucketing && !main->xzmz_use_ranges && entropic_base) {
+		flags |= DISABLE_ASLR;
+	}
+#endif
+
+#if CONFIG_MTE
+	if (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_MTE) {
+		flags |= MALLOC_MTE_TAGGABLE;
+	}
+#endif
+
+	int tag = _xzm_range_group_vm_tag_for_segment(size, huge);
+	if (os_likely(align == 0)) {
+		return mvm_allocate_pages_plat(size, XZM_SEGMENT_SHIFT, flags, tag, map);
+	} else {
+		// mvm_allocate_pages_plat takes the log2 of the alignment
+		size_t align_pow = __builtin_ctzl(align);
+		xzm_debug_assert(align_pow < UINT8_MAX);
+		align_pow = MAX(align_pow, XZM_SEGMENT_SHIFT);
+		return mvm_allocate_pages_plat(size, align_pow, flags, tag, map);
+	}
+}
+
+MALLOC_USED
+static void * __alloc_size(1)
+_xzm_range_group_alloc_anywhere_segment(mach_vm_address_t hint, size_t size,
+		size_t align, plat_map_t *map, xzm_range_group_alloc_flags_t rga_flags)
+{
+	bool huge = (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_HUGE);
+	bool purgeable = (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_PURGEABLE);
+
+	(void)map;
+	int tag = _xzm_range_group_vm_tag_for_segment(size, huge);
+
+	mach_vm_address_t vm_addr = hint;
+	mach_vm_size_t allocation_size = (mach_vm_size_t)size;
+	int flags = VM_FLAGS_ANYWHERE | VM_MAKE_TAG(tag);
+	if (os_unlikely(purgeable)) {
+		flags |= VM_FLAGS_PURGABLE;
+	}
+
+#if CONFIG_MTE
+	if (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_MTE) {
+		flags |= VM_FLAGS_MTE;
+	}
+#endif
+
+	align = MAX(align, XZM_SEGMENT_SIZE);
+	// alignment must be a power of 2 for the allocation mask to work
+	xzm_debug_assert(powerof2(align));
+	mach_vm_offset_t allocation_mask = (mach_vm_offset_t)align - 1;
+	kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, allocation_size,
+			allocation_mask, flags, MEMORY_OBJECT_NULL, 0, FALSE,
+			VM_PROT_DEFAULT, VM_PROT_ALL, VM_INHERIT_DEFAULT);
+	if (kr) {
+		if (kr != KERN_NO_SPACE) {
+			xzm_debug_abort_with_reason("Failed to allocate data segment", kr);
+			malloc_zone_error(0, false,
+					"Failed to allocate segment (size=%lu, flags=%x, kr=%d)\n",
+					(unsigned long)size, flags, kr);
+		}
+
+		return NULL;
+	}
+
+	xzm_debug_assert(vm_addr);
+	xzm_debug_assert(vm_addr % align == 0);
+	return (void *)vm_addr;
+}
+
+static uintptr_t
+_xzm_range_group_bump_alloc_segment(xzm_range_group_t rg, size_t size,
+		bool warn_on_exhaustion)
+{
+	uintptr_t segment_addr = 0;
+
+	if (rg->xzrg_warned_full) {
+		return segment_addr;
+	}
+
+	// Reserve space for a new segment
+	_malloc_lock_lock(&rg->xzrg_lock);
+	if (rg->xzrg_remaining >= size) {
+		if (rg->xzrg_next == rg->xzrg_skip_addr) {
+			if (rg->xzrg_direction == XZM_FRONT_INCREASING) {
+				rg->xzrg_next += rg->xzrg_skip_size;
+			} else {
+				xzm_debug_assert(rg->xzrg_direction == XZM_FRONT_DECREASING);
+				rg->xzrg_next -= rg->xzrg_skip_size;
+			}
+		}
+
+		// In the decreasing direction, xzrg_next points to the _end_ of what
+		// will be the next segment we serve, and we subtract the size to be
+		// allocated from its initial value.  In the increasing direction, the
+		// initial value is the start of the segment we're going to serve, and
+		// we increase afterward.
+		if (rg->xzrg_direction == XZM_FRONT_DECREASING) {
+			rg->xzrg_next -= size;
+		}
+
+		segment_addr = rg->xzrg_next;
+		xzm_debug_assert(segment_addr % size == 0);
+
+		if (rg->xzrg_direction == XZM_FRONT_INCREASING) {
+			rg->xzrg_next += size;
+		}
+
+		rg->xzrg_remaining -= size;
+	}
+
+	if (!segment_addr && warn_on_exhaustion) {
+		if (!rg->xzrg_warned_full) {
+			rg->xzrg_warned_full = true;
+			malloc_report(ASL_LEVEL_WARNING, "Failed to allocate segment from range group - out of space\n");
+		}
+	}
+
+	_malloc_lock_unlock(&rg->xzrg_lock);
+
+	return segment_addr;
+}
+
+static void * __alloc_size(2)
+_xzm_range_group_alloc_data_segment(xzm_range_group_t rg, size_t size,
+		size_t alignment, plat_map_t *map, xzm_range_group_alloc_flags_t rga_flags)
+{
+	xzm_debug_assert(rg->xzrg_id == XZM_RANGE_GROUP_DATA);
+
+#if   CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+	if (rg->xzrg_main_ref->xzmz_use_ranges) {
+		// On systems with VM user ranges, an ANYWHERE allocation with one of the
+		// VM_MEMORY_MALLOC tags will be placed in the data range automatically.
+		mach_vm_address_t hint = 0;
+
+#if CONFIG_MACOS_RANGES
+		// On macOS, the data range isn't strongly isolated.  We just choose an
+		// otherwise empty normal range of the address space to allocate into
+		// using a hint.
+		hint = rg->xzrg_base;
+#endif // CONFIG_MACOS_RANGES
+
+		return _xzm_range_group_alloc_anywhere_segment(hint, size, alignment,
+				map, rga_flags);
+	}
+#endif // CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+
+	return _xzm_range_group_alloc_mvm_segment(rg->xzrg_main_ref, size,
+			alignment, map, rga_flags);
+}
+
+static void * __alloc_size(2)
+_xzm_range_group_alloc_ptr_segment(xzm_range_group_t rg, size_t size,
+		plat_map_t *map, xzm_range_group_alloc_flags_t rga_flags)
+{
+	xzm_debug_assert(rg->xzrg_id == XZM_RANGE_GROUP_PTR);
+	xzm_debug_assert(!(rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_HUGE));
+	xzm_debug_assert(size == XZM_SEGMENT_SIZE);
+	xzm_debug_assert(rg->xzrg_main_ref->xzmz_segment_group_count !=
+			XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY);
+
+#if   CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+	if (rg->xzrg_main_ref->xzmz_use_ranges)
+#else
+	if ((0))
+#endif // MALLOC_TARGET_EXCLAVES
+	{
+		bool allow_fallback = false;
+#if CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+		if (!malloc_process_is_security_critical(malloc_process_identity)) {
+			allow_fallback = true;
+		}
+#endif // CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+
+		mach_vm_address_t segment_addr = _xzm_range_group_bump_alloc_segment(rg,
+				size, !allow_fallback);
+		if (!segment_addr) {
+#if CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+			if (allow_fallback) {
+				goto fallback;
+			}
+#endif // CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+
+			xzm_debug_abort("Pointer range exhausted");
+			return NULL;
+		}
+
+#if CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+		mach_vm_address_t vm_addr = segment_addr;
+		mach_vm_size_t vm_size = (mach_vm_size_t)size;
+		int alloc_flags = VM_FLAGS_OVERWRITE |
+				VM_MAKE_TAG(VM_MEMORY_MALLOC_SMALL);
+
+#if CONFIG_MTE
+		if (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_MTE) {
+			alloc_flags |= VM_FLAGS_MTE;
+		}
+#endif
+
+		kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, vm_size,
+				/* mask */ 0, alloc_flags, MEMORY_OBJECT_NULL,
+				/* offset */ 0, /* copy */ FALSE, VM_PROT_DEFAULT,
+				VM_PROT_ALL, VM_INHERIT_DEFAULT);
+		if (kr != KERN_SUCCESS) {
+			xzm_abort_with_reason(
+					"pointer range mach_vm_map() overwrite failed", kr);
+		}
+#endif // CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+
+		return (void *)segment_addr;
+	}
+
+#if CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+fallback:
+#endif // CONFIG_MALLOC_PROCESS_IDENTITY && CONFIG_MACOS_RANGES
+	return _xzm_range_group_alloc_mvm_segment(rg->xzrg_main_ref, size, 0, map,
+			rga_flags);
+}
+
+static void * __alloc_size(2)
+xzm_range_group_alloc_segment(xzm_range_group_t rg, size_t size,
+		size_t alignment, plat_map_t *map,
+		xzm_range_group_alloc_flags_t rga_flags)
+{
+	if (rg->xzrg_id == XZM_RANGE_GROUP_DATA) {
+		return _xzm_range_group_alloc_data_segment(rg, size, alignment, map,
+				rga_flags);
+	} else {
+		xzm_debug_assert(alignment == 0);
+		// Only huge segment bodies (which must be in the data range) can be
+		// purgable
+		xzm_debug_assert(!(rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_PURGEABLE));
+		return _xzm_range_group_alloc_ptr_segment(rg, size, map, rga_flags);
+	}
+}
+
+static void
+xzm_range_group_free_segment_body(xzm_range_group_t rg, void *body,
+		size_t size, plat_map_t *map)
+{
+	xzm_debug_assert(rg->xzrg_id == XZM_RANGE_GROUP_DATA);
+
+	int debug_flags = 0;
+#ifdef DEBUG
+	debug_flags = MALLOC_ABORT_ON_ERROR;
+#endif // DEBUG
+	mvm_deallocate_plat(body, size, debug_flags, map);
+}
+
+#if CONFIG_VM_USER_RANGES
+static bool
+parse_void_ranges(struct mach_vm_range *left_void,
+		struct mach_vm_range *right_void)
+{
+	char buf[256];
+	size_t bsz = sizeof(buf) - 1;
+	char *s;
+
+	int rc = sysctlbyname("vm.malloc_ranges", buf, &bsz, NULL, 0);
+	if (rc == -1) {
+		switch (errno) {
+		case ENOENT:
+#ifdef DEBUG
+			malloc_report(ASL_LEVEL_INFO, "VM user ranges not supported\n");
+#endif
+			break;
+		case EPERM:
+			// TODO: make this fatal in processes that strictly need VM user
+			// ranges
+			malloc_report(ASL_LEVEL_ERR,
+					"sysctlbyname(\"vm.malloc_ranges\") denied\n");
+			break;
+		default:
+			xzm_abort_with_reason("sysctlbyname(\"vm.malloc_ranges\") failed",
+					errno);
+			break;
+		}
+		return false;
+	}
+	buf[bsz] = '\0';
+
+	s = buf;
+
+	left_void->min_address = strtoull(s, &s, 16);
+	s++;
+
+	left_void->max_address = strtoull(s, &s, 16);
+	s++;
+
+	right_void->min_address = strtoull(s, &s, 16);
+	s++;
+
+	right_void->max_address = strtoull(s, &s, 16);
+
+	return true;
+}
+#endif // CONFIG_VM_USER_RANGES
+
+#if MALLOC_TARGET_EXCLAVES || CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+
+#define XZM_RANGE_SEPARATION   GiB(4)
+
+#define XZM_DATA_RANGE_SIZE    GiB(10)
+#define XZM_POINTER_RANGE_SIZE GiB(16)
+
+#define XZM_PAGE_TABLE_GRANULE MiB(32)
+#define XZM_PAGE_TABLE_BITS	   25
+
+// Exclaves don't have struct mach_vm_range, so we'll just define our own little
+// identical type
+struct xzm_vm_range {
+	uint64_t min_address;
+	uint64_t max_address;
+};
+
+
+static_assert(sizeof(struct mach_vm_range) == sizeof(struct xzm_vm_range),
+		"compatible vm range size");
+static_assert(offsetof(struct mach_vm_range, min_address) ==
+		offsetof(struct xzm_vm_range, min_address),
+		"compatible vm range min_address offset");
+static_assert(offsetof(struct mach_vm_range, max_address) ==
+		offsetof(struct xzm_vm_range, max_address),
+		"compatible vm range max_address offset");
+
+
+static void
+_xzm_main_malloc_zone_init_ptr_fronts(xzm_range_group_t range_groups,
+		size_t allocation_front_count, struct xzm_vm_range *ranges,
+		size_t range_count, plat_map_t *map)
+{
+	xzm_assert(allocation_front_count == 2);
+	xzm_assert(range_count > 0);
+	xzm_assert(ranges[0].min_address < ranges[0].max_address);
+	if (range_count > 1) {
+#if CONFIG_VM_USER_RANGES
+		xzm_assert(range_count == 2);
+		xzm_assert(ranges[1].min_address > ranges[0].max_address);
+		xzm_assert(ranges[1].min_address < ranges[1].max_address);
+#else
+		xzm_abort_with_reason("unsupported range_count", range_count);
+#endif
+	}
+
+	uint64_t total_span = 0;
+	for (size_t i = 0; i < range_count; i++) {
+		total_span += ranges[i].max_address - ranges[i].min_address;
+	}
+	uint64_t middle_pte_offset = roundup(total_span / 2,
+			XZM_PAGE_TABLE_GRANULE);
+
+	if (ranges[0].min_address + middle_pte_offset >= ranges[0].max_address) {
+		xzm_assert(range_count == 2);
+		middle_pte_offset += ranges[1].min_address - ranges[0].max_address;
+	}
+
+	uint64_t middle_pte = ranges[0].min_address + middle_pte_offset;
+	xzm_assert(middle_pte % XZM_PAGE_TABLE_GRANULE == 0);
+
+	uint64_t middle_pte_middle = middle_pte + (XZM_PAGE_TABLE_GRANULE / 2);
+
+	uint64_t rg_up_size = 0;
+	uint64_t rg_up_skip_addr = 0;
+	uint64_t rg_up_skip_size = 0;
+
+	uint64_t rg_down_size = 0;
+	uint64_t rg_down_skip_addr = 0;
+	uint64_t rg_down_skip_size = 0;
+
+	if (range_count == 2) {
+		if (middle_pte_middle > ranges[0].max_address) {
+			xzm_assert(middle_pte_middle > ranges[1].min_address);
+			xzm_assert(middle_pte_middle < ranges[1].max_address);
+
+			// The right side (up) is not split
+			rg_up_size = ranges[1].max_address - middle_pte_middle;
+
+			// The left side (down) is split
+			rg_down_size = (middle_pte_middle - ranges[1].min_address) +
+					(ranges[0].max_address - ranges[0].min_address);
+			rg_down_skip_addr = ranges[1].min_address;
+			rg_down_skip_size = ranges[1].min_address - ranges[0].max_address;
+		} else {
+			xzm_assert(middle_pte_middle < ranges[0].max_address);
+			xzm_assert(middle_pte_middle > ranges[0].min_address);
+
+			// The right side (up) is split
+			rg_up_size = (ranges[1].max_address - ranges[1].min_address) +
+					(ranges[0].max_address - middle_pte_middle);
+			rg_up_skip_addr = ranges[0].max_address;
+			rg_up_skip_size = ranges[1].min_address - ranges[0].max_address;
+
+			// The left side (down) is not split
+			rg_down_size = middle_pte_middle - ranges[0].min_address;
+		}
+	} else {
+		xzm_assert(ranges[0].min_address < middle_pte_middle);
+		xzm_assert(middle_pte_middle < ranges[0].max_address);
+
+		rg_up_size = ranges[0].max_address - middle_pte_middle;
+		rg_down_size = middle_pte_middle - ranges[0].min_address;
+	}
+
+	xzm_range_group_t ptr_rg_up = &range_groups[XZM_RANGE_GROUP_PTR + 0];
+	xzm_debug_assert(ptr_rg_up->xzrg_id == XZM_RANGE_GROUP_PTR);
+
+	ptr_rg_up->xzrg_base = middle_pte_middle;
+	ptr_rg_up->xzrg_next = ptr_rg_up->xzrg_base;
+	ptr_rg_up->xzrg_size = rg_up_size;
+	ptr_rg_up->xzrg_remaining = ptr_rg_up->xzrg_size;
+	ptr_rg_up->xzrg_skip_addr = rg_up_skip_addr;
+	ptr_rg_up->xzrg_skip_size = rg_up_skip_size;
+	ptr_rg_up->xzrg_direction = XZM_FRONT_INCREASING;
+
+	xzm_range_group_t ptr_rg_down = &range_groups[XZM_RANGE_GROUP_PTR + 1];
+	xzm_debug_assert(ptr_rg_down->xzrg_id == XZM_RANGE_GROUP_PTR);
+
+	ptr_rg_down->xzrg_base = middle_pte_middle;
+	ptr_rg_down->xzrg_next = ptr_rg_down->xzrg_base;
+	ptr_rg_down->xzrg_size = rg_down_size;
+	ptr_rg_down->xzrg_remaining = ptr_rg_down->xzrg_size;
+	ptr_rg_down->xzrg_skip_addr = rg_down_skip_addr;
+	ptr_rg_down->xzrg_skip_size = rg_down_skip_size;
+	ptr_rg_down->xzrg_direction = XZM_FRONT_DECREASING;
+
+}
+
+#if CONFIG_VM_USER_RANGES
+
+static void
+_xzm_main_malloc_zone_choose_ptr_ranges(struct mach_vm_range left_void,
+		struct mach_vm_range right_void, size_t ptr_rg_size, uint64_t entropy,
+		struct mach_vm_range *ranges_out, size_t *ranges_count_inout)
+{
+	// For now, the caller needs to be able to handle 2 result ranges
+	xzm_assert(*ranges_count_inout == 2);
+
+	xzm_assert(left_void.min_address);
+	xzm_assert(left_void.max_address >= left_void.min_address);
+	xzm_assert(right_void.min_address >= left_void.max_address);
+	xzm_assert(right_void.max_address >= right_void.min_address);
+
+#define xzm_trunc_page_table_granule(addr) \
+		((addr) & ~(XZM_PAGE_TABLE_GRANULE - 1))
+
+	// Note: the void boundaries should already be aligned to the page table
+	// granule anyway
+
+	// |<----------------total span--------------->|
+	// |<-left  void->|<-data body->|<-right void->|
+	// |<usable>|<pad>|<-data body->|<pad>|<usable>|
+	// |<usable>|<-------data span------->|<usable>|
+
+	uint64_t left_void_min = roundup(left_void.min_address,
+			XZM_PAGE_TABLE_GRANULE);
+	uint64_t left_void_limit =
+			xzm_trunc_page_table_granule(left_void.max_address);
+	if (left_void_limit < left_void_min) {
+		// Shouldn't ever happen - the kernel would have to give us a
+		// sub-granule left void that isn't granule-aligned.  If it does, we can
+		// pretend it gave us an empty left void that's actually "in" the data
+		// range, technically.
+		left_void_min = left_void_limit;
+	}
+	xzm_assert(left_void_min <= left_void_limit);
+
+	uint64_t right_void_min = roundup(right_void.min_address,
+			XZM_PAGE_TABLE_GRANULE);
+	uint64_t right_void_limit =
+			xzm_trunc_page_table_granule(right_void.max_address);
+	if (right_void_limit < right_void_min) {
+		// Same thing, shouldn't happen
+		right_void_limit = right_void_min;
+	}
+	xzm_assert(right_void_min <= right_void_limit);
+
+	xzm_assert(left_void_limit <= right_void_min);
+
+	uint64_t total_span = right_void_limit - left_void_min;
+
+	uint64_t data_body_span = right_void_min - left_void_limit;
+
+	uint64_t data_left_pad = MIN(XZM_RANGE_SEPARATION,
+			left_void_limit - left_void_min);
+	uint64_t data_left_pad_start = left_void_limit - data_left_pad;
+
+	uint64_t data_right_pad = MIN(XZM_RANGE_SEPARATION,
+			right_void_limit - right_void_min);
+	uint64_t data_right_pad_limit = right_void_min + data_right_pad;
+
+	uint64_t data_span = data_left_pad + data_body_span + data_right_pad;
+
+	xzm_assert(data_span < total_span);
+	uint64_t usable_space = total_span - data_span;
+
+	xzm_assert(usable_space >= ptr_rg_size);
+	uint64_t starting_space = usable_space - ptr_rg_size;
+
+	xzm_assert(starting_space % XZM_PAGE_TABLE_GRANULE == 0);
+
+	// Note: + 1 because the final granule address is also usable
+	uint64_t starting_candidate_granules =
+			(starting_space / XZM_PAGE_TABLE_GRANULE) + 1;
+
+	// Note: start_granules is small relative to entropy, so the modulo bias is
+	// not significant
+	uint64_t start_granule = entropy % starting_candidate_granules;
+
+	uint64_t start_address = left_void_min +
+			(start_granule * XZM_PAGE_TABLE_GRANULE);
+
+	if (start_address >= data_left_pad_start) {
+		start_address += data_span;
+	}
+
+	uint64_t limit_address = start_address + ptr_rg_size;
+
+	if (start_address < data_left_pad_start &&
+			limit_address > data_left_pad_start) {
+		// The pointer range is split across the data range
+		ranges_out[0] = (struct mach_vm_range){
+			.min_address = start_address,
+			.max_address = data_left_pad_start,
+		};
+
+		uint64_t left_range_span = data_left_pad_start - start_address;
+		uint64_t right_range_span = ptr_rg_size - left_range_span;
+		ranges_out[1] = (struct mach_vm_range){
+			.min_address = data_right_pad_limit,
+			.max_address = data_right_pad_limit + right_range_span,
+		};
+
+		*ranges_count_inout = 2;
+	} else {
+		// The pointer range is fully on one side of the data range
+		ranges_out[0] = (struct mach_vm_range){
+			.min_address = start_address,
+			.max_address = limit_address,
+		};
+
+		*ranges_count_inout = 1;
+	}
+}
+
+static kern_return_t
+_xzm_main_malloc_zone_create_ptr_range(struct mach_vm_range range)
+{
+	// It's important that we use a malloc tag in the recipe so that the kernel
+	// gives us a single object rather than chunking into many.
+	mach_vm_range_recipe_v1_t recipe = {
+		.range = range,
+		.range_tag = MACH_VM_RANGE_FIXED,
+		.vm_tag = VM_MEMORY_MALLOC_SMALL,
+	};
+
+	kern_return_t kr = mach_vm_range_create(mach_task_self(),
+			MACH_VM_RANGE_FLAVOR_V1, (mach_vm_range_recipes_raw_t)&recipe,
+			sizeof(recipe));
+	switch (kr) {
+	case KERN_SUCCESS:
+		break;
+	case KERN_DENIED:
+		// TODO: make this fatal in processes that strictly need VM user ranges
+		malloc_report(ASL_LEVEL_ERR, "mach_vm_range_create() denied\n");
+		return kr;
+	case KERN_NOT_SUPPORTED:
+		// Strange - in a process that doesn't have VM user ranges we would have
+		// expected the sysctl to fail
+		xzm_debug_abort("mach_vm_range_create() not supported?");
+		return kr;
+	default:
+		xzm_abort_with_reason("unexpected error from mach_vm_range_create()",
+				kr);
+		return kr;
+	}
+
+	// Avoid malloc-no-CoW semantics on the pointer range reservation by
+	// replacing the VM object for it with one that has a non-malloc tag.
+	// Giving it VM_PROT_NONE causes the kernel to give us a single object
+	// rather than chunking (which is important to avoid creating tons of
+	// pointless VM objects), and hides it in vmmap by default.
+	mach_vm_address_t overwrite_addr = (mach_vm_address_t)range.min_address;
+	mach_vm_size_t overwrite_size =
+			(mach_vm_size_t)(range.max_address - range.min_address);
+	int alloc_flags = VM_FLAGS_OVERWRITE;
+	kr = mach_vm_map(mach_task_self(), &overwrite_addr, overwrite_size,
+			/* mask */ 0, alloc_flags, MEMORY_OBJECT_NULL, /* offset */ 0,
+			/* copy */ FALSE, VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_DEFAULT);
+	if (kr != KERN_SUCCESS) {
+		xzm_abort_with_reason(
+				"pointer range initial overwrite failed", kr);
+	}
+
+	return KERN_SUCCESS;
+}
+
+#endif // CONFIG_VM_USER_RANGES
+
+#endif // MALLOC_TARGET_EXCLAVES || CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
+
+void
+xzm_main_malloc_zone_init_range_groups(xzm_main_malloc_zone_t main)
+{
+	// Basic initialization is done in xzm_main_malloc_zone_create() - here we
+	// mainly deal with VM user ranges.
+	MALLOC_STATIC_ASSERT(XZM_RANGE_GROUP_COUNT == 3,
+			"all range groups need to be initialized");
+
+#if   CONFIG_VM_USER_RANGES
+	struct mach_vm_range left_void, right_void;
+	bool user_ranges_supported = parse_void_ranges(&left_void, &right_void);
+	if (!user_ranges_supported) {
+		return;
+	}
+
+	// VM user range support:
+	//
+	// We'll use the kernel default heap range for the DATA range.
+	//
+	// The ranges in the PTR range group:
+	// - Should be separated from the data range (as defined by
+	//   [void1.max_address, void2.min_address)) by at least 4G
+	// - Should allow each allocation front to span 8G, possibly crossing the
+	//   DATA range if necessary
+
+	// The configurations we support are:
+	// - No user ranges at all, in which case we shouldn't get here
+	// - User ranges support with 2 allocation fronts
+	if (main->xzmz_allocation_front_count != 2) {
+		xzm_abort_with_reason("unsupported allocation front count",
+				main->xzmz_allocation_front_count);
+	}
+
+	size_t ptr_rg_size = XZM_POINTER_RANGE_SIZE;
+
+	struct mach_vm_range ptr_ranges[2];
+	size_t ptr_range_count = 2;
+	_xzm_main_malloc_zone_choose_ptr_ranges(left_void, right_void, ptr_rg_size,
+			malloc_entropy[1], ptr_ranges, &ptr_range_count);
+
+	for (size_t i = 0; i < ptr_range_count; i++) {
+		kern_return_t kr =
+				_xzm_main_malloc_zone_create_ptr_range(ptr_ranges[i]);
+		if (kr != KERN_SUCCESS) {
+			return;
+		}
+	}
+
+	main->xzmz_use_ranges = true;
+
+	_xzm_main_malloc_zone_init_ptr_fronts(main->xzmz_range_groups,
+			main->xzmz_allocation_front_count,
+			(struct xzm_vm_range *)ptr_ranges, ptr_range_count, NULL);
+
+	xzm_range_group_t data_rg = &main->xzmz_range_groups[XZM_RANGE_GROUP_DATA];
+	xzm_debug_assert(data_rg->xzrg_id == XZM_RANGE_GROUP_DATA);
+
+	// Note: these are recorded purely for introspection purposes
+	data_rg->xzrg_base = (mach_vm_address_t)left_void.max_address;
+	data_rg->xzrg_size = right_void.min_address - left_void.max_address;
+
+	// end of CONFIG_VM_USER_RANGES
+#elif CONFIG_MACOS_RANGES
+	// We want a similar layout to embedded, with:
+	// - A data range and a pointer range located in the first 64GB (L2) of the
+	//   address space to economize PTE usage
+	// - Guaranteed minimum separation between the pointer range and everything
+	//   else
+	// - Both ranges separated from the traditional "low space" by a few GB of
+	//   buffer distance
+	//
+	// However, on macOS there are no "voids" for us to need the
+	// mach_vm_range_create() interface to access, nor is there a special data
+	// range that the kernel knows about.  Instead, we create our own strongly
+	// isolated pointer range reservation, and have a more relaxed model for the
+	// data range that permits reuse with general VA, allowing us to model it as
+	// a simple starting address hint.  An implication of the data range not
+	// being strongly isolated is that it doesn't need to be contiguous.
+	//
+	// Either range should be able to grow to their standard size without
+	// overflowing the first L2.
+	//
+	// So, our placement strategy will be:
+	// - Place the pointer range, with its guards, in the space
+	// - Then choose the data range hint somewhere in the remaining space
+
+	// Start at 16GB to leave room in the low space for other VM allocations
+#define XZM_MACOS_RANGES_START GiB(16)
+	// End at 63GB to avoid crossing the commpage
+#define XZM_MACOS_RANGES_END   GiB(63)
+
+	uint64_t range_first_candidate = XZM_MACOS_RANGES_START;
+	uint64_t ptr_reservation_size = XZM_RANGE_SEPARATION +
+			XZM_POINTER_RANGE_SIZE + XZM_RANGE_SEPARATION;
+	uint64_t range_last_candidate = XZM_MACOS_RANGES_END - ptr_reservation_size;
+
+	uint64_t ptr_candidate_span = range_last_candidate - range_first_candidate;
+	uint64_t ptr_candidate_granules =
+			ptr_candidate_span / XZM_PAGE_TABLE_GRANULE;
+
+	uint64_t ptr_entropy = (uint32_t)(malloc_entropy[1]);
+	uint64_t ptr_granule = ptr_entropy % ptr_candidate_granules;
+
+	uint64_t ptr_start =
+			range_first_candidate + (ptr_granule * XZM_PAGE_TABLE_GRANULE);
+
+	xzm_assert(ptr_start + ptr_reservation_size <= XZM_MACOS_RANGES_END);
+
+	// Reserve the pointer range with a big max-protection == PROT_NONE region.
+	// It is important that we not give it a malloc tag or protection above
+	// PROT_NONE to avoid chunking or special CoW treatment from the VM - we
+	// need for this to be just one entry.
+	mach_vm_address_t ptr_addr = (mach_vm_address_t)ptr_start;
+	mach_vm_size_t reservation_size = (mach_vm_size_t)ptr_reservation_size;
+	int alloc_flags = 0; // fixed, no tag
+	kern_return_t kr = mach_vm_map(mach_task_self(), &ptr_addr,
+			reservation_size, /* mask */ 0, alloc_flags, MEMORY_OBJECT_NULL,
+			/* offset */ 0, /* copy */ FALSE, VM_PROT_NONE, VM_PROT_NONE,
+			VM_INHERIT_DEFAULT);
+	if (kr != KERN_SUCCESS) {
+		// We could fall back to mvm allocation, but we want this to fail loudly
+		// if something starts preventing us from being able to make the
+		// reservation we need
+		xzm_abort_with_reason(
+				"pointer range initial reservation failed", kr);
+	}
+
+	main->xzmz_use_ranges = true;
+
+	mach_vm_address_t ptr_base = ptr_addr + XZM_RANGE_SEPARATION;
+
+	struct xzm_vm_range range = {
+		.min_address = ptr_base,
+		.max_address = ptr_base + XZM_POINTER_RANGE_SIZE,
+	};
+	_xzm_main_malloc_zone_init_ptr_fronts(main->xzmz_range_groups,
+			main->xzmz_allocation_front_count, &range, 1, NULL);
+
+	// Choose a starting hint for the data range
+
+	uint64_t data_candidate_span = ptr_candidate_span - XZM_DATA_RANGE_SIZE;
+	uint64_t data_candidate_granules =
+			data_candidate_span / XZM_PAGE_TABLE_GRANULE;
+
+	uint64_t data_entropy = malloc_entropy[1] >> 32;
+	uint64_t data_granule = data_entropy % data_candidate_granules;
+
+	uint64_t data_start;
+	if (data_granule < ptr_granule) {
+		data_start = XZM_MACOS_RANGES_START +
+				(data_granule * XZM_PAGE_TABLE_GRANULE);
+	} else {
+		uint64_t ptr_reservation_granules =
+				ptr_reservation_size / XZM_PAGE_TABLE_GRANULE;
+		uint64_t data_adjusted_granule =
+				data_granule + ptr_reservation_granules;
+		data_start = XZM_MACOS_RANGES_START +
+				(data_adjusted_granule * XZM_PAGE_TABLE_GRANULE);
+	}
+
+	xzm_assert(data_start < ptr_start ||
+			data_start >= ptr_start + ptr_reservation_size);
+	xzm_assert(data_start + XZM_DATA_RANGE_SIZE <= XZM_MACOS_RANGES_END);
+
+	xzm_range_group_t data_rg = &main->xzmz_range_groups[XZM_RANGE_GROUP_DATA];
+	xzm_debug_assert(data_rg->xzrg_id == XZM_RANGE_GROUP_DATA);
+
+	data_rg->xzrg_base = (mach_vm_address_t)data_start;
+#endif // CONFIG_MACOS_RANGES
+}
+
+#pragma mark segment group
+
+static void _xzm_segment_group_clear_chunk(xzm_segment_group_t sg,
+		uint8_t *start, size_t size);
+
+static void _xzm_segment_group_split_huge_segment(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_slice_count_t required_slices);
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+
+static void
+__xzm_segment_cache_remove(xzm_segment_cache_t cache,
+		xzm_segment_t segment)
+{
+	xzm_debug_assert(cache->xzsc_count > 0);
+	cache->xzsc_count--;
+	TAILQ_REMOVE(&cache->xzsc_head, segment, xzs_cache_entry);
+}
+
+static void
+__xzm_segment_cache_insert(xzm_segment_cache_t cache, xzm_segment_t segment)
+{
+	xzm_debug_assert(cache->xzsc_count < cache->xzsc_max_count);
+	TAILQ_INSERT_HEAD(&cache->xzsc_head, segment, xzs_cache_entry);
+	cache->xzsc_count++;
+}
+
+static void
+_xzm_segment_group_cache_invalidate(xzm_segment_group_t sg,
+		xzm_segment_t segment)
+{
+#ifdef DEBUG
+	_malloc_lock_assert_owner(&sg->xzsg_cache.xzsc_lock);
+#endif
+	__xzm_segment_cache_remove(&sg->xzsg_cache, segment);
+	// Free memory backing segment header
+	xzm_metapool_free(&sg->xzsg_main_ref->xzmz_metapools[XZM_METAPOOL_SEGMENT],
+			segment);
+}
+
+static void
+_xzm_segment_group_cache_mark_free(xzm_segment_group_t sg,
+		xzm_segment_t segment)
+{
+#ifdef DEBUG
+	_malloc_lock_assert_owner(&sg->xzsg_cache.xzsc_lock);
+	// Make sure that this segment isn't in the segment table before we put it
+	// into the cache
+	xzm_segment_table_entry_s *entry;
+	entry = _xzm_ptr_to_table_entry(_xzm_segment_start(segment),
+			sg->xzsg_main_ref);
+	xzm_debug_assert(entry->xste_val == 0);
+#endif
+	xzm_debug_assert(segment->xzs_reclaim_id == VM_RECLAIM_ID_NULL);
+
+	xzm_main_malloc_zone_t main = sg->xzsg_main_ref;
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	uint8_t *addr = _xzm_segment_start(segment);
+	size_t size = _xzm_segment_size(segment);
+	segment->xzs_reclaim_id = _xzm_reclaim_mark_free(buffer, addr, size, false);
+	__xzm_segment_cache_insert(&sg->xzsg_cache, segment);
+}
+
+// Attempt to re-use a segment from the cache. Returns true if successful.
+// If unsuccessful, the caller should invalidate the segment's cache entry.
+static bool
+_xzm_segment_group_cache_mark_used(xzm_segment_group_t sg,
+		xzm_segment_t segment)
+{
+#ifdef DEBUG
+	_malloc_lock_assert_owner(&sg->xzsg_cache.xzsc_lock);
+#endif
+	xzm_debug_assert(segment->xzs_reclaim_id != VM_RECLAIM_ID_NULL);
+	xzm_main_malloc_zone_t main = sg->xzsg_main_ref;
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+	mach_vm_reclaim_state_t state;
+
+	state = _xzm_reclaim_mark_used(buffer, segment->xzs_reclaim_id,
+			_xzm_segment_start(segment), _xzm_segment_size(segment), false);
+	if (!mach_vm_reclaim_is_reusable(state)) {
+		// Entry has been reclaimed by the kernel since being placed in cache
+		_xzm_segment_group_cache_invalidate(sg, segment);
+		return false;
+	}
+	segment->xzs_reclaim_id = VM_RECLAIM_ID_NULL;
+	__xzm_segment_cache_remove(&sg->xzsg_cache, segment);
+	return true;
+}
+
+// Evict a segment from the cache
+static void
+_xzm_segment_group_cache_evict(xzm_segment_group_t sg)
+{
+#ifdef DEBUG
+	_malloc_lock_assert_owner(&sg->xzsg_cache.xzsc_lock);
+#endif
+	// approximate the oldest segment by evicting the tail
+	xzm_segment_t segment = TAILQ_LAST(&sg->xzsg_cache.xzsc_head,
+				xzm_segment_cache_head_s);
+	xzm_debug_assert(segment->xzs_reclaim_id != VM_RECLAIM_ID_NULL);
+	if (_xzm_segment_group_cache_mark_used(sg, segment)) {
+		_malloc_lock_unlock(&sg->xzsg_cache.xzsc_lock);
+		// Segment isn't in segment table while in the cache, so pass false for
+		// free_from_table while deallocating
+		_xzm_segment_group_segment_deallocate(sg, segment, false);
+		_malloc_lock_lock(&sg->xzsg_cache.xzsc_lock);
+	}
+}
+
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+// mimalloc: mi_slice_bin8
+MALLOC_ALWAYS_INLINE MALLOC_INLINE
+static size_t
+xzm_slice_bin8(xzm_slice_count_t slice_count)
+{
+	xzm_debug_assert(slice_count != 0);
+	if (slice_count <= 8) {
+		return slice_count - 1;
+	}
+
+	xzm_debug_assert(slice_count <= XZM_SLICES_PER_SEGMENT);
+	slice_count--;
+
+	int msb = 63 - __builtin_clzl(slice_count);
+	return ((msb << 2) + ((slice_count >> (msb - 2)) & 0x3)) - 5;
+}
+
+// mimalloc: mi_slice_bin
+MALLOC_ALWAYS_INLINE MALLOC_INLINE
+static size_t
+xzm_slice_bin(xzm_slice_count_t slice_count)
+{
+	xzm_debug_assert(slice_count * XZM_SEGMENT_SLICE_SIZE <= XZM_SEGMENT_SIZE);
+	xzm_debug_assert(xzm_slice_bin8(XZM_SLICES_PER_SEGMENT) <
+			XZM_SPAN_QUEUE_COUNT);
+	size_t bin = xzm_slice_bin8(slice_count);
+	xzm_debug_assert(bin < XZM_SPAN_QUEUE_COUNT);
+	return bin;
+}
+
+// mimalloc: mi_span_queue_for
+static xzm_span_queue_t
+xzm_span_queue_for(xzm_segment_group_t sg, xzm_slice_count_t slice_count)
+{
+	size_t bin = xzm_slice_bin(slice_count);
+	xzm_span_queue_t sq = &sg->xzsg_spans[bin];
+	xzm_debug_assert(sq->xzsq_slice_count >= slice_count);
+	return sq;
+}
+
+#ifdef DEBUG
+static void
+_xzm_segment_group_assert_correct_span_queue(xzm_segment_group_t sg,
+		xzm_slice_t slice)
+{
+	xzm_slice_kind_t kind = slice->xzc_bits.xzcb_kind;
+	xzm_assert(_xzm_slice_kind_is_free_span(kind));
+
+	xzm_slice_count_t slice_count;
+	if (kind == XZM_SLICE_KIND_SINGLE_FREE) {
+		slice_count = 1;
+	} else {
+		slice_count = slice->xzcs_slice_count;
+	}
+
+	xzm_span_queue_t sq = xzm_span_queue_for(sg, slice_count);
+	xzm_free_span_t span;
+	LIST_FOREACH(span, &sq->xzsq_queue, xzc_entry) {
+		if (span == slice) {
+			return;
+		}
+	}
+	xzm_abort("Didn't find free span in expected span queue");
+}
+
+// mimalloc: mi_segment_is_valid
+static bool
+_xzm_segment_group_segment_is_valid(xzm_segment_group_t sg,
+		xzm_segment_t segment)
+{
+	xzm_assert(segment->xzs_segment_group == sg);
+
+	xzm_slice_t end = _xzm_segment_slices_end(segment);
+	xzm_slice_t slice = _xzm_segment_slices_begin(segment);
+
+	if (segment->xzs_kind == XZM_SEGMENT_KIND_HUGE) {
+		xzm_assert(segment->xzs_used == 1);
+		xzm_chunk_t chunk = slice;
+		xzm_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_HUGE_CHUNK);
+		xzm_assert(chunk->xzcs_slice_count == segment->xzs_slice_count);
+		return true;
+	}
+
+	_malloc_lock_assert_owner(&sg->xzsg_lock);
+
+	while (slice < end) {
+		xzm_slice_kind_t kind = slice->xzc_bits.xzcb_kind;
+		switch (kind) {
+		case XZM_SLICE_KIND_TINY_CHUNK:
+			slice++;
+			break;
+		case XZM_SLICE_KIND_SMALL_CHUNK:
+		case XZM_SLICE_KIND_SMALL_FREELIST_CHUNK:
+		case XZM_SLICE_KIND_LARGE_CHUNK: {
+			size_t slice_index = _xzm_slice_index(segment, slice);
+			size_t slice_count = slice->xzcs_slice_count;
+			xzm_assert(slice_count > 1);
+
+			slice++;
+			size_t extra = MIN(slice_count - 1, XZM_MAX_SLICE_OFFSET);
+			for (size_t i = 1; i <= extra; i++, slice++) {
+				xzm_assert(slice->xzc_bits.xzcb_kind ==
+						XZM_SLICE_KIND_MULTI_BODY);
+				xzm_assert(slice->xzsl_slice_offset_bytes ==
+						(uint32_t)(sizeof(struct xzm_slice_s) * i));
+			}
+
+			size_t last_slice_index = slice_index + slice_count - 1;
+			xzm_assert(last_slice_index < segment->xzs_slice_entry_count);
+			xzm_slice_t last = &segment->xzs_slices[last_slice_index];
+			if (last >= slice) {
+				xzm_assert(last->xzc_bits.xzcb_kind ==
+						XZM_SLICE_KIND_MULTI_BODY);
+				xzm_assert(last->xzsl_slice_offset_bytes ==
+						(uint32_t)(sizeof(struct xzm_slice_s) *
+						(slice_count - 1)));
+			}
+			slice = last + 1;
+			break;
+		}
+		case XZM_SLICE_KIND_GUARD: {
+			size_t slice_count = slice->xzcs_slice_count;
+			slice++;
+
+			for (size_t i = 1; i < slice_count; i++, slice++) {
+				xzm_assert(slice->xzc_bits.xzcb_kind ==
+						XZM_SLICE_KIND_MULTI_BODY);
+				xzm_assert(slice->xzsl_slice_offset_bytes ==
+						   (uint32_t)(sizeof(struct xzm_slice_s) * i));
+			}
+
+			// Adjacent guards should always be coalesced
+			if (slice < end) {
+				xzm_assert(slice->xzc_bits.xzcb_kind != XZM_SLICE_KIND_GUARD);
+			}
+
+			break;
+		}
+		case XZM_SLICE_KIND_HUGE_CHUNK:
+			xzm_abort("huge chunk in normal segment");
+			break;
+		case XZM_SLICE_KIND_SINGLE_FREE: {
+			xzm_assert(slice->xzc_mzone_idx == XZM_MZONE_INDEX_INVALID);
+			_xzm_segment_group_assert_correct_span_queue(sg, slice);
+#if CONFIG_XZM_DEFERRED_RECLAIM
+			mach_vm_reclaim_id_t *reclaim_id =
+					_xzm_segment_slice_meta_reclaim_id(segment, slice);
+			xzm_assert(*reclaim_id == VM_RECLAIM_ID_NULL ||
+					!slice->xzc_bits.xzcb_is_pristine);
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+			slice++;
+			break;
+		}
+		case XZM_SLICE_KIND_MULTI_FREE: {
+			xzm_assert(slice->xzc_mzone_idx == XZM_MZONE_INDEX_INVALID);
+			_xzm_segment_group_assert_correct_span_queue(sg, slice);
+
+			size_t slice_index = _xzm_slice_index(segment, slice);
+			size_t slice_count = slice->xzcs_slice_count;
+			xzm_assert(slice_count > 1);
+
+			size_t last_slice_index = slice_index + slice_count - 1;
+			xzm_assert(last_slice_index < segment->xzs_slice_entry_count);
+
+			xzm_slice_t last = &segment->xzs_slices[last_slice_index];
+			xzm_assert(last->xzc_bits.xzcb_kind ==
+					XZM_SLICE_KIND_MULTI_BODY);
+			xzm_assert(last->xzsl_slice_offset_bytes ==
+					(uint32_t)(sizeof(struct xzm_slice_s) * (slice_count - 1)));
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+			mach_vm_reclaim_id_t *reclaim_id =
+					_xzm_segment_slice_meta_reclaim_id(segment, slice);
+			xzm_assert(*reclaim_id == VM_RECLAIM_ID_NULL ||
+					!slice->xzc_bits.xzcb_is_pristine);
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+			slice = last + 1;
+			break;
+		}
+		default:
+			xzm_abort_with_reason("Unexpected slice kind", (unsigned)kind);
+			break;
+		}
+	}
+
+	return true;
+}
+#endif // DEBUG
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+
+static void
+_xzm_segment_group_span_mark_free(xzm_segment_group_t sg, xzm_free_span_t span)
+{
+	xzm_debug_assert(_xzm_segment_group_uses_deferred_reclamation(sg));
+	xzm_debug_assert(_xzm_slice_kind_is_free_span(span->xzc_bits.xzcb_kind));
+
+	xzm_main_malloc_zone_t main = sg->xzsg_main_ref;
+	xzm_malloc_zone_t zone = &main->xzmz_base;
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	mach_vm_reclaim_id_t *reclaim_id = _xzm_slice_meta_reclaim_id(zone, span);
+	xzm_debug_assert(*reclaim_id == VM_RECLAIM_ID_NULL);
+	size_t span_size = _xzm_free_span_size(span);
+	uint8_t *span_start = _xzm_slice_start(zone, span);
+
+	*reclaim_id = _xzm_reclaim_mark_free(buffer, span_start, span_size, true);
+}
+
+static bool
+_xzm_segment_group_span_mark_used(xzm_segment_group_t sg, xzm_free_span_t span)
+{
+	xzm_debug_assert(_xzm_segment_group_uses_deferred_reclamation(sg));
+	xzm_debug_assert(_xzm_slice_kind_is_free_span(span->xzc_bits.xzcb_kind));
+	xzm_main_malloc_zone_t main = sg->xzsg_main_ref;
+	xzm_malloc_zone_t zone = &main->xzmz_base;
+
+	if (!_xzm_slice_is_deferred(zone, span)) {
+		// span has not been marked free
+		return true;
+	}
+
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	mach_vm_reclaim_id_t *reclaim_id = _xzm_slice_meta_reclaim_id(zone, span);
+	xzm_debug_assert(*reclaim_id != VM_RECLAIM_ID_NULL);
+	size_t span_size = _xzm_free_span_size(span);
+	uint8_t *span_start = _xzm_slice_start(zone, span);
+	mach_vm_reclaim_state_t state;
+
+	state = _xzm_reclaim_mark_used(buffer, *reclaim_id, span_start,
+			span_size, true);
+	if (mach_vm_reclaim_is_reusable(state)) {
+		*reclaim_id = VM_RECLAIM_ID_NULL;
+		return true;
+	}
+	return false;
+}
+
+static bool
+_xzm_segment_group_span_mark_smaller(xzm_segment_group_t sg,
+		xzm_free_span_t span, xzm_slice_count_t front_free_count,
+		xzm_slice_count_t used_count, xzm_slice_count_t back_free_count)
+{
+	xzm_debug_assert(_xzm_segment_group_uses_deferred_reclamation(sg));
+	xzm_debug_assert(_xzm_slice_kind_is_free_span(span->xzc_bits.xzcb_kind));
+	xzm_debug_assert(front_free_count + used_count + back_free_count ==
+			_xzm_free_span_slice_count(span));
+
+	xzm_main_malloc_zone_t main = sg->xzsg_main_ref;
+	xzm_malloc_zone_t zone = &main->xzmz_base;
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	const bool deferred = _xzm_slice_is_deferred(zone, span);
+	uint64_t *span_id = _xzm_slice_meta_reclaim_id(zone, span);
+	uint8_t *span_start = _xzm_slice_start(zone, span);
+	bool pristine = span->xzc_bits.xzcb_is_pristine;
+
+	// Actual span metadata for the front/middle/back spans has not yet been
+	// updated, we only set the deferred reclaim metadata for these spans
+	xzm_free_span_t back_span = span + front_free_count + used_count;
+	const size_t front_size = front_free_count << XZM_SEGMENT_SLICE_SHIFT;
+	const size_t used_size = used_count << XZM_SEGMENT_SLICE_SHIFT;
+	const size_t back_size = back_free_count << XZM_SEGMENT_SLICE_SHIFT;
+	xzm_debug_assert(!back_size || span_start + front_size + used_size ==
+			_xzm_slice_start(zone, back_span));
+	uint64_t *back_id = back_size ?
+			_xzm_slice_meta_reclaim_id(zone, back_span) : NULL;
+	return xzm_reclaim_mark_smaller(buffer, span_id, back_id, span_start,
+			front_size, used_size, back_size, deferred, pristine, true);
+}
+
+void
+xzm_chunk_mark_free(xzm_malloc_zone_t zone, xzm_chunk_t chunk)
+{
+	xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
+	xzm_debug_assert(_xzm_chunk_should_defer_reclamation(main, chunk));
+
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	mach_vm_reclaim_id_t *reclaim_id = _xzm_slice_meta_reclaim_id(zone, chunk);
+	xzm_debug_assert(*reclaim_id == VM_RECLAIM_ID_NULL);
+	size_t chunk_size;
+	uint8_t *chunk_start = _xzm_chunk_start_ptr(zone, chunk, &chunk_size);
+
+	*reclaim_id = _xzm_reclaim_mark_free(buffer, chunk_start, chunk_size,
+			true);
+}
+
+bool
+xzm_chunk_mark_used(xzm_malloc_zone_t zone, xzm_chunk_t chunk,
+		bool *was_reclaimed)
+{
+	xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
+	xzm_debug_assert(_xzm_slice_kind_is_chunk(chunk->xzc_bits.xzcb_kind));
+	xzm_debug_assert(_xzm_chunk_should_defer_reclamation(main, chunk));
+
+	xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
+
+	mach_vm_reclaim_id_t *reclaim_id = _xzm_slice_meta_reclaim_id(zone, chunk);
+	xzm_debug_assert(*reclaim_id != VM_RECLAIM_ID_NULL);
+	size_t chunk_size;
+	uint8_t *chunk_start = _xzm_chunk_start_ptr(zone, chunk, &chunk_size);
+	mach_vm_reclaim_state_t state;
+
+	state = _xzm_reclaim_mark_used(buffer, *reclaim_id, chunk_start,
+			chunk_size, true);
+
+	if (was_reclaimed) {
+		*was_reclaimed = (state != VM_RECLAIM_UNRECLAIMED);
+	}
+	if (mach_vm_reclaim_is_reusable(state)) {
+		*reclaim_id = VM_RECLAIM_ID_NULL;
+		return true;
+	}
+	return false;
+}
+
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+// mimalloc: mi_segment_span_free
+//
+// Precondition: sg is locked (except for huge segments)
+static void
+_xzm_segment_group_segment_span_free(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_slice_count_t slice_index,
+		xzm_slice_count_t slice_count, bool set_id, bool is_pristine)
+{
+	xzm_debug_assert(slice_count != 0);
+	xzm_debug_assert(slice_index < segment->xzs_slice_entry_count);
+
+	// set first and last slice (the intermediates can be undetermined)
+	//
+	// TODO: leaving the intermediates undetermined means that you can't
+	// reliably check whether an arbitrary slice in a segment belongs to a
+	// chunk.  That would be useful for:
+	// - the checked memcpy trick
+	// - malloc_claimed_address()
+	// - possibly other things?
+	//
+	// However, for large allocations it would require updating large numbers of
+	// slices, which is probably not worth the cost
+	xzm_free_span_t span = &segment->xzs_slices[slice_index];
+	span->xzc_bits.xzcb_is_pristine = is_pristine;
+	if (slice_count == 1) {
+		xzm_debug_assert(segment->xzs_kind != XZM_SEGMENT_KIND_HUGE);
+		span->xzc_bits.xzcb_kind = XZM_SLICE_KIND_SINGLE_FREE;
+	} else {
+		span->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_FREE;
+		span->xzcs_slice_count = slice_count;
+
+		xzm_debug_assert(slice_index + slice_count - 1 < segment->xzs_slice_entry_count);
+		xzm_slice_t last = &segment->xzs_slices[slice_index + slice_count - 1];
+		last->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+		last->xzsl_slice_offset_bytes =
+				(uint32_t)(sizeof(struct xzm_slice_s) * (slice_count - 1));
+	}
+
+	if (segment->xzs_kind == XZM_SEGMENT_KIND_NORMAL) {
+#ifdef DEBUG
+		_malloc_lock_assert_owner(&sg->xzsg_lock);
+#endif
+		xzm_span_queue_t sq = xzm_span_queue_for(sg, slice_count);
+		LIST_INSERT_HEAD(&sq->xzsq_queue, span, xzc_entry);
+	}
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	if (set_id) {
+		mach_vm_reclaim_id_t *reclaim_id = _xzm_segment_slice_meta_reclaim_id(
+				segment, span);
+		*reclaim_id = VM_RECLAIM_ID_NULL;
+	} else if (!is_pristine) {
+		xzm_debug_assert(*_xzm_segment_slice_meta_reclaim_id(segment, span) !=
+				VM_RECLAIM_ID_NULL);
+	}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+}
+
+// mimalloc: mi_segment_slice_split
+static xzm_free_span_t
+_xzm_segment_group_segment_slice_split(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_free_span_t span,
+		xzm_slice_count_t slice_count, bool uses_dr, bool front)
+{
+	xzm_debug_assert(_xzm_segment_for_slice(&sg->xzsg_main_ref->xzmz_base, span) == segment);
+	xzm_debug_assert(span->xzc_bits.xzcb_kind == XZM_SLICE_KIND_MULTI_FREE);
+	xzm_debug_assert(span->xzcs_slice_count > slice_count);
+	xzm_debug_assert(segment->xzs_kind != XZM_SEGMENT_KIND_HUGE);
+
+	// Find the start and length of the piece being split off and update its
+	// slices
+	xzm_free_span_t retval;
+	xzm_slice_count_t index_to_free;
+	xzm_slice_count_t count_to_free = span->xzcs_slice_count - slice_count;
+	if (front) {
+		retval = span + count_to_free;
+		// We don't update the backpointers here because this span is about to
+		// be used as a large chunk, but we do need to update the slice count
+		// and kind since this span could be given back to _segment_slice_split
+		// to split off the back end
+		retval->xzcs_slice_count = span->xzcs_slice_count - count_to_free;
+		// We could probably copy the bits wholesale, but for now only
+		// explicitly copy the ones we know we need
+		retval->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_FREE;
+		// Preserve whether the span is pristine, since it was undefined
+		retval->xzc_bits.xzcb_is_pristine = span->xzc_bits.xzcb_is_pristine;
+		index_to_free = _xzm_slice_index(segment, span);
+	} else {
+		retval = span;
+		index_to_free = _xzm_slice_index(segment, span) + slice_count;
+	}
+	// If the segment group uses deferred reclaim, then the reclaim id for the
+	// split span has already been initialized, so don't overwrite it
+	_xzm_segment_group_segment_span_free(sg, segment, index_to_free,
+			count_to_free, !uses_dr, span->xzc_bits.xzcb_is_pristine);
+	return retval;
+}
+
+static void
+_xzm_segment_group_segment_create_guard(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_slice_count_t index)
+{
+	xzm_slice_t slice = &segment->xzs_slices[index];
+	xzm_slice_count_t slice_count = 1;
+
+	// Coalesce with next guard page
+	if (&slice[1] < _xzm_segment_slices_end(segment) &&
+			slice[1].xzc_bits.xzcb_kind == XZM_SLICE_KIND_GUARD) {
+		slice_count += slice[1].xzcs_slice_count;
+	}
+
+	// Coalesce with previous guard page
+	if (slice > _xzm_segment_slices_begin(segment)) {
+		xzm_slice_t prev = _xzm_span_slice_first(slice - 1);
+		if (prev->xzc_bits.xzcb_kind == XZM_SLICE_KIND_GUARD) {
+			index -= prev->xzcs_slice_count;
+			slice_count += prev->xzcs_slice_count;
+			slice = prev;
+		}
+	}
+
+	if (slice_count > 1) {
+		// Setup backpointers
+		for (int i = 1; i < slice_count; i++) {
+			slice[i].xzsl_slice_offset_bytes = i * sizeof(struct xzm_slice_s);
+			slice[i].xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+		}
+	} else {
+		// This is a new guard page entry, increment segment count to avoid
+		// trying to free this segment while it has guards
+		segment->xzs_used++;
+	}
+
+	xzm_debug_assert(slice == &segment->xzs_slices[index]);
+
+	slice->xzcs_slice_count = slice_count;
+	// mprotect
+	size_t size = XZM_SEGMENT_SLICE_SIZE * slice_count;
+	void *start = _xzm_segment_slice_index_start(segment, index);
+	int rc = mprotect(start, size, PROT_NONE);
+	if (rc) {
+		xzm_abort_with_reason("Failed to mprotect guard page", errno);
+	}
+
+	// Atomic store maybe?
+	slice->xzc_bits.xzcb_kind = XZM_SLICE_KIND_GUARD;
+}
+
+// mimalloc: mi_segment_span_allocate
+static xzm_chunk_t
+_xzm_segment_group_segment_span_mark_allocated(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_slice_kind_t kind, size_t slice_index,
+		xzm_slice_count_t slice_count)
+{
+	xzm_debug_assert(_xzm_slice_kind_is_chunk(kind));
+	xzm_debug_assert(slice_index < segment->xzs_slice_entry_count);
+
+	xzm_slice_t slice = &segment->xzs_slices[slice_index];
+	xzm_chunk_t chunk = slice;
+
+	// set slice back pointers for the first XZM_MAX_SLICE_OFFSET entries
+	size_t extra = MIN(slice_count - 1, XZM_MAX_SLICE_OFFSET);
+	if (slice_index + extra >= segment->xzs_slice_entry_count) {
+		// huge objects may have more slices than available entries in the
+		// segment->xzs_slices table
+		extra = segment->xzs_slice_entry_count - slice_index - 1;
+	}
+	slice++;
+	for (size_t i = 1; i <= extra; i++, slice++) {
+		slice->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+		slice->xzsl_slice_offset_bytes =
+				(uint32_t)(sizeof(struct xzm_slice_s) * i);
+	}
+
+	// And also for the last one, if not set already (the last one is needed for
+	// coalescing)
+	size_t last_slice_index = slice_index + slice_count - 1;
+	if (kind != XZM_SLICE_KIND_HUGE_CHUNK) {
+		xzm_debug_assert(last_slice_index < segment->xzs_slice_entry_count);
+
+		xzm_slice_t last = &segment->xzs_slices[last_slice_index];
+		if (last >= slice) {
+			last->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+			last->xzsl_slice_offset_bytes =
+					(uint32_t)(sizeof(struct xzm_slice_s) * (slice_count - 1));
+		}
+	}
+
+	// Update the chunk slice last, setting the kind at the very end to
+	// "publish" the chunk for the enumerator protocol
+	if (kind != XZM_SLICE_KIND_TINY_CHUNK) {
+		chunk->xzcs_slice_count = slice_count;
+	} else {
+		xzm_debug_assert(slice_count == 1);
+	}
+	// TODO: atomic store, compiler barrier
+	chunk->xzc_bits.xzcb_kind = kind;
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	mach_vm_reclaim_id_t *reclaim_id = _xzm_segment_slice_meta_reclaim_id(
+			segment, chunk);
+	*reclaim_id = VM_RECLAIM_ID_NULL;
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+	segment->xzs_used++;
+	return chunk;
+}
+
+// Taken from xnu/osfmk/kern/zalloc.c
+static inline uint32_t
+dist_u32(uint32_t a, uint32_t b)
+{
+	return a < b ? b - a : a - b;
+}
+
+static uint32_t
+_xzm_random_clear_n_bits(uint32_t mask, uint32_t pop, uint32_t n)
+{
+	for(; n--; pop--) {
+		uint32_t bit = arc4random_uniform(pop);
+		uint32_t m = mask;
+		// Clear the bottom `bit` bits from m...
+		for (; bit; bit--) {
+			m &= (m - 1);
+		}
+		// ... in order to clear the `bit`th least significant set bit in mask
+		mask ^= 1 << __builtin_ctz(m);
+	}
+	return mask;
+}
+
+// Create a bitmap `width` bits wide with `pop` set bits
+static uint32_t
+_xzm_random_bits(uint32_t pop, uint32_t width)
+{
+	uint32_t mask = (uint32_t)((1ull << width) - 1);
+	uint32_t retval;
+	uint32_t cur;
+
+	if (3 * width / 4 <= pop) {
+		// Caller wants >75% of the bits set, so set them all and clear <25%
+		retval = mask;
+		cur = width;
+	} else if (pop <= width / 4) {
+		retval = 0;
+		cur = 0;
+	} else {
+		// A masked value from arc4random should contain ~`width/2` set bits
+		retval = arc4random() & mask;
+		cur = __builtin_popcount(retval);
+
+		if (dist_u32(cur, pop) > dist_u32(width - cur, pop)) {
+			// If the opposite mask has a closer popcount, then start with that
+			cur = width - cur;
+			retval ^= mask;
+		}
+	}
+
+	if (cur < pop) {
+		// Setting `pop - cur` bits is really clearing that many from the
+		// opposite mask.
+		retval ^= mask;
+		retval = _xzm_random_clear_n_bits(retval, width - cur, pop - cur);
+		retval ^= mask;
+	} else if (pop < cur) {
+		retval = _xzm_random_clear_n_bits(retval, cur, cur - pop);
+	}
+	xzm_debug_assert(__builtin_popcount(retval) == pop);
+	xzm_debug_assert((retval & ~mask) == 0);
+	return retval;
+}
+
+static xzm_chunk_t
+_xzm_segment_group_segment_span_init_run(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_slice_kind_t kind,
+		xzm_preallocate_list_s *preallocate_list, xzm_slice_count_t start_index,
+		xzm_slice_count_t total_slices, xzm_slice_count_t guard_count,
+		xzm_slice_count_t num_chunks)
+{
+	xzm_chunk_t retval = NULL;
+	uint32_t guard_mask;
+	if (guard_count) {
+		guard_mask = _xzm_random_bits(guard_count, num_chunks + 1);
+	} else {
+		guard_mask = 0;
+	}
+
+	xzm_slice_count_t slices_per_chunk = 0;
+	if (kind == XZM_SLICE_KIND_TINY_CHUNK) {
+		slices_per_chunk = 1;
+	} else if (kind == XZM_SLICE_KIND_SMALL_CHUNK) {
+		slices_per_chunk = XZM_SMALL_CHUNK_SIZE / XZM_SEGMENT_SLICE_SIZE;
+	} else if (kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
+		slices_per_chunk =
+				XZM_SMALL_FREELIST_CHUNK_SIZE / XZM_SEGMENT_SLICE_SIZE;
+	} else {
+		xzm_debug_assert(!preallocate_list);
+		xzm_debug_assert(guard_count == 0);
+		xzm_debug_assert(kind == XZM_SLICE_KIND_LARGE_CHUNK);
+		xzm_debug_assert(num_chunks == 1);
+		slices_per_chunk = total_slices;
+	}
+	xzm_debug_assert((num_chunks * slices_per_chunk + guard_count) ==
+			total_slices);
+	xzm_debug_assert((start_index + total_slices) <=
+			segment->xzs_slice_entry_count);
+
+	xzm_slice_count_t index = start_index;
+	bool is_pristine = segment->xzs_slices[index].xzc_bits.xzcb_is_pristine;
+
+	for (int i = 0; i < num_chunks; i++) {
+		if (guard_mask & 1) {
+			_xzm_segment_group_segment_create_guard(sg, segment, index);
+			index++;
+		}
+		guard_mask >>= 1;
+
+		xzm_chunk_t chunk = _xzm_segment_group_segment_span_mark_allocated(sg,
+				segment, kind, index, slices_per_chunk);
+		chunk->xzc_bits.xzcb_is_pristine = is_pristine;
+		index += slices_per_chunk;
+
+		if (i == 0) {
+			retval = chunk;
+		} else {
+			SLIST_INSERT_HEAD(preallocate_list, chunk, xzc_slist_entry);
+		}
+	}
+
+	xzm_debug_assert(guard_mask <= 1);
+	if (guard_mask) {
+		_xzm_segment_group_segment_create_guard(sg, segment, index);
+		index++;
+	}
+
+	xzm_debug_assert(index - start_index == total_slices);
+	return retval;
+}
+
+// mimalloc: mi_segments_page_find_and_allocate
+// Precondition: sg is locked
+static xzm_chunk_t
+_xzm_segment_group_find_and_allocate_chunk(xzm_segment_group_t sg,
+		xzm_slice_kind_t kind, xzm_xzone_guard_config_t guard_config,
+		xzm_preallocate_list_s *preallocate_list, xzm_slice_count_t slice_count,
+		size_t alignment)
+{
+	xzm_debug_assert(_xzm_slice_kind_is_chunk(kind));
+	xzm_debug_assert(kind != XZM_SLICE_KIND_TINY_CHUNK || slice_count == 1);
+	xzm_debug_assert(slice_count != 0);
+	xzm_debug_assert(slice_count * XZM_SEGMENT_SLICE_SIZE <=
+			XZM_LARGE_BLOCK_SIZE_MAX);
+	xzm_debug_assert(alignment == 0 || kind == XZM_SLICE_KIND_LARGE_CHUNK);
+
+	xzm_debug_assert(kind != XZM_SLICE_KIND_TINY_CHUNK || guard_config != NULL);
+	xzm_debug_assert(kind != XZM_SLICE_KIND_SMALL_CHUNK || guard_config != NULL);
+	xzm_debug_assert(kind != XZM_SLICE_KIND_SMALL_FREELIST_CHUNK ||
+			guard_config != NULL);
+	xzm_debug_assert(kind != XZM_SLICE_KIND_LARGE_CHUNK || guard_config == NULL);
+
+	if (alignment <= XZM_SEGMENT_SLICE_SIZE) {
+		// Large chunks guarantee page alignment
+		alignment = 0;
+	}
+	xzm_slice_count_t alignment_slices;
+	if (os_convert_overflow(alignment / XZM_SEGMENT_SLICE_SIZE, &alignment_slices)) {
+		xzm_debug_abort_with_reason("Unexpected align value", alignment);
+		return NULL;
+	}
+
+	xzm_slice_count_t total_slice_count;
+	uint8_t chunks_in_run;
+	uint8_t guards;
+	if (guard_config && guard_config->xxgc_max_run_length) {
+		chunks_in_run = arc4random_uniform(guard_config->xxgc_max_run_length) + 1;
+		total_slice_count = chunks_in_run * slice_count;
+		guards = (guard_config->xxgc_density * total_slice_count) / 256;
+		uint32_t remainder = (guard_config->xxgc_density * total_slice_count) %
+				256;
+		// short circuit to avoid a call to corecrypto in common case that the
+		// density of guard pages goes perfectly into the allocated pages
+		if (remainder && remainder > arc4random_uniform(256)) {
+			guards++;
+		}
+		total_slice_count += guards;
+	} else {
+		total_slice_count = slice_count;
+		chunks_in_run = 1;
+		guards = 0;
+	}
+	xzm_debug_assert(total_slice_count <=
+			(XZM_LARGE_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE) ||
+			// Aligned allocations can request more than LARGE_BLOCK_SIZE slices
+			// from the span queue
+			alignment != 0);
+	// At present, we only allow 1 guard page between chunks in a run, so it
+	// shouldn't be possible to have more guards than chunks
+	xzm_debug_assert(chunks_in_run >= guards);
+
+	if (alignment_slices) {
+		// We only need to allocate (slice_count + alignment_slices - 1) slices
+		// to guarantee that there will be a slice_count long span at the
+		// correct alignment
+		xzm_slice_count_t max_align_slices =
+				alignment_slices ? alignment_slices - 1 : 0;
+
+		if (os_add_overflow(total_slice_count, max_align_slices,
+				&total_slice_count)) {
+			xzm_debug_abort_with_reason("Unexpected total slice count",
+					slice_count + max_align_slices);
+			return NULL;
+		}
+
+		xzm_debug_assert(total_slice_count < XZM_SLICES_PER_SEGMENT);
+	}
+
+	for (xzm_span_queue_t sq = xzm_span_queue_for(sg, total_slice_count);
+			sq < &sg->xzsg_spans[XZM_SPAN_QUEUE_COUNT];
+			sq++) {
+		// TODO: rather than allowing a range of span sizes in a span queue,
+		// should all the spans be exactly the span queue size?  Then this would
+		// be a pop rather than a list scan.
+		xzm_free_span_t span, tmp;
+		LIST_FOREACH_SAFE(span, &sq->xzsq_queue, xzc_entry, tmp) {
+			xzm_slice_count_t span_slice_count =
+					_xzm_free_span_slice_count(span);
+			if (span_slice_count >= total_slice_count) {
+				xzm_malloc_zone_t zone = &sg->xzsg_main_ref->xzmz_base;
+				xzm_segment_t segment = _xzm_segment_for_slice(zone, span);
+#if CONFIG_XZM_DEFERRED_RECLAIM
+				xzm_slice_count_t old_total_slice_count = total_slice_count;
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+				xzm_slice_count_t front_free_count = 0;
+
+				if (alignment_slices) {
+					// Split off the front to round the address up to alignment
+					xzm_slice_count_t actual_index = _xzm_slice_index(segment,
+							span);
+					xzm_slice_count_t desired_index = roundup(actual_index,
+							alignment_slices);
+
+					front_free_count = desired_index - actual_index;
+					xzm_debug_assert(slice_count <= (total_slice_count - front_free_count));
+
+					// Take the alignment slices back out of our request
+					total_slice_count = slice_count;
+
+					if (front_free_count) {
+						span_slice_count -= front_free_count;
+					}
+				}
+
+				xzm_slice_count_t back_free_count =
+						span_slice_count - total_slice_count;
+
+				bool uses_dr = false;
+#if CONFIG_XZM_DEFERRED_RECLAIM
+				uses_dr = _xzm_segment_group_uses_deferred_reclamation(sg);
+				if (uses_dr) {
+					if (!_xzm_segment_group_span_mark_smaller(sg, span,
+							front_free_count, total_slice_count,
+							back_free_count)) {
+						total_slice_count = old_total_slice_count;
+						// span is busy being reclaimed by the kernel
+						continue;
+					}
+				}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+				LIST_REMOVE(span, xzc_entry);
+
+				if (front_free_count) {
+					span = _xzm_segment_group_segment_slice_split(sg, segment,
+							span, span_slice_count, uses_dr, true);
+				}
+
+				if (back_free_count) {
+					_xzm_segment_group_segment_slice_split(sg, segment, span,
+							total_slice_count, uses_dr, false);
+				}
+
+				xzm_slice_count_t index = _xzm_slice_index(segment, span);
+
+				xzm_chunk_t chunk;
+				chunk = _xzm_segment_group_segment_span_init_run(sg, segment,
+							kind, preallocate_list, index, total_slice_count,
+							guards, chunks_in_run);
+
+				xzm_debug_assert(chunk);
+				xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg,
+						segment));
+
+				return chunk;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+// mi_segment_init
+static xzm_chunk_t
+_xzm_segment_group_init_segment(xzm_segment_group_t sg, xzm_segment_t segment,
+		void *body, size_t body_size, bool huge, bool is_pristine)
+{
+	xzm_chunk_t chunk = NULL;
+	xzm_assert((uintptr_t)segment < XZM_LIMIT_ADDRESS);
+	xzm_assert((uintptr_t)body < XZM_LIMIT_ADDRESS);
+	xzm_debug_assert((uintptr_t)segment % XZM_METAPOOL_SEGMENT_ALIGN == 0);
+	xzm_debug_assert((uintptr_t)body % XZM_SEGMENT_SIZE == 0);
+	xzm_debug_assert(body_size % XZM_SEGMENT_SLICE_SIZE == 0);
+
+	xzm_slice_count_t total_slices = 0;
+	if (os_convert_overflow(body_size / XZM_SEGMENT_SLICE_SIZE, &total_slices)) {
+		xzm_abort("Slice count too large in init_segment");
+	}
+	segment->xzs_segment_group = sg;
+	segment->xzs_slice_count = total_slices;
+	segment->xzs_slice_entry_count = MIN(total_slices, XZM_SLICES_PER_SEGMENT);
+	segment->xzs_used = 0;
+	segment->xzs_segment_body = body;
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	segment->xzs_reclaim_id = VM_RECLAIM_ID_NULL;
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+	segment->xzs_kind = huge ? XZM_SEGMENT_KIND_HUGE : XZM_SEGMENT_KIND_NORMAL;
+	if (huge) {
+		chunk = _xzm_segment_group_segment_span_mark_allocated(sg, segment,
+				XZM_SLICE_KIND_HUGE_CHUNK, 0, segment->xzs_slice_count);
+		chunk->xzc_bits.xzcb_is_pristine = is_pristine;
+	} else {
+		// Lock the segment group to add this span - we'll return to the caller
+		// with the segment group locked so they can then directly allocate what
+		// they need
+		_malloc_lock_lock(&sg->xzsg_lock);
+
+		_xzm_segment_group_segment_span_free(sg, segment, 0, total_slices,
+				true, is_pristine);
+	}
+	xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+	return chunk;
+}
+
+// mimalloc: mi_segment_alloc
+//
+// Used to allocate both normal and huge segments.
+//
+// Postcondition: for normal segments, the segment group lock is held on
+// successful return
+static bool
+_xzm_segment_group_alloc_segment(xzm_segment_group_t sg, size_t required_bytes,
+		size_t alignment, xzm_chunk_t *huge_chunk, bool purgeable)
+{
+	xzm_chunk_t chunk;
+	xzm_debug_assert((required_bytes == 0 && huge_chunk == NULL) ||
+			(required_bytes > 0 && huge_chunk != NULL));
+
+	bool huge = (required_bytes != 0);
+
+	// non-default segment alignment is only supported for huge chunks
+	xzm_debug_assert(huge || alignment == 0);
+
+	// The total number of bytes we need to allocate is then:
+	// - For normal segments, exactly the standard segment size
+	// - For huge segments, the required body size, rounded up to the next slice
+	size_t total_required_bytes;
+	if (huge) {
+		total_required_bytes = roundup(required_bytes, XZM_SEGMENT_SLICE_SIZE);
+	} else {
+		total_required_bytes = XZM_SEGMENT_SIZE;
+	}
+
+	xzm_range_group_t rg = sg->xzsg_range_group;
+
+	xzm_range_group_alloc_flags_t rga_flags = 0;
+	if (huge) {
+		rga_flags |= XZM_RANGE_GROUP_ALLOC_FLAGS_HUGE;
+	}
+
+	if (purgeable) {
+		rga_flags |= XZM_RANGE_GROUP_ALLOC_FLAGS_PURGEABLE;
+	}
+
+#if CONFIG_MTE
+	// XXX Note: we need to allocate all data segments as taggable in order for
+	// tag_data to work, but the vast majority of the space will be for
+	// large/huge, which is a significant waste.  We're okay with that because
+	// tag_data is not the default/production configuration, but we may need to
+	// be more efficient about this in the future.
+	if (_xzm_segment_group_memtag_enabled(sg)) {
+		rga_flags |= XZM_RANGE_GROUP_ALLOC_FLAGS_MTE;
+	}
+#endif
+
+	void *segment_body = xzm_range_group_alloc_segment(rg, total_required_bytes,
+			alignment, mvm_plat_map(*map_ptr), rga_flags);
+	if (!segment_body) {
+		return false;
+	}
+
+	xzm_assert((uintptr_t)segment_body < XZM_LIMIT_ADDRESS);
+
+	xzm_segment_t segment_meta = xzm_metapool_alloc(
+			&sg->xzsg_main_ref->xzmz_metapools[XZM_METAPOOL_SEGMENT]);
+
+
+	chunk = _xzm_segment_group_init_segment(sg, segment_meta, segment_body,
+			total_required_bytes, huge, true);
+
+	// Publish the segment in the segment table now that it has been properly
+	// initialized
+	_xzm_segment_table_allocated_at(sg->xzsg_main_ref, segment_body,
+			segment_meta, !huge);
+
+	if (huge) {
+		*huge_chunk = chunk;
+	}
+	return true;
+}
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+
+static xzm_chunk_t
+_xzm_segment_group_alloc_huge_chunk_from_cache(xzm_segment_group_t sg,
+		xzm_slice_count_t slice_count)
+{
+	xzm_debug_assert(sg->xzsg_id == XZM_SEGMENT_GROUP_DATA_LARGE);
+
+	xzm_segment_t best_seg, cur_seg, seg_tmp;
+	xzm_segment_cache_t cache = &sg->xzsg_cache;
+	xzm_chunk_t chunk = NULL;
+
+	_malloc_lock_lock(&cache->xzsc_lock);
+
+	if (cache->xzsc_count == 0) {
+		_malloc_lock_unlock(&cache->xzsc_lock);
+		return NULL;
+	}
+
+	xzm_reclaim_buffer_t buffer = sg->xzsg_main_ref->xzmz_reclaim_buffer;
+	while (1) {
+		best_seg = NULL;
+		TAILQ_FOREACH_SAFE(cur_seg, &cache->xzsc_head, xzs_cache_entry, seg_tmp) {
+			if (cur_seg->xzs_slice_count >= slice_count &&
+					// allow up to 50% fragmentation
+					(cur_seg->xzs_slice_count < (2 * slice_count)) &&
+					(best_seg == NULL ||
+					cur_seg->xzs_slice_count < best_seg->xzs_slice_count)) {
+				if (_xzm_reclaim_is_reusable(buffer,
+						cur_seg->xzs_reclaim_id, true)) {
+					best_seg = cur_seg;
+				} else {
+					// Kernel has already reclaimed this entry or
+					// is in the process of trying to reclaim it.
+					_xzm_segment_group_cache_invalidate(sg, cur_seg);
+				}
+			}
+		}
+
+		if (best_seg == NULL) {
+			// Unable to find a suitable entry
+			_malloc_lock_unlock(&cache->xzsc_lock);
+			return NULL;
+		}
+
+		if (_xzm_segment_group_cache_mark_used(sg, best_seg)) {
+			// entry has been reclaimed
+			break;
+		}
+	}
+
+	_malloc_lock_unlock(&cache->xzsc_lock);
+
+	// Mark segment as allocated since it has been removed from the cache
+	_xzm_segment_table_allocated_at(sg->xzsg_main_ref,
+			_xzm_segment_start(best_seg), best_seg, false);
+
+	chunk = (xzm_chunk_t)_xzm_segment_slices_begin(best_seg);
+
+	return chunk;
+}
+
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+// mimalloc: mi_segment_huge_page_alloc
+static xzm_chunk_t
+_xzm_segment_group_alloc_huge_chunk(xzm_segment_group_t sg,
+		xzm_slice_count_t slice_count, bool clear, size_t alignment,
+		bool purgeable)
+{
+	if (alignment < XZM_SEGMENT_SIZE) {
+		// Huge chunks guarantee segment alignment
+		alignment = 0;
+	}
+
+	xzm_debug_assert(alignment % XZM_SEGMENT_SIZE == 0);
+	__assert_only bool defer_large = sg->xzsg_main_ref->xzmz_defer_large;
+	xzm_debug_assert(sg->xzsg_id == XZM_SEGMENT_GROUP_DATA_LARGE ||
+			!defer_large);
+	xzm_debug_assert(sg->xzsg_id == XZM_SEGMENT_GROUP_DATA || defer_large);
+
+	size_t required_bytes = (size_t)slice_count * XZM_SEGMENT_SLICE_SIZE;
+	xzm_chunk_t chunk = NULL;
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	if (sg->xzsg_id == XZM_SEGMENT_GROUP_DATA_LARGE &&
+			sg->xzsg_cache.xzsc_max_count > 0 &&
+			slice_count <= sg->xzsg_cache.xzsc_max_entry_slices &&
+			alignment <= XZM_SEGMENT_SIZE) {
+		chunk = _xzm_segment_group_alloc_huge_chunk_from_cache(sg, slice_count);
+		if (chunk) {
+			if (clear) {
+				size_t chunk_size = 0;
+				uint8_t *start = _xzm_chunk_start_ptr(
+						&sg->xzsg_main_ref->xzmz_base,
+						chunk, &chunk_size);
+#if CONFIG_REALLOC_CAN_USE_VMCOPY
+				// rdar://140793773
+				bzero(start, chunk_size);
+#else
+				_xzm_segment_group_clear_chunk(sg, start, chunk_size);
+#endif
+				chunk->xzc_bits.xzcb_is_pristine = true;
+			} else {
+				chunk->xzc_bits.xzcb_is_pristine = false;
+			}
+#ifdef DEBUG
+			size_t chunk_size = 0;
+			uintptr_t start = (uintptr_t)_xzm_chunk_start_ptr(
+					&sg->xzsg_main_ref->xzmz_base, chunk, &chunk_size);
+			xzm_debug_assert(alignment == 0 || (start % alignment) == 0);
+#endif // DEBUG
+			return chunk;
+		}
+	}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+	// huge chunks allocated from the VM are inherently clear
+	bool allocated = _xzm_segment_group_alloc_segment(sg, required_bytes,
+			alignment, &chunk, purgeable);
+	return allocated ? chunk : NULL;
+}
+
+static xzm_chunk_t
+_xzm_segment_group_alloc_segment_and_chunk(xzm_segment_group_t sg,
+		xzm_slice_kind_t kind, xzm_xzone_guard_config_t guard_config,
+		xzm_preallocate_list_s *preallocate_list, xzm_slice_count_t slice_count,
+		size_t alignment)
+{
+	xzm_chunk_t chunk = NULL;
+
+	bool allocated = _xzm_segment_group_alloc_segment(sg, 0, 0, NULL, false);
+	if (!allocated) {
+		goto alloc_done;
+	}
+
+	// We hold the main lock again (alloc took it for us).  Since we were
+	// able to allocate, we should be sure to get the chunk.
+	chunk = _xzm_segment_group_find_and_allocate_chunk(sg, kind, guard_config,
+			preallocate_list, slice_count, alignment);
+	xzm_debug_assert(chunk);
+	_malloc_lock_unlock(&sg->xzsg_lock);
+
+alloc_done:
+	_malloc_lock_unlock(&sg->xzsg_alloc_lock);
+	return chunk;
+}
+
+static void
+_xzm_segment_group_bzero_chunk(xzm_segment_group_t sg, uint8_t *start, size_t size)
+{
+	// Put a ceiling on the amount of memory we dirty at a time
+	size_t max_clear_size = KiB(512);
+
+	while (size) {
+		size_t next_clear_size = MIN(size, max_clear_size);
+		bzero(start, next_clear_size);
+		xzm_madvise(&sg->xzsg_main_ref->xzmz_base, start, next_clear_size);
+
+		start += next_clear_size;
+		size -= next_clear_size;
+	}
+}
+
+static void
+_xzm_segment_group_clear_chunk(xzm_segment_group_t sg, uint8_t *start, size_t size)
+{
+#if CONFIG_MADV_ZERO
+	if (madvise(start, size, MADV_ZERO)) {
+#ifdef DEBUG
+		malloc_zone_error(0, false,
+				"Failed to madvise(MADV_ZERO) chunk at %p, error: %d\n",
+				start, errno);
+#endif
+		return _xzm_segment_group_bzero_chunk(sg, start, size);
+	}
+#else
+	return _xzm_segment_group_bzero_chunk(sg, start, size);
+#endif // CONFIG_MADV_ZERO
+}
+
+static void
+_xzm_segment_group_overwrite_chunk(uint8_t *start, size_t size,
+		xzm_range_group_alloc_flags_t rga_flags)
+{
+	mach_vm_address_t vm_addr = (mach_vm_address_t)start;
+	mach_vm_size_t vm_size = (mach_vm_size_t)size;
+	int alloc_flags = VM_FLAGS_OVERWRITE | VM_MAKE_TAG(VM_MEMORY_MALLOC_SMALL);
+#if CONFIG_MTE
+	if (rga_flags & XZM_RANGE_GROUP_ALLOC_FLAGS_MTE) {
+		alloc_flags |= VM_FLAGS_MTE;
+	}
+#endif
+	kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, vm_size,
+			/* mask */ 0, alloc_flags, MEMORY_OBJECT_NULL,
+			/* offset */ 0, /* copy */ FALSE, VM_PROT_DEFAULT,
+			VM_PROT_ALL, VM_INHERIT_DEFAULT);
+	if (kr != KERN_SUCCESS) {
+		xzm_abort_with_reason("mach_vm_map() overwrite failed", kr);
+	}
+}
+
+// mimalloc: mi_segments_page_alloc
+xzm_chunk_t
+xzm_segment_group_alloc_chunk(xzm_segment_group_t sg, xzm_slice_kind_t kind,
+		xzm_xzone_guard_config_t guard_config, xzm_slice_count_t slice_count,
+		xzm_preallocate_list_s *preallocate_list, size_t alignment, bool clear,
+		bool purgeable) {
+	if (kind == XZM_SLICE_KIND_HUGE_CHUNK) {
+		xzm_debug_assert(guard_config == NULL);
+		xzm_debug_assert(preallocate_list == NULL);
+		xzm_debug_assert((slice_count >
+				XZM_LARGE_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE) ||
+				(alignment > XZM_ALIGNMENT_MAX));
+		return _xzm_segment_group_alloc_huge_chunk(sg, slice_count, clear,
+				alignment, purgeable);
+	}
+	xzm_debug_assert(kind == XZM_SLICE_KIND_LARGE_CHUNK || alignment == 0);
+
+	// Due to alignment, it's possible for the xzone layer to request a single
+	// page large chunk. The segment layer assumes that such chunks can't exist,
+	// so we round up the slice count here
+	if (kind == XZM_SLICE_KIND_LARGE_CHUNK && slice_count == 1) {
+		slice_count = 2;
+	}
+
+	// Consider: round up slice_count like mimalloc does?
+
+	// We don't want to hold the main segment group lock while interacting with
+	// the VM so that other allocations and deallocations that don't need to can
+	// be served concurrently, but we do want to limit ourselves to allocating
+	// only one new segment at a time so that we don't overshoot what we need if
+	// many threads arrive during a period where a new segment is needed.
+	//
+	// So, we also have an "allocations lock", and the protocol is that a thread
+	// wanting to allocate new VM must acquire it before going off to the VM.
+
+	xzm_chunk_t chunk = NULL;
+
+	_malloc_lock_lock(&sg->xzsg_lock);
+	chunk = _xzm_segment_group_find_and_allocate_chunk(sg, kind, guard_config,
+			preallocate_list, slice_count, alignment);
+	if (chunk) {
+		// Happy path: we got the chunk and are done.
+		_malloc_lock_unlock(&sg->xzsg_lock);
+		goto done;
+	}
+
+	// First try didn't succeed, so we need a new segment.  See if we can get
+	// the alloc lock to allocate a new segment.
+	bool gotlock = _malloc_lock_trylock(&sg->xzsg_alloc_lock);
+	if (os_likely(gotlock)) {
+		// We got it, so we can try to directly allocate a new segment.
+		_malloc_lock_unlock(&sg->xzsg_lock);
+		chunk = _xzm_segment_group_alloc_segment_and_chunk(sg, kind,
+				guard_config, preallocate_list, slice_count, alignment);
+	} else {
+		// We didn't get it, so somebody else is allocating.  We need to drop
+		// the main lock...
+		_malloc_lock_unlock(&sg->xzsg_lock);
+
+		// ... and wait for them on the alloc lock.
+		_malloc_lock_lock(&sg->xzsg_alloc_lock);
+
+		// Now that we've got the alloc lock, reacquire the main lock and try to
+		// allocate from the new segment that the thread we were waiting for
+		// would have installed.
+		_malloc_lock_lock(&sg->xzsg_lock);
+		chunk = _xzm_segment_group_find_and_allocate_chunk(sg, kind,
+				guard_config, preallocate_list, slice_count, alignment);
+		_malloc_lock_unlock(&sg->xzsg_lock);
+
+		if (chunk) {
+			// We were able to allocate from the new segment.
+			_malloc_lock_unlock(&sg->xzsg_alloc_lock);
+		} else {
+			// The entire new segment has already been exhausted while we were
+			// waiting for the alloc lock.  We have it now, so it's our turn to
+			// allocate a new segment.
+			chunk = _xzm_segment_group_alloc_segment_and_chunk(sg, kind,
+					guard_config, preallocate_list, slice_count, alignment);
+		}
+	}
+
+done:
+
+	if (chunk) {
+		size_t chunk_size;
+		uint8_t *start = _xzm_chunk_start_ptr(&sg->xzsg_main_ref->xzmz_base,
+				chunk, &chunk_size);
+#if CONFIG_MTE
+		const bool memtag_enabled =
+				_xzm_segment_group_memtag_block(sg, chunk_size);
+#endif
+		if (!chunk->xzc_bits.xzcb_is_pristine) {
+			if (_xzm_segment_group_has_madvise_workaround(sg) &&
+					kind == XZM_SLICE_KIND_LARGE_CHUNK) {
+				xzm_range_group_alloc_flags_t rga_flags = 0;
+#if CONFIG_MTE
+				if (memtag_enabled) {
+					rga_flags |= XZM_RANGE_GROUP_ALLOC_FLAGS_MTE;
+				}
+#endif
+				_xzm_segment_group_overwrite_chunk(start, chunk_size, rga_flags);
+				chunk->xzc_bits.xzcb_is_pristine = true;
+			} else if (clear) {
+				// TODO: is this the right cutoff?
+				if (kind == XZM_SLICE_KIND_TINY_CHUNK) {
+					// It's just one page that we're going to fault anyway
+					bzero(start, chunk_size);
+				} else {
+					_xzm_segment_group_clear_chunk(sg, start, chunk_size);
+				}
+
+				chunk->xzc_bits.xzcb_is_pristine = true;
+			}
+		}
+
+		if (os_unlikely(purgeable)) {
+			xzm_debug_assert(guard_config == NULL);
+			xzm_debug_assert(kind == XZM_SLICE_KIND_LARGE_CHUNK);
+			mach_vm_address_t vm_addr = (mach_vm_address_t)start;
+			mach_vm_size_t vm_size = (mach_vm_size_t)chunk_size;
+			int alloc_flags = VM_FLAGS_OVERWRITE |
+					VM_MAKE_TAG(VM_MEMORY_MALLOC_SMALL) | VM_FLAGS_PURGABLE;
+#if CONFIG_MTE
+			if (memtag_enabled) {
+				alloc_flags |= VM_FLAGS_MTE;
+			}
+#endif
+			kern_return_t kr = mach_vm_map(mach_task_self(), &vm_addr, vm_size,
+					/* mask */ 0, alloc_flags, MEMORY_OBJECT_NULL,
+					/* offset */ 0, /* copy */ FALSE, VM_PROT_DEFAULT,
+					VM_PROT_ALL, VM_INHERIT_DEFAULT);
+			if (kr != KERN_SUCCESS) {
+				xzm_abort_with_reason("mach_vm_map() overwrite failed", kr);
+			}
+		}
+	}
+
+	return chunk;
+}
+
+// mimalloc: mi_segment_span_remove_from_queue
+static void
+_xzm_segment_group_segment_span_remove_from_queue(xzm_segment_group_t sg,
+		xzm_free_span_t span, xzm_slice_count_t slice_count)
+{
+	(void)sg; (void)slice_count;
+	LIST_REMOVE(span, xzc_entry);
+}
+
+// mimalloc: mi_segment_span_free_coalesce
+//
+// TODO: more nuanced policy for zero-tracking
+// - Right now we do the easy thing, which is to mark the entire coalesced free
+//   span as dirty because the chunk being deallocated is
+// - However, that's probably not optimal if we're coalescing something small
+//   with a very large free span - e.g. the initial pristine span
+// - One possibility would be to compare the sizes of the chunk being freed and
+//   the spans being coalesced with - if the spans we're coalescing with are
+//   relatively large and already zero-initialized, it may be better to just
+//   zero the chunk being freed and maintain the zero initialization of the new
+//   span as a whole
+// - The risk of that, though, is that we may waste time zeroing chunks that
+//   aren't going to wind up being used to serve cleared allocations anyway
+static xzm_free_span_t
+_xzm_segment_group_segment_span_free_coalesce(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_chunk_t chunk, bool *success_out)
+{
+	xzm_slice_count_t slice_count;
+	if (_xzm_slice_kind_is_chunk(chunk->xzc_bits.xzcb_kind)) {
+		slice_count = _xzm_chunk_slice_count(chunk);
+	} else if (_xzm_slice_kind_is_free_span(chunk->xzc_bits.xzcb_kind)) {
+		slice_count = _xzm_free_span_slice_count(chunk);
+	} else {
+		xzm_abort("attempting to coalesce slice of unexpected type");
+	}
+
+	xzm_free_span_t span = chunk;
+
+	if (success_out) {
+		*success_out = true;
+	}
+
+	// "unpublish" the chunk for enumeration as early as possible by resetting
+	// its kind
+	span->xzc_bits.xzcb_kind = XZM_SLICE_KIND_INVALID;
+
+	xzm_slice_t next = chunk + slice_count;
+	if (next < _xzm_segment_slices_end(segment) &&
+			_xzm_slice_kind_is_free_span(next->xzc_bits.xzcb_kind)) {
+#if CONFIG_XZM_DEFERRED_RECLAIM
+		if (_xzm_segment_group_uses_deferred_reclamation(sg)) {
+			if (!_xzm_segment_group_span_mark_used(sg, next)) {
+				if (success_out) {
+					*success_out = false;
+				}
+				goto previous;
+			}
+		}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+		xzm_slice_count_t next_slice_count = _xzm_free_span_slice_count(next);
+		slice_count += next_slice_count; // extend
+		_xzm_segment_group_segment_span_remove_from_queue(sg, next,
+				next_slice_count);
+	}
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+previous:
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+	if (span > _xzm_segment_slices_begin(segment)) {
+		xzm_slice_t prev = _xzm_span_slice_first(span - 1);
+		if (_xzm_slice_kind_is_free_span(prev->xzc_bits.xzcb_kind)) {
+#if CONFIG_XZM_DEFERRED_RECLAIM
+			if (_xzm_segment_group_uses_deferred_reclamation(sg)) {
+				if (!_xzm_segment_group_span_mark_used(sg, prev)) {
+					if (success_out) {
+						*success_out = false;
+					}
+					goto done;
+				}
+			}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+			xzm_slice_count_t prev_slice_count =
+					_xzm_free_span_slice_count(prev);
+			slice_count += prev_slice_count;
+			_xzm_segment_group_segment_span_remove_from_queue(sg, prev,
+					prev_slice_count);
+			span = prev;
+		}
+	}
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+done:
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+	// and add the new free span
+	_xzm_segment_group_segment_span_free(sg, segment,
+			_xzm_slice_index(segment, span), slice_count, true, false);
+	return span;
+}
+
+static void
+_xzm_segment_group_segment_deallocate(xzm_segment_group_t sg,
+		xzm_segment_t segment, bool free_from_table)
+{
+	// Remove the segment from the segment map
+	if (free_from_table) {
+		_xzm_segment_table_freed_at(sg->xzsg_main_ref,
+				_xzm_segment_start(segment), segment, true);
+	}
+
+	size_t size = segment->xzs_slice_count * XZM_SEGMENT_SLICE_SIZE;
+	xzm_range_group_free_segment_body(sg->xzsg_range_group,
+			_xzm_segment_start(segment), size, mvm_plat_map(segment->xzs_map));
+	xzm_metapool_free(&sg->xzsg_main_ref->xzmz_metapools[XZM_METAPOOL_SEGMENT],
+			segment);
+}
+
+// mimalloc: mi_segment_free
+static void
+_xzm_segment_group_segment_free(xzm_segment_group_t sg, xzm_segment_t segment)
+{
+	xzm_debug_assert(segment->xzs_used == 0);
+	xzm_free_span_t span = _xzm_segment_slices_begin(segment);
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	xzm_free_span_t next;
+	if (_xzm_segment_group_uses_deferred_reclamation(sg)) {
+		if (!_xzm_segment_group_span_mark_used(sg, span)) {
+			// kernel is holding this span busy
+			goto fail;
+		}
+		while (_xzm_free_span_slice_count(span) < _xzm_segment_slice_count(segment)) {
+			bool success;
+			_xzm_segment_group_segment_span_remove_from_queue(sg, span,
+					span->xzcs_slice_count);
+			span = _xzm_segment_group_segment_span_free_coalesce(sg, segment,
+					span, &success);
+			if (!success) {
+				// kernel is holding an adjacent span busy
+				goto fail;
+			}
+		}
+	}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+	// The segment should have exactly one free span, which we need to now
+	// remove from its span queue
+	xzm_debug_assert(span->xzc_bits.xzcb_kind == XZM_SLICE_KIND_MULTI_FREE);
+	xzm_debug_assert(span->xzcs_slice_count == segment->xzs_slice_count);
+
+	_xzm_segment_group_segment_span_remove_from_queue(sg, span,
+			span->xzcs_slice_count);
+
+	// Drop the segment group lock before going off to the VM
+	_malloc_lock_unlock(&sg->xzsg_lock);
+
+	_xzm_segment_group_segment_deallocate(sg, segment, true);
+	return;
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+fail:;
+	// Kernel is holding a span busy, place any re-used spans back in the
+	// buffer.
+	next = _xzm_segment_slices_begin(segment);
+	do {
+		span = next;
+		if (!_xzm_segment_slice_is_deferred(segment, span)) {
+			_xzm_segment_group_span_mark_free(sg, span);
+		}
+		next = span + _xzm_free_span_slice_count(span);
+	} while (next < _xzm_segment_slices_end(segment));
+	_malloc_lock_unlock(&sg->xzsg_lock);
+	return;
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+}
+
+// trim unneeded space off the end of a huge segment
+static void
+_xzm_segment_group_split_huge_segment(xzm_segment_group_t sg, xzm_segment_t segment,
+		xzm_slice_count_t required_slices)
+{
+	xzm_debug_assert(segment->xzs_kind == XZM_SEGMENT_KIND_HUGE);
+	xzm_debug_assert(segment->xzs_slice_count >= required_slices);
+	if (segment->xzs_slice_count == required_slices) {
+		return;
+	}
+
+	uint8_t *start = _xzm_segment_start(segment);
+	uint8_t *end = _xzm_segment_end(segment);
+
+	uint8_t *remainder = (uint8_t *)(start +
+			required_slices * XZM_SEGMENT_SLICE_SIZE);
+	if (remainder < end) {
+		size_t total_remainder_size = (size_t)(end - remainder);
+#if CONFIG_XZM_DEFERRED_RECLAIM
+		// new segments must be created on a SEGMENT_SIZE boundary to be annotated
+		// in the segment table
+		uint8_t *remainder_seg = (uint8_t *)roundup((uintptr_t)remainder,
+				XZM_SEGMENT_SIZE);
+		xzm_metapool_t metapool =
+				&sg->xzsg_main_ref->xzmz_metapools[XZM_METAPOOL_SEGMENT];
+		xzm_segment_t remainder_metadata = xzm_metapool_alloc(metapool);
+		size_t remainder_seg_size = (end - remainder_seg);
+
+		// If the remainder that we're freeing spans a segment granule, we need
+		// to clear the entries from the segment map
+		if (remainder_seg < end) {
+			_xzm_segment_table_freed_at(sg->xzsg_main_ref, remainder_seg,
+					segment, false);
+		}
+
+		_malloc_lock_lock(&sg->xzsg_cache.xzsc_lock);
+		if (remainder_seg < end &&
+				remainder_seg_size > XZM_LARGE_BLOCK_SIZE_MAX &&
+				sg->xzsg_cache.xzsc_count < sg->xzsg_cache.xzsc_max_count) {
+			// create a new segment from the end of this one and add it back to
+			// the cache
+
+			_xzm_segment_group_init_segment(sg, remainder_metadata,
+					remainder_seg, remainder_seg_size, true, false);
+			_xzm_segment_group_cache_mark_free(sg, remainder_metadata);
+
+			_malloc_lock_unlock(&sg->xzsg_cache.xzsc_lock);
+
+			if (remainder_seg > remainder) {
+				// free the unused portion of the current segment
+				size_t remainder_size = total_remainder_size -
+						remainder_seg_size;
+				xzm_range_group_free_segment_body(sg->xzsg_range_group,
+						(void *)remainder, remainder_size, NULL);
+			}
+		} else {
+			_malloc_lock_unlock(&sg->xzsg_cache.xzsc_lock);
+			// cannot create a cached segment out of the remainder,
+			// free it instead.
+			xzm_metapool_free(metapool, remainder_metadata);
+			xzm_range_group_free_segment_body(sg->xzsg_range_group,
+					(void *)remainder, total_remainder_size, NULL);
+		}
+#else // CONFIG_XZM_DEFERRED_RECLAIM
+		uint8_t *remainder_seg = (uint8_t *)roundup((uintptr_t)remainder,
+				XZM_SEGMENT_SIZE);
+		// If the body that we're freeing spans a segment granule, we need to
+		// clear the entries from the segment map
+		if (remainder_seg < end) {
+			_xzm_segment_table_freed_at(sg->xzsg_main_ref, remainder_seg,
+					segment, false);
+		}
+		xzm_range_group_free_segment_body(sg->xzsg_range_group, (void *)remainder,
+				total_remainder_size, NULL);
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+		// re-initialize original segment with reduced slice count
+		_xzm_segment_group_init_segment(sg, segment,
+				_xzm_segment_start(segment),
+				required_slices * XZM_SEGMENT_SLICE_SIZE, true, false);
+	}
+	xzm_debug_assert(_xzm_segment_end(segment) == remainder);
+}
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+
+static bool
+_xzm_segment_group_free_huge_chunk_to_cache(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_chunk_t chunk) {
+	xzm_segment_cache_t cache = &sg->xzsg_cache;
+	xzm_segment_t entry = NULL, tmp_entry = NULL;
+	xzm_debug_assert(cache->xzsc_max_count > 0);
+
+	if (segment->xzs_slice_count > cache->xzsc_max_entry_slices) {
+		// Do this check (and all others that can cause us to return false)
+		// before modifying the segment table
+		return false;
+	}
+
+	// The data for this segment could be asynchronously reclaimed and reused
+	// before the metadata is invalidated/removed from the segment table, so we
+	// need to remove this segment from the segment table before putting it into
+	// the cache. If reused, the segment will be marked allocated in
+	// _xzm_segment_group_alloc_huge_chunk_from_cache
+	_xzm_segment_table_freed_at(sg->xzsg_main_ref,
+			_xzm_segment_start(segment), segment, true);
+
+#if CONFIG_MTE
+	// We are committed to returning the chunk to the cache and have removed
+	// access to it from the segment table.  We can safely retag now before taking
+	// the cache lock.
+	if (_xzm_segment_group_memtag_enabled(sg)) {
+		size_t chunk_size = 0;
+		void *ptr = _xzm_chunk_start_ptr(
+				&sg->xzsg_main_ref->xzmz_base, chunk, &chunk_size);
+		memtag_tag_canonical(ptr, chunk_size);
+		// Note: for better protection from canonical pointers into huge chunks we
+		// could retag with a random tag here (which will require code changes on
+		// the alloc path also).
+	}
+#endif
+
+	_malloc_lock_lock(&cache->xzsc_lock);
+
+	xzm_reclaim_buffer_t buffer = sg->xzsg_main_ref->xzmz_reclaim_buffer;
+
+	if (sg->xzsg_cache.xzsc_count == sg->xzsg_cache.xzsc_max_count) {
+		// cache is full, sweep through the cache to find invalid entries
+		TAILQ_FOREACH_SAFE(entry, &sg->xzsg_cache.xzsc_head,
+				xzs_cache_entry, tmp_entry) {
+			if (!_xzm_reclaim_is_reusable(buffer,
+					entry->xzs_reclaim_id, true)) {
+				_xzm_segment_group_cache_invalidate(sg, entry);
+				continue;
+			} else {
+				// cache entries are kept in LRU order - encountering an
+				// available one implies all other cache entries are also
+				// available
+				break;
+			}
+		}
+	}
+
+	while (cache->xzsc_count == cache->xzsc_max_count) {
+		// Cache is full, evict the oldest entry
+		_xzm_segment_group_cache_evict(sg);
+	}
+
+	// insert segment into cache
+	_xzm_segment_group_cache_mark_free(sg, segment);
+	_malloc_lock_unlock(&cache->xzsc_lock);
+	return true;
+}
+
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+// mimalloc: _mi_segment_huge_page_free
+static void
+_xzm_segment_group_free_huge_chunk(xzm_segment_group_t sg, xzm_chunk_t chunk,
+		bool purgeable)
+{
+	xzm_segment_t segment = _xzm_segment_for_slice(
+			&sg->xzsg_main_ref->xzmz_base, chunk);
+	xzm_debug_assert(segment->xzs_kind == XZM_SEGMENT_KIND_HUGE);
+	xzm_debug_assert(segment->xzs_used == 1);
+
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	if (sg->xzsg_cache.xzsc_max_count > 0 &&
+			!purgeable &&
+			segment->xzs_slice_count <= sg->xzsg_cache.xzsc_max_entry_slices &&
+			segment->xzs_slice_count >
+			(XZM_LARGE_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE)) {
+		if (_xzm_segment_group_free_huge_chunk_to_cache(sg, segment, chunk)) {
+			return;
+		}
+	}
+#else
+	// No special handling of purgeable huge segments without the huge cache
+	(void)purgeable;
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+	_xzm_segment_group_segment_deallocate(sg, segment, true);
+}
+
+static void
+xzm_madvise(xzm_malloc_zone_t zone, uint8_t *start, size_t size)
+{
+	__assert_only int rc = mvm_madvise_plat(start, size, MADV_FREE_REUSABLE, 0,
+			mvm_plat_map(xzm_segment_table_query(_xzm_malloc_zone_main(zone),
+			start)->xzs_map));
+
+#ifdef DEBUG
+	if (rc) {
+		// TODO: time for a compatibility break?  Make this fatal?
+		malloc_zone_error(0, false,
+				"Failed to madvise chunk at %p, error: %d\n", start, errno);
+	}
+#endif // DEBUG
+}
+
+void
+xzm_segment_group_segment_madvise_span(xzm_segment_group_t sg,
+		uint8_t *slice_start, xzm_slice_count_t count)
+{
+	xzm_debug_assert((uintptr_t)slice_start % XZM_SEGMENT_SLICE_SIZE == 0);
+	size_t span_size = count * XZM_SEGMENT_SLICE_SIZE;
+	xzm_madvise(&sg->xzsg_main_ref->xzmz_base, slice_start, span_size);
+}
+
+void
+xzm_segment_group_segment_madvise_chunk(xzm_segment_group_t sg,
+		xzm_chunk_t chunk)
+{
+	xzm_debug_assert(_xzm_slice_kind_is_chunk(chunk->xzc_bits.xzcb_kind));
+
+	size_t chunk_size = 0;
+	uint8_t *start = _xzm_chunk_start_ptr(&sg->xzsg_main_ref->xzmz_base, chunk,
+			&chunk_size);
+	xzm_madvise(&sg->xzsg_main_ref->xzmz_base, start, chunk_size);
+}
+
+// mimalloc: _mi_segment_page_free
+void
+xzm_segment_group_free_chunk(xzm_segment_group_t sg, xzm_chunk_t chunk,
+		bool purgeable, bool small_madvise_needed)
+{
+	xzm_slice_kind_t kind = chunk->xzc_bits.xzcb_kind;
+	xzm_debug_assert(_xzm_slice_kind_is_chunk(kind));
+
+	if (kind == XZM_SLICE_KIND_HUGE_CHUNK) {
+		_xzm_segment_group_free_huge_chunk(sg, chunk, purgeable);
+		return;
+	}
+
+	size_t chunk_size = 0;
+	uint8_t *start = _xzm_chunk_start_ptr(
+			&sg->xzsg_main_ref->xzmz_base, chunk, &chunk_size);
+	xzm_range_group_alloc_flags_t rga_flags = 0;
+#if CONFIG_MTE
+	if (_xzm_segment_group_memtag_enabled(sg)) {
+		rga_flags |= XZM_RANGE_GROUP_ALLOC_FLAGS_MTE;
+		// Clear tags for chunk before handing it back to segment group
+		memtag_tag_canonical(start, chunk_size);
+	}
+#endif
+
+	if (os_unlikely(purgeable)) {
+		xzm_debug_assert(kind == XZM_SLICE_KIND_LARGE_CHUNK);
+		// Remove the purgeability from this allocation before freeing back to
+		// the segment
+		_xzm_segment_group_overwrite_chunk(start, chunk_size, rga_flags);
+	}
+
+	xzm_segment_t segment = _xzm_segment_for_slice(
+			&sg->xzsg_main_ref->xzmz_base, chunk);
+
+	if (_xzm_segment_group_has_madvise_workaround(sg) &&
+			kind == XZM_SLICE_KIND_LARGE_CHUNK) {
+		_xzm_segment_group_overwrite_chunk(start, chunk_size, rga_flags);
+	} else if (!_xzm_segment_group_uses_deferred_reclamation(sg) &&
+			// Small chunks will have already been aggressively madvised
+			// by the time they are free
+			(kind != XZM_SLICE_KIND_SMALL_CHUNK || small_madvise_needed)) {
+		xzm_segment_group_segment_madvise_chunk(sg, chunk);
+	}
+
+	_malloc_lock_lock(&sg->xzsg_lock);
+
+	xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+#if CONFIG_XZM_DEFERRED_RECLAIM
+	xzm_debug_assert(!(_xzm_segment_group_uses_deferred_reclamation(sg) &&
+			_xzm_segment_slice_is_deferred(segment, chunk)));
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+	xzm_free_span_t span = _xzm_segment_group_segment_span_free_coalesce(sg, segment, chunk, NULL);
+	segment->xzs_used--;
+#if !CONFIG_XZM_DEFERRED_RECLAIM
+	(void)span;
+#endif // !CONFIG_XZM_DEFERRED_RECLAIM
+
+	xzm_debug_assert(kind != XZM_SLICE_KIND_HUGE_CHUNK);
+	const bool can_deallocate = sg->xzsg_main_ref->xzmz_deallocate_segment &&
+			_xzm_segment_group_id_is_data(segment->xzs_segment_group->xzsg_id);
+	if (segment->xzs_used == 0 && can_deallocate) {
+		// Drops the segment group lock
+		_xzm_segment_group_segment_free(sg, segment);
+	} else {
+#if CONFIG_XZM_DEFERRED_RECLAIM
+		if (_xzm_segment_group_uses_deferred_reclamation(sg)) {
+			_xzm_segment_group_span_mark_free(sg, span);
+		}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+		// TODO: sequester segments more efficiently - just leaving the final
+		// whole-segment span in its span queue means its metadata page stays
+		// dirty
+		xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+		_malloc_lock_unlock(&sg->xzsg_lock);
+	}
+}
+
+bool
+xzm_segment_group_try_realloc_large_chunk(xzm_segment_group_t sg,
+		xzm_segment_t segment, xzm_chunk_t chunk,
+		xzm_slice_count_t new_slice_count)
+{
+	xzm_debug_assert(_xzm_segment_for_slice(&sg->xzsg_main_ref->xzmz_base,
+			chunk) == segment);
+	xzm_debug_assert(new_slice_count >
+			(XZM_SMALL_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE));
+	xzm_debug_assert(new_slice_count <=
+			(XZM_LARGE_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE));
+	xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_LARGE_CHUNK);
+
+	if (chunk->xzcs_slice_count < new_slice_count) {
+		_malloc_lock_lock(&sg->xzsg_lock);
+		xzm_slice_count_t slices_to_add = (new_slice_count -
+				chunk->xzcs_slice_count);
+		xzm_slice_t next_slice = chunk + chunk->xzcs_slice_count;
+		xzm_slice_count_t next_free_slices = _xzm_free_span_slice_count(next_slice);
+		// Check if adjacent chunk is in the right segment, free, and
+		// large enough to realloc into
+		if (next_slice >= _xzm_segment_slices_end(segment) ||
+				!_xzm_slice_kind_is_free_span(next_slice->xzc_bits.xzcb_kind) ||
+				next_free_slices < slices_to_add) {
+			_malloc_lock_unlock(&sg->xzsg_lock);
+			return false;
+		}
+
+		const xzm_slice_count_t next_slices_to_free =
+				next_free_slices - slices_to_add;
+		bool uses_dr = false;
+#if CONFIG_XZM_DEFERRED_RECLAIM
+		uses_dr = _xzm_segment_group_uses_deferred_reclamation(sg);
+		if (uses_dr) {
+			if (!_xzm_segment_group_span_mark_smaller(sg, next_slice, 0,
+					slices_to_add, next_slices_to_free)) {
+				// kernel is holding next span busy
+				_malloc_lock_unlock(&sg->xzsg_lock);
+				return false;
+			}
+		}
+#endif // CONFIG_XZM_DEFERRED_RECLAIM
+
+		_xzm_segment_group_segment_span_remove_from_queue(sg, next_slice,
+				next_free_slices);
+
+		// We can only split if there will be 1 or more free slices left over
+		if (next_slices_to_free) {
+			_xzm_segment_group_segment_slice_split(sg, segment, next_slice,
+					slices_to_add, uses_dr, false);
+		}
+
+		for (int i = 0; i < slices_to_add; i++) {
+			next_slice[i].xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+			next_slice[i].xzsl_slice_offset_bytes = (uint32_t)
+					(((uintptr_t)&next_slice[i]) - ((uintptr_t)chunk));
+		}
+		chunk->xzcs_slice_count = new_slice_count;
+		xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+		_malloc_lock_unlock(&sg->xzsg_lock);
+
+#if CONFIG_MTE
+		// If block grows on realloc(), tag additional size with same tag as
+		// allocation.
+		if (_xzm_segment_group_memtag_enabled(sg)) {
+			size_t additional_size = (slices_to_add * XZM_SEGMENT_SLICE_SIZE);
+			size_t chunk_size;
+			void *start = (void *)_xzm_chunk_start(
+					&sg->xzsg_main_ref->xzmz_base, chunk, &chunk_size);
+			size_t offset = chunk_size - additional_size;
+			void *additional_start = _memtag_load_tag(start) + offset;
+			memtag_set_tag(additional_start, additional_size);
+		}
+#endif
+
+		return true;
+	} else if (chunk->xzcs_slice_count > new_slice_count) {
+		_malloc_lock_lock(&sg->xzsg_lock);
+
+		xzm_slice_count_t slices_to_free = (chunk->xzcs_slice_count -
+				new_slice_count);
+		xzm_free_span_t span_to_free = chunk + new_slice_count;
+
+		chunk->xzcs_slice_count = new_slice_count;
+
+		xzm_slice_t last_slice = chunk + (chunk->xzcs_slice_count - 1);
+		last_slice->xzc_bits.xzcb_kind = XZM_SLICE_KIND_MULTI_BODY;
+		last_slice->xzsl_slice_offset_bytes = (uint32_t)
+				(((uintptr_t)last_slice) - ((uintptr_t)chunk));
+
+		// create a fake chunk out of the remainder before freeing it
+		xzm_segment_t segment = _xzm_segment_for_slice(
+				&sg->xzsg_main_ref->xzmz_base, chunk);
+		xzm_slice_kind_t tail_kind = slices_to_free > 1 ?
+				XZM_SLICE_KIND_LARGE_CHUNK : XZM_SLICE_KIND_TINY_CHUNK;
+		_xzm_segment_group_segment_span_mark_allocated(sg, segment, tail_kind,
+				_xzm_slice_index(segment, span_to_free), slices_to_free);
+		_malloc_lock_unlock(&sg->xzsg_lock);
+		// Realloc in place is disabled for the purgeable zone, so we can always
+		// pass purgeable=false here
+		xzm_segment_group_free_chunk(sg, span_to_free, false, false);
+		return true;
+	}
+	return true; // old size == new size, so no-op
+}
+
+bool
+xzm_segment_group_try_realloc_huge_chunk(xzm_segment_group_t sg,
+		xzm_malloc_zone_t zone, xzm_segment_t segment,
+		xzm_chunk_t chunk, xzm_slice_count_t new_slice_count)
+{
+	xzm_debug_assert(_xzm_segment_for_slice(&sg->xzsg_main_ref->xzmz_base,
+			chunk) == segment);
+	xzm_debug_assert(new_slice_count >
+			(XZM_LARGE_BLOCK_SIZE_MAX / XZM_SEGMENT_SLICE_SIZE));
+	xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_HUGE_CHUNK);
+
+
+	if (chunk->xzcs_slice_count < new_slice_count) {
+		size_t current_size = 0;
+		vm_address_t current_ptr = (vm_address_t)_xzm_chunk_start(zone, chunk,
+				&current_size);
+		vm_address_t addr_to_request = current_ptr + current_size;
+		size_t slices_to_request = new_slice_count - chunk->xzcs_slice_count;
+		size_t size_to_request = slices_to_request * XZM_SEGMENT_SLICE_SIZE;
+
+		uintptr_t segment_to_check = roundup(addr_to_request, XZM_SEGMENT_SIZE);
+		while (segment_to_check < (addr_to_request+size_to_request)) {
+			// TODO: Once we have deferred reclaim for huge chunks, we have the
+			// option to do something more clever here (e.g. if all segments
+			// are unallocated or are still waiting to be reclaimed, then we
+			// can acquire those and realloc)
+			if (xzm_segment_table_query(sg->xzsg_main_ref,
+					(void *)segment_to_check)) {
+				return false;
+			}
+			segment_to_check += XZM_SEGMENT_SIZE;
+		}
+
+		int label = VM_MEMORY_REALLOC;
+		void *addr = mvm_allocate_plat(addr_to_request, size_to_request,
+				0, VM_FLAGS_FIXED, 0, label, mvm_plat_map(segment->xzs_map));
+		if (addr) {
+			size_t new_body_size = new_slice_count * XZM_SEGMENT_SLICE_SIZE;
+			_xzm_segment_group_init_segment(sg, segment,
+					_xzm_segment_start(segment), new_body_size, true, false);
+
+			// If we expanded into new segment granules, mark them as allocated
+			uintptr_t first_new_segment = roundup(addr_to_request,
+					XZM_SEGMENT_SIZE);
+			if ((uintptr_t)current_ptr + new_body_size > first_new_segment) {
+				_xzm_segment_table_allocated_at(_xzm_malloc_zone_main(zone),
+						(void *)first_new_segment, segment, false);
+#if CONFIG_MTE
+				// If block grows on realloc(), tag additional size with same tag as
+				// allocation.
+				if (_xzm_segment_group_memtag_enabled(sg)) {
+					void *tagged_addr_to_request =
+							_memtag_load_tag((void *)current_ptr) +
+							current_size;
+					memtag_set_tag(tagged_addr_to_request, size_to_request);
+				}
+#endif
+			}
+
+			xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+			return true;
+		}
+		return false;
+	} else if (chunk->xzcs_slice_count > new_slice_count) {
+		_xzm_segment_group_split_huge_segment(sg, segment, new_slice_count);
+		xzm_debug_assert(_xzm_segment_group_segment_is_valid(sg, segment));
+		return true;
+	}
+	return true; // old size == new size, so no-op
+}
+
+#endif // CONFIG_XZONE_MALLOC