-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Cannot pass small struct by value across FFI on linux-gnu #59187
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
Comments
This looks supported by the ABI: Section 3.2.3, Classification 5. (c) If the size of the aggregate exceeds two eightbytes and the first eightbyte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory. Rust implements this condition here: rust/src/librustc_target/abi/call/x86_64.rs Lines 94 to 97 in 7a4df3b
Your rust/src/librustc_target/abi/call/x86_64.rs Lines 64 to 65 in 7a4df3b
I suspect |
AFAICS none of the other targets check |
I think I ran into the same issue today. Here is a minimal snippet: #[repr(C)]
#[derive(Clone, Copy)]
pub union MyResult {
ok: MyOk,
err: MyErr,
}
#[repr(usize)]
#[derive(Clone, Copy)]
enum MyResultTag { Ok, Err }
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MyOk(MyResultTag, usize);
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MyErr(MyResultTag, isize);
#[repr(usize)]
#[derive(Clone, Copy)]
pub enum MyResult2 {
Ok(usize),
Err(isize),
}
pub fn size() -> usize {
std::mem::size_of::<MyResult>()
}
pub fn size2() -> usize {
std::mem::size_of::<MyResult2>()
}
extern {
fn t(_: MyResult);
fn t2(_: MyResult2);
}
pub fn try() {
unsafe { t(MyResult{ok:MyOk(MyResultTag::Ok, 1)}) };
}
pub fn try2() {
unsafe { t2(MyResult2::Ok(1)) };
} According to https://github.com/rust-lang/rfcs/blob/master/text/2195-really-tagged-unions.md, they should be identical for layout and FFI purposes. However Rust is trying to pass |
It also looks like any tagged argument, will be passed on the stack, even if there is only one tag (which could be optimized away) and the contents are only one byte Relevant godbolt: https://www.godbolt.org/z/xjTrXA |
@rustbot label A-ffi |
I am creating an FFI function that takes a tagged union as a parameter. It corresponds to a Rust enum with variant. The tagged union definition for the C++ code was generated with
cbindgen
. The problem is that the calling convention between Rust'sextern "C"
and the actual C calling convention aren't matching up, so it gets faulty values for the argument.This looks like it's similar to #5744.
I tried this code:
Rust:
bindings.h:
C++:
I expected to see this happen:
Instead, this happened:
It prints nothing out because Rust believes that the struct was placed in the argument build area of the stack, just above the return address. In reality, since this is a small struct, it was placed in the second and third registers
%rsi
%rdx
.According to this interpretation of the System V AMD64 ABI, structs between 2-4 words are passed sequentially through registers. Although, according to some experimentation, it looks like any struct over 2 words is also passed on the stack.
Looking at the assembly, the first SLCArgs was initialized on the stack in the argument build area just by chance, so the values that Rust reads when it looks for the struct is
Add("top text")
. However, this happens every time we callStringListContainer_do
, so we never print anything out, just keep adding the first string.However, this does work on Windows-MSVC and on Linux-GNU but only if you mark StringListContainer_do as
extern "Rust"
. Justextern
andextern "C"
will not work.The repo for this code is here, if that helps
Meta
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: