Skip to content

Commit 2f40e7b

Browse files
asbtru
authored andcommitted
[clang][RISCV] Fix bug in ABI handling of empty structs with hard FP calling conventions in C++
As reported in <llvm#58929>, Clang's handling of empty structs in the case of small structs that may be eligible to be passed using the hard FP calling convention doesn't match g++. In general, C++ record fields are never empty unless [[no_unique_address]] is used, but the RISC-V FP ABI overrides this. After this patch, fields of structs that contain empty records will be ignored, even in C++, when considering eligibility for the FP calling convention ('flattening'). It isn't explicitly noted in the RISC-V psABI, but arrays of empty records will disqualify a struct for consideration of using the FP calling convention in g++. This patch matches that behaviour. The psABI issue <riscv-non-isa/riscv-elf-psabi-doc#358> seeks to clarify this. This patch was previously committed but reverted after a bug was found. This recommit adds additional logic to prevent that bug (adding an extra check for when a candidate from detectFPCCEligibleStructHelper may not be valid). Differential Revision: https://reviews.llvm.org/D142327
1 parent 99ed472 commit 2f40e7b

File tree

5 files changed

+66
-48
lines changed

5 files changed

+66
-48
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,9 @@ RISC-V Support
945945
- The rules for ordering of extensions in ``-march`` strings were relaxed. A
946946
canonical ordering is no longer enforced on ``z*``, ``s*``, and ``x*``
947947
prefixed extensions.
948+
- An ABI mismatch between GCC and Clang related to the handling of empty
949+
structs in C++ parameter passing under the hard floating point calling
950+
conventions was fixed.
948951
- Support the RVV intrinsics v0.12. Please checkout `the RVV C intrinsics
949952
specification
950953
<https://github.com/riscv-non-isa/rvv-intrinsic-doc/releases/tag/v0.12.0>`_.

clang/lib/CodeGen/ABIInfoImpl.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ Address CodeGen::emitMergePHI(CodeGenFunction &CGF, Address Addr1,
246246
}
247247

248248
bool CodeGen::isEmptyField(ASTContext &Context, const FieldDecl *FD,
249-
bool AllowArrays) {
249+
bool AllowArrays, bool AsIfNoUniqueAddr) {
250250
if (FD->isUnnamedBitfield())
251251
return true;
252252

@@ -280,13 +280,14 @@ bool CodeGen::isEmptyField(ASTContext &Context, const FieldDecl *FD,
280280
// not arrays of records, so we must also check whether we stripped off an
281281
// array type above.
282282
if (isa<CXXRecordDecl>(RT->getDecl()) &&
283-
(WasArray || !FD->hasAttr<NoUniqueAddressAttr>()))
283+
(WasArray || (!AsIfNoUniqueAddr && !FD->hasAttr<NoUniqueAddressAttr>())))
284284
return false;
285285

286-
return isEmptyRecord(Context, FT, AllowArrays);
286+
return isEmptyRecord(Context, FT, AllowArrays, AsIfNoUniqueAddr);
287287
}
288288

289-
bool CodeGen::isEmptyRecord(ASTContext &Context, QualType T, bool AllowArrays) {
289+
bool CodeGen::isEmptyRecord(ASTContext &Context, QualType T, bool AllowArrays,
290+
bool AsIfNoUniqueAddr) {
290291
const RecordType *RT = T->getAs<RecordType>();
291292
if (!RT)
292293
return false;
@@ -297,11 +298,11 @@ bool CodeGen::isEmptyRecord(ASTContext &Context, QualType T, bool AllowArrays) {
297298
// If this is a C++ record, check the bases first.
298299
if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD))
299300
for (const auto &I : CXXRD->bases())
300-
if (!isEmptyRecord(Context, I.getType(), true))
301+
if (!isEmptyRecord(Context, I.getType(), true, AsIfNoUniqueAddr))
301302
return false;
302303

303304
for (const auto *I : RD->fields())
304-
if (!isEmptyField(Context, I, AllowArrays))
305+
if (!isEmptyField(Context, I, AllowArrays, AsIfNoUniqueAddr))
305306
return false;
306307
return true;
307308
}

