From 2a139b4980712ca4d0fe36c1016f864c1cd663e5 Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Thu, 27 Oct 2022 11:51:47 +1000 Subject: [PATCH] cxx: implement slice support for nostd OVERVIEW: This patch adds experimental support for us to use `slices` in a nostd/freestanding environment, aim is to run c++ <=> cxx-rs <=> rust on a baremental target environment and use slices, with the intention of supporting more features later. In the application `Cargo.toml` file, the following feature cfgs are required: ``` [dependencies] cxx = { ... , default-features = false } [build-dependencies] cxx-build = { ... , default-features = false } ``` Then build with: ``` RUSTFLAGS='--cfg cxx_experimental_no_alloc' cargo build ``` and in the particular `.cpp` file you may need to declare the following ``` void __assert_func (const char *__a, int, const char *__b, const char *__c) { while (true) {} } void *__gxx_personality_v0; ``` FUNCTIONALITY: Currently, this only supports slices (outside of trivial features). TESTING: Tested by compiling: - cargo test (run existing tests) - cxx/demo/ running it. - compiling with a arm toolchain setup with cmake/corrosion and running the binary on QEMU arm with basic logic assertions [1]. Current testing has been done in the direction of C++ -> Rust with a simple callback test to C++. A simple test setup can be seen here [2]. TODO: - Get features such as `Results` working. - When we build for the none `std` case (no `default-features`), instead of creating a symlink to the original `cxx.h` file, we copy it over and define the macro to disable stdlib dependencies. Perhaps there's a better way to do this? NOTES: By default, all the standard features are enabled gaurded by the `#ifndef CXXBRIDGE1_RUST_STD`, so this **shoudn't** break anything. [1] https://github.com/twilfredo/qemu_misc/tree/master/bm_arm [2] https://github.com/twilfredo/qemu_misc/blob/master/bm_arm/main.cpp Signed-off-by: Wilfred Mallawa --- build.rs | 27 ++++++++++++++++++++------- gen/build/Cargo.toml | 2 ++ gen/build/src/lib.rs | 27 ++++++++++++++++++++++----- include/cxx.h | 24 +++++++++++++++++------- src/cxx.cc | 29 ++++++++++++++++++++++------- 5 files changed, 83 insertions(+), 26 deletions(-) diff --git a/build.rs b/build.rs index a953639aa..4691d0fbd 100644 --- a/build.rs +++ b/build.rs @@ -3,13 +3,26 @@ use std::path::Path; use std::process::Command; fn main() { - cc::Build::new() - .file("src/cxx.cc") - .cpp(true) - .cpp_link_stdlib(None) // linked via link-cplusplus crate - .flag_if_supported(cxxbridge_flags::STD) - .warnings_into_errors(cfg!(deny_warnings)) - .compile("cxxbridge1"); + // Building without alloc, for a nostd target. + if !cfg!(feature = "alloc") { + cc::Build::new() + .file("src/cxx.cc") + .cpp(true) + .cpp_link_stdlib(None) // linked via link-cplusplus crate + .flag_if_supported(cxxbridge_flags::STD) + .warnings_into_errors(cfg!(deny_warnings)) + .define("CXXBRIDGE1_RUST_STD", None) // Exclude all dependencies on std + .compile("cxxbridge1"); + } else { + // Building for STD with all features + cc::Build::new() + .file("src/cxx.cc") + .cpp(true) + .cpp_link_stdlib(None) // linked via link-cplusplus crate + .flag_if_supported(cxxbridge_flags::STD) + .warnings_into_errors(cfg!(deny_warnings)) + .compile("cxxbridge1"); + } println!("cargo:rerun-if-changed=src/cxx.cc"); println!("cargo:rerun-if-changed=include/cxx.h"); diff --git a/gen/build/Cargo.toml b/gen/build/Cargo.toml index f7e660696..a8cf7e701 100644 --- a/gen/build/Cargo.toml +++ b/gen/build/Cargo.toml @@ -13,9 +13,11 @@ repository = "https://github.com/dtolnay/cxx" rust-version = "1.48" [features] +default = ["std"] parallel = ["cc/parallel"] # incomplete features that are not covered by a compatibility guarantee: experimental-async-fn = [] +std = [] [dependencies] cc = "1.0.49" diff --git a/gen/build/src/lib.rs b/gen/build/src/lib.rs index 7272a396c..f7b34e2e3 100644 --- a/gen/build/src/lib.rs +++ b/gen/build/src/lib.rs @@ -385,12 +385,29 @@ fn make_include_dir(prj: &Project) -> Result { let include_dir = prj.out_dir.join("cxxbridge").join("include"); let cxx_h = include_dir.join("rust").join("cxx.h"); let ref shared_cxx_h = prj.shared_dir.join("rust").join("cxx.h"); - if let Some(ref original) = env::var_os("DEP_CXXBRIDGE1_HEADER") { - out::symlink_file(original, cxx_h)?; - out::symlink_file(original, shared_cxx_h)?; - } else { - out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?; + + if !cfg!(feature = "std") { + // For a nonstd target, specify `#define CXXBRIDGE1_RUST_STD` to a copy of "include/cxx.h" and + // don't create a symlink to the original header (which has all std features enabled). + // This copy can now be included by a C++ file + // compiled/linked with `-nostdlib` + let mut new = "// Allow building for a nostd target by excluding std dependencies\n#define CXXBRIDGE1_RUST_STD\n" + .to_string() + .as_bytes() + .to_vec(); + let mut byte_vec = gen::include::HEADER.as_bytes().to_vec(); + new.append(&mut byte_vec); + let slice: &[u8] = &new; + out::write(shared_cxx_h, slice)?; out::symlink_file(shared_cxx_h, cxx_h)?; + } else { + if let Some(ref original) = env::var_os("DEP_CXXBRIDGE1_HEADER") { + out::symlink_file(original, cxx_h)?; + out::symlink_file(original, shared_cxx_h)?; + } else { + out::write(shared_cxx_h, gen::include::HEADER.as_bytes())?; + out::symlink_file(shared_cxx_h, cxx_h)?; + } } Ok(include_dir) } diff --git a/include/cxx.h b/include/cxx.h index 907ee829f..3edb08f93 100644 --- a/include/cxx.h +++ b/include/cxx.h @@ -1,19 +1,22 @@ #pragma once -#include #include -#include #include #include -#include #include +#include +#include + +#ifndef CXXBRIDGE1_RUST_STD +#include +#include +#include #include #include #include -#include -#include #include #include #include +#endif // CXXBRIDGE1_RUST_STD #if defined(_WIN32) #include #else @@ -30,6 +33,7 @@ template class impl; } +#ifndef CXXBRIDGE1_RUST_STD #ifndef CXXBRIDGE1_RUST_STRING #define CXXBRIDGE1_RUST_STRING // https://cxx.rs/binding/string.html @@ -151,6 +155,7 @@ class Str final { std::array repr; }; #endif // CXXBRIDGE1_RUST_STR +#endif #ifndef CXXBRIDGE1_RUST_SLICE namespace detail { @@ -412,8 +417,10 @@ using isize = ssize_t; #endif #endif // CXXBRIDGE1_RUST_ISIZE +#ifndef CXXBRIDGE1_RUST_STD std::ostream &operator<<(std::ostream &, const String &); std::ostream &operator<<(std::ostream &, const Str &); +#endif #ifndef CXXBRIDGE1_RUST_OPAQUE #define CXXBRIDGE1_RUST_OPAQUE @@ -465,14 +472,17 @@ using f32 = float; using f64 = double; // Snake case aliases for use in code that uses this style for type names. -using string = String; -using str = Str; template using slice = Slice; +#ifndef CXXBRIDGE1_RUST_STD +using string = String; +using str = Str; template using box = Box; template using vec = Vec; +#endif + using error = Error; template using fn = Fn; diff --git a/src/cxx.cc b/src/cxx.cc index a7cb6797e..b9d6c645a 100644 --- a/src/cxx.cc +++ b/src/cxx.cc @@ -1,9 +1,12 @@ #include "../include/cxx.h" #include +#ifndef CXXBRIDGE1_RUST_STD #include #include +#endif extern "C" { +#ifndef CXXBRIDGE1_RUST_STD void cxxbridge1$cxx_string$init(std::string *s, const std::uint8_t *ptr, std::size_t len) noexcept { new (s) std::string(reinterpret_cast(ptr), len); @@ -62,6 +65,7 @@ bool cxxbridge1$str$from(rust::Str *self, const char *ptr, std::size_t len) noexcept; const char *cxxbridge1$str$ptr(const rust::Str *self) noexcept; std::size_t cxxbridge1$str$len(const rust::Str *self) noexcept; +#endif // rust::Slice void cxxbridge1$slice$new(void *self, const void *ptr, @@ -73,6 +77,13 @@ std::size_t cxxbridge1$slice$len(const void *self) noexcept; namespace rust { inline namespace cxxbridge1 { +template +static bool is_aligned(const void *ptr) noexcept { + auto iptr = reinterpret_cast(ptr); + return !(iptr % alignof(T)); +} + +#ifndef CXXBRIDGE1_RUST_STD template void panic [[noreturn]] (const char *msg) { #if defined(RUST_CXX_NO_EXCEPTIONS) @@ -85,12 +96,6 @@ void panic [[noreturn]] (const char *msg) { template void panic [[noreturn]] (const char *msg); -template -static bool is_aligned(const void *ptr) noexcept { - auto iptr = reinterpret_cast(ptr); - return !(iptr % alignof(T)); -} - String::String() noexcept { cxxbridge1$string$new(this); } String::String(const String &other) noexcept { @@ -377,6 +382,7 @@ std::ostream &operator<<(std::ostream &os, const Str &s) { os.write(s.data(), s.size()); return os; } +#endif void sliceInit(void *self, const void *ptr, std::size_t len) noexcept { cxxbridge1$slice$new(self, ptr, len); @@ -406,11 +412,13 @@ static_assert(sizeof(rust::isize) == sizeof(std::intptr_t), static_assert(alignof(rust::isize) == alignof(std::intptr_t), "unsupported ssize_t alignment"); +#ifndef CXXBRIDGE1_RUST_STD static_assert(std::is_trivially_copy_constructible::value, "trivial Str(const Str &)"); static_assert(std::is_trivially_copy_assignable::value, "trivial operator=(const Str &)"); static_assert(std::is_trivially_destructible::value, "trivial ~Str()"); +#endif static_assert( std::is_trivially_copy_constructible>::value, @@ -448,6 +456,7 @@ static_assert(!std::is_same::const_iterator, Vec::iterator>::value, "Vec::const_iterator != Vec::iterator"); +#ifndef CXXBRIDGE1_RUST_STD static const char *errorCopy(const char *ptr, std::size_t len) { char *copy = new char[len]; std::memcpy(copy, ptr, len); @@ -494,6 +503,7 @@ Error &Error::operator=(Error &&other) &noexcept { other.len = 0; return *this; } +#endif const char *Error::what() const noexcept { return this->msg; } @@ -513,9 +523,11 @@ struct PtrLen final { }; } // namespace repr +#ifndef CXXBRIDGE1_RUST_STD extern "C" { repr::PtrLen cxxbridge1$exception(const char *, std::size_t len) noexcept; } +#endif namespace detail { // On some platforms size_t is the same C++ type as one of the sized integer @@ -530,7 +542,7 @@ using isize_if_unique = typename std::conditional::value || std::is_same::value, struct isize_ignore, rust::isize>::type; - +#ifndef CXXBRIDGE1_RUST_STD class Fail final { repr::PtrLen &throw$; @@ -547,6 +559,7 @@ void Fail::operator()(const char *catch$) noexcept { void Fail::operator()(const std::string &catch$) noexcept { throw$ = cxxbridge1$exception(catch$.data(), catch$.length()); } +#endif } // namespace detail } // namespace cxxbridge1 @@ -559,6 +572,7 @@ void destroy(T *ptr) { } } // namespace +#ifndef CXXBRIDGE1_RUST_STD extern "C" { void cxxbridge1$unique_ptr$std$string$null( std::unique_ptr *ptr) noexcept { @@ -790,3 +804,4 @@ inline namespace cxxbridge1 { FOR_EACH_RUST_VEC(RUST_VEC_OPS) } // namespace cxxbridge1 } // namespace rust +#endif // CXXBRIDGE1_RUST_STD