-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
SystemV ABI Mismatch on x86 with a repr(C) enum
for extern "C"
/FFI functions.
#68190
Comments
cc @eddyb |
I've reproduced this as far back as 1.22 (I have not tried earlier versions), but I believe it was only supposed to be valid since 1.24. In 1.22, the |
I think there may be an issue in general with passing non-C-like enums by value. I found this issue when digging into my own variation on the problem. On macOS (w/1.42.0-nightly, and clang 11 as shipped by Apple), I have code which either segfaults, or fails some assertions I've set up when passing a non-C-like enum by value to C++. I've built a minimal repro here, it has the details necessary to reproduce. In short though, the example contains code that exercises two variations on a simple non-C-like enum. One variation uses What I'm seeing is that when passed by value (to C++, haven't tried the other way yet), both variations of the enum end up as garbage values, which I'm assuming is just uninitialized stack memory, since it doesn't segfault. When passed by reference, everything works fine. So the memory layout matches up, but something goes pear shaped when passing by value. That seems to line up with what is seen in this issue as well, so I think it is likely the same underlying problem. Hope having another repro case helps! |
Depending on what solution we adopt here, T-lang may need to be involved. Adding tag. |
discussed at T-compiler meeting. P-high. (And decided its not relevant to T-lang.) |
assigning to @eddyb to resolve or to mentor resolution |
In an embarrassing turn of events, we don't handle
This was originally correct, but didn't get adjusted when we spec'd non-C-like |
The commits on @sw17ch's branch, sw17ch:broken-codegen-with-non-c-like-enum, add some tests around this and currently segfault or assert with incorrect results. Cherry picking these commits over there ought to do the trick. |
@eddyb I'll work on a test suite that fails without #68443 based on the tests I've already written, while also trying to introduce tests for the case brought up by @bitwalker. |
I have confirmed the patches from #68443 allows the two tests I have to pass (after confirming they still fail on |
One calls into C functions passing non-c-like enumerations by value. The other calls into C expecting non-C-like enumerations as returns. These test cases are based on the tests provided by @bitwalker on issue rust-lang#68190. The original tests were provided at: https://github.com/bitwalker/rust_non_c_like_enums_issue/tree/2688d5c672bd4e289085fcdf1c6110e99e7e8ab1
I have added tests based on the tests provided by @bitwalker above to my branch. I confirmed that they (and my original tests) fail. After that, I cherry-picked the commits from #68443 and confirmed that it fixes all the tests cases. My branch is at: https://github.com/sw17ch/rust/tree/broken-codegen-with-non-c-like-enum The relevant commits are: 7c061c2..cc2f018 If someone checks out 650e5f8 from my branch, and runs the 4 tests, they can expect 4 failures. After moving forward to cc2f018, all 4 tests should pass. Here are the test commands I used:
@eddyb thank you very much for the PR, it appears to fix the issue. |
Just to confirm something, passing and returning a struct containing a u8 and u64 works, and it's just the generated struct of a |
Correct. Structs work as expected. An enumeration with the same layout fails to conform to the SysV ABI. However, the primitive type used in |
@joshtriplett The way we handle layout today makes |
One calls into C functions passing non-c-like enumerations by value. The other calls into C expecting non-C-like enumerations as returns. These test cases are based on the tests provided by @bitwalker on issue rust-lang#68190. The original tests were provided at: https://github.com/bitwalker/rust_non_c_like_enums_issue/tree/2688d5c672bd4e289085fcdf1c6110e99e7e8ab1
The Problem
Using the FFI, without
unsafe
, it's possible to get a segfault or incorrect results from defined behaviors (see rust-lang/rfcs#2195 and #46123) around non-C-like enumerations. I have only tested this on Linux with anx86_64
processor. I have reproduced these problems with both of gcc9 and clang8. The problems are exhibited both on a recent rustcmaster
and on rustc stable 1.40.0.I've written two tests against the
rust-lang/rust
repository that demonstrate the problems described later in this issue. They are here:Side note: the language reference states that non-C-like enumerations have unspecified layout, but I believe that this is no longer true after merging #46123 when specifying a
repr
for the enumeration.Reproducing a Segmentation Fault When Returning by Value
Given an enumeration like this:
And an FFI function like this:
And an invocation from C like this:
The compiled C file linked against the Rust static library cause a segmentation fault:
Reproducing an Assertion Failure When Passing by Value
Given the same Rust
OptionLikeSome
type from above, and the same C type representation, define a Rust function that adds two options like this:Then define a C function that exercises it like this:
When running this C code, we get an unexpected result:
Other Notes
Reproduction is not limited to the exact shapes above. For example, the primitive type used in the
repr
does not seem to affect outcomes.#[repr(C,u32)]
and#[repr(C,u64)]
both exhibit the bugs.Some Analysis
I believe that Rust is internally consistent about how it passes these enumerations, but it seems to be in violation of the SystemV guidance on how to pass parameters. For example, calling the above extern functions from Rust does not exhibit the invalid behavior.
Furthermore, for enumerations with larger representations, the bugs are also not present. For example, using two
u64
values in theOptionLikeSome
definition prevents the crash or assertion failure from surfacing.SystemV Requirements
I received an enormous amount of help from @iximeow producing the following explanation.
What appears to be occurring is that
rustc
expects the caller to allocate space on the caller's stack for the return value, and then expects the caller to pass a pointer to that location in a register. gcc and clang both expect to pass smaller structures as registers. What's also interesting is that rustc does the "right thing" for structs that should have an identical layout.Here's a comparison of the assembly generated for structs and enumerations that should have extremely similar layout: https://godbolt.org/z/Mo7cJ6. Notice how the initialization of an enumeration is being done on the caller's stack while the initialization of the struct is done entirely in registers.
Here's very similar code, but in C: https://godbolt.org/z/CCxigj. Notice that neither of the C functions use the stack for initialization.
@iximeow and I believe that the proper handling for the enumeration according to SystemV can be described as follows:
{ u8, u64 }
When passing this type as an argument:
%rdi
,%rsi
,%rdx
,%rcx
,%r8
and%r9
is usedWhen returning this type:
%rax
,%rdx
is used.Summary
Something seems to treat enumerations differently from similarly laid out structs, and treats the enumerations incorrectly when passing them across SystemV ABI boundaries.
The text was updated successfully, but these errors were encountered: