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

Finn Plummer via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Aug 21 15:59:40 PDT 2026


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

This pr defines a testing harness for the packing algorithms of semantic signatures and then implements the stacked packing algorithm.

Resolves: https://github.com/llvm/llvm-project/issues/205875

>From 01bba9a0ea1389d68409ec914ef79d2cc898567a Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 19:00:44 +0000
Subject: [PATCH 1/6] review: small typo

---
 llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 8e355a05cb86c..3f2d2946d44eb 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -41,6 +41,8 @@ enum IOType {
   InOut = 0b011,
   PatchConstantOrPrimitive = 0b100,
   All = 0b111,
+
+  LLVM_MARK_AS_BITMASK_ENUM(PatchConstantOrPrimitive),
 };
 
 enum class SemanticInterpretation {

>From 37c341bf967db45e877006c36ccb051ea6d7aa20 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 2/6] 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 8976dd1b2f737..69478ac630fd8 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -17,6 +17,7 @@ add_llvm_unittest(LLVMFrontendTests
   HLSLBindingTest.cpp
   HLSLRootSignatureDumpTest.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 c28549f970d15443aaf7943b6f0c92d4f6ef0cbf 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 3/6] 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 26f935fad6be1d10bcc8c9b0dd1f4a1c0e9195d6 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 4/6] 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 03b65b6c2aed57577f7d322c4c0dcbbb6587758b 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 5/6] 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 baa1ccd76efa972019231b2b8955a448e52987c4 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 6/6] 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);



More information about the llvm-branch-commits mailing list