Skip to content

Commit e9c2e0a

Browse files
authored
[AArch64] Match GCC behaviour for zero-size structs (#124760)
We had a test claiming that this empty struct type consumes a register slot when passing it to a function with GCC, but that does not appear to be the case, at least with GCC versions going back to 4.8. This also caused a miscompilation when passing one of these structs to a variadic function, but it turned out that our implementation of `va_arg` matches GCC's ABI, so the one change fixes both bugs.
1 parent 25ae1a2 commit e9c2e0a

File tree

2 files changed

+73
-25
lines changed

2 files changed

+73
-25
lines changed

clang/lib/CodeGen/Targets/AArch64.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -415,18 +415,21 @@ ABIArgInfo AArch64ABIInfo::classifyArgumentType(QualType Ty, bool IsVariadicFn,
415415
CGCXXABI::RAA_DirectInMemory);
416416
}
417417

418-
// Empty records are always ignored on Darwin, but actually passed in C++ mode
419-
// elsewhere for GNU compatibility.
418+
// Empty records:
420419
uint64_t Size = getContext().getTypeSize(Ty);
421420
bool IsEmpty = isEmptyRecord(getContext(), Ty, true);
422421
if (!Ty->isSVESizelessBuiltinType() && (IsEmpty || Size == 0)) {
422+
// Empty records are ignored in C mode, and in C++ on Darwin.
423423
if (!getContext().getLangOpts().CPlusPlus || isDarwinPCS())
424424
return ABIArgInfo::getIgnore();
425425

426-
// GNU C mode. The only argument that gets ignored is an empty one with size
427-
// 0.
428-
if (IsEmpty && Size == 0)
426+
// In C++ mode, arguments which have sizeof() == 0 (which are non-standard
427+
// C++) are ignored. This isn't defined by any standard, so we copy GCC's
428+
// behaviour here.
429+
if (Size == 0)
429430
return ABIArgInfo::getIgnore();
431+
432+
// Otherwise, they are passed as if they have a size of 1 byte.
430433
return ABIArgInfo::getDirect(llvm::Type::getInt8Ty(getVMContext()));
431434
}
432435

clang/test/CodeGen/AArch64/args.cpp

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -emit-llvm -o - %s | FileCheck %s
2-
// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - -x c %s | FileCheck %s --check-prefix=CHECK-GNU-C
3-
// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK-GNU-CXX
1+
// RUN: %clang_cc1 -triple arm64-apple-ios7.0 -target-abi darwinpcs -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,DARWIN
2+
// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - -x c %s | FileCheck %s --check-prefixes=CHECK,C
3+
// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CXX
44

55
// Empty structs are ignored for PCS purposes on Darwin and in C mode elsewhere.
66
// In C++ mode on ELF they consume a register slot though. Functions are
@@ -15,16 +15,16 @@
1515

1616
struct Empty {};
1717

18-
// CHECK: define{{.*}} i32 @empty_arg(i32 noundef %a)
19-
// CHECK-GNU-C: define{{.*}} i32 @empty_arg(i32 noundef %a)
20-
// CHECK-GNU-CXX: define{{.*}} i32 @empty_arg(i8 %e.coerce, i32 noundef %a)
18+
// DARWIN: define{{.*}} i32 @empty_arg(i32 noundef %a)
19+
// C: define{{.*}} i32 @empty_arg(i32 noundef %a)
20+
// CXX: define{{.*}} i32 @empty_arg(i8 %e.coerce, i32 noundef %a)
2121
EXTERNC int empty_arg(struct Empty e, int a) {
2222
return a;
2323
}
2424

