Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions mozjs/src/gc/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -157,3 +158,219 @@ impl<T: Traceable + 'static> Drop for RootedTraceableBox<T> {
}
}
}

/// Inline, fixed capacity buffer of JS values with GC barriers.
/// Backed by `[Heap<JSVal>; N]`, and a manual `len`.
pub struct FixedValueArray<const N: usize> {
elems: [Heap<JSVal>; N],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jdm what do you think about this? and how it is used in servo/servo#40166 ?

len: usize,
}

unsafe impl<const N: usize> Traceable for FixedValueArray<N> {
unsafe fn trace(&self, trc: *mut JSTracer) {
for i in 0..self.len {
self.elems[i].trace(trc);
}
}
}

impl<const N: usize> FixedValueArray<N> {
/// Create a new empty array, with all slots initialized to Undefined.
pub fn new() -> Self {
Self {
elems: std::array::from_fn(|_| Heap::<JSVal>::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<N>.
pub struct RootedFixedValueArray<'a, const N: usize> {
ptr: *mut FixedValueArray<N>,
_phantom: PhantomData<&'a mut FixedValueArray<N>>,
}

impl<'a, const N: usize> RootedFixedValueArray<'a, N> {
/// Allocate a FixedValueArray<N>, register it in RootedTraceableSet so the GC
pub fn new() -> Self {
let boxed = Box::new(FixedValueArray::<N>::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<JSVal> cells so each GC cell has a stable
/// address even if the Vec reallocates.
pub struct DynamicValueArray {
elems: Vec<Box<Heap<JSVal>>>,
}

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<JSVal> used to present a HandleValueArray view to SpiderMonkey.
pub struct RootedDynamicValueArray {
ptr: *mut DynamicValueArray,
scratch: Vec<JSVal>,
}

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);
}
}
}
20 changes: 20 additions & 0 deletions mozjs/src/gc/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
}
Loading