Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Crubit assumptions about extern "C" ABI of built-in Rust types

cc_bindings_from_rs makes certain assumptions about internal implementation details of the Rust compiler. In particular, cc_bindings_from_rs assumes a specific ABI of extern “C” functions that pass/return values of types that the improper_ctypes_definitions warning complains about. Because of these assumptions, the generated ..._cc_api_impl.rs code disables the warning via #![allow(improper_ctypes_definitions)]. These ABI assumptions are documented below.

Rust built-in char type

extern “C” thunks generated in ..._cc_api_impl.rs can take char arguments (and can return char values). (Note that this section talks about the Rust char type which is different from the C++ char type.)

Rust documentation says that “Rust char is 32-bit wide” and that size_of::<char>() == 4 . Additionally, Rust documentation describes some invalid bit patterns that may result in undefined behavior: “a value in a char which is a surrogate or above char::MAX”.

Rust does not directly document the alignment of char, although it does say that “most primitives are generally aligned to their size”. Furthermore Rust does not guarantee a particular ABI (e.g. whether char value can be passed in a general-purpose register VS in a vector register VS has to be passed by pointer). cc_bindings_from_rs assumes that Rust char has the same alignment and ABI as uint32_t (and therefore the same ABI as rs::char_ from crubit/support/rs_std/char.h).

The assumptions are verified by assertions that verify the properties of the target architecture when cc_bindings_from_rs runs (layout.align(), layout.size(), and layout.abi() assertions in format_type.rs). Similar assertions are verified on C++ side in support/rs_std/char_test.cc. These assertions seem unlikely to fail, but if they do, then hopefully char_ can just be tweaked to wrap another of the C++ integer types.

Rust built-in [T] slice type

extern “C” thunks generated in ..._cc_api_impl.rs can take &[T], &mut [T], *const [T] and similar arguments (or return them).

Rust documentation describes the layout of references and pointers to arrays and slices and also documents that slice references are “represented as a pointer and a length”.

Rust does not document the ABI of slice references (i.e. if the pointer comes before or after the length in memory). cc_bindings_from_rs assumes that &[T] has the same layout as rs::SliceRef<T> - a C++ struct with 2 fields: a T* pointer, and the size_t number of slice elements. check_slice_layout inside crubit/cc_bindings_from_rs/generate_bindings/format_type.rs verifies the assumptions on the Rust slice each time a reference or a pointer to a slice is processed by format_type.rs. Similar assertions are verified on C++ side in support/rs_std/slice_ref_test.cc. The response to the unlikely failure of these assertions can be changing the types from which rs::SliceRef is built to match the Rust side again.

cc_bindings_from_rs does not assume that &[T] and rs::SliceRef<T> have the same ABI as std::span<T> from C++ 20. In particular, empty slices have a different representation in C++ and in Rust - conversions implemented by rs::SliceRef<T> will take care of using a null or non-null pointer as appropriate.

Rust built-in &str string reference

extern “C” thunks generated in ..._cc_api_impl.rs can take &str and similar arguments (or return them). Rust documentation says that “a &str is made up of two components: a pointer to some bytes, and a length”, but no additional ABI guarantees are specified.

cc_bindings_from_rs assumes that &str has the same ABI as &[u8] (see the previous section) with the additional requirement that the contents of [u8] “are always valid UTF-8”. The rs::StrRef type enforces the UTF-8 guarantees. Runtime assertions (check_slice_layout) further verify these ABI layout assumptions.

cc_bindings_from_rs does not assume that &str and rs::StrRef have the same ABI as std::string_view from C++ 17. In particular, references to empty string slices have a different representation in C++ and in Rust - conversions implemented by rs::StrRef will take care of using a null or non-null pointer as appropriate.