/* * Copyright (c) 2021-2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace GC { enum class DeferDecommit { No, Yes, }; class DecommitWorker; class GC_API BlockAllocator { public: BlockAllocator(); ~BlockAllocator(); void* allocate_block(char const* name); void deallocate_block(void*, DeferDecommit = DeferDecommit::Yes); size_t block_count(); // Wake the global decommit worker so it processes any deferred madvise // work that's piled up. Call this at the end of a GC sweep. static void wake_decommit_worker_async(); private: friend class DecommitWorker; // Slots in "ready to reuse" state -- have been MADV_FREE_REUSABLE'd by // the worker (Darwin) so allocate_block pairs them with MADV_FREE_REUSE. Vector m_blocks; // Slots freed by deallocate_block but not yet madvised. allocate_block // pops directly from here to skip the madvise round-trip on hot recycle // paths -- this is the main payoff of deferring decommit. Vector m_freshly_freed; // Protects m_blocks, m_freshly_freed, and m_in_decommit_registry. Held // briefly on the alloc/dealloc hot path; uncontended in the common case. Sync::Mutex m_mutex; // Refcount the decommit worker bumps while it has a reference to this // allocator. The destructor waits on m_worker_cv until it hits zero so // we never let our storage go away while the worker is still running. AK::Atomic m_worker_refcount { 0 }; Sync::ConditionVariable m_worker_cv; // True iff this allocator is currently in the worker's pending list. // Avoids re-registering on every dealloc; cleared by the worker at the // start of process_one, set by deallocate_block under m_mutex. bool m_in_decommit_registry { false }; }; }