// zlib open source license // // Copyright (c) 2024 to 2025 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // An arena memory allocator with recycling bins to provide heap memory. // All allocations are reference counted, because the memory allocator itself may increase the reference count as needed. // * An allocation with use count 0 will remain until the use count changes and reaches zero again. // All allocations are aligned to DSR_MAXIMUM_ALIGNMENT to prevent false sharing of cache lines between threads. // The space in front of each allocation contains a HeapHeader including: // * The total size of the allocation including padding and the header. // * How many of the allocated bytes that are actually used. // * A reference counter. // * A destructor to allow freeing the memory no matter where the reference counter decreases to zero. // * A bin index for fast recycling of memory. // * Bit flags for keeping track of the allocation's state. // In debug mode, the header also contains: // * A thread hash to keep track of data ownership. // All heap allocations are currently shared among all threads unlike virtual stack memory. // * An allocation identity that is unique for each new allocation. // When freed, the header's allocation identity is set to zero to prevent accidental use of freed memory. // When recycled as a new allocation, the same address gets a new identity, which invalidates any old SafePointer to the same address. // * An ascii name to make memory debugging easier. // Dimensions // used size <= padded size <= allocation size // * Size defines the used bytes that represent something, which affects destruction of elements. // Size can change from 0 to allocation size without having to move the data. // * The padded size defines the region where memory access is allowed for SafePointer. // Padded size is computed by rounding up to whole blocks of DSR_MAXIMUM_ALIGNMENT. // * Allocation size is the available space to work with. // To change the allocation's size, one must move to another memory location. #ifndef DFPSR_HEAP #define DFPSR_HEAP #include "SafePointer.h" #include namespace dsr { #ifdef SAFE_POINTER_CHECKS // Assign a debug name to the allocation. // Does nothing if allocation is nullptr. // Only assign constant ascii string literals. void heap_setAllocationName(void const * const allocation, const char *name); // Get the ascii name of allocation, or "none" if allocation is nullptr. const char * heap_getAllocationName(void const * const allocation); // Gets the size padded out to whole blocks of heap alignment, for constructing a SafePointer. uintptr_t heap_getPaddedSize(void const * const allocation); // Set the serialization function for a certain allocation, so that going through memory allocations will display its content when debugging memory leaks. void setAllocationSerialization(void const * const allocation, AllocationSerialization method); // Get the function used to interpret the allocation's content. AllocationSerialization getAllocationSerialization(void const * const allocation); #endif // TODO: Allow allocating fixed size allocations using a typename and pre-calculate the bin index in compile time. // This requires the bin sizes to be independent of DSR_MAXIMUM_ALIGNMENT, possibly by leaving a few bins // unused in the beginning and start counting with the header size as the smallest allocation size. // Allocate memory in the heap. // The minimumSize argument is the minimum number of bytes to allocate, but the result may give you more than you asked for. // To allow representing empty files using buffers, it is allowed to create an allocation of zero bytes. // When zeroed is true, the new memory will be zeroed. Otherwise it may contain uninitialized data. // Post-condition: Returns pointers to the payload and header. UnsafeAllocation heap_allocate(uintptr_t minimumSize, bool zeroed = true); // Increase the use count of an allocation. // Does nothing if the allocation is nullptr. void heap_increaseUseCount(void const * const allocation); void heap_increaseUseCount(AllocationHeader const * const header); // Decrease the use count of an allocation and recycle it when reaching zero. // Does nothing if the allocation is nullptr. void heap_decreaseUseCount(void const * const allocation); void heap_decreaseUseCount(AllocationHeader const * const header); // Pre-condition: // allocation points to memory allocated as heap_allocate(...).data because this feature is specific to this allocator. // Post-condition: // Returns the number of bytes in the allocation that are actually used, which is used for tight bound checks and knowing how large a buffer is. // Returns 0 if allocation is nullptr. uintptr_t heap_getUsedSize(void const * const allocation); uintptr_t heap_getUsedSize(AllocationHeader const * const header); // Side-effect: // Assigns a new used size to allocation. // Has no effect if allocation is nullptr. // Pre-condition: // You may not reserve more memory than what is available in total. // size <= heap_getAllocationSize(allocation) // If exceeded, size will be limited by the allocation's size. // Post-condition: // Returns the assigned size, which is the given size, an exceeded allocation size, or zero for an allocation that does not exist. uintptr_t heap_setUsedSize(void * const allocation, uintptr_t size); uintptr_t heap_setUsedSize(AllocationHeader * const header, uintptr_t size); // A function pointer for destructors. using HeapDestructorPointer = void(*)(void *toDestroy, void *externalResource); struct HeapDestructor { // A function pointer to a method taking toDestroy and externalResource as arguments. HeapDestructorPointer destructor = nullptr; // A pointer for freeing external resources owning the allocation. void *externalResource = nullptr; // Constructor. HeapDestructor(HeapDestructorPointer destructor = nullptr, void *externalResource = nullptr) : destructor(destructor), externalResource(externalResource) {} }; // Register a destructor function pointer to be called automatically when the allocation is freed. // externalResource is the second argument that will be given to destructor together with the freed memory to destruct. void heap_setAllocationDestructor(void * const allocation, const HeapDestructor &destructor); // Get the use count outside of transactions without locking. uintptr_t heap_getUseCount(void const * const allocation); uintptr_t heap_getUseCount(AllocationHeader const * const header); // Pre-condition: The allocation pointer must point to the start of a payload allocated using heap_allocate, no offsets nor other allocators allowed. // Post-condition: Returns the number of available bytes in the allocation. uintptr_t heap_getAllocationSize(void const * const allocation); // Pre-condition: The header pointer must point to the allocation head, as returned from heap_allocate or heap_getHeader. // Post-condition: Returns the number of available bytes in the allocation. uintptr_t heap_getAllocationSize(AllocationHeader const * const header); // Get the alignment of the heap, which depends on the largest cache line size. uintptr_t heap_getHeapAlignment(); // Pre-condition: The allocation pointer must point to the start of a payload allocated using heap_allocate, no offsets nor other allocators allowed. // Post-condition: Returns a pointer to the heap allocation's header, which is used to construct safe pointers. AllocationHeader *heap_getHeader(void * const allocation); // Call back for each used heap allocation. // Recycled allocations are not included. void heap_forAllHeapAllocations(std::function callback); // Prints the allocation to the terminal. void heap_debugPrintAllocation(void const * const allocation, uintptr_t maxLength = 128u); // Prints a list of allocations to the terminal, without creating new memory allocations. void heap_debugPrintAllocations(uintptr_t maxLength = 128u); // Called by DSR_MAIN_CALLER when the program starts. void heap_startingApplication(); // Called by DSR_MAIN_CALLER when the program closes. void heap_terminatingApplication(); // If terminating the program using std::exit, you can call this first to free all heap memory in the allocator, leaked or not. void heap_hardExitCleaning(); // Used to find the origin of memory leaks in single-threaded tests. intptr_t heap_getAllocationCount(); // Store application defined custom flags, which can be used for debugging memory leaks. // The flags do not take any additional memory, because an allocation head can not allocate less than a whole cache line. uint32_t heap_getAllocationCustomFlags(void const * const allocation); uint32_t heap_getAllocationCustomFlags(AllocationHeader * header); void heap_setAllocationCustomFlags(void const * const allocation, uint32_t customFlags); void heap_setAllocationCustomFlags(AllocationHeader * header, uint32_t customFlags); // TODO: Allow storing custom information in allocation heads for debugging memory. // Helper methods to prevent cyclic dependencies between strings and buffers when handles must be inlined for performance. Do not call these yourself. void impl_throwAllocationFailure(); void impl_throwNullException(); void impl_throwIdentityMismatch(uint64_t allocationIdentity, uint64_t pointerIdentity); } #endif