Skip to content

Commit

Permalink
Move PromiseCapability to stack
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Dec 20, 2023
1 parent c2f145c commit 1f4dc1b
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 29 deletions.
4 changes: 2 additions & 2 deletions core/engine/src/builtins/promise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,10 @@ pub(crate) use if_abrupt_reject_promise;
#[derive(Debug, Clone, Finalize)]
pub(crate) struct PromiseCapability {
/// The `[[Promise]]` field.
promise: JsObject,
pub(crate) promise: JsObject,

/// The resolving functions,
functions: ResolvingFunctions,
pub(crate) functions: ResolvingFunctions,
}

// SAFETY: manually implementing `Trace` to allow destructuring.
Expand Down
9 changes: 7 additions & 2 deletions core/engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1523,8 +1523,13 @@ impl<'ctx> ByteCompiler<'ctx> {
}
self.r#return(false);

if self.is_async_generator() {
self.locals_count += 1;
if self.is_async() {
// NOTE: +3 for the promise capability
self.locals_count += 3;
if self.is_generator() {
// NOTE: +1 for the async generator function
self.locals_count += 1;
}
}
for handler in &mut self.handlers {
handler.stack_count += self.locals_count;
Expand Down
14 changes: 10 additions & 4 deletions core/engine/src/module/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,7 +1327,7 @@ impl SourceTextModule {

// 9. Perform ! module.ExecuteModule(capability).
// 10. Return unused.
self.execute(Some(capability), context)
self.execute(Some(&capability), context)
.expect("async modules cannot directly throw");
}

Expand Down Expand Up @@ -1741,7 +1741,7 @@ impl SourceTextModule {
/// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-execute-module
fn execute(
&self,
capability: Option<PromiseCapability>,
capability: Option<&PromiseCapability>,
context: &mut Context,
) -> JsResult<()> {
// 1. Let moduleContext be a new ECMAScript code execution context.
Expand All @@ -1763,21 +1763,27 @@ impl SourceTextModule {
// 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
let env_fp = environments.len() as u32;
let mut callframe = CallFrame::new(
let callframe = CallFrame::new(
codeblock,
Some(ActiveRunnable::Module(self.parent())),
environments,
realm,
)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY);
callframe.promise_capability = capability;

// 8. Suspend the running execution context.
context
.vm
.push_frame_with_stack(callframe, JsValue::undefined(), JsValue::null());

context
.vm
.frames
.last()
.expect("there should be a frame")
.set_promise_capability(&mut context.vm.stack, capability);

// 9. If module.[[HasTLA]] is false, then
// a. Assert: capability is not present.
// b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
Expand Down
87 changes: 82 additions & 5 deletions core/engine/src/vm/call_frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
//! This module will provides everything needed to implement the `CallFrame`

use crate::{
builtins::{iterable::IteratorRecord, promise::PromiseCapability},
builtins::{
iterable::IteratorRecord,
promise::{PromiseCapability, ResolvingFunctions},
},
environments::{BindingLocator, EnvironmentStack},
object::JsObject,
object::{JsFunction, JsObject},
realm::Realm,
vm::CodeBlock,
JsValue,
Expand Down Expand Up @@ -44,7 +47,6 @@ pub struct CallFrame {
pub(crate) rp: u32,
pub(crate) argument_count: u32,
pub(crate) env_fp: u32,
pub(crate) promise_capability: Option<PromiseCapability>,

// Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
pub(crate) iterators: ThinVec<IteratorRecord>,
Expand Down Expand Up @@ -132,7 +134,10 @@ impl CallFrame {
pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
pub(crate) const THIS_POSITION: u32 = 2;
pub(crate) const FUNCTION_POSITION: u32 = 1;
pub(crate) const ASYNC_GENERATOR_OBJECT_REGISTER_INDEX: u32 = 0;
pub(crate) const PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX: u32 = 0;
pub(crate) const PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX: u32 = 1;
pub(crate) const PROMISE_CAPABILITY_REJECT_REGISTER_INDEX: u32 = 2;
pub(crate) const ASYNC_GENERATOR_OBJECT_REGISTER_INDEX: u32 = 3;

/// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new(
Expand All @@ -147,7 +152,6 @@ impl CallFrame {
rp: 0,
env_fp: 0,
argument_count: 0,
promise_capability: None,
iterators: ThinVec::new(),
binding_stack: Vec::new(),
loop_iteration_count: 0,
Expand Down Expand Up @@ -221,6 +225,68 @@ impl CallFrame {
.cloned()
}

pub(crate) fn promise_capability(&self, stack: &[JsValue]) -> Option<PromiseCapability> {
if !self.code_block().is_async() {
return None;
}

let promise = self
.local(Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX, stack)
.as_object()
.cloned()?;
let resolve = self
.local(Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX, stack)
.as_object()
.cloned()
.and_then(JsFunction::from_object)?;
let reject = self
.local(Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX, stack)
.as_object()
.cloned()
.and_then(JsFunction::from_object)?;

Some(PromiseCapability {
promise,
functions: ResolvingFunctions { resolve, reject },
})
}

pub(crate) fn set_promise_capability(
&self,
stack: &mut [JsValue],
promise_capability: Option<&PromiseCapability>,
) {
debug_assert!(
self.code_block().is_async(),
"Only async functions have a promise capability"
);

self.set_local(
Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::promise)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_local(
Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::resolve)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_local(
Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::reject)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
}

/// Returns the local at the given index.
///
/// # Panics
Expand All @@ -232,6 +298,17 @@ impl CallFrame {
&stack[at as usize]
}

/// Returns the local at the given index.
///
/// # Panics
///
/// If the index is out of bounds.
pub(crate) fn set_local(&self, index: u32, value: JsValue, stack: &mut [JsValue]) {
debug_assert!(index < self.code_block().locals_count);
let at = self.rp + index;
stack[at as usize] = value;
}

/// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
pub(crate) fn exit_early(&self) -> bool {
self.flags.contains(CallFrameFlags::EXIT_EARLY)
Expand Down
37 changes: 24 additions & 13 deletions core/engine/src/vm/opcode/await/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ impl Operation for Await {
context,
)?;

let return_value = context
.vm
.frame()
.promise_capability(&context.vm.stack)
.as_ref()
.map(PromiseCapability::promise)
.cloned()
.map(JsValue::from)
.unwrap_or_default();

let gen = GeneratorContext::from_current(context);

let captures = Gc::new(GcRefCell::new(Some(gen)));
Expand Down Expand Up @@ -125,16 +135,6 @@ impl Operation for Await {
context,
);

let return_value = context
.vm
.frame()
.promise_capability
.as_ref()
.map(PromiseCapability::promise)
.cloned()
.map(JsValue::from)
.unwrap_or_default();

context.vm.set_return_value(return_value);
Ok(CompletionType::Yield)
}
Expand All @@ -153,7 +153,12 @@ impl Operation for CreatePromiseCapability {
const COST: u8 = 8;

fn execute(context: &mut Context) -> JsResult<CompletionType> {
if context.vm.frame().promise_capability.is_some() {
if context
.vm
.frame()
.promise_capability(&context.vm.stack)
.is_some()
{
return Ok(CompletionType::Normal);
}

Expand All @@ -163,7 +168,12 @@ impl Operation for CreatePromiseCapability {
)
.expect("cannot fail per spec");

context.vm.frame_mut().promise_capability = Some(promise_capability);
context
.vm
.frames
.last()
.expect("there should be a frame")
.set_promise_capability(&mut context.vm.stack, Some(&promise_capability));
Ok(CompletionType::Normal)
}
}
Expand All @@ -183,7 +193,8 @@ impl Operation for CompletePromiseCapability {
fn execute(context: &mut Context) -> JsResult<CompletionType> {
// If the current executing function is an async function we have to resolve/reject it's promise at the end.
// The relevant spec section is 3. in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
let Some(promise_capability) = context.vm.frame_mut().promise_capability.take() else {
let Some(promise_capability) = context.vm.frame().promise_capability(&context.vm.stack)
else {
return if context.vm.pending_exception.is_some() {
Ok(CompletionType::Throw)
} else {
Expand Down
7 changes: 4 additions & 3 deletions core/engine/src/vm/opcode/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
vm::{
call_frame::GeneratorResumeKind,
opcode::{Operation, ReThrow},
CompletionType,
CallFrame, CompletionType,
},
Context, JsError, JsObject, JsResult, JsValue,
};
Expand Down Expand Up @@ -79,11 +79,12 @@ impl Operation for Generator {
};

if r#async {
let fp = frame
let rp = frame
.call_frame
.as_ref()
.map_or(0, |frame| frame.rp as usize);
frame.stack[fp] = generator.clone().into();
frame.stack[rp + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX as usize] =
generator.clone().into();

let mut gen = generator
.downcast_mut::<AsyncGenerator>()
Expand Down

0 comments on commit 1f4dc1b

Please sign in to comment.