diff --git a/Cargo.lock b/Cargo.lock index 6d626a2..0d3e419 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "autocfg" version = "1.5.0" @@ -38,22 +44,22 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "boa_gc" version = "1.0.0-dev" -source = "git+https://github.com/boa-dev/boa?branch=main#6ec44883afffa6edbc4df7df35925a6ef888543d" +source = "git+https://github.com/boa-dev/boa?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" dependencies = [ "boa_macros", - "hashbrown", + "hashbrown 0.17.1", ] [[package]] name = "boa_macros" version = "1.0.0-dev" -source = "git+https://github.com/boa-dev/boa?branch=main#6ec44883afffa6edbc4df7df35925a6ef888543d" +source = "git+https://github.com/boa-dev/boa?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" dependencies = [ "cfg-if", "cow-utils", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -200,11 +206,22 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "either" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "equivalent" @@ -246,12 +263,35 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hermit-abi" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -259,7 +299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -284,9 +324,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" @@ -304,6 +344,12 @@ version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "memchr" version = "2.8.0" @@ -335,9 +381,12 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" name = "oscars" version = "0.1.0" dependencies = [ + "arrayvec", "boa_gc", "criterion", - "hashbrown", + "either", + "hashbrown 0.16.1", + "icu_locale_core", "oscars_derive", "rustc-hash", "thin-vec", @@ -351,7 +400,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -394,9 +443,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -452,9 +501,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustversion" @@ -498,7 +547,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -534,6 +583,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -542,7 +602,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -566,6 +626,15 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "259cdf8ed4e4aca6f1e9d011e10bd53f524a2d0637d7b28450f6c64ac298c4c6" +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -678,7 +747,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -731,6 +800,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "zerocopy" version = "0.8.40" @@ -748,7 +823,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -756,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 37d6221..491bc64 100644 --- a/oscars/Cargo.toml +++ b/oscars/Cargo.toml @@ -8,6 +8,10 @@ hashbrown = "0.16.1" 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. +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 } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } @@ -39,3 +43,6 @@ mark_sweep_branded = ["mark_sweep"] null_collector = ["mark_sweep"] null_collector_branded = ["mark_sweep"] thin-vec = ["dep:thin-vec", "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 c76502d..8f4ae8a 100644 --- a/oscars/src/collectors/common.rs +++ b/oscars/src/collectors/common.rs @@ -178,3 +178,24 @@ impl Finalize for OnceCell {} impl Finalize for Cow<'static, T> where T::Owned: Finalize {} impl Finalize for PhantomData {} + +#[cfg(feature = "icu")] +impl Finalize for icu_locale_core::LanguageIdentifier {} + +#[cfg(feature = "icu")] +impl Finalize for icu_locale_core::Locale {} + +#[cfg(feature = "either")] +impl Finalize for either::Either {} + +#[cfg(feature = "arrayvec")] +impl Finalize for arrayvec::ArrayVec {} + +#[cfg(feature = "std")] +impl Finalize for std::path::Path {} +#[cfg(feature = "std")] +impl Finalize for std::path::PathBuf {} +#[cfg(feature = "std")] +impl Finalize for std::time::Instant {} +#[cfg(feature = "std")] +impl Finalize for std::time::SystemTime {} diff --git a/oscars/src/collectors/mark_sweep/trace.rs b/oscars/src/collectors/mark_sweep/trace.rs index 2a3a5ff..70458f4 100644 --- a/oscars/src/collectors/mark_sweep/trace.rs +++ b/oscars/src/collectors/mark_sweep/trace.rs @@ -54,6 +54,16 @@ pub unsafe trait Trace: Finalize { /// - An incorrect implementation may cause undefined behavior unsafe fn trace(&self, color: TraceColor); + /// Unroots handles located in the GC heap. + /// + /// # Safety + /// + /// Must only be called by the garbage collector. + // TODO: remove in the future + #[inline] + unsafe fn trace_non_roots(&self) {} + + // TODO: remove in the future fn run_finalizer(&self); } @@ -432,45 +442,26 @@ unsafe impl Trace for OnceCell { }); } -/* #[cfg(feature = "icu")] -mod icu { +mod icu_trace { use icu_locale_core::{LanguageIdentifier, Locale}; - use crate::mark_sweep::{Finalize, Trace}; - - impl Finalize for LanguageIdentifier {} + use crate::mark_sweep::Trace; // SAFETY: `LanguageIdentifier` doesn't have any traceable data. unsafe impl Trace for LanguageIdentifier { empty_trace!(); } - impl Finalize for Locale {} - // SAFETY: `LanguageIdentifier` doesn't have any traceable data. unsafe impl Trace for Locale { empty_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!(); - } - - impl Finalize for boa_string::JsString {} -} - #[cfg(feature = "either")] mod either_trace { - use crate::mark_sweep::{Finalize, Trace}; - - impl Finalize for either::Either {} + use crate::mark_sweep::Trace; unsafe impl Trace for either::Either { custom_trace!(this, mark, { @@ -481,4 +472,32 @@ mod either_trace { }); } } -*/ + +#[cfg(feature = "arrayvec")] +unsafe impl Trace for arrayvec::ArrayVec { + custom_trace!(this, mark, { + for v in this { + mark(v); + } + }); +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::Path { + empty_trace!(); +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::PathBuf { + empty_trace!(); +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::Instant { + empty_trace!(); +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::SystemTime { + empty_trace!(); +} 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/mutation_ctx.rs b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs index 5c3c26d..3bfcf61 100644 --- a/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs +++ b/oscars/src/collectors/mark_sweep_branded/mutation_ctx.rs @@ -21,6 +21,24 @@ pub struct MutationContext<'id, 'gc> { } impl<'id, 'gc> MutationContext<'id, 'gc> { + /// 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 +48,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 +70,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,7 +83,7 @@ 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. 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 8b0118c..e48b16b 100644 --- a/oscars/src/collectors/mark_sweep_branded/trace.rs +++ b/oscars/src/collectors/mark_sweep_branded/trace.rs @@ -27,8 +27,22 @@ pub unsafe trait Trace { /// /// # Safety /// - /// See `boa_gc::Trace` for safety contract details. + /// Must only be called by the garbage collector. Implementors must call + /// `Tracer::mark` on all reachable `Gc` fields and avoid other unsafe operations unsafe fn trace(&self, tracer: &mut Tracer); + + /// Unroots handles located in the GC heap. + /// + /// # Safety + /// + /// Must only be called by the garbage collector. + // TODO: remove in the future + #[inline] + unsafe fn trace_non_roots(&self) {} + + // TODO: remove in the future + #[inline] + fn run_finalizer(&self) {} } pub(crate) type TraceFn = unsafe fn(core::ptr::NonNull, &mut Tracer<'_>); @@ -76,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)); } } } @@ -137,6 +149,7 @@ empty_trace![ bool, isize, usize, + str, i8, u8, i16, @@ -163,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] { @@ -173,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); } @@ -204,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() { @@ -225,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 { @@ -279,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); @@ -288,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. @@ -316,3 +405,96 @@ unsafe impl Trace for BTreeSet { // that need tracing (Gc requires &mut self to trace). } } + +#[cfg(feature = "icu")] +mod icu_trace { + use crate::collectors::mark_sweep_branded::{Trace, Tracer}; + use icu_locale_core::{LanguageIdentifier, Locale}; + + unsafe impl Trace for LanguageIdentifier { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} + } + + unsafe impl Trace for Locale { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} + } +} + +#[cfg(feature = "either")] +mod either_trace { + use crate::collectors::mark_sweep_branded::{Trace, Tracer}; + + unsafe impl Trace for either::Either { + unsafe fn trace(&self, tracer: &mut Tracer) { + match self { + either::Either::Left(l) => l.trace(tracer), + either::Either::Right(r) => r.trace(tracer), + } + } + } +} + +#[cfg(feature = "arrayvec")] +unsafe impl Trace for arrayvec::ArrayVec { + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::Path { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::PathBuf { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::Instant { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::SystemTime { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::collections::HashMap { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for (k, v) in self { + k.trace(tracer); + v.trace(tracer); + } + } +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::collections::HashSet { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} + +unsafe impl Trace for rust_alloc::collections::BinaryHeap { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self.iter() { + v.trace(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/ephemeron.rs b/oscars/src/collectors/null_collector_branded/ephemeron.rs index 82bf7d1..209acd9 100644 --- a/oscars/src/collectors/null_collector_branded/ephemeron.rs +++ b/oscars/src/collectors/null_collector_branded/ephemeron.rs @@ -9,14 +9,14 @@ use crate::{ }; use core::marker::PhantomData; -pub struct Ephemeron<'id, K: Trace, V: Trace> { +pub struct Ephemeron<'id, K: Trace + ?Sized, V: Trace> { pub(crate) key_ptr: Option>>, 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>>, value_ptr: PoolPointer<'static, GcBox>, ) -> Self { @@ -27,27 +27,57 @@ impl<'id, K: Trace, V: Trace> Ephemeron<'id, K, V> { } } - /// Returns the value if the key is alive. - pub fn get_value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { - // In the null collector, everything stays alive until context drops. + pub fn new<'gc>(cx: &MutationContext<'id, 'gc>, key: &Gc<'gc, K>, value: V) -> Self + where + V: Finalize, + { + let value_gc = Gc::new(cx, value); + Self::new_raw(Some(key.ptr), value_gc.ptr) + } + + pub fn get_value<'gc>(&self, cx: &MutationContext<'id, 'gc>) -> Option> { + self.value(cx) + } + + /// Returns the key if still live, or `None` if collected. + /// + /// Note: Always returns `Some` in the null collector. + /// TODO: Return `None` when the key is only reachable through this ephemeron. + pub fn key<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { + self.key_ptr.map(|ptr| Gc::with_pointer(ptr)) + } + + /// Returns the value if the key is still live, or `None` if collected. + /// + /// Note: Always returns `Some` in the null collector. + /// TODO: Return `None` when the key is collected. + pub fn value<'gc>(&self, _cx: &MutationContext<'id, 'gc>) -> Option> { if self.key_ptr.is_some() { Some(Gc::with_pointer(self.value_ptr)) } else { None } } + + /// Returns `true` if the key is still live. + /// + /// Note: Always returns `true` in the null collector. + /// TODO: Return `false` once the collector clears `key_ptr`. + pub fn has_value(&self) -> bool { + self.key_ptr.is_some() + } } -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> { unsafe fn trace(&self, _tracer: &mut Tracer) {} } diff --git a/oscars/src/collectors/null_collector_branded/gc.rs b/oscars/src/collectors/null_collector_branded/gc.rs index 32ec3c2..5e8667c 100644 --- a/oscars/src/collectors/null_collector_branded/gc.rs +++ b/oscars/src/collectors/null_collector_branded/gc.rs @@ -33,24 +33,101 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> { _marker: PhantomData, } } + + pub fn new<'id>( + mc: &crate::collectors::null_collector_branded::MutationContext<'id, 'gc>, + value: T, + ) -> Self + where + T: Sized + Finalize, + { + mc.try_alloc(value).unwrap() + } } 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) } } @@ -58,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/mod.rs b/oscars/src/collectors/null_collector_branded/mod.rs index 854ab43..90b0b4c 100644 --- a/oscars/src/collectors/null_collector_branded/mod.rs +++ b/oscars/src/collectors/null_collector_branded/mod.rs @@ -22,7 +22,7 @@ use crate::alloc::mempool3::{PoolAllocError, PoolAllocator}; use core::cell::RefCell; use core::marker::PhantomData; use core::ptr::NonNull; -use gc_box::{DropFn, GcBox}; +pub use gc_box::{DropFn, GcBox}; use rust_alloc::vec::Vec; pub(crate) struct Collector { diff --git a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs index 93a81b8..c1bd788 100644 --- a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs +++ b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs @@ -18,6 +18,40 @@ 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(); + } + COLLECTOR.with(|c| { + let ptr = c as *const crate::collectors::null_collector_branded::Collector; + Self { + collector: unsafe { &*ptr }, + _marker: core::marker::PhantomData, + } + }) + } + + /// Creates a dummy `MutationContext` backed by a dangling pointer. + /// + /// # Safety + /// + /// The returned context is a placeholder and must never be used to + /// allocate or access GC objects. + pub unsafe fn dummy() -> Self { + // SAFETY: We use a dangling pointer for the collector because it's a dummy. + // It should never be used to allocate + Self { + collector: unsafe { &*core::ptr::NonNull::dangling().as_ptr() }, + _marker: PhantomData, + } + } + /// Allocates a value on the GC heap pub fn try_alloc( &self, @@ -43,14 +77,14 @@ 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 next cycle. - pub fn alloc_ephemeron( + pub fn alloc_ephemeron( &self, key: Gc<'gc, K>, value: Gc<'gc, V>, ) -> Ephemeron<'id, K, V> { // In the null collector, ephemerons don't need to be registered // since the collector never collects. - Ephemeron::new(Some(key.ptr), value.ptr) + Ephemeron::new_raw(Some(key.ptr), value.ptr) } pub fn collect(&self) { diff --git a/oscars/src/collectors/null_collector_branded/trace.rs b/oscars/src/collectors/null_collector_branded/trace.rs index be4b7d9..c16c1f5 100644 --- a/oscars/src/collectors/null_collector_branded/trace.rs +++ b/oscars/src/collectors/null_collector_branded/trace.rs @@ -17,14 +17,30 @@ use rust_alloc::vec::Vec; /// /// # Safety /// -/// See `boa_gc::Trace` for safety contract details. +/// Implementors must pass every reachable `Gc` pointer to `Tracer::mark` +/// While the null collector reclaims no memory, implementations must be +/// sound for other collectors to prevent UAF bugs. pub unsafe trait Trace { - /// Marks all Gc pointers reachable from `self`. + /// Marks all `Gc` pointers reachable from `self`. /// /// # Safety /// - /// See `boa_gc::Trace` for safety contract details. + /// Must only be called by the garbage collector. Implementors must call + /// `Tracer::mark` on all reachable `Gc` fields and avoid other unsafe operations. unsafe fn trace(&self, tracer: &mut Tracer); + + /// Unroots handles located in the GC heap. + /// + /// # Safety + /// + /// Must only be called by the garbage collector. + // TODO: remove in the future + #[inline] + unsafe fn trace_non_roots(&self) {} + + // TODO: remove in the future + #[inline] + fn run_finalizer(&self) {} } /// Dummy tracer for the null collector @@ -101,7 +117,6 @@ unsafe impl Trace for [T; N] { } unsafe impl Trace for [T] { - #[inline] unsafe fn trace(&self, tracer: &mut Tracer) { for v in self.iter() { v.trace(tracer); @@ -110,7 +125,6 @@ unsafe impl Trace for [T] { } unsafe impl Trace for Box { - #[inline] unsafe fn trace(&self, tracer: &mut Tracer) { (**self).trace(tracer); } @@ -118,7 +132,6 @@ unsafe impl Trace for Box { #[cfg(feature = "thin-vec")] unsafe impl Trace for thin_vec::ThinVec { - #[inline] unsafe fn trace(&self, tracer: &mut Tracer) { for v in self.iter() { v.trace(tracer); @@ -233,6 +246,17 @@ unsafe impl Trace for (A, B, C, D) { } } +unsafe impl Trace for (A, B, C, D, E) { + #[inline] + 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 rust_alloc::rc::Rc { #[inline] unsafe fn trace(&self, _tracer: &mut Tracer) {} @@ -255,3 +279,128 @@ unsafe impl Trace for BTreeSet { #[inline] unsafe fn trace(&self, _tracer: &mut Tracer) {} } + +unsafe impl Trace for str { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "icu")] +unsafe impl Trace for icu_locale_core::LanguageIdentifier { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "icu")] +unsafe impl Trace for icu_locale_core::Locale { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "either")] +unsafe impl Trace for either::Either { + unsafe fn trace(&self, tracer: &mut Tracer) { + match self { + either::Either::Left(l) => l.trace(tracer), + either::Either::Right(r) => r.trace(tracer), + } + } +} + +#[cfg(feature = "arrayvec")] +unsafe impl Trace for arrayvec::ArrayVec { + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} + +unsafe impl Trace for hashbrown::hash_map::HashMap { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for (k, v) in self { + k.trace(tracer); + v.trace(tracer); + } + } +} +// Finalize is already implemented in common.rs + +unsafe impl Trace for hashbrown::hash_set::HashSet { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} +// Finalize is already implemented in common.rs + +unsafe impl Trace for rust_alloc::collections::BinaryHeap { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) { + // BinaryHeap has no iter_mut(); the null collector's trace is a no-op + // so no values need to be visited here. + } +} +// Finalize is already implemented in common.rs + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::Path { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::path::PathBuf { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::Instant { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::time::SystemTime { + #[inline] + unsafe fn trace(&self, _tracer: &mut Tracer) {} +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::collections::HashMap { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for (k, v) in self { + k.trace(tracer); + v.trace(tracer); + } + } +} + +#[cfg(feature = "std")] +unsafe impl Trace for std::collections::HashSet { + #[inline] + unsafe fn trace(&self, tracer: &mut Tracer) { + for v in self { + v.trace(tracer); + } + } +} + +empty_trace!( + core::sync::atomic::AtomicBool, + core::sync::atomic::AtomicI8, + core::sync::atomic::AtomicU8, + core::sync::atomic::AtomicI16, + core::sync::atomic::AtomicU16, + core::sync::atomic::AtomicI32, + core::sync::atomic::AtomicU32, + core::sync::atomic::AtomicIsize, + core::sync::atomic::AtomicUsize, + core::sync::atomic::AtomicI64, + core::sync::atomic::AtomicU64 +); diff --git a/oscars/src/collectors/null_collector_branded/weak.rs b/oscars/src/collectors/null_collector_branded/weak.rs index db5e56e..012ca87 100644 --- a/oscars/src/collectors/null_collector_branded/weak.rs +++ b/oscars/src/collectors/null_collector_branded/weak.rs @@ -22,6 +22,16 @@ impl<'id, T: Trace + ?Sized> WeakGc<'id, T> { } } + pub fn new<'gc>( + mc: &crate::collectors::null_collector_branded::MutationContext<'id, 'gc>, + inner: &Gc<'gc, T>, + ) -> Self + where + T: Sized + Finalize, + { + mc.alloc_weak(*inner) + } + /// Attempts to upgrade to a strong `Gc<'gc, T>` pub fn upgrade<'gc>( &self, @@ -30,6 +40,10 @@ impl<'id, T: Trace + ?Sized> WeakGc<'id, T> { // In the null collector, everything stays alive until context drops. Some(Gc::with_pointer(self.ptr)) } + + pub fn is_upgradable(&self) -> bool { + true + } } impl<'id, T: Trace + ?Sized> Clone for WeakGc<'id, T> { @@ -40,6 +54,20 @@ impl<'id, T: Trace + ?Sized> Clone for WeakGc<'id, T> { impl<'id, T: Trace + ?Sized> Copy 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 { + write!(f, "WeakGc({:p})", self.ptr.as_ptr()) + } +} + +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()) + } +} + +impl<'id, T: Trace + ?Sized> Eq for WeakGc<'id, T> {} + impl<'id, T: Trace + ?Sized> Finalize for WeakGc<'id, T> {} unsafe impl<'id, T: Trace + ?Sized> Trace for WeakGc<'id, T> { unsafe fn trace(&self, _tracer: &mut crate::collectors::null_collector_branded::trace::Tracer) {