From 25ee9016e2e60dd98255a752aff557bd82561dc9 Mon Sep 17 00:00:00 2001 From: Alistair Date: Thu, 14 Nov 2024 14:57:40 +0000 Subject: [PATCH] feat(jstz_engine): define the `Trace` and `Finalize` traits This commit also implements the `Trace` and `Finalize` trait for many common types. --- crates/jstz_engine/src/gc/mod.rs | 3 + crates/jstz_engine/src/gc/ptr.rs | 15 +- crates/jstz_engine/src/gc/trace.rs | 344 +++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+), 3 deletions(-) create mode 100644 crates/jstz_engine/src/gc/trace.rs diff --git a/crates/jstz_engine/src/gc/mod.rs b/crates/jstz_engine/src/gc/mod.rs index 5ddbd9d7a..b72df5f85 100644 --- a/crates/jstz_engine/src/gc/mod.rs +++ b/crates/jstz_engine/src/gc/mod.rs @@ -20,3 +20,6 @@ //! For further details, see the [GC Implementation Guide](https://udn.realityripple.com/docs/Mozilla/Projects/SpiderMonkey/Internals/Garbage_collection). mod ptr; +mod trace; + +pub use trace::{Finalize, Trace, Tracer}; diff --git a/crates/jstz_engine/src/gc/ptr.rs b/crates/jstz_engine/src/gc/ptr.rs index 2be6a7dbd..ca8753057 100644 --- a/crates/jstz_engine/src/gc/ptr.rs +++ b/crates/jstz_engine/src/gc/ptr.rs @@ -6,9 +6,10 @@ use std::{cell::UnsafeCell, marker::PhantomPinned, mem, pin::Pin, ptr, sync::Arc use mozjs::{ jsapi::{ - jsid, HeapBigIntWriteBarriers, HeapObjectWriteBarriers, HeapScriptWriteBarriers, - HeapStringWriteBarriers, HeapValueWriteBarriers, JSFunction, JSObject, JSScript, - JSString, JS::BigInt as JSBigInt, JS::Symbol as JSSymbol, + jsid, Heap, HeapBigIntWriteBarriers, HeapObjectWriteBarriers, + HeapScriptWriteBarriers, HeapStringWriteBarriers, HeapValueWriteBarriers, + JSFunction, JSObject, JSScript, JSString, + JS::{BigInt as JSBigInt, Symbol as JSSymbol}, }, jsid::VoidId, jsval::{JSVal, UndefinedValue}, @@ -110,6 +111,14 @@ impl GcPtr { T::write_barrier(self_ptr, prev, next) } } + + /// Returns a mozjs Heap pointer + pub fn as_heap_ptr(&self) -> *mut Heap + where + T: mozjs::gc::GCMethods, + { + &*self.inner_ptr as *const _ as *mut _ + } } impl Drop for GcPtr { diff --git a/crates/jstz_engine/src/gc/trace.rs b/crates/jstz_engine/src/gc/trace.rs new file mode 100644 index 000000000..8bf83a553 --- /dev/null +++ b/crates/jstz_engine/src/gc/trace.rs @@ -0,0 +1,344 @@ +use std::{ + any::TypeId, + marker::PhantomData, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, + NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + }, + path::{Path, PathBuf}, + rc::Rc, +}; + +use mozjs::{ + c_str, + glue::{ + CallBigIntTracer, CallFunctionTracer, CallIdTracer, CallObjectTracer, + CallScriptTracer, CallStringTracer, CallSymbolTracer, CallValueTracer, + }, + jsapi::{ + jsid as JSId, BigInt as JSBigInt, JSFunction, JSObject, JSScript, JSString, + Symbol as JSSymbol, Value as JSValue, + }, +}; + +/// Visitor passed to trace methods. ALl managed pointers +/// must be traced by this visitor. +pub use mozjs::jsapi::JSTracer as Tracer; + +use super::ptr::GcPtr; + +/// The Trace trait, which needs to be implemented on garbage-collected objects. +/// +/// # Safety +/// +/// - An incorrect implementation of the trait can result in heap overflows, data corruption, +/// use-after-free, or Undefined Behaviour in general. +/// +/// - Calling any of the functions marked as `unsafe` outside of the context of the garbage collector +/// can result in Undefined Behaviour. +pub unsafe trait Trace: Finalize { + /// Marks all contained traceable objects. + unsafe fn trace(&self, trc: *mut Tracer); + + /// Runs [`Finalize::finalize`] on the object and its children. + fn run_finalizer(&self); +} + +/// Substitute for the [`Drop`] trait for garbage collected types. +pub trait Finalize { + /// Cleanup logic for a type. + fn finalize(&self) {} +} + +/// Utility macro to define an empty implementation of [`Trace`]. +/// +/// Use this for marking types as not containing any `Trace` types. +#[macro_export] +macro_rules! empty_trace { + () => { + unsafe fn trace(&self, _trc: *mut $crate::gc::Tracer) {} + + fn run_finalizer(&self) { + $crate::gc::Finalize::finalize(self); + } + }; +} + +/// Utility macro to manually implement [`Trace`] on a type. +/// +/// You define a `this` parameter name and pass in a body, which should call `mark` on every +/// traceable element inside the body. The mark implementation will automatically delegate to the +/// correct method on the argument. +/// +/// # Safety +/// +/// Misusing the `mark` function may result in Undefined Behaviour. +#[macro_export] +macro_rules! custom_trace { + ($this:ident, $marker:ident, $body:expr) => { + unsafe fn trace(&self, trc: *mut $crate::gc::Tracer) { + let $marker = |it: &dyn $crate::gc::Trace| { + // SAFETY: The implementor must ensure that `trace` is correctly implemented. + unsafe { + $crate::gc::Trace::trace(it, trc); + } + }; + let $this = self; + $body + } + + fn run_finalizer(&self) { + let $marker = |it: &dyn $crate::gc::Finalize| { + $crate::gc::Finalize::finalize(it); + }; + let $this = self; + $body + } + }; +} + +impl Finalize for &'static T {} +// SAFETY: 'static references don't need to be traced, since they live indefinitely. +unsafe impl Trace for &'static T { + empty_trace!(); +} + +macro_rules! impl_empty_finalize_trace { + ($($T:ty),*) => { + $( + impl Finalize for $T {} + + // SAFETY: + // Primitive types and string types don't have inner nodes that need to be marked. + unsafe impl Trace for $T { empty_trace!(); } + )* + } +} + +impl_empty_finalize_trace!( + (), + bool, + isize, + usize, + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + i128, + u128, + f32, + f64, + char, + TypeId, + String, + Box, + Rc, + Path, + PathBuf, + NonZeroIsize, + NonZeroUsize, + NonZeroI8, + NonZeroU8, + NonZeroI16, + NonZeroU16, + NonZeroI32, + NonZeroU32, + NonZeroI64, + NonZeroU64, + NonZeroI128, + NonZeroU128 +); + +impl Finalize for [T; N] {} + +// SAFETY: +// All elements inside the array are correctly marked. +unsafe impl Trace for [T; N] { + custom_trace!(this, mark, { + for v in this { + mark(v); + } + }); +} + +impl Finalize for Box {} + +// SAFETY: The inner value of the `Box` is correctly marked. +unsafe impl Trace for Box { + #[inline] + unsafe fn trace(&self, trc: *mut Tracer) { + // SAFETY: The implementor must ensure that `trace` is correctly implemented. + Trace::trace(&**self, trc); + } + + #[inline] + fn run_finalizer(&self) { + Finalize::finalize(self); + Trace::run_finalizer(&**self); + } +} + +impl Finalize for Vec {} + +// SAFETY: All the inner elements of the `Vec` are correctly marked. +unsafe impl Trace for Vec { + custom_trace!(this, mark, { + for e in this { + mark(e); + } + }); +} + +impl Finalize for Option {} + +// SAFETY: The inner value of the `Option` is correctly marked. +unsafe impl Trace for Option { + custom_trace!(this, mark, { + if let Some(ref v) = *this { + mark(v); + } + }); +} + +impl Finalize for Result {} + +// SAFETY: Both inner values of the `Result` are correctly marked. +unsafe impl Trace for Result { + custom_trace!(this, mark, { + match *this { + Ok(ref v) => mark(v), + Err(ref v) => mark(v), + } + }); +} + +impl Finalize for PhantomData {} + +// SAFETY: A `PhantomData` doesn't have inner data that needs to be marked. +unsafe impl Trace for PhantomData { + empty_trace!(); +} + +macro_rules! fn_finalize_trace_one { + ($ty:ty $(,$args:ident)*) => { + impl Finalize for $ty {} + // SAFETY: + // Function pointers don't have inner nodes that need to be marked. + unsafe impl Trace for $ty { empty_trace!(); } + } +} +macro_rules! fn_finalize_trace_group { + () => { + fn_finalize_trace_one!(extern "Rust" fn () -> Ret); + fn_finalize_trace_one!(extern "C" fn () -> Ret); + fn_finalize_trace_one!(unsafe extern "Rust" fn () -> Ret); + fn_finalize_trace_one!(unsafe extern "C" fn () -> Ret); + }; + ($($args:ident),*) => { + fn_finalize_trace_one!(extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_finalize_trace_one!(unsafe extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + } +} + +macro_rules! tuple_finalize_trace { + () => {}; // This case is handled above, by simple_finalize_empty_trace!(). + ($($args:ident),*) => { + impl<$($args),*> Finalize for ($($args,)*) {} + // SAFETY: + // All elements inside the tuple are correctly marked. + unsafe impl<$($args: Trace),*> Trace for ($($args,)*) { + custom_trace!(this, mark, { + #[allow(non_snake_case, unused_unsafe, unused_mut)] + let mut avoid_lints = |&($(ref $args,)*): &($($args,)*)| { + // SAFETY: The implementor must ensure a correct implementation. + unsafe { $(mark($args);)* } + }; + avoid_lints(this) + }); + } + } +} + +macro_rules! type_arg_tuple_based_finalize_trace_impls { + ($(($($args:ident),*),)*) => { + $( + fn_finalize_trace_group!($($args),*); + tuple_finalize_trace!($($args),*); + )* + } +} + +type_arg_tuple_based_finalize_trace_impls!( + (), + (A), + (A, B), + (A, B, C), + (A, B, C, D), + (A, B, C, D, E), + (A, B, C, D, E, F), + (A, B, C, D, E, F, G), + (A, B, C, D, E, F, G, H), + (A, B, C, D, E, F, G, H, I), + (A, B, C, D, E, F, G, H, I, J), + (A, B, C, D, E, F, G, H, I, J, K), + (A, B, C, D, E, F, G, H, I, J, K, L), +); + +macro_rules! impl_gcptr_finalize_trace { + + ($((*mut $T:ty, $tracer:ident, $name:literal)),*) => { + $( + impl Finalize for GcPtr<*mut $T> {} + + // SAFETY: The function is correctly traced using SM's API. + unsafe impl Trace for GcPtr<*mut $T> { + unsafe fn trace(&self, trc: *mut Tracer) { + if self.get().is_null() { + return; + } + $tracer(trc, self.as_heap_ptr(), c_str!($name)) + } + + fn run_finalizer(&self) { + Finalize::finalize(self); + } + } + )* + }; + + ($(($T:ty, $tracer:ident, $name:literal)),*) => { + $( + impl Finalize for GcPtr<$T> {} + + // SAFETY: The function is correctly traced using SM's API. + unsafe impl Trace for GcPtr<$T> { + unsafe fn trace(&self, trc: *mut Tracer) { + $tracer(trc, self.as_heap_ptr(), c_str!($name)) + } + + fn run_finalizer(&self) { + Finalize::finalize(self); + } + } + )* + }; +} + +impl_gcptr_finalize_trace!( + (*mut JSFunction, CallFunctionTracer, "function"), + (*mut JSObject, CallObjectTracer, "object"), + (*mut JSSymbol, CallSymbolTracer, "symbol"), + (*mut JSBigInt, CallBigIntTracer, "bigint"), + (*mut JSScript, CallScriptTracer, "script"), + (*mut JSString, CallStringTracer, "string"), + (JSId, CallIdTracer, "id"), + (JSValue, CallValueTracer, "value") +);