Browse Source

Refactor `Context::run()` method (#3179)

* Refactor `Context::run()` method

- Remove async generator close from run
- Remove promise capability from run
- Remove promise capability check from call_internal
- Remove generator creation from call_internal
- Move promise capability creation to separate opcode

* Inline ReThrow opcode in run
pull/3189/head
Haled Odat 1 year ago committed by GitHub
parent
commit
d8bf5f589d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      boa_engine/src/builtins/function/mod.rs
  2. 7
      boa_engine/src/bytecompiler/declarations.rs
  3. 39
      boa_engine/src/bytecompiler/function.rs
  4. 18
      boa_engine/src/bytecompiler/jump_control.rs
  5. 13
      boa_engine/src/bytecompiler/mod.rs
  6. 12
      boa_engine/src/environments/runtime/mod.rs
  7. 4
      boa_engine/src/module/source.rs
  8. 134
      boa_engine/src/vm/code_block.rs
  9. 15
      boa_engine/src/vm/flowgraph/mod.rs
  10. 337
      boa_engine/src/vm/mod.rs
  11. 74
      boa_engine/src/vm/opcode/await/mod.rs
  12. 0
      boa_engine/src/vm/opcode/control_flow/jump.rs
  13. 2
      boa_engine/src/vm/opcode/control_flow/mod.rs
  14. 156
      boa_engine/src/vm/opcode/generator/mod.rs
  15. 47
      boa_engine/src/vm/opcode/mod.rs

16
boa_engine/src/builtins/function/mod.rs

@ -775,6 +775,7 @@ impl BuiltInFunctionObject {
let code = FunctionCompiler::new()
.name(Sym::ANONYMOUS)
.generator(true)
.r#async(r#async)
.compile(
&FormalParameterList::default(),
&FunctionBody::default(),
@ -793,12 +794,15 @@ impl BuiltInFunctionObject {
Ok(function_object)
} else {
let code = FunctionCompiler::new().name(Sym::ANONYMOUS).compile(
&FormalParameterList::default(),
&FunctionBody::default(),
context.realm().environment().compile_env(),
context,
);
let code = FunctionCompiler::new()
.r#async(r#async)
.name(Sym::ANONYMOUS)
.compile(
&FormalParameterList::default(),
&FunctionBody::default(),
context.realm().environment().compile_env(),
context,
);
let environments = context.vm.environments.pop_to_global();
let function_object =

7
boa_engine/src/bytecompiler/declarations.rs

@ -1010,11 +1010,10 @@ impl ByteCompiler<'_, '_> {
if !formals.has_rest_parameter() {
self.emit_opcode(Opcode::RestParameterPop);
}
if generator {
self.emit_opcode(Opcode::PushUndefined);
// Don't need to use `AsyncGeneratorYield` since
// we just want to stop the execution of the generator.
self.emit_opcode(Opcode::GeneratorYield);
self.emit_opcode(Opcode::Generator);
self.emit_u8(self.in_async().into());
self.emit_opcode(Opcode::Pop);
}

39
boa_engine/src/bytecompiler/function.rs

@ -4,7 +4,7 @@ use crate::{
builtins::function::ThisMode,
bytecompiler::ByteCompiler,
environments::CompileTimeEnvironment,
vm::{CodeBlock, CodeBlockFlags},
vm::{CodeBlock, CodeBlockFlags, Opcode},
Context,
};
use boa_ast::function::{FormalParameterList, FunctionBody};
@ -120,6 +120,31 @@ impl FunctionCompiler {
// Function environment
compiler.push_compile_environment(true);
// Taken from:
// - 15.9.3 Runtime Semantics: EvaluateAsyncConciseBody: <https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncconcisebody>
// - 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody: <https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncfunctionbody>
//
// Note: In `EvaluateAsyncGeneratorBody` unlike the async non-generator functions we don't handle exceptions thrown by
// `FunctionDeclarationInstantiation` (so they are propagated).
//
// See: 15.6.2 Runtime Semantics: EvaluateAsyncGeneratorBody: https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncgeneratorbody
if compiler.in_async() && !compiler.in_generator() {
// 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
//
// Note: If the promise capability is already set, then we do nothing.
// This is a deviation from the spec, but it allows to set the promise capability by
// ExecuteAsyncModule ( module ): <https://tc39.es/ecma262/#sec-execute-async-module>
compiler.emit_opcode(Opcode::CreatePromiseCapability);
// 2. Let declResult be Completion(FunctionDeclarationInstantiation(functionObject, argumentsList)).
//
// Note: We push an exception handler so we catch exceptions that are thrown by the
// `FunctionDeclarationInstantiation` abstract function.
//
// Patched in `ByteCompiler::finish()`.
compiler.async_handler = Some(compiler.push_handler());
}
let (env_label, additional_env) = compiler.function_declaration_instantiation(
body,
parameters,
@ -128,6 +153,18 @@ impl FunctionCompiler {
self.generator,
);
// Taken from:
// - 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ): <https://tc39.es/ecma262/#sec-asyncgeneratorstart>
//
// Note: We do handle exceptions thrown by generator body in `AsyncGeneratorStart`.
if compiler.in_generator() {
assert!(compiler.async_handler.is_none());
if compiler.in_async() {
// Patched in `ByteCompiler::finish()`.
compiler.async_handler = Some(compiler.push_handler());
}
}
compiler.compile_statement_list(body.statements(), false, false);
if let Some(env_labels) = env_label {

18
boa_engine/src/bytecompiler/jump_control.rs

@ -122,7 +122,23 @@ impl JumpRecord {
match self.kind {
JumpRecordKind::Break => compiler.patch_jump(self.label),
JumpRecordKind::Continue => compiler.patch_jump_with_target(self.label, start_address),
JumpRecordKind::Return => compiler.emit_opcode(Opcode::Return),
JumpRecordKind::Return => {
match (compiler.in_async(), compiler.in_generator()) {
// Taken from:
// - 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ): https://tc39.es/ecma262/#sec-asyncgeneratorstart
//
// Note: If we are returning we have to close the async generator function.
(true, true) => compiler.emit_opcode(Opcode::AsyncGeneratorClose),
// Taken from:
// - 27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ): <https://tc39.es/ecma262/#sec-asyncblockstart>
//
// Note: If there is promise capability resolve or reject it based on pending exception.
(true, false) => compiler.emit_opcode(Opcode::CompletePromiseCapability),
(_, _) => {}
}
compiler.emit_opcode(Opcode::Return);
}
}
}
}

13
boa_engine/src/bytecompiler/mod.rs

@ -255,8 +255,13 @@ pub struct ByteCompiler<'ctx, 'host> {
names_map: FxHashMap<Identifier, u32>,
bindings_map: FxHashMap<BindingLocator, u32>,
jump_info: Vec<JumpControlInfo>,
in_async: bool,
pub(crate) in_async: bool,
in_generator: bool,
/// Used to handle exception throws that escape the async function types.
///
/// Async functions and async generator functions, need to be closed and resolved.
pub(crate) async_handler: Option<u32>,
json_parse: bool,
// TODO: remove when we separate scripts from the context
@ -305,6 +310,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
jump_info: Vec::new(),
in_async: false,
in_generator: false,
async_handler: None,
json_parse,
current_environment,
context,
@ -1410,7 +1416,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
#[allow(clippy::missing_const_for_fn)]
pub fn finish(mut self) -> CodeBlock {
// Push return at the end of the function compilation.
self.emit_opcode(Opcode::Return);
if let Some(async_handler) = self.async_handler {
self.patch_handler(async_handler);
}
self.r#return();
let name = self
.context

12
boa_engine/src/environments/runtime/mod.rs

@ -78,6 +78,18 @@ impl EnvironmentStack {
self.stack[0] = Environment::Declarative(global);
}
/// Gets the current global environment.
pub(crate) fn global(&self) -> Gc<DeclarativeEnvironment> {
let env = self.stack[0].clone();
match env {
Environment::Declarative(ref env) => env.clone(),
Environment::Object(_) => {
unreachable!("first environment should be the global environment")
}
}
}
/// Extends the length of the next outer function environment to the number of compiled bindings.
///
/// This is only useful when compiled bindings are added after the initial compilation (eval).

4
boa_engine/src/module/source.rs

@ -1414,6 +1414,10 @@ impl SourceTextModule {
let mut compiler =
ByteCompiler::new(Sym::MAIN, true, false, module_compile_env.clone(), context);
compiler.in_async = true;
compiler.async_handler = Some(compiler.push_handler());
let mut imports = Vec::new();
let (codeblock, functions) = {

134
boa_engine/src/vm/code_block.rs

@ -3,12 +3,7 @@
//! This module is for the `CodeBlock` which implements a function representation in the VM
use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode},
generator::{Generator, GeneratorContext, GeneratorState},
promise::PromiseCapability,
},
builtins::function::{arguments::Arguments, ConstructorKind, Function, FunctionKind, ThisMode},
context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus},
error::JsNativeError,
@ -22,7 +17,7 @@ use bitflags::bitflags;
use boa_ast::function::FormalParameterList;
use boa_gc::{empty_trace, Finalize, Gc, Trace};
use boa_profiler::Profiler;
use std::{cell::Cell, collections::VecDeque, mem::size_of, rc::Rc};
use std::{cell::Cell, mem::size_of, rc::Rc};
use thin_vec::ThinVec;
#[cfg(any(feature = "trace", feature = "flowgraph"))]
@ -325,6 +320,11 @@ impl CodeBlock {
*pc += size_of::<u8>();
result
}
Opcode::Generator => {
let result = self.read::<u8>(*pc);
*pc += size_of::<u8>();
format!("async: {}", result != 0)
}
Opcode::PushInt8 => {
let result = self.read::<i8>(*pc).to_string();
*pc += size_of::<i8>();
@ -570,6 +570,9 @@ impl CodeBlock {
| Opcode::This
| Opcode::Super
| Opcode::Return
| Opcode::AsyncGeneratorClose
| Opcode::CreatePromiseCapability
| Opcode::CompletePromiseCapability
| Opcode::PopEnvironment
| Opcode::IncrementLoopIteration
| Opcode::CreateForInIterator
@ -674,11 +677,7 @@ impl CodeBlock {
| Opcode::Reserved56
| Opcode::Reserved57
| Opcode::Reserved58
| Opcode::Reserved59
| Opcode::Reserved60
| Opcode::Reserved61
| Opcode::Reserved62
| Opcode::Reserved63 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved59 => unreachable!("Reserved opcodes are unrechable"),
}
}
}
@ -1072,7 +1071,7 @@ impl JsObject {
context.enter_realm(realm);
context.vm.active_function = Some(active_function);
let (code, mut environments, class_object, mut script_or_module, async_, gen) =
let (code, mut environments, class_object, mut script_or_module) =
match function_object.kind() {
FunctionKind::Native {
function,
@ -1108,44 +1107,26 @@ impl JsObject {
environments.clone(),
class_object.clone(),
script_or_module.clone(),
false,
false,
)
}
FunctionKind::Async {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
true,
false,
),
FunctionKind::Generator {
}
| FunctionKind::Generator {
code,
environments,
class_object,
script_or_module,
..
} => (
code.clone(),
environments.clone(),
class_object.clone(),
script_or_module.clone(),
false,
true,
),
FunctionKind::AsyncGenerator {
}
| FunctionKind::AsyncGenerator {
code,
environments,
class_object,
script_or_module,
..
} => (
@ -1153,21 +1134,11 @@ impl JsObject {
environments.clone(),
class_object.clone(),
script_or_module.clone(),
true,
true,
),
};
drop(object);
let promise_capability = (async_ && !gen).then(|| {
PromiseCapability::new(
&context.intrinsics().constructors().promise().constructor(),
context,
)
.expect("cannot fail per spec")
});
std::mem::swap(&mut environments, &mut context.vm.environments);
let lexical_this_mode = code.this_mode == ThisMode::Lexical;
@ -1277,10 +1248,9 @@ impl JsObject {
std::mem::swap(&mut context.vm.stack, &mut stack);
let mut frame = CallFrame::new(code)
let frame = CallFrame::new(code)
.with_argument_count(argument_count as u32)
.with_env_fp(env_fp);
frame.promise_capability = promise_capability.clone();
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
@ -1291,78 +1261,12 @@ impl JsObject {
.consume()
.map_err(|err| err.inject_realm(context.realm().clone()));
let call_frame = context.vm.pop_frame().expect("frame must exist");
context.vm.pop_frame().expect("frame must exist");
std::mem::swap(&mut environments, &mut context.vm.environments);
std::mem::swap(&mut context.vm.stack, &mut stack);
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
if let Some(promise_capability) = promise_capability {
Ok(promise_capability.promise().clone().into())
} else if gen {
result?;
let proto = this_function_object
.get(PROTOTYPE, context)
.expect("generator must have a prototype property")
.as_object()
.map_or_else(
|| {
if async_ {
context.intrinsics().objects().async_generator()
} else {
context.intrinsics().objects().generator()
}
},
Clone::clone,
);
let data = if async_ {
ObjectData::async_generator(AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart,
context: Some(GeneratorContext::new(
environments,
stack,
context.vm.active_function.clone(),
call_frame,
context.realm().clone(),
)),
queue: VecDeque::new(),
})
} else {
ObjectData::generator(Generator {
state: GeneratorState::SuspendedStart {
context: GeneratorContext::new(
environments,
stack,
context.vm.active_function.clone(),
call_frame,
context.realm().clone(),
),
},
})
};
let generator =
Self::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data);
if async_ {
let gen_clone = generator.clone();
let mut generator_mut = generator.borrow_mut();
let gen = generator_mut
.as_async_generator_mut()
.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
.call_frame
.as_mut()
.expect("should have a call frame initialized")
.async_generator = Some(gen_clone);
}
Ok(generator.into())
} else {
result
}
result
}
pub(crate) fn construct_internal(

15
boa_engine/src/vm/flowgraph/mod.rs

@ -62,6 +62,12 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Generator => {
pc += size_of::<u8>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PushInt8 => {
pc += size_of::<i8>();
@ -517,6 +523,9 @@ impl CodeBlock {
| Opcode::PushNewArray
| Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::AsyncGeneratorClose
| Opcode::CreatePromiseCapability
| Opcode::CompletePromiseCapability
| Opcode::GeneratorNext
| Opcode::PushClassField
| Opcode::SuperCallDerived
@ -603,11 +612,7 @@ impl CodeBlock {
| Opcode::Reserved56
| Opcode::Reserved57
| Opcode::Reserved58
| Opcode::Reserved59
| Opcode::Reserved60
| Opcode::Reserved61
| Opcode::Reserved62
| Opcode::Reserved63 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved59 => unreachable!("Reserved opcodes are unrechable"),
}
}

337
boa_engine/src/vm/mod.rs

@ -7,11 +7,10 @@
#[cfg(feature = "fuzz")]
use crate::JsNativeError;
use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
environments::{DeclarativeEnvironment, EnvironmentStack},
script::Script,
vm::code_block::Readable,
Context, JsError, JsObject, JsResult, JsValue, Module,
Context, JsError, JsNativeErrorKind, JsObject, JsResult, JsValue, Module,
};
use boa_gc::{custom_trace, Finalize, Gc, Trace};
@ -150,7 +149,9 @@ impl Vm {
self.frames.last_mut().expect("no frame found")
}
pub(crate) fn push_frame(&mut self, frame: CallFrame) {
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
let current_stack_length = self.stack.len();
frame.set_frame_pointer(current_stack_length as u32);
self.frames.push(frame);
}
@ -190,6 +191,93 @@ pub(crate) enum CompletionType {
Yield,
}
#[cfg(feature = "trace")]
impl Context<'_> {
const COLUMN_WIDTH: usize = 26;
const TIME_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH / 2;
const OPCODE_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
fn trace_call_frame(&self) {
let msg = if self.vm.frames.last().is_some() {
format!(
" Call Frame -- {} ",
self.vm.frame().code_block().name().to_std_string_escaped()
)
} else {
" VM Start ".to_string()
};
println!(
"{}",
self.vm
.frame()
.code_block
.to_interned_string(self.interner())
);
println!(
"{msg:-^width$}",
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
);
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Stack\n",
"Time",
"Opcode",
"Operands",
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
);
}
fn trace_execute_instruction(&mut self) -> JsResult<CompletionType> {
let mut pc = self.vm.frame().pc as usize;
let opcode: Opcode = self.vm.frame().code_block.read::<u8>(pc).into();
let operands = self
.vm
.frame()
.code_block
.instruction_operands(&mut pc, self.interner());
let instant = Instant::now();
let result = self.execute_instruction();
let duration = instant.elapsed();
let stack = {
let mut stack = String::from("[ ");
for (i, value) in self.vm.stack.iter().rev().enumerate() {
match value {
value if value.is_callable() => stack.push_str("[function]"),
value if value.is_object() => stack.push_str("[object]"),
value => stack.push_str(&value.display().to_string()),
}
if i + 1 != self.vm.stack.len() {
stack.push(',');
}
stack.push(' ');
}
stack.push(']');
stack
};
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
format!("{}μs", duration.as_micros()),
opcode.as_str(),
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
);
result
}
}
impl Context<'_> {
fn execute_instruction(&mut self) -> JsResult<CompletionType> {
let opcode: Opcode = {
@ -198,7 +286,7 @@ impl Context<'_> {
let frame = self.vm.frame_mut();
let pc = frame.pc;
let opcode = Opcode::from(frame.code_block.bytecode[pc as usize]);
let opcode = frame.code_block.bytecode[pc as usize].into();
frame.pc += 1;
opcode
};
@ -209,100 +297,27 @@ impl Context<'_> {
}
pub(crate) fn run(&mut self) -> CompletionRecord {
#[cfg(feature = "trace")]
const COLUMN_WIDTH: usize = 26;
#[cfg(feature = "trace")]
const TIME_COLUMN_WIDTH: usize = COLUMN_WIDTH / 2;
#[cfg(feature = "trace")]
const OPCODE_COLUMN_WIDTH: usize = COLUMN_WIDTH;
#[cfg(feature = "trace")]
const OPERAND_COLUMN_WIDTH: usize = COLUMN_WIDTH;
#[cfg(feature = "trace")]
const NUMBER_OF_COLUMNS: usize = 4;
let _timer = Profiler::global().start_event("run", "vm");
#[cfg(feature = "trace")]
if self.vm.trace {
let msg = if self.vm.frames.last().is_some() {
" Call Frame "
} else {
" VM Start "
};
println!(
"{}\n",
self.vm
.frame()
.code_block
.to_interned_string(self.interner())
);
println!(
"{msg:-^width$}",
width = COLUMN_WIDTH * NUMBER_OF_COLUMNS - 10
);
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {:<OPERAND_COLUMN_WIDTH$} Top Of Stack\n",
"Time",
"Opcode",
"Operands",
);
self.trace_call_frame();
}
let current_stack_length = self.vm.stack.len();
self.vm
.frame_mut()
.set_frame_pointer(current_stack_length as u32);
// 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 promise_capability = self.vm.frame().promise_capability.clone();
let execution_completion = loop {
loop {
#[cfg(feature = "fuzz")]
{
if self.instructions_remaining == 0 {
let err = JsError::from_native(JsNativeError::no_instructions_remain());
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
return CompletionRecord::Throw(JsError::from_native(
JsNativeError::no_instructions_remain(),
));
}
self.instructions_remaining -= 1;
}
// 1. Run the next instruction.
#[cfg(feature = "trace")]
let result = if self.vm.trace || self.vm.frame().code_block.traceable() {
let mut pc = self.vm.frame().pc as usize;
let opcode: Opcode = self
.vm
.frame()
.code_block
.read::<u8>(pc)
.try_into()
.expect("invalid opcode");
let operands = self
.vm
.frame()
.code_block
.instruction_operands(&mut pc, self.interner());
let instant = Instant::now();
let result = self.execute_instruction();
let duration = instant.elapsed();
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {}",
format!("{}μs", duration.as_micros()),
opcode.as_str(),
match self.vm.stack.last() {
Some(value) if value.is_callable() => "[function]".to_string(),
Some(value) if value.is_object() => "[object]".to_string(),
Some(value) => value.display().to_string(),
None => "<empty>".to_string(),
},
);
result
self.trace_execute_instruction()
} else {
self.execute_instruction()
};
@ -310,14 +325,21 @@ impl Context<'_> {
#[cfg(not(feature = "trace"))]
let result = self.execute_instruction();
// 2. Evaluate the result of executing the instruction.
match result {
Ok(CompletionType::Normal) => {}
Ok(CompletionType::Return) => {
break CompletionType::Return;
self.vm.stack.truncate(self.vm.frame().fp as usize);
let execution_result = self.vm.frame_mut().return_value.clone();
return CompletionRecord::Normal(execution_result);
}
Ok(CompletionType::Throw) => {
break CompletionType::Throw;
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(
self.vm
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
);
}
// Early return immediately.
Ok(CompletionType::Yield) => {
@ -325,144 +347,33 @@ impl Context<'_> {
return CompletionRecord::Return(result);
}
Err(err) => {
#[cfg(feature = "fuzz")]
{
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if native_error.is_no_instructions_remain() {
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
}
}
}
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if native_error.is_runtime_limit() {
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
match native_error.kind {
#[cfg(feature = "fuzz")]
JsNativeErrorKind::NoInstructionsRemain => {
return CompletionRecord::Throw(err);
}
JsNativeErrorKind::RuntimeLimit => {
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(err);
}
_ => {}
}
}
self.vm.pending_exception = Some(err);
let evaluation = Opcode::ReThrow
.execute(self)
.expect("Opcode::Throw cannot return Err");
if evaluation == CompletionType::Normal {
// Note: -1 because we increment after fetching the opcode.
let pc = self.vm.frame().pc.saturating_sub(1);
if self.vm.handle_exception_at(pc) {
self.vm.pending_exception = Some(err);
continue;
}
break CompletionType::Throw;
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(err);
}
}
};
#[cfg(feature = "trace")]
if self.vm.trace {
println!("\nStack:");
if self.vm.stack.is_empty() {
println!(" <empty>");
} else {
for (i, value) in self.vm.stack.iter().enumerate() {
println!(
"{i:04}{:<width$} {}",
"",
if value.is_callable() {
"[function]".to_string()
} else if value.is_object() {
"[object]".to_string()
} else {
value.display().to_string()
},
width = COLUMN_WIDTH / 2 - 4,
);
}
}
println!("\n");
}
self.vm.stack.truncate(self.vm.frame().fp as usize);
// Determine the execution result
let execution_result = self.vm.frame_mut().return_value.clone();
if let Some(promise) = promise_capability {
match execution_completion {
CompletionType::Normal => {
promise
.resolve()
.call(&JsValue::undefined(), &[], self)
.expect("cannot fail per spec");
}
CompletionType::Return => {
promise
.resolve()
.call(&JsValue::undefined(), &[execution_result.clone()], self)
.expect("cannot fail per spec");
}
CompletionType::Throw => {
let err = self
.vm
.pending_exception
.take()
.expect("Take must exist on a Throw");
promise
.reject()
.call(&JsValue::undefined(), &[err.to_opaque(self)], self)
.expect("cannot fail per spec");
self.vm.pending_exception = Some(err);
}
CompletionType::Yield => unreachable!("this is handled before"),
}
} else if let Some(generator_object) = self.vm.frame().async_generator.clone() {
// Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart)
let mut generator_object_mut = generator_object.borrow_mut();
let generator = generator_object_mut
.as_async_generator_mut()
.expect("must be async generator");
generator.state = AsyncGeneratorState::Completed;
generator.context = None;
let next = generator
.queue
.pop_front()
.expect("must have item in queue");
drop(generator_object_mut);
if execution_completion == CompletionType::Throw {
AsyncGenerator::complete_step(
&next,
Err(self
.vm
.pending_exception
.take()
.expect("err must exist on a Completion::Throw")),
true,
None,
self,
);
} else {
AsyncGenerator::complete_step(&next, Ok(execution_result), true, None, self);
}
AsyncGenerator::drain_queue(&generator_object, self);
return CompletionRecord::Normal(JsValue::undefined());
}
// Any valid return statement is re-evaluated as a normal completion vs. return (yield).
if execution_completion == CompletionType::Throw {
return CompletionRecord::Throw(
self.vm
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
);
}
CompletionRecord::Normal(execution_result)
}
}

74
boa_engine/src/vm/opcode/await_stm/mod.rs → boa_engine/src/vm/opcode/await/mod.rs

@ -125,7 +125,79 @@ impl Operation for Await {
context,
);
context.vm.push(JsValue::undefined());
if let Some(promise_capabality) = context.vm.frame().promise_capability.clone() {
context.vm.push(promise_capabality.promise().clone());
} else {
context.vm.push(JsValue::undefined());
}
Ok(CompletionType::Yield)
}
}
/// `CreatePromiseCapability` implements the Opcode Operation for `Opcode::CreatePromiseCapability`
///
/// Operation:
/// - Create a promise capacity for an async function, if not already set.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreatePromiseCapability;
impl Operation for CreatePromiseCapability {
const NAME: &'static str = "CreatePromiseCapability";
const INSTRUCTION: &'static str = "INST - CreatePromiseCapability";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.frame().promise_capability.is_some() {
return Ok(CompletionType::Normal);
}
let promise_capability = crate::builtins::promise::PromiseCapability::new(
&context.intrinsics().constructors().promise().constructor(),
context,
)
.expect("cannot fail per spec");
context.vm.frame_mut().promise_capability = Some(promise_capability);
Ok(CompletionType::Normal)
}
}
/// `CompletePromiseCapability` implements the Opcode Operation for `Opcode::CompletePromiseCapability`
///
/// Operation:
/// - Resolves or rejects the promise capability, depending if the pending exception is set.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CompletePromiseCapability;
impl Operation for CompletePromiseCapability {
const NAME: &'static str = "CompletePromiseCapability";
const INSTRUCTION: &'static str = "INST - 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 {
return if context.vm.pending_exception.is_some() {
Ok(CompletionType::Throw)
} else {
Ok(CompletionType::Normal)
};
};
if let Some(error) = context.vm.pending_exception.take() {
promise_capability
.reject()
.call(&JsValue::undefined(), &[error.to_opaque(context)], context)
.expect("cannot fail per spec");
} else {
let return_value = context.vm.frame().return_value.clone();
promise_capability
.resolve()
.call(&JsValue::undefined(), &[return_value], context)
.expect("cannot fail per spec");
};
context.vm.frame_mut().return_value = promise_capability.promise().clone().into();
Ok(CompletionType::Normal)
}
}

0
boa_engine/src/vm/opcode/jump/mod.rs → boa_engine/src/vm/opcode/control_flow/jump.rs

2
boa_engine/src/vm/opcode/control_flow/mod.rs

@ -1,5 +1,7 @@
pub(crate) mod jump;
pub(crate) mod r#return;
pub(crate) mod throw;
pub(crate) use jump::*;
pub(crate) use r#return::*;
pub(crate) use throw::*;

156
boa_engine/src/vm/opcode/generator/mod.rs

@ -1,20 +1,172 @@
pub(crate) mod yield_stm;
use std::collections::VecDeque;
use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
generator::{GeneratorContext, GeneratorState},
},
environments::EnvironmentStack,
error::JsNativeError,
object::{ObjectData, PROTOTYPE},
string::utf16,
vm::{
call_frame::GeneratorResumeKind,
opcode::{Operation, ReThrow},
CompletionType,
CallFrame, CompletionType,
},
Context, JsError, JsResult,
Context, JsError, JsObject, JsResult,
};
pub(crate) use yield_stm::*;
use super::SetReturnValue;
/// `Generator` implements the Opcode Operation for `Opcode::Generator`
///
/// Operation:
/// - Creates the generator object and yields.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Generator;
impl Operation for Generator {
const NAME: &'static str = "Generator";
const INSTRUCTION: &'static str = "INST - Generator";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let r#async = context.vm.read::<u8>() != 0;
let code_block = context.vm.frame().code_block().clone();
let pc = context.vm.frame().pc;
let mut dummy_call_frame = CallFrame::new(code_block);
dummy_call_frame.pc = pc;
let call_frame = std::mem::replace(context.vm.frame_mut(), dummy_call_frame);
let this_function_object = context
.vm
.active_function
.clone()
.expect("active function should be set to the generator");
let proto = this_function_object
.get(PROTOTYPE, context)
.expect("generator must have a prototype property")
.as_object()
.map_or_else(
|| {
if r#async {
context.intrinsics().objects().async_generator()
} else {
context.intrinsics().objects().generator()
}
},
Clone::clone,
);
let global_environement = context.vm.environments.global();
let environments = std::mem::replace(
&mut context.vm.environments,
EnvironmentStack::new(global_environement),
);
let stack = std::mem::take(&mut context.vm.stack);
let data = if r#async {
ObjectData::async_generator(AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart,
context: Some(GeneratorContext::new(
environments,
stack,
context.vm.active_function.clone(),
call_frame,
context.realm().clone(),
)),
queue: VecDeque::new(),
})
} else {
ObjectData::generator(crate::builtins::generator::Generator {
state: GeneratorState::SuspendedStart {
context: GeneratorContext::new(
environments,
stack,
context.vm.active_function.clone(),
call_frame,
context.realm().clone(),
),
},
})
};
let generator =
JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data);
if r#async {
let gen_clone = generator.clone();
let mut generator_mut = generator.borrow_mut();
let gen = generator_mut
.as_async_generator_mut()
.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
.call_frame
.as_mut()
.expect("should have a call frame initialized")
.async_generator = Some(gen_clone);
}
context.vm.push(generator);
Ok(CompletionType::Yield)
}
}
/// `AsyncGeneratorClose` implements the Opcode Operation for `Opcode::AsyncGeneratorClose`
///
/// Operation:
/// - Close an async generator function.
#[derive(Debug, Clone, Copy)]
pub(crate) struct AsyncGeneratorClose;
impl Operation for AsyncGeneratorClose {
const NAME: &'static str = "AsyncGeneratorClose";
const INSTRUCTION: &'static str = "INST - AsyncGeneratorClose";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart)
let generator_object = context
.vm
.frame()
.async_generator
.clone()
.expect("There should be a object");
let mut generator_object_mut = generator_object.borrow_mut();
let generator = generator_object_mut
.as_async_generator_mut()
.expect("must be async generator");
generator.state = AsyncGeneratorState::Completed;
generator.context = None;
let next = generator
.queue
.pop_front()
.expect("must have item in queue");
drop(generator_object_mut);
let return_value = std::mem::take(&mut context.vm.frame_mut().return_value);
if let Some(error) = context.vm.pending_exception.take() {
AsyncGenerator::complete_step(&next, Err(error), true, None, context);
} else {
AsyncGenerator::complete_step(&next, Ok(return_value), true, None, context);
}
AsyncGenerator::drain_queue(&generator_object, context);
Ok(CompletionType::Normal)
}
}
/// `GeneratorNext` implements the Opcode Operation for `Opcode::GeneratorNext`
///
/// Operation:

