[llvm] [HLSL] Add in-memory representation of Semantic Signatures (PR #209907)

Finn Plummer via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 22 10:59:31 PDT 2026


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

>From 0a531171aae974ad3df359970f4cea2d6efb08ac Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 21:15:16 +0000
Subject: [PATCH 01/12] add in-memory representation

---
 .../llvm/Frontend/HLSL/SemanticSignatures.h   | 89 +++++++++++++++++++
 1 file changed, 89 insertions(+)
 create mode 100644 llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
new file mode 100644
index 0000000000000..16461a8d2faf0
--- /dev/null
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -0,0 +1,89 @@
+//===- SemanticSignatures.h - HLSL Semantic Signature helper objects ------===//
+//
+// 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 contains structure definitions of HLSL Semantic Signature
+/// objects.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_FRONTEND_HLSL_SEMANTICSIGNATURES_H
+#define LLVM_FRONTEND_HLSL_SEMANTICSIGNATURES_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/BinaryFormat/DXContainer.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/DXILABI.h"
+#include <cstdint>
+#include <string>
+
+namespace llvm {
+namespace hlsl {
+
+// Definitions of the in-memory data layout structures
+
+// Sentinel values denoting that an element is unallocated
+static constexpr uint32_t UnallocatedRow = ~0U;
+static constexpr uint8_t UnallocatedCol = 0xFF;
+
+// Models a single packed range of signature rows with its semantic name and
+// indices, register placement, component masks, and stage-specific attributes.
+struct SemanticSignatureElement {
+  uint32_t SigId;
+  StringRef SemanticName;
+  dxil::ElementType CompType = dxil::ElementType::Invalid;
+  dxbc::PSV::SemanticKind SemanticKind = dxbc::PSV::SemanticKind::Arbitrary;
+  SmallVector<uint32_t> SemanticIndices;
+  dxbc::PSV::InterpolationMode InterpMode =
+      dxbc::PSV::InterpolationMode::Undefined;
+  uint32_t Rows = 1;
+  uint8_t Cols = 1;
+  uint32_t StartRow = UnallocatedRow;
+  uint8_t StartCol = UnallocatedCol;
+  uint8_t UsageMask = 0;
+  uint8_t DynIndexMask = 0;
+  uint32_t GSStream = 0;
+
+  bool isAllocated() const {
+    return StartRow != UnallocatedRow && StartCol != UnallocatedCol;
+  }
+
+  uint8_t getDeclaredMask() const {
+    if (!isAllocated())
+      return 0;
+    return static_cast<uint8_t>(((1U << Cols) - 1U) << StartCol);
+  }
+
+  uint8_t getAlwaysReadsMask() const { return UsageMask; }
+
+  uint8_t getNeverWritesMask() const {
+    return static_cast<uint8_t>(~UsageMask & getDeclaredMask());
+  }
+
+  dxbc::SigMinPrecision getMinPrecision(bool UseMinPrecision) const {
+    if (!UseMinPrecision)
+      return dxbc::SigMinPrecision::Default;
+    switch (CompType) {
+    case dxil::ElementType::F16:
+      return dxbc::SigMinPrecision::Float16;
+    case dxil::ElementType::I16:
+    case dxil::ElementType::SNormF16:
+    case dxil::ElementType::UNormF16:
+      return dxbc::SigMinPrecision::SInt16;
+    case dxil::ElementType::U16:
+      return dxbc::SigMinPrecision::UInt16;
+    default:
+      return dxbc::SigMinPrecision::Default;
+    }
+  }
+};
+
+} // namespace hlsl
+} // namespace llvm
+
+#endif // LLVM_FRONTEND_HLSL_SEMANTICSIGNATURES_H

>From 8bcbde9a68b9cabbdbe04f0aa5cff700cb321231 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:22:40 +0000
Subject: [PATCH 02/12] add a harness for testing metadata

---
 .../llvm/Frontend/HLSL/SemanticSignatures.h   | 12 ++++
 llvm/lib/Frontend/HLSL/CMakeLists.txt         |  1 +
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 27 +++++++++
 llvm/unittests/Frontend/CMakeLists.txt        |  1 +
 .../HLSLSemanticSignatureMetadataTest.cpp     | 57 +++++++++++++++++++
 5 files changed, 98 insertions(+)
 create mode 100644 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
 create mode 100644 llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 16461a8d2faf0..7d5efc663e02a 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -19,10 +19,15 @@
 #include "llvm/BinaryFormat/DXContainer.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/DXILABI.h"
+#include "llvm/Support/Error.h"
 #include <cstdint>
 #include <string>
 
 namespace llvm {
+
+class LLVMContext;
+class MDNode;
+
 namespace hlsl {
 
 // Definitions of the in-memory data layout structures
@@ -81,6 +86,13 @@ struct SemanticSignatureElement {
       return dxbc::SigMinPrecision::Default;
     }
   }
+
+  // Parse a signature element from its metadata representation
+  LLVM_ABI static Expected<SemanticSignatureElement>
+  fromMetadata(const MDNode *Node);
+
+  // Build the metadata representation of this signature element
+  LLVM_ABI MDNode *toMetadata(LLVMContext &Ctx) const;
 };
 
 } // namespace hlsl
