rust-bindgen: Wrong ABI used for small C++ classes on Linux.

Input C/C++ Header

#include <cstdint>

struct Foo {
    size_t data;
};

struct Bar {
    size_t data;
    ~Bar() { data = 0; }
};

Foo MakeFoo(); // { return Foo(); }

Bar MakeBar(); // { return Bar(); }

Bindgen Invocation

bindgen::Builder::default()
    .clang_args(&["-x","c++", "-std=c++11"])
    .header("input.h")
    .generate()
    .unwrap()

Actual Results

#[repr(C)]
#[derive(Debug, Copy)]
pub struct Foo {
    pub data: usize,
}
impl Clone for Foo {
    fn clone(&self) -> Self { *self }
}
#[repr(C)]
#[derive(Debug)]
pub struct Bar {
    pub data: usize,
}
extern "C" {
    #[link_name = "_ZN3BarD1Ev"]
    pub fn Bar_Bar_destructor(this: *mut Bar);
}
impl Bar {
    #[inline]
    pub unsafe fn destruct(&mut self) { Bar_Bar_destructor(self) }
}
extern "C" {
    #[link_name = "_Z7MakeFoov"]
    pub fn MakeFoo() -> Foo;
}
extern "C" {
    #[link_name = "_Z7MakeBarv"]
    pub fn MakeBar() -> Bar;
}

Expected Results

Not sure…

Discussion

MakeFoo and MakeBar look very similar, however in C++ they use different ABIs, at least on Linux. Here’s a quote from System V AMD64 ABI spec (page 20):

If a C++ object has either a non-trivial copy constructor or a non-trivial destructor 11, it is passed by invisible reference (the object is replaced in the parameter list by a pointer that has class POINTER) 12

This means that while Foo get returned by-value in the rax register, Bar must be returned by-pointer to a caller allocated memory. To Rust compiler, however, these look identical, so it expects both to be returned in rax.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 32 (14 by maintainers)

Commits related to this issue

Most upvoted comments

Intel website moved the System V AMD64 ABI pdf so the link in the issue is broken – here is an updated link: https://software.intel.com/content/dam/develop/external/us/en/documents/mpx-linux64-abi.pdf

Since we already link to clang, another solution arises: generate C++ code and have clang emit LLVM IR for it. Since rustc currently generates LLVM IR too, we can perform cross-language link-time optimization to remove all of the overhead.