Loading...
--- libmalloc/libmalloc-792.60.6/src/xzone_malloc/xzone_malloc.c
+++ /dev/null
@@ -1,8030 +0,0 @@
-/* ----------------------------------------------------------------------------
-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
-
-#if defined(DEBUG) && !MALLOC_TARGET_EXCLAVES
-#define xzm_trace(name, ...) MALLOC_TRACE(TRACE_xzone_##name, __VA_ARGS__)
-#else
-#define xzm_trace(...)
-#endif
-
-#pragma mark xzone lookup
-
-// mimalloc: mi_bin
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static uint8_t
-_xzm_bin(size_t size)
-{
- if (size == 0) {
- // TODO: fancy unmapped page
- return 0;
- } else if (size <= 128) {
- return howmany(size, XZM_GRANULE) - 1;
- } else {
- xzm_debug_assert(size <= XZM_SMALL_BLOCK_SIZE_MAX);
-
- // The bin calculation rounds up, so subtract to handle perfect fits
- size--;
-
- int msb = 63 - __builtin_clzl(size);
- return ((msb << 2) + ((size >> (msb - 2)) & 0x3)) - 20;
- }
-}
-
-MALLOC_STATIC_ASSERT(
- XZM_POINTER_BUCKETS_MAX <= 4,
- "The bucketing function supports up to 4 pointer buckets");
-
-MALLOC_STATIC_ASSERT(
- sizeof(xzm_bucketing_keys_t) == 2 * sizeof(uint64_t),
- "Invalid size for struct xzm_bucketing_keys_t");
-
-// This function assigns a type descriptor to a pointer bucket.
-// It takes as input the type descriptor itself and a set of keys, which are
-// obtained from the executable_boothash provided to the process by the kernel.
-// It is both security and performance sensitive.
-//
-// The function is designed to fulfill three requirements:
-// 1. It is stable with respect to identical type hashes, for executions of
-// the same process in the same boot session: this is required to prevent
-// an attacker from brute-forcing the bucketing assignment by repeatedly
-// crashing a given process.
-// 2. It distributes the same type hash pseudo-uniformly across buckets, when
-// the execution hash changes: this is required to prevent an attacker from
-// predicting the bucket assignment across processes, or to statically
-// determine the assignment for a given binary across boot sessions.
-// 3. It distributes different type hashes pseudo-uniformly across buckets,
-// given a fixed execution hash: it should not be possible for any
-// random pair of type hashes to always be assigned to the same bucket,
-// independently of the execution hash.
-//
-// The first requirement only holds when the key material is derived from the
-// executable_boothash. It does not hold otherwise.
-MALLOC_ALWAYS_INLINE MALLOC_INLINE MALLOC_USED
-static uint8_t
-_xzm_type_choose_ptr_bucket(const xzm_bucketing_keys_t *const keys,
- uint8_t ptr_bucket_count, malloc_type_descriptor_t type_desc)
-{
- xzm_debug_assert(ptr_bucket_count <= XZM_POINTER_BUCKETS_MAX);
- uint8_t bucket = 0;
-
- if (ptr_bucket_count > 1) {
- const uint32_t type_hash = type_desc.hash;
- const uint64_t key_a = keys->xbk_key_data[0];
- const uint64_t key_b = keys->xbk_key_data[1];
- const uint32_t hash = ((key_a * (uint64_t)type_hash) + key_b) >> 32;
-
- switch (ptr_bucket_count) {
- case 2:
- bucket = hash & 0x1;
- break;
- case 3:
- bucket = hash % 0x3;
- break;
- case 4:
- bucket = hash & 0x3;
- break;
- default:
- __builtin_unreachable();
- }
- }
-
- return bucket;
-}
-
-// mimalloc: mi_page_queue
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_xzone_index_t
-_xzm_xzone_lookup(xzm_malloc_zone_t zone, size_t size,
- malloc_type_descriptor_t type_desc)
-{
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- uint8_t bin = _xzm_bin(size);
- uint8_t bin_bucket_count = main->xzmz_xzone_bin_bucket_counts[bin];
- uint8_t bin_offset = main->xzmz_xzone_bin_offsets[bin];
-
- uint8_t bucket;
-#if XZM_BUCKET_DATA_ONLY || XZM_BUCKET_POINTER_ONLY
- (void)bin_bucket_count;
- bucket = 0;
-#else // XZM_BUCKET_DATA_ONLY || XZM_BUCKET_POINTER_ONLY
-
-#if XZM_NARROW_BUCKETING
- if (main->xzmz_narrow_bucketing) {
- if (type_desc.summary.type_kind == MALLOC_TYPE_KIND_OBJC) {
- // - There are pure-data callsites that provide a 0 type hash
- // because they assume that nothing is done with it.
- // - The ObjC runtime callsite also provides a 0 type hash, for the
- // same reason.
- //
- // To prevent these callsites from deterministically bucketing
- // together, pick a different arbitrary type hash for ObjC.
- //
- // TODO: update the callsite in the ObjC runtime directly so that
- // this is no longer necessary
- type_desc.hash = 1;
- }
- bucket = _xzm_type_choose_ptr_bucket(&main->xzmz_bucketing_keys,
- bin_bucket_count, type_desc);
- goto bucket_computed;
- }
-#endif // XZM_NARROW_BUCKETING
-
- bool pure_data = malloc_type_descriptor_is_pure_data(type_desc);
- if (pure_data) {
- bucket = XZM_XZONE_BUCKET_DATA;
- } else if (type_desc.summary.type_kind == MALLOC_TYPE_KIND_OBJC) {
- bucket = XZM_XZONE_BUCKET_OBJC;
-#if XZM_BUCKET_VISIBILITY
- } else if (type_desc.type_id == MALLOC_TYPE_ID_NONE) {
- bucket = XZM_XZONE_BUCKET_PLAIN;
- } else if (malloc_type_descriptor_is_uninferred(type_desc)) {
- bucket = XZM_XZONE_BUCKET_UNINFERRED;
-#endif // XZM_BUCKET_VISIBILITY
- } else {
- bool fallback_hash = (type_desc.type_id == MALLOC_TYPE_ID_NONE);
- if (fallback_hash) {
- type_desc.hash = malloc_entropy[0] >> 32;
- }
- uint8_t ptr_bucket = _xzm_type_choose_ptr_bucket(
- &main->xzmz_bucketing_keys,
- (bin_bucket_count - XZM_XZONE_BUCKET_POINTER_BASE), type_desc);
- bucket = XZM_XZONE_BUCKET_POINTER_BASE + ptr_bucket;
- }
-
-#if XZM_NARROW_BUCKETING
-bucket_computed:
-#endif // XZM_NARROW_BUCKETING
-
-#endif // XZM_BUCKET_DATA_ONLY || XZM_BUCKET_POINTER_ONLY
- xzm_debug_assert(bucket < bin_bucket_count);
-
- return (xzm_xzone_index_t)(bin_offset + bucket);
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_allocation_index_t
-_xzm_get_allocation_index(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_slot_config_t *cur_slot_config, bool is_xas)
-{
- xzm_slot_config_t *slot = is_xas ? &xz->xz_slot_config :
- &xz->xz_list_config;
- xzm_slot_config_t slot_config = os_atomic_load(slot, relaxed);
- xzm_debug_assert(slot_config <= zone->xzz_max_slot_config);
-
- if (cur_slot_config) {
- *cur_slot_config = slot_config;
- }
-
- switch (slot_config) {
- case XZM_SLOT_CPU:
- return _malloc_cpu_number();
- case XZM_SLOT_CLUSTER:
-#if CONFIG_XZM_CLUSTER_AWARE
- return _malloc_cpu_cluster_number();
-#else // CONFIG_XZM_CLUSTER_AWARE
- return _malloc_cpu_number() % 2;
-#endif // CONFIG_XZM_CLUSTER_AWARE
- case XZM_SLOT_SINGLE:
- default:
- return 0;
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_allocation_index_t
-_xzm_get_limit_allocation_index(xzm_slot_config_t slot_config)
-{
- switch (slot_config) {
- case XZM_SLOT_CPU:
- return logical_ncpus;
- case XZM_SLOT_CLUSTER:
-#if CONFIG_XZM_CLUSTER_AWARE
- return ncpuclusters;
-#else // CONFIG_XZM_CLUSTER_AWARE
- return MIN(2, logical_ncpus);
-#endif // CONFIG_XZM_CLUSTER_AWARE
- case XZM_SLOT_SINGLE:
- default:
- return 1;
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_chunk_list_t
-_xzm_xzone_chunk_list_for_index(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_list_t lists, xzm_allocation_index_t alloc_idx)
-{
- xzm_xzone_index_t xz_idx = xz->xz_idx;
- size_t alloc_base_idx = alloc_idx * zone->xzz_xzone_count;
- xzm_debug_assert(alloc_base_idx + xz_idx <
- zone->xzz_slot_count * zone->xzz_xzone_count);
- return &lists[alloc_base_idx + xz_idx];
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_segment_group_t
-_xzm_segment_group_for_id_and_front(xzm_malloc_zone_t zone,
- xzm_segment_group_id_t sgid, xzm_front_index_t front, bool huge)
-{
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
-#if CONFIG_XZM_CLUSTER_AWARE
- const uint32_t clusterid = _malloc_cpu_cluster_number();
- xzm_debug_assert(clusterid < ncpuclusters);
-#else
- const uint32_t clusterid = _malloc_cpu_number() % 2;
- xzm_debug_assert(clusterid < logical_ncpus);
-#endif // CONFIG_XZM_CLUSTER_AWARE
-
- xzm_debug_assert(sgid < main->xzmz_segment_group_ids_count);
- if (sgid < XZM_SEGMENT_GROUP_POINTER_XZONES) {
- xzm_debug_assert(front == XZM_FRONT_INDEX_DEFAULT);
- } else {
- xzm_debug_assert(front < main->xzmz_allocation_front_count);
- }
-
- uint8_t sg_front_index = sgid + front;
-
- if (main->xzmz_segment_group_front_count < main->xzmz_segment_group_count) {
- uint8_t sg_index;
- bool use_data_large = false;
-#if CONFIG_XZM_CLUSTER_AWARE
- // Route all huge allocations to the same global segment group that
- // has the huge cache enabled
- xzm_debug_assert(main->xzmz_defer_large);
- use_data_large = huge;
-#endif // CONFIG_XZM_CLUSTER_AWARE
- if (use_data_large) {
- sg_index = XZM_SEGMENT_GROUP_DATA_LARGE;
- } else {
- sg_index = main->xzmz_segment_group_front_count * clusterid +
- sg_front_index;
- }
- xzm_debug_assert(sg_index < main->xzmz_segment_group_count);
- return &main->xzmz_segment_groups[sg_index];
- } else {
- return &main->xzmz_segment_groups[sg_front_index];
- }
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_fork_lock_wait(xzm_malloc_zone_t zone)
-{
- // This lock is taken first during fork, so that anything that needs to be
- // locked during fork that otherwise doesn't have one can be sent here.
- _malloc_lock_lock(&zone->xzz_fork_lock);
- _malloc_lock_unlock(&zone->xzz_fork_lock);
-}
-
-MALLOC_NOINLINE MALLOC_PRESERVE_MOST
-static void
-_xzm_walk_lock_wait(xzm_malloc_zone_t zone)
-{
- // We take this lock prior to walking any chunk freelist. A chunk that is
- // marked as being walked will send any allocating and deallocating threads
- // here.
- _malloc_lock_lock(&zone->xzz_lock);
- _malloc_lock_unlock(&zone->xzz_lock);
-}
-
-#pragma mark Large allocation and deallocation
-
-// mimalloc: mi_large_huge_page_alloc
-static void * __alloc_size(2)
-_xzm_malloc_large_huge(xzm_malloc_zone_t zone, size_t size, size_t alignment,
- malloc_type_descriptor_t type_desc, xzm_malloc_options_t opt)
-{
- bool clear = (opt & XZM_MALLOC_CLEAR);
- void *ptr = NULL;
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
-
- // Consider: _mi_os_good_alloc_size()?
- size_t rounded_size = roundup(size, XZM_SEGMENT_SLICE_SIZE);
- xzm_slice_kind_t kind;
- if (rounded_size > XZM_LARGE_BLOCK_SIZE_MAX ||
- alignment > XZM_ALIGNMENT_MAX) {
- kind = XZM_SLICE_KIND_HUGE_CHUNK;
- } else {
- kind = XZM_SLICE_KIND_LARGE_CHUNK;
- }
- xzm_slice_count_t slice_count;
- if (os_convert_overflow(rounded_size / XZM_SEGMENT_SLICE_SIZE,
- &slice_count)) {
- goto out;
- }
-
- bool use_data_for_large = true;
-#if XZM_NARROW_BUCKETING
- if (main->xzmz_narrow_bucketing && !main->xzmz_use_ranges) {
- use_data_for_large = false;
- }
-#endif
-
- xzm_segment_group_id_t sg_id;
- if ((use_data_for_large && malloc_type_descriptor_is_pure_data(type_desc)) ||
- kind == XZM_SLICE_KIND_HUGE_CHUNK ||
- main->xzmz_segment_group_ids_count == XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY) {
- // Use a separate segment group for large data only when using deferred
- // reclamation
- bool use_data_large = main->xzmz_defer_large;
-
-#if CONFIG_MTE
- struct xzm_memtag_config_s *memtag_config = &zone->xzz_memtag_config;
- if (memtag_config->tag_data &&
- memtag_config->max_block_size <= XZM_SMALL_BLOCK_SIZE_MAX) {
- use_data_large = true;
- }
-#endif
-
- sg_id = use_data_large ? XZM_SEGMENT_GROUP_DATA_LARGE :
- XZM_SEGMENT_GROUP_DATA;
- } else {
- sg_id = XZM_SEGMENT_GROUP_POINTER_LARGE;
- }
- xzm_debug_assert(sg_id < main->xzmz_segment_group_ids_count);
- xzm_segment_group_t sg = _xzm_segment_group_for_id_and_front(zone, sg_id,
- XZM_FRONT_INDEX_DEFAULT, kind == XZM_SLICE_KIND_HUGE_CHUNK);
-
- bool purgeable = (zone->xzz_flags & MALLOC_PURGEABLE);
- xzm_chunk_t chunk = xzm_segment_group_alloc_chunk(sg, kind, NULL,
- slice_count, NULL, alignment, clear, purgeable);
- if (os_unlikely(!chunk)) {
- goto out;
- }
-
- xzm_debug_assert(!clear || chunk->xzc_bits.xzcb_is_pristine);
-
- // Set the mzone_idx last to publish this chunk for the enumerator protocol
- chunk->xzc_mzone_idx = zone->xzz_mzone_idx;
-
- _malloc_lock_lock(&zone->xzz_lock);
- LIST_INSERT_HEAD(&zone->xzz_chunkq_large, chunk, xzc_entry);
- _malloc_lock_unlock(&zone->xzz_lock);
-
- size_t chunk_size = 0;
- ptr = (uint8_t *)_xzm_chunk_start(zone, chunk, &chunk_size);
-
-#if CONFIG_MTE
- bool memtag_enabled = _xzm_segment_group_memtag_block(sg, chunk_size);
- bool canonical_tag = (opt & XZM_MALLOC_CANONICAL_TAG);
- if (memtag_enabled) {
- if (canonical_tag) {
- ptr = memtag_tag_canonical(ptr, chunk_size);
- } else {
- ptr = memtag_retag(ptr, chunk_size);
- }
- }
-#endif
-
-
-out:
- if (os_unlikely(!ptr)) {
- malloc_set_errno_fast(MZ_POSIX, ENOMEM);
- }
- return ptr;
-}
-
-// mimalloc: _mi_segment_huge_page_free
-MALLOC_NOINLINE
-static void
-_xzm_free_large_huge(xzm_malloc_zone_t zone, xzm_chunk_t chunk)
-{
- _malloc_lock_lock(&zone->xzz_lock);
-
- // Unset mzone_idx first to unpublish it for the enumerator protocol
- chunk->xzc_mzone_idx = XZM_MZONE_INDEX_INVALID;
- LIST_REMOVE(chunk, xzc_entry);
-
- _malloc_lock_unlock(&zone->xzz_lock);
-
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone, chunk);
- xzm_segment_group_free_chunk(sg, chunk, zone->xzz_flags & MALLOC_PURGEABLE,
- /* small_madvise_needed */ false);
-}
-
-#if CONFIG_MTE
-
-#pragma mark MTE
-
-// FIXME: `zone` parameter of the following functions is unused.
-
-// Initialize the tags for the given chunk.
-static void *
-_xzm_xzone_chunk_memtag_init(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk)
-{
- size_t chunk_size = 0;
- uint8_t *chunk_start = _xzm_chunk_start_ptr(zone, chunk, &chunk_size);
- return memtag_init_chunk(chunk_start, chunk_size, xz->xz_block_size);
-}
-
-// Retag a single block.
-static xzm_block_t
-_xzm_xzone_block_memtag_retag(xzm_malloc_zone_t zone, xzm_block_t block,
- size_t block_size)
-{
- return (xzm_block_t)memtag_retag((uint8_t *)block, block_size);
-}
-#endif // CONFIG_MTE
-
-static inline xzm_malloc_options_t
-_xzm_xzone_get_malloc_thread_options(void)
-{
-#if CONFIG_MTE
- // rdar://140822174
- // Check the flag in the TSD to see if canonical tagging was requested.
- malloc_thread_options_t options = malloc_get_thread_options();
- if (options.ReservedFlag) {
- return XZM_MALLOC_CANONICAL_TAG;
- }
-#endif // CONFIG_MTE
-
- return 0;
-}
-
-#pragma mark chunk list
-
-static void
-_xzm_chunk_list_fork_lock(xzm_chunk_list_head_t list)
-{
- xzm_chunk_list_head_u locked = {
- .xzch_fork_locked = true,
- };
-
- __assert_only xzm_chunk_list_head_u old_head = {
- .xzch_value = os_atomic_or_orig(&list->xzch_value, locked.xzch_value,
- relaxed),
- };
- xzm_debug_assert(!old_head.xzch_fork_locked);
-}
-
-static void
-_xzm_chunk_list_fork_unlock(xzm_chunk_list_head_t list)
-{
- xzm_chunk_list_head_u locked = {
- .xzch_fork_locked = true,
- };
- uint64_t unlocked = ~locked.xzch_value;
-
- __assert_only xzm_chunk_list_head_u old_head = {
- .xzch_value = os_atomic_and_orig(&list->xzch_value, unlocked, relaxed),
- };
- xzm_debug_assert(old_head.xzch_fork_locked);
-}
-
-static xzm_chunk_t
-_xzm_chunk_list_pop(xzm_malloc_zone_t zone, xzm_chunk_list_head_t list,
- xzm_chunk_linkage_t linkage, bool *contended_out)
-{
- xzm_chunk_t chunk = NULL;
- const bool is_batch = (linkage == XZM_CHUNK_LINKAGE_BATCH);
- xzm_chunk_list_head_u head = {
- .xzch_value = os_atomic_load(&list->xzch_value, dependency),
- };
-
- while (true) {
- if (os_unlikely(is_batch ?
- head.xzch_batch_fork_locked : head.xzch_fork_locked)) {
- _xzm_fork_lock_wait(zone);
- head.xzch_value = os_atomic_load(&list->xzch_value, dependency);
- continue;
- }
-
- chunk = (xzm_chunk_t)head.xzch_ptr;
- if (!chunk) {
- break;
- }
-
- xzm_chunk_list_head_u new_head;
- xzm_chunk_t next;
- if (!is_batch) {
- next = chunk->xzc_linkages[linkage];
- new_head = (xzm_chunk_list_head_u){
- .xzch_ptr = (uint64_t)next,
- .xzch_gen = head.xzch_gen + 1,
- };
- } else {
- xzm_debug_assert(head.xzch_batch_count);
- next = *_xzm_segment_slice_meta_batch_next(zone, chunk);
- new_head = (xzm_chunk_list_head_u){
- .xzch_batch_ptr = (uint64_t)next,
- .xzch_batch_gen = head.xzch_batch_gen + 1,
- .xzch_batch_count = head.xzch_batch_count - 1,
- };
- xzm_debug_assert(new_head.xzch_batch_count < head.xzch_batch_count);
- }
- xzm_debug_assert(chunk != next);
- bool success = os_atomic_cmpxchgv(&list->xzch_value, head.xzch_value,
- new_head.xzch_value, &head.xzch_value, dependency);
- if (success) {
- if (is_batch) {
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone,
- (xzm_chunk_t)new_head.xzch_batch_ptr));
-#if CONFIG_XZM_DEFERRED_RECLAIM
- *_xzm_slice_meta_reclaim_id(zone, chunk) = VM_RECLAIM_ID_NULL;
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- }
- break;
- } else if (contended_out) {
- *contended_out = true;
- }
- }
-
- return chunk;
-}
-
-static void
-_xzm_chunk_list_push(xzm_malloc_zone_t zone, xzm_chunk_list_head_t list,
- xzm_chunk_t chunk, xzm_chunk_linkage_t linkage, bool *contended_out)
-{
- xzm_debug_assert(linkage != XZM_CHUNK_LINKAGE_BATCH);
-
- xzm_chunk_list_head_u head = {
- .xzch_value = os_atomic_load(&list->xzch_value, dependency),
- };
-
- while (true) {
- if (os_unlikely(head.xzch_fork_locked)) {
- _xzm_fork_lock_wait(zone);
- head.xzch_value = os_atomic_load(&list->xzch_value, dependency);
- continue;
- }
-
- xzm_chunk_t head_chunk = (xzm_chunk_t)head.xzch_ptr;
- xzm_debug_assert(head_chunk != chunk);
- xzm_chunk_list_head_u new_head = {
- .xzch_ptr = (uint64_t)chunk,
- .xzch_gen = head.xzch_gen + 1,
- };
- chunk->xzc_linkages[linkage] = head_chunk;
-
- bool success = os_atomic_cmpxchgv(&list->xzch_value, head.xzch_value,
- new_head.xzch_value, &head.xzch_value, release);
- if (success) {
- break;
- } else if (contended_out) {
- *contended_out = true;
- }
- }
-}
-
-static xzm_chunk_list_t
-_xzm_chunk_list_get(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_slot_config_t *slot_config, xzm_chunk_list_t lists)
-{
- xzm_allocation_index_t alloc_idx = _xzm_get_allocation_index(zone, xz,
- slot_config, false);
- return _xzm_xzone_chunk_list_for_index(zone, xz, lists, alloc_idx);
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_slot_record_contention(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_xzone_slot_counters_u *xsc, xzm_slot_config_t slot_config,
- bool is_xas, bool contended);
-
-static xzm_chunk_t
-_xzm_chunk_list_slot_pop(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_list_t lists)
-{
- xzm_slot_config_t slot_config;
- xzm_chunk_list_t xcl = _xzm_chunk_list_get(zone, xz, &slot_config, lists);
-
- bool contended = false;
- xzm_chunk_t chunk = _xzm_chunk_list_pop(zone, &xcl->xcl_list,
- XZM_CHUNK_LINKAGE_MAIN, &contended);
- if (chunk) {
- _xzm_xzone_slot_record_contention(zone, xz, &xcl->xcl_counters,
- slot_config, false, contended);
- }
-
- return chunk;
-}
-
-static void
-_xzm_chunk_list_slot_push(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_list_t lists, xzm_chunk_t chunk)
-{
- xzm_slot_config_t slot_config;
- xzm_chunk_list_t xcl = _xzm_chunk_list_get(zone, xz, &slot_config, lists);
- bool contended = false;
- _xzm_chunk_list_push(zone, &xcl->xcl_list, chunk, XZM_CHUNK_LINKAGE_MAIN,
- &contended);
- _xzm_xzone_slot_record_contention(zone, xz, &xcl->xcl_counters, slot_config,
- false, contended);
-}
-
-static void
-_xzm_chunk_batch_list_fork_lock(xzm_chunk_list_head_t list)
-{
- xzm_chunk_list_head_u locked = {
- .xzch_batch_fork_locked = true,
- };
-
- __assert_only xzm_chunk_list_head_u old_head = {
- .xzch_value = os_atomic_or_orig(&list->xzch_value, locked.xzch_value,
- relaxed),
- };
- xzm_debug_assert(!old_head.xzch_batch_fork_locked);
-}
-
-static void
-_xzm_chunk_batch_list_fork_unlock(xzm_chunk_list_head_t list)
-{
- xzm_chunk_list_head_u locked = {
- .xzch_batch_fork_locked = true,
- };
- uint64_t unlocked = ~locked.xzch_value;
-
- __assert_only xzm_chunk_list_head_u old_head = {
- .xzch_value = os_atomic_and_orig(&list->xzch_value, unlocked, relaxed),
- };
- xzm_debug_assert(old_head.xzch_batch_fork_locked);
-}
-
-static void
-_xzm_xzone_madvise_batch(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk);
-
-static void
-_xzm_chunk_batch_list_push(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, size_t max)
-{
- xzm_chunk_list_head_t list = &xz->xz_batch_list;
- xzm_chunk_list_head_u head = {
- .xzch_value = os_atomic_load(&list->xzch_value, dependency),
- };
-
- bool was_full;
- while (true) {
- if (os_unlikely(head.xzch_batch_fork_locked)) {
- _xzm_fork_lock_wait(zone);
- head.xzch_value = os_atomic_load(&list->xzch_value, dependency);
- continue;
- }
-
- xzm_debug_assert(head.xzch_batch_count <= max);
- was_full = (head.xzch_batch_count >= max);
-
- xzm_chunk_list_head_u new_head = {
- .xzch_batch_ptr = (uint64_t)chunk,
- .xzch_batch_gen = head.xzch_batch_gen + 1,
- .xzch_batch_count = os_likely(!was_full) ?
- head.xzch_batch_count + 1 : 1,
- };
- xzm_debug_assert(was_full ||
- new_head.xzch_batch_count > head.xzch_batch_count);
- xzm_chunk_t next = os_likely(!was_full) ?
- (xzm_chunk_t)head.xzch_ptr : NULL;
- xzm_debug_assert(chunk != next);
- *_xzm_segment_slice_meta_batch_next(zone, chunk) = next;
-
- bool success = os_atomic_cmpxchgv(&list->xzch_value, head.xzch_value,
- new_head.xzch_value, &head.xzch_value, release);
- if (success) {
- break;
- }
- }
-
- // Perform the batch madvise on the old head
- if (os_unlikely(was_full)) {
- _xzm_xzone_madvise_batch(zone, xz, (xzm_chunk_t)head.xzch_batch_ptr);
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static bool
-_xzm_chunk_list_empty(xzm_chunk_list_head_t list)
-{
- xzm_chunk_list_head_u head = {
- .xzch_value = os_atomic_load(&list->xzch_value, relaxed),
- };
-
- return !head.xzch_ptr;
-}
-
-#pragma mark Tiny allocation
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void *
-_xzm_xzone_malloc_from_freelist_chunk_inline(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_allocation_index_t alloc_idx,
- xzm_xzone_thread_cache_t cache, xzm_chunk_t chunk, bool small,
- bool walk_wait, bool *corrupt_out, bool *contended_out,
- bool *install_empty_out)
-{
-#if CONFIG_MTE
- bool memtag_enabled = chunk->xzc_tagged;
-#endif
-
- bool install = !!install_empty_out;
- size_t granule = small ? XZM_SMALL_GRANULE : XZM_GRANULE;
-
- uint8_t *start = (uint8_t *)_xzm_chunk_start(zone, chunk, NULL);
- if (small) {
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind ==
- XZM_SLICE_KIND_SMALL_FREELIST_CHUNK);
- } else {
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK);
- }
-
- // If we're trying to cache this chunk, it's important for the enumerator
- // that we initialize the cache to point to it before we mark it as
- // installed to a thread
- if (cache) {
- if (install) {
- cache->xztc_chunk = chunk;
- cache->xztc_chunk_start = start;
- } else {
- xzm_debug_assert(cache->xztc_chunk == chunk);
- xzm_debug_assert(cache->xztc_chunk_start == start);
- }
- }
-
- void *ptr = NULL;
- bool from_free_list = false;
- struct xzm_block_inline_meta_s block_meta;
-
- uint32_t contentions = 0;
-
- // Dependency order to observe initialization and zeroing of the block we
- // allocated
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency),
- };
- while (true) {
- if (os_unlikely(old_meta.xca_walk_locked)) {
- if (walk_wait) {
- _xzm_walk_lock_wait(zone);
- old_meta.xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency);
- continue;
- } else {
- return NULL;
- }
- }
-
- xzm_chunk_atomic_meta_u new_meta = old_meta;
-
- if (install) {
- // We're considering this chunk as a candidate to install. The
- // chunk we're looking at should not be installed in any slot, and
- // should be marked as being on the partial list (though in fact
- // we'll have just popped it).
- xzm_debug_assert(old_meta.xca_alloc_idx == XZM_SLOT_INDEX_EMPTY);
- xzm_debug_assert(old_meta.xca_on_partial_list);
- xzm_debug_assert(!old_meta.xca_on_empty_list);
-
- new_meta.xca_on_partial_list = false;
-
- // Does the chunk have any blocks?
- if (old_meta.xca_free_count == 0) {
- // No, no blocks to allocate.
- if (old_meta.xca_alloc_head == XZM_FREE_MADVISED) {
- // If the chunk is fully madvised and it was on the partial
- // list, it's the caller's responsibility to move it to the
- // empty list.
- new_meta.xca_on_empty_list = true;
- *install_empty_out = true;
- } else {
- // It shouldn't be possible for us to have gotten a full
- // chunk from the partial list, so the only remaining
- // possibility is an in-progress madvise.
- xzm_debug_assert(
- old_meta.xca_alloc_head == XZM_FREE_MADVISING);
- }
-
- // We may have initialized our thread cache entry to this chunk
- // on a previous iteration of the loop. It's important that we
- // invalidate it now, before it can potentially be reused by
- // another thread cache, so that there's no ambiguity to the
- // enumerator about which cache owns the chunk.
- if (cache) {
- cache->xztc_state = XZM_XZONE_CACHE_EMPTY;
- }
-
- bool success = os_atomic_cmpxchgv(
- &chunk->xzc_atomic_meta.xca_value, old_meta.xca_value,
- new_meta.xca_value, &old_meta.xca_value, dependency);
- if (!success) {
- // The only way this can happen is MADVISING -> MADVISED
- xzm_debug_assert(!(*install_empty_out));
- continue;
- }
-
- xzm_trace(malloc_install_skip, (uint64_t)chunk,
- old_meta.xca_value_lo, new_meta.xca_value_lo,
- cache ? cache->xztc_freelist_state : 0);
- return NULL;
- } else {
- // Yes, we want to proceed with the allocation attempt and mark
- // the chunk as installed to our slot if successful.
- new_meta.xca_alloc_idx = alloc_idx + 1;
-
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- // In this configuration, we actually can't proceed with the
- // attempt until after we've marked the chunk as installed to
- // our slot, as that's what allows a thread depopulating the
- // chunk to be able to wait until we've exited the critical
- // section. So, we're going to mark the chunk as installed
- // directly, and then transition to the non-install logic to try
- // to actually allocate a block.
- bool success = os_atomic_cmpxchgv(
- &chunk->xzc_atomic_meta.xca_value, old_meta.xca_value,
- new_meta.xca_value, &old_meta.xca_value, dependency);
- if (!success) {
- continue;
- }
-
- // Update the chunk state for the next loop iteration
- old_meta = new_meta;
-
- install = false;
- continue;
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- }
- } else {
- // We found this chunk as the one currently installed in our slot,
- // and only want to allocate from it if it still is.
- if (old_meta.xca_alloc_idx != alloc_idx + 1) {
- // Shouldn't be possible when the chunk is installed to the
- // thread cache
- xzm_debug_assert(!cache);
-
- // No. Go back to try to get a different one.
- return NULL;
- }
-
- // Does the chunk have any blocks?
- if (old_meta.xca_free_count == 0) {
- // We should try to uninstall this chunk.
- new_meta.xca_alloc_idx = XZM_SLOT_INDEX_EMPTY;
-
- // It's marked as installed, so it should be full.
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_NULL);
-
- // It shouldn't be on any chunk lists either, and doesn't need
- // to be. If it eventually becomes unfull, it's the
- // responsibility of that thread to move it back to the partial
- // list.
- xzm_debug_assert(!old_meta.xca_on_partial_list);
- xzm_debug_assert(!old_meta.xca_on_empty_list);
-
- if (cache) {
- // We need to mark our thread cache entry as invalid before
- // we mark the chunk as uninstalled, so that there's no
- // ambiguity about which cache owns the chunk if we get
- // pre-empted and that chunk gets installed to another cache
- cache->xztc_state = XZM_XZONE_CACHE_EMPTY;
- }
-
- bool success = os_atomic_cmpxchgv(
- &chunk->xzc_atomic_meta.xca_value, old_meta.xca_value,
- new_meta.xca_value, &old_meta.xca_value, dependency);
- if (!success) {
- continue;
- }
-
- xzm_trace(malloc_full_skip, (uint64_t)chunk,
- old_meta.xca_value_lo, new_meta.xca_value_lo,
- cache ? cache->xztc_freelist_state : 0);
- return NULL;
- }
- }
-
- // It looks like we should be able to allocate.
- from_free_list = false;
-
- if (cache) {
- // Allocating all blocks for this cache
-
- // We initialize the cache entry to the freelist _before_ we mark
- // the chunk as installed so that the enumerator can reliably walk
- // the local freelist in the cache if it sees the chunk marked as
- // installed.
-
- new_meta.xca_alloc_head = XZM_FREE_NULL;
- new_meta.xca_free_count = 0;
-
- cache->xztc_free_count = old_meta.xca_free_count - 1;
-
- if (old_meta.xca_alloc_head < XZM_FREE_LIMIT) {
- // Pop from the alloc head
- ptr = start + (old_meta.xca_alloc_head * granule);
-#if CONFIG_MTE
- if (memtag_enabled) {
- memtag_disable_checking();
- block_meta = *(struct xzm_block_inline_meta_s *)ptr;
- memtag_enable_checking();
- } else {
-#endif
- block_meta = *(struct xzm_block_inline_meta_s *)ptr;
-#if CONFIG_MTE
- }
-#endif
-
- cache->xztc_head = block_meta.xzb_linkage.xzbl_next_offset;
- cache->xztc_head_seqno = block_meta.xzb_linkage.xzbl_next_seqno;
-
- from_free_list = true;
- } else {
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_NULL);
- xzm_debug_assert(old_meta.xca_free_count);
-
- // Note: it's important that we compute ptr in the loop, before
- // we pop, for the benefit of the memory tools: if we don't,
- // then the block will appear allocated but the tools won't be
- // able to find any references to it. Getting it into a
- // register first will ensure that there's always a reference.
- size_t capacity = xz->xz_chunk_capacity;
- size_t n_allocated = capacity - old_meta.xca_free_count;
- ptr = start + (n_allocated * xz->xz_block_size);
-
- cache->xztc_head = XZM_FREE_NULL;
- }
- } else {
- // Allocating one block
- new_meta.xca_free_count--;
- if (old_meta.xca_alloc_head < XZM_FREE_LIMIT) {
- // Pop from the alloc head
- ptr = start + (old_meta.xca_alloc_head * granule);
-
-#if CONFIG_MTE
- if (memtag_enabled) {
- memtag_disable_checking();
- block_meta = *(struct xzm_block_inline_meta_s *)ptr;
- memtag_enable_checking();
- } else {
-#endif
- block_meta = *(struct xzm_block_inline_meta_s *)ptr;
-#if CONFIG_MTE
- }
-#endif
-
- new_meta.xca_alloc_head = block_meta.xzb_linkage.xzbl_next_offset;
- new_meta.xca_head_seqno = block_meta.xzb_linkage.xzbl_next_seqno;
- from_free_list = true;
- } else {
- xzm_debug_assert(old_meta.xca_free_count);
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_NULL);
-
- // Note: it's important that we compute ptr in the loop, before we
- // pop, for the benefit of the memory tools: if we don't, then the
- // block will appear allocated but the tools won't be able to find
- // any references to it. Getting it into a register first will
- // ensure that there's always a reference.
- size_t capacity = xz->xz_chunk_capacity;
- ptr = start + ((capacity - old_meta.xca_free_count) *
- xz->xz_block_size);
- }
- }
-
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- dependency);
- if (!success) {
- // XXX: possible madvise race - record? Retry?
-
- // Record the contention
- *contended_out = true;
-
- contentions++;
-
- // Try again.
- continue;
- }
-
- // We got it.
- xzm_trace(malloc_success, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo,
- cache ? cache->xztc_freelist_state : 0);
- break;
- }
-
-#if CONFIG_MTE
- if (memtag_enabled) {
- if (small) {
- // Small blocks are tagged only on alloc
- ptr = memtag_retag(ptr, xz->xz_block_size);
- } else {
- if (from_free_list) {
- uint8_t tag = memtag_extract_tag((uint8_t *)block_meta.xzb_cookie);
- xzm_debug_assert(tag != 0);
- ptr = (void *)memtag_mix_tag(ptr, tag);
- } else {
- ptr = memtag_fixup_ptr(ptr);
- }
- }
- }
-#endif
-
- if (from_free_list) {
- uint64_t expected_cookie = (uint64_t)ptr ^ zone->xzz_freelist_cookie;
-#if CONFIG_MTE
- if (small) {
- // Small chunks do not encode the tag in the freelist cookie
- expected_cookie = (uint64_t)memtag_strip_address(
- (uint8_t *)expected_cookie);
- }
-#endif
- if (expected_cookie != block_meta.xzb_cookie) {
- *corrupt_out = true;
- goto freelist_done;
- }
-
- // Check the PAC by adding the seqno back
- union xzm_block_linkage_u linkage = {
- .xzbl_next_offset = block_meta.xzb_linkage.xzbl_next_offset,
- .xzbl_next_seqno = block_meta.xzb_linkage.xzbl_next_seqno,
- .xzbl_seqno = old_meta.xca_head_seqno,
- };
-
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(ptr,
- ptrauth_string_discriminator("xzb_linkage")));
-
- // Take the seqno back out of the signed value for the comparison with
- // the stored one
- linkage.xzbl_seqno = 0;
-
- if (block_meta.xzb_linkage.xzbl_next_value != linkage.xzbl_next_value) {
- *corrupt_out = true;
- goto freelist_done;
- }
-
-freelist_done:
- ;
- }
-
- return ptr;
-}
-
-static void *
-_xzm_xzone_malloc_from_freelist_chunk(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_allocation_index_t alloc_idx, xzm_xzone_thread_cache_t cache,
- xzm_chunk_t chunk, bool small, bool *contended_out,
- bool *install_empty_out)
-{
- const bool walk_wait = true;
- bool corrupt = false;
- void *ptr = _xzm_xzone_malloc_from_freelist_chunk_inline(zone, xz,
- alloc_idx, cache, chunk, small, walk_wait, &corrupt, contended_out,
- install_empty_out);
- if (corrupt) {
- _xzm_corruption_detected(ptr);
- }
-
- return ptr;
-}
-
-static void *
-_xzm_xzone_malloc_from_empty_freelist_chunk(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_allocation_index_t alloc_idx,
- xzm_xzone_thread_cache_t cache, xzm_chunk_t chunk,
- bool is_fresh)
-{
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK);
-#if CONFIG_MTE
- bool small = chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK;
-#endif
-
- // acquire to make sure the reclaim index is visible
- // TODO This load can be made relaxed - we already performed the necessary
- // acquire prior to this point when we marked the chunk as used, if we
- // needed to do that (i.e. if deferred reclaim is enabled and this chunk was
- // on the empty list => MADVISED)
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, acquire),
- };
-
- // We found this on the batch list or the empty list, so it is either
- // enqueued for madvising or has been madvised
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_MADVISED ||
- old_meta.xca_alloc_head == XZM_FREE_MADVISING);
-
- // A chunk is unusable if it is both on the partial list and the batch list;
- // e.g., if its insertion raced with our emptying of the partial list
- if (os_unlikely(old_meta.xca_on_partial_list)) {
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_MADVISING);
- return NULL;
- }
-
-
- // We found this chunk on the batch or isolation list. It is not installed
- // anywhere.
- xzm_debug_assert(!old_meta.xca_on_partial_list);
- xzm_debug_assert(old_meta.xca_alloc_idx == XZM_SLOT_INDEX_EMPTY);
-
- uint8_t *start = (uint8_t *)_xzm_chunk_start(zone, chunk, NULL);
- void *ptr = (void *)start;
-#if CONFIG_MTE
- const bool memtag_enabled = xz->xz_tagged;
- if (memtag_enabled) {
- if (small) {
- // For small, retag blocks lazily instead of for the entire chunk
- // up front
- ptr = memtag_retag(start, xz->xz_block_size);
- } else {
- if (is_fresh) {
- ptr = _xzm_xzone_chunk_memtag_init(zone, xz, chunk);
- } else {
- ptr = memtag_fixup_ptr(ptr);
- }
- }
- }
-#else
- (void)is_fresh;
-#endif
-
- // Preserve the seqno across reuses - this is important for anti-replay
- xzm_chunk_atomic_meta_u new_meta = old_meta;
-
- // We've taken it off the isolation list and won't be putting it back.
- new_meta.xca_on_empty_list = false;
-
- // In the absence of deferred reclaim, we're guaranteed to be able to bring
- // this chunk back into use.
- new_meta.xca_alloc_idx = alloc_idx + 1;
- if (cache) {
- new_meta.xca_alloc_head = XZM_FREE_NULL;
- new_meta.xca_free_count = 0;
-
- cache->xztc_chunk = chunk;
- cache->xztc_chunk_start = start;
- cache->xztc_head = XZM_FREE_NULL;
- cache->xztc_free_count = xz->xz_chunk_capacity - 1;
- // preserve xztc_seqno
- } else {
- new_meta.xca_alloc_head = XZM_FREE_NULL;
- new_meta.xca_free_count = xz->xz_chunk_capacity - 1;
- }
-
- // This needs an acquire barrier because the pointer we're returning is not
- // derived from the metadata, so the dependency ordering we usually rely on
- // doesn't work.
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- acquire);
- xzm_assert(success);
-
- xzm_trace(malloc_from_empty, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo, old_meta.xca_seqno);
-
- return ptr;
-}
-
-// Called whenever a tiny or small chunk is received from the span queues. This
-// function needs to perform all the initialization needed to allocate a block
-// from this chunk, or to free the chunk during zone destruction.
-static void
-_xzm_xzone_fresh_chunk_init(xzm_xzone_t xz, xzm_chunk_t chunk,
- xzm_slice_kind_t kind)
-{
- xzm_debug_assert(chunk->xzc_xzone_idx == xz->xz_idx);
-
- chunk->xzc_bits.xzcb_preallocated = false;
-
- switch (kind) {
- case XZM_SLICE_KIND_SMALL_CHUNK:
- _xzm_chunk_reset_free(xz, chunk, true);
- break;
- case XZM_SLICE_KIND_TINY_CHUNK:
- case XZM_SLICE_KIND_SMALL_FREELIST_CHUNK:
- xzm_debug_assert(xz->xz_block_size <= UINT16_MAX);
- chunk->xzc_freelist_block_size = (uint16_t)xz->xz_block_size;
-
- xzm_debug_assert(xz->xz_chunk_capacity <= UINT16_MAX);
- chunk->xzc_freelist_chunk_capacity = (uint16_t)xz->xz_chunk_capacity;
-#if CONFIG_MTE
- chunk->xzc_tagged = xz->xz_tagged;
-#endif
- break;
- default:
- xzm_abort("Unexpected chunk kind");
- break;
- }
-}
-
-static void *
-_xzm_xzone_malloc_from_fresh_freelist_chunk(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_allocation_index_t alloc_idx,
- xzm_xzone_thread_cache_t cache, xzm_chunk_t chunk, bool small)
-{
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK);
-
- _xzm_xzone_fresh_chunk_init(xz, chunk, chunk->xzc_bits.xzcb_kind);
-
- xzm_chunk_atomic_meta_u new_meta;
-
- uint8_t *start = (uint8_t *)_xzm_chunk_start(zone, chunk, NULL);
-
- if (cache) {
- new_meta = (xzm_chunk_atomic_meta_u){
- .xca_alloc_head = XZM_FREE_NULL,
- .xca_free_count = 0,
- .xca_alloc_idx = alloc_idx + 1,
- };
-
- cache->xztc_chunk = chunk;
- cache->xztc_chunk_start = start;
- cache->xztc_head = XZM_FREE_NULL;
- cache->xztc_free_count = xz->xz_chunk_capacity - 1;
- // preserve xztc_seqno
- } else {
- new_meta = (xzm_chunk_atomic_meta_u){
- .xca_alloc_head = XZM_FREE_NULL,
- .xca_free_count = xz->xz_chunk_capacity - 1,
- .xca_alloc_idx = alloc_idx + 1,
- };
- }
-
- xzm_trace(malloc_from_fresh, (uint64_t)chunk, xz->xz_idx,
- new_meta.xca_value_lo, 0);
- os_atomic_store_wide(&chunk->xzc_atomic_meta.xca_value, new_meta.xca_value,
- relaxed);
-
- // Enumerator protocol: mzone_idx is initialized last to "publish" the
- // chunk. Because there are no references to this chunk yet, this store is
- // guaranteed by dependency ordering to be visible to all future threads
- // that observe it when the chunk is store-released to its initial slot.
- chunk->xzc_mzone_idx = xz->xz_mzone_idx;
-
-#if CONFIG_MTE
- if (chunk->xzc_tagged) {
- void *ptr;
- if (!small) {
- ptr = _xzm_xzone_chunk_memtag_init(zone, xz, chunk);
- } else {
- // For small, retag blocks lazily instead of for the entire chunk
- // up front
- ptr = (void *)_xzm_xzone_block_memtag_retag(zone, (xzm_block_t)start,
- xz->xz_block_size);
- }
- return ptr;
- }
-#endif
- return (void *)start;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_slot_config_t
-_xzm_next_slot_config(xzm_slot_config_t slot_config)
-{
- switch (slot_config) {
- case XZM_SLOT_SINGLE:
-#if CONFIG_XZM_CLUSTER_AWARE
- if (ncpuclusters > 1) {
- return XZM_SLOT_CLUSTER;
- } else {
- return XZM_SLOT_CPU;
- }
-#else // CONFIG_XZM_CLUSTER_AWARE
- return XZM_SLOT_CLUSTER;
-#endif // CONFIG_XZM_CLUSTER_AWARE
- case XZM_SLOT_CLUSTER:
- return XZM_SLOT_CPU;
- case XZM_SLOT_CPU:
- xzm_debug_abort("Can't upgrade from XZM_SLOT_CPU");
- return XZM_SLOT_CPU;
- default:
- xzm_debug_abort("Invalid xzone slot config");
- return XZM_SLOT_CPU;
- }
-}
-
-#if CONFIG_XZM_THREAD_CACHE
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_thread_cache_record_contention(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_xzone_thread_cache_t cache);
-
-#endif // CONFIG_XZM_THREAD_CACHE
-
-static void
-_xzm_xzone_upgrade_freelist_slot_config(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_xzone_slot_counters_u *xsc, xzm_xzone_slot_counters_u counter,
- xzm_slot_config_t next_slot_config, bool is_xas)
-{
- if (is_xas) {
- xzm_trace(slot_upgrade, xz->xz_idx, (uint64_t)xsc,
- next_slot_config, counter.xsc_value);
- } else {
- xzm_trace(list_upgrade, xz->xz_idx, (uint64_t)xsc,
- next_slot_config, counter.xsc_value);
- }
-
- xzm_slot_config_t *slot_config_p = is_xas ? &xz->xz_slot_config :
- &xz->xz_list_config;
- os_atomic_store(slot_config_p, next_slot_config, relaxed);
-
- // Try to upgrade/reset the counters in all the slots,
- // regardless of whether or not we actually upgraded the xzone
- counter = (xzm_xzone_slot_counters_u){
- .xsc_slot_config = next_slot_config,
- };
- xzm_allocation_index_t limit_idx =
- _xzm_get_limit_allocation_index(next_slot_config);
- for (xzm_allocation_index_t alloc_idx = 0; alloc_idx < limit_idx;
- alloc_idx++) {
- xsc = is_xas ?
- &_xzm_xzone_allocation_slot_for_index(zone, xz,
- alloc_idx)->xas_counters :
- &_xzm_xzone_chunk_list_for_index(zone, xz,
- zone->xzz_partial_lists, alloc_idx)->xcl_counters;
- os_atomic_store(&xsc->xsc_value, counter.xsc_value, relaxed);
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_slot_record_contention(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_xzone_slot_counters_u *xsc, xzm_slot_config_t slot_config,
- bool is_xas, bool contended)
-{
-#if CONFIG_XZM_THREAD_CACHE
- // XXX Somewhat wasteful to re-check this condition here - consider plumbing
- // thread cache loaded earlier through to here
- if (is_xas &&
- zone->xzz_thread_cache_enabled &&
- xz->xz_block_size <= XZM_THREAD_CACHE_THRESHOLD) {
- if (contended) {
- xzm_thread_cache_t tc = _xzm_get_thread_cache();
- if (os_likely(tc)) {
- xzm_xzone_index_t xz_idx = xz->xz_idx;
- xzm_xzone_thread_cache_t cache = &tc->xtc_xz_caches[xz_idx];
- _xzm_xzone_thread_cache_record_contention(zone, xz, cache);
- }
- }
- return;
- }
-#endif // CONFIG_XZM_THREAD_CACHE
-
- xzm_xzone_slot_counters_u old_counters = {
- .xsc_value = os_atomic_load(&xsc->xsc_value,
- relaxed),
- };
-
- xzm_slot_config_t max_config = is_xas ? zone->xzz_max_slot_config :
- zone->xzz_max_list_config;
- if (old_counters.xsc_slot_config == max_config) {
- return;
- }
-
- // Only need to record if:
- // - We detected a contention, or
- // - Some contention has already been detected, so we need to record the
- // operation to assess the rate
- if (contended || old_counters.xsc_contentions) {
- uint64_t inc = 1; // inc xsc_ops
- if (contended) {
- inc |= (1ull << 32); // inc xsc_contentions
- }
-
- xzm_xzone_slot_counters_u new_counters = {
- .xsc_value = os_atomic_add(&xsc->xsc_value, inc,
- relaxed),
- };
-
- // The "current" slot config that we observe in the slot
- // after incrementing the counters may be different than the one we
- // observed originally on the xzone:
- // - It could have a lower slot config if it was just upgraded
- //
- // Regardless, we'll use the slot config that we saw in the counters as
- // the one that dictates what we should do next from here on.
- xzm_slot_config_t current_slot_config = new_counters.xsc_slot_config;
-
- if (current_slot_config == max_config) {
- return;
- }
-
- uint32_t period = is_xas ? zone->xzz_slot_upgrade_period :
- zone->xzz_list_upgrade_period;
- uint32_t *thresholds = is_xas ? zone->xzz_slot_upgrade_threshold :
- zone->xzz_list_upgrade_threshold;
- if (os_unlikely(new_counters.xsc_contentions >=
- thresholds[current_slot_config])) {
- if (new_counters.xsc_contentions >
- thresholds[current_slot_config]) {
- // Someone else is upgrading all the slots, do nothing
- return;
- }
-
- // If we've detected contention above the threshold, try to
- // upgrade the xzone
- xzm_slot_config_t next_slot_config = _xzm_next_slot_config(
- new_counters.xsc_slot_config);
- xzm_debug_assert(new_counters.xsc_slot_config < next_slot_config);
-
- _xzm_xzone_upgrade_freelist_slot_config(zone, xz, xsc,
- new_counters, next_slot_config, is_xas);
- } else if (os_unlikely(new_counters.xsc_ops >= period)) {
- if (new_counters.xsc_ops > period) {
- // Someone else is resetting the counter, do nothing
- return;
- }
- // If the detection period has elapsed without meeting the
- // threshold, reset the counters for the next period.
- xzm_xzone_slot_counters_u orig_counters = new_counters;
-
- new_counters = (xzm_xzone_slot_counters_u){
- .xsc_slot_config = current_slot_config,
- };
-
- os_atomic_rmw_loop(&xsc->xsc_value,
- old_counters.xsc_value, new_counters.xsc_value, relaxed, {
- if (old_counters.xsc_value < orig_counters.xsc_value ||
- old_counters.xsc_slot_config >
- orig_counters.xsc_slot_config) {
- os_atomic_rmw_loop_give_up(break);
- }
- });
- }
- }
-}
-
-#if !CONFIG_TINY_ALLOCATION_SLOT_LOCK
-// This inlines badly: the compiler unconditionally hoists the mrs out of the
-// loop, which is exactly what we don't want since we normally won't need it
-MALLOC_NOINLINE
-static uint32_t
-_malloc_ulock_self_owner_value(void)
-{
- mach_port_t self_port = _pthread_mach_thread_self_direct();
- return (uint32_t)self_port >> 2;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_allocation_slot_atomic_meta_u
-_xzm_allocation_slot_gate_wait(xzm_xzone_allocation_slot_t xas,
- xzm_allocation_slot_atomic_meta_u slot_meta)
-{
- xzm_allocation_slot_atomic_meta_u *xasa = &xas->xas_atomic;
-
- xzm_debug_assert(slot_meta.xasa_gate.xsg_locked);
-
- if (!slot_meta.xasa_gate.xsg_waiters) {
- // We're the first waiter, so we first need to mark ourselves as
- // waiting.
- xzm_allocation_slot_atomic_meta_u new_slot_meta = slot_meta;
- new_slot_meta.xasa_gate.xsg_waiters = true;
- bool success = os_atomic_cmpxchgv(&xasa->xasa_value,
- slot_meta.xasa_value, new_slot_meta.xasa_value,
- &slot_meta.xasa_value, dependency);
- if (!success) {
- return slot_meta;
- }
- slot_meta.xasa_gate.xsg_waiters = true;
- }
-
- uint32_t wait_op = UL_UNFAIR_LOCK | ULF_NO_ERRNO |
- ULF_WAIT_ADAPTIVE_SPIN | ULF_WAIT_WORKQ_DATA_CONTENTION;
- int rc = __ulock_wait(wait_op, &xasa->xasa_ulock,
- slot_meta.xasa_ulock, 0);
- if (os_unlikely(rc < 0)) {
- switch (-rc) {
- case EINTR:
- case EFAULT:
- break;
- default:
- xzm_abort_with_reason("ulock_wait failure", -rc);
- }
- } else {
- // We should only wake as part of a wake-all broadcast, but
- // there's no way to assert that. rc indicates the number of
- // waiters still in the ulock, but we can't assert anything
- // useful about that.
- }
-
- slot_meta = (xzm_allocation_slot_atomic_meta_u){
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
- return slot_meta;
-}
-
-static void
-_xzm_xzone_allocation_slot_fork_lock(xzm_xzone_allocation_slot_t xas)
-{
- xzm_allocation_slot_atomic_meta_u *xasa = &xas->xas_atomic;
-
- xzm_allocation_slot_atomic_meta_u slot_meta = {
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
- while (true) {
- if (slot_meta.xasa_gate.xsg_locked) {
- slot_meta = _xzm_allocation_slot_gate_wait(xas, slot_meta);
- continue;
- }
-
- xzm_allocation_slot_atomic_meta_u new_slot_meta = slot_meta;
- new_slot_meta.xasa_chunk.xsc_fork_locked = true;
-
- bool success = os_atomic_cmpxchgv(&xasa->xasa_value,
- slot_meta.xasa_value, new_slot_meta.xasa_value,
- &slot_meta.xasa_value, relaxed);
- if (!success) {
- continue;
- }
-
- break;
- }
-}
-
-static void
-_xzm_xzone_allocation_slot_fork_unlock(xzm_xzone_allocation_slot_t xas)
-{
- xzm_allocation_slot_atomic_meta_u *xasa = &xas->xas_atomic;
-
- xzm_allocation_slot_atomic_meta_u slot_meta = {
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
-
- xzm_debug_assert(slot_meta.xasa_chunk.xsc_fork_locked);
-
- xzm_allocation_slot_atomic_meta_u new_slot_meta = slot_meta;
- new_slot_meta.xasa_chunk.xsc_fork_locked = false;
-
- uint64_t prev_slot_value = os_atomic_xchg(&xasa->xasa_value,
- new_slot_meta.xasa_value, release);
- xzm_assert(prev_slot_value == slot_meta.xasa_value);
-}
-#endif // !CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
-static xzm_chunk_t
-_xzm_xzone_allocate_chunk_from_isolation(xzm_main_malloc_zone_t main,
- xzm_xzone_t xz)
-{
- xzm_debug_assert(xz->xz_sequestered);
- xzm_isolation_zone_t iz = &main->xzmz_isolation_zones[xz->xz_idx];
-
- xzm_chunk_t chunk = NULL;
-
- // Don't bother trying to take the iz lock if it doesn't look like
- // there's anything in there
- //
- // XXX TODO: this should be a proper atomic relaxed load
- if (LIST_FIRST(&iz->xziz_chunkq)) {
-#if CONFIG_XZM_DEFERRED_RECLAIM
- LIST_HEAD(, xzm_slice_s) busy_list = LIST_HEAD_INITIALIZER(busy_list);
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- _malloc_lock_lock(&iz->xziz_lock);
- chunk = LIST_FIRST(&iz->xziz_chunkq);
- while (chunk) {
- xzm_debug_assert(_xzm_chunk_is_empty(&main->xzmz_base, xz, chunk));
- LIST_REMOVE(chunk, xzc_entry);
-#if CONFIG_XZM_DEFERRED_RECLAIM
- // Chunks stay in the reclaim buffer when removed from the
- // isolation zone, and must be removed from the buffer
- if ((xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX &&
- main->xzmz_defer_small) ||
- ((xz->xz_block_size <= XZM_TINY_BLOCK_SIZE_MAX &&
- main->xzmz_defer_tiny))) {
- if (!xzm_chunk_mark_used(&main->xzmz_base, chunk, NULL)) {
- // this chunk is busy being reclaimed by the kernel
- LIST_INSERT_HEAD(&busy_list, chunk, xzc_entry);
- chunk = LIST_FIRST(&iz->xziz_chunkq);
- continue;
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- // chunk is already initialized, other than the mzone idx
- chunk->xzc_mzone_idx = xz->xz_mzone_idx;
- break;
- }
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (!LIST_EMPTY(&busy_list)) {
- // place any busy chunks back on the sequester list
- xzm_chunk_t busy_chunk, tmp_chunk;
- LIST_FOREACH_SAFE(busy_chunk, &busy_list, xzc_entry,
- tmp_chunk) {
- LIST_REMOVE(busy_chunk, xzc_entry);
- LIST_INSERT_HEAD(&iz->xziz_chunkq, busy_chunk, xzc_entry);
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- _malloc_lock_unlock(&iz->xziz_lock);
- }
-
-
- return chunk;
-}
-
-static void *
-_xzm_xzone_find_and_malloc_from_freelist_chunk(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_allocation_index_t alloc_idx,
- xzm_xzone_thread_cache_t cache, xzm_chunk_t *chunk_out,
- bool *contended_out)
-{
- void *ptr = NULL;
- xzm_chunk_t chunk = NULL;
- bool small = (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX);
-
- // The first place to try to get a new chunk from is the partial list.
- while ((chunk = _xzm_chunk_list_slot_pop(zone, xz,
- zone->xzz_partial_lists))) {
- xzm_debug_assert(chunk->xzc_atomic_meta.xca_on_partial_list &&
- !chunk->xzc_atomic_meta.xca_on_empty_list);
- bool empty = false;
- ptr = _xzm_xzone_malloc_from_freelist_chunk(zone, xz, alloc_idx, cache,
- chunk, small, contended_out, &empty);
- if (ptr) {
- // Great, we allocated from this chunk and can install it.
- goto done;
- } else if (empty) {
- xzm_debug_assert(!chunk->xzc_atomic_meta.xca_on_partial_list &&
- chunk->xzc_atomic_meta.xca_on_empty_list);
- // It's our responsibility to move this chunk to the empty list.
- _xzm_chunk_list_push(zone, &xz->xz_empty_list, chunk,
- XZM_CHUNK_LINKAGE_MAIN, NULL);
- }
- }
-
- xzm_debug_assert(!chunk);
-
- // Try the batch list. Some chunks may not be usable, so we need to build a
- // list of the busy ones that can be reinserted. Since some of these chunks
- // may still be on the partial list, we must reuse the batch linkage instead
- // of the inline linkage (c.f. empty list busy chunks below).
- xzm_chunk_t busy_chunk = NULL;
- while ((chunk = _xzm_chunk_list_pop(zone, &xz->xz_batch_list,
- XZM_CHUNK_LINKAGE_BATCH, NULL))) {
- xzm_debug_assert(!chunk->xzc_atomic_meta.xca_on_empty_list);
- ptr = _xzm_xzone_malloc_from_empty_freelist_chunk(zone, xz, alloc_idx,
- cache, chunk, false);
- if (ptr) {
- break;
- }
-
- *_xzm_segment_slice_meta_batch_next(zone, chunk) = busy_chunk;
- busy_chunk = chunk;
- }
-
- // Push unusable chunks back onto the batch list
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- while (busy_chunk) {
- xzm_debug_assert(!busy_chunk->xzc_atomic_meta.xca_on_empty_list);
- xzm_chunk_t next = *_xzm_segment_slice_meta_batch_next(zone, busy_chunk);
- _xzm_chunk_batch_list_push(zone, xz, busy_chunk, main->xzmz_batch_size);
- busy_chunk = next;
- }
-
- if (ptr) {
- // Great, we allocated from this chunk and can install it.
- goto done;
- }
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- SLIST_HEAD(, xzm_slice_s) busy_list = SLIST_HEAD_INITIALIZER(busy_list);
-#endif
- // Next, try the empty list. Some chunks may not be usable, so we need to
- // build a list of the busy ones that can be reinserted. Note that this list
- // is intrusive, and reuses the same linkage as that used by the partial and
- // empty lists.
- while ((chunk = _xzm_chunk_list_pop(zone, &xz->xz_empty_list,
- XZM_CHUNK_LINKAGE_MAIN, NULL))) {
- xzm_debug_assert(chunk->xzc_atomic_meta.xca_on_empty_list);
-
- bool was_reclaimed = true;
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if ((main->xzmz_defer_tiny && !small) ||
- (main->xzmz_defer_small && small)) {
- // We need to ensure that the reclaim ID update is visible before
- // loading it - we are _not_ implicitly guaranteed that by it having
- // come from the empty list because the thread that placed it on the
- // empty list may have done so without itself having visibility on
- // the ID update. This acquire pairs with the release that stores
- // the MADVISED state to the chunk.
- os_atomic_thread_fence(acquire);
- if (!xzm_chunk_mark_used(zone, chunk, &was_reclaimed)) {
- // this chunk is busy so we can't use it
- goto empty_busy;
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- ptr = _xzm_xzone_malloc_from_empty_freelist_chunk(zone, xz, alloc_idx,
- cache, chunk, was_reclaimed);
- if (ptr) {
- break;
- }
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
-empty_busy:
- SLIST_INSERT_HEAD(&busy_list, chunk, xzc_slist_entry);
-#else // CONFIG_XZM_DEFERRED_RECLAIM
- xzm_debug_abort("_xzm_xzone_malloc_from_empty_freelist_chunk failed");
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- }
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- // Push unusable chunks back onto the empty list
- while ((busy_chunk = SLIST_FIRST(&busy_list))) {
- xzm_debug_assert(busy_chunk->xzc_atomic_meta.xca_on_empty_list);
- SLIST_REMOVE_HEAD(&busy_list, xzc_slist_entry);
- _xzm_chunk_list_push(zone, &xz->xz_empty_list, busy_chunk,
- XZM_CHUNK_LINKAGE_MAIN, NULL);
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- if (ptr) {
- // Great, we allocated from this chunk and can install it.
- goto done;
- }
-
- // try the global isolation list
- if (xz->xz_sequestered) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- chunk = _xzm_xzone_allocate_chunk_from_isolation(main, xz);
- if (chunk) {
- xzm_debug_assert(_xzm_chunk_is_empty(zone, xz, chunk));
- ptr = _xzm_xzone_malloc_from_empty_freelist_chunk(zone, xz, alloc_idx,
- cache, chunk, true);
- xzm_debug_assert(ptr);
- goto push_to_all_list_and_unlock;
- }
- }
-
- bool zero_on_free = (xz->xz_block_size <= XZM_ZERO_ON_FREE_THRESHOLD);
- if (!(chunk = _xzm_chunk_list_pop(zone, &xz->xz_preallocated_list,
- XZM_CHUNK_LINKAGE_MAIN, NULL))) {
- // Failed to get a chunk from the preallocated list, need to go to the
- // segment group to get a fresh chunk.
- bool clear_chunk = zero_on_free;
- bool purgeable = false; // TINY chunks can't be purgeable
- xzm_preallocate_list_s preallocated;
- SLIST_INIT(&preallocated);
- xzm_preallocate_list_s *list = &preallocated;
- xzm_segment_group_t sg = _xzm_segment_group_for_id_and_front(zone,
- xz->xz_segment_group_id, xz->xz_front, false);
-
- xzm_slice_kind_t kind = XZM_SLICE_KIND_TINY_CHUNK;
- xzm_slice_count_t slice_count = 1;
- if (small) {
- kind = XZM_SLICE_KIND_SMALL_FREELIST_CHUNK;
- slice_count =
- XZM_SMALL_FREELIST_CHUNK_SIZE / XZM_SEGMENT_SLICE_SIZE;
- }
-
- chunk = xzm_segment_group_alloc_chunk(sg, kind,
- &xz->xz_guard_config, slice_count, list, 0, clear_chunk,
- purgeable);
- if (!chunk) {
- // Not able to get anything from the segment group, so give up
- // entirely.
- xzm_debug_assert(!list || !SLIST_FIRST(list));
- goto done;
- } else {
- chunk->xzc_xzone_idx = xz->xz_idx;
- xzm_chunk_t c, t;
- SLIST_FOREACH_SAFE(c, list, xzc_slist_entry, t) {
- SLIST_REMOVE_HEAD(list, xzc_slist_entry);
- c->xzc_xzone_idx = xz->xz_idx;
- c->xzc_bits.xzcb_preallocated = true;
- _xzm_chunk_list_push(zone, &xz->xz_preallocated_list, c,
- XZM_CHUNK_LINKAGE_MAIN, NULL);
- }
-
- if (xz->xz_slot_config < zone->xzz_initial_slot_config &&
- xz->xz_block_size < zone->xzz_slot_initial_threshold) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- uint64_t count = os_atomic_inc(&xz->xz_chunk_count, relaxed);
- if (count > main->xzmz_xzone_chunk_threshold) {
- // Upgrade/reset the counters in all the slots
- xzm_xzone_slot_counters_u counter = {
- .xsc_slot_config = xz->xz_slot_config,
- };
- _xzm_xzone_upgrade_freelist_slot_config(zone, xz, NULL,
- counter, zone->xzz_initial_slot_config, true);
- }
- }
- }
- } else {
- // We got a chunk from the preallocated list, which is not guaranteed to
- // be zero'd
- if (zero_on_free && !chunk->xzc_bits.xzcb_is_pristine) {
- size_t chunk_size = 0;
- void *ptr = _xzm_chunk_start_ptr(zone, chunk, &chunk_size);
- xzm_debug_assert(ptr);
- xzm_debug_assert(chunk_size == XZM_SEGMENT_SLICE_SIZE);
- bzero(ptr, chunk_size);
- }
- }
-
- ptr = _xzm_xzone_malloc_from_fresh_freelist_chunk(zone, xz, alloc_idx,
- cache, chunk, small);
- xzm_debug_assert(ptr);
-
-push_to_all_list_and_unlock:
- // Put the chunk on the "all" list for the xzone so that it can be found
- // during fork
- _xzm_chunk_list_push(zone, &xz->xz_all_list, chunk, XZM_CHUNK_LINKAGE_ALL,
- NULL);
-
-done:
- xzm_debug_assert((ptr && chunk) || (!ptr && !chunk));
-
- *chunk_out = chunk;
- return ptr;
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_malloc_freelist_outlined(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_allocation_index_t alloc_idx, xzm_xzone_allocation_slot_t xas,
- void *corrupt_block, xzm_malloc_options_t opt)
-{
- if (os_unlikely(corrupt_block)) {
- _xzm_corruption_detected(corrupt_block);
- }
-
- bool clear = (opt & XZM_MALLOC_CLEAR);
- bool zero_on_free = (xz->xz_block_size <= XZM_ZERO_ON_FREE_THRESHOLD);
-#if !CONFIG_TINY_ALLOCATION_SLOT_LOCK
- bool small = (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX);
-#endif
-#if CONFIG_MTE
- const bool memtag_enabled = xz->xz_tagged;
- bool needs_canonical_tagging = (opt & XZM_MALLOC_CANONICAL_TAG);
-#endif
-
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
-#ifdef DEBUG
- _malloc_lock_assert_owner(&xas->xas_lock);
-#endif // DEBUG
-#else // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- uint32_t self_owner_value = 0;
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- xzm_allocation_slot_atomic_meta_u *xasa = &xas->xas_atomic;
-
- xzm_allocation_slot_atomic_meta_u slot_meta = {
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
-
- void *ptr = NULL;
- xzm_chunk_t chunk = NULL;
- bool contended = false;
-
-#if !CONFIG_TINY_ALLOCATION_SLOT_LOCK
- while (true) {
- if (slot_meta.xasa_gate.xsg_locked) {
- // No chunk is currently installed, and another thread is in the
- // middle of fetching and installing one. Wait for them and then
- // try again.
- slot_meta = _xzm_allocation_slot_gate_wait(xas, slot_meta);
- continue;
- } else if (os_unlikely(slot_meta.xasa_chunk.xsc_fork_locked)) {
- // We can't acquire the gate because a fork is in progress that we
- // need to wait out.
- _xzm_fork_lock_wait(zone);
-
- slot_meta = (xzm_allocation_slot_atomic_meta_u){
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
- continue;
- }
-
- // The gate isn't locked, so the next question is whether a chunk is
- // installed.
- chunk = (xzm_chunk_t)slot_meta.xasa_chunk.xsc_ptr;
- if (chunk) {
- // A chunk is installed, so let's try to allocate from it.
- ptr = _xzm_xzone_malloc_from_freelist_chunk(zone, xz, alloc_idx,
- NULL, chunk, small, &contended,
- /* install_empty_out */ NULL);
- if (ptr) {
- // Success!
- goto done;
- }
- }
-
- if (!self_owner_value) {
- self_owner_value = _malloc_ulock_self_owner_value();
- }
-
- // Either we had no chunk reference, or the one we had couldn't be
- // allocated from. Let's try to become an allocator for our slot to go
- // get a new one.
- xzm_allocation_slot_atomic_meta_u new_slot_meta = {
- .xasa_gate = {
- .xsg_locked = true,
- .xsg_owner = self_owner_value,
- .xsg_gen = slot_meta.xasa_gate.xsg_gen,
- },
- };
- bool success = os_atomic_cmpxchgv(&xasa->xasa_value,
- slot_meta.xasa_value, new_slot_meta.xasa_value,
- &slot_meta.xasa_value, dependency);
- if (!success) {
- continue;
- }
-
- break;
- }
-
- xzm_debug_assert(self_owner_value);
-#else // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- xzm_debug_assert(!slot_meta.xasa_gate.xsg_locked);
-#if CONFIG_MTE
- chunk = (xzm_chunk_t)slot_meta.xasa_chunk.xsc_ptr;
- if (chunk && (xz->xz_tagged && (opt & XZM_MALLOC_CANONICAL_TAG))) {
- // If we're serving a canonically-tagged request, we won't have actually
- // attempted to allocate from the chunk yet, so we need to do that first
- ptr = _xzm_xzone_malloc_from_freelist_chunk(zone, xz, alloc_idx,
- NULL, chunk, /* small */ false, &contended,
- /* install_empty_out */ NULL);
- if (ptr) {
- // Success!
- goto unlock;
- }
- }
-#endif
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- // It's now up to us to get a usable chunk and install it.
-
- xzm_debug_assert(!ptr);
-
- ptr = _xzm_xzone_find_and_malloc_from_freelist_chunk(zone, xz, alloc_idx,
- NULL, &chunk, &contended);
-
- // Unlock the gate and publish our new chunk (or lack thereof)
- xzm_allocation_slot_atomic_meta_u new_slot_meta = {
- .xasa_chunk = {
- .xsc_ptr = (uint64_t)chunk,
- .xsc_gen = slot_meta.xasa_gate.xsg_gen + 1,
- },
- };
-
- uint64_t prev_slot_value = os_atomic_xchg(&xasa->xasa_value,
- new_slot_meta.xasa_value, release);
- xzm_allocation_slot_atomic_meta_u prev_slot_meta = {
- .xasa_value = prev_slot_value,
- };
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- (void)prev_slot_meta;
- xzm_debug_assert(prev_slot_meta.xasa_value == slot_meta.xasa_value);
-#else // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- xzm_debug_assert(prev_slot_meta.xasa_gate.xsg_locked);
- xzm_debug_assert(prev_slot_meta.xasa_gate.xsg_owner == self_owner_value);
- xzm_debug_assert(prev_slot_meta.xasa_gate.xsg_unused == 0);
- xzm_debug_assert(prev_slot_meta.xasa_gate.xsg_gen ==
- slot_meta.xasa_gate.xsg_gen);
-
- if (prev_slot_meta.xasa_gate.xsg_waiters) {
- uint32_t wake_op = UL_UNFAIR_LOCK | ULF_WAKE_ALL | ULF_NO_ERRNO;
- int rc = __ulock_wake(wake_op, &xasa->xasa_ulock, 0);
- if (rc && rc != -ENOENT) {
- xzm_abort_with_reason("ulock_wake failure", -rc);
- }
- }
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
-#if CONFIG_MTE
-unlock:
-#endif
- _malloc_lock_unlock(&xas->xas_lock);
-#else // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-done:
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- // Now that we've unlocked the gate, we can safely touch the chunk to
- // perform any necessary zeroing to the block we allocated. It's very
- // important that we not do this inside the gate because taking a fault in
- // there makes it much more likely for other threads to contend and get
- // stuck waiting for us.
- if (ptr) {
- // The true contention information we'd want to record in the locking
- // case would be from the trylock rather than the list pop, but we
- // didn't forward that information so there's nothing to do here
-#if !CONFIG_TINY_ALLOCATION_SLOT_LOCK
- xzm_slot_config_t slot_config = os_atomic_load(&xz->xz_slot_config,
- relaxed);
- _xzm_xzone_slot_record_contention(zone, xz, &xas->xas_counters,
- slot_config, true, contended);
-#endif // !CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
-#if CONFIG_MTE
- if (memtag_enabled) {
- if (needs_canonical_tagging) {
- ptr = memtag_tag_canonical(ptr, xz->xz_block_size);
- } else {
- xzm_debug_assert(ptr == memtag_fixup_ptr(ptr));
- }
- }
-#endif
-
- xzm_block_t block = ptr;
- *block = (struct xzm_block_inline_meta_s){ 0 };
-
- if (clear && !zero_on_free) {
- bzero(ptr, xz->xz_block_size);
- }
- } else {
- malloc_set_errno_fast(MZ_POSIX, ENOMEM);
- }
- return ptr;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void *
-_xzm_xzone_malloc_freelist(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_malloc_options_t opt, const bool small)
-{
- bool clear = (opt & XZM_MALLOC_CLEAR);
-
- xzm_slot_config_t slot_config;
- xzm_allocation_index_t alloc_idx = _xzm_get_allocation_index(zone, xz,
- &slot_config, true);
- xzm_xzone_allocation_slot_t xas =
- _xzm_xzone_allocation_slot_for_index(zone, xz, alloc_idx);
-
- bool contended = false;
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- bool contended_dummy = false;
- if (!_malloc_lock_trylock(&xas->xas_lock)) {
- contended = true;
- _malloc_lock_lock(&xas->xas_lock);
- }
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- xzm_allocation_slot_atomic_meta_u *xasa = &xas->xas_atomic;
-
- xzm_allocation_slot_atomic_meta_u slot_meta = {
- .xasa_value = os_atomic_load(&xasa->xasa_value, dependency),
- };
-
- void *ptr = NULL;
-#if CONFIG_MTE
- const bool fast_path =
- !slot_meta.xasa_gate.xsg_locked && slot_meta.xasa_chunk.xsc_ptr &&
- !(xz->xz_tagged && (opt & XZM_MALLOC_CANONICAL_TAG));
-#else
- const bool fast_path =
- !slot_meta.xasa_gate.xsg_locked && slot_meta.xasa_chunk.xsc_ptr;
-#endif
- // Fast path: the slot is not locked and a chunk is installed
- if (fast_path) {
- xzm_chunk_t chunk = (xzm_chunk_t)slot_meta.xasa_chunk.xsc_ptr;
-
- // Try to allocate from the installed chunk
- const bool walk_wait = false;
- bool corrupt = false;
- ptr = _xzm_xzone_malloc_from_freelist_chunk_inline(zone, xz, alloc_idx,
- NULL, chunk, small, walk_wait, &corrupt,
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- &contended_dummy,
-#else // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- &contended,
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
- /* install_empty_out */ NULL);
- if (ptr && !corrupt) {
- // Success!
-
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- _malloc_lock_unlock(&xas->xas_lock);
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- _xzm_xzone_slot_record_contention(zone, xz, &xas->xas_counters,
- slot_config, true, contended);
-
- xzm_block_t block = ptr;
- *block = (struct xzm_block_inline_meta_s){ 0 };
-
- if (clear && xz->xz_block_size > XZM_ZERO_ON_FREE_THRESHOLD) {
- return memset(ptr, 0, xz->xz_block_size);
- }
-
- return ptr;
- }
- }
-
- return _xzm_xzone_malloc_freelist_outlined(zone, xz, alloc_idx, xas, ptr,
- opt);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_malloc_tiny(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_malloc_options_t opt)
-{
- return _xzm_xzone_malloc_freelist(zone, xz, opt, false);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_malloc_small_freelist(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_malloc_options_t opt)
-{
- return _xzm_xzone_malloc_freelist(zone, xz, opt, true);
-}
-
-#pragma mark Tiny deallocation
-
-static void
-_xzm_xzone_freelist_chunks_mark_empty(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t *chunks, size_t chunk_count)
-{
- for (size_t i = 0; i < chunk_count; ++i) {
- xzm_chunk_t chunk = chunks[i];
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, relaxed),
- };
- while (true) {
- xzm_chunk_atomic_meta_u new_meta = old_meta;
-
- xzm_debug_assert(old_meta.xca_alloc_head == XZM_FREE_MADVISING);
- new_meta.xca_alloc_head = XZM_FREE_MADVISED;
- if (!old_meta.xca_on_partial_list) {
- new_meta.xca_on_empty_list = true;
- }
- // release to publish the reclaim index if we stored one
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- release);
- if (!success) {
- continue;
- }
-
- xzm_trace(free_madvise, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo, old_meta.xca_seqno);
- break;
- }
-
- // because the partial list is atomically singly-linked, an empty chunk
- // could remain there until it is popped off the head, so we cannot put
- // it onto the empty list until after that happens
- if (!old_meta.xca_on_partial_list) {
- _xzm_chunk_list_push(zone, &xz->xz_empty_list, chunk,
- XZM_CHUNK_LINKAGE_MAIN, NULL);
- }
- }
-}
-
-static void
-_xzm_xzone_madvise_freelist_chunk(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk)
-{
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- if ((chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK &&
- main->xzmz_defer_tiny) ||
- (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK &&
- main->xzmz_defer_small)) {
- xzm_chunk_mark_free(zone, chunk);
- } else
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- {
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone, chunk);
- xzm_segment_group_segment_madvise_chunk(sg, chunk);
- }
-
- _xzm_xzone_freelist_chunks_mark_empty(zone, xz, &chunk, 1);
-}
-
-static void
-_xzm_xzone_small_chunks_mark_empty(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t *chunk, size_t chunk_count);
-
-static void
-_xzm_xzone_madvise_batch(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk)
-{
- const xzm_slice_kind_t kind = chunk->xzc_bits.xzcb_kind;
- // We need to store the local list of entries in the batch. Otherwise,
- // the list pointers could be replaced by the deferred reclaim id, and
- // attempting to update metadata while under the reclaim buffer lock could
- // deadlock against a concurrent allocator that holds the isolation zone
- // lock and wants the reclaim buffer lock.
- xzm_chunk_t batch_list[1u << XZM_BATCH_SIZE_BITS];
- unsigned batch_list_size = 0;
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- xzm_reclaim_buffer_t buffer = main->xzmz_reclaim_buffer;
- const bool deferred_reclaim = (kind == XZM_SLICE_KIND_TINY_CHUNK) ?
- main->xzmz_defer_tiny : main->xzmz_defer_small;
- bool should_update_kernel_accounting = false;
-
- if (deferred_reclaim) {
- _malloc_lock_lock(&buffer->xrb_lock);
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- // Walk the batch, make a local copy, and perform the reclaim
- while (chunk) {
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_debug_assert(batch_list_size < main->xzmz_batch_size &&
- batch_list_size < (1u << XZM_BATCH_SIZE_BITS));
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- batch_list[batch_list_size++] = chunk;
-
- xzm_debug_assert(kind == chunk->xzc_bits.xzcb_kind);
- // Fetch the pointer for the next chunk first, because madvising it
- // may replace the linkage with the deferred reclaim id
- xzm_chunk_t next = *_xzm_segment_slice_meta_batch_next(zone, chunk);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone, next));
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (deferred_reclaim) {
- size_t size;
- uint8_t *addr = _xzm_chunk_start_ptr(zone, chunk, &size);
- uint64_t *reclaim_id = _xzm_slice_meta_reclaim_id(zone, chunk);
-
- bool should_update_kernel_chunk = false;
- // This relies on release ordering from the push of the batch
- // list to obtain the correct reclaim buffer index
- *reclaim_id = xzm_reclaim_mark_free_locked(buffer, addr, size, true,
- &should_update_kernel_chunk);
- should_update_kernel_accounting |= should_update_kernel_chunk;
- } else
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- {
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone,
- chunk);
- xzm_segment_group_segment_madvise_chunk(sg, chunk);
- }
-
- chunk = next;
- }
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (deferred_reclaim) {
- _malloc_lock_unlock(&buffer->xrb_lock);
-
- if (should_update_kernel_accounting) {
- mach_vm_reclaim_update_kernel_accounting(buffer->xrb_ringbuffer);
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- if (kind == XZM_SLICE_KIND_TINY_CHUNK ||
- kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- _xzm_xzone_freelist_chunks_mark_empty(zone, xz, batch_list,
- batch_list_size);
- } else if (kind == XZM_SLICE_KIND_SMALL_CHUNK) {
- _xzm_xzone_small_chunks_mark_empty(zone, xz, batch_list, batch_list_size);
- } else {
- xzm_abort_with_reason("Unexpected chunk kind", kind);
- }
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_free_abort(void *ptr)
-{
- xzm_client_abort_with_reason(
- "free to empty or invalid chunk detected (likely double-free)",
- ptr);
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_free_freelist_inline(xzm_malloc_zone_t zone,
- xzm_xzone_index_t xz_idx, xzm_chunk_t chunk, xzm_block_t block,
- uint16_t block_ref, size_t block_size, size_t chunk_capacity)
-{
- xzm_xzone_t xz = NULL;
- union xzm_block_linkage_u linkage = { 0 };
-
- uint64_t now = 0;
- uint64_t last_empty_ts = 0;
- bool should_madvise = true;
- bool madvise_considered = false;
-
- uint32_t contentions = 0;
-
- xzm_xzone_allocation_slot_t xas = NULL;
-
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency),
- };
-
- xzm_chunk_atomic_meta_u new_meta;
-
- bool push_to_partial = false;
- while (true) {
- if (os_unlikely(old_meta.xca_walk_locked)) {
- _xzm_walk_lock_wait(zone);
- old_meta.xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency);
- continue;
- }
-
- new_meta = old_meta;
- new_meta.xca_seqno++;
-
- xzm_debug_assert(!push_to_partial);
-
- if (old_meta.xca_free_count + 1 == chunk_capacity) {
- // We're freeing the last block.
-
- // Evaluate the madvise heuristic the first time through.
- if (!madvise_considered) {
- xz = &zone->xzz_xzones[xz_idx];
- if (old_meta.xca_alloc_idx &&
- old_meta.xca_alloc_idx != XZM_SLOT_INDEX_THREAD_INSTALLED) {
- xzm_chunk_list_t partial_list = _xzm_chunk_list_get(zone,
- xz, NULL, zone->xzz_partial_lists);
- bool last_chunk = _xzm_chunk_list_empty(&partial_list->xcl_list);
- if (last_chunk) {
- now = mach_absolute_time();
- xas = _xzm_xzone_allocation_slot_for_index(zone, xz,
- old_meta.xca_alloc_idx - 1);
- last_empty_ts = os_atomic_load(
- &xas->xas_last_chunk_empty_ts, relaxed);
- if (now - last_empty_ts < zone->xzz_tiny_thrash_threshold) {
- should_madvise = false;
- }
- }
- }
- madvise_considered = true;
- }
-
- if (should_madvise &&
- old_meta.xca_alloc_idx != XZM_SLOT_INDEX_THREAD_INSTALLED) {
- new_meta.xca_alloc_head = XZM_FREE_MADVISING;
- new_meta.xca_free_count = 0;
- new_meta.xca_alloc_idx = XZM_SLOT_INDEX_EMPTY;
- goto do_cmpxchg;
- }
- } else if (old_meta.xca_free_count == 0) {
- if (os_unlikely(old_meta.xca_alloc_head != XZM_FREE_NULL)) {
- return _xzm_free_abort((void *)block);
- }
-
- if (old_meta.xca_alloc_idx == XZM_SLOT_INDEX_EMPTY) {
- // This chunk is not currently installed anywhere. We
- // should move it to the partial list so it can be
- // considered for future allocations.
- xzm_debug_assert(!old_meta.xca_on_partial_list &&
- !old_meta.xca_on_empty_list);
- new_meta.xca_on_partial_list = true;
- push_to_partial = true;
-
- xz = &zone->xzz_xzones[xz_idx];
- } else {
- xzm_debug_assert(!old_meta.xca_on_partial_list);
- }
- }
-
- uint32_t seqno = (uint32_t)old_meta.xca_seqno & XZM_SEQNO_COUNTER_MASK;
-
- // Link the new block to the current head of the free list.
- linkage = (union xzm_block_linkage_u){
- .xzbl_next_offset = old_meta.xca_alloc_head,
- .xzbl_next_seqno = old_meta.xca_head_seqno,
- .xzbl_seqno = seqno,
- };
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(block,
- ptrauth_string_discriminator("xzb_linkage")));
- linkage.xzbl_seqno = 0;
-
- os_atomic_store(&block->xzb_linkage.xzbl_next_value,
- linkage.xzbl_next_value, relaxed);
-
- // Install the new block as the head.
- new_meta.xca_alloc_head = block_ref;
- new_meta.xca_free_count++;
- new_meta.xca_head_seqno = seqno;
-
-do_cmpxchg:;
- // Release ordering to publish our zeroing and initialization of the
- // block - pairs with the dependency order loads/cmpxchgs on allocation
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- release);
- if (!success) {
- // Try again.
- push_to_partial = false;
- contentions++;
- continue;
- }
-
- xzm_trace(free, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo,
- (uint32_t)old_meta.xca_seqno | ((uint64_t)contentions) << 32);
- break;
- }
-
- // If we checked the time while evaluating the madvise heuristic, update it
- // in the allocation slot.
- if (now) {
- xzm_debug_assert(xas);
- os_atomic_store(&xas->xas_last_chunk_empty_ts, now, relaxed);
- }
-
- if (new_meta.xca_alloc_head == XZM_FREE_MADVISING) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- xzm_debug_assert(xz);
-
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- // We need to ensure that there are no threads in the critical section
- // of the allocation slot that we uninstalled the chunk from
- if (old_meta.xca_alloc_idx != XZM_SLOT_INDEX_EMPTY) {
- xas = _xzm_xzone_allocation_slot_for_index(zone, xz,
- old_meta.xca_alloc_idx - 1);
- _malloc_lock_lock(&xas->xas_lock);
- _malloc_lock_unlock(&xas->xas_lock);
- }
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- if (!main->xzmz_batch_size) {
- // Batching is disabled, do the madvise we committed to
- _xzm_xzone_madvise_freelist_chunk(zone, xz, chunk);
- } else {
- // Batching is enabled, enqueue the chunk. Note that the batch list
- // is not exclusive, the chunk could also still be on the partial
- // list as well
- _xzm_chunk_batch_list_push(zone, xz, chunk,
- main->xzmz_batch_size);
- }
- } else if (push_to_partial) {
- xzm_debug_assert(xz);
- _xzm_chunk_list_slot_push(zone, xz, zone->xzz_partial_lists, chunk);
- }
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_xzone_free_freelist(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, xzm_block_t block)
-{
- size_t block_size = xz->xz_block_size;
- if (block_size <= XZM_ZERO_ON_FREE_THRESHOLD) {
- bzero(block, block_size);
- }
-
- void *ptr = block;
-#if CONFIG_MTE
- ptr = memtag_strip_address((uint8_t *)ptr);
-#endif
-
-#if CONFIG_MTE
- if (xz->xz_tagged && block_size <= XZM_TINY_BLOCK_SIZE_MAX) {
- // Only tiny chunks are tagged on free
- block = _xzm_xzone_block_memtag_retag(zone, block, block_size);
- }
-#endif
-
- uint64_t free_cookie = zone->xzz_freelist_cookie ^ (uint64_t)block;
-#if CONFIG_MTE
- if (block_size > XZM_TINY_BLOCK_SIZE_MAX) {
- // Small blocks do not encode the tag in the freelist cookie
- free_cookie = (uint64_t)memtag_strip_address((uint8_t *)free_cookie);
- }
-#endif
- os_atomic_store(&block->xzb_cookie, free_cookie, relaxed);
-
- uint16_t block_ref;
- if (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX) {
- size_t block_offset = _xzm_chunk_block_offset(zone, chunk, block);
- block_ref = block_offset / XZM_SMALL_GRANULE;
- } else {
- size_t block_offset = (uintptr_t)ptr & XZM_SEGMENT_SLICE_MASK;
- block_ref = block_offset / XZM_GRANULE;
- }
-
- _xzm_xzone_free_freelist_inline(zone, xz->xz_idx, chunk, block, block_ref,
- block_size, xz->xz_chunk_capacity);
-}
-
-#if CONFIG_XZM_THREAD_CACHE
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_thread_cache_free_tiny(xzm_malloc_zone_t zone,
- xzm_xzone_thread_cache_t cache, xzm_block_t block, uint16_t block_ref)
-{
- uint16_t seqno = cache->xztc_seqno & XZM_SEQNO_COUNTER_MASK;
- seqno |= XZM_SEQNO_THREAD_LOCAL;
-
- union xzm_block_linkage_u linkage = {
- .xzbl_next_offset = cache->xztc_head,
- .xzbl_next_seqno = cache->xztc_head_seqno,
- .xzbl_seqno = seqno,
- };
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(block,
- ptrauth_string_discriminator("xzb_linkage")));
- linkage.xzbl_seqno = 0;
-
- os_atomic_store(&block->xzb_linkage.xzbl_next_value,
- linkage.xzbl_next_value, relaxed);
-
-#ifdef DEBUG
- uint64_t orig_state = cache->xztc_freelist_state;
-#endif
-
- xzm_xzone_thread_cache_atomic_meta_u tc_meta = {
- .xztcam_head = block_ref,
- .xztcam_free_count = cache->xztc_free_count + 1,
- };
- os_atomic_store(&cache->xztc_atomic_meta.xztcam_value,
- tc_meta.xztcam_value, relaxed);
- cache->xztc_head_seqno = seqno;
- cache->xztc_seqno++;
-
-#ifdef DEBUG
- uint64_t new_state = cache->xztc_freelist_state;
-#endif
-
- xzm_trace(thread_cache_free, (uint64_t)cache->xztc_chunk, orig_state,
- new_state, 0);
-}
-
-static void
-_xzm_xzone_thread_cache_detach_link_to_remote(xzm_malloc_zone_t zone,
- xzm_xzone_thread_cache_t cache, xzm_chunk_atomic_meta_u old_meta)
-{
- xzm_chunk_t chunk = cache->xztc_chunk;
-
- uint8_t *start = cache->xztc_chunk_start;
- uint64_t block_granules = chunk->xzc_freelist_block_size / XZM_GRANULE;
- uint64_t max_offset = (chunk->xzc_freelist_chunk_capacity - 1) * block_granules;
-
- uint64_t zone_freelist_cookie = zone->xzz_freelist_cookie;
-
- xzm_debug_assert(old_meta.xca_alloc_head < XZM_FREE_LIMIT);
- xzm_debug_assert(old_meta.xca_free_count);
-
- xzm_block_t cur_block = NULL;
- uint64_t cur_block_offset = old_meta.xca_alloc_head;
- uint64_t cur_seqno = old_meta.xca_head_seqno;
- size_t freelist_count = 1;
- while (true) {
- cur_block = (xzm_block_t)(
- start + (cur_block_offset * XZM_GRANULE));
-
-#if CONFIG_MTE
- if (chunk->xzc_tagged) {
- cur_block = (xzm_block_t)memtag_fixup_ptr((void *)cur_block);
- }
-#endif
- uint64_t expected_cookie = zone_freelist_cookie ^ (uintptr_t)cur_block;
-
- struct xzm_block_inline_meta_s block_meta = *cur_block;
- if (os_unlikely(block_meta.xzb_cookie != expected_cookie)) {
- xzm_client_abort_with_reason(
- "corrupt tiny freelist - cookie, client likely has a buffer"
- " overflow or use-after-free bug", block_meta.xzb_cookie);
- }
-
- union xzm_block_linkage_u linkage = {
- .xzbl_next_offset = block_meta.xzb_linkage.xzbl_next_offset,
- .xzbl_next_seqno = block_meta.xzb_linkage.xzbl_next_seqno,
- .xzbl_seqno = cur_seqno,
- };
-
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(cur_block,
- ptrauth_string_discriminator("xzb_linkage")));
-
- linkage.xzbl_seqno = 0;
-
- if (block_meta.xzb_linkage.xzbl_next_value != linkage.xzbl_next_value) {
- xzm_client_abort_with_reason(
- "corrupt tiny freelist - linkage, client likely has a "
- "buffer overflow or use-after-free bug",
- block_meta.xzb_linkage.xzbl_next_value);
- }
-
- uint64_t next_seqno = (uint16_t)block_meta.xzb_linkage.xzbl_next_seqno;
- uint64_t next_block_offset = block_meta.xzb_linkage.xzbl_next_offset;
-
- if (next_block_offset == XZM_FREE_NULL) {
- // freelist walk should be complete
- if (os_unlikely(freelist_count != old_meta.xca_free_count)) {
- xzm_client_abort_with_reason(
- "corrupt tiny freelist - free count, client likely has a"
- " buffer overflow or use-after-free bug",
- freelist_count);
- }
-
- break;
- }
-
- if (os_unlikely(next_block_offset % block_granules != 0 ||
- next_block_offset > max_offset ||
- freelist_count >= old_meta.xca_free_count)) {
- xzm_client_abort_with_reason(
- "corrupt tiny freelist - inconsistent walk, client likely"
- " has a buffer overflow or use-after-free bug",
- freelist_count);
- }
-
- cur_block_offset = next_block_offset;
- cur_seqno = next_seqno;
- freelist_count++;
- }
-
- xzm_block_t tail_block = cur_block;
- uint64_t tail_seqno = cur_seqno;
-
- union xzm_block_linkage_u linkage = {
- .xzbl_next_offset = cache->xztc_head,
- .xzbl_next_seqno = cache->xztc_head_seqno,
- .xzbl_seqno = tail_seqno,
- };
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(tail_block,
- ptrauth_string_discriminator("xzb_linkage")));
- linkage.xzbl_seqno = 0;
-
- os_atomic_store(&tail_block->xzb_linkage.xzbl_next_value,
- linkage.xzbl_next_value, relaxed);
-}
-
-static void
-_xzm_xzone_thread_cache_detach(xzm_main_malloc_zone_t main, xzm_xzone_t xz,
- xzm_xzone_thread_cache_t cache)
-{
- xzm_malloc_zone_t zone = &main->xzmz_base;
- xzm_chunk_t chunk = cache->xztc_chunk;
-
- size_t chunk_capacity = xz->xz_chunk_capacity;
- uint16_t local_free_count = cache->xztc_free_count;
-
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency),
- };
-
- xzm_chunk_atomic_meta_u new_meta;
-
- bool needs_link = (cache->xztc_head < XZM_FREE_LIMIT);
- bool push_to_partial = false;
- while (true) {
- if (os_unlikely(old_meta.xca_walk_locked)) {
- _xzm_walk_lock_wait(zone);
- old_meta.xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency);
- continue;
- }
-
- new_meta = old_meta;
- new_meta.xca_alloc_idx = XZM_SLOT_INDEX_EMPTY;
-
- xzm_debug_assert(!push_to_partial);
-
- uint16_t new_free_count = old_meta.xca_free_count + local_free_count;
- if (new_free_count == chunk_capacity) {
- // The entire chunk will now be free
-
- // TODO madvise heuristic? Doesn't seem like it would really make
- // sense here
- new_meta.xca_free_count = 0;
- new_meta.xca_alloc_head = XZM_FREE_MADVISING;
- } else if (new_free_count) {
- xzm_debug_assert(!old_meta.xca_on_partial_list &&
- !old_meta.xca_on_empty_list);
- new_meta.xca_free_count = new_free_count;
- new_meta.xca_on_partial_list = true;
- push_to_partial = true;
-
- if (old_meta.xca_alloc_head == XZM_FREE_NULL) {
- new_meta.xca_alloc_head = cache->xztc_head;
- new_meta.xca_head_seqno = cache->xztc_head_seqno;
- } else {
- xzm_debug_assert(old_meta.xca_alloc_head < XZM_FREE_LIMIT);
- if (needs_link) {
- _xzm_xzone_thread_cache_detach_link_to_remote(zone, cache,
- old_meta);
- needs_link = false;
- }
- }
- }
-
- // Release ordering pairs with the dependency order loads/cmpxchgs on
- // allocation
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- release);
- if (!success) {
- // Try again.
- push_to_partial = false;
- continue;
- }
-
- xzm_trace(thread_detach, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo, cache->xztc_freelist_state);
- break;
- }
-
- if (new_meta.xca_alloc_head == XZM_FREE_MADVISING) {
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
-#error "not compatible with thread caching"
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- // Do the madvise we committed to.
- _xzm_xzone_madvise_freelist_chunk(zone, xz, chunk);
- } else if (push_to_partial) {
- _xzm_chunk_list_slot_push(zone, xz, zone->xzz_partial_lists, chunk);
- }
-}
-
-static void
-_xzm_xzone_thread_cache_destructor(void *arg)
-{
- xzm_thread_cache_t tc = arg;
- xzm_main_malloc_zone_t main = tc->xtc_main;
- xzm_malloc_zone_t zone = &main->xzmz_base;
-
- tc->xtc_teardown_gen = os_atomic_inc(
- &main->xzmz_thread_cache_teardown_gen, relaxed);
-
- for (size_t i = XZM_XZONE_INDEX_FIRST;
- i < zone->xzz_thread_cache_xzone_count; i++) {
- xzm_xzone_thread_cache_t cache = &tc->xtc_xz_caches[i];
- if (cache->xztc_head <= XZM_FREE_LIMIT) {
- xzm_xzone_t xz = &zone->xzz_xzones[i];
- _xzm_xzone_thread_cache_detach(main, xz, cache);
- }
- }
-
- _malloc_lock_lock(&main->xzmz_thread_cache_list_lock);
- LIST_REMOVE(tc, xtc_linkage);
- _malloc_lock_unlock(&main->xzmz_thread_cache_list_lock);
-
- xzm_metapool_t mp = &main->xzmz_metapools[XZM_METAPOOL_THREAD_CACHE];
- xzm_metapool_free(mp, tc);
-}
-
-#endif // CONFIG_XZM_THREAD_CACHE
-
-#pragma mark Small allocation
-
-// mimalloc: mi_page_init
-static void
-_xzm_xzone_small_chunk_init(xzm_xzone_t xz, xzm_chunk_t chunk)
-{
- // Precondition: xzcb_kind, xzcs_slice_count and xzsl_slice_offset are
- // already initialized
- xzm_debug_assert(chunk->xzc_free == 0);
- xzm_debug_assert(chunk->xzc_used == 0);
- xzm_debug_assert(chunk->xzc_mzone_idx == XZM_MZONE_INDEX_INVALID);
-
- _xzm_xzone_fresh_chunk_init(xz, chunk, XZM_SLICE_KIND_SMALL_CHUNK);
-
- // Enumerator protocol: mzone_idx is initialized last to "publish" the chunk
- chunk->xzc_mzone_idx = xz->xz_mzone_idx;
-}
-
-// mimalloc: mi_page_fresh_alloc
-static xzm_chunk_t
-_xzm_xzone_small_chunk_alloc(xzm_malloc_zone_t zone, xzm_xzone_t xz)
-{
- // First, check the preallocated list
- xzm_chunk_t chunk = NULL;
- if (LIST_FIRST(&xz->xz_chunkq_preallocated)) {
- _malloc_lock_lock(&xz->xz_lock);
- if ((chunk = LIST_FIRST(&xz->xz_chunkq_preallocated))) {
- LIST_REMOVE(chunk, xzc_entry);
- }
- _malloc_lock_unlock(&xz->xz_lock);
- }
-
- // If none are present on the preallocated list, request a new run from the
- // segment group, and enqueue the spares into the preallocated list
- if (!chunk) {
- xzm_slice_kind_t kind = XZM_SLICE_KIND_SMALL_CHUNK;
- size_t span_size = XZM_SMALL_CHUNK_SIZE;
- xzm_slice_count_t slice_count =
- (xzm_slice_count_t)(span_size / XZM_SEGMENT_SLICE_SIZE);
- bool clear = false;
- bool purgeable = false; // Purgeable only applies to LARGE/HUGE chunks
- xzm_preallocate_list_s preallocated;
- SLIST_INIT(&preallocated);
- xzm_preallocate_list_s *list = &preallocated;
- xzm_segment_group_t sg = _xzm_segment_group_for_id_and_front(zone,
- xz->xz_segment_group_id, xz->xz_front, false);
- chunk = xzm_segment_group_alloc_chunk(sg, kind, &xz->xz_guard_config,
- slice_count, list, 0, clear, purgeable);
- if (!chunk) {
- xzm_debug_assert(!list || !SLIST_FIRST(list));
- return NULL;
- }
- chunk->xzc_xzone_idx = xz->xz_idx;
- // Most processes don't get guard pages, so will only allocate one chunk
- // from the segment group. Avoid taking the lock in this common case
- if (SLIST_FIRST(list)) {
- xzm_chunk_t c, t;
- _malloc_lock_lock(&xz->xz_lock);
- SLIST_FOREACH_SAFE(c, list, xzc_slist_entry, t) {
- SLIST_REMOVE_HEAD(list, xzc_slist_entry);
- c->xzc_xzone_idx = xz->xz_idx;
- c->xzc_bits.xzcb_preallocated = true;
- LIST_INSERT_HEAD(&xz->xz_chunkq_preallocated, c, xzc_entry);
- }
- _malloc_lock_unlock(&xz->xz_lock);
- }
- }
-
- xzm_debug_assert(chunk);
- _xzm_xzone_small_chunk_init(xz, chunk);
-
- return chunk;
-}
-
-static xzm_slice_t
-_xzm_chunk_find_dirtiest_slice(xzm_malloc_zone_t zone, xzm_chunk_t chunk,
- uint32_t *slice_free_bitmap)
-{
- // Attempt to find the dirtiest slice with at least one free block
- xzm_slice_count_t slice_count = _xzm_chunk_slice_count(chunk);
- struct xzm_slice_s *slices = _xzm_chunk_slices_of(chunk, slice_count);
- uint32_t lowest_free = UINT32_MAX;
- xzm_slice_t best_slice = NULL;
- for (xzm_slice_count_t i = 0; i < slice_count; ++i) {
- xzm_slice_t slice = &slices[i];
- uint32_t slice_mask = _xzm_xzone_slice_free_mask(zone, slice);
- uint32_t slice_free = chunk->xzc_free & slice_mask;
- if (slice_free && slice_free != slice_mask) {
- // This slice contains at least one used and one free block
- // TODO: consider replacing popcount with table (max blocks per
- // slice is bounded)
- uint32_t free_count = __builtin_popcount(slice_free);
- if (free_count < lowest_free) {
- lowest_free = free_count;
- best_slice = slice;
- *slice_free_bitmap = slice_free;
- }
- }
- }
- return best_slice;
-}
-
-// mimalloc: _mi_page_malloc
-static void *
-_xzm_xzone_alloc_from_chunk(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, bool *is_zero_out)
-{
- xzm_debug_assert(!_xzm_chunk_is_full(zone, xz, chunk));
-
- *is_zero_out = false;
-
- uintptr_t block;
- xzm_block_index_t block_index = UINT32_MAX;
- uintptr_t start = _xzm_chunk_start(zone, chunk, NULL);
- const xzm_segment_t segment = _xzm_segment_for_slice(zone, chunk);
- const size_t block_size = _xzm_chunk_block_size(zone, chunk);
-
- switch (chunk->xzc_bits.xzcb_kind) {
- case XZM_SLICE_KIND_SMALL_CHUNK:
- // TODO: Find a way to determine if this block is zeroed to
- // avoid excessive zeroing
- if (_xzm_chunk_is_empty(zone, xz, chunk) ||
- (block_size % XZM_SEGMENT_SLICE_SIZE) == 0) {
- // It is impossible for a free block to exist on a dirty slice for
- // this chunk -- don't bother searching for one
- block_index = __builtin_ffs(chunk->xzc_free) - 1;
- } else {
- uint32_t slice_free;
- xzm_slice_t best_slice = _xzm_chunk_find_dirtiest_slice(zone, chunk,
- &slice_free);
-
- if (best_slice != NULL) {
- block_index = __builtin_ffs(slice_free) - 1;
-
- if (slice_free && !powerof2(slice_free)) {
- // This slice has more than one free block; check
- // if the one we selected would dirty multiple slices
- uintptr_t block_start = start +
- (block_index + xz->xz_block_size);
- xzm_slice_t start_slice = _xzm_segment_slice_of(segment,
- block_start);
- if (start_slice < best_slice) {
- // This block would dirty multiple slices; choose the
- // next free block instead. Note that the next free
- // block may also dirty multiple slices; this implies
- // that there are no free blocks in between that might
- // dirty fewer slices.
- block_index = __builtin_ffs(slice_free &
- ~(1u << block_index)) - 1;
- }
- }
- } else {
- // Unable to find a dirty slice, fall back to the first
- // available free block
- block_index = __builtin_ffs(chunk->xzc_free) - 1;
- }
- }
-
- xzm_debug_assert(block_index <= xz->xz_chunk_capacity);
- block = start + (block_index * xz->xz_block_size);
-
-
- // Mark the current block as not free
- chunk->xzc_free &= ~(1u << block_index);
-
- break;
- default:
- xzm_abort_with_reason("attempting to allocate from chunk of bad kind",
- (unsigned int)chunk->xzc_bits.xzcb_kind);
- }
-
- chunk->xzc_used++;
-
- return (void *)block;
-}
-
-static void
-_xzm_xzone_upgrade_small_slot_config(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_xzone_allocation_slot_t xas, xzm_slot_config_t slot_config)
-{
- // This allocation slot is hot, upgrade this xzone's slot config
- // for subsequent allocations and reset its contention counters
- xas->xas_contentions = 0;
-
- // Walk all the other allocation slots and reset their contention counters
- xzm_allocation_index_t limit_idx = _xzm_get_limit_allocation_index(slot_config);
- if (limit_idx > 1) {
- _malloc_lock_unlock(&xas->xas_lock);
- for (xzm_allocation_index_t alloc_idx = 0;
- alloc_idx < limit_idx;
- alloc_idx++) {
- xzm_xzone_allocation_slot_t xas_other =
- _xzm_xzone_allocation_slot_for_index(zone, xz,
- alloc_idx);
- if (xas_other != xas) {
- _malloc_lock_lock(&xas_other->xas_lock);
- xas_other->xas_contentions = 0;
- _malloc_lock_unlock(&xas_other->xas_lock);
- }
- }
- _malloc_lock_lock(&xas->xas_lock);
- }
-
- _malloc_lock_lock(&xz->xz_lock);
- // Another thread may have already upgraded the slot config
- if (xz->xz_slot_config == slot_config) {
- switch (xz->xz_slot_config) {
- case XZM_SLOT_SINGLE:
-#if CONFIG_XZM_CLUSTER_AWARE
- if (ncpuclusters > 1) {
- xz->xz_slot_config = XZM_SLOT_CLUSTER;
- } else {
- xz->xz_slot_config = XZM_SLOT_CPU;
- }
-#else // CONFIG_XZM_CLUSTER_AWARE
- xz->xz_slot_config = XZM_SLOT_CLUSTER;
-#endif // CONFIG_XZM_CLUSTER_AWARE
- break;
- case XZM_SLOT_CLUSTER:
- xz->xz_slot_config = XZM_SLOT_CPU;
- break;
- case XZM_SLOT_CPU:
- xzm_abort("Can't upgrade from XZM_SLOT_CPU");
- break;
- default:
- xzm_abort("Invalid xzone slot config");
- break;
- }
- xzm_debug_assert(xz->xz_slot_config <= zone->xzz_max_slot_config);
- }
- _malloc_lock_unlock(&xz->xz_lock);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_malloc_small(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_malloc_options_t opt)
-{
- bool clear = (opt & XZM_MALLOC_CLEAR);
- void *ptr = NULL;
- bool is_zero = false;
- xzm_slot_config_t slot_config;
-
- xzm_allocation_index_t alloc_idx = _xzm_get_allocation_index(zone, xz,
- &slot_config, true);
- xzm_xzone_allocation_slot_t xas =
- _xzm_xzone_allocation_slot_for_index(zone, xz, alloc_idx);
-
- if (slot_config < zone->xzz_max_slot_config) {
- // This xzone's slot config is upgradable -- evaluate for contention
- if (!_malloc_lock_trylock(&xas->xas_lock)) {
- // Contended for this allocation slot
- _malloc_lock_lock(&xas->xas_lock);
- xas->xas_contentions++;
- if (xas->xas_contentions > zone->xzz_slot_upgrade_threshold[slot_config]) {
- _xzm_xzone_upgrade_small_slot_config(zone, xz, xas, slot_config);
- }
- }
- xas->xas_allocs++;
- // Reset the contention counter every T allocations -- this prevents
- // low-traffic xzones in long-lived processes from being upgraded over
- // time
- if ((xas->xas_allocs % zone->xzz_slot_upgrade_period) == 0) {
- xas->xas_contentions = 0;
- }
- } else {
- _malloc_lock_lock(&xas->xas_lock);
-#ifdef DEBUG
- xas->xas_allocs++;
-#endif
- }
-
- xzm_chunk_t chunk = xas->xas_chunk;
- if (!chunk || _xzm_chunk_is_full(zone, xz, chunk)) {
- // We need to go to the xzone to get a new chunk
- _malloc_lock_lock(&xz->xz_lock);
-
- if (chunk) {
- // If we have an existing chunk that's full, we need to uninstall it and
- // move it to the full list
- LIST_INSERT_HEAD(&xz->xz_chunkq_full, chunk, xzc_entry);
-
- // This is the final step of uninstallation - as soon as the chunk
- // transitions to the uninstalled state, other threads freeing to it
- // will go to its lock instead of the lock for this slot. Store
- // release to publish all of the changes made to the chunk under the
- // slot lock, which will pair with the acquire when they take the
- // chunk lock
- os_atomic_store(&chunk->xzc_alloc_idx, XZM_SLOT_INDEX_EMPTY,
- release);
- }
-
- while ((chunk = LIST_FIRST(&xz->xz_chunkq_partial))) {
- _malloc_lock_lock(&chunk->xzc_lock);
- xzm_debug_assert(chunk->xzc_bits.xzcb_on_partial_list);
- LIST_REMOVE(chunk, xzc_entry);
- chunk->xzc_bits.xzcb_on_partial_list = false;
- if (chunk->xzc_used) {
- xzm_debug_assert(!_xzm_chunk_is_full(zone, xz, chunk));
- // We can use this chunk! Install it. We hold both the slot
- // and chunk locks now so threads that are freeing to this chunk
- // are guaranteed visibility of our modifications regardless of
- // what they observe for the slot index.
- xas->xas_chunk = chunk;
- os_atomic_store(&chunk->xzc_alloc_idx, alloc_idx + 1, relaxed);
- _malloc_lock_unlock(&chunk->xzc_lock);
- break;
- } else {
- // This chunk is fully free, and the thread that freed it is on
- // its way to remove it, so we don't want to use it. We've
- // pulled it off the partial list so nobody else spends time on
- // it and marked it accordingly so that the freeing thread
- // knows.
- _malloc_lock_unlock(&chunk->xzc_lock);
- }
- }
-
- // Attempt to reuse a chunk from the batch list
- if (!chunk && xz->xz_chunkq_batch_count) {
- chunk = xz->xz_chunkq_batch;
- xzm_debug_assert(chunk);
- xz->xz_chunkq_batch = *_xzm_segment_slice_meta_batch_next(zone,
- chunk);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone,
- xz->xz_chunkq_batch));
-#if CONFIG_XZM_DEFERRED_RECLAIM
- *_xzm_slice_meta_reclaim_id(zone, chunk) = VM_RECLAIM_ID_NULL;
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- --xz->xz_chunkq_batch_count;
- xas->xas_chunk = chunk;
- os_atomic_store(&chunk->xzc_alloc_idx, alloc_idx + 1, relaxed);
- }
-
- // We're done with the xzone now - either we got a partial chunk from
- // it, or we didn't and we need to get a sequestered or fresh one
- _malloc_lock_unlock(&xz->xz_lock);
-
- if (!chunk && xz->xz_sequestered) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- chunk = _xzm_xzone_allocate_chunk_from_isolation(main, xz);
-
- if (chunk) {
- // Now install the chunk to the slot (TODO: does this need to be
- // done under the iz lock? TODO: does this need to be done
- // before populating in exclaves?)
- xas->xas_chunk = chunk;
- os_atomic_store(&chunk->xzc_alloc_idx, alloc_idx + 1, relaxed);
- }
- }
-
- if (!chunk) {
- chunk = _xzm_xzone_small_chunk_alloc(zone, xz);
- if (chunk) {
- xzm_debug_assert(!_xzm_chunk_is_full(zone, xz, chunk));
- // Succeeded at allocating a chunk. Install it to the slot.
- xas->xas_chunk = chunk;
- os_atomic_store(&chunk->xzc_alloc_idx, alloc_idx + 1, relaxed);
- } else {
- // There's no other way for us to get a chunk, so we need to
- // give up.
- xas->xas_chunk = NULL;
- goto out;
- }
- }
- }
-
- xzm_debug_assert(xas->xas_chunk == chunk);
- xzm_debug_assert(!_xzm_chunk_is_full(zone, xz, chunk));
-
- ptr = _xzm_xzone_alloc_from_chunk(zone, xz, chunk, &is_zero);
- xzm_debug_assert(ptr);
-
-out:
- _malloc_lock_unlock(&xas->xas_lock);
-
- if (os_likely(ptr)) {
-#if CONFIG_MTE
- bool memtag_enabled = xz->xz_tagged;
- bool needs_canonical_tagging = (opt & XZM_MALLOC_CANONICAL_TAG);
- if (memtag_enabled) {
- if (needs_canonical_tagging) {
- // We need this specific block to have canonical tagging (i.e. tag = 0)
- ptr = memtag_tag_canonical(ptr, xz->xz_block_size);
- } else {
- ptr = _xzm_xzone_block_memtag_retag(zone, ptr,
- xz->xz_block_size);
- }
- }
-#endif
-
- // TODO: MallocCheckZeroOnFreeCorruption support
- if (!is_zero && clear &&
- xz->xz_block_size > XZM_ZERO_ON_FREE_THRESHOLD) {
- bzero(ptr, xz->xz_block_size);
- }
- } else {
- malloc_set_errno_fast(MZ_POSIX, ENOMEM);
- }
- return ptr;
-}
-
-#pragma mark General allocation
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static bool
-_xzm_xzone_try_reserve_early_budget(xzm_malloc_zone_t zone, xzm_xzone_t xz)
-{
- uint16_t early_budget = os_atomic_load(&xz->xz_early_budget, relaxed);
- uint16_t updated_budget;
- do {
- if (os_likely(!early_budget)) {
- return false;
- }
-
- updated_budget = early_budget - 1;
- } while (!os_atomic_cmpxchgv(&xz->xz_early_budget, early_budget,
- updated_budget, &early_budget, relaxed));
-
- return true;
-}
-
-#ifdef DEBUG
-// mimalloc: mi_mem_is_zero
-static bool
-_xzm_mem_is_zero(uint8_t *ptr, size_t size)
-{
- return !_malloc_memcmp_zero_aligned8(ptr, size);
-}
-#endif // DEBUG
-
-#if CONFIG_XZM_THREAD_CACHE
-
-static void *
-_xzm_xzone_malloc_tiny_or_early(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_malloc_options_t opt)
-{
- if (!(opt & XZM_MALLOC_NO_MFM) &&
- _xzm_xzone_try_reserve_early_budget(zone, xz)) {
- void *ptr = mfm_alloc(xz->xz_block_size);
- xzm_debug_assert(ptr);
-#ifdef DEBUG
- if (opt & XZM_MALLOC_CLEAR) {
- xzm_debug_assert(_xzm_mem_is_zero(ptr, xz->xz_block_size));
- }
-#endif
- return ptr;
- }
-
- void *ptr = _xzm_xzone_malloc_tiny(zone, xz, 0);
-#ifdef DEBUG
- if (opt & XZM_MALLOC_CLEAR) {
- xzm_debug_assert(_xzm_mem_is_zero(ptr, xz->xz_block_size));
- }
-#endif
- return ptr;
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_thread_cache_malloc_corrupt(void *corrupt_block)
-{
- // This looks kind of weird, but I couldn't convince the optimizer not to
- // push a frame in _xzm_xzone_malloc() if I put the abort here directly, so
- // we need to launder it apparently
- return _xzm_xzone_malloc_freelist_outlined(NULL, NULL, 0, NULL, corrupt_block,
- 0);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_thread_cache_fill_and_malloc(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_xzone_thread_cache_t cache)
-{
- void *ptr = NULL;
- bool contended = false;
- if (cache->xztc_state != XZM_XZONE_CACHE_EMPTY) {
- xzm_debug_assert(cache->xztc_head == XZM_FREE_NULL);
-
- ptr = _xzm_xzone_malloc_from_freelist_chunk(zone, xz,
- XZM_SLOT_INDEX_THREAD, cache, cache->xztc_chunk, false,
- &contended, NULL);
- if (ptr) {
- goto done;
- }
- }
-
- xzm_debug_assert(cache->xztc_state == XZM_XZONE_CACHE_EMPTY);
-
- xzm_chunk_t chunk = NULL;
- ptr = _xzm_xzone_find_and_malloc_from_freelist_chunk(zone, xz,
- XZM_SLOT_INDEX_THREAD, cache, &chunk, &contended);
-
-done:
- if (ptr) {
- xzm_block_t block = ptr;
- *block = (struct xzm_block_inline_meta_s){ 0 };
- } else {
- xzm_debug_assert(cache->xztc_state == XZM_XZONE_CACHE_EMPTY);
- malloc_set_errno_fast(MZ_POSIX, ENOMEM);
- }
-
- return ptr;
-}
-
-static void
-_xzm_thread_cache_create(xzm_malloc_zone_t zone)
-{
- xzm_main_malloc_zone_t main = (xzm_main_malloc_zone_t)zone;
- xzm_metapool_t mp = &main->xzmz_metapools[XZM_METAPOOL_THREAD_CACHE];
- xzm_thread_cache_t tc = xzm_metapool_alloc(mp);
-
- *tc = (struct xzm_thread_cache_s){
- .xtc_main = main,
- .xtc_thread = pthread_self(),
- };
-
- uint64_t now = mach_absolute_time();
- for (size_t i = 0; i < zone->xzz_thread_cache_xzone_count; i++) {
- tc->xtc_xz_caches[i] = (xzm_xzone_thread_cache_u){
- .xztc_state = XZM_XZONE_NOT_CACHED,
- .xztc_timestamp = now,
- };
- }
-
- _malloc_lock_lock(&main->xzmz_thread_cache_list_lock);
- LIST_INSERT_HEAD(&main->xzmz_thread_cache_list, tc, xtc_linkage);
- _malloc_lock_unlock(&main->xzmz_thread_cache_list_lock);
-
- _pthread_setspecific_direct(__TSD_MALLOC_XZONE_THREAD_CACHE, tc);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_thread_cache_create_and_malloc(xzm_malloc_zone_t zone,
- xzm_xzone_index_t xz_idx, xzm_xzone_t xz, xzm_malloc_options_t opt)
-{
- _xzm_thread_cache_create(zone);
-
- return _xzm_xzone_malloc_tiny_or_early(zone, xz, opt);
-}
-
-MALLOC_NOINLINE
-static void *
-_xzm_xzone_thread_cache_record_and_malloc_outlined(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_malloc_options_t opt,
- xzm_xzone_thread_cache_t cache)
-{
- xzm_debug_assert(cache->xztc_head == XZM_XZONE_NOT_CACHED);
-
- uint64_t now = mach_absolute_time();
- uint64_t activation_time = zone->xzz_thread_cache_xzone_activation_time;
- if (now - cache->xztc_timestamp < activation_time) {
- // We reached the threshold number of allocations within the
- // activation threshold time, so activate caching for this xzone
- cache->xztc_head = XZM_XZONE_CACHE_EMPTY;
-
- // Initialize the seqno for this cache with the low bits of the
- // timestamp to add some non-determinism to its base
- cache->xztc_seqno = cache->xztc_timestamp & XZM_SEQNO_COUNTER_MASK;
-
- return _xzm_xzone_thread_cache_fill_and_malloc(zone, xz, cache);
- }
-
- // reset for the next period
- cache->xztc_timestamp = now;
- cache->xztc_allocs = 0;
- cache->xztc_contentions = 0;
-
- return _xzm_xzone_malloc_tiny_or_early(zone, xz, opt);
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void *
-_xzm_xzone_thread_cache_record_and_malloc(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_malloc_options_t opt,
- xzm_xzone_thread_cache_t cache)
-{
- xzm_debug_assert(cache->xztc_head == XZM_XZONE_NOT_CACHED);
-
- cache->xztc_allocs++;
-
- uint32_t activation_period = zone->xzz_thread_cache_xzone_activation_period;
- if (os_unlikely(cache->xztc_allocs == activation_period)) {
- return _xzm_xzone_thread_cache_record_and_malloc_outlined(zone, xz, opt,
- cache);
- }
-
- return _xzm_xzone_malloc_tiny_or_early(zone, xz, opt);
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void
-_xzm_xzone_thread_cache_record_contention(xzm_malloc_zone_t zone,
- xzm_xzone_t xz, xzm_xzone_thread_cache_t cache)
-{
- // We can end up here after caching is already engaged if serving an
- // allocation that can't be handled from the cache
- if (cache->xztc_head != XZM_XZONE_NOT_CACHED) {
- return;
- }
-
- cache->xztc_contentions++;
-
- uint32_t activation_contentions =
- zone->xzz_thread_cache_xzone_activation_contentions;
- if (os_unlikely(cache->xztc_contentions == activation_contentions)) {
- // We reached the threshold number of contentions within the threshold
- // period, so activate caching for this xzone
- cache->xztc_head = XZM_XZONE_CACHE_EMPTY;
-
- // Initialize the seqno for this cache with the low bits of the
- // timestamp to add some non-determinism to its base
- cache->xztc_seqno = cache->xztc_timestamp & XZM_SEQNO_COUNTER_MASK;
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void *
-_xzm_xzone_thread_cache_malloc(xzm_malloc_zone_t zone, xzm_xzone_index_t xz_idx,
- xzm_xzone_t xz, xzm_malloc_options_t opt)
-{
- xzm_thread_cache_t tc = _xzm_get_thread_cache();
- if (os_unlikely(!tc)) {
- return _xzm_thread_cache_create_and_malloc(zone, xz_idx, xz, opt);
- }
-
- xzm_debug_assert(xz_idx < zone->xzz_thread_cache_xzone_count);
- xzm_xzone_thread_cache_t cache = &tc->xtc_xz_caches[xz_idx];
- if (cache->xztc_head < XZM_FREE_LIMIT) {
- uint8_t *start = cache->xztc_chunk_start;
- void *ptr = start + (cache->xztc_head * XZM_GRANULE);
-
-#if CONFIG_MTE
- if (_xzm_zone_memtag_enabled(zone)) {
- ptr = memtag_fixup_ptr(ptr);
- }
-#endif
- struct xzm_block_inline_meta_s *block_meta_p =
- (struct xzm_block_inline_meta_s *)ptr;
- struct xzm_block_inline_meta_s block_meta = *block_meta_p;
-
- // Check the PAC by adding the seqno back
- union xzm_block_linkage_u linkage = {
- .xzbl_next_offset = block_meta.xzb_linkage.xzbl_next_offset,
- .xzbl_next_seqno = block_meta.xzb_linkage.xzbl_next_seqno,
- .xzbl_seqno = cache->xztc_head_seqno,
- };
-
- linkage.xzbl_next_value = (uintptr_t)ptrauth_sign_unauthenticated(
- linkage.xzbl_next, ptrauth_key_process_dependent_data,
- ptrauth_blend_discriminator(ptr,
- ptrauth_string_discriminator("xzb_linkage")));
-
- // Take the seqno back out of the signed value for the comparison with
- // the stored one
- linkage.xzbl_seqno = 0;
-
- if (os_unlikely(block_meta.xzb_linkage.xzbl_next_value !=
- linkage.xzbl_next_value)) {
- return _xzm_xzone_thread_cache_malloc_corrupt(ptr);
- }
-
- *block_meta_p = (struct xzm_block_inline_meta_s){ 0 };
-
-#ifdef DEBUG
- uint64_t orig_state = cache->xztc_freelist_state;
-#endif
-
- // We update the local freelist head and count together with an atomic
- // store so that the enumerator gets a consistent picture of the
- // freelist. Having the head and the count agree is required in order
- // to handle the bump correctly:
- // - If the head were updated before the count, we'd think the bump
- // offset was higher than its true value, so we'd falsely report the
- // block at the bump offset as allocated when it isn't
- // - If the head were updated after the count, we'd think the bump
- // offset was lower than its true value, so we'd falsely report the
- // last block before the bump as free when it may not be
- xzm_xzone_thread_cache_atomic_meta_u tc_meta = {
- .xztcam_head = block_meta.xzb_linkage.xzbl_next_offset,
- .xztcam_free_count = cache->xztc_free_count - 1,
- };
- os_atomic_store(&cache->xztc_atomic_meta.xztcam_value,
- tc_meta.xztcam_value, relaxed);
- cache->xztc_head_seqno = block_meta.xzb_linkage.xzbl_next_seqno;
-
-#ifdef DEBUG
- uint64_t new_state = cache->xztc_freelist_state;
-#endif
-
- xzm_trace(thread_cache_malloc, (uint64_t)cache->xztc_chunk, orig_state,
- new_state, 0);
-
- return ptr;
- } else if (cache->xztc_head == XZM_FREE_NULL && cache->xztc_free_count) {
- uint8_t *start = cache->xztc_chunk_start;
- size_t capacity = xz->xz_chunk_capacity;
- void *ptr = start + ((capacity - cache->xztc_free_count) *
- xz->xz_block_size);
-#if CONFIG_MTE
- if (_xzm_zone_memtag_enabled(zone)) {
- ptr = memtag_fixup_ptr(ptr);
- }
-#endif
-
- struct xzm_block_inline_meta_s *block_meta_p =
- (struct xzm_block_inline_meta_s *)ptr;
- *block_meta_p = (struct xzm_block_inline_meta_s){ 0 };
-
-#ifdef DEBUG
- uint64_t orig_state = cache->xztc_freelist_state;
-#endif
-
- cache->xztc_free_count--;
-
-#ifdef DEBUG
- uint64_t new_state = cache->xztc_freelist_state;
-#endif
-
- xzm_trace(thread_cache_malloc, (uint64_t)cache->xztc_chunk, orig_state,
- new_state, 0);
-
- return ptr;
- } else if (cache->xztc_head == XZM_XZONE_NOT_CACHED) {
- return _xzm_xzone_thread_cache_record_and_malloc(zone, xz, opt, cache);
- } else {
- xzm_debug_assert(cache->xztc_head == XZM_FREE_NULL ||
- cache->xztc_head == XZM_XZONE_CACHE_EMPTY);
- return _xzm_xzone_thread_cache_fill_and_malloc(zone, xz, cache);
- }
-}
-
-#endif // CONFIG_XZM_THREAD_CACHE
-
-static void *
-_xzm_xzone_malloc(xzm_malloc_zone_t zone, size_t size,
- xzm_xzone_index_t xz_idx, xzm_malloc_options_t opt)
-{
- void *ptr = NULL;
-
- xzm_xzone_t xz = &zone->xzz_xzones[xz_idx];
-
-#if CONFIG_XZM_THREAD_CACHE
- // First, try thread cache
- if (zone->xzz_thread_cache_enabled &&
- size <= XZM_THREAD_CACHE_THRESHOLD
-#if CONFIG_MTE
- && !(opt & XZM_MALLOC_CANONICAL_TAG)
-#endif
- ) {
- return _xzm_xzone_thread_cache_malloc(zone, xz_idx, xz, opt);
- }
-#endif // CONFIG_XZM_THREAD_CACHE
-
- if (!(opt & XZM_MALLOC_NO_MFM) && _xzm_malloc_zone_is_main(zone) &&
-#if CONFIG_MTE
- // With memory tagging enabled, we shouldn't call into the early
- // allocator if we're being asked for a canonically tagged
- // allocation, as we don't support that in MFM.
- !((opt & XZM_MALLOC_CANONICAL_TAG) && zone->xzz_memtag_config.enabled) &&
-#endif // CONFIG_MTE
- _xzm_xzone_try_reserve_early_budget(zone, xz)) {
- // To maintain compatibility with code expecting
- // malloc_size(malloc(size)) >= malloc_good_size(size), just always use
- // the block size
- ptr = mfm_alloc(xz->xz_block_size);
- // The way that we use the early allocator, it can't ever legitimately
- // fail, and we're depending on that here (to tail-call)
- xzm_debug_assert(ptr);
- return ptr;
- }
-
- if (size <= XZM_TINY_BLOCK_SIZE_MAX) {
- return _xzm_xzone_malloc_tiny(zone, xz, opt);
- } else if (zone->xzz_small_freelist_enabled) {
- return _xzm_xzone_malloc_small_freelist(zone, xz, opt);
- }
-
- xzm_debug_assert(size <= XZM_SMALL_BLOCK_SIZE_MAX);
- return _xzm_xzone_malloc_small(zone, xz, opt);
-}
-
-static size_t
-xzm_malloc_zone_size(xzm_malloc_zone_t zone, const void *ptr);
-
-void *
-xzm_malloc_inline(xzm_malloc_zone_t zone, size_t size,
- malloc_type_descriptor_t type_desc, xzm_malloc_options_t opt)
-{
- void *ptr;
- if (size > XZM_SMALL_BLOCK_SIZE_MAX) {
- ptr = _xzm_malloc_large_huge(zone, size, 0, type_desc, opt);
- } else {
- xzm_debug_assert((zone->xzz_flags & MALLOC_PURGEABLE) == 0);
- xzm_xzone_index_t xz_idx = _xzm_xzone_lookup(zone, size, type_desc);
- ptr = _xzm_xzone_malloc(zone, size, xz_idx, opt);
- }
-
-#ifdef DEBUG
- xzm_debug_assert(ptr);
- if (opt & XZM_MALLOC_CLEAR) {
- xzm_debug_assert(_xzm_mem_is_zero(ptr, size));
- }
-#if CONFIG_MTE
- // The `size` parameter means "requested size", but we make tagging decisions
- // based on block size. Note that in the special case of allocations with
- // large alignments we may get an incorrect answer from
- // _xzm_zone_memtag_block(), since it can't account for the segment group
- // min block size, but that case doesn't take this path so the assert here
- // is still safe.
- size_t block_size = xzm_malloc_zone_size(zone, ptr);
- bool data = malloc_type_descriptor_is_pure_data(type_desc);
- bool memtag = _xzm_zone_memtag_block(zone, block_size, data);
- bool canonical_tag = (opt & XZM_MALLOC_CANONICAL_TAG);
- if (!mfm_claimed_address(ptr)) {
- if (memtag && !canonical_tag) {
- xzm_debug_assert(memtag_strip_address(ptr) != ptr);
- } else {
- xzm_debug_assert(memtag_strip_address(ptr) == ptr);
- }
- }
-#endif // CONFIG_MTE
-#endif // DEBUG
- return ptr;
-}
-
-MALLOC_NOINLINE
-void *
-xzm_malloc(xzm_malloc_zone_t zone, size_t size,
- malloc_type_descriptor_t type_desc, xzm_malloc_options_t opt)
-{
- return xzm_malloc_inline(zone, size, type_desc, opt);
-}
-
-// mimalloc: mi_heap_malloc_aligned
-static void * __alloc_align(2) __alloc_size(3)
-_xzm_memalign(xzm_malloc_zone_t zone, size_t alignment, size_t size,
- malloc_type_descriptor_t type_desc, xzm_malloc_options_t opt)
-{
- // - Assumption: alignment is a power of 2
- // - Assumption: all xzm size classes are aligned to their MSb/4
- // (e.g. the 3548-byte size class is aligned to 2048/4=512)
- // - Assumption: all power-of-2 size classes less than or equal to page size
- // are naturally aligned
- //
- // With those, the following definition of a memalign function will always
- // return properly aligned memory (for values of size/align <= page size)
- // memalign(align, size) =
- // { alloc(align) , size <= align
- // { alloc(2 * align) , align < size <= 2 * align
- // { alloc(roundup(size, 4 * align)) , 2 * align < size < 4 * align
- // { alloc(size) , 4 * align <= size
- //
- // The mimalloc memalign implementation (roundup(alloc(size + align - 1)))
- // has a fixed internal fragmentation of align - 1. For the definition
- // above, the internal fragmentation is:
- // { align - size , size <= align
- // { 2 * align - size , align < size <= 2 * align
- // { roundup(size, 4 * align) <= 2 * align , 2 * align < size < 4 * align
- // { 0 , 4 * align <= size
- //
- // In all but the third case, the internal fragmentation should be strictly
- // better than the original implementation. An example of the third case
- // being worse is memalign(2k, 5k), which will require an 8k allocation
- // under the new scheme, but a 7k allocation under the original.
- // Empirically this case seems rare enough that wins from the other three
- // cases make up for it.
-
- xzm_debug_assert(powerof2(alignment)); // should be guaranteed
-
- // The early allocator doesn't support alignment
- opt |= XZM_MALLOC_NO_MFM;
-
- void *ptr;
- if (size > XZM_SMALL_BLOCK_SIZE_MAX ||
- alignment > XZM_SEGMENT_SLICE_SIZE) {
- ptr = _xzm_malloc_large_huge(zone, size, alignment, type_desc, opt);
- } else if (size <= alignment) {
- xzm_debug_assert(alignment <= XZM_SMALL_BLOCK_SIZE_MAX);
- ptr = xzm_malloc(zone, alignment, type_desc, opt);
- } else if (size <= 2 * alignment) {
- xzm_debug_assert(2 * alignment <= XZM_SMALL_BLOCK_SIZE_MAX);
- ptr = xzm_malloc(zone, 2 * alignment, type_desc, opt);
- } else if (size < 4 * alignment) {
- xzm_debug_assert(roundup(size, 4 * alignment) <=
- XZM_SMALL_BLOCK_SIZE_MAX);
- ptr = xzm_malloc(zone, roundup(size, 4 * alignment), type_desc, opt);
- } else {
- xzm_debug_assert(size <= XZM_SMALL_BLOCK_SIZE_MAX);
- ptr = xzm_malloc(zone, size, type_desc, opt);
- }
-
- xzm_debug_assert(ptr);
- xzm_debug_assert((uintptr_t)ptr % alignment == 0);
- return ptr;
-}
-
-void *
-xzm_memalign(xzm_malloc_zone_t zone, size_t alignment, size_t size,
- malloc_type_descriptor_t type_desc, xzm_malloc_options_t opt)
-{
- return _xzm_memalign(zone, alignment, size, type_desc, opt);
-}
-
-#pragma mark Small deallocation
-
-static void
-_xzm_xzone_chunk_free(xzm_malloc_zone_t zone, xzm_xzone_t xz, xzm_chunk_t chunk,
- bool small_madvise_needed);
-
-static void
-_xzm_xzone_small_chunks_mark_empty(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t *chunks, size_t chunk_count)
-{
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- xzm_isolation_zone_t iz = &main->xzmz_isolation_zones[xz->xz_idx];
- const bool sequestered = xz->xz_sequestered;
-
- if (sequestered) {
- _malloc_lock_lock(&iz->xziz_lock);
- }
-
- for (size_t i = 0; i < chunk_count; ++i) {
- xzm_chunk_t chunk = chunks[i];
-
- // See _xzm_xzone_chunk_free
- chunk->xzc_mzone_idx = XZM_MZONE_INDEX_INVALID;
-
- if (sequestered) {
- // Reset such that the chunk is ready for next use immediately
- _xzm_chunk_reset_free(xz, chunk, true);
-
- // This chunk is no longer pristine
- chunk->xzc_bits.xzcb_is_pristine = false;
-
- LIST_INSERT_HEAD(&iz->xziz_chunkq, chunk, xzc_entry);
- } else {
- // Full reset - the chunk's span may be reused for anything
- _xzm_chunk_reset_free(xz, chunk, false);
-
- // Do not madvise again
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone,
- chunk);
- xzm_segment_group_free_chunk(sg, chunk, false, false);
- }
- }
-
- if (sequestered) {
- _malloc_lock_unlock(&iz->xziz_lock);
- }
-}
-
-static xzm_block_t
-_xzm_xzone_free_to_chunk(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, xzm_block_t block)
-{
-#if CONFIG_MTE
- const bool memtag_enabled = xz->xz_tagged;
-#endif
-
- switch(chunk->xzc_bits.xzcb_kind) {
- case XZM_SLICE_KIND_SMALL_CHUNK:
-#if CONFIG_MTE
- if (memtag_enabled) {
- block = (xzm_block_t)memtag_strip_address((uint8_t *)block);
- }
-#endif
- chunk->xzc_free |= (1u << _xzm_chunk_block_index(zone, chunk, block));
- break;
- default:
- xzm_abort_with_reason("Attempting to free to non-chunk slice",
- (unsigned int)chunk->xzc_bits.xzcb_kind);
- }
-
- chunk->xzc_used--;
-
- return block;
-}
-
-static void
-_xzm_xzone_chunk_free(xzm_malloc_zone_t zone, xzm_xzone_t xz, xzm_chunk_t chunk,
- bool small_madvise_needed)
-{
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone, chunk);
-
- // Reset mzone_idx first to "unpublish" the chunk for the enumerator
- // protocol
- //
- // TODO: compiler barrier
- chunk->xzc_mzone_idx = XZM_MZONE_INDEX_INVALID;
-
- if (xz->xz_sequestered) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- if (_xzm_chunk_should_defer_reclamation(main, chunk)) {
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (_xzm_segment_slice_is_deferred(_xzm_segment_for_slice(zone,
- chunk), chunk)) {
- // The only reason a chunk should already be in the reclaim
- // buffer when passed to this function is if it's a tiny chunk
- // and the zone is being destroyed, or it's a small chunk that's
- // being freed from the batch
- xzm_debug_assert(
- ((chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) &&
- chunk->xzc_atomic_meta.xca_alloc_head == XZM_FREE_MADVISED) ||
- (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_CHUNK &&
- !chunk->xzc_bits.xzcb_on_partial_list));
- } else {
- xzm_chunk_mark_free(zone, chunk);
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- } else {
- bool should_madvise = true;
- if (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- if (chunk->xzc_atomic_meta.xca_alloc_head == XZM_FREE_MADVISED) {
- // The chunk is already madvised, no point doing it again
- should_madvise = false;
- } else {
- chunk->xzc_atomic_meta.xca_alloc_head = XZM_FREE_MADVISED;
- chunk->xzc_atomic_meta.xca_free_count = 0;
- }
- } else if (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_CHUNK) {
- should_madvise = small_madvise_needed;
- } else {
- xzm_abort_with_reason("Unexpected chunk kind",
- chunk->xzc_bits.xzcb_kind);
- }
-
- if (should_madvise) {
- xzm_segment_group_segment_madvise_chunk(sg, chunk);
- }
- }
-
- // Reset such that the chunk is ready for next use immediately
- _xzm_chunk_reset_free(xz, chunk, true);
-
- // chunk->xzc_used == 0
- // xzc_xzone_idx unchanged
- // xzcb_kind unchanged
- // This chunk is no longer pristine, so bumping doesn't guarantee that
- // the allocation is cleared
- chunk->xzc_bits.xzcb_is_pristine = false;
-
- if (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- // Tiny chunks are only freed during zone destruction, so we
- // can access the atomic metadata directly, since we're the
- // only thread with access to this zone
- chunk->xzc_atomic_meta.xca_on_partial_list = false;
- chunk->xzc_atomic_meta.xca_on_empty_list = true;
- chunk->xzc_atomic_meta.xca_alloc_idx = XZM_SLOT_INDEX_EMPTY;
- }
-
- xzm_isolation_zone_t iz = &main->xzmz_isolation_zones[xz->xz_idx];
-
- _malloc_lock_lock(&iz->xziz_lock);
- LIST_INSERT_HEAD(&iz->xziz_chunkq, chunk, xzc_entry);
- _malloc_lock_unlock(&iz->xziz_lock);
- } else {
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_debug_assert(
- *_xzm_slice_meta_reclaim_id(zone, chunk) == VM_RECLAIM_ID_NULL);
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- // Full reset - the chunk's span may be reused for anything
- _xzm_chunk_reset_free(xz, chunk, false);
-
- // will madvise if needed
- xzm_segment_group_free_chunk(sg, chunk, false, small_madvise_needed);
- }
-}
-
-// Attempt to madvise any slices now freed by a given block
-static void
-_xzm_xzone_chunk_madvise_free_slices(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, xzm_block_t block)
-{
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_CHUNK);
-
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone, chunk);
-
- // Only multi-slice chunks that use a bitmap freelist may
- // madvise individual body slices
- if (chunk->xzc_bits.xzcb_kind != XZM_SLICE_KIND_SMALL_CHUNK ||
- _xzm_segment_group_uses_deferred_reclamation(sg)) {
- // TODO: Support for aggressive deferred reclamation of small
- // slices (rdar://112088639)
- return;
- }
-
-#if CONFIG_MTE
- if (xz->xz_tagged) {
- block = (xzm_block_t)memtag_strip_address((uint8_t *)block);
- }
-#endif // CONFIG_MTE
-
- xzm_slice_count_t slice_idx, num_slices;
- const xzm_segment_t segment = _xzm_segment_for_slice(zone, chunk);
- const xzm_slice_count_t chunk_idx = _xzm_slice_index(segment, chunk);
- const xzm_block_index_t block_idx = _xzm_chunk_block_index(zone, chunk,
- block);
- const size_t block_size = _xzm_chunk_block_size(zone, chunk);
- _xzm_chunk_block_free_slices_on_deallocate(chunk, chunk_idx,
- xz->xz_chunk_capacity, block_idx, block_size, &slice_idx,
- &num_slices);
- // At least one slice is madvisable
- if (num_slices > 0) {
- xzm_debug_assert(slice_idx >= chunk_idx);
- xzm_segment_group_segment_madvise_span(sg, _xzm_segment_start(segment) +
- slice_idx * XZM_SEGMENT_SLICE_SIZE, num_slices);
- }
-}
-
-static void
-_xzm_xzone_batch_small_push(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, size_t batch_size)
-{
-#ifdef DEBUG
- _malloc_lock_assert_owner(&xz->xz_lock);
-#endif
-
- xzm_chunk_t batch = NULL;
- // Empty the batch if it is full
- xzm_debug_assert(xz->xz_chunkq_batch_count <= batch_size);
- if (os_unlikely(xz->xz_chunkq_batch_count >= batch_size)) {
- batch = xz->xz_chunkq_batch;
- xz->xz_chunkq_batch = NULL;
- xz->xz_chunkq_batch_count = 0;
- }
-
- // Insert this chunk at the head of the batch
- *_xzm_segment_slice_meta_batch_next(zone, chunk) = xz->xz_chunkq_batch;
- xz->xz_chunkq_batch = chunk;
- ++xz->xz_chunkq_batch_count;
-
- // Perform the madvise outside the zone lock
- _malloc_lock_unlock(&xz->xz_lock);
-
- if (batch) {
- _xzm_xzone_madvise_batch(zone, xz, batch);
- }
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_xzone_free_block_to_small_chunk(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_chunk_t chunk, xzm_block_t block)
-{
- xzm_allocation_index_t alloc_idx = os_atomic_load(&chunk->xzc_alloc_idx,
- relaxed);
-
- while (true) {
- if (alloc_idx == XZM_SLOT_INDEX_EMPTY) {
- // This chunk does not appear to be installed in a slot, so we
- // should first try to take its chunk lock.
- _malloc_lock_lock(&chunk->xzc_lock);
-
- // It's possible that this chunk was installed while we were waiting
- // for the lock.
- alloc_idx = os_atomic_load(&chunk->xzc_alloc_idx, relaxed);
- if (alloc_idx != XZM_SLOT_INDEX_EMPTY) {
- // We lost the race; this chunk was installed to a slot while
- // we were taking its lock. Unlock it and try again.
- _malloc_lock_unlock(&chunk->xzc_lock);
- continue;
- }
-
- // The chunk is still not installed, so we hold the correct lock to
- // free it.
- bool chunk_was_full = _xzm_chunk_is_full(zone, xz, chunk);
- block = _xzm_xzone_free_to_chunk(zone, xz, chunk, block);
-
- // TODO: consider dropping the chunk lock while
- // madvising (additional madvise synchronization
- // required)
- _xzm_xzone_chunk_madvise_free_slices(zone, xz, chunk, block);
- if (_xzm_chunk_is_empty(zone, xz, chunk)) {
- // This chunk has become empty, so we need to go to the xzone to
- // remove it from the partial list.
-
- // We've just freed the last block and this chunk wasn't
- // installed, so it must be on the partial list (it can't be in
- // the full -> partial transition because that's done under both
- // the chunk and xzone locks).
- xzm_debug_assert(chunk->xzc_bits.xzcb_on_partial_list);
-
- // We need to acquire the xzone lock to remove this chunk from
- // the partial list. We need to drop the chunk lock to avoid a
- // lock inversion with code iterating the partial list looking
- // for a chunk to reuse. Because this chunk is empty, it's
- // guaranteed not to be reused, but it may be removed from the
- // partial list by the time we get there.
- _malloc_lock_unlock(&chunk->xzc_lock);
-
- _malloc_lock_lock(&xz->xz_lock);
-
- // Remove from the partial list, if it's still on it.
- if (chunk->xzc_bits.xzcb_on_partial_list) {
- LIST_REMOVE(chunk, xzc_entry);
- chunk->xzc_bits.xzcb_on_partial_list = false;
- }
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- const uint8_t batch_size = main->xzmz_batch_size;
- if (batch_size) {
- // Enqueue the chunk into the batch, and drop the lock.
- _xzm_xzone_batch_small_push(zone, xz, chunk, batch_size);
- } else {
- // We're done with everything we need from the xzone now.
- _malloc_lock_unlock(&xz->xz_lock);
-
- // madvise and sequester or release back to the segment
- _xzm_xzone_chunk_free(zone, xz, chunk, false);
- }
- } else if (chunk_was_full) {
- // This chunk was full, so we need to go to the xzone to put it
- // on the partial list. Full chunks that aren't installed to a
- // slot are guaranteed to be on the full list. Because this
- // chunk is on the full list, no other threads will be trying to
- // acquire its lock while holding the xzone lock. It is
- // therefore safe to take the xzone lock here while holding the
- // chunk lock.
- _malloc_lock_lock(&xz->xz_lock);
-
- // Remove from the full list
- LIST_REMOVE(chunk, xzc_entry);
- // Place on the partial list
- LIST_INSERT_HEAD(&xz->xz_chunkq_partial, chunk, xzc_entry);
- chunk->xzc_bits.xzcb_on_partial_list = true;
- xzm_debug_assert(!_xzm_chunk_is_full(zone, xz, chunk));
-
- _malloc_lock_unlock(&xz->xz_lock);
- _malloc_lock_unlock(&chunk->xzc_lock);
- } else {
- // Neither empty nor newly unfull, so just unlock.
- _malloc_lock_unlock(&chunk->xzc_lock);
- }
- break;
- } else {
- // This chunk appears to be installed in a slot, so we should first
- // try to lock that slot.
-
- xzm_xzone_allocation_slot_t xas =
- _xzm_xzone_allocation_slot_for_index(zone, xz, alloc_idx - 1);
-
- _malloc_lock_lock(&xas->xas_lock);
-
- xzm_allocation_index_t orig_alloc_idx = alloc_idx;
-
- // It's possible that this chunk was uninstalled (and maybe even
- // installed somewhere else) while we were waiting for the lock.
- alloc_idx = os_atomic_load(&chunk->xzc_alloc_idx, relaxed);
- if (alloc_idx != orig_alloc_idx) {
- // We lost the race; this chunk was uninstalled from this slot while
- // we were taking its lock. Unlock it and try again.
- _malloc_lock_unlock(&xas->xas_lock);
- continue;
- }
-
- // The chunk is installed to the slot we locked, so we can proceed.
- block = _xzm_xzone_free_to_chunk(zone, xz, chunk, block);
-
- // TODO: consider dropping the AS lock while madvising
- // (additional madvise synchronization required)
-
- uint64_t thrash_threshold = zone->xzz_small_thrash_threshold;
- uint64_t thrash_limit_size = zone->xzz_small_thrash_limit_size;
- bool thrash_mitigation = (thrash_threshold &&
- xz->xz_block_size < thrash_limit_size);
- if (!thrash_mitigation) {
- _xzm_xzone_chunk_madvise_free_slices(zone, xz, chunk, block);
- }
-
- if (_xzm_chunk_is_empty(zone, xz, chunk)) {
- if (thrash_mitigation) {
- uint64_t now = mach_absolute_time();
- uint64_t time_elapsed = now - xas->xas_last_chunk_empty_ts;
- xas->xas_last_chunk_empty_ts = now;
-
- if (time_elapsed < thrash_threshold) {
- // We seem to be thrashing, so leave the chunk in place
- _malloc_lock_unlock(&xas->xas_lock);
- break;
- }
- }
-
- // This chunk has become empty and there are other partial
- // pages available, so uninstall it from the slot.
- xas->xas_chunk = NULL;
-
- // Since we've just freed the last block and we hold the
- // slot lock, nobody can have a reference to this chunk
- // right now.
- os_atomic_store(&chunk->xzc_alloc_idx, XZM_SLOT_INDEX_EMPTY,
- relaxed);
-
- // We're done with everything we need from the slot now.
- _malloc_lock_unlock(&xas->xas_lock);
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- const uint8_t batch_size = main->xzmz_batch_size;
- if (batch_size) {
- _malloc_lock_lock(&xz->xz_lock);
- // Enqueue the chunk into the batch, and drop the lock.
- _xzm_xzone_batch_small_push(zone, xz, chunk, batch_size);
- } else {
- bool small_madvise = thrash_mitigation;
- // madvise and sequester or release back to the segment
- _xzm_xzone_chunk_free(zone, xz, chunk, small_madvise);
- }
- } else {
- // The chunk stays installed.
- _malloc_lock_unlock(&xas->xas_lock);
- }
- break;
- }
- }
-}
-
-static bool
-_xzm_xzone_freelist_chunk_lock(xzm_chunk_t chunk,
- xzm_chunk_atomic_meta_u *old_meta_out)
-{
- xzm_chunk_atomic_meta_u old_meta = {
- .xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency),
- };
- xzm_chunk_atomic_meta_u new_meta = { 0 };
- while (true) {
- xzm_debug_assert(!old_meta.xca_walk_locked);
- if (old_meta.xca_alloc_head == XZM_FREE_MADVISING ||
- old_meta.xca_alloc_head == XZM_FREE_MADVISED) {
- return false;
- }
-
- new_meta = old_meta;
- new_meta.xca_walk_locked = true;
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- dependency);
- if (!success) {
- continue;
- }
-
- xzm_trace(walk_lock, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo, 0);
- break;
- }
-
- *old_meta_out = old_meta;
- return true;
-}
-
-static void
-_xzm_xzone_freelist_chunk_unlock(xzm_chunk_t chunk,
- xzm_chunk_atomic_meta_u old_meta)
-{
- xzm_chunk_atomic_meta_u new_meta = old_meta;
- new_meta.xca_walk_locked = false;
- bool success = os_atomic_cmpxchgv(&chunk->xzc_atomic_meta.xca_value,
- old_meta.xca_value, new_meta.xca_value, &old_meta.xca_value,
- relaxed);
- xzm_assert(success);
- xzm_trace(walk_unlock, (uint64_t)chunk, old_meta.xca_value_lo,
- new_meta.xca_value_lo, 0);
-}
-
-MALLOC_NOINLINE
-static bool
-_xzm_xzone_freelist_chunk_block_is_free_slow(xzm_malloc_zone_t zone,
- xzm_chunk_t chunk, xzm_block_t block)
-{
- bool is_free = false;
-
- // Re-compute the block information so it doesn't need to be passed
- xzm_xzone_t xz = &zone->xzz_xzones[chunk->xzc_xzone_idx];
- size_t granule = (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX) ?
- XZM_SMALL_GRANULE : XZM_GRANULE;
- uint8_t *start = (uint8_t *)_xzm_chunk_start(zone, chunk, NULL);
- uint64_t block_granules = xz->xz_block_size / granule;
- uint64_t block_offset = _xzm_chunk_block_offset(zone, chunk, block);
- uint64_t max_offset = (xz->xz_chunk_capacity - 1) * block_granules;
-
- uint64_t cur_block_offset = 0;
- size_t freelist_count = 0;
-
- bool cached = false;
-
- xzm_chunk_atomic_meta_u old_meta = { 0 };
-
-#if CONFIG_XZM_THREAD_CACHE
- if (!zone->xzz_thread_cache_enabled ||
- xz->xz_block_size > XZM_THREAD_CACHE_THRESHOLD) {
- goto not_cached;
- }
-
- old_meta.xca_value = os_atomic_load_wide(&chunk->xzc_atomic_meta.xca_value,
- dependency);
- if (old_meta.xca_alloc_idx != XZM_SLOT_INDEX_THREAD_INSTALLED) {
- goto not_cached;
- }
-
- xzm_thread_cache_t tc = _xzm_get_thread_cache();
- if (!tc) {
- goto not_cached;
- }
-
- xzm_xzone_index_t xz_idx = xz->xz_idx;
- xzm_xzone_thread_cache_t cache = &tc->xtc_xz_caches[xz_idx];
-
- if (cache->xztc_head > XZM_FREE_LIMIT || cache->xztc_chunk != chunk) {
- goto not_cached;
- }
-
- cached = true;
-
- cur_block_offset = cache->xztc_head;
- freelist_count = 0;
- while (cur_block_offset != XZM_FREE_NULL &&
- cur_block_offset % block_granules == 0 &&
- cur_block_offset <= max_offset &&
- freelist_count < cache->xztc_free_count) {
- if (cur_block_offset == block_offset) {
- // We found our block on the local freelist, so it's free
- is_free = true;
- goto unlocked;
- }
-
- freelist_count++;
-
- xzm_block_t cur_block = (xzm_block_t)(
- start + (cur_block_offset * granule));
-#if CONFIG_MTE
- if (chunk->xzc_tagged) {
- cur_block = (xzm_block_t)memtag_fixup_ptr((void *)cur_block);
- }
-#endif
- cur_block_offset = cur_block->xzb_linkage.xzbl_next_offset;
- }
-
- bool local_valid = (cur_block_offset == XZM_FREE_NULL &&
- freelist_count <= cache->xztc_free_count);
- if (os_unlikely(!local_valid)) {
- xzm_client_abort_with_reason(
- "corrupt tiny local freelist, client likely has a buffer"
- " overflow or use-after-free bug", freelist_count);
- }
-
- // The local freelist has a bump offset. If the given block is above it, it
- // must be free.
- //
- // N.B. Although we can check this here, if the block really is free because
- // it's above the bump we might not get here in the first place, but it
- // isn't guaranteed to have its free cookie set.
- uint64_t local_bump_offset = (xz->xz_chunk_capacity -
- (cache->xztc_free_count - freelist_count)) * block_granules;
- if (block_offset >= local_bump_offset) {
- is_free = true;
- goto unlocked;
- }
-
-not_cached:
-#endif // CONFIG_XZM_THREAD_CACHE
-
- _malloc_lock_lock(&zone->xzz_lock);
-
- bool active = _xzm_xzone_freelist_chunk_lock(chunk, &old_meta);
- if (!active) {
- is_free = true;
- goto unlock;
- }
-
- cur_block_offset = old_meta.xca_alloc_head;
- freelist_count = 0;
- while (cur_block_offset != XZM_FREE_NULL &&
- cur_block_offset % block_granules == 0 &&
- cur_block_offset <= max_offset &&
- freelist_count < old_meta.xca_free_count) {
- if (cur_block_offset == block_offset) {
- // We found our block on the freelist, so it's free.
- is_free = true;
- goto chunk_walk_unlock;
- }
-
- freelist_count++;
-
- xzm_block_t cur_block = (xzm_block_t)(
- start + (cur_block_offset * granule));
-#if CONFIG_MTE
- if (chunk->xzc_tagged) {
- cur_block = (xzm_block_t)memtag_fixup_ptr((void *)cur_block);
- }
-#endif
- cur_block_offset = cur_block->xzb_linkage.xzbl_next_offset;
- }
-
- if (cached) {
- // If this chunk is cached, we should have fully walked the remote
- // freelist
- if (os_unlikely(freelist_count != old_meta.xca_free_count)) {
- xzm_client_abort_with_reason(
- "corrupt tiny remote freelist, client likely has a buffer"
- " overflow or use-after-free bug", freelist_count);
- }
- } else {
- // If this chunk isn't cached, we should have walked the remote freelist
- // to its terminator, which may be the bump
- if (os_unlikely(cur_block_offset != XZM_FREE_NULL)) {
- xzm_client_abort_with_reason(
- "corrupt tiny freelist, client likely has a buffer overflow"
- " or use-after-free bug", freelist_count);
- }
-
- // If there's a bump and the block is above it, it's free.
- //
- // N.B. As above, although we can check this here, if the block really
- // is free because it's above the bump we might not get here in the
- // first place, but it isn't guaranteed to have its free cookie set.
- xzm_debug_assert(freelist_count <= old_meta.xca_free_count);
-
- uint64_t bump_offset = (xz->xz_chunk_capacity -
- (old_meta.xca_free_count - freelist_count)) * block_granules;
- if (block_offset >= bump_offset) {
- is_free = true;
- goto chunk_walk_unlock;
- }
- }
-
-chunk_walk_unlock:
- old_meta.xca_walk_locked = true;
- _xzm_xzone_freelist_chunk_unlock(chunk, old_meta);
-
-unlock:
- _malloc_lock_unlock(&zone->xzz_lock);
-#if CONFIG_XZM_THREAD_CACHE
-unlocked:
-#endif
- return is_free;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static bool
-_xzm_xzone_chunk_block_is_free(xzm_malloc_zone_t zone, xzm_chunk_t chunk,
- xzm_slice_kind_t kind, size_t block_offset, size_t block_size,
- xzm_block_t block)
-{
- xzm_debug_assert(chunk->xzc_bits.xzcb_kind == kind);
- switch (kind) {
- case XZM_SLICE_KIND_TINY_CHUNK:
- case XZM_SLICE_KIND_SMALL_FREELIST_CHUNK:;
- // We optimize here for the case where the block is _not_ free, as that
- // is what's expected on the fast path during free(). Quick heuristic
- // check first: if the block does not have the free cookie we would
- // expect it to if it were on the freelist, we assume that it's
- // allocated. Note that this does _not_ work for pointers above the
- // bump, so we'll report them as allocated too - nano also does this, so
- // we have some evidence we can get away with it.
- uint64_t cookie_value;
-
-#if CONFIG_MTE
- if (chunk->xzc_tagged) {
- // We need to TCO for this load because even though we just ldg'd
- // the tag in the block pointer, if we're being asked about a block
- // that doesn't belong to us it might race to get freed and
- // re-tagged while we're looking at it.
- memtag_disable_checking();
- cookie_value = os_atomic_load(&block->xzb_cookie, relaxed);
- memtag_enable_checking();
- } else {
-#endif
- cookie_value = os_atomic_load(&block->xzb_cookie, relaxed);
-#if CONFIG_MTE
- }
-#endif
-
- uint64_t free_cookie = zone->xzz_freelist_cookie ^ (uint64_t)block;
-
-#if CONFIG_MTE
- if (kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- // Small chunks do not encode the tag in the freelist cookie
- free_cookie = (uint64_t)memtag_strip_address((uint8_t *)free_cookie);
- }
-#endif
- if (os_likely(cookie_value != free_cookie)) {
- return false;
- }
-
- // If the block _does_ look free, it's still possible that the
- // application just happened to have written the cookie value in the
- // slot. We have a choice:
- // - Nano chooses to just assume that the block really is free. This is
- // a soundness problem, as it could cause free() to fail on a valid
- // allocation.
- // - We can walk the freelist to see if we can really find the block.
- // This is sound but relatively costly.
- //
- // For now we take the latter choice, at the risk there's a workload
- // that really hammers on malloc_size() of free blocks a lot. If we
- // encounter such a workload, we could consider behaving like nano for
- // compatibility, but most uses of malloc_size() on free blocks are
- // incorrect so we would probably tell that workload to change.
- bool is_free = _xzm_xzone_freelist_chunk_block_is_free_slow(zone, chunk,
- block);
- return is_free;
- case XZM_SLICE_KIND_SMALL_CHUNK:;
- size_t block_index = block_offset / block_size;
- return _xzm_small_chunk_block_index_is_free(chunk,
- (xzm_block_index_t)block_index);
- default:
- break;
- }
-
- return false;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_chunk_t
-__xzm_ptr_lookup(xzm_malloc_zone_t zone, xzm_segment_table_entry_s entry,
- const void *orig_ptr, const void *ptr, xzm_xzone_t *xz_out,
- xzm_block_t *block_out, size_t *block_size_out)
-{
- xzm_segment_t segment = _xzm_segment_table_entry_to_segment(entry);
- if (!segment) {
- return NULL;
- }
-
- xzm_chunk_t chunk = _xzm_segment_chunk_of(segment, (uintptr_t)ptr);
- if (os_unlikely(!chunk)) {
- return NULL;
- }
-
- if (os_unlikely(chunk->xzc_mzone_idx != zone->xzz_mzone_idx)) {
- // The pointer belongs to one of the mzones, but not us. Depending on
- // the context, we _could_ just go ahead and do what we were asked to
- // (e.g. free) anyway, if we had a way to turn the mzone index back into
- // the pointer, but that would probably be confusing to libmalloc and
- // any wrapper zones, etc, so instead we'll just treat pointers
- // belonging to other mzones as completely foreign.
- return NULL;
- }
-
- xzm_block_t block = (xzm_block_t)ptr;
-
- uintptr_t start = _xzm_chunk_start(zone, chunk, NULL);
- size_t block_offset = (uintptr_t)block - start;
-
- xzm_slice_kind_t kind = chunk->xzc_bits.xzcb_kind;
- xzm_xzone_t xz = NULL;
- size_t block_size = 0;
- if (os_likely(_xzm_slice_kind_uses_xzones(kind))) {
- xz = &zone->xzz_xzones[chunk->xzc_xzone_idx];
- block_size = xz->xz_block_size;
-
- if (os_unlikely(!XZM_FAST_ALIGNED(block_offset, block_size,
- xz->xz_align_magic))) {
- return NULL;
- }
- } else {
- block_size = ((size_t)chunk->xzcs_slice_count) <<
- XZM_SEGMENT_SLICE_SHIFT;
- size_t ptr_offset = block_offset % block_size;
- if (os_unlikely(ptr_offset)) {
- return NULL;
- }
- }
-
-#ifdef DEBUG
- size_t block_index = block_offset / block_size;
- xzm_debug_assert(!_xzm_slice_kind_uses_xzones(chunk->xzc_bits.xzcb_kind) ||
- block_index < xz->xz_chunk_capacity);
-#endif
-
-#if CONFIG_MTE
- const bool memtag_enabled = _xzm_zone_memtag_enabled(zone);
- if (memtag_enabled) {
- // Load the tag for the block pointer we materialized
- block = (xzm_block_t)memtag_fixup_ptr((void *)block);
-
- // If the tag of the block doesn't match the tag we started with, we
- // consider it not allocated
- if (os_unlikely(!memtag_tags_match(orig_ptr, block))) {
- return NULL;
- }
- }
-#endif
-
- if (os_unlikely(_xzm_xzone_chunk_block_is_free(zone, chunk, kind,
- block_offset, block_size, block))) {
- return NULL;
- }
-
- if (xz_out) {
- *xz_out = xz;
- }
-
- if (block_out) {
- *block_out = block;
- }
- if (block_size_out) {
- *block_size_out = block_size;
- }
-
- return chunk;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static xzm_chunk_t
-_xzm_ptr_lookup(xzm_malloc_zone_t zone, const void *ptr, xzm_xzone_t *xz_out,
- xzm_block_t *block_out, size_t *block_size_out)
-{
- const void *orig_ptr = ptr;
-
-#if CONFIG_MTE
- // Strip the pointer to use it in pointer arithmetic operations
- ptr = memtag_strip_address((uint8_t *)ptr);
-#endif
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- xzm_segment_table_entry_s *entry_p = _xzm_ptr_to_table_entry(ptr, main);
- if (os_unlikely(!entry_p)) {
- return NULL;
- }
-
- return __xzm_ptr_lookup(zone, *entry_p, orig_ptr, ptr, xz_out, block_out,
- block_size_out);
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_free_not_found(xzm_malloc_zone_t zone, void *ptr, bool try)
-{
- if (_xzm_malloc_zone_is_main(zone) && mfm_claimed_address(ptr)) {
- mfm_free(ptr);
- return;
- }
-
- if (!try) {
-#if CONFIG_MTE
- // When tagging is enabled (based on malloc_has_sec_transition), we will
- // check if the logical tag of the pointer matches the tag stored in
- // memory. We need to do this here, in xzone, to make sure that we
- // validate the tag in the malloc_zone_free() path. All other paths
- // should be handled at the dispatch layer (malloc proper).
-#endif // CONFIG_MTE
- malloc_report_pointer_was_not_allocated(
- MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG, ptr);
- }
-
- find_zone_and_free(ptr, true);
-}
-
-MALLOC_NOINLINE
-static void
-_xzm_free_outlined(xzm_malloc_zone_t zone, void *ptr, bool try,
- xzm_segment_table_entry_s entry)
-{
- void *orig_ptr = ptr;
-
-#if CONFIG_MTE
- // Strip the pointer to use it in pointer arithmetic operations
- ptr = memtag_strip_address((uint8_t *)ptr);
-#endif
-
- xzm_xzone_t xz = NULL;
- xzm_block_t block = NULL;
- xzm_chunk_t chunk = __xzm_ptr_lookup(zone, entry, orig_ptr, ptr, &xz,
- &block, NULL);
- if (os_unlikely(!chunk)) {
- return _xzm_free_not_found(zone, orig_ptr, try);
- }
-
- if (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- _xzm_xzone_free_freelist(zone, xz, chunk, block);
- } else if (chunk->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_CHUNK) {
- _xzm_xzone_free_block_to_small_chunk(zone, xz, chunk, block);
- } else {
- xzm_debug_assert(!xz);
- _xzm_free_large_huge(zone, chunk);
- }
- return;
-}
-
-#ifdef DEBUG
-static void
-_xzm_debug_validate_chunk_metadata(xzm_xzone_t xz, xzm_chunk_t chunk)
-{
- xzm_debug_assert(xz->xz_block_size == chunk->xzc_freelist_block_size);
- xzm_debug_assert(xz->xz_chunk_capacity == chunk->xzc_freelist_chunk_capacity);
-#if CONFIG_MTE
- xzm_debug_assert(xz->xz_tagged == chunk->xzc_tagged);
-#endif
-}
-#else
-#define _xzm_debug_validate_chunk_metadata(...)
-#endif
-
-// mimalloc: mi_free
-static void
-_xzm_free(xzm_malloc_zone_t zone, void *ptr, bool try)
-{
- if (os_unlikely(!ptr)) {
- return;
- }
-
- void *orig_ptr = ptr;
-
- // Inline fast path for tiny free
-
-#if CONFIG_MTE
- // Strip the pointer to use it in pointer arithmetic operations
- ptr = memtag_strip_address((uint8_t *)ptr);
-#endif
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- xzm_segment_table_entry_s *entry_p = _xzm_ptr_to_table_entry(ptr, main);
- if (os_unlikely(!entry_p)) {
- goto not_found;
- }
-
- xzm_segment_table_entry_s entry = *entry_p;
- if (os_unlikely(!entry.xste_normal)) {
- goto outlined;
- }
-
- xzm_segment_t segment = _xzm_segment_table_entry_to_segment(entry);
- uintptr_t offset_in_segment = (uintptr_t)orig_ptr & XZM_SEGMENT_MASK;
- uint64_t slice_idx = offset_in_segment >> XZM_SEGMENT_SLICE_SHIFT;
- xzm_slice_t slice = &segment->xzs_slices[slice_idx];
-
- if (os_unlikely(slice->xzc_bits.xzcb_kind != XZM_SLICE_KIND_TINY_CHUNK)) {
- goto outlined;
- }
-
- xzm_chunk_t chunk = (xzm_chunk_t)slice;
- if (os_unlikely(chunk->xzc_mzone_idx != zone->xzz_mzone_idx)) {
- goto not_found;
- }
-
- xzm_xzone_index_t xz_idx = chunk->xzc_xzone_idx;
- __assert_only xzm_xzone_t xz = &zone->xzz_xzones[xz_idx];
- _xzm_debug_validate_chunk_metadata(xz, chunk);
-
- size_t block_size = chunk->xzc_freelist_block_size;
- size_t block_offset = (uintptr_t)orig_ptr & XZM_SEGMENT_SLICE_MASK;
- if (os_unlikely(block_offset % block_size)) {
- goto not_found;
- }
-
- // Check if block is free
-
- xzm_block_t block = (xzm_block_t)orig_ptr;
-
-#if CONFIG_MTE
- const bool tagged = chunk->xzc_tagged;
- if (tagged) {
- block = (xzm_block_t)memtag_fixup_ptr((void *)ptr);
-
- if (os_unlikely(!memtag_tags_match(orig_ptr, block))) {
- goto not_found;
- }
- }
-#endif
-
- uint64_t cookie_value = os_atomic_load(&block->xzb_cookie, relaxed);
- uint64_t free_cookie = zone->xzz_freelist_cookie ^ (uint64_t)orig_ptr;
- if (os_unlikely(cookie_value == free_cookie)) {
- goto outlined;
- }
-
- if (block_size > sizeof(struct xzm_block_inline_meta_s) &&
- block_size <= XZM_ZERO_ON_FREE_THRESHOLD) {
- bzero(block, block_size);
- }
-
-#if CONFIG_MTE
- if (tagged) {
- block = _xzm_xzone_block_memtag_retag(zone, block, block_size);
- uint8_t tag = memtag_extract_tag((uint8_t *)block);
- free_cookie = (uint64_t)memtag_mix_tag(
- memtag_strip_address((uint8_t *)free_cookie), tag);
- }
-#endif
-
- // Mark it as free now
- os_atomic_store(&block->xzb_cookie, free_cookie, relaxed);
-
- uint16_t block_ref = block_offset / XZM_GRANULE;
-
-#if CONFIG_XZM_THREAD_CACHE
- if (block_size > XZM_THREAD_CACHE_THRESHOLD ||
- !zone->xzz_thread_cache_enabled) {
- goto not_cached;
- }
-
- xzm_thread_cache_t tc = _xzm_get_thread_cache();
- if (!tc) {
- goto not_cached;
- }
-
- xzm_xzone_thread_cache_t cache = &tc->xtc_xz_caches[xz_idx];
- if (cache->xztc_head > XZM_FREE_LIMIT || cache->xztc_chunk != chunk) {
- goto not_cached;
- }
-
- return _xzm_xzone_thread_cache_free_tiny(zone, cache, block, block_ref);
-
-not_cached:
-#endif // CONFIG_XZM_THREAD_CACHE
- return _xzm_xzone_free_freelist_inline(zone, xz_idx, chunk, block,
- block_ref, block_size, chunk->xzc_freelist_chunk_capacity);
-
-outlined:
- return _xzm_free_outlined(zone, orig_ptr, try, entry);
-
-not_found:
- return _xzm_free_not_found(zone, orig_ptr, try);
-}
-
-MALLOC_NOINLINE
-static size_t
-_xzm_ptr_size_outlined(xzm_malloc_zone_t zone, const void *ptr)
-{
- // TODO: As part of rdar://101779148, check the mzone that this early
- // allocation came from
- if (_xzm_malloc_zone_is_main(zone) && mfm_claimed_address((void *)ptr)) {
- return mfm_alloc_size(ptr);
- }
-
- return 0;
-}
-
-// mimalloc: _mi_usable_size
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static size_t
-_xzm_ptr_size(xzm_malloc_zone_t zone, const void *ptr, xzm_chunk_t *out_chunk)
-{
- xzm_block_t block;
- size_t block_size;
- xzm_chunk_t chunk = _xzm_ptr_lookup(zone, ptr, NULL, &block, &block_size);
- if (out_chunk) {
- *out_chunk = chunk;
- }
-
- if (!chunk) {
- // Might be an early allocation, or a huge aligned allocation
- return _xzm_ptr_size_outlined(zone, ptr);
- }
-
- if (os_likely((void *)block == ptr)) {
- return block_size;
- }
-
- // Aligned allocation
- size_t block_offset = (uint8_t *)ptr - (uint8_t *)block;
- return block_size - block_offset;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static void *
-_xzm_realloc_inner(xzm_malloc_zone_t zone, void *ptr, size_t new_size,
- malloc_type_descriptor_t type_desc)
-{
- xzm_debug_assert(ptr);
- xzm_debug_assert(new_size);
-
- xzm_chunk_t chunk;
- size_t old_size = _xzm_ptr_size(zone, ptr, &chunk);
- void *old_ptr = ptr;
-
- if (old_size == 0) {
-#if CONFIG_MTE
- // This implicitly includes validation of the pointer tag when memory
- // tagging is enabled. We need this here to cover the realloc path of
- // malloc_zone_realloc(), which doesn't go through _realloc().
-#endif
- malloc_report_pointer_was_not_allocated(
- MALLOC_REPORT_CRASH | MALLOC_REPORT_NOLOG, ptr);
- }
-
- if (chunk && os_unlikely(chunk->xzc_mzone_idx != zone->xzz_mzone_idx)) {
- xzm_client_abort_with_reason(
- "pointer zone mismatch, client may be passing the wrong malloc"
- " zone", ptr);
- }
-
- if (chunk && old_size > XZM_SMALL_BLOCK_SIZE_MAX
- && new_size > XZM_SMALL_BLOCK_SIZE_MAX) {
- xzm_slice_count_t new_slice_count = (xzm_slice_count_t)
- (roundup(new_size,
- XZM_SEGMENT_SLICE_SIZE) / XZM_SEGMENT_SLICE_SIZE);
-
- xzm_segment_t segment = _xzm_segment_for_slice(zone, chunk);
- xzm_debug_assert(segment != NULL);
-
- bool realloc_in_place = false;
- if (old_size > XZM_LARGE_BLOCK_SIZE_MAX &&
- new_size > XZM_LARGE_BLOCK_SIZE_MAX &&
- os_likely((zone->xzz_flags & MALLOC_PURGEABLE) == 0)) {
- realloc_in_place = xzm_segment_group_try_realloc_huge_chunk(
- segment->xzs_segment_group, zone, segment, chunk,
- new_slice_count);
- } else if (old_size <= XZM_LARGE_BLOCK_SIZE_MAX &&
- segment->xzs_kind == XZM_SEGMENT_KIND_NORMAL &&
- new_size <= XZM_LARGE_BLOCK_SIZE_MAX &&
- os_likely((zone->xzz_flags & MALLOC_PURGEABLE) == 0)) {
- realloc_in_place = xzm_segment_group_try_realloc_large_chunk(
- segment->xzs_segment_group, segment, chunk,
- new_slice_count);
- }
-
- if (realloc_in_place) {
- return old_ptr;
- }
- }
-
- if (new_size <= old_size && new_size >= (old_size / 2)) {
- // reallocation still fits and not more than 50% waste
- // TODO: revisit?
- return old_ptr;
- }
-
- void *new_ptr = xzm_malloc(zone, new_size, type_desc, 0);
- if (os_unlikely(!new_ptr)) {
- return NULL;
- }
-
- const size_t valid_size = MIN(old_size, new_size);
-#if CONFIG_REALLOC_CAN_USE_VMCOPY
- // When supported, request a CoW mapping
- if (valid_size > XZM_LARGE_BLOCK_SIZE_MAX) {
- if (mach_vm_copy(mach_task_self(), (mach_vm_address_t)old_ptr,
- valid_size, (mach_vm_address_t)new_ptr) == KERN_SUCCESS) {
- return new_ptr;
- }
- }
-#endif
- memcpy(new_ptr, old_ptr, valid_size);
-
- return new_ptr;
-}
-
-void *
-xzm_realloc(xzm_malloc_zone_t zone, void *ptr, size_t new_size,
- malloc_type_descriptor_t type_desc)
-{
- if (!ptr) {
- return xzm_malloc(zone, new_size, type_desc, 0);
- } else if (new_size == 0) {
- _xzm_free(zone, ptr, false);
- return xzm_malloc(zone, new_size, type_desc, 0);
- }
-
- void *new_ptr = _xzm_realloc_inner(zone, ptr, new_size, type_desc);
- if (new_ptr && new_ptr != ptr) {
- _xzm_free(zone, ptr, false);
- }
-
- return new_ptr;
-}
-
-#pragma mark libmalloc zone
-
-static size_t
-xzm_malloc_zone_size(xzm_malloc_zone_t zone, const void *ptr)
-{
- return _xzm_ptr_size(zone, ptr, NULL);
-}
-
-static boolean_t
-xzm_malloc_zone_claimed_address(xzm_malloc_zone_t zone, void *ptr)
-{
- // Is it an early allocation?
- if (mfm_claimed_address(ptr)) {
- return true;
- }
-
-#if CONFIG_MTE
- // We strip the pointer here as xzm_segment_table_query operates on
- // canonical pointers; however, since mfm_claimed_address validates
- // the logical tag of the pointer, we need to ensure we don't strip
- // the tag before passing it to MFM.
- // We don't need to check if MTE is enabled, as this doesn't need MTE ISA.
- ptr = memtag_strip_address(ptr);
-#endif
-
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- return xzm_segment_table_query(main, ptr) != NULL;
-}
-
-static inline void *
-_xzm_malloc_zone_malloc_entry(xzm_malloc_zone_t zone, size_t size,
- malloc_type_id_t type_id, xzm_malloc_options_t options)
-{
- return xzm_malloc_inline(zone, size,
- (malloc_type_descriptor_t){ .type_id = type_id, }, options);
-}
-
-static void *
-xzm_malloc_zone_malloc_type_malloc(xzm_malloc_zone_t zone, size_t size,
- malloc_type_id_t type_id)
-{
- return _xzm_malloc_zone_malloc_entry(zone, size, type_id, 0);
-}
-
-static void *
-xzm_malloc_zone_malloc(xzm_malloc_zone_t zone, size_t size)
-{
- return _xzm_malloc_zone_malloc_entry(zone, size, malloc_get_tsd_type_id(),
- _xzm_xzone_get_malloc_thread_options());
-}
-
-static inline void *
-_xzm_malloc_zone_malloc_type_calloc_entry(xzm_malloc_zone_t zone, size_t count,
- size_t size, malloc_type_id_t type_id,
- xzm_malloc_options_t options)
-{
- size_t total_bytes;
- if (os_unlikely(calloc_get_size(count, size, 0, &total_bytes))) {
- malloc_set_errno_fast(MZ_POSIX, ENOMEM);
- return NULL;
- }
-
- return xzm_malloc_inline(zone, total_bytes,
- (malloc_type_descriptor_t){ .type_id = type_id, },
- options | XZM_MALLOC_CLEAR);
-}
-
-static void *
-xzm_malloc_zone_malloc_type_calloc(xzm_malloc_zone_t zone, size_t count,
- size_t size, malloc_type_id_t type_id)
-{
- return _xzm_malloc_zone_malloc_type_calloc_entry(zone, count, size, type_id,
- 0);
-}
-
-static void *
-xzm_malloc_zone_calloc(xzm_malloc_zone_t zone, size_t count, size_t size)
-{
- return _xzm_malloc_zone_malloc_type_calloc_entry(zone, count, size,
- malloc_get_tsd_type_id(), _xzm_xzone_get_malloc_thread_options());
-}
-
-static void * __alloc_size(2)
-xzm_malloc_zone_valloc(xzm_malloc_zone_t zone, size_t size)
-{
- return _xzm_memalign(zone, vm_page_quanta_size, size,
- malloc_get_tsd_type_descriptor(), 0);
-}
-
-static void
-xzm_malloc_zone_free(xzm_malloc_zone_t zone, void *ptr)
-{
- _xzm_free(zone, ptr, false);
-}
-
-static void
-xzm_malloc_zone_try_free_default(xzm_malloc_zone_t zone, void *ptr)
-{
- _xzm_free(zone, ptr, true);
-}
-
-static void
-xzm_malloc_zone_free_definite_size(xzm_malloc_zone_t zone, void *ptr,
- size_t size)
-{
- // Unfortunately, knowing the size doesn't really buy us anything.
- (void)size;
-
- _xzm_free(zone, ptr, false);
-}
-
-static void *
-xzm_malloc_zone_malloc_type_realloc(xzm_malloc_zone_t zone, void *ptr,
- size_t new_size, malloc_type_id_t type_id)
-{
- return xzm_realloc(zone, ptr, new_size,
- (malloc_type_descriptor_t){ .type_id = type_id, });
-}
-
-static void *
-xzm_malloc_zone_realloc(xzm_malloc_zone_t zone, void *ptr, size_t new_size)
-{
- return xzm_malloc_zone_malloc_type_realloc(zone, ptr, new_size,
- malloc_get_tsd_type_id());
-}
-
-static inline void *
-_xzm_malloc_zone_malloc_type_memalign_entry(xzm_malloc_zone_t zone,
- size_t alignment, size_t size, malloc_type_id_t type_id,
- xzm_malloc_options_t options)
-{
- return _xzm_memalign(zone, alignment, size,
- (malloc_type_descriptor_t){ .type_id = type_id, }, options);
-}
-
-static void *
-xzm_malloc_zone_malloc_type_memalign(xzm_malloc_zone_t zone, size_t alignment,
- size_t size, malloc_type_id_t type_id)
-{
- return _xzm_malloc_zone_malloc_type_memalign_entry(zone, alignment, size,
- type_id, 0);
-}
-
-static void *
-xzm_malloc_zone_memalign(xzm_malloc_zone_t zone, size_t alignment, size_t size)
-{
- return _xzm_malloc_zone_malloc_type_memalign_entry(zone, alignment, size,
- malloc_get_tsd_type_id(), _xzm_xzone_get_malloc_thread_options());
-}
-
-static void *
-xzm_malloc_zone_malloc_type_malloc_with_options(xzm_malloc_zone_t zone,
- size_t align, size_t size, malloc_zone_malloc_options_t options,
- malloc_type_id_t type_id)
-{
- xzm_malloc_options_t opt = 0;
- if (options & MALLOC_ZONE_MALLOC_OPTION_CLEAR) {
- opt |= XZM_MALLOC_CLEAR;
- }
- if (options & MALLOC_NP_OPTION_CANONICAL_TAG) {
- opt |= XZM_MALLOC_CANONICAL_TAG;
- }
-
- malloc_type_descriptor_t type_desc = { .type_id = type_id, };
- if (align > MALLOC_ZONE_MALLOC_DEFAULT_ALIGN) {
- return _xzm_memalign(zone, align, size, type_desc, opt);
- } else {
- return xzm_malloc_inline(zone, size, type_desc, opt);
- }
-}
-
-static void *
-xzm_malloc_zone_malloc_with_options(xzm_malloc_zone_t zone, size_t align,
- size_t size, malloc_zone_malloc_options_t options)
-{
- return xzm_malloc_zone_malloc_type_malloc_with_options(zone, align, size,
- options, malloc_get_tsd_type_id());
-}
-
-#pragma mark slowpath zone functions
-
-// nothing for size
-
-// nothing for claimed_address
-
-static void *
-xzm_malloc_zone_malloc_type_malloc_slow(xzm_malloc_zone_t zone, size_t size,
- malloc_type_id_t type_id)
-{
- void *ptr = NULL;
- malloc_type_descriptor_t type_desc = { .type_id = type_id, };
- xzm_malloc_options_t options = _xzm_xzone_get_malloc_thread_options();
-
- if ((zone->xzz_flags & MALLOC_PURGEABLE) &&
- (size <= XZM_SMALL_BLOCK_SIZE_MAX)) {
- xzm_malloc_zone_t main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- ptr = xzm_malloc_inline(main, size, type_desc, options);
- } else {
- ptr = xzm_malloc_inline(zone, size, type_desc, options);
- }
- if (ptr && (zone->xzz_flags & MALLOC_DO_SCRIBBLE)) {
- memset(ptr, SCRIBBLE_BYTE, size);
- }
- return ptr;
-}
-
-static void *
-xzm_malloc_zone_malloc_slow(xzm_malloc_zone_t zone, size_t size)
-{
- return xzm_malloc_zone_malloc_type_malloc_slow(zone, size,
- malloc_get_tsd_type_id());
-}
-
-static void *
-xzm_malloc_zone_malloc_type_calloc_slow(xzm_malloc_zone_t zone, size_t count,
- size_t size, malloc_type_id_t type_id)
-{
- size_t total_bytes;
- if (calloc_get_size(count, size, 0, &total_bytes)) {
- return NULL;
- }
-
- malloc_type_descriptor_t type_desc = { .type_id = type_id, };
- xzm_malloc_options_t options = _xzm_xzone_get_malloc_thread_options() |
- XZM_MALLOC_CLEAR;
- if ((zone->xzz_flags & MALLOC_PURGEABLE) == 0 ||
- total_bytes > XZM_SMALL_BLOCK_SIZE_MAX) {
- return xzm_malloc_inline(zone, total_bytes, type_desc, options);
- } else {
- xzm_malloc_zone_t main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- return xzm_malloc_inline(main, total_bytes, type_desc, options);
- }
-}
-
-static void *
-xzm_malloc_zone_calloc_slow(xzm_malloc_zone_t zone, size_t count,
- size_t size)
-{
- return xzm_malloc_zone_malloc_type_calloc_slow(zone, count, size,
- malloc_get_tsd_type_id());
-}
-
-static void *
-xzm_malloc_zone_valloc_slow(xzm_malloc_zone_t zone, size_t size)
-{
- void *ptr = NULL;
- if ((zone->xzz_flags & MALLOC_PURGEABLE) &&
- (size <= XZM_SMALL_BLOCK_SIZE_MAX)) {
- xzm_malloc_zone_t main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- ptr = xzm_malloc_zone_valloc(main, size);
- } else {
- ptr = xzm_malloc_zone_valloc(zone, size);
- }
- if (ptr && (zone->xzz_flags & MALLOC_DO_SCRIBBLE)) {
- memset(ptr, SCRIBBLE_BYTE, size);
- }
- return ptr;
-}
-
-static void
-xzm_malloc_zone_free_slow(xzm_malloc_zone_t zone, void *ptr)
-{
- if (!ptr) {
- return;
- }
-
- size_t size = _xzm_ptr_size(zone, ptr, NULL);
- // The main ref stays NULL if this allocation came from zone
- xzm_malloc_zone_t main = NULL;
- if (size == 0 && (zone->xzz_flags & MALLOC_PURGEABLE)) {
- main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- size = _xzm_ptr_size(main, ptr, NULL);
- }
- if (size == 0) {
- xzm_client_abort_with_reason("pointer being freed was not allocated",
- ptr);
- }
- if ((zone->xzz_flags & MALLOC_DO_SCRIBBLE) &&
- (size > XZM_ZERO_ON_FREE_THRESHOLD)) {
- memset(ptr, SCRABBLE_BYTE, size);
- }
-
- if (main) {
- xzm_malloc_zone_free(main, ptr);
- } else {
- xzm_malloc_zone_free(zone, ptr);
- }
-}
-
-static void
-xzm_malloc_zone_try_free_default_slow(xzm_malloc_zone_t zone, void *ptr)
-{
- if (!ptr) {
- return;
- }
-
- size_t size = _xzm_ptr_size(zone, ptr, NULL);
- xzm_malloc_zone_t main = NULL;
- if (size == 0 && (zone->xzz_flags & MALLOC_PURGEABLE)) {
- main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- size = _xzm_ptr_size(main, ptr, NULL);
- }
- if (size == 0) {
- // pointer didn't come from this zone
- find_zone_and_free(ptr, true);
- return;
- }
- if (size && size > XZM_ZERO_ON_FREE_THRESHOLD &&
- (zone->xzz_flags & MALLOC_DO_SCRIBBLE)) {
- memset(ptr, SCRABBLE_BYTE, size);
- }
-
- if (main) {
- xzm_malloc_zone_try_free_default(main, ptr);
- } else {
- xzm_malloc_zone_try_free_default(zone, ptr);
- }
-}
-
-static void
-xzm_malloc_zone_free_definite_size_slow(xzm_malloc_zone_t zone, void *ptr,
- size_t size)
-{
- if (!ptr) {
- return;
- }
-
- // TODO: Should we verify that the size the client passes in matches our
- // size?
- size_t our_size = _xzm_ptr_size(zone, ptr, NULL);
- xzm_malloc_zone_t main = NULL;
- if (our_size == 0 && (zone->xzz_flags & MALLOC_PURGEABLE)) {
- main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- our_size = _xzm_ptr_size(main, ptr, NULL);
- xzm_debug_assert(our_size != 0);
- }
- if ((zone->xzz_flags & MALLOC_DO_SCRIBBLE) &&
- (size > XZM_ZERO_ON_FREE_THRESHOLD)) {
- memset(ptr, SCRABBLE_BYTE, size);
- }
-
- if (main) {
- xzm_malloc_zone_free_definite_size(main, ptr, size);
- } else {
- xzm_malloc_zone_free_definite_size(zone, ptr, size);
- }
-}
-
-static void *
-xzm_malloc_zone_malloc_type_realloc_slow(xzm_malloc_zone_t zone, void *ptr,
- size_t new_size, malloc_type_id_t type_id)
-{
- bool purgeable = zone->xzz_flags & MALLOC_PURGEABLE;
- bool scribble = zone->xzz_flags & MALLOC_DO_SCRIBBLE;
- if (!purgeable && !scribble) {
- return xzm_malloc_zone_malloc_type_realloc(zone, ptr, new_size,
- type_id);
- }
-
- if (!ptr) {
- return xzm_malloc_zone_malloc_type_malloc_slow(zone, new_size, type_id);
- } else if (new_size == 0) {
- xzm_malloc_zone_free_slow(zone, ptr);
- return xzm_malloc_zone_malloc_type_malloc_slow(zone, new_size, type_id);
- }
-
- malloc_type_descriptor_t type_desc = { .type_id = type_id, };
- size_t old_size = _xzm_ptr_size(zone, ptr, NULL);
- xzm_malloc_zone_t main = NULL;
- if (old_size == 0 && purgeable) {
- main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- old_size = _xzm_ptr_size(main, ptr, NULL);
- }
- xzm_assert(old_size);
-
- if ((old_size > new_size) && scribble) {
- // Shrinking: no matter what, we'll definitely need to scrabble the
- // tail, so do that now
- memset((uint8_t *)ptr + new_size, SCRABBLE_BYTE, old_size - new_size);
- }
-
- void *new_ptr;
- if (!purgeable || (!main && new_size > XZM_SMALL_BLOCK_SIZE_MAX) ||
- (main && new_size <= XZM_SMALL_BLOCK_SIZE_MAX)) {
- // The old pointer and new pointer will be from the same zone
- new_ptr = _xzm_realloc_inner(zone, ptr, new_size, type_desc);
- } else {
- if (new_size <= XZM_SMALL_BLOCK_SIZE_MAX) {
- // Need to serve the new allocation from the main zone, since it's
- // too small to be purgeable
- new_ptr = xzm_malloc_inline(&_xzm_malloc_zone_main(zone)->xzmz_base,
- new_size, type_desc, 0);
- } else {
- // Serve the new allocation from the purgeable zone
- new_ptr = xzm_malloc_inline(zone, new_size, type_desc, 0);
- }
- if (new_ptr) {
- memcpy(new_ptr, ptr, MIN(old_size, new_size));
- }
- }
-
- if (new_ptr) {
- // Regardless of whether we reallocated in place or not, we always want
- // to scribble the expanded space when growing
- if ((new_size > old_size) && scribble) {
- memset((uint8_t *)new_ptr + old_size, SCRIBBLE_BYTE,
- new_size - old_size);
- }
- if (new_ptr != ptr) {
- // If we didn't reallocate in place, we have some amount of
- // scrabbling left to do on the old allocation:
- if ((old_size > new_size) && scribble) {
- // If shrinking, we already scrabbled the tail, so just scrabble
- // the head now too
- memset(ptr, SCRABBLE_BYTE, new_size);
- } else if (scribble) {
- // If growing, scrabble the whole previous allocation since we
- // haven't done any of it yet
- memset(ptr, SCRABBLE_BYTE, old_size);
- }
- if (main) {
- _xzm_free(main, ptr, false);
- } else {
- _xzm_free(zone, ptr, false);
- }
- }
- }
-
- return new_ptr;
-}
-
-static void *
-xzm_malloc_zone_realloc_slow(xzm_malloc_zone_t zone, void *ptr, size_t new_size)
-{
- return xzm_malloc_zone_malloc_type_realloc_slow(zone, ptr, new_size,
- malloc_get_tsd_type_id());
-}
-
-static void *
-xzm_malloc_zone_malloc_type_memalign_slow(xzm_malloc_zone_t zone, size_t alignment,
- size_t size, malloc_type_id_t type_id)
-{
- void *ptr = NULL;
- xzm_malloc_options_t options = _xzm_xzone_get_malloc_thread_options();
- if ((zone->xzz_flags & MALLOC_PURGEABLE) &&
- (size <= XZM_SMALL_BLOCK_SIZE_MAX)) {
- xzm_malloc_zone_t main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- ptr = _xzm_malloc_zone_malloc_type_memalign_entry(main, alignment, size,
- type_id, options);
- } else {
- ptr = _xzm_malloc_zone_malloc_type_memalign_entry(zone, alignment, size,
- type_id, options);
- }
- if (ptr && (zone->xzz_flags & MALLOC_DO_SCRIBBLE)) {
- memset(ptr, SCRIBBLE_BYTE, size);
- }
- return ptr;
-}
-
-static void *
-xzm_malloc_zone_memalign_slow(xzm_malloc_zone_t zone, size_t alignment,
- size_t size)
-{
- return xzm_malloc_zone_malloc_type_memalign_slow(zone, alignment, size,
- malloc_get_tsd_type_id());
-}
-
-static void *
-xzm_malloc_zone_malloc_type_malloc_with_options_slow(xzm_malloc_zone_t zone,
- size_t align, size_t size, malloc_zone_malloc_options_t options,
- malloc_type_id_t type_id)
-{
- void *ptr = NULL;
- if ((zone->xzz_flags & MALLOC_PURGEABLE) &&
- (size <= XZM_SMALL_BLOCK_SIZE_MAX)) {
- xzm_malloc_zone_t main = (xzm_malloc_zone_t)_xzm_malloc_zone_main(zone);
- ptr = xzm_malloc_zone_malloc_type_malloc_with_options(main, align, size,
- options, type_id);
- } else {
- ptr = xzm_malloc_zone_malloc_type_malloc_with_options(zone, align, size,
- options, type_id);
- }
- if (ptr && !(options & MALLOC_ZONE_MALLOC_OPTION_CLEAR) &&
- (zone->xzz_flags & MALLOC_DO_SCRIBBLE)) {
- memset(ptr, SCRIBBLE_BYTE, size);
- }
- return ptr;
-}
-
-static void *
-xzm_malloc_zone_malloc_with_options_slow(xzm_malloc_zone_t zone, size_t align,
- size_t size, malloc_zone_malloc_options_t options)
-{
- return xzm_malloc_zone_malloc_type_malloc_with_options_slow(zone, align,
- size, options, malloc_get_tsd_type_id());
-}
-
-#pragma mark libmalloc zone introspection
-size_t
-xzm_good_size(xzm_malloc_zone_t zone, size_t size)
-{
- if (size <= XZM_SMALL_BLOCK_SIZE_MAX) {
- xzm_main_malloc_zone_t main = _xzm_malloc_zone_main(zone);
- uint8_t bin = _xzm_bin(size);
- return main->xzmz_xzone_bin_sizes[bin];
- } else {
- // MAX() handles overflow in roundup()
- return MAX(roundup(size, XZM_SEGMENT_SLICE_SIZE), size);
- }
-}
-
-boolean_t
-xzm_check(xzm_malloc_zone_t zone)
-{
- // TODO: zone self-check
- //
- // Nano doesn't implement this - worth the trouble?
- (void)zone;
- return true;
-}
-
-void
-xzm_log(xzm_malloc_zone_t zone, void *log_address)
-{
- // Not implemented
- //
- // Doesn't seem that useful? Nano doesn't implement this either.
-}
-
-OS_ENUM(xzm_lock_action, int,
- XZM_LOCK_LOCK,
- XZM_LOCK_UNLOCK,
- XZM_LOCK_INIT,
-);
-
-static void
-_xzm_do_lock_action(_malloc_lock_s *lock, xzm_lock_action_t action)
-{
- switch (action) {
- case XZM_LOCK_LOCK:
- _malloc_lock_lock(lock);
- break;
- case XZM_LOCK_UNLOCK:
- _malloc_lock_unlock(lock);
- break;
- case XZM_LOCK_INIT:
- _malloc_lock_init(lock);
- break;
- default:
- xzm_debug_abort("invalid xzm lock action");
- break;
- }
-}
-
-static void
-_xzm_freelist_xzone_do_lock_action(xzm_malloc_zone_t zone, xzm_xzone_t xz,
- xzm_lock_action_t action)
-{
- if (action == XZM_LOCK_LOCK) {
- for (xzm_allocation_index_t i = 0; i < zone->xzz_slot_count; i++) {
- _xzm_chunk_list_fork_lock(&_xzm_xzone_chunk_list_for_index(zone,
- xz, zone->xzz_partial_lists, i)->xcl_list);
- }
-
- _xzm_chunk_batch_list_fork_lock(&xz->xz_batch_list);
- _xzm_chunk_list_fork_lock(&xz->xz_empty_list);
- _xzm_chunk_list_fork_lock(&xz->xz_preallocated_list);
- _xzm_chunk_list_fork_lock(&xz->xz_all_list);
- }
-
- xzm_chunk_list_head_u head = {
- .xzch_value = os_atomic_load(&xz->xz_all_list.xzch_value, dependency),
- };
-
- xzm_chunk_t chunk = (xzm_chunk_t)head.xzch_ptr;
- while (chunk) {
- xzm_chunk_atomic_meta_u old_meta;
- if (action == XZM_LOCK_LOCK) {
- (void)_xzm_xzone_freelist_chunk_lock(chunk, &old_meta);
- } else {
- old_meta.xca_value = os_atomic_load_wide(
- &chunk->xzc_atomic_meta.xca_value, dependency);
- if (old_meta.xca_alloc_head != XZM_FREE_MADVISING &&
- old_meta.xca_alloc_head != XZM_FREE_MADVISED) {
- _xzm_xzone_freelist_chunk_unlock(chunk, old_meta);
- }
- }
- chunk = chunk->xzc_linkages[XZM_CHUNK_LINKAGE_ALL];
- }
-
- if (action != XZM_LOCK_LOCK) {
- _xzm_chunk_list_fork_unlock(&xz->xz_all_list);
- _xzm_chunk_batch_list_fork_unlock(&xz->xz_batch_list);
- _xzm_chunk_list_fork_unlock(&xz->xz_preallocated_list);
- _xzm_chunk_list_fork_unlock(&xz->xz_empty_list);
-
- for (xzm_allocation_index_t i = 0; i < zone->xzz_slot_count; i++) {
- _xzm_chunk_list_fork_unlock(&_xzm_xzone_chunk_list_for_index(zone,
- xz, zone->xzz_partial_lists, i)->xcl_list);
- }
-
- }
-}
-
-static void
-_xzm_small_xzone_lock_all(xzm_malloc_zone_t zone, xzm_xzone_t xz)
-{
- xzm_debug_assert(xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX);
-
- while (true) {
- _malloc_lock_lock(&xz->xz_lock);
-
- xzm_chunk_t chunk = xz->xz_chunkq_batch;
- while (chunk) {
- _malloc_lock_lock(&chunk->xzc_lock);
- chunk = *_xzm_segment_slice_meta_batch_next(zone, chunk);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone, chunk));
- }
-
- LIST_FOREACH(chunk, &xz->xz_chunkq_partial, xzc_entry) {
- _malloc_lock_lock(&chunk->xzc_lock);
- }
-
- LIST_FOREACH(chunk, &xz->xz_chunkq_full, xzc_entry) {
- bool gotlock = _malloc_lock_trylock(&chunk->xzc_lock);
- if (!gotlock) {
- // Potential lock inversion - need to unwind and retry.
- xzm_chunk_t chunk2 = xz->xz_chunkq_batch;
-
- while (chunk2) {
- _malloc_lock_unlock(&chunk2->xzc_lock);
- chunk2 = *_xzm_segment_slice_meta_batch_next(zone, chunk2);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone,
- chunk2));
- }
-
- LIST_FOREACH(chunk2, &xz->xz_chunkq_partial, xzc_entry) {
- _malloc_lock_unlock(&chunk2->xzc_lock);
- }
-
- LIST_FOREACH(chunk2, &xz->xz_chunkq_full, xzc_entry) {
- if (chunk2 == chunk) {
- break;
- }
-
- _malloc_lock_unlock(&chunk2->xzc_lock);
- }
- break;
- }
- }
-
- if (chunk) {
- // Encountered a trylock failure, so we need to unlock the xzone to
- // let the holder of the chunk lock we need make progress.
- //
- // TODO: There's a priority inversion here because we're not doing
- // anything to push on the holder of the chunk lock we need. We
- // know exactly who we need to wait for, so we could give them an
- // artificial push by doing a timed ulock wait on their thread ID
- // after we release the lock - but before doing that we'd probably
- // just replace this whole approach instead.
- _malloc_lock_unlock(&xz->xz_lock);
- yield();
- } else {
- // Made it to the end of the full list without any trylock failures,
- // so we're done.
- break;
- }
- }
-}
-
-static void
-_xzm_allocation_slots_do_lock_action(xzm_malloc_zone_t zone,
- xzm_lock_action_t action)
-{
- for (uint8_t i = XZM_XZONE_INDEX_FIRST; i < zone->xzz_xzone_count; i++) {
- xzm_xzone_t xz = &zone->xzz_xzones[i];
-
- __unused xzm_slice_kind_t kind;
- if (xz->xz_block_size <= XZM_TINY_BLOCK_SIZE_MAX) {
- kind = XZM_SLICE_KIND_TINY_CHUNK;
- } else if (zone->xzz_small_freelist_enabled) {
- kind = XZM_SLICE_KIND_SMALL_FREELIST_CHUNK;
- } else {
- kind = XZM_SLICE_KIND_SMALL_CHUNK;
- }
-
- for (uint8_t j = 0; j < zone->xzz_slot_count; j++) {
- size_t slot_idx = (zone->xzz_xzone_count * j) + i;
- xzm_xzone_allocation_slot_t xas =
- &zone->xzz_xzone_allocation_slots[slot_idx];
-#if !CONFIG_TINY_ALLOCATION_SLOT_LOCK
- if (kind == XZM_SLICE_KIND_TINY_CHUNK ||
- kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- if (action == XZM_LOCK_LOCK) {
- _xzm_xzone_allocation_slot_fork_lock(xas);
- } else {
- _xzm_xzone_allocation_slot_fork_unlock(xas);
- }
- } else
-#endif // !CONFIG_TINY_ALLOCATION_SLOT_LOCK
- {
- _xzm_do_lock_action(&xas->xas_lock, action);
- }
- }
- }
-}
-
-static void
-_xzm_foreach_lock(xzm_malloc_zone_t zone, xzm_lock_action_t action)
-{
- // mzone-private locks
- //
- // There are 3 levels of locks:
- // - the xzone slot locks
- // - the xzone lock
- // - the chunk locks
- //
- // The chunk locks are the most difficult to handle for fork, as there are
- // so many of them. The simplest strategy that works is to just find and
- // acquire them all.
- //
- // The listed order is the general locking order, but when full chunks
- // transition back to partial their chunk lock is held while acquiring the
- // xzone lock. It's therefore not safe to hold the xzone lock while trying
- // to acquire the chunk lock of a full chunk. To deal with that, use
- // trylock to acquire the locks of full chunks, and on failure back off,
- // release the xzone lock and retry.
- //
- // It's not necessary to acquire the chunk locks of chunks that are empty
- // and in the transition from the partial list to the isolation list,
- // because they won't be allocated from or freed to on the child side of
- // fork - they'll effectively leak when the thread responsible for them
- // disappears as part of fork.
- //
- // TODO: this is not efficient, particularly in processes that have lots of
- // chunks at the time of fork. Before we pursue general userspace
- // enablement on macOS, where 3p code needs fork() to be reasonably fast, we
- // should do something better, but this is good enough for the situations
- // where it just needs to be correct.
-
- // The fork-lock bits send waiters to the fork lock, so we need to ensure
- // that we hold the fork lock whenever any of the fork-lock bits are set.
- // That means we need to acquire it before setting any of the fork-lock bits
- // and release it only after clearing all of them.
- //
- // The fork lock also provides mutual exclusion for mutation of the
- // fork-lock bits. This is needed to prevent collisions between concurrent
- // executions of foreach-lock: one thread un-setting the fork-lock bits must
- // not trample another one making a pass setting them.
- if (action == XZM_LOCK_LOCK) {
- _xzm_do_lock_action(&zone->xzz_fork_lock, action);
-
- // The zone lock (xzz_lock) is used for tiny chunk walk lock
- // synchronization (among other things). It is acquired under the
- // allocation slot lock when allocating from a partial chunk, and needs
- // to be held before performing any of the fork walk-locking, so it
- // needs to be taken here after the allocation slots are locked and
- // before any of the tiny chunks are locked.
- _xzm_do_lock_action(&zone->xzz_lock, action);
-
- // The freelist allocation slots must be locked before the all-chunk
- // lists are walked and unlocked after, so that no new chunks can be
- // allocated and added in the middle. That's important because we need
- // the set of chunks we walk during the lock phase to be the same as
- // during the unlock phase.
- _xzm_allocation_slots_do_lock_action(zone, action);
- }
-
- for (uint8_t i = XZM_XZONE_INDEX_FIRST; i < zone->xzz_xzone_count; i++) {
- xzm_xzone_t xz = &zone->xzz_xzones[i];
-
- xzm_slice_kind_t kind;
- if (xz->xz_block_size <= XZM_TINY_BLOCK_SIZE_MAX) {
- kind = XZM_SLICE_KIND_TINY_CHUNK;
- } else if (zone->xzz_small_freelist_enabled) {
- kind = XZM_SLICE_KIND_SMALL_FREELIST_CHUNK;
- } else {
- kind = XZM_SLICE_KIND_SMALL_CHUNK;
- }
-
- if (kind == XZM_SLICE_KIND_TINY_CHUNK ||
- kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- _xzm_freelist_xzone_do_lock_action(zone, xz, action);
- } else if (kind == XZM_SLICE_KIND_SMALL_CHUNK) {
- if (action == XZM_LOCK_LOCK) {
- _xzm_small_xzone_lock_all(zone, xz);
- } else {
- xzm_chunk_t chunk = xz->xz_chunkq_batch;
- while (chunk) {
- _xzm_do_lock_action(&chunk->xzc_lock, action);
- chunk = *_xzm_segment_slice_meta_batch_next(zone, chunk);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone,
- chunk));
- }
- LIST_FOREACH(chunk, &xz->xz_chunkq_partial, xzc_entry) {
- _xzm_do_lock_action(&chunk->xzc_lock, action);
- }
- LIST_FOREACH(chunk, &xz->xz_chunkq_full, xzc_entry) {
- _xzm_do_lock_action(&chunk->xzc_lock, action);
- }
-
- _xzm_do_lock_action(&xz->xz_lock, action);
- }
- }
- }
-
- if (action != XZM_LOCK_LOCK) {
- _xzm_allocation_slots_do_lock_action(zone, action);
- _xzm_do_lock_action(&zone->xzz_lock, action);
- _xzm_do_lock_action(&zone->xzz_fork_lock, action);
- }
-}
-
-void
-xzm_force_lock(xzm_malloc_zone_t zone)
-{
- _xzm_foreach_lock(zone, XZM_LOCK_LOCK);
-}
-
-void
-xzm_force_unlock(xzm_malloc_zone_t zone)
-{
- _xzm_foreach_lock(zone, XZM_LOCK_UNLOCK);
-}
-
-void
-xzm_reinit_lock(xzm_malloc_zone_t zone)
-{
- _xzm_foreach_lock(zone, XZM_LOCK_INIT);
-}
-
-boolean_t
-xzm_locked(xzm_malloc_zone_t zone)
-{
- // This mechanism is dead:
- // - nanov2 doesn't implement it despite having locks that could lead to the
- // deadlock this is meant to prevent
- // - there's an obvious copy-paste bug in szone_locked() for medium
- //
- // There are no references to malloc_gdb_po_unsafe() anywhere. It should
- // all be removed.
- xzm_abort("xzm_locked not implemented");
- return true;
-}
-
-static void
-_xzm_global_state_lock(xzm_main_malloc_zone_t main, xzm_lock_action_t action)
-{
- // main zone shared locks
- for (uint8_t i = XZM_XZONE_INDEX_FIRST; i < main->xzmz_base.xzz_xzone_count;
- i++) {
- xzm_isolation_zone_t iz = &main->xzmz_isolation_zones[i];
- _xzm_do_lock_action(&iz->xziz_lock, action);
- }
-
- for (uint8_t i = 0; i < main->xzmz_segment_group_count; i++) {
- xzm_segment_group_t sg = &main->xzmz_segment_groups[i];
- _xzm_do_lock_action(&sg->xzsg_alloc_lock, action);
- _xzm_do_lock_action(&sg->xzsg_lock, action);
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (sg->xzsg_id == XZM_SEGMENT_GROUP_DATA_LARGE) {
- _xzm_do_lock_action(&sg->xzsg_cache.xzsc_lock, action);
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- }
-
- for (uint8_t i = 0; i < main->xzmz_range_group_count; i++) {
- xzm_range_group_t rg = &main->xzmz_range_groups[i];
- _xzm_do_lock_action(&rg->xzrg_lock, action);
- }
-
- // The extended segment table lock is held while allocating from the segment
- // table metapool, so we need to acquire this lock before the metapool locks
- _xzm_do_lock_action(&main->xzmz_extended_segment_table_lock, action);
-
- for (int i = 0; i < main->xzmz_metapool_count; i++) {
- xzm_metapool_t mp = &main->xzmz_metapools[i];
- _xzm_do_lock_action(&mp->xzmp_lock, action);
- }
-
- _xzm_do_lock_action(&main->xzmz_mzones_lock, action);
- _xzm_do_lock_action(&main->xzmz_thread_cache_list_lock, action);
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (main->xzmz_reclaim_buffer) {
- _xzm_do_lock_action(&main->xzmz_reclaim_buffer->xrb_lock, action);
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-}
-
-void
-xzm_force_lock_global_state(malloc_zone_t *main_zone)
-{
- xzm_debug_assert(_xzm_malloc_zone_is_xzm(main_zone));
- xzm_debug_assert(_xzm_malloc_zone_is_main((xzm_malloc_zone_t)main_zone));
- xzm_main_malloc_zone_t main_ref = (xzm_main_malloc_zone_t)main_zone;
- _xzm_global_state_lock(main_ref, XZM_LOCK_LOCK);
-}
-
-void
-xzm_force_unlock_global_state(malloc_zone_t *main_zone)
-{
- xzm_debug_assert(_xzm_malloc_zone_is_xzm(main_zone));
- xzm_debug_assert(_xzm_malloc_zone_is_main((xzm_malloc_zone_t)main_zone));
- xzm_main_malloc_zone_t main_ref = (xzm_main_malloc_zone_t)main_zone;
- _xzm_global_state_lock(main_ref, XZM_LOCK_UNLOCK);
-}
-
-void
-xzm_force_reinit_lock_global_state(malloc_zone_t *main_zone)
-{
- xzm_debug_assert(_xzm_malloc_zone_is_xzm(main_zone));
- xzm_debug_assert(_xzm_malloc_zone_is_main((xzm_malloc_zone_t)main_zone));
- xzm_main_malloc_zone_t main_ref = (xzm_main_malloc_zone_t)main_zone;
- _xzm_global_state_lock(main_ref, XZM_LOCK_INIT);
-}
-
-static void
-xzm_malloc_zone_destroy(xzm_malloc_zone_t zone)
-{
- xzm_main_malloc_zone_t main_ref = zone->xzz_main_ref;
- // It is not sane to permit destroy on the main mzone in the general case:
- // the libsystem initializer makes allocations that need to stay live. A
- // sufficiently early interposing allocator (e.g. ASan) might plausibly be
- // able to do it early enough, but it doesn't do any harm to keep the main
- // zone around empty in that case.
- if (_xzm_malloc_zone_is_main(zone)) {
- return;
- }
-
- // Hold the zone lock while modifying the zone's chunk lists to guard
- // against possible fork() problems (e.g. a tiny chunk's walk_locked bit
- // being set)
- _malloc_lock_lock(&zone->xzz_lock);
-
- // During zone destroy, all the chunks belonging to this zone should exist
- // on zone local lists:
- // - tiny chunks should be on the xzone's ALL list
- // - small chunks will either be in a slot or on the partial or full list
- // - large and huge chunks will be on the zone's chunkq large
- // We require that clients not be allocating from or freeing to zones that
- // are mid destroy, so we don't need to acquire any locks while pulling
- // chunks off of these lists
- LIST_HEAD(, xzm_slice_s) chunks_to_free;
- LIST_INIT(&chunks_to_free);
-
- for (int i = XZM_XZONE_INDEX_FIRST; i < zone->xzz_xzone_count; i++) {
- xzm_xzone_t xz = &zone->xzz_xzones[i];
- if (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX &&
- !zone->xzz_small_freelist_enabled) {
- // small xzone, pull chunks off of the batch, partial, and full list
- xzm_slice_t chunk = xz->xz_chunkq_batch;
- xzm_slice_t temp_chunk = NULL;
- while (chunk) {
- xzm_debug_assert(!chunk->xzc_bits.xzcb_preallocated);
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- temp_chunk = *_xzm_segment_slice_meta_batch_next(zone, chunk);
- xzm_debug_assert(_xzm_slice_meta_is_batch_pointer(zone,
- temp_chunk));
- xzm_debug_assert(main_ref->xzmz_batch_size);
-#if CONFIG_XZM_DEFERRED_RECLAIM
- // Reset its reclaim id so it does not appear to have been deferred
- *_xzm_slice_meta_reclaim_id(zone, chunk) = VM_RECLAIM_ID_NULL;
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- chunk = temp_chunk;
- }
- LIST_FOREACH_SAFE(chunk, &xz->xz_chunkq_partial, xzc_entry,
- temp_chunk) {
- xzm_debug_assert(!chunk->xzc_bits.xzcb_preallocated);
- LIST_REMOVE(chunk, xzc_entry);
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- LIST_FOREACH_SAFE(chunk, &xz->xz_chunkq_full, xzc_entry,
- temp_chunk) {
- xzm_debug_assert(!chunk->xzc_bits.xzcb_preallocated);
- LIST_REMOVE(chunk, xzc_entry);
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- // Slots
- for (xzm_allocation_index_t j = 0; j < zone->xzz_slot_count; j++) {
- xzm_xzone_allocation_slot_t xas =
- _xzm_xzone_allocation_slot_for_index(zone, xz, j);
- chunk = xas->xas_chunk;
- if (chunk) {
- xzm_debug_assert(!chunk->xzc_bits.xzcb_preallocated);
- xzm_debug_assert(chunk->xzc_alloc_idx == j+1);
- xzm_debug_assert(!chunk->xzc_bits.xzcb_on_partial_list);
- xas->xas_chunk = NULL;
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- }
- // Preallocated chunks
- LIST_FOREACH_SAFE(chunk, &xz->xz_chunkq_preallocated, xzc_entry,
- temp_chunk)
- {
- LIST_REMOVE(chunk, xzc_entry);
- // This chunk won't have an mzone set, but otherwise will be
- // initialized enough to be freed
- _xzm_xzone_fresh_chunk_init(xz, chunk,
- XZM_SLICE_KIND_SMALL_CHUNK);
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_debug_assert(*_xzm_slice_meta_reclaim_id(zone, chunk) ==
- VM_RECLAIM_ID_NULL);
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- } else {
- // freelist xzone, empty the batch list, which will reset reclaim ids
- xzm_slice_t chunk = NULL;
- while ((chunk = _xzm_chunk_list_pop(zone, &xz->xz_batch_list,
- XZM_CHUNK_LINKAGE_BATCH, NULL))) {
- xzm_debug_assert(main_ref->xzmz_batch_size);
- }
- // enumerate all chunks via the ALL list
- while ((chunk = _xzm_chunk_list_pop(zone, &xz->xz_all_list,
- XZM_CHUNK_LINKAGE_ALL, NULL))) {
- xzm_assert(!chunk->xzc_bits.xzcb_preallocated);
- chunk->xzc_atomic_meta.xca_on_partial_list = false;
- chunk->xzc_atomic_meta.xca_on_empty_list = false;
- chunk->xzc_atomic_meta.xca_alloc_idx = XZM_SLOT_INDEX_EMPTY;
-
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- xzm_slice_kind_t kind;
- if (xz->xz_block_size > XZM_TINY_BLOCK_SIZE_MAX) {
- kind = XZM_SLICE_KIND_TINY_CHUNK;
- } else {
- kind = XZM_SLICE_KIND_SMALL_FREELIST_CHUNK;
- }
-
- // Preallocated freelist chunks
- while ((chunk = _xzm_chunk_list_pop(zone, &xz->xz_preallocated_list,
- XZM_CHUNK_LINKAGE_MAIN, NULL))) {
- // chunk should be madvised and reset by the free path below
- _xzm_xzone_fresh_chunk_init(xz, chunk, kind);
- xzm_debug_assert(chunk->xzc_atomic_meta.xca_alloc_head !=
- XZM_FREE_MADVISED);
-#if CONFIG_XZM_DEFERRED_RECLAIM
- xzm_debug_assert(*_xzm_slice_meta_reclaim_id(zone, chunk) ==
- VM_RECLAIM_ID_NULL);
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- LIST_INSERT_HEAD(&chunks_to_free, chunk, xzc_entry);
- }
- }
- }
-
- _malloc_lock_unlock(&zone->xzz_lock);
-
- xzm_slice_t span = NULL;
- xzm_slice_t temp_span = NULL;
- LIST_FOREACH_SAFE(span, &chunks_to_free, xzc_entry, temp_span) {
- xzm_xzone_t xz = &zone->xzz_xzones[span->xzc_xzone_idx];
-#if CONFIG_XZM_DEFERRED_RECLAIM
- // If this chunk is non-sequestered and has a reclaim index, we need to
- // get it back from the reclaim buffer so it can be returned to the span
- // queue in xzm_chunk_free
- if (!xz->xz_sequestered && _xzm_chunk_should_defer_reclamation(main_ref, span)) {
- int retries = 0;
- while (_xzm_segment_slice_is_deferred(_xzm_segment_for_slice(zone,
- span), span)) {
- if (xzm_chunk_mark_used(zone, span, NULL)) {
- xzm_debug_assert(span->xzc_bits.xzcb_kind ==
- XZM_SLICE_KIND_TINY_CHUNK ||
- span->xzc_bits.xzcb_kind ==
- XZM_SLICE_KIND_SMALL_FREELIST_CHUNK);
- // We don't know if the chunk was successfully reclaimed, so
- // reset the allocation head so that it is synchronously
- // madvised
- span->xzc_atomic_meta.xca_alloc_head = XZM_FREE_NULL;
- break;
- }
- xzm_reclaim_force_sync(main_ref->xzmz_reclaim_buffer);
- xzm_assert(retries < 10);
- retries++;
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- if (span->xzc_bits.xzcb_kind == XZM_SLICE_KIND_TINY_CHUNK ||
- span->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_FREELIST_CHUNK) {
- // If the chunk is already madvised, it should already be cleared if
- // it's below the zero on free threshold
- if (span->xzc_atomic_meta.xca_alloc_head != XZM_FREE_MADVISED) {
- size_t size = 0;
- void *chunk_start = _xzm_chunk_start_ptr(zone, span, &size);
- xzm_debug_assert(size == XZM_TINY_CHUNK_SIZE ||
- size == XZM_SMALL_FREELIST_CHUNK_SIZE);
- if (xz->xz_block_size <= XZM_ZERO_ON_FREE_THRESHOLD) {
-#if CONFIG_MTE
- if (xz->xz_tagged) {
- memtag_disable_checking();
- bzero(chunk_start, size);
- memtag_enable_checking();
- } else {
-#endif
- bzero(chunk_start, size);
-#if CONFIG_MTE
- }
-#endif
- }
-
- xzm_segment_group_t sg = _xzm_segment_group_for_slice(zone,
- span);
- xzm_segment_group_segment_madvise_chunk(sg, span);
- span->xzc_atomic_meta.xca_free_count = 0;
- span->xzc_atomic_meta.xca_alloc_head = XZM_FREE_MADVISED;
- }
- span->xzc_bits.xzcb_on_partial_list = false;
- _xzm_xzone_chunk_free(zone, &zone->xzz_xzones[span->xzc_xzone_idx],
- span, false);
- } else if (span->xzc_bits.xzcb_kind == XZM_SLICE_KIND_SMALL_CHUNK) {
- // Small chunks are typically, in the non-MTE configuration,
- // madvised when their individual blocks are freed. Since we're
- // freeing the whole chunk, we cautiously madvise the whole chunk
- bool madvise_needed = true;
- span->xzc_bits.xzcb_on_partial_list = false;
- _xzm_xzone_chunk_free(zone, &zone->xzz_xzones[span->xzc_xzone_idx],
- span, madvise_needed);
- } else {
- xzm_abort_with_reason("Unexpected chunk kind",
- span->xzc_bits.xzcb_kind);
- }
- }
-
- LIST_FOREACH_SAFE(span, &zone->xzz_chunkq_large, xzc_entry, temp_span) {
- // NB: _xzm_free_large_huge removes the chunk from chunkq_large, which
- // is why we need to use LIST_FOREACH_SAFE here
- _xzm_free_large_huge(zone, span);
- }
-
- xzm_metapool_t mp = &main_ref->xzmz_metapools[XZM_METAPOOL_MZONE_IDX];
- xzm_reused_mzone_index_t reused = xzm_metapool_alloc(mp);
- reused->xrmi_mzone_idx = zone->xzz_mzone_idx;
- _malloc_lock_lock(&main_ref->xzmz_mzones_lock);
- SLIST_INSERT_HEAD(&main_ref->xzmz_reusable_mzidxq, reused, xrmi_mzone_entry);
- _malloc_lock_unlock(&main_ref->xzmz_mzones_lock);
-
- mvm_deallocate_plat(zone, zone->xzz_total_size, 0, NULL);
-}
-
-#pragma mark Test helpers
-
-bool
-xzm_ptr_lookup_4test(xzm_malloc_zone_t zone, void *ptr,
- xzm_slice_kind_t *kind_out, xzm_segment_group_id_t *sgid_out,
- xzm_xzone_bucket_t *bucket_out)
-{
- xzm_xzone_t xz = NULL;
- xzm_chunk_t chunk = _xzm_ptr_lookup(zone, ptr, &xz, NULL, NULL);
- if (!chunk) {
- return false;
- }
-
- xzm_slice_kind_t kind = chunk->xzc_bits.xzcb_kind;
- *kind_out = kind;
-
- xzm_segment_t segment = _xzm_segment_for_slice(zone, chunk);
- *sgid_out = segment->xzs_segment_group->xzsg_id;
-
- if (_xzm_slice_kind_uses_xzones(kind)) {
- xzm_assert(xz);
- *bucket_out = xz->xz_bucket;
- }
-
- return true;
-}
-
-uint8_t
-xzm_type_choose_ptr_bucket_4test(const xzm_bucketing_keys_t *const keys,
- uint8_t ptr_bucket_count, malloc_type_descriptor_t type_desc)
-{
- return _xzm_type_choose_ptr_bucket(keys, ptr_bucket_count, type_desc);
-}
-
-#pragma mark Zone creation and initialization
-
-struct xzm_process_config_s {
- xzm_slot_config_t xzpc_slot_config;
- bool xzpc_madvise_workaround;
- bool xzpc_disable_vm_user_ranges;
-};
-
-typedef const struct xzm_process_config_s *xzm_process_config_t;
-
-#if CONFIG_MALLOC_PROCESS_IDENTITY
-
-static const struct xzm_process_config_s _xzm_launchd_process_config = {
- // almost everything in launchd happens on the event queue
- .xzpc_slot_config = XZM_SLOT_SINGLE,
-};
-
-static const struct xzm_process_config_s _xzm_notifyd_process_config = {
- // almost everything in notifyd happens on its global workloop
- .xzpc_slot_config = XZM_SLOT_SINGLE,
-};
-
-static const xzm_process_config_t _xzm_process_configs[MALLOC_PROCESS_COUNT] = {
- [MALLOC_PROCESS_LAUNCHD] = &_xzm_launchd_process_config,
- [MALLOC_PROCESS_NOTIFYD] = &_xzm_notifyd_process_config,
-};
-
-#endif // CONFIG_MALLOC_PROCESS_IDENTITY
-
-#define XZM_FRONT_RANDOM_SIZE 32
-
-static xzm_front_index_t
-_xzm_random_front_index(uint8_t front_random[static XZM_FRONT_RANDOM_SIZE],
- size_t allocation_front_count, xzm_xzone_index_t xz_idx)
-{
- if (allocation_front_count == 1) {
- return XZM_FRONT_INDEX_DEFAULT;
- }
-
- xzm_assert(allocation_front_count == 2);
-
- size_t byte = xz_idx / CHAR_BIT;
- size_t bit = xz_idx % CHAR_BIT;
-
- xzm_assert(byte < XZM_FRONT_RANDOM_SIZE);
- return (bool)(front_random[byte] & (1 << bit));
-}
-
-// mimalloc: MI_PAGE_QUEUES_EMPTY
-static const size_t _xzm_bin_sizes[] = {
- 16,
- 32,
- 48,
- 64,
- 80,
- 96,
- 112,
- 128,
- 160,
- 192,
- 224,
- 256,
- 320,
- 384,
- 448,
- 512,
- 640,
- 768,
- 896,
- 1024,
- 1280,
- 1536,
- 1792,
- 2048,
- 2560,
- 3072,
- 3584,
- 4096,
- 5120,
- 6144,
- 7168,
- 8192,
- 10240,
- 12288,
- 14336,
- 16384,
- 20480,
- 24576,
- 28672,
- 32768,
-};
-
-// mimalloc: MI_SEGMENT_SPAN_QUEUES_EMPTY
-static const size_t _xzm_span_queue_slice_counts[] = {
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 10,
- 12,
- 14,
- 16,
- 20,
- 24,
- 28,
- 32,
- 40,
- 48,
- 56,
- 64,
- 80,
- 96,
- 112,
- 128,
- 160,
- 192,
- 224,
- 256,
-};
-
-MALLOC_STATIC_ASSERT(
- countof_unsafe(_xzm_span_queue_slice_counts) == XZM_SPAN_QUEUE_COUNT,
- "all span queues have slice counts");
-
-static const char ptr_buckets_boot_arg[] = "xzone_ptr_buckets";
-static const char xzone_slot_config_boot_arg[] = "malloc_xzone_slot_config";
-static const char xzone_guard_pages_boot_arg[] = "xzone_guard_pages";
-
-static void
-_xzm_initialize_const_zone_data(xzm_malloc_zone_t zone,
- size_t size, xzm_mzone_index_t mzone_idx, size_t xzone_count,
- size_t slot_count, xzm_xzone_t xzones,
- xzm_xzone_allocation_slot_t slots, xzm_main_malloc_zone_t main_ref,
- xzm_slot_config_t initial_slot_config, uint32_t initial_slot_threshold,
- xzm_slot_config_t max_slot_config,
- uint32_t list_upgrade_threshold_single,
- uint32_t list_upgrade_threshold_cluster, uint32_t list_upgrade_period,
- uint32_t slot_upgrade_threshold_single,
- uint32_t slot_upgrade_threshold_cluster, uint32_t slot_upgrade_period,
- uint64_t tiny_thrash_threshold, uint64_t small_thrash_threshold,
- uint64_t small_thrash_limit_size, uint64_t debug_flags,
- bool small_freelist, xzm_slot_config_t max_list_config,
- xzm_chunk_list_t partial_lists)
-{
- // We're making some assumptions about layout here, but I think that's fine
- xzm_debug_assert((uintptr_t)zone + size >= (uintptr_t)slots +
- sizeof(struct xzm_xzone_allocation_slot_s) * xzone_count * slot_count);
- xzm_debug_assert((uintptr_t)slots >= (uintptr_t)xzones +
- sizeof(struct xzm_xzone_s) * xzone_count);
-
- *zone = (struct xzm_malloc_zone_s){
- .xzz_basic_zone = {
- .version = 16,
- .size = (void *)xzm_malloc_zone_size,
- .malloc = (void *)xzm_malloc_zone_malloc,
- .calloc = (void *)xzm_malloc_zone_calloc,
- .valloc = (void *)xzm_malloc_zone_valloc,
- .free = (void *)xzm_malloc_zone_free,
- .realloc = (void *)xzm_malloc_zone_realloc,
- .destroy = (void *)xzm_malloc_zone_destroy,
- // Note: batch_malloc and batch_free introduce way too much
- // complexity and maintenance burden to be worth whatever
- // marginal performance benefit they may offer, so we don't
- // really want to implement them.
- //
- // Technically they're supposed to be optional, so ideally we'd
- // just leave them NULL. However, we've never had a default
- // zone that didn't have them, so we open ourselves up to
- // compatibility risks with code that either isn't prepared to
- // deal with getting 0 or isn't prepared to wrap a zone that
- // doesn't have an implementation. Even within the project
- // we depend on the default zone having an implementation: the
- // virtual default zone calls through without checking for NULL. So,
- // rather than leaving them NULL, we keep trivial implementations
- // that just wrap plain malloc and free.
- .batch_malloc = malloc_zone_batch_malloc_fallback,
- .batch_free = malloc_zone_batch_free_fallback,
- .introspect = (struct malloc_introspection_t
- *)&xzm_malloc_zone_introspect,
- .memalign = (void *)xzm_malloc_zone_memalign,
- .free_definite_size = (void *)xzm_malloc_zone_free_definite_size,
- .pressure_relief = (void *)malloc_zone_pressure_relief_fallback,
- .claimed_address = (void *)xzm_malloc_zone_claimed_address,
- .try_free_default = (void *)xzm_malloc_zone_try_free_default,
- .malloc_with_options = (void *)xzm_malloc_zone_malloc_with_options,
-
- .malloc_type_malloc = (void *)xzm_malloc_zone_malloc_type_malloc,
- .malloc_type_calloc = (void *)xzm_malloc_zone_malloc_type_calloc,
- .malloc_type_realloc = (void *)xzm_malloc_zone_malloc_type_realloc,
- .malloc_type_memalign = (void *)xzm_malloc_zone_malloc_type_memalign,
- .malloc_type_malloc_with_options =
- (void *)xzm_malloc_zone_malloc_type_malloc_with_options,
- },
- .xzz_total_size = size,
- .xzz_mzone_idx = mzone_idx,
- .xzz_xzone_count = xzone_count,
- .xzz_slot_count = slot_count,
- .xzz_xzones = xzones,
- .xzz_xzone_allocation_slots = slots,
- .xzz_partial_lists = partial_lists,
- .xzz_main_ref = main_ref,
- .xzz_max_list_config = max_list_config,
- .xzz_initial_slot_config = initial_slot_config,
- .xzz_max_slot_config = max_slot_config,
- .xzz_small_freelist_enabled = small_freelist,
- .xzz_list_upgrade_threshold = {
- list_upgrade_threshold_single,
- list_upgrade_threshold_cluster,
- },
- .xzz_list_upgrade_period = list_upgrade_period,
- .xzz_slot_initial_threshold = initial_slot_threshold,
- .xzz_slot_upgrade_threshold = {
- slot_upgrade_threshold_single,
- slot_upgrade_threshold_cluster,
- },
- .xzz_slot_upgrade_period = slot_upgrade_period,
- .xzz_tiny_thrash_threshold = tiny_thrash_threshold,
- .xzz_small_thrash_threshold = small_thrash_threshold,
- .xzz_small_thrash_limit_size = small_thrash_limit_size,
- .xzz_lock = _MALLOC_LOCK_INIT,
- .xzz_fork_lock = _MALLOC_LOCK_INIT,
- .xzz_flags = debug_flags,
- };
-
- bool use_slowpath_zone_functions = false;
- if (debug_flags & MALLOC_DO_SCRIBBLE ||
- debug_flags & MALLOC_PURGEABLE) {
- use_slowpath_zone_functions = true;
- }
-
- if (use_slowpath_zone_functions) {
- zone->xzz_basic_zone.malloc = (void *)xzm_malloc_zone_malloc_slow;
- zone->xzz_basic_zone.calloc = (void *)xzm_malloc_zone_calloc_slow;
- zone->xzz_basic_zone.valloc = (void *)xzm_malloc_zone_valloc_slow;
- zone->xzz_basic_zone.free = (void *)xzm_malloc_zone_free_slow;
- zone->xzz_basic_zone.realloc = (void *)xzm_malloc_zone_realloc_slow;
- zone->xzz_basic_zone.memalign = (void *)xzm_malloc_zone_memalign_slow;
- zone->xzz_basic_zone.free_definite_size =
- (void *)xzm_malloc_zone_free_definite_size_slow;
- zone->xzz_basic_zone.try_free_default =
- (void *)xzm_malloc_zone_try_free_default_slow;
- zone->xzz_basic_zone.malloc_with_options =
- (void *)xzm_malloc_zone_malloc_with_options_slow;
- zone->xzz_basic_zone.malloc_type_malloc =
- (void *)xzm_malloc_zone_malloc_type_malloc_slow;
- zone->xzz_basic_zone.malloc_type_calloc =
- (void *)xzm_malloc_zone_malloc_type_calloc_slow;
- zone->xzz_basic_zone.malloc_type_realloc =
- (void *)xzm_malloc_zone_malloc_type_realloc_slow;
- zone->xzz_basic_zone.malloc_type_memalign =
- (void *)xzm_malloc_zone_malloc_type_memalign_slow;
- zone->xzz_basic_zone.malloc_type_malloc_with_options =
- (void *)xzm_malloc_zone_malloc_type_malloc_with_options_slow;
- }
-}
-
-static void
-_xzm_initialize_xzone_data(xzm_malloc_zone_t zone,
- xzm_slot_config_t list_config, xzm_guard_page_config_t guard_config,
- uint8_t front_random[XZM_FRONT_RANDOM_SIZE], bool all_data)
-{
- xzm_main_malloc_zone_t main_ref = _xzm_malloc_zone_main(zone);
- bool is_main = _xzm_malloc_zone_is_main(zone);
-
- xzm_debug_assert((is_main && front_random) || (!is_main && !front_random));
-
- // NOTE: although this is technically a layering violation with Libc and
- // CoreCrypto, we and they believe it to be safe
- //
- // TODO: allow overriding with a fixed value for testing
- uint64_t freelist_cookie = 0;
- arc4random_buf(&freelist_cookie, sizeof(freelist_cookie));
- if (!freelist_cookie) {
- freelist_cookie = 0xdeaddeaddeaddeadull;
- }
-#if CONFIG_MTE
- if (zone->xzz_memtag_config.enabled) {
- // Clear the tag bits from the freelist cookie to ensure they can always be
- // used to store a block's tag
- freelist_cookie = (uint64_t)memtag_strip_address(
- (uint8_t *)freelist_cookie);
- }
-#endif
- zone->xzz_freelist_cookie = freelist_cookie;
-
- xzm_xzone_index_t xzidx = XZM_XZONE_INDEX_FIRST;
- for (size_t i = 0; i < countof(_xzm_bin_sizes); i++) {
- if (is_main) {
- main_ref->xzmz_xzone_bin_offsets[i] = xzidx;
- }
- size_t bin_buckets = main_ref->xzmz_xzone_bin_bucket_counts[i];
- for (int bucket = 0; bucket < bin_buckets; xzidx++, bucket++) {
- xzm_xzone_t xz = &zone->xzz_xzones[xzidx];
- size_t block_size = main_ref->xzmz_xzone_bin_sizes[i];
- size_t chunk_size;
- if (block_size <= XZM_TINY_BLOCK_SIZE_MAX) {
- chunk_size = XZM_TINY_CHUNK_SIZE;
- } else if (zone->xzz_small_freelist_enabled) {
- chunk_size = XZM_SMALL_FREELIST_CHUNK_SIZE;
- } else {
- chunk_size = XZM_SMALL_CHUNK_SIZE;
- }
-
- size_t chunk_capacity = chunk_size / block_size;
-
- // The early budget is only in the main zone xzones, all other
- // zones decrement those
- uint16_t early_budget = 0;
- if (is_main && main_ref->xzmz_mfm_address) {
- if (block_size <= 256) {
- early_budget = 2048 / block_size;
- } else if (block_size <= 512) {
- early_budget = 4096 / block_size;
- } else if (block_size <= 2048) {
- early_budget = 8192 / block_size;
- } else if (block_size <= 8192) {
- early_budget = 1;
- }
- }
-
- xzm_segment_group_id_t sgid;
- bool sequestered;
- if ((bucket == XZM_XZONE_BUCKET_DATA || all_data) &&
-#if XZM_NARROW_BUCKETING
- !main_ref->xzmz_narrow_bucketing &&
-#endif
- !XZM_BUCKET_POINTER_ONLY) {
- sgid = XZM_SEGMENT_GROUP_DATA;
- sequestered = false;
- } else {
- sgid = XZM_SEGMENT_GROUP_POINTER_XZONES;
- sequestered = true;
- }
-
- // Enable sequestering for small chunks to avoid coalescing with
- // adjacent chunks that may not already be marked as free
- if (block_size > XZM_TINY_BLOCK_SIZE_MAX &&
- main_ref->xzmz_defer_small) {
- sequestered = true;
- }
-
- xzm_front_index_t front;
- if (is_main) {
- if (sgid == XZM_SEGMENT_GROUP_POINTER_XZONES) {
- front = _xzm_random_front_index(front_random,
- main_ref->xzmz_allocation_front_count, xzidx);
- } else {
- xzm_debug_assert(sgid == XZM_SEGMENT_GROUP_DATA);
- front = XZM_FRONT_INDEX_DEFAULT;
- }
- } else {
- xzm_xzone_t main_xz = &main_ref->xzmz_base.xzz_xzones[xzidx];
- front = main_xz->xz_front;
- }
-
- uint8_t run_length = 0;
- uint8_t guard_density = 0;
- if (guard_config->xgpc_enabled) {
- if (guard_config->xgpc_enabled_for_data) {
- // To avoid unbounded VA growth caused by unusable free
- // spans in the data segments, all xzones are sequestered
- // when guards are enabled
- sequestered = true;
- }
-
- if (sequestered) {
- if (block_size <= XZM_TINY_BLOCK_SIZE_MAX) {
- run_length = guard_config->xgpc_max_run_tiny;
- guard_density = guard_config->xgpc_tiny_guard_density;
- } else {
- run_length = guard_config->xgpc_max_run_small;
- guard_density = guard_config->xgpc_small_guard_density;
- }
- }
- }
-
- *xz = (struct xzm_xzone_s){
- .xz_segment_group_id = sgid,
- .xz_front = front,
- .xz_block_size = block_size,
- .xz_quo_magic = XZM_MAGIC_QUO(block_size),
- .xz_align_magic = XZM_MAGIC_ALIGNED(block_size),
- .xz_chunk_capacity = (uint32_t)chunk_capacity,
- .xz_lock = _MALLOC_LOCK_INIT,
- .xz_early_budget = early_budget,
- .xz_idx = xzidx,
- .xz_mzone_idx = zone->xzz_mzone_idx,
- .xz_bucket = (xzm_xzone_bucket_t)bucket,
- .xz_sequestered = sequestered,
- .xz_slot_config = XZM_SLOT_SINGLE,
- .xz_guard_config = {
- .xxgc_max_run_length = run_length,
- .xxgc_density = guard_density,
- }
- };
-
- // Initialize all possible slots with the initial slot config
- xzm_debug_assert(zone->xzz_slot_count ==
- _xzm_get_limit_allocation_index(zone->xzz_max_slot_config));
- xzm_allocation_index_t limit_list =
- _xzm_get_limit_allocation_index(zone->xzz_max_list_config);
- for (xzm_allocation_index_t i = 0; i < zone->xzz_slot_count;
- ++i) {
- xzm_xzone_allocation_slot_t xas =
- _xzm_xzone_allocation_slot_for_index(zone, xz, i);
- xas->xas_counters.xsc_slot_config = xz->xz_slot_config;
-#if CONFIG_TINY_ALLOCATION_SLOT_LOCK
- _malloc_lock_init(&xas->xas_lock);
-#endif // CONFIG_TINY_ALLOCATION_SLOT_LOCK
-
- if (i >= limit_list) {
- continue;
- }
-
- xzm_chunk_list_t xcl =
- _xzm_xzone_chunk_list_for_index(zone, xz,
- zone->xzz_partial_lists, i);
- xcl->xcl_counters.xsc_slot_config = xz->xz_list_config;
- }
-
-#if CONFIG_MTE
- xz->xz_tagged = _xzm_zone_memtag_block(zone, block_size,
- /*data=*/(bucket == XZM_XZONE_BUCKET_DATA));
-
- // Disable the early allocator for zones for which we don't
- // enable tagging. This is because MFM will always provide
- // tagged memory, and this might cause the client to violate
- // MTE mapping policies that are expected to hold for
- // data-only allocations, which are normally not tagged in
- // the default configuration.
- // `zone->xzz_memtag_config.enabled` is used as proxy for
- // `mfm_memtag_enabled`.
- if (zone->xzz_memtag_config.enabled && !xz->xz_tagged) {
- xz->xz_early_budget = 0;
- }
-#endif // CONFIG_MTE
- }
- }
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static const char *
-_xzm_development_only_getenv(const char **envp, const char *key)
-{
- return malloc_internal_security_policy ? _simple_getenv(envp, key) : NULL;
-}
-
-MALLOC_ALWAYS_INLINE MALLOC_INLINE
-static const char *
-_xzm_production_getenv(const char **envp, const char *key)
-{
- return _simple_getenv(envp, key);
-}
-
-#ifdef _simple_getenv
-#undef _simple_getenv
-#endif
-
-#define _simple_getenv(...) \
- static_assert(0, "Use _xzm_(development_only|release)_getenv instead")
-
-
-malloc_zone_t *
-xzm_main_malloc_zone_create(unsigned debug_flags, const char **envp,
- const char **apple, const char *bootargs)
-{
- bool security_critical = false, security_critical_max_perf = false;
-#if CONFIG_MALLOC_PROCESS_IDENTITY
- security_critical = malloc_process_is_security_critical(
- malloc_process_identity);
- security_critical_max_perf = malloc_process_is_security_critical_max_perf(
- malloc_process_identity);
-#endif // CONFIG_MALLOC_PROCESS_IDENTITY
-
- uint8_t front_random[XZM_FRONT_RANDOM_SIZE];
-
- xzm_bucketing_keys_t bucketing_keys;
- // executable_boothash is a salted hash of the concatenation of the current
- // boot session UUID and cdhash of the main executable
- const char *boothash = _xzm_production_getenv(apple, "executable_boothash");
- if (!boothash) {
- // executable_boothash isn't populated when the executable isn't
- // codesigned (rdar://118451590). This should be very rare (only when
- // the system is running with reduced security), so to guard against
- // accidentally breaking the boothash, crash in known security-critical
- // processes
- if (security_critical) {
- xzm_abort("couldn't find executable_boothash");
- }
-
- arc4random_buf(&bucketing_keys, sizeof(bucketing_keys));
- } else {
- size_t boothash_len = strlen(boothash);
- if (boothash_len < 32) {
- xzm_abort_with_reason("invalid executable_boothash length",
- boothash_len);
- }
-
- const size_t part_len = 16;
- char boothash_part[part_len + 1];
- unsigned long long value;
- const size_t keys_cnt = sizeof(bucketing_keys.xbk_key_data) /
- sizeof(bucketing_keys.xbk_key_data[0]);
-
- for (size_t i = 0; i < keys_cnt; i++) {
- memcpy(boothash_part, &boothash[i * part_len], part_len);
- boothash_part[part_len] = '\0';
-
- value = strtoull(boothash_part, NULL, 16);
- if ((value == 0 && errno == EINVAL) ||
- (value == ULLONG_MAX && errno == ERANGE)) {
- xzm_abort("invalid executable_boothash string");
- }
- bucketing_keys.xbk_key_data[i] = value;
- }
- }
-
- // XXX We need a bunch of additional per-boot entropy for allocation front
- // assignments. It may make sense to increase the amount of per-boot
- // entropy supplied by the kernel, but for now we make do by deriving
- // from what we've already got.
- //
- // TODO: If we do continue to derive, we should probably be seeding a PRNG
- // instead
- static_assert(sizeof(front_random) == CCSHA256_OUTPUT_SIZE,
- "front_random size");
-
- const struct ccdigest_info *di = ccsha256_di();
- ccdigest_di_decl(di, dc);
- ccdigest_init(di, dc);
- char diversifier[] = "xzone malloc front random";
- ccdigest_update(di, dc, sizeof(diversifier), diversifier);
- ccdigest_update(di, dc, sizeof(bucketing_keys),
- bucketing_keys.xbk_key_data);
- ccdigest_final(di, dc, front_random);
- ccdigest_di_clear(di, dc);
-
-
- // TODO: scrub executable_boothash from the apple array like we do for
- // malloc_entropy?
-
- xzm_process_config_t process_config = NULL;
-#if CONFIG_MALLOC_PROCESS_IDENTITY
- if (malloc_process_identity != MALLOC_PROCESS_NONE) {
- process_config = _xzm_process_configs[malloc_process_identity];
- }
-#endif // CONFIG_MALLOC_PROCESS_IDENTITY
-
- bool use_slowpath_zone_functions = false;
- if (debug_flags & MALLOC_DO_SCRIBBLE) {
- use_slowpath_zone_functions = true;
- }
-
- xzm_slot_config_t max_slot_config = XZM_SLOT_CPU;
-
- size_t override_ptr_bucket_count = 0;
-#if XZM_NARROW_BUCKETING
- // Platforms that support narrow bucketing default to using it
- bool narrow_bucketing = true;
-#endif
-
- char value_buf[256];
- const char *flag = malloc_common_value_for_key_copy(bootargs,
- ptr_buckets_boot_arg, value_buf, sizeof(value_buf));
- if (flag) {
- const char *endp;
- long value = malloc_common_convert_to_long(flag, &endp);
- if (!*endp && value > 0 && value <= XZM_POINTER_BUCKETS_MAX) {
- override_ptr_bucket_count = (size_t)value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "Invalid xzone_ptr_buckets value %ld - ignored.\n", value);
- }
- }
-
-#if MALLOC_TARGET_IOS_ONLY || TARGET_OS_WATCH
- if (!security_critical) {
-#if MALLOC_TARGET_IOS_ONLY
- override_ptr_bucket_count = 2;
-#elif TARGET_OS_WATCH
- override_ptr_bucket_count = 1;
-#endif
- }
-#endif
-
- // There are a few ways MallocMaxMagazines might have been set:
- // - JetsamProperties sets it for a number of daemons on iOS
- // - A handful of projects found the envvar on their own and set it
- // themselves in their launchd plists
- //
- // Pending an audit and rebalancing of bucketing by security sensitivity,
- // give processes that have this set and that we don't otherwise have
- // explicit policy for a reduced bucket configuration.
- //
- // If set to 1, we should also give them a reduced slot config.
- bool allow_malloc_max_magazines = true;
- // Note: this is load-bearing for wifip2pd
- if (security_critical) {
- allow_malloc_max_magazines = false;
-#if TARGET_OS_VISION
- // On visionOS, don't clamp the security-critical-max-perf processes to
- // per-cluster scaling
- if (!security_critical_max_perf) {
- max_slot_config = XZM_SLOT_CLUSTER;
- }
-#else
- // On the other platforms, since we're already clamping the
- // security-critical-max-perf processes to per-cluster scaling without
- // issue, clamp in all cases
- max_slot_config = XZM_SLOT_CLUSTER;
-#endif
- }
- if (allow_malloc_max_magazines) {
- flag = _xzm_production_getenv(envp, "MallocMaxMagazines");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 1) {
- max_slot_config = XZM_SLOT_SINGLE;
- } else if (value == 2 || value == UINT16_MAX) {
- max_slot_config = XZM_SLOT_CLUSTER;
- }
-
-#if MALLOC_TARGET_IOS_ONLY || MALLOC_TARGET_DK_IOS || \
- TARGET_OS_OSX || MALLOC_TARGET_DK_OSX
- if (value == 1 || value == 2 || value == UINT16_MAX) {
- override_ptr_bucket_count = 1;
- }
-#endif
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzonePtrBucketCount");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= XZM_POINTER_BUCKETS_MAX) {
- override_ptr_bucket_count = (size_t)value;
-#if XZM_NARROW_BUCKETING
- narrow_bucketing = false;
-#endif
- }
- }
-
- size_t ptr_bucket_count = override_ptr_bucket_count ?:
- XZM_XZONE_DEFAULT_POINTER_BUCKET_COUNT;
-
- size_t bucket_base = XZM_XZONE_BUCKET_POINTER_BASE;
-#if XZM_NARROW_BUCKETING
- if (narrow_bucketing) {
- bucket_base = 0;
- }
-#endif
-
- bool use_early_allocator = true;
-#if XZM_NARROW_BUCKETING
- if (narrow_bucketing && ptr_bucket_count == 1) {
- use_early_allocator = false;
- }
-#endif
-
- flag = _xzm_production_getenv(envp, "MallocXzoneEarlyAlloc");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- use_early_allocator = (bool)value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "MallocXzoneEarlyAlloc must be 0 or 1.\n");
- }
- }
-
- if (use_early_allocator) {
- mfm_initialize();
- }
-
- size_t buckets_per_bin = bucket_base + ptr_bucket_count;
-
- size_t bin_count = countof(_xzm_bin_sizes);
- size_t xzone_count = 1 + bin_count * buckets_per_bin;
- xzm_assert(xzone_count <= UINT8_MAX);
-
- bool madvise_workaround = false;
- if (process_config && process_config->xzpc_madvise_workaround) {
- madvise_workaround = true;
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneMadviseWorkaround");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- madvise_workaround = (bool)value;
- }
- }
-
-#if CONFIG_MTE
- struct xzm_memtag_config_s memtag_config = {0};
-
- // If the process has been spawned by setting has_sec_transition=1,
- // load a default configuration for MTE support in xzone.
- // Note that we still allow overriding the configuration through
- // environment variables.
- if (malloc_has_sec_transition) {
- memtag_config.enabled = true;
- memtag_config.tag_data = false;
- memtag_config.max_block_size = XZM_SMALL_BLOCK_SIZE_MAX;
- if (malloc_sec_transition_policy & /*TASK_SEC_POLICY_USER_DATA*/0x02) {
- memtag_config.tag_data = true;
- }
- }
-
- uint64_t max_supported_memtag_block_size = XZM_SMALL_BLOCK_SIZE_MAX;
-
- // MTE debug mode is available on public builds
- flag = _xzm_production_getenv(envp, "MallocTagAll");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 1) {
- memtag_config.tag_data = true;
- memtag_config.max_block_size = max_supported_memtag_block_size;
-
- if (!malloc_has_sec_transition) {
- malloc_report(MALLOC_REPORT_CRASH,
- "Malloc MTE debug mode (MallocTagAll=1) requires the "
- "process to be started with MTE enabled.\n");
- }
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocTagAllInternal");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 1) {
- memtag_config.tag_data = true;
- memtag_config.max_block_size = max_supported_memtag_block_size;
- }
- // Skip sanity check for general MTE enablement.
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneMemtagEnable");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- memtag_config.enabled = (bool)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneMemtagTagData");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- memtag_config.tag_data = (bool)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneMemtagMaxBlockSize");
- if (flag) {
- unsigned long long value = strtoull(flag, NULL, 10);
- // TODO: allow all sizes?
- if (value <= XZM_SMALL_BLOCK_SIZE_MAX && !(value & 0xf)) {
- memtag_config.max_block_size = value;
- }
- }
-#endif // CONFIG_MTE
-
- bool has_vm_user_ranges = true;
- if (process_config && process_config->xzpc_disable_vm_user_ranges) {
- has_vm_user_ranges = false;
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneHasRanges");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- has_vm_user_ranges = (bool)value;
- }
- }
-
-#if XZM_NARROW_BUCKETING
- // Don't waste a PTE trying to separate data at the range level if it's
- // mixed into the same xzones anyway
- if (narrow_bucketing && !security_critical) {
- has_vm_user_ranges = false;
- }
-#endif
-
- bool thread_caching = false;
-
-#if CONFIG_NANOZONE
- bool nano_config = (_malloc_engaged_nano == NANO_V2);
-
-#if MALLOC_TARGET_DK_OSX
- // TODO: clean up macOS DriverKit nano enablement config
- nano_config = false;
-#endif
-
- bool security_critical_allows_nano_config = false;
-#if CONFIG_MALLOC_PROCESS_IDENTITY
- if (malloc_process_identity == MALLOC_PROCESS_HARDENED_HEAP_CONFIG) {
- security_critical_allows_nano_config = true;
- }
-#endif
-
- if (security_critical && !security_critical_allows_nano_config) {
- // Load-bearing for MTLCompilerService
- nano_config = false;
- }
-
- if (nano_config) {
- max_slot_config = XZM_SLOT_CPU;
-
- // Not dealing with thread caching in the simulator yet
-#if !TARGET_OS_SIMULATOR
-#if CONFIG_FEATUREFLAGS_SIMPLE
- thread_caching = os_feature_enabled_simple(libmalloc,
- SecureAllocator_ThreadCaching, false);
-#else
- // Not for DriverKit yet
-#endif // CONFIG_FEATUREFLAGS_SIMPLE
-#endif // !TARGET_OS_SIMULATOR
- }
-
- flag = _xzm_production_getenv(envp, "MallocXzoneThreadCaching");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- thread_caching = (value == 1);
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "MallocXzoneThreadCaching must be one of 0,1 - got %ld\n",
- value);
- }
- }
-#endif // CONFIG_NANOZONE
-
- if (process_config && process_config->xzpc_slot_config != XZM_SLOT_LAST) {
- max_slot_config = process_config->xzpc_slot_config;
- }
-
- flag = malloc_common_value_for_key_copy(bootargs,
- xzone_slot_config_boot_arg, value_buf, sizeof(value_buf));
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < XZM_SLOT_LAST) {
- max_slot_config = (xzm_slot_config_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneSlotConfig");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < XZM_SLOT_LAST) {
- max_slot_config = (xzm_slot_config_t)value;
- }
- }
-
- xzm_slot_config_t slot_config = XZM_SLOT_SINGLE;
- uint32_t slot_threshold = 128;
- uint8_t chunk_threshold = 1;
-#if TARGET_OS_OSX
- // On macOS, not known processes get initial per-cluster
- if (!(security_critical && !security_critical_max_perf) &&
- !malloc_space_efficient_enabled) {
- slot_config = XZM_SLOT_CLUSTER;
- }
-#endif // TARGET_OS_OSX
- flag = _xzm_development_only_getenv(envp, "MallocXzoneInitialSlotConfig");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < XZM_SLOT_LAST) {
- slot_config = (xzm_slot_config_t)value;
- }
- }
- flag = _xzm_development_only_getenv(envp, "MallocXzoneInitialSlotThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- slot_threshold = (uint32_t)value;
- }
- }
- flag = _xzm_development_only_getenv(envp, "MallocXzoneInitialChunkThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value > 0 && value < UINT8_MAX) {
- chunk_threshold = (uint8_t)value;
- }
- }
-
- uint32_t list_upgrade_threshold_single = 32;
- uint32_t list_upgrade_threshold_cluster = 128;
- uint32_t slot_upgrade_threshold_single = 64;
- uint32_t slot_upgrade_threshold_cluster = 256;
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneListUpgradeThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- list_upgrade_threshold_single = (uint32_t)value;
- list_upgrade_threshold_cluster = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneListUpgradeThresholdSingle");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- list_upgrade_threshold_single = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneListUpgradeThresholdCluster");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- list_upgrade_threshold_cluster = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneSlotUpgradeThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- slot_upgrade_threshold_single = (uint32_t)value;
- slot_upgrade_threshold_cluster = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneSlotUpgradeThresholdSingle");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- slot_upgrade_threshold_single = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneSlotUpgradeThresholdCluster");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- slot_upgrade_threshold_cluster = (uint32_t)value;
- }
- }
-
- // Reset contention counters every 1K allocations to prevent long-lived
- // processes from slowly accruing additional slots
- uint32_t list_upgrade_period = 512;
- uint32_t slot_upgrade_period = 1024;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneListUpgradePeriod");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- list_upgrade_period = (uint32_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneSlotUpgradePeriod");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- slot_upgrade_period = (uint32_t)value;
- }
- }
-
- uint8_t slot_count = 0;
- switch (max_slot_config) {
- case XZM_SLOT_SINGLE:
- slot_count = 1;
- break;
- case XZM_SLOT_CLUSTER:
-#if !CONFIG_XZM_CLUSTER_AWARE
- slot_count = MIN(2, logical_ncpus);
- break;
-#else // !CONFIG_XZM_CLUSTER_AWARE
- if (ncpuclusters > 1) {
- slot_count = (uint8_t)ncpuclusters;
- break;
- }
- MALLOC_FALLTHROUGH;
-#endif // !CONFIG_XZM_CLUSTER_AWARE
- case XZM_SLOT_CPU:
- max_slot_config = XZM_SLOT_CPU; // handle fallthrough
- slot_count = (uint8_t)logical_ncpus;
- break;
- default:
- xzm_abort("Invalid xzone slot config");
- }
- if (slot_config > max_slot_config) {
- slot_config = max_slot_config;
- }
-
- const uint64_t nsec_per_msec = 1000000ull;
- mach_timebase_info_data_t tb_info;
- kern_return_t kr = mach_timebase_info(&tb_info);
- if (kr) {
- xzm_abort_with_reason("mach_timebase_info failed", kr);
- }
-
- uint64_t small_thrash_threshold = 0;
- uint64_t small_thrash_limit_size = 0;
-
- uint64_t tiny_thrash_threshold_ns = 1 * nsec_per_msec;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneTinyThrashThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- tiny_thrash_threshold_ns = (uint64_t)value * nsec_per_msec;
- }
- }
- uint64_t tiny_thrash_threshold =
- (tiny_thrash_threshold_ns * tb_info.denom / tb_info.numer);
-
- uint64_t small_thrash_threshold_ns = 0;
- uint64_t small_thrash_threshold_default_ns = 1 * nsec_per_msec;
-
- // Cap to the size limit for szone SMALL
- small_thrash_limit_size = KiB(16);
-
-#if CONFIG_NANOZONE
- if (nano_config) {
- small_thrash_threshold_ns = small_thrash_threshold_default_ns;
- }
-#else
- (void)small_thrash_threshold_default_ns;
-#endif
-
-#if CONFIG_MTE
- if (memtag_config.enabled) {
- small_thrash_threshold_ns = small_thrash_threshold_default_ns;
- }
-#endif
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneSmallThrashThreshold");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- small_thrash_threshold_ns = (uint64_t)value * nsec_per_msec;
- }
- }
-
- small_thrash_threshold =
- (small_thrash_threshold_ns * tb_info.denom / tb_info.numer);
-
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneSmallThrashLimitSize");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= XZM_SMALL_BLOCK_SIZE_MAX) {
- small_thrash_limit_size = (uint64_t)value;
- }
- }
-
- uint32_t thread_cache_activation_period = 16384;
- uint32_t thread_cache_activation_contentions = 256;
- uint64_t thread_cache_activation_time = 0;
-#if CONFIG_XZM_THREAD_CACHE
- flag = _xzm_production_getenv(envp, "MallocXzoneThreadCacheActivationPeriod");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- thread_cache_activation_period = (uint32_t)value;
- }
- }
-
- flag = _xzm_production_getenv(envp,
- "MallocXzoneThreadCacheActivationContentions");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT32_MAX) {
- thread_cache_activation_contentions = (uint32_t)value;
- }
- }
-
- uint64_t thread_cache_activation_time_ns = 1000 * nsec_per_msec;
- flag = _xzm_production_getenv(envp, "MallocXzoneThreadCacheActivationTime");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < UINT64_MAX) {
- thread_cache_activation_time_ns = (uint64_t)value * nsec_per_msec;
- }
- }
-
- thread_cache_activation_time =
- (thread_cache_activation_time_ns * tb_info.denom / tb_info.numer);
-#endif // CONFIG_XZM_THREAD_CACHE
-
-#if TARGET_OS_VISION
- bool vision_max_perf = !aggressive_madvise_enabled;
-#endif
-
- uint32_t huge_cache_max_entry_bytes = 0;
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- mach_vm_reclaim_count_t reclaim_buffer_count =
- XZM_RECLAIM_BUFFER_COUNT_DEFAULT;
- mach_vm_reclaim_count_t max_reclaim_buffer_count =
- XZM_RECLAIM_BUFFER_MAX_COUNT_DEFAULT;
- uint16_t huge_cache_size = XZM_HUGE_CACHE_SIZE_DEFAULT; // number of entries
- huge_cache_max_entry_bytes = XZM_HUGE_CACHE_MAX_ENTRY_BYTES_DEFAULT;
- bool defer_tiny = XZM_DEFERRED_RECLAIM_ENABLED_DEFAULT;
- bool defer_small = XZM_DEFERRED_RECLAIM_ENABLED_DEFAULT;
- bool defer_large = XZM_DEFERRED_RECLAIM_ENABLED_DEFAULT;
-
-#if TARGET_OS_VISION
- if (vision_max_perf) {
- defer_tiny = true;
- defer_small = true;
- defer_large = true;
- huge_cache_size = XZM_HUGE_CACHE_SIZE_ENABLED;
- }
-#endif
-
- flag = _xzm_development_only_getenv(envp, "MallocDeferredReclaim");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- defer_tiny = (value == 1);
- defer_small = (value == 1);
- defer_large = (value == 1);
- huge_cache_size = (value == 1) ? XZM_HUGE_CACHE_SIZE_ENABLED : 0;
- } else {
- malloc_report(ASL_LEVEL_ERR, "MallocDeferredReclaim must be one of 0,1 - got %ld\n", value);
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocDeferredReclaimBufferCount");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= UINT32_MAX) {
- reclaim_buffer_count = (mach_vm_reclaim_count_t)value;
- }
- }
- flag = _xzm_development_only_getenv(envp, "MallocDeferredReclaimBufferMaxCount");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= UINT32_MAX) {
- max_reclaim_buffer_count = (mach_vm_reclaim_count_t)value;
- }
- }
-
- // Round capacities up to page-alignment, or down to the maximum
- reclaim_buffer_count = mach_vm_reclaim_round_capacity(reclaim_buffer_count);
- max_reclaim_buffer_count = mach_vm_reclaim_round_capacity(max_reclaim_buffer_count);
-
- // MallocLargeCache enables both the huge cache and deferred reclamation
- // for all large allocations
- flag = _xzm_production_getenv(envp, "MallocLargeCache");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- huge_cache_size = (value == 1) ? XZM_HUGE_CACHE_SIZE_ENABLED : 0;
- defer_large = (value == 1);
- defer_tiny = (value == 1);
- defer_small = (value == 1);
- } else {
- malloc_report(ASL_LEVEL_ERR, "MallocLargeCache must be 0 or 1.\n");
- }
- }
-
-#if CONFIG_NANOZONE && !TARGET_OS_OSX
- // MallocLargeCache is only supported for processes with Nano
- if (!nano_config) {
- defer_large = false;
- huge_cache_size = 0;
-#if MALLOC_TARGET_IOS_ONLY
- defer_tiny = false;
- defer_small = false;
-#endif // MALLOC_TARGET_IOS_ONLY
- }
-#endif // CONFIG_NANOZONE && !TARGET_OS_OSX
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneHugeCacheSize");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= UINT16_MAX) {
- huge_cache_size = (uint16_t)value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "xzm: unsupported value for MallocXzoneHugeCacheSize (%ld)",
- value);
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneHugeCacheMaxEntryBytes");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value <= UINT32_MAX) {
- huge_cache_max_entry_bytes = (uint32_t)value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "xzm: unsupported value for MallocXzoneHugeCacheMaxEntryBytes (%ld)",
- value);
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneDeferTiny");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- defer_tiny = (value == 1);
- } else {
- malloc_report(ASL_LEVEL_ERR, "MallocXzoneDeferTiny must be one of 0,1 - got %ld\n", value);
- }
- }
- flag = _xzm_development_only_getenv(envp, "MallocXzoneDeferSmall");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- defer_small = (value == 1);
- } else {
- malloc_report(ASL_LEVEL_ERR, "MallocXzoneDeferSmall must be one of 0,1 - got %ld\n", value);
- }
- }
- flag = _xzm_development_only_getenv(envp, "MallocXzoneDeferLarge");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- defer_large = (value == 1);
- } else {
- malloc_report(ASL_LEVEL_ERR, "MallocXzoneDeferLarge must be one of "
- "0,1 - got %ld\n", value);
- }
- }
-
- // Known processes do not get deferred reclamation
- if ((security_critical && !security_critical_max_perf) ||
- malloc_space_efficient_enabled) {
- defer_tiny = false;
- defer_small = false;
- defer_large = false;
- huge_cache_size = 0;
- }
-
- if (huge_cache_size && !defer_large) {
- // Deferral of xzones is only supported in conjunction with large/huge
- malloc_report(ASL_LEVEL_ERR, "Huge cache requires deferred reclamation "
- "for large.\n");
- defer_large = true;
- }
-
- if ((defer_tiny || defer_small) && !defer_large) {
- // Deferral of xzones is only supported in conjunction with large/huge
- malloc_report(ASL_LEVEL_ERR, "Deferred reclamation cannot be used for "
- "xzones without large\n");
- defer_large = true;
- }
-#else // CONFIG_XZM_DEFERRED_RECLAIM
- const uint16_t huge_cache_size = 0;
- const bool defer_tiny = false;
- const bool defer_small = false;
- const bool defer_large = false;
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- bool small_freelist = false;
- bool guards_enabled = false;
- bool guards_enabled_data = false;
- uint8_t tiny_run_size = 0;
- uint8_t tiny_guard_density = 0;
- uint8_t small_run_size = 0;
- uint8_t small_guard_density = 0;
- uint8_t batch_size = 0;
-#if TARGET_OS_OSX
- batch_size = !malloc_space_efficient_enabled ? 10 : 0;
- small_freelist = (!security_critical || security_critical_max_perf)
- && !malloc_space_efficient_enabled;
-#elif TARGET_OS_VISION
- batch_size = vision_max_perf ? 10 : 0;
-#endif
-
- // Default config:
- // - Guards enabled and batching disabled for security critical (read,
- // known) processes
- // - Data guarded
- // - Tiny max run size = 8
- // - Tiny density = 64 (20% guard density)
- // - Small max run size = 3
- // - Small density = 32 (11% on 4-page small chunk config)
-
- if (security_critical) {
- guards_enabled = true;
- }
-
- if (security_critical && !security_critical_max_perf) {
- batch_size = 0;
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneGuarded");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- guards_enabled = (bool)value;
- }
- }
-
- flag = malloc_common_value_for_key_copy(bootargs,
- xzone_guard_pages_boot_arg, value_buf, sizeof(value_buf));
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- guards_enabled = (bool)value;
- } else {
- malloc_report(ASL_LEVEL_ERR, "%s must be 0 or 1.\n",
- xzone_guard_pages_boot_arg);
- }
- }
-
- if (guards_enabled) {
- guards_enabled_data = true;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneGuardedData");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- guards_enabled_data = (bool)value;
- }
- }
-
- tiny_run_size = 8;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneGuardTinyRun");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value > 0) {
- tiny_run_size = value;
- }
- }
-
- tiny_guard_density = 64;
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneGuardTinyDensity");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value > 0) {
- tiny_guard_density = value;
- }
- }
-
- small_run_size = 3;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneGuardSmallRun");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value > 0) {
- small_run_size = value;
- }
- }
-
- small_guard_density = 32;
- flag = _xzm_development_only_getenv(envp,
- "MallocXzoneGuardSmallDensity");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value > 0) {
- small_guard_density = value;
- }
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneBatchSize");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < (1u << XZM_BATCH_SIZE_BITS)) {
- batch_size = value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocSmallFreelist");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- small_freelist = (bool)value;
- }
- }
-
-#if CONFIG_XZM_CLUSTER_AWARE
- bool per_cluster_segment_groups = false;
-#endif // CONFIG_XZM_CLUSTER_AWARE
-#if TARGET_OS_OSX
- // On macOS, use per-cluster segment groups by default when not space
- // efficient
- per_cluster_segment_groups = !malloc_space_efficient_enabled;
-#elif TARGET_OS_VISION && CONFIG_XZM_CLUSTER_AWARE
- per_cluster_segment_groups = vision_max_perf;
-#endif
- size_t segment_group_ids_count = XZM_SEGMENT_GROUP_IDS_COUNT;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneDataOnly");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 1) {
- segment_group_ids_count = XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY;
- } else if (value != 0) {
- malloc_report(ASL_LEVEL_ERR,
- "MallocXzoneDataOnly must be 0 or 1.\n");
- }
- }
-
-# if CONFIG_XZM_CLUSTER_AWARE
- flag = _xzm_development_only_getenv(envp, "MallocXzonePerClusterSegmentGroups");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- per_cluster_segment_groups = value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "MallocXzonePerClusterSegmentGroups must be 0 or 1.\n");
- }
- }
-# endif // CONFIG_XZM_CLUSTER_AWARE
-
- // By default, have two allocation fronts
- size_t allocation_front_count = 2;
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneAllocationFronts");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 1 || value == 2) {
- allocation_front_count = (size_t)value;
- } else {
- malloc_report(ASL_LEVEL_ERR,
- "Unsupported MallocXzoneAllocationFronts\n");
- }
- }
-
- xzm_slot_config_t max_list_config = XZM_SLOT_SINGLE;
-#if TARGET_OS_OSX
- // On macOS, upgrade the max list config to the max slot config unless it
- // is a known process
- if ((!security_critical || security_critical_max_perf) &&
- !malloc_space_efficient_enabled) {
- max_list_config = max_slot_config;
- }
-#endif // TARGET_OS_OSX
- xzm_slot_config_t list_config = XZM_SLOT_SINGLE;
- flag = _xzm_development_only_getenv(envp, "MallocXzoneListConfig");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < XZM_SLOT_LAST) {
- list_config = (xzm_slot_config_t)value;
- }
- }
-
- flag = _xzm_development_only_getenv(envp, "MallocXzoneMaxListConfig");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value >= 0 && value < XZM_SLOT_LAST && value < max_slot_config) {
- max_list_config = (xzm_slot_config_t)value;
- }
- }
-
- bool segment_deallocate = true;
-#if TARGET_OS_OSX
- // On macOS, skip deallocation unless it is a known process
- if ((!security_critical || security_critical_max_perf) &&
- !malloc_space_efficient_enabled) {
- segment_deallocate = false;
- }
-#endif // TARGET_OS_OSX
- flag = _xzm_development_only_getenv(envp, "MallocXzoneSegmentDeallocate");
- if (flag) {
- long value = strtol(flag, NULL, 10);
- if (value == 0 || value == 1) {
- segment_deallocate = (bool)value;
- }
- }
-
-#if CONFIG_XZM_CLUSTER_AWARE
- // Known processes do not get per-cluster segment groups
- if (security_critical && !security_critical_max_perf) {
- per_cluster_segment_groups = false;
- }
-#endif // CONFIG_XZM_CLUSTER_AWARE
-
- // There is one PTR range group per allocation front, and one PTR_LARGE
- // (when applicable) and DATA each globally
- size_t range_group_count = allocation_front_count +
- (XZM_RANGE_GROUP_COUNT - 1);
-#if !CONFIG_XZM_CLUSTER_AWARE
- size_t segment_group_cluster_count = 1;
-#else
- size_t segment_group_cluster_count = per_cluster_segment_groups ? ncpuclusters : 1;
-#endif // !CONFIG_XZM_CLUSTER_AWARE
-
- size_t segment_group_front_count = segment_group_ids_count;
- if (segment_group_ids_count > XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY &&
- allocation_front_count > 1) {
- // If POINTER_XZONES is active it has N fronts
- segment_group_front_count += allocation_front_count - 1;
- }
-
- size_t segment_group_count = segment_group_front_count *
- segment_group_cluster_count;
-
- size_t metapool_count = XZM_METAPOOL_COUNT;
-
- size_t tail_allocation_offset = sizeof(struct xzm_main_malloc_zone_s);
-
- size_t main_xzones_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(struct xzm_xzone_s) * xzone_count;
-
- size_t main_xzone_slots_offset = tail_allocation_offset;
- size_t total_slot_count = xzone_count * slot_count;
- tail_allocation_offset +=
- sizeof(struct xzm_xzone_allocation_slot_s) * total_slot_count;
-
- size_t main_xzone_partial_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(struct xzm_chunk_list_s) * total_slot_count;
-
- size_t bin_sizes_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(uint64_t) * bin_count;
-
- size_t bin_bucket_counts_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(uint8_t) * bin_count;
-
- size_t xzone_bin_offsets_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(uint8_t) * bin_count;
-
- size_t isolation_zones_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(struct xzm_isolation_zone_s) * xzone_count;
-
- size_t range_groups_offset = tail_allocation_offset;
- tail_allocation_offset +=
- sizeof(struct xzm_range_group_s) * range_group_count;
-
- size_t segment_groups_offset = tail_allocation_offset;
- tail_allocation_offset +=
- sizeof(struct xzm_segment_group_s) * segment_group_count;
-
- size_t metapools_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(struct xzm_metapool_s) * metapool_count;
-
- size_t segment_table_offset = tail_allocation_offset;
- tail_allocation_offset +=
- sizeof(xzm_segment_table_entry_s) * XZM_SEGMENT_TABLE_ENTRIES;
-
-#if CONFIG_EXTERNAL_METADATA_LARGE
- size_t extended_segment_table_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(xzm_extended_segment_table_entry_s) *
- XZM_EXTENDED_SEGMENT_TABLE_ENTRIES;
-#endif // CONFIG_EXTERNAL_METADATA_LARGE
-
- size_t total_size = tail_allocation_offset;
-
- int flags = VM_FLAGS_ANYWHERE;
- int tag = 0;
- plat_map_t *plat_map_ptr = NULL;
- tag = VM_MEMORY_MALLOC;
- mach_vm_address_t vm_addr = (mach_vm_address_t)mvm_allocate_plat(0,
- total_size, 0, flags, MALLOC_GUARDED_METADATA, tag, plat_map_ptr);
- if (!vm_addr) {
- xzm_abort("Failed to allocate xzm zone");
- }
-
- xzm_main_malloc_zone_t main = (xzm_main_malloc_zone_t)vm_addr;
- xzm_xzone_t main_xzones_ptr = (xzm_xzone_t)((uintptr_t)main + main_xzones_offset);
- xzm_xzone_allocation_slot_t main_xzone_slots_ptr = (xzm_xzone_allocation_slot_t)
- ((uint8_t*)main + main_xzone_slots_offset);
-
- xzm_chunk_list_t partial_lists = (xzm_chunk_list_t)((uint8_t *)main +
- main_xzone_partial_offset);
-
- xzm_mzone_index_t mzone_idx = XZM_MZONE_INDEX_MAIN;
-
- *main = (struct xzm_main_malloc_zone_s){
- .xzmz_total_size = total_size,
- .xzmz_bucketing_keys = bucketing_keys,
-#if XZM_NARROW_BUCKETING
- .xzmz_narrow_bucketing = narrow_bucketing,
-#endif
- .xzmz_madvise_workaround = madvise_workaround,
- .xzmz_defer_tiny = defer_tiny,
- .xzmz_defer_small = defer_small,
- .xzmz_defer_large = defer_large,
- .xzmz_deallocate_segment = segment_deallocate,
- .xzmz_range_group_count = (uint8_t)range_group_count,
- .xzmz_segment_group_ids_count = segment_group_ids_count,
- .xzmz_segment_group_front_count = segment_group_front_count,
- .xzmz_segment_group_count = segment_group_count,
- .xzmz_metapool_count = (uint8_t)metapool_count,
- .xzmz_allocation_front_count = (uint8_t)allocation_front_count,
- .xzmz_mfm_address = mfm_zone_address(),
- .xzmz_batch_size = batch_size,
- .xzmz_bin_count = (uint8_t)bin_count,
- .xzmz_ptr_bucket_count = (uint8_t)ptr_bucket_count,
- .xzmz_xzone_chunk_threshold = (uint8_t)chunk_threshold,
- .xzmz_xzone_bin_sizes = (uint64_t *)
- ((uintptr_t)main + bin_sizes_offset),
- .xzmz_xzone_bin_bucket_counts = (uint8_t *)
- ((uintptr_t)main + bin_bucket_counts_offset),
- .xzmz_xzone_bin_offsets = (uint8_t *)
- ((uintptr_t)main + xzone_bin_offsets_offset),
- .xzmz_isolation_zones = (struct xzm_isolation_zone_s *)
- ((uintptr_t)main + isolation_zones_offset),
- .xzmz_range_groups = (struct xzm_range_group_s *)
- ((uintptr_t)main + range_groups_offset),
- .xzmz_segment_groups = (struct xzm_segment_group_s *)
- ((uintptr_t)main + segment_groups_offset),
- .xzmz_metapools = (struct xzm_metapool_s *)(
- (uintptr_t)main + metapools_offset),
- .xzmz_segment_table = (xzm_segment_table_entry_s *)
- ((uintptr_t)main + segment_table_offset),
-#if CONFIG_EXTERNAL_METADATA_LARGE
- .xzmz_extended_segment_table_entries =
- XZM_EXTENDED_SEGMENT_TABLE_ENTRIES,
- .xzmz_extended_segment_table = (xzm_extended_segment_table_entry_s *)
- ((uintptr_t)main + extended_segment_table_offset),
-#endif // CONFIG_EXTERNAL_METADATA_LARGE
- .xzmz_max_mzone_idx = mzone_idx,
- .xzmz_mzones_lock = _MALLOC_LOCK_INIT,
- .xzmz_guard_config = {
- .xgpc_enabled = guards_enabled,
- .xgpc_enabled_for_data = guards_enabled_data,
- .xgpc_max_run_tiny = tiny_run_size,
- .xgpc_tiny_guard_density = tiny_guard_density,
- .xgpc_max_run_small = small_run_size,
- .xgpc_small_guard_density = small_guard_density,
- },
- .xzmz_thread_cache_list =
- LIST_HEAD_INITIALIZER(main->xzmz_thread_cache_list),
- };
- _xzm_initialize_const_zone_data(&main->xzmz_base, total_size, mzone_idx,
- xzone_count, slot_count, main_xzones_ptr, main_xzone_slots_ptr,
- NULL, slot_config, slot_threshold, max_slot_config,
- list_upgrade_threshold_single, list_upgrade_threshold_cluster,
- list_upgrade_period, slot_upgrade_threshold_single,
- slot_upgrade_threshold_cluster, slot_upgrade_period,
- tiny_thrash_threshold, small_thrash_threshold,
- small_thrash_limit_size, debug_flags, small_freelist,
- max_list_config, partial_lists);
-#if CONFIG_MTE
- main->xzmz_base.xzz_memtag_config = memtag_config;
-#endif // CONFIG_MTE
-
-#if CONFIG_XZM_DEFERRED_RECLAIM
- if (defer_tiny || defer_small || defer_large || huge_cache_size) {
- bool success = xzm_reclaim_init(main, reclaim_buffer_count,
- max_reclaim_buffer_count);
- if (!success) {
- huge_cache_size = 0;
- main->xzmz_defer_tiny = defer_tiny = false;
- main->xzmz_defer_small = defer_small = false;
- main->xzmz_defer_large = defer_large = false;
- }
- }
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
-
- main->xzmz_base.xzz_thread_cache_enabled = thread_caching;
- main->xzmz_base.xzz_thread_cache_xzone_activation_period =
- thread_cache_activation_period;
- main->xzmz_base.xzz_thread_cache_xzone_activation_contentions =
- thread_cache_activation_contentions;
- main->xzmz_base.xzz_thread_cache_xzone_activation_time =
- thread_cache_activation_time;
-
-#if CONFIG_XZM_THREAD_CACHE
- if (thread_caching) {
- int rc = pthread_key_init_np(__TSD_MALLOC_XZONE_THREAD_CACHE,
- _xzm_xzone_thread_cache_destructor);
- if (os_unlikely(rc)) {
- xzm_abort_with_reason("pthread_key_init_np failed", rc);
- }
- }
-#endif // CONFIG_XZM_THREAD_CACHE
-
- // Initialize the main mzone structures
- for (size_t i = 0; i < bin_count; i++) {
- main->xzmz_xzone_bin_sizes[i] = _xzm_bin_sizes[i];
- }
-
- for (size_t i = 0; i < bin_count; i++) {
- main->xzmz_xzone_bin_bucket_counts[i] = buckets_per_bin;
- }
-
- for (size_t i = 0; i < xzone_count; i++) {
- xzm_isolation_zone_t iz = &main->xzmz_isolation_zones[i];
- _malloc_lock_init(&iz->xziz_lock);
- }
-
- size_t rg_idx = 0;
- for (size_t i = 0; i < XZM_RANGE_GROUP_COUNT; i++) {
- xzm_range_group_id_t rgid = (xzm_range_group_id_t)i;
- size_t rg_fronts = (rgid == XZM_RANGE_GROUP_PTR) ?
- allocation_front_count : 1;
- for (size_t j = 0; j < rg_fronts; j++) {
- xzm_range_group_t rg = &main->xzmz_range_groups[rg_idx];
- rg->xzrg_id = rgid;
- rg->xzrg_front = (xzm_front_index_t)j;
- rg->xzrg_main_ref = main;
- _malloc_lock_init(&rg->xzrg_lock);
-
- rg_idx++;
- }
- }
-
- if (has_vm_user_ranges) {
- xzm_main_malloc_zone_init_range_groups(main);
- }
-
- if (!main->xzmz_use_ranges) {
- // If we don't actually have ranges we can't do allocation fronts
- allocation_front_count = 1;
- main->xzmz_allocation_front_count = 1;
- }
-
- for (size_t i = 0; i < segment_group_count; i++) {
- xzm_segment_group_t sg = &main->xzmz_segment_groups[i];
- size_t sg_front_idx = i % segment_group_front_count;
- sg->xzsg_id = sg_front_idx < XZM_SEGMENT_GROUP_POINTER_XZONES ?
- (xzm_segment_group_id_t)sg_front_idx :
- XZM_SEGMENT_GROUP_POINTER_XZONES;
-
- _malloc_lock_init(&sg->xzsg_lock);
- _malloc_lock_init(&sg->xzsg_alloc_lock);
-
- if (_xzm_segment_group_id_is_data(sg->xzsg_id)) {
- sg->xzsg_range_group =
- &main->xzmz_range_groups[XZM_RANGE_GROUP_DATA];
- } else if (sg->xzsg_id == XZM_SEGMENT_GROUP_POINTER_LARGE) {
-
- if (!sg->xzsg_range_group) {
- xzm_front_index_t front = _xzm_random_front_index(front_random,
- allocation_front_count, XZM_XZONE_INDEX_INVALID);
- size_t rg_idx = XZM_RANGE_GROUP_PTR + front;
- sg->xzsg_range_group = &main->xzmz_range_groups[rg_idx];
- }
- } else {
- xzm_assert(sg->xzsg_id == XZM_SEGMENT_GROUP_POINTER_XZONES);
- size_t ptr_front_idx =
- sg_front_idx - XZM_SEGMENT_GROUP_POINTER_XZONES;
- size_t rg_idx = XZM_RANGE_GROUP_PTR + ptr_front_idx;
- xzm_debug_assert(rg_idx < main->xzmz_range_group_count);
- sg->xzsg_range_group = &main->xzmz_range_groups[rg_idx];
- }
-
- sg->xzsg_front = sg->xzsg_range_group->xzrg_front;
- sg->xzsg_main_ref = main;
-
- if (i == XZM_SEGMENT_GROUP_DATA_LARGE) {
- // Huge allocations are all routed to the same segment group,
- // so only initialize the huge cache for that one segment group
- sg->xzsg_cache = (struct xzm_segment_cache_s){
- .xzsc_max_count = huge_cache_size,
- .xzsc_count = 0,
- // TODO: consider limiting the maximum entry size based on the
- // reclaim threshold
- .xzsc_max_entry_slices =
- (huge_cache_max_entry_bytes / XZM_SEGMENT_SLICE_SIZE),
- };
- TAILQ_INIT(&sg->xzsg_cache.xzsc_head);
- _malloc_lock_init(&sg->xzsg_cache.xzsc_lock);
- }
-
- for (size_t j = 0; j < XZM_SPAN_QUEUE_COUNT; j++) {
- sg->xzsg_spans[j].xzsq_slice_count =
- (xzm_slice_count_t)_xzm_span_queue_slice_counts[j];
- }
- }
-
- // NOTE: The order of these metapool allocators matters for the fork lock,
- // we have to grab the metadata metapool last, since the other metapools
- // acquire its lock after taking their own
- xzm_metapool_t metadata_mp = &main->xzmz_metapools[XZM_METAPOOL_METADATA];
- uint32_t mp_metadata_size = MAX(sizeof(struct xzm_metapool_block_s),
- sizeof(struct xzm_metapool_slab_s));
- uint32_t mp_metadata_slab_size = KiB(16); // arbitrary slab size
- xzm_metapool_init(metadata_mp, XZM_METAPOOL_METADATA, VM_MEMORY_MALLOC,
- mp_metadata_slab_size, mp_metadata_size, mp_metadata_size, NULL);
-
- xzm_metapool_t segment_mp = &main->xzmz_metapools[XZM_METAPOOL_SEGMENT];
- xzm_metapool_init(segment_mp, XZM_METAPOOL_SEGMENT, VM_MEMORY_MALLOC,
- XZM_METAPOOL_SEGMENT_SLAB_SIZE, XZM_METAPOOL_SEGMENT_ALIGN,
- XZM_METAPOOL_SEGMENT_BLOCK_SIZE, metadata_mp);
-
- xzm_metapool_t leaf_table_mp = &main->xzmz_metapools[XZM_METAPOOL_SEGMENT_TABLE];
- xzm_metapool_init(leaf_table_mp, XZM_METAPOOL_SEGMENT_TABLE,
- VM_MEMORY_MALLOC, XZM_METAPOOL_SEGMENT_TABLE_SLAB_SIZE,
- XZM_SEGMENT_TABLE_ALIGN, XZM_SEGMENT_TABLE_SIZE, metadata_mp);
-
- xzm_metapool_t mzone_idx_mp = &main->xzmz_metapools[XZM_METAPOOL_MZONE_IDX];
- xzm_metapool_init(mzone_idx_mp, XZM_METAPOOL_MZONE_IDX, VM_MEMORY_MALLOC,
- XZM_METAPOOL_MZIDX_SLAB_SIZE, XZM_METAPOOL_MZIDX_BLOCK_ALIGN,
- XZM_METAPOOL_MZIDX_BLOCK_SIZE, NULL);
-
- xzm_metapool_t tc_mp = &main->xzmz_metapools[XZM_METAPOOL_THREAD_CACHE];
-
- // XXX Needs to be re-worked if we allow per-bin bucket counts
- xzm_debug_assert(_xzm_bin_sizes[XZM_THREAD_CACHE_BINS - 1] ==
- XZM_THREAD_CACHE_THRESHOLD);
- size_t thread_cache_xzone_count =
- 1 + (XZM_THREAD_CACHE_BINS * buckets_per_bin);
-
- main->xzmz_base.xzz_thread_cache_xzone_count =
- (uint8_t)thread_cache_xzone_count;
-
- size_t thread_cache_block_size = sizeof(struct xzm_thread_cache_s) +
- (thread_cache_xzone_count * sizeof(xzm_xzone_thread_cache_u));
- xzm_metapool_init(tc_mp, XZM_METAPOOL_THREAD_CACHE,
- VM_MEMORY_MALLOC_NANO,
- XZM_METAPOOL_THREAD_CACHE_SLAB_SIZE, 0,
- (uint32_t)thread_cache_block_size, metadata_mp);
-
- // Initialize the per-mzone structures
- bool data_only = (main->xzmz_segment_group_ids_count ==
- XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY);
- _xzm_initialize_xzone_data(&main->xzmz_base, list_config, &main->xzmz_guard_config, front_random, data_only);
-
- flag = _xzm_production_getenv(envp, "MallocReportConfig");
- if (flag) {
- // Report our config to stderr for debugging purposes
-#if CONFIG_XZM_DEFERRED_RECLAIM
- int vm_reclaim_enabled;
- size_t vm_reclaim_enabled_size = sizeof(vm_reclaim_enabled);
- int kr = sysctlbyname("vm.reclaim.enabled",
- &vm_reclaim_enabled, &vm_reclaim_enabled_size, 0, 0);
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- malloc_report(ASL_LEVEL_INFO,
- "XZM Config:\n"
- "\tData Only: %d\n"
- "\tAllocation Fronts: %d\n"
-#if XZM_NARROW_BUCKETING
- "\tNarrow Bucketing: %d\n"
-#endif
- "\tGuards Enabled: %d\n"
- "\tScribble: %d\n"
- "\tTiny/Small Batch Max: %d\n"
-#if CONFIG_XZM_DEFERRED_RECLAIM
- "\tDefer Tiny: %d\n"
- "\tDefer Small: %d\n"
- "\tDefer Large: %d\n"
- "\tHuge Cache Size: %d\n"
- "\tHuge Cache Max Entry Bytes: %u\n"
- "\tReclaim Buffer Count: %u/%u (%s)\n"
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- "\tSmall Freelist: %u\n"
-#if CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
- "\tData Range: 0x%llx/%lu\n"
- "\tPointer Range 1: 0x%llx/%lu\n"
- "\tPointer Range 2: 0x%llx/%lu\n"
-#endif // CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
- "\tEarly Allocator: %s\n"
- "\tSegment Deallocate: %u\n"
-#if CONFIG_MTE
- "\tMTE (enabled/data/max size): %d/%d/%llu\n"
-#endif
- "\tInitial Slot Config: %s/%s (Chunk, Size Thresholds: %u, %u)\n"
- "\tInitial List Config: %s/%s\n"
- "\tList Upgrade Thresholds: %d/%d, %d/%d\n"
- "\tSlot Upgrade Thresholds: %d/%d, %d/%d\n"
- "\tTiny Thrash Threshold: %llu ms\n"
- "\tSmall Thrash Threshold: %llu ms, %llu bytes\n"
-#if CONFIG_XZM_THREAD_CACHE
- "\tThread Caching: %s (%u allocs, %u contentions, %llu ms)\n"
-#endif
- "\tPointer Bucket Count: %lu\n",
- data_only,
- (int)allocation_front_count,
-#if XZM_NARROW_BUCKETING
- main->xzmz_narrow_bucketing,
-#endif
- guards_enabled, !!(debug_flags & MALLOC_DO_SCRIBBLE),
- batch_size,
-#if CONFIG_XZM_DEFERRED_RECLAIM
- defer_tiny, defer_small, defer_large,
- huge_cache_size, huge_cache_max_entry_bytes,
- reclaim_buffer_count, max_reclaim_buffer_count,
- (kr || !vm_reclaim_enabled) ? "DISABLED": "ENABLED",
-#endif // CONFIG_XZM_DEFERRED_RECLAIM
- small_freelist,
-#if CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
- !malloc_internal_security_policy ? 0 :
- main->xzmz_range_groups[XZM_RANGE_GROUP_DATA].xzrg_base,
- main->xzmz_range_groups[XZM_RANGE_GROUP_DATA].xzrg_size,
- !malloc_internal_security_policy ? 0 :
- main->xzmz_range_groups[XZM_RANGE_GROUP_PTR + 0].xzrg_base,
- main->xzmz_range_groups[XZM_RANGE_GROUP_PTR + 0].xzrg_size,
- !malloc_internal_security_policy ||
- main->xzmz_allocation_front_count < 2 ? 0 :
- main->xzmz_range_groups[XZM_RANGE_GROUP_PTR + 1].xzrg_base,
- main->xzmz_allocation_front_count < 2 ? 0 :
- main->xzmz_range_groups[XZM_RANGE_GROUP_PTR + 1].xzrg_size,
-#endif // CONFIG_VM_USER_RANGES || CONFIG_MACOS_RANGES
- main->xzmz_mfm_address ? "enabled" : "disabled",
- segment_deallocate,
-#if CONFIG_MTE
- memtag_config.enabled, memtag_config.tag_data,
- memtag_config.max_block_size,
-#endif
- _xzm_slot_config_to_string(slot_config),
- _xzm_slot_config_to_string(max_slot_config),
- chunk_threshold, slot_threshold,
- _xzm_slot_config_to_string(list_config),
- _xzm_slot_config_to_string(max_list_config),
- list_upgrade_threshold_single,
- list_upgrade_period, list_upgrade_threshold_cluster,
- list_upgrade_period, slot_upgrade_threshold_single,
- slot_upgrade_period, slot_upgrade_threshold_cluster,
- slot_upgrade_period,
- tiny_thrash_threshold_ns / nsec_per_msec,
- small_thrash_threshold_ns / nsec_per_msec,
- small_thrash_limit_size,
-#if CONFIG_XZM_THREAD_CACHE
- thread_caching ? "enabled" : "disabled",
- thread_cache_activation_period,
- thread_cache_activation_contentions,
- thread_cache_activation_time_ns / nsec_per_msec,
-#endif // CONFIG_XZM_THREAD_CACHE
- (unsigned long)ptr_bucket_count);
- }
-
- return &main->xzmz_base.xzz_basic_zone;
-}
-
-MALLOC_NOEXPORT
-malloc_zone_t *
-xzm_malloc_zone_create(unsigned debug_flags, xzm_main_malloc_zone_t main_ref)
-{
- size_t tail_allocation_offset = sizeof(struct xzm_malloc_zone_s);
- size_t xzones_offset = tail_allocation_offset;
-
- uint8_t xzone_count = main_ref->xzmz_base.xzz_xzone_count;
- uint8_t slot_count = main_ref->xzmz_base.xzz_slot_count;
- tail_allocation_offset += sizeof(struct xzm_xzone_s) * xzone_count;
- size_t slots_offset = tail_allocation_offset;
-
- size_t total_slot_count = xzone_count * slot_count;
- tail_allocation_offset += sizeof(struct xzm_xzone_allocation_slot_s) *
- total_slot_count;
-
- size_t xzone_partial_offset = tail_allocation_offset;
- tail_allocation_offset += sizeof(struct xzm_chunk_list_s) * total_slot_count;
-
- // Try to pop a previously destroyed mzone index from the reuse list,
- // otherwise use the next unused value
- _malloc_lock_lock(&main_ref->xzmz_mzones_lock);
- xzm_mzone_index_t mzone_idx;
- xzm_reused_mzone_index_t reuse = SLIST_FIRST(&main_ref->xzmz_reusable_mzidxq);
- if (reuse) {
- SLIST_REMOVE_HEAD(&main_ref->xzmz_reusable_mzidxq, xrmi_mzone_entry);
- mzone_idx = reuse->xrmi_mzone_idx;
- xzm_metapool_t mp = &main_ref->xzmz_metapools[XZM_METAPOOL_MZONE_IDX];
- xzm_metapool_free(mp, reuse);
- } else if (main_ref->xzmz_max_mzone_idx == XZM_MZONE_INDEX_MAX) {
- mzone_idx = XZM_MZONE_INDEX_INVALID;
- } else {
- main_ref->xzmz_max_mzone_idx += 1;
- mzone_idx = main_ref->xzmz_max_mzone_idx;
- }
- _malloc_lock_unlock(&main_ref->xzmz_mzones_lock);
-
- if (os_unlikely(mzone_idx == XZM_MZONE_INDEX_INVALID)) {
- return NULL;
- }
-
- // allocate a new mzone
- int flags = VM_FLAGS_ANYWHERE;
- int tag = 0;
- plat_map_t *plat_map_ptr = NULL;
- tag = VM_MEMORY_MALLOC;
-
- mach_vm_address_t vm_addr = (mach_vm_address_t)mvm_allocate_plat(0,
- tail_allocation_offset, 0, flags, MALLOC_GUARDED_METADATA, tag,
- plat_map_ptr);
- if (!vm_addr) {
- return NULL;
- }
- xzm_malloc_zone_t new_zone = (xzm_malloc_zone_t)vm_addr;
-
- xzm_chunk_list_t partial_lists = (xzm_chunk_list_t)((uint8_t *)new_zone +
- xzone_partial_offset);
-
- // Currently we copy the slot upgrade parameters from the main zone into
- // newly created zones. To limit fragmentation, we could cap the new
- // zones at single/per cluster. Pre-xzm, new malloc zones were all
- // scalable zones (you could only have one nano zone), so capping
- // scalability of non-default zones has prior art
- _xzm_initialize_const_zone_data(new_zone, tail_allocation_offset, mzone_idx,
- xzone_count, slot_count,
- (xzm_xzone_t)((uintptr_t)new_zone + xzones_offset),
- (xzm_xzone_allocation_slot_t)((uintptr_t)new_zone + slots_offset),
- main_ref, main_ref->xzmz_base.xzz_initial_slot_config,
- main_ref->xzmz_base.xzz_slot_initial_threshold,
- main_ref->xzmz_base.xzz_max_slot_config,
- main_ref->xzmz_base.xzz_list_upgrade_threshold[0],
- main_ref->xzmz_base.xzz_list_upgrade_threshold[1],
- main_ref->xzmz_base.xzz_list_upgrade_period,
- main_ref->xzmz_base.xzz_slot_upgrade_threshold[0],
- main_ref->xzmz_base.xzz_slot_upgrade_threshold[1],
- main_ref->xzmz_base.xzz_slot_upgrade_period,
- main_ref->xzmz_base.xzz_tiny_thrash_threshold,
- /* small_thrash_threshold */ 0, /* small_thrash_limit_size */ 0,
- debug_flags, main_ref->xzmz_base.xzz_small_freelist_enabled,
- main_ref->xzmz_base.xzz_max_list_config, partial_lists);
-#if CONFIG_MTE
- // TODO: Do we want a different MTE config in new zones?
- new_zone->xzz_memtag_config = main_ref->xzmz_base.xzz_memtag_config;
-#endif // CONFIG_MTE
-
- xzm_debug_assert(new_zone != NULL);
-
- bool data_only = (main_ref->xzmz_segment_group_ids_count ==
- XZM_SEGMENT_GROUP_IDS_COUNT_DATA_ONLY);
- // Currently new zones are never nano, so start all new xzone slot configs
- // at single.
- // TODO: if the envvar is set (MallocXzoneInitialSlotConfig), use
- // that slot config in non-default zones
- _xzm_initialize_xzone_data(new_zone, XZM_SLOT_SINGLE,
- &main_ref->xzmz_guard_config, NULL, data_only);
- LIST_INIT(&new_zone->xzz_chunkq_large);
-
- return &new_zone->xzz_basic_zone;
-}
-
-#endif // CONFIG_XZONE_MALLOC