diff --git a/llvm/lib/Frontend/HLSL/CMakeLists.txt b/llvm/lib/Frontend/HLSL/CMakeLists.txt
index 3d225770e8d5b..b8d1456a787ce 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
+  SemanticSignatures.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${LLVM_MAIN_INCLUDE_DIR}/llvm/Frontend
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
new file mode 100644
index 0000000000000..f5d277d91a889
--- /dev/null
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -0,0 +1,27 @@
+//===- SemanticSignatures.cpp - HLSL Semantic Signature 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 a library for working with HLSL shader input and
+/// output semantic signatures and their DirectX metadata representation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Frontend/HLSL/SemanticSignatures.h"
+#include "llvm/IR/Metadata.h"
+
+using namespace llvm;
+using namespace llvm::hlsl;
+
+Expected<SemanticSignatureElement>
+SemanticSignatureElement::fromMetadata(const MDNode *Node) {
+  return SemanticSignatureElement{};
+}
+
+MDNode *SemanticSignatureElement::toMetadata(LLVMContext &Ctx) const {
+  return MDNode::get(Ctx, {});
+}
diff --git a/llvm/unittests/Frontend/CMakeLists.txt b/llvm/unittests/Frontend/CMakeLists.txt
index 1ce34e77cb348..ff3b382a67fe9 100644
--- a/llvm/unittests/Frontend/CMakeLists.txt
+++ b/llvm/unittests/Frontend/CMakeLists.txt
@@ -15,6 +15,7 @@ set(LLVM_LINK_COMPONENTS
 add_llvm_unittest(LLVMFrontendTests
   HLSLBindingTest.cpp
   HLSLRootSignatureDumpTest.cpp
+  HLSLSemanticSignatureMetadataTest.cpp
   OpenACCTest.cpp
   OpenMPContextTest.cpp
   OpenMPIRBuilderTest.cpp
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
new file mode 100644
index 0000000000000..6784a735840fe
--- /dev/null
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -0,0 +1,57 @@
+//===- HLSLSemanticSignatureMetadataTest.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/Frontend/HLSL/SemanticSignatures.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Type.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::hlsl;
+
+namespace {
+
+class HLSLSemanticSignatureMetadataTest : public testing::Test {
+protected:
+  LLVMContext Ctx;
+
+  Metadata *getI32(uint32_t Val) {
+    return ConstantAsMetadata::get(
+        ConstantInt::get(Type::getInt32Ty(Ctx), Val));
+  }
+
+  Metadata *getI8(uint8_t Val) {
+    return ConstantAsMetadata::get(
+        ConstantInt::get(Type::getInt8Ty(Ctx), Val));
+  }
+
+  Metadata *getStr(StringRef Val) { return MDString::get(Ctx, Val); }
+
+  MDNode *getIndices(ArrayRef<uint32_t> Indices) {
+    SmallVector<Metadata *> Ops;
+    for (uint32_t I : Indices)
+      Ops.push_back(getI32(I));
+    return MDNode::get(Ctx, Ops);
+  }
+};
+
+TEST_F(HLSLSemanticSignatureMetadataTest, StructHelpers) {
+  SemanticSignatureElement Elem;
+  EXPECT_FALSE(Elem.isAllocated());
+
+  Elem.Cols = 4;
+  Elem.StartRow = 0;
+  Elem.StartCol = 0;
+  EXPECT_TRUE(Elem.isAllocated());
+  EXPECT_EQ(Elem.getDeclaredMask(), 0xF);
+}
+
+} // namespace

>From b6acceaaab4d47f9ba2ae1e5323cb9cd1580edbd Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:36:41 +0000
Subject: [PATCH 03/12] add testing of metadata to struct

---
 .../HLSLSemanticSignatureMetadataTest.cpp     | 184 ++++++++++++++++++
 1 file changed, 184 insertions(+)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
index 6784a735840fe..817f67e1c4753 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -11,6 +11,7 @@
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Type.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
@@ -41,8 +42,25 @@ class HLSLSemanticSignatureMetadataTest : public testing::Test {
       Ops.push_back(getI32(I));
     return MDNode::get(Ctx, Ops);
   }
+
+  // Assemble a raw signature element node from the example in the spec
+  MDNode *getElement(uint32_t SigId, StringRef Name, uint32_t CompType,
+                     uint32_t SemanticKind, ArrayRef<uint32_t> Indices,
+                     uint32_t InterpMode, uint32_t Rows, uint8_t Cols,
+                     uint32_t StartRow, uint8_t StartCol, uint8_t UsageMask,
+                     uint8_t DynIndexMask, uint32_t GSStream) {
+    return MDNode::get(
+        Ctx, {getI32(SigId), getStr(Name), getI32(CompType),
+              getI32(SemanticKind), getIndices(Indices), getI32(InterpMode),
+              getI32(Rows), getI8(Cols), getI32(StartRow), getI8(StartCol),
+              getI8(UsageMask), getI8(DynIndexMask), getI32(GSStream)});
+  }
 };
 
+//===----------------------------------------------------------------------===//
+// Success cases
+//===----------------------------------------------------------------------===//
+
 TEST_F(HLSLSemanticSignatureMetadataTest, StructHelpers) {
   SemanticSignatureElement Elem;
   EXPECT_FALSE(Elem.isAllocated());
@@ -54,4 +72,170 @@ TEST_F(HLSLSemanticSignatureMetadataTest, StructHelpers) {
   EXPECT_EQ(Elem.getDeclaredMask(), 0xF);
 }
 
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElement) {
+  MDNode *Node = getElement(/*SigId=*/1, "TEXCOORD", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0, 1},
+                            /*InterpMode=*/0, /*Rows=*/2, /*Cols=*/4,
+                            /*StartRow=*/1, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+
+  Expected<SemanticSignatureElement> Elem =
+      SemanticSignatureElement::fromMetadata(Node);
+  ASSERT_THAT_EXPECTED(Elem, Succeeded());
+
+  EXPECT_EQ(Elem->SigId, 1u);
+  EXPECT_EQ(Elem->SemanticName, "TEXCOORD");
+  EXPECT_EQ(Elem->CompType, dxil::ElementType::F32);
+  EXPECT_EQ(Elem->SemanticKind, dxbc::PSV::SemanticKind::Arbitrary);
+  EXPECT_THAT(Elem->SemanticIndices, testing::ElementsAre(0u, 1u));
+  EXPECT_EQ(Elem->InterpMode, dxbc::PSV::InterpolationMode::Undefined);
+  EXPECT_EQ(Elem->Rows, 2u);
+  EXPECT_EQ(Elem->Cols, 4u);
+  EXPECT_EQ(Elem->StartRow, 1u);
+  EXPECT_EQ(Elem->StartCol, 0u);
+  EXPECT_EQ(Elem->UsageMask, 0u);
+  EXPECT_EQ(Elem->DynIndexMask, 0u);
+  EXPECT_EQ(Elem->GSStream, 0u);
+}
+
+// SV_Target output with a non-zero usage/dynamic-index mask and semantic index
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementSystemValue) {
+  MDNode *Node = getElement(/*SigId=*/1, "SV_Target", /*CompType=*/9,
+                            /*SemanticKind=*/16, /*Indices=*/{1},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/4,
+                            /*StartRow=*/1, /*StartCol=*/0, /*UsageMask=*/0x7,
+                            /*DynIndexMask=*/0x1, /*GSStream=*/0);
+
+  Expected<SemanticSignatureElement> Elem =
+      SemanticSignatureElement::fromMetadata(Node);
+  ASSERT_THAT_EXPECTED(Elem, Succeeded());
+
+  EXPECT_EQ(Elem->SemanticName, "SV_Target");
+  EXPECT_EQ(Elem->SemanticKind, dxbc::PSV::SemanticKind::Target);
+  EXPECT_THAT(Elem->SemanticIndices, testing::ElementsAre(1u));
+  EXPECT_EQ(Elem->UsageMask, 0x7u);
+  EXPECT_EQ(Elem->DynIndexMask, 0x1u);
+}
+
+// An unallocated element uses the row/col sentinels
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementUnallocated) {
+  MDNode *Node = getElement(/*SigId=*/0, "POSITION", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/4,
+                            /*StartRow=*/UnallocatedRow, /*StartCol=*/UnallocatedCol,
+                            /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
+
+  Expected<SemanticSignatureElement> Elem =
+      SemanticSignatureElement::fromMetadata(Node);
+  ASSERT_THAT_EXPECTED(Elem, Succeeded());
+
+  EXPECT_EQ(Elem->StartRow, UnallocatedRow);
+  EXPECT_EQ(Elem->StartCol, UnallocatedCol);
+  EXPECT_FALSE(Elem->isAllocated());
+}
+
+// Every component type value maps onto the matching dxil::ElementType
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementComponentTypes) {
+  for (dxil::ElementType CompType :
+       {dxil::ElementType::I32, dxil::ElementType::U32,
+        dxil::ElementType::F16, dxil::ElementType::F32,
+        dxil::ElementType::F64, dxil::ElementType::I16}) {
+    MDNode *Node = getElement(
+        /*SigId=*/0, "A", static_cast<uint32_t>(CompType), /*SemanticKind=*/0,
+        /*Indices=*/{0}, /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+        /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0, /*DynIndexMask=*/0,
+        /*GSStream=*/0);
+
+    Expected<SemanticSignatureElement> Elem =
+        SemanticSignatureElement::fromMetadata(Node);
+    ASSERT_THAT_EXPECTED(Elem, Succeeded());
+    EXPECT_EQ(Elem->CompType, CompType);
+  }
+}
+
+// Every interpolation mode value maps onto the matching enumerator
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInterpModes) {
+  for (dxbc::PSV::InterpolationMode Mode :
+       {dxbc::PSV::InterpolationMode::Constant,
+        dxbc::PSV::InterpolationMode::Linear,
+        dxbc::PSV::InterpolationMode::LinearCentroid,
+        dxbc::PSV::InterpolationMode::LinearNoperspective,
+        dxbc::PSV::InterpolationMode::LinearSample}) {
+    MDNode *Node = getElement(
+        /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
+        static_cast<uint32_t>(Mode), /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0,
+        /*StartCol=*/0, /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
+
+    Expected<SemanticSignatureElement> Elem =
+        SemanticSignatureElement::fromMetadata(Node);
+    ASSERT_THAT_EXPECTED(Elem, Succeeded());
+    EXPECT_EQ(Elem->InterpMode, Mode);
+  }
+}
+
+// A column-offset allocation drives the derived declared/usage masks
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementDerivedMasks) {
+  MDNode *Node = getElement(/*SigId=*/0, "SV_Position", /*CompType=*/9,
+                            /*SemanticKind=*/3, /*Indices=*/{0},
+                            /*InterpMode=*/4, /*Rows=*/1, /*Cols=*/2,
+                            /*StartRow=*/0, /*StartCol=*/1, /*UsageMask=*/0x2,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+
+  Expected<SemanticSignatureElement> Elem =
+      SemanticSignatureElement::fromMetadata(Node);
+  ASSERT_THAT_EXPECTED(Elem, Succeeded());
+
+  EXPECT_EQ(Elem->SemanticKind, dxbc::PSV::SemanticKind::Position);
+  EXPECT_TRUE(Elem->isAllocated());
+  // ((1 << 2) - 1) << 1 == 0b0110
+  EXPECT_EQ(Elem->getDeclaredMask(), 0x6);
+  EXPECT_EQ(Elem->getAlwaysReadsMask(), 0x2);
+  // ~0x2 & 0x6 == 0x4
+  EXPECT_EQ(Elem->getNeverWritesMask(), 0x4);
+}
+
+// A geometry shader output carries a non-zero stream index
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementGSStream) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/3);
+
+  Expected<SemanticSignatureElement> Elem =
+      SemanticSignatureElement::fromMetadata(Node);
+  ASSERT_THAT_EXPECTED(Elem, Succeeded());
+  EXPECT_EQ(Elem->GSStream, 3u);
+}
+
+//===----------------------------------------------------------------------===//
+// Error cases
+//===----------------------------------------------------------------------===//
+
+// A null node is not a valid signature element
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementNull) {
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(nullptr),
+                       Failed());
+}
+
+// A node with the wrong number of operands is rejected
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementWrongOperandCount) {
+  MDNode *Node = MDNode::get(Ctx, {getI32(0), getStr("A")});
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// A node with an operand of the wrong type is rejected
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementWrongOperandType) {
+  // Operand 0 (SigId) should be an integer, not a string
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  SmallVector<Metadata *> Ops(Node->op_begin(), Node->op_end());
+  Ops[0] = getStr("not an int");
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(MDNode::get(Ctx, Ops)), Failed());
+}
+
 } // namespace

