rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.37k stars 687 forks source link

bindgen generates empty vtables #478

Closed Jascha-N closed 4 years ago

Jascha-N commented 7 years ago

bindgen generates empty vtables when cross-compiling for ARM. I have made a reduced test-case, while leaving in the automatically generated Builder settings. clang is not able to find the right gcc toolchain so the script manually passes the system include directories (which might be the wrong way to go about?).

input.hpp:

class Foo
{
  public:
    virtual void bar() = 0;
};

Actual output.rs:

/* automatically generated by rust-bindgen */

#[repr(C)]
pub struct Foo__bindgen_vtable {
}
#[repr(C)]
#[derive(Debug, Copy)]
pub struct Foo {
    pub vtable_: *const Foo__bindgen_vtable,
}
#[test]
fn bindgen_test_layout_Foo() {
    assert_eq!(::core::mem::size_of::<Foo>() , 4usize);
    assert_eq!(::core::mem::align_of::<Foo>() , 4usize);
}
impl Clone for Foo {
    fn clone(&self) -> Self { *self }
}

Expected output.rs:

/* ... */

#[repr(C)]
pub struct Foo__bindgen_vtable {
    /* Something here? */
}

/* ... */

Builder settings (pretty-printed debug):

Builder {
    options: BindgenOptions {
        hidden_types: RegexSet {
            items: [],
            set: None
        },
        opaque_types: RegexSet {
            items: [],
            set: None
        },
        whitelisted_types: RegexSet {
            items: [],
            set: None
        },
        whitelisted_functions: RegexSet {
            items: [],
            set: None
        },
        whitelisted_vars: RegexSet {
            items: [],
            set: None
        },
        bitfield_enums: RegexSet {
            items: [],
            set: None
        },
        constified_enums: RegexSet {
            items: [],
            set: None
        },
        builtins: false,
        links: [],
        emit_ast: false,
        emit_ir: false,
        enable_cxx_namespaces: false,
        disable_name_namespacing: false,
        derive_debug: true,
        unstable_rust: true,
        use_core: true,
        ctypes_prefix: Some(
            "::c_types"
        ),
        namespaced_constants: true,
        msvc_mangling: false,
        convert_floats: true,
        raw_lines: [],
        clang_args: [
            "-target",
            "thumbv6m-none-eabi",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/include/c++/4.8.3",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/include/c++/4.8.3/arm-none-eabi",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/include/c++/4.8.3/backward",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/include",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/include-fixed",
            "-isystem",
            "c:\\users\\jascha\\appdata\\local\\arduino15\\packages\\arduino\\tools\\arm-none-eabi-gcc\\4.8.3-2014q1\\bin\\../lib/gcc/arm-none-eabi/4.8.3/../../../../arm-none-eabi/include",
            "-std=gnu++11",
            "-DF_CPU=48000000L",
            "-DARDUINO=10600",
            "-DARDUINO_SAMD_FEATHER_M0",
            "-DARDUINO_ARCH_SAMD",
            "-DARDUINO_SAMD_ZERO",
            "-D__SAMD21G18A__",
            "-DUSB_VID=0x239A",
            "-DUSB_PID=0x800B",
            "-DUSBCON",
            "-DUSB_MANUFACTURER=\"Adafruit\"",
            "-DUSB_PRODUCT=\"Feather M0\"",
            "-IC:\\Users\\Jascha\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\CMSIS\\4.0.0-atmel/CMSIS/Include/",
            "-IC:\\Users\\Jascha\\AppData\\Local\\Arduino15\\packages\\arduino\\tools\\CMSIS\\4.0.0-atmel/Device/ATMEL/",
            "-IC:\\Users\\Jascha\\AppData\\Local\\Arduino15\\packages\\adafruit\\hardware\\samd\\1.0.13\\cores\\arduino",
            "-IC:\\Users\\Jascha\\AppData\\Local\\Arduino15\\packages\\adafruit\\hardware\\samd\\1.0.13\\variants\\arduino_zero"
        ],
        input_header: Some(
            "src/arduino_core.hpp"
        ),
        dummy_uses: None,
        type_chooser: None,
        codegen_config: CodegenConfig {
            functions: true,
            types: true,
            vars: true,
            methods: true,
            constructors: true
        },
        conservative_inline_namespaces: false,
        generate_comments: false,
        whitelist_recursively: true,
        objc_extern_crate: false
    }
}
emilio commented 7 years ago

Yeah, not config-specific.

We generate empty vtables always so far. That's because nobody has stopped to figure out what the correct vtable layout is in complex cases, what is the order for the vtable methods (we need to look in the base class chain for that), etc... MSVC and gcc/clang generate different vtable layouts for method overloads, also, as far as I know.

So we generate a dummy pointer-sized member so classes have the correct layout.

We definitely keep track of virtual methods and could attempt to do it (in this case it'd be trivial), but we need to consider all the complex cases too IMO, or at least a way to test that the vtable layout we generate is correct.

ebkalderon commented 6 years ago

@emilio If I were writing Rust bindings to a C++ library which hands out mostly abstract classes with pure virtual methods, would this mean I cannot rely on bindgen to correctly generate the vtables? I was not aware of this by looking at the bindgen book. Would I need resort to writing a C++ wrapper for each of these abstract classes instead, at least until bindgen can properly support them?

emilio commented 6 years ago

If I were writing Rust bindings to a C++ library which hands out mostly abstract classes with pure virtual methods, would this mean I cannot rely on bindgen to correctly generate the vtables? I was not aware of this by looking at the bindgen book. Would I need resort to writing a C++ wrapper for each of these abstract classes instead, at least until bindgen can properly support them?

Yeah, I think that's pretty much it. There's another issue where the difficulties of this are explained (#27).

I think figuring out what's the subset that works properly cross-platform (at least GCC / clang / MSVC) may not be hard, it just requires research and writing tests.

If someone is interested in giving it a shot I'm happy to mentor, but most of the information needed is probably available already from the vtable codegen code.

emilio commented 4 years ago

Let's close this as a dupe of https://github.com/rust-lang/rust-bindgen/issues/27. I think a basic implementation for where there are no overloads should be relatively easy.