clang/lib/CodeGen/ABIInfoImpl.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,19 @@ Address emitMergePHI(CodeGenFunction &CGF, Address Addr1,
122122
llvm::BasicBlock *Block2, const llvm::Twine &Name = "");
123123

124124
/// isEmptyField - Return true iff a the field is "empty", that is it
125-
/// is an unnamed bit-field or an (array of) empty record(s).
126-
bool isEmptyField(ASTContext &Context, const FieldDecl *FD, bool AllowArrays);
125+
/// is an unnamed bit-field or an (array of) empty record(s). If
126+
/// AsIfNoUniqueAddr is true, then C++ record fields are considered empty if
127+
/// the [[no_unique_address]] attribute would have made them empty.
128+
bool isEmptyField(ASTContext &Context, const FieldDecl *FD, bool AllowArrays,
129+
bool AsIfNoUniqueAddr = false);
127130

128131
/// isEmptyRecord - Return true iff a structure contains only empty
129132
/// fields. Note that a structure with a flexible array member is not
130-
/// considered empty.
131-
bool isEmptyRecord(ASTContext &Context, QualType T, bool AllowArrays);
133+
/// considered empty. If AsIfNoUniqueAddr is true, then C++ record fields are
134+
/// considered empty if the [[no_unique_address]] attribute would have made
135+
/// them empty.
136+
bool isEmptyRecord(ASTContext &Context, QualType T, bool AllowArrays,
137+
bool AsIfNoUniqueAddr = false);
132138

133139
/// isSingleElementStruct - Determine if a structure is a "single
134140
/// element struct", i.e. it has exactly one non-empty field or