>From 4b387134edce9ded753629db206768183d62a46c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:39:00 +0000
Subject: [PATCH 04/12] add metadata to struct validation tests

---
 .../HLSLSemanticSignatureMetadataTest.cpp     | 112 ++++++++++++++++++
 1 file changed, 112 insertions(+)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
index 817f67e1c4753..53ca977a603e3 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -238,4 +238,116 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementWrongOperandType) {
       SemanticSignatureElement::fromMetadata(MDNode::get(Ctx, Ops)), Failed());
 }
 
+//===--------------------------------------------------------------------===//
+// Per-operand range/enum validation
+//===--------------------------------------------------------------------===//
+
+// A component type outside dxil::ElementType is rejected
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidCompType) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/100,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// A semantic kind outside dxbc::PSV::SemanticKind is rejected
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSemanticKind) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/100, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// An interpolation mode outside dxbc::PSV::InterpolationMode is rejected
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidInterpMode) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/100, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// The number of components per row must be within 1-4
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidCols) {
+  for (uint8_t Cols : {uint8_t(0), uint8_t(5)}) {
+    MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                              /*SemanticKind=*/0, /*Indices=*/{0},
+                              /*InterpMode=*/0, /*Rows=*/1, Cols,
+                              /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                              /*DynIndexMask=*/0, /*GSStream=*/0);
+    EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node),
+                         Failed());
+  }
+}
+
+// A start column must be within 0-3 or the unallocated sentinel
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidStartCol) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/4, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// The row/col sentinels must be set together
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSentinelPair) {
+  MDNode *RowOnly = getElement(
+      /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
+      /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/UnallocatedRow,
+      /*StartCol=*/0, /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(RowOnly),
+                       Failed());
+
+  MDNode *ColOnly = getElement(
+      /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
+      /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0,
+      /*StartCol=*/UnallocatedCol, /*UsageMask=*/0, /*DynIndexMask=*/0,
+      /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(ColOnly),
+                       Failed());
+}
+
+// The usage and dynamic-index masks are 4-bit values
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidMasks) {
+  MDNode *BadUsage = getElement(
+      /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
+      /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0, /*StartCol=*/0,
+      /*UsageMask=*/0x10, /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(BadUsage),
+                       Failed());
+
+  MDNode *BadDyn = getElement(
+      /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
+      /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0, /*StartCol=*/0,
+      /*UsageMask=*/0, /*DynIndexMask=*/0x10, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(BadDyn),
+                       Failed());
+}
+
+// A geometry shader output stream index must be within 0-3
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidGSStream) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/4);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
+// The number of semantic indices must equal the number of rows
+TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementIndicesRowMismatch) {
+  MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
+                            /*SemanticKind=*/0, /*Indices=*/{0},
+                            /*InterpMode=*/0, /*Rows=*/2, /*Cols=*/1,
+                            /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
+                            /*DynIndexMask=*/0, /*GSStream=*/0);
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+}
+
 } // namespace

>From a37c23a3a84d0b55a8e88bde2f9e5b5685ea62c4 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:44:34 +0000
Subject: [PATCH 05/12] add struct to metadata tests

---
 .../HLSLSemanticSignatureMetadataTest.cpp     | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
