[llvm] [HLSLSemanticSignatures] Implement the stacked packing of elements (PR #218060)

Finn Plummer via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 11:53:45 PDT 2026


https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/218060

>From 02d7a21714246a506979f1a2787203407f593808 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 19:22:52 +0000
Subject: [PATCH 01/17] add test harness and snub

---
 .../Frontend/HLSL/SemanticSignaturePacking.h  |  42 ++++++
 llvm/lib/Frontend/HLSL/CMakeLists.txt         |   1 +
 .../HLSL/SemanticSignaturePacking.cpp         |  22 +++
 llvm/unittests/Frontend/CMakeLists.txt        |   1 +
 .../HLSLSemanticSignaturePackingTest.cpp      | 137 ++++++++++++++++++
 5 files changed, 203 insertions(+)
 create mode 100644 llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
 create mode 100644 llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
 create mode 100644 llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
new file mode 100644
index 0000000000000..8f01d07fa0c0e
--- /dev/null
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -0,0 +1,42 @@
+//===- SemanticSignaturePacking.h - HLSL signature packing helpers -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file declares helpers for packing HLSL semantic signatures.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_FRONTEND_HLSL_SEMANTICSIGNATUREPACKING_H
+#define LLVM_FRONTEND_HLSL_SEMANTICSIGNATUREPACKING_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Frontend/HLSL/SemanticSignatures.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Error.h"
+#include "llvm/TargetParser/Triple.h"
+
+namespace llvm::hlsl {
+
+/// Iterates through Elements that belong to the signature described by
+/// ShaderStage and IOTy and packs each element into 32 registers with 4
+/// components by updating its StartRow and StartCol in place. An element is
+/// left unallocated if it is not part of the signature.
+///
+/// Elements are visited in declaration order. Each element 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 same row;
+/// interpolation mode, component type, and semantic kind do not otherwise
+/// affect placement.
+///
+/// Returns an error if all eligible elements cannot be placed.
+LLVM_ABI Error
+packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
+                     Triple::EnvironmentType ShaderStage, IOType IOTy);
+
+} // namespace llvm::hlsl
+
+#endif // LLVM_FRONTEND_HLSL_SEMANTICSIGNATUREPACKING_H
diff --git a/llvm/lib/Frontend/HLSL/CMakeLists.txt b/llvm/lib/Frontend/HLSL/CMakeLists.txt
index b8d1456a787ce..4703458c68168 100644
--- a/llvm/lib/Frontend/HLSL/CMakeLists.txt
+++ b/llvm/lib/Frontend/HLSL/CMakeLists.txt
@@ -5,6 +5,7 @@ add_llvm_component_library(LLVMFrontendHLSL
   HLSLRootSignature.cpp
   RootSignatureMetadata.cpp
   RootSignatureValidations.cpp
+  SemanticSignaturePacking.cpp
   SemanticSignatures.cpp
 
   ADDITIONAL_HEADER_DIRS
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
new file mode 100644
index 0000000000000..17b17a91ac2ea
--- /dev/null
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -0,0 +1,22 @@
+//===- SemanticSignaturePacking.cpp - HLSL signature packing helpers -----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file This file implements helpers for packing HLSL semantic signatures.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/HLSL/SemanticSignaturePacking.h"
+
+using namespace llvm;
+using namespace llvm::hlsl;
+
+Error llvm::hlsl::packSignatureStacked(
+    MutableArrayRef<SemanticSignatureElement>, Triple::EnvironmentType,
+    IOType) {
+  return Error::success();
+}
diff --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt
index b8a26561069d5..2373ae29e0599 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -18,6 +18,7 @@ add_llvm_unittest(LLVMFrontendTests
   HLSLRootSignatureDumpTest.cpp
   HLSLSemanticStagesTest.cpp
   HLSLSemanticSignatureMetadataTest.cpp
+  HLSLSemanticSignaturePackingTest.cpp
   OpenACCTest.cpp
   OpenMPContextTest.cpp
   OpenMPIRBuilderTest.cpp
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
new file mode 100644
index 0000000000000..218f891025524
--- /dev/null
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -0,0 +1,137 @@
+//===- HLSLSemanticSignaturePackingTest.cpp -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Frontend/HLSL/SemanticSignaturePacking.h"
+#include "llvm/TargetParser/Triple.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <algorithm>
+#include <initializer_list>
+
+using namespace llvm;
+using namespace llvm::hlsl;
+
+namespace {
+
+class HLSLSemanticSignaturePackingTest : public testing::Test {
+protected:
+  struct ElementConfig {
+    dxbc::PSV::SemanticKind SemanticKind;
+    uint32_t Rows;
+    uint8_t Cols;
+    dxil::ElementType CompType;
+    dxbc::PSV::InterpolationMode InterpMode;
+  };
+
+  struct ExpectedLocation {
+    uint32_t Row;
+    uint8_t Col;
+  };
+
+  struct TestConfig {
+    Triple::EnvironmentType ShaderStage;
+    IOType IOTy;
+    SmallVector<ElementConfig> Elements;
+
+    TestConfig(Triple::EnvironmentType ShaderStage, IOType IOTy,
+               std::initializer_list<ElementConfig> Elements)
+        : ShaderStage(ShaderStage), IOTy(IOTy), Elements(Elements) {}
+  };
+
+  SmallVector<SemanticSignatureElement>
+  makeSignature(const TestConfig &Config) {
+    SmallVector<SemanticSignatureElement> Elements;
+    for (const ElementConfig &Element : Config.Elements) {
+      SmallVector<uint32_t> SemanticIndices;
+      for (uint32_t Row = 0; Row != Element.Rows; ++Row)
+        SemanticIndices.push_back(Row);
+
+      Elements.emplace_back(
+          /*SigId=*/static_cast<uint32_t>(Elements.size()),
+          /*SemanticName=*/"TEST",
+          /*CompType=*/Element.CompType,
+          /*SemanticKind=*/Element.SemanticKind,
+          /*SemanticIndices=*/SemanticIndices,
+          /*Cols=*/Element.Cols);
+      Elements.back().InterpMode = Element.InterpMode;
+    }
+    return Elements;
+  }
+
+  Error packStacked(SmallVectorImpl<SemanticSignatureElement> &Elements,
+                    const TestConfig &Config) {
+    return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
+  }
+
+  void expectPacking(const TestConfig &Config, unsigned ExpectedRows,
+                     std::initializer_list<ExpectedLocation> Locations) {
+    SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+    ASSERT_EQ(Elements.size(), Locations.size());
+
+    ASSERT_THAT_ERROR(packStacked(Elements, Config), Succeeded());
+
+    unsigned Rows = 0;
+    for (const SemanticSignatureElement &Element : Elements)
+      if (Element.isAllocated())
+        Rows = std::max(Rows, Element.StartRow + Element.Rows);
+    EXPECT_EQ(Rows, ExpectedRows);
+
+    unsigned Index = 0;
+    for (ExpectedLocation Location : Locations) {
+      EXPECT_EQ(Elements[Index].StartRow, Location.Row) << "element " << Index;
+      EXPECT_EQ(Elements[Index].StartCol, Location.Col) << "element " << Index;
+      ++Index;
+    }
+  }
+
+  void expectPackingError(const TestConfig &Config, StringRef Message) {
+    SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+    EXPECT_THAT_ERROR(packStacked(Elements, Config),
+                      FailedWithMessage(Message));
+  }
+};
+
+TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) {
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::Out,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Position, /*Rows=*/2, /*Cols=*/3,
+        dxil::ElementType::F16, dxbc::PSV::InterpolationMode::Constant}});
+
+  EXPECT_EQ(Config.ShaderStage, Triple::EnvironmentType::Vertex);
+  EXPECT_EQ(Config.IOTy, IOType::Out);
+
+  SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
+  ASSERT_EQ(Elements.size(), 2u);
+
+  EXPECT_EQ(Elements[0].SigId, 0u);
+  EXPECT_EQ(Elements[0].SemanticName, "TEST");
+  EXPECT_EQ(Elements[0].CompType, dxil::ElementType::F32);
+  EXPECT_EQ(Elements[0].SemanticKind, dxbc::PSV::SemanticKind::Arbitrary);
+  EXPECT_EQ(Elements[0].SemanticIndices, SmallVector<uint32_t>({0}));
+  EXPECT_EQ(Elements[0].InterpMode, dxbc::PSV::InterpolationMode::Linear);
+  EXPECT_EQ(Elements[0].Rows, 1u);
+  EXPECT_EQ(Elements[0].Cols, 2u);
+  EXPECT_EQ(Elements[0].StartRow, UnallocatedRow);
+  EXPECT_EQ(Elements[0].StartCol, UnallocatedCol);
+  EXPECT_EQ(Elements[0].UsageMask, 0u);
+  EXPECT_EQ(Elements[0].DynIndexMask, 0u);
+  EXPECT_EQ(Elements[0].GSStream, 0u);
+
+  EXPECT_EQ(Elements[1].SigId, 1u);
+  EXPECT_EQ(Elements[1].SemanticKind, dxbc::PSV::SemanticKind::Position);
+  EXPECT_EQ(Elements[1].CompType, dxil::ElementType::F16);
+  EXPECT_EQ(Elements[1].InterpMode, dxbc::PSV::InterpolationMode::Constant);
+  EXPECT_EQ(Elements[1].SemanticIndices, SmallVector<uint32_t>({0, 1}));
+  EXPECT_EQ(Elements[1].Rows, 2u);
+  EXPECT_EQ(Elements[1].Cols, 3u);
+}
+
+} // namespace

