From 7332dd66436a7b013834cd3774b727a880f9ea67 Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Mon, 5 Feb 2024 22:53:09 +0000 Subject: [PATCH] Add tests for FrameAllocator and DataChunker --- Engine/source/core/dataChunker.h | 33 +- Engine/source/core/frameAllocator.cpp | 13 +- Engine/source/core/frameAllocator.h | 18 +- Engine/source/testing/dataChunkerTest.cpp | 347 +++++++++++++++++++ Engine/source/testing/frameAllocatorTest.cpp | 195 +++++++++++ 5 files changed, 597 insertions(+), 9 deletions(-) create mode 100644 Engine/source/testing/dataChunkerTest.cpp create mode 100644 Engine/source/testing/frameAllocatorTest.cpp diff --git a/Engine/source/core/dataChunker.h b/Engine/source/core/dataChunker.h index 6e4dafefb..8112ef1d1 100644 --- a/Engine/source/core/dataChunker.h +++ b/Engine/source/core/dataChunker.h @@ -39,6 +39,8 @@ public: ChunkSize = 16384 }; + typedef T AlignmentType; + struct alignas(uintptr_t) DataBlock : public AlignedBufferAllocator { DataBlock* mNext; @@ -129,6 +131,19 @@ public: AssertFatal(mChunkHead == NULL, "Tried setting AFTER init"); mChunkSize = size; } + + bool isManagedByChunker(void* ptr) const + { + U8* chkPtr = (U8*)ptr; + for (DataBlock* itr = mChunkHead; itr; itr = itr->mNext) + { + const U8* blockStart = (U8*)itr->getAlignedBuffer(); + const U8* blockEnd = (U8*)itr->getAlignedBufferEnd(); + if (chkPtr >= blockStart && chkPtr < blockEnd) + return true; + } + return false; + } }; class DataChunker : public BaseDataChunker @@ -166,6 +181,8 @@ public: class MultiTypedChunker : private BaseDataChunker { public: + typedef uintptr_t AlignmentType; + MultiTypedChunker(dsize_t size = BaseDataChunker::ChunkSize) : BaseDataChunker(std::max(sizeof(uintptr_t), size)) { } @@ -195,7 +212,7 @@ template struct ChunkerFreeClassList mNextList = NULL; } - bool isEmpty() + bool isEmpty() const { return mNextList == NULL; } @@ -248,7 +265,15 @@ public: void freeBlocks(bool keepOne = false) { BaseDataChunker::freeBlocks(keepOne); + mFreeListHead.reset(); } + + inline bool isManagedByChunker(void* ptr) const + { + return BaseDataChunker::isManagedByChunker(ptr); + } + + inline ChunkerFreeClassList& getFreeListHead() { return mFreeListHead; } }; /// Implements a chunker which uses the data of another BaseDataChunker @@ -390,6 +415,8 @@ public: default: break; } + + item.ptr = NULL; } void freeBlocks(bool keepOne = false) @@ -398,4 +425,8 @@ public: mT2.freeBlocks(keepOne); mT3.freeBlocks(keepOne); } + + inline ClassChunker& getT1Chunker() { return mT1; } + inline ClassChunker& getT2Chunker() { return mT2; } + inline ClassChunker& getT3Chunker() { return mT3; } }; diff --git a/Engine/source/core/frameAllocator.cpp b/Engine/source/core/frameAllocator.cpp index 7f0435194..b1e276e8d 100644 --- a/Engine/source/core/frameAllocator.cpp +++ b/Engine/source/core/frameAllocator.cpp @@ -29,15 +29,18 @@ thread_local FrameAllocator::FrameAllocatorType FrameAllocator::smMainInstance thread_local dsize_t FrameAllocator::smAllocatedBytes; #endif -#if defined(TORQUE_DEBUG) +U32 FrameAllocator::smMaxFrameAllocation; -dsize_t FrameAllocator::smMaxFrameAllocation; - - -DefineEngineFunction(getMaxFrameAllocation, S32, (), , "") +U32 FrameAllocator::getMaxFrameAllocation() { return (S32)FrameAllocator::smMaxFrameAllocation; } +#if defined(TORQUE_DEBUG) + +DefineEngineFunction(getMaxFrameAllocation, S32, (), , "") +{ + return (S32)FrameAllocator::getMaxFrameAllocation(); +} #endif diff --git a/Engine/source/core/frameAllocator.h b/Engine/source/core/frameAllocator.h index 0b70528f6..9613cb7ad 100644 --- a/Engine/source/core/frameAllocator.h +++ b/Engine/source/core/frameAllocator.h @@ -103,11 +103,21 @@ public: return (U32)(numBytes / sizeof(T)); } + static inline U32 calcRequiredPaddedByteSize(const dsize_t numBytes) + { + return calcRequiredElementSize(numBytes) * sizeof(T); + } + inline T* getAlignedBuffer() const { return mBuffer; } + inline T* getAlignedBufferEnd() const + { + return mBuffer + mHighWaterMark; + } + inline U32 getPosition() const { return mWaterMark; @@ -153,7 +163,7 @@ public: class FrameAllocator { public: - static dsize_t smMaxFrameAllocation; + static U32 smMaxFrameAllocation; #ifdef TORQUE_MEM_DEBUG static thread_local dsize_t smAllocatedBytes; #endif @@ -220,6 +230,8 @@ public: return smMainInstance.getSizeBytes(); } + static U32 getMaxFrameAllocation(); + static thread_local FrameAllocatorType smMainInstance; }; @@ -253,7 +265,7 @@ public: FrameAllocator::setWaterMark(mMarker); } - void* alloc(const U32 allocSize) const + void* alloc(const U32 allocSize) { return FrameAllocator::alloc(allocSize); } @@ -336,7 +348,7 @@ public: const T& operator *() const { return *mMemory; } T** operator &() { return &mMemory; } - const T** operator &() const { return &mMemory; } + T* const * operator &() const { return &mMemory; } operator T* () { return mMemory; } operator const T* () const { return mMemory; } diff --git a/Engine/source/testing/dataChunkerTest.cpp b/Engine/source/testing/dataChunkerTest.cpp new file mode 100644 index 000000000..a66bd634f --- /dev/null +++ b/Engine/source/testing/dataChunkerTest.cpp @@ -0,0 +1,347 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2023-2024 tgemit contributors. +// See AUTHORS file and git repository for contributor information. +// +// SPDX-License-Identifier: MIT +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TESTS_ENABLED +#include "testing/unitTesting.h" +#include "core/dataChunker.h" + +struct TestClassChunkerStruct +{ + U32 value; + U32 value2; + + TestClassChunkerStruct() + { + value = 0xC001B33F; + value2 = 0x10101010; + } + + ~TestClassChunkerStruct() + { + value = 0; + value2 = 0; + } +}; + + +TEST(BaseDataChunkerTest, BaseDataChunker_Should_Function_Correctly) +{ + BaseDataChunker testChunks(1024); + BaseDataChunker testChunk4(1024); + BaseDataChunker testChunk8(1024); + + EXPECT_TRUE(testChunks.countUsedBlocks() == 0); + EXPECT_TRUE(testChunk4.countUsedBlocks() == 0); + EXPECT_TRUE(testChunk8.countUsedBlocks() == 0); + + testChunks.alloc(1); + testChunk4.alloc(1); + testChunk8.alloc(1); + + EXPECT_TRUE(testChunks.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk4.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk8.countUsedBlocks() == 1); + + testChunks.alloc(1); + testChunk4.alloc(1); + testChunk8.alloc(1); + + EXPECT_TRUE(testChunks.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk4.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk8.countUsedBlocks() == 1); + + EXPECT_TRUE(testChunks.countUsedBytes() == (sizeof(TestClassChunkerStruct) * 2)); + EXPECT_TRUE(testChunk4.countUsedBytes() == (sizeof(U32) * 2)); + EXPECT_TRUE(testChunk8.countUsedBytes() == (sizeof(U64) * 2)); + + testChunks.freeBlocks(true); + testChunk4.freeBlocks(true); + testChunk8.freeBlocks(true); + + EXPECT_TRUE(testChunks.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk4.countUsedBlocks() == 1); + EXPECT_TRUE(testChunk8.countUsedBlocks() == 1); + + testChunks.freeBlocks(false); + testChunk4.freeBlocks(false); + testChunk8.freeBlocks(false); + + EXPECT_TRUE(testChunks.countUsedBlocks() == 0); + EXPECT_TRUE(testChunk4.countUsedBlocks() == 0); + EXPECT_TRUE(testChunk8.countUsedBlocks() == 0); + + testChunks.setChunkSize(sizeof(TestClassChunkerStruct)); + testChunks.alloc(1); + EXPECT_TRUE(testChunks.countUsedBlocks() == 1); + testChunks.alloc(1); + EXPECT_TRUE(testChunks.countUsedBlocks() == 2); +} + +TEST(DataChunkerTest, DataChunker_Should_Function_Correctly) +{ + DataChunker testChunk(1024); + + testChunk.alloc(1024); + + EXPECT_TRUE(testChunk.countUsedBlocks() == 1); + + testChunk.alloc(1024); + + EXPECT_TRUE(testChunk.countUsedBlocks() == 2); + + testChunk.alloc(4096); + + EXPECT_TRUE(testChunk.countUsedBytes() == (1024+1024+4096)); + + EXPECT_TRUE(testChunk.countUsedBlocks() == 3); + + testChunk.alloc(12); + + EXPECT_TRUE(testChunk.countUsedBlocks() == 4); + + testChunk.alloc(12); + + EXPECT_TRUE(testChunk.countUsedBlocks() == 4); + + U32 reqEls = AlignedBufferAllocator::calcRequiredElementSize(12) * sizeof(uintptr_t); + + EXPECT_TRUE(testChunk.countUsedBytes() == (1024+1024+4096+reqEls+reqEls)); + + testChunk.freeBlocks(true); + EXPECT_TRUE(testChunk.countUsedBlocks() == 1); + testChunk.freeBlocks(false); + EXPECT_TRUE(testChunk.countUsedBlocks() == 0); + + // Large block cases + + testChunk.alloc(8192); + EXPECT_TRUE(testChunk.countUsedBlocks() == 1); + testChunk.freeBlocks(true); + EXPECT_TRUE(testChunk.countUsedBlocks() == 1); + + testChunk.alloc(8192); + testChunk.alloc(1024); + EXPECT_TRUE(testChunk.countUsedBlocks() == 2); + testChunk.freeBlocks(true); + EXPECT_TRUE(testChunk.countUsedBlocks() == 1); + testChunk.freeBlocks(false); + EXPECT_TRUE(testChunk.countUsedBlocks() == 0); + + // Instead using the chunk size + + for (U32 i=0; i<8; i++) + { + testChunk.alloc(1024); + } + EXPECT_TRUE(testChunk.countUsedBlocks() == 8); + testChunk.freeBlocks(false); + EXPECT_TRUE(testChunk.countUsedBlocks() == 0); +} + +TEST(ChunkerTest,Chunker_Should_Function_Correctly) +{ + Chunker foo; + TestClassChunkerStruct* value = foo.alloc(); + EXPECT_TRUE(value->value != 0xC001B33F); + EXPECT_TRUE(value->value2 != 0x10101010); + // Should otherwise just act like DataChunker +} + +TEST(MultiTypedChunkerTest,MultiTypedChunker_Should_Function_Correctly) +{ + struct TVS1 + { + int a; + int b; + }; + struct TVS2 + { + int a; + int b; + int c; + }; + MultiTypedChunker chunker; + TVS1* v1 = chunker.alloc(); + TVS2* v2 = chunker.alloc(); + TVS2* v3 = chunker.alloc(); + + EXPECT_TRUE(((U8*)v2) - ((U8*)v1) == sizeof(TVS1)); + EXPECT_TRUE(((U8*)v3) - ((U8*)v2) == AlignedBufferAllocator::calcRequiredPaddedByteSize(sizeof(TVS2))); +} + +TEST(ChunkerFreeClassListTest,ChunkerFreeClassList_Should_Function_Correctly) +{ + TestClassChunkerStruct list[5]; + ChunkerFreeClassList freeListTest; + + // Push & pop works as expected + EXPECT_TRUE(freeListTest.isEmpty() == true); + freeListTest.push((ChunkerFreeClassList*)&list[0]); + EXPECT_TRUE(freeListTest.isEmpty() == false); + freeListTest.push((ChunkerFreeClassList*)&list[4]); + EXPECT_TRUE(freeListTest.pop() == &list[4]); + EXPECT_TRUE(freeListTest.pop() == &list[0]); + EXPECT_TRUE(freeListTest.pop() == NULL); + + // Reset clears list head + freeListTest.push((ChunkerFreeClassList*)&list[4]); + freeListTest.reset(); + EXPECT_TRUE(freeListTest.pop() == NULL); +} + + +TEST(FreeListChunkerTest, FreeListChunkerTest_Should_Function_Correctly) +{ + FreeListChunker testFreeList; + + TestClassChunkerStruct* s1 = testFreeList.alloc(); + TestClassChunkerStruct* s2 = testFreeList.alloc(); + + // Allocation is sequential + EXPECT_TRUE(s2 > s1); + EXPECT_TRUE(((s2 - s1) == 1)); + + testFreeList.free(s1); + + // But previous reallocations are reused + TestClassChunkerStruct* s3 = testFreeList.alloc(); + TestClassChunkerStruct* s4 = testFreeList.alloc(); + + EXPECT_TRUE(s1 == s3); + EXPECT_TRUE(((s4 - s2) == 1)); // continues from previous free alloc + + // Check sharing + + FreeListChunker sharedChunker(testFreeList.getChunker()); + + s2 = testFreeList.alloc(); + EXPECT_TRUE(((s2 - s4) == 1)); +} + +TEST(ClassChunkerTest, ClassChunker_Should_Function_Correctly) +{ + ClassChunker testClassList; + + TestClassChunkerStruct* s1 = testClassList.alloc(); + TestClassChunkerStruct* s2 = testClassList.alloc(); + + // Allocation is sequential + EXPECT_TRUE(s2 > s1); + EXPECT_TRUE(((s2 - s1) == 1)); + + testClassList.free(s1); + EXPECT_TRUE(s1->value == 0); + EXPECT_TRUE(s1->value2 == 0); + + // But previous reallocations are reused + TestClassChunkerStruct* s3 = testClassList.alloc(); + TestClassChunkerStruct* s4 = testClassList.alloc(); + + EXPECT_TRUE(s1 == s3); + EXPECT_TRUE(((s4 - s2) == 1)); // continues from previous free alloc + + // Values should be initialized correctly for all allocs at this point + EXPECT_TRUE(s1->value == 0xC001B33F); + EXPECT_TRUE(s1->value2 == 0x10101010); + EXPECT_TRUE(s2->value == 0xC001B33F); + EXPECT_TRUE(s2->value2 == 0x10101010); + EXPECT_TRUE(s3->value == 0xC001B33F); + EXPECT_TRUE(s3->value2 == 0x10101010); + EXPECT_TRUE(s4->value == 0xC001B33F); + EXPECT_TRUE(s4->value2 == 0x10101010); + + // Should still be valid if using freeBlocks + testClassList.freeBlocks(true); + EXPECT_TRUE(s1->value == 0xC001B33F); + EXPECT_TRUE(s1->value2 == 0x10101010); + EXPECT_TRUE(s2->value == 0xC001B33F); + EXPECT_TRUE(s2->value2 == 0x10101010); + EXPECT_TRUE(s3->value == 0xC001B33F); + EXPECT_TRUE(s3->value2 == 0x10101010); + EXPECT_TRUE(s4->value == 0xC001B33F); + EXPECT_TRUE(s4->value2 == 0x10101010); +} + + +TEST(ThreeTieredChunkerTest,ThreeTieredChunker_Should_Function_Correctly) +{ + struct TThreeSA + { + uintptr_t a; + }; + struct TThreeSB + { + uintptr_t a; + uintptr_t b; + }; + struct TThreeSC + { + uintptr_t a; + uintptr_t b; + uintptr_t c; + }; + struct TThreeSD + { + uintptr_t a; + uintptr_t b; + uintptr_t c; + uintptr_t d; + }; + ThreeTieredChunker threeChunker; + + // Alloc should alloc in the correct lists + + auto h1 = threeChunker.alloc(sizeof(TThreeSA)); + auto h2 = threeChunker.alloc(sizeof(TThreeSB)); + auto h3 = threeChunker.alloc(sizeof(TThreeSC)); + auto h4 = threeChunker.alloc(sizeof(TThreeSD)); + + EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h3.ptr) == false); + EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h3.ptr) == false); + EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h3.ptr) == true); + EXPECT_TRUE(h3.tier == 3); + + EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h2.ptr) == false); + EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h2.ptr) == true); + EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h2.ptr) == false); + EXPECT_TRUE(h2.tier == 2); + + EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h1.ptr) == true); + EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h1.ptr) == false); + EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h1.ptr) == false); + EXPECT_TRUE(h1.tier == 1); + + EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h4.ptr) == false); + EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h4.ptr) == false); + EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h4.ptr) == false); + EXPECT_TRUE(h4.tier == 0); + + threeChunker.free(h1); + threeChunker.free(h2); + threeChunker.free(h3); + threeChunker.free(h4); + + EXPECT_TRUE(h1.ptr == NULL); + EXPECT_TRUE(h2.ptr == NULL); + EXPECT_TRUE(h3.ptr == NULL); + EXPECT_TRUE(h4.ptr == NULL); + + // freeBlocks should also clear ALL the list heads + + EXPECT_FALSE(threeChunker.getT1Chunker().getFreeListHead().isEmpty()); + EXPECT_FALSE(threeChunker.getT2Chunker().getFreeListHead().isEmpty()); + EXPECT_FALSE(threeChunker.getT3Chunker().getFreeListHead().isEmpty()); + + threeChunker.freeBlocks(false); + + EXPECT_TRUE(threeChunker.getT1Chunker().getFreeListHead().isEmpty()); + EXPECT_TRUE(threeChunker.getT2Chunker().getFreeListHead().isEmpty()); + EXPECT_TRUE(threeChunker.getT3Chunker().getFreeListHead().isEmpty()); +} + + +#endif diff --git a/Engine/source/testing/frameAllocatorTest.cpp b/Engine/source/testing/frameAllocatorTest.cpp new file mode 100644 index 000000000..2790f5d49 --- /dev/null +++ b/Engine/source/testing/frameAllocatorTest.cpp @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2023-2024 tgemit contributors. +// See AUTHORS file and git repository for contributor information. +// +// SPDX-License-Identifier: MIT +//----------------------------------------------------------------------------- + +#ifdef TORQUE_TESTS_ENABLED +#include "testing/unitTesting.h" +#include "core/frameAllocator.h" + +struct TestAlignmentStruct +{ + U64 values[4]; +}; + +TEST(AlignedBufferAllocatorTest, AlignedBufferAllocator_Should_Function_Correctly) +{ + AlignedBufferAllocator ba4; + AlignedBufferAllocator ba8; + AlignedBufferAllocator bas; + + const U32 bufSize32 = (sizeof(TestAlignmentStruct) / 4) * 20; + U32 testAlignmentBuffer[bufSize32]; + for (U32 i=0; i fooTemp(20); + EXPECT_TRUE(FrameAllocator::getWaterMark() == sizeof(TestAlignmentStruct)*20); + EXPECT_TRUE(&fooTemp[0] == fooTemp.address()); + EXPECT_TRUE((&fooTemp[1] - &fooTemp[0]) == 1); + EXPECT_TRUE(fooTemp.getObjectCount() == 20); + EXPECT_TRUE(fooTemp.size() == 20); + + const FrameTemp& fooC = fooTemp; + EXPECT_TRUE(&fooC[0] == fooC.address()); + EXPECT_TRUE((&fooC[1] - &fooC[0]) == 1); + EXPECT_TRUE(fooC.getObjectCount() == 20); + EXPECT_TRUE(fooC.size() == 20); + + // Accessors should work + + // Call the overloaded operators by name + TestAlignmentStruct& value = fooTemp.operator*(); + TestAlignmentStruct** ptr = fooTemp.operator&(); + const TestAlignmentStruct* constPtr = fooTemp.operator const TestAlignmentStruct * (); + TestAlignmentStruct& ref = fooTemp.operator TestAlignmentStruct & (); + const TestAlignmentStruct& constRef = fooTemp.operator const TestAlignmentStruct & (); + TestAlignmentStruct copy = fooTemp.operator TestAlignmentStruct(); + + EXPECT_TRUE(*ptr == fooTemp.address()); + EXPECT_TRUE(&value == fooTemp.address()); + EXPECT_TRUE(constPtr == fooTemp.address()); + EXPECT_TRUE(&ref == fooTemp.address()); + EXPECT_TRUE(&constRef == fooTemp.address()); + EXPECT_TRUE(© != fooTemp.address()); + + // Same for fooC + const TestAlignmentStruct& Cvalue = fooC.operator*(); + TestAlignmentStruct* const* Cptr = fooC.operator&(); + const TestAlignmentStruct* CconstPtr = fooC.operator const TestAlignmentStruct * (); + const TestAlignmentStruct& CconstRef = fooC.operator const TestAlignmentStruct & (); + + EXPECT_TRUE(*Cptr == fooC.address()); + EXPECT_TRUE(&Cvalue == fooC.address()); + EXPECT_TRUE(CconstPtr == fooC.address()); + EXPECT_TRUE(&CconstRef == fooC.address()); + EXPECT_TRUE(&ref == fooC.address()); + EXPECT_TRUE(&constRef == fooC.address()); + } + + // Exiting scope sets watermark + EXPECT_TRUE(FrameAllocator::getWaterMark() == 0); + + // Test the destructor actually gets called + + struct FTDestructTest + { + ~FTDestructTest() + { + gFTDestructTest++; + } + }; + + { + gFTDestructTest = 0; + FrameTemp foo2Temp(10); + } + + EXPECT_TRUE(gFTDestructTest == 10); +} + + +#endif