2#include "gaia/config/config.h"
9#include "gaia/cnt/fwd_llist.h"
10#include "gaia/cnt/sarray.h"
11#include "gaia/core/bit_utils.h"
12#include "gaia/core/dyn_singleton.h"
13#include "gaia/core/utility.h"
14#include "gaia/mem/mem_alloc.h"
15#include "gaia/meta/type_info.h"
16#include "gaia/util/logging.h"
21 static constexpr uint32_t MemoryBlockAlignment = 16;
24 static constexpr uint32_t MemoryBlockBytesDefault = 32768;
26 static constexpr uint32_t MemoryBlockUsableOffset = MemoryBlockAlignment;
27 static_assert(MemoryBlockUsableOffset >=
sizeof(uintptr_t));
42 template <
typename T, u
int32_t RequestedBlockSize>
48 return (num + (MemoryBlockAlignment - 1)) & uint32_t(-(int32_t)MemoryBlockAlignment);
53 constexpr uint32_t UsableBytes = RequestedBlockSize == 0 ? MemoryBlockBytesDefault : RequestedBlockSize;
66 static constexpr uint8_t FreedBlockPattern = 0xDD;
67 static constexpr uintptr_t FreedPageMarker = ~(uintptr_t)0;
127 auto StoreBlockAddress = [&](uint32_t index) {
131 GAIA_ASSERT((uintptr_t)pMemoryBlock % MemoryBlockAlignment == 0);
133 return (
void*)(pMemoryBlock + MemoryBlockUsableOffset);
137 GAIA_ASSERT(!
full() &&
"Trying to allocate too many blocks!");
145 return StoreBlockAddress(index);
156 return StoreBlockAddress(index);
165 auto ReadBlockAddress = [&](
void* pMemory) {
167 const auto* pMemoryBlock = (uint8_t*)pMemory - MemoryBlockUsableOffset;
168#if GAIA_ASSERT_ENABLED
170 GAIA_ASSERT(pageAddr == (uintptr_t)
this);
172 const auto blckAddr = (uintptr_t)pMemoryBlock;
173 GAIA_ASSERT(blckAddr % 16 == 0);
174 const auto dataAddr = (uintptr_t)
m_data;
178 const auto blockIdx = ReadBlockAddress(pBlock);
182 std::memset(pBlock, FreedBlockPattern,
MemoryBlockBytes - MemoryBlockUsableOffset);
204 GAIA_NODISCARD
bool full()
const {
216#if GAIA_ASSERT_ENABLED
221 GAIA_ASSERT(((uintptr_t)
m_data % MemoryBlockAlignment) == 0);
223 uint64_t freeMask = 0;
228 const auto bit = uint64_t(1) << next;
229 GAIA_ASSERT((freeMask & bit) == 0 &&
"Free list contains a cycle");
239 GAIA_ASSERT(((uintptr_t)pMemoryBlock % MemoryBlockAlignment) == 0);
242 const bool isFree = (freeMask & (uint64_t(1) << i)) != 0;
244 GAIA_ASSERT(pageAddr == (isFree ? FreedPageMarker : (uintptr_t)
this));
254 template <
typename T, u
int32_t RequestedBlockSize>
282 template <
typename T, u
int32_t RequestedBlockSize>
283 class PagedAllocatorImpl;
290 template <
typename T, u
int32_t RequestedBlockSize = 0>
296 template <
typename T, u
int32_t RequestedBlockSize>
297 class PagedAllocatorImpl {
298 friend ::gaia::mem::PagedAllocator<T, RequestedBlockSize>;
300 inline static char s_strPageData[256]{};
301 inline static char s_strMemPage[256]{};
303 using Page = MemoryPage<T, RequestedBlockSize>;
304 using PageContainer = MemoryPageContainer<T, RequestedBlockSize>;
307 PageContainer m_pages;
309 bool m_isDone =
false;
312 PagedAllocatorImpl() {
315 auto ct_name = meta::type_info::name<T>();
316 const auto ct_name_len = (uint32_t)ct_name.size();
317 GAIA_STRCPY(s_strPageData, 256,
"PageData_");
318 memcpy((
void*)&s_strPageData[9], (
const void*)ct_name.data(), ct_name_len);
319 s_strPageData[9 + ct_name_len] = 0;
320 GAIA_STRCPY(s_strMemPage, 256,
"MemPage_");
321 memcpy((
void*)&s_strMemPage[8], (
const void*)ct_name.data(), ct_name_len);
322 s_strMemPage[8 + ct_name_len] = 0;
329 auto memStats = stats();
330 if (memStats.mem_total != 0) {
331 GAIA_ASSERT2(
false,
"Paged allocator leaking memory");
332 GAIA_LOG_W(
"Paged allocator leaking memory!");
338 ~PagedAllocatorImpl() {
342 PagedAllocatorImpl(PagedAllocatorImpl&& world) =
delete;
343 PagedAllocatorImpl(
const PagedAllocatorImpl& world) =
delete;
344 PagedAllocatorImpl& operator=(PagedAllocatorImpl&&) =
delete;
345 PagedAllocatorImpl& operator=(
const PagedAllocatorImpl&) =
delete;
348 void* alloc([[maybe_unused]] uint32_t dummy) {
349 const detail::ArenaLock arenaLock;
350 void* pBlock =
nullptr;
353 auto* pPage = m_pages.pagesFree.first;
354 GAIA_ASSERT(pPage ==
nullptr || !pPage->full());
355 if (pPage ==
nullptr) {
357 pPage = alloc_page();
358 m_pages.pagesFree.link(pPage);
362 pBlock = pPage->alloc_block();
367 m_pages.pagesFree.unlink(pPage);
369 m_pages.pagesFull.link(pPage);
376 GAIA_CLANG_WARNING_PUSH()
378 GAIA_CLANG_WARNING_DISABLE("-Wcast-align")
381 void free(
void* pBlock) {
382 const detail::ArenaLock arenaLock;
384 const auto pageAddr = *(uintptr_t*)((uint8_t*)pBlock - MemoryBlockUsableOffset);
385 GAIA_ASSERT(pageAddr % MemoryBlockAlignment == 0);
386 auto* pPage = (Page*)pageAddr;
387 const bool wasFull = pPage->full();
389#if GAIA_ASSERT_ENABLED
391 const auto res = m_pages.pagesFull.has(pPage);
392 GAIA_ASSERT(res &&
"Memory page couldn't be found among full pages");
394 const auto res = m_pages.pagesFree.has(pPage);
395 GAIA_ASSERT(res &&
"Memory page couldn't be found among free pages");
400 pPage->free_block(pBlock);
405 m_pages.pagesFull.unlink(pPage);
407 m_pages.pagesFree.link(pPage);
415 if (pPage->empty()) {
416 GAIA_ASSERT(!m_pages.pagesFree.empty());
417 m_pages.pagesFree.unlink(pPage);
424 GAIA_CLANG_WARNING_POP()
427 MemoryPageStats stats()
const {
428 MemoryPageStats stats{};
430 stats.num_pages = (uint32_t)m_pages.pagesFree.size() + (uint32_t)m_pages.pagesFull.size();
431 stats.num_pages_free = (uint32_t)m_pages.pagesFree.size();
432 stats.mem_total = stats.num_pages * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
433 stats.mem_used = m_pages.pagesFull.size() * (size_t)Page::MemoryBlockBytes * Page::NBlocks;
434 for (
const auto& page: m_pages.pagesFree)
435 stats.mem_used += page.used_blocks_cnt() * (size_t)Page::MemoryBlockBytes;
442 const detail::ArenaLock arenaLock;
443 for (
auto it = m_pages.pagesFree.begin(); it != m_pages.pagesFree.end();) {
444 auto* pPage = &(*it);
451 m_pages.pagesFree.unlink(pPage);
460 auto memStats = stats();
461 GAIA_LOG_N(
"PagedAllocator %p stats", (
void*)
this);
462 GAIA_LOG_N(
" Allocated: %" PRIu64
" B", memStats.mem_total);
463 GAIA_LOG_N(
" Used: %" PRIu64
" B", memStats.mem_total - memStats.mem_used);
464 GAIA_LOG_N(
" Overhead: %" PRIu64
" B", memStats.mem_used);
466 " Utilization: %.1f%%",
467 memStats.mem_total != 0 ? 100.0 * ((
double)memStats.mem_used / (
double)memStats.mem_total) : 0.0);
468 GAIA_LOG_N(
" Pages: %u", memStats.num_pages);
469 GAIA_LOG_N(
" Free pages: %u", memStats.num_pages_free);
472 void verify()
const {
473#if GAIA_ASSERT_ENABLED
474 for (
const auto& page: m_pages.pagesFree) {
475 GAIA_ASSERT(page.get_fwd_llist_link().linked());
476 GAIA_ASSERT(!page.full());
480 for (
const auto& page: m_pages.pagesFull) {
481 GAIA_ASSERT(page.get_fwd_llist_link().linked());
482 GAIA_ASSERT(page.full());
489 static Page* alloc_page() {
490 const uint32_t size = Page::NBlocks * Page::MemoryBlockBytes;
491 auto* pPageData = mem::AllocHelper::alloc_alig<uint8_t>(&s_strPageData[0], MemoryBlockAlignment, size);
492 auto* pMemoryPage = mem::AllocHelper::alloc<Page>(&s_strMemPage[0]);
493 return new (pMemoryPage) Page(pPageData);
496 static void free_page(Page* pMemoryPage) {
497 GAIA_ASSERT(pMemoryPage !=
nullptr);
499 mem::AllocHelper::free_alig(&s_strPageData[0], pMemoryPage->m_data);
500 pMemoryPage->~MemoryPage();
501 mem::AllocHelper::free(&s_strMemPage[0], pMemoryPage);
508 void try_delete_this() {
Array of elements of type.
Definition darray_ext_impl.h:31
GAIA_NODISCARD pointer data() noexcept
Returns a pointer to the element storage.
Definition darray_ext_impl.h:236
GAIA_NODISCARD bool empty() const noexcept
Checks whether the container has no elements.
Definition darray_ext_impl.h:567
Gaia-ECS is a header-only library which means we want to avoid using global static variables because ...
Definition dyn_singleton.h:29
Pointer wrapper for writing memory in defined way (not causing undefined behavior)
Definition mem_alloc.h:423
Each fwd_llist node either has to inherit from fwd_llist_base or it has to provide get_fwd_llist_link...
Definition fwd_llist.h:29
Provides packed access to fixed-width unsigned values stored in a byte span.
Definition bit_utils.h:13
Lists the non-full and full pages belonging to an allocator.
Definition paged_allocator.h:255
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFull
List of full pages.
Definition paged_allocator.h:259
GAIA_NODISCARD bool empty() const
Reports whether the container has no pages.
Definition paged_allocator.h:263
cnt::fwd_llist< MemoryPage< T, RequestedBlockSize > > pagesFree
List of available pages.
Definition paged_allocator.h:257
Aggregate statistics for a paged allocator.
Definition paged_allocator.h:269
uint32_t num_pages_free
Number of free pages.
Definition paged_allocator.h:277
uint64_t mem_used
Memory actively used.
Definition paged_allocator.h:273
uint64_t mem_total
Total allocated memory.
Definition paged_allocator.h:271
uint32_t num_pages
Number of allocated pages.
Definition paged_allocator.h:275
Fixed-capacity page of equal-sized blocks.
Definition paged_allocator.h:43
uint8_t read_block_idx(uint32_t blockIdx) const
Reads one link from the packed recycled-block list.
Definition paged_allocator.h:116
static constexpr uint16_t NBlocks
Maximum number of blocks in a page.
Definition paged_allocator.h:60
void free_block(void *pBlock)
Release the block allocated by this page.
Definition paged_allocator.h:161
void write_block_idx(uint32_t blockIdx, uint32_t value)
Writes one link in the packed recycled-block list.
Definition paged_allocator.h:104
BlockArray m_blocks
Implicit list of blocks.
Definition paged_allocator.h:80
static constexpr uint32_t calculate_block_size()
Selects and aligns the configured block size, including its header.
Definition paged_allocator.h:52
uint32_t m_nextFreeBlock
Index of the next block to recycle.
Definition paged_allocator.h:87
void verify() const
Verifies page invariants in assertion-enabled builds.
Definition paged_allocator.h:215
uint32_t m_freeBlocks
Number of blocks to recycle.
Definition paged_allocator.h:89
GAIA_NODISCARD bool empty() const
Reports whether no page blocks are allocated.
Definition paged_allocator.h:210
GAIA_NODISCARD bool full() const
Reports whether all page blocks are allocated.
Definition paged_allocator.h:204
uint32_t m_usedBlocks
Number of used blocks out of NBlocks.
Definition paged_allocator.h:85
static constexpr uint32_t BlockArrayBytes
Bytes occupied by the packed block-index array.
Definition paged_allocator.h:70
static constexpr uint32_t MemoryBlockBytes
Size of one block in bytes.
Definition paged_allocator.h:58
MemoryPage(void *ptr)
Free bits to use in the future.
Definition paged_allocator.h:95
uint32_t m_blockCnt
Number of blocks in the block array.
Definition paged_allocator.h:83
GAIA_NODISCARD uint32_t used_blocks_cnt() const
Returns the number of live blocks.
Definition paged_allocator.h:198
static constexpr uint32_t InvalidBlockId
Sentinel terminating the recycled-block list.
Definition paged_allocator.h:64
static constexpr uint16_t NBlocks_Bits
Bits required to encode a block index.
Definition paged_allocator.h:62
GAIA_NODISCARD void * alloc_block()
Allocate a new block for this page.
Definition paged_allocator.h:126
static constexpr uint32_t next_multiple_of_alignment(uint32_t num)
Rounds a size up to MemoryBlockAlignment.
Definition paged_allocator.h:47