StagingBuffer: Large slots should be aligned to alignment

Slots whose stride size is larger than the slot alignment value
should have their size rounded up to the alignment size so that
the slots that follow them will also be aligned.
This commit is contained in:
2025-11-20 00:03:50 -04:00
parent 5789a31e23
commit 0cfb0a9c07
3 changed files with 167 additions and 361 deletions
@@ -68,10 +68,13 @@ static size_t calculateMaxAlignment(
void StagingBuffer::computeSlotStrideAndBufferSize() void StagingBuffer::computeSlotStrideAndBufferSize()
{ {
// Slot stride is the maximum of alignment and padding // Slot stride is the maximum of alignment and padding, rounded up to a multiple of alignment
slotStrideNBytes = std::max( size_t minSlotStride = std::max(
inputConstraints.slotStartAlignmentByteVal, inputConstraints.slotStartAlignmentByteVal,
inputConstraints.slotPadToNBytes); inputConstraints.slotPadToNBytes);
slotStrideNBytes = ((minSlotStride + inputConstraints.slotStartAlignmentByteVal - 1)
/ inputConstraints.slotStartAlignmentByteVal)
* inputConstraints.slotStartAlignmentByteVal;
// Calculate maximum alignment needed for first slot (must satisfy both frame and slot alignment) // Calculate maximum alignment needed for first slot (must satisfy both frame and slot alignment)
size_t maxAlignment = calculateMaxAlignment( size_t maxAlignment = calculateMaxAlignment(
+1 -1
View File
@@ -96,7 +96,7 @@ public:
+ ": StimulusFrame: failed to create clBuffer"); + ": StimulusFrame: failed to create clBuffer");
} }
std::cout << __func__ << ": StimulusFrame: created clBuffer with size " << slotDesc.nBytes << " bytes @ " << (const void*)slotDesc.vaddr << std::endl; // std::cout << __func__ << ": StimulusFrame: created clBuffer with size " << slotDesc.nBytes << " bytes @ " << (const void*)slotDesc.vaddr << std::endl;
} }
~StimulusFrame() = default; ~StimulusFrame() = default;
@@ -9,76 +9,6 @@
namespace smo { namespace smo {
namespace stim_buff { namespace stim_buff {
// Helper function to calculate expected buffer size using the same logic as computeSlotStrideAndBufferSize
static size_t calculateExpectedBufferSize(
size_t nSlots,
const StagingBuffer::IOEngineConstraints& constraints)
{
// Slot stride is the maximum of alignment and padding
size_t slotStrideNBytes = std::max(
constraints.slotStartAlignmentByteVal,
constraints.slotPadToNBytes);
// Calculate maximum alignment needed for first slot
size_t maxAlignment;
if (constraints.frameStartAlignmentByteVal >= constraints.slotStartAlignmentByteVal)
{
if (constraints.frameStartAlignmentByteVal % constraints.slotStartAlignmentByteVal == 0)
{
maxAlignment = constraints.frameStartAlignmentByteVal;
}
else
{
maxAlignment = std::max(
constraints.frameStartAlignmentByteVal,
constraints.slotStartAlignmentByteVal);
}
}
else
{
if (constraints.slotStartAlignmentByteVal % constraints.frameStartAlignmentByteVal == 0)
{
maxAlignment = constraints.slotStartAlignmentByteVal;
}
else
{
maxAlignment = std::max(
constraints.frameStartAlignmentByteVal,
constraints.slotStartAlignmentByteVal);
}
}
// Calculate minimum buffer size
size_t minBufferSize = std::max(
constraints.framePadToNBytes,
constraints.slotPadToNBytes);
// Calculate total size needed for nSlots slots
size_t slotAreaSize = nSlots * slotStrideNBytes;
// Add padding space at buffer start for alignment offset (worst case: max alignment - 1)
size_t alignmentPadding = maxAlignment - 1;
// Total size needed: alignment padding + slot area, then ensure minimum is met
size_t rawSize = alignmentPadding + slotAreaSize;
if (rawSize < minBufferSize)
{
rawSize = minBufferSize;
}
// Align up to the maximum alignment to ensure we can always find a valid offset
return ((rawSize + maxAlignment - 1) / maxAlignment) * maxAlignment;
}
// Helper function to calculate expected slot stride
static size_t calculateExpectedSlotStride(
const StagingBuffer::IOEngineConstraints& constraints)
{
return std::max(
constraints.slotStartAlignmentByteVal,
constraints.slotPadToNBytes);
}
// Helper function to create test constraints // Helper function to create test constraints
static StagingBuffer::IOEngineConstraints createTestConstraints( static StagingBuffer::IOEngineConstraints createTestConstraints(
size_t slotStartAlignment, size_t slotStartAlignment,
@@ -117,357 +47,230 @@ protected:
size_t pageSize; size_t pageSize;
}; };
// Test 1: Single slot, minimal padding, small alignments // Helper function to verify all slots are page-aligned and have correct stride
TEST_F(StagingBufferTest, SingleSlotMinimalAlignment) { static void verifyAllSlotsPageAligned(
size_t nSlots = 1; const StagingBuffer& buffer,
auto constraints = createTestConstraints(4, 16, 4, 16); size_t expectedSlotStride,
size_t pageSize)
StagingBuffer buffer(constraints, constraints, nSlots); {
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify FrameAssemblyDesc
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer); auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr); ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots); ASSERT_GT(frameDesc->slots.size(), 0u);
EXPECT_EQ(frameDesc->slots.size(), nSlots);
// Verify first slot alignment // Verify slot stride is a multiple of page size
if (!frameDesc->slots.empty()) { EXPECT_EQ(expectedSlotStride % pageSize, 0u)
verifyAlignment(frameDesc->slots[0].vaddr, constraints.slotStartAlignmentByteVal, << "Slot stride " << expectedSlotStride
"First slot"); << " should be a multiple of page size " << pageSize;
verifyAlignment(frameDesc->slots[0].vaddr, constraints.frameStartAlignmentByteVal,
"First slot frame alignment");
}
}
// Test 2: Multiple slots, no alignment padding needed // Verify first slot is page-aligned
TEST_F(StagingBufferTest, MultipleSlotsMinimalAlignment) { verifyAlignment(frameDesc->slots[0].vaddr, pageSize, "First slot");
size_t nSlots = 10;
auto constraints = createTestConstraints(4, 16, 4, 16);
StagingBuffer buffer(constraints, constraints, nSlots); // Verify all subsequent slots are page-aligned and have correct stride
for (size_t i = 1; i < frameDesc->slots.size(); ++i) {
verifyAlignment(frameDesc->slots[i].vaddr, pageSize,
"Slot " + std::to_string(i));
size_t expectedSlotStride = calculateExpectedSlotStride(constraints); // Verify actual stride matches expected stride
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify all slots are properly spaced
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
for (size_t i = 0; i < frameDesc->slots.size(); ++i) {
if (i > 0) {
size_t actualStride = reinterpret_cast<uintptr_t>(frameDesc->slots[i].vaddr) - size_t actualStride = reinterpret_cast<uintptr_t>(frameDesc->slots[i].vaddr) -
reinterpret_cast<uintptr_t>(frameDesc->slots[i-1].vaddr); reinterpret_cast<uintptr_t>(frameDesc->slots[i-1].vaddr);
EXPECT_EQ(actualStride, expectedSlotStride) EXPECT_EQ(actualStride, expectedSlotStride)
<< "Slot " << i << " stride mismatch"; << "Slot " << i << " stride mismatch: expected " << expectedSlotStride
} << ", got " << actualStride;
} }
} }
// Test 3: OpenCL Mesh constraints (real-world scenario) // Test 1: Small slot stride (< page size) - should round up to page size
TEST_F(StagingBufferTest, OpenClMeshConstraints) { TEST_F(StagingBufferTest, SmallSlotStrideRoundsUpToPageSize) {
size_t nSlots = 909; // 30000ms / 33ms size_t nSlots = 10;
size_t nDgramsPerFrame = 84; size_t smallSlotPad = 256; // Much smaller than typical page size (4096)
size_t nPointsPerDgram = 96; // SingleFirst/Strongest/Dual return mode
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 3; // 96768
auto constraints = createTestConstraints( auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment pageSize, // slotStartAlignment (page size)
smallSlotPad, // slotPadToNBytes (small)
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
// Slot stride should be rounded up to page size
size_t expectedSlotStride = pageSize;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Small slot pad should round up to page size";
verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
}
// Test 2: Slot stride equal to page size - should remain page size
TEST_F(StagingBufferTest, SlotStrideEqualToPageSize) {
size_t nSlots = 20;
auto constraints = createTestConstraints(
pageSize, // slotStartAlignment
pageSize, // slotPadToNBytes (equal to page size)
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = pageSize;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Slot stride equal to page size should remain unchanged";
verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
}
// Test 3: Slot stride slightly larger than page size (e.g., 336) - should round up
TEST_F(StagingBufferTest, SlotStrideSlightlyLargerThanPageSize) {
size_t nSlots = 50;
size_t slotPadToNBytes = 336; // Slightly larger than page size (4096)
auto constraints = createTestConstraints(
pageSize, // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment pageSize, // frameStartAlignment
pageSize); // framePadToNBytes pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots); StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints); // Should round up to next multiple of page size (4096)
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints); size_t expectedSlotStride = pageSize;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Slot stride 336 should round up to page size " << pageSize;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride); verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize); }
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
// Verify FrameAssemblyDesc // Test 4: Slot stride much larger than page size (e.g., 32256) - should round up to 32768
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer); TEST_F(StagingBufferTest, SlotStrideMuchLargerThanPageSize) {
ASSERT_NE(frameDesc, nullptr); size_t nSlots = 100;
EXPECT_EQ(frameDesc->numSlots, nSlots); size_t slotPadToNBytes = 32256; // Much larger than page size
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes); auto constraints = createTestConstraints(
pageSize, // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
// Verify first slot alignment StagingBuffer buffer(constraints, constraints, nSlots);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, pageSize, "First slot frame alignment"); // Should round up to next multiple of page size
// 32256 / 4096 = 7.875, so rounds up to 8 pages = 32768
size_t expectedSlotStride = ((slotPadToNBytes + pageSize - 1) / pageSize) * pageSize;
EXPECT_EQ(expectedSlotStride, 32768u)
<< "32256 should round up to 32768 (8 pages)";
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Slot stride should be rounded up to " << expectedSlotStride;
verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
}
// Test 5: Slot stride already a multiple of page size - should remain unchanged
TEST_F(StagingBufferTest, SlotStrideAlreadyMultipleOfPageSize) {
size_t nSlots = 30;
size_t slotPadToNBytes = pageSize * 3; // Already a multiple (e.g., 12288)
auto constraints = createTestConstraints(
pageSize, // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes (already multiple)
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
// Should remain unchanged
size_t expectedSlotStride = slotPadToNBytes;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Slot stride already a multiple of page size should remain unchanged";
verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
}
// Test 6: Multiple different slot stride sizes in sequence
TEST_F(StagingBufferTest, MultipleDifferentSlotStrideSizes) {
// Test with various slot pad sizes
struct TestCase {
size_t slotPadToNBytes;
size_t expectedRoundedStride;
};
std::vector<TestCase> testCases = {
{256, pageSize}, // Small: rounds to 1 page
{pageSize, pageSize}, // Equal: stays 1 page
{pageSize + 1, pageSize * 2}, // Slightly larger: rounds to 2 pages
{32256, 32768}, // Much larger: rounds to 8 pages
{pageSize * 5, pageSize * 5}, // Already multiple: stays 5 pages
{pageSize * 10 + 100, pageSize * 11}, // Large with remainder: rounds to 11 pages
};
for (const auto& testCase : testCases) {
size_t nSlots = 10;
auto constraints = createTestConstraints(
pageSize,
testCase.slotPadToNBytes,
pageSize,
pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
EXPECT_EQ(buffer.slotStrideNBytes, testCase.expectedRoundedStride)
<< "Slot pad " << testCase.slotPadToNBytes
<< " should round to " << testCase.expectedRoundedStride;
verifyAllSlotsPageAligned(buffer, testCase.expectedRoundedStride, pageSize);
} }
} }
// Test 4: OpenCL Intensity constraints // Test 7: Real-world scenario - PcloudIntensityStimulusBuffer (32256 bytes)
TEST_F(StagingBufferTest, OpenClIntensityConstraints) { TEST_F(StagingBufferTest, RealWorldPcloudIntensityScenario) {
size_t nSlots = 909; // 30000ms / 33ms size_t nSlots = 909; // histbuffMs=30000 / CONFIG_STIMBUFF_FRAME_PERIOD_MS=33
size_t nDgramsPerFrame = 84; size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 96; size_t nPointsPerDgram = 96;
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 1; // 32256 size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 1; // 32256
auto constraints = createTestConstraints( auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment pageSize, // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment pageSize, // frameStartAlignment
pageSize); // framePadToNBytes pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots); StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints); // Should round up to 32768 (8 pages)
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints); size_t expectedSlotStride = 32768;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Real-world 32256-byte slots should round to 32768";
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride); verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
// Verify FrameAssemblyDesc
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer); auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr); ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots); EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes); EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
} }
// Test 5: OpenCL Ambience constraints // Test 8: Real-world scenario - PcloudAmbienceStimulusBuffer (336 bytes)
TEST_F(StagingBufferTest, OpenClAmbienceConstraints) { TEST_F(StagingBufferTest, RealWorldPcloudAmbienceScenario) {
size_t nSlots = 909; // 30000ms / 33ms size_t nSlots = 909;
size_t nDgramsPerFrame = 84; size_t nDgramsPerFrame = 84;
size_t slotPadToNBytes = nDgramsPerFrame * sizeof(float); // 336 size_t slotPadToNBytes = nDgramsPerFrame * sizeof(float); // 336
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
}
// Test 6: io_uring constraints
TEST_F(StagingBufferTest, IoUringConstraints) {
size_t nSlots = 84;
auto constraints = createTestConstraints( auto constraints = createTestConstraints(
pageSize, // slotStartAlignment pageSize, // slotStartAlignment
1472, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(expectedSlotStride, pageSize) << "Slot stride should be max(alignment, padding) = pageSize";
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
}
// Test 7: Slot alignment larger than padding
TEST_F(StagingBufferTest, SlotAlignmentLargerThanPadding) {
size_t nSlots = 10;
auto constraints = createTestConstraints(64, 16, 64, 64);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
EXPECT_EQ(expectedSlotStride, 64u) << "Slot stride should be max(64, 16) = 64";
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, 64, "First slot");
}
}
// Test 8: Frame alignment larger than slot alignment
TEST_F(StagingBufferTest, FrameAlignmentLargerThanSlotAlignment) {
size_t nSlots = 10;
auto constraints = createTestConstraints(4, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % pageSize, 0u)
<< "Buffer size should be page-aligned";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, pageSize, "First slot frame alignment");
verifyAlignment(frameDesc->slots[0].vaddr, 4, "First slot slot alignment");
}
}
// Test 9: Slot alignment larger than frame alignment
TEST_F(StagingBufferTest, SlotAlignmentLargerThanFrameAlignment) {
size_t nSlots = 10;
size_t largeAlignment = 8192;
auto constraints = createTestConstraints(largeAlignment, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len % largeAlignment, 0u)
<< "Buffer size should be aligned to max alignment";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
verifyAlignment(frameDesc->slots[0].vaddr, largeAlignment, "First slot");
}
}
// Test 10: Minimum buffer size larger than calculated size
TEST_F(StagingBufferTest, MinimumBufferSizeEnforcement) {
size_t nSlots = 1;
auto constraints = createTestConstraints(4, 16, 4, 1024);
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
size_t actualBufferSize = buffer.getIoUringRegisterIoVec().iov_len;
EXPECT_GE(actualBufferSize, 1024u) << "Buffer size should be at least framePadToNBytes";
EXPECT_EQ(actualBufferSize, expectedBufferSize);
}
// Test 11: Actual PcloudIntensityStimulusBuffer scenario
TEST_F(StagingBufferTest, RealWorldPcloudIntensityScenario) {
size_t nSlots = 909; // histbuffMs=30000 / CONFIG_STIMBUFF_FRAME_PERIOD_MS=33
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 96; // SingleFirst/Strongest/Dual
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 1; // 32256
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment pageSize, // frameStartAlignment
pageSize); // framePadToNBytes pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots); StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints); // Should round up to page size
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints); size_t expectedSlotStride = pageSize;
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride)
<< "Real-world 336-byte slots should round to page size";
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride); verifyAllSlotsPageAligned(buffer, expectedSlotStride, pageSize);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
// Verify the buffer can hold all slots // Verify FrameAssemblyDesc
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer); auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr); ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->numSlots, nSlots); EXPECT_EQ(frameDesc->numSlots, nSlots);
// Verify all slots fit within buffer
if (!frameDesc->slots.empty()) {
uint8_t* bufferStart = buffer.getIoUringRegisterIoVec().iov_base;
size_t bufferSize = buffer.getIoUringRegisterIoVec().iov_len;
uint8_t* lastSlotEnd = frameDesc->slots.back().vaddr + frameDesc->slots.back().nBytes;
EXPECT_LE(lastSlotEnd - bufferStart, static_cast<ptrdiff_t>(bufferSize))
<< "Last slot should fit within buffer";
}
}
// Test 12: Different return modes - Triple (90 points)
TEST_F(StagingBufferTest, TripleReturnModeMesh) {
size_t nSlots = 909;
size_t nDgramsPerFrame = 84;
size_t nPointsPerDgram = 90; // Triple return mode
size_t slotPadToNBytes = nDgramsPerFrame * nPointsPerDgram * sizeof(float) * 3; // 90720
auto constraints = createTestConstraints(
sizeof(float), // slotStartAlignment
slotPadToNBytes, // slotPadToNBytes
pageSize, // frameStartAlignment
pageSize); // framePadToNBytes
StagingBuffer buffer(constraints, constraints, nSlots);
size_t expectedSlotStride = calculateExpectedSlotStride(constraints);
size_t expectedBufferSize = calculateExpectedBufferSize(nSlots, constraints);
EXPECT_EQ(buffer.slotStrideNBytes, expectedSlotStride);
EXPECT_EQ(buffer.getIoUringRegisterIoVec().iov_len, expectedBufferSize);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes); EXPECT_EQ(frameDesc->slotSizeBytes, slotPadToNBytes);
} }
// Test 13: Verify firstSlotOffsetNBytes is valid
TEST_F(StagingBufferTest, FirstSlotOffsetValidation) {
size_t nSlots = 100;
auto constraints = createTestConstraints(4, 16, pageSize, pageSize);
StagingBuffer buffer(constraints, constraints, nSlots);
EXPECT_LE(buffer.firstSlotOffsetNBytes, buffer.getIoUringRegisterIoVec().iov_len)
<< "First slot offset should be within buffer";
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
if (!frameDesc->slots.empty()) {
uint8_t* bufferStart = buffer.getIoUringRegisterIoVec().iov_base;
uint8_t* firstSlot = frameDesc->slots[0].vaddr;
size_t calculatedOffset = firstSlot - bufferStart;
EXPECT_EQ(calculatedOffset, buffer.firstSlotOffsetNBytes)
<< "First slot offset should match calculated value";
}
}
// Test 14: Verify all slots are properly aligned
TEST_F(StagingBufferTest, AllSlotsProperlyAligned) {
size_t nSlots = 50;
auto constraints = createTestConstraints(64, 64, 64, 64);
StagingBuffer buffer(constraints, constraints, nSlots);
auto frameDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(buffer);
ASSERT_NE(frameDesc, nullptr);
for (size_t i = 0; i < frameDesc->slots.size(); ++i) {
verifyAlignment(frameDesc->slots[i].vaddr, constraints.slotStartAlignmentByteVal,
"Slot " + std::to_string(i));
verifyAlignment(frameDesc->slots[i].vaddr, constraints.frameStartAlignmentByteVal,
"Slot " + std::to_string(i) + " frame alignment");
}
}
} // namespace stim_buff } // namespace stim_buff
} // namespace smo } // namespace smo