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

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
0abacfc
adding support for Root Descriptors
Apr 24, 2025
8b8c02a
clean up
Apr 24, 2025
7ac9641
addressing comments
Apr 25, 2025
c105458
formating
Apr 26, 2025
efe76aa
try fix test
Apr 26, 2025
a928e9d
addressing comments
Apr 26, 2025
a38f10b
refactoring mcdxbc struct to store root parameters out of order
Apr 25, 2025
9a7c359
changing name
Apr 28, 2025
d6c2b55
changing variant to host pointers
Apr 28, 2025
93e4cf2
clean up
Apr 28, 2025
b45b1b6
fix
Apr 28, 2025
f804a23
fix
Apr 28, 2025
15eb6f5
fix naming
May 5, 2025
b9d7f07
fix naming
May 5, 2025
46cc8c1
addressing comments
May 8, 2025
1b3e10a
addressing comments
May 8, 2025
1f31957
addressing comments
May 8, 2025
e8fbfce
clean up
May 8, 2025
a31e5a5
removing v parameter
May 9, 2025
a394ad0
Merge branch 'obj2yaml/root-descriptors' into refactoring/remove-union
May 9, 2025
ad415a7
clean up
May 9, 2025
8ff4845
Merge branch 'main' into refactoring/remove-union
May 9, 2025
f875555
adding support for root descriptors
May 13, 2025
4f7f998
removing none as a flag option
May 13, 2025
3eb5e10
adding tests
May 13, 2025
58e1789
clean up and add more tests
May 13, 2025
81915ad
addressing comments
May 30, 2025
a515e28
Merge branch 'main' into metadata/root-descriptors
Jun 2, 2025
0d54162
clean
Jun 2, 2025
7f70dc5
cleanup
Jun 2, 2025
3e6b07e
add parsing
Jun 2, 2025
bb1a61f
add tests
Jun 2, 2025
8979ab7
adding support on analysis printer
Jun 2, 2025
be60d51
adding support on analysis printer
Jun 2, 2025
0c570c8
adding requested comment
Jun 2, 2025
c3d24b6
clean up
Jun 3, 2025
d1ca37d
addressing PR comments
Jun 3, 2025
cb0780b
formating
Jun 3, 2025
eeffded
addressing PR comments
Jun 3, 2025
3cbe0cf
formating
Jun 3, 2025
92b766b
formating
Jun 3, 2025
0259cf7
adding test
Jun 3, 2025
8732594
adding test
Jun 3, 2025
fdb8b98
clean up
Jun 3, 2025
8b61ffd
Merge branch 'metadata/root-descriptors' into metadata/descriptor-table
Jun 3, 2025
0b3e16b
format
Jun 3, 2025
750a6d5
Merge branch 'main' into metadata/descriptor-table
Jun 4, 2025
72b8dbc
addressing comments
Jun 6, 2025
8f069c7
fix
Jun 6, 2025
440566c
addressing comments
Jun 9, 2025
72ca44b
address coments
Jun 9, 2025
90f7403
remove diff
Jun 9, 2025
7a4382e
address comments
Jun 9, 2025
1c608f4
address comments
Jun 11, 2025
3b8b2e1
Merge branch 'bugfix/wrong-values-root-descriptor-flags' into metadat…
Jun 11, 2025
e0bc5e3
fix
Jun 11, 2025
35a2e0b
fix other values
Jun 11, 2025
655b3b4
Merge branch 'bugfix/wrong-values-root-descriptor-flags' into metadat…
Jun 11, 2025
91346a7
adding more tests
Jun 11, 2025
e8066df
address comments
Jun 12, 2025
15f4f49
Merge branch 'main' into metadata/descriptor-table
Jun 17, 2025
638d961
remove empty file
Jun 18, 2025
8b831f8
adding check for mutually exclusive descriptor flags
Jun 18, 2025
5c677a5
format
Jun 18, 2025
bce790c
address comments
Jun 18, 2025
fefe820
remove value
Jun 18, 2025
e577503
adding support to version in metadata
Jun 19, 2025
746c54a
fix tests
Jun 19, 2025
c5a1009
Merge branch 'metadata/add-versioning-support' into metadata/descript…
Jun 19, 2025
a66e6a3
add test
Jun 19, 2025
351eee2
Merge branch 'main' into metadata/descriptor-table
Jun 20, 2025
4a0a955
fix merge and test issues
Jun 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions llvm/include/llvm/BinaryFormat/DXContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef LLVM_BINARYFORMAT_DXCONTAINER_H
#define LLVM_BINARYFORMAT_DXCONTAINER_H

