Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Commit

Permalink
Add emplacement for cxx::UniquePtr.
Browse files Browse the repository at this point in the history
This allows cxx::UniquePtr::emplace of any New made using
moveit norms.

UniquePtr is different from the other smart pointers supported
by moveit, in that UniquePtr is _intrinsically_ pinned. You can't
get an unpinned mutable reference to its contents; therefore there
is no need to use Pin<UniquePtr>. This commit therefore splits
the Emplace trait into Emplace and EmplaceUnpinned, with the goal
that users can naturally call {Box, Arc, Rc, UniquePtr}::emplace
in a similar fashion, so long as both traits are in scope.

UniquePtr<T> emplacement support requires the T to implement
a new trait, MakeCppStorage, which can provide uninitialized
space for the T in some heap cell which can later be deleted
using the std::unique_ptr default destructor. It's not expected
that most users will implement this trait, but that it will
instead be generated automatically by code generators atop moveit
and cxx (for instance, autocxx).

Testing these cxx bindings requires inclusion of C++ code,
and we don't want to link against C++ code for the production
rlib - therefore a separate module is created within the workspace
to host these tests.
  • Loading branch information
adetaylor committed Jan 7, 2022
1 parent 9d7d6c0 commit 9ea3128
Show file tree
Hide file tree
Showing 11 changed files with 386 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@v2

- name: cargo test
run: cargo test --verbose
run: cargo test --verbose --all
- name: cargo doc
run: cargo doc --verbose

Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ description = "A library for safe, in-place construction of Rust (and C++!) obje
[features]
alloc = []
default = ["alloc"]

[dependencies.cxx]
version = "1.0"
optional = true

[workspace]
members = ["cxx-tests"]
31 changes: 31 additions & 0 deletions cxx-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[package]
name = "moveit-cxx-tests"
version = "0.0.0"
authors = ["Miguel Young de la Sota <mcyoung@google.com>"]
edition = "2018"
repository = "https://github.com/google/moveit"
publish = false

[dependencies]
cxx = "1.0"

[dependencies.moveit]
path = ".."
features = ["cxx"]

[build-dependencies]
cxx-build = "1"
23 changes: 23 additions & 0 deletions cxx-tests/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

fn main() {
cxx_build::bridge("src/tests.rs")
.flag_if_supported("-std=c++14")
.include("src")
.compile("moveit-cxx-tests");

println!("cargo:rerun-if-changed=src/tests.rs");
println!("cargo:rerun-if-changed=src/cxx_support_test_cpp.h");
}
52 changes: 52 additions & 0 deletions cxx-tests/src/cxx_support_test_cpp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Build in test mode only to test cxx integration.

#ifndef CXX_SUPPORT_TEST_CPP
#define CXX_SUPPORT_TEST_CPP

#include <cstdint>
#include <cstring>
#include <memory>

constexpr uint8_t kUninitialized = 0;
constexpr uint8_t kInitialized = 1;
constexpr uint8_t kMethodCalled = 2;

class Foo {
public:
Foo() { data[0] = kInitialized; }
uint8_t get_status() const { return data[0]; }
void modify() { data[0] = kMethodCalled; }

private:
uint32_t data[4]; // exactly match layout declared in Rust.
};

inline Foo* CreateUninitializedFoo() {
std::allocator<Foo> alloc;
Foo* data = alloc.allocate(1);
std::memcpy(data, &kUninitialized, 1);
return data;
}

inline void FreeUninitializedFoo(Foo* foo) {
std::allocator<Foo> alloc;
alloc.deallocate(foo, 1);
}

inline void foo_constructor(Foo& foo) { new (&foo) Foo(); }

#endif // CXX_SUPPORT_TEST_CPP
20 changes: 20 additions & 0 deletions cxx-tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Tests for `cxx` support in `moveit`. This is a separate crate because
//! these tests require linking C++ code in a `build.rs`, which we do not
//! want to apply to normal users of `moveit`.
#[cfg(test)]
mod tests;
115 changes: 115 additions & 0 deletions cxx-tests/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use cxx::UniquePtr;
use moveit::moveit;
use moveit::Emplace;
use moveit::EmplaceUnpinned;

// Shared with C++
const UNINITIALIZED: u8 = 0;
const INITIALIZED: u8 = 1;
const METHOD_CALLED: u8 = 2;

#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx_support_test_cpp.h");
type Foo = super::bindgenish::Foo;
fn CreateUninitializedFoo() -> *mut Foo;
unsafe fn FreeUninitializedFoo(ptr: *mut Foo);

fn foo_constructor(_this: Pin<&mut Foo>);

fn get_status(self: &Foo) -> u8;
fn modify(self: Pin<&mut Foo>);
}
// Ensures that cxx creates bindings for UniquePtr<Foo>
// even though that isn't used in any of the above APIs.
impl UniquePtr<Foo> {}
}

mod bindgenish {
use std::marker::PhantomData;
use std::marker::PhantomPinned;

use cxx::kind::Opaque;
use cxx::type_id;
use cxx::ExternType;

use moveit::MakeCppStorage;
use moveit::New;

#[repr(C)]
pub struct Foo {
// opaque
_pin: PhantomData<PhantomPinned>,
_data: [u32; 4],
}

unsafe impl ExternType for Foo {
type Id = type_id!("Foo");
type Kind = Opaque;
}

unsafe impl MakeCppStorage for Foo {
unsafe fn allocate_uninitialized_cpp_storage() -> *mut Self {
let foo = super::ffi::CreateUninitializedFoo();
assert_eq!(foo.as_ref().unwrap().get_status(), super::UNINITIALIZED);
foo
}

unsafe fn free_uninitialized_cpp_storage(ptr: *mut Self) {
super::ffi::FreeUninitializedFoo(ptr);
}
}

impl Foo {
pub fn new() -> impl New<Output = Self> {
unsafe {
moveit::new::by_raw(|space| {
// TODO can we get rid of the transmute?
let space = std::mem::transmute(space);
super::ffi::foo_constructor(space)
})
}
}
}
}

