-
Notifications
You must be signed in to change notification settings - Fork 808
[DXIL] Add -fhlsl-unused-resource-bindings=reserve-all to ensure consistent binding assignments #7643
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
base: main
Are you sure you want to change the base?
[DXIL] Add -fhlsl-unused-resource-bindings=reserve-all to ensure consistent binding assignments #7643
Conversation
…he IDxcRewriter, this will allow generating register ids for registers that don't fully define them.
…ully qualified registers.
…allow passing different rewriter flags. Also added support for cbuffer auto bindings.
…dxil lowering properly, it's similar to -flegacy-resource-reservation but distinct in some key ways: the option only works for reserved registers and we need it to work on any register that has been compiled out. We also don't necessarily need the register to stay at the end of DXIL generation, in fact we'd rather not. Because having the register present can cause unintended performance penalties (for example, I use this reflection to know what transitions to issue)
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
|
Important information to add is that the spirv backend already handles this correctly, so no fix is needed there. Will add a unit test as well. |
…g registers, $Globals, samplers, readonly, readwrite, etc.)
|
@Nielsbishere - Looking at this change and your proposed update to add -keep-all-resources flag, could you please help me understand what the benefit of the It seems to me that Also, as you’ve seen, implicit resource bindings tend to cause trouble since they make shader–application interfaces unpredictable and potentially compiler-dependent. It’d be great if you could find a way to stick with the explicit bindings to make sure your resource layouts stay stable and consistent. |
lib/HLSL/DxilCondenseResources.cpp
Outdated
| bChanged |= ResourceRegisterAllocator.AllocateRegisters(DM); | ||
|
|
||
| if (DM.GetConsistentBindings()) | ||
| DM.RemoveResourcesWithUnusedSymbols(); |
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.
Above, after the call to LegalizeResources, it calls RemoveResourcesWithUnusedSymbols if bLocalChanged. Don't you want to predicate that with if (!DM.GetConsistentBindings())? Otherwise, resources may be removed before calling AllocateRegisters.
Also, it seems we should be tracking changes from this with bChanged. bChanged = bChanged || (numResources != newResources); is used earlier for this, and a similar thing could be done here after updating newResources. An alternative is to modify RemoveResourcesWithUnusedSymbols to return true if any changes were made.
Side note: Looking a little further up, I noticed when ApplyBindingTableFromMetadata is called, it doesn't update bChanged either, but that's an existing bug.
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.
We probably need a test where LegalizeResources eliminates a resource use to test that code path. This relies on DxilValueCache, which was added largely to handle known-constant values in -Od cases where these values aren't simplified in the IR, but certain transformations we need to perform require these known values. In other words, you may be able to hit this code path using -Od with something that uses one resource or another in two code paths, depending on some condition that can be simplified to a constant, but is not normally simplified for -Od.
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.
Good catches, have adjusted RemoveResourcesWithUnusedSymbols to return isModified and passing it along to bChanged. Also fixed the issue you said with LegalizeResources.
Since the issue ApplyBindingTableFromMetadata was already in there, it might be worth tackling this in a further PR/issue.
As for the test case for LegalizeResources; I'm not entirely sure where this would happen, I tried to read the code of LegalizeResources but it wasn't completely clear to me. Is there a unit test that tests legalize resources that I can combine with -consistent-bindings as a test case?
|
@hekota excuse me for the (following) long message, but these two have two very different uses in my previous work place. I'm not maintaining there anymore, but I still want them to have these changes to continue my work and will tell them how it can be used.
The pipeline going forward could be:
-keep-all-resourcesis intended to (currently not working as it should in reflection, due to a missing createHandle) allow behavior similar to -Od with spirv; keep all resources even if optimized out. The reason why you don't want this by default is because reflection (presence of a register) may be used to guide for example transitions (or to know which real registers to keep). Why would you even want optimized out registers? Because sometimes you want a consistent interface that knows about the inputs to a shader, even if those shaders don't use the resource. It can still optimize it in the background but the interface will still show this variable as present. An example could be a visual scripting language that relies on this. The one I'm talking about relies on manual shader parsing to find out optimized out registers for the stability, adding this feature would allow to remove the manual parsing eventually. Consistent bindings in comparison, doesn't affect the emitted registers, only the bindings. So -keep-all-resources could be used to discover reflection info first and then a final compile per entrypoint could be used with -consistent-bindings to ensure no registers will be leftover. -consistent-bindingsis a separate issue in the same system. In modern or legacy pipelines, shaders might have been written without explicit bindings or legacy semantics. These get registers auto assigned. Once again, the same shader parser is used to replace these legacy semantics with register bindings to avoid this issue this PR fixes. A legit use case for this is poor man's bindless, where you have arrays of textures/buffers. You could have 1 central include that defines this layout and puts them at space1 (no explicit bind point). If you change the first array size or add a register to this include it will automatically update. Otherwise you need to adjust all subsequent resource bindings. The system I'm talking about also validates that the final shader reflection matches the expected bindings to avoid accidental mismatches. |
…ymbols now return bool isModified. Passing isModified along from RemoveResourcesWithUnusedSymbols to bChanged and fixed the LegalizeResources + -consistent-bindings oversight.
|
I see what you mean though, I think this is very possible and I've added behavior to keep-all-resources that would make this possible, but it has a bug I'm trying to figure out now (duplicates functions somehow?). The big problem with that PR relative to the consistent bindings (when it works) is that I'm not sure how this would interact with any tools out in the wild. Anything that might assume registers will be stripped if they're unused would get issues if they see it, because they don't utilize this flag to detect if it's really unused or not. So personally I'd see it more as a 'give me more reflection' option rather than something people should ship binaries with until other tools/drivers are aware of this change in behavior. |
…operly trigger a modification
|
There is also the option to combine |
|
@hekota In theory yes, especially with my latest changes made that mark a resource with an unused flag to determine if a resource is used in the final binary. However, with -Qstrip_reflect It ends up only stripping the name for me (context: the simplified consistent bindings test from the other PR): This one still contains the unused resource. Or do you want me to modify Qstrip_reflect to drop unused resources? |
…iler into rewriter_generate_consistent_bindings
|
Hi @Nielsbishere! You are very fast with the updates :) We have realized we did not take into account the existing with the option to add Could you please rename the values you are adding to |
hekota
left a comment
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.
Thank you for your contribution! As I mentioned above, we would like the options to
be called reserve-all and keep-all to enable adding additional options like reserve-explicit in the future.
If would be best if you could just add reserve-all value in this change, and once it is completed, you can update your other PR to add keep-all value to the enumeration. We generally want to avoid adding options that are not implemented or tested.
Also, the HLModule, DxilModule and CodeGenOptions should all use the UnusedResourceBindings enumeration instead of having separate flags for values (to avoid dealing with unsupported combinations of flags).
…release notes done by my markdown editor. TODO: Use UnusedResourceBindings in HLModule, DxilModule and CodeGenOptions
…b.com/Oxsomi/DirectXShaderCompiler into rewriter_generate_consistent_bindings
…ilShaderFlags instead. This is because 'keep-all' from the other PR relies on this Enum to be serialized; because reflection needs to know if unused resources are preserved.
|
No problem, fixed it. I moved the enum to DxilConstants.h, not sure if that's the right place but seems like it's the easiest to access from all of these various places. |
hekota
left a comment
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.
Looking good and getting close! Could you please update the title and description to reflect the latest changes since that will become the commit message?
@tex3d - could you please comment on whether DxilConstants.h is a good place for the new enumeration?
include/dxc/DXIL/DxilConstants.h
Outdated
| static_assert(UnusedResourceBinding::Count <= UnusedResourceBinding(7), | ||
| "Only 3 bits are reserved for UnusedResourceBinding by HLOptions " | ||
| "and ShaderFlags"); | ||
|
|
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.
Love this!
Commit messages are usually not as verbose as you have it now. We can shorten it when merging though. Usually there is an issue filed that describes the problem, and then a PR that fixes it with a short description on what was done, and which issue it resolves. |
tex3d
left a comment
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 have requested several key changes:
- Don't use ShaderFlags for this, use HLSLOptions/IntermediateOptions instead.
- Don't bother removing from resource list in
HLModule::RemoveGlobal, unconditionally set undef symbol instead. - Don't condition calls to
RemoveResourcesWithUnusedSymbolsinDxilCondenseResources. Instead:- add
bool AfterAllocation=falseparam - check
AfterAllocation,UnusedResourceBinding, andLegacyResourceReservation(which will be folded intoUnusedResourceBinding) for whether to remove resource - if not removing resource, set symbol to undef
- add
- Coding standards / naming
- Need IR-based pass tests
lib/HLSL/DxilGenerationPass.cpp
Outdated
| M.SetDisableOptimization(H.GetHLOptions().bDisableOptimizations); | ||
| M.SetLegacyResourceReservation(H.GetHLOptions().bLegacyResourceReservation); | ||
| M.SetUnusedResourceBinding( | ||
| UnusedResourceBinding(H.GetHLOptions().bUnusedResourceBinding)); |
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.
Need IR-based pass test to ensure intermediate options passed along.
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.
We need an IR-based pass test for changes that impact this pass.
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.
Where do I add that? Any examples? (Same with the comment on DxilGenerationPass.cpp)
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.
Regarding these IR-based tests, there's a helper script: utils/hct/ExtractIRForPassTest.py -h for option help.
You pass in the desired pass and it stops at that point and generates the test output with the needed RUN line. You'll want to add -hlsl-dxilemit to the end of the pass list to emit the metadata.
However, as I was anticipating questions related to how to target this specific area, I tried crafting a test myself. Along the way, I ran into a bug in the pass, so I put a PR up to fix the bug, along with a couple pass tests.
Here's the PR: #7934
You could copy and modify the legalize-resource-phi.ll pass test for the purposes of testing changes to this hlsl-dxil-lower-handle-for-lib pass.
Notice how the metadata indicates unbound resources, then it checks the binding. For this change, this should check several things:
- The unused resources are removed from the resource list, like the example test already checks.
- The binding remains as-if the removed resources reserved registers (so u2 has binding 2 instead of 0 as this test currently checks).
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.
Will do in a bit. Already pulled your PR into mine locally.
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.
@tex3d I have a bit of trouble trying to wrap my head around this process. So if I simplify it in steps maybe you can see where it goes wrong.
python3 utils\hct\ExtractIRForPassTest.py D:\programming\repos\DirectXShaderCompiler\tools\clang\test\HLSLFileCheck\d3dreflect\consistent_bindings_simple.hlsl -p hlsl-dxil-lower-handle-for-lib -o test.txt -- -T cs_6_3 -E mainB -auto-binding-space 0 -fhlsl-unused-resource-bindings=reserve-all- Changing the RUN %dxopt line in the file it generated and adding -hlsl-dxilemit there like your example
- Changing the UAV line/other checks to my example as well as the other checks to ensure it goes well?
- Running the dxopt manually can allow me to see what I should be checking against/validate it goes well
So a few questions:
- I still get DI (debug info?) in my ll, as well as the dxc version info and resource info. How did you end up stripping that? Qstrip_debug doesn't work.
- Do I need to use the same HLSL file you generated the IL from so it behaves about the same? Or is the consistent bindings simple test fine as long as I keep similar checks as yours regarding the resources (e.g. check for u1 and u0 to be stripped)
… resource bindings from shader flags. Added to IntermediateFlags instead. Updated static assert and enum cast.
…ng removal of registers for ReserveAll it only kicks in after allocate registers.
|
@hekota Is the new description better? Also made an issue to match. |
…plicit. Changed IntermediateFlags to m_UnusedResourceBindings. ClearIntermediateOptions->ResetUnusedResourceBinding. Left -flegacy-resource-reservation as is for now, though it routes to UnusedResourceBinding::ReserveExplicit internally.
…resources. The problem before was that it never called RemoveResourcesWithUnusedSymbols on early exit. Also my earlier attempt tried to access *p while it should've accessed *c, since *p was already incremented.
Adds a new compiler option:
-fhlsl-unused-resource-bindings=[strip|reserve-all].stripkeeps the current behavior.reserve-alldelays removing unused resources until after binding assignment, ensuring consistent register mappings across all entry points and library compilations when resources do not have explicit register annotations.Under
reserve-all, DCE removes only symbols initially, bindings are assigned with the complete resource list, and unused resources are stripped at the end. This produces deterministic and stable bindings without affecting optimization correctness.Fixes #7931