Skip to content

[DirectX] adding support in obj2yaml and yaml2obj to root constants #127840

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 130 commits into from
Apr 17, 2025

Conversation

joaosaffran
Copy link
Contributor

@joaosaffran joaosaffran commented Feb 19, 2025

Adding support for Root Constant in MC, Object and obj2yaml and yaml2obj, this PR adds:

  • new structures to dxbc definition.
  • serialize and desirialize logic from dxcontainer to yaml
  • tests validating against dxc
  • adding support to multiple parts.

Closes: #126633

@joaosaffran joaosaffran marked this pull request as ready for review February 19, 2025 20:26
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.

A couple of minor things but this is looking fairly close.

My one major concern is that the error path of obj2yaml behaves quite poorly and isn't tested.

Given the following:

// RUN: dxc -T cs_6_5 -rootsig-define ROOT_SIGNATURE %s -Fo %t.dxil
#define ROOT_SIGNATURE "SRV(t0),RootConstants(b0, num32BitConstants = 4),"

[numthreads(1,1,1)]
void main() {}

Running obj2yaml on the output prints "Error: Invalid value for parameter type", generates YAML like the following, and exits successfully:

  - Name:            RTS0
    Size:            72
    RootSignature:
      Version:         2
      NumStaticSamplers: 0
      StaticSamplersOffset: 72
      Parameters:
        - ParameterType:   Constants32Bit
          ShaderVisibility: All
          Constants:
            Num32BitValues:  4
            RegisterSpace:   0
            ShaderRegister:  0

Other than a single line of text that's easy to miss, there's no indication that anything is wrong here at all. We may need to think about what behaviour we want here.

