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

[RISCV] Handle empty structs/unions passing in C++ #97315

Merged
merged 4 commits into from
Jul 9, 2024

Conversation

svs-quic
Copy link
Contributor

@svs-quic svs-quic commented Jul 1, 2024

According to RISC-V integer calling convention empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

Fixes #97285

@topperc topperc requested review from asb, jrtc27 and kito-cheng July 1, 2024 16:46
@topperc
Copy link
Collaborator

topperc commented Jul 1, 2024

Why is this in Draft?

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 1, 2024

Just waiting for the pre-checkins to complete. I can push it for review if I don't really need to wait for it.

Edit: Pushing the patch for review since its late here and I'll need to log off. Also wanted to mention that this change is similar to what LoongArch did in #70320.

@svs-quic svs-quic marked this pull request as ready for review July 1, 2024 17:06
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:RISC-V clang:codegen labels Jul 1, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 1, 2024

@llvm/pr-subscribers-backend-risc-v

@llvm/pr-subscribers-clang-codegen

Author: Sudharsan Veeravalli (svs-quic)

Changes

According to RISC-V integer calling convention empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

Fixes #97285


Full diff: https://github.com/llvm/llvm-project/pull/97315.diff

2 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/RISCV.cpp (+5-4)
  • (modified) clang/test/CodeGen/RISCV/abi-empty-structs.c (+34)
diff --git a/clang/lib/CodeGen/Targets/RISCV.cpp b/clang/lib/CodeGen/Targets/RISCV.cpp
index a4c5ec315b8df..f2add9351c03c 100644
--- a/clang/lib/CodeGen/Targets/RISCV.cpp
+++ b/clang/lib/CodeGen/Targets/RISCV.cpp
@@ -361,12 +361,13 @@ ABIArgInfo RISCVABIInfo::classifyArgumentType(QualType Ty, bool IsFixed,
                                            CGCXXABI::RAA_DirectInMemory);
   }
 
-  // Ignore empty structs/unions.
-  if (isEmptyRecord(getContext(), Ty, true))
-    return ABIArgInfo::getIgnore();
-
   uint64_t Size = getContext().getTypeSize(Ty);
 
