[llvm-branch-commits] [clang] [llvm] [HLSL][DirectX] Add support for `rootsig` as a target environment (PR #156373)

Finn Plummer via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Sep 1 14:12:46 PDT 2025


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

This pr implements support for a root signature as a target, as specified [here](https://github.com/llvm/wg-hlsl/blob/main/proposals/0029-root-signature-driver-options.md#target-root-signature-version).

This is implemented in the following steps:
1. Add `rootsignature` as a shader model environment type and define `rootsig` as a `target_profile`. Only valid as versions 1.0 and 1
2. Updates `HLSLFrontendAction` to invoke a special handling of constructing the `ASTContext` if we are considering an `hlsl` file and with a `rootsignature` target
3. Defines the special handling to minimally instantiate the `Parser` and `Sema` to insert the `RootSignatureDecl`
4. Updates `CGHLSLRuntime` to emit the constructed root signature decl as part of `dx.rootsignatures` with a `null` entry function
5. Updates `DXILRootSignature` to handle emitting a root signature without an entry function
6. Updates `ToolChains/HLSL` to invoke `only-section=RTS0` to strip any other generated information

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

##### Implementation Considerations
Ideally we could invoke this as part of `clang-dxc` without the need of a source file. However, the initialization of the `Parser` and `Lexer` becomes quite complicated to handle this.

Technically, we could avoid generating any of the extra information that is removed in step 6. However, it seems better to re-use the logic in `llvm-objcopy` without any need for additional custom logic in `DXILRootSignature`.

>From 64237b0b410049018a093a4b9d35ba7346b729dc Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 29 Aug 2025 09:42:59 -0700
Subject: [PATCH 01/11] add support for rootsig as an hlsl environment

---
 clang/include/clang/Basic/Attr.td                 | 1 +
 clang/include/clang/Driver/Options.td             | 3 ++-
 clang/lib/Driver/ToolChains/HLSL.cpp              | 5 +++++
 clang/lib/Sema/SemaHLSL.cpp                       | 4 ++++
 llvm/include/llvm/TargetParser/Triple.h           | 3 ++-
 llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp | 2 ++
 llvm/lib/TargetParser/Triple.cpp                  | 3 +++
 7 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 29364c5903d31..f9a8f2f555fbe 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1185,6 +1185,7 @@ static llvm::Triple::EnvironmentType getEnvironmentType(llvm::StringRef Environm
              .Case("callable", llvm::Triple::Callable)
              .Case("mesh", llvm::Triple::Mesh)
              .Case("amplification", llvm::Triple::Amplification)
+             .Case("rootsignature", llvm::Triple::RootSignature)
              .Case("library", llvm::Triple::Library)
              .Default(llvm::Triple::UnknownEnvironment);
 }
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index f507968d30670..0ca2baada1421 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -9430,7 +9430,8 @@ def target_profile : DXCJoinedOrSeparate<"T">, MetaVarName<"<profile>">,
          "cs_6_0, cs_6_1, cs_6_2, cs_6_3, cs_6_4, cs_6_5, cs_6_6, cs_6_7,"
          "lib_6_3, lib_6_4, lib_6_5, lib_6_6, lib_6_7, lib_6_x,"
          "ms_6_5, ms_6_6, ms_6_7,"
-         "as_6_5, as_6_6, as_6_7">;
+         "as_6_5, as_6_6, as_6_7,"
+         "rootsig_1_0, rootsig_1_1">;
 def emit_pristine_llvm : DXCFlag<"emit-pristine-llvm">,
   HelpText<"Emit pristine LLVM IR from the frontend by not running any LLVM passes at all."
            "Same as -S + -emit-llvm + -disable-llvm-passes.">;
diff --git a/clang/lib/Driver/ToolChains/HLSL.cpp b/clang/lib/Driver/ToolChains/HLSL.cpp
index 660661945d62a..304c367cc4367 100644
--- a/clang/lib/Driver/ToolChains/HLSL.cpp
+++ b/clang/lib/Driver/ToolChains/HLSL.cpp
@@ -62,6 +62,10 @@ bool isLegalShaderModel(Triple &T) {
     VersionTuple MinVer(6, 5);
     return MinVer <= Version;
   } break;
+  case Triple::EnvironmentType::RootSignature:
+    VersionTuple MinVer(1, 0);
+    VersionTuple MaxVer(1, 1);
+    return MinVer <= Version && Version <= MaxVer;
   }
   return false;
 }
@@ -84,6 +88,7 @@ std::optional<std::string> tryParseProfile(StringRef Profile) {
           .Case("lib", Triple::EnvironmentType::Library)
           .Case("ms", Triple::EnvironmentType::Mesh)
           .Case("as", Triple::EnvironmentType::Amplification)
+          .Case("rootsig", Triple::EnvironmentType::RootSignature)
           .Default(Triple::EnvironmentType::UnknownEnvironment);
   if (Kind == Triple::EnvironmentType::UnknownEnvironment)
     return std::nullopt;
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 1e5ec952c1ecf..9e99f9a1525a9 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -765,6 +765,8 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
     case llvm::Triple::UnknownEnvironment:
     case llvm::Triple::Library:
       break;
+    case llvm::Triple::RootSignature:
+      llvm_unreachable("rootsig environment has no functions");
     default:
       llvm_unreachable("Unhandled environment in triple");
     }
@@ -827,6 +829,8 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
       }
     }
     break;
+  case llvm::Triple::RootSignature:
+    llvm_unreachable("rootsig environment has no function entry point");
   default:
     llvm_unreachable("Unhandled environment in triple");
   }
