From daae431e5605df4995818b3eb2dfb4f7fe932fb7 Mon Sep 17 00:00:00 2001 From: Jack Elliott Date: Tue, 1 Sep 2026 08:42:52 +1200 Subject: [PATCH 1/3] [HLSL] Pad LinAlg MatVec descriptor strides to a multiple of 16 bytes Proposal 0035 requires that the Stride argument of a matrix Load on a [RW]ByteAddressBuffer be a multiple of 16 bytes. matrixStrideBytes returned the tightly packed stride, so three positive tests were driving the shader with an 8-byte descriptor stride: MatVecMul_Thread_4x8_F16_ColumnMajor 4 * 2 bytes = 8 MatVecMul_Thread_4x8_I8_Interpreted 8 * 1 bytes = 8 MatVecMul_Thread_4x8_U8_Interpreted 8 * 1 bytes = 8 These pass today, but the proposal does not define runtime behaviour once the precondition is violated, so a pass cannot be used as conformance evidence and a stricter implementation could fail them at any time. Round the stride up to a multiple of 16 in matrixStrideBytes, which is the single value feeding the host buffer encoding, the buffer size and the -DMATRIX_STRIDE shader argument, so all three stay consistent by construction. Padding only inserts zero gaps between rows or columns; calculateExpected indexes the logical matrix values and never consults the stride, so no oracle changes. The other three MatVec cases are already 16-byte aligned and are unaffected: U32 4x8 RowMajor is 32, FP8 4x16 is 16, and F16 4x8 RowMajor is 16. Also add the stride postcondition to isCaseValid, which was deliberately left out while the I8 and U8 cases still used a stride of 8. Negative controls, both predicted before running. Removing the padding while keeping the postcondition failed exactly the three cases above and nothing else, confirming they were the only violators. Keeping the padded shader stride while encoding the host buffer at the packed stride also failed exactly those three, confirming the padded stride reaches the shader and the buffer is really laid out at the new stride. Per-test differential against 832047681 with no outcome changes in either configuration: released SDK 78/73/0/5, preview SDK 78/74/2/2. The two preview failures are the pre-existing toLinAlgDataType FP8 gap, unrelated to this change. Assisted-by: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b --- tools/clang/unittests/HLSLExec/LinAlgTests.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp index 7d77a72d89..21d294cd4c 100644 --- a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp +++ b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp @@ -2089,6 +2089,8 @@ encodePackedVector(ComponentType Type, const std::vector &Values) { return Bytes; } +static constexpr size_t MatrixStrideAlignmentBytes = 16; + static std::optional matrixStrideBytes(const CaseData &Case) { const std::optional ComponentSize = componentByteSize(Case.MatrixType); @@ -2096,7 +2098,9 @@ static std::optional matrixStrideBytes(const CaseData &Case) { return std::nullopt; const size_t MinorCount = Case.Layout == MatrixLayout::RowMajor ? Case.N : Case.M; - return MinorCount * *ComponentSize; + const size_t PackedStride = MinorCount * *ComponentSize; + return (PackedStride + MatrixStrideAlignmentBytes - 1) / + MatrixStrideAlignmentBytes * MatrixStrideAlignmentBytes; } static std::optional> @@ -2191,6 +2195,10 @@ static bool isCaseValid(const CaseData &Case) { return false; } + const std::optional Stride = matrixStrideBytes(Case); + if (!Stride || *Stride % MatrixStrideAlignmentBytes != 0) + return false; + const bool ExpectedSigned = Case.ResultType != ComponentType::U32; return Case.OutputSigned == ExpectedSigned; } From 25bf3feae603e2cb8d6a09d007870506b4ee5a61 Mon Sep 17 00:00:00 2001 From: Jack Elliott Date: Tue, 1 Sep 2026 09:13:20 +1200 Subject: [PATCH 2/3] [HLSL] Align LinAlg memory strides and group-shared matrix starts Proposal 0035 requires a matrix stride in memory to be a multiple of 16 bytes, and requires the start of a group-shared matrix to be 128-byte aligned. Seven more positive tests were driving out-of-contract layouts: CopyConvert_Wave_4x8_F32_ToF16_Transpose destination stride 8 LoadStoreMemory_Wave_4x8_F16_RowMajorOffsetPadded offset 8, stride 24 and canonical stride 8 LoadStoreMemory_Wave_4x8_F32_ColumnMajorOffsetPadded offset 16, stride 24 LoadStoreMemory_ThreadGroup_4x8_F16 offset 8, stride 12 AccumulateMemory_Wave_16x16_F16 padded subcase offset 8, stride 24 AccumulateMemoryContention_Wave_4x8_F16 offset 8, stride 24 AccumulateMemoryContention_Wave_4x8_I32 offset 16, stride 40 Round MatrixParams::strideBytes up to a multiple of 16, which covers the CopyConvert destination and every other descriptor stride derived from it, and keeps returning 0 for the optimal layouts where the proposal requires it. Move the alignment constants next to it so the MatVec helper, the group-shared gate and the existing DescriptorAlignedOffset all share one definition instead of repeating the literals. Re-pad the group-shared layouts so each one still exercises a padded stride and a non-zero start while satisfying the preconditions, and enforce both rules in getGroupSharedBufferDescription, which every group-shared Load, Store and Accumulate path already funnels through. That turns a silent out-of-contract layout into an immediate failure and stops this class of violation being reintroduced. The oracles follow the layout rather than the other way around: buffer sizes, the host encoding and the shader offset and stride arguments are all derived from the same MatrixBufferLayout, so re-padding needs no separate expectation update. The unaligned layouts remaining in LinAlgCPUOracleTests are deliberate. Those are host-only tests of writeMatrixBuffer and verifyMatrixBuffer padding behaviour, they run no shader, and the DXIL preconditions do not apply to them. Negative controls, both predicted before running. Reverting the ThreadGroup target offset to 8 failed exactly that test with "Invalid group-shared buffer description", confirming the new gate is live. Keeping the padded DST_STRIDE while encoding the host destination at the packed stride failed exactly CopyConvert_Wave_4x8_F32_ToF16_Transpose, confirming the padded stride reaches the shader. Per-test differential against 832047681 with no outcome changes in either configuration: released SDK 78/73/0/5, preview SDK 78/74/2/2. abicheck EXITCODE=0. AccumulateMemoryContention_Wave_4x8_I32 is capability-skipped on the test device, so its new layout is compiled but not executed here. Assisted-by: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b --- .../clang/unittests/HLSLExec/LinAlgTests.cpp | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp index 21d294cd4c..5d25c87cce 100644 --- a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp +++ b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp @@ -81,6 +81,16 @@ static uint8_t elementSize(ComponentType CT) { } } +// Proposal 0035 requires matrix strides in memory to be a multiple of 16 bytes +// and non-zero matrix start offsets to be 128-byte aligned. +static constexpr size_t MatrixStrideAlignmentBytes = 16; +static constexpr size_t MatrixOffsetAlignmentBytes = 128; + +static constexpr size_t alignMatrixStride(size_t PackedStrideBytes) { + return (PackedStrideBytes + MatrixStrideAlignmentBytes - 1) / + MatrixStrideAlignmentBytes * MatrixStrideAlignmentBytes; +} + struct MatrixParams { ComponentType CompType; MatrixDim M; @@ -95,9 +105,9 @@ struct MatrixParams { size_t strideBytes() const { uint32_t ES = elementSize(CompType); if (Layout == MatrixLayout::RowMajor) - return N * ES; + return alignMatrixStride(N * ES); if (Layout == MatrixLayout::ColumnMajor) - return M * ES; + return alignMatrixStride(M * ES); // If not Row/Col major, spec says to use 0 return 0; } @@ -2089,8 +2099,6 @@ encodePackedVector(ComponentType Type, const std::vector &Values) { return Bytes; } -static constexpr size_t MatrixStrideAlignmentBytes = 16; - static std::optional matrixStrideBytes(const CaseData &Case) { const std::optional ComponentSize = componentByteSize(Case.MatrixType); @@ -2098,9 +2106,7 @@ static std::optional matrixStrideBytes(const CaseData &Case) { return std::nullopt; const size_t MinorCount = Case.Layout == MatrixLayout::RowMajor ? Case.N : Case.M; - const size_t PackedStride = MinorCount * *ComponentSize; - return (PackedStride + MatrixStrideAlignmentBytes - 1) / - MatrixStrideAlignmentBytes * MatrixStrideAlignmentBytes; + return alignMatrixStride(MinorCount * *ComponentSize); } static std::optional> @@ -3876,7 +3882,7 @@ static cpu_oracle::MatrixBufferLayout packedLayout(const MatrixParams &Params) { // Where the padded cases put the matrix. Independent of the alignment above, // which is the contract rather than a placement, but constrained by it. -static constexpr size_t DescriptorAlignedOffset = 128; +static constexpr size_t DescriptorAlignedOffset = MatrixOffsetAlignmentBytes; static_assert(DescriptorAlignedOffset % DescriptorDeclaredAlignment == 0, "descriptor offset must keep the first element aligned"); @@ -7665,6 +7671,13 @@ getGroupSharedBufferDescription(const MatrixParams &Params, if (*RequiredBytes % ElementBytes != 0) return false; + // Proposal 0035 requires a 16-byte aligned stride and a 128-byte aligned + // matrix start for group-shared Load, Store and Accumulate. + if (Layout.StrideBytes % MatrixStrideAlignmentBytes != 0) + return false; + if (Layout.OffsetBytes % MatrixOffsetAlignmentBytes != 0) + return false; + // Safely compute the GuardBytes and BufferSize. if (!cpu_oracle::checkedMultiply(GroupSharedTrailingGuardElements, ElementBytes, GuardBytes)) @@ -7949,13 +7962,13 @@ void DxilConf_SM610_LinAlg:: const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::RowMajor, - /*OffsetBytes=*/8, - /*StrideBytes=*/24, + /*OffsetBytes=*/128, + /*StrideBytes=*/32, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::ColumnMajor, /*OffsetBytes=*/0, - /*StrideBytes=*/8, + /*StrideBytes=*/16, }; runBidirectionalGroupSharedTransfer(D3DDevice, DxcSupport, Params, Target, Canonical, VerboseLogging, @@ -7983,8 +7996,8 @@ void DxilConf_SM610_LinAlg:: const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::ColumnMajor, - /*OffsetBytes=*/16, - /*StrideBytes=*/24, + /*OffsetBytes=*/128, + /*StrideBytes=*/32, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::RowMajor, @@ -8015,8 +8028,8 @@ void DxilConf_SM610_LinAlg::LoadStoreMemory_ThreadGroup_4x8_F16() { const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::ColumnMajor, - /*OffsetBytes=*/8, - /*StrideBytes=*/12, + /*OffsetBytes=*/128, + /*StrideBytes=*/32, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::RowMajor, @@ -8289,8 +8302,8 @@ static void runPaddedGroupSharedAccumulateCase( const cpu_oracle::MatrixBufferLayout Memory = { MatrixLayout::RowMajor, - /*OffsetBytes=*/8, - /*StrideBytes=*/24, + /*OffsetBytes=*/128, + /*StrideBytes=*/48, }; runGroupSharedAccumulate(Device, DxcSupport, Params, Memory, /*InitialValue=*/12, @@ -8326,8 +8339,9 @@ static void runGroupSharedAccumulateContention( const size_t ElementBytes = elementSize(CompType); const cpu_oracle::MatrixBufferLayout Memory = { MatrixLayout::RowMajor, - /*OffsetBytes=*/4 * ElementBytes, - /*StrideBytes=*/Params.N * ElementBytes + 8, + /*OffsetBytes=*/MatrixOffsetAlignmentBytes, + /*StrideBytes=*/alignMatrixStride(Params.N * ElementBytes) + + MatrixStrideAlignmentBytes, }; runGroupSharedAccumulate(Device, DxcSupport, Params, Memory, /*InitialValue=*/7, From 52992693ebc7603a77733fabef5e5c117b8e830a Mon Sep 17 00:00:00 2001 From: Jack Elliott Date: Tue, 1 Sep 2026 12:24:50 +1200 Subject: [PATCH 3/3] [HLSL] Derive LinAlg padded layouts from the alignment constants The previous commit introduced MatrixStrideAlignmentBytes, MatrixOffsetAlignmentBytes and alignMatrixStride(), but only runGroupSharedAccumulateContention actually used them. The four other group-shared layouts it updated kept hand-computed literals, so the file carried both the rule and a set of numbers that happened to satisfy it, with nothing tying the two together. Each padded offset is now MatrixOffsetAlignmentBytes, and each stride is derived from the matrix that uses it: alignMatrixStride() of the minor extent times the component size for a tight-but-aligned stride, plus one or two MatrixStrideAlignmentBytes units where the case deliberately pads. That makes the intent readable at the use site - a reader can now see which layouts are packed and which are padded without recomputing element sizes - and it keeps the strides legal if a case ever changes shape or component type. Zero offsets are left as literals. Proposal 0035 constrains the alignment of a non-zero matrix start, so a zero there means "no offset" rather than an alignment choice, and naming it after the alignment constant would misdescribe it. Two nearby groups of literals are deliberately unchanged. The descriptor layouts already route their offsets through DescriptorAlignedOffset and carry comments deriving their strides, and they are not touched by this change. The LinAlgCPUOracleTests layouts use offsets and strides such as 4 and 12 precisely because they are not legal GPU matrix strides; they exercise the host codec's arithmetic, and forcing them onto 16- and 128-byte boundaries would delete that coverage. No value changes. Every substitution was checked against the literal it replaced by static_assert in a standalone translation unit, and that check was verified non-vacuous by injecting two wrong expectations and confirming both failed to compile. On preview WARP the suite is Total=78, Passed=74, Failed=2, Skipped=2, with zero per-test differences against the parent commit across all 78 methods. The two failures are the FP8 conversion cases, which need the type mapping in #8866 and are unrelated to this change. Addresses review feedback from @alsepkow. Assisted-by: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b --- .../clang/unittests/HLSLExec/LinAlgTests.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp index 5d25c87cce..2992737896 100644 --- a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp +++ b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp @@ -7960,15 +7960,17 @@ void DxilConf_SM610_LinAlg:: SelectedWaveSize)) return; + const size_t ElementBytes = elementSize(Params.CompType); const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::RowMajor, - /*OffsetBytes=*/128, - /*StrideBytes=*/32, + /*OffsetBytes=*/MatrixOffsetAlignmentBytes, + /*StrideBytes=*/alignMatrixStride(Params.N * ElementBytes) + + MatrixStrideAlignmentBytes, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::ColumnMajor, /*OffsetBytes=*/0, - /*StrideBytes=*/16, + /*StrideBytes=*/alignMatrixStride(Params.M * ElementBytes), }; runBidirectionalGroupSharedTransfer(D3DDevice, DxcSupport, Params, Target, Canonical, VerboseLogging, @@ -7994,15 +7996,17 @@ void DxilConf_SM610_LinAlg:: SelectedWaveSize)) return; + const size_t ElementBytes = elementSize(Params.CompType); const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::ColumnMajor, - /*OffsetBytes=*/128, - /*StrideBytes=*/32, + /*OffsetBytes=*/MatrixOffsetAlignmentBytes, + /*StrideBytes=*/alignMatrixStride(Params.M * ElementBytes) + + MatrixStrideAlignmentBytes, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::RowMajor, /*OffsetBytes=*/0, - /*StrideBytes=*/32, + /*StrideBytes=*/alignMatrixStride(Params.N * ElementBytes), }; runBidirectionalGroupSharedTransfer(D3DDevice, DxcSupport, Params, Target, Canonical, VerboseLogging, @@ -8026,15 +8030,17 @@ void DxilConf_SM610_LinAlg::LoadStoreMemory_ThreadGroup_4x8_F16() { SelectedWaveSize)) return; + const size_t ElementBytes = elementSize(Params.CompType); const cpu_oracle::MatrixBufferLayout Target = { MatrixLayout::ColumnMajor, - /*OffsetBytes=*/128, - /*StrideBytes=*/32, + /*OffsetBytes=*/MatrixOffsetAlignmentBytes, + /*StrideBytes=*/alignMatrixStride(Params.M * ElementBytes) + + MatrixStrideAlignmentBytes, }; const cpu_oracle::MatrixBufferLayout Canonical = { MatrixLayout::RowMajor, /*OffsetBytes=*/0, - /*StrideBytes=*/16, + /*StrideBytes=*/alignMatrixStride(Params.N * ElementBytes), }; runBidirectionalGroupSharedTransfer(D3DDevice, DxcSupport, Params, Target, Canonical, VerboseLogging, @@ -8300,10 +8306,12 @@ static void runPaddedGroupSharedAccumulateCase( return; } + const size_t ElementBytes = elementSize(Params.CompType); const cpu_oracle::MatrixBufferLayout Memory = { MatrixLayout::RowMajor, - /*OffsetBytes=*/128, - /*StrideBytes=*/48, + /*OffsetBytes=*/MatrixOffsetAlignmentBytes, + /*StrideBytes=*/alignMatrixStride(Params.N * ElementBytes) + + 2 * MatrixStrideAlignmentBytes, }; runGroupSharedAccumulate(Device, DxcSupport, Params, Memory, /*InitialValue=*/12,