[clang] [llvm] [HLSLSemanticSignatures] Implement the indexed packing of elements (PR #218061)

Finn Plummer via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 21 16:02:54 PDT 2026


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

Resolves: #205877

Assisted by: Claude Opus 5 and GPT-5.6 Sol

>From e52b84aa7ee4c878202e01826bfaa40de0128bf4 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:40:58 +0000
Subject: [PATCH 01/28] nfc: move def to semasig

---
 clang/include/clang/Sema/SemaHLSL.h           | 21 +++++--------------
 clang/lib/Sema/SemaHLSL.cpp                   |  2 ++
 .../llvm/Frontend/HLSL/SemanticSignatures.h   | 15 +++++++++++++
 3 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 8928524e49783..0f834df21c40d 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -23,6 +23,7 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringSet.h"
+#include "llvm/Frontend/HLSL/SemanticSignatures.h"
 #include "llvm/TargetParser/Triple.h"
 #include <initializer_list>
 
@@ -289,14 +290,6 @@ class SemaHLSL : public SemaBase {
     std::optional<uint32_t> Index = std::nullopt;
   };
 
-  // Bitmask used to recall if the current semantic subtree is
-  // input, output or inout.
-  enum IOType {
-    In = 0b01,
-    Out = 0b10,
-    InOut = 0b11,
-  };
-
   // The context shared by all semantics with the same IOType during
   // flattening.
   struct SemanticContext {
@@ -307,12 +300,7 @@ class SemaHLSL : public SemaBase {
     // index collisions.
     llvm::StringSet<> ActiveSemantics = {};
     // The IOType of this semantic set.
-    IOType CurrentIOType;
-  };
-
-  struct SemanticStageInfo {
-    llvm::Triple::EnvironmentType Stage;
-    IOType AllowedIOTypesMask;
+    llvm::hlsl::IOType CurrentIOType;
   };
 
 private:
@@ -343,8 +331,9 @@ class SemaHLSL : public SemaBase {
       std::initializer_list<llvm::Triple::EnvironmentType> AllowedStages);
 
   void diagnoseSemanticStageMismatch(
-      const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
-      std::initializer_list<SemanticStageInfo> AllowedStages);
+      const Attr *A, llvm::Triple::EnvironmentType Stage,
+      llvm::hlsl::IOType CurrentIOType,
+      std::initializer_list<llvm::hlsl::SemanticStageInfo> AllowedStages);
 
   void handleGlobalStructOrArrayOfWithResources(VarDecl *VD);
 
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 184339044e5bf..6bcd6d9182e44 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -54,6 +54,8 @@
 
 using namespace clang;
 using namespace clang::hlsl;
+using llvm::hlsl::IOType;
+using llvm::hlsl::SemanticStageInfo;
 using RegisterType = HLSLResourceBindingAttr::RegisterType;
 
 static CXXRecordDecl *createHostLayoutStruct(Sema &S,
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 0d0da50189e53..042b4afdf6faf 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -21,6 +21,7 @@
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/DXILABI.h"
 #include "llvm/Support/Error.h"
+#include "llvm/TargetParser/Triple.h"
 #include <cstdint>
 
 namespace llvm {
@@ -32,6 +33,20 @@ namespace hlsl {
 
 // Definitions of the in-memory data layout structures
 
+// Bitmask denoting whether a semantic is an input, output, or a value that is
+// constant across a patch (hull/domain shaders) or primitive (mesh shaders).
+enum IOType {
+  In = 0b001,
+  Out = 0b010,
+  InOut = 0b011,
+  PatchConstantOrPrimitive = 0b100,
+};
+
+struct SemanticStageInfo {
+  Triple::EnvironmentType Stage;
+  IOType AllowedIOTypesMask;
+};
+
 // Sentinel values denoting that an element is unallocated
 static constexpr uint32_t UnallocatedRow = ~0U;
 static constexpr uint8_t UnallocatedCol = 0xFF;

>From 0a79bad0fc01ced54e83f66e55fa3d289ae02229 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:42:51 +0000
Subject: [PATCH 02/28] extend to for patch constants

---
 clang/lib/Sema/SemaHLSL.cpp | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 6bcd6d9182e44..5ed6d4632d8ee 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1158,12 +1158,19 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
             ValidType.push_back("input");
           if (Case.AllowedIOTypesMask & IOType::Out)
             ValidType.push_back("output");
+          if (Case.AllowedIOTypesMask & IOType::PatchConstantOrPrimitive)
+            ValidType.push_back("patch constant or primitive");
           return std::string(
                      HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage)) +
                  " " + join(ValidType, "/");
         });
+    StringRef CurrentIOTypeName = "patch constant or primitive";
+    if (CurrentIOType & IOType::In)
+      CurrentIOTypeName = "input";
+    else if (CurrentIOType & IOType::Out)
+      CurrentIOTypeName = "output";
     Diag(A->getLoc(), diag::err_hlsl_semantic_unsupported_iotype_for_stage)
-        << A->getAttrName() << (CurrentIOType & IOType::In ? "input" : "output")
+        << A->getAttrName() << CurrentIOTypeName
         << llvm::Triple::getEnvironmentTypeName(Case.Stage)
         << join(ValidCases, ", ");
     return;

>From a65ec0ce3b98b7330cc61d0c7b47f5f51a0cc497 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:54:45 +0000
Subject: [PATCH 03/28] define common info getter

---
 llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h | 3 +++
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp        | 6 ++++++
 2 files changed, 9 insertions(+)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 042b4afdf6faf..5749f0b0538a9 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -119,6 +119,9 @@ struct SemanticSignatureElement {
 
 LLVM_ABI dxbc::PSV::SemanticKind getSemanticKind(StringRef SemanticName);
 
+LLVM_ABI ArrayRef<SemanticStageInfo>
+getAvailableStages(dxbc::PSV::SemanticKind SemanticKind);
+
 } // namespace hlsl
 } // namespace llvm
 
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 48d68c68946d7..72a5366bafb70 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -17,6 +17,7 @@
 #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;
@@ -54,6 +55,11 @@ dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) {
   return dxbc::PSV::SemanticKind::Invalid;
 }
 
+ArrayRef<SemanticStageInfo>
+hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
+  llvm_unreachable("available stages for given semantic kind are not handled");
+}
+
 Expected<SemanticSignatureElement>
 SemanticSignatureElement::fromMetadata(const MDNode *Node) {
   // Operand positions within a signature element metadata node.

>From cf60ce9fdef9340dc1860e6bce45d521bc4d8d7b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:56:51 +0000
Subject: [PATCH 04/28] categorize compute semantics

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 72a5366bafb70..68f8e20b4ff8b 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -57,7 +57,19 @@ dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) {
 
 ArrayRef<SemanticStageInfo>
 hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
-  llvm_unreachable("available stages for given semantic kind are not handled");
+  switch (SemanticKind) {
+  case dxbc::PSV::SemanticKind::DispatchThreadID:
+  case dxbc::PSV::SemanticKind::GroupID:
+  case dxbc::PSV::SemanticKind::GroupIndex:
+  case dxbc::PSV::SemanticKind::GroupThreadID: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Compute, IOType::In}};
+    return Stages;
+  }
+  default:
+    llvm_unreachable(
+        "available stages for given semantic kind are not handled");
+  }
 }
 
 Expected<SemanticSignatureElement>

>From 66534f60a860bc77aafbe42cb93d3fda558eac69 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:56:57 +0000
Subject: [PATCH 05/28] categorize target

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 68f8e20b4ff8b..7b631fa22d545 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -66,6 +66,11 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
         {Triple::Compute, IOType::In}};
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::Target: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Pixel, IOType::Out}};
+    return Stages;
+  }
   default:
     llvm_unreachable(
         "available stages for given semantic kind are not handled");

>From ab60f591d0e0b153a3c4b73e7b99f4a8c5eb980e Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:03 +0000
Subject: [PATCH 06/28] categorize vertexid

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 7b631fa22d545..4704f2859be82 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -71,6 +71,11 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
         {Triple::Pixel, IOType::Out}};
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::VertexID: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Vertex, IOType::In}};
+    return Stages;
+  }
   default:
     llvm_unreachable(
         "available stages for given semantic kind are not handled");

>From 910bf701476377dab90ea5ee78a70b3781862a1b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:12 +0000
Subject: [PATCH 07/28] categorize position

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 4704f2859be82..1ac635e50d635 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -76,6 +76,11 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
         {Triple::Vertex, IOType::In}};
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::Position: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Vertex, IOType::InOut}, {Triple::Pixel, IOType::In}};
+    return Stages;
+  }
   default:
     llvm_unreachable(
         "available stages for given semantic kind are not handled");

