Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13147,6 +13147,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">;
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -9432,7 +9432,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.">;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Parse/ParseHLSLRootSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ IdentifierInfo *ParseHLSLRootSignature(Sema &Actions,
llvm::dxbc::RootSignatureVersion Version,
StringLiteral *Signature);

void HandleRootSignatureTarget(Sema &S, StringRef EntryRootSig);

} // namespace hlsl
} // namespace clang

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Sema/SemaHLSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 20 additions & 7 deletions clang/lib/CodeGen/CGHLSLRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,18 @@ 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);
MDNode *RootSignature = RSBuilder.BuildRootSignature();

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);
Expand Down Expand Up @@ -448,6 +448,19 @@ void CGHLSLRuntime::addBuffer(const HLSLBufferDecl *BufDecl) {
}
}

void CGHLSLRuntime::addRootSignature(
const HLSLRootSignatureDecl *SignatureDecl) {
llvm::Module &M = CGM.getModule();
Triple T(M.getTargetTriple());

// Generated later with the function decl if not targeting root signature
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);
Expand Down Expand Up @@ -651,8 +664,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);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGHLSLRuntime.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class VarDecl;
class ParmVarDecl;
class InitListExpr;
class HLSLBufferDecl;
class HLSLRootSignatureDecl;
class HLSLVkBindingAttr;
class HLSLResourceBindingAttr;
class Type;
Expand Down Expand Up @@ -151,6 +152,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);
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7537,7 +7537,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));
Expand Down
39 changes: 35 additions & 4 deletions clang/lib/Driver/ToolChains/HLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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, "_");
Expand All @@ -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;
Expand Down Expand Up @@ -147,8 +152,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;
}
Expand Down Expand Up @@ -258,6 +269,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,
Expand Down Expand Up @@ -317,6 +341,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(),
Expand Down Expand Up @@ -493,7 +523,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,
Expand Down
17 changes: 14 additions & 3 deletions clang/lib/Frontend/FrontendActions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1309,16 +1309,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(
Expand Down
36 changes: 35 additions & 1 deletion clang/lib/Parse/ParseHLSLRootSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Comment on lines +1486 to +1487
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.

P->Initialize();
S.ActOnStartOfTranslationUnit();

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

Comment on lines +1491 to +1494
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.

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
38 changes: 25 additions & 13 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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");
}
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions clang/test/AST/HLSL/RootSignature-Target-AST.hlsl
Original file line number Diff line number Diff line change
@@ -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: }
9 changes: 9 additions & 0 deletions clang/test/CodeGenHLSL/RootSignature-Target.hlsl
Original file line number Diff line number Diff line change
@@ -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)"
Loading