1+ use crate :: set_data_ptr;
12use crate :: trace:: Trace ;
3+ use std:: alloc:: { alloc, dealloc, Layout } ;
24use std:: cell:: { Cell , RefCell } ;
35use std:: mem;
46use std:: ptr:: { self , NonNull } ;
57
8+ #[ cfg( feature = "nightly" ) ]
9+ use std:: marker:: Unsize ;
10+
611struct 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
1217impl Drop for GcState {
@@ -43,7 +48,7 @@ pub fn finalizer_safe() -> bool {
4348thread_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
4954const MARK_MASK : usize = 1 << ( usize:: BITS - 1 ) ;
@@ -57,10 +62,10 @@ pub(crate) struct GcBoxHeader {
5762
5863impl 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
107112pub ( crate ) struct GcBox < T : Trace + ?Sized + ' static > {
108113 header : GcBoxHeader ,
109114 data : T ,
110115}
111116
112117impl < 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
153220impl < 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}
0 commit comments