index 53ca977a603e3..73aa5d19cac11 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -55,6 +55,16 @@ class HLSLSemanticSignatureMetadataTest : public testing::Test {
               getI32(Rows), getI8(Cols), getI32(StartRow), getI8(StartCol),
               getI8(UsageMask), getI8(DynIndexMask), getI32(GSStream)});
   }
+
+  // Read back an integer operand
+  uint64_t getIntOp(const MDNode *N, unsigned I) {
+    return mdconst::extract<ConstantInt>(N->getOperand(I))->getZExtValue();
+  }
+
+  // Read back a string operand
+  StringRef getStrOp(const MDNode *N, unsigned I) {
+    return cast<MDString>(N->getOperand(I))->getString();
+  }
 };
 
 //===----------------------------------------------------------------------===//
@@ -350,4 +360,117 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementIndicesRowMismatch) {
   EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
 }
 
+//===--------------------------------------------------------------------===//
+// struct -> metadata
+//===--------------------------------------------------------------------===//
+
+// A fully populated element emits all 13 operands in order
+TEST_F(HLSLSemanticSignatureMetadataTest, ElementToMetadata) {
+  SemanticSignatureElement Elem;
+  Elem.SigId = 1;
+  Elem.SemanticName = "TEXCOORD";
+  Elem.CompType = dxil::ElementType::F32;
+  Elem.SemanticKind = dxbc::PSV::SemanticKind::Arbitrary;
+  Elem.SemanticIndices = {0, 1};
+  Elem.InterpMode = dxbc::PSV::InterpolationMode::Undefined;
+  Elem.Rows = 2;
+  Elem.Cols = 4;
+  Elem.StartRow = 1;
+  Elem.StartCol = 0;
+  Elem.UsageMask = 0;
+  Elem.DynIndexMask = 0;
+  Elem.GSStream = 0;
+
+  MDNode *Node = Elem.toMetadata(Ctx);
+  ASSERT_EQ(Node->getNumOperands(), 13u);
+  EXPECT_EQ(getIntOp(Node, 0), 1u);
+  EXPECT_EQ(getStrOp(Node, 1), "TEXCOORD");
+  EXPECT_EQ(getIntOp(Node, 2), 9u);
+  EXPECT_EQ(getIntOp(Node, 3), 0u);
+  EXPECT_EQ(Node->getOperand(4).get(), getIndices({0, 1}));
+  EXPECT_EQ(getIntOp(Node, 5), 0u);
+  EXPECT_EQ(getIntOp(Node, 6), 2u);
+  EXPECT_EQ(getIntOp(Node, 7), 4u);
+  EXPECT_EQ(getIntOp(Node, 8), 1u);
+  EXPECT_EQ(getIntOp(Node, 9), 0u);
+  EXPECT_EQ(getIntOp(Node, 10), 0u);
+  EXPECT_EQ(getIntOp(Node, 11), 0u);
+  EXPECT_EQ(getIntOp(Node, 12), 0u);
+}
+
+// System value, non-zero masks and a non-zero stream index are emitted
+TEST_F(HLSLSemanticSignatureMetadataTest, ElementToMetadataSystemValue) {
+  SemanticSignatureElement Elem;
+  Elem.SigId = 1;
+  Elem.SemanticName = "SV_Target";
+  Elem.CompType = dxil::ElementType::F32;
+  Elem.SemanticKind = dxbc::PSV::SemanticKind::Target;
+  Elem.SemanticIndices = {1};
+  Elem.Rows = 1;
+  Elem.Cols = 4;
+  Elem.StartRow = 1;
+  Elem.StartCol = 0;
+  Elem.UsageMask = 0x7;
+  Elem.DynIndexMask = 0x1;
+  Elem.GSStream = 2;
+
+  MDNode *Node = Elem.toMetadata(Ctx);
+  ASSERT_EQ(Node->getNumOperands(), 13u);
+  EXPECT_EQ(getStrOp(Node, 1), "SV_Target");
+  EXPECT_EQ(getIntOp(Node, 3), 16u);
+  EXPECT_EQ(Node->getOperand(4).get(), getIndices({1}));
+  EXPECT_EQ(getIntOp(Node, 10), 0x7u);
+  EXPECT_EQ(getIntOp(Node, 11), 0x1u);
+  EXPECT_EQ(getIntOp(Node, 12), 2u);
+}
+
+// An unallocated element emits the row/col sentinels
+TEST_F(HLSLSemanticSignatureMetadataTest, ElementToMetadataUnallocated) {
+  SemanticSignatureElement Elem;
+  Elem.SemanticName = "POSITION";
+  Elem.CompType = dxil::ElementType::F32;
+  Elem.SemanticIndices = {0};
+
+  MDNode *Node = Elem.toMetadata(Ctx);
+  ASSERT_EQ(Node->getNumOperands(), 13u);
+  EXPECT_EQ(getIntOp(Node, 8), UnallocatedRow);
+  EXPECT_EQ(getIntOp(Node, 9), UnallocatedCol);
+}
+
+// Emitting then parsing yields an equivalent element
+TEST_F(HLSLSemanticSignatureMetadataTest, ElementRoundTrip) {
+  SemanticSignatureElement Elem;
+  Elem.SigId = 2;
+  Elem.SemanticName = "TEXCOORD";
+  Elem.CompType = dxil::ElementType::F32;
+  Elem.SemanticKind = dxbc::PSV::SemanticKind::Arbitrary;
+  Elem.SemanticIndices = {1};
+  Elem.InterpMode = dxbc::PSV::InterpolationMode::LinearNoperspective;
+  Elem.Rows = 1;
+  Elem.Cols = 4;
+  Elem.StartRow = 2;
+  Elem.StartCol = 0;
+  Elem.UsageMask = 0x7;
+  Elem.DynIndexMask = 0;
+  Elem.GSStream = 0;
+
+  Expected<SemanticSignatureElement> Parsed =
+      SemanticSignatureElement::fromMetadata(Elem.toMetadata(Ctx));
+  ASSERT_THAT_EXPECTED(Parsed, Succeeded());
+  EXPECT_EQ(Parsed->SigId, Elem.SigId);
+  EXPECT_EQ(Parsed->SemanticName, Elem.SemanticName);
+  EXPECT_EQ(Parsed->CompType, Elem.CompType);
+  EXPECT_EQ(Parsed->SemanticKind, Elem.SemanticKind);
+  EXPECT_THAT(Parsed->SemanticIndices,
+              testing::ElementsAreArray(Elem.SemanticIndices));
+  EXPECT_EQ(Parsed->InterpMode, Elem.InterpMode);
+  EXPECT_EQ(Parsed->Rows, Elem.Rows);
+  EXPECT_EQ(Parsed->Cols, Elem.Cols);
+  EXPECT_EQ(Parsed->StartRow, Elem.StartRow);
+  EXPECT_EQ(Parsed->StartCol, Elem.StartCol);
+  EXPECT_EQ(Parsed->UsageMask, Elem.UsageMask);
+  EXPECT_EQ(Parsed->DynIndexMask, Elem.DynIndexMask);
+  EXPECT_EQ(Parsed->GSStream, Elem.GSStream);
+}
+
 } // namespace

