[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
inbelic wrote: I don't have any strong arguments against structuring it like described and have updated accordingly. Since we are changing the calling convention of the `parse.*` methods to use `std::optional` I had originally also updated the `parseDescriptorTable` and `parseDescriptorTableClause` as well. But it made the diff difficult to read. Changes to make the calling convention consistent with those methods are in the branch [here](https://github.com/inbelic/llvm-project/tree/inbelic/rs-update-call-convs). I will create the clean up pr once this one merges https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/bogner approved this pull request. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/133800 >From 9ff87eb37437dc92a554d1d89b236e9a13249694 Mon Sep 17 00:00:00 2001 From: Finn Plummer Date: Mon, 31 Mar 2025 18:29:26 + Subject: [PATCH 01/10] [HLSL][RootSignature] Add infastructure to parse parameters - defines `ParamType` as a way to represent a reference to some parameter in a root signature - defines `ParseParam` and `ParseParams` as an infastructure to define how the parameters of a given struct should be parsed in an orderless manner --- .../clang/Basic/DiagnosticParseKinds.td | 4 +- .../clang/Parse/ParseHLSLRootSignature.h | 32 clang/lib/Parse/ParseHLSLRootSignature.cpp| 51 +++ .../llvm/Frontend/HLSL/HLSLRootSignature.h| 6 +++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 954f538e15026..2a0bf81c68e44 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1830,8 +1830,10 @@ def err_hlsl_virtual_function def err_hlsl_virtual_inheritance : Error<"virtual inheritance is unsupported in HLSL">; -// HLSL Root Siganture diagnostic messages +// HLSL Root Signature Parser Diagnostics def err_hlsl_unexpected_end_of_params : Error<"expected %0 to denote end of parameters, or, another valid parameter of %1">; +def err_hlsl_rootsig_repeat_param : Error<"specified the same parameter '%0' multiple times">; +def err_hlsl_rootsig_missing_param : Error<"did not specify mandatory parameter '%0'">; } // end of Parser diagnostics diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h index 18cc2c6692551..55d84f91b8834 100644 --- a/clang/include/clang/Parse/ParseHLSLRootSignature.h +++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h @@ -69,6 +69,38 @@ class RootSignatureParser { bool parseDescriptorTable(); bool parseDescriptorTableClause(); + /// Each unique ParamType will have a custom parse method defined that we can + /// use to invoke the parameters. + /// + /// This function will switch on the ParamType using std::visit and dispatch + /// onto the corresponding parse method + bool parseParam(llvm::hlsl::rootsig::ParamType Ref); + + /// Parameter arguments (eg. `bReg`, `space`, ...) can be specified in any + /// order, exactly once, and only a subset are mandatory. This function acts + /// as the infastructure to do so in a declarative way. + /// + /// For the example: + /// SmallDenseMap Params = { + ///TokenKind::bReg, &Clause.Register, + ///TokenKind::kw_space, &Clause.Space + /// }; + /// SmallDenseSet Mandatory = { + ///TokenKind::kw_numDescriptors + /// }; + /// + /// We can read it is as: + /// + /// when 'b0' is encountered, invoke the parse method for the type + /// of &Clause.Register (Register *) and update the parameter + /// when 'space' is encountered, invoke a parse method for the type + /// of &Clause.Space (uint32_t *) and update the parameter + /// + /// and 'bReg' must be specified + bool parseParams( + llvm::SmallDenseMap &Params, + llvm::SmallDenseSet &Mandatory); + /// Invoke the Lexer to consume a token and update CurToken with the result void consumeNextToken() { CurToken = Lexer.ConsumeToken(); } diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp index 93a9689ebdf72..54c51e56b84dd 100644 --- a/clang/lib/Parse/ParseHLSLRootSignature.cpp +++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp @@ -120,6 +120,57 @@ bool RootSignatureParser::parseDescriptorTableClause() { return false; } +// Helper struct so that we can use the overloaded notation of std::visit +template struct ParseMethods : Ts... { using Ts::operator()...; }; +template ParseMethods(Ts...) -> ParseMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + bool Error = true; + std::visit(ParseMethods{}, Ref); + + return Error; +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.Kind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.Kind; + return true; +} +Seen.insert(CurToken.Kind); + +if (parseParam(Params[CurToken.Kind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = S
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/133800 >From 9ff87eb37437dc92a554d1d89b236e9a13249694 Mon Sep 17 00:00:00 2001 From: Finn Plummer Date: Mon, 31 Mar 2025 18:29:26 + Subject: [PATCH 01/11] [HLSL][RootSignature] Add infastructure to parse parameters - defines `ParamType` as a way to represent a reference to some parameter in a root signature - defines `ParseParam` and `ParseParams` as an infastructure to define how the parameters of a given struct should be parsed in an orderless manner --- .../clang/Basic/DiagnosticParseKinds.td | 4 +- .../clang/Parse/ParseHLSLRootSignature.h | 32 clang/lib/Parse/ParseHLSLRootSignature.cpp| 51 +++ .../llvm/Frontend/HLSL/HLSLRootSignature.h| 6 +++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 954f538e15026..2a0bf81c68e44 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1830,8 +1830,10 @@ def err_hlsl_virtual_function def err_hlsl_virtual_inheritance : Error<"virtual inheritance is unsupported in HLSL">; -// HLSL Root Siganture diagnostic messages +// HLSL Root Signature Parser Diagnostics def err_hlsl_unexpected_end_of_params : Error<"expected %0 to denote end of parameters, or, another valid parameter of %1">; +def err_hlsl_rootsig_repeat_param : Error<"specified the same parameter '%0' multiple times">; +def err_hlsl_rootsig_missing_param : Error<"did not specify mandatory parameter '%0'">; } // end of Parser diagnostics diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h index 18cc2c6692551..55d84f91b8834 100644 --- a/clang/include/clang/Parse/ParseHLSLRootSignature.h +++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h @@ -69,6 +69,38 @@ class RootSignatureParser { bool parseDescriptorTable(); bool parseDescriptorTableClause(); + /// Each unique ParamType will have a custom parse method defined that we can + /// use to invoke the parameters. + /// + /// This function will switch on the ParamType using std::visit and dispatch + /// onto the corresponding parse method + bool parseParam(llvm::hlsl::rootsig::ParamType Ref); + + /// Parameter arguments (eg. `bReg`, `space`, ...) can be specified in any + /// order, exactly once, and only a subset are mandatory. This function acts + /// as the infastructure to do so in a declarative way. + /// + /// For the example: + /// SmallDenseMap Params = { + ///TokenKind::bReg, &Clause.Register, + ///TokenKind::kw_space, &Clause.Space + /// }; + /// SmallDenseSet Mandatory = { + ///TokenKind::kw_numDescriptors + /// }; + /// + /// We can read it is as: + /// + /// when 'b0' is encountered, invoke the parse method for the type + /// of &Clause.Register (Register *) and update the parameter + /// when 'space' is encountered, invoke a parse method for the type + /// of &Clause.Space (uint32_t *) and update the parameter + /// + /// and 'bReg' must be specified + bool parseParams( + llvm::SmallDenseMap &Params, + llvm::SmallDenseSet &Mandatory); + /// Invoke the Lexer to consume a token and update CurToken with the result void consumeNextToken() { CurToken = Lexer.ConsumeToken(); } diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp index 93a9689ebdf72..54c51e56b84dd 100644 --- a/clang/lib/Parse/ParseHLSLRootSignature.cpp +++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp @@ -120,6 +120,57 @@ bool RootSignatureParser::parseDescriptorTableClause() { return false; } +// Helper struct so that we can use the overloaded notation of std::visit +template struct ParseMethods : Ts... { using Ts::operator()...; }; +template ParseMethods(Ts...) -> ParseMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + bool Error = true; + std::visit(ParseMethods{}, Ref); + + return Error; +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.Kind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.Kind; + return true; +} +Seen.insert(CurToken.Kind); + +if (parseParam(Params[CurToken.Kind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = S
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
bogner wrote: I'm not entirely convinced the generic `ParsedParamState` is the right level of abstraction. In some ways the declarative nature is nice, but I think it's quite a bit more complex than a simple recursive descent here. Warning - lots of untested and never compiled code follows. Consider an API like so: ```c++ struct ParsedParam { std::optional Register; std::optional Space; }; std::optional parseParameter(TokenKind RegType); ``` This could then be used in `parseDescriptorClause for the various parameter kinds: ```c++ DescriptorTableClause Clause; std::optional P; switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; P = parseParameter(TokenKind::bReg); break; // ... } if (!P.has_value()) // diagnose // otherwise we have a parameter! ``` then parseParameter can just can follow the same pattern and just recurse into the various members: ```c++ std::optional parseParameter(TokenKind RegType) { if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, ParamKind)) return std::nullopt; ParsedParam Result; do { if (tryConsumeExpectedToken(RegType)) { if (Result.Register.has_value()) { getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) << CurToken.TokKind; return true; } if (auto Register = parseRegister(Params.Register)) Result.Register = *Register; } if (tryConsumeExpectedToken(RootSignatureToken::Kind::kw_space)) { if (Result.Space.has_value()) { getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) << CurToken.TokKind; return true; } if (auto Space = parseUIntParam()) Result.Register = *Register; } } while (tryConsumeExpectedToken(TokenKind::pu_comma)); if (!consumeExpectedToken(TokenKind::pu_r_paren, diag::err_hlsl_unexpected_end_of_params, /*param of=*/ParamKind)) return std::nullopt; if (!Result.Register.has_value()) { getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) << ExpectedRegister; return std::nullopt; } return Result; ``` This also makes the functions match the shape of the grammar formulations in the EBNF notation. I suspect that any code duplication we find by doing it this way would be fairly easy to clean up with a few helper functions here and there, and since the functions should stay relatively short I think it keeps it reasonably simple. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -32,11 +39,19 @@ struct DescriptorTable { using ClauseType = llvm::dxil::ResourceClass; struct DescriptorTableClause { ClauseType Type; + Register Register; + uint32_t Space = 0; }; // Models RootElement : DescriptorTable | DescriptorTableClause using RootElement = std::variant; +// ParamType is used as an 'any' type that will reference to a parameter in +// RootElement. Each variant of ParamType is expected to have a Parse method +// defined that will be dispatched on when we are attempting to parse a +// parameter +using ParamType = std::variant; inbelic wrote: Can be removed completely now if we use the struct approach https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { inbelic wrote: > I don't think what you've described int he code example here is where the > genericness becomes a problem. > I think you draw a false equivalence. You seem to be saying we either do this > generically, or we do this like DXC... That's not what I'm saying. The > approach that Clang takes generally in parsing is to parse things out, and > from the parsed information construct a declaration or statement, which then > gets validated during construction. > > That is not how DXC's parser for root signatures works, nor is it what you're > proposing, but maybe it's the better approach. I see, then I did misinterpret what the other approach could be. IIUC in this context, we might have an intermediate object `ParsedParamState` used to represent parameter values. `parseParams` would go through and parse the values to this object. Then we can construct our in-memory structs (`DescriptorTableClause`) from this object and validate the parameter values were specified correctly. The most recent commit is a prototype of this, expect we allow some validation to be done when parsing. > I think the bigger question is at what layer of abstraction does being > generic help and at what layer of abstraction is it a hinderance. I think a good case to illustrate what level of abstraction we want is to consider parsing the `flags = FLAGS` parameter, where `FLAGS` could be a variety of flag types (`RootFlags`, `DescriptorRangeFlags`, etc. Given the root element, (`RootCBV`, `CBV`, etc), it will directly imply the expected flag type. Imo, we should validate it is the expected flag type during parsing instead of validation. Otherwise, we are throwing out that information and are forced to parse the flags in a general manner, only to rederive what the valid type is later. Similar with register types, if we have `CBV` we should only parse a `b` register. This allows for better localized diag messages by raising an error earlier at the invalid register or flag type. So, I don't think the abstraction to have _all_ validation after parsing is beneficial. But we can have the ones that make sense to be when constructing the elements (checking which are mandatory), as this is more clear. > Having a `parseRootParam` function that "generically" parses a root-level > parameter seems like a good idea, but should it remain generic based on the > type of the parameter? Should we have a single "parseArgument" function? > Maybe... Should these functions produce the same structural result for every > parameter? These things are less clear to me. > The reasons that led you to a generic solution, might be reasons why > following existing patterns that Clang uses for order-independent parsing > (like attributes), might be the better architectural decision. Maybe we > should borrow more from Clang's AST design and have polymorphic returned > objects rather than variants. I do think that using variants and mapping the variant types to a `parseMethod` is more confusing than it needs to be. Using a stateful struct is more clear in assigning them and easy to follow. And it removes the need for having to work around having an `any` type with polymorphic objects or variants. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic updated https://github.com/llvm/llvm-project/pull/133800 >From 9ff87eb37437dc92a554d1d89b236e9a13249694 Mon Sep 17 00:00:00 2001 From: Finn Plummer Date: Mon, 31 Mar 2025 18:29:26 + Subject: [PATCH 1/8] [HLSL][RootSignature] Add infastructure to parse parameters - defines `ParamType` as a way to represent a reference to some parameter in a root signature - defines `ParseParam` and `ParseParams` as an infastructure to define how the parameters of a given struct should be parsed in an orderless manner --- .../clang/Basic/DiagnosticParseKinds.td | 4 +- .../clang/Parse/ParseHLSLRootSignature.h | 32 clang/lib/Parse/ParseHLSLRootSignature.cpp| 51 +++ .../llvm/Frontend/HLSL/HLSLRootSignature.h| 6 +++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 954f538e15026..2a0bf81c68e44 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1830,8 +1830,10 @@ def err_hlsl_virtual_function def err_hlsl_virtual_inheritance : Error<"virtual inheritance is unsupported in HLSL">; -// HLSL Root Siganture diagnostic messages +// HLSL Root Signature Parser Diagnostics def err_hlsl_unexpected_end_of_params : Error<"expected %0 to denote end of parameters, or, another valid parameter of %1">; +def err_hlsl_rootsig_repeat_param : Error<"specified the same parameter '%0' multiple times">; +def err_hlsl_rootsig_missing_param : Error<"did not specify mandatory parameter '%0'">; } // end of Parser diagnostics diff --git a/clang/include/clang/Parse/ParseHLSLRootSignature.h b/clang/include/clang/Parse/ParseHLSLRootSignature.h index 18cc2c6692551..55d84f91b8834 100644 --- a/clang/include/clang/Parse/ParseHLSLRootSignature.h +++ b/clang/include/clang/Parse/ParseHLSLRootSignature.h @@ -69,6 +69,38 @@ class RootSignatureParser { bool parseDescriptorTable(); bool parseDescriptorTableClause(); + /// Each unique ParamType will have a custom parse method defined that we can + /// use to invoke the parameters. + /// + /// This function will switch on the ParamType using std::visit and dispatch + /// onto the corresponding parse method + bool parseParam(llvm::hlsl::rootsig::ParamType Ref); + + /// Parameter arguments (eg. `bReg`, `space`, ...) can be specified in any + /// order, exactly once, and only a subset are mandatory. This function acts + /// as the infastructure to do so in a declarative way. + /// + /// For the example: + /// SmallDenseMap Params = { + ///TokenKind::bReg, &Clause.Register, + ///TokenKind::kw_space, &Clause.Space + /// }; + /// SmallDenseSet Mandatory = { + ///TokenKind::kw_numDescriptors + /// }; + /// + /// We can read it is as: + /// + /// when 'b0' is encountered, invoke the parse method for the type + /// of &Clause.Register (Register *) and update the parameter + /// when 'space' is encountered, invoke a parse method for the type + /// of &Clause.Space (uint32_t *) and update the parameter + /// + /// and 'bReg' must be specified + bool parseParams( + llvm::SmallDenseMap &Params, + llvm::SmallDenseSet &Mandatory); + /// Invoke the Lexer to consume a token and update CurToken with the result void consumeNextToken() { CurToken = Lexer.ConsumeToken(); } diff --git a/clang/lib/Parse/ParseHLSLRootSignature.cpp b/clang/lib/Parse/ParseHLSLRootSignature.cpp index 93a9689ebdf72..54c51e56b84dd 100644 --- a/clang/lib/Parse/ParseHLSLRootSignature.cpp +++ b/clang/lib/Parse/ParseHLSLRootSignature.cpp @@ -120,6 +120,57 @@ bool RootSignatureParser::parseDescriptorTableClause() { return false; } +// Helper struct so that we can use the overloaded notation of std::visit +template struct ParseMethods : Ts... { using Ts::operator()...; }; +template ParseMethods(Ts...) -> ParseMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + bool Error = true; + std::visit(ParseMethods{}, Ref); + + return Error; +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.Kind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.Kind; + return true; +} +Seen.insert(CurToken.Kind); + +if (parseParam(Params[CurToken.Kind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = See
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { llvm-beanz wrote: > Hm, okay. I think that is where we disagree then: whether it is worthwhile to > make this generic or not. > > I will make the (opinionated) argument for having it implemented generically: > > * There isn't much of a difference in how we will parse the different root > parameter types, `RootSBV(...), CBV(...), StaticSampler(...), > RootConstants(...), etc`, so, adding their respective parse methods in future > prs will just be something like: I don't think what you've described int he code example here is where the genericness becomes a problem. > * We can contrast that with DXC, > [here](https://github.com/microsoft/DirectXShaderCompiler/blob/c940161bb3398ff988fafc343ed1623d4a3fad6c/tools/clang/lib/Parse/HLSLRootSignature.cpp#L1301), > where it needs to redefine the same "seen" and "mandatory" functionality > over and over again. > * I assume that if we don't want to have a generic method in clang then the > code flow would follow a similar pattern as DXC (maybe I don't understand the > struct approach correctly and that is a wrong assumption?). I think you draw a false equivalence. You seem to be saying we either do this generically, or we do this like DXC... That's not what I'm saying. The approach that Clang takes generally in parsing is to parse things out, and from the parsed information construct a declaration or statement, which then gets validated during construction. That is not how DXC's parser for root signatures works, nor is it what you're proposing, but maybe it's the better approach. > * Therefore, using a generic way to parse the parameters of root parameter > types will be clearer in its intent (it is declarative) and easier to extend > (instead of copying functionality over) when we add the other parts of the > RootSignatureParser I think the bigger question is at what layer of abstraction does being generic help and at what layer of abstraction is it a hinderance. Having a `parseRootParam` function that "generically" parses a root-level parameter seems like a good idea, but should it remain generic based on the type of the parameter? Should we have a single "parseArgument" function? Maybe... Should these functions produce the same structural result for every parameter? These things are less clear to me. > These reasons are why I went with the current generic implementation. > > Regarding scalability, the struct of arrays or the map will have a statically > known `N` elements (or pairs), where `N` is the number of parameters for a > given root parameter type. (`N` is not equal to the total number of token > kinds). The largest `N` would be is for `StaticSamplers` with 13, and then > for example `DescriptorTableClause` it is 5. And we could make > `Mandatory/Seen` just be two `uint32_t` bitfields. The reasons that led you to a generic solution, might be reasons why following existing patterns that Clang uses for order-independent parsing (like attributes), might be the better architectural decision. Maybe we should borrow more from Clang's AST design and have polymorphic returned objects rather than variants. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { inbelic wrote: Hm, okay. I think that is where we disagree then: whether it is worthwhile to make this generic or not. I will make the (opinionated) argument for having it implemented generically: - There isn't much of a difference in how we will parse the different root parameter types, `RootSBV(...), CBV(...), StaticSampler(...), RootConstants(...), etc`, so, adding their respective parse methods in future prs will just be something like: ``` bool RootSignatureParser::parseStaticSampler() { using StaticSamplerParams = ParamMap<13>; // N = # of params locally StaticSampler Sampler; StaticSamplerParams Params = { /* Kinds=*/ = { TokenKind::sReg, TokenKind::kw_filter, ... }, /* ParamType=*/ = { &Sampler.Register &Sampler.Filter, ... }, /*Seen=*/ 0x0, /*Mandatory=*/0x1 // only register is required }; if (parseParams(Params)) return true; ... } ``` - We can contrast that with DXC, [here](https://github.com/microsoft/DirectXShaderCompiler/blob/c940161bb3398ff988fafc343ed1623d4a3fad6c/tools/clang/lib/Parse/HLSLRootSignature.cpp#L1301), where it needs to redefine the same "seen" and "mandatory" functionality over and over again. - I assume that if we don't want to have a generic method in clang then the code flow would follow a similar pattern as DXC (maybe I don't understand the struct approach correctly and that is a wrong assumption?). - Therefore, using a generic way to parse the parameters of root parameter types will be clearer in its intent (it is declarative) and easier to extend (instead of copying functionality over) when we add the other parts of the RootSignatureParser These reasons are why I went with the current generic implementation. Regarding scalability, the struct of arrays or the map will have a statically known `N` elements (or pairs), where `N` is the number of parameters for a given root parameter type. (`N` is not equal to the total number of token kinds). The largest `N` would be is for `StaticSamplers` with 13, and then for example `DescriptorTableClause` it is 4. And we could make `Mandatory/Seen` just be two `uint32_t` bitfields. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { llvm-beanz wrote: I'm a bit torn. The pattern used in Clang for things that can be parsed in arbitrary order (like attributes) might be a bit overkill. Clang fully separates parsing from semantic analysis, so it parses all your attributes separate from converting them into AST nodes and attaching them to the proper parent. The structure-of-array approach storing all tokens has some scalability concerns to me, and I'm not sure the genericness-gives benefit. Similarly I'm not sure the map approach as implemented is the right approach. My gut feeling is that we probably shouldn't use the same implementation for parsing descriptor table parameters and sampler parameters. I think a parameters structure for 6 parameters with either bools (which could be uint32_t bitfield members) or optionals to signal if they are seen makes sense. I suspect if they're mandatory should be something we know from context and don't need additional tracking of. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/joaosaffran approved this pull request. LGTM, not an expert, though. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { + {ExpectedRegister, &Clause.Register}, + {TokenKind::kw_space, &Clause.Space}, + }; + llvm::SmallDenseSet Mandatory = { + ExpectedRegister, + }; + + if (parseParams(Params, Mandatory)) +return true; + + if (consumeExpectedToken(TokenKind::pu_r_paren, + diag::err_hlsl_unexpected_end_of_params, + /*param of=*/ParamKind)) return true; Elements.push_back(Clause); return false; } +// Helper struct defined to use the overloaded notation of std::visit. +template struct ParseParamTypeMethods : Ts... { + using Ts::operator()...; +}; +template +ParseParamTypeMethods(Ts...) -> ParseParamTypeMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + return std::visit( + ParseParamTypeMethods{ + [this](Register *X) -> bool { return parseRegister(X); }, + [this](uint32_t *X) -> bool { +return consumeExpectedToken(TokenKind::pu_equal, +diag::err_expected_after, +CurToken.TokKind) || + parseUIntParam(X); + }, + }, + Ref); +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.TokKind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.TokKind; + return true; +} +Seen.insert(CurToken.TokKind); + +if (parseParam(Params[CurToken.TokKind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = Seen.contains(Kind); +if (!SeenParam) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) + << Kind; +} +AllMandatoryDefined &= SeenParam; + } + + return !AllMandatoryDefined; +} + +bool RootSignatureParser::parseUIntParam(uint32_t *X) { + assert(CurToken.TokKind == TokenKind::pu_equal && + "Expects to only be invoked starting at given keyword"); + tryConsumeExpectedToken(TokenKind::pu_plus); + return consumeExpectedToken(TokenKind::int_literal, diag::err_expected_after, + CurToken.TokKind) || + handleUIntLiteral(X); +} + +bool RootSignatureParser::parseRegister(Register *Register) { + assert((CurToken.TokKind == TokenKind::bReg || + CurToken.TokKind == TokenKind::tReg || + CurToken.TokKind == TokenKind::uReg || + CurToken.TokKind == TokenKind::sReg) && + "Expects to only be invoked starting at given keyword"); + + switch (CurToken.TokKind) { + default: +llvm_unreachable("Switch for consumed token was not provided"); + case TokenKind::bReg: +Register->ViewType = RegisterType::BReg; +break; + case TokenKind::tReg: +Register->ViewType = RegisterType::TReg; +break; + case TokenKind::uReg: +Register->ViewType = RegisterType::UReg; +break; + case TokenKind::sReg: +Register->ViewType = RegisterType::SReg; +break; + } + + if (handleUIntLiteral(&Register->Number)) +return true; // propogate NumericLiteralParser error + + return false; +} + +bool RootSignatureParser::handleUIntLiteral(uin
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { inbelic wrote: For descriptor table clauses specifically it will grow to 6 params, which would be maintainable as a struct. But if we want to reuse the `parseParams` logic for `StaticSampler` then a common state struct would be around ~20 members. Notably, this mapping also serves as a mapping to which parse method should be invoked based on the encountered token, it is not immediately clear how we would retain that info using the optionals struct. Although maybe you are implicitly suggesting that we don't have such a generic `parseParams` method. If we are concerned about dynamic allocations/lookup, the size of this mapping is known statically, so we could also do something like: ``` template struct ParamMap { TokenKind Kinds[N]; ParamType Types[N]; bool Mandatory[N]; bool Seen[N]; } ``` WDYT? https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { llvm-beanz wrote: Hypothetically, based on the grammar what is the maximum number of token->param mappings we could have here? The reason I'm asking: is it better to have a map with dynamic allocations and lookup, or just a struct full of optionals to serve as the state? https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { + {ExpectedRegister, &Clause.Register}, + {TokenKind::kw_space, &Clause.Space}, + }; + llvm::SmallDenseSet Mandatory = { + ExpectedRegister, + }; + + if (parseParams(Params, Mandatory)) +return true; + + if (consumeExpectedToken(TokenKind::pu_r_paren, + diag::err_hlsl_unexpected_end_of_params, + /*param of=*/ParamKind)) return true; Elements.push_back(Clause); return false; } +// Helper struct defined to use the overloaded notation of std::visit. +template struct ParseParamTypeMethods : Ts... { + using Ts::operator()...; +}; +template +ParseParamTypeMethods(Ts...) -> ParseParamTypeMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + return std::visit( + ParseParamTypeMethods{ + [this](Register *X) -> bool { return parseRegister(X); }, + [this](uint32_t *X) -> bool { +return consumeExpectedToken(TokenKind::pu_equal, +diag::err_expected_after, +CurToken.TokKind) || + parseUIntParam(X); + }, + }, + Ref); +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.TokKind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.TokKind; + return true; +} +Seen.insert(CurToken.TokKind); + +if (parseParam(Params[CurToken.TokKind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = Seen.contains(Kind); +if (!SeenParam) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) + << Kind; +} +AllMandatoryDefined &= SeenParam; + } + + return !AllMandatoryDefined; +} + +bool RootSignatureParser::parseUIntParam(uint32_t *X) { + assert(CurToken.TokKind == TokenKind::pu_equal && + "Expects to only be invoked starting at given keyword"); + tryConsumeExpectedToken(TokenKind::pu_plus); + return consumeExpectedToken(TokenKind::int_literal, diag::err_expected_after, + CurToken.TokKind) || + handleUIntLiteral(X); +} + +bool RootSignatureParser::parseRegister(Register *Register) { + assert((CurToken.TokKind == TokenKind::bReg || + CurToken.TokKind == TokenKind::tReg || + CurToken.TokKind == TokenKind::uReg || + CurToken.TokKind == TokenKind::sReg) && + "Expects to only be invoked starting at given keyword"); + + switch (CurToken.TokKind) { + default: +llvm_unreachable("Switch for consumed token was not provided"); + case TokenKind::bReg: +Register->ViewType = RegisterType::BReg; +break; + case TokenKind::tReg: +Register->ViewType = RegisterType::TReg; +break; + case TokenKind::uReg: +Register->ViewType = RegisterType::UReg; +break; + case TokenKind::sReg: +Register->ViewType = RegisterType::SReg; +break; + } + + if (handleUIntLiteral(&Register->Number)) +return true; // propogate NumericLiteralParser error + + return false; +} + +bool RootSignatureParser::handleUIntLiteral(uin
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { + {ExpectedRegister, &Clause.Register}, + {TokenKind::kw_space, &Clause.Space}, + }; + llvm::SmallDenseSet Mandatory = { + ExpectedRegister, + }; + + if (parseParams(Params, Mandatory)) +return true; + + if (consumeExpectedToken(TokenKind::pu_r_paren, + diag::err_hlsl_unexpected_end_of_params, + /*param of=*/ParamKind)) return true; Elements.push_back(Clause); return false; } +// Helper struct defined to use the overloaded notation of std::visit. +template struct ParseParamTypeMethods : Ts... { + using Ts::operator()...; +}; +template +ParseParamTypeMethods(Ts...) -> ParseParamTypeMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + return std::visit( + ParseParamTypeMethods{ + [this](Register *X) -> bool { return parseRegister(X); }, + [this](uint32_t *X) -> bool { +return consumeExpectedToken(TokenKind::pu_equal, +diag::err_expected_after, +CurToken.TokKind) || + parseUIntParam(X); + }, + }, + Ref); +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.TokKind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.TokKind; + return true; +} +Seen.insert(CurToken.TokKind); + +if (parseParam(Params[CurToken.TokKind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = Seen.contains(Kind); +if (!SeenParam) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) + << Kind; +} +AllMandatoryDefined &= SeenParam; + } + + return !AllMandatoryDefined; +} + +bool RootSignatureParser::parseUIntParam(uint32_t *X) { + assert(CurToken.TokKind == TokenKind::pu_equal && inbelic wrote: The assert is there to communicate that it should only be called after consuming the preceding `=` symbol, when specifying a parameter value `space = u32`, `numDescriptors = u32`. Maybe you are confused why it is restricted to that case? If so, we can modify the assert to include other valid tokens that come before this token, but keep a clear communication about what is allowed. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/inbelic edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/joaosaffran edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/joaosaffran commented: Few questions to help me understand better the changes being introduced here. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { + {ExpectedRegister, &Clause.Register}, + {TokenKind::kw_space, &Clause.Space}, + }; + llvm::SmallDenseSet Mandatory = { + ExpectedRegister, + }; + + if (parseParams(Params, Mandatory)) +return true; + + if (consumeExpectedToken(TokenKind::pu_r_paren, + diag::err_hlsl_unexpected_end_of_params, + /*param of=*/ParamKind)) return true; Elements.push_back(Clause); return false; } +// Helper struct defined to use the overloaded notation of std::visit. +template struct ParseParamTypeMethods : Ts... { + using Ts::operator()...; +}; +template +ParseParamTypeMethods(Ts...) -> ParseParamTypeMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + return std::visit( + ParseParamTypeMethods{ + [this](Register *X) -> bool { return parseRegister(X); }, + [this](uint32_t *X) -> bool { +return consumeExpectedToken(TokenKind::pu_equal, +diag::err_expected_after, +CurToken.TokKind) || + parseUIntParam(X); + }, + }, + Ref); +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.TokKind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.TokKind; + return true; +} +Seen.insert(CurToken.TokKind); + +if (parseParam(Params[CurToken.TokKind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = Seen.contains(Kind); +if (!SeenParam) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) + << Kind; +} +AllMandatoryDefined &= SeenParam; + } + + return !AllMandatoryDefined; +} + +bool RootSignatureParser::parseUIntParam(uint32_t *X) { + assert(CurToken.TokKind == TokenKind::pu_equal && + "Expects to only be invoked starting at given keyword"); + tryConsumeExpectedToken(TokenKind::pu_plus); + return consumeExpectedToken(TokenKind::int_literal, diag::err_expected_after, + CurToken.TokKind) || + handleUIntLiteral(X); +} + +bool RootSignatureParser::parseRegister(Register *Register) { + assert((CurToken.TokKind == TokenKind::bReg || + CurToken.TokKind == TokenKind::tReg || + CurToken.TokKind == TokenKind::uReg || + CurToken.TokKind == TokenKind::sReg) && + "Expects to only be invoked starting at given keyword"); + + switch (CurToken.TokKind) { + default: +llvm_unreachable("Switch for consumed token was not provided"); + case TokenKind::bReg: +Register->ViewType = RegisterType::BReg; +break; + case TokenKind::tReg: +Register->ViewType = RegisterType::TReg; +break; + case TokenKind::uReg: +Register->ViewType = RegisterType::UReg; +break; + case TokenKind::sReg: +Register->ViewType = RegisterType::SReg; +break; + } + + if (handleUIntLiteral(&Register->Number)) +return true; // propogate NumericLiteralParser error + + return false; +} + +bool RootSignatureParser::handleUIntLiteral(uin
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -89,37 +88,178 @@ bool RootSignatureParser::parseDescriptorTableClause() { CurToken.TokKind == TokenKind::kw_UAV || CurToken.TokKind == TokenKind::kw_Sampler) && "Expects to only be invoked starting at given keyword"); + TokenKind ParamKind = CurToken.TokKind; // retain for diagnostics DescriptorTableClause Clause; - switch (CurToken.TokKind) { + TokenKind ExpectedRegister; + switch (ParamKind) { default: llvm_unreachable("Switch for consumed token was not provided"); case TokenKind::kw_CBV: Clause.Type = ClauseType::CBuffer; +ExpectedRegister = TokenKind::bReg; break; case TokenKind::kw_SRV: Clause.Type = ClauseType::SRV; +ExpectedRegister = TokenKind::tReg; break; case TokenKind::kw_UAV: Clause.Type = ClauseType::UAV; +ExpectedRegister = TokenKind::uReg; break; case TokenKind::kw_Sampler: Clause.Type = ClauseType::Sampler; +ExpectedRegister = TokenKind::sReg; break; } if (consumeExpectedToken(TokenKind::pu_l_paren, diag::err_expected_after, - CurToken.TokKind)) + ParamKind)) return true; - if (consumeExpectedToken(TokenKind::pu_r_paren, diag::err_expected_after, - CurToken.TokKind)) + llvm::SmallDenseMap Params = { + {ExpectedRegister, &Clause.Register}, + {TokenKind::kw_space, &Clause.Space}, + }; + llvm::SmallDenseSet Mandatory = { + ExpectedRegister, + }; + + if (parseParams(Params, Mandatory)) +return true; + + if (consumeExpectedToken(TokenKind::pu_r_paren, + diag::err_hlsl_unexpected_end_of_params, + /*param of=*/ParamKind)) return true; Elements.push_back(Clause); return false; } +// Helper struct defined to use the overloaded notation of std::visit. +template struct ParseParamTypeMethods : Ts... { + using Ts::operator()...; +}; +template +ParseParamTypeMethods(Ts...) -> ParseParamTypeMethods; + +bool RootSignatureParser::parseParam(ParamType Ref) { + return std::visit( + ParseParamTypeMethods{ + [this](Register *X) -> bool { return parseRegister(X); }, + [this](uint32_t *X) -> bool { +return consumeExpectedToken(TokenKind::pu_equal, +diag::err_expected_after, +CurToken.TokKind) || + parseUIntParam(X); + }, + }, + Ref); +} + +bool RootSignatureParser::parseParams( +llvm::SmallDenseMap &Params, +llvm::SmallDenseSet &Mandatory) { + + // Initialize a vector of possible keywords + SmallVector Keywords; + for (auto Pair : Params) +Keywords.push_back(Pair.first); + + // Keep track of which keywords have been seen to report duplicates + llvm::SmallDenseSet Seen; + + while (tryConsumeExpectedToken(Keywords)) { +if (Seen.contains(CurToken.TokKind)) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_repeat_param) + << CurToken.TokKind; + return true; +} +Seen.insert(CurToken.TokKind); + +if (parseParam(Params[CurToken.TokKind])) + return true; + +if (!tryConsumeExpectedToken(TokenKind::pu_comma)) + break; + } + + bool AllMandatoryDefined = true; + for (auto Kind : Mandatory) { +bool SeenParam = Seen.contains(Kind); +if (!SeenParam) { + getDiags().Report(CurToken.TokLoc, diag::err_hlsl_rootsig_missing_param) + << Kind; +} +AllMandatoryDefined &= SeenParam; + } + + return !AllMandatoryDefined; +} + +bool RootSignatureParser::parseUIntParam(uint32_t *X) { + assert(CurToken.TokKind == TokenKind::pu_equal && joaosaffran wrote: This made me confused. The only place this is called is parsing `TokenKind::pu_equal` before calling it, so it seems confusing, IMHO, that this assert is here. https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
https://github.com/joaosaffran edited https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] [llvm] [HLSL][RootSignature] Add infastructure to parse parameters (PR #133800)
@@ -32,11 +39,19 @@ struct DescriptorTable { using ClauseType = llvm::dxil::ResourceClass; struct DescriptorTableClause { ClauseType Type; + Register Register; + uint32_t Space = 0; }; // Models RootElement : DescriptorTable | DescriptorTableClause using RootElement = std::variant; +// ParamType is used as an 'any' type that will reference to a parameter in +// RootElement. Each variant of ParamType is expected to have a Parse method +// defined that will be dispatched on when we are attempting to parse a +// parameter +using ParamType = std::variant; inbelic wrote: This is probably better moved to `ParseHLSLRootSignature.h` since it is specific to there https://github.com/llvm/llvm-project/pull/133800 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits