[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