diff --git a/engine/utils/allocators/Tlsf.cpp b/engine/utils/allocators/Tlsf.cpp new file mode 100644 index 00000000..ffb186b5 --- /dev/null +++ b/engine/utils/allocators/Tlsf.cpp @@ -0,0 +1,465 @@ +// +// Copyright (c) 2020-present Caps Collective & contributors +// Originally authored by Jonathan Moallem (@jonjondev) & Aryeh Zinn (@Raelr) +// +// This code is released under an unmodified zlib license. +// For conditions of distribution and use, please see: +// https://opensource.org/licenses/Zlib +// + +#include "Tlsf.h" + +#include + +#include "../Logging.h" + +#define TO_BYTES(val) reinterpret_cast(val) +#define TO_HEADER(val) reinterpret_cast(val) +#define TO_FOOTER(val) reinterpret_cast(val) +#define TO_FREE_BLOCK(val) reinterpret_cast(val) + +#define HEADER_SIZE sizeof(BlockHeader) +#define FOOTER_SIZE sizeof(BlockFooter) +#define FREE_BLOCK_SIZE sizeof(FreeBlockNode) +#define METADATA_OVERHEAD HEADER_SIZE + FOOTER_SIZE +#define PAD_SIZE(size) size + METADATA_OVERHEAD + +#define MAX_SL_BUCKETS 16 +#define FL(size) 63 - __builtin_clzll(size) +#define SL(size, fl) (size >> fl) & ((1 << MIN_SIZE_INDEX) - 1); +#define FLAG_OFFSET 3 +#define INVALID_INDEX(T) std::numeric_limits::max() +#define MIN_ALLOCATION_SIZE 16 + +namespace Siege +{ +template +uint8_t TlsfAllocator::MIN_SIZE_INDEX = 4; + +template +TlsfAllocator::TlsfAllocator() +{} + +template +TlsfAllocator::TlsfAllocator(const T size) +{ + T paddedSize = PAD_SIZE(size); + if (size == 0 || size < MIN_ALLOCATION_SIZE || size > (INVALID_INDEX(T) - METADATA_OVERHEAD) || + paddedSize < size) + return; + + T maxBuckets = (FL(size) - MIN_SIZE_INDEX) + 1; + + T slBucketSize = sizeof(uint16_t) * maxBuckets; + T freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); + + T allocSize = paddedSize + slBucketSize + freeListSize; + + totalSize = paddedSize; + totalBytesRemaining = paddedSize; + + capacity = size; + bytesRemaining = size; + + data = TO_BYTES(calloc(1, allocSize)); + + CC_ASSERT( + data, + "Allocation returned null. This should not happen and implies an implementation failure.") + + freeList = (FreeBlockNode**) (data + paddedSize); + slBitmasks = (uint16_t*) (data + paddedSize + freeListSize); + + CreateHeader(data, paddedSize, FREE); + + AddNewBlock(paddedSize, TO_HEADER(data)); +} + +template +TlsfAllocator::~TlsfAllocator() +{ + if (data) free(data); + + totalBytesRemaining = 0; + capacity = 0; + bytesRemaining = 0; + totalSize = 0; + + flBitmask = 0; + + data = nullptr; + freeList = nullptr; + slBitmasks = nullptr; +} + +template +void TlsfAllocator::CreateHeader(uint8_t* ptr, const T size, HeaderFlags flags) +{ + if (!ptr) return; + BlockHeader* header = TO_HEADER(ptr); + header->sizeAndFlags = (size << FLAG_OFFSET) | flags; +} + +template +void* TlsfAllocator::Allocate(const T& size) +{ + T requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + if (!data || capacity == 0 || size == 0 || requiredSize < size || + requiredSize > totalBytesRemaining || size < MIN_ALLOCATION_SIZE) + return nullptr; + + FreeBlockNode* block = FindFreeBlock(requiredSize); + + if (!block) return nullptr; + + BlockHeader* header = TrySplitBlock(block, requiredSize); + + header->sizeAndFlags = (requiredSize << FLAG_OFFSET) | FULL; + CreateFooter(TO_BYTES(GetFooter(header)), requiredSize); + + uint8_t* ptr = GetBlockData(header); + bytesRemaining -= size; + totalBytesRemaining -= requiredSize; + return ptr; +} + +template +void TlsfAllocator::Deallocate(void** ptr) +{ + uint8_t* raw = (uint8_t*) *ptr; + if (!raw) return; + if (raw < data || raw >= (data + capacity)) return; + + BlockHeader* header = GetHeader(raw); + + if (IsFree(header)) return; + + T blockSize = GetHeaderSize(header); + + T totalBlockSize = blockSize; + header = TryCoalesce(header, totalBlockSize); + BlockFooter* footer = GetFooter(header); + + header->sizeAndFlags = (totalBlockSize << 3) | (header->sizeAndFlags & PREV_IS_FREE) | FREE; + footer->totalBlockSize = totalBlockSize; + + BlockHeader* nextHeader = GetNextHeader(header); + + if (nextHeader && ((uint8_t*) nextHeader < (data + capacity))) + { + nextHeader->sizeAndFlags |= PREV_IS_FREE; + } + + AddNewBlock(totalBlockSize, header); + + bytesRemaining += blockSize - sizeof(BlockHeader) - sizeof(BlockFooter); + totalBytesRemaining += blockSize; + + *ptr = nullptr; +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock( + TlsfAllocator::FreeBlockNode* node, + T& allocatedSize) +{ + if (!node) return nullptr; + + T oldSize = GetHeaderSize(GetHeader(node)); + + BlockHeader* header = GetHeader(node); + + if (!RemoveFreeBlock(node)) return nullptr; + + if (oldSize <= allocatedSize || + ((oldSize - allocatedSize) < (HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) + { + allocatedSize = oldSize; + return header; + } + + BlockHeader* newFreeBlock = TO_HEADER(TO_BYTES(header) + allocatedSize); + + AddNewBlock(oldSize - allocatedSize, newFreeBlock); + + return header; +} + +template +void TlsfAllocator::AddNewBlock(const T size, BlockHeader* header) +{ + T fl = 0, sl = 0, index; + index = CalculateFreeBlockIndices(size, fl, sl); + + CreateHeader(TO_BYTES(header), size, FREE); + FreeBlockNode* node = CreateFreeBlock(TO_BYTES(GetFreeBlock(header)), nullptr, freeList[index]); + CreateFooter(TO_BYTES(GetFooter(header)), size); + + if (node && node->next) node->next->prev = node; + + freeList[index] = node; + flBitmask |= (1ULL << fl); + slBitmasks[fl] |= (1 << sl); +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, + OUT T& size) +{ + BlockHeader* start = header; + BlockHeader* prev = GetPrevHeader(start); + BlockHeader* next = GetNextHeader(header); + + if (prev && IsFree(prev)) + { + start = prev; + size += GetHeaderSize(prev); + RemoveFreeBlock(GetFreeBlock(prev)); + } + + if (next && IsFree(next)) + { + size += GetHeaderSize(next); + RemoveFreeBlock(GetFreeBlock(next)); + } + + return start; +} + +template +bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) +{ + BlockHeader* header = GetHeader(node); + + if (!header) return false; + + T oldSize = GetHeaderSize(header); + + T fl, sl, index; + + index = CalculateFreeBlockIndices(oldSize, fl, sl); + + if (!node->prev) freeList[index] = node->next; + else node->prev->next = node->next; + + if (node->next) node->next->prev = node->prev; + + if (!freeList[index]) slBitmasks[fl] &= ~(1 << sl); + if (!slBitmasks[fl]) flBitmask &= ~(1ULL << fl); + + node->next = nullptr; + node->prev = nullptr; + + return true; +} + +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const T& size) +{ + T fl, sl, index; + index = CalculateFreeBlockIndices(size, fl, sl); + + if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); + if (index == INVALID_INDEX(T)) return nullptr; + + return freeList[index]; +} + +template +const T TlsfAllocator::GetNextFreeSlotIndex(T& fl, T& sl) +{ + sl = __builtin_ctz(slBitmasks[fl] & ~(((1 << (sl + 1)) - 1))); + + if (sl == 32) sl = 0; + if (sl) return fl * MAX_SL_BUCKETS + sl; + + fl = flBitmask & ~(((1ULL << (fl + 1)) - 1)); + + if (!fl) return INVALID_INDEX(T); + + fl = __builtin_ctzll(fl); + CC_ASSERT(slBitmasks[fl] > 0, + "SlBitmasks is returning 0. This should not be happening and indicates an " + "implementation error.") + + sl = __builtin_ctz(slBitmasks[fl]); + + return fl * MAX_SL_BUCKETS + sl; +} + +template +T TlsfAllocator::CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl) +{ + T rawFl = FL(size); + fl = rawFl - MIN_SIZE_INDEX; + sl = SL(size, fl); + return fl * MAX_SL_BUCKETS + sl; +} + +template +void TlsfAllocator::CreateFooter(uint8_t* ptr, const T size) +{ + if (!ptr) return; + BlockFooter* footer = TO_FOOTER(ptr); + footer->totalBlockSize = size; +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader( + TlsfAllocator::FreeBlockNode* node) +{ + if (!node) return nullptr; + uint8_t* rawHeader = TO_BYTES(node) - HEADER_SIZE; + if (!IsValid(rawHeader)) return nullptr; + return TO_HEADER(rawHeader); +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(uint8_t* ptr) +{ + if (!ptr) return nullptr; + uint8_t* rawHeader = ptr - HEADER_SIZE; + if (!IsValid(rawHeader)) return nullptr; + return TO_HEADER(rawHeader); +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader( + TlsfAllocator::BlockHeader* header) +{ + if (!header) return nullptr; + uint8_t* rawPrevFooter = TO_BYTES(GetPrevFooter(header)); + if (!IsValid(rawPrevFooter)) return nullptr; + uint8_t* prevHeader = (rawPrevFooter - TO_FOOTER(rawPrevFooter)->totalBlockSize) + FOOTER_SIZE; + if (!IsValid(prevHeader)) return nullptr; + return TO_HEADER(prevHeader); +} + +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader( + TlsfAllocator::BlockHeader* header) +{ + if (!header) return nullptr; + uint8_t* rawHeader = TO_BYTES(header); + uint8_t* next = rawHeader + GetHeaderSize(header); + if (!IsValid(next)) return nullptr; + return TO_HEADER(next); +} + +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock( + uint8_t* ptr, + TlsfAllocator::FreeBlockNode* prev, + TlsfAllocator::FreeBlockNode* next) +{ + if (!IsValid(ptr)) return nullptr; + + FreeBlockNode* node = TO_FREE_BLOCK(ptr); + node->prev = prev; + node->next = next; + return node; +} + +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) +{ + if (!header || !IsFree(header)) return nullptr; + uint8_t* rawBlock = TO_BYTES(header) + HEADER_SIZE; + if (!IsValid(rawBlock)) return nullptr; + return TO_FREE_BLOCK(rawBlock); +} + +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const T fl, const T sl) +{ + return freeList[fl * MAX_SL_BUCKETS + sl]; +} + +template +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter( + TlsfAllocator::BlockHeader* header) +{ + if (!header) return nullptr; + T size = GetHeaderSize(header); + uint8_t* rawFooter = TO_BYTES(header) + (size - FOOTER_SIZE); + if (!rawFooter || !IsValid(rawFooter)) return nullptr; + return TO_FOOTER(rawFooter); +} + +template +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter( + TlsfAllocator::BlockHeader* header) +{ + if (!header) return nullptr; + uint8_t* rawFooter = TO_BYTES(header) - FOOTER_SIZE; + if (!IsValid(rawFooter)) return nullptr; + return TO_FOOTER(rawFooter); +} + +template +uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) +{ + return TO_BYTES(header) + HEADER_SIZE; +} + +template +const T TlsfAllocator::GetHeaderSize(BlockHeader* header) +{ + return header->sizeAndFlags >> FLAG_OFFSET; +} + +template +bool TlsfAllocator::IsFree(BlockHeader* header) +{ + return header->sizeAndFlags & FREE; +} + +template +bool TlsfAllocator::IsFree(T fl, T sl) +{ + return slBitmasks[fl] & (1 << sl); +} + +template +bool TlsfAllocator::IsFull() +{ + return totalBytesRemaining == 0; +} + +template +bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) +{ + uint8_t* raw = TO_BYTES(header); + if (raw == data) return false; + + BlockFooter* prevFooter = GetPrevFooter(header); + if (!prevFooter) return false; + + uint8_t* rawPrevHeader = raw - (prevFooter->totalBlockSize - FOOTER_SIZE); + if (!rawPrevHeader || !IsValid(rawPrevHeader)) return false; + + return IsFree(TO_HEADER(rawPrevHeader)); +} + +template +bool TlsfAllocator::IsValid(uint8_t* ptr) +{ + return ptr && ptr >= data && ptr < (data + (capacity + HEADER_SIZE + FOOTER_SIZE)); +} + +template +const T TlsfAllocator::Capacity() +{ + return capacity; +} + +template +const T TlsfAllocator::BytesRemaining() +{ + return bytesRemaining; +} + +template class TlsfAllocator; +template class TlsfAllocator; +template class TlsfAllocator; +} // namespace Siege diff --git a/engine/utils/allocators/Tlsf.h b/engine/utils/allocators/Tlsf.h new file mode 100644 index 00000000..2473655b --- /dev/null +++ b/engine/utils/allocators/Tlsf.h @@ -0,0 +1,401 @@ +// +// Copyright (c) 2020-present Caps Collective & contributors +// Originally authored by Jonathan Moallem (@jonjondev) & Aryeh Zinn (@Raelr) +// +// This code is released under an unmodified zlib license. +// For conditions of distribution and use, please see: +// https://opensource.org/licenses/Zlib +// + +#ifndef SIEGE_ENGINE_TLSF_H +#define SIEGE_ENGINE_TLSF_H + +#include + +#include "../Macros.h" + +/** + * A shorthand macro for making deallocations easier to manage. + */ +#define FREE(ptr) Deallocate((void**) &ptr) + +namespace Siege +{ +template +class TlsfAllocator +{ +public: + + /** + * @brief Flags for determining the state of a memory block. Every header has a flag where the + * first three bits are dedicated to keeping track of the block's state + */ + enum HeaderFlags + { + FULL = 0, + FREE = 1, + PREV_IS_FREE = 2 + }; + + /** + * @brief linked list nodes for representing free memory blocks + */ + struct FreeBlockNode + { + FreeBlockNode* next {nullptr}; + FreeBlockNode* prev {nullptr}; + }; + + /** + * @brief A struct representing the header for each memory block in the allocator. This struct + * stores the size of the entire block alongside the state of the block + */ + struct BlockHeader + { + T sizeAndFlags {0}; + }; + + /** + * @brief a struct which stores the size of the the entire memory block. Used to navigate the + * allocator and find the block's header + */ + struct BlockFooter + { + T totalBlockSize {0}; + }; + + // S'tructors + + /** + * @brief Empty constructor Initialises the constructor and size to 0 (null) + */ + TlsfAllocator(); + + /** + * @brief Size initialiser. Accepts a number representing the number of bytes to allocate to + * the allocator's memory buffer + * @param size the number of bytes to allocate, cannot be less than 16 + */ + TlsfAllocator(const T size); + + /** + * @brief Destructor, deallocates all memory and sets all values to zero + */ + ~TlsfAllocator(); + + // Deleted copy and move constructors + + TlsfAllocator(const TlsfAllocator& other) = delete; + TlsfAllocator(const TlsfAllocator&& other) = delete; + + // Other functions + + /** + * @brief Checks if a block's preceding neighbour is free (the block that comes before it) + * @param header the current header + * @return true if the previous block is free, otherwise false + */ + bool PrevBlockIsFree(BlockHeader* header); + + /** + * @brief Checks if a block is free for allocation + * @param header the header to check for free space + * @return true if the free flag is set, otherwise false + */ + bool IsFree(BlockHeader* header); + + /** + * @brief Checks if an entry exists in the FreeList for a specific combination of first and + * second level indices + * @param fl the index to search the first level bitmask with + * @param sl the index to search the second level bitmask with + * @return true if a free block exists, false otherwise + */ + bool IsFree(T fl, T sl); + + /** + * @brief Checks if the allocator is full + * @return true if there are no bytes left in the allocator, otherwise false + */ + bool IsFull(); + + // Buffer manipulation Functions + + /** + * @brief Retrieves a free block from a header if possible + * @param header the header from which to get a FreeBlock + * @return a FreeBlock if applicable, otherwise a nullptr + */ + FreeBlockNode* GetFreeBlock(BlockHeader* header); + + /** + * @brief Retrieves a free block using first and second level indices + * @param fl the first level index to be calculated + * @param sl the second level index to be calculated + * @return the FreeBlock at the specified first and second level indices + */ + FreeBlockNode* GetFreeBlock(const T fl, const T sl); + + /** + * @brief Retrieves the raw allocated data stored within the memory block + * @param header the header to extrapolate the data from + * @return a pointer to the data stored by the memory block + */ + uint8_t* GetBlockData(BlockHeader* header); + + /** + * @brief Gets the footer for a memory block + * @param header the header of the memory block + * @return the footer associated with the memory block or nullptr if out of bounds + */ + BlockFooter* GetFooter(BlockHeader* header); + + /** + * @brief Returns the footer of the previous memory block + * @param header the current header + * @return the previous footer or nullptr if it doesnt exist + */ + BlockFooter* GetPrevFooter(BlockHeader* header); + + /** + * @brief Returns the header associated with a FreeBlock + * @param node the FreeBlock to search + * @return the BlockHeader associated with the free block + */ + BlockHeader* GetHeader(FreeBlockNode* node); + + /** + * @brief Returns the header of a data pointer. Assumes that the data is pointing to the start + * of the data pointer + * @param ptr the raw data pointer + * @return a BlockHeader if one exists + */ + BlockHeader* GetHeader(uint8_t* ptr); + + /** + * @brief Returns the header of the previous data block if within the allocator's range + * @param header the current header + * @return the header of the previous block or nullptr if none exist + */ + BlockHeader* GetPrevHeader(BlockHeader* header); + + /** + * @brief Returns the header of the next memory block if within the allocator's range + * @param header the current header + * @return the header of the next memory block or nullptr if out of bounds + */ + BlockHeader* GetNextHeader(BlockHeader* header); + + // Allocate/Deallocate + + /** + * @brief Allocates a memory block and returns a pointer to the allocator's buffer + * @param size the size in bytes to allocate. The minimum allocatable chunk of memory is 16 + * bytes + * @return a pointer to the memory block + */ + void* Allocate(const T& size); + + /** + * @brief Deallocates a memory block and returns the memory to the allocator's pool + * @param ptr the pointer to deallocate + */ + void Deallocate(void** ptr); + + // Getters + + /** + * @brief Returns the size of a memory block from its header + * @param header the header of the memory block + * @return the fully padded size of the memory block + */ + const T GetHeaderSize(BlockHeader* header); + + /** + * Returns the maximum capacity of the allocator + * @return the allocator's capacity in bytes + */ + const T Capacity(); + + /** + * Returns the number of unpadded bytes remaining + * @return the number of bytes remaining in the allocator + */ + const T BytesRemaining(); + + /** + * Returns the total bytes remaining with padding + * @return the total bytes remaining with padding + */ + const T TotalBytesRemaining() + { + return totalBytesRemaining; + } + + /** + * Returns the total size of allocated memory by the allocator with padding + * @return the total size of allocated memory by the allocator with padding + */ + const T TotalSize() + { + return totalSize; + } + + /** + * Returns the data buffer held by the allocator + * @return the data buffer held by the allocator + */ + const uint8_t* Data() + { + return data; + } + + /** + * Returns the bitmask representing the first level of size classes + * @return the bitmask representing the first level of size classes + */ + const T FlBitmask() + { + return flBitmask; + } + + /** + * Returns the second level bitmask stored by the allocator + * @param fl the first level index to search over + * @return the value of the second level bitmask + */ + const uint16_t& SlBitmask(const T fl) + { + return slBitmasks[fl]; + } + +private: + + /** + * @brief Calculates the FreeList, first level, and second level indices for a given size + * @param size the size class to search for in the allocator + * @param fl the first level index to be calculated + * @param sl the second level index to be calculated + * @return the FreeList index + */ + T CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl); + + /** + * @brief Creates and initialises a header at a specified memory location + * @param ptr the memory location, represented as a pointer to a set of bytes + * @param size the size to allocate to the memory block. Size must have metadata sizes factored + * into its calculation + * @param flags The state flags for header (see HeaderFlags) + */ + void CreateHeader(uint8_t* ptr, const T size, HeaderFlags flags); + + /** + * @brief Creates a footer at a specified memory location. Footers store the size of the entire + * memory block (including metadata padding) + * @param ptr the memory location to allocate the footer to (represented as a byte pointer) + * @param size the size to assign the footer (should be the full block size) + */ + void CreateFooter(uint8_t* ptr, const T size); + + /** + * @brief Creates a FreeBlockNode at a specified memory location and positions it within the + * FreeList + * @param ptr the memory location to initialise the block to + * @param prev the FreeBlock that precedes this block + * @param next the FreeBlock that follows this block + * @return a pointer to the new FreeBlock + */ + FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next); + + /** + * @brief Finds a free block for a specified size. If none exist, will attempt to find the next + * available block + * @param size the minimum size of the free block + * @return a pointer to a FreeBlockNode if one exists, otherwise nullptr + */ + FreeBlockNode* FindFreeBlock(const T& size); + + /** + * @brief Returns the next available free block (if any exist) + * @param fl the index to search the first level bitmask with + * @param sl the index to search the second level bitmask with + * @return the index to the FreeList where the block exists + */ + const T GetNextFreeSlotIndex(T& fl, T& sl); + + /** + * Attempts to split a block in two, prioritising splitting the block into the requested size + * first and allocating the remainder to a second block. If an attempted split results in too + * few bytes remaining, the block will allocate the full free block + * @param node the node to try and split + * @param allocatedSize the size to allocate to the first split block + * @return a BlockHeader representing the split block + */ + BlockHeader* TrySplitBlock(FreeBlockNode* node, T& allocatedSize); + + /** + * Removes a free block from the FreeList, unlinking it from its class linked list + * @param node the node to remove + * @return true if successfully removed, otherwise false + */ + bool RemoveFreeBlock(FreeBlockNode* node); + + /** + * Adds a new free block to the allocator + * @param size the size in bytes of the new FreeBlock + * @param currentNode the header of the FreeNode you wish to add + */ + void AddNewBlock(const T size, BlockHeader* currentNode); + + /** + * Attempts to combine a FreeBlock with its neighbours and updates the size of the header if + * memory blocks were successfully combined + * @param header the header to attempt to coalesce + * @param size the size in bytes of the combined new memory block + * @return the BlockHeader of the combined memory block (if combined) + */ + BlockHeader* TryCoalesce(BlockHeader* header, OUT T& size); + + /** + * Tests if a pointer is valid within the allocator + * @param ptr the pointer to test + * @return true if the pointer is valid, otherwise false + */ + bool IsValid(uint8_t* ptr); + + static uint8_t MIN_SIZE_INDEX; + + T totalSize {0}; + T totalBytesRemaining {0}; + + T capacity {0}; + T bytesRemaining {0}; + + uint8_t* data {nullptr}; + FreeBlockNode** freeList {nullptr}; + + // bitmasks + T flBitmask {0}; + uint16_t* slBitmasks {nullptr}; +}; + +/** + * @brief A TLSF allocator capable of storing up to 8KB of memory. Each allocation has a overhead of + * 4 bytes and can only allocate a minimum of 16 bytes + */ +typedef TlsfAllocator SmallTlsfAllocator; + +/** + * A TLSF allocator capable of storing up to 512MB of memory. Each allocation has a overhead of + * 8 bytes and can only allocate a minimum of 16 bytes + */ +typedef TlsfAllocator MediumTlsfAllocator; + +/** + * A TLSF allocator capable of storing up to 8GB of memory. Each allocation has a overhead of + * 16 bytes and can only allocate a minimum of 16 bytes + */ +typedef TlsfAllocator LargeTlsfAllocator; +} // namespace Siege + +#endif // SIEGE_ENGINE_TLSF_H diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp new file mode 100644 index 00000000..46f3b028 --- /dev/null +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -0,0 +1,1425 @@ +// +// Copyright (c) 2020-present Caps Collective & contributors +// Originally authored by Jonathan Moallem (@jonjondev) & Aryeh Zinn (@Raelr) +// +// This code is released under an unmodified zlib license. +// For conditions of distribution and use, please see: +// https://opensource.org/licenses/Zlib +// + +#include +#include +#include + +#include +#include + +using namespace Siege; + +struct TestStruct +{ + uint64_t inta {0}; + uint64_t intb {0}; +}; + +UTEST(test_MediumTlsfAllocator, EmptyConstructor) +{ + MediumTlsfAllocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); + ASSERT_EQ(0, a.BytesRemaining()); +} + +UTEST(test_MediumTlsfAllocator, ConstructorWithSize) +{ + MediumTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.TotalSize(), 72); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(4, a.SlBitmask(2)); + + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 2); + ASSERT_TRUE(block); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + MediumTlsfAllocator::BlockHeader* header = a.GetHeader(block); + ASSERT_TRUE(header); + ASSERT_EQ(72, a.GetHeaderSize(header)); + ASSERT_TRUE(a.IsFree(header)); + ASSERT_FALSE(a.PrevBlockIsFree(header)); + + MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + ASSERT_TRUE(footer); + ASSERT_EQ(72, footer->totalBlockSize); +} + +UTEST(test_MediumTlsfAllocator, ConstructorWithTooSmallSize) +{ + Siege::MediumTlsfAllocator a(7); + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(test_MediumTlsfAllocator, TestAllocateFunction) +{ + MediumTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(4, a.SlBitmask(2)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(a.BytesRemaining(), 48); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(1, a.FlBitmask()); + ASSERT_EQ(0, a.SlBitmask(1)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(192, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(MediumTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(32, a.BytesRemaining()); + + // Edge cases + + // Empty allocator + MediumTlsfAllocator emptyA; + + TestStruct* ptr = (TestStruct*) emptyA.Allocate(sizeof(TestStruct)); + ASSERT_FALSE(ptr); + + // Allocate 0 bytes + void* emptyAllocPtr = a.Allocate(0); + ASSERT_FALSE(emptyAllocPtr); + + // Allocate an amount that causes a value overflow + MediumTlsfAllocator overflowAlloc(UINT32_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT32_MAX); + ASSERT_FALSE(overflowPtr); + + void* tooLargeCase = a.Allocate(100); + ASSERT_FALSE(tooLargeCase); + + MediumTlsfAllocator tooSmallAlloc(64); + void* tooLargeAlloc = tooSmallAlloc.Allocate(100); + ASSERT_FALSE(tooLargeAlloc); +} + +UTEST(Test_MediumTlsfAllocator, TestAllocateFunctionWithRandomInputs) +{ + MediumTlsfAllocator a(1024 * 1024); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(12345); + + std::uniform_int_distribution sizeDist(1, 256); + + MediumTlsfAllocator::BlockHeader* header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + + for (int i = 0; i < 10000; i++) + { + size_t randomSize = sizeDist(generator); + void* ptr = a.Allocate(randomSize); + + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + + uint64_t expectedSize = header->sizeAndFlags >> 3; + uint32_t* data = (uint32_t*) a.GetBlockData(header); + ASSERT_EQ(0xDEADC0DE, *data); + MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + header = a.GetNextHeader(header); + } + else continue; + } +} + +UTEST(Test_MediumTlsfAllocator, TestDeallocateFunction) +{ + MediumTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(4, a.SlBitmask(2)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(48, a.BytesRemaining()); + + a.FREE(p); + + // Should be empty + ASSERT_EQ(577, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & MediumTlsfAllocator::FREE); + ASSERT_FALSE(p); + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(4, a.SlBitmask(2)); + ASSERT_EQ(64, a.BytesRemaining()); + + p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + data = (TestStruct*) a.GetBlockData(header); + footer = a.GetFooter(header); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(48, a.BytesRemaining()); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(1, a.FlBitmask()); + ASSERT_EQ(0, a.SlBitmask(1)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(192, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(MediumTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(32, a.BytesRemaining()); + + a.FREE(p); + ASSERT_FALSE(p); + ASSERT_TRUE(strHeader->sizeAndFlags & MediumTlsfAllocator::PREV_IS_FREE); + ASSERT_EQ(193, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & MediumTlsfAllocator::FREE); + MediumTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(header); + ASSERT_NE(nullptr, newFreeBlock->next); + ASSERT_EQ(nullptr, newFreeBlock->prev); + ASSERT_EQ(48, a.BytesRemaining()); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(577, header->sizeAndFlags); + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(4, a.SlBitmask(2)); + ASSERT_EQ(64, a.BytesRemaining()); + + MediumTlsfAllocator::FreeBlockNode* newNode = (MediumTlsfAllocator::FreeBlockNode*) data; + ASSERT_TRUE(newNode); + // Check if the new node is head + ASSERT_EQ(nullptr, newNode->prev); + ASSERT_EQ(nullptr, newNode->next); +} + +UTEST(Test_MediumTlsfAllocator, TestBlockCoalescing) +{ + MediumTlsfAllocator a(128); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 128); + ASSERT_EQ(128, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 8); + ASSERT_EQ(2, a.SlBitmask(3)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(4096, a.SlBitmask(2)); + // 0001 0000 0000 0000 + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 12); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(112, a.BytesRemaining()); + + auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(64, a.SlBitmask(2)); + + MediumTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(2, 6); + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + ASSERT_EQ(96, a.BytesRemaining()); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(192, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(MediumTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + + TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p2->inta = 10; + p2->intb = 20; + + ASSERT_EQ(10, p2->inta); + ASSERT_EQ(20, p2->intb); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(1, a.SlBitmask(2)); + + MediumTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(2, 0); + ASSERT_EQ(newFreeBlock->next, nullptr); + ASSERT_EQ(newFreeBlock->prev, nullptr); + ASSERT_EQ(80, a.BytesRemaining()); + + auto newHeader = a.GetNextHeader(strHeader); + auto newData = (String*) a.GetBlockData(newHeader); + auto newFooter = a.GetFooter(newHeader); + + ASSERT_EQ(192, newHeader->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + a.FREE(p); + + MediumTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 8); + ASSERT_EQ(firstFree->next, nullptr); + ASSERT_EQ(firstFree->prev, nullptr); + ASSERT_EQ(96, a.BytesRemaining()); + + MediumTlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); + ASSERT_TRUE(firstFreeHeader->sizeAndFlags & MediumTlsfAllocator::FREE); + ASSERT_FALSE(firstFreeHeader->sizeAndFlags & MediumTlsfAllocator::PREV_IS_FREE); + + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(256, a.SlBitmask(0)); + + a.FREE(p2); + ASSERT_FALSE(p2); + ASSERT_EQ(112, a.BytesRemaining()); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(256, a.SlBitmask(0)); + ASSERT_EQ(64, a.SlBitmask(2)); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(128, a.BytesRemaining()); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(3)); + + // Edge Cases + + void* badPointer = nullptr; + a.FREE(badPointer); + ASSERT_FALSE(badPointer); + + // try to deallocate pointer not in allocator; + + uint64_t* val = new uint64_t; + a.FREE(val); // Should do nothing and be ignored. + free(val); +} + +UTEST(Test_MediumTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) +{ + MediumTlsfAllocator a(128); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 128); + ASSERT_EQ(128, a.BytesRemaining()); + + void* p0 = a.Allocate(16); + void* p1 = a.Allocate(32); + void* p2 = a.Allocate(16); + void* p3 = a.Allocate(32); + ASSERT_EQ(0, a.TotalBytesRemaining()); + ASSERT_TRUE(a.IsFull()); + + a.FREE(p0); + a.FREE(p2); + + ASSERT_EQ(64, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(24); + ASSERT_FALSE(tooLargeVal); +} + +UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) +{ + MediumTlsfAllocator a(1024 * 1024); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(12345); + + std::uniform_int_distribution actionDist(0, 10); + std::uniform_int_distribution sizeDist(1, 256); + + std::vector pointers; + pointers.reserve(10000); + + for (int i = 0; i < 10000; i++) + { + int action = actionDist(generator); + + if (action < 8) + { + size_t randomSize = sizeDist(generator); + + void* ptr = a.Allocate(randomSize); + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + pointers.push_back(ptr); + MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); + } + } + else if (action <= 10 && !pointers.empty()) + { + std::uniform_int_distribution indices(0, pointers.size() - 1); + size_t index = indices(generator); + + void*& ptrToFree = pointers[index]; + ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); + MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + + a.FREE(ptrToFree); + MediumTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + + ASSERT_FALSE(ptrToFree); + pointers.erase(pointers.begin() + index); + } + } + + for (void* ptr : pointers) + { + a.FREE(ptr); + } + + ASSERT_EQ(a.TotalSize(), a.TotalBytesRemaining()); +} + +// Small AllocatorTests + +UTEST(Test_SmallTlsfAllocator, EmptyConstructor) +{ + SmallTlsfAllocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); + ASSERT_EQ(0, a.BytesRemaining()); +} + +UTEST(Test_SmallTlsfAllocator, ConstructorWithSize) +{ + SmallTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(68, a.TotalSize()); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(2, a.SlBitmask(2)); + + SmallTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 1); + ASSERT_TRUE(block); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + SmallTlsfAllocator::BlockHeader* header = a.GetHeader(block); + ASSERT_TRUE(header); + ASSERT_EQ(68, a.GetHeaderSize(header)); + ASSERT_TRUE(a.IsFree(header)); + ASSERT_FALSE(a.PrevBlockIsFree(header)); + + SmallTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + ASSERT_TRUE(footer); + ASSERT_EQ(68, footer->totalBlockSize); +} + +UTEST(Test_SmallTlsfAllocator, ConstructorWithTooSmallSize) +{ + Siege::SmallTlsfAllocator a(15); + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(Test_SmallTlsfAllocator, TestAllocateFunction) +{ + SmallTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(2, a.SlBitmask(2)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + SmallTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (SmallTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(160, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(SmallTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(a.BytesRemaining(), 48); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(1, a.FlBitmask()); + ASSERT_EQ(0, a.SlBitmask(1)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(160, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(SmallTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(32, a.BytesRemaining()); + + // Edge cases + + // Empty allocator + SmallTlsfAllocator emptyA; + + TestStruct* ptr = (TestStruct*) emptyA.Allocate(sizeof(TestStruct)); + ASSERT_FALSE(ptr); + + // Allocate 0 bytes + void* emptyAllocPtr = a.Allocate(0); + ASSERT_FALSE(emptyAllocPtr); + + // Allocate an amount that causes a value overflow + SmallTlsfAllocator overflowAlloc(UINT16_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT16_MAX); + ASSERT_FALSE(overflowPtr); + + void* tooLargeCase = a.Allocate(100); + ASSERT_FALSE(tooLargeCase); + + SmallTlsfAllocator tooSmallAlloc(64); + void* tooLargeAlloc = tooSmallAlloc.Allocate(100); + ASSERT_FALSE(tooLargeAlloc); +} + +UTEST(Test_SmallTlsfAllocator, TestAllocateFunctionWithRandomInputs) +{ + SmallTlsfAllocator a(8000); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(seed); + + std::uniform_int_distribution sizeDist(1, 256); + + SmallTlsfAllocator::BlockHeader* header = (SmallTlsfAllocator::BlockHeader*) a.Data(); + + for (int i = 0; i < 10000; i++) + { + size_t randomSize = sizeDist(generator); + void* ptr = a.Allocate(randomSize); + + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + + uint64_t expectedSize = header->sizeAndFlags >> 3; + uint32_t* data = (uint32_t*) a.GetBlockData(header); + ASSERT_EQ(0xDEADC0DE, *data); + SmallTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + header = a.GetNextHeader(header); + } + else continue; + } +} + +UTEST(Test_SmallTlsfAllocator, TestDeallocateFunction) +{ + SmallTlsfAllocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(2, a.SlBitmask(2)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + SmallTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (SmallTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(160, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(SmallTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(48, a.BytesRemaining()); + + a.FREE(p); + + // Should be empty + ASSERT_EQ(545, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & SmallTlsfAllocator::FREE); + ASSERT_FALSE(p); + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(2)); + ASSERT_EQ(64, a.BytesRemaining()); + + p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(2, a.FlBitmask()); + ASSERT_EQ(256, a.SlBitmask(1)); + + block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + header = (SmallTlsfAllocator::BlockHeader*) a.Data(); + data = (TestStruct*) a.GetBlockData(header); + footer = a.GetFooter(header); + + ASSERT_EQ(160, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(SmallTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(48, a.BytesRemaining()); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(1, a.FlBitmask()); + ASSERT_EQ(0, a.SlBitmask(1)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(160, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(SmallTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(32, a.BytesRemaining()); + + a.FREE(p); + ASSERT_FALSE(p); + ASSERT_TRUE(strHeader->sizeAndFlags & SmallTlsfAllocator::PREV_IS_FREE); + ASSERT_EQ(161, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & SmallTlsfAllocator::FREE); + SmallTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(header); + ASSERT_EQ(nullptr, newFreeBlock->next); + ASSERT_EQ(nullptr, newFreeBlock->prev); + ASSERT_EQ(48, a.BytesRemaining()); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(545, header->sizeAndFlags); + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(2)); + ASSERT_EQ(64, a.BytesRemaining()); + + SmallTlsfAllocator::FreeBlockNode* newNode = (SmallTlsfAllocator::FreeBlockNode*) data; + ASSERT_TRUE(newNode); + // Check if the new node is head + ASSERT_EQ(nullptr, newNode->prev); + ASSERT_EQ(nullptr, newNode->next); +} + +UTEST(Test_SmallTlsfAllocator, TestBlockCoalescing) +{ + SmallTlsfAllocator a(128); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 128); + ASSERT_EQ(128, a.BytesRemaining()); + + ASSERT_EQ(a.FlBitmask(), 8); + ASSERT_EQ(1, a.SlBitmask(3)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(4096, a.SlBitmask(2)); + // 0001 0000 0000 0000 + SmallTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 12); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(112, a.BytesRemaining()); + + auto header = (SmallTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(160, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(SmallTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(128, a.SlBitmask(2)); + + SmallTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(2, 7); + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + ASSERT_EQ(96, a.BytesRemaining()); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(160, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(SmallTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + + TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p2->inta = 10; + p2->intb = 20; + + ASSERT_EQ(10, p2->inta); + ASSERT_EQ(20, p2->intb); + + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(4, a.SlBitmask(2)); + + SmallTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(2, 2); + ASSERT_EQ(newFreeBlock->next, nullptr); + ASSERT_EQ(newFreeBlock->prev, nullptr); + ASSERT_EQ(80, a.BytesRemaining()); + + auto newHeader = a.GetNextHeader(strHeader); + auto newData = (String*) a.GetBlockData(newHeader); + auto newFooter = a.GetFooter(newHeader); + + ASSERT_EQ(160, newHeader->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(SmallTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + a.FREE(p); + + SmallTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 4); + ASSERT_EQ(firstFree->next, nullptr); + ASSERT_EQ(firstFree->prev, nullptr); + ASSERT_EQ(96, a.BytesRemaining()); + + SmallTlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); + ASSERT_TRUE(firstFreeHeader->sizeAndFlags & SmallTlsfAllocator::FREE); + ASSERT_FALSE(firstFreeHeader->sizeAndFlags & SmallTlsfAllocator::PREV_IS_FREE); + + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(16, a.SlBitmask(0)); + + a.FREE(p2); + ASSERT_FALSE(p2); + ASSERT_EQ(112, a.BytesRemaining()); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(16, a.SlBitmask(0)); + ASSERT_EQ(128, a.SlBitmask(2)); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(128, a.BytesRemaining()); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(1, a.SlBitmask(3)); + + // Edge Cases + + void* badPointer = nullptr; + a.FREE(badPointer); + ASSERT_FALSE(badPointer); + + // try to deallocate pointer not in allocator; + + uint64_t* val = new uint64_t; + a.FREE(val); // Should do nothing and be ignored. + free(val); +} + +UTEST(Test_SmallTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) +{ + SmallTlsfAllocator a(128); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 128); + ASSERT_EQ(128, a.BytesRemaining()); + + void* p0 = a.Allocate(20); + void* p1 = a.Allocate(36); + void* p2 = a.Allocate(20); + void* p3 = a.Allocate(36); + ASSERT_EQ(0, a.TotalBytesRemaining()); + ASSERT_TRUE(a.IsFull()); + + a.FREE(p0); + a.FREE(p2); + + ASSERT_EQ(48, a.TotalBytesRemaining()); + void* tooLargeVal = a.Allocate(24); + ASSERT_FALSE(tooLargeVal); +} + +UTEST(Test_SmallTlsfAllocator, TestRandomAllocationsAndDeallocations) +{ + SmallTlsfAllocator a(8000); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(seed); + + std::uniform_int_distribution actionDist(0, 10); + std::uniform_int_distribution sizeDist(1, 256); + + std::vector pointers; + pointers.reserve(10000); + + for (int i = 0; i < 10000; i++) + { + int action = actionDist(generator); + + if (action < 8) + { + size_t randomSize = sizeDist(generator); + + void* ptr = a.Allocate(randomSize); + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + pointers.push_back(ptr); + SmallTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); + } + } + else if (action <= 10 && !pointers.empty()) + { + std::uniform_int_distribution indices(0, pointers.size() - 1); + size_t index = indices(generator); + + void*& ptrToFree = pointers[index]; + ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); + SmallTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + + a.FREE(ptrToFree); + SmallTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + + ASSERT_FALSE(ptrToFree); + pointers.erase(pointers.begin() + index); + } + } + + for (void* ptr : pointers) + { + a.FREE(ptr); + } + + ASSERT_EQ(a.TotalSize(), a.TotalBytesRemaining()); +} + +// Large Allocator Tests + +UTEST(Test_LargeTlsfAllocator, EmptyConstructor) +{ + LargeTlsfAllocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); + ASSERT_EQ(0, a.BytesRemaining()); +} + +UTEST(Test_LargeTlsfAllocator, ConstructorWithSize) +{ + LargeTlsfAllocator a(256); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.TotalSize(), 272); + ASSERT_EQ(256, a.BytesRemaining()); + + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(4, 1); + ASSERT_TRUE(block); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + LargeTlsfAllocator::BlockHeader* header = a.GetHeader(block); + ASSERT_TRUE(header); + ASSERT_EQ(272, a.GetHeaderSize(header)); + ASSERT_TRUE(a.IsFree(header)); + ASSERT_FALSE(a.PrevBlockIsFree(header)); + + LargeTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + ASSERT_TRUE(footer); + ASSERT_EQ(272, footer->totalBlockSize); +} + +UTEST(Test_LargeTlsfAllocator, ConstructorWithTooSmallSize) +{ + Siege::LargeTlsfAllocator a(15); + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(Test_LargeTlsfAllocator, TestAllocateFunction) +{ + LargeTlsfAllocator a(256); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); + + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); + + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(3, 14); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(256, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(a.BytesRemaining(), 240); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(1024, a.SlBitmask(3)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(256, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(LargeTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(224, a.BytesRemaining()); + + // Edge cases + + // Empty allocator + LargeTlsfAllocator emptyA; + + TestStruct* ptr = (TestStruct*) emptyA.Allocate(sizeof(TestStruct)); + ASSERT_FALSE(ptr); + + // Allocate 0 bytes + void* emptyAllocPtr = a.Allocate(0); + ASSERT_FALSE(emptyAllocPtr); + + // Allocate an amount that causes a value overflow + LargeTlsfAllocator overflowAlloc(UINT64_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); + ASSERT_FALSE(overflowPtr); + + LargeTlsfAllocator tooSmallAlloc(256); + void* tooLargeAlloc = tooSmallAlloc.Allocate(1024); + ASSERT_FALSE(tooLargeAlloc); +} + +UTEST(Test_LargeTlsfAllocator, TestAllocateFunctionWithRandomInputs) +{ + LargeTlsfAllocator a(4096 * 4096); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(seed); + + std::uniform_int_distribution sizeDist(1, 2048); + + LargeTlsfAllocator::BlockHeader* header = (LargeTlsfAllocator::BlockHeader*) a.Data(); + + for (int i = 0; i < 10000; i++) + { + size_t randomSize = sizeDist(generator); + void* ptr = a.Allocate(randomSize); + + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + + uint64_t expectedSize = header->sizeAndFlags >> 3; + uint32_t* data = (uint32_t*) a.GetBlockData(header); + ASSERT_EQ(0xDEADC0DE, *data); + LargeTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + header = a.GetNextHeader(header); + } + else continue; + } +} + +UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) +{ + LargeTlsfAllocator a(256); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); + + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); + + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(3, 14); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(256, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(240, a.BytesRemaining()); + + a.FREE(p); + + // Should be empty + ASSERT_EQ(2177, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & LargeTlsfAllocator::FREE); + ASSERT_FALSE(p); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + ASSERT_EQ(256, a.BytesRemaining()); + + p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); + + block = a.GetFreeBlock(3, 14); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + header = (LargeTlsfAllocator::BlockHeader*) a.Data(); + data = (TestStruct*) a.GetBlockData(header); + footer = a.GetFooter(header); + + ASSERT_EQ(256, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), + footer->totalBlockSize); + ASSERT_EQ(240, a.BytesRemaining()); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(1024, a.SlBitmask(3)); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(256, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(LargeTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + ASSERT_EQ(224, a.BytesRemaining()); + + a.FREE(p); + ASSERT_FALSE(p); + ASSERT_TRUE(strHeader->sizeAndFlags & LargeTlsfAllocator::PREV_IS_FREE); + ASSERT_EQ(257, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & LargeTlsfAllocator::FREE); + LargeTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(header); + ASSERT_EQ(nullptr, newFreeBlock->next); + ASSERT_EQ(nullptr, newFreeBlock->prev); + ASSERT_EQ(240, a.BytesRemaining()); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(2177, header->sizeAndFlags); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + ASSERT_EQ(256, a.BytesRemaining()); + + LargeTlsfAllocator::FreeBlockNode* newNode = (LargeTlsfAllocator::FreeBlockNode*) data; + ASSERT_TRUE(newNode); + // Check if the new node is head + ASSERT_EQ(nullptr, newNode->prev); + ASSERT_EQ(nullptr, newNode->next); +} + +UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) +{ + LargeTlsfAllocator a(256); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); + + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + + TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); + + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(3, 14); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(240, a.BytesRemaining()); + + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); + auto data = (TestStruct*) a.GetBlockData(header); + auto footer = a.GetFooter(header); + + ASSERT_EQ(256, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(), "Hello There!"); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(1024, a.SlBitmask(3)); + + LargeTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(3, 10); + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + ASSERT_EQ(224, a.BytesRemaining()); + + auto strHeader = a.GetNextHeader(header); + auto strData = (String*) a.GetBlockData(strHeader); + auto strFooter = a.GetFooter(strHeader); + + ASSERT_EQ(256, strHeader->sizeAndFlags); + ASSERT_EQ(strData, str); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(LargeTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); + + TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); + + p2->inta = 10; + p2->intb = 20; + + ASSERT_EQ(10, p2->inta); + ASSERT_EQ(20, p2->intb); + + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(64, a.SlBitmask(3)); + + LargeTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(3, 6); + ASSERT_EQ(newFreeBlock->next, nullptr); + ASSERT_EQ(newFreeBlock->prev, nullptr); + ASSERT_EQ(208, a.BytesRemaining()); + + auto newHeader = a.GetNextHeader(strHeader); + auto newData = (String*) a.GetBlockData(newHeader); + auto newFooter = a.GetFooter(newHeader); + + ASSERT_EQ(256, newHeader->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), + footer->totalBlockSize); + + a.FREE(p); + + LargeTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(3, 6); + ASSERT_EQ(firstFree->next, nullptr); + ASSERT_EQ(firstFree->prev, nullptr); + ASSERT_EQ(224, a.BytesRemaining()); + + LargeTlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); + ASSERT_TRUE(firstFreeHeader->sizeAndFlags & LargeTlsfAllocator::FREE); + ASSERT_FALSE(firstFreeHeader->sizeAndFlags & LargeTlsfAllocator::PREV_IS_FREE); + + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 10); + ASSERT_EQ(1, a.SlBitmask(1)); + + a.FREE(p2); + ASSERT_FALSE(p2); + ASSERT_EQ(240, a.BytesRemaining()); + ASSERT_EQ(a.FlBitmask(), 10); + ASSERT_EQ(1024, a.SlBitmask(3)); + ASSERT_EQ(1, a.SlBitmask(1)); + + a.FREE(str); + ASSERT_FALSE(str); + ASSERT_EQ(256, a.BytesRemaining()); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + + // Edge Cases + + void* badPointer = nullptr; + a.FREE(badPointer); + ASSERT_FALSE(badPointer); + + // try to deallocate pointer not in allocator; + + uint64_t* val = new uint64_t; + a.FREE(val); // Should do nothing and be ignored. + free(val); +} + +UTEST(Test_LargeTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) +{ + LargeTlsfAllocator a(256); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); + + void* p0 = a.Allocate(32); + void* p1 = a.Allocate(64); + void* p2 = a.Allocate(32); + void* p3 = a.Allocate(64); + ASSERT_EQ(0, a.TotalBytesRemaining()); + ASSERT_TRUE(a.IsFull()); + + a.FREE(p0); + a.FREE(p2); + + ASSERT_EQ(128, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(34); + ASSERT_FALSE(tooLargeVal); +} + +UTEST(Test_LargeTlsfAllocator, TestRandomAllocationsAndDeallocations) +{ + LargeTlsfAllocator a(4096 * 4096); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(seed); + + std::uniform_int_distribution actionDist(0, 10); + std::uniform_int_distribution sizeDist(1, 4096); + + std::vector pointers; + pointers.reserve(10000); + + for (int i = 0; i < 10000; i++) + { + int action = actionDist(generator); + + if (action < 8) + { + size_t randomSize = sizeDist(generator); + + void* ptr = a.Allocate(randomSize); + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; + pointers.push_back(ptr); + LargeTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); + } + } + else if (action <= 10 && !pointers.empty()) + { + std::uniform_int_distribution indices(0, pointers.size() - 1); + size_t index = indices(generator); + + void*& ptrToFree = pointers[index]; + ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); + LargeTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + + a.FREE(ptrToFree); + LargeTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + + ASSERT_FALSE(ptrToFree); + pointers.erase(pointers.begin() + index); + } + } + + for (void* ptr : pointers) + { + a.FREE(ptr); + } + + ASSERT_EQ(a.TotalSize(), a.TotalBytesRemaining()); +} \ No newline at end of file