-
Notifications
You must be signed in to change notification settings - Fork 14.5k
[ARM] Empty structs are 1-byte for C++ ABI #124762
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4d883f0
[ARM] Empty structs are 1-byte for C++ ABI
ostannard 441052f
Add the -fclang-abi-compat=20 option
ostannard f0eef1b
Allow reverting ABI change with -fclang-abi-compat=20
ostannard 9bcf7b2
Revert unneeded change
ostannard e396582
Add release note
ostannard 665877e
Assume this will be backported to llvm-19
ostannard 363adc6
Correct architecture in release notes
ostannard 8833aba
Merge remote-tracking branch 'origin/main' into empty-struct-aarch32
ostannard File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -328,6 +329,31 @@ 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; | ||
|
||
// Clang 19.0 and earlier always ignored empty struct arguments in C++ mode. | ||
if (getContext().getLangOpts().getClangABICompat() <= | ||
LangOptions::ClangABI::Ver19) | ||
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: | ||
|
@@ -366,9 +392,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)) | ||
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 | ||
|
@@ -588,7 +620,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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this change actually have any effect? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is needed for the |
||
return ABIArgInfo::getIgnore(); | ||
|
||
// Check for homogeneous aggregates with AAPCS-VFP. | ||
|
@@ -752,7 +785,9 @@ 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); | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// 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 armv7a-linux-gnueabi -emit-llvm -o - %s -fclang-abi-compat=19 | FileCheck %s --check-prefixes=CHECK,CXXCLANG19 | ||
// 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) | ||
// CXXCLANG19: define{{.*}} i32 @empty_arg(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() | ||
// CXXCLANG19: 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) | ||
// CXXCLANG19: 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) | ||
// CXXCLANG19: define{{.*}} i32 @sort_of_empty_arg(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() | ||
// CXXCLANG19: 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 | ||
// CXXCLANG19: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4 | ||
// CXXCLANG19-NOT: {{ getelementptr }} | ||
// 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 }} | ||
// CXXCLANG19: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4 | ||
// CXXCLANG19-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 }} | ||
// CXXCLANG19: %argp.next = getelementptr inbounds i8, ptr %argp.cur, i32 4 | ||
// CXXCLANG19-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; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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:
This should be equivalent because an empty struct in C is size zero anyway.
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.