//! The garbage collector for the language //! //! The structure of the GC might change, but for now it's simply a `LinkedList` of `Object`s. use std::{ collections::LinkedList, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, ops::Deref, ptr::NonNull, }; use dbg_pls::DebugPls; use crate::{runtime::vm::Value, HashMap, HashSet}; /// A pointer to a garbage collected value. This pointer *must* always be valid, and a value /// is only allowed to be freed once no Gc is pointing at it anymore. This is achieved through /// tracing through all objects from a few known roots and marking every reachable value. All other /// values will be swept. pub struct Gc { ptr: NonNull, } #[cfg(feature = "_debug")] impl dbg_pls::DebugPls for Gc { fn fmt(&self, f: dbg_pls::Formatter<'_>) { DebugPls::fmt(self.deref(), f) } } impl Deref for Gc { type Target = T; fn deref(&self) -> &Self::Target { // SAFETY: Gc will always point to a valid T, since T will only be freed once all Gc are gone // This requires tracing through *all* roots without forgetting any // I would guess that there will be some errors with the garbage collector, but once they are // all fixed this will be sound. But who knows. unsafe { &*self.ptr.as_ptr() } } } impl Debug for Gc { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { T::fmt(self, f) } } impl Clone for Gc { fn clone(&self) -> Self { Self { ..*self } } } impl Copy for Gc {} /// An reference to an interned String. Hashing and Equality are O(1) and just look at the pointer address #[derive(Clone, Copy)] pub struct Symbol { gc: Gc, } type ObjectMap = HashMap; /// A reference to an Object on the heap. /// ```js /// let x = {}; /// ``` /// This is inside the local x now. #[derive(Clone, Copy)] #[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))] pub struct Object { gc: Gc, } #[derive(Debug)] #[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))] struct HeapObject { kind: HeapObjectKind, } #[derive(Debug)] #[cfg_attr(feature = "_debug", derive(dbg_pls::DebugPls))] enum HeapObjectKind { String(Gc), Object(ObjectMap), } #[derive(Debug)] pub struct RtAlloc { symbols: HashSet, objects: LinkedList, } #[derive(Debug)] struct NonNullStrWrapper(NonNull); impl Hash for NonNullStrWrapper { fn hash(&self, state: &mut H) { // SAFETY: Assume the ptr is valid, same rules as `Gc` unsafe { self.0.as_ref().hash(state) } } } impl PartialEq for NonNullStrWrapper { fn eq(&self, other: &Self) -> bool { // SAFETY: Assume the ptr is valid, same rules as `Gc` unsafe { self.0.as_ref().eq(other.0.as_ref()) } } } impl Eq for NonNullStrWrapper {} impl RtAlloc { /// # Safety /// Promise to not forget to mark any roots and to not deref `Gc` after you've dropped me 🥺 pub unsafe fn new() -> Self { Self { symbols: HashSet::default(), objects: LinkedList::new(), } } fn alloc_str(&mut self, str: &str) -> Gc { let ptr = Box::into_raw(str.to_owned().into_boxed_str()); // SAFETY: Box cannot be null let new_nonnull = unsafe { NonNull::new_unchecked(ptr) }; let gc = Gc { ptr: new_nonnull }; let object = HeapObject { kind: HeapObjectKind::String(gc), }; self.objects.push_back(object); gc } pub fn alloc_obj(&mut self, obj: ObjectMap) -> Object { self.objects.push_back(HeapObject { kind: HeapObjectKind::Object(obj), }); let ptr = self.objects.back().unwrap(); Object { gc: Gc { ptr: NonNull::from(ptr), }, } } pub fn intern_string(&mut self, str: &str) -> Symbol { let original_nonnull = NonNull::from(str); if let Some(interned) = self.symbols.get(&NonNullStrWrapper(original_nonnull)) { Symbol::new(Gc { ptr: interned.0 }) } else { let allocated = self.alloc_str(str); self.symbols.insert(NonNullStrWrapper(allocated.ptr)); Symbol::new(allocated) } } } impl Drop for RtAlloc { fn drop(&mut self) { // free all interned strings for str in &self.symbols { let raw = str.0.as_ptr(); // SAFETY: No one has free these, see `Gc` drop(unsafe { Box::from_raw(raw) }); } } } impl Symbol { pub fn new(gc: Gc) -> Self { Self { gc } } fn address(&self) -> usize { self.gc.ptr.as_ptr() as *mut u8 as usize } pub fn as_str(&self) -> &str { &*self.gc } } impl Hash for Symbol { fn hash(&self, state: &mut H) { self.address().hash(state); } } impl PartialEq for Symbol { fn eq(&self, other: &Self) -> bool { self.address() == other.address() } } impl Eq for Symbol {} impl Deref for Symbol { type Target = str; fn deref(&self) -> &Self::Target { self.as_str() } } impl Debug for Symbol { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Debug::fmt(self.as_str(), f) } } #[cfg(feature = "_debug")] impl dbg_pls::DebugPls for Symbol { fn fmt(&self, f: dbg_pls::Formatter<'_>) { DebugPls::fmt(self.as_str(), f) } } impl Deref for Object { type Target = ObjectMap; fn deref(&self) -> &Self::Target { match self.gc.deref().kind { HeapObjectKind::Object(ref map) => map, _ => unreachable!(), } } } impl Debug for Object { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Debug::fmt(self.gc.deref(), f) } }