clang/lib/CodeGen/Targets/RISCV.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ bool RISCVABIInfo::detectFPCCEligibleStructHelper(QualType Ty, CharUnits CurOff,
151151
if (const ConstantArrayType *ATy = getContext().getAsConstantArrayType(Ty)) {
152152
uint64_t ArraySize = ATy->getSize().getZExtValue();
153153
QualType EltTy = ATy->getElementType();
154+
// Non-zero-length arrays of empty records make the struct ineligible for
155+
// the FP calling convention in C++.
156+
if (const auto *RTy = EltTy->getAs<RecordType>()) {
157+
if (ArraySize != 0 && isa<CXXRecordDecl>(RTy->getDecl()) &&
158+
isEmptyRecord(getContext(), EltTy, true, true))
159+
return false;
160+
}
154161
CharUnits EltSize = getContext().getTypeSizeInChars(EltTy);
155162
for (uint64_t i = 0; i < ArraySize; ++i) {
156163
bool Ret = detectFPCCEligibleStructHelper(EltTy, CurOff, Field1Ty,
@@ -167,7 +174,7 @@ bool RISCVABIInfo::detectFPCCEligibleStructHelper(QualType Ty, CharUnits CurOff,
167174
// copy constructor are not eligible for the FP calling convention.
168175
if (getRecordArgABI(Ty, CGT.getCXXABI()))
169176
return false;
170-
if (isEmptyRecord(getContext(), Ty, true))
177+
if (isEmptyRecord(getContext(), Ty, true, true))
171178
return true;
172179
const RecordDecl *RD = RTy->getDecl();
173180
// Unions aren't eligible unless they're empty (which is caught above).
@@ -237,6 +244,8 @@ bool RISCVABIInfo::detectFPCCEligibleStruct(QualType Ty, llvm::Type *&Field1Ty,
237244
NeededArgFPRs = 0;
238245
bool IsCandidate = detectFPCCEligibleStructHelper(
239246
Ty, CharUnits::Zero(), Field1Ty, Field1Off, Field2Ty, Field2Off);
247+
if (!Field1Ty)
248+
return false;
240249
// Not really a candidate if we have a single int but no float.
241250
if (Field1Ty && !Field2Ty && !Field1Ty->isFloatingPointTy())
242251
return false;

clang/test/CodeGen/RISCV/abi-empty-structs.c

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature --full-function-signature --filter "^define |^entry:"
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --filter "^define |^entry:" --version 2
22
// RUN: %clang_cc1 -triple riscv32 -target-feature +f -target-abi ilp32f -emit-llvm %s -o - \
33
// RUN: | FileCheck -check-prefixes=CHECK-C,CHECK32-C %s
44
// RUN: %clang_cc1 -triple riscv32 -target-feature +f -target-feature +d -target-abi ilp32d -emit-llvm %s -o - \
@@ -19,8 +19,9 @@
1919
#include <stdint.h>
2020

2121
// Fields containing empty structs or unions are ignored when flattening
22-
// structs for the hard FP ABIs, even in C++.
23-
// FIXME: This isn't currently respected.
22+
// structs for the hard FP ABIs, even in C++. The rules for arrays of empty
23+
// structs or unions are subtle and documented in
24+
// <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc#hardware-floating-point-calling-convention>.
2425

2526
struct empty { struct { struct { } e; }; };
2627
struct s1 { struct empty e; float f; };
@@ -29,13 +30,9 @@ struct s1 { struct empty e; float f; };
2930
// CHECK-C-SAME: (float [[TMP0:%.*]]) #[[ATTR0:[0-9]+]] {
3031
// CHECK-C: entry:
3132
//
32-
// CHECK32-CXX-LABEL: define dso_local [2 x i32] @_Z7test_s12s1
33-
// CHECK32-CXX-SAME: ([2 x i32] [[A_COERCE:%.*]]) #[[ATTR0:[0-9]+]] {
34-
// CHECK32-CXX: entry:
35-
//
36-
// CHECK64-CXX-LABEL: define dso_local i64 @_Z7test_s12s1
37-
// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0:[0-9]+]] {
38-
// CHECK64-CXX: entry:
33+
// CHECK-CXX-LABEL: define dso_local float @_Z7test_s12s1
34+
// CHECK-CXX-SAME: (float [[TMP0:%.*]]) #[[ATTR0:[0-9]+]] {
35+
// CHECK-CXX: entry:
3936
//
4037
struct s1 test_s1(struct s1 a) {
4138
return a;
@@ -47,13 +44,9 @@ struct s2 { struct empty e; int32_t i; float f; };
4744
// CHECK-C-SAME: (i32 [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
4845
// CHECK-C: entry:
4946
//
50-
// CHECK32-CXX-LABEL: define dso_local void @_Z7test_s22s2
51-
// CHECK32-CXX-SAME: (ptr noalias sret([[STRUCT_S2:%.*]]) align 4 [[AGG_RESULT:%.*]], ptr noundef [[A:%.*]]) #[[ATTR0]] {
52-
// CHECK32-CXX: entry:
53-
//
54-
// CHECK64-CXX-LABEL: define dso_local [2 x i64] @_Z7test_s22s2
55-
// CHECK64-CXX-SAME: ([2 x i64] [[A_COERCE:%.*]]) #[[ATTR0]] {
56-
// CHECK64-CXX: entry:
47+
// CHECK-CXX-LABEL: define dso_local { i32, float } @_Z7test_s22s2
48+
// CHECK-CXX-SAME: (i32 [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
49+
// CHECK-CXX: entry:
5750
//
5851
struct s2 test_s2(struct s2 a) {
5952
return a;
@@ -65,13 +58,9 @@ struct s3 { struct empty e; float f; float g; };
6558
// CHECK-C-SAME: (float [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
6659
// CHECK-C: entry:
6760
//
68-
// CHECK32-CXX-LABEL: define dso_local void @_Z7test_s32s3
69-
// CHECK32-CXX-SAME: (ptr noalias sret([[STRUCT_S3:%.*]]) align 4 [[AGG_RESULT:%.*]], ptr noundef [[A:%.*]]) #[[ATTR0]] {
70-
// CHECK32-CXX: entry:
71-
//
72-
// CHECK64-CXX-LABEL: define dso_local [2 x i64] @_Z7test_s32s3
73-
// CHECK64-CXX-SAME: ([2 x i64] [[A_COERCE:%.*]]) #[[ATTR0]] {
74-
// CHECK64-CXX: entry:
61+
// CHECK-CXX-LABEL: define dso_local { float, float } @_Z7test_s32s3
62+
// CHECK-CXX-SAME: (float [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
63+
// CHECK-CXX: entry:
7564
//
7665
struct s3 test_s3(struct s3 a) {
7766
return a;
@@ -83,13 +72,9 @@ struct s4 { struct empty e; float __complex__ c; };
8372
// CHECK-C-SAME: (float [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
8473
// CHECK-C: entry:
8574
//
86-
// CHECK32-CXX-LABEL: define dso_local void @_Z7test_s42s4
87-
// CHECK32-CXX-SAME: (ptr noalias sret([[STRUCT_S4:%.*]]) align 4 [[AGG_RESULT:%.*]], ptr noundef [[A:%.*]]) #[[ATTR0]] {
88-
// CHECK32-CXX: entry:
89-
//
90-
// CHECK64-CXX-LABEL: define dso_local [2 x i64] @_Z7test_s42s4
91-
// CHECK64-CXX-SAME: ([2 x i64] [[A_COERCE:%.*]]) #[[ATTR0]] {
92-
// CHECK64-CXX: entry:
75+
// CHECK-CXX-LABEL: define dso_local { float, float } @_Z7test_s42s4
76+
// CHECK-CXX-SAME: (float [[TMP0:%.*]], float [[TMP1:%.*]]) #[[ATTR0]] {
77+
// CHECK-CXX: entry:
9378
//
9479
struct s4 test_s4(struct s4 a) {
9580
return a;
@@ -142,7 +127,7 @@ struct s7 { struct empty e[0]; float f; };
142127
// CHECK-C: entry:
143128
//
144129
// CHECK-CXX-LABEL: define dso_local float @_Z7test_s72s7
145-
// CHECK-CXX-SAME: (float [[TMP0:%.*]]) #[[ATTR0:[0-9]+]] {
130+
// CHECK-CXX-SAME: (float [[TMP0:%.*]]) #[[ATTR0]] {
146131
// CHECK-CXX: entry:
147132
//
148133
struct s7 test_s7(struct s7 a) {
@@ -156,17 +141,31 @@ struct s8 { struct empty_arr0 e; float f; };
156141
// CHECK-C-SAME: (float [[TMP0:%.*]]) #[[ATTR0]] {
157142
// CHECK-C: entry:
158143
//
159-
// CHECK32-CXX-LABEL: define dso_local i32 @_Z7test_s82s8
144+
// CHECK-CXX-LABEL: define dso_local float @_Z7test_s82s8
145+
// CHECK-CXX-SAME: (float [[TMP0:%.*]]) #[[ATTR0]] {
146+
// CHECK-CXX: entry:
147+
//
148+
struct s8 test_s8(struct s8 a) {
149+
return a;
150+
}
151+
152+
struct s9 {
153+
struct empty e;
154+
};
155+
156+
// CHECK-C-LABEL: define dso_local void @test_s9
157+
// CHECK-C-SAME: () #[[ATTR0]] {
158+
// CHECK-C: entry:
159+
//
160+
// CHECK32-CXX-LABEL: define dso_local void @_Z7test_s92s9
160161
// CHECK32-CXX-SAME: (i32 [[A_COERCE:%.*]]) #[[ATTR0]] {
161162
// CHECK32-CXX: entry:
162163
//
163-
// CHECK64-CXX-LABEL: define dso_local i64 @_Z7test_s82s8
164+
// CHECK64-CXX-LABEL: define dso_local void @_Z7test_s92s9
164165
// CHECK64-CXX-SAME: (i64 [[A_COERCE:%.*]]) #[[ATTR0]] {
165166
// CHECK64-CXX: entry:
166167
//
167-
struct s8 test_s8(struct s8 a) {
168-
return a;
169-
}
168+
void test_s9(struct s9 a) {}
170169

171170
//// NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
172171
// CHECK32-C: {{.*}}

0 commit comments

Comments
 (0)