Skip to content

[HLSL][RootSignature] Implement parsing of a DescriptorTable with empty clauses #133302

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 7 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
22 changes: 11 additions & 11 deletions clang/include/clang/Lex/HLSLRootSignatureTokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
//===----------------------------------------------------------------------===//

#ifndef TOK
#define TOK(X)
#define TOK(X, SPELLING)
#endif
#ifndef PUNCTUATOR
#define PUNCTUATOR(X,Y) TOK(pu_ ## X)
#define PUNCTUATOR(X,Y) TOK(pu_ ## X, Y)
#endif
#ifndef KEYWORD
#define KEYWORD(X) TOK(kw_ ## X)
#define KEYWORD(X) TOK(kw_ ## X, #X)
#endif
#ifndef ENUM
#define ENUM(NAME, LIT) TOK(en_ ## NAME)
#define ENUM(NAME, LIT) TOK(en_ ## NAME, LIT)
#endif

// Defines the various types of enum
Expand All @@ -49,15 +49,15 @@
#endif

// General Tokens:
TOK(invalid)
TOK(end_of_stream)
TOK(int_literal)
TOK(invalid, "invalid identifier")
TOK(end_of_stream, "end of stream")
TOK(int_literal, "integer literal")

// Register Tokens:
TOK(bReg)
TOK(tReg)
TOK(uReg)
TOK(sReg)
TOK(bReg, "b register")
TOK(tReg, "t register")
TOK(uReg, "u register")
TOK(sReg, "s register")

// Punctuators:
PUNCTUATOR(l_paren, '(')
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Lex/LexHLSLRootSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace hlsl {

struct RootSignatureToken {
enum Kind {
#define TOK(X) X,
#define TOK(X, SPELLING) X,
#include "clang/Lex/HLSLRootSignatureTokenKinds.def"
};

Expand Down
110 changes: 110 additions & 0 deletions clang/include/clang/Parse/ParseHLSLRootSignature.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//===--- ParseHLSLRootSignature.h -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the ParseHLSLRootSignature interface.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_PARSE_PARSEHLSLROOTSIGNATURE_H
#define LLVM_CLANG_PARSE_PARSEHLSLROOTSIGNATURE_H

#include "clang/Basic/DiagnosticParse.h"
#include "clang/Lex/LexHLSLRootSignature.h"
#include "clang/Lex/Preprocessor.h"

#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"

#include "llvm/Frontend/HLSL/HLSLRootSignature.h"

namespace clang {
namespace hlsl {

class RootSignatureParser {
public:
RootSignatureParser(SmallVector<llvm::hlsl::rootsig::RootElement> &Elements,
RootSignatureLexer &Lexer, clang::Preprocessor &PP);

/// Consumes tokens from the Lexer and constructs the in-memory
/// representations of the RootElements. Tokens are consumed until an
/// error is encountered or the end of the buffer.
///
/// Returns true if a parsing error is encountered.
bool Parse();

private:
DiagnosticsEngine &Diags() { return PP.getDiagnostics(); }

// All private Parse.* methods follow a similar pattern:
// - Each method will start with an assert to denote what the CurToken is
// expected to be and will parse from that token forward
//
// - Therefore, it is the callers responsibility to ensure that you are
// at the correct CurToken. This should be done with the pattern of:
//
// if (TryConsumeExpectedToken(TokenKind))
// if (Parse.*())
// return true;
//
// or,
//
// if (ConsumeExpectedToken(TokenKind, ...))
// return true;
// if (Parse.*())
// return true;
//
// - All methods return true if a parsing error is encountered. It is the
// callers responsibility to propogate this error up, or deal with it
// otherwise
//
// - An error will be raised if the proceeding tokens are not what is
// expected, or, there is a lexing error

/// Root Element parse methods:
bool ParseDescriptorTable();
bool ParseDescriptorTableClause();

/// Invoke the Lexer to consume a token and update CurToken with the result
void ConsumeNextToken() { CurToken = Lexer.ConsumeToken(); }

/// Return true if the next token one of the expected kinds
bool PeekExpectedToken(TokenKind Expected);
bool PeekExpectedToken(ArrayRef<TokenKind> AnyExpected);

/// Consumes the next token and report an error if it is not of the expected
/// kind.
///
/// Returns true if there was an error reported.
bool ConsumeExpectedToken(TokenKind Expected,
unsigned DiagID = diag::err_expected,
TokenKind Context = TokenKind::invalid);
bool ConsumeExpectedToken(ArrayRef<TokenKind> AnyExpected,
unsigned DiagID = diag::err_expected,
TokenKind Context = TokenKind::invalid);

/// Peek if the next token is of the expected kind and if it is then consume
/// it.
///
/// Returns true if it successfully matches the expected kind and the token
/// was consumed.
bool TryConsumeExpectedToken(TokenKind Expected);
bool TryConsumeExpectedToken(ArrayRef<TokenKind> Expected);

private:
SmallVector<llvm::hlsl::rootsig::RootElement> &Elements;
RootSignatureLexer &Lexer;

clang::Preprocessor &PP;

RootSignatureToken CurToken;
};

} // namespace hlsl
} // namespace clang

#endif // LLVM_CLANG_PARSE_PARSEHLSLROOTSIGNATURE_H
1 change: 1 addition & 0 deletions clang/lib/Parse/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ add_clang_library(clangParse
ParseExpr.cpp
ParseExprCXX.cpp
ParseHLSL.cpp
ParseHLSLRootSignature.cpp
ParseInit.cpp
ParseObjc.cpp
ParseOpenMP.cpp
Expand Down
188 changes: 188 additions & 0 deletions clang/lib/Parse/ParseHLSLRootSignature.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include "clang/Parse/ParseHLSLRootSignature.h"

#include "llvm/Support/raw_ostream.h"

using namespace llvm::hlsl::rootsig;

namespace clang {
namespace hlsl {

static std::string FormatTokenKinds(ArrayRef<TokenKind> Kinds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You can simplify this while also making it more efficient by implementing a TokenFormatter class with an operator<< for DiagnosticBuilder. The class should be okay to hold an ArrayRef as a member since it will always be used as a temporary, like DB << TokenFormatter(Tokens);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to define the const operator<< for TokenKind and removed the api to use consumeExpectedToken(ArrayRef)

std::string TokenString;
llvm::raw_string_ostream Out(TokenString);
bool First = true;
for (auto Kind : Kinds) {
if (!First)
Out << ", ";
switch (Kind) {
#define TOK(X, SPELLING) \
case TokenKind::X: \
Out << SPELLING; \
break;
#include "clang/Lex/HLSLRootSignatureTokenKinds.def"
}
First = false;
}

return TokenString;
}

// Parser Definitions
Copy link
Contributor Author

Choose a reason for hiding this comment

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

nit: can remove


RootSignatureParser::RootSignatureParser(SmallVector<RootElement> &Elements,
RootSignatureLexer &Lexer,
Preprocessor &PP)
: Elements(Elements), Lexer(Lexer), PP(PP), CurToken(SourceLocation()) {}

bool RootSignatureParser::Parse() {
// Iterate as many RootElements as possible
while (TryConsumeExpectedToken(TokenKind::kw_DescriptorTable)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Something that makes me confuse with that approach is, how will that look once you add more RootElements? Will that become something like:

while (
TryConsumeExpectedToken(TokenKind::kw_DescriptorTable) || 
TryConsumeExpectedToken(TokenKind::kw_RootDescriptor) || 
TryConsumeExpectedToken(TokenKind::kw_RootConstant)) {
...
}

My expectation for that code would be something like:

do{
// Parsing logic here
} while(TryConsumeExpectedToken(TokenKind::pu_comma));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, it will look like how you mentioned in the other comment:

while (TryConsumeExpectedToken({TokenKind::kw_DescriptorTable, TokenKind::kw_RootConstant, ... }) {
...
}

bool Error = false;
// Dispatch onto parser method.
// We guard against the unreachable here as we just ensured that CurToken
// will be one of the kinds in the while condition
switch (CurToken.Kind) {
case TokenKind::kw_DescriptorTable:
Error = ParseDescriptorTable();
break;
default:
llvm_unreachable("Switch for consumed token was not provided");
}

if (Error)
return true;

if (!TryConsumeExpectedToken(TokenKind::pu_comma))
break;
}

return ConsumeExpectedToken(TokenKind::end_of_stream, diag::err_expected);
}

bool RootSignatureParser::ParseDescriptorTable() {
assert(CurToken.Kind == TokenKind::kw_DescriptorTable &&
"Expects to only be invoked starting at given keyword");

DescriptorTable Table;

if (ConsumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after,
CurToken.Kind))
return true;

// Iterate as many Clauses as possible
while (TryConsumeExpectedToken({TokenKind::kw_CBV, TokenKind::kw_SRV,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I imagine the above loop would be something like this, once you add more Root Elements?

TokenKind::kw_UAV, TokenKind::kw_Sampler})) {
if (ParseDescriptorTableClause())
return true;

Table.NumClauses++;

if (!TryConsumeExpectedToken(TokenKind::pu_comma))
break;
}

if (ConsumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after,
CurToken.Kind))
return true;

Elements.push_back(Table);
return false;
}

bool RootSignatureParser::ParseDescriptorTableClause() {
assert((CurToken.Kind == TokenKind::kw_CBV ||
CurToken.Kind == TokenKind::kw_SRV ||
CurToken.Kind == TokenKind::kw_UAV ||
CurToken.Kind == TokenKind::kw_Sampler) &&
"Expects to only be invoked starting at given keyword");

DescriptorTableClause Clause;
switch (CurToken.Kind) {
default:
break; // Unreachable given Try + assert pattern
case TokenKind::kw_CBV:
Clause.Type = ClauseType::CBuffer;
break;
case TokenKind::kw_SRV:
Clause.Type = ClauseType::SRV;
break;
case TokenKind::kw_UAV:
Clause.Type = ClauseType::UAV;
break;
case TokenKind::kw_Sampler:
Clause.Type = ClauseType::Sampler;
break;
}

if (ConsumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after,
CurToken.Kind))
return true;

if (ConsumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after,
CurToken.Kind))
return true;

Elements.push_back(Clause);
return false;
}

// Returns true when given token is one of the expected kinds
static bool IsExpectedToken(TokenKind Kind, ArrayRef<TokenKind> AnyExpected) {
for (auto Expected : AnyExpected)
if (Kind == Expected)
return true;
return false;
}

bool RootSignatureParser::PeekExpectedToken(TokenKind Expected) {
return PeekExpectedToken(ArrayRef{Expected});
}

bool RootSignatureParser::PeekExpectedToken(ArrayRef<TokenKind> AnyExpected) {
RootSignatureToken Result = Lexer.PeekNextToken();
return IsExpectedToken(Result.Kind, AnyExpected);
}

bool RootSignatureParser::ConsumeExpectedToken(TokenKind Expected,
unsigned DiagID,
TokenKind Context) {
return ConsumeExpectedToken(ArrayRef{Expected}, DiagID, Context);
}

bool RootSignatureParser::ConsumeExpectedToken(ArrayRef<TokenKind> AnyExpected,
unsigned DiagID,
TokenKind Context) {
if (TryConsumeExpectedToken(AnyExpected))
return false;

// Report unexpected token kind error
DiagnosticBuilder DB = Diags().Report(CurToken.TokLoc, DiagID);
switch (DiagID) {
case diag::err_expected:
DB << FormatTokenKinds(AnyExpected);
break;
case diag::err_expected_either:
case diag::err_expected_after:
DB << FormatTokenKinds(AnyExpected) << FormatTokenKinds({Context});
break;
default:
break;
}
return true;
}

bool RootSignatureParser::TryConsumeExpectedToken(TokenKind Expected) {
return TryConsumeExpectedToken(ArrayRef{Expected});
}

bool RootSignatureParser::TryConsumeExpectedToken(
ArrayRef<TokenKind> AnyExpected) {
// If not the expected token just return
if (!PeekExpectedToken(AnyExpected))
return false;
ConsumeNextToken();
return true;
}

} // namespace hlsl
} // namespace clang
1 change: 1 addition & 0 deletions clang/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ endfunction()

add_subdirectory(Basic)
add_subdirectory(Lex)
add_subdirectory(Parse)
add_subdirectory(Driver)
if(CLANG_ENABLE_STATIC_ANALYZER)
add_subdirectory(Analysis)
Expand Down
2 changes: 1 addition & 1 deletion clang/unittests/Lex/LexHLSLRootSignatureTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ TEST_F(LexHLSLRootSignatureTest, ValidLexAllTokensTest) {

SmallVector<hlsl::RootSignatureToken> Tokens;
SmallVector<hlsl::TokenKind> Expected = {
#define TOK(NAME) hlsl::TokenKind::NAME,
#define TOK(NAME, SPELLING) hlsl::TokenKind::NAME,
#include "clang/Lex/HLSLRootSignatureTokenKinds.def"
};

Expand Down
Loading