use crate::header::GcHeader;
use std::alloc::Layout;
pub const REGION_SIZE: usize = 2 * 1024 * 1024;
pub struct Region {
base: *mut u8,
cursor: usize,
limit: usize,
live_bytes: usize,
}
unsafe impl Send for Region {}
impl Region {
pub fn new() -> Self {
let base = unsafe {
libc::mmap(
std::ptr::null_mut(),
REGION_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
assert!(
base != libc::MAP_FAILED,
"mmap failed to allocate {} byte region",
REGION_SIZE
);
let base = base as *mut u8;
Self {
base,
cursor: 0,
limit: REGION_SIZE,
live_bytes: 0,
}
}
pub fn try_alloc(&mut self, layout: Layout) -> Option<*mut u8> {
let header_size = std::mem::size_of::<GcHeader>();
let obj_size = layout.size();
let total = (header_size + obj_size + 7) & !7;
if self.cursor + total > self.limit {
return None;
}
let header_ptr = unsafe { self.base.add(self.cursor) } as *mut GcHeader;
let obj_ptr = unsafe { self.base.add(self.cursor + header_size) };
unsafe {
header_ptr.write(GcHeader::new(0, obj_size as u32));
}
self.cursor += total;
Some(obj_ptr)
}
#[inline]
pub fn contains(&self, ptr: *const u8) -> bool {
let addr = ptr as usize;
let base = self.base as usize;
addr >= base && addr < base + REGION_SIZE
}
pub fn base(&self) -> *mut u8 {
self.base
}
pub fn used_bytes(&self) -> usize {
self.cursor
}
pub fn remaining(&self) -> usize {
self.limit - self.cursor
}
pub fn set_cursor(&mut self, cursor: usize) {
debug_assert!(cursor <= self.limit);
self.cursor = cursor;
}
pub fn reset(&mut self) {
self.cursor = 0;
self.live_bytes = 0;
}
pub fn for_each_object(&self, mut f: impl FnMut(&GcHeader, *mut u8)) {
let header_size = std::mem::size_of::<GcHeader>();
let mut offset = 0;
while offset < self.cursor {
let header_ptr = unsafe { self.base.add(offset) } as *const GcHeader;
let header = unsafe { &*header_ptr };
let obj_ptr = unsafe { self.base.add(offset + header_size) };
let obj_size = header.size as usize;
let total = (header_size + obj_size + 7) & !7;
f(header, obj_ptr);
offset += total;
}
}
pub fn for_each_object_mut(&mut self, mut f: impl FnMut(&mut GcHeader, *mut u8)) {
let header_size = std::mem::size_of::<GcHeader>();
let mut offset = 0;
let cursor = self.cursor;
while offset < cursor {
let header_ptr = unsafe { self.base.add(offset) } as *mut GcHeader;
let header = unsafe { &mut *header_ptr };
let obj_ptr = unsafe { self.base.add(offset + header_size) };
let obj_size = header.size as usize;
let total = (header_size + obj_size + 7) & !7;
f(header, obj_ptr);
offset += total;
}
}
pub fn protect(&self) {
unsafe {
libc::mprotect(self.base as *mut libc::c_void, REGION_SIZE, libc::PROT_NONE);
}
}
pub fn unprotect(&self) {
unsafe {
libc::mprotect(
self.base as *mut libc::c_void,
REGION_SIZE,
libc::PROT_READ | libc::PROT_WRITE,
);
}
}
pub fn live_bytes(&self) -> usize {
self.live_bytes
}
pub fn set_live_bytes(&mut self, bytes: usize) {
self.live_bytes = bytes;
}
}
impl Drop for Region {
fn drop(&mut self) {
unsafe {
libc::munmap(self.base as *mut libc::c_void, REGION_SIZE);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_region_alloc_and_contains() {
let mut region = Region::new();
let layout = Layout::from_size_align(64, 8).unwrap();
let ptr = region.try_alloc(layout).expect("allocation failed");
assert!(region.contains(ptr));
assert!(!region.contains(std::ptr::null()));
}
#[test]
fn test_region_exhaustion() {
let mut region = Region::new();
let big_layout = Layout::from_size_align(REGION_SIZE, 8).unwrap();
assert!(region.try_alloc(big_layout).is_none());
}
#[test]
fn test_region_iteration() {
let mut region = Region::new();
let layout = Layout::from_size_align(16, 8).unwrap();
let _p1 = region.try_alloc(layout).unwrap();
let _p2 = region.try_alloc(layout).unwrap();
let _p3 = region.try_alloc(layout).unwrap();
let mut count = 0;
region.for_each_object(|header, _ptr| {
assert_eq!(header.size, 16);
count += 1;
});
assert_eq!(count, 3);
}
}