>From 436498e9d4268754d5b3e1f2da18a887ad445bf6 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:45:54 +0000
Subject: [PATCH 06/12] implement the toMetadata function

---
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 24 ++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index f5d277d91a889..8450e18114a9a 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -12,7 +12,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Frontend/HLSL/SemanticSignatures.h"
+#include "llvm/IR/Constants.h"
 #include "llvm/IR/Metadata.h"
+#include "llvm/IR/Type.h"
 
 using namespace llvm;
 using namespace llvm::hlsl;
@@ -23,5 +25,25 @@ SemanticSignatureElement::fromMetadata(const MDNode *Node) {
 }
 
 MDNode *SemanticSignatureElement::toMetadata(LLVMContext &Ctx) const {
-  return MDNode::get(Ctx, {});
+  Type *I32Ty = Type::getInt32Ty(Ctx);
+  Type *I8Ty = Type::getInt8Ty(Ctx);
+  auto GetI32 = [&](uint32_t Val) -> Metadata * {
+    return ConstantAsMetadata::get(ConstantInt::get(I32Ty, Val));
+  };
+  auto GetI8 = [&](uint8_t Val) -> Metadata * {
+    return ConstantAsMetadata::get(ConstantInt::get(I8Ty, Val));
+  };
+
+  SmallVector<Metadata *> IndexOps;
+  for (uint32_t Index : SemanticIndices)
+    IndexOps.push_back(GetI32(Index));
+
+  return MDNode::get(
+      Ctx, {GetI32(SigId), MDString::get(Ctx, SemanticName),
+            GetI32(static_cast<uint32_t>(CompType)),
+            GetI32(static_cast<uint32_t>(SemanticKind)),
+            MDNode::get(Ctx, IndexOps),
+            GetI32(static_cast<uint32_t>(InterpMode)), GetI32(Rows),
+            GetI8(Cols), GetI32(StartRow), GetI8(StartCol), GetI8(UsageMask),
+            GetI8(DynIndexMask), GetI32(GSStream)});
 }

>From 74a8dc05b45b705fe64baed86c89fce757f93c19 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:47:47 +0000
Subject: [PATCH 07/12] implement the fromMetadata function

---
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 127 +++++++++++++++++-
 1 file changed, 126 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 8450e18114a9a..8032f5975ae6a 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -15,13 +15,138 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Type.h"
+#include "llvm/Support/ErrorHandling.h"
 
 using namespace llvm;
 using namespace llvm::hlsl;
 
+namespace {
+// The fixed number of operands in a signature element node
+constexpr unsigned NumElementOperands = 13;
+
+// Inclusive upper bounds of the operand enums
+constexpr uint32_t MaxCompType =
+    static_cast<uint32_t>(dxil::ElementType::PackedU8x32);
+constexpr uint32_t MaxSemanticKind =
+    static_cast<uint32_t>(dxbc::PSV::SemanticKind::Invalid);
+constexpr uint32_t MaxInterpMode =
+    static_cast<uint32_t>(dxbc::PSV::InterpolationMode::Invalid);
+
+Error makeError(const Twine &Msg) {
+  return createStringError(inconvertibleErrorCode(), Msg);
+}
+
+Expected<uint64_t> extractInt(const MDNode *Node, unsigned OpId) {
+  auto *CI = mdconst::dyn_extract_or_null<ConstantInt>(Node->getOperand(OpId));
+  if (!CI)
+    return makeError("expected integer operand " + Twine(OpId));
+  return CI->getZExtValue();
+}
+} // namespace
+
 Expected<SemanticSignatureElement>
 SemanticSignatureElement::fromMetadata(const MDNode *Node) {
-  return SemanticSignatureElement{};
+  if (!Node)
+    return makeError("signature element node is null");
+  if (Node->getNumOperands() != NumElementOperands)
+    return makeError("signature element node has wrong number of operands");
+
+  SemanticSignatureElement Elem;
+
+  Expected<uint64_t> SigId = extractInt(Node, 0);
+  if (!SigId)
+    return SigId.takeError();
+  Elem.SigId = *SigId;
+
+  auto *Name = dyn_cast<MDString>(Node->getOperand(1));
+  if (!Name)
+    return makeError("expected semantic name string");
+  Elem.SemanticName = Name->getString();
+
+  Expected<uint64_t> CompType = extractInt(Node, 2);
+  if (!CompType)
+    return CompType.takeError();
+  if (*CompType > MaxCompType)
+    return makeError("invalid component type");
+  Elem.CompType = static_cast<dxil::ElementType>(*CompType);
+
+  Expected<uint64_t> SemanticKind = extractInt(Node, 3);
+  if (!SemanticKind)
+    return SemanticKind.takeError();
+  if (*SemanticKind > MaxSemanticKind)
+    return makeError("invalid semantic kind");
+  Elem.SemanticKind = static_cast<dxbc::PSV::SemanticKind>(*SemanticKind);
+
+  auto *Indices = dyn_cast<MDNode>(Node->getOperand(4));
+  if (!Indices)
+    return makeError("expected semantic indices node");
+  for (unsigned I = 0, E = Indices->getNumOperands(); I != E; ++I) {
+    Expected<uint64_t> Index = extractInt(Indices, I);
+    if (!Index)
+      return Index.takeError();
+    Elem.SemanticIndices.push_back(*Index);
+  }
+
+  Expected<uint64_t> InterpMode = extractInt(Node, 5);
+  if (!InterpMode)
+    return InterpMode.takeError();
+  if (*InterpMode > MaxInterpMode)
+    return makeError("invalid interpolation mode");
+  Elem.InterpMode = static_cast<dxbc::PSV::InterpolationMode>(*InterpMode);
+
+  Expected<uint64_t> Rows = extractInt(Node, 6);
+  if (!Rows)
+    return Rows.takeError();
+  Elem.Rows = *Rows;
+
+  Expected<uint64_t> Cols = extractInt(Node, 7);
+  if (!Cols)
+    return Cols.takeError();
+  if (*Cols < 1 || *Cols > 4)
+    return makeError("number of components per row must be within 1-4");
+  Elem.Cols = *Cols;
+
+  Expected<uint64_t> StartRow = extractInt(Node, 8);
+  if (!StartRow)
+    return StartRow.takeError();
+  Elem.StartRow = *StartRow;
+
+  Expected<uint64_t> StartCol = extractInt(Node, 9);
+  if (!StartCol)
+    return StartCol.takeError();
+  if (*StartCol > 3 && *StartCol != UnallocatedCol)
+    return makeError("start column must be within 0-3 or unallocated");
+  Elem.StartCol = *StartCol;
+
+  // The row/col sentinels are always set together
+  if ((Elem.StartRow == UnallocatedRow) != (Elem.StartCol == UnallocatedCol))
+    return makeError("start row and column sentinels must be set together");
+
+  Expected<uint64_t> UsageMask = extractInt(Node, 10);
+  if (!UsageMask)
+    return UsageMask.takeError();
+  if (*UsageMask > 0xF)
+    return makeError("usage mask must be a 4-bit value");
+  Elem.UsageMask = *UsageMask;
+
+  Expected<uint64_t> DynIndexMask = extractInt(Node, 11);
+  if (!DynIndexMask)
+    return DynIndexMask.takeError();
+  if (*DynIndexMask > 0xF)
+    return makeError("dynamic index mask must be a 4-bit value");
+  Elem.DynIndexMask = *DynIndexMask;
+
+  Expected<uint64_t> GSStream = extractInt(Node, 12);
+  if (!GSStream)
+    return GSStream.takeError();
+  if (*GSStream > 3)
+    return makeError("geometry shader stream index must be within 0-3");
+  Elem.GSStream = *GSStream;
+
+  if (Elem.SemanticIndices.size() != Elem.Rows)
+    return makeError("number of semantic indices must equal the number of rows");
+
+  return Elem;
 }
 
 MDNode *SemanticSignatureElement::toMetadata(LLVMContext &Ctx) const {

>From 067a40010a0bc2d3f0f0bf7d9a94b2537b97334c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 18:52:20 +0000
Subject: [PATCH 08/12] update tests to check error strings

---
 .../HLSLSemanticSignatureMetadataTest.cpp     | 56 ++++++++++++-------
 1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
index 73aa5d19cac11..8950498887e2f 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -224,14 +224,17 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementGSStream) {
 
 // A null node is not a valid signature element
 TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementNull) {
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(nullptr),
-                       Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(nullptr),
+      FailedWithMessage("signature element node is null"));
 }
 
 // A node with the wrong number of operands is rejected
 TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementWrongOperandCount) {
   MDNode *Node = MDNode::get(Ctx, {getI32(0), getStr("A")});
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(Node),
+      FailedWithMessage("signature element node has wrong number of operands"));
 }
 
 // A node with an operand of the wrong type is rejected
