diff --git a/mozjs/src/gc/collections.rs b/mozjs/src/gc/collections.rs index 28dd76867f..9b942281b6 100644 --- a/mozjs/src/gc/collections.rs +++ b/mozjs/src/gc/collections.rs @@ -5,6 +5,7 @@ use mozjs_sys::jsapi::JS; use mozjs_sys::jsgc::GCMethods; use mozjs_sys::jsval::JSVal; use mozjs_sys::trace::Traceable; +use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; /// A vector of items to be rooted with `RootedVec`. @@ -157,3 +158,219 @@ impl Drop for RootedTraceableBox { } } } + +/// Inline, fixed capacity buffer of JS values with GC barriers. +/// Backed by `[Heap; N]`, and a manual `len`. +pub struct FixedValueArray { + elems: [Heap; N], + len: usize, +} + +unsafe impl Traceable for FixedValueArray { + unsafe fn trace(&self, trc: *mut JSTracer) { + for i in 0..self.len { + self.elems[i].trace(trc); + } + } +} + +impl FixedValueArray { + /// Create a new empty array, with all slots initialized to Undefined. + pub fn new() -> Self { + Self { + elems: std::array::from_fn(|_| Heap::::default()), + len: 0, + } + } + + /// Current logical length. + pub fn len(&self) -> usize { + self.len + } + + /// Max capacity (const generic). + pub fn capacity(&self) -> usize { + N + } + + /// Push a JSVal into the next slot. + /// Panics if you exceed capacity N. + pub fn push(&mut self, v: JSVal) { + assert!(self.len < N, "FixedValueArray capacity ({}) exceeded", N); + self.elems[self.len].set(v); + self.len += 1; + } + + /// Return a stable HandleValueArray that SpiderMonkey can consume. + pub fn as_handle_value_array(&self) -> JS::HandleValueArray { + JS::HandleValueArray { + length_: self.len, + elements_: self.elems.as_ptr() as *const JSVal, + } + } +} + +/// A rooted wrapper for a FixedValueArray. +pub struct RootedFixedValueArray<'a, const N: usize> { + ptr: *mut FixedValueArray, + _phantom: PhantomData<&'a mut FixedValueArray>, +} + +impl<'a, const N: usize> RootedFixedValueArray<'a, N> { + /// Allocate a FixedValueArray, register it in RootedTraceableSet so the GC + pub fn new() -> Self { + let boxed = Box::new(FixedValueArray::::new()); + let raw = Box::into_raw(boxed); + + unsafe { + RootedTraceableSet::add(raw); + } + + RootedFixedValueArray { + ptr: raw, + _phantom: PhantomData, + } + } + + /// Push a JSVal into the underlying fixed array. + pub fn push(&mut self, v: JSVal) { + unsafe { (&mut *self.ptr).push(v) } + } + + /// Produce a stable HandleValueArray view into the initialized prefix. + pub fn as_handle_value_array(&self) -> JS::HandleValueArray { + unsafe { (&*self.ptr).as_handle_value_array() } + } + + pub fn len(&self) -> usize { + unsafe { (&*self.ptr).len() } + } + + pub fn capacity(&self) -> usize { + unsafe { (&*self.ptr).capacity() } + } +} + +impl<'a, const N: usize> Drop for RootedFixedValueArray<'a, N> { + fn drop(&mut self) { + unsafe { + RootedTraceableSet::remove(self.ptr); + let _ = Box::from_raw(self.ptr); + } + } +} + +/// A growable, rooted, GC traceable collection of JS values. +/// Elements are stored in boxed Heap cells so each GC cell has a stable +/// address even if the Vec reallocates. +pub struct DynamicValueArray { + elems: Vec>>, +} + +unsafe impl Traceable for DynamicValueArray { + unsafe fn trace(&self, trc: *mut JSTracer) { + for heap_box in &self.elems { + heap_box.trace(trc); + } + } +} + +impl DynamicValueArray { + pub fn new() -> Self { + DynamicValueArray { elems: Vec::new() } + } + + pub fn with_capacity(cap: usize) -> Self { + DynamicValueArray { + elems: Vec::with_capacity(cap), + } + } + + pub fn push(&mut self, v: JSVal) { + let cell = Heap::boxed(v); + self.elems.push(cell); + } + + pub fn len(&self) -> usize { + self.elems.len() + } +} + +/// A rooted, wrapper for DynamicValueArray which also owns +/// Vec used to present a HandleValueArray view to SpiderMonkey. +pub struct RootedDynamicValueArray { + ptr: *mut DynamicValueArray, + scratch: Vec, +} + +unsafe impl Traceable for RootedDynamicValueArray { + unsafe fn trace(&self, trc: *mut JSTracer) { + (&*self.ptr).trace(trc) + } +} + +impl RootedDynamicValueArray { + pub fn new() -> Self { + let boxed = Box::new(DynamicValueArray::new()); + let raw = Box::into_raw(boxed); + + unsafe { + RootedTraceableSet::add(raw); + } + + RootedDynamicValueArray { + ptr: raw, + scratch: Vec::new(), + } + } + + pub fn with_capacity(cap: usize) -> Self { + let boxed = Box::new(DynamicValueArray::with_capacity(cap)); + let raw = Box::into_raw(boxed); + + unsafe { + RootedTraceableSet::add(raw); + } + + RootedDynamicValueArray { + ptr: raw, + scratch: Vec::with_capacity(cap), + } + } + + pub fn push(&mut self, v: JSVal) { + unsafe { + (&mut *self.ptr).push(v); + } + } + + fn rebuild_scratch(&mut self) { + let inner = unsafe { &*self.ptr }; + self.scratch.clear(); + self.scratch.reserve(inner.len()); + for heap_box in &inner.elems { + self.scratch.push(heap_box.get()); + } + } + + pub fn as_handle_value_array(&mut self) -> JS::HandleValueArray { + self.rebuild_scratch(); + JS::HandleValueArray { + length_: self.scratch.len(), + elements_: self.scratch.as_ptr(), + } + } + + pub fn len(&self) -> usize { + unsafe { (&*self.ptr).len() } + } +} + +impl Drop for RootedDynamicValueArray { + fn drop(&mut self) { + unsafe { + RootedTraceableSet::remove(self.ptr); + let _ = Box::from_raw(self.ptr); + } + } +} diff --git a/mozjs/src/gc/macros.rs b/mozjs/src/gc/macros.rs index 0fd70c9270..fc575d1ccd 100644 --- a/mozjs/src/gc/macros.rs +++ b/mozjs/src/gc/macros.rs @@ -47,3 +47,23 @@ macro_rules! auto_root { let $($var)+: $crate::rust::CustomAutoRootedGuard<$type> = __root.root($cx); }; } + +#[macro_export] +macro_rules! rooted_fixed_array { + (let mut $name:ident : $cap:expr) => { + let mut $name = $crate::gc::RootedFixedValueArray::<'_, { $cap }>::new(); + }; + (let $name:ident : $cap:expr) => { + let $name = $crate::gc::RootedFixedValueArray::<'_, { $cap }>::new(); + }; +} + +#[macro_export] +macro_rules! rooted_dynamic_array { + (let mut $name:ident) => { + let mut $name = $crate::gc::RootedDynamicValueArray::new(); + }; + (let mut $name:ident : $cap:expr) => { + let mut $name = $crate::gc::RootedDynamicValueArray::with_capacity($cap); + }; +}