diff --git a/llvm/include/llvm/TargetParser/Triple.h b/llvm/include/llvm/TargetParser/Triple.h
index ede9797ac7488..891f2de60a4d4 100644
--- a/llvm/include/llvm/TargetParser/Triple.h
+++ b/llvm/include/llvm/TargetParser/Triple.h
@@ -303,6 +303,7 @@ class Triple {
     Callable,
     Mesh,
     Amplification,
+    RootSignature,
     OpenCL,
     OpenHOS,
     Mlibc,
@@ -870,7 +871,7 @@ class Triple {
            Env == Triple::Intersection || Env == Triple::AnyHit ||
            Env == Triple::ClosestHit || Env == Triple::Miss ||
            Env == Triple::Callable || Env == Triple::Mesh ||
-           Env == Triple::Amplification;
+           Env == Triple::Amplification || Env == Triple::RootSignature;
   }
 
   /// Tests whether the target is SPIR (32- or 64-bit).
diff --git a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
index 82bcacee7a6dd..9eebcc9b13063 100644
--- a/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
+++ b/llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp
@@ -127,6 +127,8 @@ static StringRef getShortShaderStage(Triple::EnvironmentType Env) {
     return "ms";
   case Triple::Amplification:
     return "as";
+  case Triple::RootSignature:
+    return "rootsig";
   default:
     break;
   }
diff --git a/llvm/lib/TargetParser/Triple.cpp b/llvm/lib/TargetParser/Triple.cpp
index 6acb0bc49ecfe..fa0d6974b3042 100644
--- a/llvm/lib/TargetParser/Triple.cpp
+++ b/llvm/lib/TargetParser/Triple.cpp
@@ -387,6 +387,8 @@ StringRef Triple::getEnvironmentTypeName(EnvironmentType Kind) {
   case Callable: return "callable";
   case Mesh: return "mesh";
   case Amplification: return "amplification";
+  case RootSignature:
+    return "rootsignature";
   case OpenCL:
     return "opencl";
   case OpenHOS: return "ohos";
@@ -780,6 +782,7 @@ static Triple::EnvironmentType parseEnvironment(StringRef EnvironmentName) {
       .StartsWith("callable", Triple::Callable)
       .StartsWith("mesh", Triple::Mesh)
       .StartsWith("amplification", Triple::Amplification)
+      .StartsWith("rootsignature", Triple::RootSignature)
       .StartsWith("opencl", Triple::OpenCL)
       .StartsWith("ohos", Triple::OpenHOS)
       .StartsWith("pauthtest", Triple::PAuthTest)

>From 9fc43d49920e4545b02df9f9ece2ee5be5435163 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 12:40:32 -0700
Subject: [PATCH 02/11] add profile test

---
 llvm/unittests/TargetParser/TripleTest.cpp | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/llvm/unittests/TargetParser/TripleTest.cpp b/llvm/unittests/TargetParser/TripleTest.cpp
index 35927e340a257..635b9f4f1e219 100644
--- a/llvm/unittests/TargetParser/TripleTest.cpp
+++ b/llvm/unittests/TargetParser/TripleTest.cpp
@@ -546,6 +546,22 @@ TEST(TripleTest, ParsedIDs) {
   EXPECT_EQ(VersionTuple(1, 8), T.getDXILVersion());
   EXPECT_EQ(Triple::Amplification, T.getEnvironment());
 
+  T = Triple("dxilv1.0-unknown-shadermodel1.0-rootsignature");
+  EXPECT_EQ(Triple::dxil, T.getArch());
+  EXPECT_EQ(Triple::DXILSubArch_v1_0, T.getSubArch());
+  EXPECT_EQ(Triple::UnknownVendor, T.getVendor());
+  EXPECT_EQ(Triple::ShaderModel, T.getOS());
+  EXPECT_EQ(VersionTuple(1, 0), T.getDXILVersion());
+  EXPECT_EQ(Triple::RootSignature, T.getEnvironment());
+
+  T = Triple("dxilv1.1-unknown-shadermodel1.1-rootsignature");
+  EXPECT_EQ(Triple::dxil, T.getArch());
+  EXPECT_EQ(Triple::DXILSubArch_v1_1, T.getSubArch());
+  EXPECT_EQ(Triple::UnknownVendor, T.getVendor());
+  EXPECT_EQ(Triple::ShaderModel, T.getOS());
+  EXPECT_EQ(VersionTuple(1, 1), T.getDXILVersion());
+  EXPECT_EQ(Triple::RootSignature, T.getEnvironment());
+
   T = Triple("dxilv1.8-unknown-shadermodel6.15-library");
   EXPECT_EQ(Triple::dxil, T.getArch());
   EXPECT_EQ(Triple::DXILSubArch_v1_8, T.getSubArch());

>From 11f61c163cc7a89e15142b37958d205ff1973e0b Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 29 Aug 2025 10:26:38 -0700
Subject: [PATCH 03/11] introduce custom handling for root signature target

---
 .../clang/Parse/ParseHLSLRootSignature.h      |  2 ++
 clang/lib/Frontend/FrontendActions.cpp        | 14 ++++++++++--
 clang/lib/Parse/ParseHLSLRootSignature.cpp    | 22 ++++++++++++++++++-
 3 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h
index c87e6637c7fce..2ae383c38a486 100644
--- a/clang/include/clang/Parse/ParseHLSLRootSignature.h
+++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h
@@ -240,6 +240,8 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
                                        llvm::dxbc::RootSignatureVersion Version,
                                        StringLiteral *Signature);
 
+void HandleRootSignatureTarget(Sema &S);
+
 } // namespace hlsl
 } // namespace clang
 
diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index ccda2c4ce4b6d..666923101401b 100644
--- a/clang/lib/Frontend/FrontendActions.cpp
+++ b/clang/lib/Frontend/FrontendActions.cpp
@@ -1296,6 +1296,9 @@ class InjectRootSignatureCallback : public PPCallbacks {
 
 void HLSLFrontendAction::ExecuteAction() {
   // Pre-requisites to invoke
+  if (getCurrentFileKind().getLanguage() != Language::HLSL)
+    return WrapperFrontendAction::ExecuteAction();
+
   CompilerInstance &CI = getCompilerInstance();
   if (!CI.hasASTContext() || !CI.hasPreprocessor())
     return WrapperFrontendAction::ExecuteAction();
@@ -1309,6 +1312,10 @@ void HLSLFrontendAction::ExecuteAction() {
                   /*CodeCompleteConsumer=*/nullptr);
   Sema &S = CI.getSema();
 
+  auto &TargetInfo = CI.getASTContext().getTargetInfo();
+  bool IsRootSignatureTarget =
+      TargetInfo.getTriple().getEnvironment() == llvm::Triple::RootSignature;
+
   // Register HLSL specific callbacks
   auto LangOpts = CI.getLangOpts();
   auto MacroCallback = std::make_unique<InjectRootSignatureCallback>(
@@ -1317,8 +1324,11 @@ void HLSLFrontendAction::ExecuteAction() {
   Preprocessor &PP = CI.getPreprocessor();
   PP.addPPCallbacks(std::move(MacroCallback));
 
-  // Invoke as normal
-  WrapperFrontendAction::ExecuteAction();
+  // If we are targeting a root signature, invoke custom handling
+  if (IsRootSignatureTarget)
+    return hlsl::HandleRootSignatureTarget(S);
+  else // otherwise, invoke as normal
+    return WrapperFrontendAction::ExecuteAction();
 }
 
 HLSLFrontendAction::HLSLFrontendAction(
diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp
index 1af72f8b1c934..11af62f132be8 100644
--- a/clang/lib/Parse/ParseHLSLRootSignature.cpp
+++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp
@@ -7,8 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Parse/ParseHLSLRootSignature.h"
-
+#include "clang/AST/ASTConsumer.h"
 #include "clang/Lex/LiteralSupport.h"
+#include "clang/Parse/Parser.h"
 #include "clang/Sema/Sema.h"
 
 using namespace llvm::hlsl::rootsig;
@@ -1472,5 +1473,24 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
   return DeclIdent;
 }
 
+void HandleRootSignatureTarget(Sema &S) {
+  ASTConsumer *Consumer = &S.getASTConsumer();
+
+  // Minimally initalize the parser. This does a couple things:
+  // - initializes Sema scope handling
+  // - invokes HLSLExternalSemaSource
+  // - invokes the preprocessor to lex the macros in the file
+  std::unique_ptr<Parser> P(new Parser(S.getPreprocessor(), S, true));
+  S.getPreprocessor().EnterMainSourceFile();
+
+  bool HaveLexer = S.getPreprocessor().getCurrentLexer();
+  if (HaveLexer) {
+    P->Initialize();
+    S.ActOnStartOfTranslationUnit();
+  }
+
+  Consumer->HandleTranslationUnit(S.getASTContext());
+}
+
 } // namespace hlsl
 } // namespace clang

>From e6747824bd95cb0c048dfad4e29c0a52d6673458 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 29 Aug 2025 10:45:00 -0700
Subject: [PATCH 04/11] nfc: pull out lookup of root signature

---
 clang/include/clang/Sema/SemaHLSL.h |  2 ++
 clang/lib/Sema/SemaHLSL.cpp         | 34 ++++++++++++++++++-----------
 2 files changed, 23 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 5cbe1b658f5cd..4bad26e7a09a7 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -157,6 +157,8 @@ class SemaHLSL : public SemaBase {
     RootSigOverrideIdent = DeclIdent;
   }
 
+  HLSLRootSignatureDecl *lookupRootSignatureOverrideDecl(DeclContext *DC) const;
+
   // Returns true if any RootSignatureElement is invalid and a diagnostic was
   // produced
   bool
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 9e99f9a1525a9..6ef43b833b759 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -729,19 +729,15 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
 
   // If we have specified a root signature to override the entry function then
   // attach it now
-  if (RootSigOverrideIdent) {
-    LookupResult R(SemaRef, RootSigOverrideIdent, SourceLocation(),
-                   Sema::LookupOrdinaryName);
-    if (SemaRef.LookupQualifiedName(R, FD->getDeclContext()))
-      if (auto *SignatureDecl =
-              dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl())) {
-        FD->dropAttr<RootSignatureAttr>();
-        // We could look up the SourceRange of the macro here as well
-        AttributeCommonInfo AL(RootSigOverrideIdent, AttributeScopeInfo(),
-                               SourceRange(), ParsedAttr::Form::Microsoft());
-        FD->addAttr(::new (getASTContext()) RootSignatureAttr(
-            getASTContext(), AL, RootSigOverrideIdent, SignatureDecl));
-      }
+  HLSLRootSignatureDecl *SignatureDecl =
+      lookupRootSignatureOverrideDecl(FD->getDeclContext());
+  if (SignatureDecl) {
+    FD->dropAttr<RootSignatureAttr>();
+    // We could look up the SourceRange of the macro here as well
+    AttributeCommonInfo AL(RootSigOverrideIdent, AttributeScopeInfo(),
+                           SourceRange(), ParsedAttr::Form::Microsoft());
+    FD->addAttr(::new (getASTContext()) RootSignatureAttr(
+        getASTContext(), AL, RootSigOverrideIdent, SignatureDecl));
   }
 
   llvm::Triple::EnvironmentType Env = TargetInfo.getTriple().getEnvironment();
@@ -1111,6 +1107,18 @@ void SemaHLSL::ActOnFinishRootSignatureDecl(
   SemaRef.PushOnScopeChains(SignatureDecl, SemaRef.getCurScope());
 }
 
+HLSLRootSignatureDecl *
+SemaHLSL::lookupRootSignatureOverrideDecl(DeclContext *DC) const {
+  if (RootSigOverrideIdent) {
+    LookupResult R(SemaRef, RootSigOverrideIdent, SourceLocation(),
+                   Sema::LookupOrdinaryName);
+    if (SemaRef.LookupQualifiedName(R, DC))
+      return dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl());
+  }
+
+  return nullptr;
+}
+
 namespace {
 
 struct PerVisibilityBindingChecker {

>From f41c4c4aedbf010a0b8472315c30c8e452460b5d Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 11:34:15 -0700
Subject: [PATCH 05/11] construct the RootSignatureDecl node in ASTContext

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 ++
 .../clang/Parse/ParseHLSLRootSignature.h      |  2 +-
 clang/lib/Frontend/FrontendActions.cpp        |  8 ++++--
 clang/lib/Parse/ParseHLSLRootSignature.cpp    | 12 +++++++-
 .../AST/HLSL/RootSignature-Target-AST.hlsl    | 28 +++++++++++++++++++
 .../SemaHLSL/RootSignature-target-err.hlsl    |  5 ++++
 6 files changed, 53 insertions(+), 4 deletions(-)
 create mode 100644 clang/test/AST/HLSL/RootSignature-Target-AST.hlsl
 create mode 100644 clang/test/SemaHLSL/RootSignature-target-err.hlsl

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c934fed2c7462..f1d64cdca5da4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -13136,6 +13136,8 @@ def err_hlsl_attribute_needs_intangible_type: Error<"attribute %0 can be used on
 def err_hlsl_incorrect_num_initializers: Error<
   "too %select{few|many}0 initializers in list for type %1 "
   "(expected %2 but found %3)">;
+def err_hlsl_rootsignature_entry: Error<
+   "rootsignature specified as target environment but entry, %0, was not defined">;
 
 def err_hlsl_operator_unsupported : Error<
   "the '%select{&|*|->}0' operator is unsupported in HLSL">;
diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h
index 2ae383c38a486..b06846fd83c09 100644
--- a/clang/include/clang/Parse/ParseHLSLRootSignature.h
+++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h
@@ -240,7 +240,7 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
                                        llvm::dxbc::RootSignatureVersion Version,
                                        StringLiteral *Signature);
 
-void HandleRootSignatureTarget(Sema &S);
+void HandleRootSignatureTarget(Sema &S, StringRef EntryRootSig);
 
 } // namespace hlsl
 } // namespace clang
diff --git a/clang/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index 666923101401b..9e00494bcfbcf 100644
--- a/clang/lib/Frontend/FrontendActions.cpp
+++ b/clang/lib/Frontend/FrontendActions.cpp
@@ -1315,18 +1315,22 @@ void HLSLFrontendAction::ExecuteAction() {
   auto &TargetInfo = CI.getASTContext().getTargetInfo();
   bool IsRootSignatureTarget =
       TargetInfo.getTriple().getEnvironment() == llvm::Triple::RootSignature;
+  StringRef HLSLEntry = TargetInfo.getTargetOpts().HLSLEntry;
 
   // Register HLSL specific callbacks
   auto LangOpts = CI.getLangOpts();
+  StringRef RootSigName =
+      IsRootSignatureTarget ? HLSLEntry : LangOpts.HLSLRootSigOverride;
+
   auto MacroCallback = std::make_unique<InjectRootSignatureCallback>(
-      S, LangOpts.HLSLRootSigOverride, LangOpts.HLSLRootSigVer);
+      S, RootSigName, LangOpts.HLSLRootSigVer);
 
   Preprocessor &PP = CI.getPreprocessor();
   PP.addPPCallbacks(std::move(MacroCallback));
 
   // If we are targeting a root signature, invoke custom handling
   if (IsRootSignatureTarget)
