Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 37 additions & 6 deletions clang/lib/CodeGen/Targets/ARM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class ARMABIInfo : public ABIInfo {
unsigned functionCallConv) const;
ABIArgInfo classifyHomogeneousAggregate(QualType Ty, const Type *Base,
uint64_t Members) const;
bool shouldIgnoreEmptyArg(QualType Ty) const;
ABIArgInfo coerceIllegalVector(QualType Ty) const;
bool isIllegalVectorType(QualType Ty) const;
bool containsAnyFP16Vectors(QualType Ty) const;
Expand Down Expand Up @@ -328,6 +329,26 @@ ABIArgInfo ARMABIInfo::classifyHomogeneousAggregate(QualType Ty,
return ABIArgInfo::getDirect(nullptr, 0, nullptr, false, Align);
}

bool ARMABIInfo::shouldIgnoreEmptyArg(QualType Ty) const {
uint64_t Size = getContext().getTypeSize(Ty);
assert((isEmptyRecord(getContext(), Ty, true) || Size == 0) &&
"Arg is not empty");

// Empty records are ignored in C mode, and in C++ on WatchOS.
if (!getContext().getLangOpts().CPlusPlus ||
getABIKind() == ARMABIKind::AAPCS16_VFP)
return true;

// In C++ mode, arguments which have sizeof() == 0 are ignored. This is not a
// situation which is defined by any C++ standard or ABI, but this matches
// GCC's de facto ABI.
if (Size == 0)
return true;

// Otherwise, they are passed as if they have a size of 1 byte.
return false;
}