Comment on lines 168 to 178
#define ROOT_PARAMETER(Val, Enum) \
case Val: \
return dxbc::RootParameterType::Enum;
inline llvm::Expected<dxbc::RootParameterType>
safeParseParameterType(uint32_t V) {
switch (V) {
#include "DXContainerConstants.def"
}
return createStringError(std::errc::invalid_argument,
"Invalid value for parameter type");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be simpler to just make this a "isValid" function and let the caller figure out what they want to do with it, ie:

#define SHADER_VISIBILITY(Val, Enum)                                           \
  case Val:                                                                    \
    return true;
inline bool isValidShaderVisibility(uint32_t V) {
  switch (V) {
#include "DXContainerConstants.def"
  }
  return false;
}

and then in DXContainerYAML:

    if (!dxbc::isValidParameterType(PH.ParameterType)) {
      llvm::errs() << "Error: Unknown parameter: " << PH.ParameterType << "\n";
      continue;
    }
    NewP.Type = static_cast<dxbc::RootParameterType>(PH.ParameterType);

Comment on lines 62 to 65
if (Error E = ParamViewOrErr.takeError()) {
llvm::errs() << "Error: " << E << "\n";
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to "consume" the error here, otherwise we crash like so when this is run in asserts:

Program aborted due to an unhandled Error:
Invalid value for parameter type

The easiest way to do that if we just want to print it is to std::move the error into a toString call:

      llvm::errs() << "Error: " << toString(std::move(E)) << "\n";

We should really have tests for the error paths here so that this is obvious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added 2 new tests to address this scenario and a scenario for invalid shader visibility. I've also changed how the data is created, so we can propagate the errors properly.

@joaosaffran joaosaffran requested a review from bogner April 11, 2025 19:46
@joaosaffran joaosaffran requested a review from damyanp April 14, 2025 18:41
@joaosaffran joaosaffran requested a review from damyanp April 14, 2025 20:51
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.

Just nitpicks left on the code. This looks good.

Please add a couple of tests where RootParametersOffset is some number larger than 24 (one reading and one writing)

Comment on lines 278 to 280
switch (Param.Type) {

case static_cast<uint32_t>(dxbc::RootParameterType::Constants32Bit):
Copy link
Contributor

Choose a reason for hiding this comment

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

Unusual whitespace

Suggested change
switch (Param.Type) {
case static_cast<uint32_t>(dxbc::RootParameterType::Constants32Bit):
switch (Param.Type) {
case static_cast<uint32_t>(dxbc::RootParameterType::Constants32Bit):

Comment on lines 40 to 87
Val = (Flags & (uint32_t)dxbc::RootElementFlag::Val) > 0;
RootSigDesc.Val = (Flags & (uint32_t)dxbc::RootElementFlag::Val) > 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

I realize this isn't added, but it's probably clearer to use llvm::to_underlying instead of the cast here (We don't have std::to_underlying yet, that's c++23)

IO.mapRequired("ShaderVisibility", P.Visibility);

switch (P.Type) {
case static_cast<uint32_t>(dxbc::RootParameterType::Constants32Bit):
Copy link
Contributor

Choose a reason for hiding this comment

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

Better to use llvm::to_underlying

size_t Size = sizeof(dxbc::RootSignatureHeader) +
Parameters.size() * sizeof(dxbc::RootParameterHeader);

for (const auto &P : Parameters) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Best to only use auto when the type is somewhere in the expression (or it's something obvious but long like an iterator)

Suggested change
for (const auto &P : Parameters) {
for (const mcdxbc::RootParameter &P : Parameters) {

support::endian::write(BOS, Flags, llvm::endianness::little);

SmallVector<uint32_t> ParamsOffsets;
for (const auto &P : Parameters) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here

DataSize = sizeof(dxbc::RootConstants);
break;
}
auto EndOfSectionByte = getNumStaticSamplers() == 0
Copy link
Contributor

Choose a reason for hiding this comment

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

size_t is clearer here.

Suggested change
auto EndOfSectionByte = getNumStaticSamplers() == 0
size_t EndOfSectionByte = getNumStaticSamplers() == 0

assert(NumParameters == ParamsOffsets.size());
for (size_t I = 0; I < NumParameters; ++I) {
rewriteOffsetToCurrentByte(BOS, ParamsOffsets[I]);
const auto &P = Parameters[I];
Copy link
Contributor

Choose a reason for hiding this comment

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

Another case of auto being less clear

uint32_t Flags = Data.getFlags();
for (const auto &PH : Data.param_headers()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This one too

@joaosaffran joaosaffran requested a review from bogner April 16, 2025 16:47
@bogner
Copy link
Contributor

bogner commented Apr 16, 2025

Please add a couple of tests where RootParametersOffset is some number larger than 24 (one reading and one writing)

I think you misunderstood me. I didn't mean we needed tests where we had an incorrect / invalid RootParametersOffset, I meant we need tests where the RootParametersOffset is not 24, but the root signature is valid. Nothing in the root signature document suggests that this must be 24 (and why have it if it's a constant?). For example, we could have arbitrary padding after the header, or we could have the static samplers be listed before the parameters.

@@ -822,14 +823,65 @@ TEST(DXCFile, MalformedSignature) {
}
}

TEST(RootSignature, MalformedData) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to rename this - this test isn't malformed

NumRootParameters: 2
RootParametersOffset: 24
NumStaticSamplers: 0
StaticSamplersOffset: 12
Copy link
Contributor

Choose a reason for hiding this comment

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

Why use 12 here? It's good to test that the value is round-tripped correctly, but it seems odd to use an obviously silly value (this says the samplers start in the middle of the header!). Probably better to use some number that would be after the parameters.

@@ -19,6 +19,7 @@
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
#include <cstdint>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is cstdint still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, missed that. It has been removed

Copy link
Collaborator

@llvm-beanz llvm-beanz left a comment

Choose a reason for hiding this comment

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

Two style nits, otherwise LGTM.

Comment on lines 68 to 70
if (Error E = ParamViewOrErr.takeError()) {
return std::move(E);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (Error E = ParamViewOrErr.takeError()) {
return std::move(E);
}
if (Error E = ParamViewOrErr.takeError())
return std::move(E);

Comment on lines 75 to 77
if (Error E = ConstantsOrErr.takeError()) {
return std::move(E);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (Error E = ConstantsOrErr.takeError()) {
return std::move(E);
}
if (Error E = ConstantsOrErr.takeError())
return std::move(E);

@joaosaffran joaosaffran merged commit 53eae22 into llvm:main Apr 17, 2025
5 of 9 checks passed
var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
…lvm#127840)

Adding support for Root Constant in MC, Object and obj2yaml and
yaml2obj, this PR adds:
  - new structures to dxbc definition.
  - serialize and desirialize logic from dxcontainer to yaml
  - tests validating against dxc
  - adding support to multiple parts.

Closes: llvm#126633

---------

Co-authored-by: joaosaffran <[email protected]>
@damyanp damyanp moved this to Closed in HLSL Support Apr 25, 2025
@joaosaffran joaosaffran deleted the obj2yaml/root-constants branch April 29, 2025 18:51
@damyanp damyanp removed this from HLSL Support Jun 25, 2025
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.

[DirectX] Add support to Root Signature Constant element to obj2yaml
6 participants