This is an archive of the discontinued LLVM Phabricator instance.

[clang][LoongArch] Fix the calling convention for empty struct in C++ mode
AbandonedPublic

Authored by wangleiat on May 23 2023, 11:47 PM.

Details

Summary

Prior to this patch, paramter or return value whose type was a
structure containing empty structure members and fewer than three
floating-point members did not meet the calling convention.

With this patch, an empty struct will always be passed.

An empty struct type that is not non-trivial for the purposes of calls
will be treated as though it were the following C type:

struct {
  char c;
};

Diff Detail

Event Timeline

wangleiat created this revision.May 23 2023, 11:47 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 23 2023, 11:47 PM
wangleiat requested review of this revision.May 23 2023, 11:47 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 23 2023, 11:47 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
wangleiat edited the summary of this revision. (Show Details)May 23 2023, 11:49 PM
xry111 requested changes to this revision.May 23 2023, 11:52 PM

Blocking this as it's a deliberate decision made in D132285.

Is there any imperative reason we must really pass the empty struct? The C++ standard only treats struct {} as size 1 for the semantics of pointer comparison. While there is no pointers to registers, ignoring it in the register calling convention will make no harm.

And AFAIK it will be an undefined behavior attempting to (mis)use the padding space of/after the empty struct to pass any information.

This revision now requires changes to proceed.May 23 2023, 11:52 PM

Note that we are using the "de-facto" ABI throwing away this empty struct since the first release of GCC supporting LoongArch, and also Clang. So is there an imperative reason we must change it?

And IIUC we've agreed to revise the ABI since Apr 2022 for this, but unfortunately I've never got enough English skill to do the revision myself :(.

wangleiat added a comment.EditedMay 24 2023, 12:12 AM

Blocking this as it's a deliberate decision made in D132285.

Is there any imperative reason we must really pass the empty struct? The C++ standard only treats struct {} as size 1 for the semantics of pointer comparison. While there is no pointers to registers, ignoring it in the register calling convention will make no harm.

And AFAIK it will be an undefined behavior attempting to (mis)use the padding space of/after the empty struct to pass any information.

Our current modifications are closer to the description of Itanium C++ ABI, and we try to keep it consistent with the description of the calling convention under reasonable premise.

Blocking this as it's a deliberate decision made in D132285.

Is there any imperative reason we must really pass the empty struct? The C++ standard only treats struct {} as size 1 for the semantics of pointer comparison. While there is no pointers to registers, ignoring it in the register calling convention will make no harm.

And AFAIK it will be an undefined behavior attempting to (mis)use the padding space of/after the empty struct to pass any information.

Our current modifications are closer to the description of Itanium C++ ABI, and we try to keep it consistent with the description of the calling convention under reasonable premise.

Hmm, could you provide a link to the section saying this in the Itanium C++ ABI?

I see it has some words about passing or returning an empty class, but there seems no words about passing a class containing an empty class.

And for our own psABI, it's easier to simply reword it (making it similar to the RISC-V psABI about passing args with FPRs) instead of modifying both Clang and GCC (causing the code of Clang and GCC more nasty, and both compilers slower, and we'll need to add a -Wpsabi warning at least in GCC too). And it already needs a reword considering empty arrays and zero-width bit-fields anyway.

Blocking this as it's a deliberate decision made in D132285.

Is there any imperative reason we must really pass the empty struct? The C++ standard only treats struct {} as size 1 for the semantics of pointer comparison. While there is no pointers to registers, ignoring it in the register calling convention will make no harm.

And AFAIK it will be an undefined behavior attempting to (mis)use the padding space of/after the empty struct to pass any information.

Our current modifications are closer to the description of Itanium C++ ABI, and we try to keep it consistent with the description of the calling convention under reasonable premise.

Hmm, could you provide a link to the section saying this in the Itanium C++ ABI?

http://itanium-cxx-abi.github.io/cxx-abi/abi.html#empty-parameters
http://itanium-cxx-abi.github.io/cxx-abi/abi.html#emptty-return-values

I see it has some words about passing or returning an empty class, but there seems no words about passing a class containing an empty class.

It's possible that my understanding is incorrect. There is indeed no place to see how an empty class is passed, just like there is no documentation on how to pass class A { class B { char c;};};.

And for our own psABI, it's easier to simply reword it (making it similar to the RISC-V psABI about passing args with FPRs) instead of modifying both Clang and GCC (causing the code of Clang and GCC more nasty, and both compilers slower, and we'll need to add a -Wpsabi warning at least in GCC too). And it already needs a reword considering empty arrays and zero-width bit-fields anyway.

