[llvm] [HLSLSemanticSignatures] Implement the optimal packing of elements (PR #218064)
Finn Plummer via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 23 13:51:24 PDT 2026
https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/218064
>From 4a231a71ab2291a5c0cb9196d9ffe60ecf11891a Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 21:51:35 +0000
Subject: [PATCH 01/17] add optimal packing api and docs
---
llvm/docs/DirectX/SemanticSignatures.md | 75 +++++++++++++++++++
.../Frontend/HLSL/SemanticSignaturePacking.h | 8 ++
.../HLSL/SemanticSignaturePacking.cpp | 9 +++
3 files changed, 92 insertions(+)
diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 87602be153066..56994bb07ad9d 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -232,6 +232,81 @@ reg2: A[2].xyz | D.w
reg3: C.xy | unused.zw
```
+### Prefix-Stable Packing
+
+Prefix-stable packing is used for signatures that connect programmable shader
+stages or carry patch constant data. Elements are visited in declaration order
+and placed at the first compatible location in the 32-row by 4-column register
+space. Once an element is placed it is never moved, so appending elements to a
+signature does not change the locations assigned to its existing prefix.
+
+Elements can share unused components in a row when all applicable packing
+constraints are satisfied:
+
+- Every element in a row must have a compatible interpolation mode.
+- When native 16-bit types are enabled, every element in a row must have the
+ same component width. Without native 16-bit types, min-precision values
+ occupy 32-bit components.
+- Components are ordered from arbitrary values, to system values, to system
+ generated values.
+- A system value or system generated value cannot be placed in a dynamically
+ indexed row. Multi-row elements define the dynamically indexed range that
+ they cover.
+
+Some semantic interpretations require additional handling:
+
+- `SV_ClipDistance` and `SV_CullDistance` are packed only with each other in
+ dedicated rows. Together they may occupy at most eight components across at
+ most two rows. The rows must be adjacent when a clip or cull element spans
+ multiple rows.
+- A multi-row tessellation factor is searched for only in the last column.
+- Geometry shader output streams are packed independently.
+
+For example:
+
+```hlsl
+struct VSOut {
+ float3 A[3] : A;
+ float1x2 B : B;
+ float2 C : C;
+ float D : D;
+};
+```
+
+Assuming `B` has column-major matrix orientation, the signature is allocated
+as:
+
+```text
+reg0: A[0].xyz | B[0][0].w
+reg1: A[1].xyz | B[0][1].w
+reg2: A[2].xyz | D.w
+reg3: C.xy | unused.zw
+```
+
+### Optimized Packing
+
+Optimized packing partitions eligible elements into groups and packs the groups
+in this order:
+
+1. Four-column arbitrary and system-value elements.
+2. Multi-row tessellation factors, which are restricted to the last column.
+3. Arbitrary elements.
+4. System-value elements, including single-row tessellation factors.
+5. `SV_ClipDistance` and `SV_CullDistance` elements.
+6. System-generated-value elements.
+
+Within each group, elements are ordered first by the numeric value of their
+interpolation mode, then by decreasing row count, then by decreasing column
+count, and finally by increasing signature ID. Component bit width is not a
+sort key; it remains a compatibility constraint when elements are placed in a
+row.
+
+The sorted elements are then placed using the prefix-stable packing algorithm.
+This is a greedy optimized ordering rather than an exhaustive search for a
+minimum-row layout. Reordering can reduce the number of rows occupied, but
+means that appending an element may change locations assigned to existing
+elements.
+
### Indexed Packing
Indexed packing is used for a pixel shader output signature. Each eligible
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index b7e6eacce9269..175354b661d03 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -105,6 +105,14 @@ LLVM_ABI Expected<unsigned>
packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
+/// Sorts eligible signature elements using the optimized ordering and then
+/// packs them with the prefix-stable packing algorithm.
+///
+/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
+LLVM_ABI Error packSignatureOptimized(
+ MutableArrayRef<SemanticSignatureElement> Elements,
+ Triple::EnvironmentType ShaderStage, IOType IOTy, bool UseNative16BitTypes);
+
} // namespace llvm::hlsl
#endif // LLVM_FRONTEND_HLSL_SEMANTICSIGNATUREPACKING_H
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 7395b42ec4f28..0264e9177691a 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -543,3 +543,12 @@ Expected<unsigned> llvm::hlsl::packSignatureIndexed(
return NumRows;
}
+
+Error llvm::hlsl::packSignatureOptimized(
+ MutableArrayRef<SemanticSignatureElement> Elements,
+ Triple::EnvironmentType ShaderStage, IOType IOTy,
+ bool UseNative16BitTypes) {
+ // TODO: Sort Elements for optimal packing.
+ return packSignaturePrefixStable(Elements, ShaderStage, IOTy,
+ UseNative16BitTypes);
+}
>From 565865014e909bf5e358e9eaf47945de7e641f81 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 22:07:37 +0000
Subject: [PATCH 02/17] add testing
---
.../HLSLSemanticSignaturePackingTest.cpp | 366 ++++++++++++++----
1 file changed, 291 insertions(+), 75 deletions(-)
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index bef7d881f3f1d..094a831612aba 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -61,6 +61,7 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
Stacked,
Indexed,
PrefixStable,
+ Optimized,
};
SmallVector<SemanticSignatureElement>
@@ -95,6 +96,9 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
case PackingMethod::PrefixStable:
return packSignaturePrefixStable(Elements, Config.ShaderStage,
Config.IOTy, Config.UseNative16BitTypes);
+ case PackingMethod::Optimized:
+ return packSignatureOptimized(Elements, Config.ShaderStage, Config.IOTy,
+ Config.UseNative16BitTypes);
}
llvm_unreachable("invalid packing method");
}
@@ -184,7 +188,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, EmptySignature) {
TestConfig Config(Triple::EnvironmentType::Vertex, IOType::Out, {});
for (PackingMethod Method :
- {PackingMethod::Stacked, PackingMethod::PrefixStable}) {
+ {PackingMethod::Stacked, PackingMethod::PrefixStable,
+ PackingMethod::Optimized}) {
Config.IOTy = Method == PackingMethod::Stacked ? IOType::In : IOType::Out;
verifyPacking(Method, Config, /*ExpectedRows=*/0, {});
}
@@ -220,6 +225,9 @@ TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) {
Config.ShaderStage = Triple::Pixel;
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, Unallocated, {/*Row=*/1, /*Col=*/0}});
+ // Optimized packing places the wider B before A.
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/1, /*Col=*/0}, Unallocated, {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) {
@@ -281,14 +289,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, CoPackingDependsOnMethod) {
{/*Row=*/2, /*Col=*/0},
{/*Row=*/3, /*Col=*/0}});
- // Prefix-stable layout for the corresponding vertex output signature:
+ // Prefix-stable and optimized layout for the corresponding vertex output:
// reg0: A.x | B.y | C.z | D.w
Config.IOTy = IOType::Out;
- verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
- {{/*Row=*/0, /*Col=*/0},
- {/*Row=*/0, /*Col=*/1},
- {/*Row=*/0, /*Col=*/2},
- {/*Row=*/0, /*Col=*/3}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/1},
+ {/*Row=*/0, /*Col=*/2},
+ {/*Row=*/0, /*Col=*/3}});
}
TEST_F(HLSLSemanticSignaturePackingTest, StacksMultiRowElements) {
@@ -335,7 +345,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, ExactlyFillsSignature) {
// Expected layout:
// reg0-31: A[0-31].xyzw
for (PackingMethod Method :
- {PackingMethod::Stacked, PackingMethod::PrefixStable}) {
+ {PackingMethod::Stacked, PackingMethod::PrefixStable,
+ PackingMethod::Optimized}) {
Config.IOTy = Method == PackingMethod::Stacked ? IOType::In : IOType::Out;
verifyPacking(Method, Config, /*ExpectedRows=*/MaxSignatureRows,
{{/*Row=*/0, /*Col=*/0}});
@@ -363,7 +374,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSignatureOverflow) {
// The last element is the one that no longer fits.
for (PackingMethod Method :
- {PackingMethod::Stacked, PackingMethod::PrefixStable}) {
+ {PackingMethod::Stacked, PackingMethod::PrefixStable,
+ PackingMethod::Optimized}) {
Config.IOTy = Method == PackingMethod::Stacked ? IOType::In : IOType::Out;
verifyPackingError(Method, Config, SignaturePackingError::SignatureOverflow,
/*ExpectedElementIndex=*/MaxSignatureRows);
@@ -383,7 +395,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSingleElementOverflow) {
dxbc::PSV::InterpolationMode::Linear}});
for (PackingMethod Method :
- {PackingMethod::Stacked, PackingMethod::PrefixStable}) {
+ {PackingMethod::Stacked, PackingMethod::PrefixStable,
+ PackingMethod::Optimized}) {
Config.IOTy = Method == PackingMethod::Stacked ? IOType::In : IOType::Out;
verifyPackingError(Method, Config, SignaturePackingError::SignatureOverflow,
/*ExpectedElementIndex=*/0);
@@ -406,7 +419,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) {
dxbc::PSV::InterpolationMode::Linear}});
for (PackingMethod Method :
- {PackingMethod::Stacked, PackingMethod::PrefixStable}) {
+ {PackingMethod::Stacked, PackingMethod::PrefixStable,
+ PackingMethod::Optimized}) {
Config.IOTy = Method == PackingMethod::Stacked ? IOType::In : IOType::Out;
verifyPackingError(Method, Config, SignaturePackingError::SignatureOverflow,
/*ExpectedElementIndex=*/1);
@@ -474,8 +488,10 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableWhenAppended) {
// Expected layout:
// reg0: A.xyz | unused.w
// reg1: B.xy | unused.zw
- verifyPacking(PackingMethod::PrefixStable, PrefixConfig, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, PrefixConfig, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
// struct Extended {
// float3 A : A;
@@ -493,9 +509,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableWhenAppended) {
//
// C is packed into the gap A left behind, and A and B keep the locations
// they were given in Prefix.
- verifyPacking(
- PackingMethod::PrefixStable, ExtendedConfig, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/3}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, ExtendedConfig, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/3}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableFillsAllRows) {
@@ -514,14 +533,17 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableFillsAllRows) {
dxil::ElementType::F32,
dxbc::PSV::InterpolationMode::Linear});
- SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
- Expected<unsigned> Rows = pack(PackingMethod::PrefixStable, Elements, Config);
- ASSERT_THAT_EXPECTED(Rows, Succeeded());
- EXPECT_EQ(*Rows, MaxSignatureRows);
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized}) {
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ Expected<unsigned> Rows = pack(Method, Elements, Config);
+ ASSERT_THAT_EXPECTED(Rows, Succeeded());
+ EXPECT_EQ(*Rows, MaxSignatureRows);
- for (unsigned I = 0; I != MaxSignatureRows; ++I) {
- EXPECT_EQ(Elements[I].StartRow, I) << "element " << I;
- EXPECT_EQ(Elements[I].StartCol, 0u) << "element " << I;
+ for (unsigned I = 0; I != MaxSignatureRows; ++I) {
+ EXPECT_EQ(Elements[I].StartRow, I) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, 0u) << "element " << I;
+ }
}
}
@@ -558,7 +580,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeneralPacking) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A.xy | F.zw
// reg1: B.xy | D.zw
// reg2: C.xyz | G.w
@@ -571,6 +593,20 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeneralPacking) {
{/*Row=*/3, /*Col=*/0},
{/*Row=*/0, /*Col=*/2},
{/*Row=*/2, /*Col=*/3}});
+
+ // Optimized layout:
+ // reg0: E.x | unused.yzw
+ // reg1: C.xyz | G.w
+ // reg2: A.xy | F.zw
+ // reg3: B.xy | D.zw
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/2, /*Col=*/0},
+ {/*Row=*/3, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/3, /*Col=*/2},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/1, /*Col=*/3}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNative16BitWidth) {
@@ -592,9 +628,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNative16BitWidth) {
// Expected layout:
// reg0: A.xy | C.zw
// reg1: B.xy | unused.zw
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/2}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableInterpolationMode) {
@@ -613,12 +652,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableInterpolationMode) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A.xy | C.zw
// reg1: B.xy | unused.zw
verifyPacking(
PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+
+ // Optimized layout:
+ // reg0: B.xy | unused.zw
+ // reg1: A.xy | C.zw
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/2}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableCompatible16BitTypes) {
@@ -634,10 +680,15 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableCompatible16BitTypes) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Constant}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A.x | B.yzw
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}});
+
+ // Optimized layout:
+ // reg0: B.xyz | A.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/3}, {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNormalized16BitTypes) {
@@ -662,9 +713,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableNormalized16BitTypes) {
// Expected layout:
// reg0: A.x | B.y | unused.zw
// reg1: C.x | unused.yzw
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}, {/*Row=*/1, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/1},
+ {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMinPrecisionWidth) {
@@ -691,9 +745,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMinPrecisionWidth) {
// Expected layout:
// reg0: A.xy | B.zw
// reg1: C.xy | unused.zw
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}, {/*Row=*/1, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/2},
+ {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableUndefinedInterpMode) {
@@ -715,12 +772,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableUndefinedInterpMode) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A.xy | B.z | unused.w
// reg1: C.x | unused.yzw
verifyPacking(
PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}, {/*Row=*/1, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: A.xy | C.z | unused.w
+ // reg1: B.x | unused.yzw
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -743,12 +807,19 @@ TEST_F(HLSLSemanticSignaturePackingTest,
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A.xy | C.zw
// reg1: B.xy | unused.zw
verifyPacking(
PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+
+ // Optimized layout:
+ // reg0: B.xy | A.zw
+ // reg1: C.xy | unused.zw
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/2}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableDistinctInterpModes) {
@@ -775,9 +846,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableDistinctInterpModes) {
// reg0: A.xy | unused.zw
// reg1: B.xy | unused.zw
// reg2: C.xy | unused.zw
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/0}});
}
//===----------------------------------------------------------------------===//
@@ -806,9 +880,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSystemValueOrdering) {
// Expected layout:
// reg0: A.x | Position.y | IsFrontFace.z | unused.w
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}, {/*Row=*/0, /*Col=*/2}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/1},
+ {/*Row=*/0, /*Col=*/2}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableArbitraryNotRightOfSV) {
@@ -828,11 +905,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableArbitraryNotRightOfSV) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: Position.xy | unused.zw
// reg1: A.xy | unused.zw
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: A.xy | Position.zw
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/2}, {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSGVIsRightmost) {
@@ -855,12 +937,18 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSGVIsRightmost) {
/*Cols=*/1, dxil::ElementType::F32,
dxbc::PSV::InterpolationMode::Constant}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: IsFrontFace.x | unused.yzw
// reg1: A.x | Position.y | unused.zw
verifyPacking(
PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/1, /*Col=*/1}});
+
+ // Optimized layout:
+ // reg0: A.x | Position.y | IsFrontFace.z | unused.w
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/2}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}});
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -894,6 +982,20 @@ TEST_F(HLSLSemanticSignaturePackingTest,
verifyPackingError(PackingMethod::PrefixStable, Config,
SignaturePackingError::SignatureOverflow,
/*ExpectedElementIndex=*/MaxSignatureRows);
+
+ // Optimized layout:
+ // reg0: A0.xyz | IsFrontFace.w
+ // reg1-31: A1-A31.xyz | unused.w
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ Expected<unsigned> Rows = pack(PackingMethod::Optimized, Elements, Config);
+ ASSERT_THAT_EXPECTED(Rows, Succeeded());
+ EXPECT_EQ(*Rows, MaxSignatureRows);
+ EXPECT_EQ(Elements[0].StartRow, 0u);
+ EXPECT_EQ(Elements[0].StartCol, 3u);
+ for (unsigned I = 0; I != MaxSignatureRows; ++I) {
+ EXPECT_EQ(Elements[I + 1].StartRow, I) << "element " << I + 1;
+ EXPECT_EQ(Elements[I + 1].StartCol, 0u) << "element " << I + 1;
+ }
}
//===----------------------------------------------------------------------===//
@@ -925,7 +1027,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedRanges) {
{dxbc::PSV::SemanticKind::Position, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: A[0].xy | B[0].z | C.w
// reg1: A[1].xy | B[1].z | unused.w
// reg2: unused.xy | B[2].z | unused.w
@@ -935,6 +1037,17 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedRanges) {
{/*Row=*/0, /*Col=*/2},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/3, /*Col=*/0}});
+
+ // Optimized layout: the longer indexed range is packed first.
+ // reg0: B[0].x | A[0].yz | C.w
+ // reg1: B[1].x | A[1].yz | unused.w
+ // reg2: B[2].x | unused.yzw
+ // reg3: Position.x | unused.yzw
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/1},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/3},
+ {/*Row=*/3, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedAfterSystemValue) {
@@ -953,12 +1066,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedAfterSystemValue) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: Position.x | unused.yzw
// reg1: A[0].xyz | unused.w
// reg2: A[1].xyz | unused.w
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: A[0].xyz | unused.w
+ // reg1: A[1].xyz | unused.w
+ // reg2: Position.x | unused.yzw
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/2, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}});
}
//===----------------------------------------------------------------------===//
@@ -981,11 +1101,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableTessFactors) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
- // Expected layout:
+ // Prefix-stable and optimized layout:
// reg0: Data[0].xyz | TessFactor[0].w
// reg1: Data[1].xyz | TessFactor[1].w
- verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/3}, {/*Row=*/0, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/3}, {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSingleRowTessFactor) {
@@ -1004,11 +1126,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSingleRowTessFactor) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: TessFactor.x | unused.yzw
// reg1: Data.xyz | unused.w
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: Data.xyz | TessFactor.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/3}, {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -1051,7 +1178,7 @@ TEST_F(HLSLSemanticSignaturePackingTest,
{dxbc::PSV::SemanticKind::TessFactor, /*Rows=*/2, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: Data[0].xyz | unused.w
// reg1: Data[1].xyz | unused.w
// reg2: Data[2].xyz | unused.w
@@ -1059,6 +1186,15 @@ TEST_F(HLSLSemanticSignaturePackingTest,
// reg4: unused.xyz | TessFactor[1].w
verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/5,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/3}});
+
+ // Optimized layout:
+ // reg0: unused.xyz | TessFactor[0].w
+ // reg1: unused.xyz | TessFactor[1].w
+ // reg2: Data[0].xyz | unused.w
+ // reg3: Data[1].xyz | unused.w
+ // reg4: Data[2].xyz | unused.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/5,
+ {{/*Row=*/2, /*Col=*/0}, {/*Row=*/0, /*Col=*/3}});
}
//===----------------------------------------------------------------------===//
@@ -1093,7 +1229,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCull) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: First.xyz | WithFirst.w
// reg1: Clip0.x | Cull1.yzw
// reg2: Cull0.x | Clip1.yz | unused.w
@@ -1106,6 +1242,20 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCull) {
{/*Row=*/2, /*Col=*/1},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/3, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: First.xyz | WithFirst.w
+ // reg1: AfterClipCull.x | unused.yzw
+ // reg2: Cull1.xyz | Clip0.w
+ // reg3: Clip1.xy | Cull0.z | unused.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/3},
+ {/*Row=*/2, /*Col=*/0},
+ {/*Row=*/3, /*Col=*/2},
+ {/*Row=*/3, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedClipCull) {
@@ -1133,7 +1283,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedClipCull) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: First.xyz | WithFirst.w
// reg1: Clip0.x | Cull1[0].yz | Clip1.w
// reg2: unused.x | Cull1[1].yz | unused.w
@@ -1145,6 +1295,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedClipCull) {
{/*Row=*/1, /*Col=*/3},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/3, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: First.xyz | WithFirst.w
+ // reg1: AfterClipCull.x | unused.yzw
+ // reg2: Cull1[0].xy | Clip0.z | Clip1.w
+ // reg3: Cull1[1].xy | unused.zw
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/2, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/3},
+ {/*Row=*/0, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMultipleIndexedClipCull) {
@@ -1172,7 +1335,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMultipleIndexedClipCull) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: First.xyz | WithFirst.w
// reg1: Clip0.x | Cull1[0].yz | Clip1[0].w
// reg2: unused.x | Cull1[1].yz | Clip1[1].w
@@ -1184,6 +1347,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMultipleIndexedClipCull) {
{/*Row=*/1, /*Col=*/3},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/3, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0: First.xyz | WithFirst.w
+ // reg1: AfterClipCull.x | unused.yzw
+ // reg2: Cull1[0].xy | Clip1[0].z | Clip0.w
+ // reg3: Cull1[1].xy | Clip1[1].z | unused.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/3},
+ {/*Row=*/2, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/0, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullFillsTwoRows) {
@@ -1202,12 +1378,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullFillsTwoRows) {
{dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/4,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable and optimized layout:
// reg0: Clip0.xyzw
// reg1: Cull0.xyzw
- verifyPacking(PackingMethod::PrefixStable, Config,
- /*ExpectedRows=*/MaxClipCullRows,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/MaxClipCullRows,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSeparatesClipCullRows) {
@@ -1231,7 +1408,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSeparatesClipCullRows) {
{dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: Clip0.xyz | unused.w
// reg1-30: A[0-29].xyzw
// reg31: Cull0.xyz | unused.w
@@ -1240,6 +1417,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSeparatesClipCullRows) {
{{/*Row=*/0, /*Col=*/0},
{/*Row=*/1, /*Col=*/0},
{/*Row=*/31, /*Col=*/0}});
+
+ // Optimized layout:
+ // reg0-29: A[0-29].xyzw
+ // reg30: Clip0.xyz | unused.w
+ // reg31: Cull0.xyz | unused.w
+ verifyPacking(PackingMethod::Optimized, Config,
+ /*ExpectedRows=*/MaxSignatureRows,
+ {{/*Row=*/30, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/31, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullAsArbitrary) {
@@ -1261,13 +1448,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullAsArbitrary) {
{dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
- // Expected layout:
+ // Prefix-stable and optimized layout:
// reg0: Clip0.xyz | unused.w
// reg1: Clip1.xyz | unused.w
// reg2: Cull0.xyz | unused.w
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/3,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
@@ -1290,7 +1480,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
{dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/2, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- // Expected layout:
+ // Prefix-stable layout:
// reg0: First.xyz | unused.w
// reg1: Clip0.x | Cull1[0].yz | unused.w
// reg2: unused.x | Cull1[1].yz | unused.w
@@ -1298,6 +1488,14 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
PackingMethod::PrefixStable, PrefixConfig, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/1, /*Col=*/1}});
+ // Optimized layout:
+ // reg0: First.xyz | unused.w
+ // reg1: Cull1[0].xy | Clip0.z | unused.w
+ // reg2: Cull1[1].xy | unused.zw
+ verifyPacking(
+ PackingMethod::Optimized, PrefixConfig, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/2}, {/*Row=*/1, /*Col=*/0}});
+
// struct Extended {
// float3 First : First;
// float Clip0 : SV_ClipDistance0;
@@ -1317,6 +1515,19 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+ // Optimized layout:
+ // reg0: First.xyz | WithFirst.w
+ // reg1: AfterClipCull.x | unused.yzw
+ // reg2: Cull1[0].xy | Clip0.z | Clip1.w
+ // reg3: Cull1[1].xy | unused.zw
+ verifyPacking(PackingMethod::Optimized, ExtendedConfig, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/2, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/3},
+ {/*Row=*/0, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/0}});
+
// The complete layout is covered by PrefixStableIndexedClipCull. Here,
// check that each appended element leaves all earlier locations unchanged.
SmallVector<SemanticSignatureElement> PreviousElements =
@@ -1477,9 +1688,10 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsClipCullOverflow) {
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- verifyPackingError(PackingMethod::PrefixStable, Config,
- SignaturePackingError::ClipCullOverflow,
- /*ExpectedElementIndex=*/2);
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPackingError(Method, Config, SignaturePackingError::ClipCullOverflow,
+ /*ExpectedElementIndex=*/2);
SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
EXPECT_THAT_EXPECTED(pack(PackingMethod::PrefixStable, Elements, Config),
@@ -1507,9 +1719,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
- verifyPackingError(PackingMethod::PrefixStable, Config,
- SignaturePackingError::ClipCullOverflow,
- /*ExpectedElementIndex=*/2);
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPackingError(Method, Config, SignaturePackingError::ClipCullOverflow,
+ /*ExpectedElementIndex=*/2);
}
//===----------------------------------------------------------------------===//
@@ -1646,13 +1859,16 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeometryStreams) {
dxbc::PSV::InterpolationMode::Linear, /*SemanticIndex=*/0,
/*GSStream=*/0}});
- // Expected layout:
+ // Prefix-stable and optimized layout:
// stream0 reg0: A.xyzw
// stream0 reg1: C.xy | unused.zw
// stream1 reg0: B.xyzw
- verifyPacking(
- PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0}});
}
//===----------------------------------------------------------------------===//
>From 787bf9b0b5f52299549e9ff74280513dffa3dde9 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 22:12:31 +0000
Subject: [PATCH 03/17] add impl
---
.../Frontend/HLSL/SemanticSignaturePacking.h | 11 ++-
.../HLSL/SemanticSignaturePacking.cpp | 89 ++++++++++++++++++-
2 files changed, 93 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 175354b661d03..ef8731b5d71ec 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -109,9 +109,14 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
/// packs them with the prefix-stable packing algorithm.
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
-LLVM_ABI Error packSignatureOptimized(
- MutableArrayRef<SemanticSignatureElement> Elements,
- Triple::EnvironmentType ShaderStage, IOType IOTy, bool UseNative16BitTypes);
+///
+/// Returns one past the highest allocated row, or zero if no elements were
+/// allocated. For geometry outputs this is the maximum extent of any stream,
+/// not the sum of their extents.
+LLVM_ABI Expected<unsigned>
+packSignatureOptimized(MutableArrayRef<SemanticSignatureElement> Elements,
+ Triple::EnvironmentType ShaderStage, IOType IOTy,
+ bool UseNative16BitTypes);
} // namespace llvm::hlsl
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 0264e9177691a..c6f5ae12eeff9 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -107,6 +107,42 @@ static uint8_t getStartColumn(uint8_t ColumnMask) {
return countr_zero(ColumnMask);
}
+// Returns a lower value for groups that must be packed earlier.
+static unsigned
+getOptimizedPackingPriority(const SemanticSignatureElement &Element,
+ Triple::EnvironmentType ShaderStage, IOType IOTy) {
+ const SemanticInterpretation Interpretation =
+ getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
+ assert((Interpretation != SemanticInterpretation::Invalid &&
+ Interpretation != SemanticInterpretation::Target) &&
+ "unexpected semantic interpretation for optimized packing");
+
+ if (Element.Cols == MaxSignatureCols &&
+ (Interpretation == SemanticInterpretation::Arbitrary ||
+ Interpretation == SemanticInterpretation::SV))
+ return 0;
+
+ if (Interpretation == SemanticInterpretation::TessFactor && Element.Rows > 1)
+ return 1;
+
+ switch (Interpretation) {
+ case SemanticInterpretation::Arbitrary:
+ return 2;
+ case SemanticInterpretation::SV:
+ case SemanticInterpretation::TessFactor:
+ return 3;
+ case SemanticInterpretation::ClipCull:
+ return 4;
+ case SemanticInterpretation::SGV:
+ return 5;
+ case SemanticInterpretation::NotAllocated:
+ return 6;
+ default:
+ break;
+ }
+ llvm_unreachable("unexpected semantic interpretation for optimized packing");
+}
+
static unsigned getComponentWidth(dxil::ElementType ComponentType,
bool UseNative16BitTypes) {
assert(ComponentType != dxil::ElementType::I64 &&
@@ -544,11 +580,56 @@ Expected<unsigned> llvm::hlsl::packSignatureIndexed(
return NumRows;
}
-Error llvm::hlsl::packSignatureOptimized(
+Expected<unsigned> llvm::hlsl::packSignatureOptimized(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
bool UseNative16BitTypes) {
- // TODO: Sort Elements for optimal packing.
- return packSignaturePrefixStable(Elements, ShaderStage, IOTy,
- UseNative16BitTypes);
+ SmallVector<unsigned> SortedIndices;
+ SortedIndices.reserve(Elements.size());
+ for (unsigned Index = 0; Index != Elements.size(); ++Index)
+ SortedIndices.push_back(Index);
+
+ llvm::sort(SortedIndices, [&](unsigned LeftIndex, unsigned RightIndex) {
+ const SemanticSignatureElement &Left = Elements[LeftIndex];
+ const SemanticSignatureElement &Right = Elements[RightIndex];
+ const unsigned LeftPriority =
+ getOptimizedPackingPriority(Left, ShaderStage, IOTy);
+ const unsigned RightPriority =
+ getOptimizedPackingPriority(Right, ShaderStage, IOTy);
+
+ if (LeftPriority != RightPriority)
+ return LeftPriority < RightPriority;
+ if (Left.InterpMode != Right.InterpMode)
+ return Left.InterpMode < Right.InterpMode;
+ if (Left.Rows != Right.Rows)
+ return Left.Rows > Right.Rows;
+ if (Left.Cols != Right.Cols)
+ return Left.Cols > Right.Cols;
+ return Left.SigId < Right.SigId;
+ });
+
+ // Pack a copy so Elements remains in its original signature order.
+ SmallVector<SemanticSignatureElement> SortedElements;
+ SortedElements.reserve(Elements.size());
+ for (unsigned Index : SortedIndices)
+ SortedElements.push_back(Elements[Index]);
+
+ Expected<unsigned> NumRows = packSignaturePrefixStable(
+ SortedElements, ShaderStage, IOTy, UseNative16BitTypes);
+
+ for (const auto &[SortedIndex, OriginalIndex] : enumerate(SortedIndices)) {
+ Elements[OriginalIndex].StartRow = SortedElements[SortedIndex].StartRow;
+ Elements[OriginalIndex].StartCol = SortedElements[SortedIndex].StartCol;
+ }
+
+ if (NumRows)
+ return *NumRows;
+
+ return handleErrors(
+ NumRows.takeError(), [&](const SignaturePackingError &Err) -> Error {
+ assert(Err.getElementIndex() < SortedIndices.size() &&
+ "invalid sorted element index");
+ return make_error<SignaturePackingError>(
+ Err.getErrorKind(), SortedIndices[Err.getElementIndex()]);
+ });
}
>From ff3afde0f332e9afd5b701b36192f34eab350d61 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:40:41 +0000
Subject: [PATCH 04/17] review: correct conflict resolution
---
llvm/docs/DirectX/SemanticSignatures.md | 107 +++++++-----------------
1 file changed, 28 insertions(+), 79 deletions(-)
diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 56994bb07ad9d..dee73747972c5 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -147,34 +147,6 @@ in-memory element representation they operate on is declared in
[SemanticSignaturePacking.h]: https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
-### Stacked Packing
-
-Stacked packing is used for a vertex shader input signature. Eligible elements
-are visited in declaration order. Each starts at column zero of the first row
-after the preceding element, and a multi-row element occupies consecutive rows.
-Elements are never co-packed into the unused columns of another element, and
-interpolation mode, component type, and semantic interpretation do not otherwise
-affect placement.
-
-For example:
-
-```hlsl
-struct VSIn {
- float A : A;
- float3 B[2] : B;
- uint VertexID : SV_VertexID;
-};
-```
-
-The signature is allocated as:
-
-```text
-reg0: A.x | unused.yzw
-reg1: B[0].xyz | unused.w
-reg2: B[1].xyz | unused.w
-reg3: VertexID.x | unused.yzw
-```
-
### Prefix-Stable Packing
Prefix-stable packing is used for signatures that connect programmable shader
@@ -232,57 +204,6 @@ reg2: A[2].xyz | D.w
reg3: C.xy | unused.zw
```
-### Prefix-Stable Packing
-
-Prefix-stable packing is used for signatures that connect programmable shader
-stages or carry patch constant data. Elements are visited in declaration order
-and placed at the first compatible location in the 32-row by 4-column register
-space. Once an element is placed it is never moved, so appending elements to a
-signature does not change the locations assigned to its existing prefix.
-
-Elements can share unused components in a row when all applicable packing
-constraints are satisfied:
-
-- Every element in a row must have a compatible interpolation mode.
-- When native 16-bit types are enabled, every element in a row must have the
- same component width. Without native 16-bit types, min-precision values
- occupy 32-bit components.
-- Components are ordered from arbitrary values, to system values, to system
- generated values.
-- A system value or system generated value cannot be placed in a dynamically
- indexed row. Multi-row elements define the dynamically indexed range that
- they cover.
-
-Some semantic interpretations require additional handling:
-
-- `SV_ClipDistance` and `SV_CullDistance` are packed only with each other in
- dedicated rows. Together they may occupy at most eight components across at
- most two rows. The rows must be adjacent when a clip or cull element spans
- multiple rows.
-- A multi-row tessellation factor is searched for only in the last column.
-- Geometry shader output streams are packed independently.
-
-For example:
-
-```hlsl
-struct VSOut {
- float3 A[3] : A;
- float1x2 B : B;
- float2 C : C;
- float D : D;
-};
-```
-
-Assuming `B` has column-major matrix orientation, the signature is allocated
-as:
-
-```text
-reg0: A[0].xyz | B[0][0].w
-reg1: A[1].xyz | B[0][1].w
-reg2: A[2].xyz | D.w
-reg3: C.xy | unused.zw
-```
-
### Optimized Packing
Optimized packing partitions eligible elements into groups and packs the groups
@@ -307,6 +228,34 @@ minimum-row layout. Reordering can reduce the number of rows occupied, but
means that appending an element may change locations assigned to existing
elements.
+### Stacked Packing
+
+Stacked packing is used for a vertex shader input signature. Eligible elements
+are visited in declaration order. Each starts at column zero of the first row
+after the preceding element, and a multi-row element occupies consecutive rows.
+Elements are never co-packed into the unused columns of another element, and
+interpolation mode, component type, and semantic interpretation do not otherwise
+affect placement.
+
+For example:
+
+```hlsl
+struct VSIn {
+ float A : A;
+ float3 B[2] : B;
+ uint VertexID : SV_VertexID;
+};
+```
+
+The signature is allocated as:
+
+```text
+reg0: A.x | unused.yzw
+reg1: B[0].xyz | unused.w
+reg2: B[1].xyz | unused.w
+reg3: VertexID.x | unused.yzw
+```
+
### Indexed Packing
Indexed packing is used for a pixel shader output signature. Each eligible
>From 31e5753569a1e755665edbbf12226257b84b201c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:42:32 +0000
Subject: [PATCH 05/17] review: apply review comments from previous prs
---
.../Frontend/HLSL/SemanticSignaturePacking.h | 9 +-
.../HLSL/SemanticSignaturePacking.cpp | 7 +-
.../HLSLSemanticSignaturePackingTest.cpp | 188 +++++++++++++-----
3 files changed, 149 insertions(+), 55 deletions(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index ef8731b5d71ec..5b4b6fbac5c6b 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -106,13 +106,20 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
/// Sorts eligible signature elements using the optimized ordering and then
-/// packs them with the prefix-stable packing algorithm.
+/// packs them with the prefix-stable packing algorithm. Like prefix-stable
+/// packing, this is not valid for vertex inputs or pixel outputs.
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
///
/// Returns one past the highest allocated row, or zero if no elements were
/// allocated. For geometry outputs this is the maximum extent of any stream,
/// not the sum of their extents.
+///
+/// Elements remains in its original signature order. On failure, elements
+/// packed before the failing element in optimized order keep their assigned
+/// locations; the failing element and those after it in that order retain the
+/// unallocated row and column sentinels. The returned SignaturePackingError
+/// identifies the failing element by its index in the original Elements array.
LLVM_ABI Expected<unsigned>
packSignatureOptimized(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index c6f5ae12eeff9..f0e76e49bc607 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -115,7 +115,8 @@ getOptimizedPackingPriority(const SemanticSignatureElement &Element,
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
assert((Interpretation != SemanticInterpretation::Invalid &&
Interpretation != SemanticInterpretation::Target) &&
- "unexpected semantic interpretation for optimized packing");
+ "unexpected semantic interpretation for optimized packing, "
+ "should have been diagnosed by Sema");
if (Element.Cols == MaxSignatureCols &&
(Interpretation == SemanticInterpretation::Arbitrary ||
@@ -584,6 +585,10 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
bool UseNative16BitTypes) {
+ assert(!(ShaderStage == Triple::Vertex && IOTy == IOType::In) &&
+ !(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
+ "optimized packing is not valid for vertex inputs or pixel outputs");
+
SmallVector<unsigned> SortedIndices;
SortedIndices.reserve(Elements.size());
for (unsigned Index = 0; Index != Elements.size(); ++Index)
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 094a831612aba..3f08a51e947b2 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -454,8 +454,12 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableSupportedSignatures) {
Signature.Stage, Signature.IOTy,
{{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined}});
- verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
- {{/*Row=*/0, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized}) {
+ SCOPED_TRACE(static_cast<unsigned>(Method));
+ verifyPacking(Method, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0}});
+ }
}
}
@@ -465,8 +469,9 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableOnlyUnallocatedElements) {
{{dxbc::PSV::SemanticKind::ViewID, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}});
- verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/0,
- {Unallocated});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/0, {Unallocated});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableWhenAppended) {
@@ -1156,8 +1161,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
// Expected layout:
// reg0: Data.xy | TessFactor.z | unused.w
- verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/1,
- {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/2}});
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -1515,19 +1522,6 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCullWhenAppended) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
- // Optimized layout:
- // reg0: First.xyz | WithFirst.w
- // reg1: AfterClipCull.x | unused.yzw
- // reg2: Cull1[0].xy | Clip0.z | Clip1.w
- // reg3: Cull1[1].xy | unused.zw
- verifyPacking(PackingMethod::Optimized, ExtendedConfig, /*ExpectedRows=*/4,
- {{/*Row=*/0, /*Col=*/0},
- {/*Row=*/2, /*Col=*/2},
- {/*Row=*/2, /*Col=*/0},
- {/*Row=*/2, /*Col=*/3},
- {/*Row=*/0, /*Col=*/3},
- {/*Row=*/1, /*Col=*/0}});
-
// The complete layout is covered by PrefixStableIndexedClipCull. Here,
// check that each appended element leaves all earlier locations unchanged.
SmallVector<SemanticSignatureElement> PreviousElements =
@@ -1586,6 +1580,14 @@ TEST_F(HLSLSemanticSignaturePackingTest,
EXPECT_EQ(Elements[1].StartCol, 0u);
EXPECT_EQ(Elements[2].StartRow, UnallocatedRow);
EXPECT_EQ(Elements[2].StartCol, UnallocatedCol);
+
+ // Optimized packing places Color first and reserves adjacent rows for Cull.
+ // reg0: Color.xyzw
+ // reg1: Cull[0].x | Clip.y | unused.zw
+ // reg2: Cull[1].x | unused.yzw
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/1, /*Col=*/1}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -1626,6 +1628,17 @@ TEST_F(HLSLSemanticSignaturePackingTest,
EXPECT_EQ(Elements[3].StartRow, UnallocatedRow);
EXPECT_EQ(Elements[3].StartCol, UnallocatedCol);
+ // Optimized packing places the indexed clip element before scalar clip/cull
+ // values, so Color cannot separate its reserved rows.
+ // reg0: Color.xyzw
+ // reg1: Clip1[0].x | Clip0.yzw
+ // reg2: Clip1[1].x | Cull0.yz | unused.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/1, /*Col=*/1},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/1},
+ {/*Row=*/1, /*Col=*/0}});
+
// Declaring Color last leaves adjacent clip/cull rows and the same
// components fit. This is an adjacency failure, not a clip/cull overflow.
ElementConfig Color = Config.Elements[1];
@@ -1663,11 +1676,19 @@ TEST_F(HLSLSemanticSignaturePackingTest,
{/*Row=*/MaxSignatureRows - 2, /*Col=*/0},
{/*Row=*/MaxSignatureRows - 2, /*Col=*/1}});
- // Extending Data to 31 rows pushes Clip into the last signature row.
+ // Optimized packing reserves Cull's two rows before placing scalar Clip.
+ verifyPacking(PackingMethod::Optimized, Config,
+ /*ExpectedRows=*/MaxSignatureRows,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/MaxSignatureRows - 2, /*Col=*/1},
+ {/*Row=*/MaxSignatureRows - 2, /*Col=*/0}});
+
+ // Extending Data to 31 rows leaves no room for Cull's two-row range.
++Config.Elements[0].Rows;
- verifyPackingError(PackingMethod::PrefixStable, Config,
- SignaturePackingError::SignatureOverflow,
- /*ExpectedElementIndex=*/2);
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPackingError(Method, Config, SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/2);
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsClipCullOverflow) {
@@ -1689,15 +1710,17 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsClipCullOverflow) {
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
for (PackingMethod Method :
- {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ {PackingMethod::PrefixStable, PackingMethod::Optimized}) {
+ SCOPED_TRACE(static_cast<unsigned>(Method));
verifyPackingError(Method, Config, SignaturePackingError::ClipCullOverflow,
/*ExpectedElementIndex=*/2);
- SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
- EXPECT_THAT_EXPECTED(pack(PackingMethod::PrefixStable, Elements, Config),
- FailedWithMessage("clip/cull elements do not fit in " +
- std::to_string(MaxClipCullRows) +
- " rows (element 2)"));
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ EXPECT_THAT_EXPECTED(pack(Method, Elements, Config),
+ FailedWithMessage("clip/cull elements do not fit in " +
+ std::to_string(MaxClipCullRows) +
+ " rows (element 2)"));
+ }
}
TEST_F(HLSLSemanticSignaturePackingTest,
@@ -1760,6 +1783,56 @@ TEST_F(HLSLSemanticSignaturePackingTest,
}
}
+TEST_F(HLSLSemanticSignaturePackingTest, OptimizedPreservesPartialAllocation) {
+ // Declaration order is Small, Full, Last. Optimized packing places Full
+ // first, then fails on Small. Report Small's original index (zero), not its
+ // index in packing order (one), and preserve Full's allocation.
+ //
+ // struct VSOut {
+ // float2 Small : Small;
+ // float4 Full[32] : Full;
+ // float Last : Last;
+ // };
+ // The clip/cull case uses clip-distance semantics and Full[2] instead.
+ for (bool IsClipCull : {false, true}) {
+ SCOPED_TRACE(IsClipCull);
+ const auto Kind = IsClipCull ? dxbc::PSV::SemanticKind::ClipDistance
+ : dxbc::PSV::SemanticKind::Arbitrary;
+ const unsigned RowCount = IsClipCull ? MaxClipCullRows : MaxSignatureRows;
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{Kind, /*Rows=*/1, /*Cols=*/2, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear},
+ {Kind, RowCount, /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear},
+ {Kind, /*Rows=*/1, /*Cols=*/1, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear}});
+ verifyPackingError(PackingMethod::Optimized, Config,
+ IsClipCull ? SignaturePackingError::ClipCullOverflow
+ : SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/0);
+
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ EXPECT_THAT_EXPECTED(
+ pack(PackingMethod::Optimized, Elements, Config),
+ FailedWithMessage(
+ std::string(IsClipCull ? "clip/cull elements do not fit in "
+ : "signature elements do not fit in ") +
+ std::to_string(RowCount) + " rows (element 0)"));
+ EXPECT_EQ(Elements[1].StartRow, 0u);
+ EXPECT_EQ(Elements[1].StartCol, 0u);
+ for (unsigned I : {0u, 2u}) {
+ EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ }
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ EXPECT_EQ(Elements[I].SigId, I) << "element " << I;
+ EXPECT_EQ(Elements[I].Rows, Config.Elements[I].Rows) << "element " << I;
+ EXPECT_EQ(Elements[I].Cols, Config.Elements[I].Cols) << "element " << I;
+ }
+ }
+}
+
//===----------------------------------------------------------------------===//
// Prefix-stable geometry stream tests
//===----------------------------------------------------------------------===//
@@ -1787,26 +1860,34 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsInvalidStreams) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
/*SemanticIndex=*/1, Signature.InvalidStream},
- {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/4,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
/*SemanticIndex=*/2}});
- verifyPackingError(PackingMethod::PrefixStable, Config,
- SignaturePackingError::InvalidGeometryStream,
- /*ExpectedElementIndex=*/1);
-
- SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
- EXPECT_THAT_EXPECTED(
- pack(PackingMethod::PrefixStable, Elements, Config),
- FailedWithMessage(
- "signature element has an invalid geometry stream: expected an "
- "index less than " +
- std::to_string(MaxGeometryStreams) +
- " for geometry outputs, or zero otherwise (element 1)"));
- EXPECT_EQ(Elements[0].StartRow, 0u);
- EXPECT_EQ(Elements[0].StartCol, 0u);
- for (unsigned I = 1; I != Elements.size(); ++I) {
- EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
- EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized}) {
+ SCOPED_TRACE(static_cast<unsigned>(Method));
+ verifyPackingError(Method, Config,
+ SignaturePackingError::InvalidGeometryStream,
+ /*ExpectedElementIndex=*/1);
+
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ EXPECT_THAT_EXPECTED(
+ pack(Method, Elements, Config),
+ FailedWithMessage(
+ "signature element has an invalid geometry stream: expected an "
+ "index less than " +
+ std::to_string(MaxGeometryStreams) +
+ " for geometry outputs, or zero otherwise (element 1)"));
+ const bool IsOptimized = Method == PackingMethod::Optimized;
+ EXPECT_EQ(Elements[0].StartRow, IsOptimized ? 1u : 0u);
+ EXPECT_EQ(Elements[0].StartCol, 0u);
+ EXPECT_EQ(Elements[1].StartRow, UnallocatedRow);
+ EXPECT_EQ(Elements[1].StartCol, UnallocatedCol);
+ // Optimized packing places the four-column element first, so it is
+ // allocated even though it follows the invalid stream in source order.
+ // The error still identifies the original element, not its sorted index.
+ EXPECT_EQ(Elements[2].StartRow, IsOptimized ? 0u : UnallocatedRow);
+ EXPECT_EQ(Elements[2].StartCol, IsOptimized ? 0u : UnallocatedCol);
}
}
}
@@ -1821,12 +1902,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableFullGeometryStreams) {
// Every stream can fill its entire register space independently. Return
// the maximum extent, not the sum of all streams' extents.
- verifyPacking(PackingMethod::PrefixStable, Config,
- /*ExpectedRows=*/MaxSignatureRows,
- {{/*Row=*/0, /*Col=*/0},
- {/*Row=*/0, /*Col=*/0},
- {/*Row=*/0, /*Col=*/0},
- {/*Row=*/0, /*Col=*/0}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized})
+ verifyPacking(Method, Config, /*ExpectedRows=*/MaxSignatureRows,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/0}});
}
TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeometryStreams) {
>From 0491497a7596e805391f39779ddc7006ae8f5ee1 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:46:09 +0000
Subject: [PATCH 06/17] review: name optimized signature packing groups
---
llvm/docs/DirectX/SemanticSignatures.md | 2 +-
.../HLSL/SemanticSignaturePacking.cpp | 42 ++++++++++++-------
.../HLSLSemanticSignaturePackingTest.cpp | 5 ++-
3 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index dee73747972c5..4e559ab698ca9 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -209,7 +209,7 @@ reg3: C.xy | unused.zw
Optimized packing partitions eligible elements into groups and packs the groups
in this order:
-1. Four-column arbitrary and system-value elements.
+1. Arbitrary and system-value elements that occupy a full register.
2. Multi-row tessellation factors, which are restricted to the last column.
3. Arbitrary elements.
4. System-value elements, including single-row tessellation factors.
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index f0e76e49bc607..f04561c539b6c 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -100,6 +100,17 @@ struct ClipCullState {
unsigned RowsUsed = 0;
};
+// Groups are packed in increasing order.
+enum class PackingGroup : unsigned {
+ FullRegister,
+ IndexedTessFactor,
+ Arbitrary,
+ SystemValue,
+ ClipCull,
+ SystemGenerated,
+ NotAllocated,
+};
+
} // namespace
static uint8_t getStartColumn(uint8_t ColumnMask) {
@@ -107,9 +118,8 @@ static uint8_t getStartColumn(uint8_t ColumnMask) {
return countr_zero(ColumnMask);
}
-// Returns a lower value for groups that must be packed earlier.
-static unsigned
-getOptimizedPackingPriority(const SemanticSignatureElement &Element,
+static PackingGroup
+getOptimizedPackingGroup(const SemanticSignatureElement &Element,
Triple::EnvironmentType ShaderStage, IOType IOTy) {
const SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
@@ -121,23 +131,23 @@ getOptimizedPackingPriority(const SemanticSignatureElement &Element,
if (Element.Cols == MaxSignatureCols &&
(Interpretation == SemanticInterpretation::Arbitrary ||
Interpretation == SemanticInterpretation::SV))
- return 0;
+ return PackingGroup::FullRegister;
if (Interpretation == SemanticInterpretation::TessFactor && Element.Rows > 1)
- return 1;
+ return PackingGroup::IndexedTessFactor;
switch (Interpretation) {
case SemanticInterpretation::Arbitrary:
- return 2;
+ return PackingGroup::Arbitrary;
case SemanticInterpretation::SV:
case SemanticInterpretation::TessFactor:
- return 3;
+ return PackingGroup::SystemValue;
case SemanticInterpretation::ClipCull:
- return 4;
+ return PackingGroup::ClipCull;
case SemanticInterpretation::SGV:
- return 5;
+ return PackingGroup::SystemGenerated;
case SemanticInterpretation::NotAllocated:
- return 6;
+ return PackingGroup::NotAllocated;
default:
break;
}
@@ -597,13 +607,13 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
llvm::sort(SortedIndices, [&](unsigned LeftIndex, unsigned RightIndex) {
const SemanticSignatureElement &Left = Elements[LeftIndex];
const SemanticSignatureElement &Right = Elements[RightIndex];
- const unsigned LeftPriority =
- getOptimizedPackingPriority(Left, ShaderStage, IOTy);
- const unsigned RightPriority =
- getOptimizedPackingPriority(Right, ShaderStage, IOTy);
+ const PackingGroup LeftGroup =
+ getOptimizedPackingGroup(Left, ShaderStage, IOTy);
+ const PackingGroup RightGroup =
+ getOptimizedPackingGroup(Right, ShaderStage, IOTy);
- if (LeftPriority != RightPriority)
- return LeftPriority < RightPriority;
+ if (LeftGroup != RightGroup)
+ return LeftGroup < RightGroup;
if (Left.InterpMode != Right.InterpMode)
return Left.InterpMode < Right.InterpMode;
if (Left.Rows != Right.Rows)
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 3f08a51e947b2..a16fd6d89d85d 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -1883,8 +1883,9 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableRejectsInvalidStreams) {
EXPECT_EQ(Elements[0].StartCol, 0u);
EXPECT_EQ(Elements[1].StartRow, UnallocatedRow);
EXPECT_EQ(Elements[1].StartCol, UnallocatedCol);
- // Optimized packing places the four-column element first, so it is
- // allocated even though it follows the invalid stream in source order.
+ // Optimized packing places elements that occupy a full register first.
+ // This element is therefore allocated even though it follows the invalid
+ // stream in source order.
// The error still identifies the original element, not its sorted index.
EXPECT_EQ(Elements[2].StartRow, IsOptimized ? 0u : UnallocatedRow);
EXPECT_EQ(Elements[2].StartCol, IsOptimized ? 0u : UnallocatedCol);
>From b6f6b8c95c720b0c7b1f2dd83b6532a76c3a6535 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:46:23 +0000
Subject: [PATCH 07/17] review: exhaustively handle signature interpretations
---
llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index f04561c539b6c..aafdac73b9fff 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -148,7 +148,8 @@ getOptimizedPackingGroup(const SemanticSignatureElement &Element,
return PackingGroup::SystemGenerated;
case SemanticInterpretation::NotAllocated:
return PackingGroup::NotAllocated;
- default:
+ case SemanticInterpretation::Invalid:
+ case SemanticInterpretation::Target:
break;
}
llvm_unreachable("unexpected semantic interpretation for optimized packing");
>From 624faf3cd78a281506f642e83f35f89a737d03fb Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:47:52 +0000
Subject: [PATCH 08/17] review: precompute optimized signature sorting keys
---
.../HLSL/SemanticSignaturePacking.cpp | 47 ++++++++++---------
.../HLSLSemanticSignaturePackingTest.cpp | 25 ++++++++++
2 files changed, 50 insertions(+), 22 deletions(-)
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index aafdac73b9fff..2dad2b706b4e7 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -600,21 +600,24 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
!(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
"optimized packing is not valid for vertex inputs or pixel outputs");
- SmallVector<unsigned> SortedIndices;
- SortedIndices.reserve(Elements.size());
- for (unsigned Index = 0; Index != Elements.size(); ++Index)
- SortedIndices.push_back(Index);
-
- llvm::sort(SortedIndices, [&](unsigned LeftIndex, unsigned RightIndex) {
- const SemanticSignatureElement &Left = Elements[LeftIndex];
- const SemanticSignatureElement &Right = Elements[RightIndex];
- const PackingGroup LeftGroup =
- getOptimizedPackingGroup(Left, ShaderStage, IOTy);
- const PackingGroup RightGroup =
- getOptimizedPackingGroup(Right, ShaderStage, IOTy);
-
- if (LeftGroup != RightGroup)
- return LeftGroup < RightGroup;
+ struct SortKey {
+ PackingGroup Group;
+ dxbc::PSV::InterpolationMode InterpMode;
+ uint32_t Rows;
+ uint8_t Cols;
+ uint32_t SigId;
+ unsigned OriginalIndex;
+ };
+ SmallVector<SortKey> SortedKeys;
+ SortedKeys.reserve(Elements.size());
+ for (auto [Index, Element] : enumerate(Elements))
+ SortedKeys.push_back({getOptimizedPackingGroup(Element, ShaderStage, IOTy),
+ Element.InterpMode, Element.Rows, Element.Cols,
+ Element.SigId, static_cast<unsigned>(Index)});
+
+ llvm::sort(SortedKeys, [](const SortKey &Left, const SortKey &Right) {
+ if (Left.Group != Right.Group)
+ return Left.Group < Right.Group;
if (Left.InterpMode != Right.InterpMode)
return Left.InterpMode < Right.InterpMode;
if (Left.Rows != Right.Rows)
@@ -627,15 +630,15 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
// Pack a copy so Elements remains in its original signature order.
SmallVector<SemanticSignatureElement> SortedElements;
SortedElements.reserve(Elements.size());
- for (unsigned Index : SortedIndices)
- SortedElements.push_back(Elements[Index]);
+ for (const SortKey &Key : SortedKeys)
+ SortedElements.push_back(Elements[Key.OriginalIndex]);
Expected<unsigned> NumRows = packSignaturePrefixStable(
SortedElements, ShaderStage, IOTy, UseNative16BitTypes);
- for (const auto &[SortedIndex, OriginalIndex] : enumerate(SortedIndices)) {
- Elements[OriginalIndex].StartRow = SortedElements[SortedIndex].StartRow;
- Elements[OriginalIndex].StartCol = SortedElements[SortedIndex].StartCol;
+ for (const auto &[SortedIndex, Key] : enumerate(SortedKeys)) {
+ Elements[Key.OriginalIndex].StartRow = SortedElements[SortedIndex].StartRow;
+ Elements[Key.OriginalIndex].StartCol = SortedElements[SortedIndex].StartCol;
}
if (NumRows)
@@ -643,9 +646,9 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
return handleErrors(
NumRows.takeError(), [&](const SignaturePackingError &Err) -> Error {
- assert(Err.getElementIndex() < SortedIndices.size() &&
+ assert(Err.getElementIndex() < SortedKeys.size() &&
"invalid sorted element index");
return make_error<SignaturePackingError>(
- Err.getErrorKind(), SortedIndices[Err.getElementIndex()]);
+ Err.getErrorKind(), SortedKeys[Err.getElementIndex()].OriginalIndex);
});
}
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index a16fd6d89d85d..62d527297eef3 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -1954,6 +1954,31 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeometryStreams) {
{/*Row=*/1, /*Col=*/0}});
}
+//===----------------------------------------------------------------------===//
+// Optimized ordering tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, OptimizedUsesSignatureIDToBreakTies) {
+ TestConfig Config(Triple::Vertex, IOType::Out, {});
+ for (unsigned I = 0; I != 3; ++I)
+ Config.Elements.push_back(
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ Elements[0].SigId = 2;
+ Elements[1].SigId = 0;
+ Elements[2].SigId = 1;
+
+ Expected<unsigned> Rows = pack(PackingMethod::Optimized, Elements, Config);
+ ASSERT_THAT_EXPECTED(Rows, Succeeded());
+ EXPECT_EQ(*Rows, 1u);
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ EXPECT_EQ(Elements[I].SigId, (I + 2) % 3);
+ EXPECT_EQ(Elements[I].StartRow, 0u);
+ EXPECT_EQ(Elements[I].StartCol, Elements[I].SigId);
+ }
+}
+
//===----------------------------------------------------------------------===//
// Indexed packing tests
//===----------------------------------------------------------------------===//
>From b55d3315312357513277f84b8bbe09dc507c9313 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 15:49:28 +0000
Subject: [PATCH 09/17] review: pack signature locations without copying
metadata
Traverse original elements in the selected order and pass a location-only result through the placement helpers. Both packers read metadata through const references and update only StartRow and StartCol. Avoid deep copies of SemanticIndices and the copy-back/error-remapping pass.
Test metadata and semantic-index storage preservation for both packing methods.
Review: https://github.com/llvm/llvm-project/pull/218064#discussion_r3858467512
---
.../Frontend/HLSL/SemanticSignaturePacking.h | 5 +-
.../HLSL/SemanticSignaturePacking.cpp | 94 +++++++++----------
.../HLSLSemanticSignaturePackingTest.cpp | 48 ++++++++++
3 files changed, 98 insertions(+), 49 deletions(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 5b4b6fbac5c6b..3df48d9bde811 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -69,7 +69,7 @@ packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
/// Packs eligible signature elements without moving previously placed
-/// elements.
+/// elements. Only StartRow and StartCol are modified.
///
/// See llvm/docs/DirectX/SemanticSignatures.md#prefix-stable-packing for
/// details.
@@ -115,7 +115,8 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
/// allocated. For geometry outputs this is the maximum extent of any stream,
/// not the sum of their extents.
///
-/// Elements remains in its original signature order. On failure, elements
+/// Only StartRow and StartCol are modified; Elements remains in its original
+/// signature order. On failure, elements
/// packed before the failing element in optimized order keep their assigned
/// locations; the failing element and those after it in that order retain the
/// unallocated row and column sentinels. The returned SignaturePackingError
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 2dad2b706b4e7..5d0524bd4799f 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -91,6 +91,12 @@ struct ElementPlacement {
SemanticInterpretation Interpretation;
};
+// The only fields written back to a semantic signature element by packing.
+struct ElementLocation {
+ uint32_t Row = UnallocatedRow;
+ uint8_t Col = UnallocatedCol;
+};
+
// Clip/cull elements are first packed into an independent two-row grid. Each
// row used in that grid maps to a whole reserved row in the signature.
struct ClipCullState {
@@ -287,20 +293,20 @@ static void placeRowsAt(MutableArrayRef<SignatureRow> Rows, unsigned StartRow,
static void placeAt(MutableArrayRef<SignatureRow> Rows, unsigned StartRow,
const ElementPlacement &Placement, uint8_t ColumnMask,
- SemanticSignatureElement &Element) {
+ ElementLocation &Location) {
placeRowsAt(Rows, StartRow, Placement, ColumnMask);
- Element.StartRow = StartRow;
- Element.StartCol = getStartColumn(ColumnMask);
+ Location.Row = StartRow;
+ Location.Col = getStartColumn(ColumnMask);
}
-static bool prefixPackElement(SemanticSignatureElement &Element,
+static bool prefixPackElement(ElementLocation &Location,
MutableArrayRef<SignatureRow> Rows,
const ElementPlacement &Placement) {
for (unsigned StartRow = 0; StartRow != Rows.size(); ++StartRow) {
std::optional<uint8_t> ColumnMask = canPlaceAt(Rows, StartRow, Placement);
if (!ColumnMask)
continue;
- placeAt(Rows, StartRow, Placement, *ColumnMask, Element);
+ placeAt(Rows, StartRow, Placement, *ColumnMask, Location);
return true;
}
return false;
@@ -381,7 +387,7 @@ reserveClipCullSignatureRows(MutableArrayRef<SignatureRow> SignatureRows,
}
static std::optional<SignaturePackingError::ErrorKind>
-packClipCullElement(SemanticSignatureElement &Element,
+packClipCullElement(ElementLocation &Location,
MutableArrayRef<SignatureRow> SignatureRows,
ClipCullState &State, const ElementPlacement &Placement) {
std::optional<uint8_t> ColumnMask;
@@ -403,8 +409,8 @@ packClipCullElement(SemanticSignatureElement &Element,
placeRowsAt(State.Rows, ClipCullStartRow, Placement, *ColumnMask);
State.RowsUsed = std::max(State.RowsUsed, NewRowsUsed);
- Element.StartRow = State.SignatureRows[ClipCullStartRow];
- Element.StartCol = getStartColumn(*ColumnMask);
+ Location.Row = State.SignatureRows[ClipCullStartRow];
+ Location.Col = getStartColumn(*ColumnMask);
return std::nullopt;
}
@@ -469,15 +475,12 @@ Expected<unsigned> llvm::hlsl::packSignatureStacked(
return NextRow;
}
-Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
- MutableArrayRef<SemanticSignatureElement> Elements,
- Triple::EnvironmentType ShaderStage, IOType IOTy,
- bool UseNative16BitTypes) {
- assert(!(ShaderStage == Triple::Vertex && IOTy == IOType::In) &&
- !(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
- "prefix-stable packing is not valid for vertex inputs or pixel "
- "outputs");
-
+template <typename IndexRange>
+static Expected<unsigned>
+packSignatureInOrder(MutableArrayRef<SemanticSignatureElement> Elements,
+ const IndexRange &Order,
+ Triple::EnvironmentType ShaderStage, IOType IOTy,
+ bool UseNative16BitTypes) {
// Only a geometry shader output signature packs its streams independently.
const unsigned StreamCount =
ShaderStage == Triple::EnvironmentType::Geometry && IOTy == IOType::Out
@@ -487,7 +490,8 @@ Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
SmallVector<std::array<SignatureRow, MaxSignatureRows>, 1> Rows(StreamCount);
SmallVector<ClipCullState, 1> ClipCullStates(StreamCount);
unsigned NumRows = 0;
- for (auto &&[Index, Element] : enumerate(Elements)) {
+ for (unsigned Index : Order) {
+ const SemanticSignatureElement &Element = Elements[Index];
assert(Element.StartRow == UnallocatedRow &&
Element.StartCol == UnallocatedCol && "already allocated?");
assert(Element.Rows > 0 && "signature element must have at least one row");
@@ -526,25 +530,39 @@ Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
const unsigned StreamIndex = Element.GSStream;
MutableArrayRef<SignatureRow> StreamRows = Rows[StreamIndex];
+ ElementLocation Location;
if (Interpretation == SemanticInterpretation::ClipCull) {
if (std::optional<SignaturePackingError::ErrorKind> Kind =
- packClipCullElement(Element, StreamRows,
+ packClipCullElement(Location, StreamRows,
ClipCullStates[StreamIndex], Placement))
- return make_error<SignaturePackingError>(*Kind,
- static_cast<unsigned>(Index));
- } else if (!prefixPackElement(Element, StreamRows, Placement)) {
+ return make_error<SignaturePackingError>(*Kind, Index);
+ } else if (!prefixPackElement(Location, StreamRows, Placement)) {
return make_error<SignaturePackingError>(
SignaturePackingError::SignatureOverflow,
static_cast<unsigned>(Index));
}
- NumRows = std::max(NumRows, Element.StartRow + Element.Rows);
+ Elements[Index].StartRow = Location.Row;
+ Elements[Index].StartCol = Location.Col;
+ NumRows = std::max(NumRows, Location.Row + Element.Rows);
}
return NumRows;
}
+Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
+ MutableArrayRef<SemanticSignatureElement> Elements,
+ Triple::EnvironmentType ShaderStage, IOType IOTy,
+ bool UseNative16BitTypes) {
+ assert(!(ShaderStage == Triple::Vertex && IOTy == IOType::In) &&
+ !(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
+ "prefix-stable packing is not valid for vertex inputs or pixel "
+ "outputs");
+ return packSignatureInOrder(Elements, seq<unsigned>(0, Elements.size()),
+ ShaderStage, IOTy, UseNative16BitTypes);
+}
+
Expected<unsigned> llvm::hlsl::packSignatureIndexed(
MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy) {
@@ -627,28 +645,10 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
return Left.SigId < Right.SigId;
});
- // Pack a copy so Elements remains in its original signature order.
- SmallVector<SemanticSignatureElement> SortedElements;
- SortedElements.reserve(Elements.size());
- for (const SortKey &Key : SortedKeys)
- SortedElements.push_back(Elements[Key.OriginalIndex]);
-
- Expected<unsigned> NumRows = packSignaturePrefixStable(
- SortedElements, ShaderStage, IOTy, UseNative16BitTypes);
-
- for (const auto &[SortedIndex, Key] : enumerate(SortedKeys)) {
- Elements[Key.OriginalIndex].StartRow = SortedElements[SortedIndex].StartRow;
- Elements[Key.OriginalIndex].StartCol = SortedElements[SortedIndex].StartCol;
- }
-
- if (NumRows)
- return *NumRows;
-
- return handleErrors(
- NumRows.takeError(), [&](const SignaturePackingError &Err) -> Error {
- assert(Err.getElementIndex() < SortedKeys.size() &&
- "invalid sorted element index");
- return make_error<SignaturePackingError>(
- Err.getErrorKind(), SortedKeys[Err.getElementIndex()].OriginalIndex);
- });
+ // Traverse the original elements in packing order without copying their
+ // metadata or changing their signature order. Only locations are written.
+ auto Order = map_range(SortedKeys,
+ [](const SortKey &Key) { return Key.OriginalIndex; });
+ return packSignatureInOrder(Elements, Order, ShaderStage, IOTy,
+ UseNative16BitTypes);
}
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 62d527297eef3..81350c8c2e290 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -121,6 +121,21 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
}
}
+ void verifyMetadata(const SemanticSignatureElement &Before,
+ const SemanticSignatureElement &After) {
+ EXPECT_EQ(After.SigId, Before.SigId);
+ EXPECT_EQ(After.SemanticName, Before.SemanticName);
+ EXPECT_EQ(After.CompType, Before.CompType);
+ EXPECT_EQ(After.SemanticKind, Before.SemanticKind);
+ EXPECT_EQ(After.SemanticIndices, Before.SemanticIndices);
+ EXPECT_EQ(After.InterpMode, Before.InterpMode);
+ EXPECT_EQ(After.Rows, Before.Rows);
+ EXPECT_EQ(After.Cols, Before.Cols);
+ EXPECT_EQ(After.UsageMask, Before.UsageMask);
+ EXPECT_EQ(After.DynIndexMask, Before.DynIndexMask);
+ EXPECT_EQ(After.GSStream, Before.GSStream);
+ }
+
void verifyPackingError(PackingMethod Method, const TestConfig &Config,
SignaturePackingError::ErrorKind ExpectedKind,
unsigned ExpectedElementIndex) {
@@ -1958,6 +1973,39 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeometryStreams) {
// Optimized ordering tests
//===----------------------------------------------------------------------===//
+TEST_F(HLSLSemanticSignaturePackingTest, PackingPreservesElementMetadata) {
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows,
+ /*Cols=*/1, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear}});
+ for (PackingMethod Method :
+ {PackingMethod::PrefixStable, PackingMethod::Optimized}) {
+ SCOPED_TRACE(static_cast<unsigned>(Method));
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ Elements[0].SemanticName = "Scalar";
+ Elements[1].SemanticName = "Array";
+ Elements[0].UsageMask = 3;
+ Elements[1].UsageMask = 1;
+ Elements[1].DynIndexMask = 1;
+ const SmallVector<SemanticSignatureElement> Before = Elements;
+ const uint32_t *Indices = Elements[1].SemanticIndices.data();
+
+ Expected<unsigned> Rows = pack(Method, Elements, Config);
+ ASSERT_THAT_EXPECTED(Rows, Succeeded());
+ EXPECT_EQ(*Rows, MaxSignatureRows);
+ EXPECT_EQ(Elements[1].SemanticIndices.data(), Indices);
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ SCOPED_TRACE(I);
+ verifyMetadata(Before[I], Elements[I]);
+ EXPECT_NE(Elements[I].StartRow, UnallocatedRow);
+ EXPECT_NE(Elements[I].StartCol, UnallocatedCol);
+ }
+ }
+}
+
TEST_F(HLSLSemanticSignaturePackingTest, OptimizedUsesSignatureIDToBreakTies) {
TestConfig Config(Triple::Vertex, IOType::Out, {});
for (unsigned I = 0; I != 3; ++I)
>From ba2e83b08c40cb1fea80f0a696d902fce90854de Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 17:37:17 +0000
Subject: [PATCH 10/17] review: make optimized clip/cull packing an atomic
phase
Split optimized packing into the preceding groups, clip/cull, and
subsequent groups. Plan clip/cull placement in scratch state and publish
locations and row state only after every geometry stream succeeds
---
llvm/docs/DirectX/SemanticSignatures.md | 13 +-
.../Frontend/HLSL/SemanticSignaturePacking.h | 23 +-
.../HLSL/SemanticSignaturePacking.cpp | 237 ++++++++--
.../HLSLSemanticSignaturePackingTest.cpp | 432 +++++++++++++++++-
4 files changed, 636 insertions(+), 69 deletions(-)
diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 4e559ab698ca9..722594bf6f83f 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -165,8 +165,8 @@ constraints are satisfied:
- Within each row, elements are ordered by category: arbitrary values first,
followed by system values, and then system-generated values. For example, a
system value can never be packed to the left of an arbitrary value.
- `ClipCull` and `TessFactor` follow these categories in the internal ordering,
- with the additional placement rules described below.
+ Indexed tessellation factors follow these categories in the component
+ ordering. Clip/cull values occupy dedicated rows as described below.
- A system value or system generated value cannot be placed in a dynamically
indexed row. A dynamically indexed row is a row within the range covered by
a multi-row element, where the row is selected using a dynamic index.
@@ -218,11 +218,12 @@ in this order:
Within each group, elements are ordered first by the numeric value of their
interpolation mode, then by decreasing row count, then by decreasing column
-count, and finally by increasing signature ID. Component bit width is not a
-sort key; it remains a compatibility constraint when elements are placed in a
-row.
+count, and finally by increasing signature ID.
+
+Unlike prefix-stable packing, optimized packing does not reserve whole rows for
+clip/cull values. They may share compatible rows with other values following the
+same constraints.
-The sorted elements are then placed using the prefix-stable packing algorithm.
This is a greedy optimized ordering rather than an exhaustive search for a
minimum-row layout. Reordering can reduce the number of rows occupied, but
means that appending an element may change locations assigned to existing
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 3df48d9bde811..ddbe113e2a0a0 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -105,9 +105,10 @@ LLVM_ABI Expected<unsigned>
packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
-/// Sorts eligible signature elements using the optimized ordering and then
-/// packs them with the prefix-stable packing algorithm. Like prefix-stable
-/// packing, this is not valid for vertex inputs or pixel outputs.
+/// Packs eligible signature elements in optimized order, allowing clip/cull
+/// values to share compatible rows while preserving their two-row limit.
+/// Like prefix-stable packing, this is not valid for vertex inputs or pixel
+/// outputs.
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
///
@@ -116,11 +117,17 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
/// not the sum of their extents.
///
/// Only StartRow and StartCol are modified; Elements remains in its original
-/// signature order. On failure, elements
-/// packed before the failing element in optimized order keep their assigned
-/// locations; the failing element and those after it in that order retain the
-/// unallocated row and column sentinels. The returned SignaturePackingError
-/// identifies the failing element by its index in the original Elements array.
+/// signature order. Earlier successful allocations are preserved on failure,
+/// except that the clip/cull phase is atomic across all geometry streams:
+/// failure in that phase leaves every clip/cull element unallocated, without
+/// changing preceding non-clip/cull allocations. Later groups are not packed.
+///
+/// SignaturePackingError uses indices in the original Elements array. An
+/// intrinsic clip/cull overflow identifies the first element that fails the
+/// temporary two-row packing. A single-row group's placement failure identifies
+/// its first element; failure to find an adjacent pair identifies the first
+/// clip/cull element of that stream. Invalid streams identify the offending
+/// element. A group error need not indicate that this element alone is invalid.
LLVM_ABI Expected<unsigned>
packSignatureOptimized(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 5d0524bd4799f..d8f2402270350 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -81,6 +81,8 @@ struct SignatureRow {
SemanticInterpretation::Arbitrary;
};
+using SignatureRows = std::array<SignatureRow, MaxSignatureRows>;
+
// Everything the packing rules need to know about the element that is being
// placed. It applies to every row that the element covers.
struct ElementPlacement {
@@ -97,6 +99,12 @@ struct ElementLocation {
uint8_t Col = UnallocatedCol;
};
+struct OptimizedClipCullElement {
+ unsigned Index;
+ ElementPlacement Placement;
+ ElementLocation Location;
+};
+
// Clip/cull elements are first packed into an independent two-row grid. Each
// row used in that grid maps to a whole reserved row in the signature.
struct ClipCullState {
@@ -126,7 +134,7 @@ static uint8_t getStartColumn(uint8_t ColumnMask) {
static PackingGroup
getOptimizedPackingGroup(const SemanticSignatureElement &Element,
- Triple::EnvironmentType ShaderStage, IOType IOTy) {
+ Triple::EnvironmentType ShaderStage, IOType IOTy) {
const SemanticInterpretation Interpretation =
getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
assert((Interpretation != SemanticInterpretation::Invalid &&
@@ -185,6 +193,26 @@ static unsigned getComponentWidth(dxil::ElementType ComponentType,
}
}
+static ElementPlacement
+getElementPlacement(const SemanticSignatureElement &Element,
+ SemanticInterpretation Interpretation,
+ bool UseNative16BitTypes) {
+ // Only indexed tessellation factors need the reserved last column.
+ if (Interpretation == SemanticInterpretation::TessFactor && Element.Rows == 1)
+ Interpretation = SemanticInterpretation::SV;
+ return {Element.Rows, Element.Cols,
+ getComponentWidth(Element.CompType, UseNative16BitTypes),
+ Element.InterpMode, Interpretation};
+}
+
+static SemanticInterpretation
+getComponentOrder(SemanticInterpretation Interpretation) {
+ // Clip/cull values have system-value component ordering, but may be indexed.
+ return Interpretation == SemanticInterpretation::ClipCull
+ ? SemanticInterpretation::SV
+ : Interpretation;
+}
+
// Returns whether Placement may be co-packed into a Row that it covers, where
// IndexedRange is the range of rows that it is dynamically indexed over.
static bool canCoPack(const SignatureRow &Row,
@@ -219,7 +247,8 @@ static bool canCoPack(const SignatureRow &Row,
// Indexed tess factors are reserved in the last column, so arbitrary values
// may still fill the columns to their left without violating that ordering.
if (Row.OccupiedColumns &&
- Placement.Interpretation < Row.RightmostInterpretation &&
+ getComponentOrder(Placement.Interpretation) <
+ getComponentOrder(Row.RightmostInterpretation) &&
!(Placement.Interpretation == SemanticInterpretation::Arbitrary &&
Row.RightmostInterpretation == SemanticInterpretation::TessFactor))
return false;
@@ -414,6 +443,129 @@ packClipCullElement(ElementLocation &Location,
return std::nullopt;
}
+// Work only on scratch rows and locations. The caller commits the entire
+// clip/cull phase after every stream succeeds.
+static Error
+packOptimizedClipCullStream(MutableArrayRef<OptimizedClipCullElement> Elements,
+ MutableArrayRef<SignatureRow> Rows) {
+ if (Elements.empty())
+ return Error::success();
+
+ std::array<SignatureRow, MaxClipCullRows> LocalRows;
+ bool HasIndexed = false;
+ for (auto &Element : Elements) {
+ if (!prefixPackElement(Element.Location, LocalRows, Element.Placement))
+ return make_error<SignaturePackingError>(
+ SignaturePackingError::ClipCullOverflow, Element.Index);
+ HasIndexed |= Element.Placement.Rows > 1;
+ }
+
+ if (!HasIndexed) {
+ // Keep single-row elements grouped into at most two rows, rather than
+ // scattering individual distances across unrelated signature gaps.
+ std::array<ElementLocation, MaxClipCullRows> Destinations;
+ for (unsigned Row = 0; Row != MaxClipCullRows; ++Row) {
+ auto First = llvm::find_if(Elements, [Row](const auto &Element) {
+ return Element.Location.Row == Row;
+ });
+ if (First == Elements.end())
+ continue;
+ ElementPlacement Bundle = First->Placement;
+ Bundle.Cols = popcount(LocalRows[Row].OccupiedColumns);
+ if (!prefixPackElement(Destinations[Row], Rows, Bundle))
+ return make_error<SignaturePackingError>(
+ SignaturePackingError::SignatureOverflow, First->Index);
+ // Later elements can establish an initially undefined interpolation mode.
+ Rows[Destinations[Row].Row].InterpMode = LocalRows[Row].InterpMode;
+ }
+ for (auto &Element : Elements) {
+ ElementLocation Destination = Destinations[Element.Location.Row];
+ Element.Location = {
+ Destination.Row,
+ static_cast<uint8_t>(Destination.Col + Element.Location.Col)};
+ }
+ return Error::success();
+ }
+
+ // Try the whole indexed group in each adjacent pair. Preserve absolute row
+ // coordinates for existing indexed ranges, even those extending beyond it.
+ for (unsigned Row = 0; Row + MaxClipCullRows <= Rows.size(); ++Row) {
+ SmallVector<SignatureRow, MaxSignatureRows> CandidateRows(Rows.begin(),
+ Rows.end());
+ bool Fits = true;
+ for (auto &Element : Elements) {
+ Element.Location = {};
+ for (unsigned Start = Row;
+ Start + Element.Placement.Rows <= Row + MaxClipCullRows; ++Start) {
+ if (auto Mask = canPlaceAt(CandidateRows, Start, Element.Placement)) {
+ placeAt(CandidateRows, Start, Element.Placement, *Mask,
+ Element.Location);
+ break;
+ }
+ }
+ if (Element.Location.Row == UnallocatedRow) {
+ Fits = false;
+ break;
+ }
+ }
+ if (Fits) {
+ llvm::copy(ArrayRef(CandidateRows).slice(Row, MaxClipCullRows),
+ Rows.begin() + Row);
+ return Error::success();
+ }
+ }
+ // No element alone necessarily caused this failure; identify the group.
+ return make_error<SignaturePackingError>(
+ SignaturePackingError::SignatureOverflow, Elements.front().Index);
+}
+
+static Expected<unsigned>
+packOptimizedClipCull(MutableArrayRef<SemanticSignatureElement> Elements,
+ ArrayRef<unsigned> Order,
+ MutableArrayRef<SignatureRows> Rows,
+ bool UseNative16BitTypes) {
+ if (Order.empty())
+ return 0;
+ SmallVector<SmallVector<OptimizedClipCullElement>, MaxGeometryStreams>
+ Streams(Rows.size());
+ for (unsigned Index : Order) {
+ const auto &Element = Elements[Index];
+ assert(Element.StartRow == UnallocatedRow &&
+ Element.StartCol == UnallocatedCol && "already allocated?");
+ assert(Element.Rows > 0 && "signature element must have at least one row");
+ assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
+ "signature element must have between 1 and 4 columns");
+ if (Element.GSStream >= Rows.size())
+ return make_error<SignaturePackingError>(
+ SignaturePackingError::InvalidGeometryStream, Index);
+ Streams[Element.GSStream].push_back(
+ {Index,
+ getElementPlacement(Element, SemanticInterpretation::ClipCull,
+ UseNative16BitTypes),
+ {}});
+ }
+
+ SmallVector<SignatureRows, MaxGeometryStreams> CandidateRows(Rows.begin(),
+ Rows.end());
+ for (unsigned Stream = 0; Stream != Streams.size(); ++Stream)
+ if (Error Err =
+ packOptimizedClipCullStream(Streams[Stream], CandidateRows[Stream]))
+ return std::move(Err);
+
+ // Publish only after every stream succeeds. No rollback or partial-prefix
+ // recovery is needed, and preceding non-clip/cull allocations remain intact.
+ llvm::copy(CandidateRows, Rows.begin());
+ unsigned NumRows = 0;
+ for (const auto &Stream : Streams)
+ for (const auto &Element : Stream) {
+ Elements[Element.Index].StartRow = Element.Location.Row;
+ Elements[Element.Index].StartCol = Element.Location.Col;
+ NumRows =
+ std::max(NumRows, Element.Location.Row + Element.Placement.Rows);
+ }
+ return NumRows;
+}
+
void SignaturePackingError::log(raw_ostream &OS) const {
switch (Kind) {
case SignatureOverflow:
@@ -476,19 +628,11 @@ Expected<unsigned> llvm::hlsl::packSignatureStacked(
}
template <typename IndexRange>
-static Expected<unsigned>
-packSignatureInOrder(MutableArrayRef<SemanticSignatureElement> Elements,
- const IndexRange &Order,
- Triple::EnvironmentType ShaderStage, IOType IOTy,
- bool UseNative16BitTypes) {
- // Only a geometry shader output signature packs its streams independently.
- const unsigned StreamCount =
- ShaderStage == Triple::EnvironmentType::Geometry && IOTy == IOType::Out
- ? MaxGeometryStreams
- : 1;
-
- SmallVector<std::array<SignatureRow, MaxSignatureRows>, 1> Rows(StreamCount);
- SmallVector<ClipCullState, 1> ClipCullStates(StreamCount);
+static Expected<unsigned> packSignatureInOrder(
+ MutableArrayRef<SemanticSignatureElement> Elements, const IndexRange &Order,
+ Triple::EnvironmentType ShaderStage, IOType IOTy, bool UseNative16BitTypes,
+ MutableArrayRef<SignatureRows> Rows) {
+ SmallVector<ClipCullState, 1> ClipCullStates(Rows.size());
unsigned NumRows = 0;
for (unsigned Index : Order) {
const SemanticSignatureElement &Element = Elements[Index];
@@ -497,7 +641,7 @@ packSignatureInOrder(MutableArrayRef<SemanticSignatureElement> Elements,
assert(Element.Rows > 0 && "signature element must have at least one row");
assert(Element.Cols > 0 && Element.Cols <= MaxSignatureCols &&
"signature element must have between 1 and 4 columns");
- if (Element.GSStream >= StreamCount)
+ if (Element.GSStream >= Rows.size())
return make_error<SignaturePackingError>(
SignaturePackingError::InvalidGeometryStream,
static_cast<unsigned>(Index));
@@ -515,18 +659,8 @@ packSignatureInOrder(MutableArrayRef<SemanticSignatureElement> Elements,
"unexpected semantic interpretation for prefix-stable packing, "
"should have been diagnosed by Sema");
- const unsigned ComponentWidth =
- getComponentWidth(Element.CompType, UseNative16BitTypes);
- // Only a tess factor that covers multiple rows is dynamically indexable
- // and needs to be reserved in the last column.
- const SemanticInterpretation PackingInterpretation =
- Interpretation == SemanticInterpretation::TessFactor &&
- Element.Rows == 1
- ? SemanticInterpretation::SV
- : Interpretation;
- const ElementPlacement Placement = {Element.Rows, Element.Cols,
- ComponentWidth, Element.InterpMode,
- PackingInterpretation};
+ const ElementPlacement Placement =
+ getElementPlacement(Element, Interpretation, UseNative16BitTypes);
const unsigned StreamIndex = Element.GSStream;
MutableArrayRef<SignatureRow> StreamRows = Rows[StreamIndex];
@@ -559,8 +693,13 @@ Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
!(ShaderStage == Triple::Pixel && IOTy == IOType::Out) &&
"prefix-stable packing is not valid for vertex inputs or pixel "
"outputs");
+ const unsigned StreamCount =
+ ShaderStage == Triple::Geometry && IOTy == IOType::Out
+ ? MaxGeometryStreams
+ : 1;
+ SmallVector<SignatureRows, 1> Rows(StreamCount);
return packSignatureInOrder(Elements, seq<unsigned>(0, Elements.size()),
- ShaderStage, IOTy, UseNative16BitTypes);
+ ShaderStage, IOTy, UseNative16BitTypes, Rows);
}
Expected<unsigned> llvm::hlsl::packSignatureIndexed(
@@ -645,10 +784,38 @@ Expected<unsigned> llvm::hlsl::packSignatureOptimized(
return Left.SigId < Right.SigId;
});
- // Traverse the original elements in packing order without copying their
- // metadata or changing their signature order. Only locations are written.
- auto Order = map_range(SortedKeys,
- [](const SortKey &Key) { return Key.OriginalIndex; });
- return packSignatureInOrder(Elements, Order, ShaderStage, IOTy,
- UseNative16BitTypes);
+ const unsigned StreamCount =
+ ShaderStage == Triple::Geometry && IOTy == IOType::Out
+ ? MaxGeometryStreams
+ : 1;
+ SmallVector<SignatureRows, 1> Rows(StreamCount);
+ auto ClipBegin = llvm::partition_point(SortedKeys, [](const SortKey &Key) {
+ return Key.Group < PackingGroup::ClipCull;
+ });
+ auto ClipEnd = llvm::partition_point(
+ make_range(ClipBegin, SortedKeys.end()),
+ [](const SortKey &Key) { return Key.Group == PackingGroup::ClipCull; });
+
+ auto Pack = [&](auto Begin, auto End) {
+ auto Order = map_range(make_range(Begin, End), [](const SortKey &Key) {
+ return Key.OriginalIndex;
+ });
+ return packSignatureInOrder(Elements, Order, ShaderStage, IOTy,
+ UseNative16BitTypes, Rows);
+ };
+
+ Expected<unsigned> Before = Pack(SortedKeys.begin(), ClipBegin);
+ if (!Before)
+ return Before.takeError();
+ SmallVector<unsigned> ClipCullOrder;
+ for (const SortKey &Key : make_range(ClipBegin, ClipEnd))
+ ClipCullOrder.push_back(Key.OriginalIndex);
+ Expected<unsigned> ClipCull =
+ packOptimizedClipCull(Elements, ClipCullOrder, Rows, UseNative16BitTypes);
+ if (!ClipCull)
+ return ClipCull.takeError();
+ Expected<unsigned> After = Pack(ClipEnd, SortedKeys.end());
+ if (!After)
+ return After.takeError();
+ return std::max({*Before, *ClipCull, *After});
}
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 81350c8c2e290..7bfc254301f0b 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -1267,15 +1267,14 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableClipCull) {
// Optimized layout:
// reg0: First.xyz | WithFirst.w
- // reg1: AfterClipCull.x | unused.yzw
+ // reg1: AfterClipCull.x | Clip1.yz | Cull0.w
// reg2: Cull1.xyz | Clip0.w
- // reg3: Clip1.xy | Cull0.z | unused.w
- verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0},
{/*Row=*/2, /*Col=*/3},
{/*Row=*/2, /*Col=*/0},
- {/*Row=*/3, /*Col=*/2},
- {/*Row=*/3, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/1},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/1, /*Col=*/0}});
}
@@ -1320,14 +1319,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableIndexedClipCull) {
// Optimized layout:
// reg0: First.xyz | WithFirst.w
- // reg1: AfterClipCull.x | unused.yzw
- // reg2: Cull1[0].xy | Clip0.z | Clip1.w
- // reg3: Cull1[1].xy | unused.zw
- verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ // reg1: AfterClipCull.x | Cull1[0].yz | Clip0.w
+ // reg2: Clip1.x | Cull1[1].yz | unused.w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0},
- {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/1, /*Col=*/3},
+ {/*Row=*/1, /*Col=*/1},
{/*Row=*/2, /*Col=*/0},
- {/*Row=*/2, /*Col=*/3},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/1, /*Col=*/0}});
}
@@ -1372,14 +1370,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableMultipleIndexedClipCull) {
// Optimized layout:
// reg0: First.xyz | WithFirst.w
- // reg1: AfterClipCull.x | unused.yzw
- // reg2: Cull1[0].xy | Clip1[0].z | Clip0.w
- // reg3: Cull1[1].xy | Clip1[1].z | unused.w
- verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ // reg1: AfterClipCull.x | Cull1[0].yz | Clip1[0].w
+ // reg2: Clip0.x | Cull1[1].yz | Clip1[1].w
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0},
- {/*Row=*/2, /*Col=*/3},
{/*Row=*/2, /*Col=*/0},
- {/*Row=*/2, /*Col=*/2},
+ {/*Row=*/1, /*Col=*/1},
+ {/*Row=*/1, /*Col=*/3},
{/*Row=*/0, /*Col=*/3},
{/*Row=*/1, /*Col=*/0}});
}
@@ -1801,7 +1798,8 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest, OptimizedPreservesPartialAllocation) {
// Declaration order is Small, Full, Last. Optimized packing places Full
// first, then fails on Small. Report Small's original index (zero), not its
- // index in packing order (one), and preserve Full's allocation.
+ // index in packing order (one). Preserve Full's allocation for ordinary
+ // elements, but leave the entire clip/cull phase unallocated on failure.
//
// struct VSOut {
// float2 Small : Small;
@@ -1834,8 +1832,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, OptimizedPreservesPartialAllocation) {
std::string(IsClipCull ? "clip/cull elements do not fit in "
: "signature elements do not fit in ") +
std::to_string(RowCount) + " rows (element 0)"));
- EXPECT_EQ(Elements[1].StartRow, 0u);
- EXPECT_EQ(Elements[1].StartCol, 0u);
+ EXPECT_EQ(Elements[1].StartRow, IsClipCull ? UnallocatedRow : 0u);
+ EXPECT_EQ(Elements[1].StartCol, IsClipCull ? UnallocatedCol : 0u);
for (unsigned I : {0u, 2u}) {
EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
@@ -1848,6 +1846,188 @@ TEST_F(HLSLSemanticSignaturePackingTest, OptimizedPreservesPartialAllocation) {
}
}
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedSingleRowClipCullFailureIsAtomic) {
+ // struct VSOut {
+ // float2 Clip1 : SV_ClipDistance1;
+ // float4 Fill[31] : FILL;
+ // float Cull : SV_CullDistance0;
+ // float3 Clip0 : SV_ClipDistance0;
+ // };
+ // The first bundle (Clip0 + Cull) fits in row 31, but Clip1's bundle fails.
+ // Neither bundle is published; only the preceding Fill allocation remains.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/1},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows - 1,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPackingError(PackingMethod::Optimized, Config,
+ SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/0);
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ const SmallVector<SemanticSignatureElement> Before = Elements;
+ EXPECT_THAT_EXPECTED(pack(PackingMethod::Optimized, Elements, Config),
+ FailedWithMessage("signature elements do not fit in " +
+ std::to_string(MaxSignatureRows) +
+ " rows (element 0)"));
+ EXPECT_EQ(Elements[1].StartRow, 0u);
+ EXPECT_EQ(Elements[1].StartCol, 0u);
+ for (unsigned I : {0u, 2u, 3u}) {
+ EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ }
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ SCOPED_TRACE(I);
+ verifyMetadata(Before[I], Elements[I]);
+ }
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedIndexedClipCullFailureIsAtomic) {
+ // struct VSOut {
+ // float2 Cull : SV_CullDistance0;
+ // float4 Fill[30] : FILL;
+ // float A[2] : A;
+ // float2 Clip[2] : SV_ClipDistance0;
+ // };
+ // The final pair fits Clip in yz but not Cull.xy. Do not retain Clip's
+ // speculative allocation. The error identifies Clip, first in group order.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows - 2,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/2, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPackingError(PackingMethod::Optimized, Config,
+ SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/3);
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ const SmallVector<SemanticSignatureElement> Before = Elements;
+ EXPECT_THAT_EXPECTED(pack(PackingMethod::Optimized, Elements, Config),
+ FailedWithMessage("signature elements do not fit in " +
+ std::to_string(MaxSignatureRows) +
+ " rows (element 3)"));
+ EXPECT_EQ(Elements[1].StartRow, 0u);
+ EXPECT_EQ(Elements[1].StartCol, 0u);
+ EXPECT_EQ(Elements[2].StartRow, MaxSignatureRows - 2);
+ EXPECT_EQ(Elements[2].StartCol, 0u);
+ for (unsigned I : {0u, 3u}) {
+ EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ }
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ SCOPED_TRACE(I);
+ verifyMetadata(Before[I], Elements[I]);
+ }
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullFailureIsAtomicAcrossStreams) {
+ const struct {
+ unsigned Rows;
+ unsigned Stream;
+ SignaturePackingError::ErrorKind Kind;
+ } Failures[] = {
+ {1, 1, SignaturePackingError::SignatureOverflow},
+ {3, 1, SignaturePackingError::ClipCullOverflow},
+ {1, MaxGeometryStreams, SignaturePackingError::InvalidGeometryStream}};
+ for (const auto &Failure : Failures) {
+ SCOPED_TRACE(Failure.Kind);
+ TestConfig Config(
+ Triple::Geometry, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, /*GSStream=*/1},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, /*GSStream=*/0},
+ {dxbc::PSV::SemanticKind::CullDistance, Failure.Rows, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, Failure.Stream}});
+ // Stream 0 fits, but failure in stream 1 (or an invalid stream) must leave
+ // every clip/cull element unallocated, without undoing the earlier Fill.
+ verifyPackingError(PackingMethod::Optimized, Config, Failure.Kind,
+ /*ExpectedElementIndex=*/2);
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ const SmallVector<SemanticSignatureElement> Before = Elements;
+ EXPECT_THAT_EXPECTED(pack(PackingMethod::Optimized, Elements, Config),
+ Failed<SignaturePackingError>());
+ EXPECT_EQ(Elements[0].StartRow, 0u);
+ EXPECT_EQ(Elements[0].StartCol, 0u);
+ for (unsigned I : {1u, 2u}) {
+ EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ }
+ for (unsigned I = 0; I != Elements.size(); ++I) {
+ SCOPED_TRACE(I);
+ verifyMetadata(Before[I], Elements[I]);
+ }
+ }
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullFailureSkipsLaterGroups) {
+ TestConfig Config(
+ Triple::Pixel, IOType::In,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/MaxClipCullRows + 1,
+ /*Cols=*/1, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+ verifyPackingError(PackingMethod::Optimized, Config,
+ SignaturePackingError::ClipCullOverflow,
+ /*ExpectedElementIndex=*/1);
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ EXPECT_THAT_EXPECTED(pack(PackingMethod::Optimized, Elements, Config),
+ Failed<SignaturePackingError>());
+ EXPECT_EQ(Elements[0].StartRow, 0u);
+ EXPECT_EQ(Elements[0].StartCol, 0u);
+ for (unsigned I : {1u, 2u}) {
+ EXPECT_EQ(Elements[I].StartRow, UnallocatedRow) << "element " << I;
+ EXPECT_EQ(Elements[I].StartCol, UnallocatedCol) << "element " << I;
+ }
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedLaterFailurePreservesClipCullAllocations) {
+ TestConfig Config(
+ Triple::Pixel, IOType::In,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows - 1,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/4,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+ verifyPackingError(PackingMethod::Optimized, Config,
+ SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/2);
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ EXPECT_THAT_EXPECTED(pack(PackingMethod::Optimized, Elements, Config),
+ Failed<SignaturePackingError>());
+ EXPECT_EQ(Elements[0].StartRow, 0u);
+ EXPECT_EQ(Elements[0].StartCol, 0u);
+ EXPECT_EQ(Elements[1].StartRow, MaxSignatureRows - 1);
+ EXPECT_EQ(Elements[1].StartCol, 0u);
+ EXPECT_EQ(Elements[2].StartRow, UnallocatedRow);
+ EXPECT_EQ(Elements[2].StartCol, UnallocatedCol);
+}
+
//===----------------------------------------------------------------------===//
// Prefix-stable geometry stream tests
//===----------------------------------------------------------------------===//
@@ -1969,6 +2149,218 @@ TEST_F(HLSLSemanticSignaturePackingTest, PrefixStableGeometryStreams) {
{/*Row=*/1, /*Col=*/0}});
}
+//===----------------------------------------------------------------------===//
+// Optimized clip/cull packing tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, OptimizedClipCullSharesArbitraryRow) {
+ // struct VSOut { float A : A; float3 Clip : SV_ClipDistance; };
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPacking(PackingMethod::PrefixStable, Config, /*ExpectedRows=*/2,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+ // Optimized layout: reg0: A.x | Clip.yzw.
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, OptimizedClipCullFillsSignature) {
+ // struct VSOut {
+ // float4 Fill[31] : FILL;
+ // float A : A;
+ // float3 Clip : SV_ClipDistance;
+ // };
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows - 1,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPackingError(PackingMethod::PrefixStable, Config,
+ SignaturePackingError::SignatureOverflow,
+ /*ExpectedElementIndex=*/2);
+ // All 128 components fit, including A.x | Clip.yzw in the last row.
+ verifyPacking(PackingMethod::Optimized, Config,
+ /*ExpectedRows=*/MaxSignatureRows,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/MaxSignatureRows - 1, /*Col=*/0},
+ {/*Row=*/MaxSignatureRows - 1, /*Col=*/1}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullKeepsScalarGroupsTogether) {
+ // struct VSOut {
+ // float3 A[3] : A;
+ // float Clip0 : SV_ClipDistance0;
+ // float Clip1 : SV_ClipDistance1;
+ // float Cull0 : SV_CullDistance0;
+ // };
+ // Filling the three w gaps individually would use three clip/cull rows.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/3, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/1},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/4,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/3, /*Col=*/0},
+ {/*Row=*/3, /*Col=*/1},
+ {/*Row=*/3, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedSingleRowClipCullCanUseNonAdjacentRows) {
+ // Three arbitrary float2 values establish distinct interpolation modes.
+ // Cull fits after the first and Clip after the third; row 1 separates them.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::LinearCentroid},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/2,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::LinearCentroid}});
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/1, /*Col=*/0},
+ {/*Row=*/2, /*Col=*/0},
+ {/*Row=*/0, /*Col=*/2},
+ {/*Row=*/2, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedIndexedClipCullCannotShareSystemValueRows) {
+ // Position fixes its row's indexed range to empty. The Clip array must
+ // start in the next row even though Position leaves three free columns.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Position, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/2, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedIndexedClipCullRetriesAdjacentPairs) {
+ // struct VSOut {
+ // float3 A[2] : A;
+ // float Clip[2] : SV_ClipDistance;
+ // float Cull : SV_CullDistance;
+ // };
+ // The pair at row 0 fits Clip but not Cull. The next pair fits both.
+ TestConfig Config(
+ Triple::Vertex, IOType::Out,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/2, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ // Optimized layout:
+ // reg0: A[0].xyz | unused.w
+ // reg1: A[1].xyz | Clip[0].w
+ // reg2: Cull.x | unused.yz | Clip[1].w
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/3}, {/*Row=*/2, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullRespectsRowCompatibility) {
+ for (bool Native16Bit : {false, true}) {
+ SCOPED_TRACE(Native16Bit);
+ for (bool DifferentInterp : {false, true}) {
+ SCOPED_TRACE(DifferentInterp);
+ TestConfig Config(
+ Triple::Vertex, IOType::Out, Native16Bit,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F16,
+ DifferentInterp ? dxbc::PSV::InterpolationMode::Constant
+ : dxbc::PSV::InterpolationMode::Linear},
+ {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+ const bool Separate = Native16Bit || DifferentInterp;
+ verifyPacking(PackingMethod::Optimized, Config,
+ /*ExpectedRows=*/Separate ? 2 : 1,
+ {{/*Row=*/0, /*Col=*/0},
+ {/*Row=*/Separate ? 1u : 0u,
+ /*Col=*/static_cast<uint8_t>(Separate ? 0 : 1)}});
+ }
+ }
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullPrecedesSystemGeneratedValues) {
+ // struct PSIn {
+ // nointerpolation uint A : A;
+ // nointerpolation float Cull : SV_CullDistance;
+ // bool IsFrontFace : SV_IsFrontFace;
+ // };
+ TestConfig Config(
+ Triple::Pixel, IOType::In,
+ {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::CullDistance, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
+ {dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
+ dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+ verifyPacking(
+ PackingMethod::Optimized, Config, /*ExpectedRows=*/1,
+ {{/*Row=*/0, /*Col=*/0}, {/*Row=*/0, /*Col=*/1}, {/*Row=*/0, /*Col=*/2}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest,
+ OptimizedClipCullPacksGeometryStreamsIndependently) {
+ TestConfig Config(Triple::Geometry, IOType::Out, {});
+ for (unsigned Stream = 0; Stream != MaxGeometryStreams; ++Stream) {
+ Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary,
+ /*Rows=*/MaxSignatureRows - 1,
+ /*Cols=*/4, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, Stream});
+ Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
+ /*Cols=*/1, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, Stream});
+ Config.Elements.push_back({dxbc::PSV::SemanticKind::ClipDistance,
+ /*Rows=*/1, /*Cols=*/3, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear,
+ /*SemanticIndex=*/0, Stream});
+ }
+ SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+ Expected<unsigned> Rows = pack(PackingMethod::Optimized, Elements, Config);
+ ASSERT_THAT_EXPECTED(Rows, Succeeded());
+ EXPECT_EQ(*Rows, MaxSignatureRows);
+ for (unsigned Stream = 0; Stream != MaxGeometryStreams; ++Stream) {
+ SCOPED_TRACE(Stream);
+ EXPECT_EQ(Elements[3 * Stream].StartRow, 0u);
+ EXPECT_EQ(Elements[3 * Stream].StartCol, 0u);
+ EXPECT_EQ(Elements[3 * Stream + 1].StartRow, MaxSignatureRows - 1);
+ EXPECT_EQ(Elements[3 * Stream + 1].StartCol, 0u);
+ EXPECT_EQ(Elements[3 * Stream + 2].StartRow, MaxSignatureRows - 1);
+ EXPECT_EQ(Elements[3 * Stream + 2].StartCol, 1u);
+ }
+}
+
//===----------------------------------------------------------------------===//
// Optimized ordering tests
//===----------------------------------------------------------------------===//
>From 7e143566db7b212f99b880aabbe45a40b3a34a3b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 21:24:06 +0000
Subject: [PATCH 11/17] self-review: clean up
---
.../Frontend/HLSL/SemanticSignaturePacking.h | 23 ++++++-------------
.../HLSL/SemanticSignaturePacking.cpp | 4 ----
2 files changed, 7 insertions(+), 20 deletions(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index ddbe113e2a0a0..25e5e66b82668 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -105,10 +105,9 @@ LLVM_ABI Expected<unsigned>
packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
-/// Packs eligible signature elements in optimized order, allowing clip/cull
-/// values to share compatible rows while preserving their two-row limit.
-/// Like prefix-stable packing, this is not valid for vertex inputs or pixel
-/// outputs.
+/// Packs eligible signature elements in an optimized order by reordering
+/// elements into a optimal packind order and allowing clip/cull to share
+/// comapatible rows. Only StartRow and StartCol are modified
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
///
@@ -116,18 +115,10 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
/// allocated. For geometry outputs this is the maximum extent of any stream,
/// not the sum of their extents.
///
-/// Only StartRow and StartCol are modified; Elements remains in its original
-/// signature order. Earlier successful allocations are preserved on failure,
-/// except that the clip/cull phase is atomic across all geometry streams:
-/// failure in that phase leaves every clip/cull element unallocated, without
-/// changing preceding non-clip/cull allocations. Later groups are not packed.
-///
-/// SignaturePackingError uses indices in the original Elements array. An
-/// intrinsic clip/cull overflow identifies the first element that fails the
-/// temporary two-row packing. A single-row group's placement failure identifies
-/// its first element; failure to find an adjacent pair identifies the first
-/// clip/cull element of that stream. Invalid streams identify the offending
-/// element. A group error need not indicate that this element alone is invalid.
+/// On failure, Elements is left partially packed: the elements preceding the
+/// one reported by the returned SignaturePackingError keep the locations
+/// they were assigned, while that element and the ones following it retain the
+/// unallocated row and column sentinels.
LLVM_ABI Expected<unsigned>
packSignatureOptimized(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy,
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index d8f2402270350..b2b0542226835 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -83,8 +83,6 @@ struct SignatureRow {
using SignatureRows = std::array<SignatureRow, MaxSignatureRows>;
-// Everything the packing rules need to know about the element that is being
-// placed. It applies to every row that the element covers.
struct ElementPlacement {
unsigned Rows;
unsigned Cols;
@@ -93,7 +91,6 @@ struct ElementPlacement {
SemanticInterpretation Interpretation;
};
-// The only fields written back to a semantic signature element by packing.
struct ElementLocation {
uint32_t Row = UnallocatedRow;
uint8_t Col = UnallocatedCol;
@@ -114,7 +111,6 @@ struct ClipCullState {
unsigned RowsUsed = 0;
};
-// Groups are packed in increasing order.
enum class PackingGroup : unsigned {
FullRegister,
IndexedTessFactor,
>From e28d7b8eb358ecc976bd4766f8ce60d327701315 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 21 Sep 2026 21:27:50 +0000
Subject: [PATCH 12/17] clang format
---
.../unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 7bfc254301f0b..47417bddedd74 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -2401,9 +2401,9 @@ TEST_F(HLSLSemanticSignaturePackingTest, PackingPreservesElementMetadata) {
TEST_F(HLSLSemanticSignaturePackingTest, OptimizedUsesSignatureIDToBreakTies) {
TestConfig Config(Triple::Vertex, IOType::Out, {});
for (unsigned I = 0; I != 3; ++I)
- Config.Elements.push_back(
- {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
- dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear});
+ Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
+ /*Cols=*/1, dxil::ElementType::F32,
+ dxbc::PSV::InterpolationMode::Linear});
SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
Elements[0].SigId = 2;
Elements[1].SigId = 0;
>From 992e141008288f2a6c6e19c5820a9db3ebe843a5 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 23 Sep 2026 14:56:34 +0000
Subject: [PATCH 13/17] review: clarify optimized packing documentation
---
llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 25e5e66b82668..56d0440eb2ccd 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -106,8 +106,8 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
Triple::EnvironmentType ShaderStage, IOType IOTy);
/// Packs eligible signature elements in an optimized order by reordering
-/// elements into a optimal packind order and allowing clip/cull to share
-/// comapatible rows. Only StartRow and StartCol are modified
+/// elements into an optimal packing order and allowing clip/cull to share
+/// compatible rows. Only StartRow and StartCol are modified.
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
///
>From 56eef236f6a0c06e4f9ea5d49bdc11b57cb38473 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 23 Sep 2026 14:56:44 +0000
Subject: [PATCH 14/17] review: document optimized packing row count
---
llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 56d0440eb2ccd..ba4bf6137ead8 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -111,7 +111,7 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
///
/// See llvm/docs/DirectX/SemanticSignatures.md#optimized-packing for details.
///
-/// Returns one past the highest allocated row, or zero if no elements were
+/// Returns the number of allocated rows, or zero if no elements were
/// allocated. For geometry outputs this is the maximum extent of any stream,
/// not the sum of their extents.
///
>From 50d69d9e39b48088d992916e526f279ae3271449 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 23 Sep 2026 14:56:50 +0000
Subject: [PATCH 15/17] review: fix optimized packing failure wording
---
llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index ba4bf6137ead8..1fbe0968c77a0 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -115,7 +115,7 @@ packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
/// allocated. For geometry outputs this is the maximum extent of any stream,
/// not the sum of their extents.
///
-/// On failure, Elements is left partially packed: the elements preceding the
+/// On failure, Elements are left partially packed: the elements preceding the
/// one reported by the returned SignaturePackingError keep the locations
/// they were assigned, while that element and the ones following it retain the
/// unallocated row and column sentinels.
>From 9d21301e41328a43bd5a6bb67fde2f00db17a024 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 23 Sep 2026 14:57:43 +0000
Subject: [PATCH 16/17] review: document optimized test signatures
---
.../HLSLSemanticSignaturePackingTest.cpp | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 47417bddedd74..6013c9d25500f 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -1935,6 +1935,14 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedClipCullFailureIsAtomicAcrossStreams) {
+ // struct Stream0 {
+ // float Clip : SV_ClipDistance;
+ // };
+ //
+ // struct Stream1 {
+ // float4 Fill[32] : FILL;
+ // float Cull[Failure.Rows] : SV_CullDistance;
+ // };
const struct {
unsigned Rows;
unsigned Stream;
@@ -1959,6 +1967,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
/*SemanticIndex=*/0, Failure.Stream}});
// Stream 0 fits, but failure in stream 1 (or an invalid stream) must leave
// every clip/cull element unallocated, without undoing the earlier Fill.
+ //
+ // Partial optimized layout after failure:
+ // stream0: Clip unallocated
+ // stream1 reg0-31: Fill[0-31].xyzw
+ // Cull remains unallocated.
verifyPackingError(PackingMethod::Optimized, Config, Failure.Kind,
/*ExpectedElementIndex=*/2);
SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
@@ -1980,6 +1993,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedClipCullFailureSkipsLaterGroups) {
+ // struct PSIn {
+ // nointerpolation float A : A;
+ // nointerpolation float Cull[3] : SV_CullDistance;
+ // bool IsFrontFace : SV_IsFrontFace;
+ // };
TestConfig Config(
Triple::Pixel, IOType::In,
{{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
@@ -1989,6 +2007,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxbc::PSV::InterpolationMode::Constant},
{dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+
+ // Partial optimized layout after failure:
+ // reg0: A.x | unused.yzw
+ // Cull and IsFrontFace remain unallocated.
verifyPackingError(PackingMethod::Optimized, Config,
SignaturePackingError::ClipCullOverflow,
/*ExpectedElementIndex=*/1);
@@ -2005,6 +2027,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedLaterFailurePreservesClipCullAllocations) {
+ // struct PSIn {
+ // nointerpolation float4 Fill[31] : FILL;
+ // nointerpolation float4 Cull : SV_CullDistance;
+ // bool IsFrontFace : SV_IsFrontFace;
+ // };
TestConfig Config(
Triple::Pixel, IOType::In,
{{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows - 1,
@@ -2014,6 +2041,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
{dxbc::PSV::SemanticKind::IsFrontFace, /*Rows=*/1, /*Cols=*/1,
dxil::ElementType::I1, dxbc::PSV::InterpolationMode::Constant}});
+
+ // Partial optimized layout after failure:
+ // reg0-30: Fill[0-30].xyzw
+ // reg31: Cull.xyzw
+ // IsFrontFace remains unallocated.
verifyPackingError(PackingMethod::Optimized, Config,
SignaturePackingError::SignatureOverflow,
/*ExpectedElementIndex=*/2);
@@ -2223,6 +2255,13 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedSingleRowClipCullCanUseNonAdjacentRows) {
+ // struct VSOut {
+ // nointerpolation float2 A : A;
+ // float2 B : B;
+ // centroid float2 C : C;
+ // nointerpolation float2 Cull : SV_CullDistance;
+ // centroid float2 Clip : SV_ClipDistance;
+ // };
// Three arbitrary float2 values establish distinct interpolation modes.
// Cull fits after the first and Clip after the third; row 1 separates them.
TestConfig Config(
@@ -2237,6 +2276,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Constant},
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/2,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::LinearCentroid}});
+
+ // Optimized layout:
+ // reg0: A.xy | Cull.zw
+ // reg1: B.xy | unused.zw
+ // reg2: C.xy | Clip.zw
verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0},
{/*Row=*/1, /*Col=*/0},
@@ -2247,6 +2291,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedIndexedClipCullCannotShareSystemValueRows) {
+ // struct VSOut {
+ // float Position : SV_Position;
+ // float Clip[2] : SV_ClipDistance;
+ // };
// Position fixes its row's indexed range to empty. The Clip array must
// start in the next row even though Position leaves three free columns.
TestConfig Config(
@@ -2255,6 +2303,11 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/2, /*Cols=*/1,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+ // Optimized layout:
+ // reg0: Position.x | unused.yzw
+ // reg1: Clip[0].x | unused.yzw
+ // reg2: Clip[1].x | unused.yzw
verifyPacking(PackingMethod::Optimized, Config, /*ExpectedRows=*/3,
{{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}});
}
@@ -2286,6 +2339,12 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedClipCullRespectsRowCompatibility) {
+ // struct VSOut {
+ // half A : A;
+ // float3 Clip : SV_ClipDistance;
+ // };
+ //
+ // The variants enable native 16-bit types and nointerpolation on A.
for (bool Native16Bit : {false, true}) {
SCOPED_TRACE(Native16Bit);
for (bool DifferentInterp : {false, true}) {
@@ -2299,6 +2358,12 @@ TEST_F(HLSLSemanticSignaturePackingTest,
{dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
const bool Separate = Native16Bit || DifferentInterp;
+
+ // Compatible optimized layout:
+ // reg0: A.x | Clip.yzw
+ // Incompatible optimized layout:
+ // reg0: A.x | unused.yzw
+ // reg1: Clip.xyz | unused.w
verifyPacking(PackingMethod::Optimized, Config,
/*ExpectedRows=*/Separate ? 2 : 1,
{{/*Row=*/0, /*Col=*/0},
@@ -2330,6 +2395,13 @@ TEST_F(HLSLSemanticSignaturePackingTest,
TEST_F(HLSLSemanticSignaturePackingTest,
OptimizedClipCullPacksGeometryStreamsIndependently) {
+ // Each of the four geometry output streams has this structure:
+ //
+ // struct StreamN {
+ // float4 Fill[31] : FILL;
+ // float A : A;
+ // float3 Clip : SV_ClipDistance;
+ // };
TestConfig Config(Triple::Geometry, IOType::Out, {});
for (unsigned Stream = 0; Stream != MaxGeometryStreams; ++Stream) {
Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary,
@@ -2346,6 +2418,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
dxbc::PSV::InterpolationMode::Linear,
/*SemanticIndex=*/0, Stream});
}
+
+ // Optimized layout for each stream:
+ // reg0-30: Fill[0-30].xyzw
+ // reg31: A.x | Clip.yzw
SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
Expected<unsigned> Rows = pack(PackingMethod::Optimized, Elements, Config);
ASSERT_THAT_EXPECTED(Rows, Succeeded());
@@ -2366,6 +2442,10 @@ TEST_F(HLSLSemanticSignaturePackingTest,
//===----------------------------------------------------------------------===//
TEST_F(HLSLSemanticSignaturePackingTest, PackingPreservesElementMetadata) {
+ // struct VSOut {
+ // float2 Scalar : SCALAR;
+ // float Array[32] : ARRAY;
+ // };
TestConfig Config(
Triple::Vertex, IOType::Out,
{{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
@@ -2373,6 +2453,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PackingPreservesElementMetadata) {
{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows,
/*Cols=*/1, dxil::ElementType::F32,
dxbc::PSV::InterpolationMode::Linear}});
+
+ // Prefix-stable layout:
+ // reg0: Scalar.xy | Array[0].z | unused.w
+ // reg1-31: unused.xy | Array[1-31].z | unused.w
+ // Optimized layout:
+ // reg0: Array[0].x | Scalar.yz | unused.w
+ // reg1-31: Array[1-31].x | unused.yzw
for (PackingMethod Method :
{PackingMethod::PrefixStable, PackingMethod::Optimized}) {
SCOPED_TRACE(static_cast<unsigned>(Method));
@@ -2399,6 +2486,13 @@ TEST_F(HLSLSemanticSignaturePackingTest, PackingPreservesElementMetadata) {
}
TEST_F(HLSLSemanticSignaturePackingTest, OptimizedUsesSignatureIDToBreakTies) {
+ // struct VSOut {
+ // float A : A;
+ // float B : B;
+ // float C : C;
+ // };
+ //
+ // Assign the elements signature IDs 2, 0, 1, respectively.
TestConfig Config(Triple::Vertex, IOType::Out, {});
for (unsigned I = 0; I != 3; ++I)
Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
@@ -2409,6 +2503,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, OptimizedUsesSignatureIDToBreakTies) {
Elements[1].SigId = 0;
Elements[2].SigId = 1;
+ // Optimized layout:
+ // reg0: B.x | C.y | A.z | unused.w
Expected<unsigned> Rows = pack(PackingMethod::Optimized, Elements, Config);
ASSERT_THAT_EXPECTED(Rows, Succeeded());
EXPECT_EQ(*Rows, 1u);
>From 0de95ffcfe591620ed04022ff2ac7357a84bea64 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 23 Sep 2026 17:33:50 +0000
Subject: [PATCH 17/17] self-review: add missing import and namespace
---
llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index b2b0542226835..85808e0e83f9c 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -12,6 +12,7 @@
#include "llvm/Frontend/HLSL/SemanticSignaturePacking.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Sequence.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/bit.h"
#include <algorithm>
@@ -694,7 +695,7 @@ Expected<unsigned> llvm::hlsl::packSignaturePrefixStable(
? MaxGeometryStreams
: 1;
SmallVector<SignatureRows, 1> Rows(StreamCount);
- return packSignatureInOrder(Elements, seq<unsigned>(0, Elements.size()),
+ return packSignatureInOrder(Elements, llvm::seq<unsigned>(0, Elements.size()),
ShaderStage, IOTy, UseNative16BitTypes, Rows);
}
More information about the llvm-commits
mailing list