rust-diplomat / diplomat

Experimental Rust tool for generating FFI definitions allowing many other languages to call Rust code
https://rust-diplomat.github.io/book/
Other
512 stars 48 forks source link

Add Padding for _intoFFI #643

Closed ambiguousname closed 1 month ago

ambiguousname commented 1 month ago

Fixes #642

Manishearth commented 1 month ago

This is going to complicate Option code unfortunately.

Manishearth commented 1 month ago

I'm not convinced this is correct

#[repr(C)]
#[derive(Debug)]
pub struct HasPadding {
    first: u8,
    second: u32,
}

extern "C" {
    fn diplomat_console_log_js(ptr: *const u8, len: usize);
}

fn log(x: &str) {

        unsafe { diplomat_console_log_js(x.as_bytes() as *const [u8] as  *const u8, x.len()) };
}
#[no_mangle]
pub fn myfn(x: HasPadding) {
    log(&format!("{:?}", x))
}

This struct has padding, however myfn needs to be called as myfn(first, second), with no padding bytes.

Manishearth commented 1 month ago

I'm fine with forbidding 64-bti types in structs for now

Manishearth commented 1 month ago

Interesting. When I replace u64 with u32 in the test, it does need padding.

This is because LLVM for this struct passes it down as a struct

%"structs::ffi::MyStruct" = type { i8, i8, i8, [1 x i8], i32, i32, i32, i32 }

define dso_local void @MyStruct_assert_value(%"structs::ffi::MyStruct" %0) unnamed_addr #0 !dbg !25583 {
...

whereas for the function above it gets passed in as two parameters, needing no padding

```llvm
define dso_local void @foo(i8 %x.0, i32 %x.1) unnamed_addr #0 {
Manishearth commented 1 month ago

Okay, this is interesting:

#[repr(C)]
pub struct HasPadding {
    first: u8,
    second: u32,
}

#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct MyStruct {
    a: u8,
    b: bool,
    c: u8,
    d: u32,
    e: i32,
}

#[no_mangle]
pub extern "C" fn foo(x: HasPadding) {}
#[no_mangle]
pub extern "C" fn bar(x: MyStruct) {}

produces

%MyStruct = type { i8, i8, i8, [1 x i8], i32, i32 }

; Function Attrs: nounwind
define dso_local void @foo(i8 %x.0, i32 %x.1) unnamed_addr #0 {
start:
  ret void
}

; Function Attrs: nounwind
define dso_local void @bar(%MyStruct %0) unnamed_addr #0 {
start:
  %x = alloca [12 x i8], align 4
  store %MyStruct %0, ptr %x, align 4
  ret void
}

So what's the threshold for when it decides to introduce padding?

Manishearth commented 1 month ago

Adding a second u32 field to HasPadding promotes it to a struct with padding.

Manishearth commented 1 month ago

Trying to see if there's some documentation on this in https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md