Skip to content

[HLSL] Add descriptor table metadata parsing #142492

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 73 commits into from
Jun 20, 2025

Conversation

joaosaffran
Copy link
Contributor

@joaosaffran joaosaffran commented Jun 2, 2025

Implements descriptor table parsing from root signature metadata. This is required to support root signatures in hlsl.
Closes: #126640

@joaosaffran joaosaffran requested review from bogner, inbelic and alsepkow and removed request for nikic and a team June 17, 2025 18:19
return Flags == FlagT::DESCRIPTORS_VOLATILE;
}

// The data-specific flags are mutually exclusive.
Copy link
Contributor

Choose a reason for hiding this comment

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

the descriptor flags are also mutually exclusive. Do we want an explicit check for this? It seems like it is covered below. Maybe it is good for clarity?

DESCRIPTOR_RANGE(1, UAV)
DESCRIPTOR_RANGE(2, CBV)
DESCRIPTOR_RANGE(3, Sampler)
DESCRIPTOR_RANGE(4, NONE)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have a value for NONE here? A value of 4 in the metadata would certainly be invalid, and this appears to be unused.

Copy link
Contributor Author

@joaosaffran joaosaffran Jun 18, 2025

Choose a reason for hiding this comment

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

This came as a request from another PR, where Finn mentioned those values are useful in the frontend, since we are planning on sharing this, I've added those back.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have a link to that conversation handy? I'd like to see how NONE would be used here...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is an example use case of NONE in the frontend:

std::optional<RootFlags> Flags = RootFlags::None;
.

But that discussion was in the context of flags, I will remove this one from here, since it doesn't seem to match the pattern of other NONE values

Comment on lines 200 to 202
.Default(-1u);

if (Range.RangeType == -1u)
Copy link
Contributor

Choose a reason for hiding this comment

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

Clearer to use ~0U rather than -1U, and this way avoids some warnings from MSVC in any case.

@@ -174,6 +174,94 @@ static bool parseRootDescriptors(LLVMContext *Ctx,
return false;
}

static bool parseDescriptorRange(LLVMContext *Ctx,
mcdxbc::RootSignatureDesc &RSD,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is RSD passed in here?

Comment on lines 355 to 359
if (Version == 1) {
if (IsSampler)
return Flags == FlagT::NONE;
return Flags == FlagT::DESCRIPTORS_VOLATILE;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These values are still incorrect. As I mentioned in #142492 (comment) samplers can only have DESCRIPTORS_VOLATILE, and otherwise this needs to be DATA_VOLATILE.

Also, please add a test for this - we should be catching that this is obviously wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is some confusion in the spec, there are 2 sections saying different values are valid for this field. Reached in private to discuss this.

I'll add the test.

Comment on lines 398 to 399
// When no descriptor flag is set, any data flag is allowed.
return (Flags & ~DataFlags) == FlagT::NONE;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not correct. Samplers can never have the DATA_* flags set.

Comment on lines 612 to 621
OS << indent(2) << "- Range Type: " << Range.RangeType << "\n";
OS << indent(4) << "Register Space: " << Range.RegisterSpace << "\n";
OS << indent(4)
<< "Base Shader Register: " << Range.BaseShaderRegister << "\n";
OS << indent(4) << "Num Descriptors: " << Range.NumDescriptors
<< "\n";
OS << indent(4) << "Offset In Descriptors From Table Start: "
<< Range.OffsetInDescriptorsFromTableStart << "\n";
if (RS.Version > 1)
OS << indent(4) << "Flags: " << Range.Flags << "\n";
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's just me, but I find using the indent() function with a small constant to be quite a bit less clear than just putting the spaces in the strings. Consider:

          OS << "  - Range Type: " << Range.RangeType << "\n"
             << "    Register Space: " << Range.RegisterSpace << "\n"
             << "    Base Shader Register: " << Range.BaseShaderRegister << "\n"
             << "    Num Descriptors: " << Range.NumDescriptors << "\n"
             << "    Offset In Descriptors From Table Start: "
             << Range.OffsetInDescriptorsFromTableStart << "\n";
          if (RS.Version > 1)
            OS << "    Flags: " << Range.Flags << "\n";

Here, you can see how the strings line up just by looking at them. Though admittedly it can get a bit messier with longer lines. IMO indent() is mostly useful when the indentation varies depending on context, or is large enough that just writing it out makes things less readable.

@joaosaffran joaosaffran changed the base branch from main to users/joaosaffran/144957 June 19, 2025 21:28
@joaosaffran joaosaffran changed the base branch from users/joaosaffran/144957 to main June 20, 2025 00:41
@joaosaffran joaosaffran requested review from bogner and inbelic June 20, 2025 01:46
Copy link
Contributor

@bogner bogner left a comment

Choose a reason for hiding this comment

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

LGTM other than a couple of fairly minor comments inline.

const bool IsSampler =
(Type == llvm::to_underlying(dxbc::DescriptorRangeType::Sampler));

if (Version == 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably worth a comment.

Suggested change
if (Version == 1) {
if (Version == 1) {
// Since the metadata is unversioned, we expect to explicitly see the values
// that map to the version 1 behaviour here.

Comment on lines 397 to 400
// When no descriptor flag is set, any data flag is allowed.
if (!IsSampler)
return (Flags & ~DataFlags) == FlagT::NONE;
return (Flags & ~FlagT::NONE) == FlagT::NONE;
Copy link
Contributor

Choose a reason for hiding this comment

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

It feels weird to do this case differently than above, since they're doing the same thing. Technically we could simplify this just by removing the DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS if condition, since that logic should be correct whether or not that flag is set, but I can see why you might avoid that so that the comments and the code flow are clear. In that case, I guess we should repeat the explicit approach as we did in the two other cases above:

Suggested change
// When no descriptor flag is set, any data flag is allowed.
if (!IsSampler)
return (Flags & ~DataFlags) == FlagT::NONE;
return (Flags & ~FlagT::NONE) == FlagT::NONE;
// When no descriptor flag is set, any data flag is allowed.
FlagT Mask = FlagT::None;
if (!IsSampler) {
Mask |= FlagT::DATA_VOLATILE;
Mask |= FlagT::DATA_STATIC;
Mask |= FlagT::DATA_STATIC_WHILE_SET_AT_EXECUTE;
}
return (Flags & ~Mask) == FlagT::NONE;


if (!verifyDescriptorRangeFlag(RSD.Version, Range.RangeType,
Range.Flags))
return reportValueError(Ctx, "DescriptorFlag", Range.Flags);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this differentiate from non-range DescriptorFlag?

Suggested change
return reportValueError(Ctx, "DescriptorFlag", Range.Flags);
return reportValueError(Ctx, "DescriptorRangeFlag", Range.Flags);

@joaosaffran joaosaffran merged commit b5d5708 into llvm:main Jun 20, 2025
8 checks passed
; DXC-NEXT: NumDescriptors: 0
; DXC-NEXT: BaseShaderRegister: 1
; DXC-NEXT: RegisterSpace: 0
; DXC-NEXT: OffsetInDescriptorsFromTableStart: 4294967295
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OffsetInDescriptorsFromTableStart, is the 5th value in the metadata representation, in the first range this value is -1, which denote the special case where it was set to D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND, here is a link to the docs describing this: OffsetInDescriptorsFromTableStart

Jaddyen pushed a commit to Jaddyen/llvm-project that referenced this pull request Jun 23, 2025
Implements descriptor table parsing from root signature metadata. This
is required to support root signatures in hlsl.
Closes: #[126640](llvm#126640)

---------

Co-authored-by: joaosaffran <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants