cxx icon indicating copy to clipboard operation
cxx copied to clipboard

Support shared struct with C++ source of truth

Open CAD97 opened this issue 4 years ago • 5 comments

I want to be able to write the following or equivalent:

#[cxx::bridge]
fn ffi {
    struct FVector {
        pub X: f32,
        pub Y: f32,
        pub Z: f32,
    }

    extern "C++" {
        include!("Vector.h");
        type FVector;
    }
}

and it have it expand to the the following or equivalent:

// Rust
#[repr(C)]
struct FVector {
    pub X: f32,
    pub Y: f32,
    pub Z: f32,
}
// Cxx
#include "Vector.h"

#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <utility>

static_assert(::std::is_class<FVector>::value, "expected class");
static_assert(sizeof(FVector) == 12, "incorrect size");
static_assert(offsetof(FVector, X) == 0, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().X), float>::value, "disagrees with the value in #[cxx::bridge]");
static_assert(offsetof(FVector, Y) == 4, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().Y), float>::value, "disagrees with the value in #[cxx::bridge]");
static_assert(offsetof(FVector, Z) == 8, "disagrees with the value in #[cxx::bridge]");
static_assert(::std::is_same<decltype(::std::declval<FVector>().Z), float>::value, "disagrees with the value in #[cxx::bridge]");

This uses only C++11 functionality as written. The only member types supported would be primitive types or (potentially) shared types defined in the same bridge module. This requires knowledge of the repr(C) layout algorithm to assert that it is correct; for this use case we can use the simple definition:

fn repr_c(field_layouts: Vec<Layout>) -> Result<StructLayout> {
    let mut layout = Layout::new::<()>();
    let mut fields = vec![];
    for field in field_layouts {
        let (extended, offset) = layout.extend(field_layout)?;
        layout = extended;
        offsets.push(FieldLayout { layout: field_layout, offset });
    }
    layout.pad_to_align();
    StructLayout { layout, fields }
}

For extra paranoia we could also emit assertions that our understanding is correct on the Rust side as well.

Preprocessor macros to make doing so manually easier
#define PREPROCESSOR_TO_STRING(x) PREPROCESSOR_TO_STRING_INNER(x)
#define PREPROCESSOR_TO_STRING_INNER(x) #x

#define ASSERT_TYPE_SIZE(Type, Size) \
    static_assert(                   \
        sizeof(Type) == Size,        \
        PREPROCESSOR_TO_STRING(Type) " does not have expected size " PREPROCESSOR_TO_STRING(Size));

#define ASSERT_TYPE_MEMBER(Type, Offset, MemberType, Member, ...)                                                                       \
    static_assert(                                                                                                                      \
        offsetof(Type, Member) == Offset,                                                                                               \
        PREPROCESSOR_TO_STRING(Type) "::" PREPROCESSOR_TO_STRING(Member) " is not at expected offset " PREPROCESSOR_TO_STRING(Offset)); \
    static_assert(                                                                                                                      \
        std::is_same<decltype(std::declval<Type>().Member), MemberType>::value,                                                         \
        PREPROCESSOR_TO_STRING(Type) "::" PREPROCESSOR_TO_STRING(Member) " is not expected type " PREPROCESSOR_TO_STRING(MemberType));

ASSERT_TYPE_SIZE(FVector, 12);
ASSERT_TYPE_MEMBER(FVector, 0, float, X);
ASSERT_TYPE_MEMBER(FVector, 4, float, Y);
ASSERT_TYPE_MEMBER(FVector, 8, float, Z);

CAD97 avatar May 10 '21 05:05 CAD97

If you're feeling bold, you can try autocxx and specifically generate_pod!, which should do what you want.

adetaylor avatar May 10 '21 05:05 adetaylor

Unfortunately, the C++ code I'm trying to interface with is too complicated for autocxx/bindgen to process cleanly at the moment, so while I'd love to use autocxx, it's not possible quite yet.

It's worth noting that you can already do exactly this, just without the checks that your mapping is correct. It would be nice to extend the checks for extern shared enums to extern shared structs.

CAD97 avatar May 11 '21 01:05 CAD97

Unfortunately, the C++ code I'm trying to interface with is too complicated for autocxx/bindgen to process cleanly at the moment

If you get a chance, it'd be great if you could file an autocxx bug per these instructions. In particular if you can run the codegen with AUTOCXX_PREPROCESS=out.h then sent me out.h I should be able to minimize to get a small test case. Obviously you can only do that if your code is open source.

The general state of play is that it can cope with any codebase right now, so long as you are specific about bindings what you want to generate using generate! directives (as opposed to asking it to generate bindings for every C++ API it encounters). If there's a C++ codebase which is just plain dies on, it'd be great to get a bug report.

adetaylor avatar May 12 '21 14:05 adetaylor

Reported https://github.com/google/autocxx/issues/479. Unfortunately the code isn't reproducible but it is freely obtainable, so perhaps someone able to track down where autocxx is panicking can reproduce the panic and minimize the issue.

CAD97 avatar May 12 '21 21:05 CAD97