-
-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Distinguish repr(C) ZSTs from others in ABI computation
#156112
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,33 +21,129 @@ | |
| extern crate minicore; | ||
| use minicore::*; | ||
|
|
||
| // Make sure the argument is always passed when explicitly requesting a Windows ABI. | ||
| #[repr(C)] | ||
| struct CMaybeZst; | ||
|
|
||
| #[repr(transparent)] | ||
| struct CMaybeZst2((), CMaybeZst, ()); | ||
|
|
||
| // Make sure the argument is always passed when explicitly requesting a Windows ABI, | ||
| // and it is `repr(C)` - but not if it is `repr(Rust)`. | ||
| // Our goal here is to match clang: <https://clang.godbolt.org/z/Wr4jMWq3P>. | ||
|
|
||
| // CHECK: define win64cc void @pass_zst_win64(ptr {{[^,]*}}) | ||
| // CHECK: define win64cc void @pass_rust_zst_win64() | ||
| #[no_mangle] | ||
| extern "win64" fn pass_rust_zst_win64(_: ()) {} | ||
|
Member
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 will be interesting... I am not sure how to make sense of this ABI on non-MSVC targets. Given that the type layout itself is "wrong", how could one possibly actually call such a function...?
Contributor
Author
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. You could call such a function from C with a signature of
Member
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. Currently, you could not. We don't allow any arguments to be omitted at the moment.
Member
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. Also note that I am talking about the "win64" ABI in general, not just this particular function -- I should have been more clear about that. A "win64" function call on a windows-gnu or Linux target that involves a zero-field struct (or struct whose only field is a 0-element array) might just produce nonsense, right? We'd need
Member
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. Reading through prior discussion in rust-lang/unsafe-code-guidelines#552, I still agree with what I wrote there:
We should emit FFI warnings (and, eventually, errors) for code that uses an ABI outside of what the vendor has (explicitly or implicitly) defined. We should not take what GCC/clang do as gospel for Windows targets. For this concrete question that means we should disallow passing ZST. And it seems like windows-gnu targets use the same ABI so this applies to them as well. Is that too radical or is it something we could get away with?
Member
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. But GCC and clang don't...
Contributor
Author
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. …with consistent layout between them. So it's not a problem. As long as two different compilers don't give a different ABI to identical type declarations, we are good
Member
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. On a windows target, MSVC might start giving a different answer eventually. Seems bad for us to draw ourselves into that corner.
Contributor
Author
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. That's possible, true. But given the dubious practical utility of such code, I don't think it's likely. And it's really GCC and Clang that drew themselves into that corner;
Member
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.
It would be us needlessly following them into that corner. |
||
|
|
||
| // CHECK: define win64cc void @pass_c_maybezst_win64(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "win64" fn pass_c_maybezst_win64(_: CMaybeZst) {} | ||
|
|
||
| // CHECK: define win64cc void @pass_c_maybezst_2_win64(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "win64" fn pass_c_maybezst_2_win64(_: CMaybeZst2) {} | ||
|
|
||
| // CHECK: define x86_vectorcallcc void @pass_rust_zst_vectorcall() | ||
| #[no_mangle] | ||
| extern "vectorcall" fn pass_rust_zst_vectorcall(_: ()) {} | ||
|
|
||
| // CHECK: define x86_vectorcallcc void @pass_c_maybezst_vectorcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "win64" fn pass_zst_win64(_: ()) {} | ||
| extern "vectorcall" fn pass_c_maybezst_vectorcall(_: CMaybeZst) {} | ||
|
|
||
| // CHECK: define x86_vectorcallcc void @pass_zst_vectorcall(ptr {{[^,]*}}) | ||
| // CHECK: define x86_vectorcallcc void @pass_c_maybezst_2_vectorcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "vectorcall" fn pass_zst_vectorcall(_: ()) {} | ||
| extern "vectorcall" fn pass_c_maybezst_2_vectorcall(_: CMaybeZst2) {} | ||
|
|
||
| // windows-gnu: define void @pass_zst_fastcall(ptr {{[^,]*}}) | ||
| // windows-msvc: define void @pass_zst_fastcall(ptr {{[^,]*}}) | ||
| // windows-gnu: define void @pass_rust_zst_fastcall() | ||
| // windows-msvc: define void @pass_rust_zst_fastcall() | ||
| #[no_mangle] | ||
| #[cfg(windows)] // "fastcall" is not valid on 64bit Linux | ||
| extern "fastcall" fn pass_zst_fastcall(_: ()) {} | ||
| extern "fastcall" fn pass_rust_zst_fastcall(_: ()) {} | ||
|
|
||
| // windows-gnu: define void @pass_c_maybezst_fastcall(ptr {{[^,]*}}) | ||
| // windows-msvc: define void @pass_c_maybezst_fastcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| #[cfg(windows)] // "fastcall" is not valid on 64bit Linux | ||
| extern "fastcall" fn pass_c_maybezst_fastcall(_: CMaybeZst) {} | ||
|
|
||
| // windows-gnu: define void @pass_c_maybezst_2_fastcall(ptr {{[^,]*}}) | ||
| // windows-msvc: define void @pass_c_maybezst_2_fastcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| #[cfg(windows)] // "fastcall" is not valid on 64bit Linux | ||
| extern "fastcall" fn pass_c_maybezst_2_fastcall(_: CMaybeZst2) {} | ||
|
|
||
| // The sysv64 ABI ignores ZST. | ||
|
|
||
| // CHECK: define x86_64_sysvcc void @pass_zst_sysv64() | ||
| // CHECK: define x86_64_sysvcc void @pass_rust_zst_sysv64() | ||
| #[no_mangle] | ||
| extern "sysv64" fn pass_rust_zst_sysv64(_: ()) {} | ||
|
|
||
| // CHECK: define x86_64_sysvcc void @pass_c_maybezst_sysv64() | ||
| #[no_mangle] | ||
| extern "sysv64" fn pass_c_maybezst_sysv64(_: CMaybeZst) {} | ||
|
|
||
| // CHECK: define x86_64_sysvcc void @pass_c_maybezst_2_sysv64() | ||
| #[no_mangle] | ||
| extern "sysv64" fn pass_zst_sysv64(_: ()) {} | ||
| extern "sysv64" fn pass_c_maybezst_2_sysv64(_: CMaybeZst2) {} | ||
|
|
||
| // For `extern "C"` functions, ZST are ignored on Linux put passed on Windows. | ||
|
|
||
| // linux: define void @pass_zst_c() | ||
| // windows-msvc: define void @pass_zst_c(ptr {{[^,]*}}) | ||
| // windows-gnu: define void @pass_zst_c(ptr {{[^,]*}}) | ||
| // linux: define void @pass_rust_zst_c() | ||
| // windows-msvc: define void @pass_rust_zst_c() | ||
| // windows-gnu: define void @pass_rust_zst_c() | ||
| #[no_mangle] | ||
| extern "C" fn pass_rust_zst_c(_: ()) {} | ||
|
|
||
| // linux: define void @pass_c_maybezst_c() | ||
| // windows-msvc: define void @pass_c_maybezst_c(ptr {{[^,]*}}) | ||
| // windows-gnu: define void @pass_c_maybezst_c(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "C" fn pass_c_maybezst_c(_: CMaybeZst) {} | ||
|
|
||
| // linux: define void @pass_c_maybezst_2_c() | ||
| // windows-msvc: define void @pass_c_maybezst_2_c(ptr {{[^,]*}}) | ||
| // windows-gnu: define void @pass_c_maybezst_2_c(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "C" fn pass_c_maybezst_2_c(_: CMaybeZst2) {} | ||
|
|
||
| // Now check `repr(C)` return types. | ||
| // Again, we seek to match clang: <https://clang.godbolt.org/z/hKv74ThnE> | ||
|
|
||
| // CHECK: define win64cc void @ret_c_maybezst_win64(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "win64" fn ret_c_maybezst_win64() -> CMaybeZst { | ||
| CMaybeZst | ||
| } | ||
|
|
||
| // CHECK: define x86_vectorcallcc void @ret_c_maybezst_vectorcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "vectorcall" fn ret_c_maybezst_vectorcall() -> CMaybeZst { | ||
| CMaybeZst | ||
| } | ||
|
|
||
| // windows-gnu: define void @ret_c_maybezst_fastcall(ptr {{[^,]*}}) | ||
| // windows-msvc: define void @ret_c_maybezst_fastcall(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| #[cfg(windows)] // "fastcall" is not valid on 64bit Linux | ||
| extern "fastcall" fn ret_c_maybezst_fastcall() -> CMaybeZst { | ||
| CMaybeZst | ||
| } | ||
|
|
||
| // The sysv64 ABI ignores ZST. | ||
|
|
||
| // CHECK: define x86_64_sysvcc void @ret_c_maybezst_sysv64() | ||
| #[no_mangle] | ||
| extern "sysv64" fn ret_c_maybezst_sysv64() -> CMaybeZst { | ||
| CMaybeZst | ||
| } | ||
|
|
||
| // For `extern "C"` functions, ZST are ignored on Linux put reted on Windows. | ||
|
|
||
| // linux: define void @ret_c_maybezst_c() | ||
| // windows-msvc: define void @ret_c_maybezst_c(ptr {{[^,]*}}) | ||
| // windows-gnu: define void @ret_c_maybezst_c(ptr {{[^,]*}}) | ||
| #[no_mangle] | ||
| extern "C" fn pass_zst_c(_: ()) {} | ||
| extern "C" fn ret_c_maybezst_c() -> CMaybeZst { | ||
| CMaybeZst | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
This could be:
Or even:
But I don't want this PR to get bogged down in controversy, so let's leave it alone for now (unless there is lang team consensus).
If we do change this, the documentation in
library/core/src/primitive_docs.rswill need to change as well.View changes since the review
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.
It's unclear to me what the consequence of either of the tree possible options here is.
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.
false: all ZST arrays have trivial ABI for all element typeselement.repr_c: ZST arrays are passed like arepr(C)ZST if the element type isrepr(C)true: ZST arrays are passed like arepr(C)ZST for all element typesThere 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.
This codepath affects all arrays, not just ZST, right? I guess your point is that for non-ZST
repr_c(currently) doesn't make a difference?That's what I expected based on the description.
... now I can't find it any more, did you change the doc comment for this field? Propagating this through arrays made sense to me at first sight but I did not think deeply about it.
Also, is it correct to say "passed like a repr(C) ZST"? For all I know, C might have different zero-sized types that are passed differently.
Uh oh!
There was an error while loading. Please reload this page.
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.
Yes.
"Passed like a repr(C) ZST with the same alignment as the element type".
Standard C doesn't have zero-sized types at all. But yes, widespread compiler extensions do make this happen: https://godbolt.org/z/1xWqqj3qv
Currently, Rust can't match the flexible-array-member ABI. This PR leaves that unchanged.
Uh oh!
There was an error while loading. Please reload this page.
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.
Ah, it looks like flexible array members having a different ABI is just a quirk of Clang, not replicated by GCC: https://godbolt.org/z/eWKazGxxW
Filed an issue with LLVM: llvm/llvm-project#195572
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.
This makes me question again whether we should support any kind of ZST in our C interop, given that those types do not exist in standard C, and C compilers cannot even agree among themselves...
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.
These types are used in the Linux kernel, including in the UAPI, so we don't have much of a choice. That being said, all practical usage is behind a pointer.
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.
Then we could maybe get away with only giving them a defined layout, not a defined ABI.
(That does not avoid the win64 problem though.)