-    return hlsl::HandleRootSignatureTarget(S);
+    return hlsl::HandleRootSignatureTarget(S, HLSLEntry);
   else // otherwise, invoke as normal
     return WrapperFrontendAction::ExecuteAction();
 }
diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp
index 11af62f132be8..4e5b8b83200e0 100644
--- a/clang/lib/Parse/ParseHLSLRootSignature.cpp
+++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp
@@ -1473,7 +1473,7 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
   return DeclIdent;
 }
 
-void HandleRootSignatureTarget(Sema &S) {
+void HandleRootSignatureTarget(Sema &S, StringRef EntryRootSig) {
   ASTConsumer *Consumer = &S.getASTConsumer();
 
   // Minimally initalize the parser. This does a couple things:
@@ -1487,6 +1487,16 @@ void HandleRootSignatureTarget(Sema &S) {
   if (HaveLexer) {
     P->Initialize();
     S.ActOnStartOfTranslationUnit();
+
+    HLSLRootSignatureDecl *SignatureDecl =
+        S.HLSL().lookupRootSignatureOverrideDecl(
+            S.getASTContext().getTranslationUnitDecl());
+
+    if (SignatureDecl)
+      Consumer->HandleTopLevelDecl(DeclGroupRef(SignatureDecl));
+    else
+      S.getDiagnostics().Report(diag::err_hlsl_rootsignature_entry)
+          << EntryRootSig;
   }
 
   Consumer->HandleTranslationUnit(S.getASTContext());
diff --git a/clang/test/AST/HLSL/RootSignature-Target-AST.hlsl b/clang/test/AST/HLSL/RootSignature-Target-AST.hlsl
new file mode 100644
index 0000000000000..91441e32e047d
--- /dev/null
+++ b/clang/test/AST/HLSL/RootSignature-Target-AST.hlsl
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-rootsignature -ast-dump \
+// RUN:  -hlsl-entry EntryRootSig -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-V1_1
+
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-rootsignature -ast-dump \
+// RUN:  -fdx-rootsignature-version=rootsig_1_0 \
+// RUN:  -hlsl-entry EntryRootSig -disable-llvm-passes -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK-V1_0
+
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-rootsignature -ast-dump \
+// RUN:  -D CmdRS='"UAV(u0)"'\
+// RUN:  -hlsl-entry CmdRS -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CMD
+
+// CHECK: -HLSLRootSignatureDecl 0x{{.*}} {{.*}} implicit [[ENTRY_RS_DECL:__hlsl_rootsig_decl_\d*]]
+// CHECK-V1_0-SAME: version: 1.0,
+// CHECK-V1_1-SAME: version: 1.1,
+// CHECK-SAME: RootElements{
+// CHECK-SAME: RootCBV(b0,
+// CHECK-SAME:   space = 0, visibility = All,
+// CHECK-V1_0-SAME: flags = DataVolatile
+// CHECK-V1_1-SAME: flags = DataStaticWhileSetAtExecute
+// CHECK-SAME: )
+// CHECK-SAME: }
+#define EntryRootSig "CBV(b0)"
+
+// CMD: -HLSLRootSignatureDecl 0x{{.*}} {{.*}} implicit [[CMD_RS_DECL:__hlsl_rootsig_decl_\d*]]
+// CMD-SAME: version: 1.1,
+// CMD-SAME: RootElements{
+// CMD-SAME: RootUAV(u0, space = 0, visibility = All, flags = DataVolatile)
+// CMD-SAME: }
diff --git a/clang/test/SemaHLSL/RootSignature-target-err.hlsl b/clang/test/SemaHLSL/RootSignature-target-err.hlsl
new file mode 100644
index 0000000000000..49aca9ed6b377
--- /dev/null
+++ b/clang/test/SemaHLSL/RootSignature-target-err.hlsl
@@ -0,0 +1,5 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-rootsignature -hlsl-entry NotFoundRS -fsyntax-only %s -verify
+
+// expected-error@* {{rootsignature specified as target environment but entry, NotFoundRS, was not defined}}
+
+#define EntryRootSig "CBV(b0)"

>From 0033778084f517588382fb805f156cb0af76b27c Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 11:55:24 -0700
Subject: [PATCH 06/11] add support for generating the metadata

---
 clang/lib/CodeGen/CGHLSLRuntime.cpp           | 28 ++++++++++++++-----
 clang/lib/CodeGen/CGHLSLRuntime.h             |  2 ++
 clang/lib/CodeGen/CodeGenModule.cpp           |  2 +-
 .../CodeGenHLSL/RootSignature-Target.hlsl     |  9 ++++++
 4 files changed, 33 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CodeGenHLSL/RootSignature-Target.hlsl

diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index f32d01ae78658..4370c0082ce7f 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -67,9 +67,9 @@ void addDxilValVersion(StringRef ValVersionStr, llvm::Module &M) {
   DXILValMD->addOperand(Val);
 }
 
-void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer,
-                      ArrayRef<llvm::hlsl::rootsig::RootElement> Elements,
-                      llvm::Function *Fn, llvm::Module &M) {
+void addRootSignatureMD(llvm::dxbc::RootSignatureVersion RootSigVer,
+                        ArrayRef<llvm::hlsl::rootsig::RootElement> Elements,
+                        llvm::Function *Fn, llvm::Module &M) {
   auto &Ctx = M.getContext();
 
   llvm::hlsl::rootsig::MetadataBuilder RSBuilder(Ctx, Elements);
@@ -77,8 +77,8 @@ void addRootSignature(llvm::dxbc::RootSignatureVersion RootSigVer,
 
   ConstantAsMetadata *Version = ConstantAsMetadata::get(ConstantInt::get(
       llvm::Type::getInt32Ty(Ctx), llvm::to_underlying(RootSigVer)));
-  MDNode *MDVals =
-      MDNode::get(Ctx, {ValueAsMetadata::get(Fn), RootSignature, Version});
+  ValueAsMetadata *EntryFunc = Fn ? ValueAsMetadata::get(Fn) : nullptr;
+  MDNode *MDVals = MDNode::get(Ctx, {EntryFunc, RootSignature, Version});
 
   StringRef RootSignatureValKey = "dx.rootsignatures";
   auto *RootSignatureValMD = M.getOrInsertNamedMetadata(RootSignatureValKey);
@@ -381,6 +381,20 @@ void CGHLSLRuntime::addBuffer(const HLSLBufferDecl *BufDecl) {
   }
 }
 
+void CGHLSLRuntime::addRootSignature(
+    const HLSLRootSignatureDecl *SignatureDecl) {
+  llvm::Module &M = CGM.getModule();
+  Triple T(M.getTargetTriple());
+
+  // If we are not targeting a root signature enviornment then this decl will
+  // be generated when the function decl it is attached is handled
+  if (T.getEnvironment() != Triple::EnvironmentType::RootSignature)
+    return;
+
+  addRootSignatureMD(SignatureDecl->getVersion(),
+                     SignatureDecl->getRootElements(), nullptr, M);
+}
+
 llvm::TargetExtType *
 CGHLSLRuntime::getHLSLBufferLayoutType(const RecordType *StructType) {
   const auto Entry = LayoutTypes.find(StructType);
@@ -584,8 +598,8 @@ void CGHLSLRuntime::emitEntryFunction(const FunctionDecl *FD,
   for (const Attr *Attr : FD->getAttrs()) {
     if (const auto *RSAttr = dyn_cast<RootSignatureAttr>(Attr)) {
       auto *RSDecl = RSAttr->getSignatureDecl();
-      addRootSignature(RSDecl->getVersion(), RSDecl->getRootElements(), EntryFn,
-                       M);
+      addRootSignatureMD(RSDecl->getVersion(), RSDecl->getRootElements(),
+                         EntryFn, M);
     }
   }
 }
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index b872f9ef0e9b6..613d0ad6bca5c 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -62,6 +62,7 @@ class VarDecl;
 class ParmVarDecl;
 class InitListExpr;
 class HLSLBufferDecl;
+class HLSLRootSignatureDecl;
 class HLSLVkBindingAttr;
 class HLSLResourceBindingAttr;
 class Type;
@@ -150,6 +151,7 @@ class CGHLSLRuntime {
   void generateGlobalCtorDtorCalls();
 
   void addBuffer(const HLSLBufferDecl *D);
+  void addRootSignature(const HLSLRootSignatureDecl *D);
   void finishCodeGen();
 
   void setHLSLEntryAttributes(const FunctionDecl *FD, llvm::Function *Fn);
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 323823c964a79..f90a8196ecced 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7535,7 +7535,7 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
     break;
 
   case Decl::HLSLRootSignature:
-    // Will be handled by attached function
+    getHLSLRuntime().addRootSignature(cast<HLSLRootSignatureDecl>(D));
     break;
   case Decl::HLSLBuffer:
     getHLSLRuntime().addBuffer(cast<HLSLBufferDecl>(D));
diff --git a/clang/test/CodeGenHLSL/RootSignature-Target.hlsl b/clang/test/CodeGenHLSL/RootSignature-Target.hlsl
new file mode 100644
index 0000000000000..50e6bae6786f0
--- /dev/null
+++ b/clang/test/CodeGenHLSL/RootSignature-Target.hlsl
@@ -0,0 +1,9 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-rootsignature \
+// RUN: -hlsl-entry EntryRS -emit-llvm -o - %s | FileCheck %s
+
+// CHECK: !dx.rootsignatures = !{![[#ENTRY:]]}
+// CHECK: ![[#ENTRY]] = !{null, ![[#ENTRY_RS:]], i32 2}
+// CHECK: ![[#ENTRY_RS]] = !{![[#ROOT_CBV:]]}
+// CHECK: ![[#ROOT_CBV]] = !{!"RootCBV", i32 0, i32 0, i32 0, i32 4}
+
+#define EntryRS "CBV(b0)"

>From 34cce6b34ce52564344f1cab57aa9db8c83a91e4 Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 11:56:42 -0700
Subject: [PATCH 07/11] nfc: pull out handling of rs node

---
 llvm/lib/Target/DirectX/DXILRootSignature.cpp | 22 +++++++++++--------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.cpp b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
index 2436d3869464f..5b70e29a12073 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.cpp
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
@@ -72,11 +72,11 @@ analyzeModule(Module &M) {
   if (RootSignatureNode == nullptr)
     return RSDMap;
 
-  for (const auto &RSDefNode : RootSignatureNode->operands()) {
+  auto HandleNode = [&Ctx, &RSDMap](MDNode *RSDefNode) {
     if (RSDefNode->getNumOperands() != 3) {
       reportError(Ctx, "Invalid Root Signature metadata - expected function, "
                        "signature, and version.");
-      continue;
+      return;
     }
 
     // Function was pruned during compilation.
@@ -84,38 +84,38 @@ analyzeModule(Module &M) {
     if (FunctionPointerMdNode == nullptr) {
       reportError(
           Ctx, "Function associated with Root Signature definition is null.");
-      continue;
+      return;
     }
 
     ValueAsMetadata *VAM =
         llvm::dyn_cast<ValueAsMetadata>(FunctionPointerMdNode.get());
     if (VAM == nullptr) {
       reportError(Ctx, "First element of root signature is not a Value");
-      continue;
+      return;
     }
 
     Function *F = dyn_cast<Function>(VAM->getValue());
     if (F == nullptr) {
       reportError(Ctx, "First element of root signature is not a Function");
-      continue;
+      return;
     }
 
     Metadata *RootElementListOperand = RSDefNode->getOperand(1).get();
 
     if (RootElementListOperand == nullptr) {
       reportError(Ctx, "Root Element mdnode is null.");
-      continue;
+      return;
     }
 
     MDNode *RootElementListNode = dyn_cast<MDNode>(RootElementListOperand);
     if (RootElementListNode == nullptr) {
       reportError(Ctx, "Root Element is not a metadata node.");
-      continue;
+      return;
     }
     std::optional<uint32_t> V = extractMdIntValue(RSDefNode, 2);
     if (!V.has_value()) {
       reportError(Ctx, "Invalid RSDefNode value, expected constant int");
-      continue;
+      return;
     }
 
     llvm::hlsl::rootsig::MetadataParser MDParser(RootElementListNode);
@@ -126,7 +126,7 @@ analyzeModule(Module &M) {
       handleAllErrors(RSDOrErr.takeError(), [&](ErrorInfoBase &EIB) {
         Ctx->emitError(EIB.message());
       });
-      continue;
+      return;
     }
 
     auto &RSD = *RSDOrErr;
@@ -140,8 +140,12 @@ analyzeModule(Module &M) {
     RSD.StaticSamplersOffset = 0u;
 
     RSDMap.insert(std::make_pair(F, RSD));
+  };
   }
 
+  for (MDNode *RSDefNode : RootSignatureNode->operands())
+    HandleNode(RSDefNode);
+
   return RSDMap;
 }
 

>From 229aa65013160ec062126d09c9ae168ba834fefc Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Fri, 29 Aug 2025 15:38:09 -0700
Subject: [PATCH 08/11] add support constructing dxcontainer for rootsignature
 without entry function

---
 llvm/include/llvm/BinaryFormat/DXContainer.h  |  2 +-
 .../lib/Target/DirectX/DXContainerGlobals.cpp | 16 +++++--
 llvm/lib/Target/DirectX/DXILRootSignature.cpp | 46 +++++++++++--------
 3 files changed, 40 insertions(+), 24 deletions(-)

diff --git a/llvm/include/llvm/BinaryFormat/DXContainer.h b/llvm/include/llvm/BinaryFormat/DXContainer.h
index f74c9775cb3f3..0547d96a8dec7 100644
--- a/llvm/include/llvm/BinaryFormat/DXContainer.h
+++ b/llvm/include/llvm/BinaryFormat/DXContainer.h
@@ -45,7 +45,7 @@ namespace dxbc {
 LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
 
 inline Triple::EnvironmentType getShaderStage(uint32_t Kind) {
-  assert(Kind <= Triple::Amplification - Triple::Pixel &&
+  assert(Kind <= Triple::RootSignature - Triple::Pixel &&
          "Shader kind out of expected range.");
   return static_cast<Triple::EnvironmentType>(Triple::Pixel + Kind);
 }
diff --git a/llvm/lib/Target/DirectX/DXContainerGlobals.cpp b/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
index a1ef2578f00aa..37c58e041f4df 100644
--- a/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
+++ b/llvm/lib/Target/DirectX/DXContainerGlobals.cpp
@@ -158,10 +158,14 @@ void DXContainerGlobals::addRootSignature(Module &M,
   if (MMI.ShaderProfile == llvm::Triple::Library)
     return;
 
-  assert(MMI.EntryPropertyVec.size() == 1);
-
   auto &RSA = getAnalysis<RootSignatureAnalysisWrapper>().getRSInfo();
-  const Function *EntryFunction = MMI.EntryPropertyVec[0].Entry;
+  const Function *EntryFunction = nullptr;
+
+  if (MMI.ShaderProfile != llvm::Triple::RootSignature) {
+    assert(MMI.EntryPropertyVec.size() == 1);
+    EntryFunction = MMI.EntryPropertyVec[0].Entry;
+  }
+
   const mcdxbc::RootSignatureDesc *RS = RSA.getDescForFunction(EntryFunction);
 
   if (!RS)
@@ -258,7 +262,8 @@ void DXContainerGlobals::addPipelineStateValidationInfo(
   dxil::ModuleMetadataInfo &MMI =
       getAnalysis<DXILMetadataAnalysisWrapperPass>().getModuleMetadata();
   assert(MMI.EntryPropertyVec.size() == 1 ||
-         MMI.ShaderProfile == Triple::Library);
+         MMI.ShaderProfile == Triple::Library ||
+         MMI.ShaderProfile == Triple::RootSignature);
   PSV.BaseData.ShaderStage =
       static_cast<uint8_t>(MMI.ShaderProfile - Triple::Pixel);
 
@@ -279,7 +284,8 @@ void DXContainerGlobals::addPipelineStateValidationInfo(
     break;
   }
 
-  if (MMI.ShaderProfile != Triple::Library)
+  if (MMI.ShaderProfile != Triple::Library &&
+      MMI.ShaderProfile != Triple::RootSignature)
     PSV.EntryName = MMI.EntryPropertyVec[0].Entry->getName();
 
   PSV.finalize(MMI.ShaderProfile);
diff --git a/llvm/lib/Target/DirectX/DXILRootSignature.cpp b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
index 5b70e29a12073..8db9436eabc57 100644
--- a/llvm/lib/Target/DirectX/DXILRootSignature.cpp
+++ b/llvm/lib/Target/DirectX/DXILRootSignature.cpp
@@ -72,32 +72,35 @@ analyzeModule(Module &M) {
   if (RootSignatureNode == nullptr)
     return RSDMap;
 
-  auto HandleNode = [&Ctx, &RSDMap](MDNode *RSDefNode) {
+  auto HandleNode = [&Ctx, &RSDMap](MDNode *RSDefNode, bool NullFunc = false) {
     if (RSDefNode->getNumOperands() != 3) {
       reportError(Ctx, "Invalid Root Signature metadata - expected function, "
                        "signature, and version.");
       return;
     }
 
-    // Function was pruned during compilation.
-    const MDOperand &FunctionPointerMdNode = RSDefNode->getOperand(0);
-    if (FunctionPointerMdNode == nullptr) {
-      reportError(
-          Ctx, "Function associated with Root Signature definition is null.");
-      return;
-    }
+    Function *F = nullptr;
+    if (!NullFunc) {
+      // Function was pruned during compilation.
+      const MDOperand &FunctionPointerMdNode = RSDefNode->getOperand(0);
+      if (FunctionPointerMdNode == nullptr) {
+        reportError(
+            Ctx, "Function associated with Root Signature definition is null.");
+        return;
+      }
 
-    ValueAsMetadata *VAM =
-        llvm::dyn_cast<ValueAsMetadata>(FunctionPointerMdNode.get());
-    if (VAM == nullptr) {
-      reportError(Ctx, "First element of root signature is not a Value");
-      return;
-    }
+      ValueAsMetadata *VAM =
+          llvm::dyn_cast<ValueAsMetadata>(FunctionPointerMdNode.get());
+      if (VAM == nullptr) {
+        reportError(Ctx, "First element of root signature is not a Value");
+        return;
+      }
 
-    Function *F = dyn_cast<Function>(VAM->getValue());
-    if (F == nullptr) {
-      reportError(Ctx, "First element of root signature is not a Function");
-      return;
+      F = dyn_cast<Function>(VAM->getValue());
+      if (F == nullptr) {
+        reportError(Ctx, "First element of root signature is not a Function");
+        return;
+      }
     }
 
     Metadata *RootElementListOperand = RSDefNode->getOperand(1).get();
@@ -141,6 +144,13 @@ analyzeModule(Module &M) {
 
     RSDMap.insert(std::make_pair(F, RSD));
   };
+
+  if (M.getTargetTriple().getEnvironment() ==
+      Triple::EnvironmentType::RootSignature) {
+    assert(RootSignatureNode->getNumOperands() == 1);
+    MDNode *RSDefNode = RootSignatureNode->getOperand(0);
+    HandleNode(RSDefNode, true);
+    return RSDMap;
   }
 
   for (MDNode *RSDefNode : RootSignatureNode->operands())

>From 028a74a38805a8a8d4bf81046edbce155813ddfe Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 12:39:50 -0700
Subject: [PATCH 09/11] nfc: extract intermediate parse profile step

---
 clang/lib/Driver/ToolChains/HLSL.cpp | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/HLSL.cpp b/clang/lib/Driver/ToolChains/HLSL.cpp
index 304c367cc4367..0c96935ba93b7 100644
--- a/clang/lib/Driver/ToolChains/HLSL.cpp
+++ b/clang/lib/Driver/ToolChains/HLSL.cpp
@@ -70,7 +70,7 @@ bool isLegalShaderModel(Triple &T) {
   return false;
 }
 
-std::optional<std::string> tryParseProfile(StringRef Profile) {
+std::optional<llvm::Triple> tryParseTriple(StringRef Profile) {
   // [ps|vs|gs|hs|ds|cs|ms|as]_[major]_[minor]
   SmallVector<StringRef, 3> Parts;
   Profile.split(Parts, "_");
@@ -149,8 +149,14 @@ std::optional<std::string> tryParseProfile(StringRef Profile) {
   T.setOSName(Triple::getOSTypeName(Triple::OSType::ShaderModel).str() +
               VersionTuple(Major, Minor).getAsString());
   T.setEnvironment(Kind);
-  if (isLegalShaderModel(T))
-    return T.getTriple();
+
+  return T;
+}
+
+std::optional<std::string> tryParseProfile(StringRef Profile) {
+  std::optional<llvm::Triple> MaybeT = tryParseTriple(Profile);
+  if (MaybeT && isLegalShaderModel(*MaybeT))
+    return MaybeT->getTriple();
   else
     return std::nullopt;
 }

>From 060eae14a68056e2ffc082608244da03137d4c5f Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 12:40:20 -0700
Subject: [PATCH 10/11] add invocation to just emit root signature of
 dxcontainer

---
 clang/lib/Driver/ToolChains/HLSL.cpp          | 22 ++++++++++++++++++-
 .../test/Driver/dxc_rootsignature_target.hlsl |  8 +++++++
 2 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Driver/dxc_rootsignature_target.hlsl

diff --git a/clang/lib/Driver/ToolChains/HLSL.cpp b/clang/lib/Driver/ToolChains/HLSL.cpp
index 0c96935ba93b7..543ab268540da 100644
--- a/clang/lib/Driver/ToolChains/HLSL.cpp
+++ b/clang/lib/Driver/ToolChains/HLSL.cpp
@@ -266,6 +266,19 @@ bool checkExtensionArgsAreValid(ArrayRef<std::string> SpvExtensionArgs,
   }
   return AllValid;
 }
+
+bool isRootSignatureTarget(StringRef Profile) {
+  if (std::optional<llvm::Triple> T = tryParseTriple(Profile))
+    return T->getEnvironment() == Triple::EnvironmentType::RootSignature;
+  return false;
+}
+
+bool isRootSignatureTarget(DerivedArgList &Args) {
+  if (const Arg *A = Args.getLastArg(options::OPT_target_profile))
+    return isRootSignatureTarget(A->getValue());
+  return false;
+}
+
 } // namespace
 
 void tools::hlsl::Validator::ConstructJob(Compilation &C, const JobAction &JA,
@@ -325,6 +338,12 @@ void tools::hlsl::LLVMObjcopy::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back(Frs);
   }
 
+  if (const Arg *A = Args.getLastArg(options::OPT_target_profile))
+    if (isRootSignatureTarget(A->getValue())) {
+      const char *Fos = Args.MakeArgString("--only-section=RTS0");
+      CmdArgs.push_back(Fos);
+    }
+
   assert(CmdArgs.size() > 2 && "Unnecessary invocation of objcopy.");
 
   C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),
@@ -501,7 +520,8 @@ bool HLSLToolChain::requiresBinaryTranslation(DerivedArgList &Args) const {
 
 bool HLSLToolChain::requiresObjcopy(DerivedArgList &Args) const {
   return Args.hasArg(options::OPT_dxc_Fo) &&
-         Args.hasArg(options::OPT_dxc_strip_rootsignature);
+         (Args.hasArg(options::OPT_dxc_strip_rootsignature) ||
+          isRootSignatureTarget(Args));
 }
 
 bool HLSLToolChain::isLastJob(DerivedArgList &Args,
diff --git a/clang/test/Driver/dxc_rootsignature_target.hlsl b/clang/test/Driver/dxc_rootsignature_target.hlsl
new file mode 100644
index 0000000000000..a732c50507cdb
--- /dev/null
+++ b/clang/test/Driver/dxc_rootsignature_target.hlsl
@@ -0,0 +1,8 @@
+// RUN: %clang_dxc -E EntryRS -T rootsig_1_1 /Fo %t.dxo -### %s 2>&1 | FileCheck %s --check-prefix=CMDS
+
+// CMDS: "{{.*}}clang{{.*}}" "-cc1"
+// CMDS-SAME: "-triple" "dxilv1.1-unknown-shadermodel1.1-rootsignature"
+// CMDS-SAME: "-hlsl-entry" "EntryRS"
+// CMDS: "{{.*}}llvm-objcopy{{(.exe)?}}" "{{.*}}.dxo" "--only-section=RTS0"
+
+#define EntryRS "CBV(b0)"

>From 50d72e692c0d45134422956fff10e779ce822b7f Mon Sep 17 00:00:00 2001
From: Finn Plummer <mail at inbelic.dev>
Date: Mon, 1 Sep 2025 12:57:54 -0700
Subject: [PATCH 11/11] add clang_dxc test to demonstrate lexing bug

---
 clang/lib/Parse/ParseHLSLRootSignature.cpp    |  4 +++
 .../test/Driver/dxc_rootsignature_target.hlsl | 34 ++++++++++++++++++-
 2 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp
index 4e5b8b83200e0..91976489ee660 100644
--- a/clang/lib/Parse/ParseHLSLRootSignature.cpp
+++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp
@@ -1488,6 +1488,10 @@ void HandleRootSignatureTarget(Sema &S, StringRef EntryRootSig) {
     P->Initialize();
     S.ActOnStartOfTranslationUnit();
 
+    // Skim through the file to parse to find the define
+    while (P->getCurToken().getKind() != tok::eof)
+      P->ConsumeAnyToken();
+
     HLSLRootSignatureDecl *SignatureDecl =
         S.HLSL().lookupRootSignatureOverrideDecl(
             S.getASTContext().getTranslationUnitDecl());
diff --git a/clang/test/Driver/dxc_rootsignature_target.hlsl b/clang/test/Driver/dxc_rootsignature_target.hlsl
index a732c50507cdb..011fa4be9050d 100644
--- a/clang/test/Driver/dxc_rootsignature_target.hlsl
+++ b/clang/test/Driver/dxc_rootsignature_target.hlsl
@@ -1,8 +1,40 @@
 // RUN: %clang_dxc -E EntryRS -T rootsig_1_1 /Fo %t.dxo -### %s 2>&1 | FileCheck %s --check-prefix=CMDS
 
+// RUN: %clang_dxc -E EntryRS -T rootsig_1_1 /Fo %t.dxo %s
+// RUN: obj2yaml %t.dxo | FileCheck %s --check-prefix=OBJ
+
 // CMDS: "{{.*}}clang{{.*}}" "-cc1"
 // CMDS-SAME: "-triple" "dxilv1.1-unknown-shadermodel1.1-rootsignature"
 // CMDS-SAME: "-hlsl-entry" "EntryRS"
 // CMDS: "{{.*}}llvm-objcopy{{(.exe)?}}" "{{.*}}.dxo" "--only-section=RTS0"
 
-#define EntryRS "CBV(b0)"
+#define EntryRS "UAV(u0)"
+
+// OBJ: --- !dxcontainer
+// FileSize = 32 (header) + 48 (RTS0 content) + 4 (1 part offset) + 8 (1 part header)
+// OBJ:       FileSize: 92
+// OBJ-NEXT:  PartCount:        1
+// OBJ-NEXT:  PartOffsets:      [ 36 ]
+// OBJ-NEXT:  Parts:
+// OBJ-NOT:   DXIL
+// OBJ-NOT:   SFI0
+// OBJ-NOT:   HASH
+// OBJ-NOT:   ISG0
+// OBJ-NOT:   OSG0
+
+// OBJ:       - Name: RTS0
+// OBJ-NEXT:    Size:         48
+// OBJ-NEXT:    RootSignature:
+// OBJ-NEXT:      Version:         2
+// OBJ-NEXT:      NumRootParameters: 1
+// OBJ-NEXT:      RootParametersOffset: 24
+
+// OBJ:         Parameters:
+// UAV(u0)
+// OBJ:          - ParameterType:   4
+// OBJ-NEXT:       ShaderVisibility: 0
+// OBJ-NEXT:       Descriptor:
+// OBJ-NEXT:         RegisterSpace:   0
+// OBJ-NEXT:         ShaderRegister:  0
+
+// OBJ-NOT: PSV0



More information about the llvm-branch-commits mailing list