/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Heap.hpp Date: 2021-6-9 Author: Reece ***/ #pragma once namespace Aurora::Memory { struct ProxyHeap; static const AuUInt8 kHeapSize = 128; static const AuUInt8 kHeap2Size = 255; template struct CppHeapWrapper; struct Heap { virtual AuSPtr AllocateDivision(AuUInt32 heap, AuUInt32 alignment = 32) = 0; virtual Types::size_t GetChunkSize(const void *pHead) = 0; virtual HeapStats &GetStats() = 0; virtual void WalkHeap(bool(*fCallback)(void *, void *), void *pSecondArg) = 0; // Potentially slower, zero allocate template T ZAlloc(Types::size_t uLength) { if constexpr (AuIsVoid_v>) { return reinterpret_cast(_ZAlloc(uLength)); } else { return reinterpret_cast(_ZAlloc(uLength, alignof(AuRemovePointer_t))); } } template T ZAlloc(Types::size_t uLength, Types::size_t uAlignment) { return reinterpret_cast(_ZAlloc(uLength, uAlignment)); } template T *ZAlloc() { return reinterpret_cast(_ZAlloc(sizeof(T), alignof(T))); } template T *NewArray(Types::size_t uLength) { return ZAlloc(uLength * sizeof(T), alignof(T)); } template T *NewArray(Types::size_t uLength, Types::size_t uAlignment) { return ZAlloc(uLength * sizeof(T), uAlignment); } /// Fast, unsafe alloc template T FAlloc(Types::size_t uLength) { if constexpr (AuIsVoid_v>) { return reinterpret_cast(_FAlloc(uLength)); } else { return reinterpret_cast(_FAlloc(uLength, alignof(AuRemovePointer_t))); } } template T FAlloc(Types::size_t uLength, Types::size_t uAlignment) { return reinterpret_cast(_FAlloc(uLength, uAlignment)); } template T *FAlloc() { return reinterpret_cast(_FAlloc(sizeof(T), alignof(T))); } // Reallocs template T ZRealloc(T pHead, Types::size_t uLength) { if constexpr (AuIsVoid_v>) { return reinterpret_cast(_ZRealloc(reinterpret_cast(pHead), uLength)); } else { return reinterpret_cast(_ZRealloc(reinterpret_cast(pHead), uLength, alignof(AuRemovePointer_t))); } } template T ZRealloc(T pHead, Types::size_t uLength, Types::size_t uAlignment) { return reinterpret_cast(_ZRealloc(reinterpret_cast(pHead), uLength, uAlignment)); } template T FRealloc(T pHead, Types::size_t uLength) { if constexpr (AuIsVoid_v>) { return reinterpret_cast(_FRealloc(reinterpret_cast(pHead), uLength)); } else { return reinterpret_cast(_FRealloc(reinterpret_cast(pHead), uLength, alignof(AuRemovePointer_t))); } } template T FRealloc(T pHead, Types::size_t uLength, Types::size_t uAlignment) { return reinterpret_cast(_FRealloc(reinterpret_cast(pHead), uLength, uAlignment)); } // Free template void Free(T pHead) { _Free(reinterpret_cast(pHead)); } protected: template static void DeleteThat(T *pThat) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *)); if constexpr (AuIsClass_v && !AuIsTriviallyDestructible_v) { pThat->~T(); } auto &pHeap = *(Heap **)(((char *)pThat) - kAlignment); pHeap->_Free(&pHeap); } template static void DeleteThatArray(T *pThat) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *) * 2); auto pVoids = (void **)(((char *)pThat) - kAlignment); auto pHeap = (Heap *)pVoids[0]; auto uLength = (AuUInt)pVoids[1]; if constexpr (AuIsClass_v && !AuIsTriviallyDestructible_v) { for (AU_ITERATE_N(i, uLength)) { auto &refElement = pThat[i]; refElement.~T(); } } pHeap->_Free(pVoids); } template static void DeleteThatCastedOnce(T *pThat) { static const auto kAlignment = AuMax(alignof(Z), sizeof(void *)); auto pBaseClass = AuStaticCast(pThat); if constexpr (AuIsClass_v && !AuIsTriviallyDestructible_v) { pBaseClass->~Z(); } auto &pHeap = *(Heap **)(((char *)pBaseClass) - kAlignment); pHeap->_Free(&pHeap); } template static void RetardedSpecWrittenByRetards(T *pThat) { } public: template AuSPtr NewClass(Args &&...args) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *)); AuUInt8 *pPtr; auto pThat = this->GetSelfReferenceRaw(); if (!pThat) { pThat = this; } if constexpr (AuIsClass_v && !AuIsTriviallyConstructible_v) { pPtr = pThat->FAlloc(sizeof(T) + kAlignment, kAlignment); if (pPtr) { new (pPtr + kAlignment) T(AuForward(args)...); } } else { pPtr = pThat->ZAlloc(sizeof(T) + kAlignment, kAlignment); } if (!pPtr) { return {}; } *(void **)pPtr = pThat; auto pTThat = (T *)(pPtr + kAlignment); AUROXTL_COMMODITY_TRY { return AuSPtr(pTThat, &Heap::DeleteThat, CppHeapWrapper { this }); } AUROXTL_COMMODITY_CATCH { Heap::DeleteThat(pTThat); return {}; } } // note: callers can use AuHUPOf_t pUniquePointer = AuNullHeapPointer() template AuUPtr)> NewClassUnique(Args &&...args) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *)); AuUInt8 *pPtr; auto pThat = this->GetSelfReferenceRaw(); if (!pThat) { pThat = this; } if constexpr (AuIsClass_v && !AuIsTriviallyConstructible_v) { pPtr = pThat->FAlloc(sizeof(T) + kAlignment, kAlignment); if (pPtr) { new (pPtr + kAlignment) T(AuForward(args)...); } } else { pPtr = pThat->ZAlloc(sizeof(T) + kAlignment, kAlignment); } if (!pPtr) { return AuUPtr)>(nullptr, &Heap::RetardedSpecWrittenByRetards); } *(void **)pPtr = pThat; if constexpr (AuIsSame_v) { return AuUPtr)>((T *)(pPtr + kAlignment), &Heap::DeleteThat); } else { return Heap::CastPointer(AuMove(AuUPtr)>((T *)(pPtr + kAlignment), &Heap::DeleteThat))); } } template AuSPtr NewClassArray(AuUInt uElements, Args &&... fillCtr) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *) * 2); AuUInt8 *pPtr; if (!uElements) { return {}; } auto pThat = this->GetSelfReferenceRaw(); if (!pThat) { pThat = this; } if constexpr (AuIsClass_v && !AuIsTriviallyConstructible_v) { if (bool(pPtr = pThat->FAlloc((sizeof(T) * uElements) + kAlignment, kAlignment))) { for (AU_ITERATE_N(i, uElements)) { new (pPtr + kAlignment + (sizeof(T) * i)) T(AuForward(fillCtr)...); } } } else { if (bool(pPtr = pThat->ZAlloc((sizeof(T) * uElements) + kAlignment, kAlignment))) { if constexpr (sizeof...(Args) != 0) { #if defined(AURT_HEAP_NO_STL) static_assert(false); #else auto pElements = (T *)(pPtr + kAlignment); std::fill(pElements, pElements + uElements, AuForward(fillCtr)...); #endif } } } if (!pPtr) { return {}; } auto pVoids = (void **)pPtr; pVoids[0] = pThat; pVoids[1] = (void *)uElements; auto pTThat = (T *)(pPtr + kAlignment); AUROXTL_COMMODITY_TRY { return AuSPtr(pTThat, &Heap::DeleteThatArray, CppHeapWrapper { this }); } AUROXTL_COMMODITY_CATCH { Heap::DeleteThatArray(pTThat); return {}; } } // note: callers can use AuHUPOf_t pUniquePointer = AuNullHeapPointer() template AuUPtr)> NewClassArrayUnique(AuUInt uElements, Args &&... fillCtr) { static const auto kAlignment = AuMax(alignof(T), sizeof(void *) * 2); AuUInt8 *pPtr; if (!uElements) { return AuUPtr)>(nullptr, &Heap::RetardedSpecWrittenByRetards); } auto pThat = this->GetSelfReferenceRaw(); if (!pThat) { pThat = this; } if constexpr (AuIsClass_v && !AuIsTriviallyConstructible_v) { if (bool(pPtr = pThat->FAlloc((sizeof(T) * uElements) + kAlignment, kAlignment))) { for (AU_ITERATE_N(i, uElements)) { new (pPtr + kAlignment + (sizeof(T) * i)) T(AuForward(fillCtr)...); } } } else { if (bool(pPtr = pThat->ZAlloc((sizeof(T) * uElements) + kAlignment, kAlignment))) { if constexpr (sizeof...(Args) != 0) { #if defined(AURT_HEAP_NO_STL) static_assert(false); #else auto pElements = (T *)(pPtr + kAlignment); std::fill(pElements, pElements + uElements, AuForward(fillCtr)...); #endif } } } if (!pPtr) { return AuUPtr)>(nullptr, &Heap::RetardedSpecWrittenByRetards); } auto pVoids = (void **)pPtr; pVoids[0] = pThat; pVoids[1] = (void *)uElements; return AuUPtr)>((T *)(pPtr + kAlignment), &Heap::DeleteThatArray); } template cstatic AuUPtr)> NullUniquePointer() { return AuUPtr)>(nullptr, &Heap::RetardedSpecWrittenByRetards); } template cstatic AuUPtr)> CastPointer(AuUPtr)> &&pInPointer) { if (!pInPointer) { return NullUniquePointer(); } else if (pInPointer.get_deleter() == &Heap::DeleteThat) { return AuUPtr)>(AuStaticCast(pInPointer.release()), &Heap::DeleteThatCastedOnce); } else { return NullUniquePointer(); } } template using HUPOf_t = AuUPtr)>; protected: friend struct ProxyHeap; friend struct HeapAccessor; virtual AuSPtr GetSelfReference() = 0; // may return empty/default. not all heaps are sharable. virtual Heap *GetSelfReferenceRaw() = 0; virtual AU_ALLOC void *_ZAlloc(Types::size_t uLength) = 0; virtual AU_ALLOC void *_ZAlloc(Types::size_t uLength, Types::size_t uAlignment) = 0; virtual AU_ALLOC void *_FAlloc(Types::size_t uLength) = 0; virtual AU_ALLOC void *_FAlloc(Types::size_t uLength, Types::size_t uAlignment) = 0; virtual AU_ALLOC void *_ZRealloc(void *pBase, Types::size_t uLength, Types::size_t uAlign) = 0; virtual AU_ALLOC void *_ZRealloc(void *pBase, Types::size_t uLength) = 0; virtual AU_ALLOC void *_FRealloc(void *pBase, Types::size_t uLength, Types::size_t uAlign) = 0; virtual AU_ALLOC void *_FRealloc(void *pBase, Types::size_t uLength) = 0; virtual void _Free(void* pBase) = 0; }; struct HeapAccessor { cstatic AuSPtr GetSelfReference(Heap *pHeap) { return pHeap->GetSelfReference(); } cstatic Heap *GetSelfReferenceRaw(Heap *pHeap) { return pHeap->GetSelfReferenceRaw(); } }; /** * Returns a heap interface backed by the default allocator */ AUKN_SHARED_API(DefaultDiscontiguousHeap, Heap); inline Heap *GetDefaultDiscontiguousHeap() { return DefaultDiscontiguousHeapNew(); } inline AuSPtr GetDefaultDiscontiguousHeapShared() { // Might not allocate the control block under some STLs, unlike DefaultDiscontiguousHeapSharedShared() which will generally always allocate a control block under most STLs return AuUnsafeRaiiToShared(GetDefaultDiscontiguousHeap()); } /** * Allocates uLength amount of contiguous virtual memory * @return a heap backed by uLength bytes of virtual memory * @warning the SOO variant cannot guarantee release-on-last-free and will panic if uLength cannot be allocated. Use AllocHeap[Shared/Unique/New](uLength) instead. */ AUKN_SHARED_SOO2_NCM(AllocHeap, Heap, kHeapSize, ((AuUInt, uLength)), AuUInt uLength); /** * @warning the SOO variant cannot guarantee release-on-last-free and will panic if an invalid memory handle is provided. */ AUKN_SHARED_SOO2_NCM(RequestHeapOfRegion, Heap, kHeapSize, ((const MemoryViewWrite &, memory)), const MemoryViewWrite &memory); /** * @warning the SOO variant cannot guarantee release-on-last-free and will panic if an invalid memory handle is provided. */ AUKN_SHARED_SOO2_NCM(RequestHeapOfSharedRegion, Heap, kHeapSize, ((const AuSPtr &, memory)), const AuSPtr &pMemory); /** * Proxies an existing heap with encapsulated statistics. * This is intended for debugging purposes when accurate heap stats of a heap-subset are desired. * @warning this heap cannot guarantee release-on-last-free */ AUKN_SHARED_SOO2_NCM(HeapProxy, Heap, kHeap2Size, ((const AuSPtr &, pHead)), const AuSPtr &pHead); /** * Proxies an existing heap with encapsulated statistics and leak detector * This is intended for debugging purposes when accurate heap stats of a heap-subset are desired. * @warning this heap cannot guarantee release-on-last-free */ AUKN_SHARED_SOO2_NCM(HeapProxyEx, Heap, kHeap2Size, ((const AuSPtr &,pHead), (LeakFinderAlloc_f, pfAlloc), (LeakFinderFree_f, pfFree)), const AuSPtr &pHead, LeakFinderAlloc_f pfAlloc, LeakFinderFree_f pfFree); }