Skip to content

Commit

Permalink
Refactor the [[HostDefined]] implementation.
Browse files Browse the repository at this point in the history
Currently `[[HostDefined]]` doesn't permit you to mutably borrow two objects from the `[[HostDefined]]` field since the `FxHashMap` is wrapped under a `GcRefCell`.

This commit instead wraps all `Box<dyn NativeObject>` entries with a `GcRefCell`, permitting a more granular borrowing scheme.

Additionally, this commit takes the opportunity to provide automatic downcasting on the `insert` and `remove` methods for `[[HostDefined]]`.
  • Loading branch information
johnyob committed Oct 9, 2023
1 parent 348c757 commit 127a7f5
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 56 deletions.
91 changes: 38 additions & 53 deletions boa_engine/src/host_defined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,97 +6,82 @@ use rustc_hash::FxHashMap;

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>,
env: FxHashMap<TypeId, GcRefCell<Box<dyn NativeObject>>>,
}

#[allow(unsafe_op_in_unsafe_fn)]
unsafe fn downcast_boxed_native_object_unchecked<T: NativeObject>(
obj: Box<dyn NativeObject>,
) -> Box<T> {
let raw: *mut dyn NativeObject = Box::into_raw(obj);
Box::from_raw(raw as *mut 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()
.insert(TypeId::of::<T>(), Box::<T>::default())
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
self.env
.insert(TypeId::of::<T>(), GcRefCell::new(Box::new(T::default())))
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// 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()
.insert(TypeId::of::<T>(), Box::new(value))
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
self.env
.insert(TypeId::of::<T>(), GcRefCell::new(Box::new(value)))
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// Check if the [`HostDefined`] has type T.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed mutably.
#[track_caller]
pub fn has<T: NativeObject>(&self) -> bool {
self.state.borrow().contains_key(&TypeId::of::<T>())
self.env.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.env
.remove(&TypeId::of::<T>())
.map(|obj| unsafe { downcast_boxed_native_object_unchecked(obj.into_inner()) })
}

/// Get type T from [`HostDefined`], if it exits.
/// Get type T from [`HostDefined`], if it exists.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Panics if T's entry in [`HostDefined`] is mutably borrowed.
#[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>)
let entry = self.env.get(&TypeId::of::<T>())?;

GcRef::try_map(entry.borrow(), |obj| {
obj.as_ref().downcast_ref::<T>()
})
}

/// Get type T from [`HostDefined`], if it exits.
/// Get type T from [`HostDefined`], if it exists.
///
/// # Panics
///
/// Panics if [`HostDefined`] field is borrowed.
/// Panics if T's entry in [`HostDefined`] is borrowed.
#[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>(&self) -> Option<GcRefMut<'_, Box<dyn NativeObject>, T>> {
let entry = self.env.get(&TypeId::of::<T>())?;

GcRefMut::try_map(entry.borrow_mut(), |obj| {
obj.as_mut().downcast_mut::<T>()
})
}

/// Clears all the objects.
Expand All @@ -105,7 +90,7 @@ impl HostDefined {
///
/// Panics if [`HostDefined`] field is borrowed.
#[track_caller]
pub fn clear(&self) {
self.state.borrow_mut().clear();
pub fn clear(&mut self) {
self.env.clear();
}
}
6 changes: 3 additions & 3 deletions boa_engine/src/realm.rs
Original file line number Diff line number Diff line change
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 @@ -107,7 +107,7 @@ impl Realm {
/// [spec]: https://tc39.es/ecma262/#table-realm-record-fields
#[inline]
#[must_use]
pub fn host_defined(&self) -> &HostDefined {
pub fn host_defined(&self) -> &GcRefCell<HostDefined> {
&self.inner.host_defined
}

Expand Down

0 comments on commit 127a7f5

Please sign in to comment.