47
boa_engine/src/vm/opcode/mod.rs

@ -2,7 +2,7 @@
use crate::{vm::CompletionType, Context, JsResult};
// Operation modules
mod await_stm;
mod r#await;
mod binary_ops;
mod call;
mod concat;
@ -15,7 +15,6 @@ mod environment;
mod generator;
mod get;
mod iteration;
mod jump;
mod meta;
mod new;
mod nop;
@ -33,8 +32,6 @@ mod value;
// Operation structs
#[doc(inline)]
pub(crate) use await_stm::*;
#[doc(inline)]
pub(crate) use binary_ops::*;
#[doc(inline)]
pub(crate) use call::*;
@ -59,8 +56,6 @@ pub(crate) use get::*;
#[doc(inline)]
pub(crate) use iteration::*;
#[doc(inline)]
pub(crate) use jump::*;
#[doc(inline)]
pub(crate) use meta::*;
#[doc(inline)]
pub(crate) use new::*;
@ -71,6 +66,8 @@ pub(crate) use pop::*;
#[doc(inline)]
pub(crate) use push::*;
#[doc(inline)]
pub(crate) use r#await::*;
#[doc(inline)]
pub(crate) use require::*;
#[doc(inline)]
pub(crate) use rest_parameter::*;
@ -1333,6 +1330,20 @@ generate_impl! {
/// Stack: **=>**
Return,
/// Close an async generator function.
///
/// Operands:
///
/// Stack: **=>**
AsyncGeneratorClose,
/// Creates the generator object and yields.
///
/// Operands: async: `u8`
///
/// Stack: **=>** resume_kind
Generator,
/// Get return value of a function.
///
/// Operands:
@ -1561,6 +1572,22 @@ generate_impl! {
/// Stack: value **=>** received
AsyncGeneratorYield,
/// Create a promise capacity for an async function, if not already set.
///
/// Operands:
///
/// Stack: **=>**
CreatePromiseCapability,
/// Resolves or rejects the promise capability of an async function.
///
/// If the pending exception is set, reject and rethrow the exception, otherwise resolve.
///
/// Operands:
///
/// Stack: **=>**
CompletePromiseCapability,
/// Jumps to the specified address if the resume kind is not equal.
///
/// Operands: `exit`: `u32`, `resume_kind`: `u8`.
@ -1763,14 +1790,6 @@ generate_impl! {
Reserved58 => Reserved,
/// Reserved [`Opcode`].
Reserved59 => Reserved,
/// Reserved [`Opcode`].
Reserved60 => Reserved,
/// Reserved [`Opcode`].
Reserved61 => Reserved,
/// Reserved [`Opcode`].
Reserved62 => Reserved,
/// Reserved [`Opcode`].
Reserved63 => Reserved,
}
}

Loading…
Cancel
Save