ABIArgInfo ARMABIInfo::classifyArgumentType(QualType Ty, bool isVariadic,
unsigned functionCallConv) const {
// 6.1.2.1 The following argument types are VFP CPRCs:
Expand Down Expand Up @@ -366,9 +387,15 @@ ABIArgInfo ARMABIInfo::classifyArgumentType(QualType Ty, bool isVariadic,
return getNaturalAlignIndirect(Ty, RAA == CGCXXABI::RAA_DirectInMemory);
}

// Ignore empty records.
if (isEmptyRecord(getContext(), Ty, true))
return ABIArgInfo::getIgnore();
// Empty records are either ignored completely or passed as if they were a
// 1-byte object, depending on the ABI and language standard.
if (isEmptyRecord(getContext(), Ty, true) ||
getContext().getTypeSize(Ty) == 0) {
if (shouldIgnoreEmptyArg(Ty))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I'd prefer to write this something like:

if (size == zero)
  return getIgnore();
if (isempty() && isWatchOS())
  return getIgnore();

This should be equivalent because an empty struct in C is size zero anyway.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, hmm... do we want to add fclang-abi-compat support for this change, or do we think it's too niche to matter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wasn't aware of the -fclang-abi-compat option, but I think that would be worth adding for this. I'll also add a release note as suggested in #124760.

return ABIArgInfo::getIgnore();
else
return ABIArgInfo::getDirect(llvm::Type::getInt8Ty(getVMContext()));
}

if (IsAAPCS_VFP) {
// Homogeneous Aggregates need to be expanded when we can fit the aggregate
Expand Down Expand Up @@ -588,7 +615,8 @@ ABIArgInfo ARMABIInfo::classifyReturnType(QualType RetTy, bool isVariadic,

// Otherwise this is an AAPCS variant.

if (isEmptyRecord(getContext(), RetTy, true))
if (isEmptyRecord(getContext(), RetTy, true) ||
getContext().getTypeSize(RetTy) == 0)
Comment on lines +623 to +624
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does this change actually have any effect?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is needed for the SortOfEmpty case, which isn't considered empty, but does have size 0.

return ABIArgInfo::getIgnore();

// Check for homogeneous aggregates with AAPCS-VFP.
Expand Down Expand Up @@ -752,10 +780,13 @@ RValue ARMABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
CharUnits SlotSize = CharUnits::fromQuantity(4);

// Empty records are ignored for parameter passing purposes.
if (isEmptyRecord(getContext(), Ty, true))
uint64_t Size = getContext().getTypeSize(Ty);
bool IsEmpty = isEmptyRecord(getContext(), Ty, true);
if ((IsEmpty || Size == 0) && shouldIgnoreEmptyArg(Ty))
return Slot.asRValue();

CharUnits TySize = getContext().getTypeSizeInChars(Ty);
CharUnits TySize =
std::max(getContext().getTypeSizeInChars(Ty), CharUnits::fromQuantity(1));
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we get here, we know the size isn't zero, I think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was left over from a previous version, I'll remove it.

CharUnits TyAlignForABI = getContext().getTypeUnadjustedAlignInChars(Ty);

// Use indirect if size of the illegal vector is bigger than 16 bytes.
Expand Down
119 changes: 119 additions & 0 deletions clang/test/CodeGen/arm-empty-args.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// RUN: %clang_cc1 -triple armv7a-linux-gnueabi -emit-llvm -o - -x c %s | FileCheck %s --check-prefixes=CHECK,C
// RUN: %clang_cc1 -triple armv7a-linux-gnueabi -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CXX
// RUN: %clang_cc1 -triple thumbv7k-apple-watchos2.0 -target-abi aapcs16 -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,WATCHOS

// Empty structs are ignored for PCS purposes on WatchOS and in C mode
// elsewhere. In C++ mode they consume a register slot though. Functions are
// slightly bigger than minimal to make confirmation against actual GCC
// behaviour easier.

#if __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif

struct Empty {};

// C: define{{.*}} i32 @empty_arg(i32 noundef %a)
// CXX: define{{.*}} i32 @empty_arg(i8 %e.coerce, i32 noundef %a)
// WATCHOS: define{{.*}} i32 @empty_arg(i32 noundef %a)
EXTERNC int empty_arg(struct Empty e, int a) {
return a;
}

// C: define{{.*}} void @empty_ret()
// CXX: define{{.*}} void @empty_ret()
// WATCHOS: define{{.*}} void @empty_ret()
EXTERNC struct Empty empty_ret(void) {
struct Empty e;
return e;
}

// However, what counts as "empty" is a baroque mess. This is super-empty, it's
// ignored even in C++ mode. It also has sizeof == 0, violating C++, but that's
// legacy for you:

struct SuperEmpty {
int arr[0];
};

// C: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
// CXX: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
// WATCHOS: define{{.*}} i32 @super_empty_arg(i32 noundef %a)
EXTERNC int super_empty_arg(struct SuperEmpty e, int a) {
return a;
}

struct SortOfEmpty {
struct SuperEmpty e;
};

// C: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
// CXX: define{{.*}} i32 @sort_of_empty_arg(i8 %e.coerce, i32 noundef %a)
// WATCHOS: define{{.*}} i32 @sort_of_empty_arg(i32 noundef %a)
EXTERNC int sort_of_empty_arg(struct Empty e, int a) {
return a;
}

// C: define{{.*}} void @sort_of_empty_ret()
// CXX: define{{.*}} void @sort_of_empty_ret()
// WATCHOS: define{{.*}} void @sort_of_empty_ret()
EXTERNC struct SortOfEmpty sort_of_empty_ret(void) {
struct SortOfEmpty e;
return e;
}

#include <stdarg.h>

// va_arg matches the above rules, consuming an incoming argument in cases
// where one would be passed, and not doing so when the argument should be
// ignored.

EXTERNC int empty_arg_variadic(int a, ...) {
// CHECK-LABEL: @empty_arg_variadic(
// C: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// C-NOT: {{ getelementptr }}
// CXX: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// CXX: %argp.next2 = getelementptr inbounds i8, ptr %argp.cur1, i32 4
// WATCHOS: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// WATCHOS-NOT: {{ getelementptr }}
va_list vl;
va_start(vl, a);
struct Empty b = va_arg(vl, struct Empty);
int c = va_arg(vl, int);
va_end(vl);
return c;
}

EXTERNC int super_empty_arg_variadic(int a, ...) {
// CHECK-LABEL: @super_empty_arg_variadic(
// C: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// C-NOT: {{ getelementptr }}
// CXX: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// CXX-NOT: {{ getelementptr }}
// WATCHOS: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// WATCHOS-NOT: {{ getelementptr }}
va_list vl;
va_start(vl, a);
struct SuperEmpty b = va_arg(vl, struct SuperEmpty);
int c = va_arg(vl, int);
va_end(vl);
return c;
}

EXTERNC int sort_of_empty_arg_variadic(int a, ...) {
// CHECK-LABEL: @sort_of_empty_arg_variadic(
// C: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// C-NOT: {{ getelementptr }}
// CXX: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// CXX-NOT: {{ getelementptr }}
// WATCHOS: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4
// WATCHOS-NOT: {{ getelementptr }}
va_list vl;
va_start(vl, a);
struct SortOfEmpty b = va_arg(vl, struct SortOfEmpty);
int c = va_arg(vl, int);
va_end(vl);
return c;
}