@@ -245,7 +248,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementWrongOperandType) {
   SmallVector<Metadata *> Ops(Node->op_begin(), Node->op_end());
   Ops[0] = getStr("not an int");
   EXPECT_THAT_EXPECTED(
-      SemanticSignatureElement::fromMetadata(MDNode::get(Ctx, Ops)), Failed());
+      SemanticSignatureElement::fromMetadata(MDNode::get(Ctx, Ops)),
+      FailedWithMessage("expected integer operand 0"));
 }
 
 //===--------------------------------------------------------------------===//
@@ -259,7 +263,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidCompType) {
                             /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node),
+                       FailedWithMessage("invalid component type"));
 }
 
 // A semantic kind outside dxbc::PSV::SemanticKind is rejected
@@ -269,7 +274,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSemanticKind)
                             /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node),
+                       FailedWithMessage("invalid semantic kind"));
 }
 
 // An interpolation mode outside dxbc::PSV::InterpolationMode is rejected
@@ -279,7 +285,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidInterpMode) {
                             /*InterpMode=*/100, /*Rows=*/1, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node),
+                       FailedWithMessage("invalid interpolation mode"));
 }
 
 // The number of components per row must be within 1-4
@@ -290,8 +297,9 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidCols) {
                               /*InterpMode=*/0, /*Rows=*/1, Cols,
                               /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                               /*DynIndexMask=*/0, /*GSStream=*/0);
-    EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node),
-                         Failed());
+    EXPECT_THAT_EXPECTED(
+        SemanticSignatureElement::fromMetadata(Node),
+        FailedWithMessage("number of components per row must be within 1-4"));
   }
 }
 
@@ -302,7 +310,9 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidStartCol) {
                             /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/4, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(Node),
+      FailedWithMessage("start column must be within 0-3 or unallocated"));
 }
 
 // The row/col sentinels must be set together
@@ -311,16 +321,18 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSentinelPair)
       /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
       /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/UnallocatedRow,
       /*StartCol=*/0, /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(RowOnly),
-                       Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(RowOnly),
+      FailedWithMessage("start row and column sentinels must be set together"));
 
   MDNode *ColOnly = getElement(
       /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
       /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0,
       /*StartCol=*/UnallocatedCol, /*UsageMask=*/0, /*DynIndexMask=*/0,
       /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(ColOnly),
-                       Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(ColOnly),
+      FailedWithMessage("start row and column sentinels must be set together"));
 }
 
 // The usage and dynamic-index masks are 4-bit values
@@ -330,14 +342,15 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidMasks) {
       /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0, /*StartCol=*/0,
       /*UsageMask=*/0x10, /*DynIndexMask=*/0, /*GSStream=*/0);
   EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(BadUsage),
-                       Failed());
+                       FailedWithMessage("usage mask must be a 4-bit value"));
 
   MDNode *BadDyn = getElement(
       /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
       /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/0, /*StartCol=*/0,
       /*UsageMask=*/0, /*DynIndexMask=*/0x10, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(BadDyn),
-                       Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(BadDyn),
+      FailedWithMessage("dynamic index mask must be a 4-bit value"));
 }
 
 // A geometry shader output stream index must be within 0-3
@@ -347,7 +360,9 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidGSStream) {
                             /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/4);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(Node),
+      FailedWithMessage("geometry shader stream index must be within 0-3"));
 }
 
 // The number of semantic indices must equal the number of rows
@@ -357,7 +372,10 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementIndicesRowMismatch) {
                             /*InterpMode=*/0, /*Rows=*/2, /*Cols=*/1,
                             /*StartRow=*/0, /*StartCol=*/0, /*UsageMask=*/0,
                             /*DynIndexMask=*/0, /*GSStream=*/0);
-  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(Node), Failed());
+  EXPECT_THAT_EXPECTED(
+      SemanticSignatureElement::fromMetadata(Node),
+      FailedWithMessage(
+          "number of semantic indices must equal the number of rows"));
 }
 
 //===--------------------------------------------------------------------===//

>From b54b529e1323d501c74c0cd5965d6bb8c28e36ad Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 21:53:01 +0000
Subject: [PATCH 09/12] review: clang-format

---
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 19 ++++++------
 .../HLSLSemanticSignatureMetadataTest.cpp     | 31 ++++++++++---------
 2 files changed, 26 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 8032f5975ae6a..860fa7905fce3 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -144,7 +144,8 @@ SemanticSignatureElement::fromMetadata(const MDNode *Node) {
   Elem.GSStream = *GSStream;
 
   if (Elem.SemanticIndices.size() != Elem.Rows)
-    return makeError("number of semantic indices must equal the number of rows");
+    return makeError(
+        "number of semantic indices must equal the number of rows");
 
   return Elem;
 }