>From f7df75f0ba00cf358cb795755f2413b730b81c1c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:26 +0000
Subject: [PATCH 08/28] categorize arbitrary

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 1ac635e50d635..480e6c0f122d4 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -58,6 +58,18 @@ dxbc::PSV::SemanticKind hlsl::getSemanticKind(StringRef SemanticName) {
 ArrayRef<SemanticStageInfo>
 hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   switch (SemanticKind) {
+  case dxbc::PSV::SemanticKind::Arbitrary: {
+    static constexpr IOType AllIOTypes =
+        static_cast<IOType>(IOType::InOut | IOType::PatchConstantOrPrimitive);
+    static constexpr IOType OutOrPatchConstant =
+        static_cast<IOType>(IOType::Out | IOType::PatchConstantOrPrimitive);
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Vertex, IOType::InOut}, {Triple::Geometry, IOType::InOut},
+        {Triple::Hull, AllIOTypes},      {Triple::Domain, AllIOTypes},
+        {Triple::Pixel, IOType::In},     {Triple::Mesh, OutOrPatchConstant},
+    };
+    return Stages;
+  }
   case dxbc::PSV::SemanticKind::DispatchThreadID:
   case dxbc::PSV::SemanticKind::GroupID:
   case dxbc::PSV::SemanticKind::GroupIndex:

>From 6c2656c0136147e770aa72ed54ab99a4c8b08a95 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:38 +0000
Subject: [PATCH 09/28] categorize clip/cull

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 480e6c0f122d4..58e44fe756a75 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -93,6 +93,17 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
         {Triple::Vertex, IOType::InOut}, {Triple::Pixel, IOType::In}};
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::ClipDistance:
+  case dxbc::PSV::SemanticKind::CullDistance: {
+    static constexpr IOType AllIOTypes =
+        static_cast<IOType>(IOType::InOut | IOType::PatchConstantOrPrimitive);
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Vertex, IOType::InOut}, {Triple::Hull, AllIOTypes},
+        {Triple::Domain, AllIOTypes},    {Triple::Geometry, IOType::InOut},
+        {Triple::Pixel, IOType::In},     {Triple::Mesh, IOType::Out},
+    };
+    return Stages;
+  }
   default:
     llvm_unreachable(
         "available stages for given semantic kind are not handled");

>From 5e5f6474d507f3110de7b9ef2e1015111b113d3f Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:46 +0000
Subject: [PATCH 10/28] categorize tess factors

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 58e44fe756a75..f28979bf702f6 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -104,6 +104,14 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
     };
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::TessFactor:
+  case dxbc::PSV::SemanticKind::InsideTessFactor: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Hull, IOType::PatchConstantOrPrimitive},
+        {Triple::Domain, IOType::PatchConstantOrPrimitive},
+    };
+    return Stages;
+  }
   default:
     llvm_unreachable(
         "available stages for given semantic kind are not handled");

>From 0be9a96259a637132ee32e7b388a228590c969f9 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:57:53 +0000
Subject: [PATCH 11/28] categorize front-face semantics

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

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index f28979bf702f6..5971275fd0dde 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -88,6 +88,11 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
         {Triple::Vertex, IOType::In}};
     return Stages;
   }
+  case dxbc::PSV::SemanticKind::IsFrontFace: {
+    static constexpr SemanticStageInfo Stages[] = {
+        {Triple::Geometry, IOType::Out}, {Triple::Pixel, IOType::In}};
+    return Stages;
+  }
   case dxbc::PSV::SemanticKind::Position: {
     static constexpr SemanticStageInfo Stages[] = {
         {Triple::Vertex, IOType::InOut}, {Triple::Pixel, IOType::In}};

>From 03e6d10cf6c65923c5b27c556db3b931d7ada19b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:59:05 +0000
Subject: [PATCH 12/28] nfc: define all in enum

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

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 5749f0b0538a9..0af56b46d60b1 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -40,6 +40,7 @@ enum IOType {
   Out = 0b010,
   InOut = 0b011,
   PatchConstantOrPrimitive = 0b100,
+  All = 0b111,
 };
 
 struct SemanticStageInfo {
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 5971275fd0dde..09fd5c4ee6744 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -59,13 +59,11 @@ ArrayRef<SemanticStageInfo>
 hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   switch (SemanticKind) {
   case dxbc::PSV::SemanticKind::Arbitrary: {
-    static constexpr IOType AllIOTypes =
-        static_cast<IOType>(IOType::InOut | IOType::PatchConstantOrPrimitive);
     static constexpr IOType OutOrPatchConstant =
         static_cast<IOType>(IOType::Out | IOType::PatchConstantOrPrimitive);
     static constexpr SemanticStageInfo Stages[] = {
         {Triple::Vertex, IOType::InOut}, {Triple::Geometry, IOType::InOut},
-        {Triple::Hull, AllIOTypes},      {Triple::Domain, AllIOTypes},
+        {Triple::Hull, IOType::All},     {Triple::Domain, IOType::All},
         {Triple::Pixel, IOType::In},     {Triple::Mesh, OutOrPatchConstant},
     };
     return Stages;
@@ -100,11 +98,9 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   }
   case dxbc::PSV::SemanticKind::ClipDistance:
   case dxbc::PSV::SemanticKind::CullDistance: {
-    static constexpr IOType AllIOTypes =
-        static_cast<IOType>(IOType::InOut | IOType::PatchConstantOrPrimitive);
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Vertex, IOType::InOut}, {Triple::Hull, AllIOTypes},
-        {Triple::Domain, AllIOTypes},    {Triple::Geometry, IOType::InOut},
+        {Triple::Vertex, IOType::InOut}, {Triple::Hull, IOType::All},
+        {Triple::Domain, IOType::All},   {Triple::Geometry, IOType::InOut},
         {Triple::Pixel, IOType::In},     {Triple::Mesh, IOType::Out},
     };
     return Stages;

>From a127af01ebc58492dbd2a9af1b7233b295e874e6 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 17:59:46 +0000
Subject: [PATCH 13/28] define interpretation kinds enum

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

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 0af56b46d60b1..85ddabe266c1e 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -43,6 +43,17 @@ enum IOType {
   All = 0b111,
 };
 
+enum class SemanticInterpretation {
+  Invalid,
+  NotAllocated,
+  Arbitrary,
+  SV,
+  SGV,
+  ClipCull,
+  TessFactor,
+  Target,
+};
+
 struct SemanticStageInfo {
   Triple::EnvironmentType Stage;
   IOType AllowedIOTypesMask;

>From afd27adc4a2c4d149948c3869338ef17cc2f7f4e Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 18:02:29 +0000
Subject: [PATCH 14/28] include interpretation in semantic stage info

---
 .../llvm/Frontend/HLSL/SemanticSignatures.h   |  1 +
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp | 42 +++++++++++++------
 2 files changed, 30 insertions(+), 13 deletions(-)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 85ddabe266c1e..30d04f0da098e 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -57,6 +57,7 @@ enum class SemanticInterpretation {
 struct SemanticStageInfo {
   Triple::EnvironmentType Stage;
   IOType AllowedIOTypesMask;
+  SemanticInterpretation Interpretation;
 };
 
 // Sentinel values denoting that an element is unallocated
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 09fd5c4ee6744..9361f57115f5b 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -62,9 +62,12 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
     static constexpr IOType OutOrPatchConstant =
         static_cast<IOType>(IOType::Out | IOType::PatchConstantOrPrimitive);
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Vertex, IOType::InOut}, {Triple::Geometry, IOType::InOut},
-        {Triple::Hull, IOType::All},     {Triple::Domain, IOType::All},
-        {Triple::Pixel, IOType::In},     {Triple::Mesh, OutOrPatchConstant},
+        {Triple::Vertex, IOType::InOut, SemanticInterpretation::Arbitrary},
+        {Triple::Geometry, IOType::InOut, SemanticInterpretation::Arbitrary},
+        {Triple::Hull, IOType::All, SemanticInterpretation::Arbitrary},
+        {Triple::Domain, IOType::All, SemanticInterpretation::Arbitrary},
+        {Triple::Pixel, IOType::In, SemanticInterpretation::Arbitrary},
+        {Triple::Mesh, OutOrPatchConstant, SemanticInterpretation::Arbitrary},
     };
     return Stages;
   }
@@ -73,43 +76,56 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   case dxbc::PSV::SemanticKind::GroupIndex:
   case dxbc::PSV::SemanticKind::GroupThreadID: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Compute, IOType::In}};
+        {Triple::Compute, IOType::In, SemanticInterpretation::NotAllocated}};
     return Stages;
   }
   case dxbc::PSV::SemanticKind::Target: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Pixel, IOType::Out}};
+        {Triple::Pixel, IOType::Out, SemanticInterpretation::Target}};
     return Stages;
   }
   case dxbc::PSV::SemanticKind::VertexID: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Vertex, IOType::In}};