I'm sorry, I couldn't quite understand what you meant.

Blocking this as it's a deliberate decision made in D132285.

Is there any imperative reason we must really pass the empty struct? The C++ standard only treats struct {} as size 1 for the semantics of pointer comparison. While there is no pointers to registers, ignoring it in the register calling convention will make no harm.

And AFAIK it will be an undefined behavior attempting to (mis)use the padding space of/after the empty struct to pass any information.

Our current modifications are closer to the description of Itanium C++ ABI, and we try to keep it consistent with the description of the calling convention under reasonable premise.

Hmm, could you provide a link to the section saying this in the Itanium C++ ABI?

http://itanium-cxx-abi.github.io/cxx-abi/abi.html#empty-parameters
http://itanium-cxx-abi.github.io/cxx-abi/abi.html#emptty-return-values

I see it has some words about passing or returning an empty class, but there seems no words about passing a class containing an empty class.

It's possible that my understanding is incorrect. There is indeed no place to see how an empty class is passed, just like there is no documentation on how to pass class A { class B { char c;};};.

I think the paragraph means:

class Empty {};
int test(Empty empty, int a);

Then we should put a into a1, not a0. And we are indeed doing so.

And for our own psABI, it's easier to simply reword it (making it similar to the RISC-V psABI about passing args with FPRs) instead of modifying both Clang and GCC (causing the code of Clang and GCC more nasty, and both compilers slower, and we'll need to add a -Wpsabi warning at least in GCC too). And it already needs a reword considering empty arrays and zero-width bit-fields anyway.

I'm sorry, I couldn't quite understand what you meant.

I mean now GCC and Clang have the same behavior, so it's easier to just document the behavior in our psABI doc instead of making both Clang and GCC rigidly following the interpretation of psABI (which is currently unclear about zero-sized fields now) anyway.

And the current behavior of GCC and Clang treating a class containing two floating-point members and some empty fields is same as RISC-V, so it's highly unlikely we are violating the C++ standard or IA64 C++ ABI (or the entire RISC-V ecosystem would be violating them). The only issue is our psABI is unclear about empty fields, and the easiest way to solve the issue is revising the psABI (maybe, just "borrowing" some paragraphs from RISC-V psABI if there is no copyright issue).

wangleiat added a comment.EditedMay 24 2023, 1:42 AM

I think the paragraph means:

class Empty {};
int test(Empty empty, int a);

Then we should put a into a1, not a0. And we are indeed doing so.

yes. with this patch, a will be passed with a1 register.

I mean now GCC and Clang have the same behavior, so it's easier to just document the behavior in our psABI doc instead of making both Clang and GCC rigidly following the interpretation of psABI (which is currently unclear about zero-sized fields now) anyway.

And the current behavior of GCC and Clang treating a class containing two floating-point members and some empty fields is same as RISC-V, so it's highly unlikely we are violating the C++ standard or IA64 C++ ABI (or the entire RISC-V ecosystem would be violating them). The only issue is our psABI is unclear about empty fields, and the easiest way to solve the issue is revising the psABI (maybe, just "borrowing" some paragraphs from RISC-V psABI if there is no copyright issue).

If we want to ignore empty structures, it is not enough to only modify psABI, because the current implementations of gcc and clang do not ignore empty structures in all cases. For example:

struct { struct{}; int i; }; // in this case, the empty struct is not ignored.
struct { struct{}; float f; }; // ignored.

Whether to ignore empty structures or not can affect the testing of gdb. @seehearfeel knows more details.

I think the paragraph means:

class Empty {};
int test(Empty empty, int a);

Then we should put a into a1, not a0. And we are indeed doing so.

yes. with this patch, a will be passed with a1 register.

I mean now GCC and Clang have the same behavior, so it's easier to just document the behavior in our psABI doc instead of making both Clang and GCC rigidly following the interpretation of psABI (which is currently unclear about zero-sized fields now) anyway.

And the current behavior of GCC and Clang treating a class containing two floating-point members and some empty fields is same as RISC-V, so it's highly unlikely we are violating the C++ standard or IA64 C++ ABI (or the entire RISC-V ecosystem would be violating them). The only issue is our psABI is unclear about empty fields, and the easiest way to solve the issue is revising the psABI (maybe, just "borrowing" some paragraphs from RISC-V psABI if there is no copyright issue).

If we want to ignore empty structures, it is not enough to only modify psABI, because the current implementations of gcc and clang do not ignore empty structures in all cases. For example:

struct { struct{}; int i; }; // in this case, the empty struct is not ignored.
struct { struct{}; float f; }; // ignored.