#[test]
fn test_stack_emplacement() {
moveit! {
let mut foo = bindgenish::Foo::new();
}
assert_eq!(foo.get_status(), INITIALIZED);
foo.as_mut().modify();
assert_eq!(foo.get_status(), METHOD_CALLED);
}

#[test]
fn test_box_emplacement() {
let mut foo = Box::emplace(bindgenish::Foo::new());
assert_eq!(foo.get_status(), INITIALIZED);
foo.as_mut().modify();
assert_eq!(foo.get_status(), METHOD_CALLED);
}

#[test]
fn test_unique_ptr_emplacement() {
let mut foo = UniquePtr::emplace(bindgenish::Foo::new());
assert_eq!(foo.get_status(), INITIALIZED);
foo.pin_mut().modify();
assert_eq!(foo.get_status(), METHOD_CALLED);
}
14 changes: 7 additions & 7 deletions src/alloc_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use alloc::sync::Arc;

use crate::move_ref::DerefMove;
use crate::move_ref::MoveRef;
use crate::new::Emplace;
use crate::new::EmplaceUnpinned;
use crate::new::TryNew;
use crate::slot::DroppingSlot;

Expand All @@ -46,8 +46,8 @@ unsafe impl<T> DerefMove for Box<T> {
}
}

impl<T> Emplace<T> for Box<T> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Pin<Self>, N::Error> {
impl<T> EmplaceUnpinned<T> for Pin<Box<T>> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Self, N::Error> {
let mut uninit = Box::new(MaybeUninit::<T>::uninit());
unsafe {
let pinned = Pin::new_unchecked(&mut *uninit);
Expand All @@ -59,8 +59,8 @@ impl<T> Emplace<T> for Box<T> {
}
}

impl<T> Emplace<T> for Rc<T> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Pin<Self>, N::Error> {
impl<T> EmplaceUnpinned<T> for Pin<Rc<T>> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Self, N::Error> {
let uninit = Rc::new(MaybeUninit::<T>::uninit());
unsafe {
let pinned = Pin::new_unchecked(&mut *(Rc::as_ptr(&uninit) as *mut _));
Expand All @@ -72,8 +72,8 @@ impl<T> Emplace<T> for Rc<T> {
}
}

impl<T> Emplace<T> for Arc<T> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Pin<Self>, N::Error> {
impl<T> EmplaceUnpinned<T> for Pin<Arc<T>> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Self, N::Error> {
let uninit = Arc::new(MaybeUninit::<T>::uninit());
unsafe {
let pinned = Pin::new_unchecked(&mut *(Arc::as_ptr(&uninit) as *mut _));
Expand Down
77 changes: 77 additions & 0 deletions src/cxx_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Support for `cxx` types.
use std::mem::MaybeUninit;
use std::pin::Pin;

use cxx::memory::UniquePtrTarget;
use cxx::UniquePtr;

use crate::EmplaceUnpinned;
use crate::TryNew;

/// A type which has the ability to create heap storage space
/// for itself in C++, without initializing that storage.
///
/// # Safety
///
/// Implementers must ensure that the pointer returned by
/// `allocate_uninitialized_cpp_storage` is a valid, non-null,
/// pointer to a new but uninitialized storage block, and that
/// such blocks must be freeable using either of these routes:
///
/// * before they're initialized, using `free_uninitialized_cpp_storage`
/// * after they're initialized, via a delete expression like `delete p;`
pub unsafe trait MakeCppStorage: Sized {
/// Allocates heap space for this type in C++ and return a pointer
/// to that space, but do not initialize that space (i.e. do not
/// yet call a constructor).
///
/// # Safety
///
/// To avoid memory leaks, callers must ensure that this space is
/// freed using `free_uninitialized_cpp_storage`, or is converted into
/// a [`UniquePtr`] such that it can later be freed by
/// `std::unique_ptr<T, std::default_delete<T>>`.
unsafe fn allocate_uninitialized_cpp_storage() -> *mut Self;

/// Frees a C++ allocation which has not yet
/// had a constructor called.
///
/// # Safety
///
/// Callers guarantee that the pointer here was allocated by
/// `allocate_uninitialized_cpp_storage` and has not been
/// initialized.
unsafe fn free_uninitialized_cpp_storage(ptr: *mut Self);
}

impl<T: MakeCppStorage + UniquePtrTarget> EmplaceUnpinned<T> for UniquePtr<T> {
fn try_emplace<N: TryNew<Output = T>>(n: N) -> Result<Self, N::Error> {
unsafe {
let uninit_ptr = T::allocate_uninitialized_cpp_storage();
let uninit =
Pin::new_unchecked(&mut *(uninit_ptr as *mut MaybeUninit<T>));
// FIXME - this is not panic safe.
let result = n.try_new(uninit);
if let Err(err) = result {
T::free_uninitialized_cpp_storage(uninit_ptr);
return Err(err);
}
Ok(UniquePtr::from_raw(uninit_ptr))
}
}
}
Loading

0 comments on commit 9ea3128

Please sign in to comment.