Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[[HostDefined]] Improvements #3460

Merged
merged 2 commits into from
Nov 10, 2023
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions boa_engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ writeable = { workspace = true, optional = true }
yoke = { workspace = true, optional = true }
zerofrom = { workspace = true, optional = true }
fixed_decimal = { workspace = true, features = ["ryu"], optional = true}
hashbrown.workspace = true

[target.'cfg(all(target_family = "wasm", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies]
web-time = { version = "0.2.3", optional = true }
Expand Down
173 changes: 108 additions & 65 deletions boa_engine/src/host_defined.rs
Original file line number Diff line number Diff line change
@@ -1,111 +1,154 @@
use std::any::TypeId;

use boa_gc::{GcRef, GcRefCell, GcRefMut};
use boa_macros::{Finalize, Trace};
use rustc_hash::FxHashMap;
use hashbrown::hash_map::HashMap;

use crate::object::NativeObject;

/// Map used to store the host defined objects.
#[doc(hidden)]
type HostDefinedMap = FxHashMap<TypeId, Box<dyn NativeObject>>;

/// This represents a `ECMASCript` specification \[`HostDefined`\] field.
///
/// This allows storing types which are mapped by their [`TypeId`].
#[derive(Default, Trace, Finalize)]
#[allow(missing_debug_implementations)]
pub struct HostDefined {
state: GcRefCell<HostDefinedMap>,
// INVARIANT: All key-value pairs `(id, obj)` satisfy:
// `id == TypeId::of::<T>() && obj.is::<T>()`
// for some type `T : NativeObject`.
types: HashMap<TypeId, Box<dyn NativeObject>>,
}

// TODO: Track https://github.com/rust-lang/rust/issues/65991 and
// https://github.com/rust-lang/rust/issues/90850 to remove this
// when those are stabilized.
fn downcast_boxed_native_object_unchecked<T: NativeObject>(obj: Box<dyn NativeObject>) -> Box<T> {
let raw: *mut dyn NativeObject = Box::into_raw(obj);

// SAFETY: We know that `obj` is of type `T` (due to the INVARIANT of `HostDefined`).
// See `HostDefined::insert`, `HostDefined::insert_default` and `HostDefined::remove`.
unsafe { Box::from_raw(raw.cast::<T>()) }
}

impl HostDefined {
/// Insert a type into the [`HostDefined`].
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn insert_default<T: NativeObject + Default>(&self) -> Option<Box<dyn NativeObject>> {
self.state
.borrow_mut()
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::<T>::default())
.map(downcast_boxed_native_object_unchecked)
}

/// Insert a type into the [`HostDefined`].
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn insert<T: NativeObject>(&self, value: T) -> Option<Box<dyn NativeObject>> {
self.state
.borrow_mut()
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::new(value))
.map(downcast_boxed_native_object_unchecked)
}

/// Check if the [`HostDefined`] has type T.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed mutably.
#[must_use]
#[track_caller]
pub fn has<T: NativeObject>(&self) -> bool {
self.state.borrow().contains_key(&TypeId::of::<T>())
self.types.contains_key(&TypeId::of::<T>())
}

