diff --git a/gc/src/gc.rs b/gc/src/gc.rs index 7d72dbc..58cb3c2 100644 --- a/gc/src/gc.rs +++ b/gc/src/gc.rs @@ -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, @@ -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 { header: GcBoxHeader, data: T, @@ -125,6 +130,58 @@ impl GcBox { } } +impl< + #[cfg(not(feature = "nightly"))] T: Trace, + #[cfg(feature = "nightly")] T: Trace + Unsize + ?Sized, + > GcBox +{ + /// 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) -> NonNull { + let header_layout = Layout::new::(); + let value_layout = Layout::for_value::(&*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, gcbox_addr); + + // Move the data. + ptr::addr_of_mut!((*gcbox).header).write(GcBoxHeader::new()); + ptr::addr_of_mut!((*gcbox).data) + .cast::() + .copy_from_nonoverlapping(value.cast::(), value_layout.size()); + + // Deallocate the former Box. (Box only allocates for size + // != 0.) + if value_layout.size() != 0 { + dealloc(value.cast::(), 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. diff --git a/gc/src/lib.rs b/gc/src/lib.rs index 19e09a5..927705a 100644 --- a/gc/src/lib.rs +++ b/gc/src/lib.rs @@ -379,6 +379,19 @@ impl From for Gc { } } +impl< + #[cfg(not(feature = "nightly"))] T: Trace, + #[cfg(feature = "nightly")] T: Trace + Unsize + ?Sized, + > From> for Gc +{ + /// 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) -> Gc { + unsafe { Gc::from_gcbox(GcBox::from_box(v)) } + } +} + impl std::borrow::Borrow for Gc { fn borrow(&self) -> &T { self diff --git a/gc/tests/from_box.rs b/gc/tests/from_box.rs new file mode 100644 index 0000000..567b7b5 --- /dev/null +++ b/gc/tests/from_box.rs @@ -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 = Box::new(Bar); + let _: Gc = Gc::from(b); +}