>From 012a752a7685e14e217a397f1cd201882ef899f1 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 19:26:01 +0000
Subject: [PATCH 02/17] add error kind

---
 .../Frontend/HLSL/SemanticSignaturePacking.h  | 29 ++++++++++++++++++-
 .../HLSL/SemanticSignaturePacking.cpp         | 11 +++++++
 .../HLSLSemanticSignaturePackingTest.cpp      | 12 ++++++--
 3 files changed, 48 insertions(+), 4 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 8f01d07fa0c0e..311fe6abef742 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -21,6 +21,32 @@
 
 namespace llvm::hlsl {
 
+/// Denotes the element that could not be packed and why.
+class SignaturePackingError : public ErrorInfo<SignaturePackingError> {
+public:
+  enum ErrorKind {
+    SignatureOverflow,
+  };
+
+  LLVM_ABI static char ID;
+
+  SignaturePackingError(ErrorKind Kind, unsigned ElementIndex)
+      : Kind(Kind), ElementIndex(ElementIndex) {}
+
+  ErrorKind getErrorKind() const { return Kind; }
+  unsigned getElementIndex() const { return ElementIndex; }
+
+  LLVM_ABI void log(raw_ostream &OS) const override;
+
+  std::error_code convertToErrorCode() const override {
+    return llvm::inconvertibleErrorCode();
+  }
+
+private:
+  ErrorKind Kind;
+  unsigned ElementIndex;
+};
+
 /// Iterates through Elements that belong to the signature described by
 /// ShaderStage and IOTy and packs each element into 32 registers with 4
 /// components by updating its StartRow and StartCol in place. An element is
@@ -32,7 +58,8 @@ namespace llvm::hlsl {
 /// interpolation mode, component type, and semantic kind do not otherwise
 /// affect placement.
 ///
-/// Returns an error if all eligible elements cannot be placed.
+/// Returns a SignaturePackingError that denotes the first element that cannot
+/// be placed, or success if all eligible elements were placed.
 LLVM_ABI Error
 packSignatureStacked(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 17b17a91ac2ea..75a8f2a0e9700 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -15,6 +15,17 @@
 using namespace llvm;
 using namespace llvm::hlsl;
 
+char SignaturePackingError::ID;
+
+void SignaturePackingError::log(raw_ostream &OS) const {
+  switch (Kind) {
+  case SignatureOverflow:
+    OS << "signature elements do not fit in 32 rows";
+    break;
+  }
+  OS << " (element " << ElementIndex << ")";
+}
+
 Error llvm::hlsl::packSignatureStacked(
     MutableArrayRef<SemanticSignatureElement>, Triple::EnvironmentType,
     IOType) {
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 218f891025524..8c3b0aa231650 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -90,10 +90,16 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     }
   }
 
-  void expectPackingError(const TestConfig &Config, StringRef Message) {
+  void expectPackingError(const TestConfig &Config,
+                          SignaturePackingError::ErrorKind ExpectedKind,
+                          unsigned ExpectedElementIndex) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
-    EXPECT_THAT_ERROR(packStacked(Elements, Config),
-                      FailedWithMessage(Message));
+    Error E = packStacked(Elements, Config);
+    ASSERT_TRUE(E.isA<SignaturePackingError>());
+    handleAllErrors(std::move(E), [&](const SignaturePackingError &PackingErr) {
+      EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind);
+      EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);
+    });
   }
 };
 

>From 4372ac3707e66f6f8593bae8fbf12b82eb65081b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 19:57:20 +0000
Subject: [PATCH 03/17] add test cases

---
 .../HLSLSemanticSignaturePackingTest.cpp      | 199 ++++++++++++++++++
 1 file changed, 199 insertions(+)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 8c3b0aa231650..a1fd495638613 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -34,6 +34,9 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     uint8_t Col;
   };
 
+  static constexpr ExpectedLocation Unallocated = {UnallocatedRow,
+                                                   UnallocatedCol};
+
   struct TestConfig {
     Triple::EnvironmentType ShaderStage;
     IOType IOTy;
@@ -95,6 +98,10 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
                           unsigned ExpectedElementIndex) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
     Error E = packStacked(Elements, Config);
+    if (!E) {
+      ADD_FAILURE() << "expected a SignaturePackingError";
+      return;
+    }
     ASSERT_TRUE(E.isA<SignaturePackingError>());
     handleAllErrors(std::move(E), [&](const SignaturePackingError &PackingErr) {
       EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind);
@@ -140,4 +147,196 @@ TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) {
   EXPECT_EQ(Elements[1].Cols, 3u);
 }
 
+//===----------------------------------------------------------------------===//
+// Valid packing tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) {
+  // Semantics accessed through dedicated intrinsics do not consume signature
+  // rows and remain unallocated.
+
+  // struct CSIn {
+  //   uint3 DispatchThreadID : SV_DispatchThreadID;
+  //   uint3 GroupID          : SV_GroupID;
+  //   uint GroupIndex        : SV_GroupIndex;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Compute, IOType::In,
+      {{dxbc::PSV::SemanticKind::DispatchThreadID, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::GroupID, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined},
+       {dxbc::PSV::SemanticKind::GroupIndex, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}});
+
+  // Expected layout: no registers are used.
+  expectPacking(Config, /*ExpectedRows=*/0,
+                {Unallocated, Unallocated, Unallocated});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) {
+  // Elements are assigned whole rows in declaration order, regardless of their
+  // semantic interpretation.
+
+  // struct VSIn {
+  //   uint VertexID       : SV_VertexID;
+  //   float2 Data         : DATA;
+  //   float3 ClipDistance : SV_ClipDistance;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::In,
+      {{dxbc::PSV::SemanticKind::VertexID, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Constant},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ClipDistance, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: VertexID.x       | unused.yzw
+  // reg1: Data.xy          | unused.zw
+  // reg2: ClipDistance.xyz | unused.w
+  expectPacking(
+      Config, /*ExpectedRows=*/3,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, DoesNotCoPackElements) {
+  // Elements are never co-packed even when they would fit in one row.
+
+  // struct VSIn {
+  //   float A : A;
+  //   float B : B;
+  //   float C : C;
+  //   float D : D;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::In,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        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::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A.x | unused.yzw
+  // reg1: B.x | unused.yzw
+  // reg2: C.x | unused.yzw
+  // reg3: D.x | unused.yzw
+  expectPacking(Config, /*ExpectedRows=*/4,
+                {{/*Row=*/0, /*Col=*/0},
+                 {/*Row=*/1, /*Col=*/0},
+                 {/*Row=*/2, /*Col=*/0},
+                 {/*Row=*/3, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, StacksMultiRowElements) {
+  // A multi-row element occupies consecutive whole rows.
+
+  // struct VSIn {
+  //   float A[3]  : A;
+  //   float3 B[2] : B;
+  //   float4 C    : C;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::In,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/3, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0: A[0].x   | unused.yzw
+  // reg1: A[1].x   | unused.yzw
+  // reg2: A[2].x   | unused.yzw
+  // reg3: B[0].xyz | unused.w
+  // reg4: B[1].xyz | unused.w
+  // reg5: C.xyzw
+  expectPacking(
+      Config, /*ExpectedRows=*/6,
+      {{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/0}, {/*Row=*/5, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, ExactlyFillsSignature) {
+  // An element may occupy all available signature rows.
+
+  // struct VSIn {
+  //   float4 A[32] : A;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Vertex, IOType::In,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/MaxSignatureRows,
+        /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
+        dxbc::PSV::InterpolationMode::Linear}});
+
+  // Expected layout:
+  // reg0-31: A[0-31].xyzw
+  expectPacking(Config, /*ExpectedRows=*/MaxSignatureRows,
+                {{/*Row=*/0, /*Col=*/0}});
+}
+
+//===----------------------------------------------------------------------===//
+// Packing error tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, RejectsSignatureOverflow) {
+  // A signature that requires more than 32 rows cannot be packed.
+
+  // struct VSIn {
+  //   float4 A0  : A0;
+  //   ...
+  //   float4 A32 : A32;
+  // };
+  TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In, {});
+  for (unsigned I = 0; I != MaxSignatureRows + 1; ++I)
+    Config.Elements.push_back({dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1,
+                               /*Cols=*/MaxSignatureCols,
+                               dxil::ElementType::F32,
+                               dxbc::PSV::InterpolationMode::Linear});
+
+  // The last element is the one that no longer fits.
+  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+                     /*ExpectedElementIndex=*/MaxSignatureRows);
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, RejectsSingleElementOverflow) {
+  // A single element may also require more rows than the signature provides.
+
+  // struct VSIn {
+  //   float4 A[33] : A;
+  // };
+  TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In,
+                    {{dxbc::PSV::SemanticKind::Arbitrary,
+                      /*Rows=*/MaxSignatureRows + 1,
+                      /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
+                      dxbc::PSV::InterpolationMode::Linear}});
+
+  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+                     /*ExpectedElementIndex=*/0);
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) {
+  // Each element is valid on its own, but together they require 33 rows.
+
+  // struct VSIn {
+  //   float4 A[31] : A;
+  //   float4 B[2]  : B;
+  // };
+  TestConfig Config(Triple::EnvironmentType::Vertex, IOType::In,
+                    {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/31,
+                      /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
+                      dxbc::PSV::InterpolationMode::Linear},
+                     {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/2,
+                      /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
+                      dxbc::PSV::InterpolationMode::Linear}});
+
+  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+                     /*ExpectedElementIndex=*/1);
+}
+
 } // namespace

