Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support shared struct with C++ source of truth #871

Open
CAD97 opened this issue May 10, 2021 · 4 comments
Open

Support shared struct with C++ source of truth #871

CAD97 opened this issue May 10, 2021 · 4 comments

Comments

@CAD97
Copy link
Contributor

CAD97 commented May 10, 2021

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);
@adetaylor
Copy link
Collaborator

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

@CAD97
Copy link
Contributor Author

CAD97 commented May 11, 2021

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.

@adetaylor
Copy link
Collaborator

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.

@CAD97
Copy link
Contributor Author

CAD97 commented May 12, 2021

Reported google/autocxx#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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants