| // Part of the Crubit project, under the Apache License v2.0 with LLVM |
| // Exceptions. See /LICENSE for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #ifndef THIRD_PARTY_CRUBIT_SUPPORT_BRIDGE_H_ |
| #define THIRD_PARTY_CRUBIT_SUPPORT_BRIDGE_H_ |
| |
| #include <concepts> |
| #include <cstddef> |
| #include <cstring> |
| #include <optional> |
| #include <tuple> |
| #include <utility> |
| |
| namespace crubit { |
| |
| class Encoder; |
| class Decoder; |
| |
| // A mutually understood ABI for sending bridge types between Rust and C++. |
| // |
| // Bridging values between Rust and C++ is typically done by breaking down |
| // values into their primitive, ABI-compatible parts like integers and pointers |
| // in the native language. Then, these primitive parts are sent across the |
| // language boundary, where the target language can reconstruct the semantically |
| // equivalent value. This typically happens by sending the parts as function |
| // arguments on an extern C function, but this doesn't work for |
| // generic/templated types without monomorphizing each instantiation since C |
| // doesn't have templates. |
| // |
| // The solution to this is to transform the value into an ABI-compatible layout |
| // that both languages understand, allowing for passing arbitrarily complex |
| // types through C as a char pointer to a stack allocated buffer. The |
| // `is_crubit_abi` check is used to describe this mutually understood ABI. |
| // |
| // Let's walk through the example of how `Option<T>` is bridged. The first step |
| // is to define an ABI for how it should be bridged, which is represented by a |
| // type that is `is_crubit_abi`. |
| // |
| // ```cpp |
| // template <typename Abi> |
| // requires(is_crubit_abi<Abi>) |
| // struct OptionAbi { |
| // using Value = std::optional<typename Abi::Value>; |
| // // other items omitted... |
| // }; |
| // ``` |
| // |
| // This is saying "`OptionAbi<Abi>` is a description of how to bridge a |
| // `std::optional<typename Abi::Value>`." But before we proceed, we need to |
| // decide: what will the std::optional ABI be? To keep things general we'll |
| // choose to bridge `std::optional<T>` as a bool, followed by the value if the |
| // bool is true. To express this, we need to implement the other items: |
| // |
| // ```cpp |
| // template <typename Abi> |
| // requires(is_crubit_abi<Abi>) |
| // struct OptionAbi { |
| // using Value = std::optional<typename Abi::Value>; |
| // static constexpr size_t kSize = sizeof(bool) + Abi::kSize; |
| // static void Encode(Value value, Encoder& encoder) { |
| // if (value.has_value()) { |
| // encoder.EncodeTransmute(true); |
| // encoder.Encode<Abi>(*std::move(value)); |
| // } else { |
| // encoder.EncodeTransmute(false); |
| // } |
| // } |
| // static Value Decode(Decoder& decoder) { |
| // if (!decoder.DecodeTransmute<bool>()) { |
| // return std::nullopt; |
| // } |
| // return decoder.Decode<Abi>(); |
| // } |
| // }; |
| // ``` |
| // |
| // There are several things going on here. First, we need to define the `kSize` |
| // constant. This information is used to statically compute the size of the |
| // buffer required to encode/decode an `std::optional<T>` with this ABI, |
| // allowing us to stack allocate the buffer. Importantly, the current |
| // implementation packs all the data with unaligned writes/reads, so alignment |
| // information is not needed. Second, we need to define the `Encode` and |
| // `Decode` functions. These functions implement the agreed-upon ABI: bool, |
| // optionally followed by the value if the bool is true. |
| // |
| // # Safety |
| // |
| // It's safety critical that the C++ implementation matches the Rust |
| // implementation exactly, since the ABI is supposed to be mutually understood. |
| template <typename Abi> |
| constexpr bool is_crubit_abi = requires { |
| // The type that this CrubitAbi is encoding and decoding. |
| typename Abi::Value; |
| |
| // The size in bytes of a `Value` when encoded with this ABI. This is used to |
| // statically compute the size of the buffer required to encode/decode a |
| // `Value` with this ABI. |
| { Abi::kSize } -> std::convertible_to<size_t>; |
| |
| // Encodes a `Value`, advancing the encoders's position by `kSize` bytes. |
| // |
| // Aside from implementations for primitives, most implementations of this |
| // function will be composed of other calls to [`Encoder::Encode::<Abi>`], |
| // each one advancing the encoder's position by `A::kSize` bytes. The |
| // implementation should ensure that the these calls do not advance the |
| // encoder's position by more than `kSize` bytes. This is because the `kSize` |
| // constant is used to compute the buffer size statically, and if the |
| // encoder's position is advanced by more than `kSize`, the encoder may panic |
| // in debug builds, or cause undefined behavior in release builds. |
| // |
| // # Notes |
| // |
| // The value must be semantically moved into the encoder. This means that if |
| // you're transferring ownership of anything, you must ensure that the |
| // original owner leaks the resource so it can later be reclaimed by |
| // decoding. Prefer functions that explicitly leak, or defer to moving values |
| // into a casted `alignof(T) char buf[sizeof(T)]` if leaking APIs are |
| // unavailable. |
| // |
| // # Examples |
| // |
| // ```cpp |
| // template <typename Abi1, typename Abi2> |
| // requires(is_crubit_abi<Abi1> && is_crubit_abi<Abi2>) |
| // struct PairAbi { |
| // static void Encode(Value value, Encoder& encoder) { |
| // encoder.Encode<Abi1>(std::move(value.first)); |
| // encoder.Encode<Abi2>(std::move(value.second)); |
| // } |
| // // other items omitted... |
| // }; |
| // ``` |
| { |
| Abi::Encode(std::declval<typename Abi::Value>(), std::declval<Encoder&>()) |
| } -> std::same_as<void>; |
| |
| // Decodes a [`Value`], advancing the decoder's position by `kSize` bytes. |
| // |
| // Aside from implementations for primitives, most implementations of this |
| // function will be composed of other calls to [`Decoder::Decode::<Abi>`], |
| // each one advancing the decoder's position by `A::kSize` bytes. The |
| // implementation should ensure that the these calls do not advance the |
| // decoder's position by more than `kSize` bytes. This is because the `kSize` |
| // constant is used to compute the buffer size statically, and if the |
| // decoder's position is advanced by more than `kSize`, the decoder may panic |
| // in debug builds, or cause undefined behavior in release builds. |
| // |
| // # Examples |
| // |
| // ```cpp |
| // template <typename Abi1, typename Abi2> |
| // requires(is_crubit_abi<Abi1> && is_crubit_abi<Abi2>) |
| // struct PairAbi { |
| // static Value Decode(Decoder& decoder) { |
| // return { |
| // .first = decoder.Decode<Abi1>(), |
| // .second = decoder.Decode<Abi2>(), |
| // }; |
| // } |
| // // other items omitted... |
| // }; |
| // ``` |
| // |
| // # Safety |
| // |
| // The caller guarantees that the buffer's current position contains a |
| // `Value` that was encoded with this ABI (either from Rust or C++). |
| { |
| Abi::Decode(std::declval<Decoder&>()) |
| } -> std::same_as<typename Abi::Value>; |
| }; |
| |
| namespace internal { |
| |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| void Encode(unsigned char* buf, typename Abi::Value value); |
| |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| typename Abi::Value Decode(const unsigned char* buf); |
| |
| } // namespace internal |
| |
| // A wrapper around a buffer that tracks which parts of a buffer have already |
| // been written to. |
| class Encoder { |
| explicit Encoder(size_t remaining_bytes, unsigned char* buf) |
| : remaining_bytes_(remaining_bytes), buf_(buf) {} |
| |
| public: |
| // Encodes a value by a provided schema. |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| void Encode(typename Abi::Value value) & { |
| Abi::Encode(std::move(value), *this); |
| } |
| |
| // Encodes a value via `memcpy`. |
| template <typename T> |
| void EncodeTransmute(T value) &; |
| |
| void* Next(size_t size) & { |
| remaining_bytes_ -= size; |
| return buf_ + remaining_bytes_; |
| } |
| |
| private: |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| friend void internal::Encode(unsigned char* buf, typename Abi::Value value); |
| // The number of bytes remaining in the buffer. |
| size_t remaining_bytes_; |
| unsigned char* buf_; |
| }; |
| |
| // A wrapper around a buffer that tracks which parts of a buffer have already |
| // been read from. |
| class Decoder { |
| explicit Decoder(size_t remaining_bytes, const unsigned char* buf) |
| : remaining_bytes_(remaining_bytes), buf_(buf) {} |
| |
| public: |
| // Decodes a value by a provided schema. The caller must ensure that the |
| // buffer contains a value that was encoded with the same schema. |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| typename Abi::Value Decode() & { |
| return Abi::Decode(*this); |
| } |
| |
| // Decodes a value via `memcpy`. The caller must ensure that the buffer |
| // contains a value that was encoded with ByTransmute. |
| template <typename T> |
| T DecodeTransmute() &; |
| |
| const void* Next(size_t size) & { |
| remaining_bytes_ -= size; |
| return buf_ + remaining_bytes_; |
| } |
| |
| private: |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| friend typename Abi::Value internal::Decode(const unsigned char* buf); |
| // The number of bytes remaining in the buffer. |
| size_t remaining_bytes_; |
| const unsigned char* buf_; |
| }; |
| |
| // A Crubit ABI for encoding and decoding a value of type `T` by copying the |
| // memory of the value using `memcpy`. |
| template <typename T> |
| requires(std::move_constructible<T>) |
| struct TransmuteAbi { |
| using Value = T; |
| static constexpr size_t kSize = sizeof(Value); |
| static void Encode(Value value, Encoder& encoder) { |
| // Move-construct the value into a type erased buffer, ensuring that value |
| // is in a "moved from" state. Then copy the value into the encoder buffer. |
| // We use an intermediate buffer and a memcpy to avoid strict aliasing |
| // violations. Furthermore, the destructor is not called- this is intended, |
| // because we have semantically moved the value into the buffer. |
| alignas(Value) char buf[kSize]; |
| std::memcpy(encoder.Next(kSize), new (buf) Value(std::move(value)), kSize); |
| } |
| static Value Decode(Decoder& decoder) { |
| alignas(Value) char buf[kSize]; |
| // Copy the value from the decoder buffer into the intermediate buffer. |
| std::memcpy(buf, decoder.Next(kSize), kSize); |
| // Move-construct the value from the buffer. |
| return std::move(*reinterpret_cast<Value*>(buf)); |
| } |
| }; |
| |
| template <typename T> |
| void Encoder::EncodeTransmute(T value) & { |
| TransmuteAbi<T>::Encode(std::move(value), *this); |
| } |
| |
| template <typename T> |
| T Decoder::DecodeTransmute() & { |
| return TransmuteAbi<T>::Decode(*this); |
| } |
| |
| template <typename... Abis> |
| requires(is_crubit_abi<Abis> && ...) |
| struct TupleAbi { |
| using Value = std::tuple<typename Abis::Value...>; |
| static constexpr size_t kSize = (0 + ... + Abis::kSize); |
| static void Encode(Value value, Encoder& encoder) { |
| std::apply( |
| [&](typename Abis::Value&&... args) { |
| (encoder.Encode<Abis>(args), ...); |
| }, |
| std::move(value)); |
| } |
| static Value Decode(Decoder& decoder) { |
| return std::make_tuple(decoder.Decode<Abis>()...); |
| } |
| }; |
| |
| template <typename Abi1, typename Abi2> |
| requires(is_crubit_abi<Abi1> && is_crubit_abi<Abi2>) |
| struct PairAbi { |
| using Value = std::pair<typename Abi1::Value, typename Abi2::Value>; |
| static constexpr size_t kSize = Abi1::kSize + Abi2::kSize; |
| static void Encode(Value value, Encoder& encoder) { |
| encoder.Encode<Abi1>(std::move(value.first)); |
| encoder.Encode<Abi2>(std::move(value.second)); |
| } |
| static Value Decode(Decoder& decoder) { |
| return { |
| .first = decoder.Decode<Abi1>(), |
| .second = decoder.Decode<Abi2>(), |
| }; |
| } |
| }; |
| |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| struct OptionAbi { |
| using Value = std::optional<typename Abi::Value>; |
| static constexpr size_t kSize = sizeof(bool) + Abi::kSize; |
| static void Encode(Value value, Encoder& encoder) { |
| if (value.has_value()) { |
| encoder.EncodeTransmute(true); |
| encoder.Encode<Abi>(*std::move(value)); |
| } else { |
| encoder.EncodeTransmute(false); |
| } |
| } |
| static Value Decode(Decoder& decoder) { |
| if (!decoder.DecodeTransmute<bool>()) { |
| return std::nullopt; |
| } |
| return decoder.Decode<Abi>(); |
| } |
| }; |
| |
| template <typename T> |
| requires(std::move_constructible<T>) |
| struct BoxedAbi { |
| using Value = T; |
| static constexpr size_t kSize = sizeof(void*); |
| static void Encode(Value value, Encoder& encoder) { |
| void* box = new Value(std::move(value)); |
| encoder.EncodeTransmute(box); |
| } |
| static Value Decode(Decoder& decoder) { |
| Value* box = reinterpret_cast<Value*>(decoder.DecodeTransmute<void*>()); |
| Value value(std::move(*box)); |
| delete box; |
| return value; |
| } |
| }; |
| |
| namespace internal { |
| |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| void Encode(unsigned char* buf, typename Abi::Value value) { |
| Encoder encoder(Abi::kSize, buf); |
| encoder.Encode<Abi>(std::move(value)); |
| } |
| |
| template <typename Abi> |
| requires(is_crubit_abi<Abi>) |
| typename Abi::Value Decode(const unsigned char* buf) { |
| Decoder decoder(Abi::kSize, buf); |
| return decoder.Decode<Abi>(); |
| } |
| |
| } // namespace internal |
| |
| } // namespace crubit |
| |
| #endif // THIRD_PARTY_CRUBIT_SUPPORT_BRIDGE_H_ |