Browse Source

Move `PromiseCapability` to stack (#3528)

* Move `PromiseCapability` to stack

* Apply review

* Rename locals to register for consistency
pull/3535/head
Haled Odat 11 months ago committed by GitHub
parent
commit
0b37ad6ea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      core/engine/src/builtins/generator/mod.rs
  2. 4
      core/engine/src/builtins/promise/mod.rs
  3. 20
      core/engine/src/bytecompiler/mod.rs
  4. 14
      core/engine/src/module/source.rs
  5. 106
      core/engine/src/vm/call_frame/mod.rs
  6. 4
      core/engine/src/vm/code_block.rs
  7. 8
      core/engine/src/vm/mod.rs
  8. 37
      core/engine/src/vm/opcode/await/mod.rs
  9. 7
      core/engine/src/vm/opcode/generator/mod.rs

4
core/engine/src/builtins/generator/mod.rs

@ -75,8 +75,8 @@ impl GeneratorContext {
frame.rp = CallFrame::FUNCTION_PROLOGUE + frame.argument_count; frame.rp = CallFrame::FUNCTION_PROLOGUE + frame.argument_count;
// NOTE: Since we get a pre-built call frame with stack, and we reuse them. // NOTE: Since we get a pre-built call frame with stack, and we reuse them.
// So we don't need to push the locals in subsequent calls. // So we don't need to push the registers in subsequent calls.
frame.flags |= CallFrameFlags::LOCALS_ALREADY_PUSHED; frame.flags |= CallFrameFlags::REGISTERS_ALREADY_PUSHED;
Self { Self {
call_frame: Some(frame), call_frame: Some(frame),

4
core/engine/src/builtins/promise/mod.rs

@ -168,10 +168,10 @@ pub(crate) use if_abrupt_reject_promise;
#[derive(Debug, Clone, Finalize)] #[derive(Debug, Clone, Finalize)]
pub(crate) struct PromiseCapability { pub(crate) struct PromiseCapability {
/// The `[[Promise]]` field. /// The `[[Promise]]` field.
promise: JsObject, pub(crate) promise: JsObject,
/// The resolving functions, /// The resolving functions,
functions: ResolvingFunctions, pub(crate) functions: ResolvingFunctions,
} }
// SAFETY: manually implementing `Trace` to allow destructuring. // SAFETY: manually implementing `Trace` to allow destructuring.

20
core/engine/src/bytecompiler/mod.rs

@ -255,7 +255,7 @@ pub struct ByteCompiler<'ctx> {
/// The number of arguments expected. /// The number of arguments expected.
pub(crate) length: u32, pub(crate) length: u32,
pub(crate) locals_count: u32, pub(crate) register_count: u32,
/// \[\[ThisMode\]\] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
@ -329,7 +329,7 @@ impl<'ctx> ByteCompiler<'ctx> {
params: FormalParameterList::default(), params: FormalParameterList::default(),
current_open_environments_count: 0, current_open_environments_count: 0,
locals_count: 0, register_count: 0,
current_stack_value_count: 0, current_stack_value_count: 0,
code_block_flags, code_block_flags,
handlers: ThinVec::default(), handlers: ThinVec::default(),
@ -1523,17 +1523,25 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
self.r#return(false); self.r#return(false);
if self.is_async_generator() { if self.is_async() {
self.locals_count += 1; // NOTE: +3 for the promise capability
self.register_count += 3;
if self.is_generator() {
// NOTE: +1 for the async generator function
self.register_count += 1;
} }
}
// NOTE: Offset the handlers stack count so we don't pop the registers
// when a exception is thrown.
for handler in &mut self.handlers { for handler in &mut self.handlers {
handler.stack_count += self.locals_count; handler.stack_count += self.register_count;
} }
CodeBlock { CodeBlock {
name: self.function_name, name: self.function_name,
length: self.length, length: self.length,
locals_count: self.locals_count, register_count: self.register_count,
this_mode: self.this_mode, this_mode: self.this_mode,
params: self.params, params: self.params,
bytecode: self.bytecode.into_boxed_slice(), bytecode: self.bytecode.into_boxed_slice(),

14
core/engine/src/module/source.rs

@ -1327,7 +1327,7 @@ impl SourceTextModule {
// 9. Perform ! module.ExecuteModule(capability). // 9. Perform ! module.ExecuteModule(capability).
// 10. Return unused. // 10. Return unused.
self.execute(Some(capability), context) self.execute(Some(&capability), context)
.expect("async modules cannot directly throw"); .expect("async modules cannot directly throw");
} }
@ -1741,7 +1741,7 @@ impl SourceTextModule {
/// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-execute-module /// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-execute-module
fn execute( fn execute(
&self, &self,
capability: Option<PromiseCapability>, capability: Option<&PromiseCapability>,
context: &mut Context, context: &mut Context,
) -> JsResult<()> { ) -> JsResult<()> {
// 1. Let moduleContext be a new ECMAScript code execution context. // 1. Let moduleContext be a new ECMAScript code execution context.
@ -1763,7 +1763,7 @@ impl SourceTextModule {
// 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
let env_fp = environments.len() as u32; let env_fp = environments.len() as u32;
let mut callframe = CallFrame::new( let callframe = CallFrame::new(
codeblock, codeblock,
Some(ActiveRunnable::Module(self.parent())), Some(ActiveRunnable::Module(self.parent())),
environments, environments,
@ -1771,13 +1771,19 @@ impl SourceTextModule {
) )
.with_env_fp(env_fp) .with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY); .with_flags(CallFrameFlags::EXIT_EARLY);
callframe.promise_capability = capability;
// 8. Suspend the running execution context. // 8. Suspend the running execution context.
context context
.vm .vm
.push_frame_with_stack(callframe, JsValue::undefined(), JsValue::null()); .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 // 9. If module.[[HasTLA]] is false, then
// a. Assert: capability is not present. // a. Assert: capability is not present.
// b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.

106
core/engine/src/vm/call_frame/mod.rs

@ -3,9 +3,12 @@
//! This module will provides everything needed to implement the `CallFrame` //! This module will provides everything needed to implement the `CallFrame`
use crate::{ use crate::{
builtins::{iterable::IteratorRecord, promise::PromiseCapability}, builtins::{
iterable::IteratorRecord,
promise::{PromiseCapability, ResolvingFunctions},
},
environments::{BindingLocator, EnvironmentStack}, environments::{BindingLocator, EnvironmentStack},
object::JsObject, object::{JsFunction, JsObject},
realm::Realm, realm::Realm,
vm::CodeBlock, vm::CodeBlock,
JsValue, JsValue,
@ -26,8 +29,8 @@ bitflags::bitflags! {
/// Was this [`CallFrame`] created from the `__construct__()` internal object method? /// Was this [`CallFrame`] created from the `__construct__()` internal object method?
const CONSTRUCT = 0b0000_0010; const CONSTRUCT = 0b0000_0010;
/// Does this [`CallFrame`] need to push local variables on [`Vm::push_frame()`]. /// Does this [`CallFrame`] need to push registers on [`Vm::push_frame()`].
const LOCALS_ALREADY_PUSHED = 0b0000_0100; const REGISTERS_ALREADY_PUSHED = 0b0000_0100;
} }
} }
@ -44,7 +47,6 @@ pub struct CallFrame {
pub(crate) rp: u32, pub(crate) rp: u32,
pub(crate) argument_count: u32, pub(crate) argument_count: u32,
pub(crate) env_fp: 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. // Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
pub(crate) iterators: ThinVec<IteratorRecord>, pub(crate) iterators: ThinVec<IteratorRecord>,
@ -132,7 +134,10 @@ impl CallFrame {
pub(crate) const FUNCTION_PROLOGUE: u32 = 2; pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
pub(crate) const THIS_POSITION: u32 = 2; pub(crate) const THIS_POSITION: u32 = 2;
pub(crate) const FUNCTION_POSITION: u32 = 1; 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`. /// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new( pub(crate) fn new(
@ -147,7 +152,6 @@ impl CallFrame {
rp: 0, rp: 0,
env_fp: 0, env_fp: 0,
argument_count: 0, argument_count: 0,
promise_capability: None,
iterators: ThinVec::new(), iterators: ThinVec::new(),
binding_stack: Vec::new(), binding_stack: Vec::new(),
loop_iteration_count: 0, loop_iteration_count: 0,
@ -216,22 +220,95 @@ impl CallFrame {
return None; return None;
} }
self.local(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, stack) self.register(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, stack)
.as_object() .as_object()
.cloned() .cloned()
} }
/// Returns the local at the given index. pub(crate) fn promise_capability(&self, stack: &[JsValue]) -> Option<PromiseCapability> {
if !self.code_block().is_async() {
return None;
}
let promise = self
.register(Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX, stack)
.as_object()
.cloned()?;
let resolve = self
.register(Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX, stack)
.as_object()
.cloned()
.and_then(JsFunction::from_object)?;
let reject = self
.register(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_register(
Self::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::promise)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_register(
Self::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::resolve)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
self.set_register(
Self::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX,
promise_capability
.map(PromiseCapability::reject)
.cloned()
.map_or_else(JsValue::undefined, Into::into),
stack,
);
}
/// Returns the register at the given index.
/// ///
/// # Panics /// # Panics
/// ///
/// If the index is out of bounds. /// If the index is out of bounds.
pub(crate) fn local<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue { pub(crate) fn register<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue {
debug_assert!(index < self.code_block().locals_count); debug_assert!(index < self.code_block().register_count);
let at = self.rp + index; let at = self.rp + index;
&stack[at as usize] &stack[at as usize]
} }
/// Sets the register at the given index.
///
/// # Panics
///
/// If the index is out of bounds.
pub(crate) fn set_register(&self, index: u32, value: JsValue, stack: &mut [JsValue]) {
debug_assert!(index < self.code_block().register_count);
let at = self.rp + index;
stack[at as usize] = value;
}
/// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag. /// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
pub(crate) fn exit_early(&self) -> bool { pub(crate) fn exit_early(&self) -> bool {
self.flags.contains(CallFrameFlags::EXIT_EARLY) self.flags.contains(CallFrameFlags::EXIT_EARLY)
@ -244,9 +321,10 @@ impl CallFrame {
pub(crate) fn construct(&self) -> bool { pub(crate) fn construct(&self) -> bool {
self.flags.contains(CallFrameFlags::CONSTRUCT) self.flags.contains(CallFrameFlags::CONSTRUCT)
} }
/// Does this [`CallFrame`] need to push local variables on [`Vm::push_frame()`]. /// Does this [`CallFrame`] need to push registers on [`Vm::push_frame()`].
pub(crate) fn locals_already_pushed(&self) -> bool { pub(crate) fn registers_already_pushed(&self) -> bool {
self.flags.contains(CallFrameFlags::LOCALS_ALREADY_PUSHED) self.flags
.contains(CallFrameFlags::REGISTERS_ALREADY_PUSHED)
} }
} }

4
core/engine/src/vm/code_block.rs

@ -177,7 +177,7 @@ pub struct CodeBlock {
/// The number of arguments expected. /// The number of arguments expected.
pub(crate) length: u32, pub(crate) length: u32,
pub(crate) locals_count: u32, pub(crate) register_count: u32,
/// \[\[ThisMode\]\] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
@ -218,7 +218,7 @@ impl CodeBlock {
name, name,
flags: Cell::new(flags), flags: Cell::new(flags),
length, length,
locals_count: 0, register_count: 0,
this_mode: ThisMode::Global, this_mode: ThisMode::Global,
params: FormalParameterList::default(), params: FormalParameterList::default(),
handlers: ThinVec::default(), handlers: ThinVec::default(),

8
core/engine/src/vm/mod.rs

@ -162,13 +162,13 @@ impl Vm {
std::mem::swap(&mut self.environments, &mut frame.environments); std::mem::swap(&mut self.environments, &mut frame.environments);
std::mem::swap(&mut self.realm, &mut frame.realm); std::mem::swap(&mut self.realm, &mut frame.realm);
// NOTE: We need to check if we already pushed the locals, // NOTE: We need to check if we already pushed the registers,
// since generator-like functions push the same call // since generator-like functions push the same call
// frame with pre-built stack. // frame with pre-built stack.
if !frame.locals_already_pushed() { if !frame.registers_already_pushed() {
let locals_count = frame.code_block().locals_count; let register_count = frame.code_block().register_count;
self.stack.resize_with( self.stack.resize_with(
current_stack_length + locals_count as usize, current_stack_length + register_count as usize,
JsValue::undefined, JsValue::undefined,
); );
} }

37
core/engine/src/vm/opcode/await/mod.rs

@ -33,6 +33,16 @@ impl Operation for Await {
context, 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 gen = GeneratorContext::from_current(context);
let captures = Gc::new(GcRefCell::new(Some(gen))); let captures = Gc::new(GcRefCell::new(Some(gen)));
@ -125,16 +135,6 @@ impl Operation for Await {
context, 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); context.vm.set_return_value(return_value);
Ok(CompletionType::Yield) Ok(CompletionType::Yield)
} }
@ -153,7 +153,12 @@ impl Operation for CreatePromiseCapability {
const COST: u8 = 8; const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> { 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); return Ok(CompletionType::Normal);
} }
@ -163,7 +168,12 @@ impl Operation for CreatePromiseCapability {
) )
.expect("cannot fail per spec"); .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) Ok(CompletionType::Normal)
} }
} }
@ -183,7 +193,8 @@ impl Operation for CompletePromiseCapability {
fn execute(context: &mut Context) -> JsResult<CompletionType> { 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. // 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). // 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() { return if context.vm.pending_exception.is_some() {
Ok(CompletionType::Throw) Ok(CompletionType::Throw)
} else { } else {

7
core/engine/src/vm/opcode/generator/mod.rs

@ -13,7 +13,7 @@ use crate::{
vm::{ vm::{
call_frame::GeneratorResumeKind, call_frame::GeneratorResumeKind,
opcode::{Operation, ReThrow}, opcode::{Operation, ReThrow},
CompletionType, CallFrame, CompletionType,
}, },
Context, JsError, JsObject, JsResult, JsValue, Context, JsError, JsObject, JsResult, JsValue,
}; };
@ -79,11 +79,12 @@ impl Operation for Generator {
}; };
if r#async { if r#async {
let fp = frame let rp = frame
.call_frame .call_frame
.as_ref() .as_ref()
.map_or(0, |frame| frame.rp as usize); .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 let mut gen = generator
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()

Loading…
Cancel
Save