@@ -163,12 +164,12 @@ MDNode *SemanticSignatureElement::toMetadata(LLVMContext &Ctx) const {
   for (uint32_t Index : SemanticIndices)
     IndexOps.push_back(GetI32(Index));
 
-  return MDNode::get(
-      Ctx, {GetI32(SigId), MDString::get(Ctx, SemanticName),
-            GetI32(static_cast<uint32_t>(CompType)),
-            GetI32(static_cast<uint32_t>(SemanticKind)),
-            MDNode::get(Ctx, IndexOps),
-            GetI32(static_cast<uint32_t>(InterpMode)), GetI32(Rows),
-            GetI8(Cols), GetI32(StartRow), GetI8(StartCol), GetI8(UsageMask),
-            GetI8(DynIndexMask), GetI32(GSStream)});
+  return MDNode::get(Ctx,
+                     {GetI32(SigId), MDString::get(Ctx, SemanticName),
+                      GetI32(static_cast<uint32_t>(CompType)),
+                      GetI32(static_cast<uint32_t>(SemanticKind)),
+                      MDNode::get(Ctx, IndexOps),
+                      GetI32(static_cast<uint32_t>(InterpMode)), GetI32(Rows),
+                      GetI8(Cols), GetI32(StartRow), GetI8(StartCol),
+                      GetI8(UsageMask), GetI8(DynIndexMask), GetI32(GSStream)});
 }
diff --git a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
index 8950498887e2f..6833ee7c60f96 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignatureMetadataTest.cpp
@@ -30,8 +30,7 @@ class HLSLSemanticSignatureMetadataTest : public testing::Test {
   }
 
   Metadata *getI8(uint8_t Val) {
-    return ConstantAsMetadata::get(
-        ConstantInt::get(Type::getInt8Ty(Ctx), Val));
+    return ConstantAsMetadata::get(ConstantInt::get(Type::getInt8Ty(Ctx), Val));
   }
 
   Metadata *getStr(StringRef Val) { return MDString::get(Ctx, Val); }
