Skip to content
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

Unions support in HLSL. #4132

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open

Conversation

danbrown-amd
Copy link
Contributor

Unions support in HLSL.

@mthatish mthatish mentioned this pull request Dec 6, 2021
@@ -1580,7 +1580,9 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,

if (!Name && !TemplateId && (DS.getTypeSpecType() == DeclSpec::TST_error ||
TUK != Sema::TUK_Definition)) {
if (DS.getTypeSpecType() != DeclSpec::TST_error) {
if (getLangOpts().HLSL && TagTokKind == tok::kw_union) {
Copy link

Choose a reason for hiding this comment

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

Leaving it as syntax error makes more sense to me than the semantic error. Please let me know if you people think differently.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I do think this is better suited as a semantic error, I'm also not sure this actually works... see my comment on your test.

@AppVeyorBot
Copy link

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.

Not all of your unit tests are running. You need to add the following to the end of tools\clang\unittest\HLSL\VerifierTest.cpp to run the HLSL test cases that you've added:

TEST_F(VerifierTest, UnionAnon) {
  CheckVerifiesHLSL(L"union_anon.hlsl");
}

TEST_F(VerifierTest, UnionDerivedToBase) {
  CheckVerifiesHLSL(L"union-derived-to-base.hlsl");
}

TEST_F(VerifierTest, UnionSizeOf) {
  CheckVerifiesHLSL(L"union-sizeof.hlsl");
}

TEST_F(VerifierTest, Unions0) {
  CheckVerifiesHLSL(L"unions_0.hlsl");
}

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@mthatish
Copy link

mthatish commented Dec 8, 2021

Not all of your unit tests are running. You need to add the following to the end of tools\clang\unittest\HLSL\VerifierTest.cpp to run the HLSL test cases that you've added:

TEST_F(VerifierTest, UnionAnon) {
  CheckVerifiesHLSL(L"union_anon.hlsl");
}

TEST_F(VerifierTest, UnionDerivedToBase) {
  CheckVerifiesHLSL(L"union-derived-to-base.hlsl");
}

TEST_F(VerifierTest, UnionSizeOf) {
  CheckVerifiesHLSL(L"union-sizeof.hlsl");
}

TEST_F(VerifierTest, Unions0) {
  CheckVerifiesHLSL(L"unions_0.hlsl");
}

Done

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.

Thank you for working on this. This is really shaping up!

A few thoughts.

Should we even be disabling anonymous unions? They can be super useful, and are frequently used for vector and SIMD code.

We should have tests that test some of the interactions between unions and other HLSL features. I don't think the struct annotations are correct, and one test that demonstrates it is adding a union to a ConstantBuffer.

Additionally we should test applying semantics to union elements (which should be an error).

@@ -1580,7 +1580,9 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,

if (!Name && !TemplateId && (DS.getTypeSpecType() == DeclSpec::TST_error ||
TUK != Sema::TUK_Definition)) {
if (DS.getTypeSpecType() != DeclSpec::TST_error) {
if (getLangOpts().HLSL && TagTokKind == tok::kw_union) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do think this is better suited as a semantic error, I'm also not sure this actually works... see my comment on your test.

@@ -0,0 +1,14 @@
// RUN: %clang_cc1 -enable-unions -fsyntax-only -ffreestanding -verify %s
// RUN: %clang_cc1 -HV 2021 -fsyntax-only -ffreestanding -verify %s
union union { /* expected-error {{anonymous unions are not supported in HLSL}} */ /* expected-warning {{declaration does not declare anything}} */
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not an anonymous union declaration. This is repeating the union keyword and tripping up the parser. Actual anonymous unions slip right through.

@@ -1137,6 +1137,9 @@ unsigned CGMSHLSLRuntime::ConstructStructAnnotation(DxilStructAnnotation *annota
fieldAnnotation.SetPrecise();
}

if (RD->isUnion())
break;

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you put a union in a constant buffer, the size of the union comes back as 0, which isn't correct.

Copy link

Choose a reason for hiding this comment

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

Your comment above::
I don't think the struct annotations are correct, and one test that demonstrates it is adding a union to a ConstantBuffer.

Can you tell me which test case is this about?

Copy link
Collaborator

Choose a reason for hiding this comment

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

There isn’t a test case in the PR that covers this, but any shader using a ConstantBuffer<T>, where T is or contains a union type will generate incorrect cbuffer layout information.

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@mthatish
Copy link

Can I please get another review? Thanks

@llvm-beanz
Copy link
Collaborator

The PR still doesn't have a test case verifying unions in cbuffers. I checked out the PR locally built and tried to run the following code and the compiler crashes:

// RUN: %dxc -E main -T ps_6_0 -HV 2021 %s | FileCheck %s

union U {
  uint x;
  float4 v;
};

cbuffer Foo
{
  U g;
};


float4 main(int2 a : A) : SV_TARGET
{
  return g.v;
}

@@ -71,7 +71,8 @@ class Type {
StructTyID, ///< 12: Structures
ArrayTyID, ///< 13: Arrays
PointerTyID, ///< 14: Pointers
VectorTyID ///< 15: SIMD 'packed' format, or other vector type
VectorTyID, ///< 15: SIMD 'packed' format, or other vector type
UnionTyID ///< 16: Unions
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems unnecessary. We shouldn't need a new union type in the LLVM IR layer, and I don't think this is even used.

Choose a reason for hiding this comment

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

This is being used at \lib\HLSL\HLOperationLower.cpp

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right, but normally Unions are treated as structs in the IR, and since the case you added is if(struct || union) you’re not differentiating a union from a struct.

@@ -499,6 +499,8 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
opts.EnableOperatorOverloading = true;
// Enable template support
opts.EnableTemplates = true;
// Enable Unions support
opts.EnableUnions = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be gated on HLSL 202x rather than 2021. You'll need to rebase your branch to get the new version support.

Choose a reason for hiding this comment

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

Done

@@ -0,0 +1,16 @@
// RUN: %dxc -E main -T ps_6_0 -enable-unions -HV 2021 %s | FileCheck %s

// CHECK:define void @main
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is not sufficient to test that main is defined in the generated IR. Tests should test that the output is correct as expected.

In this test case verifying the union member access IR is appropriate.

Other tests in this PR also need revision.

Choose a reason for hiding this comment

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

Done

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@AppVeyorBot
Copy link

@mthatish
Copy link

Can i please get another review?

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.

I want to start by apologizing because I think you've done a lot of work here (much of it great), but we're moving the goal posts on you 😭.

There is a set of problems relating to union types that are a bit challenging to address as we look at how unions interact with other HLSL features. We didn't do a great job for HLSL 2021 of assessing the impact of new features and the way they interact (sometimes poorly) with existing features.

With HLSL 202x we're trying to address that by being a bit more thoughtful up front about how these features interact, and for unions I think the simplest approach is to just disallow certain use cases.

A good example of one of the challenges is if you use a union inside a structure that is encapsulated in a resource, how do you interpolate the resource data? For this reason we think it is best to restrict how unions are used to cases that are clear and understandable.

I started drafting a feature proposal in our new proposal process for unions:
https://github.com/microsoft/hlsl-specs/blob/main/proposals/0004-unions.md

While I do have some concrete suggestions that I've included inline, I think we also need to work out some of the details about where unions can and can't be used.

@@ -261,8 +261,10 @@ void CGRecordLowering::lower(bool NVBaseType) {
// 8) Format the complete list of members in a way that can be consumed by
// CodeGenTypes::ComputeRecordLayout.
CharUnits Size = NVBaseType ? Layout.getNonVirtualSize() : Layout.getSize();
#if 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems incorrect.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To clarify, I don’t understand why you’re skipping lowering the union.

For the most part we shouldn’t need special handling for unions we should be able to use the existing clang support. If we’re modifying how unions are implemented in Clang we should understand why and explain in comments.

@@ -300,6 +300,8 @@ def disable_lifetime_markers : Flag<["-", "/"], "disable-lifetime-markers">, Gro
HelpText<"Disable generation of lifetime markers where they would be otherwise (6.6+)">;
def enable_templates: Flag<["-", "/"], "enable-templates">, Group<hlslcomp_Group>, Flags<[CoreOption, HelpHidden]>,
HelpText<"Enable template support for HLSL.">;
def enable_unions: Flag<["-", "/"], "enable-unions">, Group<hlslcomp_Group>, Flags<[CoreOption]>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

@pow2clk or @tex3d may have a different opinion from me, but I'd prefer if we didn't add individual feature flags for HLSL 202x features. I think they complicate the testing configurations, and we won't want to support them on release.

Choose a reason for hiding this comment

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

We still have this flag in the PR, but enable unions by default with LangStd::v202x. We can remove the flag entirely if you'd still like.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should remove the option entirely, and add test cases to verify that use of unions causes an error with -HV 2021.

I think the code still enables unions under HLSL 2021 because the comparison is >= 2021, and should probably be >= 202x.

@@ -71,7 +71,8 @@ class Type {
StructTyID, ///< 12: Structures
ArrayTyID, ///< 13: Arrays
PointerTyID, ///< 14: Pointers
VectorTyID ///< 15: SIMD 'packed' format, or other vector type
VectorTyID, ///< 15: SIMD 'packed' format, or other vector type
UnionTyID ///< 16: Unions
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand why this is necessary. Clang has never needed an IR Union type before, so I think adding one is concerning. Additionally, if we do require this, it changes DXIL so we'll need to evaluate the impact of this on other hardware vendors.

Choose a reason for hiding this comment

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

As mentioned this was unnecessary and we've since removed it.

// Update offset.
CBufferSize = std::max(CBufferSize, CBufferOffset + size);
CBufferOffset = CBufferSize;

++Field;
}

if (RD->isUnion() && CBufferSize == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

How do we handle empty structs here? Unions and structs should be interchangeable in this case.

@github-actions
Copy link
Contributor

PR contains clang-format violations. First 50 lines of the diff:

--- lib/DxcSupport/HLSLOptions.cpp	(before formatting)
+++ lib/DxcSupport/HLSLOptions.cpp	(after formatting)
@@ -532,7 +532,7 @@
     opts.EnableUnions = true;
 
   } else {
-     opts.EnableUnions = Args.hasFlag(OPT_enable_unions, OPT_INVALID, false);
+    opts.EnableUnions = Args.hasFlag(OPT_enable_unions, OPT_INVALID, false);
     if (opts.HLSLVersion <= hlsl::LangStd::v2015) {
       if (opts.EnableUnions)
         errors << "/enable-unions is not supported with HLSL Version "
--- lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp	(before formatting)
+++ lib/Transforms/Scalar/ScalarReplAggregatesHLSL.cpp	(after formatting)
@@ -2636,14 +2636,14 @@
     while (SrcST) {
       // If the layouts match, just replace the type
       if (SrcST->isLayoutIdentical(DstST)) {
-        // When handling unions replacing the type results in a broken GEP if the union
-        // contains a struct of multiple elements.
-        // To handle this union/bitcast scenario we replace the bitcast with the new
-        // allocation
+        // When handling unions replacing the type results in a broken GEP if
+        // the union contains a struct of multiple elements. To handle this
+        // union/bitcast scenario we replace the bitcast with the new allocation
         if (NewElts.size() == 1) {
           Type *ValTy = Val->getType();
           if (ValTy->isPointerTy()) {
-            StructType *ValST = dyn_cast<StructType>(ValTy->getPointerElementType());
+            StructType *ValST =
+                dyn_cast<StructType>(ValTy->getPointerElementType());
             if (ValST) {
               BCI->mutateType(NewElts[0]->getType());
               BCI->replaceAllUsesWith(NewElts[0]);
--- tools/clang/include/clang/Lex/Token.h	(before formatting)
+++ tools/clang/include/clang/Lex/Token.h	(after formatting)
@@ -294,25 +294,17 @@
   
   // HLSL Change Starts
   bool isHLSLReserved() const {
-    return
-      is(tok::kw___is_signed) ||
-      is(tok::kw___declspec) ||
-      is(tok::kw___forceinline) ||
-      is(tok::kw_auto) ||
-      is(tok::kw_catch) || is(tok::kw_const_cast) ||
-      is(tok::kw_delete) || is(tok::kw_dynamic_cast) ||
-      is(tok::kw_enum) || is(tok::kw_explicit) ||
-      is(tok::kw_friend) ||
-      is(tok::kw_goto) ||
-      is(tok::kw_mutable) ||

See action log for the full diff.

@github-actions

This comment was marked as resolved.

@github-actions

This comment was marked as resolved.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

Alexander Johnston added 22 commits November 6, 2023 16:46
ConstructStructAnnotation can now handle most union cases.
This change stops the function from iterating through every field of
the RD when calculating CBufferSize/Offset and building
fieldAnnotations for unions.
Now when handling unions it finds the appropriate field matching the
structType in the annotation, and only builds a fieldAnnotation for
that field.

Currently bitfields are not fully handled in unions in
ConstructStructAnnotation.
Author: Meghana Thatishetti
@Nielsbishere
Copy link
Contributor

Will this ever be merged? This is really nice.

@danbrown-amd
Copy link
Contributor Author

Not until there is a SPIR-V implementation, I'm told, and that has been set aside pending approval of an enabling SPIR-V extension.

@llvm-beanz
Copy link
Collaborator

I'm not convinced that we need a SPIR-V extension to implement this, but we do require all language features be supported by both DXIL and SPIR-V.

In addition to the SPIR-V implementation I think we had some outstanding design considerations we needed to be sure to address. Specifically we need to make sure to understand all the ways this interfaces with data storage (groupshared device and local memory), as well as put in place appropriate limitations for shader inputs and outputs.

@devshgraphicsprogramming

SPIR-V bitcast can do most stuff you'd want for a union as long as the type you're casting between has the same size.

Theoretically for every "unioned" load, one could generate a synthetic (ofc hash-consed) SPIR-V struct with a member of the unioned type at the correct manually declared offset.

Ofc this kinda hinges on scalar layout, IMHO I'd gatekeep a lot of interesting features behind -fvk-use-scalar-layout.

P.S. It might be useful to "legalize" bitcasts between the same type, otherwise the generated SPIR-V has issue with passing legalization now or in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: New
Development

Successfully merging this pull request may close these issues.

7 participants