Skip to content

Commit

Permalink
cxx: implement slice support for nostd
Browse files Browse the repository at this point in the history
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<Ok, Err>` 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 <wilfred.mallawa@wdc.com>
  • Loading branch information
twilfredo committed Nov 3, 2022
1 parent c7060d4 commit 9705b85
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 26 deletions.
27 changes: 20 additions & 7 deletions build.rs
Expand Up @@ -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");
Expand Down
2 changes: 2 additions & 0 deletions gen/build/Cargo.toml
Expand Up @@ -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"
Expand Down
27 changes: 22 additions & 5 deletions gen/build/src/lib.rs
Expand Up @@ -385,12 +385,29 @@ fn make_include_dir(prj: &Project) -> Result<PathBuf> {
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)
}
Expand Down
24 changes: 17 additions & 7 deletions include/cxx.h
@@ -1,16 +1,18 @@
#pragma once
#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <initializer_list>
#include <stdexcept>
#include <string>

#ifndef CXXBRIDGE1_RUST_STD
#include <algorithm>
#include <cassert>
#include <exception>
#include <iosfwd>
#include <iterator>
#include <new>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
Expand All @@ -19,6 +21,7 @@
#else
#include <sys/types.h>
#endif
#endif // CXXBRIDGE1_RUST_STD

namespace rust {
inline namespace cxxbridge1 {
Expand All @@ -30,6 +33,7 @@ template <typename T>
class impl;
}

#ifndef CXXBRIDGE1_RUST_STD
#ifndef CXXBRIDGE1_RUST_STRING
#define CXXBRIDGE1_RUST_STRING
// https://cxx.rs/binding/string.html
Expand Down Expand Up @@ -151,6 +155,7 @@ class Str final {
std::array<std::uintptr_t, 2> repr;
};
#endif // CXXBRIDGE1_RUST_STR
#endif

#ifndef CXXBRIDGE1_RUST_SLICE
namespace detail {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 <typename T>
using slice = Slice<T>;
#ifndef CXXBRIDGE1_RUST_STD
using string = String;
using str = Str;
template <typename T>
using box = Box<T>;
template <typename T>
using vec = Vec<T>;
#endif

using error = Error;
template <typename Signature>
using fn = Fn<Signature>;
Expand Down
29 changes: 22 additions & 7 deletions src/cxx.cc
@@ -1,9 +1,12 @@
#include "../include/cxx.h"
#include <cstring>
#ifndef CXXBRIDGE1_RUST_STD
#include <iostream>
#include <memory>
#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<const char *>(ptr), len);
Expand Down Expand Up @@ -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,
Expand All @@ -73,6 +77,13 @@ std::size_t cxxbridge1$slice$len(const void *self) noexcept;
namespace rust {
inline namespace cxxbridge1 {

template <typename T>
static bool is_aligned(const void *ptr) noexcept {
auto iptr = reinterpret_cast<std::uintptr_t>(ptr);
return !(iptr % alignof(T));
}

#ifndef CXXBRIDGE1_RUST_STD
template <typename Exception>
void panic [[noreturn]] (const char *msg) {
#if defined(RUST_CXX_NO_EXCEPTIONS)
Expand All @@ -85,12 +96,6 @@ void panic [[noreturn]] (const char *msg) {

template void panic<std::out_of_range> [[noreturn]] (const char *msg);

template <typename T>
static bool is_aligned(const void *ptr) noexcept {
auto iptr = reinterpret_cast<std::uintptr_t>(ptr);
return !(iptr % alignof(T));
}

String::String() noexcept { cxxbridge1$string$new(this); }

String::String(const String &other) noexcept {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<Str>::value,
"trivial Str(const Str &)");
static_assert(std::is_trivially_copy_assignable<Str>::value,
"trivial operator=(const Str &)");
static_assert(std::is_trivially_destructible<Str>::value, "trivial ~Str()");
#endif

static_assert(
std::is_trivially_copy_constructible<Slice<const std::uint8_t>>::value,
Expand Down Expand Up @@ -448,6 +456,7 @@ static_assert(!std::is_same<Vec<std::uint8_t>::const_iterator,
Vec<std::uint8_t>::iterator>::value,
"Vec<T>::const_iterator != Vec<T>::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);
Expand Down Expand Up @@ -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; }

Expand All @@ -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
Expand All @@ -530,7 +542,7 @@ using isize_if_unique =
typename std::conditional<std::is_same<rust::isize, int64_t>::value ||
std::is_same<rust::isize, int32_t>::value,
struct isize_ignore, rust::isize>::type;

#ifndef CXXBRIDGE1_RUST_STD
class Fail final {
repr::PtrLen &throw$;

Expand All @@ -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
Expand All @@ -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<std::string> *ptr) noexcept {
Expand Down Expand Up @@ -790,3 +804,4 @@ inline namespace cxxbridge1 {
FOR_EACH_RUST_VEC(RUST_VEC_OPS)
} // namespace cxxbridge1
} // namespace rust
#endif // CXXBRIDGE1_RUST_STD

0 comments on commit 9705b85

Please sign in to comment.