#include "llvm/ADT/BitmaskEnum.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Error.h"
Expand Down Expand Up @@ -40,6 +41,8 @@ template <typename T> struct EnumEntry;

namespace dxbc {

LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();

inline Triple::EnvironmentType getShaderStage(uint32_t Kind) {
assert(Kind <= Triple::Amplification - Triple::Pixel &&
"Shader kind out of expected range.");
Expand Down Expand Up @@ -167,6 +170,8 @@ enum class RootDescriptorFlag : uint32_t {
#define DESCRIPTOR_RANGE_FLAG(Num, Val) Val = Num,
enum class DescriptorRangeFlag : uint32_t {
#include "DXContainerConstants.def"

LLVM_MARK_AS_BITMASK_ENUM(DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS)
Copy link
Contributor

Choose a reason for hiding this comment

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

review note: the value used in the macro is required to be the highest value

};

#define ROOT_PARAMETER(Val, Enum) Enum = Val,
Expand Down
10 changes: 10 additions & 0 deletions llvm/include/llvm/BinaryFormat/DXContainerConstants.def
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ DESCRIPTOR_RANGE_FLAG(0x10000, DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS)
#undef DESCRIPTOR_RANGE_FLAG
#endif // DESCRIPTOR_RANGE_FLAG

// DESCRIPTOR_RANGE(value, name).
#ifdef DESCRIPTOR_RANGE
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: the other ifdef blocks have a new line after the ifdef.


DESCRIPTOR_RANGE(0, SRV)
DESCRIPTOR_RANGE(1, UAV)
DESCRIPTOR_RANGE(2, CBV)
DESCRIPTOR_RANGE(3, Sampler)
#undef DESCRIPTOR_RANGE
#endif // DESCRIPTOR_RANGE

#ifdef ROOT_PARAMETER

ROOT_PARAMETER(0, DescriptorTable)
Expand Down
237 changes: 206 additions & 31 deletions llvm/lib/Target/DirectX/DXILRootSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,93 @@ static bool parseRootDescriptors(LLVMContext *Ctx,
return false;
}

static bool parseDescriptorRange(LLVMContext *Ctx,
mcdxbc::DescriptorTable &Table,
MDNode *RangeDescriptorNode) {

if (RangeDescriptorNode->getNumOperands() != 6)
return reportError(Ctx, "Invalid format for Descriptor Range");

dxbc::RTS0::v2::DescriptorRange Range;

std::optional<StringRef> ElementText =
extractMdStringValue(RangeDescriptorNode, 0);

if (!ElementText.has_value())
return reportError(Ctx, "Descriptor Range, first element is not a string.");

Range.RangeType =
StringSwitch<uint32_t>(*ElementText)
.Case("CBV", llvm::to_underlying(dxbc::DescriptorRangeType::CBV))
.Case("SRV", llvm::to_underlying(dxbc::DescriptorRangeType::SRV))
.Case("UAV", llvm::to_underlying(dxbc::DescriptorRangeType::UAV))
.Case("Sampler",
llvm::to_underlying(dxbc::DescriptorRangeType::Sampler))
.Default(~0U);

if (Range.RangeType == ~0U)
return reportError(Ctx, "Invalid Descriptor Range type: " + *ElementText);

if (std::optional<uint32_t> Val = extractMdIntValue(RangeDescriptorNode, 1))
Range.NumDescriptors = *Val;
else
return reportError(Ctx, "Invalid value for Number of Descriptor in Range");
Copy link
Contributor

Choose a reason for hiding this comment

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

We could probably have a function like reportInvalidTypeError for all of these. Similar to reportValueError but with a message that will differentiate between the two.

Something like "Parameter: ... expected metadata node of type ... but got ..." I am not sure if you can retrieve the type of an MDNode?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have addressed this in a separate branch, will send a follow-up PR

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 the PR: #144425


if (std::optional<uint32_t> Val = extractMdIntValue(RangeDescriptorNode, 2))
Range.BaseShaderRegister = *Val;
else
return reportError(Ctx, "Invalid value for BaseShaderRegister");

if (std::optional<uint32_t> Val = extractMdIntValue(RangeDescriptorNode, 3))
Range.RegisterSpace = *Val;
else
return reportError(Ctx, "Invalid value for RegisterSpace");

if (std::optional<uint32_t> Val = extractMdIntValue(RangeDescriptorNode, 4))
Range.OffsetInDescriptorsFromTableStart = *Val;
else
return reportError(Ctx,
"Invalid value for OffsetInDescriptorsFromTableStart");

if (std::optional<uint32_t> Val = extractMdIntValue(RangeDescriptorNode, 5))
Range.Flags = *Val;
else
return reportError(Ctx, "Invalid value for Descriptor Range Flags");

Table.Ranges.push_back(Range);
return false;
}

static bool parseDescriptorTable(LLVMContext *Ctx,
mcdxbc::RootSignatureDesc &RSD,
MDNode *DescriptorTableNode) {
const unsigned int NumOperands = DescriptorTableNode->getNumOperands();
if (NumOperands < 2)
return reportError(Ctx, "Invalid format for Descriptor Table");

dxbc::RTS0::v1::RootParameterHeader Header;
if (std::optional<uint32_t> Val = extractMdIntValue(DescriptorTableNode, 1))
Header.ShaderVisibility = *Val;
else
return reportError(Ctx, "Invalid value for ShaderVisibility");

mcdxbc::DescriptorTable Table;
Header.ParameterType =
llvm::to_underlying(dxbc::RootParameterType::DescriptorTable);

for (unsigned int I = 2; I < NumOperands; I++) {
MDNode *Element = dyn_cast<MDNode>(DescriptorTableNode->getOperand(I));
if (Element == nullptr)
return reportError(Ctx, "Missing Root Element Metadata Node.");

if (parseDescriptorRange(Ctx, Table, Element))
return true;
}

RSD.ParametersContainer.addParameter(Header, Table);
return false;
}

static bool parseRootSignatureElement(LLVMContext *Ctx,
mcdxbc::RootSignatureDesc &RSD,
MDNode *Element) {
Expand All @@ -188,6 +275,7 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
.Case("RootCBV", RootSignatureElementKind::CBV)
.Case("RootSRV", RootSignatureElementKind::SRV)
.Case("RootUAV", RootSignatureElementKind::UAV)
.Case("DescriptorTable", RootSignatureElementKind::DescriptorTable)
.Default(RootSignatureElementKind::Error);

switch (ElementKind) {
Expand All @@ -200,6 +288,8 @@ static bool parseRootSignatureElement(LLVMContext *Ctx,
case RootSignatureElementKind::SRV:
case RootSignatureElementKind::UAV:
return parseRootDescriptors(Ctx, RSD, Element, ElementKind);
case RootSignatureElementKind::DescriptorTable:
return parseDescriptorTable(Ctx, RSD, Element);
case RootSignatureElementKind::Error:
return reportError(Ctx, "Invalid Root Signature Element: " + *ElementText);
}
Expand Down Expand Up @@ -241,6 +331,75 @@ static bool verifyRegisterSpace(uint32_t RegisterSpace) {

static bool verifyDescriptorFlag(uint32_t Flags) { return (Flags & ~0xE) == 0; }

static bool verifyRangeType(uint32_t Type) {
switch (Type) {
case llvm::to_underlying(dxbc::DescriptorRangeType::CBV):
case llvm::to_underlying(dxbc::DescriptorRangeType::SRV):
case llvm::to_underlying(dxbc::DescriptorRangeType::UAV):
case llvm::to_underlying(dxbc::DescriptorRangeType::Sampler):
return true;
};

return false;
}

static bool verifyDescriptorRangeFlag(uint32_t Version, uint32_t Type,
uint32_t FlagsVal) {
using FlagT = dxbc::DescriptorRangeFlag;
FlagT Flags = FlagT(FlagsVal);

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.

if (IsSampler)
return Flags == FlagT::DESCRIPTORS_VOLATILE;
return Flags == (FlagT::DATA_VOLATILE | 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?

FlagT DataFlags = FlagT::DATA_VOLATILE | FlagT::DATA_STATIC |
FlagT::DATA_STATIC_WHILE_SET_AT_EXECUTE;

if (popcount(llvm::to_underlying(Flags & DataFlags)) > 1)
return false;

// The descriptor-specific flags are mutually exclusive.
FlagT DescriptorFlags =
FlagT::DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS |
FlagT::DESCRIPTORS_VOLATILE;
if (popcount(llvm::to_underlying(Flags & DescriptorFlags)) > 1)
return false;

// For volatile descriptors, DATA_STATIC is never valid.
if ((Flags & FlagT::DESCRIPTORS_VOLATILE) == FlagT::DESCRIPTORS_VOLATILE) {
FlagT Mask = FlagT::DESCRIPTORS_VOLATILE;
if (!IsSampler) {
Mask |= FlagT::DATA_VOLATILE;
Mask |= FlagT::DATA_STATIC_WHILE_SET_AT_EXECUTE;
}
return (Flags & ~Mask) == FlagT::NONE;
}

// For "STATIC_KEEPING_BUFFER_BOUNDS_CHECKS" descriptors,
// the other data-specific flags may all be set.
if ((Flags & FlagT::DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS) ==
FlagT::DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS) {
FlagT Mask = FlagT::DESCRIPTORS_STATIC_KEEPING_BUFFER_BOUNDS_CHECKS;
if (!IsSampler) {
Mask |= FlagT::DATA_VOLATILE;
Mask |= FlagT::DATA_STATIC;
Mask |= FlagT::DATA_STATIC_WHILE_SET_AT_EXECUTE;
}
return (Flags & ~Mask) == FlagT::NONE;
}

// When no descriptor flag is set, any data flag is allowed.
if (!IsSampler)
return (Flags & ~DataFlags) == FlagT::NONE;
return (Flags & ~FlagT::NONE) == FlagT::NONE;
Comment on lines +397 to +400
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;

}

static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {

if (!verifyVersion(RSD.Version)) {
Expand Down Expand Up @@ -279,6 +438,22 @@ static bool validate(LLVMContext *Ctx, const mcdxbc::RootSignatureDesc &RSD) {
}
break;
}
case llvm::to_underlying(dxbc::RootParameterType::DescriptorTable): {
const mcdxbc::DescriptorTable &Table =
RSD.ParametersContainer.getDescriptorTable(Info.Location);
for (const dxbc::RTS0::v2::DescriptorRange &Range : Table) {
if (!verifyRangeType(Range.RangeType))
return reportValueError(Ctx, "RangeType", Range.RangeType);

if (!verifyRegisterSpace(Range.RegisterSpace))
return reportValueError(Ctx, "RegisterSpace", Range.RegisterSpace);

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);

}
break;
}
}
}

Expand Down Expand Up @@ -388,67 +563,67 @@ PreservedAnalyses RootSignatureAnalysisPrinter::run(Module &M,

OS << "Root Signature Definitions"
<< "\n";
uint8_t Space = 0;
for (const Function &F : M) {
auto It = RSDMap.find(&F);
if (It == RSDMap.end())
continue;
const auto &RS = It->second;
OS << "Definition for '" << F.getName() << "':\n";

// start root signature header
Space++;
Copy link
Contributor

Choose a reason for hiding this comment

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

These don't seem like a related change? I guess it is to conform to use explicit idents everywhere instead of incremeting/decrementing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it is just to add consistency in the idents

OS << indent(Space) << "Flags: " << format_hex(RS.Flags, 8) << "\n";
OS << indent(Space) << "Version: " << RS.Version << "\n";
OS << indent(Space) << "RootParametersOffset: " << RS.RootParameterOffset
<< "\n";
OS << indent(Space) << "NumParameters: " << RS.ParametersContainer.size()
<< "\n";
Space++;
OS << "Flags: " << format_hex(RS.Flags, 8) << "\n"
<< "Version: " << RS.Version << "\n"
<< "RootParametersOffset: " << RS.RootParameterOffset << "\n"
<< "NumParameters: " << RS.ParametersContainer.size() << "\n";
for (size_t I = 0; I < RS.ParametersContainer.size(); I++) {
const auto &[Type, Loc] =
RS.ParametersContainer.getTypeAndLocForParameter(I);
const dxbc::RTS0::v1::RootParameterHeader Header =
RS.ParametersContainer.getHeader(I);

OS << indent(Space) << "- Parameter Type: " << Type << "\n";
OS << indent(Space + 2)
<< "Shader Visibility: " << Header.ShaderVisibility << "\n";
OS << "- Parameter Type: " << Type << "\n"
<< " Shader Visibility: " << Header.ShaderVisibility << "\n";

switch (Type) {
case llvm::to_underlying(dxbc::RootParameterType::Constants32Bit): {
const dxbc::RTS0::v1::RootConstants &Constants =
RS.ParametersContainer.getConstant(Loc);
OS << indent(Space + 2) << "Register Space: " << Constants.RegisterSpace
<< "\n";
OS << indent(Space + 2)
<< "Shader Register: " << Constants.ShaderRegister << "\n";
OS << indent(Space + 2)
<< "Num 32 Bit Values: " << Constants.Num32BitValues << "\n";
OS << " Register Space: " << Constants.RegisterSpace << "\n"
<< " Shader Register: " << Constants.ShaderRegister << "\n"
<< " Num 32 Bit Values: " << Constants.Num32BitValues << "\n";
break;
}
case llvm::to_underlying(dxbc::RootParameterType::CBV):
case llvm::to_underlying(dxbc::RootParameterType::UAV):
case llvm::to_underlying(dxbc::RootParameterType::SRV): {
const dxbc::RTS0::v2::RootDescriptor &Descriptor =
RS.ParametersContainer.getRootDescriptor(Loc);
OS << indent(Space + 2)
<< "Register Space: " << Descriptor.RegisterSpace << "\n";
OS << indent(Space + 2)
<< "Shader Register: " << Descriptor.ShaderRegister << "\n";
OS << " Register Space: " << Descriptor.RegisterSpace << "\n"
<< " Shader Register: " << Descriptor.ShaderRegister << "\n";
if (RS.Version > 1)
OS << indent(Space + 2) << "Flags: " << Descriptor.Flags << "\n";
OS << " Flags: " << Descriptor.Flags << "\n";
break;
}
case llvm::to_underlying(dxbc::RootParameterType::DescriptorTable): {
const mcdxbc::DescriptorTable &Table =
RS.ParametersContainer.getDescriptorTable(Loc);
OS << " NumRanges: " << Table.Ranges.size() << "\n";

for (const dxbc::RTS0::v2::DescriptorRange Range : Table) {
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";
}
break;
}
}
Space--;
}
OS << indent(Space) << "NumStaticSamplers: " << 0 << "\n";
OS << indent(Space) << "StaticSamplersOffset: " << RS.StaticSamplersOffset
<< "\n";

Space--;
// end root signature header
OS << "NumStaticSamplers: " << 0 << "\n";
OS << "StaticSamplersOffset: " << RS.StaticSamplersOffset << "\n";
}
return PreservedAnalyses::all();
}
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/DirectX/DXILRootSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum class RootSignatureElementKind {
SRV = 3,
UAV = 4,
CBV = 5,
DescriptorTable = 6,
};
class RootSignatureAnalysis : public AnalysisInfoMixin<RootSignatureAnalysis> {
friend AnalysisInfoMixin<RootSignatureAnalysis>;
Expand Down
Loading