Skip to content

Conversation

inbelic
Copy link
Contributor

@inbelic inbelic commented Sep 1, 2025

This pr implements support for a root signature as a target, as specified here.

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.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: #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.

Comment on lines +1491 to +1494
// Skim through the file to parse to find the define
while (P->getCurToken().getKind() != tok::eof)
P->ConsumeAnyToken();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason this is required when invoking with clang-dxc. Without it the OBJ portion of this test case will fail with an error that EntryRS is not defined.

If these lines are removed all other test cases still pass. The other test-cases are all invoked directly with clang -cc1 and will invoke the MacroDefined callback during the first lex in P->Initialize().

Perhaps there is some option that clang-dxc invokes clang -cc1 that disables macro expansion before directly needed?

Copy link
Contributor Author

@inbelic inbelic Sep 2, 2025

Choose a reason for hiding this comment

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

From offline: this is caused because clang-dxc will add the -finclude-default-header option to the invocation of clang -cc1 (thanks Chris for helping me find this).

With reference to the test case.

When we include the header, it means that P->Initialize() will start parsing from the header file and encounter the first non-preprocessor token there. So we will not have encountered our EntryRS define in the source file yet.

When we invoke this without the default header, the first non-preprocessor token will be tok::eof of our source file and EntryRS will have been found. To solve this, we can either:

  1. Remove the -finclude-default-header when invoking clang cc1 from clang-dxc with the root signature target.
  2. Keep this logic in to skim through the file

Noting that DXC will happily ignore any code in the source file, it seems like option 2. makes more sense. For instance,
dxc -T rootsig1_1 -E EntryRS %s will compile this fine by ignoring the function definition (or any other code):

void some_func() {}

#define EntryRS "CBV(b0)"

Whereas, if we remove the skimming logic above, clang-dxc -T rootsig1_1 -E EntryRS %s would not parse enough tokens to encounter the macro and issue the error that EntryRS is not defined.

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 think we can remove -finclude-default-header because that may cause the input file to have parse errors, which could impede finding the define. I agree that consuming the file seems necessary here.

@inbelic inbelic linked an issue Sep 2, 2025 that may be closed by this pull request
3 tasks
- P->Initialize() will only parse until the first non-preprocessor token
is found. If a user specifies any code before their entry root signature
macro definition, we would not find the definition and incorrectly
report that it is not defined

This change ensures that we will parse through the entire source file to
resolve our macro and match DXC's behaviour
Comment on lines 1299 to 1300
if (getCurrentFileKind().getLanguage() != Language::HLSL)
return WrapperFrontendAction::ExecuteAction();
Copy link
Contributor

Choose a reason for hiding this comment

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

How can we get here? I don't really understand what the added language check here is doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. TLDR: it is not required and I forgot to remove it from early iterations.

It was originally added because I was hitting this assert when invoking with -emit-obj <file> and so I thought it was because it was first creating a .ll file and then creating an object from that as two separate jobs. Turns out it is because you need to specify the file as -emit-obj -o <file>.

Comment on lines +1486 to +1487
bool HaveLexer = S.getPreprocessor().getCurrentLexer();
if (HaveLexer) {
Copy link
Contributor

Choose a reason for hiding this comment

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

When can we not have a lexer? Is this the case where we try to do this without any input file?

Copy link
Contributor Author

@inbelic inbelic Sep 4, 2025

Choose a reason for hiding this comment

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

I took the same pre-caution as here. So it prevents a faulty pre-compiled header to cause further issues, perhaps that is not a concern for us?

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, matching the similar logic seems fine here.

Comment on lines +1491 to +1494
// Skim through the file to parse to find the define
while (P->getCurToken().getKind() != tok::eof)
P->ConsumeAnyToken();

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 think we can remove -finclude-default-header because that may cause the input file to have parse errors, which could impede finding the define. I agree that consuming the file seems necessary here.

Comment on lines 148 to 157
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())
HandleNode(RSDefNode);
Copy link
Contributor

Choose a reason for hiding this comment

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

It's mostly a matter of style, but I think it might be slightly more readable to keep this as a loop rather than pulling the lambda out like this. You'd need to do the root signature book keeping before the loop:

  bool AllowNullFunctions = false;
  if (M.getTargetTriple().getEnvironment() ==
      Triple::EnvironmentType::RootSignature) {
    assert(RootSignatureNode->getNumOperands() == 1);
    AllowNullFunctions = true;
  }

  for (MDNode *RSDefNode : RootSignatureNode->operands())
    // ...

