Skip to content

Commit c2fba6d

Browse files
authored
[clang][SPARC] Treat empty structs as if it's a one-bit type in the CC (#90338)
Make sure that empty structs are treated as if it has a size of one bit in function parameters and return types so that it occupies a full argument and/or return register slot. This fixes crashes and miscompilations when passing and/or returning empty structs. Reviewed by: @s-barannikov
1 parent 95e307c commit c2fba6d

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

clang/lib/CodeGen/Targets/Sparc.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,10 @@ SparcV9ABIInfo::classifyType(QualType Ty, unsigned SizeLimit) const {
263263

264264
CoerceBuilder CB(getVMContext(), getDataLayout());
265265
CB.addStruct(0, StrTy);
266-
CB.pad(llvm::alignTo(CB.DL.getTypeSizeInBits(StrTy), 64));
266+
// All structs, even empty ones, should take up a register argument slot,
267+
// so pin the minimum struct size to one bit.
268+
CB.pad(llvm::alignTo(
269+
std::max(CB.DL.getTypeSizeInBits(StrTy).getKnownMinValue(), 1UL), 64));
267270

268271
// Try to use the original type for coercion.
269272
llvm::Type *CoerceTy = CB.isUsableType(StrTy) ? StrTy : CB.getType();

clang/test/CodeGen/sparcv9-abi.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,47 @@ char f_int_4(char x) { return x; }
2121
// CHECK-LABEL: define{{.*}} fp128 @f_ld(fp128 noundef %x)
2222
long double f_ld(long double x) { return x; }
2323

24+
// Zero-sized structs reserves an argument register slot if passed directly.
25+
struct empty {};
26+
struct emptyarr { struct empty a[10]; };
27+
28+
// CHECK-LABEL: define{{.*}} i64 @f_empty(i64 %x.coerce)
29+
struct empty f_empty(struct empty x) { return x; }
30+
31+
// CHECK-LABEL: define{{.*}} i64 @f_emptyarr(i64 %x.coerce)
32+
struct empty f_emptyarr(struct emptyarr x) { return x.a[0]; }
33+
34+
// CHECK-LABEL: define{{.*}} i64 @f_emptyvar(i32 noundef zeroext %count, ...)
35+
long f_emptyvar(unsigned count, ...) {
36+
long ret;
37+
va_list args;
38+
va_start(args, count);
39+
40+
// CHECK: %[[CUR:[^ ]+]] = load ptr, ptr %args
41+
// CHECK-DAG: %[[NXT:[^ ]+]] = getelementptr inbounds i8, ptr %[[CUR]], i64 8
42+
// CHECK-DAG: store ptr %[[NXT]], ptr %args
43+
va_arg(args, struct empty);
44+
45+
// CHECK: %[[CUR:[^ ]+]] = load ptr, ptr %args
46+
// CHECK-DAG: %[[NXT:[^ ]+]] = getelementptr inbounds i8, ptr %[[CUR]], i64 8
47+
// CHECK-DAG: store ptr %[[NXT]], ptr %args
48+
// CHECK-DAG: load i64, ptr %[[CUR]]
49+
ret = va_arg(args, long);
50+
va_end(args);
51+
return ret;
52+
}
53+
54+
// If the zero-sized struct is contained in a non-zero-sized struct,
55+
// though, it doesn't reserve any registers.
56+
struct emptymixed { struct empty a; long b; };
57+
struct emptyflex { unsigned count; struct empty data[10]; };
58+
59+
// CHECK-LABEL: define{{.*}} i64 @f_emptymixed(i64 %x.coerce)
60+
long f_emptymixed(struct emptymixed x) { return x.b; }
61+
62+
// CHECK-LABEL: define{{.*}} i64 @f_emptyflex(i64 %x.coerce, i64 noundef %y)
63+
long f_emptyflex(struct emptyflex x, long y) { return y; }
64+
2465
// Small structs are passed in registers.
2566
struct small {
2667
int *a, *b;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %clang_cc1 -triple sparcv9-unknown-unknown -emit-llvm %s -o - | FileCheck %s
2+
3+
class Empty {
4+
};
5+
6+
class Long : public Empty {
7+
public:
8+
long l;
9+
};
10+
11+
// CHECK: define{{.*}} i64 @_Z4foo15Empty(i64 %e.coerce)
12+
Empty foo1(Empty e) {
13+
return e;
14+
}
15+
16+
// CHECK: define{{.*}} %class.Long @_Z4foo24Long(i64 %l.coerce)
17+
Long foo2(Long l) {
18+
return l;
19+
}
20+
21+
// CHECK: define{{.*}} i64 @_Z4foo34Long(i64 %l.coerce)
22+
long foo3(Long l) {
23+
return l.l;
24+
}

0 commit comments

Comments
 (0)