diff --git a/Cargo.lock b/Cargo.lock index 6d626a2..9379c7f 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,25 +44,38 @@ 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.git?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.git?branch=main#2bad4acf8da93d25fba617d41d536d0bab07f09d" dependencies = [ "cfg-if", "cow-utils", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "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" @@ -200,11 +219,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" @@ -212,6 +242,12 @@ 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" @@ -246,12 +282,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 +318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -284,9 +343,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 +363,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 +400,13 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" name = "oscars" version = "0.1.0" dependencies = [ + "arrayvec", "boa_gc", + "boa_string", "criterion", - "hashbrown", + "either", + "hashbrown 0.16.1", + "icu_locale_core", "oscars_derive", "rustc-hash", "thin-vec", @@ -351,10 +420,16 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "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" @@ -394,9 +469,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 +527,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" @@ -462,6 +537,12 @@ 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" @@ -498,7 +579,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -523,6 +604,12 @@ 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" @@ -534,6 +621,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 +640,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -566,6 +664,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 +785,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -731,6 +838,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 +861,7 @@ checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/oscars/Cargo.toml b/oscars/Cargo.toml index 37d6221..2d1e2d8 100644 --- a/oscars/Cargo.toml +++ b/oscars/Cargo.toml @@ -8,6 +8,11 @@ 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. +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 } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } @@ -39,3 +44,7 @@ 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/collectors/common.rs b/oscars/src/collectors/common.rs index c76502d..7bd5299 100644 --- a/oscars/src/collectors/common.rs +++ b/oscars/src/collectors/common.rs @@ -178,3 +178,27 @@ 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 = "boa_string")] +impl Finalize for boa_string::JsString {} + +#[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..f96faae 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,22 +442,17 @@ 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 {} - // 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!(); @@ -462,16 +467,12 @@ mod boa_string_trace { 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 {} - unsafe impl Trace for either::Either { custom_trace!(this, mark, { match this { @@ -481,4 +482,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/trace.rs b/oscars/src/collectors/mark_sweep_branded/trace.rs index 8b0118c..61f29e6 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<'_>); @@ -316,3 +330,106 @@ 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::{Finalize, 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 = "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}; + + 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/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..0eb5749 100644 --- a/oscars/src/collectors/null_collector_branded/gc.rs +++ b/oscars/src/collectors/null_collector_branded/gc.rs @@ -33,6 +33,16 @@ 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> { 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..ced2fd6 100644 --- a/oscars/src/collectors/null_collector_branded/mutation_ctx.rs +++ b/oscars/src/collectors/null_collector_branded/mutation_ctx.rs @@ -18,6 +18,35 @@ pub struct MutationContext<'id, 'gc> { } impl<'id, 'gc> MutationContext<'id, 'gc> { + /// Creates a global thread-local MutationContext for the null collector. + 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 +72,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..278f75f 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,134 @@ unsafe impl Trace for BTreeSet { #[inline] 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) {} +} + +#[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) {