Browse Source

Refactor vm calling convention to allow locals (#3496)

* Refactor vm calling convention to allow locals

* Move async generator object to the stack

* Apply review and some clean-up

* Add todo for storing fp in CallFrame

* Rename ASYNC_GENERATOR_OBJECT_LOCAL_INDEX to ASYNC_GENERATOR_OBJECT_REGISTER_INDEX
pull/3529/head
Haled Odat 11 months ago committed by GitHub
parent
commit
af9d3953cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      core/engine/src/builtins/function/mod.rs
  2. 43
      core/engine/src/builtins/generator/mod.rs
  3. 14
      core/engine/src/bytecompiler/mod.rs
  4. 2
      core/engine/src/module/synthetic.rs
  5. 139
      core/engine/src/vm/call_frame/mod.rs
  6. 10
      core/engine/src/vm/code_block.rs
  7. 43
      core/engine/src/vm/mod.rs
  8. 8
      core/engine/src/vm/opcode/arguments.rs
  9. 18
      core/engine/src/vm/opcode/await/mod.rs
  10. 69
      core/engine/src/vm/opcode/generator/mod.rs
  11. 9
      core/engine/src/vm/opcode/generator/yield_stm.rs
  12. 10
      core/engine/src/vm/opcode/get/argument.rs
  13. 6
      core/engine/src/vm/opcode/rest_parameter/mod.rs

20
core/engine/src/builtins/function/mod.rs

@ -938,10 +938,7 @@ pub(crate) fn function_call(
context.vm.push_frame(frame); context.vm.push_frame(frame);
let fp = context.vm.stack.len() - argument_count - CallFrame::FUNCTION_PROLOGUE; let this = context.vm.frame().this(&context.vm);
context.vm.frame_mut().fp = fp as u32;
let this = context.vm.stack[fp + CallFrame::THIS_POSITION].clone();
let lexical_this_mode = code.this_mode == ThisMode::Lexical; let lexical_this_mode = code.this_mode == ThisMode::Lexical;
@ -1013,8 +1010,6 @@ fn function_construct(
let new_target = context.vm.pop(); let new_target = context.vm.pop();
let at = context.vm.stack.len() - argument_count;
let this = if code.is_derived_constructor() { let this = if code.is_derived_constructor() {
None None
} else { } else {
@ -1040,9 +1035,12 @@ fn function_construct(
.with_env_fp(env_fp) .with_env_fp(env_fp)
.with_flags(CallFrameFlags::CONSTRUCT); .with_flags(CallFrameFlags::CONSTRUCT);
let len = context.vm.stack.len();
context.vm.push_frame(frame); context.vm.push_frame(frame);
context.vm.frame_mut().fp = at as u32 - 1; // NOTE(HalidOdat): +1 because we insert `this` value below.
context.vm.frame_mut().rp = len as u32 + 1;
let mut last_env = 0; let mut last_env = 0;
@ -1075,10 +1073,10 @@ fn function_construct(
); );
// Insert `this` value // Insert `this` value
context context.vm.stack.insert(
.vm len - argument_count - 1,
.stack this.map(JsValue::new).unwrap_or_default(),
.insert(at - 1, this.map(JsValue::new).unwrap_or_default()); );
Ok(CallValue::Ready) Ok(CallValue::Ready)
} }

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

@ -20,7 +20,7 @@ use crate::{
string::common::StaticJsStrings, string::common::StaticJsStrings,
symbol::JsSymbol, symbol::JsSymbol,
value::JsValue, value::JsValue,
vm::{CallFrame, CompletionRecord, GeneratorResumeKind}, vm::{CallFrame, CallFrameFlags, CompletionRecord, GeneratorResumeKind},
Context, JsArgs, JsData, JsError, JsResult, JsString, Context, JsArgs, JsData, JsError, JsResult, JsString,
}; };
use boa_gc::{custom_trace, Finalize, Trace}; use boa_gc::{custom_trace, Finalize, Trace};
@ -64,28 +64,24 @@ pub(crate) struct GeneratorContext {
} }
impl GeneratorContext { impl GeneratorContext {
/// Creates a new `GeneratorContext` from the raw `Context` state components.
pub(crate) fn new(stack: Vec<JsValue>, call_frame: CallFrame) -> Self {
Self {
stack,
call_frame: Some(call_frame),
}
}
/// Creates a new `GeneratorContext` from the current `Context` state. /// Creates a new `GeneratorContext` from the current `Context` state.
pub(crate) fn from_current(context: &mut Context) -> Self { pub(crate) fn from_current(context: &mut Context) -> Self {
let mut frame = context.vm.frame().clone(); let mut frame = context.vm.frame().clone();
frame.environments = context.vm.environments.clone(); frame.environments = context.vm.environments.clone();
frame.realm = context.realm().clone(); frame.realm = context.realm().clone();
let fp = context.vm.frame().fp as usize; let fp = frame.fp() as usize;
let this = Self { let stack = context.vm.stack.split_off(fp);
call_frame: Some(frame),
stack: context.vm.stack[fp..].to_vec(),
};
context.vm.stack.truncate(fp); frame.rp = CallFrame::FUNCTION_PROLOGUE + frame.argument_count;
this // 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.
frame.flags |= CallFrameFlags::LOCALS_ALREADY_PUSHED;
Self {
call_frame: Some(frame),
stack,
}
} }
/// Resumes execution with `GeneratorContext` as the current execution context. /// Resumes execution with `GeneratorContext` as the current execution context.
@ -96,11 +92,11 @@ impl GeneratorContext {
context: &mut Context, context: &mut Context,
) -> CompletionRecord { ) -> CompletionRecord {
std::mem::swap(&mut context.vm.stack, &mut self.stack); std::mem::swap(&mut context.vm.stack, &mut self.stack);
context let frame = self.call_frame.take().expect("should have a call frame");
.vm let rp = frame.rp;
.push_frame(self.call_frame.take().expect("should have a call frame")); context.vm.push_frame(frame);
context.vm.frame_mut().fp = 0; context.vm.frame_mut().rp = rp;
context.vm.frame_mut().set_exit_early(true); context.vm.frame_mut().set_exit_early(true);
if let Some(value) = value { if let Some(value) = value {
@ -115,6 +111,13 @@ impl GeneratorContext {
assert!(self.call_frame.is_some()); assert!(self.call_frame.is_some());
result result
} }
/// Returns the async generator object, if the function that this [`GeneratorContext`] is from an async generator, [`None`] otherwise.
pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
self.call_frame
.as_ref()
.and_then(|frame| frame.async_generator_object(&self.stack))
}
} }
/// The internal representation of a `Generator` object. /// The internal representation of a `Generator` object.

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

@ -255,6 +255,8 @@ 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,
/// \[\[ThisMode\]\] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
@ -327,8 +329,8 @@ impl<'ctx> ByteCompiler<'ctx> {
params: FormalParameterList::default(), params: FormalParameterList::default(),
current_open_environments_count: 0, current_open_environments_count: 0,
// This starts at two because the first value is the `this` value, then function object. locals_count: 0,
current_stack_value_count: 2, current_stack_value_count: 0,
code_block_flags, code_block_flags,
handlers: ThinVec::default(), handlers: ThinVec::default(),
ic: Vec::default(), ic: Vec::default(),
@ -1521,9 +1523,17 @@ impl<'ctx> ByteCompiler<'ctx> {
} }
self.r#return(false); self.r#return(false);
if self.is_async_generator() {
self.locals_count += 1;
}
for handler in &mut self.handlers {
handler.stack_count += self.locals_count;
}
CodeBlock { CodeBlock {
name: self.function_name, name: self.function_name,
length: self.length, length: self.length,
locals_count: self.locals_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(),

2
core/engine/src/module/synthetic.rs

@ -324,7 +324,7 @@ impl SyntheticModule {
// 11. Suspend moduleContext and remove it from the execution context stack. // 11. Suspend moduleContext and remove it from the execution context stack.
// 12. Resume the context that is now on the top of the execution context stack as the running execution context. // 12. Resume the context that is now on the top of the execution context stack as the running execution context.
let frame = context.vm.pop_frame().expect("there should be a frame"); let frame = context.vm.pop_frame().expect("there should be a frame");
context.vm.stack.truncate(frame.fp as usize); frame.restore_stack(&mut context.vm);
// 13. Let pc be ! NewPromiseCapability(%Promise%). // 13. Let pc be ! NewPromiseCapability(%Promise%).
let (promise, ResolvingFunctions { resolve, reject }) = JsPromise::new_pending(context); let (promise, ResolvingFunctions { resolve, reject }) = JsPromise::new_pending(context);

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

@ -25,6 +25,9 @@ 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()`].
const LOCALS_ALREADY_PUSHED = 0b0000_0100;
} }
} }
@ -33,17 +36,16 @@ bitflags::bitflags! {
pub struct CallFrame { pub struct CallFrame {
pub(crate) code_block: Gc<CodeBlock>, pub(crate) code_block: Gc<CodeBlock>,
pub(crate) pc: u32, pub(crate) pc: u32,
pub(crate) fp: u32, /// The register pointer, points to the first register in the stack.
pub(crate) env_fp: u32, ///
// Tracks the number of environments in environment entry. // TODO: Check if storing the frame pointer instead of argument count and computing the
// On abrupt returns this is used to decide how many environments need to be pop'ed. // argument count based on the pointers would be better for accessing the arguments
// and the elements before the register pointer.
pub(crate) rp: u32,
pub(crate) argument_count: u32, pub(crate) argument_count: u32,
pub(crate) env_fp: u32,
pub(crate) promise_capability: Option<PromiseCapability>, pub(crate) promise_capability: Option<PromiseCapability>,
// When an async generator is resumed, the generator object is needed
// to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart).
pub(crate) async_generator: Option<JsObject>,
// 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>,
@ -81,22 +83,56 @@ impl CallFrame {
impl CallFrame { impl CallFrame {
/// This is the size of the function prologue. /// This is the size of the function prologue.
/// ///
/// The position of the elements are relative to the [`CallFrame::fp`]. /// The position of the elements are relative to the [`CallFrame::fp`] (register pointer).
///
/// ```text
/// Setup by the caller
/// ┌─────────────────────────────────────────────────────────┐ ┌───── register pointer
/// ▼ ▼ ▼
/// | -(2 + N): this | -(1 + N): func | -N: arg1 | ... | -1: argN | 0: local1 | ... | K: localK |
/// ▲ ▲ ▲ ▲ ▲ ▲
/// └──────────────────────────────┘ └──────────────────────┘ └─────────────────────────┘
/// function prologue arguments Setup by the callee
/// ▲
/// └─ Frame pointer
/// ```
///
/// ### Example
///
/// The following function calls, generate the following stack:
///
/// ```JavaScript
/// function x(a) {
/// }
/// function y(b, c) {
/// return x(b + c)
/// }
///
/// y(1, 2)
/// ```
/// ///
/// ```text /// ```text
/// --- frame pointer arguments /// caller prologue caller arguments callee prologue callee arguments
/// / __________________________/ /// ┌─────────────────┐ ┌─────────┐ ┌─────────────────┐ ┌──────┐
/// / / \ /// ▼ ▼ ▼ ▼ │ ▼ ▼ ▼
/// | 0: this | 1: func | 2: arg1 | ... | (2 + N): argN | /// | 0: undefined | 1: y | 2: 1 | 3: 2 | 4: undefined | 5: x | 6: 3 |
/// \ / /// ▲ ▲ ▲
/// ------------ /// │ caller register pointer ────┤ │
/// | /// │ │ callee register pointer
/// function prolugue /// │ callee frame pointer
/// │
/// └───── caller frame pointer
///
/// ``` /// ```
pub(crate) const FUNCTION_PROLOGUE: usize = 2; ///
pub(crate) const THIS_POSITION: usize = 0; /// Some questions:
pub(crate) const FUNCTION_POSITION: usize = 1; ///
pub(crate) const FIRST_ARGUMENT_POSITION: usize = 2; /// - Who is responsible for cleaning up the stack after a call? The rust caller.
///
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;
/// Creates a new `CallFrame` with the provided `CodeBlock`. /// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new( pub(crate) fn new(
@ -108,11 +144,10 @@ impl CallFrame {
Self { Self {
code_block, code_block,
pc: 0, pc: 0,
fp: 0, rp: 0,
env_fp: 0, env_fp: 0,
argument_count: 0, argument_count: 0,
promise_capability: None, promise_capability: None,
async_generator: None,
iterators: ThinVec::new(), iterators: ThinVec::new(),
binding_stack: Vec::new(), binding_stack: Vec::new(),
loop_iteration_count: 0, loop_iteration_count: 0,
@ -142,19 +177,61 @@ impl CallFrame {
} }
pub(crate) fn this(&self, vm: &Vm) -> JsValue { pub(crate) fn this(&self, vm: &Vm) -> JsValue {
let this_index = self.fp as usize + Self::THIS_POSITION; let this_index = self.rp - self.argument_count - Self::THIS_POSITION;
vm.stack[this_index].clone() vm.stack[this_index as usize].clone()
} }
pub(crate) fn function(&self, vm: &Vm) -> Option<JsObject> { pub(crate) fn function(&self, vm: &Vm) -> Option<JsObject> {
let function_index = self.fp as usize + Self::FUNCTION_POSITION; let function_index = self.rp - self.argument_count - Self::FUNCTION_POSITION;
if let Some(object) = vm.stack[function_index].as_object() { if let Some(object) = vm.stack[function_index as usize].as_object() {
return Some(object.clone()); return Some(object.clone());
} }
None None
} }
pub(crate) fn arguments<'stack>(&self, vm: &'stack Vm) -> &'stack [JsValue] {
let rp = self.rp as usize;
let argument_count = self.argument_count as usize;
let arguments_start = rp - argument_count;
&vm.stack[arguments_start..rp]
}
pub(crate) fn argument<'stack>(&self, index: usize, vm: &'stack Vm) -> Option<&'stack JsValue> {
self.arguments(vm).get(index)
}
pub(crate) fn fp(&self) -> u32 {
self.rp - self.argument_count - Self::FUNCTION_PROLOGUE
}
pub(crate) fn restore_stack(&self, vm: &mut Vm) {
let fp = self.fp();
vm.stack.truncate(fp as usize);
}
/// Returns the async generator object, if the function that this [`CallFrame`] is from an async generator, [`None`] otherwise.
pub(crate) fn async_generator_object(&self, stack: &[JsValue]) -> Option<JsObject> {
if !self.code_block().is_async_generator() {
return None;
}
self.local(Self::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX, stack)
.as_object()
.cloned()
}
/// Returns the local at the given index.
///
/// # Panics
///
/// If the index is out of bounds.
pub(crate) fn local<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue {
debug_assert!(index < self.code_block().locals_count);
let at = self.rp + index;
&stack[at as usize]
}
/// 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)
@ -167,12 +244,16 @@ 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()`].
pub(crate) fn locals_already_pushed(&self) -> bool {
self.flags.contains(CallFrameFlags::LOCALS_ALREADY_PUSHED)
}
} }
/// ---- `CallFrame` stack methods ---- /// ---- `CallFrame` stack methods ----
impl CallFrame { impl CallFrame {
pub(crate) fn set_frame_pointer(&mut self, pointer: u32) { pub(crate) fn set_register_pointer(&mut self, pointer: u32) {
self.fp = pointer; self.rp = pointer;
} }
} }

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

@ -177,6 +177,8 @@ 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,
/// \[\[ThisMode\]\] /// \[\[ThisMode\]\]
pub(crate) this_mode: ThisMode, pub(crate) this_mode: ThisMode,
@ -216,6 +218,7 @@ impl CodeBlock {
name, name,
flags: Cell::new(flags), flags: Cell::new(flags),
length, length,
locals_count: 0,
this_mode: ThisMode::Global, this_mode: ThisMode::Global,
params: FormalParameterList::default(), params: FormalParameterList::default(),
handlers: ThinVec::default(), handlers: ThinVec::default(),
@ -286,6 +289,13 @@ impl CodeBlock {
self.flags.get().contains(CodeBlockFlags::IS_GENERATOR) self.flags.get().contains(CodeBlockFlags::IS_GENERATOR)
} }
/// Returns true if this function a async generator function.
pub(crate) fn is_async_generator(&self) -> bool {
self.flags
.get()
.contains(CodeBlockFlags::IS_ASYNC | CodeBlockFlags::IS_GENERATOR)
}
/// Returns true if this function an async function. /// Returns true if this function an async function.
pub(crate) fn is_ordinary(&self) -> bool { pub(crate) fn is_ordinary(&self) -> bool {
!self.is_async() && !self.is_generator() !self.is_async() && !self.is_generator()

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

@ -158,29 +158,34 @@ impl Vm {
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) { pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
let current_stack_length = self.stack.len(); let current_stack_length = self.stack.len();
frame.set_frame_pointer(current_stack_length as u32); frame.set_register_pointer(current_stack_length as u32);
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,
// since generator-like functions push the same call
// frame with pre-built stack.
if !frame.locals_already_pushed() {
let locals_count = frame.code_block().locals_count;
self.stack.resize_with(
current_stack_length + locals_count as usize,
JsValue::undefined,
);
}
self.frames.push(frame); self.frames.push(frame);
} }
pub(crate) fn push_frame_with_stack( pub(crate) fn push_frame_with_stack(
&mut self, &mut self,
mut frame: CallFrame, frame: CallFrame,
this: JsValue, this: JsValue,
function: JsValue, function: JsValue,
) { ) {
let current_stack_length = self.stack.len();
frame.set_frame_pointer(current_stack_length as u32);
self.push(this); self.push(this);
self.push(function); self.push(function);
std::mem::swap(&mut self.environments, &mut frame.environments); self.push_frame(frame);
std::mem::swap(&mut self.realm, &mut frame.realm);
self.frames.push(frame);
} }
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> { pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
@ -205,7 +210,7 @@ impl Vm {
let catch_address = handler.handler(); let catch_address = handler.handler();
let environment_sp = frame.env_fp + handler.environment_count; let environment_sp = frame.env_fp + handler.environment_count;
let sp = frame.fp + handler.stack_count; let sp = frame.rp + handler.stack_count;
// Go to handler location. // Go to handler location.
frame.pc = catch_address; frame.pc = catch_address;
@ -320,7 +325,12 @@ impl Context {
let result = self.execute_instruction(f); let result = self.execute_instruction(f);
let duration = instant.elapsed(); let duration = instant.elapsed();
let fp = self.vm.frames.last().map(|frame| frame.fp as usize); let fp = self
.vm
.frames
.last()
.map(CallFrame::fp)
.map(|fp| fp as usize);
let stack = { let stack = {
let mut stack = String::from("[ "); let mut stack = String::from("[ ");
@ -422,7 +432,7 @@ impl Context {
break; break;
} }
fp = frame.fp as usize; fp = frame.fp() as usize;
env_fp = frame.env_fp as usize; env_fp = frame.env_fp as usize;
self.vm.pop_frame(); self.vm.pop_frame();
} }
@ -449,7 +459,8 @@ impl Context {
match result { match result {
CompletionType::Normal => {} CompletionType::Normal => {}
CompletionType::Return => { CompletionType::Return => {
self.vm.stack.truncate(self.vm.frame().fp as usize); let fp = self.vm.frame().fp() as usize;
self.vm.stack.truncate(fp);
let result = self.vm.take_return_value(); let result = self.vm.take_return_value();
if self.vm.frame().exit_early() { if self.vm.frame().exit_early() {
@ -460,7 +471,7 @@ impl Context {
self.vm.pop_frame(); self.vm.pop_frame();
} }
CompletionType::Throw => { CompletionType::Throw => {
let mut fp = self.vm.frame().fp; let mut fp = self.vm.frame().fp();
let mut env_fp = self.vm.frame().env_fp; let mut env_fp = self.vm.frame().env_fp;
if self.vm.frame().exit_early() { if self.vm.frame().exit_early() {
self.vm.environments.truncate(env_fp as usize); self.vm.environments.truncate(env_fp as usize);
@ -476,8 +487,8 @@ impl Context {
self.vm.pop_frame(); self.vm.pop_frame();
while let Some(frame) = self.vm.frames.last_mut() { while let Some(frame) = self.vm.frames.last_mut() {
fp = frame.fp; fp = frame.fp();
env_fp = frame.fp; env_fp = frame.env_fp;
let pc = frame.pc; let pc = frame.pc;
let exit_early = frame.exit_early(); let exit_early = frame.exit_early();

8
core/engine/src/vm/opcode/arguments.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
builtins::function::arguments::{MappedArguments, UnmappedArguments}, builtins::function::arguments::{MappedArguments, UnmappedArguments},
vm::{CallFrame, CompletionType}, vm::CompletionType,
Context, JsResult, Context, JsResult,
}; };
@ -19,7 +19,6 @@ impl Operation for CreateMappedArgumentsObject {
const COST: u8 = 8; const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let arguments_start = context.vm.frame().fp as usize + CallFrame::FIRST_ARGUMENT_POSITION;
let function_object = context let function_object = context
.vm .vm
.frame() .frame()
@ -27,7 +26,7 @@ impl Operation for CreateMappedArgumentsObject {
.clone() .clone()
.expect("there should be a function object"); .expect("there should be a function object");
let code = context.vm.frame().code_block().clone(); let code = context.vm.frame().code_block().clone();
let args = context.vm.stack[arguments_start..].to_vec(); let args = context.vm.frame().arguments(&context.vm).to_vec();
let env = context.vm.environments.current(); let env = context.vm.environments.current();
let arguments = MappedArguments::new( let arguments = MappedArguments::new(
@ -55,8 +54,7 @@ impl Operation for CreateUnmappedArgumentsObject {
const COST: u8 = 4; const COST: u8 = 4;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let arguments_start = context.vm.frame().fp as usize + CallFrame::FIRST_ARGUMENT_POSITION; let args = context.vm.frame().arguments(&context.vm).to_vec();
let args = context.vm.stack[arguments_start..].to_vec();
let arguments = UnmappedArguments::new(&args, context); let arguments = UnmappedArguments::new(&args, context);
context.vm.push(arguments); context.vm.push(arguments);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)

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

@ -49,17 +49,16 @@ impl Operation for Await {
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
let mut gen = captures.borrow_mut().take().expect("should only run once"); let mut gen = captures.borrow_mut().take().expect("should only run once");
// NOTE: We need to get the object before resuming, since it could clear the stack.
let async_generator = gen.async_generator_object();
gen.resume( gen.resume(
Some(args.get_or_undefined(0).clone()), Some(args.get_or_undefined(0).clone()),
GeneratorResumeKind::Normal, GeneratorResumeKind::Normal,
context, context,
); );
if let Some(async_generator) = gen if let Some(async_generator) = async_generator {
.call_frame
.as_ref()
.and_then(|f| f.async_generator.clone())
{
async_generator async_generator
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be async generator") .expect("must be async generator")
@ -92,17 +91,16 @@ impl Operation for Await {
let mut gen = captures.borrow_mut().take().expect("should only run once"); let mut gen = captures.borrow_mut().take().expect("should only run once");
// NOTE: We need to get the object before resuming, since it could clear the stack.
let async_generator = gen.async_generator_object();
gen.resume( gen.resume(
Some(args.get_or_undefined(0).clone()), Some(args.get_or_undefined(0).clone()),
GeneratorResumeKind::Throw, GeneratorResumeKind::Throw,
context, context,
); );
if let Some(async_generator) = gen if let Some(async_generator) = async_generator {
.call_frame
.as_ref()
.and_then(|f| f.async_generator.clone())
{
async_generator async_generator
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be async generator") .expect("must be async generator")

69
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},
CallFrame, CompletionType, CompletionType,
}, },
Context, JsError, JsObject, JsResult, JsValue, Context, JsError, JsObject, JsResult, JsValue,
}; };
@ -37,36 +37,12 @@ impl Operation for Generator {
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let r#async = context.vm.read::<u8>() != 0; let r#async = context.vm.read::<u8>() != 0;
let frame = context.vm.frame(); let active_function = context.vm.frame().function(&context.vm);
let code_block = frame.code_block().clone();
let active_runnable = frame.active_runnable.clone();
let active_function = frame.function(&context.vm);
let environments = frame.environments.clone();
let realm = frame.realm.clone();
let pc = frame.pc;
let mut dummy_call_frame = CallFrame::new(code_block, active_runnable, environments, realm);
dummy_call_frame.pc = pc;
let mut call_frame = std::mem::replace(context.vm.frame_mut(), dummy_call_frame);
context
.vm
.frame_mut()
.set_exit_early(call_frame.exit_early());
call_frame.environments = context.vm.environments.clone();
call_frame.realm = context.realm().clone();
let fp = call_frame.fp as usize;
let stack = context.vm.stack[fp..].to_vec();
context.vm.stack.truncate(fp);
call_frame.fp = 0;
let this_function_object = let this_function_object =
active_function.expect("active function should be set to the generator"); active_function.expect("active function should be set to the generator");
let mut frame = GeneratorContext::from_current(context);
let proto = this_function_object let proto = this_function_object
.get(PROTOTYPE, context) .get(PROTOTYPE, context)
.expect("generator must have a prototype property") .expect("generator must have a prototype property")
@ -88,7 +64,7 @@ impl Operation for Generator {
proto, proto,
AsyncGenerator { AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart, state: AsyncGeneratorState::SuspendedStart,
context: Some(GeneratorContext::new(stack, call_frame)), context: None,
queue: VecDeque::new(), queue: VecDeque::new(),
}, },
) )
@ -97,25 +73,29 @@ impl Operation for Generator {
context.root_shape(), context.root_shape(),
proto, proto,
crate::builtins::generator::Generator { crate::builtins::generator::Generator {
state: GeneratorState::SuspendedStart { state: GeneratorState::Completed,
context: GeneratorContext::new(stack, call_frame),
},
}, },
) )
}; };
if r#async { if r#async {
let gen_clone = generator.clone(); let fp = frame
.call_frame
.as_ref()
.map_or(0, |frame| frame.rp as usize);
frame.stack[fp] = generator.clone().into();
let mut gen = generator let mut gen = generator
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be object here"); .expect("must be object here");
let gen_context = gen.context.as_mut().expect("must exist");
// TODO: try to move this to the context itself. gen.context = Some(frame);
gen_context } else {
.call_frame let mut gen = generator
.as_mut() .downcast_mut::<crate::builtins::generator::Generator>()
.expect("should have a call frame initialized") .expect("must be object here");
.async_generator = Some(gen_clone);
gen.state = GeneratorState::SuspendedStart { context: frame };
} }
context.vm.set_return_value(generator.into()); context.vm.set_return_value(generator.into());
@ -137,14 +117,13 @@ impl Operation for AsyncGeneratorClose {
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
// Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) // Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart)
let generator_object = context let async_generator_object = context
.vm .vm
.frame() .frame()
.async_generator .async_generator_object(&context.vm.stack)
.clone()
.expect("There should be a object"); .expect("There should be a object");
let mut gen = generator_object let mut gen = async_generator_object
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be async generator"); .expect("must be async generator");
@ -162,7 +141,7 @@ impl Operation for AsyncGeneratorClose {
} else { } else {
AsyncGenerator::complete_step(&next, Ok(return_value), true, None, context); AsyncGenerator::complete_step(&next, Ok(return_value), true, None, context);
} }
AsyncGenerator::drain_queue(&generator_object, context); AsyncGenerator::drain_queue(&async_generator_object, context);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }

9
core/engine/src/vm/opcode/generator/yield_stm.rs

@ -38,14 +38,13 @@ impl Operation for AsyncGeneratorYield {
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let value = context.vm.pop(); let value = context.vm.pop();
let async_gen = context let async_generator_object = context
.vm .vm
.frame() .frame()
.async_generator .async_generator_object(&context.vm.stack)
.clone()
.expect("`AsyncGeneratorYield` must only be called inside async generators"); .expect("`AsyncGeneratorYield` must only be called inside async generators");
let completion = Ok(value); let completion = Ok(value);
let next = async_gen let next = async_generator_object
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be async generator object") .expect("must be async generator object")
.queue .queue
@ -55,7 +54,7 @@ impl Operation for AsyncGeneratorYield {
// TODO: 7. Let previousContext be the second to top element of the execution context stack. // TODO: 7. Let previousContext be the second to top element of the execution context stack.
AsyncGenerator::complete_step(&next, completion, false, None, context); AsyncGenerator::complete_step(&next, completion, false, None, context);
let mut generator_object_mut = async_gen.borrow_mut(); let mut generator_object_mut = async_generator_object.borrow_mut();
let gen = generator_object_mut let gen = generator_object_mut
.downcast_mut::<AsyncGenerator>() .downcast_mut::<AsyncGenerator>()
.expect("must be async generator object"); .expect("must be async generator object");

10
core/engine/src/vm/opcode/get/argument.rs

@ -13,12 +13,10 @@ pub(crate) struct GetArgument;
impl GetArgument { impl GetArgument {
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let fp = context.vm.frame().fp as usize; let value = context
let argument_index = fp + 2; .vm
let argument_count = context.vm.frame().argument_count as usize; .frame()
.argument(index, &context.vm)
let value = context.vm.stack[argument_index..(argument_index + argument_count)]
.get(index)
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
context.vm.push(value); context.vm.push(value);

6
core/engine/src/vm/opcode/rest_parameter/mod.rs

@ -17,11 +17,11 @@ impl Operation for RestParameterInit {
const COST: u8 = 6; const COST: u8 = 6;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let arg_count = context.vm.frame().argument_count as usize; let argument_count = context.vm.frame().argument_count as usize;
let param_count = context.vm.frame().code_block().params.as_ref().len(); let param_count = context.vm.frame().code_block().params.as_ref().len();
let array = if arg_count >= param_count { let array = if argument_count >= param_count {
let rest_count = arg_count - param_count + 1; let rest_count = argument_count - param_count + 1;
let args = context.vm.pop_n_values(rest_count); let args = context.vm.pop_n_values(rest_count);
Array::create_array_from_list(args, context) Array::create_array_from_list(args, context)
} else { } else {

Loading…
Cancel
Save