use std::ptr;
use std::mem;
use std::cell::{Cell, RefCell};
use trace::Trace;
const GC_THRESHOLD: usize = 100;
struct GcState {
bytes_allocated: usize,
boxes_start: Option<Box<GcBoxTrait + 'static>>,
boxes_end: *mut Option<Box<GcBoxTrait + 'static>>,
}
thread_local!(static GC_SWEEPING: Cell<bool> = Cell::new(false));
thread_local!(static GC_STATE: RefCell<GcState> = RefCell::new(GcState {
bytes_allocated: 0,
boxes_start: None,
boxes_end: ptr::null_mut(),
}));
pub struct GcBoxHeader {
roots: Cell<usize>,
next: Option<Box<GcBoxTrait + 'static>>,
marked: Cell<bool>,
}
trait GcBoxTrait {
fn header(&self) -> &GcBoxHeader;
fn header_mut(&mut self) -> &mut GcBoxHeader;
unsafe fn trace_value(&self);
fn size_of(&self) -> usize;
}
pub struct GcBox<T: Trace + ?Sized + 'static> {
header: GcBoxHeader,
data: T,
}
impl<T: Trace> GcBox<T> {
pub fn new(value: T) -> *mut GcBox<T> {
GC_STATE.with(|_st| {
let mut st = _st.borrow_mut();
if st.bytes_allocated > GC_THRESHOLD {
collect_garbage(&mut *st);
}
let mut gcbox = Box::new(GcBox {
header: GcBoxHeader {
roots: Cell::new(1),
marked: Cell::new(false),
next: None,
},
data: value,
});
let gcbox_ptr = &mut *gcbox as *mut _;
let next_boxes_end = &mut gcbox.header.next as *mut _;
if st.boxes_end.is_null() {
assert!(st.boxes_start.is_none(),
"If something had been allocated, boxes_end would be set");
st.boxes_end = next_boxes_end;
st.boxes_start = Some(gcbox);
} else {
unsafe {
*st.boxes_end = Some(gcbox);
}
st.boxes_end = next_boxes_end;
}
st.bytes_allocated += mem::size_of::<GcBox<T>>();
gcbox_ptr
})
}
}
impl<T: Trace + ?Sized> GcBox<T> {
pub unsafe fn trace_inner(&self) {
let marked = self.header.marked.get();
if !marked {
self.header.marked.set(true);
self.data.trace();
}
}
pub unsafe fn root_inner(&self) {
self.header.roots.set(self.header.roots.get() + 1);
}
pub unsafe fn unroot_inner(&self) {
self.header.roots.set(self.header.roots.get() - 1);
}
pub fn value(&self) -> &T {
GC_SWEEPING.with(|sweeping| assert!(!sweeping.get(),
"Gc pointers may be invalid when GC is running"));
&self.data
}
}
impl<T: Trace> GcBoxTrait for GcBox<T> {
fn header(&self) -> &GcBoxHeader { &self.header }
fn header_mut(&mut self) -> &mut GcBoxHeader { &mut self.header }
unsafe fn trace_value(&self) { self.trace_inner() }
fn size_of(&self) -> usize { mem::size_of::<T>() }
}
fn collect_garbage(st: &mut GcState) {
let mut next_node = &mut st.boxes_start
as *mut Option<Box<GcBoxTrait + 'static>>;
loop {
if let Some(ref mut node) = *unsafe { &mut *next_node } {
{
let header = node.header_mut();
next_node = &mut header.next as *mut _;
if header.roots.get() == 0 { continue }
}
unsafe { node.trace_value(); }
} else { break }
}
GC_SWEEPING.with(|collecting| collecting.set(true));
let mut next_node = &mut st.boxes_start
as *mut Option<Box<GcBoxTrait + 'static>>;
loop {
if let Some(ref mut node) = *unsafe { &mut *next_node } {
let size = node.size_of();
let header = node.header_mut();
if header.marked.get() {
header.marked.set(false);
next_node = &mut header.next;
} else {
st.bytes_allocated -= size;
let mut tmp = None;
mem::swap(&mut tmp, &mut header.next);
mem::swap(&mut tmp, unsafe { &mut *next_node });
}
} else { break }
}
st.boxes_end = next_node;
GC_SWEEPING.with(|collecting| collecting.set(false));
}
pub fn force_collect() {
GC_STATE.with(|_st| {
let mut st = _st.borrow_mut();
collect_garbage(&mut *st);
});
}