use std::fmt;
use std::marker::PhantomData;
use std::ptr::NonNull;
use crate::gc::trace::{Trace, Tracer};
use crate::objects::heap_object::HeapObject;
pub unsafe trait GcObject: Trace + Sized {
fn header(&self) -> &HeapObject {
unsafe { &*(self as *const Self as *const HeapObject) }
}
fn header_mut(&mut self) -> &mut HeapObject {
unsafe { &mut *(self as *mut Self as *mut HeapObject) }
}
}
#[repr(transparent)]
pub struct GcPtr<T: GcObject> {
ptr: NonNull<T>,
_marker: PhantomData<*mut T>,
}
impl<T: GcObject> Copy for GcPtr<T> {}
impl<T: GcObject> Clone for GcPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: GcObject> GcPtr<T> {
pub unsafe fn from_raw(ptr: NonNull<T>) -> Self {
Self {
ptr,
_marker: PhantomData,
}
}
pub fn as_ptr(self) -> *mut T {
self.ptr.as_ptr()
}
pub unsafe fn as_ref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
pub unsafe fn as_mut(&mut self) -> &mut T {
unsafe { self.ptr.as_mut() }
}
pub unsafe fn header(&self) -> &HeapObject {
unsafe { &*(self.ptr.as_ptr() as *const HeapObject) }
}
pub fn as_heap_object_ptr(self) -> *mut HeapObject {
self.ptr.as_ptr() as *mut HeapObject
}
pub unsafe fn from_heap_object_ptr(ptr: *mut HeapObject) -> Self {
Self {
ptr: unsafe { NonNull::new_unchecked(ptr as *mut T) },
_marker: PhantomData,
}
}
}
impl<T: GcObject> fmt::Debug for GcPtr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GcPtr({:p})", self.ptr.as_ptr())
}
}
impl<T: GcObject> fmt::Pointer for GcPtr<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
impl<T: GcObject> PartialEq for GcPtr<T> {
fn eq(&self, other: &Self) -> bool {
self.ptr == other.ptr
}
}
impl<T: GcObject> Eq for GcPtr<T> {}
impl<T: GcObject> std::hash::Hash for GcPtr<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.ptr.hash(state);
}
}
impl<T: GcObject> Trace for GcPtr<T> {
fn trace(&self, tracer: &mut Tracer) {
unsafe { tracer.mark_raw(self.ptr.as_ptr() as *mut u8) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gc::trace::Tracer;
use crate::objects::heap_object::HeapObject;
use std::alloc::Layout;
use std::mem::align_of;
#[repr(C)]
struct TestObj {
header: HeapObject,
value: u64,
}
impl Trace for TestObj {
fn trace(&self, _tracer: &mut Tracer) {
}
}
unsafe impl GcObject for TestObj {}
fn make_test_obj(value: u64) -> TestObj {
TestObj {
header: HeapObject::new_null(),
value,
}
}
#[test]
fn gc_ptr_round_trip_through_raw() {
let mut obj = make_test_obj(42);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc = unsafe { GcPtr::from_raw(ptr) };
assert_eq!(gc.as_ptr(), ptr.as_ptr());
assert_eq!(unsafe { gc.as_ref() }.value, 42);
}
#[test]
fn gc_ptr_as_heap_object_ptr_and_back() {
let mut obj = make_test_obj(99);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc: GcPtr<TestObj> = unsafe { GcPtr::from_raw(ptr) };
let heap_ptr = gc.as_heap_object_ptr();
assert_eq!(heap_ptr as usize, &obj as *const TestObj as usize);
let gc2: GcPtr<TestObj> = unsafe { GcPtr::from_heap_object_ptr(heap_ptr) };
assert_eq!(gc, gc2);
}
#[test]
fn gc_ptr_header_access() {
let mut obj = make_test_obj(7);
obj.header.init_alloc_size(123);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc = unsafe { GcPtr::from_raw(ptr) };
assert_eq!(unsafe { gc.header() }.alloc_size(), 123);
}
#[test]
fn gc_ptr_is_copy() {
let mut obj = make_test_obj(1);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc = unsafe { GcPtr::from_raw(ptr) };
let gc2 = gc; assert_eq!(gc.as_ptr(), gc2.as_ptr());
}
#[test]
fn gc_ptr_equality() {
let mut obj = make_test_obj(1);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let a = unsafe { GcPtr::from_raw(ptr) };
let b = unsafe { GcPtr::from_raw(ptr) };
assert_eq!(a, b);
}
#[test]
fn gc_ptr_trace_marks_object() {
let mut obj = make_test_obj(5);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc = unsafe { GcPtr::from_raw(ptr) };
let mut tracer = Tracer::new();
gc.trace(&mut tracer);
assert_eq!(tracer.gray_stack.len(), 1);
assert_eq!(tracer.gray_stack[0], ptr.as_ptr() as *mut u8);
}
#[test]
fn gc_object_default_header_accessors() {
let mut obj = make_test_obj(0);
obj.header.init_alloc_size(256);
assert_eq!(obj.header().alloc_size(), 256);
obj.header_mut().increment_age();
assert_eq!(obj.header().age(), 1);
}
#[test]
fn gc_ptr_debug_format() {
let mut obj = make_test_obj(1);
let ptr = NonNull::new(&mut obj as *mut TestObj).unwrap();
let gc = unsafe { GcPtr::from_raw(ptr) };
let debug = format!("{gc:?}");
assert!(debug.starts_with("GcPtr(0x"), "debug output: {debug}");
}
#[test]
fn heap_alloc_typed_returns_gc_ptr() {
use crate::gc::heap::Heap;
let mut heap = Heap::new();
let gc = heap.alloc(make_test_obj(42));
assert!(gc.is_some(), "typed allocation must succeed");
let gc = gc.unwrap();
let obj = unsafe { gc.as_ref() };
assert_eq!(obj.value, 42);
assert!(
unsafe { gc.header() }.alloc_size() > 0,
"alloc_size must be set"
);
}
#[test]
fn heap_alloc_typed_alloc_size_covers_full_type() {
use crate::gc::heap::Heap;
let mut heap = Heap::new();
let gc = heap.alloc(make_test_obj(0)).unwrap();
let layout = Layout::new::<TestObj>()
.align_to(align_of::<HeapObject>())
.unwrap()
.pad_to_align();
assert_eq!(
unsafe { gc.header() }.alloc_size() as usize,
layout.size(),
"alloc_size must match the padded layout size of the type"
);
}
#[test]
fn heap_alloc_typed_as_mut_modifies_value() {
use crate::gc::heap::Heap;
let mut heap = Heap::new();
let mut gc = heap.alloc(make_test_obj(10)).unwrap();
unsafe { gc.as_mut() }.value = 20;
assert_eq!(unsafe { gc.as_ref() }.value, 20);
}
}