/// Remove type T from [`HostDefined`], if it exists.
///
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn remove<T: NativeObject>(&self) -> Option<Box<dyn NativeObject>> {
self.state.borrow_mut().remove(&TypeId::of::<T>())
pub fn remove<T: NativeObject>(&mut self) -> Option<Box<T>> {
self.types
.remove(&TypeId::of::<T>())
.map(downcast_boxed_native_object_unchecked)
}

/// Get type T from [`HostDefined`], if it exits.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Get type T from [`HostDefined`], if it exists.
#[track_caller]
pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> {
GcRef::try_map(self.state.borrow(), |state| {
state
.get(&TypeId::of::<T>())
.map(Box::as_ref)
.and_then(<dyn NativeObject>::downcast_ref::<T>)
})
pub fn get<T: NativeObject>(&self) -> Option<&T> {
self.types
.get(&TypeId::of::<T>())
.map(Box::as_ref)
.and_then(<dyn NativeObject>::downcast_ref::<T>)
}

/// Get type T from [`HostDefined`], if it exits.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Get type T from [`HostDefined`], if it exists.
#[track_caller]
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> {
GcRefMut::try_map(
self.state.borrow_mut(),
|state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| {
state
.get_mut(&TypeId::of::<T>())
.map(Box::as_mut)
.and_then(<dyn NativeObject>::downcast_mut::<T>)
},
)
pub fn get_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
self.types
.get_mut(&TypeId::of::<T>())
.map(Box::as_mut)
.and_then(<dyn NativeObject>::downcast_mut::<T>)
}

/// Get type a tuple of types from [`HostDefined`], if they exist.
#[track_caller]
pub fn get_many_mut<T, const SIZE: usize>(&mut self) -> Option<T::NativeTupleMutRef<'_>>
where
T: NativeTuple<SIZE>,
{
let ids = T::as_type_ids();
let refs: [&TypeId; SIZE] = ids
.iter()
.collect::<Vec<_>>()
.try_into()
.expect("tuple should be of size `SIZE`");

self.types.get_many_mut(refs).and_then(T::mut_ref_from_anys)
}

/// Clears all the objects.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn clear(&self) {
self.state.borrow_mut().clear();
pub fn clear(&mut self) {
self.types.clear();
}
}

/// This trait represents a tuple of [`NativeObject`]s capable of being
/// used in [`HostDefined`].
///
/// This allows accessing multiple types from [`HostDefined`] at once.
pub trait NativeTuple<const SIZE: usize> {
type NativeTupleMutRef<'a>;

fn as_type_ids() -> Vec<TypeId>;

fn mut_ref_from_anys(
anys: [&'_ mut Box<dyn NativeObject>; SIZE],
) -> Option<Self::NativeTupleMutRef<'_>>;
}

macro_rules! impl_native_tuple {
($size:literal $(,$name:ident)* ) => {
impl<$($name: NativeObject,)*> NativeTuple<$size> for ($($name,)*) {
type NativeTupleMutRef<'a> = ($(&'a mut $name,)*);

fn as_type_ids() -> Vec<TypeId> {
vec![$(TypeId::of::<$name>(),)*]
}

fn mut_ref_from_anys(
anys: [&'_ mut Box<dyn NativeObject>; $size],
) -> Option<Self::NativeTupleMutRef<'_>> {
#[allow(unused_variables, unused_mut)]
let mut anys = anys.into_iter();
Some(($(
anys.next().expect("Expect `anys` to be of length `SIZE`").downcast_mut::<$name>()?,
)*))
}
}
}
}

impl_native_tuple!(0);
impl_native_tuple!(1, A);
impl_native_tuple!(2, A, B);
impl_native_tuple!(3, A, B, C);
impl_native_tuple!(4, A, B, C, D);
impl_native_tuple!(5, A, B, C, D, E);
impl_native_tuple!(6, A, B, C, D, E, F);
impl_native_tuple!(7, A, B, C, D, E, F, G);
impl_native_tuple!(8, A, B, C, D, E, F, G, H);
impl_native_tuple!(9, A, B, C, D, E, F, G, H, I);
impl_native_tuple!(10, A, B, C, D, E, F, G, H, I, J);
impl_native_tuple!(11, A, B, C, D, E, F, G, H, I, J, K);
impl_native_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L);
31 changes: 25 additions & 6 deletions boa_engine/src/realm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
object::shape::RootShape,
HostDefined, JsObject, JsString,
};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_gc::{Finalize, Gc, GcRef, GcRefCell, GcRefMut, Trace};
use boa_profiler::Profiler;

/// Representation of a Realm.
Expand Down Expand Up @@ -61,7 +61,7 @@ struct Inner {
loaded_modules: GcRefCell<FxHashMap<JsString, Module>>,
host_classes: GcRefCell<FxHashMap<TypeId, StandardConstructor>>,

host_defined: HostDefined,
host_defined: GcRefCell<HostDefined>,
}

impl Realm {
Expand All @@ -86,7 +86,7 @@ impl Realm {
template_map: GcRefCell::default(),
loaded_modules: GcRefCell::default(),
host_classes: GcRefCell::default(),
host_defined: HostDefined::default(),
host_defined: GcRefCell::default(),
}),
};

Expand All @@ -102,13 +102,32 @@ impl Realm {
&self.inner.intrinsics
}

/// Returns the [`ECMAScript specification`][spec] defined [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
/// Returns an immutable reference to the [`ECMAScript specification`][spec] defined
/// [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
///
/// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
///
/// # Panics
///
/// Panics if [`HostDefined`] field is mutably borrowed.
#[inline]
#[must_use]
pub fn host_defined(&self) -> GcRef<'_, HostDefined> {
self.inner.host_defined.borrow()
}

/// Returns a mutable reference to [`ECMAScript specification`][spec] defined
/// [`\[\[\HostDefined]\]`][`HostDefined`] field of the [`Realm`].
///
/// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
#[inline]
#[must_use]
pub fn host_defined(&self) -> &HostDefined {
&self.inner.host_defined
pub fn host_defined_mut(&self) -> GcRefMut<'_, HostDefined> {
self.inner.host_defined.borrow_mut()
}

/// Checks if this `Realm` has the class `C` registered into its class map.
Expand Down
Loading
Loading