What do you think?

@bogner bogner added backend:DirectX HLSL HLSL Language Support labels Sep 4, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 4, 2025

@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-backend-directx

Author: Finn Plummer (inbelic)

Changes

This pr implements support for a root signature as a target, as specified here.

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.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: #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.


Patch is 30.74 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/156373.diff

24 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+1)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Driver/Options.td (+2-1)
  • (modified) clang/include/clang/Parse/ParseHLSLRootSignature.h (+2)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+2)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.cpp (+21-7)
  • (modified) clang/lib/CodeGen/CGHLSLRuntime.h (+2)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+1-1)
  • (modified) clang/lib/Driver/ToolChains/HLSL.cpp (+35-4)
  • (modified) clang/lib/Frontend/FrontendActions.cpp (+17-3)
  • (modified) clang/lib/Parse/ParseHLSLRootSignature.cpp (+35-1)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+25-13)
  • (added) clang/test/AST/HLSL/RootSignature-Target-AST.hlsl (+28)
  • (added) clang/test/CodeGenHLSL/RootSignature-Target.hlsl (+9)
  • (added) clang/test/Driver/dxc_rootsignature_target.hlsl (+8)
  • (added) clang/test/SemaHLSL/RootSignature-target-err.hlsl (+5)
  • (modified) llvm/include/llvm/BinaryFormat/DXContainer.h (+1-1)
  • (modified) llvm/include/llvm/TargetParser/Triple.h (+2-1)
  • (modified) llvm/lib/Target/DirectX/DXContainerGlobals.cpp (+11-5)
  • (modified) llvm/lib/Target/DirectX/DXILRootSignature.cpp (+37-23)
  • (modified) llvm/lib/Target/DirectX/DXILTranslateMetadata.cpp (+2)
  • (modified) llvm/lib/TargetParser/Triple.cpp (+3)
  • (added) llvm/test/CodeGen/DirectX/ContainerData/RootSignature-Target.ll (+23)
  • (modified) llvm/unittests/TargetParser/TripleTest.cpp (+16)
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/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/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/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h
index c87e6637c7fce..b06846fd83c09 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, StringRef EntryRootSig);
+
 } // namespace hlsl
 } // namespace clang
 
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/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/lib/Driver/ToolChains/HLSL.cpp b/clang/lib/Driver/ToolChains/HLSL.cpp
index 660661945d62a..543ab268540da 100644
--- a/clang/lib/Driver/ToolChains/HLSL.cpp
+++ b/clang/lib/Driver/ToolChains/HLSL.cpp
@@ -62,11 +62,15 @@ 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;
 }
 
-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, "_");
@@ -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;
@@ -144,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;
 }
@@ -255,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,
@@ -314,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(),
@@ -490,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/lib/Frontend/FrontendActions.cpp b/clang/lib/Frontend/FrontendActions.cpp
index ccda2c4ce4b6d..9e00494bcfbcf 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,16 +1312,27 @@ void HLSLFrontendAction::ExecuteAction() {
                   /*CodeCompleteConsumer=*/nullptr);
   Sema &S = CI.getSema();
 
+  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));
 
-  // Invoke as normal
-  WrapperFrontendAction::ExecuteAction();
+  // If we are targeting a root signature, invoke custom handling
+  if (IsRootSignatureTarget)
+    return hlsl::HandleRootSignatureTarget(S, HLSLEntry);
+  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..91976489ee660 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,38 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
   return DeclIdent;
 }
 
+void HandleRootSignatureTarget(Sema &S, StringRef EntryRootSig) {
+  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();
+
+    // 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());
+
+    if (SignatureDecl)
+      Consumer->HandleTopLevelDecl(DeclGroupRef(SignatureDecl));
+    else
+      S.getDiagnostics().Report(diag::err_hlsl_rootsignature_entry)
+          << EntryRootSig;
+  }
+
+  Consumer->HandleTranslationUnit(S.getASTContext());
+}
+
 } // namespace hlsl
 } // namespace clang
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 1e5ec952c1ecf..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();
@@ -765,6 +761,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 +825,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");
   }
@@ -1107,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 {
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/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)"
diff --git a/clang/test/Driver/dxc_rootsignature_target.hlsl b/clang/test/Driver/dxc_rootsignature_target.hlsl
new file mode 100644
index 0000000000000..08cd1ab00089b
--- /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 "UAV(u0)"
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 NotFound...
[truncated]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:DirectX HLSL HLSL Language Support
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HLSL][RootSignature] Add support for root signature as a target
3 participants