[HLSL][RootSignature] Define and integrate rootsig clang attr and decl (#137690)

- Defines a new declaration node `HLSLRootSignature` in `DeclNodes.td`
that will consist of a `TrailingObjects` of the in-memory construction
of the root signature, namely an array of `hlsl::rootsig::RootElement`s

- Defines a new clang attr `RootSignature` which simply holds an
identifier to a corresponding root signature declaration as above

- Integrate the `HLSLRootSignatureParser` to construct the decl node in
`ParseMicrosoftAttributes` and then attach the parsed attr with an
identifier to the entry point function declaration.

- Defines the various required declaration methods

- Add testing that the declaration and reference attr are created
correctly, and some syntactical error tests.

It was previously proposed that we could have the root elements
reference be stored directly as an additional member of the attribute
and to not have a separate root signature decl. In contrast, by defining
them separately as this change proposes, we will allow a unique root
signature to have its own declaration in the AST tree. This allows us to
only construct a single root signature for all duplicate root signature
attributes. Having it located directly as a declaration might also prove
advantageous when we consider root signature libraries.

Resolves https://github.com/llvm/llvm-project/issues/119011
This commit is contained in:
Finn Plummer 2025-05-12 09:59:46 -07:00 committed by GitHub
parent fb9b43a0c5
commit dd3d7cfe2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 347 additions and 0 deletions

View File

@ -41,6 +41,7 @@
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Frontend/HLSL/HLSLRootSignature.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/TrailingObjects.h"
@ -5178,6 +5179,42 @@ public:
friend class ASTDeclWriter;
};
class HLSLRootSignatureDecl final
: public NamedDecl,
private llvm::TrailingObjects<HLSLRootSignatureDecl,
llvm::hlsl::rootsig::RootElement> {
friend TrailingObjects;
unsigned NumElems;
llvm::hlsl::rootsig::RootElement *getElems() {
return getTrailingObjects<llvm::hlsl::rootsig::RootElement>();
}
const llvm::hlsl::rootsig::RootElement *getElems() const {
return getTrailingObjects<llvm::hlsl::rootsig::RootElement>();
}
HLSLRootSignatureDecl(DeclContext *DC, SourceLocation Loc, IdentifierInfo *ID,
unsigned NumElems);
public:
static HLSLRootSignatureDecl *
Create(ASTContext &C, DeclContext *DC, SourceLocation Loc, IdentifierInfo *ID,
ArrayRef<llvm::hlsl::rootsig::RootElement> RootElements);
static HLSLRootSignatureDecl *CreateDeserialized(ASTContext &C,
GlobalDeclID ID);
ArrayRef<llvm::hlsl::rootsig::RootElement> getRootElements() const {
return {getElems(), NumElems};
}
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K == HLSLRootSignature; }
};
/// Insertion operator for diagnostics. This allows sending NamedDecl's
/// into a diagnostic with <<.
inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &PD,

View File

@ -1599,6 +1599,8 @@ DEF_TRAVERSE_DECL(EmptyDecl, {})
DEF_TRAVERSE_DECL(HLSLBufferDecl, {})
DEF_TRAVERSE_DECL(HLSLRootSignatureDecl, {})
DEF_TRAVERSE_DECL(LifetimeExtendedTemporaryDecl, {
TRY_TO(TraverseStmt(D->getTemporaryExpr()));
})

View File

@ -408,6 +408,7 @@ public:
void
VisitLifetimeExtendedTemporaryDecl(const LifetimeExtendedTemporaryDecl *D);
void VisitHLSLBufferDecl(const HLSLBufferDecl *D);
void VisitHLSLRootSignatureDecl(const HLSLRootSignatureDecl *D);
void VisitHLSLOutArgExpr(const HLSLOutArgExpr *E);
void VisitOpenACCConstructStmt(const OpenACCConstructStmt *S);
void VisitOpenACCLoopConstruct(const OpenACCLoopConstruct *S);

View File

@ -4735,6 +4735,17 @@ def Error : InheritableAttr {
let Documentation = [ErrorAttrDocs];
}
/// HLSL Root Signature Attribute
def RootSignature : Attr {
/// [RootSignature(Signature)]
let Spellings = [Microsoft<"RootSignature">];
let Args = [IdentifierArgument<"Signature">];
let Subjects = SubjectList<[Function],
ErrorDiag, "'function'">;
let LangOpts = [HLSL];
let Documentation = [RootSignatureDocs];
}
def HLSLNumThreads: InheritableAttr {
let Spellings = [Microsoft<"numthreads">];
let Args = [IntArgument<"X">, IntArgument<"Y">, IntArgument<"Z">];

View File

@ -8195,6 +8195,17 @@ and https://microsoft.github.io/hlsl-specs/proposals/0013-wave-size-range.html
}];
}
def RootSignatureDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``RootSignature`` attribute applies to HLSL entry functions to define what
types of resources are bound to the graphics pipeline.
For details about the use and specification of Root Signatures please see here:
https://learn.microsoft.com/en-us/windows/win32/direct3d12/root-signatures
}];
}
def NumThreadsDocs : Documentation {
let Category = DocCatFunction;
let Content = [{

View File

@ -111,5 +111,6 @@ def Empty : DeclNode<Decl>;
def RequiresExprBody : DeclNode<Decl>, DeclContext;
def LifetimeExtendedTemporary : DeclNode<Decl>;
def HLSLBuffer : DeclNode<Named, "HLSLBuffer">, DeclContext;
def HLSLRootSignature : DeclNode<Named, "HLSLRootSignature">;
def OpenACCDeclare : DeclNode<Decl, "#pragma acc declare">;
def OpenACCRoutine : DeclNode<Decl, "#pragma acc routine">;

View File

@ -3093,6 +3093,7 @@ private:
return AttrsParsed;
}
void ParseMicrosoftUuidAttributeArgs(ParsedAttributes &Attrs);
void ParseMicrosoftRootSignatureAttributeArgs(ParsedAttributes &Attrs);
void ParseMicrosoftAttributes(ParsedAttributes &Attrs);
bool MaybeParseMicrosoftDeclSpecs(ParsedAttributes &Attrs) {
if (getLangOpts().DeclSpecKeyword && Tok.is(tok::kw___declspec)) {

View File

@ -119,6 +119,7 @@ public:
bool IsCompAssign);
void emitLogicalOperatorFixIt(Expr *LHS, Expr *RHS, BinaryOperatorKind Opc);
void handleRootSignatureAttr(Decl *D, const ParsedAttr &AL);
void handleNumThreadsAttr(Decl *D, const ParsedAttr &AL);
void handleWaveSizeAttr(Decl *D, const ParsedAttr &AL);
void handleSV_DispatchThreadIDAttr(Decl *D, const ParsedAttr &AL);

View File

@ -2,6 +2,7 @@ set(LLVM_LINK_COMPONENTS
BinaryFormat
Core
FrontendOpenMP
FrontendHLSL
Support
TargetParser
)

View File

@ -5847,6 +5847,38 @@ bool HLSLBufferDecl::buffer_decls_empty() {
return DefaultBufferDecls.empty() && decls_empty();
}
//===----------------------------------------------------------------------===//
// HLSLRootSignatureDecl Implementation
//===----------------------------------------------------------------------===//
HLSLRootSignatureDecl::HLSLRootSignatureDecl(DeclContext *DC,
SourceLocation Loc,
IdentifierInfo *ID,
unsigned NumElems)
: NamedDecl(Decl::Kind::HLSLRootSignature, DC, Loc, DeclarationName(ID)),
NumElems(NumElems) {}
HLSLRootSignatureDecl *HLSLRootSignatureDecl::Create(
ASTContext &C, DeclContext *DC, SourceLocation Loc, IdentifierInfo *ID,
ArrayRef<llvm::hlsl::rootsig::RootElement> RootElements) {
HLSLRootSignatureDecl *RSDecl =
new (C, DC,
additionalSizeToAlloc<llvm::hlsl::rootsig::RootElement>(
RootElements.size()))
HLSLRootSignatureDecl(DC, Loc, ID, RootElements.size());
auto *StoredElems = RSDecl->getElems();
std::uninitialized_copy(RootElements.begin(), RootElements.end(),
StoredElems);
return RSDecl;
}
HLSLRootSignatureDecl *
HLSLRootSignatureDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
HLSLRootSignatureDecl *Result = new (C, ID)
HLSLRootSignatureDecl(nullptr, SourceLocation(), nullptr, /*NumElems=*/0);
return Result;
}
//===----------------------------------------------------------------------===//
// ImportDecl Implementation
//===----------------------------------------------------------------------===//

View File

@ -886,6 +886,7 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
case ObjCProperty:
case MSProperty:
case HLSLBuffer:
case HLSLRootSignature:
return IDNS_Ordinary;
case Label:
return IDNS_Label;

View File

@ -24,6 +24,7 @@
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TypeTraits.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Frontend/HLSL/HLSLRootSignature.h"
#include <algorithm>
#include <utility>
@ -3037,6 +3038,12 @@ void TextNodeDumper::VisitHLSLBufferDecl(const HLSLBufferDecl *D) {
dumpName(D);
}
void TextNodeDumper::VisitHLSLRootSignatureDecl(
const HLSLRootSignatureDecl *D) {
dumpName(D);
llvm::hlsl::rootsig::dumpRootElements(OS, D->getRootElements());
}
void TextNodeDumper::VisitHLSLOutArgExpr(const HLSLOutArgExpr *E) {
OS << (E->isInOut() ? " inout" : " out");
}

View File

@ -106,6 +106,7 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
case Decl::Binding:
case Decl::UnresolvedUsingIfExists:
case Decl::HLSLBuffer:
case Decl::HLSLRootSignature:
llvm_unreachable("Declaration should not be in declstmts!");
case Decl::Record: // struct/union/class X;
case Decl::CXXRecord: // struct/union/class X; [C++]

View File

@ -21,10 +21,12 @@
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Lex/LiteralSupport.h"
#include "clang/Parse/ParseHLSLRootSignature.h"
#include "clang/Parse/Parser.h"
#include "clang/Parse/RAIIObjectsForParser.h"
#include "clang/Sema/DeclSpec.h"
#include "clang/Sema/EnterExpressionEvaluationContext.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/SemaCodeCompletion.h"
@ -5311,6 +5313,90 @@ void Parser::ParseMicrosoftUuidAttributeArgs(ParsedAttributes &Attrs) {
}
}
void Parser::ParseMicrosoftRootSignatureAttributeArgs(ParsedAttributes &Attrs) {
assert(Tok.is(tok::identifier) &&
"Expected an identifier to denote which MS attribute to consider");
IdentifierInfo *RootSignatureIdent = Tok.getIdentifierInfo();
assert(RootSignatureIdent->getName() == "RootSignature" &&
"Expected RootSignature identifier for root signature attribute");
SourceLocation RootSignatureLoc = Tok.getLocation();
ConsumeToken();
// Ignore the left paren location for now.
BalancedDelimiterTracker T(*this, tok::l_paren);
if (T.consumeOpen()) {
Diag(Tok, diag::err_expected) << tok::l_paren;
return;
}
auto ProcessStringLiteral = [this]() -> std::optional<StringLiteral *> {
if (!isTokenStringLiteral())
return std::nullopt;
ExprResult StringResult = ParseUnevaluatedStringLiteralExpression();
if (StringResult.isInvalid())
return std::nullopt;
if (auto Lit = dyn_cast<StringLiteral>(StringResult.get()))
return Lit;
return std::nullopt;
};
auto StrLiteral = ProcessStringLiteral();
if (!StrLiteral.has_value()) {
Diag(Tok, diag::err_expected_string_literal)
<< /*in attributes...*/ 4 << RootSignatureIdent->getName();
SkipUntil(tok::r_paren, StopAtSemi | StopBeforeMatch);
T.consumeClose();
return;
}
// Construct our identifier
StringRef Signature = StrLiteral.value()->getString();
auto Hash = llvm::hash_value(Signature);
std::string IdStr = "__hlsl_rootsig_decl_" + std::to_string(Hash);
IdentifierInfo *DeclIdent = &(Actions.getASTContext().Idents.get(IdStr));
LookupResult R(Actions, DeclIdent, SourceLocation(),
Sema::LookupOrdinaryName);
// Check if we have already found a decl of the same name, if we haven't
// then parse the root signature string and construct the in-memory elements
if (!Actions.LookupQualifiedName(R, Actions.CurContext)) {
SourceLocation SignatureLoc =
StrLiteral.value()->getExprLoc().getLocWithOffset(
1); // offset 1 for '"'
// Invoke the root signature parser to construct the in-memory constructs
hlsl::RootSignatureLexer Lexer(Signature, SignatureLoc);
SmallVector<llvm::hlsl::rootsig::RootElement> RootElements;
hlsl::RootSignatureParser Parser(RootElements, Lexer, PP);
if (Parser.parse()) {
T.consumeClose();
return;
}
// Create the Root Signature
auto *SignatureDecl = HLSLRootSignatureDecl::Create(
Actions.getASTContext(), /*DeclContext=*/Actions.CurContext,
RootSignatureLoc, DeclIdent, RootElements);
SignatureDecl->setImplicit();
Actions.PushOnScopeChains(SignatureDecl, getCurScope());
}
// Create the arg for the ParsedAttr
IdentifierLoc *ILoc = ::new (Actions.getASTContext())
IdentifierLoc(RootSignatureLoc, DeclIdent);
ArgsVector Args = {ILoc};
if (!T.consumeClose())
Attrs.addNew(RootSignatureIdent,
SourceRange(RootSignatureLoc, T.getCloseLocation()), nullptr,
SourceLocation(), Args.data(), Args.size(),
ParsedAttr::Form::Microsoft());
}
/// ParseMicrosoftAttributes - Parse Microsoft attributes [Attr]
///
/// [MS] ms-attribute:
@ -5345,6 +5431,8 @@ void Parser::ParseMicrosoftAttributes(ParsedAttributes &Attrs) {
break;
if (Tok.getIdentifierInfo()->getName() == "uuid")
ParseMicrosoftUuidAttributeArgs(Attrs);
else if (Tok.getIdentifierInfo()->getName() == "RootSignature")
ParseMicrosoftRootSignatureAttributeArgs(Attrs);
else {
IdentifierInfo *II = Tok.getIdentifierInfo();
SourceLocation NameLoc = Tok.getLocation();

View File

@ -7481,6 +7481,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
break;
// HLSL attributes:
case ParsedAttr::AT_RootSignature:
S.HLSL().handleRootSignatureAttr(D, AL);
break;
case ParsedAttr::AT_HLSLNumThreads:
S.HLSL().handleNumThreadsAttr(D, AL);
break;

View File

@ -29,6 +29,7 @@
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/ParsedAttr.h"
#include "clang/Sema/Sema.h"
#include "clang/Sema/Template.h"
@ -950,6 +951,33 @@ void SemaHLSL::emitLogicalOperatorFixIt(Expr *LHS, Expr *RHS,
<< NewFnName << FixItHint::CreateReplacement(FullRange, OS.str());
}
void SemaHLSL::handleRootSignatureAttr(Decl *D, const ParsedAttr &AL) {
if (AL.getNumArgs() != 1) {
Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
return;
}
IdentifierInfo *Ident = AL.getArgAsIdent(0)->getIdentifierInfo();
if (auto *RS = D->getAttr<RootSignatureAttr>()) {
if (RS->getSignature() != Ident) {
Diag(AL.getLoc(), diag::err_disallowed_duplicate_attribute) << RS;
return;
}
Diag(AL.getLoc(), diag::warn_duplicate_attribute_exact) << RS;
return;
}
LookupResult R(SemaRef, Ident, SourceLocation(), Sema::LookupOrdinaryName);
if (SemaRef.LookupQualifiedName(R, D->getDeclContext()))
if (auto *SignatureDecl =
dyn_cast<HLSLRootSignatureDecl>(R.getFoundDecl())) {
// Perform validation of constructs here
D->addAttr(::new (getASTContext())
RootSignatureAttr(getASTContext(), AL, Ident));
}
}
void SemaHLSL::handleNumThreadsAttr(Decl *D, const ParsedAttr &AL) {
llvm::VersionTuple SMVersion =
getASTContext().getTargetInfo().getTriple().getOSVersion();

View File

@ -999,6 +999,11 @@ Decl *TemplateDeclInstantiator::VisitHLSLBufferDecl(HLSLBufferDecl *Decl) {
llvm_unreachable("HLSL buffer declarations cannot be instantiated");
}
Decl *TemplateDeclInstantiator::VisitHLSLRootSignatureDecl(
HLSLRootSignatureDecl *Decl) {
llvm_unreachable("HLSL root signature declarations cannot be instantiated");
}
Decl *
TemplateDeclInstantiator::VisitPragmaCommentDecl(PragmaCommentDecl *D) {
llvm_unreachable("pragma comment cannot be instantiated");

View File

@ -458,6 +458,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
case Decl::RequiresExprBody:
case Decl::UnresolvedUsingIfExists:
case Decl::HLSLBuffer:
case Decl::HLSLRootSignature:
case Decl::OpenACCDeclare:
case Decl::OpenACCRoutine:
return false;

View File

@ -0,0 +1,75 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -ast-dump \
// RUN: -disable-llvm-passes -o - %s | FileCheck %s
// This test ensures that the sample root signature is parsed without error and
// the Attr AST Node is created succesfully. If an invalid root signature was
// passed in then we would exit out of Sema before the Attr is created.
#define SampleRS \
"DescriptorTable( " \
" CBV(b1), " \
" SRV(t1, numDescriptors = 8, " \
" flags = DESCRIPTORS_VOLATILE), " \
" UAV(u1, numDescriptors = 0, " \
" flags = DESCRIPTORS_VOLATILE) " \
"), " \
"DescriptorTable(Sampler(s0, numDescriptors = 4, space = 1))"
// CHECK: -HLSLRootSignatureDecl 0x{{.*}} {{.*}} implicit [[SAMPLE_RS_DECL:__hlsl_rootsig_decl_\d*]]
// CHECK-SAME: RootElements{
// CHECK-SAME: CBV(b1, numDescriptors = 1, space = 0,
// CHECK-SAME: offset = DescriptorTableOffsetAppend, flags = DataStaticWhileSetAtExecute),
// CHECK-SAME: SRV(t1, numDescriptors = 8, space = 0,
// CHECK-SAME: offset = DescriptorTableOffsetAppend, flags = DescriptorsVolatile),
// CHECK-SAME: UAV(u1, numDescriptors = 0, space = 0,
// CHECK-SAME: offset = DescriptorTableOffsetAppend, flags = DescriptorsVolatile),
// CHECK-SAME: DescriptorTable(numClauses = 3, visibility = All),
// CHECK-SAME: Sampler(s0, numDescriptors = 4, space = 1,
// CHECK-SAME: offset = DescriptorTableOffsetAppend, flags = None),
// CHECK-SAME: DescriptorTable(numClauses = 1, visibility = All)
// CHECK-SAME: }
// CHECK: -RootSignatureAttr 0x{{.*}} {{.*}} [[SAMPLE_RS_DECL]]
[RootSignature(SampleRS)]
void rs_main() {}
// Ensure that if multiple root signatures are specified at different entry
// points that we point to the correct root signature
// CHECK: -RootSignatureAttr 0x{{.*}} {{.*}} [[SAMPLE_RS_DECL]]
[RootSignature(SampleRS)]
void same_rs_main() {}
// Define the same root signature to ensure that the entry point will still
// link to the same root signature declaration
#define SampleSameRS \
"DescriptorTable( " \
" CBV(b1), " \
" SRV(t1, numDescriptors = 8, " \
" flags = DESCRIPTORS_VOLATILE), " \
" UAV(u1, numDescriptors = 0, " \
" flags = DESCRIPTORS_VOLATILE) " \
"), " \
"DescriptorTable(Sampler(s0, numDescriptors = 4, space = 1))"
// CHECK: -RootSignatureAttr 0x{{.*}} {{.*}} [[SAMPLE_RS_DECL]]
[RootSignature(SampleSameRS)]
void same_rs_string_main() {}
#define SampleDifferentRS \
"DescriptorTable(Sampler(s0, numDescriptors = 4, space = 1))"
// Ensure that when we define a different type root signature that it creates
// a seperate decl and identifier to reference
// CHECK: -HLSLRootSignatureDecl 0x{{.*}} {{.*}} implicit [[DIFF_RS_DECL:__hlsl_rootsig_decl_\d*]]
// CHECK-SAME: RootElements{
// CHECK-SAME: Sampler(s0, numDescriptors = 4, space = 1,
// CHECK-SAME: offset = DescriptorTableOffsetAppend, flags = None),
// CHECK-SAME: DescriptorTable(numClauses = 1, visibility = All)
// CHECK-SAME: }
// CHECK: -RootSignatureAttr 0x{{.*}} {{.*}} [[DIFF_RS_DECL]]
[RootSignature(SampleDifferentRS)]
void different_rs_string_main() {}

View File

@ -0,0 +1,20 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - %s -verify
// Attr test
[RootSignature()] // expected-error {{expected string literal as argument of 'RootSignature' attribute}}
void bad_root_signature_0() {}
// expected-error@+2 {{expected ')'}}
// expected-note@+1 {{to match this '('}}
[RootSignature("", "")]
void bad_root_signature_1() {}
[RootSignature(""), RootSignature("DescriptorTable()")] // expected-error {{attribute 'RootSignature' cannot appear more than once on a declaration}}
void bad_root_signature_2() {}
[RootSignature(""), RootSignature("")] // expected-warning {{attribute 'RootSignature' is already applied}}
void bad_root_signature_3() {}
[RootSignature("DescriptorTable(), invalid")] // expected-error {{expected end of stream to denote end of parameters, or, another valid parameter of RootSignature}}
void bad_root_signature_4() {}

View File

@ -7229,6 +7229,7 @@ CXCursor clang_getCursorDefinition(CXCursor C) {
case Decl::MSProperty:
case Decl::MSGuid:
case Decl::HLSLBuffer:
case Decl::HLSLRootSignature:
case Decl::UnnamedGlobalConstant:
case Decl::TemplateParamObject:
case Decl::IndirectField:

View File

@ -14,6 +14,7 @@
#ifndef LLVM_FRONTEND_HLSL_HLSLROOTSIGNATURE_H
#define LLVM_FRONTEND_HLSL_HLSLROOTSIGNATURE_H
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/DXILABI.h"
#include "llvm/Support/raw_ostream.h"
#include <variant>
@ -122,6 +123,8 @@ struct DescriptorTableClause {
using RootElement = std::variant<RootFlags, RootConstants, DescriptorTable,
DescriptorTableClause>;
void dumpRootElements(raw_ostream &OS, ArrayRef<RootElement> Elements);
} // namespace rootsig
} // namespace hlsl
} // namespace llvm

View File

@ -144,6 +144,22 @@ void DescriptorTableClause::dump(raw_ostream &OS) const {
OS << ", flags = " << Flags << ")";
}
void dumpRootElements(raw_ostream &OS, ArrayRef<RootElement> Elements) {
OS << "RootElements{";
bool First = true;
for (const RootElement &Element : Elements) {
if (!First)
OS << ",";
OS << " ";
First = false;
if (const auto &Clause = std::get_if<DescriptorTableClause>(&Element))
Clause->dump(OS);
if (const auto &Table = std::get_if<DescriptorTable>(&Element))
Table->dump(OS);
}
OS << "}";
}
} // namespace rootsig
} // namespace hlsl
} // namespace llvm