Skip to content
Open
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
2 changes: 1 addition & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
target=$(rustc -vV | awk '/^host/ { print $2 }' | tr '[:lower:]' '[:upper:]' | tr '-' '_')
export CARGO_TARGET_${target}_RUSTFLAGS='-D warnings'

if ! command -v cargo-make >/dev/null 2>&1; then
if ! cargo help make >/dev/null 2>&1; then
echo "cargo-make is not installed. Install it with:"
echo " cargo install cargo-make"
exit 1
Expand Down
31 changes: 28 additions & 3 deletions cli/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ impl Executor {
/// Continually run all pending async jobs.
//
/// This does not need to yield to the async executor after every run because
/// it assumes that every async job will not would never
/// exit.
/// it assumes that every async job will eventually yield to the executor.
async fn run_async_jobs(&self, context: &RefCell<&mut Context>) {
let mut group = FutureGroup::new();
let mut listener = pin!(EventListener::new(&self.wake_event));
Expand Down Expand Up @@ -198,7 +197,7 @@ impl JobExecutor for Executor {
Job::TimeoutJob(job) => {
let event = Rc::new(Event::new());
let listener = EventListenerRc::new(Rc::clone(&event));
job.set_cancellation_callback(move || {
job.cancellation_token().push_callback(move |_| {
event.notify(u8::MAX);
});
self.async_jobs
Expand All @@ -215,6 +214,32 @@ impl JobExecutor for Executor {
timer.or(cancel).await
}));
}
Job::IntervalJob(job) => {
let event = Rc::new(Event::new());
let listener = EventListenerRc::new(Rc::clone(&event));
let printer = self.printer.clone();
job.cancellation_token().push_callback(move |_| {
event.notify(u8::MAX);
});
self.async_jobs
.borrow_mut()
.push_back(NativeAsyncJob::new(async move |context| {
let timer = async {
let mut interval = smol::Timer::interval(job.interval().into());
loop {
interval.next().await;
if let Err(err) = job.call(&mut context.borrow_mut()) {
printer.print(uncaught_job_error(&err));
}
}
};
let cancel = async {
listener.await;
Ok(JsValue::undefined())
};
timer.or(cancel).await
}));
}
Job::GenericJob(job) => self.generic_jobs.borrow_mut().push_back(job),
Job::FinalizationRegistryCleanupJob(job) => {
self.finalization_registry_jobs.borrow_mut().push_back(job);
Expand Down
4 changes: 2 additions & 2 deletions core/engine/src/builtins/atomics/futex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ pub(super) unsafe fn wait_async<E: Element + PartialEq>(
timeout,
);

let tc = job.cancellation_token();
let tc = job.cancellation_token().clone();

// 4. Perform HostEnqueueTimeoutJob(timeoutJob, currentRealm, 𝔽(waiterRecord.[[TimeoutTime]]) - now).
context.enqueue_job(job.into());
Expand Down Expand Up @@ -619,7 +619,7 @@ pub(super) unsafe fn wait_async<E: Element + PartialEq>(
}

if let Some(token) = timeout_cancel {
token.cancel();
token.cancel(&mut context.borrow_mut());
}

Ok(JsValue::undefined())
Expand Down
19 changes: 13 additions & 6 deletions core/engine/src/context/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The ECMAScript context.

use std::any::Any;
use std::{cell::Cell, path::Path, rc::Rc};

use boa_ast::StatementList;
Expand All @@ -19,7 +20,7 @@ use crate::js_error;
use crate::module::DynModuleLoader;
use crate::vm::{CodeBlock, RuntimeLimits, create_function_object_fast};
use crate::{
HostDefined, JsNativeError, JsResult, JsString, JsValue, NativeObject, Source, builtins,
HostDefined, JsNativeError, JsResult, JsString, JsValue, Source, builtins,
class::{Class, ClassBuilder},
job::{JobExecutor, SimpleJobExecutor},
js_string,
Expand Down Expand Up @@ -129,7 +130,7 @@ pub struct Context {
/// Unique identifier for each parser instance used during the context lifetime.
parser_identifier: u32,

data: HostDefined,
data: HostDefined<dyn Any>,
}

impl std::fmt::Debug for Context {
Expand Down Expand Up @@ -601,29 +602,35 @@ impl Context {
self.can_block
}

/// Gets a mutable reference to the inner [`HostDefined`] field.
#[inline]
pub fn host_defined_mut(&mut self) -> &mut HostDefined<dyn Any> {
&mut self.data
}

/// Insert a type into the context-specific [`HostDefined`] field.
#[inline]
pub fn insert_data<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
pub fn insert_data<T: Any>(&mut self, value: T) -> Option<Box<T>> {
self.data.insert(value)
}

/// Check if the context-specific [`HostDefined`] has type T.
#[inline]
#[must_use]
pub fn has_data<T: NativeObject>(&self) -> bool {
pub fn has_data<T: Any>(&self) -> bool {
self.data.has::<T>()
}

/// Remove type T from the context-specific [`HostDefined`], if it exists.
#[inline]
pub fn remove_data<T: NativeObject>(&mut self) -> Option<Box<T>> {
pub fn remove_data<T: Any>(&mut self) -> Option<Box<T>> {
self.data.remove::<T>()
}

/// Get type T from the context-specific [`HostDefined`], if it exists.
#[inline]
#[must_use]
pub fn get_data<T: NativeObject>(&self) -> Option<&T> {
pub fn get_data<T: Any>(&self) -> Option<&T> {
self.data.get::<T>()
}
}
Expand Down
148 changes: 117 additions & 31 deletions core/engine/src/host_defined.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,138 @@
use std::any::TypeId;
use std::any::{Any, TypeId};

use boa_macros::{Finalize, Trace};
use boa_gc::{Finalize, Trace, custom_trace};
use hashbrown::hash_map::HashMap;

use crate::object::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 {
pub struct HostDefined<T: ?Sized = dyn NativeObject> {
// 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>>,
types: HashMap<TypeId, Box<T>>,
}

// 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);
impl<T: ?Sized> Default for HostDefined<T> {
fn default() -> Self {
Self {
types: HashMap::default(),
}
}
}

// 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>()) }
// SAFETY: All traceable values are marked here, making this implementation
// safe.
unsafe impl<T: ?Sized + Trace> Trace for HostDefined<T> {
custom_trace!(this, mark, {
for value in this.types.values() {
mark(value);
}
});
}

impl HostDefined {
impl<T: ?Sized + Finalize> Finalize for HostDefined<T> {}

impl HostDefined<dyn Any> {
/// Insert a type into the [`HostDefined`].
#[track_caller]
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
pub fn insert_default<T: Any + Default>(&mut self) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::<T>::default())
.map(downcast_boxed_native_object_unchecked)
.and_then(|t| t.downcast().ok())
}

/// Insert a type into the [`HostDefined`].
#[track_caller]
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
pub fn insert<T: Any>(&mut self, value: T) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::new(value))
.map(downcast_boxed_native_object_unchecked)
.and_then(|t| t.downcast().ok())
}

/// Check if the [`HostDefined`] has type T.
#[must_use]
/// Remove type T from [`HostDefined`], if it exists.
///
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
#[track_caller]
pub fn has<T: NativeObject>(&self) -> bool {
self.types.contains_key(&TypeId::of::<T>())
pub fn remove<T: Any>(&mut self) -> Option<Box<T>> {
self.types
.remove(&TypeId::of::<T>())
.and_then(|t| t.downcast().ok())
}

/// Get type T from [`HostDefined`], if it exists.
#[track_caller]
pub fn get<T: Any>(&self) -> Option<&T> {
self.types
.get(&TypeId::of::<T>())
.map(Box::as_ref)
.and_then(<dyn Any>::downcast_ref::<T>)
}

/// Get type T from [`HostDefined`], if it exists.
#[track_caller]
pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
self.types
.get_mut(&TypeId::of::<T>())
.map(Box::as_mut)
.and_then(<dyn Any>::downcast_mut::<T>)
}

/// Get a tuple of types from [`HostDefined`], returning `None` for the types that are not on the map.
#[track_caller]
pub fn get_many_mut<T, const SIZE: usize>(&mut self) -> T::NativeTupleMutRef<'_>
where
T: NativeTuple<SIZE>,
{
let ids = T::as_type_ids();
let refs: [&TypeId; SIZE] = std::array::from_fn(|i| &ids[i]);
let anys = self
.types
.get_disjoint_mut(refs)
.map(|o| o.map(|v| &mut **v));

T::mut_ref_from_anys(anys)
}
}

impl HostDefined<dyn NativeObject> {
/// Insert a type into the [`HostDefined`].
#[track_caller]
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::<T>::default())
.and_then(|t| {
// triggers downcast from NativeObject to Any
let t: Box<dyn Any> = t;
t.downcast().ok()
})
}

/// Insert a type into the [`HostDefined`].
#[track_caller]
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
self.types
.insert(TypeId::of::<T>(), Box::new(value))
.and_then(|t| {
// triggers downcast from NativeObject to Any
let t: Box<dyn Any> = t;
t.downcast().ok()
})
}

/// Remove type T from [`HostDefined`], if it exists.
///
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
#[track_caller]
pub fn remove<T: NativeObject>(&mut self) -> Option<Box<T>> {
self.types
.remove(&TypeId::of::<T>())
.map(downcast_boxed_native_object_unchecked)
self.types.remove(&TypeId::of::<T>()).and_then(|t| {
// triggers downcast from NativeObject to Any
let t: Box<dyn Any> = t;
t.downcast().ok()
})
}

/// Get type T from [`HostDefined`], if it exists.
Expand Down Expand Up @@ -88,8 +161,23 @@ impl HostDefined {
{
let ids = T::as_type_ids();
let refs: [&TypeId; SIZE] = std::array::from_fn(|i| &ids[i]);
let anys = self.types.get_disjoint_mut(refs).map(|o| {
o.map(|v| {
let v: &mut dyn Any = &mut **v;
v
})
});

T::mut_ref_from_anys(anys)
}
}

T::mut_ref_from_anys(self.types.get_disjoint_mut(refs))
impl<T: ?Sized> HostDefined<T> {
/// Check if the [`HostDefined`] has type T.
#[must_use]
#[track_caller]
pub fn has<U: 'static>(&self) -> bool {
self.types.contains_key(&TypeId::of::<U>())
}

/// Clears all the objects.
Expand All @@ -108,14 +196,12 @@ pub trait NativeTuple<const SIZE: usize> {

fn as_type_ids() -> [TypeId; SIZE];

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

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

fn as_type_ids() -> [TypeId; $size] {
Expand All @@ -124,7 +210,7 @@ macro_rules! impl_native_tuple {

#[allow(unused_variables, unused_mut, clippy::unused_unit)]
fn mut_ref_from_anys(
anys: [Option<&'_ mut Box<dyn NativeObject>>; $size],
anys: [Option<&'_ mut dyn Any>; $size],
) -> Self::NativeTupleMutRef<'_> {
let mut anys = anys.into_iter();
($(
Expand Down
Loading
Loading