+  // Ignore empty structs/unions whose size is zero. According to the calling
+  // convention empty structs/unions are required to be sized types in C++.
+  if (isEmptyRecord(getContext(), Ty, true) && Size == 0)
+    return ABIArgInfo::getIgnore();
+
   // Pass floating point values via FPRs if possible.
   if (IsFixed && Ty->isFloatingType() && !Ty->isComplexType() &&
       FLen >= Size && ArgFPRsLeft) {
diff --git a/clang/test/CodeGen/RISCV/abi-empty-structs.c b/clang/test/CodeGen/RISCV/abi-empty-structs.c
index c48a2891627d4..877ed4962d4e8 100644
--- a/clang/test/CodeGen/RISCV/abi-empty-structs.c
+++ b/clang/test/CodeGen/RISCV/abi-empty-structs.c
@@ -167,6 +167,40 @@ struct s9 {
 //
 void test_s9(struct s9 a) {}
 
+struct s10 { };
+// CHECK-C-LABEL: define dso_local void @test_s10
+// CHECK-C-SAME: () #[[ATTR0]] {
+// CHECK-C:  entry:
+//
+// CHECK32-CXX-LABEL: define dso_local i32 @_Z8test_s103s10
+// CHECK32-CXX-SAME: (i32 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK32-CXX:  entry:
+//
+// CHECK64-CXX-LABEL: define dso_local i64 @_Z8test_s103s10
+// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK64-CXX:  entry:
+//
+struct s10 test_s10(struct s10 a) {
+  return a;
+}
+
+struct s11 { int : 0; };
+// CHECK-C-LABEL: define dso_local void @test_s11
+// CHECK-C-SAME: () #[[ATTR0]] {
+// CHECK-C:  entry:
+//
+// CHECK32-CXX-LABEL: define dso_local i32 @_Z8test_s113s11
+// CHECK32-CXX-SAME: (i32 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK32-CXX:  entry:
+//
+// CHECK64-CXX-LABEL: define dso_local i64 @_Z8test_s113s11
+// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK64-CXX:  entry:
+//
+struct s11 test_s11(struct s11 a) {
+  return a;
+}
+
 //// NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
 // CHECK32-C: {{.*}}
 // CHECK64-C: {{.*}}

@llvmbot
Copy link
Member

llvmbot commented Jul 1, 2024

@llvm/pr-subscribers-clang

Author: Sudharsan Veeravalli (svs-quic)

Changes

According to RISC-V integer calling convention empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

Fixes #97285


Full diff: https://github.com/llvm/llvm-project/pull/97315.diff

2 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/RISCV.cpp (+5-4)
  • (modified) clang/test/CodeGen/RISCV/abi-empty-structs.c (+34)
diff --git a/clang/lib/CodeGen/Targets/RISCV.cpp b/clang/lib/CodeGen/Targets/RISCV.cpp
index a4c5ec315b8df..f2add9351c03c 100644
--- a/clang/lib/CodeGen/Targets/RISCV.cpp
+++ b/clang/lib/CodeGen/Targets/RISCV.cpp
@@ -361,12 +361,13 @@ ABIArgInfo RISCVABIInfo::classifyArgumentType(QualType Ty, bool IsFixed,
                                            CGCXXABI::RAA_DirectInMemory);
   }
 
-  // Ignore empty structs/unions.
-  if (isEmptyRecord(getContext(), Ty, true))
-    return ABIArgInfo::getIgnore();
-
   uint64_t Size = getContext().getTypeSize(Ty);
 
+  // Ignore empty structs/unions whose size is zero. According to the calling
+  // convention empty structs/unions are required to be sized types in C++.
+  if (isEmptyRecord(getContext(), Ty, true) && Size == 0)
+    return ABIArgInfo::getIgnore();
+
   // Pass floating point values via FPRs if possible.
   if (IsFixed && Ty->isFloatingType() && !Ty->isComplexType() &&
       FLen >= Size && ArgFPRsLeft) {
diff --git a/clang/test/CodeGen/RISCV/abi-empty-structs.c b/clang/test/CodeGen/RISCV/abi-empty-structs.c
index c48a2891627d4..877ed4962d4e8 100644
--- a/clang/test/CodeGen/RISCV/abi-empty-structs.c
+++ b/clang/test/CodeGen/RISCV/abi-empty-structs.c
@@ -167,6 +167,40 @@ struct s9 {
 //
 void test_s9(struct s9 a) {}
 
+struct s10 { };
+// CHECK-C-LABEL: define dso_local void @test_s10
+// CHECK-C-SAME: () #[[ATTR0]] {
+// CHECK-C:  entry:
+//
+// CHECK32-CXX-LABEL: define dso_local i32 @_Z8test_s103s10
+// CHECK32-CXX-SAME: (i32 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK32-CXX:  entry:
+//
+// CHECK64-CXX-LABEL: define dso_local i64 @_Z8test_s103s10
+// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK64-CXX:  entry:
+//
+struct s10 test_s10(struct s10 a) {
+  return a;
+}
+
+struct s11 { int : 0; };
+// CHECK-C-LABEL: define dso_local void @test_s11
+// CHECK-C-SAME: () #[[ATTR0]] {
+// CHECK-C:  entry:
+//
+// CHECK32-CXX-LABEL: define dso_local i32 @_Z8test_s113s11
+// CHECK32-CXX-SAME: (i32 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK32-CXX:  entry:
+//
+// CHECK64-CXX-LABEL: define dso_local i64 @_Z8test_s113s11
+// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0]] {
+// CHECK64-CXX:  entry:
+//
+struct s11 test_s11(struct s11 a) {
+  return a;
+}
+
 //// NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
 // CHECK32-C: {{.*}}
 // CHECK64-C: {{.*}}

@efriedma-quic
Copy link
Collaborator

Please add a test for struct X { int x[0]; };.

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 2, 2024

Thanks @efriedma-quic. I tried adding a test case for it locally and see that the code produced is different for llvm and gcc: https://godbolt.org/z/vdhGbvj6W

Test case:

struct s12 {int x[0];};
struct s12 test_s12(struct s12 a) {
  return a;
}

For llvm this is an empty record of size zero and so it ignores it where as gcc loads 0 into a0. Do we handle this case similar to what gcc does? Or do I add a fixme for now?

@topperc @asb

@efriedma-quic
Copy link
Collaborator

The current spec language is:

Empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

So empty structs in C are ignored, empty structs in C++ are treated as non-empty.

The "C++ struct containing an empty array" is sort of an edge case, since it's not actually legal C++, but the spec says the distinguishing factor is whether we're in C++ mode. And gcc seems to do that, so let's do the same thing. So instead of checking for Size == 0, check for LangOpts.CPlusPlus.

Also, please add a release note for this.

@efriedma-quic efriedma-quic self-requested a review July 5, 2024 22:20
@topperc
Copy link
Collaborator

topperc commented Jul 5, 2024

The current spec language is:

Empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

So empty structs in C are ignored, empty structs in C++ are treated as non-empty.

The "C++ struct containing an empty array" is sort of an edge case, since it's not actually legal C++, but the spec says the distinguishing factor is whether we're in C++ mode. And gcc seems to do that, so let's do the same thing. So instead of checking for Size == 0, check for LangOpts.CPlusPlus.

Also, please add a release note for this.

gcc does ignore the empty array case for assigning argument registers in C++. Compare the two cases here https://godbolt.org/z/j4WP89rE8

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 6, 2024

The current spec language is:

Empty structs or union arguments or return values are ignored by C compilers which support them as a non-standard extension. This is not the case for C++, which requires them to be sized types.

So empty structs in C are ignored, empty structs in C++ are treated as non-empty.

The "C++ struct containing an empty array" is sort of an edge case, since it's not actually legal C++, but the spec says the distinguishing factor is whether we're in C++ mode. And gcc seems to do that, so let's do the same thing. So instead of checking for Size == 0, check for LangOpts.CPlusPlus.

Also, please add a release note for this.

Thanks Eli. I have made these changes.

@efriedma-quic
Copy link
Collaborator

gcc does ignore the empty array case for assigning argument registers in C++. Compare the two cases here https://godbolt.org/z/j4WP89rE8

Oh, you're right, I didn't actually do any testing myself, I was just assuming @svs-quic did the right test.

So I guess the previous size==0 check was actually correct.

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 8, 2024

Thanks @efriedma-quic. I tried adding a test case for it locally and see that the code produced is different for llvm and gcc: https://godbolt.org/z/vdhGbvj6W

Test case:

struct s12 {int x[0];};
struct s12 test_s12(struct s12 a) {
  return a;
}

For llvm this is an empty record of size zero and so it ignores it where as gcc loads 0 into a0. Do we handle this case similar to what gcc does? Or do I add a fixme for now?

@topperc @asb

What about the case I have mentioned in https://godbolt.org/z/vdhGbvj6W ?

Having a check for both Size==0 and LangOpts.CPlusPlus handles this as well.

@efriedma-quic
Copy link
Collaborator

What about the case I have mentioned in https://godbolt.org/z/vdhGbvj6W ?

That test doesn't really prove anything useful. The zeroing of a0 doesn't have any ABI significance: "struct s12" doesn't have any data in it, so the caller can't use the value in a0 for anything. At most, it's a hint about how gcc's internal logic works.

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 8, 2024

Thanks. I have modified the check and added a relevant test for empty arrays.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Collaborator

@topperc topperc left a comment

Choose a reason for hiding this comment

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

LGTM

@svs-quic
Copy link
Contributor Author

svs-quic commented Jul 9, 2024

Could someone please merge this change? I do not have permissions to do so.

@topperc topperc merged commit d65f423 into llvm:main Jul 9, 2024
8 checks passed
@topperc
Copy link
Collaborator

topperc commented Jul 9, 2024

Could someone please merge this change? I do not have permissions to do so.

Done

aaryanshukla pushed a commit to aaryanshukla/llvm-project that referenced this pull request Jul 14, 2024
According to RISC-V integer calling convention empty structs or union
arguments or return values are ignored by C compilers which support them
as a non-standard extension. This is not the case for C++, which
requires them to be sized types.

Fixes llvm#97285
@svs-quic svs-quic deleted the riscv-empty-structs branch July 15, 2024 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:RISC-V clang:codegen clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[RISC-V] Empty structs are ignored when passing in C++
4 participants