>From d5f9b864e8b7c4c0fc67e5d86c7756379bb2ecb8 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:09:34 +0000
Subject: [PATCH 04/17] implement stacked sort

---
 .../Frontend/HLSL/SemanticSignaturePacking.h  |  3 ++
 .../HLSL/SemanticSignaturePacking.cpp         | 34 +++++++++++++++++--
 2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 311fe6abef742..354de1d83dc73 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -21,6 +21,9 @@
 
 namespace llvm::hlsl {
 
+static constexpr unsigned MaxSignatureRows = 32;
+static constexpr unsigned MaxSignatureCols = 4;
+
 /// Denotes the element that could not be packed and why.
 class SignaturePackingError : public ErrorInfo<SignaturePackingError> {
 public:
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 75a8f2a0e9700..59289497dce2b 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -11,6 +11,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Frontend/HLSL/SemanticSignaturePacking.h"
+#include "llvm/ADT/STLExtras.h"
+#include <cassert>
 
 using namespace llvm;
 using namespace llvm::hlsl;
@@ -27,7 +29,35 @@ void SignaturePackingError::log(raw_ostream &OS) const {
 }
 
 Error llvm::hlsl::packSignatureStacked(
-    MutableArrayRef<SemanticSignatureElement>, Triple::EnvironmentType,
-    IOType) {
+    MutableArrayRef<SemanticSignatureElement> Elements,
+    Triple::EnvironmentType ShaderStage, IOType IOTy) {
+  unsigned NextRow = 0;
+  for (const auto &[Index, Element] : enumerate(Elements)) {
+    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");
+
+    SemanticInterpretation Interpretation =
+        getInterpretationKind(Element.SemanticKind, ShaderStage, IOTy);
+    if (Interpretation == SemanticInterpretation::NotAllocated)
+      continue;
+
+    assert((Interpretation == SemanticInterpretation::Arbitrary ||
+            Interpretation == SemanticInterpretation::SV ||
+            Interpretation == SemanticInterpretation::SGV) &&
+           "unexpected semantic interpretation for stacked packing");
+
+    if (Element.Rows > MaxSignatureRows - NextRow)
+      return make_error<SignaturePackingError>(
+          SignaturePackingError::SignatureOverflow,
+          static_cast<unsigned>(Index));
+
+    Element.StartRow = NextRow;
+    Element.StartCol = 0;
+    NextRow += Element.Rows;
+  }
+
   return Error::success();
 }

>From 11257007c625c4f6d8076638623601d19830ba29 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:20:15 +0000
Subject: [PATCH 05/17] document stacked signature packing

---
 llvm/docs/DirectX/SemanticSignatures.md       | 48 +++++++++++++++++++
 .../Frontend/HLSL/SemanticSignaturePacking.h  | 14 +-----
 2 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 6860d9519221a..985a6d596a9e5 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -125,3 +125,51 @@ The following container fields are derived from the operands above:
 
 A metadata node of one or more semantic indices. Its length must equal the
 `Rows` field of the containing signature element.
+
+## Signature Packing
+
+Before a semantic signature is serialized, each element that participates in
+packing is assigned a location in a fixed register space of 32 rows and 4
+columns. An element occupies a rectangle of `Rows` consecutive registers and
+`Cols` consecutive components. Its allocated location is recorded in
+`StartRow` and `StartCol`.
+
+The packing helper classifies each element from its semantic kind, shader stage,
+and I/O type. Elements with the `NotAllocated` interpretation are accessed by
+other means and retain the unallocated row and column sentinels. The remaining
+interpretations accepted by a packing algorithm are assigned locations
+according to that algorithm's rules. If an eligible element cannot be placed,
+packing returns a `SignaturePackingError` identifying the element that failed.
+
+The packing APIs and their in-memory element representation are declared in
+[SemanticSignaturePacking.h].
+
+[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
+```
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 354de1d83dc73..14cd477994d68 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -50,19 +50,9 @@ class SignaturePackingError : public ErrorInfo<SignaturePackingError> {
   unsigned ElementIndex;
 };
 
-/// Iterates through Elements that belong to the signature described by
-/// ShaderStage and IOTy and packs each element into 32 registers with 4
-/// components by updating its StartRow and StartCol in place. An element is
-/// left unallocated if it is not part of the signature.
+/// Packs eligible signature elements into consecutive rows.
 ///
-/// Elements are visited in declaration order. Each element 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 same row;
-/// interpolation mode, component type, and semantic kind do not otherwise
-/// affect placement.
-///
-/// Returns a SignaturePackingError that denotes the first element that cannot
-/// be placed, or success if all eligible elements were placed.
+/// See llvm/docs/DirectX/SemanticSignatures.md#stacked-packing for details.
 LLVM_ABI Error
 packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
                      Triple::EnvironmentType ShaderStage, IOType IOTy);

>From f037a272e6fc41fb66b06be210246194cb75e785 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 18:48:42 +0000
Subject: [PATCH 06/17] review: rename expected to verify

---
 .../HLSLSemanticSignaturePackingTest.cpp      | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index a1fd495638613..57908a3a8dcc0 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -72,7 +72,7 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
   }
 
-  void expectPacking(const TestConfig &Config, unsigned ExpectedRows,
+  void verifyPacking(const TestConfig &Config, unsigned ExpectedRows,
                      std::initializer_list<ExpectedLocation> Locations) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
     ASSERT_EQ(Elements.size(), Locations.size());
@@ -93,7 +93,7 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     }
   }
 
-  void expectPackingError(const TestConfig &Config,
+  void verifyPackingError(const TestConfig &Config,
                           SignaturePackingError::ErrorKind ExpectedKind,
                           unsigned ExpectedElementIndex) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
@@ -170,7 +170,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) {
         dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}});
 
   // Expected layout: no registers are used.
-  expectPacking(Config, /*ExpectedRows=*/0,
+  verifyPacking(Config, /*ExpectedRows=*/0,
                 {Unallocated, Unallocated, Unallocated});
 }
 
@@ -196,7 +196,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) {
   // reg0: VertexID.x       | unused.yzw
   // reg1: Data.xy          | unused.zw
   // reg2: ClipDistance.xyz | unused.w
-  expectPacking(
+  verifyPacking(
       Config, /*ExpectedRows=*/3,
       {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
 }
@@ -226,7 +226,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, DoesNotCoPackElements) {
   // reg1: B.x | unused.yzw
   // reg2: C.x | unused.yzw
   // reg3: D.x | unused.yzw
-  expectPacking(Config, /*ExpectedRows=*/4,
+  verifyPacking(Config, /*ExpectedRows=*/4,
                 {{/*Row=*/0, /*Col=*/0},
                  {/*Row=*/1, /*Col=*/0},
                  {/*Row=*/2, /*Col=*/0},
@@ -257,7 +257,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, StacksMultiRowElements) {
   // reg3: B[0].xyz | unused.w
   // reg4: B[1].xyz | unused.w
   // reg5: C.xyzw
-  expectPacking(
+  verifyPacking(
       Config, /*ExpectedRows=*/6,
       {{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/0}, {/*Row=*/5, /*Col=*/0}});
 }
@@ -276,7 +276,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, ExactlyFillsSignature) {
 
   // Expected layout:
   // reg0-31: A[0-31].xyzw
-  expectPacking(Config, /*ExpectedRows=*/MaxSignatureRows,
+  verifyPacking(Config, /*ExpectedRows=*/MaxSignatureRows,
                 {{/*Row=*/0, /*Col=*/0}});
 }
 
@@ -300,7 +300,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSignatureOverflow) {
                                dxbc::PSV::InterpolationMode::Linear});
 
   // The last element is the one that no longer fits.
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  verifyPackingError(Config, SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/MaxSignatureRows);
 }
 
@@ -316,7 +316,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSingleElementOverflow) {
                       /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
                       dxbc::PSV::InterpolationMode::Linear}});
 
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  verifyPackingError(Config, SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/0);
 }
 
@@ -335,7 +335,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) {
                       /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
                       dxbc::PSV::InterpolationMode::Linear}});
 
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  verifyPackingError(Config, SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/1);
 }
 

>From f9195f475e3372b53aef32617ba74095d8a29c23 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 18:50:28 +0000
Subject: [PATCH 07/17] review: update doc reference

---
 llvm/docs/DirectX/SemanticSignatures.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 985a6d596a9e5..a30199f8ccf45 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -141,8 +141,9 @@ interpretations accepted by a packing algorithm are assigned locations
 according to that algorithm's rules. If an eligible element cannot be placed,
 packing returns a `SignaturePackingError` identifying the element that failed.
 
-The packing APIs and their in-memory element representation are declared in
-[SemanticSignaturePacking.h].
+The packing APIs are declared in [SemanticSignaturePacking.h], and the
+in-memory element representation they operate on is declared in
+[SemanticSignatures.h].
 
 [SemanticSignaturePacking.h]: https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
 

>From 1333db46be701be2bb24ecd2be1af5a45d11bc4b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 18:56:12 +0000
Subject: [PATCH 08/17] review: add assert of non-vertex shader input
 invocation

---
 llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 59289497dce2b..d4ddaf19fbdfb 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -31,6 +31,9 @@ void SignaturePackingError::log(raw_ostream &OS) const {
 Error llvm::hlsl::packSignatureStacked(
     MutableArrayRef<SemanticSignatureElement> Elements,
     Triple::EnvironmentType ShaderStage, IOType IOTy) {
+  assert(ShaderStage == Triple::Vertex && IOTy == IOType::In &&
+         "stacked packing is only valid for a vertex shader input signature");
+
   unsigned NextRow = 0;
   for (const auto &[Index, Element] : enumerate(Elements)) {
     assert(Element.StartRow == UnallocatedRow &&

>From 89c618d4c046ea15ba83a151cefe3eea3cd7da1e Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 19:03:08 +0000
Subject: [PATCH 09/17] review: update not allocated test to show the row is
 not used

adds the ViewID semantic table for the purpose of testing it validly
---
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 16 ++++++++++
 .../HLSLSemanticSignaturePackingTest.cpp      | 31 ++++++++++---------
 2 files changed, 33 insertions(+), 14 deletions(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 7c4480075d6af..fc13cb76f821b 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -85,6 +85,22 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
     };
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::ViewID: {
+    static constexpr IOType InOrPatchConstant =
+        static_cast<IOType>(IOType::In | IOType::PatchConstantOrPrimitive);
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Vertex, IOType::In, SemanticInterpretation::NotAllocated},
+        {Triple::Hull, InOrPatchConstant, SemanticInterpretation::NotAllocated},
+        {Triple::Domain, InOrPatchConstant,
+         SemanticInterpretation::NotAllocated},
+        {Triple::Geometry, IOType::In, SemanticInterpretation::NotAllocated},
+        {Triple::Pixel, IOType::In, SemanticInterpretation::NotAllocated},
+        {Triple::Mesh, InOrPatchConstant, SemanticInterpretation::NotAllocated},
+        {Triple::Amplification, IOType::In,
+         SemanticInterpretation::NotAllocated},
+    };
+    return Stages;
+  }
   case dxbc::PSV::SemanticKind::Target: {
     static constexpr SemanticStageInfo Stages[] = {
         {Triple::Pixel, IOType::Out, SemanticInterpretation::Target}};
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 57908a3a8dcc0..eab2f04ff4c98 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -153,25 +153,28 @@ TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) {
 
 TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) {
   // Semantics accessed through dedicated intrinsics do not consume signature
-  // rows and remain unallocated.
+  // rows and remain unallocated. The elements around them are packed as if the
+  // unallocated element was not declared at all.
 
-  // struct CSIn {
-  //   uint3 DispatchThreadID : SV_DispatchThreadID;
-  //   uint3 GroupID          : SV_GroupID;
-  //   uint GroupIndex        : SV_GroupIndex;
+  // struct VSIn {
+  //   float2 A    : A;
+  //   uint ViewID : SV_ViewID;
+  //   float3 B    : B;
   // };
   TestConfig Config(
-      Triple::EnvironmentType::Compute, IOType::In,
-      {{dxbc::PSV::SemanticKind::DispatchThreadID, /*Rows=*/1, /*Cols=*/3,
-        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined},
-       {dxbc::PSV::SemanticKind::GroupID, /*Rows=*/1, /*Cols=*/3,
+      Triple::EnvironmentType::Vertex, IOType::In,
+      {{dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear},
+       {dxbc::PSV::SemanticKind::ViewID, /*Rows=*/1, /*Cols=*/1,
         dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined},
-       {dxbc::PSV::SemanticKind::GroupIndex, /*Rows=*/1, /*Cols=*/1,
-        dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}});
+       {dxbc::PSV::SemanticKind::Arbitrary, /*Rows=*/1, /*Cols=*/3,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Linear}});
 
-  // Expected layout: no registers are used.
-  verifyPacking(Config, /*ExpectedRows=*/0,
-                {Unallocated, Unallocated, Unallocated});
+  // Expected layout:
+  // reg0: A.xy  | unused.zw
+  // reg1: B.xyz | unused.w
+  verifyPacking(Config, /*ExpectedRows=*/2,
+                {{/*Row=*/0, /*Col=*/0}, Unallocated, {/*Row=*/1, /*Col=*/0}});
 }
 
 TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) {

>From 19ae1085f721804db6fefa91c0659d486cd9fcef Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 19:55:53 +0000
Subject: [PATCH 10/17] review: remove assert of error type

---
 .../Frontend/HLSLSemanticSignaturePackingTest.cpp | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index eab2f04ff4c98..a72f2f6e5826b 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -102,11 +102,16 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
       ADD_FAILURE() << "expected a SignaturePackingError";
       return;
     }
-    ASSERT_TRUE(E.isA<SignaturePackingError>());
-    handleAllErrors(std::move(E), [&](const SignaturePackingError &PackingErr) {
-      EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind);
-      EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);
-    });
+    handleAllErrors(
+        std::move(E),
+        [&](const SignaturePackingError &PackingErr) {
+          EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind);
+          EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);
+        },
+        [](const ErrorInfoBase &Other) {
+          ADD_FAILURE() << "expected a SignaturePackingError, got: "
+                        << Other.message();
+        });
   }
 };
 

>From 0731dd3aa0e27a700675746982daba0e108c54b1 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 19:57:24 +0000
Subject: [PATCH 11/17] review: mark class with llvm_abi

---
 llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 14cd477994d68..828195031ce6f 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -25,13 +25,14 @@ static constexpr unsigned MaxSignatureRows = 32;
 static constexpr unsigned MaxSignatureCols = 4;
 
 /// Denotes the element that could not be packed and why.
-class SignaturePackingError : public ErrorInfo<SignaturePackingError> {
+class LLVM_ABI SignaturePackingError
+    : public ErrorInfo<SignaturePackingError> {
 public:
   enum ErrorKind {
     SignatureOverflow,
   };
 
-  LLVM_ABI static char ID;
+  static char ID;
 
   SignaturePackingError(ErrorKind Kind, unsigned ElementIndex)
       : Kind(Kind), ElementIndex(ElementIndex) {}
@@ -39,7 +40,7 @@ class SignaturePackingError : public ErrorInfo<SignaturePackingError> {
   ErrorKind getErrorKind() const { return Kind; }
   unsigned getElementIndex() const { return ElementIndex; }
 
-  LLVM_ABI void log(raw_ostream &OS) const override;
+  void log(raw_ostream &OS) const override;
 
   std::error_code convertToErrorCode() const override {
     return llvm::inconvertibleErrorCode();

>From f301fb9a11837252870ee34246e2aa0b30c0761e Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 19:58:16 +0000
Subject: [PATCH 12/17] review: remove magic number

---
 llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index d4ddaf19fbdfb..beb05f2c436c3 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -22,7 +22,7 @@ char SignaturePackingError::ID;
 void SignaturePackingError::log(raw_ostream &OS) const {
   switch (Kind) {
   case SignatureOverflow:
-    OS << "signature elements do not fit in 32 rows";
+    OS << "signature elements do not fit in " << MaxSignatureRows << " rows";
     break;
   }
   OS << " (element " << ElementIndex << ")";

>From 2f98b3ef30d33882316d1c2685f00adc8dc52184 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 19:58:40 +0000
Subject: [PATCH 13/17] review: remove misleading const

---
 llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index beb05f2c436c3..739d4b46e6988 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -35,7 +35,7 @@ Error llvm::hlsl::packSignatureStacked(
          "stacked packing is only valid for a vertex shader input signature");
 
   unsigned NextRow = 0;
-  for (const auto &[Index, Element] : enumerate(Elements)) {
+  for (auto &&[Index, Element] : enumerate(Elements)) {
     assert(Element.StartRow == UnallocatedRow &&
            Element.StartCol == UnallocatedCol && "already allocated?");
     assert(Element.Rows > 0 && "signature element must have at least one row");

>From 538ce4ada60086f347d1d5f098969f03603830b7 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 20:03:48 +0000
Subject: [PATCH 14/17] review: add failure comment

partially allocated is good so that diagnostics can provide richer info
when wanted
---
 llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 828195031ce6f..9143d01d31094 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -54,6 +54,11 @@ class LLVM_ABI SignaturePackingError
 /// Packs eligible signature elements into consecutive rows.
 ///
 /// See llvm/docs/DirectX/SemanticSignatures.md#stacked-packing for details.
+///
+/// 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 Error
 packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
                      Triple::EnvironmentType ShaderStage, IOType IOTy);

>From 2c35388619dbde079a3bfda52493cb12974a9bfc Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 20:06:42 +0000
Subject: [PATCH 15/17] review: return number of rows packed into

---
 .../Frontend/HLSL/SemanticSignaturePacking.h  |  2 +-
 .../HLSL/SemanticSignaturePacking.cpp         |  4 ++--
 .../HLSLSemanticSignaturePackingTest.cpp      | 21 ++++++++-----------
 3 files changed, 12 insertions(+), 15 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 9143d01d31094..522b8b76a5592 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -59,7 +59,7 @@ class LLVM_ABI SignaturePackingError
 /// 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 Error
+LLVM_ABI Expected<unsigned>
 packSignatureStacked(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 739d4b46e6988..786170fc71f66 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -28,7 +28,7 @@ void SignaturePackingError::log(raw_ostream &OS) const {
   OS << " (element " << ElementIndex << ")";
 }
 
-Error llvm::hlsl::packSignatureStacked(
+Expected<unsigned> llvm::hlsl::packSignatureStacked(
     MutableArrayRef<SemanticSignatureElement> Elements,
     Triple::EnvironmentType ShaderStage, IOType IOTy) {
   assert(ShaderStage == Triple::Vertex && IOTy == IOType::In &&
@@ -62,5 +62,5 @@ Error llvm::hlsl::packSignatureStacked(
     NextRow += Element.Rows;
   }
 
-  return Error::success();
+  return NextRow;
 }
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index a72f2f6e5826b..5ee3223313b50 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -67,8 +67,9 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     return Elements;
   }
 
-  Error packStacked(SmallVectorImpl<SemanticSignatureElement> &Elements,
-                    const TestConfig &Config) {
+  Expected<unsigned>
+  packStacked(SmallVectorImpl<SemanticSignatureElement> &Elements,
+              const TestConfig &Config) {
     return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
   }
 
@@ -77,13 +78,9 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
     ASSERT_EQ(Elements.size(), Locations.size());
 
-    ASSERT_THAT_ERROR(packStacked(Elements, Config), Succeeded());
-
-    unsigned Rows = 0;
-    for (const SemanticSignatureElement &Element : Elements)
-      if (Element.isAllocated())
-        Rows = std::max(Rows, Element.StartRow + Element.Rows);
-    EXPECT_EQ(Rows, ExpectedRows);
+    Expected<unsigned> Rows = packStacked(Elements, Config);
+    ASSERT_THAT_EXPECTED(Rows, Succeeded());
+    EXPECT_EQ(*Rows, ExpectedRows);
 
     unsigned Index = 0;
     for (ExpectedLocation Location : Locations) {
@@ -97,13 +94,13 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
                           SignaturePackingError::ErrorKind ExpectedKind,
                           unsigned ExpectedElementIndex) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
-    Error E = packStacked(Elements, Config);
-    if (!E) {
+    Expected<unsigned> Rows = packStacked(Elements, Config);
+    if (Rows) {
       ADD_FAILURE() << "expected a SignaturePackingError";
       return;
     }
     handleAllErrors(
-        std::move(E),
+        Rows.takeError(),
         [&](const SignaturePackingError &PackingErr) {
           EXPECT_EQ(PackingErr.getErrorKind(), ExpectedKind);
           EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);

>From 5c084c38c5079df28eb6bf4040926bbd6401cdd1 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 9 Sep 2026 20:09:33 +0000
Subject: [PATCH 16/17] review: clarify that the assert is sufficient handling

---
 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 786170fc71f66..278c6a4f06d7d 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -50,7 +50,8 @@ Expected<unsigned> llvm::hlsl::packSignatureStacked(
     assert((Interpretation == SemanticInterpretation::Arbitrary ||
             Interpretation == SemanticInterpretation::SV ||
             Interpretation == SemanticInterpretation::SGV) &&
-           "unexpected semantic interpretation for stacked packing");
+           "unexpected semantic interpretation for stacked packing, should "
+           "have been diagnosed by Sema");
 
     if (Element.Rows > MaxSignatureRows - NextRow)
       return make_error<SignaturePackingError>(

>From cd7e3beaea8c69fb536a34b903e3980ed4b8b084 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Thu, 10 Sep 2026 18:50:31 +0000
Subject: [PATCH 17/17] self-review: remove static_cast

---
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index fc13cb76f821b..0d17004b6a8c0 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -87,7 +87,7 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   }
   case dxbc::PSV::SemanticKind::ViewID: {
     static constexpr IOType InOrPatchConstant =
-        static_cast<IOType>(IOType::In | IOType::PatchConstantOrPrimitive);
+        IOType::In | IOType::PatchConstantOrPrimitive;
     static constexpr SemanticStageInfo Stages[] = {
         {Triple::Vertex, IOType::In, SemanticInterpretation::NotAllocated},
         {Triple::Hull, InOrPatchConstant, SemanticInterpretation::NotAllocated},



More information about the llvm-commits mailing list