use crate::header::{GcColor, GcHeader};
use crate::marker::Marker;
use crate::region::Region;
use std::alloc::Layout;
use std::cell::UnsafeCell;
const TLAB_SIZE: usize = 32 * 1024;
struct Tlab {
base: *mut u8,
cursor: usize,
limit: usize,
region_index: usize,
}
impl Tlab {
fn new(base: *mut u8, size: usize, region_index: usize) -> Self {
Self {
base,
cursor: 0,
limit: size,
region_index,
}
}
fn try_alloc(&mut self, total_size: usize) -> Option<*mut u8> {
if self.cursor + total_size > self.limit {
return None;
}
let ptr = unsafe { self.base.add(self.cursor) };
self.cursor += total_size;
Some(ptr)
}
}
pub struct BumpAllocator {
regions: UnsafeCell<Vec<Region>>,
tlab: UnsafeCell<Option<Tlab>>,
}
unsafe impl Send for BumpAllocator {}
unsafe impl Sync for BumpAllocator {}
impl BumpAllocator {
pub fn new() -> Self {
Self {
regions: UnsafeCell::new(Vec::new()),
tlab: UnsafeCell::new(None),
}
}
pub fn alloc(&self, layout: Layout) -> *mut u8 {
let header_size = std::mem::size_of::<GcHeader>();
let obj_size = layout.size();
let total = (header_size + obj_size + 7) & !7;
let tlab = unsafe { &mut *self.tlab.get() };
if let Some(t) = tlab {
if let Some(raw_ptr) = t.try_alloc(total) {
let header_ptr = raw_ptr as *mut GcHeader;
unsafe {
header_ptr.write(GcHeader::new(0, obj_size as u32));
}
return unsafe { raw_ptr.add(header_size) };
}
}
self.alloc_slow(layout, total)
}
fn alloc_slow(&self, layout: Layout, total: usize) -> *mut u8 {
let header_size = std::mem::size_of::<GcHeader>();
let obj_size = layout.size();
if total > TLAB_SIZE {
return self.alloc_large(layout);
}
let regions = unsafe { &mut *self.regions.get() };
let tlab = unsafe { &mut *self.tlab.get() };
let new_region = Region::new();
let base = new_region.base();
let region_index = regions.len();
regions.push(new_region);
let new_tlab = Tlab::new(base, TLAB_SIZE, region_index);
*tlab = Some(new_tlab);
let t = tlab.as_mut().unwrap();
let raw_ptr = t
.try_alloc(total)
.expect("fresh TLAB should have space for allocation");
let header_ptr = raw_ptr as *mut GcHeader;
unsafe {
header_ptr.write(GcHeader::new(0, obj_size as u32));
}
unsafe { raw_ptr.add(header_size) }
}
fn alloc_large(&self, layout: Layout) -> *mut u8 {
let regions = unsafe { &mut *self.regions.get() };
let mut region = Region::new();
let ptr = region
.try_alloc(layout)
.expect("fresh region should fit large allocation");
regions.push(region);
ptr
}
fn flush_tlab(&self) {
let tlab = unsafe { &mut *self.tlab.get() };
if let Some(t) = tlab {
let regions = unsafe { &mut *self.regions.get() };
if t.region_index < regions.len() {
regions[t.region_index].set_cursor(t.cursor);
}
}
}
pub fn flush_tlab_for_sweep(&self) {
self.flush_tlab();
}
pub fn sweep(&self, _marker: &Marker) -> usize {
self.flush_tlab();
let regions = unsafe { &mut *self.regions.get() };
let mut total_collected = 0;
for region in regions.iter_mut() {
let mut live_bytes = 0;
region.for_each_object_mut(|header, _obj_ptr| {
if header.color() == GcColor::White {
total_collected += header.size as usize;
} else {
live_bytes += header.size as usize;
header.set_color(GcColor::White);
}
});
region.set_live_bytes(live_bytes);
}
let tlab = unsafe { &mut *self.tlab.get() };
*tlab = None;
total_collected
}
pub fn total_region_bytes(&self) -> usize {
let regions = unsafe { &*self.regions.get() };
regions.len() * crate::region::REGION_SIZE
}
pub fn region_count(&self) -> usize {
let regions = unsafe { &*self.regions.get() };
regions.len()
}
pub fn regions(&self) -> &Vec<Region> {
unsafe { &*self.regions.get() }
}
pub fn regions_mut(&self) -> &mut Vec<Region> {
unsafe { &mut *self.regions.get() }
}
}
impl Default for BumpAllocator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_allocation() {
let alloc = BumpAllocator::new();
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = alloc.alloc(layout);
assert!(!ptr.is_null());
let header_ptr =
unsafe { (ptr as *const u8).sub(std::mem::size_of::<GcHeader>()) } as *const GcHeader;
let header = unsafe { &*header_ptr };
assert_eq!(header.size, 64);
assert_eq!(header.color(), GcColor::White);
}
#[test]
fn test_multiple_allocations() {
let alloc = BumpAllocator::new();
let layout = Layout::from_size_align(32, 8).unwrap();
let mut ptrs = Vec::new();
for _ in 0..100 {
ptrs.push(alloc.alloc(layout));
}
for i in 0..ptrs.len() {
for j in (i + 1)..ptrs.len() {
assert_ne!(ptrs[i], ptrs[j]);
}
}
}
#[test]
fn test_tlab_refill() {
let alloc = BumpAllocator::new();
let layout = Layout::from_size_align(1024, 8).unwrap();
for _ in 0..50 {
let ptr = alloc.alloc(layout);
assert!(!ptr.is_null());
}
}
}