This is the same behavior as RISC-V. While attempting to pass a struct through FPRs, the empty field is ignored. But if passing through FPR does not work and it's passed through GPRs, the empty fields are not ignored:

https://godbolt.org/z/T1PKoxbYM

Whether to ignore empty structures or not can affect the testing of gdb. @seehearfeel knows more details.

I guess we should be able to fix it for GDB because they must have fixed it for RISC-V already.

xry111 added a comment.EditedMay 24 2023, 2:01 AM

I think the paragraph means:

class Empty {};
int test(Empty empty, int a);

Then we should put a into a1, not a0. And we are indeed doing so.

yes. with this patch, a will be passed with a1 register.

I mean now GCC and Clang have the same behavior, so it's easier to just document the behavior in our psABI doc instead of making both Clang and GCC rigidly following the interpretation of psABI (which is currently unclear about zero-sized fields now) anyway.

And the current behavior of GCC and Clang treating a class containing two floating-point members and some empty fields is same as RISC-V, so it's highly unlikely we are violating the C++ standard or IA64 C++ ABI (or the entire RISC-V ecosystem would be violating them). The only issue is our psABI is unclear about empty fields, and the easiest way to solve the issue is revising the psABI (maybe, just "borrowing" some paragraphs from RISC-V psABI if there is no copyright issue).

If we want to ignore empty structures, it is not enough to only modify psABI, because the current implementations of gcc and clang do not ignore empty structures in all cases. For example:

struct { struct{}; int i; }; // in this case, the empty struct is not ignored.
struct { struct{}; float f; }; // ignored.

This is the same behavior as RISC-V. While attempting to pass a struct through FPRs, the empty field is ignored. But if passing through FPR does not work and it's passed through GPRs, the empty fields are not ignored:

https://godbolt.org/z/T1PKoxbYM

Whether to ignore empty structures or not can affect the testing of gdb. @seehearfeel knows more details.

I guess we should be able to fix it for GDB because they must have fixed it for RISC-V already.

@seehearfeel: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=9f0272f8548164b024ff9fd151686b2b904a5d59

If we want to ignore empty structures, it is not enough to only modify psABI, because the current implementations of gcc and clang do not ignore empty structures in all cases. For example:

struct { struct{}; int i; }; // in this case, the empty struct is not ignored.
struct { struct{}; float f; }; // ignored.

This is the same behavior as RISC-V. While attempting to pass a struct through FPRs, the empty field is ignored. But if passing through FPR does not work and it's passed through GPRs, the empty fields are not ignored:

Yes, but our psABI still differs from RISC-V in the description of parameter passing, and it would be better to have consistent behavior regardless of whether there are floating points or not.

If we want to ignore empty structures, it is not enough to only modify psABI, because the current implementations of gcc and clang do not ignore empty structures in all cases. For example:

struct { struct{}; int i; }; // in this case, the empty struct is not ignored.
struct { struct{}; float f; }; // ignored.

This is the same behavior as RISC-V. While attempting to pass a struct through FPRs, the empty field is ignored. But if passing through FPR does not work and it's passed through GPRs, the empty fields are not ignored:

Yes, but our psABI still differs from RISC-V in the description of parameter passing, and it would be better to have consistent behavior regardless of whether there are floating points or not.

But it's easier to just modify the text of the ABI doc, in order to avoid an ABI incompatibility between different GCC of Clang versions (as GCC and Clang are only C++ compilers supporting LoongArch now).

xen0n added a comment.May 24 2023, 3:30 AM

FYI, in the matching GCC patch discussion it was suggested that such a treatment wouldn't be necessary in principle.

aaron.ballman added a subscriber: aaron.ballman.

Adding codegen code owners as reviewers.

If you are really determined to do this, then OK. I'm in a very bad
mood and I don't want to spend my mental strength on debating (esp. on a
corner case unlikely to affect "real" code) anymore.

But remember to add a entry in GCC 14 changes.html, and test this thing:

struct Empty {};

struct Something : Empty
{

double a, b;

};

If we are not careful enough we may introduce a ABI mismatch between -
std=c++14 and -std=c++17 here. See https://gcc.gnu.org/PR94383.

As far as I'm concerned as editor of the Itanium ABI, the ABI treatment of trivial-for-the-purposes-of-calls classes is purely a psABI matter, and the Itanium ABI's wording around empty classes is merely a suggestion if the psABI doesn't have more specific rules (because empty structs are normally invalid in C). Do what you think is best for your ABI.

wangleiat abandoned this revision.Jul 20 2023, 6:09 AM

We have decided to keep it unchanged. Thank you, everyone.