Skip to content

[HLSL] Adding support for root descriptors in root signature metadata representation #139781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 38 commits into from
Jun 4, 2025

Conversation

joaosaffran
Copy link
Contributor

  • adds parsing from metadata into dxcontainer binary
  • adds validations as described in the spec
  • adds testing scenarios
    closes: #126638

@llvmbot
Copy link
Member

llvmbot commented May 13, 2025

@llvm/pr-subscribers-backend-directx

Author: None (joaosaffran)

Changes
  • adds parsing from metadata into dxcontainer binary
  • adds validations as described in the spec
  • adds testing scenarios
    closes: #126638

Full diff: https://github.com/llvm/llvm-project/pull/139781.diff

7 Files Affected:

  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.cpp (+121-1)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.h (+2-1)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-Flags.ll (+18)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterKind.ll (+18)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterSpace.ll (+18)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterValue.ll (+18)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor.ll (+34)
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.cpp b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
index 1bd816b026fec..3ee52d32eaf2d 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.cpp
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
@@ -55,6 +55,14 @@ static std::optional<uint32_t> extractMdIntValue(MDNode *Node,
   return std::nullopt;
 }
 
+static std::optional<StringRef> extractMdStringValue(MDNode *Node,
+                                                     unsigned int OpId) {
+  MDString *NodeText = cast<MDString>(Node->getOperand(OpId));
+  if (NodeText == nullptr)
+    return std::nullopt;
+  return NodeText->getString();
+}
+
 static bool parseRootFlags(LLVMContext *Ctx, mcdxbc::RootSignatureDesc &RSD,
                            MDNode *RootFlagNode) {
 
@@ -105,6 +113,56 @@ static bool parseRootConstants(LLVMContext *Ctx, mcdxbc::RootSignatureDesc &RSD,
   return false;
 }
 
+static bool parseRootDescriptors(LLVMContext *Ctx,
+                                 mcdxbc::RootSignatureDesc &RSD,
+                                 MDNode *RootDescriptorNode) {
+
+  if (RootDescriptorNode->getNumOperands() != 5)
+    return reportError(Ctx, "Invalid format for RootConstants Element");
+
+  std::optional<StringRef> ElementText =
+      extractMdStringValue(RootDescriptorNode, 0);
+  assert(!ElementText->empty());
+
+  dxbc::RootParameterHeader Header;
+  Header.ParameterType =
+      StringSwitch<uint32_t>(*ElementText)
+          .Case("RootCBV", llvm::to_underlying(dxbc::RootParameterType::CBV))
+          .Case("RootSRV", llvm::to_underlying(dxbc::RootParameterType::SRV))
+          .Case("RootUAV", llvm::to_underlying(dxbc::RootParameterType::UAV));
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(RootDescriptorNode, 1))
+    Header.ShaderVisibility = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderVisibility");
+
+  dxbc::RTS0::v1::RootDescriptor Descriptor;
+  if (std::optional<uint32_t> Val = extractMdIntValue(RootDescriptorNode, 2))
+    Descriptor.ShaderRegister = *Val;
+  else
+    return reportError(Ctx, "Invalid value for ShaderRegister");
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(RootDescriptorNode, 3))
+    Descriptor.RegisterSpace = *Val;
+  else
+    return reportError(Ctx, "Invalid value for RegisterSpace");
+
+  if (RSD.Version == 1) {
+    RSD.ParametersContainer.addParameter(Header, Descriptor);
+    return false;
+  }
+  assert(RSD.Version > 1);
+  dxbc::RTS0::v2::RootDescriptor DescriptorV2(Descriptor);
+
+  if (std::optional<uint32_t> Val = extractMdIntValue(RootDescriptorNode, 4))
+    DescriptorV2.Flags = *Val;
+  else
+    return reportError(Ctx, "Invalid value for Root Descriptor Flags");
+
+  RSD.ParametersContainer.addParameter(Header, DescriptorV2);
+  return false;
+}
+
 static bool parseRootSignatureElement(LLVMContext *Ctx,
                                       mcdxbc::RootSignatureDesc &RSD,
                                       MDNode *Element) {
@@ -116,6 +174,9 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
       StringSwitch<RootSignatureElementKind>(ElementText->getString())
           .Case("RootFlags", RootSignatureElementKind::RootFlags)
           .Case("RootConstants", RootSignatureElementKind::RootConstants)
+          .Case("RootCBV", RootSignatureElementKind::RootDescriptors)
+          .Case("RootSRV", RootSignatureElementKind::RootDescriptors)
+          .Case("RootUAV", RootSignatureElementKind::RootDescriptors)
           .Default(RootSignatureElementKind::Error);
 
   switch (ElementKind) {
@@ -124,7 +185,8 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
     return parseRootFlags(Ctx, RSD, Element);
   case RootSignatureElementKind::RootConstants:
     return parseRootConstants(Ctx, RSD, Element);
-    break;
+  case RootSignatureElementKind::RootDescriptors:
+    return parseRootDescriptors(Ctx, RSD, Element);
   case RootSignatureElementKind::Error:
     return reportError(Ctx, "Invalid Root Signature Element: " +
                                 ElementText->getString());
@@ -155,6 +217,16 @@ static bool verifyVersion(uint32_t Version) {
   return (Version == 1 || Version == 2);
 }
 
+static bool verifyRegisterValue(uint32_t RegisterValue) {
+  return !(RegisterValue == 0xFFFFFFFF);
+}
+
+static bool verifyRegisterSpace(uint32_t RegisterSpace) {
+  return !(RegisterSpace >= 0xFFFFFFF0 && RegisterSpace <= 0xFFFFFFFF);
+}
+
+static bool verifyDescriptorFlag(uint32_t Flags) { return (Flags & ~0xE) == 0; }
+
 static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
 
   if (!verifyVersion(RSD.Version)) {
@@ -172,6 +244,39 @@ static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
 
     assert(dxbc::isValidParameterType(Info.Header.ParameterType) &&
            "Invalid value for ParameterType");
+
+    auto P = RSD.ParametersContainer.getParameter(&Info);
+    if (!P)
+      return reportError(Ctx, "Cannot locate parameter from Header Info");
+
+    if (std::holds_alternative<const dxbc::RTS0::v1::RootDescriptor *>(*P)) {
+      auto *Descriptor =
+          std::get<const dxbc::RTS0::v1::RootDescriptor *>(P.value());
+
+      if (!verifyRegisterValue(Descriptor->ShaderRegister))
+        return reportValueError(Ctx, "ShaderRegister",
+                                Descriptor->ShaderRegister);
+
+      if (!verifyRegisterSpace(Descriptor->RegisterSpace))
+        return reportValueError(Ctx, "RegisterSpace",
+                                Descriptor->RegisterSpace);
+
+    } else if (std::holds_alternative<const dxbc::RTS0::v2::RootDescriptor *>(
+                   *P)) {
+      auto *Descriptor =
+          std::get<const dxbc::RTS0::v2::RootDescriptor *>(P.value());
+
+      if (!verifyRegisterValue(Descriptor->ShaderRegister))
+        return reportValueError(Ctx, "ShaderRegister",
+                                Descriptor->ShaderRegister);
+
+      if (!verifyRegisterSpace(Descriptor->RegisterSpace))
+        return reportValueError(Ctx, "RegisterSpace",
+                                Descriptor->RegisterSpace);
+
+      if (!verifyDescriptorFlag(Descriptor->Flags))
+        return reportValueError(Ctx, "DescriptorFlag", Descriptor->Flags);
+    }
   }
 
   return false;
@@ -308,6 +413,21 @@ PreservedAnalyses RootSignatureAnalysisPrinter::run(Module &M,
            << "Shader Register: " << Constants->ShaderRegister << "\n";
         OS << indent(Space + 2)
            << "Num 32 Bit Values: " << Constants->Num32BitValues << "\n";
+      } else if (std::holds_alternative<const dxbc::RTS0::v1::RootDescriptor *>(
+                     *P)) {
+        auto *Constants = std::get<const dxbc::RTS0::v1::RootDescriptor *>(*P);
+        OS << indent(Space + 2)
+           << "Register Space: " << Constants->RegisterSpace << "\n";
+        OS << indent(Space + 2)
+           << "Shader Register: " << Constants->ShaderRegister << "\n";
+      } else if (std::holds_alternative<const dxbc::RTS0::v2::RootDescriptor *>(
+                     *P)) {
+        auto *Constants = std::get<const dxbc::RTS0::v2::RootDescriptor *>(*P);
+        OS << indent(Space + 2)
+           << "Register Space: " << Constants->RegisterSpace << "\n";
+        OS << indent(Space + 2)
+           << "Shader Register: " << Constants->ShaderRegister << "\n";
+        OS << indent(Space + 2) << "Flags: " << Constants->Flags << "\n";
       }
     }
     Space--;
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.h b/llvm/lib/Target/DirectX/DXILRootSignature.h
index 93ec614f1ab85..b8742d1b1fdfd 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.h
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.h
@@ -27,7 +27,8 @@ namespace dxil {
 enum class RootSignatureElementKind {
   Error = 0,
   RootFlags = 1,
-  RootConstants = 2
+  RootConstants = 2,
+  RootDescriptors = 3
 };
 class RootSignatureAnalysis : public AnalysisInfoMixin<RootSignatureAnalysis> {
   friend AnalysisInfoMixin<RootSignatureAnalysis>;
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-Flags.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-Flags.ll
new file mode 100644
index 0000000000000..4229981240918
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-Flags.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+
+; CHECK: error: Invalid value for DescriptorFlag: 3
+; CHECK-NOT: Root Signature Definitions
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"RootCBV", i32 0, i32 1, i32 2, i32 3  }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterKind.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterKind.ll
new file mode 100644
index 0000000000000..4aed84efbe2bc
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterKind.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+
+; CHECK: error:  Invalid Root Signature Element: Invalid 
+; CHECK-NOT: Root Signature Definitions
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"Invalid", i32 0, i32 1, i32 2, i32 3  }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterSpace.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterSpace.ll
new file mode 100644
index 0000000000000..020d117ba45dc
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterSpace.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+
+; CHECK: error:  Invalid value for RegisterSpace: 4294967280
+; CHECK-NOT: Root Signature Definitions
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"RootCBV", i32 0, i32 1, i32 4294967280, i32 0  }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterValue.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterValue.ll
new file mode 100644
index 0000000000000..edb8b943c6e35
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor-Invalid-RegisterValue.ll
@@ -0,0 +1,18 @@
+; RUN: not opt -passes='print<dxil-root-signature>' %s -S -o - 2>&1 | FileCheck %s
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+
+; CHECK: error: Invalid value for ShaderRegister: 4294967295 
+; CHECK-NOT: Root Signature Definitions
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"RootCBV", i32 0, i32 4294967295, i32 2, i32 3  }
diff --git a/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor.ll b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor.ll
new file mode 100644
index 0000000000000..9217945855cd9
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/ContainerData/RootSignature-RootDescriptor.ll
@@ -0,0 +1,34 @@
+; RUN: opt %s -dxil-embed -dxil-globals -S -o - | FileCheck %s
+; RUN: llc %s --filetype=obj -o - | obj2yaml | FileCheck %s --check-prefix=DXC
+
+target triple = "dxil-unknown-shadermodel6.0-compute"
+
+; CHECK: @dx.rts0 = private constant [48 x i8]  c"{{.*}}", section "RTS0", align 4
+
+define void @main() #0 {
+entry:
+  ret void
+}
+attributes #0 = { "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" }
+
+
+!dx.rootsignatures = !{!2} ; list of function/root signature pairs
+!2 = !{ ptr @main, !3 } ; function, root signature
+!3 = !{ !5 } ; list of root signature elements
+!5 = !{ !"RootCBV", i32 0, i32 1, i32 2, i32 8  }
+
+; DXC:  - Name:            RTS0
+; DXC-NEXT:    Size:            48
+; DXC-NEXT:    RootSignature:
+; DXC-NEXT:      Version:         2
+; DXC-NEXT:      NumRootParameters: 1 
+; DXC-NEXT:      RootParametersOffset: 24 
+; DXC-NEXT:      NumStaticSamplers: 0
+; DXC-NEXT:      StaticSamplersOffset: 0
+; DXC-NEXT:      Parameters:
+; DXC-NEXT:        - ParameterType:   2
+; DXC-NEXT:          ShaderVisibility: 0
+; DXC-NEXT:          Descriptor:
+; DXC-NEXT:            RegisterSpace: 2
+; DXC-NEXT:            ShaderRegister: 1
+; DXC-NEXT:            DATA_STATIC: true

Copy link

github-actions bot commented Jun 2, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

joaosaffran added 2 commits June 2, 2025 18:12
@joaosaffran joaosaffran requested review from inbelic and bogner June 2, 2025 18:23
Copy link
Contributor

@inbelic inbelic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than a missing test case, LGTM

return reportError(Ctx, "Invalid format for RootConstants Element");

std::optional<StringRef> ElementText =
extractMdStringValue(RootDescriptorNode, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, can we also add a test case to cover it?

StringSwitch<uint32_t>(*ElementText)
.Case("RootCBV", llvm::to_underlying(dxbc::RootParameterType::CBV))
.Case("RootSRV", llvm::to_underlying(dxbc::RootParameterType::SRV))
.Case("RootUAV", llvm::to_underlying(dxbc::RootParameterType::UAV));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, can we add a comment explaining where it was previously asserted? Or we could assert that here as well?

Comment on lines 60 to 62
MDString *NodeText = cast<MDString>(Node->getOperand(OpId));
if (NodeText == nullptr)
return std::nullopt;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look correct. cast<> will assert if the type is wrong, not return null, so this condition is unreachable (except maybe if Node itself is null?). I think you meant to use dyn_cast here.

I suspect this is why a test case like the following currently crashes:

!dx.rootsignatures = !{!0}
!0 = !{ ptr @main, !1 }
!1 = !{ !2 }
!2 = !{ i32 0 }

Copy link
Contributor

@bogner bogner Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like you fixed the crash here, but could you please add a test?

return reportError(Ctx, "Invalid format for RootConstants Element");

std::optional<StringRef> ElementText =
extractMdStringValue(RootDescriptorNode, 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that test would cover this. That test checks that the register space is in range (ie, it tests the verifyRegisterSpace function. The "first element is not a string" error here isn't reached in the current tests.

StringSwitch<uint32_t>(*ElementText)
.Case("RootCBV", llvm::to_underlying(dxbc::RootParameterType::CBV))
.Case("RootSRV", llvm::to_underlying(dxbc::RootParameterType::SRV))
.Case("RootUAV", llvm::to_underlying(dxbc::RootParameterType::UAV));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we pass ElementKind in to this function as a parameter when we call this in parseRootSignatureElement, rather than parsing the string twice?

}

static bool verifyRegisterSpace(uint32_t RegisterSpace) {
return !(RegisterSpace >= 0xFFFFFFF0 && RegisterSpace <= 0xFFFFFFFF);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment about what the condition we're checking here is? Why are specifically the largest 16 values of a uint32 invalid?

@@ -157,6 +222,16 @@ static bool verifyVersion(uint32_t Version) {
return (Version == 1 || Version == 2);
}

static bool verifyRegisterValue(uint32_t RegisterValue) {
return !(RegisterValue == 0xFFFFFFFF);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearer to use std::numeric_limits or ~0U here. Also !(x == y) is harder to understand than x != y

Comment on lines 415 to 416
case llvm::to_underlying(dxbc::RootParameterType::CBV):
case llvm::to_underlying(dxbc::RootParameterType::UAV):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clang-format, this looks off.

@joaosaffran joaosaffran requested review from bogner and inbelic June 3, 2025 17:39
@joaosaffran joaosaffran merged commit ad6575f into llvm:main Jun 4, 2025
12 checks passed
rorth pushed a commit to rorth/llvm-project that referenced this pull request Jun 11, 2025
… representation (llvm#139781)

- adds parsing from metadata into dxcontainer binary
- adds validations as described in the spec
- adds testing scenarios
closes: [llvm#126638](llvm#126638)

---------

Co-authored-by: joaosaffran <joao.saffran@microsoft.com>
DhruvSrivastavaX pushed a commit to DhruvSrivastavaX/lldb-for-aix that referenced this pull request Jun 12, 2025
… representation (llvm#139781)

- adds parsing from metadata into dxcontainer binary
- adds validations as described in the spec
- adds testing scenarios
closes: [llvm#126638](llvm#126638)

---------

Co-authored-by: joaosaffran <joao.saffran@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL] Add Root Descriptor element support to dxcontainer generation
4 participants