Skip to content

Commit a243a10

Browse files
authored
Merge pull request #160 from andersk/from-box
Implement From<Box<T>> for Gc<T>
2 parents 1adb281 + 743271e commit a243a10

File tree

3 files changed

+167
-58
lines changed

3 files changed

+167
-58
lines changed

gc/src/gc.rs

Lines changed: 112 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
use crate::set_data_ptr;
12
use crate::trace::Trace;
3+
use std::alloc::{alloc, dealloc, Layout};
24
use std::cell::{Cell, RefCell};
35
use std::mem;
46
use std::ptr::{self, NonNull};
57

8+
#[cfg(feature = "nightly")]
9+
use std::marker::Unsize;
10+
611
struct GcState {
712
stats: GcStats,
813
config: GcConfig,
9-
boxes_start: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
14+
boxes_start: Option<NonNull<GcBox<dyn Trace>>>,
1015
}
1116

1217
impl Drop for GcState {
@@ -43,7 +48,7 @@ pub fn finalizer_safe() -> bool {
4348
thread_local!(static GC_STATE: RefCell<GcState> = RefCell::new(GcState {
4449
stats: GcStats::default(),
4550
config: GcConfig::default(),
46-
boxes_start: Cell::new(None),
51+
boxes_start: None,
4752
}));
4853

4954
const MARK_MASK: usize = 1 << (usize::BITS - 1);
@@ -57,10 +62,10 @@ pub(crate) struct GcBoxHeader {
5762

5863
impl GcBoxHeader {
5964
#[inline]
60-
pub fn new(next: Option<NonNull<GcBox<dyn Trace>>>) -> Self {
65+
pub fn new() -> Self {
6166
GcBoxHeader {
6267
roots: Cell::new(1), // unmarked and roots count = 1
63-
next: Cell::new(next),
68+
next: Cell::new(None),
6469
}
6570
}
6671

@@ -103,51 +108,113 @@ impl GcBoxHeader {
103108
}
104109
}
105110

106-
#[repr(C)] // to justify the layout computation in Gc::from_raw
111+
#[repr(C)] // to justify the layout computations in GcBox::from_box, Gc::from_raw
107112
pub(crate) struct GcBox<T: Trace + ?Sized + 'static> {
108113
header: GcBoxHeader,
109114
data: T,
110115
}
111116

112117
impl<T: Trace> GcBox<T> {
113118
/// Allocates a garbage collected `GcBox` on the heap,
114-
/// and appends it to the thread-local `GcBox` chain.
119+
/// and appends it to the thread-local `GcBox` chain. This might
120+
/// trigger a collection.
115121
///
116122
/// A `GcBox` allocated this way starts its life rooted.
117123
pub(crate) fn new(value: T) -> NonNull<Self> {
118-
GC_STATE.with(|st| {
119-
let mut st = st.borrow_mut();
120-
121-
// XXX We should probably be more clever about collecting
122-
if st.stats.bytes_allocated > st.config.threshold {
123-
collect_garbage(&mut st);
124-
125-
if st.stats.bytes_allocated as f64
126-
> st.config.threshold as f64 * st.config.used_space_ratio
127-
{
128-
// we didn't collect enough, so increase the
129-
// threshold for next time, to avoid thrashing the
130-
// collector too much/behaving quadratically.
131-
st.config.threshold =
132-
(st.stats.bytes_allocated as f64 / st.config.used_space_ratio) as usize;
133-
}
124+
let gcbox = NonNull::from(Box::leak(Box::new(GcBox {
125+
header: GcBoxHeader::new(),
126+
data: value,
127+
})));
128+
unsafe { insert_gcbox(gcbox) };
129+
gcbox
130+
}
131+
}
132+
133+
impl<
134+
#[cfg(not(feature = "nightly"))] T: Trace,
135+
#[cfg(feature = "nightly")] T: Trace + Unsize<dyn Trace> + ?Sized,
136+
> GcBox<T>
137+
{
138+
/// Consumes a `Box`, moving the value inside into a new `GcBox`
139+
/// on the heap. Adds the new `GcBox` to the thread-local `GcBox`
140+
/// chain. This might trigger a collection.
141+
///
142+
/// A `GcBox` allocated this way starts its life rooted.
143+
pub(crate) fn from_box(value: Box<T>) -> NonNull<Self> {
144+
let header_layout = Layout::new::<GcBoxHeader>();
145+
let value_layout = Layout::for_value::<T>(&*value);
146+
// This relies on GcBox being #[repr(C)].
147+
let gcbox_layout = header_layout.extend(value_layout).unwrap().0.pad_to_align();
148+
149+
unsafe {
150+
// Allocate the GcBox in a way that's compatible with Box,
151+
// since the collector will deallocate it via
152+
// Box::from_raw.
153+
let gcbox_addr = alloc(gcbox_layout);
154+
155+
// Since we're not allowed to move the value out of an
156+
// active Box, and we will need to deallocate the Box
157+
// without calling the destructor, convert it to a raw
158+
// pointer first.
159+
let value = Box::into_raw(value);
160+
161+
// Create a pointer with the metadata of value and the
162+
// address and provenance of the GcBox.
163+
let gcbox = set_data_ptr(value as *mut GcBox<T>, gcbox_addr);
164+
165+
// Move the data.
166+
ptr::addr_of_mut!((*gcbox).header).write(GcBoxHeader::new());
167+
ptr::addr_of_mut!((*gcbox).data)
168+
.cast::<u8>()
169+
.copy_from_nonoverlapping(value.cast::<u8>(), value_layout.size());
170+
171+
// Deallocate the former Box. (Box only allocates for size
172+
// != 0.)
173+
if value_layout.size() != 0 {
174+
dealloc(value.cast::<u8>(), value_layout);
134175
}
135176

136-
let gcbox = Box::into_raw(Box::new(GcBox {
137-
header: GcBoxHeader::new(st.boxes_start.take()),
138-
data: value,
139-
}));
177+
// Add the new GcBox to the chain and return it.
178+
let gcbox = NonNull::new_unchecked(gcbox);
179+
insert_gcbox(gcbox);
180+
gcbox
181+
}
182+
}
183+
}
184+
185+
/// Add a new `GcBox` to the current thread's `GcBox` chain. This
186+
/// might trigger a collection first if enough bytes have been
187+
/// allocated since the previous collection.
188+
///
189+
/// # Safety
190+
///
191+
/// `gcbox` must point to a valid `GcBox` that is not yet in a `GcBox`
192+
/// chain.
193+
unsafe fn insert_gcbox(gcbox: NonNull<GcBox<dyn Trace>>) {
194+
GC_STATE.with(|st| {
195+
let mut st = st.borrow_mut();
140196

141-
st.boxes_start
142-
.set(Some(unsafe { NonNull::new_unchecked(gcbox) }));
197+
// XXX We should probably be more clever about collecting
198+
if st.stats.bytes_allocated > st.config.threshold {
199+
collect_garbage(&mut st);
200+
201+
if st.stats.bytes_allocated as f64
202+
> st.config.threshold as f64 * st.config.used_space_ratio
203+
{
204+
// we didn't collect enough, so increase the
205+
// threshold for next time, to avoid thrashing the
206+
// collector too much/behaving quadratically.
207+
st.config.threshold =
208+
(st.stats.bytes_allocated as f64 / st.config.used_space_ratio) as usize;
209+
}
210+
}
143211

144-
// We allocated some bytes! Let's record it
145-
st.stats.bytes_allocated += mem::size_of::<GcBox<T>>();
212+
let next = st.boxes_start.replace(gcbox);
213+
gcbox.as_ref().header.next.set(next);
146214

147-
// Return the pointer to the newly allocated data
148-
unsafe { NonNull::new_unchecked(gcbox) }
149-
})
150-
}
215+
// We allocated some bytes! Let's record it
216+
st.stats.bytes_allocated += mem::size_of_val::<GcBox<_>>(gcbox.as_ref());
217+
});
151218
}
152219

153220
impl<T: Trace + ?Sized> GcBox<T> {
@@ -199,35 +266,35 @@ fn collect_garbage(st: &mut GcState) {
199266
// Walk the tree, tracing and marking the nodes
200267
let mut mark_head = head.get();
201268
while let Some(node) = mark_head {
202-
if (*node.as_ptr()).header.roots() > 0 {
203-
(*node.as_ptr()).trace_inner();
269+
if node.as_ref().header.roots() > 0 {
270+
node.as_ref().trace_inner();
204271
}
205272

206-
mark_head = (*node.as_ptr()).header.next.get();
273+
mark_head = node.as_ref().header.next.get();
207274
}
208275

209276
// Collect a vector of all of the nodes which were not marked,
210277
// and unmark the ones which were.
211278
let mut unmarked = Vec::new();
212279
let mut unmark_head = head;
213280
while let Some(node) = unmark_head.get() {
214-
if (*node.as_ptr()).header.is_marked() {
215-
(*node.as_ptr()).header.unmark();
281+
if node.as_ref().header.is_marked() {
282+
node.as_ref().header.unmark();
216283
} else {
217284
unmarked.push(Unmarked {
218285
incoming: unmark_head,
219286
this: node,
220287
});
221288
}
222-
unmark_head = &(*node.as_ptr()).header.next;
289+
unmark_head = &node.as_ref().header.next;
223290
}
224291
unmarked
225292
}
226293

227294
unsafe fn sweep(finalized: Vec<Unmarked<'_>>, bytes_allocated: &mut usize) {
228295
let _guard = DropGuard::new();
229296
for node in finalized.into_iter().rev() {
230-
if (*node.this.as_ptr()).header.is_marked() {
297+
if node.this.as_ref().header.is_marked() {
231298
continue;
232299
}
233300
let incoming = node.incoming;
@@ -240,14 +307,15 @@ fn collect_garbage(st: &mut GcState) {
240307
st.stats.collections_performed += 1;
241308

242309
unsafe {
243-
let unmarked = mark(&st.boxes_start);
310+
let head = Cell::from_mut(&mut st.boxes_start);
311+
let unmarked = mark(head);
244312
if unmarked.is_empty() {
245313
return;
246314
}
247315
for node in &unmarked {
248-
Trace::finalize_glue(&(*node.this.as_ptr()).data);
316+
Trace::finalize_glue(&node.this.as_ref().data);
249317
}
250-
mark(&st.boxes_start);
318+
mark(head);
251319
sweep(unmarked, &mut st.stats.bytes_allocated);
252320
}
253321
}

gc/src/lib.rs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,30 @@ impl<T: Trace> Gc<T> {
7272
/// assert_eq!(*five, 5);
7373
/// ```
7474
pub fn new(value: T) -> Self {
75-
assert!(mem::align_of::<GcBox<T>>() > 1);
75+
unsafe { Gc::from_gcbox(GcBox::new(value)) }
76+
}
77+
}
7678

77-
unsafe {
78-
// Allocate the memory for the object
79-
let ptr = GcBox::new(value);
79+
impl<T: Trace + ?Sized> Gc<T> {
80+
/// Constructs a `Gc` that points to a new `GcBox`.
81+
///
82+
/// # Safety
83+
///
84+
/// `ptr` must point to a valid `GcBox` on the thread-local
85+
/// `GcBox` chain.
86+
#[inline]
87+
unsafe fn from_gcbox(ptr: NonNull<GcBox<T>>) -> Gc<T> {
88+
assert!(mem::align_of_val::<GcBox<T>>(ptr.as_ref()) > 1);
8089

81-
// When we create a Gc<T>, all pointers which have been moved to the
82-
// heap no longer need to be rooted, so we unroot them.
83-
(*ptr.as_ptr()).value().unroot();
84-
let gc = Gc {
85-
ptr_root: Cell::new(NonNull::new_unchecked(ptr.as_ptr())),
86-
marker: PhantomData,
87-
};
88-
gc.set_root();
89-
gc
90-
}
90+
// When we create a Gc<T>, all pointers which have been moved to the
91+
// heap no longer need to be rooted, so we unroot them.
92+
ptr.as_ref().value().unroot();
93+
let gc = Gc {
94+
ptr_root: Cell::new(ptr),
95+
marker: PhantomData,
96+
};
97+
gc.set_root();
98+
gc
9199
}
92100
}
93101

@@ -371,6 +379,19 @@ impl<T: Trace> From<T> for Gc<T> {
371379
}
372380
}
373381

382+
impl<
383+
#[cfg(not(feature = "nightly"))] T: Trace,
384+
#[cfg(feature = "nightly")] T: Trace + Unsize<dyn Trace> + ?Sized,
385+
> From<Box<T>> for Gc<T>
386+
{
387+
/// Moves a boxed value into a new garbage-collected
388+
/// allocation. If the `nightly` crate feature is enabled, the
389+
/// value may be an unsized trait object.
390+
fn from(v: Box<T>) -> Gc<T> {
391+
unsafe { Gc::from_gcbox(GcBox::from_box(v)) }
392+
}
393+
}
394+
374395
impl<T: Trace + ?Sized> std::borrow::Borrow<T> for Gc<T> {
375396
fn borrow(&self) -> &T {
376397
self

gc/tests/from_box.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use gc::{Finalize, Gc, Trace};
2+
3+
trait Foo: Trace {}
4+
5+
#[derive(Trace, Finalize)]
6+
struct Bar;
7+
impl Foo for Bar {}
8+
9+
#[test]
10+
fn test_from_box_sized() {
11+
let b: Box<[i32; 3]> = Box::new([1, 2, 3]);
12+
let _: Gc<[i32; 3]> = Gc::from(b);
13+
}
14+
15+
#[cfg(feature = "nightly")]
16+
#[test]
17+
fn test_from_box_dyn() {
18+
let b: Box<dyn Foo> = Box::new(Bar);
19+
let _: Gc<dyn Foo> = Gc::from(b);
20+
}

0 commit comments

Comments
 (0)