diff --git a/Cargo.lock b/Cargo.lock index 9379c7f..0d3e419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "boa_gc" version = "1.0.0-dev" -source = "git+https://github.com/boa-dev/boa.git?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" +source = "git+https://github.com/boa-dev/boa?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" dependencies = [ "boa_macros", "hashbrown 0.17.1", @@ -53,7 +53,7 @@ dependencies = [ [[package]] name = "boa_macros" version = "1.0.0-dev" -source = "git+https://github.com/boa-dev/boa.git?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" +source = "git+https://github.com/boa-dev/boa?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" dependencies = [ "cfg-if", "cow-utils", @@ -63,19 +63,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "boa_string" -version = "1.0.0-dev" -source = "git+https://github.com/boa-dev/boa.git?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" -dependencies = [ - "fast-float2", - "itoa", - "pastey", - "rustc-hash", - "ryu-js", - "static_assertions", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -242,12 +229,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "fast-float2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" - [[package]] name = "foldhash" version = "0.2.0" @@ -402,7 +383,6 @@ version = "0.1.0" dependencies = [ "arrayvec", "boa_gc", - "boa_string", "criterion", "either", "hashbrown 0.16.1", @@ -424,12 +404,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "pastey" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - [[package]] name = "plotters" version = "0.3.7" @@ -537,12 +511,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu-js" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d056b875a9d2e6cb9a61d127afee9ac5999b9f87bcb32079d1318e505be714" - [[package]] name = "same-file" version = "1.0.6" @@ -604,12 +572,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "syn" version = "2.0.117" @@ -869,3 +831,7 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[patch.unused]] +name = "boa_string" +version = "1.0.0-dev" diff --git a/oscars/Cargo.toml b/oscars/Cargo.toml index 2d1e2d8..491bc64 100644 --- a/oscars/Cargo.toml +++ b/oscars/Cargo.toml @@ -9,7 +9,6 @@ oscars_derive = { path = "../oscars_derive", version = "0.1.0" } rustc-hash = "2.1.1" thin-vec = { version = "0.2", optional = true } # Optional Trace/Finalize impls for external types. -boa_string = { git = "https://github.com/boa-dev/boa.git", branch = "main", optional = true } icu_locale_core = { version = "2.2.0", default-features = false, optional = true } either = { version = "1.16.0", optional = true } arrayvec = { version = "0.7.6", optional = true } @@ -44,7 +43,6 @@ mark_sweep_branded = ["mark_sweep"] null_collector = ["mark_sweep"] null_collector_branded = ["mark_sweep"] thin-vec = ["dep:thin-vec", "mark_sweep"] -boa_string = ["dep:boa_string", "mark_sweep"] icu = ["dep:icu_locale_core", "mark_sweep"] either = ["dep:either", "mark_sweep"] arrayvec = ["dep:arrayvec", "mark_sweep"] diff --git a/oscars/src/alloc/mempool3/alloc.rs b/oscars/src/alloc/mempool3/alloc.rs index 813440f..581709e 100644 --- a/oscars/src/alloc/mempool3/alloc.rs +++ b/oscars/src/alloc/mempool3/alloc.rs @@ -89,10 +89,7 @@ impl<'pool, T: ?Sized> PoolPointer<'pool, T> { self.0 } - pub fn to_erased(self) -> ErasedPoolPointer<'pool> - where - T: Sized, - { + pub fn to_erased(self) -> ErasedPoolPointer<'pool> { ErasedPoolPointer(self.0.cast::(), PhantomData) } diff --git a/oscars/src/collectors/common.rs b/oscars/src/collectors/common.rs index 7bd5299..8f4ae8a 100644 --- a/oscars/src/collectors/common.rs +++ b/oscars/src/collectors/common.rs @@ -185,9 +185,6 @@ impl Finalize for icu_locale_core::LanguageIdentifier {} #[cfg(feature = "icu")] impl Finalize for icu_locale_core::Locale {} -#[cfg(feature = "boa_string")] -impl Finalize for boa_string::JsString {} - #[cfg(feature = "either")] impl Finalize for either::Either {} diff --git a/oscars/src/collectors/mark_sweep/internals/mod.rs b/oscars/src/collectors/mark_sweep/internals/mod.rs index e87ffd1..667f465 100644 --- a/oscars/src/collectors/mark_sweep/internals/mod.rs +++ b/oscars/src/collectors/mark_sweep/internals/mod.rs @@ -4,6 +4,7 @@ mod gc_header; mod vtable; pub(crate) use ephemeron::Ephemeron; +#[allow(unused_imports)] pub(crate) use gc_header::{GcHeader, HeaderColor}; pub(crate) use vtable::{DropFn, FinalizeFn, TraceFn, VTable, vtable_of}; diff --git a/oscars/src/collectors/mark_sweep/trace.rs b/oscars/src/collectors/mark_sweep/trace.rs index f96faae..70458f4 100644 --- a/oscars/src/collectors/mark_sweep/trace.rs +++ b/oscars/src/collectors/mark_sweep/trace.rs @@ -446,7 +446,7 @@ unsafe impl Trace for OnceCell { mod icu_trace { use icu_locale_core::{LanguageIdentifier, Locale}; - use crate::mark_sweep::{Finalize, Trace}; + use crate::mark_sweep::Trace; // SAFETY: `LanguageIdentifier` doesn't have any traceable data. unsafe impl Trace for LanguageIdentifier { @@ -459,19 +459,9 @@ mod icu_trace { } } -#[cfg(feature = "boa_string")] -mod boa_string_trace { - use crate::mark_sweep::{Finalize, Trace}; - - // SAFETY: `boa_string::JsString` doesn't have any traceable data. - unsafe impl Trace for boa_string::JsString { - empty_trace!(); - } -} - #[cfg(feature = "either")] mod either_trace { - use crate::mark_sweep::{Finalize, Trace}; + use crate::mark_sweep::Trace; unsafe impl Trace for either::Either { custom_trace!(this, mark, { diff --git a/oscars/src/collectors/mark_sweep_branded/cell.rs b/oscars/src/collectors/mark_sweep_branded/cell.rs index 50a6ebe..779fb9d 100644 --- a/oscars/src/collectors/mark_sweep_branded/cell.rs +++ b/oscars/src/collectors/mark_sweep_branded/cell.rs @@ -5,7 +5,11 @@ use core::cell::{Ref, RefCell, RefMut}; use core::ops::{Deref, DerefMut}; /// A GC-aware wrapper around [`RefCell`]. -pub struct GcRefCell { +/// +/// Unlike a plain `RefCell`, this can hold unsized `T` through a `Box` +/// indirection when needed. The `T: Trace` bound ensures the GC can visit +/// the contained value. +pub struct GcRefCell { inner: RefCell, } @@ -16,7 +20,31 @@ impl GcRefCell { inner: RefCell::new(value), } } +} + +impl Clone for GcRefCell { + #[inline] + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl core::fmt::Debug for GcRefCell { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.inner, f) + } +} + +impl Default for GcRefCell { + #[inline] + fn default() -> Self { + Self::new(T::default()) + } +} +impl GcRefCell { /// Acquires a shared borrow of the inner value. /// /// # Panics @@ -34,37 +62,119 @@ impl GcRefCell { pub fn borrow_mut(&self) -> GcRefMut<'_, T> { GcRefMut(self.inner.borrow_mut()) } + + pub fn try_borrow(&self) -> Result, core::cell::BorrowError> { + self.inner.try_borrow().map(GcRef) + } + + pub fn try_borrow_mut(&self) -> Result, core::cell::BorrowMutError> { + self.inner.try_borrow_mut().map(GcRefMut) + } } /// A shared borrow guard returned by [`GcRefCell::borrow`]. -pub struct GcRef<'a, T: Trace>(Ref<'a, T>); +pub struct GcRef<'a, T: Trace + ?Sized>(Ref<'a, T>); -impl Deref for GcRef<'_, T> { +impl Deref for GcRef<'_, T> { type Target = T; fn deref(&self) -> &T { &self.0 } } +impl core::fmt::Debug for GcRef<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&**self, f) + } +} + +impl core::fmt::Display for GcRef<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&**self, f) + } +} + +impl<'a, T: Trace + ?Sized> GcRef<'a, T> { + /// Projects a `GcRef` to a `GcRef` via a closure, mirroring + /// [`Ref::map`]. + pub fn map(orig: Self, f: F) -> GcRef<'a, U> + where + U: Trace + ?Sized, + F: FnOnce(&T) -> &U, + { + GcRef(Ref::map(orig.0, f)) + } + + pub fn try_map(orig: GcRef<'a, T>, f: F) -> Option> + where + F: FnOnce(&T) -> Option<&U>, + { + Ref::filter_map(orig.0, f).ok().map(GcRef) + } + + /// Casts a `GcRef` to a `GcRef` without type checking. + /// + /// # Safety + /// + /// `T` and `U` must be pointer-compatible. The underlying value must be + /// a valid `U`. + pub unsafe fn cast(orig: GcRef<'a, T>) -> GcRef<'a, U> { + GcRef(Ref::map(orig.0, |t| unsafe { + &*((t as *const T).cast::()) + })) + } +} + /// A mutable borrow guard returned by [`GcRefCell::borrow_mut`]. -pub struct GcRefMut<'a, T: Trace>(RefMut<'a, T>); +pub struct GcRefMut<'a, T: Trace + ?Sized>(RefMut<'a, T>); -impl Deref for GcRefMut<'_, T> { +impl Deref for GcRefMut<'_, T> { type Target = T; fn deref(&self) -> &T { &self.0 } } -impl DerefMut for GcRefMut<'_, T> { +impl DerefMut for GcRefMut<'_, T> { fn deref_mut(&mut self) -> &mut T { &mut self.0 } } -impl Finalize for GcRefCell {} +impl<'a, T: Trace + ?Sized> GcRefMut<'a, T> { + /// Projects a `GcRefMut` to a `GcRefMut` via a closure, mirroring + /// [`RefMut::map`]. + pub fn map(orig: Self, f: F) -> GcRefMut<'a, U> + where + U: Trace + ?Sized, + F: FnOnce(&mut T) -> &mut U, + { + GcRefMut(RefMut::map(orig.0, f)) + } + + pub fn try_map(orig: GcRefMut<'a, T>, f: F) -> Option> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + RefMut::filter_map(orig.0, f).ok().map(GcRefMut) + } + + /// Casts a `GcRefMut` to a `GcRefMut` without type checking. + /// + /// # Safety + /// + /// `T` and `U` must be pointer compatible, the underlying value must be + /// a valid `U` + pub unsafe fn cast(orig: GcRefMut<'a, T>) -> GcRefMut<'a, U> { + GcRefMut(RefMut::map(orig.0, |t| unsafe { + &mut *((t as *mut T).cast::()) + })) + } +} + +impl Finalize for GcRefCell {} -unsafe impl Trace for GcRefCell { +unsafe impl Trace for GcRefCell { unsafe fn trace(&self, tracer: &mut Tracer) { let val = unsafe { &*self.inner.as_ptr() }; unsafe { diff --git a/oscars/src/collectors/mark_sweep_branded/ephemeron.rs b/oscars/src/collectors/mark_sweep_branded/ephemeron.rs index bc3c4c9..f183f2d 100644 --- a/oscars/src/collectors/mark_sweep_branded/ephemeron.rs +++ b/oscars/src/collectors/mark_sweep_branded/ephemeron.rs @@ -9,15 +9,20 @@ use crate::{ }; use core::marker::PhantomData; -pub struct Ephemeron<'id, K: Trace, V: Trace> { +/// A weak key / strong value association. +/// +/// The value is kept alive by the collector as long as the key is reachable +/// from a root. Once the key is swept, `get_value()` / `value()` return `None` +/// and the entry is eligible for cleanup. +pub struct Ephemeron<'id, K: Trace + ?Sized, V: Trace> { pub(crate) key_ptr: Option>>, pub(crate) key_alloc_id: usize, pub(crate) value_ptr: PoolPointer<'static, GcBox>, pub(crate) _marker: PhantomData<*mut &'id ()>, } -impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { - pub(crate) fn new( +impl<'id, K: Trace + ?Sized, V: Trace> Ephemeron<'id, K, V> { + pub(crate) fn new_raw( key_ptr: Option>>, key_alloc_id: usize, value_ptr: PoolPointer<'static, GcBox>, @@ -30,9 +35,31 @@ impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { } } - /// Returns the value if the key is alive. + /// Allocates a new ephemeron binding `key` to `value`. + /// + /// The value is kept alive by the collector until the key is swept. + pub fn new_with_mc<'gc>(cx: &MutationContext<'id, 'gc>, key: &Gc<'gc, K>, value: V) -> Self + where + K: Finalize, + V: Finalize, + { + cx.alloc_ephemeron(key, cx.try_alloc(value).expect("Ephemeron value alloc")) + } + + /// Convenience alias matching the `null_collector_branded` API. + #[inline] + pub fn new<'gc>(cx: &MutationContext<'id, 'gc>, key: &Gc<'gc, K>, value: V) -> Self + where + K: Finalize, + V: Finalize, + { + Self::new_with_mc(cx, key, value) + } +} + +impl<'id, K: Trace + ?Sized, V: Trace> Ephemeron<'id, K, V> { + /// Returns the value if the key is still alive, or `None` if collected. pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { - // SAFETY: `_cx` proves the collector is alive, alloc_id guards ABA let key_alive = self .key_ptr .is_some_and(|p| unsafe { (*p.as_ptr().as_ptr()).0.alloc_id == self.key_alloc_id }); @@ -42,18 +69,46 @@ impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { None } } + + /// Returns the key if still alive, or `None` if collected. + pub fn key<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { + let key_alive = self + .key_ptr + .is_some_and(|p| unsafe { (*p.as_ptr().as_ptr()).0.alloc_id == self.key_alloc_id }); + if key_alive { + self.key_ptr.map(|ptr| Gc::with_pointer(ptr)) + } else { + None + } + } + + /// Returns the value if the key is still alive, or `None` if collected. + /// + /// Alias for [`Self::get_value`] to match the `null_collector_branded` API. + pub fn value<'gc>(&self, cx: &MutationContext<'id, 'gc>) -> Option> { + self.get_value(cx) + } + + /// Returns `true` if the key is still alive (the value is reachable). + pub fn has_value(&self) -> bool { + self.key_ptr + .is_some_and(|p| unsafe { (*p.as_ptr().as_ptr()).0.alloc_id == self.key_alloc_id }) + } } -impl<'id, K: Trace, V: Trace> Clone for Ephemeron<'id, K, V> { +impl<'id, K: Trace + ?Sized, V: Trace> Clone for Ephemeron<'id, K, V> { fn clone(&self) -> Self { *self } } -impl<'id, K: Trace, V: Trace> Copy for Ephemeron<'id, K, V> {} +impl<'id, K: Trace + ?Sized, V: Trace> Copy for Ephemeron<'id, K, V> {} -impl<'id, K: Trace, V: Trace> Finalize for Ephemeron<'id, K, V> {} +impl<'id, K: Trace + ?Sized, V: Trace> Finalize for Ephemeron<'id, K, V> {} -unsafe impl<'id, K: Trace, V: Trace> Trace for Ephemeron<'id, K, V> { +unsafe impl<'id, K: Trace + ?Sized, V: Trace> Trace for Ephemeron<'id, K, V> { + // Ephemerons do not mark their key; liveness of the key is determined + // by the GC independently. The value is marked via the GC's ephemeron + // fixpoint phase in `Collector::collect`. unsafe fn trace(&self, _tracer: &mut Tracer) {} } diff --git a/oscars/src/collectors/mark_sweep_branded/gc.rs b/oscars/src/collectors/mark_sweep_branded/gc.rs index e220515..683c6c2 100644 --- a/oscars/src/collectors/mark_sweep_branded/gc.rs +++ b/oscars/src/collectors/mark_sweep_branded/gc.rs @@ -35,33 +35,121 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { } } -impl<'gc, T: Trace + 'gc> Gc<'gc, T> { +impl<'gc, T: Trace + Finalize + 'gc> Gc<'gc, T> { + /// Allocates `value` on the GC heap via the provided `MutationContext`. + /// + /// This is a convenience wrapper over [`crate::collectors::mark_sweep_branded::MutationContext::try_alloc`] that + /// panics on allocation failure. It mirrors the `null_collector_branded` + /// `Gc::new` so engine call-sites remain unchanged under `mark_sweep_backend`. + #[inline] + pub fn new( + mc: &crate::collectors::mark_sweep_branded::MutationContext<'_, 'gc>, + value: T, + ) -> Self { + mc.try_alloc(value) + .expect("mark_sweep_branded: Gc::new allocation failed") + } +} + +impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { /// Returns a shared reference to the value. #[inline] - pub fn get(&self) -> &T { + pub(crate) fn inner_ref(&self) -> &T { // SAFETY: `ptr` is non-null and valid for `'gc` by construction. // The `'gc` lifetime is scoped to a `mutate()` closure, collection only occurs // via `cx.collect()` within that same closure and `Gc<'gc, T>` can't // escape the closure. unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value } } + + #[inline] + pub fn as_ptr(&self) -> *const T { + self.inner_ref() as *const T + } + + #[inline] + #[must_use] + pub fn ptr_eq(this: &Self, other: &Gc<'gc, U>) -> bool { + core::ptr::eq(this.as_ptr() as *const (), other.as_ptr() as *const ()) + } + + /// Casts the internal pointer to a different type. + /// + /// # Safety + /// The caller must ensure that the inner value is valid for the target type `U`. + #[inline] + pub unsafe fn cast_unchecked(self) -> Gc<'gc, U> { + let raw = self + .ptr + .as_ptr() + .cast::>>(); + Gc { + ptr: unsafe { crate::alloc::mempool3::PoolPointer::from_raw(raw) }, + _marker: PhantomData, + } + } + + #[inline] + pub fn is(&self) -> bool { + let actual_type_name = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_name }; + actual_type_name == core::any::type_name::() + } + + #[inline] + #[allow(private_interfaces)] + pub fn into_raw(self) -> core::ptr::NonNull>> { + let ptr = self.ptr.as_ptr(); + let _ = self; + ptr + } + + /// Constructs a `Gc` from a raw pointer. + /// + /// # Safety + /// The pointer must have been previously obtained from `into_raw`. + #[inline] + #[allow(private_interfaces)] + pub unsafe fn from_raw( + ptr: core::ptr::NonNull>>, + ) -> Self { + Self { + ptr: unsafe { crate::alloc::mempool3::PoolPointer::from_raw(ptr) }, + _marker: PhantomData, + } + } +} + +impl<'gc, T: Trace + ?Sized + 'gc> AsRef for Gc<'gc, T> { + #[inline] + fn as_ref(&self) -> &T { + self.inner_ref() + } +} + +#[cfg(feature = "std")] +impl<'gc, T: Trace + Finalize + Default + 'gc> Default for Gc<'gc, T> { + fn default() -> Self { + crate::collectors::mark_sweep_branded::MutationContext::global() + .try_alloc(Default::default()) + .unwrap() + } } -impl<'gc, T: Trace + fmt::Display + 'gc> fmt::Display for Gc<'gc, T> { +impl<'gc, T: Trace + fmt::Display + ?Sized + 'gc> fmt::Display for Gc<'gc, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.get(), f) + fmt::Display::fmt(self.inner_ref(), f) } } -impl<'gc, T: Trace + 'gc> Deref for Gc<'gc, T> { +impl<'gc, T: Trace + ?Sized + 'gc> Deref for Gc<'gc, T> { type Target = T; fn deref(&self) -> &T { - self.get() + self.inner_ref() } } impl Finalize for Gc<'_, T> {} -unsafe impl Trace for Gc<'_, T> { +unsafe impl Trace for Gc<'_, T> { unsafe fn trace(&self, tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) { tracer.mark(self); } diff --git a/oscars/src/collectors/mark_sweep_branded/gc_box.rs b/oscars/src/collectors/mark_sweep_branded/gc_box.rs index f4a300d..833af15 100644 --- a/oscars/src/collectors/mark_sweep_branded/gc_box.rs +++ b/oscars/src/collectors/mark_sweep_branded/gc_box.rs @@ -23,7 +23,7 @@ pub(crate) enum GcColor { /// Heap wrapper for a garbage-collected value. /// /// Allocated via [`PoolAllocator`]. -pub(crate) struct GcBox { +pub struct GcBox { /// tricolor marking state, updated by the mark phase pub(crate) color: Cell, /// Type-erased trace function. @@ -32,6 +32,8 @@ pub(crate) struct GcBox { pub(crate) drop_fn: DropFn, /// Allocation ID used to validate weak pointers. pub(crate) alloc_id: usize, + /// Type name of the underlying value + pub(crate) type_name: &'static str, /// The user value. pub(crate) value: T, } @@ -48,6 +50,7 @@ impl GcBox { trace_fn, drop_fn, alloc_id, + type_name: core::any::type_name::(), value, } } diff --git a/oscars/src/collectors/mark_sweep_branded/mod.rs b/oscars/src/collectors/mark_sweep_branded/mod.rs index 702b61c..21f757d 100644 --- a/oscars/src/collectors/mark_sweep_branded/mod.rs +++ b/oscars/src/collectors/mark_sweep_branded/mod.rs @@ -35,7 +35,7 @@ pub(crate) struct EphemeronEntry { pub(crate) value_ptr: PoolPointer<'static, GcBox<()>>, } -pub(crate) struct Collector { +pub struct Collector { // SAFETY: We use 'static here because the PoolAllocator owns its memory, // and we ensure that `Gc` objects and pool allocations do not outlive // the `Collector` instance @@ -48,7 +48,7 @@ pub(crate) struct Collector { } impl Collector { - fn new() -> Self { + pub fn new() -> Self { Self { pool: RefCell::new(PoolAllocator::default()), root_pool: RefCell::new(PoolAllocator::default()), @@ -143,9 +143,19 @@ impl Collector { } /// Runs a collection cycle - pub(crate) fn collect(&self) { + pub fn collect(&self) { + self.collect_with_roots(|_| {}) + } + + /// Runs a collection cycle, allowing external roots to be traced + pub(crate) fn collect_with_roots(&self, trace_external: F) + where + F: FnOnce(&mut Tracer), + { let mut tracer = Tracer::new(); + trace_external(&mut tracer); + for link_ptr in self.sentinel.iter() { unsafe { // SAFETY: link_ptr points to the `link` field which is first in repr(C) RootNode. diff --git a/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs index 5c3c26d..2befa4d 100644 --- a/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs +++ b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs @@ -21,6 +21,38 @@ pub struct MutationContext<'id, 'gc> { } impl<'id, 'gc> MutationContext<'id, 'gc> { + /// Creates an erased `MutationContext` tied to the provided `Collector`. + /// + /// # Safety + /// The caller must ensure that the returned `MutationContext` (and any + /// `Gc` pointers it creates) do not outlive the `Collector`. + pub unsafe fn from_collector_erased( + collector: &crate::collectors::mark_sweep_branded::Collector, + ) -> MutationContext<'static, 'static> { + let ptr = collector as *const _; + MutationContext { + collector: unsafe { &*ptr }, + _marker: PhantomData, + } + } + /// Creates a global thread-local MutationContext for the mark sweep collector. + /// + /// **Note**: This is a temporary workaround to keep `boa_engine` working. + /// It breaks the normal safety rules of the collector, and should only be + /// used to support older code that relies on `Default`. + #[cfg(feature = "std")] + pub fn global() -> Self { + std::thread_local! { + static COLLECTOR: crate::collectors::mark_sweep_branded::Collector = crate::collectors::mark_sweep_branded::Collector::new(); + } + COLLECTOR.with(|c| { + let ptr = c as *const crate::collectors::mark_sweep_branded::Collector; + Self { + collector: unsafe { &*ptr }, + _marker: core::marker::PhantomData, + } + }) + } /// Allocates a value on the GC heap. pub fn try_alloc( &self, @@ -30,7 +62,10 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { } /// Downgrades a `Gc` into a weak reference - pub fn alloc_weak(&self, gc: Gc<'gc, T>) -> WeakGc<'id, T> { + pub fn alloc_weak( + &self, + gc: &Gc<'gc, T>, + ) -> WeakGc<'id, T> { let alloc_id = unsafe { (*gc.ptr.as_ptr().as_ptr()).0.alloc_id }; WeakGc::with_pointer_and_alloc_id(gc.ptr, alloc_id) } @@ -49,9 +84,9 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { /// The value is kept alive by the collector as long as the key remains /// reachable from a root. Once the key is collected, `get_value` returns /// `None` and the value is eligible for collection on the next cycle. - pub fn alloc_ephemeron( + pub fn alloc_ephemeron( &self, - key: Gc<'gc, K>, + key: &Gc<'gc, K>, value: Gc<'gc, V>, ) -> Ephemeron<'id, K, V> { let key_alloc_id = unsafe { (*key.ptr.as_ptr().as_ptr()).0.alloc_id }; @@ -62,11 +97,19 @@ impl<'id, 'gc> MutationContext<'id, 'gc> { let erased_value: PoolPointer<'static, GcBox<()>> = unsafe { value.ptr.to_erased().to_typed_pool_pointer::>() }; self.collector.register_ephemeron(erased_key, erased_value); - Ephemeron::new(Some(key.ptr), key_alloc_id, value.ptr) + Ephemeron::new_raw(Some(key.ptr), key_alloc_id, value.ptr) } /// Triggers a gc cycle. pub fn collect(&self) { self.collector.collect(); } + + /// Triggers a gc cycle, allowing external roots to be traced. + pub fn collect_with_roots( + &self, + trace_external: F, + ) { + self.collector.collect_with_roots(trace_external); + } } diff --git a/oscars/src/collectors/mark_sweep_branded/tests/ephemeron.rs b/oscars/src/collectors/mark_sweep_branded/tests/ephemeron.rs index b793f95..c68a640 100644 --- a/oscars/src/collectors/mark_sweep_branded/tests/ephemeron.rs +++ b/oscars/src/collectors/mark_sweep_branded/tests/ephemeron.rs @@ -7,7 +7,7 @@ fn ephemeron_value_survives_when_key_is_rooted() { let key = cx.try_alloc(1u32).unwrap(); let value = cx.try_alloc(42u32).unwrap(); let root_key = cx.root(key).unwrap(); - let eph = cx.alloc_ephemeron(key, value); + let eph = cx.alloc_ephemeron(&key, value); (root_key, eph) }); @@ -17,7 +17,7 @@ fn ephemeron_value_survives_when_key_is_rooted() { let val = eph .get_value(cx) .expect("value must be alive while key is rooted"); - assert_eq!(*val.get(), 42); + assert_eq!(*val, 42); drop(root_key); }); }); @@ -29,7 +29,7 @@ fn ephemeron_value_collected_when_key_unrooted() { let eph = ctx.mutate(|cx| { let key = cx.try_alloc(1u32).unwrap(); let value = cx.try_alloc(99u32).unwrap(); - cx.alloc_ephemeron(key, value) + cx.alloc_ephemeron(&key, value) }); ctx.collect(); @@ -55,8 +55,8 @@ fn ephemeron_chain_fixpoint() { let b = cx.try_alloc(2u32).unwrap(); let c = cx.try_alloc(3u32).unwrap(); let root_a = cx.root(a).unwrap(); - let eph_ab = cx.alloc_ephemeron(a, b); - let eph_bc = cx.alloc_ephemeron(b, c); + let eph_ab = cx.alloc_ephemeron(&a, b); + let eph_bc = cx.alloc_ephemeron(&b, c); (root_a, eph_ab, eph_bc) }); @@ -64,11 +64,11 @@ fn ephemeron_chain_fixpoint() { ctx.mutate(|cx| { let b_val = eph_ab.get_value(cx).expect("b must survive: a is rooted"); - assert_eq!(*b_val.get(), 2); + assert_eq!(*b_val, 2); let c_val = eph_bc .get_value(cx) .expect("c must survive: b is kept alive by ephemeron"); - assert_eq!(*c_val.get(), 3); + assert_eq!(*c_val, 3); }); drop(root_a); @@ -94,7 +94,7 @@ fn ephemeron_entry_cleaned_up_after_sweep() { ctx.mutate(|cx| { let key = cx.try_alloc(0u32).unwrap(); let value = cx.try_alloc(0u32).unwrap(); - cx.alloc_ephemeron(key, value); + cx.alloc_ephemeron(&key, value); }); assert_eq!(ctx.ephemeron_count(), 1); diff --git a/oscars/src/collectors/mark_sweep_branded/tests/mod.rs b/oscars/src/collectors/mark_sweep_branded/tests/mod.rs index cea7039..dd95e42 100644 --- a/oscars/src/collectors/mark_sweep_branded/tests/mod.rs +++ b/oscars/src/collectors/mark_sweep_branded/tests/mod.rs @@ -16,7 +16,7 @@ fn unrooted_alloc_is_swept() { with_gc(|ctx| { let weak = ctx.mutate(|cx| { cx.alloc_weak( - cx.try_alloc(JsObject { + &cx.try_alloc(JsObject { name: "ephemeral".into(), value: 999, }) @@ -46,8 +46,8 @@ fn rooted_alloc_survives_collection() { ctx.collect(); ctx.mutate(|cx| { let gc = root.get(cx); - assert_eq!(gc.get().value, 42); - assert_eq!(gc.get().name, "pinned"); + assert_eq!(gc.value, 42); + assert_eq!(gc.name, "pinned"); }); }); } @@ -57,7 +57,7 @@ fn weak_upgrade_after_collection_without_root_is_none() { with_gc(|ctx| { let weak = ctx.mutate(|cx| { cx.alloc_weak( - cx.try_alloc(JsObject { + &cx.try_alloc(JsObject { name: "weak".into(), value: 10, }) @@ -84,7 +84,7 @@ fn weak_upgrade_with_live_root_is_some() { let root = cx.root(obj).unwrap(); let weak = cx.alloc_weak( - cx.try_alloc(JsObject { + &cx.try_alloc(JsObject { name: "weak_entry".into(), value: 77, }) @@ -95,7 +95,7 @@ fn weak_upgrade_with_live_root_is_some() { ctx.collect(); ctx.mutate(|cx| { assert!(weak.upgrade(cx).is_none()); - assert_eq!(root.get(cx).get().value, 7); + assert_eq!(root.get(cx).value, 7); }); }); } @@ -112,15 +112,15 @@ fn multiple_roots_are_independent() { ctx.collect(); ctx.mutate(|cx| { - assert_eq!(*root1.get(cx).get(), 100); - assert_eq!(*root2.get(cx).get(), 200); + assert_eq!(*root1.get(cx), 100); + assert_eq!(*root2.get(cx), 200); }); drop(root1); ctx.collect(); ctx.mutate(|cx| { - assert_eq!(*root2.get(cx).get(), 200); + assert_eq!(*root2.get(cx), 200); }); }); } @@ -136,7 +136,7 @@ fn root_escapes_closure_safely() { ctx.collect(); ctx.mutate(|cx| { - assert_eq!(*root.get(cx).get(), 555); + assert_eq!(*root.get(cx), 555); }); }); } diff --git a/oscars/src/collectors/mark_sweep_branded/trace.rs b/oscars/src/collectors/mark_sweep_branded/trace.rs index 61f29e6..e48b16b 100644 --- a/oscars/src/collectors/mark_sweep_branded/trace.rs +++ b/oscars/src/collectors/mark_sweep_branded/trace.rs @@ -90,16 +90,14 @@ impl<'a> Tracer<'a> { /// Marks `gc` as reachable (White → Gray). #[inline] - pub fn mark(&mut self, gc: &Gc<'_, T>) { + pub fn mark(&mut self, gc: &Gc<'_, T>) { // SAFETY: `gc.ptr` is a valid `PoolItem>`. unsafe { let gc_box = &(*gc.ptr.as_ptr().as_ptr()).0; if gc_box.color.get() == GcColor::White { gc_box.color.set(GcColor::Gray); - self.worklist.push(( - gc.ptr.as_ptr().cast::(), - crate::collectors::mark_sweep_branded::gc_box::trace_value::, - )); + self.worklist + .push((gc.ptr.as_ptr().cast::(), gc_box.trace_fn)); } } } @@ -151,6 +149,7 @@ empty_trace![ bool, isize, usize, + str, i8, u8, i16, @@ -177,6 +176,8 @@ empty_trace![ core::num::NonZeroU64, core::num::NonZeroI128, core::num::NonZeroU128, + core::sync::atomic::AtomicIsize, + core::sync::atomic::AtomicUsize, ]; unsafe impl Trace for [T; N] { @@ -187,7 +188,7 @@ unsafe impl Trace for [T; N] { } } -unsafe impl Trace for Box { +unsafe impl Trace for Box { unsafe fn trace(&self, tracer: &mut Tracer) { (**self).trace(tracer); } @@ -218,6 +219,15 @@ unsafe impl Trace for Vec { } } +#[cfg(feature = "thin-vec")] +unsafe impl Trace for thin_vec::ThinVec { + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self.iter() { + v.trace(tracer); + } + } +} + unsafe impl Trace for VecDeque { unsafe fn trace(&self, tracer: &mut Tracer) { for v in self.iter() { @@ -239,6 +249,20 @@ unsafe impl Trace for PhantomData { unsafe fn trace(&self, _tracer: &mut Tracer) {} } +unsafe impl Trace for [T] { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} + +unsafe impl Trace for core::any::TypeId { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + // Cell> requires T: Copy to safely read the value via Cell::get(). // For non-Copy types, use GcRefCell instead. unsafe impl Trace for Cell { @@ -293,7 +317,6 @@ unsafe impl Trace for (A, B, C) { } unsafe impl Trace for (A, B, C, D) { - #[inline] unsafe fn trace(&self, tracer: &mut Tracer) { self.0.trace(tracer); self.1.trace(tracer); @@ -302,6 +325,58 @@ unsafe impl Trace for (A, B, C, D) { } } +unsafe impl Trace for (A, B, C, D, E) { + unsafe fn trace(&self, tracer: &mut Tracer) { + self.0.trace(tracer); + self.1.trace(tracer); + self.2.trace(tracer); + self.3.trace(tracer); + self.4.trace(tracer); + } +} + +unsafe impl Trace + for (A, B, C, D, E, F) +{ + unsafe fn trace(&self, tracer: &mut Tracer) { + self.0.trace(tracer); + self.1.trace(tracer); + self.2.trace(tracer); + self.3.trace(tracer); + self.4.trace(tracer); + self.5.trace(tracer); + } +} + +unsafe impl Trace + for (A, B, C, D, E, F, G) +{ + unsafe fn trace(&self, tracer: &mut Tracer) { + self.0.trace(tracer); + self.1.trace(tracer); + self.2.trace(tracer); + self.3.trace(tracer); + self.4.trace(tracer); + self.5.trace(tracer); + self.6.trace(tracer); + } +} + +unsafe impl Trace + for (A, B, C, D, E, F, G, H) +{ + unsafe fn trace(&self, tracer: &mut Tracer) { + self.0.trace(tracer); + self.1.trace(tracer); + self.2.trace(tracer); + self.3.trace(tracer); + self.4.trace(tracer); + self.5.trace(tracer); + self.6.trace(tracer); + self.7.trace(tracer); + } +} + // Rc and Arc do not contain Gc pointers (they use reference counting, not GC). // If you need to store Gc pointers inside Rc/Arc, wrap them in a GC-allocated // struct instead. @@ -333,7 +408,7 @@ unsafe impl Trace for BTreeSet { #[cfg(feature = "icu")] mod icu_trace { - use crate::collectors::mark_sweep_branded::{Finalize, Trace, Tracer}; + use crate::collectors::mark_sweep_branded::{Trace, Tracer}; use icu_locale_core::{LanguageIdentifier, Locale}; unsafe impl Trace for LanguageIdentifier { @@ -347,19 +422,9 @@ mod icu_trace { } } -#[cfg(feature = "boa_string")] -mod boa_string_trace { - use crate::collectors::mark_sweep_branded::{Finalize, Trace, Tracer}; - - unsafe impl Trace for boa_string::JsString { - #[inline] - unsafe fn trace(&self, _tracer: &mut Tracer) {} - } -} - #[cfg(feature = "either")] mod either_trace { - use crate::collectors::mark_sweep_branded::{Finalize, Trace, Tracer}; + use crate::collectors::mark_sweep_branded::{Trace, Tracer}; unsafe impl Trace for either::Either { unsafe fn trace(&self, tracer: &mut Tracer) { diff --git a/oscars/src/collectors/mark_sweep_branded/weak.rs b/oscars/src/collectors/mark_sweep_branded/weak.rs index 1f9c62e..21564d9 100644 --- a/oscars/src/collectors/mark_sweep_branded/weak.rs +++ b/oscars/src/collectors/mark_sweep_branded/weak.rs @@ -17,7 +17,7 @@ pub struct WeakGc<'id, T: Trace + ?Sized> { pub(crate) _marker: PhantomData<*mut &'id ()>, } -impl<'id, T: Trace> WeakGc<'id, T> { +impl<'id, T: Trace + ?Sized> WeakGc<'id, T> { pub(crate) fn with_pointer_and_alloc_id( ptr: PoolPointer<'static, GcBox>, alloc_id: usize, @@ -29,6 +29,21 @@ impl<'id, T: Trace> WeakGc<'id, T> { } } + /// Creates a new weak reference to a GC-managed value. + /// + /// This is a convenience wrapper over [`crate::collectors::mark_sweep_branded::MutationContext::alloc_weak`] that + /// mirrors the `null_collector_branded` `WeakGc::new` API. + #[inline] + pub fn new<'gc>( + cx: &crate::collectors::mark_sweep_branded::MutationContext<'id, 'gc>, + value: &Gc<'gc, T>, + ) -> Self + where + T: Finalize, + { + cx.alloc_weak(value) + } + /// Attempts to upgrade to a strong `Gc<'gc, T>`. pub fn upgrade<'gc>( &self, @@ -45,6 +60,12 @@ impl<'id, T: Trace> WeakGc<'id, T> { None } } + + /// Returns `true` if the referenced value is still alive. + pub fn is_upgradable(&self) -> bool { + let is_valid = unsafe { (*self.ptr.as_ptr().as_ptr()).0.alloc_id == self.alloc_id }; + is_valid + } } impl<'id, T: Trace + ?Sized> Clone for WeakGc<'id, T> { @@ -55,8 +76,24 @@ impl<'id, T: Trace + ?Sized> Clone for WeakGc<'id, T> { impl<'id, T: Trace + ?Sized> Copy for WeakGc<'id, T> {} -impl<'id, T: Trace> Finalize for WeakGc<'id, T> {} -unsafe impl<'id, T: Trace> Trace for WeakGc<'id, T> { +impl<'id, T: Trace + ?Sized> core::fmt::Debug for WeakGc<'id, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("WeakGc") + .field("ptr", &self.ptr.as_ptr()) + .field("alloc_id", &self.alloc_id) + .finish() + } +} + +impl<'id, T: Trace + ?Sized> PartialEq for WeakGc<'id, T> { + fn eq(&self, other: &Self) -> bool { + core::ptr::addr_eq(self.ptr.as_ptr().as_ptr(), other.ptr.as_ptr().as_ptr()) + && self.alloc_id == other.alloc_id + } +} + +impl<'id, T: Trace + ?Sized> Finalize for WeakGc<'id, T> {} +unsafe impl<'id, T: Trace + ?Sized> Trace for WeakGc<'id, T> { // Weak references do not mark their target, upgrade() returning None after collection is the intended behaviour. unsafe fn trace(&self, _tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {} } diff --git a/oscars/src/collectors/null_collector_branded/gc.rs b/oscars/src/collectors/null_collector_branded/gc.rs index 0eb5749..5e8667c 100644 --- a/oscars/src/collectors/null_collector_branded/gc.rs +++ b/oscars/src/collectors/null_collector_branded/gc.rs @@ -46,21 +46,88 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { } impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { - /// Gets a reference to the inner value. + /// Returns a shared reference to the value. #[inline] - pub fn get(&self) -> &T { - unsafe { &self.ptr.as_ptr().as_ref().0.value } + pub(crate) fn inner_ref(&self) -> &T { + // SAFETY: `ptr` is non-null and valid for `'gc` by construction. + unsafe { &(*self.ptr.as_ptr().as_ptr()).0.value } } #[inline] pub fn as_ptr(&self) -> *const T { - self.get() as *const T + self.inner_ref() as *const T + } + + #[inline] + #[must_use] + pub fn ptr_eq(this: &Self, other: &Gc<'gc, U>) -> bool { + core::ptr::eq(this.as_ptr() as *const (), other.as_ptr() as *const ()) + } + + /// Casts the internal pointer to a different type. + /// + /// # Safety + /// The caller must ensure that the inner value is valid for the target type `U`. + #[inline] + pub unsafe fn cast_unchecked(self) -> Gc<'gc, U> { + let raw = self + .ptr + .as_ptr() + .cast::>>(); + Gc { + ptr: unsafe { crate::alloc::mempool3::PoolPointer::from_raw(raw) }, + _marker: core::marker::PhantomData, + } + } + + #[inline] + pub fn is(&self) -> bool { + let actual_type_name = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_name }; + actual_type_name == core::any::type_name::() + } + + #[inline] + #[allow(private_interfaces)] + pub fn into_raw(self) -> core::ptr::NonNull>> { + let ptr = self.ptr.as_ptr(); + let _ = self; + ptr + } + + /// Constructs a `Gc` from a raw pointer. + /// + /// # Safety + /// The pointer must have been previously obtained from `into_raw`. + #[inline] + #[allow(private_interfaces)] + pub unsafe fn from_raw( + ptr: core::ptr::NonNull>>, + ) -> Self { + Self { + ptr: unsafe { crate::alloc::mempool3::PoolPointer::from_raw(ptr) }, + _marker: core::marker::PhantomData, + } } } +impl<'gc, T: Trace + ?Sized + 'gc> AsRef for Gc<'gc, T> { + #[inline] + fn as_ref(&self) -> &T { + self.inner_ref() + } +} + +#[cfg(feature = "std")] +impl<'gc, T: Trace + Finalize + Default + 'gc> Default for Gc<'gc, T> { + fn default() -> Self { + crate::collectors::null_collector_branded::MutationContext::global() + .try_alloc(Default::default()) + .unwrap() + } +} impl<'gc, T: Trace + ?Sized + fmt::Display + 'gc> fmt::Display for Gc<'gc, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self.get(), f) + fmt::Display::fmt(self.inner_ref(), f) } } @@ -68,7 +135,7 @@ impl<'gc, T: Trace + ?Sized + 'gc> Deref for Gc<'gc, T> { type Target = T; #[inline] fn deref(&self) -> &T { - self.get() + self.inner_ref() } } diff --git a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs index ced2fd6..c1bd788 100644 --- a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs +++ b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs @@ -19,6 +19,11 @@ pub struct MutationContext<'id, 'gc> { impl<'id, 'gc> MutationContext<'id, 'gc> { /// Creates a global thread-local MutationContext for the null collector. + /// + /// **Note**: This is a temporary workaround to keep `boa_engine` working. + /// It breaks the normal safety rules of the collector, and should only be + /// used to support older code that relies on `Default` + #[cfg(feature = "std")] pub fn global() -> Self { std::thread_local! { static COLLECTOR: crate::collectors::null_collector_branded::Collector = crate::collectors::null_collector_branded::Collector::new(); diff --git a/oscars/src/collectors/null_collector_branded/trace.rs b/oscars/src/collectors/null_collector_branded/trace.rs index 278f75f..c16c1f5 100644 --- a/oscars/src/collectors/null_collector_branded/trace.rs +++ b/oscars/src/collectors/null_collector_branded/trace.rs @@ -280,12 +280,6 @@ unsafe impl Trace for BTreeSet { unsafe fn trace(&self, _tracer: &mut Tracer) {} } -#[cfg(feature = "boa_string")] -unsafe impl Trace for boa_string::JsString { - #[inline] - unsafe fn trace(&self, _tracer: &mut Tracer) {} -} - unsafe impl Trace for str { #[inline] unsafe fn trace(&self, _tracer: &mut Tracer) {}