Given the following setup:
test.rs:
#![feature(i128_type)]
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Debug)]
struct Foo {
a: i128,
b: i8,
c: u16,
}
#[link(name = "test", kind = "static")]
extern "C" {
fn foo(f: Foo) -> Foo;
}
fn main() {
unsafe {
let a = Foo { a: 1, b: 2, c: 3 };
let b = foo(a);
assert_eq!(a, b);
}
}
test.c:
#include <stdint.h>
struct Foo {
__int128 a;
int8_t b;
uint16_t c;
};
struct Foo foo(struct Foo foo) {
return foo;
}
gcc version 6.2.0, rust of current master (ac5cd3bd43b9dbe681417e4), and a 64 bit gnu/linux platform, main will panic:
thread 'main' panicked at 'assertion failed: `(left == right)` (left: `Foo { a: 139755467620616, b: 2, c: 3 }`, right: `Foo { a: 1, b: 2, c: 3 }`)'
cc #35118 (tracking issue)
cc @nagisa , me
139755467620616 looks suspiciously like an address. My suspicion is that there鈥檚 a level of indirection missing here?
The value is passed correctly to the C side, but after return memcpy value becomes wrong.
These are the signatures at the LLVM level:
; C
define void @foo(%struct.Foo* noalias nocapture sret, %struct.Foo* byval align 16) local_unnamed_addr #0;
; rust
declare void @foo(%Foo* noalias nocapture sret dereferenceable(24), %Foo* byval noalias nocapture dereferenceable(24)) unnamed_addr #2;
Concerns:
alloca the value with alignment of 8 (still fails even though the pointer is 16-aligned).type { i128, i8, i16, [12 x i8] }, Rust = type { i128, i8, i16 }), not sure how much it matters.What happens here is that the memcpy on C side overwrites the padding that does not exist on the rust side.
That鈥檚 how rust instructs LLVM to allocate stack:
%sret = %Foo, aligned 8 ; sret slot (is 24 bytes)
%arg = %Foo, aligned 8 ; argument slot (is 24 bytes)
%somepointer
Then C side does memcpy(%sret, %arg, 32 bytes) and overwrites some of the space occupied by %arg with %somepointer.
The fix here is to calculate alignment correctly, which I seem to have messed up. Ugh.
#[repr(C)]
struct Foo {
a: i128,
b: i8,
c: u16,
}
fn main() {
assert_eq!(16, ::std::mem::align_of::<Foo>()); // fails currently
}
clang has this code snippet which ignores the data-layout of the target (probably to match sysv64 ABI. Also potentially has buggy interactions with the LLVM expectations?).
We can鈥檛 quite do that because of various assertions that the sizes and alignments match the data-layout. Any suggestions @eddyb? Is changing data-layout string an option here?
EDIT: Oh %Foo* byval noalias nocapture dereferenceable(24)) just needs an alignment attr.
We can add extra alignment attributes through cabi_* without a lot of work, if that's needed.
https://github.com/rust-lang/rust/pull/38870 fixes the by-value case, but
struct Foo foo(struct Foo *a) {
return *a;
}
let b;
let a = Foo { a: 1, b: 2, c: 3 };
b = foo(&a);
assert_eq!(a, b);
will still sigsegv(!) for the same reasons if both rustc and C sides are compiled with opts on my machine.
LLVM patch has landed as https://github.com/llvm-mirror/llvm/commit/d11a6cc2e7e67840fd733ef7ef22f1f488072b27, however only for x86_64 and PPC64, so we might eventually run into a similar issue on more obscure platforms later.
Not sure if the patch is important enough to backport into our branch as opposed to just waiting for LLVM 4.0 to get released.
EDIT: it got backed out; clang sucks.
Most helpful comment
LLVM patch has landed as https://github.com/llvm-mirror/llvm/commit/d11a6cc2e7e67840fd733ef7ef22f1f488072b27, however only for x86_64 and PPC64, so we might eventually run into a similar issue on more obscure platforms later.
Not sure if the patch is important enough to backport into our branch as opposed to just waiting for LLVM 4.0 to get released.
EDIT: it got backed out; clang sucks.