+        {Triple::Vertex, IOType::In, SemanticInterpretation::SV}};
     return Stages;
   }
   case dxbc::PSV::SemanticKind::IsFrontFace: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Geometry, IOType::Out}, {Triple::Pixel, IOType::In}};
+        {Triple::Geometry, IOType::Out, SemanticInterpretation::SGV},
+        {Triple::Pixel, IOType::In, SemanticInterpretation::SGV}};
     return Stages;
   }
   case dxbc::PSV::SemanticKind::Position: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Vertex, IOType::InOut}, {Triple::Pixel, IOType::In}};
+        {Triple::Vertex, IOType::In, SemanticInterpretation::Arbitrary},
+        {Triple::Vertex, IOType::Out, SemanticInterpretation::SV},
+        {Triple::Pixel, IOType::In, SemanticInterpretation::SV}};
     return Stages;
   }
   case dxbc::PSV::SemanticKind::ClipDistance:
   case dxbc::PSV::SemanticKind::CullDistance: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Vertex, IOType::InOut}, {Triple::Hull, IOType::All},
-        {Triple::Domain, IOType::All},   {Triple::Geometry, IOType::InOut},
-        {Triple::Pixel, IOType::In},     {Triple::Mesh, IOType::Out},
+        {Triple::Vertex, IOType::In, SemanticInterpretation::Arbitrary},
+        {Triple::Vertex, IOType::Out, SemanticInterpretation::ClipCull},
+        {Triple::Hull, IOType::InOut, SemanticInterpretation::ClipCull},
+        {Triple::Hull, IOType::PatchConstantOrPrimitive,
+         SemanticInterpretation::Arbitrary},
+        {Triple::Domain, IOType::InOut, SemanticInterpretation::ClipCull},
+        {Triple::Domain, IOType::PatchConstantOrPrimitive,
+         SemanticInterpretation::Arbitrary},
+        {Triple::Geometry, IOType::InOut, SemanticInterpretation::ClipCull},
+        {Triple::Pixel, IOType::In, SemanticInterpretation::ClipCull},
+        {Triple::Mesh, IOType::Out, SemanticInterpretation::ClipCull},
     };
     return Stages;
   }
   case dxbc::PSV::SemanticKind::TessFactor:
   case dxbc::PSV::SemanticKind::InsideTessFactor: {
     static constexpr SemanticStageInfo Stages[] = {
-        {Triple::Hull, IOType::PatchConstantOrPrimitive},
-        {Triple::Domain, IOType::PatchConstantOrPrimitive},
+        {Triple::Hull, IOType::PatchConstantOrPrimitive,
+         SemanticInterpretation::TessFactor},
+        {Triple::Domain, IOType::PatchConstantOrPrimitive,
+         SemanticInterpretation::TessFactor},
     };
     return Stages;
   }

>From e22de0a6a70d27808e867748a216a9b08381935f Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 18:10:44 +0000
Subject: [PATCH 15/28] add kind helper

---
 llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h | 4 ++++
 llvm/lib/Frontend/HLSL/SemanticSignatures.cpp        | 9 +++++++++
 2 files changed, 13 insertions(+)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
index 30d04f0da098e..8e355a05cb86c 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignatures.h
@@ -135,6 +135,10 @@ LLVM_ABI dxbc::PSV::SemanticKind getSemanticKind(StringRef SemanticName);
 LLVM_ABI ArrayRef<SemanticStageInfo>
 getAvailableStages(dxbc::PSV::SemanticKind SemanticKind);
 
+LLVM_ABI SemanticInterpretation
+getInterpretationKind(dxbc::PSV::SemanticKind SemanticKind,
+                      Triple::EnvironmentType ShaderStage, IOType IOTy);
+
 } // namespace hlsl
 } // namespace llvm
 
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
index 9361f57115f5b..7a0e3a21564d4 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignatures.cpp
@@ -135,6 +135,15 @@ hlsl::getAvailableStages(dxbc::PSV::SemanticKind SemanticKind) {
   }
 }
 
+SemanticInterpretation
+hlsl::getInterpretationKind(dxbc::PSV::SemanticKind SemanticKind,
+                            Triple::EnvironmentType ShaderStage, IOType IOTy) {
+  for (const SemanticStageInfo &Info : getAvailableStages(SemanticKind))
+    if (Info.Stage == ShaderStage && (Info.AllowedIOTypesMask & IOTy))
+      return Info.Interpretation;
+  return SemanticInterpretation::Invalid;
+}
+
 Expected<SemanticSignatureElement>
 SemanticSignatureElement::fromMetadata(const MDNode *Node) {
   // Operand positions within a signature element metadata node.

>From 11ef54bdc4fdbb1084f4c6b043a54beab6e52122 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 18:17:04 +0000
Subject: [PATCH 16/28] nfc: refactor semahlsl to use look-up table

---
 clang/include/clang/Sema/SemaHLSL.h           |  3 +-
 clang/lib/Sema/SemaHLSL.cpp                   | 73 +++++++++----------
 .../CodeGenHLSL/semantics/SV_Position.ps.hlsl |  4 +-
 .../semantics/semantic.array.output.hlsl      |  2 +-
 .../Availability/attr-availability-pixel.hlsl |  2 +-
 .../Semantics/arbitrary.ps.output.hlsl        |  7 ++
 .../Semantics/position.ps.struct.hlsl         |  5 +-
 .../Semantics/position.ps.struct.reuse.hlsl   |  5 +-
 .../SemaHLSL/Semantics/target.ps.input.hlsl   |  2 +-
 clang/test/SemaHLSL/num_threads.hlsl          |  7 +-
 clang/test/SemaHLSL/shader_type_attr.hlsl     |  2 +-
 11 files changed, 59 insertions(+), 53 deletions(-)
 create mode 100644 clang/test/SemaHLSL/Semantics/arbitrary.ps.output.hlsl

diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 0f834df21c40d..dcf165be082a2 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -20,6 +20,7 @@
 #include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Sema/SemaBase.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringSet.h"
@@ -333,7 +334,7 @@ class SemaHLSL : public SemaBase {
   void diagnoseSemanticStageMismatch(
       const Attr *A, llvm::Triple::EnvironmentType Stage,
       llvm::hlsl::IOType CurrentIOType,
-      std::initializer_list<llvm::hlsl::SemanticStageInfo> AllowedStages);
+      llvm::ArrayRef<llvm::hlsl::SemanticStageInfo> AllowedStages);
 
   void handleGlobalStructOrArrayOfWithResources(VarDecl *VD);
 
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 5ed6d4632d8ee..a678df9e4774b 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -1080,15 +1080,19 @@ void SemaHLSL::checkSemanticAnnotation(
   assert(ShaderAttr && "Entry point has no shader attribute");
   llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
 
-  auto SemanticName = SemanticAttr->getSemanticName().upper();
-  if (SemanticName == "SV_DISPATCHTHREADID" ||
-      SemanticName == "SV_GROUPINDEX" || SemanticName == "SV_GROUPTHREADID" ||
-      SemanticName == "SV_GROUPID") {
-
-    if (ST != llvm::Triple::Compute)
-      diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
-                                    {{llvm::Triple::Compute, IOType::In}});
+  llvm::dxbc::PSV::SemanticKind SemanticKind =
+      llvm::hlsl::getSemanticKind(SemanticAttr->getSemanticName());
+  llvm::hlsl::SemanticInterpretation Interpretation =
+      llvm::hlsl::getInterpretationKind(SemanticKind, ST, SC.CurrentIOType);
+  if (Interpretation == llvm::hlsl::SemanticInterpretation::Invalid)
+    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
+                                  llvm::hlsl::getAvailableStages(SemanticKind));
 
+  switch (SemanticKind) {
+  case llvm::dxbc::PSV::SemanticKind::DispatchThreadID:
+  case llvm::dxbc::PSV::SemanticKind::GroupID:
+  case llvm::dxbc::PSV::SemanticKind::GroupIndex:
+  case llvm::dxbc::PSV::SemanticKind::GroupThreadID:
     if (SemanticAttr->getSemanticIndex() != 0) {
       std::string PrettyName =
           "'" + SemanticAttr->getSemanticName().str() + "'";
@@ -1096,33 +1100,10 @@ void SemaHLSL::checkSemanticAnnotation(
            diag::err_hlsl_semantic_indexing_not_supported)
           << PrettyName;
     }
-    return;
-  }
-
-  if (SemanticName == "SV_POSITION") {
-    // SV_Position can be an input or output in vertex shaders,
-    // but only an input in pixel shaders.
-    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
-                                  {{llvm::Triple::Vertex, IOType::InOut},
-                                   {llvm::Triple::Pixel, IOType::In}});
-    return;
-  }
-  if (SemanticName == "SV_VERTEXID") {
-    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
-                                  {{llvm::Triple::Vertex, IOType::In}});
-    return;
-  }
-
-  if (SemanticName == "SV_TARGET") {
-    diagnoseSemanticStageMismatch(SemanticAttr, ST, SC.CurrentIOType,
-                                  {{llvm::Triple::Pixel, IOType::Out}});
-    return;
+    break;
+  default:
+    break;
   }
-
-  // FIXME: catch-all for non-implemented system semantics reaching this
-  // location.
-  if (SemanticAttr->getAttrName()->getName().starts_with_insensitive("SV_"))
-    llvm_unreachable("Unknown SemanticAttr");
 }
 
 void SemaHLSL::diagnoseAttrStageMismatch(
@@ -1141,9 +1122,21 @@ void SemaHLSL::diagnoseAttrStageMismatch(
 
 void SemaHLSL::diagnoseSemanticStageMismatch(
     const Attr *A, llvm::Triple::EnvironmentType Stage, IOType CurrentIOType,
-    std::initializer_list<SemanticStageInfo> Allowed) {
+    ArrayRef<SemanticStageInfo> Allowed) {
+  SmallVector<SemanticStageInfo, 8> CombinedAllowed;
+  for (const SemanticStageInfo &Case : Allowed) {
+    auto It = llvm::find_if(CombinedAllowed, [&](SemanticStageInfo Info) {
+      return Info.Stage == Case.Stage;
+    });
+    if (It == CombinedAllowed.end()) {
+      CombinedAllowed.push_back(Case);
+      continue;
+    }
+    It->AllowedIOTypesMask =
+        static_cast<IOType>(It->AllowedIOTypesMask | Case.AllowedIOTypesMask);
+  }
 
-  for (auto &Case : Allowed) {
+  for (auto &Case : CombinedAllowed) {
     if (Case.Stage != Stage)
       continue;
 
@@ -1152,7 +1145,8 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
 
     SmallVector<std::string, 8> ValidCases;
     llvm::transform(
-        Allowed, std::back_inserter(ValidCases), [](SemanticStageInfo Case) {
+        CombinedAllowed, std::back_inserter(ValidCases),
+        [](SemanticStageInfo Case) {
           SmallVector<std::string, 2> ValidType;
           if (Case.AllowedIOTypesMask & IOType::In)
             ValidType.push_back("input");
@@ -1178,14 +1172,15 @@ void SemaHLSL::diagnoseSemanticStageMismatch(
 
   SmallVector<StringRef, 8> StageStrings;
   llvm::transform(
-      Allowed, std::back_inserter(StageStrings), [](SemanticStageInfo Case) {
+      CombinedAllowed, std::back_inserter(StageStrings),
+      [](SemanticStageInfo Case) {
         return StringRef(
             HLSLShaderAttr::ConvertEnvironmentTypeToStr(Case.Stage));
       });
 
   Diag(A->getLoc(), diag::err_hlsl_attr_unsupported_in_stage)
       << A->getAttrName() << llvm::Triple::getEnvironmentTypeName(Stage)
-      << (Allowed.size() != 1) << join(StageStrings, ", ");
+      << (CombinedAllowed.size() != 1) << join(StageStrings, ", ");
 }
 
 template <CastKind Kind>
diff --git a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
index 095532863ac5a..b1a0a2b16c3d1 100644
--- a/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
@@ -4,10 +4,10 @@
 // CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
 
 // CHECK: define void @main() {{.*}} {
-float4 main(float4 p : SV_Position) : A {
+float4 main(float4 p : SV_Position) : SV_Target {
   // CHECK-SPIRV: %[[P:.*]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 4
   // CHECK-SPIRV: %[[R:.*]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[P]])
-  // CHECK-SPIRV:            store <4 x float> %[[R]], ptr addrspace(8) @A0, align 4
+  // CHECK-SPIRV:            store <4 x float> %[[R]], ptr addrspace(8) @SV_Target0, align 4
 
   // CHECK-DXIL: %[[INPUT:.*]] = call <4 x float> @llvm.dx.load.input.v4f32(i32 0, i32 0, i8 0, i32 poison)
   // CHECK-DXIL: %[[RESULT:.*]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %[[INPUT]])
diff --git a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
index 5427a569e5eee..8a8ba0f52bc1e 100644
--- a/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
+++ b/clang/test/CodeGenHLSL/semantics/semantic.array.output.hlsl
@@ -8,7 +8,7 @@ struct S0 {
 
 // CHECK-SPIRV-DAG:    @A0 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#METADATA_0:]]
 
-[shader("pixel")]
+[shader("vertex")]
 S0 main1(float4 input : A) : B {
 // CHECK:         %[[ARG:.*]] = alloca %struct.S0
 // CHECK-SPIRV: %[[INPUT:.*]] = load <4 x float>, ptr addrspace(7) @A0, align 4
diff --git a/clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl b/clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl
index 83c49738f8810..0f24f7f10f980 100644
--- a/clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl
+++ b/clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl
@@ -36,7 +36,7 @@ __attribute__((availability(shadermodel, introduced = 5.0, environment = compute
 __attribute__((availability(shadermodel, introduced = 6.0, environment = mesh)))
 unsigned f8();
 
-int main() : A {
+float main() : SV_Target {
     // expected-error@#f1_call {{'f1' is only available on Shader Model 6.0 or newer}}
     // expected-note@#f1 {{'f1' has been marked as being introduced in Shader Model 6.0 here, but the deployment target is Shader Model 5.0}}
     unsigned A = f1(); // #f1_call
diff --git a/clang/test/SemaHLSL/Semantics/arbitrary.ps.output.hlsl b/clang/test/SemaHLSL/Semantics/arbitrary.ps.output.hlsl
new file mode 100644
index 0000000000000..308bb48b096ba
--- /dev/null
+++ b/clang/test/SemaHLSL/Semantics/arbitrary.ps.output.hlsl
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -finclude-default-header -x hlsl -verify -o - %s
+// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header -x hlsl -verify -o - %s
+
+float4 main(float4 a : A) : B {
+// expected-error at -1 {{semantic 'B' is unsupported in pixel shaders as output}}
+  return a;
+}
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl b/clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl
index 213a53e30155b..d8fdd58ba0855 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.struct.hlsl
@@ -9,13 +9,12 @@ struct S {
 // CHECK-NEXT:  HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <col:15> "SV_Position" 3
 };
 
-// FIXME(Keenuts): add mandatory output semantic once those are implemented.
-float4 main(S s) : B {
+float4 main(S s) : SV_Target {
 // CHECK: FunctionDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> line:[[@LINE-1]]:8 main 'float4 (S)'
 // CHECK-NEXT: ParmVarDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:15 used s 'S'
 // CHECK-NEXT:  HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <line:4:15> "SV_Position" 0
 // CHECK-NEXT:  HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <line:7:15> "SV_Position" 3
 
-// CHECK:       HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:20> "B" 0
+// CHECK:       HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:20> "SV_Target" 0
   return s.f1;
 }
diff --git a/clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl b/clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl
index d10c817d53af2..532d73131895b 100644
--- a/clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl
+++ b/clang/test/SemaHLSL/Semantics/position.ps.struct.reuse.hlsl
@@ -16,14 +16,13 @@ struct Top {
 };
 
 
-// FIXME(Keenuts): add mandatory output semantic once those are implemented.
-float4 main(Top s : D) : F4 {
+float4 main(Top s : D) : SV_Target {
 // CHECK: FunctionDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> line:[[@LINE-1]]:8 main 'float4 (Top)'
 // CHECK-NEXT: ParmVarDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:17 used s 'Top'
 // CHECK-NEXT:  HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <col:21> "D" 0
 // CHECK-NEXT:  HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:21> "D" 0
 // CHECK-NEXT:  HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:21> "D" 1
 
-// CHECK:       HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:26> "F" 4
+// CHECK:       HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:26> "SV_Target" 0
   return s.f0.x;
 }
diff --git a/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
index a77b46c0e9f1a..87972640e294a 100644
--- a/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
+++ b/clang/test/SemaHLSL/Semantics/target.ps.input.hlsl
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -finclude-default-header -x hlsl -verify -o - %s
 // RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header -x hlsl -verify -o - %s
 
-float4 main(float4 a : SV_Target) : A {
+float4 main(float4 a : SV_Target) : SV_Target {
 // expected-error at -1 {{semantic 'SV_Target' is unsupported in pixel shaders as input, requires one of the following: pixel out}}
   return a;
 }
diff --git a/clang/test/SemaHLSL/num_threads.hlsl b/clang/test/SemaHLSL/num_threads.hlsl
index 52e71ec458161..6713af80ba9d6 100644
--- a/clang/test/SemaHLSL/num_threads.hlsl
+++ b/clang/test/SemaHLSL/num_threads.hlsl
@@ -130,7 +130,12 @@ int largeZ();
 #endif
 // expected-error-re at +1 {{attribute 'numthreads' is unsupported in '{{[A-Za-z]+}}' shaders, requires one of the following: compute, amplification, mesh}}
 [numthreads(1,1,1)]
-int main() : A {
+#if __SHADER_TARGET_STAGE == __SHADER_STAGE_PIXEL
+float main() : SV_Target
+#else
+int main() : A
+#endif
+{
  return 1;
 }
 
diff --git a/clang/test/SemaHLSL/shader_type_attr.hlsl b/clang/test/SemaHLSL/shader_type_attr.hlsl
index 5f30a520b7255..c0c3ec4fe0e6d 100644
--- a/clang/test/SemaHLSL/shader_type_attr.hlsl
+++ b/clang/test/SemaHLSL/shader_type_attr.hlsl
@@ -31,7 +31,7 @@ static void oops() {}
 [shader("pixel")]
 // expected-note at +1 {{conflicting attribute is here}}
 [shader("vertex")]
-int doubledUp() : A {
+float doubledUp() : SV_Target {
   return 1;
 }
 

>From 0c4a469003efa74ed82544bb96db8898b2fc75d6 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 18:21:46 +0000
Subject: [PATCH 17/28] nfc: refactor cghlsl to not do logic on the names

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp | 132 ++++++++++++++--------------
 clang/lib/CodeGen/CGHLSLRuntime.h   |  12 +--
 2 files changed, 74 insertions(+), 70 deletions(-)

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 05f755edde64b..66178493a9ccc 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -1435,18 +1435,17 @@ void CGHLSLRuntime::emitUserSemanticStore(IRBuilder<> &B, llvm::Value *Source,
 }
 
 llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
-    IRBuilder<> &B, const FunctionDecl *FD, llvm::Type *Type,
-    const clang::DeclaratorDecl *Decl, HLSLAppliedSemanticAttr *Semantic,
-    std::optional<unsigned> Index, SemanticSignatures &Signature) {
-
-  std::string SemanticName = Semantic->getAttrName()->getName().upper();
-  if (SemanticName == "SV_GROUPINDEX") {
+    IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
+    HLSLAppliedSemanticAttr *Semantic,
+    llvm::dxbc::PSV::SemanticKind SemanticKind, std::optional<unsigned> Index,
+    SemanticSignatures &Signature) {
+  switch (SemanticKind) {
+  case llvm::dxbc::PSV::SemanticKind::GroupIndex: {
     llvm::Function *GroupIndex =
         CGM.getIntrinsic(getFlattenedThreadIdInGroupIntrinsic());
     return B.CreateCall(FunctionCallee(GroupIndex));
   }
-
-  if (SemanticName == "SV_DISPATCHTHREADID") {
+  case llvm::dxbc::PSV::SemanticKind::DispatchThreadID: {
     llvm::Intrinsic::ID IntrinID = getThreadIdIntrinsic();
     llvm::Function *ThreadIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -1454,8 +1453,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
             : CGM.getIntrinsic(IntrinID);
     return buildVectorInput(B, ThreadIDIntrinsic, Type);
   }
-
-  if (SemanticName == "SV_GROUPTHREADID") {
+  case llvm::dxbc::PSV::SemanticKind::GroupThreadID: {
     llvm::Intrinsic::ID IntrinID = getGroupThreadIdIntrinsic();
     llvm::Function *GroupThreadIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -1463,8 +1461,7 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
             : CGM.getIntrinsic(IntrinID);
     return buildVectorInput(B, GroupThreadIDIntrinsic, Type);
   }
-
-  if (SemanticName == "SV_GROUPID") {
+  case llvm::dxbc::PSV::SemanticKind::GroupID: {
     llvm::Intrinsic::ID IntrinID = getGroupIdIntrinsic();
     llvm::Function *GroupIDIntrinsic =
         llvm::Intrinsic::isOverloaded(IntrinID)
@@ -1472,38 +1469,26 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
             : CGM.getIntrinsic(IntrinID);
     return buildVectorInput(B, GroupIDIntrinsic, Type);
   }
-
-  const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
-  assert(ShaderAttr && "Entry point has no shader attribute");
-  llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
-
-  if (SemanticName == "SV_POSITION") {
-    if (ST == Triple::EnvironmentType::Pixel) {
-      if (CGM.getTarget().getTriple().isSPIRV())
-        return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
-                                      Semantic->getAttrName()->getName(),
-                                      /* BuiltIn::FragCoord */ 15);
-      if (CGM.getTarget().getTriple().isDXIL())
-        return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index,
-                                        Signature);
-    }
-
-    if (ST == Triple::EnvironmentType::Vertex) {
-      return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index,
-                                  Signature);
-    }
-  }
-
-  if (SemanticName == "SV_VERTEXID") {
-    if (ST == Triple::EnvironmentType::Vertex) {
-      if (CGM.getTarget().getTriple().isSPIRV())
-        return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
-                                      Semantic->getAttrName()->getName(),
-                                      /* BuiltIn::VertexIndex */ 42);
-      else
-        return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index,
-                                        Signature);
-    }
+  case llvm::dxbc::PSV::SemanticKind::Position:
+    if (CGM.getTarget().getTriple().isSPIRV())
+      return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
+                                    Semantic->getAttrName()->getName(),
+                                    /* BuiltIn::FragCoord */ 15);
+    if (CGM.getTarget().getTriple().isDXIL())
+      return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index,
+                                      Signature);
+    break;
+  case llvm::dxbc::PSV::SemanticKind::VertexID:
+    if (CGM.getTarget().getTriple().isSPIRV())
+      return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
+                                    Semantic->getAttrName()->getName(),
+                                    /* BuiltIn::VertexIndex */ 42);
+    if (CGM.getTarget().getTriple().isDXIL())
+      return emitDXILUserSemanticLoad(B, Type, Decl, Semantic, Index,
+                                      Signature);
+    break;
+  default:
+    break;
   }
 
   llvm_unreachable(
@@ -1524,30 +1509,29 @@ static void createSPIRVBuiltinStore(IRBuilder<> &B, llvm::Module &M,
   B.CreateStore(Source, GV);
 }
 
-void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
-                                            const clang::DeclaratorDecl *Decl,
-                                            HLSLAppliedSemanticAttr *Semantic,
-                                            std::optional<unsigned> Index,
-                                            SemanticSignatures &Signature) {
-
-  std::string SemanticName = Semantic->getAttrName()->getName().upper();
-  if (SemanticName == "SV_POSITION") {
+void CGHLSLRuntime::emitSystemSemanticStore(
+    IRBuilder<> &B, llvm::Value *Source, const clang::DeclaratorDecl *Decl,
+    HLSLAppliedSemanticAttr *Semantic,
+    llvm::dxbc::PSV::SemanticKind SemanticKind, std::optional<unsigned> Index,
+    SemanticSignatures &Signature) {
+  switch (SemanticKind) {
+  case llvm::dxbc::PSV::SemanticKind::Position:
     if (CGM.getTarget().getTriple().isDXIL()) {
       emitDXILUserSemanticStore(B, Source, Decl, Semantic, Index, Signature);
       return;
     }
-
     if (CGM.getTarget().getTriple().isSPIRV()) {
       createSPIRVBuiltinStore(B, CGM.getModule(), Source,
                               Semantic->getAttrName()->getName(),
                               /* BuiltIn::Position */ 0);
       return;
     }
-  }
-
-  if (SemanticName == "SV_TARGET") {
+    break;
+  case llvm::dxbc::PSV::SemanticKind::Target:
     emitUserSemanticStore(B, Source, Decl, Semantic, Index, Signature);
     return;
+  default:
+    break;
   }
 
   llvm_unreachable(
@@ -1560,10 +1544,19 @@ llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
     SemanticSignatures &Signature) {
 
   std::optional<unsigned> Index = Semantic->getSemanticIndex();
-  if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
-    return emitSystemSemanticLoad(B, FD, Type, Decl, Semantic, Index,
-                                  Signature);
-  return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index, Signature);
+  llvm::dxbc::PSV::SemanticKind SemanticKind =
+      llvm::hlsl::getSemanticKind(Semantic->getAttrName()->getName());
+  const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
+  assert(ShaderAttr && "Entry point has no shader attribute");
+  llvm::hlsl::SemanticInterpretation Interpretation =
+      llvm::hlsl::getInterpretationKind(SemanticKind, ShaderAttr->getType(),
+                                        llvm::hlsl::IOType::In);
+  if (Interpretation == llvm::hlsl::SemanticInterpretation::Invalid)
+    llvm_unreachable("invalid semantic should have been diagnosed by Sema");
+  if (Interpretation == llvm::hlsl::SemanticInterpretation::Arbitrary)
+    return emitUserSemanticLoad(B, FD, Type, Decl, Semantic, Index, Signature);
+  return emitSystemSemanticLoad(B, Type, Decl, Semantic, SemanticKind, Index,
+                                Signature);
 }
 
 void CGHLSLRuntime::handleScalarSemanticStore(IRBuilder<> &B,
@@ -1573,10 +1566,21 @@ void CGHLSLRuntime::handleScalarSemanticStore(IRBuilder<> &B,
                                               HLSLAppliedSemanticAttr *Semantic,
                                               SemanticSignatures &Signature) {
   std::optional<unsigned> Index = Semantic->getSemanticIndex();
-  if (Semantic->getAttrName()->getName().starts_with_insensitive("SV_"))
-    emitSystemSemanticStore(B, Source, Decl, Semantic, Index, Signature);
-  else
-    emitUserSemanticStore(B, Source, Decl, Semantic, Index, Signature);
+  llvm::dxbc::PSV::SemanticKind SemanticKind =
+      llvm::hlsl::getSemanticKind(Semantic->getAttrName()->getName());
+  const auto *ShaderAttr = FD->getAttr<HLSLShaderAttr>();
+  assert(ShaderAttr && "Entry point has no shader attribute");
+
+  llvm::hlsl::SemanticInterpretation Interpretation =
+      llvm::hlsl::getInterpretationKind(SemanticKind, ShaderAttr->getType(),
+                                        llvm::hlsl::IOType::Out);
+  assert(Interpretation != llvm::hlsl::SemanticInterpretation::Invalid &&
+         "invalid semantic should have been diagnosed by Sema");
+
+  if (Interpretation == llvm::hlsl::SemanticInterpretation::Arbitrary)
+    return emitUserSemanticStore(B, Source, Decl, Semantic, Index, Signature);
+  emitSystemSemanticStore(B, Source, Decl, Semantic, SemanticKind, Index,
+                          Signature);
 }
 
 std::pair<llvm::Value *, specific_attr_iterator<HLSLAppliedSemanticAttr>>
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 2f251505cfa3e..29d085b2c76e0 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -222,16 +222,16 @@ class CGHLSLRuntime {
 
   CodeGenModule &CGM;
 
-  llvm::Value *emitSystemSemanticLoad(llvm::IRBuilder<> &B,
-                                      const FunctionDecl *FD, llvm::Type *Type,
-                                      const clang::DeclaratorDecl *Decl,
-                                      HLSLAppliedSemanticAttr *Semantic,
-                                      std::optional<unsigned> Index,
-                                      SemanticSignatures &Signature);
+  llvm::Value *emitSystemSemanticLoad(
+      llvm::IRBuilder<> &B, llvm::Type *Type, const clang::DeclaratorDecl *Decl,
+      HLSLAppliedSemanticAttr *Semantic,
+      llvm::dxbc::PSV::SemanticKind SemanticKind, std::optional<unsigned> Index,
+      SemanticSignatures &Signature);
 
   void emitSystemSemanticStore(llvm::IRBuilder<> &B, llvm::Value *Source,
                                const clang::DeclaratorDecl *Decl,
                                HLSLAppliedSemanticAttr *Semantic,
+                               llvm::dxbc::PSV::SemanticKind SemanticKind,
                                std::optional<unsigned> Index,
                                SemanticSignatures &Signature);
 

>From 73a666ce1a12e2d2e4a46d2430404a628b7f34a0 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 18/28] 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 82a3418db7fa040579a50c99749dfceec5bf1a1e 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 19/28] 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 75c7969edafc78b35695a63592d9d2a1adebb7e6 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 20/28] 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 ae2d3c3faaded0a1c5f7d58bb010d3199e6057b6 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 21/28] 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 2358932c90d125608029b175ade924d58383d56c 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 22/28] 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 3dcda307785d7bdb4df57dc85e83eb6b2b85f675 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 23/28] document stacked signature packing

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

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

>From 57cd7cb6c41f175ef506f4b3f564254822cc1a42 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:13:01 +0000
Subject: [PATCH 24/28] add a stub indexed signature packing API

---
 .../llvm/Frontend/HLSL/SemanticSignaturePacking.h     | 11 +++++++++++
 llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp   |  6 ++++++
 2 files changed, 17 insertions(+)

diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index 14cd477994d68..dacbedbd02d6e 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -57,6 +57,17 @@ LLVM_ABI Error
 packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
                      Triple::EnvironmentType ShaderStage, IOType IOTy);
 
+/// Packs each eligible element at the row denoted by its semantic index and at
+/// column zero. Declaration order does not affect placement, and gaps between
+/// semantic indices remain unused. An element is left unallocated if it is not
+/// part of the signature.
+///
+/// Returns a SignaturePackingError that denotes the first element that cannot
+/// be placed, or success if all eligible elements were placed.
+LLVM_ABI Error
+packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
+                     Triple::EnvironmentType ShaderStage, IOType IOTy);
+
 } // namespace llvm::hlsl
 
 #endif // LLVM_FRONTEND_HLSL_SEMANTICSIGNATUREPACKING_H
diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 59289497dce2b..2c17809e1c4f5 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -61,3 +61,9 @@ Error llvm::hlsl::packSignatureStacked(
 
   return Error::success();
 }
+
+Error llvm::hlsl::packSignatureIndexed(
+    MutableArrayRef<SemanticSignatureElement>, Triple::EnvironmentType,
+    IOType) {
+  return Error::success();
+}

>From 6556437921616a38d2a727d1e643f41209950886 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:14:13 +0000
Subject: [PATCH 25/28] add indexed signature packing tests

---
 .../HLSLSemanticSignaturePackingTest.cpp      | 126 ++++++++++++++++--
 1 file changed, 118 insertions(+), 8 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index a1fd495638613..210a4763d4db4 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -27,6 +27,7 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     uint8_t Cols;
     dxil::ElementType CompType;
     dxbc::PSV::InterpolationMode InterpMode;
+    uint32_t SemanticIndex = 0;
   };
 
   struct ExpectedLocation {
@@ -53,7 +54,7 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     for (const ElementConfig &Element : Config.Elements) {
       SmallVector<uint32_t> SemanticIndices;
       for (uint32_t Row = 0; Row != Element.Rows; ++Row)
-        SemanticIndices.push_back(Row);
+        SemanticIndices.push_back(Element.SemanticIndex + Row);
 
       Elements.emplace_back(
           /*SigId=*/static_cast<uint32_t>(Elements.size()),
@@ -72,12 +73,20 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
   }
 
-  void expectPacking(const TestConfig &Config, unsigned ExpectedRows,
-                     std::initializer_list<ExpectedLocation> Locations) {
+  Error packIndexed(SmallVectorImpl<SemanticSignatureElement> &Elements,
+                    const TestConfig &Config) {
+    return packSignatureIndexed(Elements, Config.ShaderStage, Config.IOTy);
+  }
+
+  void expectPackingImpl(const TestConfig &Config, unsigned ExpectedRows,
+                         std::initializer_list<ExpectedLocation> Locations,
+                         bool Indexed) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
     ASSERT_EQ(Elements.size(), Locations.size());
 
-    ASSERT_THAT_ERROR(packStacked(Elements, Config), Succeeded());
+    Error E =
+        Indexed ? packIndexed(Elements, Config) : packStacked(Elements, Config);
+    ASSERT_THAT_ERROR(std::move(E), Succeeded());
 
     unsigned Rows = 0;
     for (const SemanticSignatureElement &Element : Elements)
@@ -93,11 +102,22 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     }
   }
 
-  void expectPackingError(const TestConfig &Config,
-                          SignaturePackingError::ErrorKind ExpectedKind,
-                          unsigned ExpectedElementIndex) {
+  void expectPacking(const TestConfig &Config, unsigned ExpectedRows,
+                     std::initializer_list<ExpectedLocation> Locations) {
+    expectPackingImpl(Config, ExpectedRows, Locations, /*Indexed=*/false);
+  }
+
+  void expectIndexedPacking(const TestConfig &Config, unsigned ExpectedRows,
+                            std::initializer_list<ExpectedLocation> Locations) {
+    expectPackingImpl(Config, ExpectedRows, Locations, /*Indexed=*/true);
+  }
+
+  void expectPackingErrorImpl(const TestConfig &Config,
+                              SignaturePackingError::ErrorKind ExpectedKind,
+                              unsigned ExpectedElementIndex, bool Indexed) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
-    Error E = packStacked(Elements, Config);
+    Error E =
+        Indexed ? packIndexed(Elements, Config) : packStacked(Elements, Config);
     if (!E) {
       ADD_FAILURE() << "expected a SignaturePackingError";
       return;
@@ -108,6 +128,20 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
       EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);
     });
   }
+
+  void expectPackingError(const TestConfig &Config,
+                          SignaturePackingError::ErrorKind ExpectedKind,
+                          unsigned ExpectedElementIndex) {
+    expectPackingErrorImpl(Config, ExpectedKind, ExpectedElementIndex,
+                           /*Indexed=*/false);
+  }
+
+  void expectIndexedPackingError(const TestConfig &Config,
+                                 SignaturePackingError::ErrorKind ExpectedKind,
+                                 unsigned ExpectedElementIndex) {
+    expectPackingErrorImpl(Config, ExpectedKind, ExpectedElementIndex,
+                           /*Indexed=*/true);
+  }
 };
 
 TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) {
@@ -339,4 +373,80 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) {
                      /*ExpectedElementIndex=*/1);
 }
 
+//===----------------------------------------------------------------------===//
+// Indexed packing tests
+//===----------------------------------------------------------------------===//
+
+TEST_F(HLSLSemanticSignaturePackingTest, IndexedUsesSemanticIndices) {
+  // Target elements are assigned the row denoted by their semantic index, not
+  // their declaration order. Every target starts at column zero.
+
+  // struct PSOut {
+  //   float4 Color3 : SV_Target3;
+  //   float Color0  : SV_Target0;
+  //   float2 Color2 : SV_Target2;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::Out,
+      {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/3},
+       {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/1,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/0},
+       {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/2,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/2}});
+
+  // Expected layout:
+  // reg0: Color0.x    | unused.yzw
+  // reg1: unused.xyzw
+  // reg2: Color2.xy   | unused.zw
+  // reg3: Color3.xyzw
+  expectIndexedPacking(
+      Config, /*ExpectedRows=*/4,
+      {{/*Row=*/3, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, IndexedLeavesSemanticIndexGaps) {
+  // Rows without a corresponding target semantic remain unused.
+
+  // struct PSOut {
+  //   float4 Color1 : SV_Target1;
+  //   float4 Color7 : SV_Target7;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::Out,
+      {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/1},
+       {dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/7}});
+
+  // Expected layout:
+  // reg0: unused.xyzw
+  // reg1: Color1.xyzw
+  // reg2-6: unused.xyzw
+  // reg7: Color7.xyzw
+  expectIndexedPacking(Config, /*ExpectedRows=*/8,
+                       {{/*Row=*/1, /*Col=*/0}, {/*Row=*/7, /*Col=*/0}});
+}
+
+TEST_F(HLSLSemanticSignaturePackingTest, IndexedRejectsSemanticIndexOverflow) {
+  // A semantic index outside the 32-row signature cannot be allocated.
+
+  // struct PSOut {
+  //   float4 Color32 : SV_Target32;
+  // };
+  TestConfig Config(
+      Triple::EnvironmentType::Pixel, IOType::Out,
+      {{dxbc::PSV::SemanticKind::Target, /*Rows=*/1, /*Cols=*/4,
+        dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
+        /*SemanticIndex=*/MaxSignatureRows}});
+
+  expectIndexedPackingError(Config, SignaturePackingError::SignatureOverflow,
+                            /*ExpectedElementIndex=*/0);
+}
+
 } // namespace

>From 36b3b9d7963c5aaf5a00c3a15790f1a7f6b30352 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:16:27 +0000
Subject: [PATCH 26/28] implement indexed signature packing

---
 .../HLSL/SemanticSignaturePacking.cpp         | 31 +++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
index 2c17809e1c4f5..5b25584f33539 100644
--- a/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
+++ b/llvm/lib/Frontend/HLSL/SemanticSignaturePacking.cpp
@@ -63,7 +63,34 @@ Error llvm::hlsl::packSignatureStacked(
 }
 
 Error llvm::hlsl::packSignatureIndexed(
-    MutableArrayRef<SemanticSignatureElement>, Triple::EnvironmentType,
-    IOType) {
+    MutableArrayRef<SemanticSignatureElement> Elements,
+    Triple::EnvironmentType ShaderStage, IOType IOTy) {
+  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::Target &&
+           "unexpected semantic interpretation for indexed packing");
+    assert(Element.Rows == 1 && Element.SemanticIndices.size() == 1 &&
+           "target elements must occupy one semantic row");
+
+    const uint32_t Row = Element.SemanticIndices.front();
+    if (Row >= MaxSignatureRows)
+      return make_error<SignaturePackingError>(
+          SignaturePackingError::SignatureOverflow,
+          static_cast<unsigned>(Index));
+
+    Element.StartRow = Row;
+    Element.StartCol = 0;
+  }
+
   return Error::success();
 }

>From 030ba4bdb9ae3cd98c122fc744ea0316af127ae6 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:21:45 +0000
Subject: [PATCH 27/28] document indexed signature packing

---
 llvm/docs/DirectX/SemanticSignatures.md       | 27 +++++++++++++++++++
 .../Frontend/HLSL/SemanticSignaturePacking.h  |  8 ++----
 2 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/llvm/docs/DirectX/SemanticSignatures.md b/llvm/docs/DirectX/SemanticSignatures.md
index 985a6d596a9e5..56f78a7e4599c 100644
--- a/llvm/docs/DirectX/SemanticSignatures.md
+++ b/llvm/docs/DirectX/SemanticSignatures.md
@@ -173,3 +173,30 @@ reg1: B[0].xyz   | unused.w
 reg2: B[1].xyz   | unused.w
 reg3: VertexID.x | unused.yzw
 ```
+
+### Indexed Packing
+
+Indexed packing is used for a pixel shader output signature. Each eligible
+`SV_Target` element occupies one row and starts at column zero. Its semantic
+index directly selects that row, so declaration order does not affect placement
+and rows without a corresponding semantic index remain unused. Elements that do
+not contribute to the target register space remain unallocated.
+
+For example:
+
+```hlsl
+struct PSOut {
+  float4 Color3 : SV_Target3;
+  float Color0  : SV_Target0;
+  float2 Color2 : SV_Target2;
+};
+```
+
+The signature is allocated as:
+
+```text
+reg0: Color0.x  | unused.yzw
+reg1: unused.xyzw
+reg2: Color2.xy | unused.zw
+reg3: Color3.xyzw
+```
diff --git a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
index dacbedbd02d6e..07be894251a65 100644
--- a/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
+++ b/llvm/include/llvm/Frontend/HLSL/SemanticSignaturePacking.h
@@ -57,13 +57,9 @@ LLVM_ABI Error
 packSignatureStacked(MutableArrayRef<SemanticSignatureElement> Elements,
                      Triple::EnvironmentType ShaderStage, IOType IOTy);
 
-/// Packs each eligible element at the row denoted by its semantic index and at
-/// column zero. Declaration order does not affect placement, and gaps between
-/// semantic indices remain unused. An element is left unallocated if it is not
-/// part of the signature.
+/// Packs eligible signature elements at rows selected by semantic index.
 ///
-/// 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#indexed-packing for details.
 LLVM_ABI Error
 packSignatureIndexed(MutableArrayRef<SemanticSignatureElement> Elements,
                      Triple::EnvironmentType ShaderStage, IOType IOTy);

>From b8f2ae645f21270e0718da03df356a90abfb752d Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 21 Aug 2026 20:42:32 +0000
Subject: [PATCH 28/28] simplify signature packing test harness

---
 .../HLSLSemanticSignaturePackingTest.cpp      | 100 ++++++++----------
 1 file changed, 42 insertions(+), 58 deletions(-)

diff --git a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
index 210a4763d4db4..027d6c267531a 100644
--- a/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
+++ b/llvm/unittests/Frontend/HLSLSemanticSignaturePackingTest.cpp
@@ -48,6 +48,11 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
         : ShaderStage(ShaderStage), IOTy(IOTy), Elements(Elements) {}
   };
 
+  enum class PackingMethod {
+    Stacked,
+    Indexed,
+  };
+
   SmallVector<SemanticSignatureElement>
   makeSignature(const TestConfig &Config) {
     SmallVector<SemanticSignatureElement> Elements;
@@ -68,25 +73,25 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     return Elements;
   }
 
-  Error packStacked(SmallVectorImpl<SemanticSignatureElement> &Elements,
-                    const TestConfig &Config) {
-    return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
-  }
-
-  Error packIndexed(SmallVectorImpl<SemanticSignatureElement> &Elements,
-                    const TestConfig &Config) {
-    return packSignatureIndexed(Elements, Config.ShaderStage, Config.IOTy);
+  Error pack(PackingMethod Method,
+             SmallVectorImpl<SemanticSignatureElement> &Elements,
+             const TestConfig &Config) {
+    switch (Method) {
+    case PackingMethod::Stacked:
+      return packSignatureStacked(Elements, Config.ShaderStage, Config.IOTy);
+    case PackingMethod::Indexed:
+      return packSignatureIndexed(Elements, Config.ShaderStage, Config.IOTy);
+    }
+    llvm_unreachable("invalid packing method");
   }
 
-  void expectPackingImpl(const TestConfig &Config, unsigned ExpectedRows,
-                         std::initializer_list<ExpectedLocation> Locations,
-                         bool Indexed) {
+  void expectPacking(PackingMethod Method, const TestConfig &Config,
+                     unsigned ExpectedRows,
+                     std::initializer_list<ExpectedLocation> Locations) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
     ASSERT_EQ(Elements.size(), Locations.size());
 
-    Error E =
-        Indexed ? packIndexed(Elements, Config) : packStacked(Elements, Config);
-    ASSERT_THAT_ERROR(std::move(E), Succeeded());
+    ASSERT_THAT_ERROR(pack(Method, Elements, Config), Succeeded());
 
     unsigned Rows = 0;
     for (const SemanticSignatureElement &Element : Elements)
@@ -102,22 +107,11 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
     }
   }
 
-  void expectPacking(const TestConfig &Config, unsigned ExpectedRows,
-                     std::initializer_list<ExpectedLocation> Locations) {
-    expectPackingImpl(Config, ExpectedRows, Locations, /*Indexed=*/false);
-  }
-
-  void expectIndexedPacking(const TestConfig &Config, unsigned ExpectedRows,
-                            std::initializer_list<ExpectedLocation> Locations) {
-    expectPackingImpl(Config, ExpectedRows, Locations, /*Indexed=*/true);
-  }
-
-  void expectPackingErrorImpl(const TestConfig &Config,
-                              SignaturePackingError::ErrorKind ExpectedKind,
-                              unsigned ExpectedElementIndex, bool Indexed) {
+  void expectPackingError(PackingMethod Method, const TestConfig &Config,
+                          SignaturePackingError::ErrorKind ExpectedKind,
+                          unsigned ExpectedElementIndex) {
     SmallVector<SemanticSignatureElement> Elements = makeSignature(Config);
-    Error E =
-        Indexed ? packIndexed(Elements, Config) : packStacked(Elements, Config);
+    Error E = pack(Method, Elements, Config);
     if (!E) {
       ADD_FAILURE() << "expected a SignaturePackingError";
       return;
@@ -128,20 +122,6 @@ class HLSLSemanticSignaturePackingTest : public testing::Test {
       EXPECT_EQ(PackingErr.getElementIndex(), ExpectedElementIndex);
     });
   }
-
-  void expectPackingError(const TestConfig &Config,
-                          SignaturePackingError::ErrorKind ExpectedKind,
-                          unsigned ExpectedElementIndex) {
-    expectPackingErrorImpl(Config, ExpectedKind, ExpectedElementIndex,
-                           /*Indexed=*/false);
-  }
-
-  void expectIndexedPackingError(const TestConfig &Config,
-                                 SignaturePackingError::ErrorKind ExpectedKind,
-                                 unsigned ExpectedElementIndex) {
-    expectPackingErrorImpl(Config, ExpectedKind, ExpectedElementIndex,
-                           /*Indexed=*/true);
-  }
 };
 
 TEST_F(HLSLSemanticSignaturePackingTest, CreatesSignatureFromConfig) {
@@ -204,7 +184,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, SkipsNotAllocatedElements) {
         dxil::ElementType::U32, dxbc::PSV::InterpolationMode::Undefined}});
 
   // Expected layout: no registers are used.
-  expectPacking(Config, /*ExpectedRows=*/0,
+  expectPacking(PackingMethod::Stacked, Config, /*ExpectedRows=*/0,
                 {Unallocated, Unallocated, Unallocated});
 }
 
@@ -231,7 +211,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, StacksInDeclarationOrder) {
   // reg1: Data.xy          | unused.zw
   // reg2: ClipDistance.xyz | unused.w
   expectPacking(
-      Config, /*ExpectedRows=*/3,
+      PackingMethod::Stacked, Config, /*ExpectedRows=*/3,
       {{/*Row=*/0, /*Col=*/0}, {/*Row=*/1, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
 }
 
@@ -260,7 +240,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, DoesNotCoPackElements) {
   // reg1: B.x | unused.yzw
   // reg2: C.x | unused.yzw
   // reg3: D.x | unused.yzw
-  expectPacking(Config, /*ExpectedRows=*/4,
+  expectPacking(PackingMethod::Stacked, Config, /*ExpectedRows=*/4,
                 {{/*Row=*/0, /*Col=*/0},
                  {/*Row=*/1, /*Col=*/0},
                  {/*Row=*/2, /*Col=*/0},
@@ -292,7 +272,7 @@ TEST_F(HLSLSemanticSignaturePackingTest, StacksMultiRowElements) {
   // reg4: B[1].xyz | unused.w
   // reg5: C.xyzw
   expectPacking(
-      Config, /*ExpectedRows=*/6,
+      PackingMethod::Stacked, Config, /*ExpectedRows=*/6,
       {{/*Row=*/0, /*Col=*/0}, {/*Row=*/3, /*Col=*/0}, {/*Row=*/5, /*Col=*/0}});
 }
 
@@ -310,8 +290,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, ExactlyFillsSignature) {
 
   // Expected layout:
   // reg0-31: A[0-31].xyzw
-  expectPacking(Config, /*ExpectedRows=*/MaxSignatureRows,
-                {{/*Row=*/0, /*Col=*/0}});
+  expectPacking(PackingMethod::Stacked, Config,
+                /*ExpectedRows=*/MaxSignatureRows, {{/*Row=*/0, /*Col=*/0}});
 }
 
 //===----------------------------------------------------------------------===//
@@ -334,7 +314,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSignatureOverflow) {
                                dxbc::PSV::InterpolationMode::Linear});
 
   // The last element is the one that no longer fits.
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  expectPackingError(PackingMethod::Stacked, Config,
+                     SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/MaxSignatureRows);
 }
 
@@ -350,7 +331,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsSingleElementOverflow) {
                       /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
                       dxbc::PSV::InterpolationMode::Linear}});
 
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  expectPackingError(PackingMethod::Stacked, Config,
+                     SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/0);
 }
 
@@ -369,7 +351,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, RejectsMultiRowSignatureOverflow) {
                       /*Cols=*/MaxSignatureCols, dxil::ElementType::F32,
                       dxbc::PSV::InterpolationMode::Linear}});
 
-  expectPackingError(Config, SignaturePackingError::SignatureOverflow,
+  expectPackingError(PackingMethod::Stacked, Config,
+                     SignaturePackingError::SignatureOverflow,
                      /*ExpectedElementIndex=*/1);
 }
 
@@ -403,8 +386,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, IndexedUsesSemanticIndices) {
   // reg1: unused.xyzw
   // reg2: Color2.xy   | unused.zw
   // reg3: Color3.xyzw
-  expectIndexedPacking(
-      Config, /*ExpectedRows=*/4,
+  expectPacking(
+      PackingMethod::Indexed, Config, /*ExpectedRows=*/4,
       {{/*Row=*/3, /*Col=*/0}, {/*Row=*/0, /*Col=*/0}, {/*Row=*/2, /*Col=*/0}});
 }
 
@@ -429,8 +412,8 @@ TEST_F(HLSLSemanticSignaturePackingTest, IndexedLeavesSemanticIndexGaps) {
   // reg1: Color1.xyzw
   // reg2-6: unused.xyzw
   // reg7: Color7.xyzw
-  expectIndexedPacking(Config, /*ExpectedRows=*/8,
-                       {{/*Row=*/1, /*Col=*/0}, {/*Row=*/7, /*Col=*/0}});
+  expectPacking(PackingMethod::Indexed, Config, /*ExpectedRows=*/8,
+                {{/*Row=*/1, /*Col=*/0}, {/*Row=*/7, /*Col=*/0}});
 }
 
 TEST_F(HLSLSemanticSignaturePackingTest, IndexedRejectsSemanticIndexOverflow) {
@@ -445,8 +428,9 @@ TEST_F(HLSLSemanticSignaturePackingTest, IndexedRejectsSemanticIndexOverflow) {
         dxil::ElementType::F32, dxbc::PSV::InterpolationMode::Undefined,
         /*SemanticIndex=*/MaxSignatureRows}});
 
-  expectIndexedPackingError(Config, SignaturePackingError::SignatureOverflow,
-                            /*ExpectedElementIndex=*/0);
+  expectPackingError(PackingMethod::Indexed, Config,
+                     SignaturePackingError::SignatureOverflow,
+                     /*ExpectedElementIndex=*/0);
 }
 
 } // namespace



More information about the cfe-commits mailing list