Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a weak type Gc pointer #148

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
256 changes: 172 additions & 84 deletions gc/src/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::cell::{Cell, RefCell};
use std::mem;
use std::ptr::{self, NonNull};

struct GcState {
pub(crate) struct GcState {
stats: GcStats,
config: GcConfig,
boxes_start: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
Expand Down Expand Up @@ -45,12 +45,19 @@ thread_local!(static GC_STATE: RefCell<GcState> = RefCell::new(GcState {
boxes_start: Cell::new(None),
}));

pub enum GcBoxType {
Standard,
Weak,
Ephemeron,
}

const MARK_MASK: usize = 1 << (usize::BITS - 1);
const ROOTS_MASK: usize = !MARK_MASK;
const ROOTS_MAX: usize = ROOTS_MASK; // max allowed value of roots

pub(crate) struct GcBoxHeader {
roots: Cell<usize>, // high bit is used as mark flag
ephemeron_flag: Cell<bool>,
next: Cell<Option<NonNull<GcBox<dyn Trace>>>>,
}

Expand All @@ -59,6 +66,25 @@ impl GcBoxHeader {
pub fn new(next: Option<NonNull<GcBox<dyn Trace>>>) -> Self {
GcBoxHeader {
roots: Cell::new(1), // unmarked and roots count = 1
ephemeron_flag: Cell::new(false),
next: Cell::new(next),
}
}

#[inline]
pub fn new_ephemeron(next: Option<NonNull<GcBox<dyn Trace>>>) -> Self {
GcBoxHeader {
roots: Cell::new(0),
ephemeron_flag: Cell::new(true),
next: Cell::new(next),
}
}

#[inline]
pub fn new_weak(next: Option<NonNull<GcBox<dyn Trace>>>) -> Self {
GcBoxHeader {
roots: Cell::new(0),
ephemeron_flag: Cell::new(false),
next: Cell::new(next),
}
}
Expand Down Expand Up @@ -100,20 +126,73 @@ impl GcBoxHeader {
pub fn unmark(&self) {
self.roots.set(self.roots.get() & !MARK_MASK)
}

#[inline]
pub fn is_ephemeron(&self) -> bool {
self.ephemeron_flag.get()
}
}

#[repr(C)] // to justify the layout computation in Gc::from_raw
pub(crate) struct GcBox<T: Trace + ?Sized + 'static> {
pub struct GcBox<T: Trace + ?Sized + 'static> {
header: GcBoxHeader,
data: T,
}

impl<T: Trace + ?Sized> GcBox<T> {
/// Returns `true` if the two references refer to the same `GcBox`.
pub(crate) fn ptr_eq(this: &GcBox<T>, other: &GcBox<T>) -> bool {
// Use .header to ignore fat pointer vtables, to work around
// https://github.com/rust-lang/rust/issues/46139
ptr::eq(&this.header, &other.header)
}

/// Marks this `GcBox` and marks through its data.
pub(crate) unsafe fn trace_inner(&self) {
if !self.header.is_marked() && !self.header.is_ephemeron() {
self.header.mark();
self.data.trace();
}
}

/// Trace inner data
pub(crate) unsafe fn weak_trace_inner(&self, queue: &mut Vec<NonNull<GcBox<dyn Trace>>>) {
self.data.weak_trace(queue);
}

/// Increases the root count on this `GcBox`.
/// Roots prevent the `GcBox` from being destroyed by the garbage collector.
pub(crate) unsafe fn root_inner(&self) {
self.header.inc_roots();
}

/// Decreases the root count on this `GcBox`.
/// Roots prevent the `GcBox` from being destroyed by the garbage collector.
pub(crate) unsafe fn unroot_inner(&self) {
self.header.dec_roots();
}

/// Returns a pointer to the `GcBox`'s value, without dereferencing it.
pub(crate) fn value_ptr(this: *const GcBox<T>) -> *const T {
unsafe { ptr::addr_of!((*this).data) }
}

/// Returns a reference to the `GcBox`'s value.
pub(crate) fn value(&self) -> &T {
&self.data
}

pub(crate) fn is_marked(&self) -> bool {
self.header.is_marked()
}
}

impl<T: Trace> GcBox<T> {
/// Allocates a garbage collected `GcBox` on the heap,
/// and appends it to the thread-local `GcBox` chain.
///
/// A `GcBox` allocated this way starts its life rooted.
pub(crate) fn new(value: T) -> NonNull<Self> {
pub(crate) fn new(value: T, box_type: GcBoxType) -> NonNull<Self> {
GC_STATE.with(|st| {
let mut st = st.borrow_mut();

Expand All @@ -132,8 +211,14 @@ impl<T: Trace> GcBox<T> {
}
}

let header = match box_type {
GcBoxType::Standard => GcBoxHeader::new(st.boxes_start.take()),
GcBoxType::Weak => GcBoxHeader::new_weak(st.boxes_start.take()),
GcBoxType::Ephemeron => GcBoxHeader::new_ephemeron(st.boxes_start.take()),
};

let gcbox = Box::into_raw(Box::new(GcBox {
header: GcBoxHeader::new(st.boxes_start.take()),
header,
data: value,
}));

Expand All @@ -149,105 +234,108 @@ impl<T: Trace> GcBox<T> {
}
}

impl<T: Trace + ?Sized> GcBox<T> {
/// Returns `true` if the two references refer to the same `GcBox`.
pub(crate) fn ptr_eq(this: &GcBox<T>, other: &GcBox<T>) -> bool {
// Use .header to ignore fat pointer vtables, to work around
// https://github.com/rust-lang/rust/issues/46139
ptr::eq(&this.header, &other.header)
}

/// Marks this `GcBox` and marks through its data.
pub(crate) unsafe fn trace_inner(&self) {
if !self.header.is_marked() {
self.header.mark();
self.data.trace();
}
}

/// Increases the root count on this `GcBox`.
/// Roots prevent the `GcBox` from being destroyed by the garbage collector.
pub(crate) unsafe fn root_inner(&self) {
self.header.inc_roots();
}

/// Decreases the root count on this `GcBox`.
/// Roots prevent the `GcBox` from being destroyed by the garbage collector.
pub(crate) unsafe fn unroot_inner(&self) {
self.header.dec_roots();
}

/// Returns a pointer to the `GcBox`'s value, without dereferencing it.
pub(crate) fn value_ptr(this: *const GcBox<T>) -> *const T {
unsafe { ptr::addr_of!((*this).data) }
}

/// Returns a reference to the `GcBox`'s value.
pub(crate) fn value(&self) -> &T {
&self.data
}
}

/// Collects garbage.
fn collect_garbage(st: &mut GcState) {
st.stats.collections_performed += 1;

struct Unmarked<'a> {
incoming: &'a Cell<Option<NonNull<GcBox<dyn Trace>>>>,
this: NonNull<GcBox<dyn Trace>>,
}
unsafe fn mark(head: &Cell<Option<NonNull<GcBox<dyn Trace>>>>) -> Vec<Unmarked<'_>> {
unsafe fn mark(
head: &Cell<Option<NonNull<GcBox<dyn Trace>>>>,
) -> Vec<NonNull<GcBox<dyn Trace>>> {
// Walk the tree, tracing and marking the nodes
let mut mark_head = head.get();
while let Some(node) = mark_head {
if (*node.as_ptr()).header.roots() > 0 {
(*node.as_ptr()).trace_inner();
let mut finalize = Vec::new();
let mut ephemeron_queue = Vec::new();
let mut mark_head = head;
while let Some(node) = mark_head.get() {
if (*node.as_ptr()).header.is_ephemeron() {
ephemeron_queue.push(node);
} else {
if (*node.as_ptr()).header.roots() > 0 {
(*node.as_ptr()).trace_inner();
} else {
finalize.push(node)
}
}
mark_head = &(*node.as_ptr()).header.next;
}

mark_head = (*node.as_ptr()).header.next.get();
// Ephemeron Evaluation
if !ephemeron_queue.is_empty() {
loop {
let mut reachable_nodes = Vec::new();
let mut other_nodes = Vec::new();
// iterate through ephemeron queue, sorting nodes by whether they
// are reachable or unreachable<?>
for node in ephemeron_queue {
if (*node.as_ptr()).data.is_marked_ephemeron() {
(*node.as_ptr()).header.mark();
reachable_nodes.push(node);
} else {
other_nodes.push(node);
}
}
// Replace the old queue with the unreachable<?>
ephemeron_queue = other_nodes;

// If reachable nodes is not empty, trace values. If it is empty,
// break from the loop
if !reachable_nodes.is_empty() {
// iterate through reachable nodes and trace their values,
// enqueuing any ephemeron that is found during the trace
for node in reachable_nodes {
(*node.as_ptr()).weak_trace_inner(&mut ephemeron_queue)
}
} else {
break;
}
}
}

// Collect a vector of all of the nodes which were not marked,
// and unmark the ones which were.
let mut unmarked = Vec::new();
let mut unmark_head = head;
while let Some(node) = unmark_head.get() {
if (*node.as_ptr()).header.is_marked() {
(*node.as_ptr()).header.unmark();
} else {
unmarked.push(Unmarked {
incoming: unmark_head,
this: node,
});
// Any left over nodes in the ephemeron queue at this point are
// unreachable and need to be notified/finalized.
finalize.extend(ephemeron_queue);

finalize
}

unsafe fn finalize(finalize_vec: Vec<NonNull<GcBox<dyn Trace>>>) {
for node in finalize_vec {
// We double check that the unreachable nodes are actually unreachable
// prior to finalization as they could have been marked by a different
// trace after initially being added to the queue
if !(*node.as_ptr()).header.is_marked() {
Trace::finalize_glue(&(*node.as_ptr()).data)
}
unmark_head = &(*node.as_ptr()).header.next;
}
unmarked
}

unsafe fn sweep(finalized: Vec<Unmarked<'_>>, bytes_allocated: &mut usize) {
unsafe fn sweep(head: &Cell<Option<NonNull<GcBox<dyn Trace>>>>, bytes_allocated: &mut usize) {
let _guard = DropGuard::new();
for node in finalized.into_iter().rev() {
if (*node.this.as_ptr()).header.is_marked() {
continue;

let mut sweep_head = head;
while let Some(node) = sweep_head.get() {
if (*node.as_ptr()).header.is_marked() {
(*node.as_ptr()).header.unmark();
sweep_head = &(*node.as_ptr()).header.next;
} else {
let unmarked_node = Box::from_raw(node.as_ptr());
*bytes_allocated -= mem::size_of_val::<GcBox<_>>(&*unmarked_node);
sweep_head.set(unmarked_node.header.next.take());
}
let incoming = node.incoming;
let node = Box::from_raw(node.this.as_ptr());
*bytes_allocated -= mem::size_of_val::<GcBox<_>>(&*node);
incoming.set(node.header.next.take());
}
}

unsafe {
let unmarked = mark(&st.boxes_start);
if unmarked.is_empty() {
return;
}
for node in &unmarked {
Trace::finalize_glue(&(*node.this.as_ptr()).data);
}
mark(&st.boxes_start);
sweep(unmarked, &mut st.stats.bytes_allocated);
// Run mark and return vector of nonreachable porperties
let unreachable_nodes = mark(&st.boxes_start);
// Finalize the unreachable properties
finalize(unreachable_nodes);
// Run mark again to mark any nodes that are resurrected by their finalizer
//
// At this point, _f should be filled with all nodes that are unreachable and
// have already been finalized, so they can be ignored.
let _f = mark(&st.boxes_start);
// Run sweep: unmarking all marked nodes and freeing any unmarked nodes
sweep(&st.boxes_start, &mut st.stats.bytes_allocated);
}
}

Expand Down
Loading