Skip to content

Commit ec4dd9f

Browse files
committed
Clarify allocation ownership model
1 parent 9b05709 commit ec4dd9f

File tree

1 file changed

+42
-49
lines changed

1 file changed

+42
-49
lines changed

gc/src/gc.rs

+42-49
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,24 @@ const USED_SPACE_RATIO: f64 = 0.7;
1414
struct GcState {
1515
bytes_allocated: usize,
1616
threshold: usize,
17-
boxes_start: Option<NonNull<GcBox<dyn Trace>>>,
17+
boxes_start: Option<Box<GcBox<dyn Trace>>>,
1818
}
1919

2020
impl Drop for GcState {
2121
fn drop(&mut self) {
22-
unsafe {
23-
{
24-
let mut p = &self.boxes_start;
25-
while let Some(node) = *p {
26-
Finalize::finalize(&(*node.as_ptr()).data);
27-
p = &(*node.as_ptr()).header.next;
28-
}
29-
}
22+
let mut head = &self.boxes_start;
23+
while let Some(ref node) = *head {
24+
Finalize::finalize(&node.data);
25+
head = &node.header.next;
26+
}
3027

31-
let _guard = DropGuard::new();
32-
while let Some(node) = self.boxes_start {
33-
let node = Box::from_raw(node.as_ptr());
34-
self.boxes_start = node.header.next;
35-
}
28+
// Drop all allocations in the singly-linked list.
29+
// This could be done with `self.boxes_start = None;`,
30+
// but that might lead to a large number of recursive drops.
31+
let _guard = DropGuard::new();
32+
let mut head = self.boxes_start.take();
33+
while let Some(mut node) = head {
34+
head = node.header.next.take();
3635
}
3736
}
3837
}
@@ -68,7 +67,7 @@ pub(crate) struct GcBoxHeader {
6867
// We are using a word word bool - there is a full 63 bits of unused data :(
6968
// XXX: Should be able to store marked in the high bit of roots?
7069
roots: Cell<usize>,
71-
next: Option<NonNull<GcBox<dyn Trace>>>,
70+
next: Option<Box<GcBox<dyn Trace>>>,
7271
marked: Cell<bool>,
7372
}
7473

@@ -98,22 +97,23 @@ impl<T: Trace> GcBox<T> {
9897
}
9998
}
10099

101-
let gcbox = Box::into_raw(Box::new(GcBox {
100+
let gcbox = Box::new(GcBox {
102101
header: GcBoxHeader {
103102
roots: Cell::new(1),
104103
marked: Cell::new(false),
105104
next: st.boxes_start.take(),
106105
},
107106
data: value,
108-
}));
107+
});
108+
let ptr = NonNull::from(&*gcbox);
109109

110-
st.boxes_start = Some(unsafe { NonNull::new_unchecked(gcbox) });
110+
st.boxes_start = Some(gcbox);
111111

112112
// We allocated some bytes! Let's record it
113113
st.bytes_allocated += mem::size_of::<GcBox<T>>();
114114

115115
// Return the pointer to the newly allocated data
116-
unsafe { NonNull::new_unchecked(gcbox) }
116+
ptr
117117
})
118118
}
119119
}
@@ -152,49 +152,43 @@ impl<T: Trace + ?Sized> GcBox<T> {
152152

153153
/// Collects garbage.
154154
fn collect_garbage(st: &mut GcState) {
155-
struct Unmarked {
156-
incoming: *mut Option<NonNull<GcBox<dyn Trace>>>,
157-
this: NonNull<GcBox<dyn Trace>>,
158-
}
159-
unsafe fn mark(head: &mut Option<NonNull<GcBox<dyn Trace>>>) -> Vec<Unmarked> {
155+
unsafe fn mark(
156+
head: &mut Option<Box<GcBox<dyn Trace>>>,
157+
) -> Vec<Box<GcBox<dyn Trace>>> {
160158
// Walk the tree, tracing and marking the nodes
161-
let mut mark_head = *head;
162-
while let Some(node) = mark_head {
163-
if (*node.as_ptr()).header.roots.get() > 0 {
164-
(*node.as_ptr()).trace_inner();
159+
let mut mark_head = &*head;
160+
while let Some(ref node) = *mark_head {
161+
if node.header.roots.get() > 0 {
162+
node.trace_inner();
165163
}
166164

167-
mark_head = (*node.as_ptr()).header.next;
165+
mark_head = &node.header.next;
168166
}
169167

170-
// Collect a vector of all of the nodes which were not marked,
171-
// and unmark the ones which were.
168+
// Collect the unmarked nodes from the allocation list into a vector.
169+
// Also unmark the nodes which were marked, to prepare for the next GC.
172170
let mut unmarked = Vec::new();
173171
let mut unmark_head = head;
174-
while let Some(node) = *unmark_head {
175-
if (*node.as_ptr()).header.marked.get() {
176-
(*node.as_ptr()).header.marked.set(false);
172+
while let Some(mut node) = unmark_head.take() {
173+
if node.header.marked.get() {
174+
node.header.marked.set(false);
175+
// `get_or_insert()` will always re-insert `node`.
176+
// It is just used to get a reference to the next node pointer.
177+
unmark_head = &mut unmark_head.get_or_insert(node).header.next;
177178
} else {
178-
unmarked.push(Unmarked {
179-
incoming: unmark_head,
180-
this: node,
181-
});
179+
// Replace the pointer to `node` with a pointer to the next node
180+
*unmark_head = node.header.next.take();
181+
unmarked.push(node);
182182
}
183-
unmark_head = &mut (*node.as_ptr()).header.next;
184183
}
185184
unmarked
186185
}
187186

188-
unsafe fn sweep(finalized: Vec<Unmarked>, bytes_allocated: &mut usize) {
187+
unsafe fn sweep(finalized: Vec<Box<GcBox<dyn Trace>>>, bytes_allocated: &mut usize) {
189188
let _guard = DropGuard::new();
190-
for node in finalized.into_iter().rev() {
191-
if (*node.this.as_ptr()).header.marked.get() {
192-
continue;
193-
}
194-
let incoming = node.incoming;
195-
let mut node = Box::from_raw(node.this.as_ptr());
189+
for node in finalized {
196190
*bytes_allocated -= mem::size_of_val::<GcBox<_>>(&*node);
197-
*incoming = node.header.next.take();
191+
// `node` is dropped here, freeing the allocation
198192
}
199193
}
200194

@@ -204,9 +198,8 @@ fn collect_garbage(st: &mut GcState) {
204198
return;
205199
}
206200
for node in &unmarked {
207-
Trace::finalize_glue(&(*node.this.as_ptr()).data);
201+
Trace::finalize_glue(&node.data);
208202
}
209-
mark(&mut st.boxes_start);
210203
sweep(unmarked, &mut st.bytes_allocated);
211204
}
212205
}

0 commit comments

Comments
 (0)