Skip to content

Commit

Permalink
Support rooting arbitrary types (#535)
Browse files Browse the repository at this point in the history
* Support rooting optional Traceable values.

Signed-off-by: Josh Matthews <[email protected]>

* Separate heap barrier operations from notion of safe initialization.

Signed-off-by: Josh Matthews <[email protected]>

* Mark Rooted/RootedGuard/CustomAutoRooter/CustomAutoRooterGuard with crown annotations.

Signed-off-by: Josh Matthews <[email protected]>

* Re-export TraceableTrace for custom rootable types.

Signed-off-by: Josh Matthews <[email protected]>

* Move Traceable trait to mozjs-sys.

Signed-off-by: Josh Matthews <[email protected]>

* Rename TraceableTrace to Rootable, and reduce overlap with Traceable.

Signed-off-by: Josh Matthews <[email protected]>

* Add test for new Rootable usage.

Signed-off-by: Josh Matthews <[email protected]>

* Formatting.

Signed-off-by: Josh Matthews <[email protected]>

---------

Signed-off-by: Josh Matthews <[email protected]>
  • Loading branch information
jdm authored Dec 25, 2024
1 parent e68fdba commit 1765a7c
Show file tree
Hide file tree
Showing 12 changed files with 622 additions and 503 deletions.
1 change: 1 addition & 0 deletions mozjs-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ doctest = false
debugmozjs = []
profilemozjs = []
jitspew = []
crown = []

[dependencies]
libc.workspace = true
Expand Down
160 changes: 97 additions & 63 deletions mozjs-sys/src/jsgc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::glue::CallPropertyDescriptorTracer;
use crate::jsapi::js::TraceValueArray;
use crate::jsapi::JS;
use crate::jsapi::{jsid, JSFunction, JSObject, JSScript, JSString, JSTracer};

use crate::jsid::VoidId;
use std::cell::UnsafeCell;
use std::ffi::{c_char, c_void};
Expand Down Expand Up @@ -68,9 +65,9 @@ impl RootKind for JS::Value {
const KIND: JS::RootKind = JS::RootKind::Value;
}

impl<T: TraceableTrace> RootKind for T {
impl<T: Rootable> RootKind for T {
type Vtable = *const RootedVFTable;
const VTABLE: Self::Vtable = &<Self as TraceableTrace>::VTABLE;
const VTABLE: Self::Vtable = &<Self as Rootable>::VTABLE;
const KIND: JS::RootKind = JS::RootKind::Traceable;
}

Expand All @@ -93,32 +90,25 @@ impl RootedVFTable {
pub const PADDING: [usize; 2] = [0, 0];
}

/// `Rooted<T>` with a T that uses the Traceable RootKind uses dynamic dispatch on the C++ side
/// for custom tracing. This trait provides trace logic via a vtable when creating a Rust instance
/// of the object.
pub unsafe trait TraceableTrace: Sized {
/// Marker trait that allows any type that implements the [trace::Traceable] trait to be used
/// with the [Rooted] type.
///
/// `Rooted<T>` relies on dynamic dispatch in C++ when T uses the Traceable RootKind.
/// This trait initializes the vtable when creating a Rust instance of the Rooted object.
pub trait Rootable: crate::trace::Traceable + Sized {
const VTABLE: RootedVFTable = RootedVFTable {
padding: RootedVFTable::PADDING,
trace: Self::trace,
trace: <Self as Rootable>::trace,
};

unsafe extern "C" fn trace(this: *mut c_void, trc: *mut JSTracer, _name: *const c_char) {
let rooted = this as *mut Rooted<Self>;
let rooted = rooted.as_mut().unwrap();
Self::do_trace(&mut rooted.ptr, trc);
<Self as crate::trace::Traceable>::trace(&mut rooted.ptr, trc);
}

/// Used by `TraceableTrace` implementer to trace its contents.
/// Corresponds to virtual `trace` call in a `Rooted` that inherits from
/// StackRootedTraceableBase (C++).
unsafe fn do_trace(&mut self, trc: *mut JSTracer);
}

unsafe impl TraceableTrace for JS::PropertyDescriptor {
unsafe fn do_trace(&mut self, trc: *mut JSTracer) {
CallPropertyDescriptorTracer(trc, self);
}
}
impl<T: Rootable> Rootable for Option<T> {}

// The C++ representation of Rooted<T> inherits from StackRootedBase, which
// contains the actual pointers that get manipulated. The Rust representation
Expand All @@ -135,37 +125,63 @@ pub struct RootedBase {

// Annoyingly, bindgen can't cope with SM's use of templates, so we have to roll our own.
#[repr(C)]
#[derive(Debug)]
#[cfg_attr(
feature = "crown",
crown::unrooted_must_root_lint::allow_unrooted_interior
)]
pub struct Rooted<T: RootKind> {
pub vtable: T::Vtable,
pub base: RootedBase,
pub ptr: T,
}

/// Trait that provides a GC-safe default value for the given type, if one exists.
pub trait Initialize: Sized {
/// Create a default value. If there is no meaningful default possible, returns None.
/// SAFETY:
/// The default must not be a value that can be meaningfully garbage collected.
unsafe fn initial() -> Option<Self>;
}

impl<T> Initialize for Option<T> {
unsafe fn initial() -> Option<Self> {
Some(None)
}
}

/// A trait for types which can place appropriate GC barriers.
/// * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/Garbage_collection#Incremental_marking
/// * https://dxr.mozilla.org/mozilla-central/source/js/src/gc/Barrier.h
pub trait GCMethods {
pub trait GCMethods: Initialize {
/// Create a default value
unsafe fn initial() -> Self;
unsafe fn initial() -> Self {
<Self as Initialize>::initial()
.expect("Types used with heap GC methods must have a valid default")
}

/// Place a post-write barrier
unsafe fn post_barrier(v: *mut Self, prev: Self, next: Self);
}

impl GCMethods for *mut JSObject {
unsafe fn initial() -> *mut JSObject {
ptr::null_mut()
impl Initialize for *mut JSObject {
unsafe fn initial() -> Option<*mut JSObject> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JSObject {
unsafe fn post_barrier(v: *mut *mut JSObject, prev: *mut JSObject, next: *mut JSObject) {
JS::HeapObjectWriteBarriers(v, prev, next);
}
}

impl GCMethods for *mut JSFunction {
unsafe fn initial() -> *mut JSFunction {
ptr::null_mut()
impl Initialize for *mut JSFunction {
unsafe fn initial() -> Option<*mut JSFunction> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JSFunction {
unsafe fn post_barrier(v: *mut *mut JSFunction, prev: *mut JSFunction, next: *mut JSFunction) {
JS::HeapObjectWriteBarriers(
mem::transmute(v),
Expand All @@ -175,60 +191,83 @@ impl GCMethods for *mut JSFunction {
}
}

impl GCMethods for *mut JSString {
unsafe fn initial() -> *mut JSString {
ptr::null_mut()
impl Initialize for *mut JSString {
unsafe fn initial() -> Option<*mut JSString> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JSString {
unsafe fn post_barrier(v: *mut *mut JSString, prev: *mut JSString, next: *mut JSString) {
JS::HeapStringWriteBarriers(v, prev, next);
}
}

impl GCMethods for *mut JS::Symbol {
unsafe fn initial() -> *mut JS::Symbol {
ptr::null_mut()
impl Initialize for *mut JS::Symbol {
unsafe fn initial() -> Option<*mut JS::Symbol> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JS::Symbol {
unsafe fn post_barrier(_: *mut *mut JS::Symbol, _: *mut JS::Symbol, _: *mut JS::Symbol) {}
}

impl GCMethods for *mut JS::BigInt {
unsafe fn initial() -> *mut JS::BigInt {
ptr::null_mut()
impl Initialize for *mut JS::BigInt {
unsafe fn initial() -> Option<*mut JS::BigInt> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JS::BigInt {
unsafe fn post_barrier(v: *mut *mut JS::BigInt, prev: *mut JS::BigInt, next: *mut JS::BigInt) {
JS::HeapBigIntWriteBarriers(v, prev, next);
}
}

impl GCMethods for *mut JSScript {
unsafe fn initial() -> *mut JSScript {
ptr::null_mut()
impl Initialize for *mut JSScript {
unsafe fn initial() -> Option<*mut JSScript> {
Some(ptr::null_mut())
}
}

impl GCMethods for *mut JSScript {
unsafe fn post_barrier(v: *mut *mut JSScript, prev: *mut JSScript, next: *mut JSScript) {
JS::HeapScriptWriteBarriers(v, prev, next);
}
}

impl GCMethods for jsid {
unsafe fn initial() -> jsid {
VoidId()
impl Initialize for jsid {
unsafe fn initial() -> Option<jsid> {
Some(VoidId())
}
}

impl GCMethods for jsid {
unsafe fn post_barrier(_: *mut jsid, _: jsid, _: jsid) {}
}

impl GCMethods for JS::Value {
unsafe fn initial() -> JS::Value {
JS::Value::default()
impl Initialize for JS::Value {
unsafe fn initial() -> Option<JS::Value> {
Some(JS::Value::default())
}
}

impl GCMethods for JS::Value {
unsafe fn post_barrier(v: *mut JS::Value, prev: JS::Value, next: JS::Value) {
JS::HeapValueWriteBarriers(v, &prev, &next);
}
}

impl GCMethods for JS::PropertyDescriptor {
unsafe fn initial() -> JS::PropertyDescriptor {
JS::PropertyDescriptor::default()
impl Rootable for JS::PropertyDescriptor {}

impl Initialize for JS::PropertyDescriptor {
unsafe fn initial() -> Option<JS::PropertyDescriptor> {
Some(JS::PropertyDescriptor::default())
}
}

impl GCMethods for JS::PropertyDescriptor {
unsafe fn post_barrier(
_: *mut JS::PropertyDescriptor,
_: JS::PropertyDescriptor,
Expand Down Expand Up @@ -258,19 +297,14 @@ impl<const N: usize> ValueArray<N> {
}
}

unsafe impl<const N: usize> TraceableTrace for ValueArray<N> {
unsafe fn do_trace(&mut self, trc: *mut JSTracer) {
TraceValueArray(trc, N, self.get_mut_ptr());
}
}
impl<const N: usize> Rootable for ValueArray<N> {}

impl<const N: usize> GCMethods for ValueArray<N> {
unsafe fn initial() -> Self {
Self {
elements: [JS::Value::initial(); N],
}
impl<const N: usize> Initialize for ValueArray<N> {
unsafe fn initial() -> Option<Self> {
Some(Self {
elements: [<JS::Value as GCMethods>::initial(); N],
})
}
unsafe fn post_barrier(_: *mut Self, _: Self, _: Self) {}
}

/// RootedValueArray roots an internal fixed-size array of Values
Expand Down Expand Up @@ -371,7 +405,7 @@ impl<T: GCMethods + Copy> Drop for Heap<T> {
fn drop(&mut self) {
unsafe {
let ptr = self.ptr.get();
T::post_barrier(ptr, *ptr, T::initial());
T::post_barrier(ptr, *ptr, <T as GCMethods>::initial());
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions mozjs-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#![allow(unused_extern_crates)]
#![cfg_attr(feature = "crown", feature(register_tool))]
#![cfg_attr(feature = "crown", register_tool(crown))]

// These extern crates are needed for linking
extern crate encoding_c;
Expand All @@ -18,12 +20,14 @@ pub mod glue;
pub mod jsgc;
pub mod jsid;
pub mod jsval;
pub mod trace;

// Reexport the bindings in the jsapi module
pub use crate::generated::root as jsapi;

// The bindings generated by bindgen
#[doc(hidden)]
#[allow(dead_code)]
mod generated {
include!(concat!(env!("OUT_DIR"), "/build/jsapi.rs"));
}
Expand Down
Loading

0 comments on commit 1765a7c

Please sign in to comment.