25-
// CHECK: define{{.*}} void @empty_ret()
26-
// CHECK-GNU-C: define{{.*}} void @empty_ret()
27-
// CHECK-GNU-CXX: define{{.*}} void @empty_ret()
25+
// DARWIN: define{{.*}} void @empty_ret()
26+
// C: define{{.*}} void @empty_ret()
27+
// CXX: define{{.*}} void @empty_ret()
2828
EXTERNC struct Empty empty_ret(void) {
2929
struct Empty e;
3030
return e;
@@ -38,30 +38,75 @@ struct SuperEmpty {
3838
int arr[0];
3939
};
4040

41-
// CHECK: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
42-
// CHECK-GNU-C: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
43-
// CHECK-GNU-CXX: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
41+
// DARWIN: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
42+
// C: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
43+
// CXX: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
4444
EXTERNC int super_empty_arg(struct SuperEmpty e, int a) {
4545
return a;
4646
}
4747

48-
// This is not empty. It has 0 size but consumes a register slot for GCC.
48+
// This is also not empty, and non-standard. We previously considered it to
49+
// consume a register slot, but GCC does not, so we match that.
4950

5051
struct SortOfEmpty {
5152
struct SuperEmpty e;
5253
};
5354

54-
// CHECK: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
55-
// CHECK-GNU-C: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
56-
// CHECK-GNU-CXX: define{{.*}} i32 @sort_of_empty_arg(i8 %e.coerce, i32 noundef %a)
57-
EXTERNC int sort_of_empty_arg(struct Empty e, int a) {
55+
// DARWIN: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
56+
// C: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
57+
// CXX: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
58+
EXTERNC int sort_of_empty_arg(struct SortOfEmpty e, int a) {
5859
return a;
5960
}
6061

61-
// CHECK: define{{.*}} void @sort_of_empty_ret()
62-
// CHECK-GNU-C: define{{.*}} void @sort_of_empty_ret()
63-
// CHECK-GNU-CXX: define{{.*}} void @sort_of_empty_ret()
62+
// DARWIN: define{{.*}} void @sort_of_empty_ret()
63+
// C: define{{.*}} void @sort_of_empty_ret()
64+
// CXX: define{{.*}} void @sort_of_empty_ret()
6465
EXTERNC struct SortOfEmpty sort_of_empty_ret(void) {
6566
struct SortOfEmpty e;
6667
return e;
6768
}
69+
70+
#include <stdarg.h>
71+
72+
// va_arg matches the above rules, consuming an incoming argument in cases
73+
// where one would be passed, and not doing so when the argument should be
74+
// ignored.
75+
76+
EXTERNC struct Empty empty_arg_variadic(int a, ...) {
77+
// CHECK-LABEL: @empty_arg_variadic(
78+
// DARWIN-NOT: {{ getelementptr }}
79+
// C-NOT: {{ getelementptr }}
80+
// CXX: %new_reg_offs = add i32 %gr_offs, 8
81+
// CXX: %new_stack = getelementptr inbounds i8, ptr %stack, i64 8
82+
va_list vl;
83+
va_start(vl, a);
84+
struct Empty b = va_arg(vl, struct Empty);
85+
va_end(vl);
86+
return b;
87+
}
88+
89+
EXTERNC struct SuperEmpty super_empty_arg_variadic(int a, ...) {
90+
// CHECK-LABEL: @super_empty_arg_variadic(
91+
// DARWIN-NOT: {{ getelementptr }}
92+
// C-NOT: {{ getelementptr }}
93+
// CXX-NOT: {{ getelementptr }}
94+
va_list vl;
95+
va_start(vl, a);
96+
struct SuperEmpty b = va_arg(vl, struct SuperEmpty);
97+
va_end(vl);
98+
return b;
99+
}
100+
101+
EXTERNC struct SortOfEmpty sort_of_empty_arg_variadic(int a, ...) {
102+
// CHECK-LABEL: @sort_of_empty_arg_variadic(
103+
// DARWIN: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i64 0
104+
// C-NOT: {{ getelementptr }}
105+
// CXX-NOT: {{ getelementptr }}
106+
va_list vl;
107+
va_start(vl, a);
108+
struct SortOfEmpty b = va_arg(vl, struct SortOfEmpty);
109+
va_end(vl);
110+
return b;
111+
}
112+

0 commit comments

Comments
 (0)