-
Notifications
You must be signed in to change notification settings - Fork 13.3k
[HLSL][RootSignature] Implement initial parsing of the descriptor table clause params #133800
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
Changes from 7 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9ff87eb
[HLSL][RootSignature] Add infastructure to parse parameters
180c33c
define ParseUInt and ParseRegister to plug into parameters
ebab0ca
self-review: use consumeExpectedToken api to report unexpected_end_of…
cd5c142
self-review: fix-up comments
490e6b9
rebase changes: update to build issue fixes
8155c60
clang-formatting
3a598fd
self-review: clang-format fix up
1d2bd91
review: prototype using a stateful struct for parsed params
5d1ec2f
review: remove abstraction of parsing arbitrary params
86fde97
NFC review: update calling conventions to use std::optional instead o…
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 forStaticSampler
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:
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
RootSBV(...), CBV(...), StaticSampler(...), RootConstants(...), etc
, so, adding their respective parse methods in future prs will just be something like: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), whereN
is the number of parameters for a given root parameter type. (N
is not equal to the total number of token kinds). The largestN
would be is forStaticSamplers
with 13, and then for exampleDescriptorTableClause
it is 5. And we could makeMandatory/Seen
just be twouint32_t
bitfields.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 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.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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 a good case to illustrate what level of abstraction we want is to consider parsing the
flags = FLAGS
parameter, whereFLAGS
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 ab
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.
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 anany
type with polymorphic objects or variants.