Skip to content

Commit

Permalink
feat(ecmascript): Make Job contain Globals (#540)
Browse files Browse the repository at this point in the history
  • Loading branch information
aapoalas authored Jan 18, 2025
1 parent 815bc72 commit acf37fd
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ impl AwaitReactionIdentifier {
// which resume execution of the function.
let handler = PromiseReactionHandler::Await(self);
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::resolve(agent, awaited_value, gc);
let promise = Promise::resolve(agent, awaited_value, gc.reborrow())
.unbind()
.bind(gc.nogc());
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
inner_promise_then(agent, promise, handler, handler, None);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl PromiseCapability {
}

/// [27.2.1.3.2 Promise Resolve Functions](https://tc39.es/ecma262/#sec-promise-resolve-functions)
pub fn resolve(self, agent: &mut Agent, resolution: Value, gc: GcScope) {
pub fn resolve(self, agent: &mut Agent, resolution: Value, mut gc: GcScope) {
// 1. Let F be the active function object.
// 2. Assert: F has a [[Promise]] internal slot whose value is an Object.
// 3. Let promise be F.[[Promise]].
Expand Down Expand Up @@ -175,7 +175,12 @@ impl PromiseCapability {
};

// 9. Let then be Completion(Get(resolution, "then")).
let then_action = match get(agent, resolution, BUILTIN_STRING_MEMORY.then.into(), gc) {
let then_action = match get(
agent,
resolution,
BUILTIN_STRING_MEMORY.then.into(),
gc.reborrow(),
) {
// 11. Let thenAction be then.[[Value]].
Ok(then_action) => then_action,
// 10. If then is an abrupt completion, then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! ## [27.2.2 Promise Jobs](https://tc39.es/ecma262/#sec-promise-jobs)
use crate::engine::context::GcScope;
use crate::engine::Global;
use crate::{
ecmascript::{
abstract_operations::operations_on_objects::{call_function, get_function_realm},
Expand All @@ -24,17 +25,23 @@ use super::{
promise_resolving_functions::{PromiseResolvingFunctionHeapData, PromiseResolvingFunctionType},
};

#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub(crate) struct PromiseResolveThenableJob {
promise_to_resolve: Promise<'static>,
thenable: Object<'static>,
then: Function<'static>,
promise_to_resolve: Global<Promise<'static>>,
thenable: Global<Object<'static>>,
then: Global<Function<'static>>,
}
impl PromiseResolveThenableJob {
pub(crate) fn run(self, agent: &mut Agent, gc: GcScope) -> JsResult<()> {
pub(crate) fn run(self, agent: &mut Agent, mut gc: GcScope) -> JsResult<()> {
let Self {
promise_to_resolve,
thenable,
then,
} = self;
// The following are substeps of point 1 in NewPromiseResolveThenableJob.
// a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve).
let promise_capability = PromiseCapability::from_promise(self.promise_to_resolve, false);
let promise_capability =
PromiseCapability::from_promise(promise_to_resolve.take(agent), false);
let resolve_function = agent
.heap
.create(PromiseResolvingFunctionHeapData {
Expand All @@ -56,12 +63,14 @@ impl PromiseResolveThenableJob {
// TODO: Add the HostCallJobCallback host hook. For now we're using its default
// implementation, which is calling the thenable, since only browsers should use a different
// implementation.
let then = then.take(agent).bind(gc.nogc());
let thenable = thenable.take(agent).bind(gc.nogc()).into_value();
let then_call_result = call_function(
agent,
self.then,
self.thenable.into_value(),
then.unbind(),
thenable.unbind(),
Some(ArgumentsList(&[resolve_function, reject_function])),
gc,
gc.reborrow(),
);

// c. If thenCallResult is an abrupt completion, then
Expand Down Expand Up @@ -93,30 +102,33 @@ pub(crate) fn new_promise_resolve_thenable_job(
Job {
realm: Some(then_realm),
inner: InnerJob::PromiseResolveThenable(PromiseResolveThenableJob {
promise_to_resolve: promise_to_resolve.unbind(),
thenable: thenable.unbind(),
then: then.unbind(),
promise_to_resolve: Global::new(agent, promise_to_resolve.unbind()),
thenable: Global::new(agent, thenable.unbind()),
then: Global::new(agent, then.unbind()),
}),
}
}

#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub(crate) struct PromiseReactionJob {
reaction: PromiseReaction,
argument: Value,
reaction: Global<PromiseReaction>,
argument: Global<Value>,
}
impl PromiseReactionJob {
pub(crate) fn run(self, agent: &mut Agent, mut gc: GcScope) -> JsResult<()> {
let Self { reaction, argument } = self;
let reaction = reaction.take(agent);
let argument = argument.take(agent).bind(gc.nogc());
// The following are substeps of point 1 in NewPromiseReactionJob.
let handler_result = match agent[self.reaction].handler {
PromiseReactionHandler::Empty => match agent[self.reaction].reaction_type {
let handler_result = match agent[reaction].handler {
PromiseReactionHandler::Empty => match agent[reaction].reaction_type {
PromiseReactionType::Fulfill => {
// d.i.1. Let handlerResult be NormalCompletion(argument).
Ok(self.argument)
Ok(argument)
}
PromiseReactionType::Reject => {
// d.ii.1. Let handlerResult be ThrowCompletion(argument).
Err(JsError::new(self.argument))
Err(JsError::new(argument))
}
},
// e.1. Let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)).
Expand All @@ -127,13 +139,13 @@ impl PromiseReactionJob {
agent,
callback,
Value::Undefined,
Some(ArgumentsList(&[self.argument])),
Some(ArgumentsList(&[argument])),
gc.reborrow(),
),
PromiseReactionHandler::Await(await_reaction) => {
assert!(agent[self.reaction].capability.is_none());
let reaction_type = agent[self.reaction].reaction_type;
await_reaction.resume(agent, reaction_type, self.argument, gc.reborrow());
assert!(agent[reaction].capability.is_none());
let reaction_type = agent[reaction].reaction_type;
await_reaction.resume(agent, reaction_type, argument, gc.reborrow());
// [27.7.5.3 Await ( value )](https://tc39.es/ecma262/#await)
// 3. f. Return undefined.
// 5. f. Return undefined.
Expand All @@ -142,7 +154,7 @@ impl PromiseReactionJob {
};

// f. If promiseCapability is undefined, then
let Some(promise_capability) = agent[self.reaction].capability else {
let Some(promise_capability) = agent[reaction].capability else {
// i. Assert: handlerResult is not an abrupt completion.
handler_result.unwrap();
// ii. Return empty.
Expand Down Expand Up @@ -197,6 +209,8 @@ pub(crate) fn new_promise_reaction_job(
};

// 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }.
let reaction = Global::new(agent, reaction);
let argument = Global::new(agent, argument);
Job {
realm: handler_realm,
inner: InnerJob::PromiseReaction(PromiseReactionJob { reaction, argument }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
builtins::control_abstraction_objects::async_function_objects::await_reaction::AwaitReactionIdentifier,
execution::Agent, types::Function,
},
engine::rootable::{HeapRootData, HeapRootRef, Rootable},
heap::{indexes::BaseIndex, CreateHeapData, Heap, HeapMarkAndSweep},
};

Expand Down Expand Up @@ -119,6 +120,30 @@ impl HeapMarkAndSweep for PromiseReaction {
}
}

impl Rootable for PromiseReaction {
type RootRepr = HeapRootRef;

fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
Err(HeapRootData::PromiseReaction(value))
}

fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
Err(*value)
}

fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
heap_ref
}

fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
if let HeapRootData::PromiseReaction(data) = heap_data {
Some(data)
} else {
None
}
}
}

impl HeapMarkAndSweep for PromiseReactionRecord {
fn mark_values(&self, queues: &mut crate::heap::WorkQueues) {
self.capability.mark_values(queues);
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/execution/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl Job {
self.realm
}

pub fn run(&self, agent: &mut Agent, gc: GcScope) -> JsResult<()> {
pub fn run(self, agent: &mut Agent, gc: GcScope) -> JsResult<()> {
let mut pushed_context = false;
if let Some(realm) = self.realm {
if agent.current_realm_id() != realm {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ pub(crate) fn evaluate_async_function_body<'a>(
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::resolve(agent, awaited_value, gc.reborrow());
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
inner_promise_then(agent, promise, handler, handler, None);
inner_promise_then(agent, promise.unbind(), handler, handler, None);
}
ExecutionResult::Yield { .. } => unreachable!(),
}
Expand Down
6 changes: 4 additions & 2 deletions nova_vm/src/ecmascript/types/language/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3714,8 +3714,10 @@ impl TryFrom<HeapRootData> for Object<'_> {
HeapRootData::Module(module) => Ok(Self::Module(module)),
HeapRootData::EmbedderObject(embedder_object) => {
Ok(Self::EmbedderObject(embedder_object))
} // Note: Do not use _ => Err(()) to make sure any added
// HeapRootData Value variants cause compile errors if not handled.
}
HeapRootData::PromiseReaction(_) => Err(()),
// Note: Do not use _ => Err(()) to make sure any added
// HeapRootData Value variants cause compile errors if not handled.
}
}
}
6 changes: 4 additions & 2 deletions nova_vm/src/ecmascript/types/language/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,8 +1369,10 @@ impl Rootable for Value {
HeapRootData::Module(module) => Some(Self::Module(module)),
HeapRootData::EmbedderObject(embedder_object) => {
Some(Self::EmbedderObject(embedder_object))
} // Note: Do not use _ => Err(()) to make sure any added
// HeapRootData Value variants cause compile errors if not handled.
}
HeapRootData::PromiseReaction(_) => None,
// Note: Do not use _ => Err(()) to make sure any added
// HeapRootData Value variants cause compile errors if not handled.
}
}
}
Expand Down
16 changes: 14 additions & 2 deletions nova_vm/src/engine/rootable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ use crate::{
module::Module,
primitive_objects::PrimitiveObject,
promise::Promise,
promise_objects::promise_abstract_operations::promise_resolving_functions::BuiltinPromiseResolvingFunction,
promise_objects::promise_abstract_operations::{
promise_reaction_records::PromiseReaction,
promise_resolving_functions::BuiltinPromiseResolvingFunction,
},
proxy::Proxy,
set::Set,
Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction,
Expand Down Expand Up @@ -105,7 +108,10 @@ mod private {
module::Module,
primitive_objects::PrimitiveObject,
promise::Promise,
promise_objects::promise_abstract_operations::promise_resolving_functions::BuiltinPromiseResolvingFunction,
promise_objects::promise_abstract_operations::{
promise_reaction_records::PromiseReaction,
promise_resolving_functions::BuiltinPromiseResolvingFunction,
},
proxy::Proxy,
set::Set,
Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction,
Expand Down Expand Up @@ -147,6 +153,7 @@ mod private {
impl RootableSealed for Primitive<'_> {}
impl RootableSealed for PrimitiveObject<'_> {}
impl RootableSealed for Promise<'_> {}
impl RootableSealed for PromiseReaction {}
impl RootableSealed for PropertyKey<'_> {}
impl RootableSealed for Proxy<'_> {}
#[cfg(feature = "regexp")]
Expand Down Expand Up @@ -315,6 +322,7 @@ pub enum HeapRootData {
//
// The order here shouldn't be important at all, feel free to eg. keep
// these in alphabetical order.
PromiseReaction(PromiseReaction),
}

impl From<Object<'static>> for HeapRootData {
Expand Down Expand Up @@ -500,6 +508,7 @@ impl HeapMarkAndSweep for HeapRootData {
HeapRootData::Generator(generator) => generator.mark_values(queues),
HeapRootData::Module(module) => module.mark_values(queues),
HeapRootData::EmbedderObject(embedder_object) => embedder_object.mark_values(queues),
HeapRootData::PromiseReaction(promise_reaction) => promise_reaction.mark_values(queues),
}
}

Expand Down Expand Up @@ -590,6 +599,9 @@ impl HeapMarkAndSweep for HeapRootData {
HeapRootData::EmbedderObject(embedder_object) => {
embedder_object.sweep_values(compactions)
}
HeapRootData::PromiseReaction(promise_reaction) => {
promise_reaction.sweep_values(compactions)
}
}
}
}
6 changes: 3 additions & 3 deletions nova_vm/src/engine/rootable/global.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::marker::PhantomData;

use crate::engine::context::GcScope;
use crate::engine::context::NoGcScope;
use crate::{
ecmascript::execution::Agent,
engine::rootable::{HeapRootRef, Rootable},
Expand Down Expand Up @@ -74,7 +74,7 @@ impl<T: Rootable> Global<T> {

/// Access the rooted value from inside this Global without releasing the
/// Global.
pub fn get(&self, agent: &mut Agent, _: GcScope) -> T {
pub fn get(&self, agent: &mut Agent, _: NoGcScope) -> T {
let heap_ref = match T::from_root_repr(&self.0) {
Ok(value) => {
// The value didn't need rooting
Expand All @@ -100,7 +100,7 @@ impl<T: Rootable> Global<T> {
/// original Global and the cloned one must be explicitly released before
/// the rooted value can be garbage collected.
#[must_use]
pub fn clone(&self, agent: &mut Agent, gc: GcScope) -> Self {
pub fn clone(&self, agent: &mut Agent, gc: NoGcScope) -> Self {
let value = self.get(agent, gc);
Self::new(agent, value)
}
Expand Down

0 comments on commit acf37fd

Please sign in to comment.