From 0767c462cfd4c98ce8a6800d0d7f72f164eccce7 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Tue, 12 Aug 2025 20:41:21 +1000 Subject: [PATCH 01/15] ADDED: base constructor and allocation class and tests --- tests/src/resources/test_ResourceSystem.cpp | 279 ++++++++++++++++++++ 1 file changed, 279 insertions(+) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index df40b2f4..8d7cbb74 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -561,3 +561,282 @@ UTEST_F(test_ResourceSystem, LoadAnimationData) } } } +struct TestStruct +{ + uint64_t inta {0}; + uint64_t intb {0}; +}; + +struct FreeBlock +{ + size_t size {0}; + FreeBlock* prev {nullptr}; + FreeBlock* next {nullptr}; +}; + +struct SmallBlockHeader +{ + uint16_t sizeAndFlags {0}; +}; + +struct SmallBlockFooter +{ + uint16_t sizeAndFlags {0}; +}; + +struct BlockHeader +{ + uint32_t sizeAndFlags {0}; +}; + +struct BlockFooter +{ + uint32_t sizeAndFlags {0}; +}; + +struct LargeBlockHeader +{ + uint64_t sizeAndFlags {0}; +}; + +struct LargeBlockFooter +{ + uint64_t sizeAndFlags {0}; +}; + +#define FL_MIN 4 +#define FL_INDEX(capacity) 63 - __builtin_clzll(capacity) +#define MAX_SL_INDEX 16 + +#define SL_BITS 4 // we get the log(2) the max sl index +#define SL_MASK ((1 << SL_BITS) - 1) + +#define INVALID_INDEX SIZE_T_MAX + +class Allocator +{ +public: + Allocator() {}; + Allocator(const size_t capacity) + : capacity {capacity} + { + data = reinterpret_cast(calloc(sizeof(FreeBlock), capacity)); + + // __builtin_clzll calculates the leading zeroes of a 64 bit integer. To find the furthest + // bit set to 1 we need to minus 63 from the result (since a 64 bit integer has a bit range + // of 0 - 63) + uint64_t rawFl = FL_INDEX(capacity); + uint64_t maxIndices = rawFl - FL_MIN + 1; + + freeList = reinterpret_cast(calloc(maxIndices * MAX_SL_INDEX, sizeof(FreeBlock*))); + slBitmasks = reinterpret_cast(calloc(maxIndices, sizeof(uint16_t))); + + FreeBlock* firstBlock = reinterpret_cast(data); + firstBlock->size = capacity; + + size_t offsetFlIdx = rawFl - FL_MIN; + + size_t sl = SlIndex(capacity, rawFl); + size_t freeListIdx = (offsetFlIdx) * MAX_SL_INDEX + sl; + + freeList[freeListIdx] = firstBlock; + + flBitmask |= 1ULL << (offsetFlIdx); + slBitmasks[offsetFlIdx] |= 1ULL << sl; + } + + ~Allocator() + { + if (data) free(data); + if (freeList) free(freeList); + if (slBitmasks) free(slBitmasks); + + capacity = 0; + data = nullptr; + freeList = nullptr; + slBitmasks = nullptr; + } + + void* Allocate(size_t size) + { + if (size < 16) return nullptr; + + size_t rawFl = FL_INDEX(size); + size_t sl = SlIndex(size, rawFl); + size_t fl = rawFl - FL_MIN; + + size_t freeListIdx = 0; + + freeListIdx = fl * MAX_SL_INDEX + sl; + + size_t freeSlotFl = fl; + size_t freeSlotSl = sl; + + if (!IsFree(fl, sl)) freeListIdx = GetNextFreeSlotIndex(freeSlotFl, freeSlotSl); + if (freeListIdx == INVALID_INDEX) return nullptr; + + FreeBlock* block = freeList[freeListIdx]; + + size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + + // Needs to be split + if (block->size > requiredSize) + { + slBitmasks[freeSlotFl] &= ~(1 << freeSlotSl); + if (!slBitmasks[freeSlotFl]) flBitmask &= ~(1 << freeSlotFl); + + size_t newSize = block->size - requiredSize; + size_t newRawFl = FL_INDEX(newSize); + size_t newFl = newRawFl - FL_MIN; + size_t newSl = SlIndex(newSize, newRawFl); + size_t newFreeListIdx = newFl * MAX_SL_INDEX + newSl; + + FreeBlock* newBlock = reinterpret_cast(reinterpret_cast(block) + requiredSize); + + newBlock->size = newSize; + newBlock->prev = nullptr; + newBlock->next = nullptr; + + freeList[newFreeListIdx] = newBlock; + + flBitmask |= 1ULL << (newFl); + slBitmasks[newFl] |= 1ULL << newSl; + } + + BlockHeader* header = reinterpret_cast(reinterpret_cast(block)); + size_t flags = 0; + flags |= BitMask(0); + + BlockFooter* prev = reinterpret_cast(reinterpret_cast(header) - sizeof(BlockFooter)); + BlockHeader* prevHeader = reinterpret_cast(reinterpret_cast(prev) - prev->sizeAndFlags - sizeof(BlockHeader)); + + if ((unsigned char*)header != data && !Mask(prevHeader->sizeAndFlags,0)) flags |= BitMask(1); + + header->sizeAndFlags = (requiredSize << 3) | flags; + + unsigned char* ptr = reinterpret_cast(reinterpret_cast(header)+sizeof(BlockHeader)); + + BlockFooter* footer = reinterpret_cast(ptr+size); + footer->sizeAndFlags = size; + + return ptr; + } + + size_t GetNextFreeSlotIndex(OUT size_t& fl,OUT size_t& sl) + { + sl = FindLargerSlots(slBitmasks[fl], sl); + if (sl) return fl * MAX_SL_INDEX + __builtin_ctz(sl); + + fl = FindLargerSlots64(flBitmask, fl); + + if (!fl) return INVALID_INDEX; + + 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_INDEX + sl; + } + + bool IsFree(size_t fl, size_t sl) { return Mask(slBitmasks[fl], BitMask(sl)); } + const size_t FindLargerSlots(size_t mask, size_t idx) { return Mask(mask, FlipBits(BitMask(idx+1)-1)); } + const size_t FindLargerSlots64(size_t mask, size_t idx) { return Mask(mask, FlipBits(BitMask64(idx+1)-1)); } + const size_t BitMask(size_t shiftAmount) { return 1 << shiftAmount; } + const size_t BitMask64(size_t shiftAmount) { return 1ULL << shiftAmount; } + const size_t FlipBits(size_t mask) { return ~(mask); } + const size_t Mask(size_t value, size_t mask) { return value & mask; } + + + size_t& Capacity() { return capacity;} + unsigned char* Data() { return data; }; + size_t SlIndex(size_t size, size_t fl) { return (size >> (fl - SL_BITS)) & SL_MASK; } + const uint64_t& FlBitmask() { return flBitmask; } + uint16_t*& SlBitmask() { return slBitmasks; } + FreeBlock** FreeList() { return freeList; } + +private: + size_t capacity {0}; + unsigned char* data {nullptr}; + FreeBlock** freeList {nullptr}; + + // bitmasks + uint64_t flBitmask {0}; + uint16_t* slBitmasks {nullptr}; +}; + +UTEST(test_ResourceSystem, TestEmptyAllocatorCreation) +{ + Allocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) +{ + Allocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + + // 0001 0000 + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(a.SlBitmask()[2], 1); + + FreeBlock* block = a.FreeList()[2 * 16]; + ASSERT_EQ(block->size,64); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); +} + +UTEST(test_ResourceSystem, TestAllocateFunction) +{ + Allocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + + 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(a.FlBitmask(), 2); + ASSERT_EQ(16, a.SlBitmask()[1]); + + FreeBlock* block = a.FreeList()[(1 * 16) + 4]; + ASSERT_EQ(block->size,40); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (BlockHeader*)a.Data(); + auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); + auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); + + ASSERT_EQ(193, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(TestStruct), footer->sizeAndFlags); + + Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); + + *str = "Hello There!"; + ASSERT_STREQ(str->Str(),"Hello There!"); + + ASSERT_EQ(a.FlBitmask(), 1); + ASSERT_EQ(0, a.SlBitmask()[1]); + + FreeBlock* NewBlock = a.FreeList()[0]; + ASSERT_EQ(NewBlock->size,16); + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + + auto newHeader = (BlockHeader*)((unsigned char*)footer + sizeof(BlockFooter)); + auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); + auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); + + ASSERT_EQ(195, newHeader->sizeAndFlags); + ASSERT_EQ(newData, str); + ASSERT_EQ(sizeof(Siege::String), newFooter->sizeAndFlags); +} From 5bf417f6908016140f1e773f3f901adf8d3871e8 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Wed, 13 Aug 2025 20:59:15 +1000 Subject: [PATCH 02/15] fixed incorrect memory layout --- tests/src/resources/test_ResourceSystem.cpp | 343 +++++++++++++++----- 1 file changed, 259 insertions(+), 84 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 8d7cbb74..07418561 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -567,11 +567,10 @@ struct TestStruct uint64_t intb {0}; }; -struct FreeBlock +struct FreeBlockNode { - size_t size {0}; - FreeBlock* prev {nullptr}; - FreeBlock* next {nullptr}; + FreeBlockNode* prev {nullptr}; + FreeBlockNode* next {nullptr}; }; struct SmallBlockHeader @@ -586,12 +585,16 @@ struct SmallBlockFooter struct BlockHeader { + // bitmask where the first three bits are flags: + // 0 = is the block free + // 1 = is the previous block free + // 2 = padding uint32_t sizeAndFlags {0}; }; struct BlockFooter { - uint32_t sizeAndFlags {0}; + uint32_t totalBlockSize {0}; }; struct LargeBlockHeader @@ -607,10 +610,20 @@ struct LargeBlockFooter #define FL_MIN 4 #define FL_INDEX(capacity) 63 - __builtin_clzll(capacity) #define MAX_SL_INDEX 16 +#define MIN_ALLOC_SIZE 16 + +#define TO_BYTES(val) ((unsigned char*)val) +#define TO_HBLOCK(val) ((BlockHeader*)val) +#define TO_FBLOCK(val) ((BlockFooter*)val) +#define TO_FREEBLOCK(val) ((FreeBlockNode*)val) #define SL_BITS 4 // we get the log(2) the max sl index #define SL_MASK ((1 << SL_BITS) - 1) +#define FLAG_BITS 3 +#define IS_FREE_FLAG 1 +#define IS_PREV_FREE_FLAG 2 + #define INVALID_INDEX SIZE_T_MAX class Allocator @@ -620,29 +633,28 @@ class Allocator Allocator(const size_t capacity) : capacity {capacity} { - data = reinterpret_cast(calloc(sizeof(FreeBlock), capacity)); + data = TO_BYTES(calloc(1, sizeof(BlockHeader) + sizeof(BlockFooter) + capacity)); - // __builtin_clzll calculates the leading zeroes of a 64 bit integer. To find the furthest - // bit set to 1 we need to minus 63 from the result (since a 64 bit integer has a bit range - // of 0 - 63) - uint64_t rawFl = FL_INDEX(capacity); - uint64_t maxIndices = rawFl - FL_MIN + 1; + size_t fl = 0, sl = 0, index; - freeList = reinterpret_cast(calloc(maxIndices * MAX_SL_INDEX, sizeof(FreeBlock*))); - slBitmasks = reinterpret_cast(calloc(maxIndices, sizeof(uint16_t))); + CalculateIndices(capacity, fl, sl ,index); - FreeBlock* firstBlock = reinterpret_cast(data); - firstBlock->size = capacity; + uint64_t maxIndices = fl + 1; - size_t offsetFlIdx = rawFl - FL_MIN; + freeList = (FreeBlockNode**)calloc(maxIndices * MAX_SL_INDEX, sizeof(FreeBlockNode*)); + slBitmasks = (uint16_t*)calloc(maxIndices, sizeof(uint16_t)); - size_t sl = SlIndex(capacity, rawFl); - size_t freeListIdx = (offsetFlIdx) * MAX_SL_INDEX + sl; + BlockHeader* firstHeader = (BlockHeader*)data; + firstHeader->sizeAndFlags = (capacity << FLAG_BITS) | IS_FREE_FLAG; - freeList[freeListIdx] = firstBlock; + FreeBlockNode* firstBlock = TO_FREEBLOCK((data+sizeof(BlockHeader))); - flBitmask |= 1ULL << (offsetFlIdx); - slBitmasks[offsetFlIdx] |= 1ULL << sl; + BlockFooter* firstFooter = TO_FBLOCK(TO_BYTES(firstBlock)+capacity); + firstFooter->totalBlockSize = capacity; + freeList[index] = firstBlock; + + flBitmask = SetBitOn64(flBitmask, fl); + slBitmasks[fl] = SetBitOn(slBitmasks[fl], sl); } ~Allocator() @@ -659,69 +671,42 @@ class Allocator void* Allocate(size_t size) { - if (size < 16) return nullptr; - - size_t rawFl = FL_INDEX(size); - size_t sl = SlIndex(size, rawFl); - size_t fl = rawFl - FL_MIN; - - size_t freeListIdx = 0; - - freeListIdx = fl * MAX_SL_INDEX + sl; - - size_t freeSlotFl = fl; - size_t freeSlotSl = sl; - - if (!IsFree(fl, sl)) freeListIdx = GetNextFreeSlotIndex(freeSlotFl, freeSlotSl); - if (freeListIdx == INVALID_INDEX) return nullptr; - - FreeBlock* block = freeList[freeListIdx]; - size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + if (!data || capacity == 0 ||size == 0 || requiredSize < size) return nullptr; + if (requiredSize < MIN_ALLOC_SIZE) return nullptr; - // Needs to be split - if (block->size > requiredSize) - { - slBitmasks[freeSlotFl] &= ~(1 << freeSlotSl); - if (!slBitmasks[freeSlotFl]) flBitmask &= ~(1 << freeSlotFl); - - size_t newSize = block->size - requiredSize; - size_t newRawFl = FL_INDEX(newSize); - size_t newFl = newRawFl - FL_MIN; - size_t newSl = SlIndex(newSize, newRawFl); - size_t newFreeListIdx = newFl * MAX_SL_INDEX + newSl; + size_t freeListIdx = 0, fl = 0, sl = 0; - FreeBlock* newBlock = reinterpret_cast(reinterpret_cast(block) + requiredSize); + FreeBlockNode* block = FindFreeBlock(requiredSize, fl, sl, freeListIdx); + BlockHeader* header = GetHeader(block); - newBlock->size = newSize; - newBlock->prev = nullptr; - newBlock->next = nullptr; + TrySplitBlock(header, block, requiredSize, fl, sl, freeListIdx); - freeList[newFreeListIdx] = newBlock; - - flBitmask |= 1ULL << (newFl); - slBitmasks[newFl] |= 1ULL << newSl; - } + BlockHeader* newHeader = TO_HBLOCK(TO_BYTES(header)); - BlockHeader* header = reinterpret_cast(reinterpret_cast(block)); size_t flags = 0; - flags |= BitMask(0); + TrySetPreviousHeaderFlag(newHeader, flags); - BlockFooter* prev = reinterpret_cast(reinterpret_cast(header) - sizeof(BlockFooter)); - BlockHeader* prevHeader = reinterpret_cast(reinterpret_cast(prev) - prev->sizeAndFlags - sizeof(BlockHeader)); + newHeader->sizeAndFlags = (requiredSize << FLAG_BITS) | flags; - if ((unsigned char*)header != data && !Mask(prevHeader->sizeAndFlags,0)) flags |= BitMask(1); + unsigned char* ptr = TO_BYTES(TO_BYTES(header)+sizeof(BlockHeader)); - header->sizeAndFlags = (requiredSize << 3) | flags; - - unsigned char* ptr = reinterpret_cast(reinterpret_cast(header)+sizeof(BlockHeader)); - - BlockFooter* footer = reinterpret_cast(ptr+size); - footer->sizeAndFlags = size; + BlockFooter* footer = TO_FBLOCK((ptr+size)); + footer->totalBlockSize = requiredSize; return ptr; } +// void Deallocate(void* ptr) +// { +// if (!ptr) return; +// +// BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); +// size_t blockSize = GetHeaderSize(header); +// +// TryCoalesce(header, blockSize); +// } +// size_t GetNextFreeSlotIndex(OUT size_t& fl,OUT size_t& sl) { sl = FindLargerSlots(slBitmasks[fl], sl); @@ -745,21 +730,154 @@ class Allocator const size_t FindLargerSlots64(size_t mask, size_t idx) { return Mask(mask, FlipBits(BitMask64(idx+1)-1)); } const size_t BitMask(size_t shiftAmount) { return 1 << shiftAmount; } const size_t BitMask64(size_t shiftAmount) { return 1ULL << shiftAmount; } + const size_t SetBitOn64(size_t val, size_t bit) { return val | BitMask64(bit);} + const size_t SetBitOn(size_t val, size_t bit) { return val | BitMask(bit);} const size_t FlipBits(size_t mask) { return ~(mask); } const size_t Mask(size_t value, size_t mask) { return value & mask; } +// + const size_t GetHeaderSize(BlockHeader* header) { return header->sizeAndFlags >> FLAG_BITS; } + const size_t PrevBlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_PREV_FREE_FLAG); } + const size_t BlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_FREE_FLAG); } + BlockHeader* GetHeader(FreeBlockNode* node) { return TO_HBLOCK((TO_BYTES(node) - sizeof(BlockHeader))); } +// +// BlockHeader* TryCoalesce(BlockHeader* header, OUT size_t& size) +// { +// BlockHeader* start = header; +// BlockHeader* prev = GetPreviousHeader(header); +// +// if (prev && PrevBlockIsFree(header)) +// { +// start = prev; +// size += GetHeaderSize(prev); +// RemoveFromFreeList(prev); +// } +// +// BlockHeader* next = GetNextHeader(header, GetHeaderSize(header)); +// +// if (next && BlockIsFree(next)) +// { +// size += GetHeaderSize(next); +// RemoveFromFreeList(next); +// } +// +// start->sizeAndFlags = (size << FLAG_BITS) | IS_FREE_FLAG; +// CreateNewBlock(TO_BYTES(start), size); +// } + + BlockHeader* GetPreviousHeader(void* currentHeader) + { + if (IsHead(currentHeader)) return nullptr; + BlockFooter* prev = TO_FBLOCK(TO_BYTES(currentHeader) - sizeof(BlockFooter)); + BlockHeader* prevHeader = TO_HBLOCK(TO_BYTES(currentHeader) - prev->totalBlockSize); + return prevHeader; + } + +// BlockHeader* GetNextHeader(void* currentHeader, size_t offset) +// { +// unsigned char* next = TO_BYTES(currentHeader) + offset; +// if (next >= (data+capacity)) return nullptr; +// if (IsTail(next)) return nullptr; +// return TO_HBLOCK(next); +// } + + void TrySetPreviousHeaderFlag(void* currentHeader, size_t& flags) + { + BlockHeader* prevHeader = GetPreviousHeader(currentHeader); + if (prevHeader && + !Mask(prevHeader->sizeAndFlags,IS_FREE_FLAG)) flags |= IS_PREV_FREE_FLAG; + } + + void TrySplitBlock(BlockHeader* header, FreeBlockNode* block, size_t size, size_t fl, size_t sl, size_t index) + { + size_t blockSize = GetHeaderSize(header); + if (blockSize <= size) return; + RemoveFromFreeList(block, fl, sl, index); + CreateNewBlock(TO_BYTES(header) + size, blockSize - size); + } + + void RemoveFromFreeList(FreeBlockNode* block, size_t fl, size_t sl, size_t index) + { + if (!block) return; + if (block->prev) block->prev->next = block->next; + if (block->next) block->next->prev = block->prev; + + freeList[index] = block->next; + + slBitmasks[fl] = Mask(slBitmasks[fl],FlipBits(BitMask(sl))); + if (!slBitmasks[fl]) flBitmask = Mask(flBitmask,FlipBits(BitMask(fl))); + } + +// void RemoveFromFreeList(BlockHeader* block) +// { +// size_t size = GetHeaderSize(block); +// size_t fl, sl, index; +// +// FreeBlockNode* freeBlock = FindFreeBlock(size, fl, sl, index); +// RemoveFromFreeList(freeBlock, fl, sl, index); +// } + + void CalculateIndices(size_t size, OUT size_t& fl, OUT size_t& sl, OUT size_t& index) + { + GetIndices(size, fl, sl); + index = fl * MAX_SL_INDEX + sl; + } + + void CreateNewBlock(unsigned char* ptr, size_t size) + { + size_t fl = 0,sl = 0, index; + CalculateIndices(size, fl, sl, index); + + size_t sizeFlag = (size << FLAG_BITS); + + BlockHeader* header = TO_HBLOCK(ptr); + header->sizeAndFlags = sizeFlag; + + FreeBlockNode* block = TO_FREEBLOCK((TO_BYTES(header) + sizeof(BlockHeader))); + BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(block)+size)); + firstFooter->totalBlockSize = size; + + block->next = freeList[index]; + block->prev = nullptr; + + if (block->next) block->next->prev = block; + + freeList[index] = block; + + flBitmask = SetBitOn64(flBitmask, fl); + slBitmasks[fl] = SetBitOn(slBitmasks[fl], sl); + } + + FreeBlockNode* FindFreeBlock(size_t size, OUT size_t& fl, OUT size_t& sl, OUT size_t& index) + { + CalculateIndices(size, fl, sl, index); + + if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); + if (index == INVALID_INDEX) return nullptr; + + return freeList[index]; + } + void GetIndices(const size_t size, OUT size_t& fl, OUT size_t& sl) + { + size_t rawFl = FL_INDEX(size); + sl = SlIndex(size, rawFl); + fl = rawFl - FL_MIN; + } + + bool IsHead(void* ptr) { return TO_BYTES(ptr) == data ;} + bool IsTail(void* ptr) { return TO_BYTES(ptr) + sizeof(BlockHeader) >= (data + capacity); } size_t& Capacity() { return capacity;} unsigned char* Data() { return data; }; size_t SlIndex(size_t size, size_t fl) { return (size >> (fl - SL_BITS)) & SL_MASK; } const uint64_t& FlBitmask() { return flBitmask; } uint16_t*& SlBitmask() { return slBitmasks; } - FreeBlock** FreeList() { return freeList; } + FreeBlockNode** FreeList() { return freeList; } private: size_t capacity {0}; unsigned char* data {nullptr}; - FreeBlock** freeList {nullptr}; + FreeBlockNode** freeList {nullptr}; // bitmasks uint64_t flBitmask {0}; @@ -783,10 +901,19 @@ UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) ASSERT_EQ(a.FlBitmask(), 4); ASSERT_EQ(a.SlBitmask()[2], 1); - FreeBlock* block = a.FreeList()[2 * 16]; - ASSERT_EQ(block->size,64); + FreeBlockNode* block = a.FreeList()[2 * 16]; ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); + + BlockHeader* header = TO_HBLOCK((TO_BYTES(block) - sizeof(BlockHeader))); + ASSERT_TRUE(header); + ASSERT_EQ(64, a.GetHeaderSize(header)); + ASSERT_TRUE(a.BlockIsFree(header)); + ASSERT_FALSE(a.PrevBlockIsFree(header)); + + BlockFooter* footer = TO_FBLOCK(TO_BYTES(block) + a.GetHeaderSize(header)); + ASSERT_TRUE(header); + ASSERT_EQ(64, footer->totalBlockSize); } UTEST(test_ResourceSystem, TestAllocateFunction) @@ -806,8 +933,7 @@ UTEST(test_ResourceSystem, TestAllocateFunction) ASSERT_EQ(a.FlBitmask(), 2); ASSERT_EQ(16, a.SlBitmask()[1]); - FreeBlock* block = a.FreeList()[(1 * 16) + 4]; - ASSERT_EQ(block->size,40); + FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); @@ -815,28 +941,77 @@ UTEST(test_ResourceSystem, TestAllocateFunction) auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); - ASSERT_EQ(193, header->sizeAndFlags); + ASSERT_EQ(192, header->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(TestStruct), footer->sizeAndFlags); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); *str = "Hello There!"; ASSERT_STREQ(str->Str(),"Hello There!"); - ASSERT_EQ(a.FlBitmask(), 1); + ASSERT_EQ(1, a.FlBitmask()); ASSERT_EQ(0, a.SlBitmask()[1]); - FreeBlock* NewBlock = a.FreeList()[0]; - ASSERT_EQ(NewBlock->size,16); + FreeBlockNode* NewBlock = a.FreeList()[0]; ASSERT_EQ(NewBlock->next, nullptr); ASSERT_EQ(NewBlock->prev, nullptr); - auto newHeader = (BlockHeader*)((unsigned char*)footer + sizeof(BlockFooter)); + auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - ASSERT_EQ(195, newHeader->sizeAndFlags); + ASSERT_EQ(194, newHeader->sizeAndFlags); ASSERT_EQ(newData, str); - ASSERT_EQ(sizeof(Siege::String), newFooter->sizeAndFlags); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); + + // Edge cases + + // Empty allocator + Allocator 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 + Allocator overflowAlloc(SIZE_T_MAX); + void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); + ASSERT_FALSE(overflowPtr); } +// +//UTEST(test_ResourceSystem, TestDeallocateFunction) +//{ +// Allocator a(64); +// ASSERT_NE(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 64); +// +// 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(a.FlBitmask(), 2); +// ASSERT_EQ(16, a.SlBitmask()[1]); +// +// FreeBlock* block = a.FreeList()[(1 * 16) + 4]; +// ASSERT_EQ(block->size,40); +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// +// auto header = (BlockHeader*)a.Data(); +// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); +// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); +// +// a.Deallocate(p); +//} From 9bf6559cfb0f90f5a3ab2a862d0ff0acba0a9e3f Mon Sep 17 00:00:00 2001 From: Aryeh Date: Wed, 13 Aug 2025 22:31:29 +1000 Subject: [PATCH 03/15] added Deallocate function --- tests/src/resources/test_ResourceSystem.cpp | 248 +++++++++++++------- 1 file changed, 162 insertions(+), 86 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 07418561..9a6fb368 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -697,16 +697,28 @@ class Allocator return ptr; } -// void Deallocate(void* ptr) -// { -// if (!ptr) return; -// -// BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); -// size_t blockSize = GetHeaderSize(header); -// -// TryCoalesce(header, blockSize); -// } -// + template + void Deallocate(T*& ptr) + { + if (!ptr) return; + + BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); + size_t blockSize = GetHeaderSize(header); + + size_t totalBlockSize = blockSize; + BlockHeader* targetHeader = TryCoalesce(header, totalBlockSize); + BlockFooter* footer = TO_FBLOCK((TO_BYTES(targetHeader) + totalBlockSize - sizeof(BlockFooter))); + + targetHeader->sizeAndFlags = (totalBlockSize << FLAG_BITS) | (targetHeader->sizeAndFlags & IS_PREV_FREE_FLAG) | IS_FREE_FLAG; + footer->totalBlockSize = totalBlockSize; + + BlockHeader* nextHeader = GetNextHeader(targetHeader, totalBlockSize); + if (nextHeader) nextHeader->sizeAndFlags |= IS_PREV_FREE_FLAG; + + CreateNewBlock(TO_BYTES(targetHeader), totalBlockSize); + ptr = nullptr; + } + size_t GetNextFreeSlotIndex(OUT size_t& fl,OUT size_t& sl) { sl = FindLargerSlots(slBitmasks[fl], sl); @@ -734,35 +746,34 @@ class Allocator const size_t SetBitOn(size_t val, size_t bit) { return val | BitMask(bit);} const size_t FlipBits(size_t mask) { return ~(mask); } const size_t Mask(size_t value, size_t mask) { return value & mask; } -// + const size_t GetHeaderSize(BlockHeader* header) { return header->sizeAndFlags >> FLAG_BITS; } const size_t PrevBlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_PREV_FREE_FLAG); } const size_t BlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_FREE_FLAG); } BlockHeader* GetHeader(FreeBlockNode* node) { return TO_HBLOCK((TO_BYTES(node) - sizeof(BlockHeader))); } -// -// BlockHeader* TryCoalesce(BlockHeader* header, OUT size_t& size) -// { -// BlockHeader* start = header; -// BlockHeader* prev = GetPreviousHeader(header); -// -// if (prev && PrevBlockIsFree(header)) -// { -// start = prev; -// size += GetHeaderSize(prev); -// RemoveFromFreeList(prev); -// } -// -// BlockHeader* next = GetNextHeader(header, GetHeaderSize(header)); -// -// if (next && BlockIsFree(next)) -// { -// size += GetHeaderSize(next); -// RemoveFromFreeList(next); -// } -// -// start->sizeAndFlags = (size << FLAG_BITS) | IS_FREE_FLAG; -// CreateNewBlock(TO_BYTES(start), size); -// } + + BlockHeader* TryCoalesce(BlockHeader* header, OUT size_t& size) + { + BlockHeader* start = header; + BlockHeader* prev = GetPreviousHeader(header); + + if (prev && BlockIsFree(prev)) + { + start = prev; + size += GetHeaderSize(prev); + RemoveFromFreeList(prev); + } + + BlockHeader* next = GetNextHeader(start, size); + + if (next && BlockIsFree(next)) + { + size += GetHeaderSize(next); + RemoveFromFreeList(next); + } + + return start; + } BlockHeader* GetPreviousHeader(void* currentHeader) { @@ -772,19 +783,19 @@ class Allocator return prevHeader; } -// BlockHeader* GetNextHeader(void* currentHeader, size_t offset) -// { -// unsigned char* next = TO_BYTES(currentHeader) + offset; -// if (next >= (data+capacity)) return nullptr; -// if (IsTail(next)) return nullptr; -// return TO_HBLOCK(next); -// } + BlockHeader* GetNextHeader(void* currentHeader, size_t offset) + { + unsigned char* next = TO_BYTES(currentHeader) + offset; + if (next >= (data+capacity)) return nullptr; + if (IsTail(next)) return nullptr; + return TO_HBLOCK(next); + } void TrySetPreviousHeaderFlag(void* currentHeader, size_t& flags) { BlockHeader* prevHeader = GetPreviousHeader(currentHeader); if (prevHeader && - !Mask(prevHeader->sizeAndFlags,IS_FREE_FLAG)) flags |= IS_PREV_FREE_FLAG; + Mask(prevHeader->sizeAndFlags,IS_FREE_FLAG)) flags |= IS_PREV_FREE_FLAG; } void TrySplitBlock(BlockHeader* header, FreeBlockNode* block, size_t size, size_t fl, size_t sl, size_t index) @@ -807,14 +818,16 @@ class Allocator if (!slBitmasks[fl]) flBitmask = Mask(flBitmask,FlipBits(BitMask(fl))); } -// void RemoveFromFreeList(BlockHeader* block) -// { -// size_t size = GetHeaderSize(block); -// size_t fl, sl, index; -// -// FreeBlockNode* freeBlock = FindFreeBlock(size, fl, sl, index); -// RemoveFromFreeList(freeBlock, fl, sl, index); -// } + void RemoveFromFreeList(BlockHeader* block) + { + size_t size = GetHeaderSize(block); + size_t fl, sl, index; + + CalculateIndices(size, fl, sl, index); + + FreeBlockNode* freeBlock = TO_FREEBLOCK((TO_BYTES(block) + sizeof(BlockHeader))); + RemoveFromFreeList(freeBlock, fl, sl, index); + } void CalculateIndices(size_t size, OUT size_t& fl, OUT size_t& sl, OUT size_t& index) { @@ -830,11 +843,11 @@ class Allocator size_t sizeFlag = (size << FLAG_BITS); BlockHeader* header = TO_HBLOCK(ptr); - header->sizeAndFlags = sizeFlag; + header->sizeAndFlags = (sizeFlag | IS_FREE_FLAG); FreeBlockNode* block = TO_FREEBLOCK((TO_BYTES(header) + sizeof(BlockHeader))); - BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(block)+size)); + BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(header)+(size-sizeof(BlockFooter)))); firstFooter->totalBlockSize = size; block->next = freeList[index]; @@ -982,36 +995,99 @@ UTEST(test_ResourceSystem, TestAllocateFunction) void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); ASSERT_FALSE(overflowPtr); } -// -//UTEST(test_ResourceSystem, TestDeallocateFunction) -//{ -// Allocator a(64); -// ASSERT_NE(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 64); -// -// 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(a.FlBitmask(), 2); -// ASSERT_EQ(16, a.SlBitmask()[1]); -// -// FreeBlock* block = a.FreeList()[(1 * 16) + 4]; -// ASSERT_EQ(block->size,40); -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// -// auto header = (BlockHeader*)a.Data(); -// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); -// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); -// -// a.Deallocate(p); -//} + +UTEST(test_ResourceSystem, TestDeallocateFunction) +{ + Allocator a(64); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 64); + + 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(a.FlBitmask(), 2); + ASSERT_EQ(16, a.SlBitmask()[1]); + + FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (BlockHeader*)a.Data(); + auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); + auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); + + a.Deallocate(p); + + // Should be empty + ASSERT_EQ(513, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 4); + ASSERT_EQ(a.SlBitmask()[2], 1); + + p = (TestStruct*) a.Allocate(sizeof(TestStruct)); + p->inta = 10; + p->intb = 20; + + ASSERT_EQ(10, p->inta); + ASSERT_EQ(20, p->intb); + + ASSERT_EQ(a.FlBitmask(), 2); + ASSERT_EQ(16, a.SlBitmask()[1]); + + block = a.FreeList()[(1 * 16) + 4]; + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + header = (BlockHeader*)a.Data(); + data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); + footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); + + 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]); + + FreeBlockNode* NewBlock = a.FreeList()[0]; + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + + auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); + auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); + auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); + + ASSERT_EQ(194, newHeader->sizeAndFlags); + ASSERT_EQ(newData, str); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); + + a.Deallocate(p); + ASSERT_FALSE(p); + ASSERT_TRUE(newHeader->sizeAndFlags & IS_PREV_FREE_FLAG); + ASSERT_EQ(194, newHeader->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); + FreeBlockNode* newFreeBlock = TO_FREEBLOCK((TO_BYTES(header)+sizeof(BlockHeader))); + ASSERT_EQ(newFreeBlock->next, nullptr); + ASSERT_EQ(newFreeBlock->prev, nullptr); + + a.Deallocate(str); +// ASSERT_FALSE(str); +// ASSERT_EQ(513, header->sizeAndFlags); +// ASSERT_EQ(a.FlBitmask(), 4); +// ASSERT_EQ(a.SlBitmask()[2], 1); +} From 2c4a2f61fd6d4532941d201dd9ef8b9818e5dbe6 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 17 Aug 2025 09:09:21 +1000 Subject: [PATCH 04/15] Added unit tests for Deallocation function --- tests/src/resources/test_ResourceSystem.cpp | 171 ++++++++++++++++++-- 1 file changed, 158 insertions(+), 13 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 9a6fb368..14ccc0b3 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -631,7 +631,7 @@ class Allocator public: Allocator() {}; Allocator(const size_t capacity) - : capacity {capacity} + : capacity {capacity}, bytesRemaining {capacity} { data = TO_BYTES(calloc(1, sizeof(BlockHeader) + sizeof(BlockFooter) + capacity)); @@ -672,7 +672,9 @@ class Allocator void* Allocate(size_t size) { size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); - if (!data || capacity == 0 ||size == 0 || requiredSize < size) return nullptr; + if (!data || capacity == 0 ||size == 0 + || requiredSize < size || requiredSize > bytesRemaining) return nullptr; + if (requiredSize < MIN_ALLOC_SIZE) return nullptr; size_t freeListIdx = 0, fl = 0, sl = 0; @@ -694,6 +696,8 @@ class Allocator BlockFooter* footer = TO_FBLOCK((ptr+size)); footer->totalBlockSize = requiredSize; + bytesRemaining -= requiredSize; + return ptr; } @@ -701,6 +705,7 @@ class Allocator void Deallocate(T*& ptr) { if (!ptr) return; + if ((TO_BYTES(ptr) < data || TO_BYTES(ptr) >= (data + capacity))) return; BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); size_t blockSize = GetHeaderSize(header); @@ -716,6 +721,9 @@ class Allocator if (nextHeader) nextHeader->sizeAndFlags |= IS_PREV_FREE_FLAG; CreateNewBlock(TO_BYTES(targetHeader), totalBlockSize); + + bytesRemaining += blockSize; + ptr = nullptr; } @@ -755,7 +763,7 @@ class Allocator BlockHeader* TryCoalesce(BlockHeader* header, OUT size_t& size) { BlockHeader* start = header; - BlockHeader* prev = GetPreviousHeader(header); + BlockHeader* prev = GetPreviousHeader(start); if (prev && BlockIsFree(prev)) { @@ -775,11 +783,11 @@ class Allocator return start; } - BlockHeader* GetPreviousHeader(void* currentHeader) + BlockHeader* GetPreviousHeader(BlockHeader* currentHeader) { if (IsHead(currentHeader)) return nullptr; - BlockFooter* prev = TO_FBLOCK(TO_BYTES(currentHeader) - sizeof(BlockFooter)); - BlockHeader* prevHeader = TO_HBLOCK(TO_BYTES(currentHeader) - prev->totalBlockSize); + BlockFooter* prev = TO_FBLOCK((TO_BYTES(currentHeader) - sizeof(BlockFooter))); + BlockHeader* prevHeader = TO_HBLOCK((TO_BYTES(currentHeader) - prev->totalBlockSize)); return prevHeader; } @@ -791,7 +799,7 @@ class Allocator return TO_HBLOCK(next); } - void TrySetPreviousHeaderFlag(void* currentHeader, size_t& flags) + void TrySetPreviousHeaderFlag(BlockHeader* currentHeader, size_t& flags) { BlockHeader* prevHeader = GetPreviousHeader(currentHeader); if (prevHeader && @@ -842,6 +850,8 @@ class Allocator size_t sizeFlag = (size << FLAG_BITS); + // 0000 0000 0000 0000 + BlockHeader* header = TO_HBLOCK(ptr); header->sizeAndFlags = (sizeFlag | IS_FREE_FLAG); @@ -886,9 +896,11 @@ class Allocator const uint64_t& FlBitmask() { return flBitmask; } uint16_t*& SlBitmask() { return slBitmasks; } FreeBlockNode** FreeList() { return freeList; } + const size_t BytesRemaining() { return bytesRemaining; } private: size_t capacity {0}; + size_t bytesRemaining {0}; unsigned char* data {nullptr}; FreeBlockNode** freeList {nullptr}; @@ -909,6 +921,7 @@ UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) Allocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); // 0001 0000 ASSERT_EQ(a.FlBitmask(), 4); @@ -934,6 +947,7 @@ UTEST(test_ResourceSystem, TestAllocateFunction) Allocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -957,6 +971,7 @@ UTEST(test_ResourceSystem, TestAllocateFunction) ASSERT_EQ(192, header->sizeAndFlags); ASSERT_EQ(data, p); ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); + ASSERT_EQ(40, a.BytesRemaining()); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); @@ -974,9 +989,10 @@ UTEST(test_ResourceSystem, TestAllocateFunction) auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - ASSERT_EQ(194, newHeader->sizeAndFlags); + ASSERT_EQ(192, newHeader->sizeAndFlags); ASSERT_EQ(newData, str); ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); + ASSERT_EQ(16, a.BytesRemaining()); // Edge cases @@ -994,6 +1010,13 @@ UTEST(test_ResourceSystem, TestAllocateFunction) Allocator overflowAlloc(SIZE_T_MAX); void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); ASSERT_FALSE(overflowPtr); + + void* tooLargeCase = a.Allocate(100); + ASSERT_FALSE(tooLargeCase); + + Allocator tooSmallAlloc(64); + void* tooLargeAlloc = a.Allocate(100); + ASSERT_FALSE(tooLargeCase); } UTEST(test_ResourceSystem, TestDeallocateFunction) @@ -1001,6 +1024,7 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) Allocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(64, a.BytesRemaining()); TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -1016,6 +1040,7 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(40, a.BytesRemaining()); auto header = (BlockHeader*)a.Data(); auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); @@ -1033,6 +1058,7 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) ASSERT_FALSE(p); ASSERT_EQ(a.FlBitmask(), 4); ASSERT_EQ(a.SlBitmask()[2], 1); + ASSERT_EQ(64, a.BytesRemaining()); p = (TestStruct*) a.Allocate(sizeof(TestStruct)); p->inta = 10; @@ -1047,6 +1073,7 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) block = a.FreeList()[(1 * 16) + 4]; ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(40, a.BytesRemaining()); header = (BlockHeader*)a.Data(); data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); @@ -1067,12 +1094,13 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) FreeBlockNode* NewBlock = a.FreeList()[0]; ASSERT_EQ(NewBlock->next, nullptr); ASSERT_EQ(NewBlock->prev, nullptr); + ASSERT_EQ(16, a.BytesRemaining()); auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - ASSERT_EQ(194, newHeader->sizeAndFlags); + ASSERT_EQ(192, newHeader->sizeAndFlags); ASSERT_EQ(newData, str); ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); @@ -1084,10 +1112,127 @@ UTEST(test_ResourceSystem, TestDeallocateFunction) FreeBlockNode* newFreeBlock = TO_FREEBLOCK((TO_BYTES(header)+sizeof(BlockHeader))); ASSERT_EQ(newFreeBlock->next, nullptr); ASSERT_EQ(newFreeBlock->prev, nullptr); + ASSERT_EQ(40, a.BytesRemaining()); a.Deallocate(str); -// ASSERT_FALSE(str); -// ASSERT_EQ(513, header->sizeAndFlags); -// ASSERT_EQ(a.FlBitmask(), 4); -// ASSERT_EQ(a.SlBitmask()[2], 1); + ASSERT_FALSE(str); + ASSERT_EQ(513, header->sizeAndFlags); + ASSERT_EQ(4, a.FlBitmask()); + ASSERT_EQ(1, a.SlBitmask()[2]); + ASSERT_EQ(64, a.BytesRemaining()); +} + +UTEST(test_ResourceSystem, TestBlockCoalescing) +{ + Allocator a(128); + ASSERT_NE(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 128); + ASSERT_EQ(128, a.BytesRemaining()); + + 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(0, a.SlBitmask()[1]); + + FreeBlockNode* block = a.FreeList()[42]; + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(104, a.BytesRemaining()); + + auto header = (BlockHeader*)a.Data(); + auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); + auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(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(0, a.SlBitmask()[1]); + + FreeBlockNode* NewBlock = a.FreeList()[36]; + ASSERT_EQ(NewBlock->next, nullptr); + ASSERT_EQ(NewBlock->prev, nullptr); + ASSERT_EQ(80, a.BytesRemaining()); + + auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); + auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); + auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); + + ASSERT_EQ(192, newHeader->sizeAndFlags); + ASSERT_EQ(newData, str); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->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(a.FlBitmask(), 2); + ASSERT_EQ(4096, a.SlBitmask()[1]); + + FreeBlockNode* newFreeBlock = a.FreeList()[28]; + ASSERT_EQ(newFreeBlock->next, nullptr); + ASSERT_EQ(newFreeBlock->prev, nullptr); + ASSERT_EQ(56, a.BytesRemaining()); + + auto newHeader2 = (BlockHeader*)(TO_BYTES(newFooter) + sizeof(BlockFooter)); + auto newData2 = (TestStruct*) (((unsigned char*)newHeader2) + sizeof(BlockHeader)); + auto newFooter2 = (BlockFooter*) (((unsigned char*)newData2) + sizeof(TestStruct)); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); + + a.Deallocate(p); + + FreeBlockNode* firstFree = a.FreeList()[8]; + ASSERT_EQ(firstFree->next, nullptr); + ASSERT_EQ(firstFree->prev, nullptr); + ASSERT_EQ(80, a.BytesRemaining()); + + BlockHeader* firstFreeHeader = TO_HBLOCK((TO_BYTES(firstFree) - sizeof(BlockHeader))); + ASSERT_TRUE(firstFreeHeader->sizeAndFlags & IS_FREE_FLAG); + ASSERT_FALSE(firstFreeHeader->sizeAndFlags & IS_PREV_FREE_FLAG); + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 3); + ASSERT_EQ(256, a.SlBitmask()[0]); + + a.Deallocate(p2); + ASSERT_FALSE(p2); + ASSERT_EQ(104, a.BytesRemaining()); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(256, a.SlBitmask()[0]); + ASSERT_EQ(16, a.SlBitmask()[2]); + + a.Deallocate(str); + ASSERT_FALSE(str); + ASSERT_EQ(128, a.BytesRemaining()); + ASSERT_EQ(a.FlBitmask(), 8); + ASSERT_EQ(a.SlBitmask()[3], 1); + + // Edge Cases + + void* badPointer = nullptr; + a.Deallocate(badPointer); + ASSERT_FALSE(badPointer); + + // try to deallocate pointer not in allocator; + uint64_t* val = new uint64_t; + + a.Deallocate(val); } From 53c9e8b1a3a2e20a42baebca49d0164f790d878d Mon Sep 17 00:00:00 2001 From: Aryeh Date: Mon, 18 Aug 2025 07:49:53 +1000 Subject: [PATCH 05/15] Fixed unit test for allocating a fragment with no available slot --- tests/src/resources/test_ResourceSystem.cpp | 32 ++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 14ccc0b3..e8f4f066 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -680,6 +680,9 @@ class Allocator size_t freeListIdx = 0, fl = 0, sl = 0; FreeBlockNode* block = FindFreeBlock(requiredSize, fl, sl, freeListIdx); + + if (freeListIdx == INVALID_INDEX) return nullptr; + BlockHeader* header = GetHeader(block); TrySplitBlock(header, block, requiredSize, fl, sl, freeListIdx); @@ -809,8 +812,9 @@ class Allocator void TrySplitBlock(BlockHeader* header, FreeBlockNode* block, size_t size, size_t fl, size_t sl, size_t index) { size_t blockSize = GetHeaderSize(header); - if (blockSize <= size) return; RemoveFromFreeList(block, fl, sl, index); + if (blockSize <= size) return; + CreateNewBlock(TO_BYTES(header) + size, blockSize - size); } @@ -850,8 +854,6 @@ class Allocator size_t sizeFlag = (size << FLAG_BITS); - // 0000 0000 0000 0000 - BlockHeader* header = TO_HBLOCK(ptr); header->sizeAndFlags = (sizeFlag | IS_FREE_FLAG); @@ -1232,7 +1234,29 @@ UTEST(test_ResourceSystem, TestBlockCoalescing) ASSERT_FALSE(badPointer); // try to deallocate pointer not in allocator; + uint64_t* val = new uint64_t; + a.Deallocate(val); // Should do nothing and be ignored. + free(val); +} + +UTEST(test_ResourceSystem, TestAllocationWhenNoAppropriateFragmentExists) +{ + Allocator 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.BytesRemaining()); + + a.Deallocate(p0); + a.Deallocate(p2); - a.Deallocate(val); + ASSERT_EQ(48, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(24); + ASSERT_FALSE(tooLargeVal); } From 50c5f367cb069a767de1d51c512075b3659a5812 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Tue, 19 Aug 2025 22:43:53 +1000 Subject: [PATCH 06/15] Added randomised allocation/deallocation test --- tests/src/resources/test_ResourceSystem.cpp | 78 ++++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index e8f4f066..0de68fca 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -16,6 +16,7 @@ #include #include #include +#include using namespace Siege; @@ -683,7 +684,9 @@ class Allocator if (freeListIdx == INVALID_INDEX) return nullptr; - BlockHeader* header = GetHeader(block); + BlockHeader* header = reinterpret_cast(reinterpret_cast(block) - sizeof(BlockHeader)); + + TrySplitBlock(header, block, requiredSize, fl, sl, freeListIdx); @@ -694,7 +697,7 @@ class Allocator newHeader->sizeAndFlags = (requiredSize << FLAG_BITS) | flags; - unsigned char* ptr = TO_BYTES(TO_BYTES(header)+sizeof(BlockHeader)); + unsigned char* ptr = TO_BYTES(header)+sizeof(BlockHeader); BlockFooter* footer = TO_FBLOCK((ptr+size)); footer->totalBlockSize = requiredSize; @@ -732,8 +735,10 @@ class Allocator size_t GetNextFreeSlotIndex(OUT size_t& fl,OUT size_t& sl) { - sl = FindLargerSlots(slBitmasks[fl], sl); - if (sl) return fl * MAX_SL_INDEX + __builtin_ctz(sl); + sl = __builtin_ctz(FindLargerSlots(slBitmasks[fl], sl)); + + if (sl == 32) sl = 0; + if (sl) return fl * MAX_SL_INDEX + sl; fl = FindLargerSlots64(flBitmask, fl); @@ -813,7 +818,7 @@ class Allocator { size_t blockSize = GetHeaderSize(header); RemoveFromFreeList(block, fl, sl, index); - if (blockSize <= size) return; + if (blockSize <= size || ((blockSize - size) < (sizeof(BlockHeader) + sizeof(BlockFooter)))) return; CreateNewBlock(TO_BYTES(header) + size, blockSize - size); } @@ -821,10 +826,11 @@ class Allocator void RemoveFromFreeList(FreeBlockNode* block, size_t fl, size_t sl, size_t index) { if (!block) return; - if (block->prev) block->prev->next = block->next; - if (block->next) block->next->prev = block->prev; - freeList[index] = block->next; + if (!block->prev) freeList[index] = block->next; + else block->prev->next = block->next; + + if (block->next) block->next->prev = block->prev; slBitmasks[fl] = Mask(slBitmasks[fl],FlipBits(BitMask(sl))); if (!slBitmasks[fl]) flBitmask = Mask(flBitmask,FlipBits(BitMask(fl))); @@ -857,7 +863,9 @@ class Allocator BlockHeader* header = TO_HBLOCK(ptr); header->sizeAndFlags = (sizeFlag | IS_FREE_FLAG); - FreeBlockNode* block = TO_FREEBLOCK((TO_BYTES(header) + sizeof(BlockHeader))); + FreeBlockNode* block = reinterpret_cast(reinterpret_cast(header) + sizeof(BlockHeader)); + +// FreeBlockNode* block = TO_FREEBLOCK((TO_BYTES(header) + sizeof(BlockHeader))); BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(header)+(size-sizeof(BlockFooter)))); firstFooter->totalBlockSize = size; @@ -865,7 +873,7 @@ class Allocator block->next = freeList[index]; block->prev = nullptr; - if (block->next) block->next->prev = block; + if (IsValid(block->next)) block->next->prev = block; freeList[index] = block; @@ -892,6 +900,7 @@ class Allocator bool IsHead(void* ptr) { return TO_BYTES(ptr) == data ;} bool IsTail(void* ptr) { return TO_BYTES(ptr) + sizeof(BlockHeader) >= (data + capacity); } + bool IsValid(FreeBlockNode* ptr) { return ptr != nullptr; } size_t& Capacity() { return capacity;} unsigned char* Data() { return data; }; size_t SlIndex(size_t size, size_t fl) { return (size >> (fl - SL_BITS)) & SL_MASK; } @@ -1260,3 +1269,52 @@ UTEST(test_ResourceSystem, TestAllocationWhenNoAppropriateFragmentExists) void* tooLargeVal = a.Allocate(24); ASSERT_FALSE(tooLargeVal); } + +UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) +{ + Allocator a(1024 * 1024); + + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + //std::default_random_engine generator(seed); + std::default_random_engine generator(12345); + + std::uniform_int_distribution actionDist(0, 10); + std::uniform_int_distribution sizeDist(1, 256); + + std::vector pointers; + + 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); + } + } + 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(*(uint32_t*)ptrToFree, 0xDEADC0DE); + + a.Deallocate(ptrToFree); + ASSERT_FALSE(ptrToFree); + pointers.erase(pointers.begin() + index); + } + } + + for (void* ptr : pointers) + { + a.Deallocate(ptr); + } + + ASSERT_EQ(a.Capacity(), a.BytesRemaining()); +} \ No newline at end of file From 7ade7938791d198fad10524ef5e55c8421077a00 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Wed, 20 Aug 2025 18:18:20 +1000 Subject: [PATCH 07/15] Added TlsfAllocator class and unit tests --- engine/utils/TlsfAllocator.cpp | 283 +++++++ engine/utils/TlsfAllocator.h | 107 +++ tests/src/resources/test_ResourceSystem.cpp | 813 ++++++++++---------- tests/src/utils/test_TlsfAllocator.cpp | 139 ++++ 4 files changed, 935 insertions(+), 407 deletions(-) create mode 100644 engine/utils/TlsfAllocator.cpp create mode 100644 engine/utils/TlsfAllocator.h create mode 100644 tests/src/utils/test_TlsfAllocator.cpp diff --git a/engine/utils/TlsfAllocator.cpp b/engine/utils/TlsfAllocator.cpp new file mode 100644 index 00000000..297bcd17 --- /dev/null +++ b/engine/utils/TlsfAllocator.cpp @@ -0,0 +1,283 @@ +// +// 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 "TlsfAllocator.h" +#include "Logging.h" + +#include + +#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 PAD_SIZE(size) size + HEADER_SIZE + FOOTER_SIZE + +#define FL_MIN_INDEX 4 +#define MAX_SL_BUCKETS 16 +#define FL(size) 63 - __builtin_clzll(size) +#define SL(size, fl) (size >> fl) & ((1 << FL_MIN_INDEX) - 1); +#define FLAG_OFFSET 3 +#define MIN_ALLOCATION 16 +#define INVALID_INDEX UINT64_MAX + +namespace Siege +{ + +TlsfAllocator::TlsfAllocator() {} + +TlsfAllocator::TlsfAllocator(const uint64_t size) + : capacity {size}, bytesRemaining {size} +{ + if (size == 0 || size < MIN_ALLOCATION || size >= UINT64_MAX) + { + capacity = 0; + bytesRemaining = 0; + return; + } + + uint64_t maxBuckets = (FL(size) - FL_MIN_INDEX) + 1; + + uint64_t totalSize = PAD_SIZE(size); + uint64_t slBucketSize = sizeof(uint16_t) * maxBuckets; + uint64_t freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); + + uint64_t allocSize = totalSize + slBucketSize + freeListSize; + + data = TO_BYTES(calloc(1, allocSize)); + + CC_ASSERT(data, "Allocation returned null. This should not happen and implies an implementation failure.") + + freeList = (FreeBlockNode**)(data + totalSize); + slBitmasks = (uint16_t*)(data + totalSize + freeListSize); + + BlockHeader* firstHeader = CreateHeader(data, totalSize, FREE); + + AddNewBlock(totalSize, firstHeader); +} + +TlsfAllocator::BlockHeader* TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, + HeaderFlags flags) +{ + if (!ptr) return nullptr; + BlockHeader* header = TO_HEADER(ptr); + header->sizeAndFlags = (size << FLAG_OFFSET) | flags; + return header; +} + +void* TlsfAllocator::Allocate(const uint64_t& size) +{ + size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + if (!data || capacity == 0 ||size == 0 || requiredSize < size || requiredSize > bytesRemaining + || requiredSize < MIN_ALLOCATION) return nullptr; + + FreeBlockNode* block = FindFreeBlock(requiredSize); + + if (!block) return nullptr; + + BlockHeader* header = TrySplitBlock(block, requiredSize); + header = CreateHeader(TO_BYTES(header), requiredSize, FULL); + BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(header)) , requiredSize); + + uint8_t* ptr = GetBlockData(header); + bytesRemaining -= requiredSize; + return ptr; +} + +TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, uint64_t allocatedSize) +{ + if (!node) return nullptr; + + uint64_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))) return header; + + AddNewBlock(oldSize - allocatedSize, TO_HEADER(TO_BYTES(header) + allocatedSize)); + + return header; +} + +void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) +{ + uint64_t fl = 0,sl = 0, index; + index = CalculateFreeBlockIndices(size, fl, sl); + + BlockHeader* newHeader = CreateHeader(TO_BYTES(header), size, FREE); + FreeBlockNode* node = CreateFreeBlock(TO_BYTES(GetFreeBlock(newHeader)), nullptr, freeList[index]); + BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(newHeader)), size); + + if (node->next) node->next->prev = node; + + freeList[index] = node; + flBitmask |= (1ULL << fl); + slBitmasks[fl] |= (1 << sl); +} + +bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) +{ + BlockHeader* header = GetHeader(node); + + if (!header) return false; + + uint64_t oldSize = GetHeaderSize(header); + + uint64_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; +} + +TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size) +{ + uint64_t fl, sl, index; + index = CalculateFreeBlockIndices(size, fl, sl); + + if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); + if (index == INVALID_INDEX) return nullptr; + + return freeList[index]; +} + +const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_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 << (sl + 1)) - 1)); + + if (!fl) return INVALID_INDEX; + + 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; +} + +uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl) +{ + uint64_t rawFl = FL(size); + fl = rawFl - FL_MIN_INDEX; + sl = SL(size, fl); + return fl * MAX_SL_BUCKETS + sl; +} + +TlsfAllocator::BlockFooter* TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size) +{ + if (!ptr) return nullptr; + BlockFooter* footer = TO_FOOTER(ptr); + footer->totalBlockSize = size; + return footer; +} + +TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node) +{ + if (!node) return nullptr; + return TO_HEADER(TO_BYTES(node) - HEADER_SIZE); +} + +TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header) +{ + if (!header || TO_BYTES(header) == data) return nullptr; + return TO_HEADER(TO_BYTES(header) - GetPrevFooter(header)->totalBlockSize); +} + +TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header) +{ + if (!header) return nullptr; + return TO_HEADER(TO_BYTES(header) + GetHeaderSize(header)); +} + +TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, + TlsfAllocator::FreeBlockNode* prev, + TlsfAllocator::FreeBlockNode* next) +{ + if (!ptr) return nullptr; + FreeBlockNode* node = TO_FREE_BLOCK(ptr); + node->prev = prev; + node->next = next; + return node; +} + +TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) +{ + if (!IsFree(header)) return nullptr; + return TO_FREE_BLOCK(TO_BYTES(header) + HEADER_SIZE); +} + +TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl) +{ + return freeList[fl * MAX_SL_BUCKETS + sl]; +} + +TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) +{ + uint64_t size = GetHeaderSize(header); + return TO_FOOTER(TO_BYTES(header) + (size - FOOTER_SIZE)); +} + +TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header) +{ + uint8_t* raw = TO_BYTES(header); + if (raw == data) return nullptr; + return TO_FOOTER(raw - FOOTER_SIZE); +} + +uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) +{ + return TO_BYTES(header) + HEADER_SIZE; +} + +const uint64_t TlsfAllocator::GetHeaderSize(BlockHeader* header) +{ + return header->sizeAndFlags >> FLAG_OFFSET; +} + +bool TlsfAllocator::IsFree(BlockHeader* header) +{ + return header->sizeAndFlags & FREE; +} + +bool TlsfAllocator::IsFree(uint64_t fl, uint64_t sl) +{ + return slBitmasks[fl] & (1 << sl); +} + +bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) +{ + uint8_t* raw = TO_BYTES(header); + if (raw == data) return false; + return header && IsFree(TO_HEADER(raw - GetPrevFooter(header)->totalBlockSize)); +} + +} // namespace Siege \ No newline at end of file diff --git a/engine/utils/TlsfAllocator.h b/engine/utils/TlsfAllocator.h new file mode 100644 index 00000000..9b77650b --- /dev/null +++ b/engine/utils/TlsfAllocator.h @@ -0,0 +1,107 @@ +// +// 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_TLSFALLOCATOR_H +#define SIEGE_ENGINE_TLSFALLOCATOR_H + +#include +#include "Macros.h" + +namespace Siege +{ + +class TlsfAllocator +{ +public: + enum HeaderFlags + { + FULL = 0, + FREE = 1, + PREV_IS_FREE = 2 + }; + + struct FreeBlockNode + { + FreeBlockNode* next {nullptr}; + FreeBlockNode* prev {nullptr}; + }; + + struct BlockHeader { + uint32_t sizeAndFlags {0}; + }; + + struct BlockFooter + { + uint32_t totalBlockSize {0}; + }; + + // S'tructors + + TlsfAllocator(); + TlsfAllocator(const uint64_t size); + + // Deleted copy and move constructors + + TlsfAllocator(const TlsfAllocator& other) = delete; + TlsfAllocator(const TlsfAllocator&& other) = delete; + + // Other functions + + BlockHeader* CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags); + BlockFooter* CreateFooter(uint8_t* ptr, const uint64_t size); + FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next); + FreeBlockNode* FindFreeBlock(const uint64_t& size); + + const uint64_t GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl); + bool IsFree(BlockHeader* header); + bool IsFree(uint64_t fl, uint64_t sl); + bool PrevBlockIsFree(BlockHeader* header); + uint64_t CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl); + + // Buffer manipulation Functions + + FreeBlockNode* GetFreeBlock(BlockHeader* header); + FreeBlockNode* GetFreeBlock(const uint64_t fl, const uint64_t sl); + uint8_t* GetBlockData(BlockHeader* header); + BlockFooter* GetFooter(BlockHeader* header); + BlockFooter* GetPrevFooter(BlockHeader* header); + BlockHeader* GetHeader(FreeBlockNode* node); + BlockHeader* GetPrevHeader(BlockHeader* header); + BlockHeader* GetNextHeader(BlockHeader* header); + + // Allocate/Deallocate + + void* Allocate(const uint64_t& size); + BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t allocatedSize); + bool RemoveFreeBlock(FreeBlockNode* node); + void AddNewBlock(const uint64_t size, BlockHeader* currentNode); + + // Getters + + const uint64_t GetHeaderSize(BlockHeader* header); + const uint64_t Capacity() { return capacity; } + const uint64_t BytesRemaining() { return bytesRemaining; } + const uint8_t* Data() { return data; } + const uint64_t FlBitmask() { return flBitmask; } + const uint16_t& SlBitmask(const uint64_t fl) { return slBitmasks[fl]; } +private: + uint64_t capacity {0}; + uint64_t bytesRemaining {0}; + + uint8_t* data {nullptr}; + FreeBlockNode** freeList {nullptr}; + + // bitmasks + uint64_t flBitmask {0}; + uint16_t* slBitmasks {nullptr}; +}; + +} // namespace Siege + +#endif // SIEGE_ENGINE_TLSFALLOCATOR_H diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 0de68fca..3856e368 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -586,10 +586,6 @@ struct SmallBlockFooter struct BlockHeader { - // bitmask where the first three bits are flags: - // 0 = is the block free - // 1 = is the previous block free - // 2 = padding uint32_t sizeAndFlags {0}; }; @@ -618,7 +614,7 @@ struct LargeBlockFooter #define TO_FBLOCK(val) ((BlockFooter*)val) #define TO_FREEBLOCK(val) ((FreeBlockNode*)val) -#define SL_BITS 4 // we get the log(2) the max sl index +#define SL_BITS 4 #define SL_MASK ((1 << SL_BITS) - 1) #define FLAG_BITS 3 @@ -686,8 +682,6 @@ class Allocator BlockHeader* header = reinterpret_cast(reinterpret_cast(block) - sizeof(BlockHeader)); - - TrySplitBlock(header, block, requiredSize, fl, sl, freeListIdx); BlockHeader* newHeader = TO_HBLOCK(TO_BYTES(header)); @@ -714,6 +708,9 @@ class Allocator if ((TO_BYTES(ptr) < data || TO_BYTES(ptr) >= (data + capacity))) return; BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); + + if (BlockIsFree(header)) return; + size_t blockSize = GetHeaderSize(header); size_t totalBlockSize = blockSize; @@ -832,6 +829,9 @@ class Allocator if (block->next) block->next->prev = block->prev; + block->next = nullptr; + block->prev = nullptr; + slBitmasks[fl] = Mask(slBitmasks[fl],FlipBits(BitMask(sl))); if (!slBitmasks[fl]) flBitmask = Mask(flBitmask,FlipBits(BitMask(fl))); } @@ -865,8 +865,6 @@ class Allocator FreeBlockNode* block = reinterpret_cast(reinterpret_cast(header) + sizeof(BlockHeader)); -// FreeBlockNode* block = TO_FREEBLOCK((TO_BYTES(header) + sizeof(BlockHeader))); - BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(header)+(size-sizeof(BlockFooter)))); firstFooter->totalBlockSize = size; @@ -920,401 +918,402 @@ class Allocator uint16_t* slBitmasks {nullptr}; }; -UTEST(test_ResourceSystem, TestEmptyAllocatorCreation) -{ - Allocator a; - ASSERT_EQ(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 0); -} - -UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) -{ - Allocator a(64); - ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); - ASSERT_EQ(64, a.BytesRemaining()); - - // 0001 0000 - ASSERT_EQ(a.FlBitmask(), 4); - ASSERT_EQ(a.SlBitmask()[2], 1); - - FreeBlockNode* block = a.FreeList()[2 * 16]; - ASSERT_EQ(block->next, nullptr); - ASSERT_EQ(block->prev, nullptr); - - BlockHeader* header = TO_HBLOCK((TO_BYTES(block) - sizeof(BlockHeader))); - ASSERT_TRUE(header); - ASSERT_EQ(64, a.GetHeaderSize(header)); - ASSERT_TRUE(a.BlockIsFree(header)); - ASSERT_FALSE(a.PrevBlockIsFree(header)); - - BlockFooter* footer = TO_FBLOCK(TO_BYTES(block) + a.GetHeaderSize(header)); - ASSERT_TRUE(header); - ASSERT_EQ(64, footer->totalBlockSize); -} - -UTEST(test_ResourceSystem, TestAllocateFunction) -{ - Allocator a(64); - ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); - ASSERT_EQ(64, a.BytesRemaining()); - - 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(a.FlBitmask(), 2); - ASSERT_EQ(16, a.SlBitmask()[1]); - - FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; - ASSERT_EQ(block->next, nullptr); - ASSERT_EQ(block->prev, nullptr); - - auto header = (BlockHeader*)a.Data(); - auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); - auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); - - ASSERT_EQ(192, header->sizeAndFlags); - ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); - ASSERT_EQ(40, 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]); - - FreeBlockNode* NewBlock = a.FreeList()[0]; - ASSERT_EQ(NewBlock->next, nullptr); - ASSERT_EQ(NewBlock->prev, nullptr); - - auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); - auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); - auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - - ASSERT_EQ(192, newHeader->sizeAndFlags); - ASSERT_EQ(newData, str); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); - ASSERT_EQ(16, a.BytesRemaining()); - - // Edge cases - - // Empty allocator - Allocator 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 - Allocator overflowAlloc(SIZE_T_MAX); - void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); - ASSERT_FALSE(overflowPtr); - - void* tooLargeCase = a.Allocate(100); - ASSERT_FALSE(tooLargeCase); - - Allocator tooSmallAlloc(64); - void* tooLargeAlloc = a.Allocate(100); - ASSERT_FALSE(tooLargeCase); -} - -UTEST(test_ResourceSystem, TestDeallocateFunction) -{ - Allocator a(64); - ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); - ASSERT_EQ(64, a.BytesRemaining()); - - 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(a.FlBitmask(), 2); - ASSERT_EQ(16, a.SlBitmask()[1]); - - FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; - ASSERT_EQ(block->next, nullptr); - ASSERT_EQ(block->prev, nullptr); - ASSERT_EQ(40, a.BytesRemaining()); - - auto header = (BlockHeader*)a.Data(); - auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); - auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); - - ASSERT_EQ(192, header->sizeAndFlags); - ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); - - a.Deallocate(p); - - // Should be empty - ASSERT_EQ(513, header->sizeAndFlags); - ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); - ASSERT_FALSE(p); - ASSERT_EQ(a.FlBitmask(), 4); - ASSERT_EQ(a.SlBitmask()[2], 1); - 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(a.FlBitmask(), 2); - ASSERT_EQ(16, a.SlBitmask()[1]); - - block = a.FreeList()[(1 * 16) + 4]; - ASSERT_EQ(block->next, nullptr); - ASSERT_EQ(block->prev, nullptr); - ASSERT_EQ(40, a.BytesRemaining()); - - header = (BlockHeader*)a.Data(); - data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); - footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); - - ASSERT_EQ(192, header->sizeAndFlags); - ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); - - 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]); - - FreeBlockNode* NewBlock = a.FreeList()[0]; - ASSERT_EQ(NewBlock->next, nullptr); - ASSERT_EQ(NewBlock->prev, nullptr); - ASSERT_EQ(16, a.BytesRemaining()); - - auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); - auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); - auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - - ASSERT_EQ(192, newHeader->sizeAndFlags); - ASSERT_EQ(newData, str); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); - - a.Deallocate(p); - ASSERT_FALSE(p); - ASSERT_TRUE(newHeader->sizeAndFlags & IS_PREV_FREE_FLAG); - ASSERT_EQ(194, newHeader->sizeAndFlags); - ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); - FreeBlockNode* newFreeBlock = TO_FREEBLOCK((TO_BYTES(header)+sizeof(BlockHeader))); - ASSERT_EQ(newFreeBlock->next, nullptr); - ASSERT_EQ(newFreeBlock->prev, nullptr); - ASSERT_EQ(40, a.BytesRemaining()); - - a.Deallocate(str); - ASSERT_FALSE(str); - ASSERT_EQ(513, header->sizeAndFlags); - ASSERT_EQ(4, a.FlBitmask()); - ASSERT_EQ(1, a.SlBitmask()[2]); - ASSERT_EQ(64, a.BytesRemaining()); -} - -UTEST(test_ResourceSystem, TestBlockCoalescing) -{ - Allocator a(128); - ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 128); - ASSERT_EQ(128, a.BytesRemaining()); - - 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(0, a.SlBitmask()[1]); - - FreeBlockNode* block = a.FreeList()[42]; - ASSERT_EQ(block->next, nullptr); - ASSERT_EQ(block->prev, nullptr); - ASSERT_EQ(104, a.BytesRemaining()); - - auto header = (BlockHeader*)a.Data(); - auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); - auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); - - ASSERT_EQ(192, header->sizeAndFlags); - ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(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(0, a.SlBitmask()[1]); - - FreeBlockNode* NewBlock = a.FreeList()[36]; - ASSERT_EQ(NewBlock->next, nullptr); - ASSERT_EQ(NewBlock->prev, nullptr); - ASSERT_EQ(80, a.BytesRemaining()); - - auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); - auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); - auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); - - ASSERT_EQ(192, newHeader->sizeAndFlags); - ASSERT_EQ(newData, str); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->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(a.FlBitmask(), 2); - ASSERT_EQ(4096, a.SlBitmask()[1]); - - FreeBlockNode* newFreeBlock = a.FreeList()[28]; - ASSERT_EQ(newFreeBlock->next, nullptr); - ASSERT_EQ(newFreeBlock->prev, nullptr); - ASSERT_EQ(56, a.BytesRemaining()); - - auto newHeader2 = (BlockHeader*)(TO_BYTES(newFooter) + sizeof(BlockFooter)); - auto newData2 = (TestStruct*) (((unsigned char*)newHeader2) + sizeof(BlockHeader)); - auto newFooter2 = (BlockFooter*) (((unsigned char*)newData2) + sizeof(TestStruct)); - - ASSERT_EQ(192, header->sizeAndFlags); - ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); - - a.Deallocate(p); - - FreeBlockNode* firstFree = a.FreeList()[8]; - ASSERT_EQ(firstFree->next, nullptr); - ASSERT_EQ(firstFree->prev, nullptr); - ASSERT_EQ(80, a.BytesRemaining()); - - BlockHeader* firstFreeHeader = TO_HBLOCK((TO_BYTES(firstFree) - sizeof(BlockHeader))); - ASSERT_TRUE(firstFreeHeader->sizeAndFlags & IS_FREE_FLAG); - ASSERT_FALSE(firstFreeHeader->sizeAndFlags & IS_PREV_FREE_FLAG); - ASSERT_FALSE(p); - ASSERT_EQ(a.FlBitmask(), 3); - ASSERT_EQ(256, a.SlBitmask()[0]); - - a.Deallocate(p2); - ASSERT_FALSE(p2); - ASSERT_EQ(104, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 5); - ASSERT_EQ(256, a.SlBitmask()[0]); - ASSERT_EQ(16, a.SlBitmask()[2]); - - a.Deallocate(str); - ASSERT_FALSE(str); - ASSERT_EQ(128, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 8); - ASSERT_EQ(a.SlBitmask()[3], 1); - - // Edge Cases - - void* badPointer = nullptr; - a.Deallocate(badPointer); - ASSERT_FALSE(badPointer); - - // try to deallocate pointer not in allocator; - - uint64_t* val = new uint64_t; - a.Deallocate(val); // Should do nothing and be ignored. - free(val); -} - -UTEST(test_ResourceSystem, TestAllocationWhenNoAppropriateFragmentExists) -{ - Allocator 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.BytesRemaining()); - - a.Deallocate(p0); - a.Deallocate(p2); - - ASSERT_EQ(48, a.BytesRemaining()); - void* tooLargeVal = a.Allocate(24); - ASSERT_FALSE(tooLargeVal); -} - -UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) -{ - Allocator a(1024 * 1024); - - unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); - //std::default_random_engine generator(seed); - std::default_random_engine generator(12345); - - std::uniform_int_distribution actionDist(0, 10); - std::uniform_int_distribution sizeDist(1, 256); - - std::vector pointers; - - 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); - } - } - 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(*(uint32_t*)ptrToFree, 0xDEADC0DE); - - a.Deallocate(ptrToFree); - ASSERT_FALSE(ptrToFree); - pointers.erase(pointers.begin() + index); - } - } - - for (void* ptr : pointers) - { - a.Deallocate(ptr); - } - - ASSERT_EQ(a.Capacity(), a.BytesRemaining()); -} \ No newline at end of file +//UTEST(test_ResourceSystem, TestEmptyAllocatorCreation) +//{ +// Allocator a; +// ASSERT_EQ(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 0); +//} +// +//UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) +//{ +// Allocator a(64); +// ASSERT_NE(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 64); +// ASSERT_EQ(64, a.BytesRemaining()); +// +// // 0001 0000 +// ASSERT_EQ(a.FlBitmask(), 4); +// ASSERT_EQ(a.SlBitmask()[2], 1); +// +// FreeBlockNode* block = a.FreeList()[2 * 16]; +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// +// BlockHeader* header = TO_HBLOCK((TO_BYTES(block) - sizeof(BlockHeader))); +// ASSERT_TRUE(header); +// ASSERT_EQ(64, a.GetHeaderSize(header)); +// ASSERT_TRUE(a.BlockIsFree(header)); +// ASSERT_FALSE(a.PrevBlockIsFree(header)); +// +// BlockFooter* footer = TO_FBLOCK(TO_BYTES(block) + a.GetHeaderSize(header)); +// ASSERT_TRUE(header); +// ASSERT_EQ(64, footer->totalBlockSize); +//} +// +//UTEST(test_ResourceSystem, TestAllocateFunction) +//{ +// Allocator a(64); +// ASSERT_NE(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 64); +// ASSERT_EQ(64, a.BytesRemaining()); +// +// 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(a.FlBitmask(), 2); +// ASSERT_EQ(16, a.SlBitmask()[1]); +// +// FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// +// auto header = (BlockHeader*)a.Data(); +// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); +// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); +// ASSERT_EQ(40, 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]); +// +// FreeBlockNode* NewBlock = a.FreeList()[0]; +// ASSERT_EQ(NewBlock->next, nullptr); +// ASSERT_EQ(NewBlock->prev, nullptr); +// +// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); +// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); +// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, newHeader->sizeAndFlags); +// ASSERT_EQ(newData, str); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); +// ASSERT_EQ(16, a.BytesRemaining()); +// +// // Edge cases +// +// // Empty allocator +// Allocator 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 +// Allocator overflowAlloc(SIZE_T_MAX); +// void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); +// ASSERT_FALSE(overflowPtr); +// +// void* tooLargeCase = a.Allocate(100); +// ASSERT_FALSE(tooLargeCase); +// +// Allocator tooSmallAlloc(64); +// void* tooLargeAlloc = a.Allocate(100); +// ASSERT_FALSE(tooLargeCase); +//} +// +//UTEST(test_ResourceSystem, TestDeallocateFunction) +//{ +// Allocator a(64); +// ASSERT_NE(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 64); +// ASSERT_EQ(64, a.BytesRemaining()); +// +// 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(a.FlBitmask(), 2); +// ASSERT_EQ(16, a.SlBitmask()[1]); +// +// FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// ASSERT_EQ(40, a.BytesRemaining()); +// +// auto header = (BlockHeader*)a.Data(); +// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); +// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); +// +// a.Deallocate(p); +// +// // Should be empty +// ASSERT_EQ(513, header->sizeAndFlags); +// ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); +// ASSERT_FALSE(p); +// ASSERT_EQ(a.FlBitmask(), 4); +// ASSERT_EQ(a.SlBitmask()[2], 1); +// 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(a.FlBitmask(), 2); +// ASSERT_EQ(16, a.SlBitmask()[1]); +// +// block = a.FreeList()[(1 * 16) + 4]; +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// ASSERT_EQ(40, a.BytesRemaining()); +// +// header = (BlockHeader*)a.Data(); +// data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); +// footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); +// +// 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]); +// +// FreeBlockNode* NewBlock = a.FreeList()[0]; +// ASSERT_EQ(NewBlock->next, nullptr); +// ASSERT_EQ(NewBlock->prev, nullptr); +// ASSERT_EQ(16, a.BytesRemaining()); +// +// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); +// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); +// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, newHeader->sizeAndFlags); +// ASSERT_EQ(newData, str); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); +// +// a.Deallocate(p); +// ASSERT_FALSE(p); +// ASSERT_TRUE(newHeader->sizeAndFlags & IS_PREV_FREE_FLAG); +// ASSERT_EQ(194, newHeader->sizeAndFlags); +// ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); +// FreeBlockNode* newFreeBlock = TO_FREEBLOCK((TO_BYTES(header)+sizeof(BlockHeader))); +// ASSERT_EQ(newFreeBlock->next, nullptr); +// ASSERT_EQ(newFreeBlock->prev, nullptr); +// ASSERT_EQ(40, a.BytesRemaining()); +// +// a.Deallocate(str); +// ASSERT_FALSE(str); +// ASSERT_EQ(513, header->sizeAndFlags); +// ASSERT_EQ(4, a.FlBitmask()); +// ASSERT_EQ(1, a.SlBitmask()[2]); +// ASSERT_EQ(64, a.BytesRemaining()); +//} +// +//UTEST(test_ResourceSystem, TestBlockCoalescing) +//{ +// Allocator a(128); +// ASSERT_NE(a.Data(), nullptr); +// ASSERT_EQ(a.Capacity(), 128); +// ASSERT_EQ(128, a.BytesRemaining()); +// +// 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(0, a.SlBitmask()[1]); +// +// FreeBlockNode* block = a.FreeList()[42]; +// ASSERT_EQ(block->next, nullptr); +// ASSERT_EQ(block->prev, nullptr); +// ASSERT_EQ(104, a.BytesRemaining()); +// +// auto header = (BlockHeader*)a.Data(); +// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); +// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(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(0, a.SlBitmask()[1]); +// +// FreeBlockNode* NewBlock = a.FreeList()[36]; +// ASSERT_EQ(NewBlock->next, nullptr); +// ASSERT_EQ(NewBlock->prev, nullptr); +// ASSERT_EQ(80, a.BytesRemaining()); +// +// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); +// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); +// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, newHeader->sizeAndFlags); +// ASSERT_EQ(newData, str); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->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(a.FlBitmask(), 2); +// ASSERT_EQ(4096, a.SlBitmask()[1]); +// +// FreeBlockNode* newFreeBlock = a.FreeList()[28]; +// ASSERT_EQ(newFreeBlock->next, nullptr); +// ASSERT_EQ(newFreeBlock->prev, nullptr); +// ASSERT_EQ(56, a.BytesRemaining()); +// +// auto newHeader2 = (BlockHeader*)(TO_BYTES(newFooter) + sizeof(BlockFooter)); +// auto newData2 = (TestStruct*) (((unsigned char*)newHeader2) + sizeof(BlockHeader)); +// auto newFooter2 = (BlockFooter*) (((unsigned char*)newData2) + sizeof(TestStruct)); +// +// ASSERT_EQ(192, header->sizeAndFlags); +// ASSERT_EQ(data, p); +// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); +// +// a.Deallocate(p); +// +// FreeBlockNode* firstFree = a.FreeList()[8]; +// ASSERT_EQ(firstFree->next, nullptr); +// ASSERT_EQ(firstFree->prev, nullptr); +// ASSERT_EQ(80, a.BytesRemaining()); +// +// BlockHeader* firstFreeHeader = TO_HBLOCK((TO_BYTES(firstFree) - sizeof(BlockHeader))); +// ASSERT_TRUE(firstFreeHeader->sizeAndFlags & IS_FREE_FLAG); +// ASSERT_FALSE(firstFreeHeader->sizeAndFlags & IS_PREV_FREE_FLAG); +// ASSERT_FALSE(p); +// ASSERT_EQ(a.FlBitmask(), 3); +// ASSERT_EQ(256, a.SlBitmask()[0]); +// +// a.Deallocate(p2); +// ASSERT_FALSE(p2); +// ASSERT_EQ(104, a.BytesRemaining()); +// ASSERT_EQ(a.FlBitmask(), 5); +// ASSERT_EQ(256, a.SlBitmask()[0]); +// ASSERT_EQ(16, a.SlBitmask()[2]); +// +// a.Deallocate(str); +// ASSERT_FALSE(str); +// ASSERT_EQ(128, a.BytesRemaining()); +// ASSERT_EQ(a.FlBitmask(), 8); +// ASSERT_EQ(a.SlBitmask()[3], 1); +// +// // Edge Cases +// +// void* badPointer = nullptr; +// a.Deallocate(badPointer); +// ASSERT_FALSE(badPointer); +// +// // try to deallocate pointer not in allocator; +// +// uint64_t* val = new uint64_t; +// a.Deallocate(val); // Should do nothing and be ignored. +// free(val); +//} +// +//UTEST(test_ResourceSystem, TestAllocationWhenNoAppropriateFragmentExists) +//{ +// Allocator 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.BytesRemaining()); +// +// a.Deallocate(p0); +// a.Deallocate(p2); +// +// ASSERT_EQ(48, a.BytesRemaining()); +// void* tooLargeVal = a.Allocate(24); +// ASSERT_FALSE(tooLargeVal); +//} +// +//UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) +//{ +// Allocator a(1024 * 1024); +// +// unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); +// //std::default_random_engine generator(seed); +// 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); +// } +// } +// 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); +// +// a.Deallocate(ptrToFree); +// ASSERT_FALSE(ptrToFree); +// pointers.erase(pointers.begin() + index); +// } +// } +// +// for (void* ptr : pointers) +// { +// a.Deallocate(ptr); +// } +// +// ASSERT_EQ(a.Capacity(), a.BytesRemaining()); +//} \ No newline at end of file diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp new file mode 100644 index 00000000..0d87f1f1 --- /dev/null +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -0,0 +1,139 @@ +// +// 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 + +using namespace Siege; + +struct TestStruct +{ + uint64_t inta {0}; + uint64_t intb {0}; +}; + + +UTEST(test_TlsfAllocator, EmptyConstructor) +{ + TlsfAllocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); + ASSERT_EQ(0, a.BytesRemaining()); +} + +UTEST(test_TlsfAllocator, ConstructorWithSize) +{ + TlsfAllocator 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)); + + TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 2); + ASSERT_TRUE(block); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + TlsfAllocator::BlockHeader* header = a.GetHeader(block); + ASSERT_TRUE(header); + ASSERT_EQ(72, a.GetHeaderSize(header)); + ASSERT_TRUE(a.IsFree(header)); + ASSERT_FALSE(a.PrevBlockIsFree(header)); + + TlsfAllocator::BlockFooter* footer = a.GetFooter(header); + ASSERT_TRUE(footer); + ASSERT_EQ(72, footer->totalBlockSize); +} + +UTEST(test_TlsfAllocator, ConstructorWithTooSmallSize) +{ + Siege::TlsfAllocator a(15); + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(test_TlsfAllocator, TestAllocateFunction) +{ + TlsfAllocator 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)); + + TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + ASSERT_EQ(40, 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(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); + ASSERT_EQ(16, a.BytesRemaining()); + + // Edge cases + + // Empty allocator + TlsfAllocator 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 + TlsfAllocator overflowAlloc(UINT16_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT16_MAX); + ASSERT_FALSE(overflowPtr); + + void* tooLargeCase = a.Allocate(100); + ASSERT_FALSE(tooLargeCase); + + TlsfAllocator tooSmallAlloc(64); + void* tooLargeAlloc = tooSmallAlloc.Allocate(100); + ASSERT_FALSE(tooLargeAlloc); +} \ No newline at end of file From d03108a30ca8badbfc2fd2a09a6c7918c479bdf3 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 24 Aug 2025 21:18:10 +1000 Subject: [PATCH 08/15] Fixed broken unit tests --- engine/utils/TlsfAllocator.cpp | 161 ++++++++---- engine/utils/TlsfAllocator.h | 53 +++- tests/src/utils/test_TlsfAllocator.cpp | 350 ++++++++++++++++++++++++- 3 files changed, 509 insertions(+), 55 deletions(-) diff --git a/engine/utils/TlsfAllocator.cpp b/engine/utils/TlsfAllocator.cpp index 297bcd17..cd9482dd 100644 --- a/engine/utils/TlsfAllocator.cpp +++ b/engine/utils/TlsfAllocator.cpp @@ -20,7 +20,8 @@ #define HEADER_SIZE sizeof(BlockHeader) #define FOOTER_SIZE sizeof(BlockFooter) #define FREE_BLOCK_SIZE sizeof(FreeBlockNode) -#define PAD_SIZE(size) size + HEADER_SIZE + FOOTER_SIZE +#define METADATA_OVERHEAD HEADER_SIZE + FOOTER_SIZE +#define PAD_SIZE(size) size + METADATA_OVERHEAD #define FL_MIN_INDEX 4 #define MAX_SL_BUCKETS 16 @@ -36,64 +37,66 @@ namespace Siege TlsfAllocator::TlsfAllocator() {} TlsfAllocator::TlsfAllocator(const uint64_t size) - : capacity {size}, bytesRemaining {size} { - if (size == 0 || size < MIN_ALLOCATION || size >= UINT64_MAX) - { - capacity = 0; - bytesRemaining = 0; + uint64_t paddedSize = PAD_SIZE(size); + if (size == 0 || size < MIN_ALLOCATION || size > (UINT64_MAX - METADATA_OVERHEAD) || paddedSize < size) return; - } uint64_t maxBuckets = (FL(size) - FL_MIN_INDEX) + 1; - uint64_t totalSize = PAD_SIZE(size); uint64_t slBucketSize = sizeof(uint16_t) * maxBuckets; uint64_t freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); - uint64_t allocSize = totalSize + slBucketSize + freeListSize; + uint64_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 + totalSize); - slBitmasks = (uint16_t*)(data + totalSize + freeListSize); + freeList = (FreeBlockNode**)(data + paddedSize); + slBitmasks = (uint16_t*)(data + paddedSize + freeListSize); - BlockHeader* firstHeader = CreateHeader(data, totalSize, FREE); + CreateHeader(data, paddedSize, FREE); - AddNewBlock(totalSize, firstHeader); + AddNewBlock(paddedSize, TO_HEADER(data)); } -TlsfAllocator::BlockHeader* TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, +void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags) { - if (!ptr) return nullptr; + if (!ptr) return; BlockHeader* header = TO_HEADER(ptr); header->sizeAndFlags = (size << FLAG_OFFSET) | flags; - return header; } void* TlsfAllocator::Allocate(const uint64_t& size) { - size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); - if (!data || capacity == 0 ||size == 0 || requiredSize < size || requiredSize > bytesRemaining - || requiredSize < MIN_ALLOCATION) return nullptr; + uint64_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + if (!data || capacity == 0 ||size == 0 || requiredSize < size || requiredSize > totalBytesRemaining + || size < MIN_ALLOCATION) return nullptr; FreeBlockNode* block = FindFreeBlock(requiredSize); if (!block) return nullptr; BlockHeader* header = TrySplitBlock(block, requiredSize); - header = CreateHeader(TO_BYTES(header), requiredSize, FULL); - BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(header)) , requiredSize); + + header->sizeAndFlags = (requiredSize << FLAG_OFFSET) | FULL; + CreateFooter(TO_BYTES(GetFooter(header)), requiredSize); uint8_t* ptr = GetBlockData(header); - bytesRemaining -= requiredSize; + bytesRemaining -= size; + totalBytesRemaining -= requiredSize; return ptr; } -TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, uint64_t allocatedSize) +TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, uint64_t& allocatedSize) { if (!node) return nullptr; @@ -104,9 +107,15 @@ TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBloc if (!RemoveFreeBlock(node)) return nullptr; if (oldSize <= allocatedSize || ((oldSize - allocatedSize) < - (HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) return header; + (HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) + { + allocatedSize = oldSize; + return header; + } + + BlockHeader* newFreeBlock = TO_HEADER(TO_BYTES(header) + allocatedSize); - AddNewBlock(oldSize - allocatedSize, TO_HEADER(TO_BYTES(header) + allocatedSize)); + AddNewBlock(oldSize - allocatedSize, newFreeBlock); return header; } @@ -116,17 +125,39 @@ void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) uint64_t fl = 0,sl = 0, index; index = CalculateFreeBlockIndices(size, fl, sl); - BlockHeader* newHeader = CreateHeader(TO_BYTES(header), size, FREE); - FreeBlockNode* node = CreateFreeBlock(TO_BYTES(GetFreeBlock(newHeader)), nullptr, freeList[index]); - BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(newHeader)), size); + CreateHeader(TO_BYTES(header), size, FREE); + FreeBlockNode* node = CreateFreeBlock(TO_BYTES(GetFreeBlock(header)), nullptr, freeList[index]); + CreateFooter(TO_BYTES(GetFooter(header)), size); - if (node->next) node->next->prev = node; + if (node && node->next) node->next->prev = node; freeList[index] = node; flBitmask |= (1ULL << fl); slBitmasks[fl] |= (1 << sl); } +TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT uint64_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; +} + bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) { BlockHeader* header = GetHeader(node); @@ -156,6 +187,7 @@ bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size) { uint64_t fl, sl, index; + // 1000 0000 0000 1000 index = CalculateFreeBlockIndices(size, fl, sl); if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); @@ -171,7 +203,7 @@ const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) if (sl == 32) sl = 0; if (sl) return fl * MAX_SL_BUCKETS + sl; - fl = flBitmask & ~(((1ULL << (sl + 1)) - 1)); + fl = flBitmask & ~(((1ULL << (fl + 1)) - 1)); if (!fl) return INVALID_INDEX; @@ -192,37 +224,54 @@ uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & return fl * MAX_SL_BUCKETS + sl; } -TlsfAllocator::BlockFooter* TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size) +void TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size) { - if (!ptr) return nullptr; + if (!ptr) return; BlockFooter* footer = TO_FOOTER(ptr); footer->totalBlockSize = size; - return footer; } TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node) { if (!node) return nullptr; - return TO_HEADER(TO_BYTES(node) - HEADER_SIZE); + uint8_t* rawHeader = TO_BYTES(node) - HEADER_SIZE; + if (!IsValid(rawHeader)) return nullptr; + return TO_HEADER(rawHeader); +} + +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); } TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header) { - if (!header || TO_BYTES(header) == data) return nullptr; - return TO_HEADER(TO_BYTES(header) - GetPrevFooter(header)->totalBlockSize); + 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); } TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; - return TO_HEADER(TO_BYTES(header) + GetHeaderSize(header)); + uint8_t* rawHeader = TO_BYTES(header); + uint8_t* next = rawHeader + GetHeaderSize(header); + if (!IsValid(next)) return nullptr; + return TO_HEADER(next); } TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, TlsfAllocator::FreeBlockNode* prev, TlsfAllocator::FreeBlockNode* next) { - if (!ptr) return nullptr; + if (!IsValid(ptr)) return nullptr; + FreeBlockNode* node = TO_FREE_BLOCK(ptr); node->prev = prev; node->next = next; @@ -231,8 +280,10 @@ TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) { - if (!IsFree(header)) return nullptr; - return TO_FREE_BLOCK(TO_BYTES(header) + HEADER_SIZE); + if (!header || !IsFree(header)) return nullptr; + uint8_t* rawBlock = TO_BYTES(header) + HEADER_SIZE; + if (!IsValid(rawBlock)) return nullptr; + return TO_FREE_BLOCK(rawBlock); } TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl) @@ -242,15 +293,19 @@ TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, con TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) { + if (!header) return nullptr; uint64_t size = GetHeaderSize(header); - return TO_FOOTER(TO_BYTES(header) + (size - FOOTER_SIZE)); + uint8_t* rawFooter = TO_BYTES(header) + (size - FOOTER_SIZE); + if (!rawFooter || !IsValid(rawFooter)) return nullptr; + return TO_FOOTER(rawFooter); } TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header) { - uint8_t* raw = TO_BYTES(header); - if (raw == data) return nullptr; - return TO_FOOTER(raw - FOOTER_SIZE); + if (!header) return nullptr; + uint8_t* rawFooter = TO_BYTES(header) - FOOTER_SIZE; + if (!IsValid(rawFooter)) return nullptr; + return TO_FOOTER(rawFooter); } uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) @@ -277,7 +332,23 @@ bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) { uint8_t* raw = TO_BYTES(header); if (raw == data) return false; - return header && IsFree(TO_HEADER(raw - GetPrevFooter(header)->totalBlockSize)); + + 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)); } +bool TlsfAllocator::IsValid(uint8_t* ptr) +{ + return ptr && ptr >= data && ptr < (data + (capacity + HEADER_SIZE + FOOTER_SIZE)); +} + +const uint64_t TlsfAllocator::Capacity() { return capacity; } + +const uint64_t TlsfAllocator::BytesRemaining() { return bytesRemaining; } + } // namespace Siege \ No newline at end of file diff --git a/engine/utils/TlsfAllocator.h b/engine/utils/TlsfAllocator.h index 9b77650b..9f9d51df 100644 --- a/engine/utils/TlsfAllocator.h +++ b/engine/utils/TlsfAllocator.h @@ -53,8 +53,8 @@ class TlsfAllocator // Other functions - BlockHeader* CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags); - BlockFooter* CreateFooter(uint8_t* ptr, const uint64_t size); + void CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags); + void CreateFooter(uint8_t* ptr, const uint64_t size); FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next); FreeBlockNode* FindFreeBlock(const uint64_t& size); @@ -72,25 +72,68 @@ class TlsfAllocator BlockFooter* GetFooter(BlockHeader* header); BlockFooter* GetPrevFooter(BlockHeader* header); BlockHeader* GetHeader(FreeBlockNode* node); + BlockHeader* GetHeader(uint8_t* ptr); BlockHeader* GetPrevHeader(BlockHeader* header); BlockHeader* GetNextHeader(BlockHeader* header); // Allocate/Deallocate void* Allocate(const uint64_t& size); - BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t allocatedSize); + + template + void Deallocate(T*& 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; + + uint64_t blockSize = GetHeaderSize(header); + + uint64_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; + } + BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t& allocatedSize); bool RemoveFreeBlock(FreeBlockNode* node); void AddNewBlock(const uint64_t size, BlockHeader* currentNode); + BlockHeader* TryCoalesce(BlockHeader* header, OUT uint64_t& size); // Getters + bool IsValid(uint8_t* ptr); const uint64_t GetHeaderSize(BlockHeader* header); - const uint64_t Capacity() { return capacity; } - const uint64_t BytesRemaining() { return bytesRemaining; } + const uint64_t Capacity(); + const uint64_t BytesRemaining(); + const uint64_t TotalBytesRemaining() { return totalBytesRemaining; } + const uint64_t TotalSize() { return totalSize; } const uint8_t* Data() { return data; } const uint64_t FlBitmask() { return flBitmask; } const uint16_t& SlBitmask(const uint64_t fl) { return slBitmasks[fl]; } private: + uint64_t totalSize {0}; + uint64_t totalBytesRemaining {0}; + uint64_t capacity {0}; uint64_t bytesRemaining {0}; diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index 0d87f1f1..2bee0bde 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -11,6 +11,8 @@ #include #include +#include + using namespace Siege; struct TestStruct @@ -32,7 +34,7 @@ UTEST(test_TlsfAllocator, ConstructorWithSize) { TlsfAllocator a(64); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); + ASSERT_EQ(a.TotalSize(), 72); ASSERT_EQ(64, a.BytesRemaining()); ASSERT_EQ(a.FlBitmask(), 4); @@ -94,7 +96,7 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_EQ(data, p); ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); - ASSERT_EQ(40, a.BytesRemaining()); + ASSERT_EQ(a.BytesRemaining(), 48); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); @@ -111,7 +113,7 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); - ASSERT_EQ(16, a.BytesRemaining()); + ASSERT_EQ(32, a.BytesRemaining()); // Edge cases @@ -126,8 +128,8 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_FALSE(emptyAllocPtr); // Allocate an amount that causes a value overflow - TlsfAllocator overflowAlloc(UINT16_MAX); - void* overflowPtr = overflowAlloc.Allocate(UINT16_MAX); + TlsfAllocator overflowAlloc(UINT64_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); ASSERT_FALSE(overflowPtr); void* tooLargeCase = a.Allocate(100); @@ -136,4 +138,342 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) TlsfAllocator tooSmallAlloc(64); void* tooLargeAlloc = tooSmallAlloc.Allocate(100); ASSERT_FALSE(tooLargeAlloc); +} + +UTEST(test_TlsfAllocator, TestAllocateFunctionWithRandomInputs) +{ + TlsfAllocator 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); + + TlsfAllocator::BlockHeader* header = (TlsfAllocator::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); + TlsfAllocator::BlockFooter* footer = a.GetFooter(header); + header = a.GetNextHeader(header); + } + else continue; + } +} + +UTEST(test_TlsfAllocator, TestDeallocateFunction) +{ + TlsfAllocator 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)); + + TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + ASSERT_EQ(48, a.BytesRemaining()); + + a.Deallocate(p); + + // Should be empty + ASSERT_EQ(577, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & TlsfAllocator::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 = (TlsfAllocator::BlockHeader*)a.Data(); + data = (TestStruct*)a.GetBlockData(header); + footer = a.GetFooter(header); + + ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(data, p); + ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); + ASSERT_EQ(32, a.BytesRemaining()); + + a.Deallocate(p); + ASSERT_FALSE(p); + ASSERT_TRUE(strHeader->sizeAndFlags & TlsfAllocator::PREV_IS_FREE); + ASSERT_EQ(193, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & TlsfAllocator::FREE); + TlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(header); + ASSERT_NE(nullptr, newFreeBlock->next); + ASSERT_EQ(nullptr, newFreeBlock->prev); + ASSERT_EQ(48, a.BytesRemaining()); + + a.Deallocate(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()); + + TlsfAllocator::FreeBlockNode* newNode = (TlsfAllocator::FreeBlockNode*) data; + ASSERT_TRUE(newNode); + // Check if the new node is head + ASSERT_EQ(nullptr, newNode->prev); + ASSERT_EQ(nullptr, newNode->next); +} + +UTEST(test_TlsfAllocator, TestBlockCoalescing) +{ + TlsfAllocator 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 + TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 12); + ASSERT_EQ(block->next, nullptr); + ASSERT_EQ(block->prev, nullptr); + ASSERT_EQ(112, a.BytesRemaining()); + + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(TlsfAllocator::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)); + + TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(TlsfAllocator::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)); + + TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + + a.Deallocate(p); + + TlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 8); + ASSERT_EQ(firstFree->next, nullptr); + ASSERT_EQ(firstFree->prev, nullptr); + ASSERT_EQ(96, a.BytesRemaining()); + + TlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*)firstFree); + ASSERT_TRUE(firstFreeHeader->sizeAndFlags & TlsfAllocator::FREE); + ASSERT_FALSE(firstFreeHeader->sizeAndFlags & TlsfAllocator::PREV_IS_FREE); + + ASSERT_FALSE(p); + ASSERT_EQ(a.FlBitmask(), 5); + ASSERT_EQ(256, a.SlBitmask(0)); + + a.Deallocate(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.Deallocate(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.Deallocate(badPointer); + ASSERT_FALSE(badPointer); + + // try to deallocate pointer not in allocator; + + uint64_t* val = new uint64_t; + a.Deallocate(val); // Should do nothing and be ignored. + free(val); +} + +UTEST(test_TlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) +{ + TlsfAllocator 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()); + + a.Deallocate(p0); + a.Deallocate(p2); + + ASSERT_EQ(64, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(24); + ASSERT_FALSE(tooLargeVal); +} + +UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) +{ + TlsfAllocator a(1024 * 1024); + + 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); + TlsfAllocator::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); + TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + + a.Deallocate(ptrToFree); + TlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + + ASSERT_FALSE(ptrToFree); + pointers.erase(pointers.begin() + index); + } + } + + for (void* ptr : pointers) + { + a.Deallocate(ptr); + } + + ASSERT_EQ(a.TotalSize(), a.TotalBytesRemaining()); } \ No newline at end of file From aa47b7567590ad9696b616cc68cb9769c53aa031 Mon Sep 17 00:00:00 2001 From: aryeh Date: Mon, 25 Aug 2025 22:07:46 +1000 Subject: [PATCH 09/15] Added explicit include and removed old allocator code --- tests/src/resources/test_ResourceSystem.cpp | 757 +------------------- tests/src/utils/test_TlsfAllocator.cpp | 1 + 2 files changed, 2 insertions(+), 756 deletions(-) diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 3856e368..67949efc 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -561,759 +561,4 @@ UTEST_F(test_ResourceSystem, LoadAnimationData) ASSERT_NEAR(expectedKey.rotation.w, actualKey.rotation.w, 0.01); } } -} -struct TestStruct -{ - uint64_t inta {0}; - uint64_t intb {0}; -}; - -struct FreeBlockNode -{ - FreeBlockNode* prev {nullptr}; - FreeBlockNode* next {nullptr}; -}; - -struct SmallBlockHeader -{ - uint16_t sizeAndFlags {0}; -}; - -struct SmallBlockFooter -{ - uint16_t sizeAndFlags {0}; -}; - -struct BlockHeader -{ - uint32_t sizeAndFlags {0}; -}; - -struct BlockFooter -{ - uint32_t totalBlockSize {0}; -}; - -struct LargeBlockHeader -{ - uint64_t sizeAndFlags {0}; -}; - -struct LargeBlockFooter -{ - uint64_t sizeAndFlags {0}; -}; - -#define FL_MIN 4 -#define FL_INDEX(capacity) 63 - __builtin_clzll(capacity) -#define MAX_SL_INDEX 16 -#define MIN_ALLOC_SIZE 16 - -#define TO_BYTES(val) ((unsigned char*)val) -#define TO_HBLOCK(val) ((BlockHeader*)val) -#define TO_FBLOCK(val) ((BlockFooter*)val) -#define TO_FREEBLOCK(val) ((FreeBlockNode*)val) - -#define SL_BITS 4 -#define SL_MASK ((1 << SL_BITS) - 1) - -#define FLAG_BITS 3 -#define IS_FREE_FLAG 1 -#define IS_PREV_FREE_FLAG 2 - -#define INVALID_INDEX SIZE_T_MAX - -class Allocator -{ -public: - Allocator() {}; - Allocator(const size_t capacity) - : capacity {capacity}, bytesRemaining {capacity} - { - data = TO_BYTES(calloc(1, sizeof(BlockHeader) + sizeof(BlockFooter) + capacity)); - - size_t fl = 0, sl = 0, index; - - CalculateIndices(capacity, fl, sl ,index); - - uint64_t maxIndices = fl + 1; - - freeList = (FreeBlockNode**)calloc(maxIndices * MAX_SL_INDEX, sizeof(FreeBlockNode*)); - slBitmasks = (uint16_t*)calloc(maxIndices, sizeof(uint16_t)); - - BlockHeader* firstHeader = (BlockHeader*)data; - firstHeader->sizeAndFlags = (capacity << FLAG_BITS) | IS_FREE_FLAG; - - FreeBlockNode* firstBlock = TO_FREEBLOCK((data+sizeof(BlockHeader))); - - BlockFooter* firstFooter = TO_FBLOCK(TO_BYTES(firstBlock)+capacity); - firstFooter->totalBlockSize = capacity; - freeList[index] = firstBlock; - - flBitmask = SetBitOn64(flBitmask, fl); - slBitmasks[fl] = SetBitOn(slBitmasks[fl], sl); - } - - ~Allocator() - { - if (data) free(data); - if (freeList) free(freeList); - if (slBitmasks) free(slBitmasks); - - capacity = 0; - data = nullptr; - freeList = nullptr; - slBitmasks = nullptr; - } - - void* Allocate(size_t size) - { - size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); - if (!data || capacity == 0 ||size == 0 - || requiredSize < size || requiredSize > bytesRemaining) return nullptr; - - if (requiredSize < MIN_ALLOC_SIZE) return nullptr; - - size_t freeListIdx = 0, fl = 0, sl = 0; - - FreeBlockNode* block = FindFreeBlock(requiredSize, fl, sl, freeListIdx); - - if (freeListIdx == INVALID_INDEX) return nullptr; - - BlockHeader* header = reinterpret_cast(reinterpret_cast(block) - sizeof(BlockHeader)); - - TrySplitBlock(header, block, requiredSize, fl, sl, freeListIdx); - - BlockHeader* newHeader = TO_HBLOCK(TO_BYTES(header)); - - size_t flags = 0; - TrySetPreviousHeaderFlag(newHeader, flags); - - newHeader->sizeAndFlags = (requiredSize << FLAG_BITS) | flags; - - unsigned char* ptr = TO_BYTES(header)+sizeof(BlockHeader); - - BlockFooter* footer = TO_FBLOCK((ptr+size)); - footer->totalBlockSize = requiredSize; - - bytesRemaining -= requiredSize; - - return ptr; - } - - template - void Deallocate(T*& ptr) - { - if (!ptr) return; - if ((TO_BYTES(ptr) < data || TO_BYTES(ptr) >= (data + capacity))) return; - - BlockHeader* header = TO_HBLOCK((TO_BYTES(ptr) - sizeof(BlockHeader))); - - if (BlockIsFree(header)) return; - - size_t blockSize = GetHeaderSize(header); - - size_t totalBlockSize = blockSize; - BlockHeader* targetHeader = TryCoalesce(header, totalBlockSize); - BlockFooter* footer = TO_FBLOCK((TO_BYTES(targetHeader) + totalBlockSize - sizeof(BlockFooter))); - - targetHeader->sizeAndFlags = (totalBlockSize << FLAG_BITS) | (targetHeader->sizeAndFlags & IS_PREV_FREE_FLAG) | IS_FREE_FLAG; - footer->totalBlockSize = totalBlockSize; - - BlockHeader* nextHeader = GetNextHeader(targetHeader, totalBlockSize); - if (nextHeader) nextHeader->sizeAndFlags |= IS_PREV_FREE_FLAG; - - CreateNewBlock(TO_BYTES(targetHeader), totalBlockSize); - - bytesRemaining += blockSize; - - ptr = nullptr; - } - - size_t GetNextFreeSlotIndex(OUT size_t& fl,OUT size_t& sl) - { - sl = __builtin_ctz(FindLargerSlots(slBitmasks[fl], sl)); - - if (sl == 32) sl = 0; - if (sl) return fl * MAX_SL_INDEX + sl; - - fl = FindLargerSlots64(flBitmask, fl); - - if (!fl) return INVALID_INDEX; - - 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_INDEX + sl; - } - - bool IsFree(size_t fl, size_t sl) { return Mask(slBitmasks[fl], BitMask(sl)); } - const size_t FindLargerSlots(size_t mask, size_t idx) { return Mask(mask, FlipBits(BitMask(idx+1)-1)); } - const size_t FindLargerSlots64(size_t mask, size_t idx) { return Mask(mask, FlipBits(BitMask64(idx+1)-1)); } - const size_t BitMask(size_t shiftAmount) { return 1 << shiftAmount; } - const size_t BitMask64(size_t shiftAmount) { return 1ULL << shiftAmount; } - const size_t SetBitOn64(size_t val, size_t bit) { return val | BitMask64(bit);} - const size_t SetBitOn(size_t val, size_t bit) { return val | BitMask(bit);} - const size_t FlipBits(size_t mask) { return ~(mask); } - const size_t Mask(size_t value, size_t mask) { return value & mask; } - - const size_t GetHeaderSize(BlockHeader* header) { return header->sizeAndFlags >> FLAG_BITS; } - const size_t PrevBlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_PREV_FREE_FLAG); } - const size_t BlockIsFree(BlockHeader* header) { return Mask(header->sizeAndFlags, IS_FREE_FLAG); } - BlockHeader* GetHeader(FreeBlockNode* node) { return TO_HBLOCK((TO_BYTES(node) - sizeof(BlockHeader))); } - - BlockHeader* TryCoalesce(BlockHeader* header, OUT size_t& size) - { - BlockHeader* start = header; - BlockHeader* prev = GetPreviousHeader(start); - - if (prev && BlockIsFree(prev)) - { - start = prev; - size += GetHeaderSize(prev); - RemoveFromFreeList(prev); - } - - BlockHeader* next = GetNextHeader(start, size); - - if (next && BlockIsFree(next)) - { - size += GetHeaderSize(next); - RemoveFromFreeList(next); - } - - return start; - } - - BlockHeader* GetPreviousHeader(BlockHeader* currentHeader) - { - if (IsHead(currentHeader)) return nullptr; - BlockFooter* prev = TO_FBLOCK((TO_BYTES(currentHeader) - sizeof(BlockFooter))); - BlockHeader* prevHeader = TO_HBLOCK((TO_BYTES(currentHeader) - prev->totalBlockSize)); - return prevHeader; - } - - BlockHeader* GetNextHeader(void* currentHeader, size_t offset) - { - unsigned char* next = TO_BYTES(currentHeader) + offset; - if (next >= (data+capacity)) return nullptr; - if (IsTail(next)) return nullptr; - return TO_HBLOCK(next); - } - - void TrySetPreviousHeaderFlag(BlockHeader* currentHeader, size_t& flags) - { - BlockHeader* prevHeader = GetPreviousHeader(currentHeader); - if (prevHeader && - Mask(prevHeader->sizeAndFlags,IS_FREE_FLAG)) flags |= IS_PREV_FREE_FLAG; - } - - void TrySplitBlock(BlockHeader* header, FreeBlockNode* block, size_t size, size_t fl, size_t sl, size_t index) - { - size_t blockSize = GetHeaderSize(header); - RemoveFromFreeList(block, fl, sl, index); - if (blockSize <= size || ((blockSize - size) < (sizeof(BlockHeader) + sizeof(BlockFooter)))) return; - - CreateNewBlock(TO_BYTES(header) + size, blockSize - size); - } - - void RemoveFromFreeList(FreeBlockNode* block, size_t fl, size_t sl, size_t index) - { - if (!block) return; - - if (!block->prev) freeList[index] = block->next; - else block->prev->next = block->next; - - if (block->next) block->next->prev = block->prev; - - block->next = nullptr; - block->prev = nullptr; - - slBitmasks[fl] = Mask(slBitmasks[fl],FlipBits(BitMask(sl))); - if (!slBitmasks[fl]) flBitmask = Mask(flBitmask,FlipBits(BitMask(fl))); - } - - void RemoveFromFreeList(BlockHeader* block) - { - size_t size = GetHeaderSize(block); - size_t fl, sl, index; - - CalculateIndices(size, fl, sl, index); - - FreeBlockNode* freeBlock = TO_FREEBLOCK((TO_BYTES(block) + sizeof(BlockHeader))); - RemoveFromFreeList(freeBlock, fl, sl, index); - } - - void CalculateIndices(size_t size, OUT size_t& fl, OUT size_t& sl, OUT size_t& index) - { - GetIndices(size, fl, sl); - index = fl * MAX_SL_INDEX + sl; - } - - void CreateNewBlock(unsigned char* ptr, size_t size) - { - size_t fl = 0,sl = 0, index; - CalculateIndices(size, fl, sl, index); - - size_t sizeFlag = (size << FLAG_BITS); - - BlockHeader* header = TO_HBLOCK(ptr); - header->sizeAndFlags = (sizeFlag | IS_FREE_FLAG); - - FreeBlockNode* block = reinterpret_cast(reinterpret_cast(header) + sizeof(BlockHeader)); - - BlockFooter* firstFooter = TO_FBLOCK((TO_BYTES(header)+(size-sizeof(BlockFooter)))); - firstFooter->totalBlockSize = size; - - block->next = freeList[index]; - block->prev = nullptr; - - if (IsValid(block->next)) block->next->prev = block; - - freeList[index] = block; - - flBitmask = SetBitOn64(flBitmask, fl); - slBitmasks[fl] = SetBitOn(slBitmasks[fl], sl); - } - - FreeBlockNode* FindFreeBlock(size_t size, OUT size_t& fl, OUT size_t& sl, OUT size_t& index) - { - CalculateIndices(size, fl, sl, index); - - if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); - if (index == INVALID_INDEX) return nullptr; - - return freeList[index]; - } - - void GetIndices(const size_t size, OUT size_t& fl, OUT size_t& sl) - { - size_t rawFl = FL_INDEX(size); - sl = SlIndex(size, rawFl); - fl = rawFl - FL_MIN; - } - - bool IsHead(void* ptr) { return TO_BYTES(ptr) == data ;} - bool IsTail(void* ptr) { return TO_BYTES(ptr) + sizeof(BlockHeader) >= (data + capacity); } - bool IsValid(FreeBlockNode* ptr) { return ptr != nullptr; } - size_t& Capacity() { return capacity;} - unsigned char* Data() { return data; }; - size_t SlIndex(size_t size, size_t fl) { return (size >> (fl - SL_BITS)) & SL_MASK; } - const uint64_t& FlBitmask() { return flBitmask; } - uint16_t*& SlBitmask() { return slBitmasks; } - FreeBlockNode** FreeList() { return freeList; } - const size_t BytesRemaining() { return bytesRemaining; } - -private: - size_t capacity {0}; - size_t bytesRemaining {0}; - unsigned char* data {nullptr}; - FreeBlockNode** freeList {nullptr}; - - // bitmasks - uint64_t flBitmask {0}; - uint16_t* slBitmasks {nullptr}; -}; - -//UTEST(test_ResourceSystem, TestEmptyAllocatorCreation) -//{ -// Allocator a; -// ASSERT_EQ(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 0); -//} -// -//UTEST(test_ResourceSystem, TestAllocatorCreationWithSize) -//{ -// Allocator a(64); -// ASSERT_NE(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 64); -// ASSERT_EQ(64, a.BytesRemaining()); -// -// // 0001 0000 -// ASSERT_EQ(a.FlBitmask(), 4); -// ASSERT_EQ(a.SlBitmask()[2], 1); -// -// FreeBlockNode* block = a.FreeList()[2 * 16]; -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// -// BlockHeader* header = TO_HBLOCK((TO_BYTES(block) - sizeof(BlockHeader))); -// ASSERT_TRUE(header); -// ASSERT_EQ(64, a.GetHeaderSize(header)); -// ASSERT_TRUE(a.BlockIsFree(header)); -// ASSERT_FALSE(a.PrevBlockIsFree(header)); -// -// BlockFooter* footer = TO_FBLOCK(TO_BYTES(block) + a.GetHeaderSize(header)); -// ASSERT_TRUE(header); -// ASSERT_EQ(64, footer->totalBlockSize); -//} -// -//UTEST(test_ResourceSystem, TestAllocateFunction) -//{ -// Allocator a(64); -// ASSERT_NE(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 64); -// ASSERT_EQ(64, a.BytesRemaining()); -// -// 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(a.FlBitmask(), 2); -// ASSERT_EQ(16, a.SlBitmask()[1]); -// -// FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// -// auto header = (BlockHeader*)a.Data(); -// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); -// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); -// ASSERT_EQ(40, 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]); -// -// FreeBlockNode* NewBlock = a.FreeList()[0]; -// ASSERT_EQ(NewBlock->next, nullptr); -// ASSERT_EQ(NewBlock->prev, nullptr); -// -// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); -// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); -// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, newHeader->sizeAndFlags); -// ASSERT_EQ(newData, str); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); -// ASSERT_EQ(16, a.BytesRemaining()); -// -// // Edge cases -// -// // Empty allocator -// Allocator 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 -// Allocator overflowAlloc(SIZE_T_MAX); -// void* overflowPtr = overflowAlloc.Allocate(SIZE_T_MAX); -// ASSERT_FALSE(overflowPtr); -// -// void* tooLargeCase = a.Allocate(100); -// ASSERT_FALSE(tooLargeCase); -// -// Allocator tooSmallAlloc(64); -// void* tooLargeAlloc = a.Allocate(100); -// ASSERT_FALSE(tooLargeCase); -//} -// -//UTEST(test_ResourceSystem, TestDeallocateFunction) -//{ -// Allocator a(64); -// ASSERT_NE(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 64); -// ASSERT_EQ(64, a.BytesRemaining()); -// -// 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(a.FlBitmask(), 2); -// ASSERT_EQ(16, a.SlBitmask()[1]); -// -// FreeBlockNode* block = a.FreeList()[(1 * 16) + 4]; -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// ASSERT_EQ(40, a.BytesRemaining()); -// -// auto header = (BlockHeader*)a.Data(); -// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); -// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); -// -// a.Deallocate(p); -// -// // Should be empty -// ASSERT_EQ(513, header->sizeAndFlags); -// ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); -// ASSERT_FALSE(p); -// ASSERT_EQ(a.FlBitmask(), 4); -// ASSERT_EQ(a.SlBitmask()[2], 1); -// 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(a.FlBitmask(), 2); -// ASSERT_EQ(16, a.SlBitmask()[1]); -// -// block = a.FreeList()[(1 * 16) + 4]; -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// ASSERT_EQ(40, a.BytesRemaining()); -// -// header = (BlockHeader*)a.Data(); -// data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); -// footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); -// -// 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]); -// -// FreeBlockNode* NewBlock = a.FreeList()[0]; -// ASSERT_EQ(NewBlock->next, nullptr); -// ASSERT_EQ(NewBlock->prev, nullptr); -// ASSERT_EQ(16, a.BytesRemaining()); -// -// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); -// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); -// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, newHeader->sizeAndFlags); -// ASSERT_EQ(newData, str); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->totalBlockSize); -// -// a.Deallocate(p); -// ASSERT_FALSE(p); -// ASSERT_TRUE(newHeader->sizeAndFlags & IS_PREV_FREE_FLAG); -// ASSERT_EQ(194, newHeader->sizeAndFlags); -// ASSERT_TRUE(header->sizeAndFlags & IS_FREE_FLAG); -// FreeBlockNode* newFreeBlock = TO_FREEBLOCK((TO_BYTES(header)+sizeof(BlockHeader))); -// ASSERT_EQ(newFreeBlock->next, nullptr); -// ASSERT_EQ(newFreeBlock->prev, nullptr); -// ASSERT_EQ(40, a.BytesRemaining()); -// -// a.Deallocate(str); -// ASSERT_FALSE(str); -// ASSERT_EQ(513, header->sizeAndFlags); -// ASSERT_EQ(4, a.FlBitmask()); -// ASSERT_EQ(1, a.SlBitmask()[2]); -// ASSERT_EQ(64, a.BytesRemaining()); -//} -// -//UTEST(test_ResourceSystem, TestBlockCoalescing) -//{ -// Allocator a(128); -// ASSERT_NE(a.Data(), nullptr); -// ASSERT_EQ(a.Capacity(), 128); -// ASSERT_EQ(128, a.BytesRemaining()); -// -// 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(0, a.SlBitmask()[1]); -// -// FreeBlockNode* block = a.FreeList()[42]; -// ASSERT_EQ(block->next, nullptr); -// ASSERT_EQ(block->prev, nullptr); -// ASSERT_EQ(104, a.BytesRemaining()); -// -// auto header = (BlockHeader*)a.Data(); -// auto data = (TestStruct*) (((unsigned char*)header) + sizeof(BlockHeader)); -// auto footer = (BlockFooter*) (((unsigned char*)data) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(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(0, a.SlBitmask()[1]); -// -// FreeBlockNode* NewBlock = a.FreeList()[36]; -// ASSERT_EQ(NewBlock->next, nullptr); -// ASSERT_EQ(NewBlock->prev, nullptr); -// ASSERT_EQ(80, a.BytesRemaining()); -// -// auto newHeader = (BlockHeader*)(TO_BYTES(footer) + sizeof(BlockFooter)); -// auto newData = (Siege::String*) (((unsigned char*)newHeader) + sizeof(BlockHeader)); -// auto newFooter = (BlockFooter*) (((unsigned char*)newData) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, newHeader->sizeAndFlags); -// ASSERT_EQ(newData, str); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(Siege::String) + sizeof(BlockFooter), newFooter->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(a.FlBitmask(), 2); -// ASSERT_EQ(4096, a.SlBitmask()[1]); -// -// FreeBlockNode* newFreeBlock = a.FreeList()[28]; -// ASSERT_EQ(newFreeBlock->next, nullptr); -// ASSERT_EQ(newFreeBlock->prev, nullptr); -// ASSERT_EQ(56, a.BytesRemaining()); -// -// auto newHeader2 = (BlockHeader*)(TO_BYTES(newFooter) + sizeof(BlockFooter)); -// auto newData2 = (TestStruct*) (((unsigned char*)newHeader2) + sizeof(BlockHeader)); -// auto newFooter2 = (BlockFooter*) (((unsigned char*)newData2) + sizeof(TestStruct)); -// -// ASSERT_EQ(192, header->sizeAndFlags); -// ASSERT_EQ(data, p); -// ASSERT_EQ(sizeof(BlockHeader) + sizeof(TestStruct) + sizeof(BlockFooter), footer->totalBlockSize); -// -// a.Deallocate(p); -// -// FreeBlockNode* firstFree = a.FreeList()[8]; -// ASSERT_EQ(firstFree->next, nullptr); -// ASSERT_EQ(firstFree->prev, nullptr); -// ASSERT_EQ(80, a.BytesRemaining()); -// -// BlockHeader* firstFreeHeader = TO_HBLOCK((TO_BYTES(firstFree) - sizeof(BlockHeader))); -// ASSERT_TRUE(firstFreeHeader->sizeAndFlags & IS_FREE_FLAG); -// ASSERT_FALSE(firstFreeHeader->sizeAndFlags & IS_PREV_FREE_FLAG); -// ASSERT_FALSE(p); -// ASSERT_EQ(a.FlBitmask(), 3); -// ASSERT_EQ(256, a.SlBitmask()[0]); -// -// a.Deallocate(p2); -// ASSERT_FALSE(p2); -// ASSERT_EQ(104, a.BytesRemaining()); -// ASSERT_EQ(a.FlBitmask(), 5); -// ASSERT_EQ(256, a.SlBitmask()[0]); -// ASSERT_EQ(16, a.SlBitmask()[2]); -// -// a.Deallocate(str); -// ASSERT_FALSE(str); -// ASSERT_EQ(128, a.BytesRemaining()); -// ASSERT_EQ(a.FlBitmask(), 8); -// ASSERT_EQ(a.SlBitmask()[3], 1); -// -// // Edge Cases -// -// void* badPointer = nullptr; -// a.Deallocate(badPointer); -// ASSERT_FALSE(badPointer); -// -// // try to deallocate pointer not in allocator; -// -// uint64_t* val = new uint64_t; -// a.Deallocate(val); // Should do nothing and be ignored. -// free(val); -//} -// -//UTEST(test_ResourceSystem, TestAllocationWhenNoAppropriateFragmentExists) -//{ -// Allocator 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.BytesRemaining()); -// -// a.Deallocate(p0); -// a.Deallocate(p2); -// -// ASSERT_EQ(48, a.BytesRemaining()); -// void* tooLargeVal = a.Allocate(24); -// ASSERT_FALSE(tooLargeVal); -//} -// -//UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) -//{ -// Allocator a(1024 * 1024); -// -// unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); -// //std::default_random_engine generator(seed); -// 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); -// } -// } -// 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); -// -// a.Deallocate(ptrToFree); -// ASSERT_FALSE(ptrToFree); -// pointers.erase(pointers.begin() + index); -// } -// } -// -// for (void* ptr : pointers) -// { -// a.Deallocate(ptr); -// } -// -// ASSERT_EQ(a.Capacity(), a.BytesRemaining()); -//} \ No newline at end of file +} \ No newline at end of file diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index 2bee0bde..3e3acbc4 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace Siege; From 645bd7b3e498d5cbd535fa697c8b5d9bca467122 Mon Sep 17 00:00:00 2001 From: Jonathan Moallem Date: Sun, 14 Sep 2025 17:15:28 +1000 Subject: [PATCH 10/15] Ran format --- engine/utils/TlsfAllocator.cpp | 48 +++++---- engine/utils/TlsfAllocator.h | 38 +++++-- tests/src/resources/test_ResourceSystem.cpp | 3 +- tests/src/utils/test_TlsfAllocator.cpp | 111 +++++++++++--------- 4 files changed, 121 insertions(+), 79 deletions(-) diff --git a/engine/utils/TlsfAllocator.cpp b/engine/utils/TlsfAllocator.cpp index cd9482dd..41cd5f52 100644 --- a/engine/utils/TlsfAllocator.cpp +++ b/engine/utils/TlsfAllocator.cpp @@ -8,10 +8,11 @@ // #include "TlsfAllocator.h" -#include "Logging.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) @@ -39,7 +40,8 @@ TlsfAllocator::TlsfAllocator() {} TlsfAllocator::TlsfAllocator(const uint64_t size) { uint64_t paddedSize = PAD_SIZE(size); - if (size == 0 || size < MIN_ALLOCATION || size > (UINT64_MAX - METADATA_OVERHEAD) || paddedSize < size) + if (size == 0 || size < MIN_ALLOCATION || size > (UINT64_MAX - METADATA_OVERHEAD) || + paddedSize < size) return; uint64_t maxBuckets = (FL(size) - FL_MIN_INDEX) + 1; @@ -57,18 +59,19 @@ TlsfAllocator::TlsfAllocator(const uint64_t size) data = TO_BYTES(calloc(1, allocSize)); - CC_ASSERT(data, "Allocation returned null. This should not happen and implies an implementation failure.") + 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); + freeList = (FreeBlockNode**) (data + paddedSize); + slBitmasks = (uint16_t*) (data + paddedSize + freeListSize); CreateHeader(data, paddedSize, FREE); AddNewBlock(paddedSize, TO_HEADER(data)); } -void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, - HeaderFlags flags) +void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags) { if (!ptr) return; BlockHeader* header = TO_HEADER(ptr); @@ -78,8 +81,9 @@ void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, void* TlsfAllocator::Allocate(const uint64_t& size) { uint64_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); - if (!data || capacity == 0 ||size == 0 || requiredSize < size || requiredSize > totalBytesRemaining - || size < MIN_ALLOCATION) return nullptr; + if (!data || capacity == 0 || size == 0 || requiredSize < size || + requiredSize > totalBytesRemaining || size < MIN_ALLOCATION) + return nullptr; FreeBlockNode* block = FindFreeBlock(requiredSize); @@ -96,7 +100,8 @@ void* TlsfAllocator::Allocate(const uint64_t& size) return ptr; } -TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, uint64_t& allocatedSize) +TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, + uint64_t& allocatedSize) { if (!node) return nullptr; @@ -106,8 +111,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBloc if (!RemoveFreeBlock(node)) return nullptr; - if (oldSize <= allocatedSize || ((oldSize - allocatedSize) < - (HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) + if (oldSize <= allocatedSize || + ((oldSize - allocatedSize) < (HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) { allocatedSize = oldSize; return header; @@ -122,7 +127,7 @@ TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBloc void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) { - uint64_t fl = 0,sl = 0, index; + uint64_t fl = 0, sl = 0, index; index = CalculateFreeBlockIndices(size, fl, sl); CreateHeader(TO_BYTES(header), size, FREE); @@ -209,14 +214,15 @@ const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) fl = __builtin_ctzll(fl); CC_ASSERT(slBitmasks[fl] > 0, - "SlBitmasks is returning 0. This should not be happening and indicates an implementation error.") + "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; } -uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl) +uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl) { uint64_t rawFl = FL(size); fl = rawFl - FL_MIN_INDEX; @@ -253,7 +259,7 @@ TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHea 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; + if (!IsValid(prevHeader)) return nullptr; return TO_HEADER(prevHeader); } @@ -347,8 +353,14 @@ bool TlsfAllocator::IsValid(uint8_t* ptr) return ptr && ptr >= data && ptr < (data + (capacity + HEADER_SIZE + FOOTER_SIZE)); } -const uint64_t TlsfAllocator::Capacity() { return capacity; } +const uint64_t TlsfAllocator::Capacity() +{ + return capacity; +} -const uint64_t TlsfAllocator::BytesRemaining() { return bytesRemaining; } +const uint64_t TlsfAllocator::BytesRemaining() +{ + return bytesRemaining; +} } // namespace Siege \ No newline at end of file diff --git a/engine/utils/TlsfAllocator.h b/engine/utils/TlsfAllocator.h index 9f9d51df..8c8b3d99 100644 --- a/engine/utils/TlsfAllocator.h +++ b/engine/utils/TlsfAllocator.h @@ -11,6 +11,7 @@ #define SIEGE_ENGINE_TLSFALLOCATOR_H #include + #include "Macros.h" namespace Siege @@ -19,6 +20,7 @@ namespace Siege class TlsfAllocator { public: + enum HeaderFlags { FULL = 0, @@ -32,7 +34,8 @@ class TlsfAllocator FreeBlockNode* prev {nullptr}; }; - struct BlockHeader { + struct BlockHeader + { uint32_t sizeAndFlags {0}; }; @@ -62,7 +65,7 @@ class TlsfAllocator bool IsFree(BlockHeader* header); bool IsFree(uint64_t fl, uint64_t sl); bool PrevBlockIsFree(BlockHeader* header); - uint64_t CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl); + uint64_t CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl); // Buffer manipulation Functions @@ -83,7 +86,7 @@ class TlsfAllocator template void Deallocate(T*& ptr) { - uint8_t* raw = (uint8_t*)ptr; + uint8_t* raw = (uint8_t*) ptr; if (!raw) return; if (raw < data || raw >= (data + capacity)) return; @@ -102,7 +105,7 @@ class TlsfAllocator BlockHeader* nextHeader = GetNextHeader(header); - if (nextHeader && ((uint8_t*)nextHeader < (data + capacity))) + if (nextHeader && ((uint8_t*) nextHeader < (data + capacity))) { nextHeader->sizeAndFlags |= PREV_IS_FREE; } @@ -125,12 +128,29 @@ class TlsfAllocator const uint64_t GetHeaderSize(BlockHeader* header); const uint64_t Capacity(); const uint64_t BytesRemaining(); - const uint64_t TotalBytesRemaining() { return totalBytesRemaining; } - const uint64_t TotalSize() { return totalSize; } - const uint8_t* Data() { return data; } - const uint64_t FlBitmask() { return flBitmask; } - const uint16_t& SlBitmask(const uint64_t fl) { return slBitmasks[fl]; } + const uint64_t TotalBytesRemaining() + { + return totalBytesRemaining; + } + const uint64_t TotalSize() + { + return totalSize; + } + const uint8_t* Data() + { + return data; + } + const uint64_t FlBitmask() + { + return flBitmask; + } + const uint16_t& SlBitmask(const uint64_t fl) + { + return slBitmasks[fl]; + } + private: + uint64_t totalSize {0}; uint64_t totalBytesRemaining {0}; diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 67949efc..04804b7c 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -16,6 +16,7 @@ #include #include #include + #include using namespace Siege; @@ -561,4 +562,4 @@ UTEST_F(test_ResourceSystem, LoadAnimationData) ASSERT_NEAR(expectedKey.rotation.w, actualKey.rotation.w, 0.01); } } -} \ No newline at end of file +} diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index 3e3acbc4..b576c300 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -8,11 +8,11 @@ // #include -#include #include +#include -#include #include +#include using namespace Siege; @@ -22,7 +22,6 @@ struct TestStruct uint64_t intb {0}; }; - UTEST(test_TlsfAllocator, EmptyConstructor) { TlsfAllocator a; @@ -89,31 +88,34 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (TlsfAllocator::BlockHeader*)a.Data(); - auto data = (TestStruct*)a.GetBlockData(header); + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + sizeof(TlsfAllocator::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_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 strData = (String*) a.GetBlockData(strHeader); auto strFooter = a.GetFooter(strHeader); ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); + ASSERT_EQ( + sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); // Edge cases @@ -143,32 +145,32 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) UTEST(test_TlsfAllocator, TestAllocateFunctionWithRandomInputs) { - TlsfAllocator a(1024 * 1024); + TlsfAllocator a(1024 * 1024); - unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); - std::default_random_engine generator(12345); + unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); + std::default_random_engine generator(12345); - std::uniform_int_distribution sizeDist(1, 256); + std::uniform_int_distribution sizeDist(1, 256); - TlsfAllocator::BlockHeader* header = (TlsfAllocator::BlockHeader*)a.Data(); + TlsfAllocator::BlockHeader* header = (TlsfAllocator::BlockHeader*) a.Data(); - for(int i = 0; i < 10000; i++) - { - size_t randomSize = sizeDist(generator); - void* ptr = a.Allocate(randomSize); + for (int i = 0; i < 10000; i++) + { + size_t randomSize = sizeDist(generator); + void* ptr = a.Allocate(randomSize); - if (ptr) - { - *((uint32_t*)ptr) = 0xDEADC0DE; + if (ptr) + { + *((uint32_t*) ptr) = 0xDEADC0DE; - uint64_t expectedSize = header->sizeAndFlags >> 3; - uint32_t* data = (uint32_t*)a.GetBlockData(header); - ASSERT_EQ(0xDEADC0DE, *data); - TlsfAllocator::BlockFooter* footer = a.GetFooter(header); - header = a.GetNextHeader(header); - } - else continue; + uint64_t expectedSize = header->sizeAndFlags >> 3; + uint32_t* data = (uint32_t*) a.GetBlockData(header); + ASSERT_EQ(0xDEADC0DE, *data); + TlsfAllocator::BlockFooter* footer = a.GetFooter(header); + header = a.GetNextHeader(header); } + else continue; + } } UTEST(test_TlsfAllocator, TestDeallocateFunction) @@ -196,14 +198,15 @@ UTEST(test_TlsfAllocator, TestDeallocateFunction) ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (TlsfAllocator::BlockHeader*)a.Data(); - auto data = (TestStruct*)a.GetBlockData(header); + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + sizeof(TlsfAllocator::BlockFooter), + footer->totalBlockSize); ASSERT_EQ(48, a.BytesRemaining()); a.Deallocate(p); @@ -230,31 +233,34 @@ UTEST(test_TlsfAllocator, TestDeallocateFunction) ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - header = (TlsfAllocator::BlockHeader*)a.Data(); - data = (TestStruct*)a.GetBlockData(header); + header = (TlsfAllocator::BlockHeader*) a.Data(); + data = (TestStruct*) a.GetBlockData(header); footer = a.GetFooter(header); ASSERT_EQ(192, header->sizeAndFlags); ASSERT_EQ(data, p); ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + sizeof(TlsfAllocator::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_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 strData = (String*) a.GetBlockData(strHeader); auto strFooter = a.GetFooter(strHeader); ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); + ASSERT_EQ( + sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); a.Deallocate(p); @@ -307,19 +313,20 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(block->prev, nullptr); ASSERT_EQ(112, a.BytesRemaining()); - auto header = (TlsfAllocator::BlockHeader*)a.Data(); - auto data = (TestStruct*)a.GetBlockData(header); + auto header = (TlsfAllocator::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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + sizeof(TlsfAllocator::BlockFooter), + footer->totalBlockSize); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); *str = "Hello There!"; - ASSERT_STREQ(str->Str(),"Hello There!"); + ASSERT_STREQ(str->Str(), "Hello There!"); ASSERT_EQ(4, a.FlBitmask()); ASSERT_EQ(64, a.SlBitmask(2)); @@ -330,13 +337,14 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(96, a.BytesRemaining()); auto strHeader = a.GetNextHeader(header); - auto strData = (String*)a.GetBlockData(strHeader); + auto strData = (String*) a.GetBlockData(strHeader); auto strFooter = a.GetFooter(strHeader); ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(Siege::String) + - sizeof(TlsfAllocator::BlockFooter), strFooter->totalBlockSize); + sizeof(TlsfAllocator::BlockFooter), + strFooter->totalBlockSize); TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -355,13 +363,14 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(80, a.BytesRemaining()); auto newHeader = a.GetNextHeader(strHeader); - auto newData = (String*)a.GetBlockData(newHeader); + auto newData = (String*) a.GetBlockData(newHeader); auto newFooter = a.GetFooter(newHeader); ASSERT_EQ(192, newHeader->sizeAndFlags); ASSERT_EQ(data, p); ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), footer->totalBlockSize); + sizeof(TlsfAllocator::BlockFooter), + footer->totalBlockSize); a.Deallocate(p); @@ -370,7 +379,7 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(firstFree->prev, nullptr); ASSERT_EQ(96, a.BytesRemaining()); - TlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*)firstFree); + TlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); ASSERT_TRUE(firstFreeHeader->sizeAndFlags & TlsfAllocator::FREE); ASSERT_FALSE(firstFreeHeader->sizeAndFlags & TlsfAllocator::PREV_IS_FREE); @@ -438,7 +447,7 @@ UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) std::vector pointers; pointers.reserve(10000); - for(int i = 0; i < 10000; i++) + for (int i = 0; i < 10000; i++) { int action = actionDist(generator); @@ -449,18 +458,18 @@ UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) void* ptr = a.Allocate(randomSize); if (ptr) { - *((uint32_t*)ptr) = 0xDEADC0DE; + *((uint32_t*) ptr) = 0xDEADC0DE; pointers.push_back(ptr); - TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*)ptr); + TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); } } else if (action <= 10 && !pointers.empty()) { - std::uniform_int_distribution indices(0, pointers.size()-1); + std::uniform_int_distribution indices(0, pointers.size() - 1); size_t index = indices(generator); void*& ptrToFree = pointers[index]; - ASSERT_EQ(0xDEADC0DE, *(uint32_t*)ptrToFree); + ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); a.Deallocate(ptrToFree); From 54669c747e93340d73a611903e4a0c728100bc02 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 31 Aug 2025 14:06:36 +1000 Subject: [PATCH 11/15] Split TLSF Allocator into size classes --- .../Tlsf.cpp} | 155 ++- .../{TlsfAllocator.h => allocators/Tlsf.h} | 62 +- tests/src/utils/test_TlsfAllocator.cpp | 1082 +++++++++++++++-- 3 files changed, 1139 insertions(+), 160 deletions(-) rename engine/utils/{TlsfAllocator.cpp => allocators/Tlsf.cpp} (63%) rename engine/utils/{TlsfAllocator.h => allocators/Tlsf.h} (71%) diff --git a/engine/utils/TlsfAllocator.cpp b/engine/utils/allocators/Tlsf.cpp similarity index 63% rename from engine/utils/TlsfAllocator.cpp rename to engine/utils/allocators/Tlsf.cpp index 41cd5f52..ce51b9e2 100644 --- a/engine/utils/TlsfAllocator.cpp +++ b/engine/utils/allocators/Tlsf.cpp @@ -7,11 +7,9 @@ // https://opensource.org/licenses/Zlib // -#include "TlsfAllocator.h" - +#include "Tlsf.h" #include - -#include "Logging.h" +#include "../Logging.h" #define TO_BYTES(val) reinterpret_cast(val) #define TO_HEADER(val) reinterpret_cast(val) @@ -24,27 +22,30 @@ #define METADATA_OVERHEAD HEADER_SIZE + FOOTER_SIZE #define PAD_SIZE(size) size + METADATA_OVERHEAD -#define FL_MIN_INDEX 4 #define MAX_SL_BUCKETS 16 #define FL(size) 63 - __builtin_clzll(size) -#define SL(size, fl) (size >> fl) & ((1 << FL_MIN_INDEX) - 1); +#define SL(size, fl) (size >> fl) & ((1 << MIN_SIZE_INDEX) - 1); #define FLAG_OFFSET 3 -#define MIN_ALLOCATION 16 -#define INVALID_INDEX UINT64_MAX +#define INVALID_INDEX(T) std::numeric_limits::max() +#define MIN_ALLOCATION_SIZE 16 namespace Siege { +template +uint8_t TlsfAllocator::MIN_SIZE_INDEX = sizeof(T) < 4 ? 4 : sizeof(T); -TlsfAllocator::TlsfAllocator() {} +template +TlsfAllocator::TlsfAllocator() {} -TlsfAllocator::TlsfAllocator(const uint64_t size) +template +TlsfAllocator::TlsfAllocator(const uint64_t size) { uint64_t paddedSize = PAD_SIZE(size); - if (size == 0 || size < MIN_ALLOCATION || size > (UINT64_MAX - METADATA_OVERHEAD) || + if (size == 0 || size < MIN_ALLOCATION_SIZE || size > (INVALID_INDEX(T) - METADATA_OVERHEAD) || paddedSize < size) return; - uint64_t maxBuckets = (FL(size) - FL_MIN_INDEX) + 1; + uint64_t maxBuckets = (FL(size) - MIN_SIZE_INDEX) + 1; uint64_t slBucketSize = sizeof(uint16_t) * maxBuckets; uint64_t freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); @@ -71,18 +72,20 @@ TlsfAllocator::TlsfAllocator(const uint64_t size) AddNewBlock(paddedSize, TO_HEADER(data)); } -void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags) +template +void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags) { if (!ptr) return; BlockHeader* header = TO_HEADER(ptr); header->sizeAndFlags = (size << FLAG_OFFSET) | flags; } -void* TlsfAllocator::Allocate(const uint64_t& size) +template +void* TlsfAllocator::Allocate(const uint64_t& size) { uint64_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); if (!data || capacity == 0 || size == 0 || requiredSize < size || - requiredSize > totalBytesRemaining || size < MIN_ALLOCATION) + requiredSize > totalBytesRemaining || size < MIN_ALLOCATION_SIZE) return nullptr; FreeBlockNode* block = FindFreeBlock(requiredSize); @@ -100,7 +103,43 @@ void* TlsfAllocator::Allocate(const uint64_t& size) return ptr; } -TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, +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; + + uint64_t blockSize = GetHeaderSize(header); + + uint64_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, uint64_t& allocatedSize) { if (!node) return nullptr; @@ -125,7 +164,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBloc return header; } -void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) +template +void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) { uint64_t fl = 0, sl = 0, index; index = CalculateFreeBlockIndices(size, fl, sl); @@ -141,7 +181,8 @@ void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) slBitmasks[fl] |= (1 << sl); } -TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT uint64_t& size) +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT uint64_t& size) { BlockHeader* start = header; BlockHeader* prev = GetPrevHeader(start); @@ -163,7 +204,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT return start; } -bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) +template +bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) { BlockHeader* header = GetHeader(node); @@ -189,19 +231,21 @@ bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) return true; } -TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size) +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size) { uint64_t fl, sl, index; // 1000 0000 0000 1000 index = CalculateFreeBlockIndices(size, fl, sl); if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); - if (index == INVALID_INDEX) return nullptr; + if (index == INVALID_INDEX(T)) return nullptr; return freeList[index]; } -const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) +template +const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) { sl = __builtin_ctz(slBitmasks[fl] & ~(((1 << (sl + 1)) - 1))); @@ -210,7 +254,7 @@ const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) fl = flBitmask & ~(((1ULL << (fl + 1)) - 1)); - if (!fl) return INVALID_INDEX; + if (!fl) return INVALID_INDEX(T); fl = __builtin_ctzll(fl); CC_ASSERT(slBitmasks[fl] > 0, @@ -222,22 +266,25 @@ const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) return fl * MAX_SL_BUCKETS + sl; } -uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl) +template +uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl) { uint64_t rawFl = FL(size); - fl = rawFl - FL_MIN_INDEX; + fl = rawFl - MIN_SIZE_INDEX; sl = SL(size, fl); return fl * MAX_SL_BUCKETS + sl; } -void TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size) +template +void TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size) { if (!ptr) return; BlockFooter* footer = TO_FOOTER(ptr); footer->totalBlockSize = size; } -TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node) +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node) { if (!node) return nullptr; uint8_t* rawHeader = TO_BYTES(node) - HEADER_SIZE; @@ -245,7 +292,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNod return TO_HEADER(rawHeader); } -TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(uint8_t* ptr) +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(uint8_t* ptr) { if (!ptr) return nullptr; uint8_t* rawHeader = ptr - HEADER_SIZE; @@ -253,7 +301,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(uint8_t* ptr) return TO_HEADER(rawHeader); } -TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header) +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawPrevFooter = TO_BYTES(GetPrevFooter(header)); @@ -263,7 +312,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHea return TO_HEADER(prevHeader); } -TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header) +template +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawHeader = TO_BYTES(header); @@ -272,7 +322,8 @@ TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHea return TO_HEADER(next); } -TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, TlsfAllocator::FreeBlockNode* prev, TlsfAllocator::FreeBlockNode* next) { @@ -284,7 +335,8 @@ TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, return node; } -TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) { if (!header || !IsFree(header)) return nullptr; uint8_t* rawBlock = TO_BYTES(header) + HEADER_SIZE; @@ -292,12 +344,14 @@ TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header) return TO_FREE_BLOCK(rawBlock); } -TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl) +template +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl) { return freeList[fl * MAX_SL_BUCKETS + sl]; } -TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) +template +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint64_t size = GetHeaderSize(header); @@ -306,7 +360,8 @@ TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* return TO_FOOTER(rawFooter); } -TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header) +template +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawFooter = TO_BYTES(header) - FOOTER_SIZE; @@ -314,27 +369,32 @@ TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHea return TO_FOOTER(rawFooter); } -uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) +template +uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) { return TO_BYTES(header) + HEADER_SIZE; } -const uint64_t TlsfAllocator::GetHeaderSize(BlockHeader* header) +template +const uint64_t TlsfAllocator::GetHeaderSize(BlockHeader* header) { return header->sizeAndFlags >> FLAG_OFFSET; } -bool TlsfAllocator::IsFree(BlockHeader* header) +template +bool TlsfAllocator::IsFree(BlockHeader* header) { return header->sizeAndFlags & FREE; } -bool TlsfAllocator::IsFree(uint64_t fl, uint64_t sl) +template +bool TlsfAllocator::IsFree(uint64_t fl, uint64_t sl) { return slBitmasks[fl] & (1 << sl); } -bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) +template +bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) { uint8_t* raw = TO_BYTES(header); if (raw == data) return false; @@ -348,19 +408,26 @@ bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header) return IsFree(TO_HEADER(rawPrevHeader)); } -bool TlsfAllocator::IsValid(uint8_t* ptr) +template +bool TlsfAllocator::IsValid(uint8_t* ptr) { return ptr && ptr >= data && ptr < (data + (capacity + HEADER_SIZE + FOOTER_SIZE)); } -const uint64_t TlsfAllocator::Capacity() +template +const uint64_t TlsfAllocator::Capacity() { return capacity; } -const uint64_t TlsfAllocator::BytesRemaining() +template +const uint64_t TlsfAllocator::BytesRemaining() { return bytesRemaining; } -} // namespace Siege \ No newline at end of file +template class TlsfAllocator; +template class TlsfAllocator; +template class TlsfAllocator; +} + diff --git a/engine/utils/TlsfAllocator.h b/engine/utils/allocators/Tlsf.h similarity index 71% rename from engine/utils/TlsfAllocator.h rename to engine/utils/allocators/Tlsf.h index 8c8b3d99..93393c71 100644 --- a/engine/utils/TlsfAllocator.h +++ b/engine/utils/allocators/Tlsf.h @@ -7,20 +7,21 @@ // https://opensource.org/licenses/Zlib // -#ifndef SIEGE_ENGINE_TLSFALLOCATOR_H -#define SIEGE_ENGINE_TLSFALLOCATOR_H +#ifndef SIEGE_ENGINE_TLSF_H +#define SIEGE_ENGINE_TLSF_H #include -#include "Macros.h" +#include "../Macros.h" + +#define FREE(ptr) Deallocate((void**)&ptr) namespace Siege { - +template class TlsfAllocator { public: - enum HeaderFlags { FULL = 0, @@ -36,12 +37,12 @@ class TlsfAllocator struct BlockHeader { - uint32_t sizeAndFlags {0}; + T sizeAndFlags {0}; }; struct BlockFooter { - uint32_t totalBlockSize {0}; + T totalBlockSize {0}; }; // S'tructors @@ -82,41 +83,8 @@ class TlsfAllocator // Allocate/Deallocate void* Allocate(const uint64_t& size); + void Deallocate(void** ptr); - template - void Deallocate(T*& 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; - - uint64_t blockSize = GetHeaderSize(header); - - uint64_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; - } BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t& allocatedSize); bool RemoveFreeBlock(FreeBlockNode* node); void AddNewBlock(const uint64_t size, BlockHeader* currentNode); @@ -151,6 +119,8 @@ class TlsfAllocator private: + static uint8_t MIN_SIZE_INDEX; + uint64_t totalSize {0}; uint64_t totalBytesRemaining {0}; @@ -165,6 +135,12 @@ class TlsfAllocator uint16_t* slBitmasks {nullptr}; }; -} // namespace Siege +// Maximum buffer possible here is around 8KB. +typedef TlsfAllocator SmallTlsfAllocator; +// Maximum buffer possible here is around 512MB. +typedef TlsfAllocator MediumTlsfAllocator; +// Maximum buffer possible here is around 2,147,483.6GB. +typedef TlsfAllocator LargeTlsfAllocator; +} -#endif // SIEGE_ENGINE_TLSFALLOCATOR_H +#endif // SIEGE_ENGINE_TLSF_H diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index b576c300..f5673e34 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -22,17 +22,17 @@ struct TestStruct uint64_t intb {0}; }; -UTEST(test_TlsfAllocator, EmptyConstructor) +UTEST(test_MediumTlsfAllocator, EmptyConstructor) { - TlsfAllocator a; + MediumTlsfAllocator a; ASSERT_EQ(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 0); ASSERT_EQ(0, a.BytesRemaining()); } -UTEST(test_TlsfAllocator, ConstructorWithSize) +UTEST(test_MediumTlsfAllocator, ConstructorWithSize) { - TlsfAllocator a(64); + MediumTlsfAllocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.TotalSize(), 72); ASSERT_EQ(64, a.BytesRemaining()); @@ -40,32 +40,32 @@ UTEST(test_TlsfAllocator, ConstructorWithSize) ASSERT_EQ(a.FlBitmask(), 4); ASSERT_EQ(4, a.SlBitmask(2)); - TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 2); + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 2); ASSERT_TRUE(block); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - TlsfAllocator::BlockHeader* header = a.GetHeader(block); + 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)); - TlsfAllocator::BlockFooter* footer = a.GetFooter(header); + MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); ASSERT_TRUE(footer); ASSERT_EQ(72, footer->totalBlockSize); } -UTEST(test_TlsfAllocator, ConstructorWithTooSmallSize) +UTEST(test_MediumTlsfAllocator, ConstructorWithTooSmallSize) { - Siege::TlsfAllocator a(15); + Siege::MediumTlsfAllocator a(7); ASSERT_EQ(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 0); } -UTEST(test_TlsfAllocator, TestAllocateFunction) +UTEST(test_MediumTlsfAllocator, TestAllocateFunction) { - TlsfAllocator a(64); + MediumTlsfAllocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 64); ASSERT_EQ(64, a.BytesRemaining()); @@ -84,18 +84,18 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_EQ(2, a.FlBitmask()); ASSERT_EQ(256, a.SlBitmask(1)); - TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (TlsfAllocator::BlockHeader*) a.Data(); + 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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), footer->totalBlockSize); ASSERT_EQ(a.BytesRemaining(), 48); @@ -114,14 +114,14 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ( - sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), + sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); // Edge cases // Empty allocator - TlsfAllocator emptyA; + MediumTlsfAllocator emptyA; TestStruct* ptr = (TestStruct*) emptyA.Allocate(sizeof(TestStruct)); ASSERT_FALSE(ptr); @@ -131,28 +131,28 @@ UTEST(test_TlsfAllocator, TestAllocateFunction) ASSERT_FALSE(emptyAllocPtr); // Allocate an amount that causes a value overflow - TlsfAllocator overflowAlloc(UINT64_MAX); + MediumTlsfAllocator overflowAlloc(UINT64_MAX); void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); ASSERT_FALSE(overflowPtr); void* tooLargeCase = a.Allocate(100); ASSERT_FALSE(tooLargeCase); - TlsfAllocator tooSmallAlloc(64); + MediumTlsfAllocator tooSmallAlloc(64); void* tooLargeAlloc = tooSmallAlloc.Allocate(100); ASSERT_FALSE(tooLargeAlloc); } -UTEST(test_TlsfAllocator, TestAllocateFunctionWithRandomInputs) +UTEST(Test_MediumTlsfAllocator, TestAllocateFunctionWithRandomInputs) { - TlsfAllocator a(1024 * 1024); + 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); - TlsfAllocator::BlockHeader* header = (TlsfAllocator::BlockHeader*) a.Data(); + MediumTlsfAllocator::BlockHeader* header = (MediumTlsfAllocator::BlockHeader*) a.Data(); for (int i = 0; i < 10000; i++) { @@ -166,16 +166,16 @@ UTEST(test_TlsfAllocator, TestAllocateFunctionWithRandomInputs) uint64_t expectedSize = header->sizeAndFlags >> 3; uint32_t* data = (uint32_t*) a.GetBlockData(header); ASSERT_EQ(0xDEADC0DE, *data); - TlsfAllocator::BlockFooter* footer = a.GetFooter(header); + MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); header = a.GetNextHeader(header); } else continue; } } -UTEST(test_TlsfAllocator, TestDeallocateFunction) +UTEST(Test_MediumTlsfAllocator, TestDeallocateFunction) { - TlsfAllocator a(64); + MediumTlsfAllocator a(64); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 64); ASSERT_EQ(64, a.BytesRemaining()); @@ -194,26 +194,26 @@ UTEST(test_TlsfAllocator, TestDeallocateFunction) ASSERT_EQ(2, a.FlBitmask()); ASSERT_EQ(256, a.SlBitmask(1)); - TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (TlsfAllocator::BlockHeader*) a.Data(); + 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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), footer->totalBlockSize); ASSERT_EQ(48, a.BytesRemaining()); - a.Deallocate(p); + a.FREE(p); // Should be empty ASSERT_EQ(577, header->sizeAndFlags); - ASSERT_TRUE(header->sizeAndFlags & TlsfAllocator::FREE); + ASSERT_TRUE(header->sizeAndFlags & MediumTlsfAllocator::FREE); ASSERT_FALSE(p); ASSERT_EQ(4, a.FlBitmask()); ASSERT_EQ(4, a.SlBitmask(2)); @@ -233,14 +233,14 @@ UTEST(test_TlsfAllocator, TestDeallocateFunction) ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - header = (TlsfAllocator::BlockHeader*) a.Data(); + 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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), footer->totalBlockSize); ASSERT_EQ(48, a.BytesRemaining()); @@ -259,37 +259,37 @@ UTEST(test_TlsfAllocator, TestDeallocateFunction) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ( - sizeof(TlsfAllocator::BlockHeader) + sizeof(String) + sizeof(TlsfAllocator::BlockFooter), + sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); - a.Deallocate(p); + a.FREE(p); ASSERT_FALSE(p); - ASSERT_TRUE(strHeader->sizeAndFlags & TlsfAllocator::PREV_IS_FREE); + ASSERT_TRUE(strHeader->sizeAndFlags & MediumTlsfAllocator::PREV_IS_FREE); ASSERT_EQ(193, header->sizeAndFlags); - ASSERT_TRUE(header->sizeAndFlags & TlsfAllocator::FREE); - TlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(header); + 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.Deallocate(str); + 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()); - TlsfAllocator::FreeBlockNode* newNode = (TlsfAllocator::FreeBlockNode*) data; + 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_TlsfAllocator, TestBlockCoalescing) +UTEST(Test_MediumTlsfAllocator, TestBlockCoalescing) { - TlsfAllocator a(128); + MediumTlsfAllocator a(128); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 128); ASSERT_EQ(128, a.BytesRemaining()); @@ -308,19 +308,19 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(4, a.FlBitmask()); ASSERT_EQ(4096, a.SlBitmask(2)); // 0001 0000 0000 0000 - TlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 12); + MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 12); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); ASSERT_EQ(112, a.BytesRemaining()); - auto header = (TlsfAllocator::BlockHeader*) a.Data(); + 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(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), footer->totalBlockSize); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); @@ -331,7 +331,7 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(4, a.FlBitmask()); ASSERT_EQ(64, a.SlBitmask(2)); - TlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(2, 6); + MediumTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(2, 6); ASSERT_EQ(NewBlock->next, nullptr); ASSERT_EQ(NewBlock->prev, nullptr); ASSERT_EQ(96, a.BytesRemaining()); @@ -342,8 +342,8 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(Siege::String) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(MediumTlsfAllocator::BlockFooter), strFooter->totalBlockSize); TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -357,7 +357,7 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(4, a.FlBitmask()); ASSERT_EQ(1, a.SlBitmask(2)); - TlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(2, 0); + MediumTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(2, 0); ASSERT_EQ(newFreeBlock->next, nullptr); ASSERT_EQ(newFreeBlock->prev, nullptr); ASSERT_EQ(80, a.BytesRemaining()); @@ -368,33 +368,33 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) ASSERT_EQ(192, newHeader->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(TlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(TlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(MediumTlsfAllocator::BlockFooter), footer->totalBlockSize); - a.Deallocate(p); + a.FREE(p); - TlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 8); + MediumTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 8); ASSERT_EQ(firstFree->next, nullptr); ASSERT_EQ(firstFree->prev, nullptr); ASSERT_EQ(96, a.BytesRemaining()); - TlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); - ASSERT_TRUE(firstFreeHeader->sizeAndFlags & TlsfAllocator::FREE); - ASSERT_FALSE(firstFreeHeader->sizeAndFlags & TlsfAllocator::PREV_IS_FREE); + 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.Deallocate(p2); + 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.Deallocate(str); + a.FREE(str); ASSERT_FALSE(str); ASSERT_EQ(128, a.BytesRemaining()); ASSERT_EQ(8, a.FlBitmask()); @@ -403,19 +403,19 @@ UTEST(test_TlsfAllocator, TestBlockCoalescing) // Edge Cases void* badPointer = nullptr; - a.Deallocate(badPointer); + a.FREE(badPointer); ASSERT_FALSE(badPointer); // try to deallocate pointer not in allocator; uint64_t* val = new uint64_t; - a.Deallocate(val); // Should do nothing and be ignored. + a.FREE(val); // Should do nothing and be ignored. free(val); } -UTEST(test_TlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) +UTEST(Test_MediumTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) { - TlsfAllocator a(128); + MediumTlsfAllocator a(128); ASSERT_NE(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 128); ASSERT_EQ(128, a.BytesRemaining()); @@ -426,8 +426,8 @@ UTEST(test_TlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) void* p3 = a.Allocate(32); ASSERT_EQ(0, a.TotalBytesRemaining()); - a.Deallocate(p0); - a.Deallocate(p2); + a.FREE(p0); + a.FREE(p2); ASSERT_EQ(64, a.BytesRemaining()); void* tooLargeVal = a.Allocate(24); @@ -436,7 +436,943 @@ UTEST(test_TlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) { - TlsfAllocator a(1024 * 1024); + 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(UINT64_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT64_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(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); + + 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()); + + 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(1024 * 1024); + + 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) +{ + MediumTlsfAllocator a; + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); + ASSERT_EQ(0, a.BytesRemaining()); +} + +UTEST(Test_LargeTlsfAllocator, 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_LargeTlsfAllocator, ConstructorWithTooSmallSize) +{ + Siege::MediumTlsfAllocator a(15); + ASSERT_EQ(a.Data(), nullptr); + ASSERT_EQ(a.Capacity(), 0); +} + +UTEST(Test_LargeTlsfAllocator, 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(UINT64_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT64_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_LargeTlsfAllocator, 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_LargeTlsfAllocator, 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_LargeTlsfAllocator, 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_LargeTlsfAllocator, 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()); + + a.FREE(p0); + a.FREE(p2); + + ASSERT_EQ(64, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(24); + ASSERT_FALSE(tooLargeVal); +} + +UTEST(Test_LargeTlsfAllocator, TestRandomAllocationsAndDeallocations) +{ + MediumTlsfAllocator a(1024 * 1024); unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); std::default_random_engine generator(seed); @@ -460,7 +1396,7 @@ UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) { *((uint32_t*) ptr) = 0xDEADC0DE; pointers.push_back(ptr); - TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); + MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); } } else if (action <= 10 && !pointers.empty()) @@ -470,10 +1406,10 @@ UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) void*& ptrToFree = pointers[index]; ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); - TlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); - a.Deallocate(ptrToFree); - TlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + a.FREE(ptrToFree); + MediumTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); ASSERT_FALSE(ptrToFree); pointers.erase(pointers.begin() + index); @@ -482,7 +1418,7 @@ UTEST(test_ResourceSystem, TestRandomAllocationsAndDeallocations) for (void* ptr : pointers) { - a.Deallocate(ptr); + a.FREE(ptr); } ASSERT_EQ(a.TotalSize(), a.TotalBytesRemaining()); From c7658b3687efac6f30c43d449871887e2fbc3f99 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 31 Aug 2025 15:17:48 +1000 Subject: [PATCH 12/15] Adjusted allocator input sizes to match size classes --- engine/utils/allocators/Tlsf.cpp | 62 +++--- engine/utils/allocators/Tlsf.h | 48 ++-- tests/src/utils/test_TlsfAllocator.cpp | 291 ++++++++++++------------- 3 files changed, 199 insertions(+), 202 deletions(-) diff --git a/engine/utils/allocators/Tlsf.cpp b/engine/utils/allocators/Tlsf.cpp index ce51b9e2..60836766 100644 --- a/engine/utils/allocators/Tlsf.cpp +++ b/engine/utils/allocators/Tlsf.cpp @@ -32,25 +32,25 @@ namespace Siege { template -uint8_t TlsfAllocator::MIN_SIZE_INDEX = sizeof(T) < 4 ? 4 : sizeof(T); +uint8_t TlsfAllocator::MIN_SIZE_INDEX = 4; template TlsfAllocator::TlsfAllocator() {} template -TlsfAllocator::TlsfAllocator(const uint64_t size) +TlsfAllocator::TlsfAllocator(const T size) { - uint64_t paddedSize = PAD_SIZE(size); + T paddedSize = PAD_SIZE(size); if (size == 0 || size < MIN_ALLOCATION_SIZE || size > (INVALID_INDEX(T) - METADATA_OVERHEAD) || paddedSize < size) return; - uint64_t maxBuckets = (FL(size) - MIN_SIZE_INDEX) + 1; + T maxBuckets = (FL(size) - MIN_SIZE_INDEX) + 1; - uint64_t slBucketSize = sizeof(uint16_t) * maxBuckets; - uint64_t freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); + T slBucketSize = sizeof(uint16_t) * maxBuckets; + T freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*); - uint64_t allocSize = paddedSize + slBucketSize + freeListSize; + T allocSize = paddedSize + slBucketSize + freeListSize; totalSize = paddedSize; totalBytesRemaining = paddedSize; @@ -73,7 +73,7 @@ TlsfAllocator::TlsfAllocator(const uint64_t size) } template -void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags) +void TlsfAllocator::CreateHeader(uint8_t* ptr, const T size, HeaderFlags flags) { if (!ptr) return; BlockHeader* header = TO_HEADER(ptr); @@ -81,9 +81,9 @@ void TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFla } template -void* TlsfAllocator::Allocate(const uint64_t& size) +void* TlsfAllocator::Allocate(const T& size) { - uint64_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); + T requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter); if (!data || capacity == 0 || size == 0 || requiredSize < size || requiredSize > totalBytesRemaining || size < MIN_ALLOCATION_SIZE) return nullptr; @@ -114,9 +114,9 @@ void TlsfAllocator::Deallocate(void** ptr) if (IsFree(header)) return; - uint64_t blockSize = GetHeaderSize(header); + T blockSize = GetHeaderSize(header); - uint64_t totalBlockSize = blockSize; + T totalBlockSize = blockSize; header = TryCoalesce(header, totalBlockSize); BlockFooter* footer = GetFooter(header); @@ -140,11 +140,11 @@ void TlsfAllocator::Deallocate(void** ptr) template typename TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, - uint64_t& allocatedSize) + T& allocatedSize) { if (!node) return nullptr; - uint64_t oldSize = GetHeaderSize(GetHeader(node)); + T oldSize = GetHeaderSize(GetHeader(node)); BlockHeader* header = GetHeader(node); @@ -165,9 +165,9 @@ typename TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllo } template -void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) +void TlsfAllocator::AddNewBlock(const T size, BlockHeader* header) { - uint64_t fl = 0, sl = 0, index; + T fl = 0, sl = 0, index; index = CalculateFreeBlockIndices(size, fl, sl); CreateHeader(TO_BYTES(header), size, FREE); @@ -182,7 +182,7 @@ void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header) } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT uint64_t& size) +typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT T& size) { BlockHeader* start = header; BlockHeader* prev = GetPrevHeader(start); @@ -211,9 +211,9 @@ bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) if (!header) return false; - uint64_t oldSize = GetHeaderSize(header); + T oldSize = GetHeaderSize(header); - uint64_t fl, sl, index; + T fl, sl, index; index = CalculateFreeBlockIndices(oldSize, fl, sl); @@ -232,9 +232,9 @@ bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node) } template -typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size) +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const T& size) { - uint64_t fl, sl, index; + T fl, sl, index; // 1000 0000 0000 1000 index = CalculateFreeBlockIndices(size, fl, sl); @@ -245,7 +245,7 @@ typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const } template -const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl) +const T TlsfAllocator::GetNextFreeSlotIndex(T& fl, T& sl) { sl = __builtin_ctz(slBitmasks[fl] & ~(((1 << (sl + 1)) - 1))); @@ -267,16 +267,16 @@ const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl } template -uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl) +T TlsfAllocator::CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl) { - uint64_t rawFl = FL(size); + 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 uint64_t size) +void TlsfAllocator::CreateFooter(uint8_t* ptr, const T size) { if (!ptr) return; BlockFooter* footer = TO_FOOTER(ptr); @@ -345,7 +345,7 @@ typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHe } template -typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl) +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const T fl, const T sl) { return freeList[fl * MAX_SL_BUCKETS + sl]; } @@ -354,7 +354,7 @@ template typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; - uint64_t size = GetHeaderSize(header); + T size = GetHeaderSize(header); uint8_t* rawFooter = TO_BYTES(header) + (size - FOOTER_SIZE); if (!rawFooter || !IsValid(rawFooter)) return nullptr; return TO_FOOTER(rawFooter); @@ -376,7 +376,7 @@ uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header) } template -const uint64_t TlsfAllocator::GetHeaderSize(BlockHeader* header) +const T TlsfAllocator::GetHeaderSize(BlockHeader* header) { return header->sizeAndFlags >> FLAG_OFFSET; } @@ -388,7 +388,7 @@ bool TlsfAllocator::IsFree(BlockHeader* header) } template -bool TlsfAllocator::IsFree(uint64_t fl, uint64_t sl) +bool TlsfAllocator::IsFree(T fl, T sl) { return slBitmasks[fl] & (1 << sl); } @@ -415,13 +415,13 @@ bool TlsfAllocator::IsValid(uint8_t* ptr) } template -const uint64_t TlsfAllocator::Capacity() +const T TlsfAllocator::Capacity() { return capacity; } template -const uint64_t TlsfAllocator::BytesRemaining() +const T TlsfAllocator::BytesRemaining() { return bytesRemaining; } diff --git a/engine/utils/allocators/Tlsf.h b/engine/utils/allocators/Tlsf.h index 93393c71..a6ef97c8 100644 --- a/engine/utils/allocators/Tlsf.h +++ b/engine/utils/allocators/Tlsf.h @@ -48,7 +48,7 @@ class TlsfAllocator // S'tructors TlsfAllocator(); - TlsfAllocator(const uint64_t size); + TlsfAllocator(const T size); // Deleted copy and move constructors @@ -57,21 +57,21 @@ class TlsfAllocator // Other functions - void CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags); - void CreateFooter(uint8_t* ptr, const uint64_t size); + void CreateHeader(uint8_t* ptr, const T size, HeaderFlags flags); + void CreateFooter(uint8_t* ptr, const T size); FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next); - FreeBlockNode* FindFreeBlock(const uint64_t& size); + FreeBlockNode* FindFreeBlock(const T& size); - const uint64_t GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl); + const T GetNextFreeSlotIndex(T& fl, T& sl); bool IsFree(BlockHeader* header); - bool IsFree(uint64_t fl, uint64_t sl); + bool IsFree(T fl, T sl); bool PrevBlockIsFree(BlockHeader* header); - uint64_t CalculateFreeBlockIndices(uint64_t size, OUT uint64_t& fl, OUT uint64_t& sl); + T CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl); // Buffer manipulation Functions FreeBlockNode* GetFreeBlock(BlockHeader* header); - FreeBlockNode* GetFreeBlock(const uint64_t fl, const uint64_t sl); + FreeBlockNode* GetFreeBlock(const T fl, const T sl); uint8_t* GetBlockData(BlockHeader* header); BlockFooter* GetFooter(BlockHeader* header); BlockFooter* GetPrevFooter(BlockHeader* header); @@ -82,25 +82,25 @@ class TlsfAllocator // Allocate/Deallocate - void* Allocate(const uint64_t& size); + void* Allocate(const T& size); void Deallocate(void** ptr); - BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t& allocatedSize); + BlockHeader* TrySplitBlock(FreeBlockNode* node, T& allocatedSize); bool RemoveFreeBlock(FreeBlockNode* node); - void AddNewBlock(const uint64_t size, BlockHeader* currentNode); - BlockHeader* TryCoalesce(BlockHeader* header, OUT uint64_t& size); + void AddNewBlock(const T size, BlockHeader* currentNode); + BlockHeader* TryCoalesce(BlockHeader* header, OUT T& size); // Getters bool IsValid(uint8_t* ptr); - const uint64_t GetHeaderSize(BlockHeader* header); - const uint64_t Capacity(); - const uint64_t BytesRemaining(); - const uint64_t TotalBytesRemaining() + const T GetHeaderSize(BlockHeader* header); + const T Capacity(); + const T BytesRemaining(); + const T TotalBytesRemaining() { return totalBytesRemaining; } - const uint64_t TotalSize() + const T TotalSize() { return totalSize; } @@ -108,11 +108,11 @@ class TlsfAllocator { return data; } - const uint64_t FlBitmask() + const T FlBitmask() { return flBitmask; } - const uint16_t& SlBitmask(const uint64_t fl) + const uint16_t& SlBitmask(const T fl) { return slBitmasks[fl]; } @@ -121,17 +121,17 @@ class TlsfAllocator static uint8_t MIN_SIZE_INDEX; - uint64_t totalSize {0}; - uint64_t totalBytesRemaining {0}; + T totalSize {0}; + T totalBytesRemaining {0}; - uint64_t capacity {0}; - uint64_t bytesRemaining {0}; + T capacity {0}; + T bytesRemaining {0}; uint8_t* data {nullptr}; FreeBlockNode** freeList {nullptr}; // bitmasks - uint64_t flBitmask {0}; + T flBitmask {0}; uint16_t* slBitmasks {nullptr}; }; diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index f5673e34..8256cb6c 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -131,8 +131,8 @@ UTEST(test_MediumTlsfAllocator, TestAllocateFunction) ASSERT_FALSE(emptyAllocPtr); // Allocate an amount that causes a value overflow - MediumTlsfAllocator overflowAlloc(UINT64_MAX); - void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); + MediumTlsfAllocator overflowAlloc(UINT32_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT32_MAX); ASSERT_FALSE(overflowPtr); void* tooLargeCase = a.Allocate(100); @@ -599,8 +599,8 @@ UTEST(Test_SmallTlsfAllocator, TestAllocateFunction) ASSERT_FALSE(emptyAllocPtr); // Allocate an amount that causes a value overflow - SmallTlsfAllocator overflowAlloc(UINT64_MAX); - void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); + SmallTlsfAllocator overflowAlloc(UINT16_MAX); + void* overflowPtr = overflowAlloc.Allocate(UINT16_MAX); ASSERT_FALSE(overflowPtr); void* tooLargeCase = a.Allocate(100); @@ -613,10 +613,10 @@ UTEST(Test_SmallTlsfAllocator, TestAllocateFunction) UTEST(Test_SmallTlsfAllocator, TestAllocateFunctionWithRandomInputs) { - SmallTlsfAllocator a(1024 * 1024); + SmallTlsfAllocator a(8000); unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); - std::default_random_engine generator(12345); + std::default_random_engine generator(seed); std::uniform_int_distribution sizeDist(1, 256); @@ -904,7 +904,7 @@ UTEST(Test_SmallTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) UTEST(Test_SmallTlsfAllocator, TestRandomAllocationsAndDeallocations) { - SmallTlsfAllocator a(1024 * 1024); + SmallTlsfAllocator a(8000); unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); std::default_random_engine generator(seed); @@ -960,7 +960,7 @@ UTEST(Test_SmallTlsfAllocator, TestRandomAllocationsAndDeallocations) UTEST(Test_LargeTlsfAllocator, EmptyConstructor) { - MediumTlsfAllocator a; + LargeTlsfAllocator a; ASSERT_EQ(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 0); ASSERT_EQ(0, a.BytesRemaining()); @@ -968,46 +968,46 @@ UTEST(Test_LargeTlsfAllocator, EmptyConstructor) UTEST(Test_LargeTlsfAllocator, ConstructorWithSize) { - MediumTlsfAllocator a(64); + LargeTlsfAllocator a(256); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.TotalSize(), 72); - ASSERT_EQ(64, a.BytesRemaining()); + ASSERT_EQ(a.TotalSize(), 272); + ASSERT_EQ(256, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 4); - ASSERT_EQ(4, a.SlBitmask(2)); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); - MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(2, 2); + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(4, 1); ASSERT_TRUE(block); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - MediumTlsfAllocator::BlockHeader* header = a.GetHeader(block); + LargeTlsfAllocator::BlockHeader* header = a.GetHeader(block); ASSERT_TRUE(header); - ASSERT_EQ(72, a.GetHeaderSize(header)); + ASSERT_EQ(272, a.GetHeaderSize(header)); ASSERT_TRUE(a.IsFree(header)); ASSERT_FALSE(a.PrevBlockIsFree(header)); - MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + LargeTlsfAllocator::BlockFooter* footer = a.GetFooter(header); ASSERT_TRUE(footer); - ASSERT_EQ(72, footer->totalBlockSize); + ASSERT_EQ(272, footer->totalBlockSize); } UTEST(Test_LargeTlsfAllocator, ConstructorWithTooSmallSize) { - Siege::MediumTlsfAllocator a(15); + Siege::LargeTlsfAllocator a(15); ASSERT_EQ(a.Data(), nullptr); ASSERT_EQ(a.Capacity(), 0); } UTEST(Test_LargeTlsfAllocator, TestAllocateFunction) { - MediumTlsfAllocator a(64); + LargeTlsfAllocator a(256); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); - ASSERT_EQ(64, a.BytesRemaining()); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 4); - ASSERT_EQ(4, a.SlBitmask(2)); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -1017,47 +1017,47 @@ UTEST(Test_LargeTlsfAllocator, TestAllocateFunction) ASSERT_EQ(10, p->inta); ASSERT_EQ(20, p->intb); - ASSERT_EQ(2, a.FlBitmask()); - ASSERT_EQ(256, a.SlBitmask(1)); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); - MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(3, 14); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); auto data = (TestStruct*) a.GetBlockData(header); auto footer = a.GetFooter(header); - ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(256, header->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), footer->totalBlockSize); - ASSERT_EQ(a.BytesRemaining(), 48); + 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(1, a.FlBitmask()); - ASSERT_EQ(0, a.SlBitmask(1)); + 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(192, strHeader->sizeAndFlags); + ASSERT_EQ(256, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ( - sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), + sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(LargeTlsfAllocator::BlockFooter), strFooter->totalBlockSize); - ASSERT_EQ(32, a.BytesRemaining()); + ASSERT_EQ(224, a.BytesRemaining()); // Edge cases // Empty allocator - MediumTlsfAllocator emptyA; + LargeTlsfAllocator emptyA; TestStruct* ptr = (TestStruct*) emptyA.Allocate(sizeof(TestStruct)); ASSERT_FALSE(ptr); @@ -1067,28 +1067,25 @@ UTEST(Test_LargeTlsfAllocator, TestAllocateFunction) ASSERT_FALSE(emptyAllocPtr); // Allocate an amount that causes a value overflow - MediumTlsfAllocator overflowAlloc(UINT64_MAX); + LargeTlsfAllocator overflowAlloc(UINT64_MAX); void* overflowPtr = overflowAlloc.Allocate(UINT64_MAX); ASSERT_FALSE(overflowPtr); - void* tooLargeCase = a.Allocate(100); - ASSERT_FALSE(tooLargeCase); - - MediumTlsfAllocator tooSmallAlloc(64); - void* tooLargeAlloc = tooSmallAlloc.Allocate(100); + LargeTlsfAllocator tooSmallAlloc(256); + void* tooLargeAlloc = tooSmallAlloc.Allocate(1024); ASSERT_FALSE(tooLargeAlloc); } UTEST(Test_LargeTlsfAllocator, TestAllocateFunctionWithRandomInputs) { - MediumTlsfAllocator a(1024 * 1024); + LargeTlsfAllocator a(4096 * 4096); unsigned seed = std::chrono::steady_clock::now().time_since_epoch().count(); - std::default_random_engine generator(12345); + std::default_random_engine generator(seed); - std::uniform_int_distribution sizeDist(1, 256); + std::uniform_int_distribution sizeDist(1, 2048); - MediumTlsfAllocator::BlockHeader* header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + LargeTlsfAllocator::BlockHeader* header = (LargeTlsfAllocator::BlockHeader*) a.Data(); for (int i = 0; i < 10000; i++) { @@ -1102,7 +1099,7 @@ UTEST(Test_LargeTlsfAllocator, TestAllocateFunctionWithRandomInputs) uint64_t expectedSize = header->sizeAndFlags >> 3; uint32_t* data = (uint32_t*) a.GetBlockData(header); ASSERT_EQ(0xDEADC0DE, *data); - MediumTlsfAllocator::BlockFooter* footer = a.GetFooter(header); + LargeTlsfAllocator::BlockFooter* footer = a.GetFooter(header); header = a.GetNextHeader(header); } else continue; @@ -1111,13 +1108,13 @@ UTEST(Test_LargeTlsfAllocator, TestAllocateFunctionWithRandomInputs) UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) { - MediumTlsfAllocator a(64); + LargeTlsfAllocator a(256); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 64); - ASSERT_EQ(64, a.BytesRemaining()); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 4); - ASSERT_EQ(4, a.SlBitmask(2)); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -1127,33 +1124,33 @@ UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) ASSERT_EQ(10, p->inta); ASSERT_EQ(20, p->intb); - ASSERT_EQ(2, a.FlBitmask()); - ASSERT_EQ(256, a.SlBitmask(1)); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); - MediumTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(1, 8); + LargeTlsfAllocator::FreeBlockNode* block = a.GetFreeBlock(3, 14); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); auto data = (TestStruct*) a.GetBlockData(header); auto footer = a.GetFooter(header); - ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(256, header->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), footer->totalBlockSize); - ASSERT_EQ(48, a.BytesRemaining()); + ASSERT_EQ(240, a.BytesRemaining()); a.FREE(p); // Should be empty - ASSERT_EQ(577, header->sizeAndFlags); - ASSERT_TRUE(header->sizeAndFlags & MediumTlsfAllocator::FREE); + ASSERT_EQ(2177, header->sizeAndFlags); + ASSERT_TRUE(header->sizeAndFlags & LargeTlsfAllocator::FREE); ASSERT_FALSE(p); - ASSERT_EQ(4, a.FlBitmask()); - ASSERT_EQ(4, a.SlBitmask(2)); - ASSERT_EQ(64, a.BytesRemaining()); + 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; @@ -1162,61 +1159,61 @@ UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) ASSERT_EQ(10, p->inta); ASSERT_EQ(20, p->intb); - ASSERT_EQ(2, a.FlBitmask()); - ASSERT_EQ(256, a.SlBitmask(1)); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(16384, a.SlBitmask(3)); - block = a.GetFreeBlock(1, 8); + block = a.GetFreeBlock(3, 14); ASSERT_EQ(block->next, nullptr); ASSERT_EQ(block->prev, nullptr); - header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + header = (LargeTlsfAllocator::BlockHeader*) a.Data(); data = (TestStruct*) a.GetBlockData(header); footer = a.GetFooter(header); - ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(256, header->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), footer->totalBlockSize); - ASSERT_EQ(48, a.BytesRemaining()); + 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(1, a.FlBitmask()); - ASSERT_EQ(0, a.SlBitmask(1)); + 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(192, strHeader->sizeAndFlags); + ASSERT_EQ(256, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); ASSERT_EQ( - sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), + sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(LargeTlsfAllocator::BlockFooter), strFooter->totalBlockSize); - ASSERT_EQ(32, a.BytesRemaining()); + ASSERT_EQ(224, 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_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(48, a.BytesRemaining()); + ASSERT_EQ(240, 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()); + ASSERT_EQ(2177, header->sizeAndFlags); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); + ASSERT_EQ(256, a.BytesRemaining()); - MediumTlsfAllocator::FreeBlockNode* newNode = (MediumTlsfAllocator::FreeBlockNode*) data; + LargeTlsfAllocator::FreeBlockNode* newNode = (LargeTlsfAllocator::FreeBlockNode*) data; ASSERT_TRUE(newNode); // Check if the new node is head ASSERT_EQ(nullptr, newNode->prev); @@ -1225,13 +1222,13 @@ UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) { - MediumTlsfAllocator a(128); + LargeTlsfAllocator a(256); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 128); - ASSERT_EQ(128, a.BytesRemaining()); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); - ASSERT_EQ(a.FlBitmask(), 8); - ASSERT_EQ(2, a.SlBitmask(3)); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); TestStruct* p = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -1241,22 +1238,22 @@ UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) 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(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(112, a.BytesRemaining()); + ASSERT_EQ(240, a.BytesRemaining()); - auto header = (MediumTlsfAllocator::BlockHeader*) a.Data(); + auto header = (LargeTlsfAllocator::BlockHeader*) a.Data(); auto data = (TestStruct*) a.GetBlockData(header); auto footer = a.GetFooter(header); - ASSERT_EQ(192, header->sizeAndFlags); + ASSERT_EQ(256, header->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), footer->totalBlockSize); Siege::String* str = (Siege::String*) a.Allocate(sizeof(Siege::String)); @@ -1264,22 +1261,22 @@ UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) *str = "Hello There!"; ASSERT_STREQ(str->Str(), "Hello There!"); - ASSERT_EQ(4, a.FlBitmask()); - ASSERT_EQ(64, a.SlBitmask(2)); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(1024, a.SlBitmask(3)); - MediumTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(2, 6); + LargeTlsfAllocator::FreeBlockNode* NewBlock = a.GetFreeBlock(3, 10); ASSERT_EQ(NewBlock->next, nullptr); ASSERT_EQ(NewBlock->prev, nullptr); - ASSERT_EQ(96, a.BytesRemaining()); + ASSERT_EQ(224, 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(256, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(Siege::String) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(Siege::String) + + sizeof(LargeTlsfAllocator::BlockFooter), strFooter->totalBlockSize); TestStruct* p2 = (TestStruct*) a.Allocate(sizeof(TestStruct)); @@ -1290,51 +1287,51 @@ UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) ASSERT_EQ(10, p2->inta); ASSERT_EQ(20, p2->intb); - ASSERT_EQ(4, a.FlBitmask()); - ASSERT_EQ(1, a.SlBitmask(2)); + ASSERT_EQ(8, a.FlBitmask()); + ASSERT_EQ(64, a.SlBitmask(3)); - MediumTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(2, 0); + LargeTlsfAllocator::FreeBlockNode* newFreeBlock = a.GetFreeBlock(3, 6); ASSERT_EQ(newFreeBlock->next, nullptr); ASSERT_EQ(newFreeBlock->prev, nullptr); - ASSERT_EQ(80, a.BytesRemaining()); + ASSERT_EQ(208, 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(256, newHeader->sizeAndFlags); ASSERT_EQ(data, p); - ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(TestStruct) + - sizeof(MediumTlsfAllocator::BlockFooter), + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(TestStruct) + + sizeof(LargeTlsfAllocator::BlockFooter), footer->totalBlockSize); a.FREE(p); - MediumTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(0, 8); + LargeTlsfAllocator::FreeBlockNode* firstFree = a.GetFreeBlock(3, 6); ASSERT_EQ(firstFree->next, nullptr); ASSERT_EQ(firstFree->prev, nullptr); - ASSERT_EQ(96, a.BytesRemaining()); + ASSERT_EQ(224, a.BytesRemaining()); - MediumTlsfAllocator::BlockHeader* firstFreeHeader = a.GetHeader((uint8_t*) firstFree); - ASSERT_TRUE(firstFreeHeader->sizeAndFlags & MediumTlsfAllocator::FREE); - ASSERT_FALSE(firstFreeHeader->sizeAndFlags & MediumTlsfAllocator::PREV_IS_FREE); + 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(), 5); - ASSERT_EQ(256, a.SlBitmask(0)); + ASSERT_EQ(a.FlBitmask(), 10); + ASSERT_EQ(1, a.SlBitmask(1)); 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)); + 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(128, a.BytesRemaining()); - ASSERT_EQ(8, a.FlBitmask()); - ASSERT_EQ(2, a.SlBitmask(3)); + ASSERT_EQ(256, a.BytesRemaining()); + ASSERT_EQ(16, a.FlBitmask()); + ASSERT_EQ(2, a.SlBitmask(4)); // Edge Cases @@ -1351,34 +1348,34 @@ UTEST(Test_LargeTlsfAllocator, TestBlockCoalescing) UTEST(Test_LargeTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) { - MediumTlsfAllocator a(128); + LargeTlsfAllocator a(256); ASSERT_NE(a.Data(), nullptr); - ASSERT_EQ(a.Capacity(), 128); - ASSERT_EQ(128, a.BytesRemaining()); + ASSERT_EQ(a.Capacity(), 256); + ASSERT_EQ(256, a.BytesRemaining()); - void* p0 = a.Allocate(16); - void* p1 = a.Allocate(32); - void* p2 = a.Allocate(16); - void* p3 = a.Allocate(32); + 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()); a.FREE(p0); a.FREE(p2); - ASSERT_EQ(64, a.BytesRemaining()); - void* tooLargeVal = a.Allocate(24); + ASSERT_EQ(128, a.BytesRemaining()); + void* tooLargeVal = a.Allocate(34); ASSERT_FALSE(tooLargeVal); } UTEST(Test_LargeTlsfAllocator, TestRandomAllocationsAndDeallocations) { - MediumTlsfAllocator a(1024 * 1024); + 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, 256); + std::uniform_int_distribution sizeDist(1, 4096); std::vector pointers; pointers.reserve(10000); @@ -1396,7 +1393,7 @@ UTEST(Test_LargeTlsfAllocator, TestRandomAllocationsAndDeallocations) { *((uint32_t*) ptr) = 0xDEADC0DE; pointers.push_back(ptr); - MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); + LargeTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptr); } } else if (action <= 10 && !pointers.empty()) @@ -1406,10 +1403,10 @@ UTEST(Test_LargeTlsfAllocator, TestRandomAllocationsAndDeallocations) void*& ptrToFree = pointers[index]; ASSERT_EQ(0xDEADC0DE, *(uint32_t*) ptrToFree); - MediumTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); + LargeTlsfAllocator::BlockHeader* header = a.GetHeader((uint8_t*) ptrToFree); a.FREE(ptrToFree); - MediumTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); + LargeTlsfAllocator::FreeBlockNode* newNode = a.GetFreeBlock(header); ASSERT_FALSE(ptrToFree); pointers.erase(pointers.begin() + index); From 609f4da3e9f22a8c08049eb7eb0b730349b9956b Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 31 Aug 2025 17:19:43 +1000 Subject: [PATCH 13/15] Ran format --- engine/utils/allocators/Tlsf.cpp | 44 ++++++++++++++------- engine/utils/allocators/Tlsf.h | 6 ++- tests/src/resources/test_ResourceSystem.cpp | 2 - tests/src/utils/test_TlsfAllocator.cpp | 39 +++++++++--------- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/engine/utils/allocators/Tlsf.cpp b/engine/utils/allocators/Tlsf.cpp index 60836766..54dc5f6e 100644 --- a/engine/utils/allocators/Tlsf.cpp +++ b/engine/utils/allocators/Tlsf.cpp @@ -8,7 +8,9 @@ // #include "Tlsf.h" + #include + #include "../Logging.h" #define TO_BYTES(val) reinterpret_cast(val) @@ -35,7 +37,8 @@ template uint8_t TlsfAllocator::MIN_SIZE_INDEX = 4; template -TlsfAllocator::TlsfAllocator() {} +TlsfAllocator::TlsfAllocator() +{} template TlsfAllocator::TlsfAllocator(const T size) @@ -139,8 +142,9 @@ void TlsfAllocator::Deallocate(void** ptr) } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, - T& allocatedSize) +typename TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock( + TlsfAllocator::FreeBlockNode* node, + T& allocatedSize) { if (!node) return nullptr; @@ -182,7 +186,8 @@ void TlsfAllocator::AddNewBlock(const T size, BlockHeader* header) } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, OUT T& size) +typename TlsfAllocator::BlockHeader* TlsfAllocator::TryCoalesce(BlockHeader* header, + OUT T& size) { BlockHeader* start = header; BlockHeader* prev = GetPrevHeader(start); @@ -284,7 +289,8 @@ void TlsfAllocator::CreateFooter(uint8_t* ptr, const T size) } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node) +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader( + TlsfAllocator::FreeBlockNode* node) { if (!node) return nullptr; uint8_t* rawHeader = TO_BYTES(node) - HEADER_SIZE; @@ -302,7 +308,8 @@ typename TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(uint8_t* ptr } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header) +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader( + TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawPrevFooter = TO_BYTES(GetPrevFooter(header)); @@ -313,7 +320,8 @@ typename TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllo } template -typename TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header) +typename TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader( + TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawHeader = TO_BYTES(header); @@ -323,9 +331,10 @@ typename TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllo } template -typename TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr, - TlsfAllocator::FreeBlockNode* prev, - TlsfAllocator::FreeBlockNode* next) +typename TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock( + uint8_t* ptr, + TlsfAllocator::FreeBlockNode* prev, + TlsfAllocator::FreeBlockNode* next) { if (!IsValid(ptr)) return nullptr; @@ -351,7 +360,8 @@ typename TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const T } template -typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header) +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter( + TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; T size = GetHeaderSize(header); @@ -361,7 +371,8 @@ typename TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocato } template -typename TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header) +typename TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter( + TlsfAllocator::BlockHeader* header) { if (!header) return nullptr; uint8_t* rawFooter = TO_BYTES(header) - FOOTER_SIZE; @@ -393,6 +404,12 @@ 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) { @@ -429,5 +446,4 @@ const T TlsfAllocator::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 index a6ef97c8..980f6bcd 100644 --- a/engine/utils/allocators/Tlsf.h +++ b/engine/utils/allocators/Tlsf.h @@ -14,7 +14,7 @@ #include "../Macros.h" -#define FREE(ptr) Deallocate((void**)&ptr) +#define FREE(ptr) Deallocate((void**) &ptr) namespace Siege { @@ -22,6 +22,7 @@ template class TlsfAllocator { public: + enum HeaderFlags { FULL = 0, @@ -65,6 +66,7 @@ class TlsfAllocator const T GetNextFreeSlotIndex(T& fl, T& sl); bool IsFree(BlockHeader* header); bool IsFree(T fl, T sl); + bool IsFull(); bool PrevBlockIsFree(BlockHeader* header); T CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl); @@ -141,6 +143,6 @@ typedef TlsfAllocator SmallTlsfAllocator; typedef TlsfAllocator MediumTlsfAllocator; // Maximum buffer possible here is around 2,147,483.6GB. typedef TlsfAllocator LargeTlsfAllocator; -} +} // namespace Siege #endif // SIEGE_ENGINE_TLSF_H diff --git a/tests/src/resources/test_ResourceSystem.cpp b/tests/src/resources/test_ResourceSystem.cpp index 04804b7c..df40b2f4 100644 --- a/tests/src/resources/test_ResourceSystem.cpp +++ b/tests/src/resources/test_ResourceSystem.cpp @@ -17,8 +17,6 @@ #include #include -#include - using namespace Siege; // Define test fixture diff --git a/tests/src/utils/test_TlsfAllocator.cpp b/tests/src/utils/test_TlsfAllocator.cpp index 8256cb6c..46f3b028 100644 --- a/tests/src/utils/test_TlsfAllocator.cpp +++ b/tests/src/utils/test_TlsfAllocator.cpp @@ -113,9 +113,9 @@ UTEST(test_MediumTlsfAllocator, TestAllocateFunction) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(MediumTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); // Edge cases @@ -258,9 +258,9 @@ UTEST(Test_MediumTlsfAllocator, TestDeallocateFunction) ASSERT_EQ(192, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(MediumTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(MediumTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(MediumTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); a.FREE(p); @@ -425,6 +425,7 @@ UTEST(Test_MediumTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) 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); @@ -581,9 +582,9 @@ UTEST(Test_SmallTlsfAllocator, TestAllocateFunction) ASSERT_EQ(160, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(SmallTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(SmallTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); // Edge cases @@ -726,9 +727,9 @@ UTEST(Test_SmallTlsfAllocator, TestDeallocateFunction) ASSERT_EQ(160, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(SmallTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(SmallTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(SmallTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(32, a.BytesRemaining()); a.FREE(p); @@ -893,6 +894,7 @@ UTEST(Test_SmallTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) 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); @@ -1049,9 +1051,9 @@ UTEST(Test_LargeTlsfAllocator, TestAllocateFunction) ASSERT_EQ(256, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(LargeTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(LargeTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(224, a.BytesRemaining()); // Edge cases @@ -1191,9 +1193,9 @@ UTEST(Test_LargeTlsfAllocator, TestDeallocateFunction) ASSERT_EQ(256, strHeader->sizeAndFlags); ASSERT_EQ(strData, str); - ASSERT_EQ( - sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + sizeof(LargeTlsfAllocator::BlockFooter), - strFooter->totalBlockSize); + ASSERT_EQ(sizeof(LargeTlsfAllocator::BlockHeader) + sizeof(String) + + sizeof(LargeTlsfAllocator::BlockFooter), + strFooter->totalBlockSize); ASSERT_EQ(224, a.BytesRemaining()); a.FREE(p); @@ -1358,6 +1360,7 @@ UTEST(Test_LargeTlsfAllocator, TestAllocationWhenNoAppropriateFragmentExists) 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); From 50efe4e05ead8d6e86d9b02c73e85ed26cb04757 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Sun, 31 Aug 2025 21:38:57 +1000 Subject: [PATCH 14/15] Documented all TLSF allocator classes --- engine/utils/allocators/Tlsf.cpp | 1 - engine/utils/allocators/Tlsf.h | 279 +++++++++++++++++++++++++++++-- 2 files changed, 263 insertions(+), 17 deletions(-) diff --git a/engine/utils/allocators/Tlsf.cpp b/engine/utils/allocators/Tlsf.cpp index 54dc5f6e..59a83c26 100644 --- a/engine/utils/allocators/Tlsf.cpp +++ b/engine/utils/allocators/Tlsf.cpp @@ -240,7 +240,6 @@ template typename TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const T& size) { T fl, sl, index; - // 1000 0000 0000 1000 index = CalculateFreeBlockIndices(size, fl, sl); if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl); diff --git a/engine/utils/allocators/Tlsf.h b/engine/utils/allocators/Tlsf.h index 980f6bcd..175108ed 100644 --- a/engine/utils/allocators/Tlsf.h +++ b/engine/utils/allocators/Tlsf.h @@ -14,6 +14,9 @@ #include "../Macros.h" +/** + * A shorthand macro for making deallocations easier to manage. + */ #define FREE(ptr) Deallocate((void**) &ptr) namespace Siege @@ -23,6 +26,10 @@ 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, @@ -30,17 +37,28 @@ class TlsfAllocator 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}; @@ -48,7 +66,15 @@ class TlsfAllocator // 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); // Deleted copy and move constructors @@ -58,62 +84,180 @@ class TlsfAllocator // Other functions - void CreateHeader(uint8_t* ptr, const T size, HeaderFlags flags); - void CreateFooter(uint8_t* ptr, const T size); - FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next); - FreeBlockNode* FindFreeBlock(const T& size); + /** + * @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); - const T GetNextFreeSlotIndex(T& fl, T& sl); + /** + * @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(); - bool PrevBlockIsFree(BlockHeader* header); - T CalculateFreeBlockIndices(T size, OUT T& fl, OUT T& sl); // 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); - void Deallocate(void** ptr); - BlockHeader* TrySplitBlock(FreeBlockNode* node, T& allocatedSize); - bool RemoveFreeBlock(FreeBlockNode* node); - void AddNewBlock(const T size, BlockHeader* currentNode); - BlockHeader* TryCoalesce(BlockHeader* header, OUT 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 - bool IsValid(uint8_t* ptr); + /** + * @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]; @@ -121,6 +265,98 @@ class TlsfAllocator 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}; @@ -137,11 +373,22 @@ class TlsfAllocator uint16_t* slBitmasks {nullptr}; }; -// Maximum buffer possible here is around 8KB. +/** + * @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; -// Maximum buffer possible here is around 512MB. + +/** + * 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; -// Maximum buffer possible here is around 2,147,483.6GB. + +/** + * 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 From a2012c07a71d72403fe691d111f5388cb9372a33 Mon Sep 17 00:00:00 2001 From: Aryeh Date: Mon, 1 Sep 2025 18:54:10 +1000 Subject: [PATCH 15/15] Added destructor to TlsfAllocator --- engine/utils/allocators/Tlsf.cpp | 17 +++++++++++++++++ engine/utils/allocators/Tlsf.h | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/engine/utils/allocators/Tlsf.cpp b/engine/utils/allocators/Tlsf.cpp index 59a83c26..ffb186b5 100644 --- a/engine/utils/allocators/Tlsf.cpp +++ b/engine/utils/allocators/Tlsf.cpp @@ -75,6 +75,23 @@ TlsfAllocator::TlsfAllocator(const T size) 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) { diff --git a/engine/utils/allocators/Tlsf.h b/engine/utils/allocators/Tlsf.h index 175108ed..2473655b 100644 --- a/engine/utils/allocators/Tlsf.h +++ b/engine/utils/allocators/Tlsf.h @@ -70,6 +70,7 @@ class TlsfAllocator * @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 @@ -77,6 +78,11 @@ class TlsfAllocator */ 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;