@@ -129,11 +128,12 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementSystemValue) {
 
 // An unallocated element uses the row/col sentinels
 TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementUnallocated) {
-  MDNode *Node = getElement(/*SigId=*/0, "POSITION", /*CompType=*/9,
-                            /*SemanticKind=*/0, /*Indices=*/{0},
-                            /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/4,
-                            /*StartRow=*/UnallocatedRow, /*StartCol=*/UnallocatedCol,
-                            /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
+  MDNode *Node =
+      getElement(/*SigId=*/0, "POSITION", /*CompType=*/9,
+                 /*SemanticKind=*/0, /*Indices=*/{0},
+                 /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/4,
+                 /*StartRow=*/UnallocatedRow, /*StartCol=*/UnallocatedCol,
+                 /*UsageMask=*/0, /*DynIndexMask=*/0, /*GSStream=*/0);
 
   Expected<SemanticSignatureElement> Elem =
       SemanticSignatureElement::fromMetadata(Node);
@@ -147,9 +147,9 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementUnallocated) {
 // Every component type value maps onto the matching dxil::ElementType
 TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementComponentTypes) {
   for (dxil::ElementType CompType :
-       {dxil::ElementType::I32, dxil::ElementType::U32,
-        dxil::ElementType::F16, dxil::ElementType::F32,
-        dxil::ElementType::F64, dxil::ElementType::I16}) {
+       {dxil::ElementType::I32, dxil::ElementType::U32, dxil::ElementType::F16,
+        dxil::ElementType::F32, dxil::ElementType::F64,
+        dxil::ElementType::I16}) {
     MDNode *Node = getElement(
         /*SigId=*/0, "A", static_cast<uint32_t>(CompType), /*SemanticKind=*/0,
         /*Indices=*/{0}, /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
@@ -224,9 +224,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementGSStream) {
 
 // A null node is not a valid signature element
 TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementNull) {
-  EXPECT_THAT_EXPECTED(
-      SemanticSignatureElement::fromMetadata(nullptr),
-      FailedWithMessage("signature element node is null"));
+  EXPECT_THAT_EXPECTED(SemanticSignatureElement::fromMetadata(nullptr),
+                       FailedWithMessage("signature element node is null"));
 }
 
 // A node with the wrong number of operands is rejected
@@ -268,7 +267,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidCompType) {
 }
 
 // A semantic kind outside dxbc::PSV::SemanticKind is rejected
-TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSemanticKind) {
+TEST_F(HLSLSemanticSignatureMetadataTest,
+       MetadataToElementInvalidSemanticKind) {
   MDNode *Node = getElement(/*SigId=*/0, "A", /*CompType=*/9,
                             /*SemanticKind=*/100, /*Indices=*/{0},
                             /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1,
@@ -316,7 +316,8 @@ TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidStartCol) {
 }
 
 // The row/col sentinels must be set together
-TEST_F(HLSLSemanticSignatureMetadataTest, MetadataToElementInvalidSentinelPair) {
+TEST_F(HLSLSemanticSignatureMetadataTest,
+       MetadataToElementInvalidSentinelPair) {
   MDNode *RowOnly = getElement(
       /*SigId=*/0, "A", /*CompType=*/9, /*SemanticKind=*/0, /*Indices=*/{0},
       /*InterpMode=*/0, /*Rows=*/1, /*Cols=*/1, /*StartRow=*/UnallocatedRow,

>From e18bd05581f3f07c96521ffcd87fb3125fc3d3cd Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 15 Jul 2026 21:53:21 +0000
Subject: [PATCH 10/12] review: remove unused includes

---
 llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h | 1 -
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp        | 1 -
 2 files changed, 2 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 7d5efc663e02a..639ba3cd66b32 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -21,7 +21,6 @@
 #include "llvm/Support/DXILABI.h"
 #include "llvm/Support/Error.h"
 #include <cstdint>
-#include <string>
 
 namespace llvm {
 
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 860fa7905fce3..71e018dba5d38 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -15,7 +15,6 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Type.h"
-#include "llvm/Support/ErrorHandling.h"
 
 using namespace llvm;
 using namespace llvm::hlsl;

>From d4f24d14fe769c06bee0083972f7557ec7d1b042 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 22 Jul 2026 17:55:37 +0000
Subject: [PATCH 11/12] review: correct minprecision and add opindex helper

---
 .../llvm/Frontend/HLSL/SemanticSignatures.h   |  2 -
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 58 ++++++++++++++-----
 2 files changed, 43 insertions(+), 17 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 639ba3cd66b32..ab3f87f1ff579 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -76,8 +76,6 @@ struct SemanticSignatureElement {
     case dxil::ElementType::F16:
       return dxbc::SigMinPrecision::Float16;
     case dxil::ElementType::I16:
-    case dxil::ElementType::SNormF16:
-    case dxil::ElementType::UNormF16:
       return dxbc::SigMinPrecision::SInt16;
     case dxil::ElementType::U16:
       return dxbc::SigMinPrecision::UInt16;
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 71e018dba5d38..778f654524a63 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Frontend/HLSL/SemanticSignatures.h"
+#include "llvm/ADT/STLForwardCompat.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/Metadata.h"
 #include "llvm/IR/Type.h"
@@ -20,8 +21,6 @@ using namespace llvm;
 using namespace llvm::hlsl;
 
 namespace {
-// The fixed number of operands in a signature element node
-constexpr unsigned NumElementOperands = 13;
 
 // Inclusive upper bounds of the operand enums
 constexpr uint32_t MaxCompType =
@@ -45,6 +44,25 @@ Expected<uint64_t> extractInt(const MDNode *Node, unsigned OpId) {
 
 Expected<SemanticSignatureElement>
 SemanticSignatureElement::fromMetadata(const MDNode *Node) {
+  // Operand positions within a signature element metadata node.
+  enum class OpIdx : unsigned {
+    SigId,
+    SemanticName,
+    CompType,
+    SemanticKind,
+    SemanticIndices,
+    InterpMode,
+    Rows,
+    Cols,
+    StartRow,
+    StartCol,
+    UsageMask,
+    DynIndexMask,
+    GSStream,
+    NumOperands,
+  };
+  const unsigned NumElementOperands = to_underlying(OpIdx::NumOperands);
+
   if (!Node)
     return makeError("signature element node is null");
   if (Node->getNumOperands() != NumElementOperands)
@@ -52,31 +70,35 @@ SemanticSignatureElement::fromMetadata(const MDNode *Node) {
 
   SemanticSignatureElement Elem;
 
-  Expected<uint64_t> SigId = extractInt(Node, 0);
+  Expected<uint64_t> SigId = extractInt(Node, to_underlying(OpIdx::SigId));
   if (!SigId)
     return SigId.takeError();
   Elem.SigId = *SigId;
 
-  auto *Name = dyn_cast<MDString>(Node->getOperand(1));
+  auto *Name =
+      dyn_cast<MDString>(Node->getOperand(to_underlying(OpIdx::SemanticName)));
   if (!Name)
     return makeError("expected semantic name string");
   Elem.SemanticName = Name->getString();
 
-  Expected<uint64_t> CompType = extractInt(Node, 2);
+  Expected<uint64_t> CompType =
+      extractInt(Node, to_underlying(OpIdx::CompType));
   if (!CompType)
     return CompType.takeError();
   if (*CompType > MaxCompType)
     return makeError("invalid component type");
   Elem.CompType = static_cast<dxil::ElementType>(*CompType);
 
-  Expected<uint64_t> SemanticKind = extractInt(Node, 3);
+  Expected<uint64_t> SemanticKind =
+      extractInt(Node, to_underlying(OpIdx::SemanticKind));
   if (!SemanticKind)
     return SemanticKind.takeError();
   if (*SemanticKind > MaxSemanticKind)
     return makeError("invalid semantic kind");
   Elem.SemanticKind = static_cast<dxbc::PSV::SemanticKind>(*SemanticKind);
 
-  auto *Indices = dyn_cast<MDNode>(Node->getOperand(4));
+  auto *Indices =
+      dyn_cast<MDNode>(Node->getOperand(to_underlying(OpIdx::SemanticIndices)));
   if (!Indices)
     return makeError("expected semantic indices node");
   for (unsigned I = 0, E = Indices->getNumOperands(); I != E; ++I) {
@@ -86,31 +108,34 @@ SemanticSignatureElement::fromMetadata(const MDNode *Node) {
     Elem.SemanticIndices.push_back(*Index);
   }
 
-  Expected<uint64_t> InterpMode = extractInt(Node, 5);
+  Expected<uint64_t> InterpMode =
+      extractInt(Node, to_underlying(OpIdx::InterpMode));
   if (!InterpMode)
     return InterpMode.takeError();
   if (*InterpMode > MaxInterpMode)
     return makeError("invalid interpolation mode");
   Elem.InterpMode = static_cast<dxbc::PSV::InterpolationMode>(*InterpMode);
 
-  Expected<uint64_t> Rows = extractInt(Node, 6);
+  Expected<uint64_t> Rows = extractInt(Node, to_underlying(OpIdx::Rows));
   if (!Rows)
     return Rows.takeError();
   Elem.Rows = *Rows;
 
-  Expected<uint64_t> Cols = extractInt(Node, 7);
+  Expected<uint64_t> Cols = extractInt(Node, to_underlying(OpIdx::Cols));
   if (!Cols)
     return Cols.takeError();
   if (*Cols < 1 || *Cols > 4)
     return makeError("number of components per row must be within 1-4");
   Elem.Cols = *Cols;
 
-  Expected<uint64_t> StartRow = extractInt(Node, 8);
+  Expected<uint64_t> StartRow =
+      extractInt(Node, to_underlying(OpIdx::StartRow));
   if (!StartRow)
     return StartRow.takeError();
   Elem.StartRow = *StartRow;
 
-  Expected<uint64_t> StartCol = extractInt(Node, 9);
+  Expected<uint64_t> StartCol =
+      extractInt(Node, to_underlying(OpIdx::StartCol));
   if (!StartCol)
     return StartCol.takeError();
   if (*StartCol > 3 && *StartCol != UnallocatedCol)
@@ -121,21 +146,24 @@ SemanticSignatureElement::fromMetadata(const MDNode *Node) {
   if ((Elem.StartRow == UnallocatedRow) != (Elem.StartCol == UnallocatedCol))
     return makeError("start row and column sentinels must be set together");
 
-  Expected<uint64_t> UsageMask = extractInt(Node, 10);
+  Expected<uint64_t> UsageMask =
+      extractInt(Node, to_underlying(OpIdx::UsageMask));
   if (!UsageMask)
     return UsageMask.takeError();
   if (*UsageMask > 0xF)
     return makeError("usage mask must be a 4-bit value");
   Elem.UsageMask = *UsageMask;
 
-  Expected<uint64_t> DynIndexMask = extractInt(Node, 11);
+  Expected<uint64_t> DynIndexMask =
+      extractInt(Node, to_underlying(OpIdx::DynIndexMask));
   if (!DynIndexMask)
     return DynIndexMask.takeError();
   if (*DynIndexMask > 0xF)
     return makeError("dynamic index mask must be a 4-bit value");
   Elem.DynIndexMask = *DynIndexMask;
 
-  Expected<uint64_t> GSStream = extractInt(Node, 12);
+  Expected<uint64_t> GSStream =
+      extractInt(Node, to_underlying(OpIdx::GSStream));
   if (!GSStream)
     return GSStream.takeError();
   if (*GSStream > 3)

>From 5692455af6d48b18efdbf293eb46746df0d25b2c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Wed, 22 Jul 2026 17:58:59 +0000
Subject: [PATCH 12/12] review: update mandatory arguments to not have a
 default value

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

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index ab3f87f1ff579..c5be2516a7364 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -40,13 +40,13 @@ static constexpr uint8_t UnallocatedCol = 0xFF;
 struct SemanticSignatureElement {
   uint32_t SigId;
   StringRef SemanticName;
-  dxil::ElementType CompType = dxil::ElementType::Invalid;
-  dxbc::PSV::SemanticKind SemanticKind = dxbc::PSV::SemanticKind::Arbitrary;
+  dxil::ElementType CompType;
+  dxbc::PSV::SemanticKind SemanticKind;
   SmallVector<uint32_t> SemanticIndices;
   dxbc::PSV::InterpolationMode InterpMode =
       dxbc::PSV::InterpolationMode::Undefined;
-  uint32_t Rows = 1;
-  uint8_t Cols = 1;
+  uint32_t Rows;
+  uint8_t Cols;
   uint32_t StartRow = UnallocatedRow;
   uint8_t StartCol = UnallocatedCol;
   uint8_t UsageMask = 0;



More information about the llvm-commits mailing list