Skip to content

Commit

Permalink
Implement From<Box<T>> for Gc<T>
Browse files Browse the repository at this point in the history
This is especially useful on nightly, where it works for unsized trait
objects.

Signed-off-by: Anders Kaseorg <[email protected]>
  • Loading branch information
andersk committed Dec 22, 2022
1 parent 5d6a7a1 commit 743271e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 1 deletion.
59 changes: 58 additions & 1 deletion gc/src/gc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use crate::set_data_ptr;
use crate::trace::Trace;
use std::alloc::{alloc, dealloc, Layout};
use std::cell::{Cell, RefCell};
use std::mem;
use std::ptr::{self, NonNull};

#[cfg(feature = "nightly")]
use std::marker::Unsize;

struct GcState {
stats: GcStats,
config: GcConfig,
Expand Down Expand Up @@ -103,7 +108,7 @@ impl GcBoxHeader {
}
}

#[repr(C)] // to justify the layout computation in Gc::from_raw
#[repr(C)] // to justify the layout computations in GcBox::from_box, Gc::from_raw
pub(crate) struct GcBox<T: Trace + ?Sized + 'static> {
header: GcBoxHeader,
data: T,
Expand All @@ -125,6 +130,58 @@ impl<T: Trace> GcBox<T> {
}
}

impl<
#[cfg(not(feature = "nightly"))] T: Trace,
#[cfg(feature = "nightly")] T: Trace + Unsize<dyn Trace> + ?Sized,
> GcBox<T>
{
/// Consumes a `Box`, moving the value inside into a new `GcBox`
/// on the heap. Adds the new `GcBox` to the thread-local `GcBox`
/// chain. This might trigger a collection.
///
/// A `GcBox` allocated this way starts its life rooted.
pub(crate) fn from_box(value: Box<T>) -> NonNull<Self> {
let header_layout = Layout::new::<GcBoxHeader>();
let value_layout = Layout::for_value::<T>(&*value);
// This relies on GcBox being #[repr(C)].
let gcbox_layout = header_layout.extend(value_layout).unwrap().0.pad_to_align();

unsafe {
// Allocate the GcBox in a way that's compatible with Box,
// since the collector will deallocate it via
// Box::from_raw.
let gcbox_addr = alloc(gcbox_layout);

// Since we're not allowed to move the value out of an
// active Box, and we will need to deallocate the Box
// without calling the destructor, convert it to a raw
// pointer first.
let value = Box::into_raw(value);

// Create a pointer with the metadata of value and the
// address and provenance of the GcBox.
let gcbox = set_data_ptr(value as *mut GcBox<T>, gcbox_addr);

// Move the data.
ptr::addr_of_mut!((*gcbox).header).write(GcBoxHeader::new());
ptr::addr_of_mut!((*gcbox).data)
.cast::<u8>()
.copy_from_nonoverlapping(value.cast::<u8>(), value_layout.size());

// Deallocate the former Box. (Box only allocates for size
// != 0.)
if value_layout.size() != 0 {
dealloc(value.cast::<u8>(), value_layout);
}

// Add the new GcBox to the chain and return it.
let gcbox = NonNull::new_unchecked(gcbox);
insert_gcbox(gcbox);
gcbox
}
}
}

/// Add a new `GcBox` to the current thread's `GcBox` chain. This
/// might trigger a collection first if enough bytes have been
/// allocated since the previous collection.
Expand Down
13 changes: 13 additions & 0 deletions gc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,19 @@ impl<T: Trace> From<T> for Gc<T> {
}
}

impl<
#[cfg(not(feature = "nightly"))] T: Trace,
#[cfg(feature = "nightly")] T: Trace + Unsize<dyn Trace> + ?Sized,
> From<Box<T>> for Gc<T>
{
/// Moves a boxed value into a new garbage-collected
/// allocation. If the `nightly` crate feature is enabled, the
/// value may be an unsized trait object.
fn from(v: Box<T>) -> Gc<T> {
unsafe { Gc::from_gcbox(GcBox::from_box(v)) }
}
}

impl<T: Trace + ?Sized> std::borrow::Borrow<T> for Gc<T> {
fn borrow(&self) -> &T {
self
Expand Down
20 changes: 20 additions & 0 deletions gc/tests/from_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use gc::{Finalize, Gc, Trace};

trait Foo: Trace {}

#[derive(Trace, Finalize)]
struct Bar;
impl Foo for Bar {}

#[test]
fn test_from_box_sized() {
let b: Box<[i32; 3]> = Box::new([1, 2, 3]);
let _: Gc<[i32; 3]> = Gc::from(b);
}

#[cfg(feature = "nightly")]
#[test]
fn test_from_box_dyn() {
let b: Box<dyn Foo> = Box::new(Bar);
let _: Gc<dyn Foo> = Gc::from(b);
}

0 comments on commit 743271e

Please sign in to comment.