[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