Browse Source

Bruteforce tested virtual stack allocation with multiple threads.

David Piuva 1 year ago
parent
commit
81a31bd82d

+ 1 - 0
Source/DFPSR/base/virtualStack.cpp

@@ -101,6 +101,7 @@ namespace dsr {
 	thread_local int32_t stackIndex = -1;
 	thread_local int32_t stackIndex = -1;
 
 
 	UnsafeAllocation virtualStack_push(uint64_t paddedSize, uintptr_t alignmentAndMask) {
 	UnsafeAllocation virtualStack_push(uint64_t paddedSize, uintptr_t alignmentAndMask) {
+		// TODO: Assert that the alignment mask begins with ones and ends with zeroes, in case that the caller accidentally truncated the beginning of the mask.
 		if (stackIndex < 0) {
 		if (stackIndex < 0) {
 			UnsafeAllocation result = stackAllocate(fixedMemory, paddedSize, alignmentAndMask);
 			UnsafeAllocation result = stackAllocate(fixedMemory, paddedSize, alignmentAndMask);
 			// Check that we did not run out of memory.
 			// Check that we did not run out of memory.

+ 3 - 0
Source/DFPSR/base/virtualStack.h

@@ -57,7 +57,10 @@ namespace dsr {
 	};
 	};
 	// Allocate memory in the virtual stack owned by the current thread.
 	// Allocate memory in the virtual stack owned by the current thread.
 	//   paddedSize is the number of bytes to allocate including all elements and internal padding.
 	//   paddedSize is the number of bytes to allocate including all elements and internal padding.
+	//     paddedSize must be at least 1, but has no rounding requirements.
 	//   alignmentMask should only contain zeroes at the bits to round away for alignment.
 	//   alignmentMask should only contain zeroes at the bits to round away for alignment.
+	//     alignmentMask should be the bitwise negation of the alignment minus one, where the alignment is a power of two.
+	//     ~(alignment - 1)
 	UnsafeAllocation virtualStack_push(uint64_t paddedSize, uintptr_t alignmentAndMask);
 	UnsafeAllocation virtualStack_push(uint64_t paddedSize, uintptr_t alignmentAndMask);
 
 
 	// A simpler way to get the correct alignment is to allocate a number of elements with a specific type.
 	// A simpler way to get the correct alignment is to allocate a number of elements with a specific type.

+ 60 - 2
Source/test/tests/VirtualStackTest.cpp

@@ -1,6 +1,43 @@
 
 
 #include "../testTools.h"
 #include "../testTools.h"
 #include "../../DFPSR/base/virtualStack.h"
 #include "../../DFPSR/base/virtualStack.h"
+#include "../../DFPSR/base/threading.h"
+#include <random>
+
+inline int random(int min, int max) {
+	return (std::rand() % (1 + max - min)) + min;
+}
+
+static bool bruteTest(int maxSize, int maxDepth) {
+	// Select a random power of two as the alignment.
+	uintptr_t alignment = 1 << random(0, 8);
+	// The padded size does not have rounding requirements.
+	uint64_t paddedSize = random(1, maxSize);
+	uint64_t start = random(0, 255);
+	uint64_t stride = random(0, 255);
+	UnsafeAllocation allocation = virtualStack_push(paddedSize, memory_createAlignmentAndMask(alignment));
+	if ((uintptr_t)(allocation.data) % alignment != 0) {
+		virtualStack_pop();
+		return false;
+	}
+	for (int i = 0; i < paddedSize; i++) {
+		allocation.data[i] = i * stride + start;
+	}
+	if (maxDepth > 1) {
+		if (!bruteTest(maxSize, maxDepth - 1)) {
+			virtualStack_pop();
+			return false;
+		}
+	}
+	for (int i = 0; i < paddedSize; i++) {
+		if (allocation.data[i] != (uint8_t)(i * stride + start)) {
+			virtualStack_pop();
+			return false;
+		}
+	}
+	virtualStack_pop();
+	return true;
+}
 
 
 START_TEST(VirtualStack)
 START_TEST(VirtualStack)
 	{ // Single threaded
 	{ // Single threaded
@@ -21,21 +58,26 @@ START_TEST(VirtualStack)
 			{
 			{
 				VirtualStackAllocation<int32_t> y(3);
 				VirtualStackAllocation<int32_t> y(3);
 				pointerY = y;
 				pointerY = y;
+				// Check that the memory address pointed to is evenly divisible by the type's alignment.
 				ASSERT_EQUAL((uintptr_t)(y.getUnsafe()) % alignof(int32_t), 0);
 				ASSERT_EQUAL((uintptr_t)(y.getUnsafe()) % alignof(int32_t), 0);
 				y[0] =  2147483000;
 				y[0] =  2147483000;
 				y[1] = -2147483000;
 				y[1] = -2147483000;
 				y[2] =  65;
 				y[2] =  65;
 				#ifdef SAFE_POINTER_CHECKS
 				#ifdef SAFE_POINTER_CHECKS
+					// This should crash because -1 is outside of the 0..2 range.
 					ASSERT_CRASH(y[-1]);
 					ASSERT_CRASH(y[-1]);
 				#endif
 				#endif
+				// Reading within bounds and checking that the data was stored correctly.
 				ASSERT_EQUAL(y[0],  2147483000);
 				ASSERT_EQUAL(y[0],  2147483000);
 				ASSERT_EQUAL(y[1], -2147483000);
 				ASSERT_EQUAL(y[1], -2147483000);
 				ASSERT_EQUAL(y[2],  65);
 				ASSERT_EQUAL(y[2],  65);
 				#ifdef SAFE_POINTER_CHECKS
 				#ifdef SAFE_POINTER_CHECKS
+					// This should crash because 3 is outside of the 0..2 range.
 					ASSERT_CRASH(y[3]);
 					ASSERT_CRASH(y[3]);
 				#endif
 				#endif
 			}
 			}
 			#ifdef SAFE_POINTER_CHECKS
 			#ifdef SAFE_POINTER_CHECKS
+				// This should crash because pointerY points to memory that was freed when y's scope ended.
 				ASSERT_CRASH(pointerY[0]);
 				ASSERT_CRASH(pointerY[0]);
 			#endif
 			#endif
 		}
 		}
@@ -54,8 +96,24 @@ START_TEST(VirtualStack)
 		#ifdef SAFE_POINTER_CHECKS
 		#ifdef SAFE_POINTER_CHECKS
 			ASSERT_CRASH(x[9]);
 			ASSERT_CRASH(x[9]);
 		#endif
 		#endif
+		// TODO: Try to access memory from another thread and assert that it triggers an exception.
+	}
+	{ // Single threaded bruteforce test
+		ASSERT(bruteTest(10000, 1000));
 	}
 	}
-	{ // Multi threaded
-		// TODO: Create a bruteforce test using multiple threads.
+	{ // Multi threaded bruteforce test
+		const int jobCount = 10;
+		bool results[jobCount] = {};
+		List<std::function<void()>> jobs;
+		for (int i = 0; i < jobCount; i++) {
+			bool* result = &results[i];
+			jobs.push([result, i](){
+				*result = bruteTest(10000, 1000);
+			});
+		}
+		threadedWorkFromList(jobs);
+		for (int i = 0; i < jobCount; i++) {
+			ASSERT(results[i]);
+		}
 	}
 	}
 END_TEST
 END_TEST