Browse Source

Refactor environment, exception handling and jumping in VM (#3059)

* Refactor environment, exception handling, generators and jumping in VM

* Add helper for JumpIfNotResumeKind opcode

* Better handler display for CodeBlock

* Update documentaion

* Factor exception handling from throw opcodes

* Simplify try compilation

* Only push try statement jump controll info if needed

* Add helper functions checks in async and generator

* Run prettier

* Remove redundant check for end of bytecode.

We always emit a `Return` opcode at the end of a function, so this should never be reached.

* Fix doc link

* Implement stack_count calculation of handlers

* Fix typo

* Rename `LoopContinue` to `IncrementLoopIteration`

* Fix typo

* Remove #[allow(unused)] from Handler field
pull/3186/head
Haled Odat 10 months ago committed by GitHub
parent
commit
be055a30e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      boa_engine/src/builtins/eval/mod.rs
  2. 3
      boa_engine/src/builtins/generator/mod.rs
  3. 5
      boa_engine/src/builtins/json/mod.rs
  4. 15
      boa_engine/src/bytecompiler/class.rs
  5. 42
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  6. 1
      boa_engine/src/bytecompiler/declarations.rs
  7. 2
      boa_engine/src/bytecompiler/env.rs
  8. 25
      boa_engine/src/bytecompiler/expression/mod.rs
  9. 14
      boa_engine/src/bytecompiler/function.rs
  10. 368
      boa_engine/src/bytecompiler/jump_control.rs
  11. 83
      boa_engine/src/bytecompiler/mod.rs
  12. 85
      boa_engine/src/bytecompiler/statement/break.rs
  13. 135
      boa_engine/src/bytecompiler/statement/continue.rs
  14. 9
      boa_engine/src/bytecompiler/statement/labelled.rs
  15. 60
      boa_engine/src/bytecompiler/statement/loop.rs
  16. 37
      boa_engine/src/bytecompiler/statement/mod.rs
  17. 5
      boa_engine/src/bytecompiler/statement/switch.rs
  18. 129
      boa_engine/src/bytecompiler/statement/try.rs
  19. 49
      boa_engine/src/bytecompiler/utils.rs
  20. 5
      boa_engine/src/module/source.rs
  21. 7
      boa_engine/src/script.rs
  22. 89
      boa_engine/src/vm/call_frame/abrupt_record.rs
  23. 201
      boa_engine/src/vm/call_frame/env_stack.rs
  24. 80
      boa_engine/src/vm/call_frame/mod.rs
  25. 153
      boa_engine/src/vm/code_block.rs
  26. 240
      boa_engine/src/vm/flowgraph/mod.rs
  27. 94
      boa_engine/src/vm/mod.rs
  28. 3
      boa_engine/src/vm/opcode/await_stm/mod.rs
  29. 54
      boa_engine/src/vm/opcode/control_flow/break.rs
  30. 67
      boa_engine/src/vm/opcode/control_flow/continue.rs
  31. 148
      boa_engine/src/vm/opcode/control_flow/finally.rs
  32. 55
      boa_engine/src/vm/opcode/control_flow/labelled.rs
  33. 10
      boa_engine/src/vm/opcode/control_flow/mod.rs
  34. 36
      boa_engine/src/vm/opcode/control_flow/return.rs
  35. 215
      boa_engine/src/vm/opcode/control_flow/throw.rs
  36. 68
      boa_engine/src/vm/opcode/control_flow/try.rs
  37. 81
      boa_engine/src/vm/opcode/generator/mod.rs
  38. 7
      boa_engine/src/vm/opcode/generator/yield_stm.rs
  39. 68
      boa_engine/src/vm/opcode/iteration/iterator.rs
  40. 117
      boa_engine/src/vm/opcode/iteration/loop_ops.rs
  41. 39
      boa_engine/src/vm/opcode/jump/mod.rs
  42. 172
      boa_engine/src/vm/opcode/mod.rs
  43. 1
      boa_engine/src/vm/opcode/pop/mod.rs
  44. 2
      boa_engine/src/vm/opcode/push/environment.rs
  45. 29
      docs/boa_object.md
  46. 27
      docs/vm.md

5
boa_engine/src/builtins/eval/mod.rs

@ -253,7 +253,10 @@ impl Eval {
context.vm.environments.extend_outer_function_environment();
}
context.vm.push_frame(CallFrame::new(code_block));
let env_fp = context.vm.environments.len() as u32;
context
.vm
.push_frame(CallFrame::new(code_block).with_env_fp(env_fp));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();

3
boa_engine/src/builtins/generator/mod.rs

@ -108,10 +108,11 @@ impl GeneratorContext {
context
.vm
.push_frame(self.call_frame.take().expect("should have a call frame"));
context.vm.frame_mut().generator_resume_kind = resume_kind;
if let Some(value) = value {
context.vm.push(value);
}
context.vm.push(resume_kind);
let result = context.run();

5
boa_engine/src/builtins/json/mod.rs

@ -128,7 +128,10 @@ impl Json {
Gc::new(compiler.finish())
};
context.vm.push_frame(CallFrame::new(code_block));
let env_fp = context.vm.environments.len() as u32;
context
.vm
.push_frame(CallFrame::new(code_block).with_env_fp(env_fp));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();

15
boa_engine/src/bytecompiler/class.rs

@ -69,14 +69,12 @@ impl ByteCompiler<'_, '_> {
compiler.pop_compile_environment();
compiler.code_block_flags |= CodeBlockFlags::IS_CLASS_CONSTRUCTOR;
}
compiler.emit_opcode(Opcode::SetReturnValue);
if class.name().is_some() && class.has_binding_identifier() {
compiler.pop_compile_environment();
}
compiler.emit_opcode(Opcode::SetReturnValue);
compiler.emit_opcode(Opcode::Return);
let code = Gc::new(compiler.finish());
let index = self.functions.len() as u32;
self.functions.push(code);
@ -268,12 +266,11 @@ impl ByteCompiler<'_, '_> {
} else {
field_compiler.emit_opcode(Opcode::PushUndefined);
}
field_compiler.emit_opcode(Opcode::SetReturnValue);
field_compiler.pop_compile_environment();
field_compiler.pop_compile_environment();
field_compiler.emit_opcode(Opcode::SetReturnValue);
field_compiler.emit_opcode(Opcode::Return);
field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER;
let code = field_compiler.finish();
@ -306,7 +303,6 @@ impl ByteCompiler<'_, '_> {
field_compiler.pop_compile_environment();
field_compiler.emit_opcode(Opcode::SetReturnValue);
field_compiler.emit_opcode(Opcode::Return);
field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER;
@ -346,12 +342,11 @@ impl ByteCompiler<'_, '_> {
} else {
field_compiler.emit_opcode(Opcode::PushUndefined);
}
field_compiler.emit_opcode(Opcode::SetReturnValue);
field_compiler.pop_compile_environment();
field_compiler.pop_compile_environment();
field_compiler.emit_opcode(Opcode::SetReturnValue);
field_compiler.emit_opcode(Opcode::Return);
field_compiler.code_block_flags |= CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER;
let code = field_compiler.finish();

42
boa_engine/src/bytecompiler/declaration/declaration_pattern.rs

@ -180,11 +180,39 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::GetIterator);
// TODO: maybe, refactor this to be more condensed.
let handler_index = self.push_handler();
for element in pattern.bindings() {
self.compile_array_pattern_element(element, def);
}
self.emit_opcode(Opcode::PushFalse);
let exit = self.jump();
self.patch_handler(handler_index);
self.emit_opcode(Opcode::Exception);
self.emit_opcode(Opcode::PushTrue);
self.patch_jump(exit);
self.current_stack_value_count += 2;
let iterator_close_handler = self.push_handler();
self.iterator_close(false);
let exit = self.jump();
self.patch_handler(iterator_close_handler);
self.current_stack_value_count -= 2;
{
let jump = self.jump_if_false();
self.emit_opcode(Opcode::Throw);
self.patch_jump(jump);
}
self.emit_opcode(Opcode::ReThrow);
self.patch_jump(exit);
let jump = self.jump_if_false();
self.emit_opcode(Opcode::Throw);
self.patch_jump(jump);
}
}
}
@ -198,15 +226,15 @@ impl ByteCompiler<'_, '_> {
match element {
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorNextWithoutPop);
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorValue);
self.emit_opcode(Opcode::IteratorNextWithoutPop);
self.emit_opcode(Opcode::IteratorValueWithoutPop);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
@ -216,8 +244,8 @@ impl ByteCompiler<'_, '_> {
}
PropertyAccess { access } => {
self.access_set(Access::Property { access }, false, |compiler, _level| {
compiler.emit_opcode(Opcode::IteratorNext);
compiler.emit_opcode(Opcode::IteratorValue);
compiler.emit_opcode(Opcode::IteratorNextWithoutPop);
compiler.emit_opcode(Opcode::IteratorValueWithoutPop);
});
}
// BindingElement : BindingPattern Initializer[opt]
@ -225,8 +253,8 @@ impl ByteCompiler<'_, '_> {
pattern,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorValue);
self.emit_opcode(Opcode::IteratorNextWithoutPop);
self.emit_opcode(Opcode::IteratorValueWithoutPop);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);

1
boa_engine/src/bytecompiler/declarations.rs

@ -1015,6 +1015,7 @@ impl ByteCompiler<'_, '_> {
// 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::Pop);
}
// 27. If hasParameterExpressions is false, then

2
boa_engine/src/bytecompiler/env.rs

@ -7,6 +7,7 @@ use boa_ast::expression::Identifier;
impl ByteCompiler<'_, '_> {
/// Push either a new declarative or function environment on the compile time environment stack.
pub(crate) fn push_compile_environment(&mut self, function_scope: bool) {
self.current_open_environments_count += 1;
self.current_environment = Rc::new(CompileTimeEnvironment::new(
self.current_environment.clone(),
function_scope,
@ -16,6 +17,7 @@ impl ByteCompiler<'_, '_> {
/// Pops the top compile time environment and returns its index in the compile time environments array.
#[track_caller]
pub(crate) fn pop_compile_environment(&mut self) -> u32 {
self.current_open_environments_count -= 1;
let index = self.compile_environments.len() as u32;
self.compile_environments
.push(self.current_environment.clone());

25
boa_engine/src/bytecompiler/expression/mod.rs

@ -7,7 +7,7 @@ mod update;
use super::{Access, Callable, NodeKind};
use crate::{
bytecompiler::{ByteCompiler, Literal},
vm::Opcode,
vm::{GeneratorResumeKind, Opcode},
};
use boa_ast::{
expression::{
@ -153,31 +153,41 @@ impl ByteCompiler<'_, '_> {
}
}
Expression::Yield(r#yield) => {
// stack:
if let Some(expr) = r#yield.target() {
self.compile_expr(expr, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
// stack: value
if r#yield.delegate() {
if self.in_async_generator {
if self.in_async() {
self.emit_opcode(Opcode::GetAsyncIterator);
} else {
self.emit_opcode(Opcode::GetIterator);
}
// stack:
self.emit_opcode(Opcode::PushUndefined);
// stack: undefined
self.emit_push_integer(GeneratorResumeKind::Normal as i32);
// stack: resume_kind, undefined
let start_address = self.next_opcode_location();
let (throw_method_undefined, return_method_undefined) =
self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateNext);
if self.in_async_generator {
if self.in_async() {
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Await);
}
let (return_gen, exit) =
self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateResume);
if self.in_async_generator {
if self.in_async() {
self.emit_opcode(Opcode::IteratorValue);
self.async_generator_yield();
} else {
@ -188,16 +198,17 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(return_gen);
self.patch_jump(return_method_undefined);
if self.in_async_generator {
if self.in_async() {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::Pop);
}
self.close_active_iterators();
self.emit_opcode(Opcode::SetReturnValue);
self.emit_opcode(Opcode::GeneratorResumeReturn);
self.r#return();
self.patch_jump(throw_method_undefined);
self.iterator_close(self.in_async_generator);
self.iterator_close(self.in_async());
self.emit_opcode(Opcode::Throw);
self.patch_jump(exit);

14
boa_engine/src/bytecompiler/function.rs

@ -4,7 +4,7 @@ use crate::{
builtins::function::ThisMode,
bytecompiler::ByteCompiler,
environments::CompileTimeEnvironment,
vm::{CodeBlock, CodeBlockFlags, Opcode},
vm::{CodeBlock, CodeBlockFlags},
Context,
};
use boa_ast::function::{FormalParameterList, FunctionBody};
@ -99,7 +99,8 @@ impl FunctionCompiler {
let mut compiler = ByteCompiler::new(self.name, self.strict, false, outer_env, context);
compiler.length = length;
compiler.in_async_generator = self.generator && self.r#async;
compiler.in_async = self.r#async;
compiler.in_generator = self.generator;
if self.arrow {
compiler.this_mode = ThisMode::Lexical;
@ -151,15 +152,6 @@ impl FunctionCompiler {
compiler.params = parameters.clone();
if compiler
.bytecode
.last()
.filter(|last| **last == Opcode::Return as u8)
.is_none()
{
compiler.emit_opcode(Opcode::Return);
}
Gc::new(compiler.finish())
}
}

368
boa_engine/src/bytecompiler/jump_control.rs

@ -9,38 +9,157 @@
//! [try spec]: https://tc39.es/ecma262/#sec-try-statement
//! [labelled spec]: https://tc39.es/ecma262/#sec-labelled-statements
use crate::bytecompiler::{ByteCompiler, Label};
use crate::{
bytecompiler::{ByteCompiler, Label},
vm::{Handler, Opcode},
};
use bitflags::bitflags;
use boa_interner::Sym;
/// An actions to be performed for the local control flow.
#[derive(Debug, Clone, Copy)]
pub(crate) enum JumpRecordAction {
/// Places a [`Opcode::Jump`], transfers to a specified [`JumpControlInfo`] to be handled when it gets poped.
Transfer {
/// [`JumpControlInfo`] index to be transferred.
index: u32,
},
/// Places [`Opcode::PopEnvironment`] opcodes, `count` times.
PopEnvironments { count: u32 },
/// Closes the an iterator.
CloseIterator { r#async: bool },
/// Handles finally, this needs to be done if we are in the try or catch section of a try statement that
/// has a finally block.
///
/// It places push integer value [`Opcode`] as well as [`Opcode::PushFalse`], which means don't [`ReThrow`](Opcode::ReThrow).
///
/// The integer is an index used to jump. See [`Opcode::JumpTable`]. This is needed because the following code:
///
/// ```JavaScript
/// do {
/// try {
/// if (cond) {
/// continue;
/// }
///
/// break;
/// } finally {
/// // Must execute the finally, even if `continue` is executed or `break` is executed.
/// }
/// } while (true)
/// ```
///
/// Both `continue` and `break` must go through the finally, but the `continue` goes to the beginning of the loop,
/// and the `break` goes to the end of the loop, this is solved by having a jump table (See [`Opcode::JumpTable`])
/// at the end of finally (It is constructed in [`ByteCompiler::pop_try_with_finally_control_info()`]).
HandleFinally {
/// Jump table index.
index: u32,
},
}
/// Local Control flow type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum JumpRecordKind {
Break,
Continue,
Return,
}
/// This represents a local control flow handling. See [`JumpRecordKind`] for types.
#[derive(Debug, Clone)]
pub(crate) struct JumpRecord {
kind: JumpRecordKind,
label: Label,
actions: Vec<JumpRecordAction>,
}
impl JumpRecord {
pub(crate) const fn new(kind: JumpRecordKind, actions: Vec<JumpRecordAction>) -> Self {
Self {
kind,
label: ByteCompiler::DUMMY_LABEL,
actions,
}
}
/// Performs the [`JumpRecordAction`]s.
pub(crate) fn perform_actions(
mut self,
start_address: u32,
compiler: &mut ByteCompiler<'_, '_>,
) {
while let Some(action) = self.actions.pop() {
match action {
JumpRecordAction::Transfer { index } => {
self.label = compiler.jump();
compiler.jump_info[index as usize].jumps.push(self);
// Don't continue actions, let the delegate jump control info handle it!
return;
}
JumpRecordAction::PopEnvironments { count } => {
for _ in 0..count {
compiler.emit_opcode(Opcode::PopEnvironment);
}
}
JumpRecordAction::HandleFinally { index: value } => {
// Note: +1 because 0 is reserved for default entry in jump table (for fallthrough).
let index = value as i32 + 1;
compiler.emit_push_integer(index);
compiler.emit_opcode(Opcode::PushFalse);
}
JumpRecordAction::CloseIterator { r#async } => {
compiler.iterator_close(r#async);
}
}
}
// If there are no actions left, finalize the jump record.
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),
}
}
}
/// Boa's `ByteCompiler` jump information tracking struct.
#[derive(Debug, Clone)]
pub(crate) struct JumpControlInfo {
label: Option<Sym>,
start_address: u32,
flags: JumpControlInfoFlags,
set_jumps: Vec<Label>,
breaks: Vec<Label>,
try_continues: Vec<Label>,
pub(crate) flags: JumpControlInfoFlags,
pub(crate) jumps: Vec<JumpRecord>,
current_open_environments_count: u32,
}
bitflags! {
/// A bitflag that contains the type flags and relevant booleans for `JumpControlInfo`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct JumpControlInfoFlags: u16 {
pub(crate) struct JumpControlInfoFlags: u8 {
const LOOP = 0b0000_0001;
const SWITCH = 0b0000_0010;
const TRY_BLOCK = 0b0000_0100;
const LABELLED = 0b0000_1000;
const IN_FINALLY = 0b0001_0000;
const HAS_FINALLY = 0b0010_0000;
const ITERATOR_LOOP = 0b0100_0000;
const FOR_AWAIT_OF_LOOP = 0b1000_0000;
/// A try statement with a finally block.
///
/// We emit special instructions to handle [`JumpRecord`]s in [`ByteCompiler::pop_try_with_finally_control_info()`].
const TRY_WITH_FINALLY = 0b0000_0100;
/// Are we in the finally block of the try statement?
const IN_FINALLY = 0b0000_1000;
const LABELLED = 0b0001_0000;
const ITERATOR_LOOP = 0b0010_0000;
const FOR_AWAIT_OF_LOOP = 0b0100_0000;
/// Is the statement compiled with use_expr set to true.
///
/// This bitflag is inherited if the previous [`JumpControlInfo`].
const USE_EXPR = 0b0001_0000_0000;
const USE_EXPR = 0b1000_0000;
}
}
@ -50,21 +169,18 @@ impl Default for JumpControlInfoFlags {
}
}
impl Default for JumpControlInfo {
fn default() -> Self {
/// ---- `JumpControlInfo` Creation Methods ----
impl JumpControlInfo {
fn new(current_open_environments_count: u32) -> Self {
Self {
label: None,
start_address: u32::MAX,
start_address: ByteCompiler::DUMMY_ADDRESS,
flags: JumpControlInfoFlags::default(),
set_jumps: Vec::new(),
breaks: Vec::new(),
try_continues: Vec::new(),
jumps: Vec::new(),
current_open_environments_count,
}
}
}
/// ---- `JumpControlInfo` Creation Methods ----
impl JumpControlInfo {
pub(crate) const fn with_label(mut self, label: Option<Sym>) -> Self {
self.label = label;
self
@ -85,8 +201,9 @@ impl JumpControlInfo {
self
}
pub(crate) fn with_try_block_flag(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::TRY_BLOCK, value);
pub(crate) fn with_try_with_finally_flag(mut self, value: bool) -> Self {
self.flags
.set(JumpControlInfoFlags::TRY_WITH_FINALLY, value);
self
}
@ -95,11 +212,6 @@ impl JumpControlInfo {
self
}
pub(crate) fn with_has_finally(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::HAS_FINALLY, value);
self
}
pub(crate) fn with_iterator_loop(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::ITERATOR_LOOP, value);
self
@ -130,8 +242,8 @@ impl JumpControlInfo {
self.flags.contains(JumpControlInfoFlags::SWITCH)
}
pub(crate) const fn is_try_block(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::TRY_BLOCK)
pub(crate) const fn is_try_with_finally_block(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::TRY_WITH_FINALLY)
}
pub(crate) const fn is_labelled(&self) -> bool {
@ -142,10 +254,6 @@ impl JumpControlInfo {
self.flags.contains(JumpControlInfoFlags::IN_FINALLY)
}
pub(crate) const fn has_finally(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::HAS_FINALLY)
}
pub(crate) const fn use_expr(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::USE_EXPR)
}
@ -171,25 +279,6 @@ impl JumpControlInfo {
pub(crate) fn set_start_address(&mut self, start_address: u32) {
self.start_address = start_address;
}
/// Set the `in_finally` field of `JumpControlInfo`.
pub(crate) fn set_in_finally(&mut self, value: bool) {
self.flags.set(JumpControlInfoFlags::IN_FINALLY, value);
}
/// Pushes a `Label` onto the `break` vector of `JumpControlInfo`.
pub(crate) fn push_break_label(&mut self, break_label: Label) {
self.breaks.push(break_label);
}
/// Pushes a `Label` onto the `try_continues` vector of `JumpControlInfo`.
pub(crate) fn push_try_continue_label(&mut self, try_continue_label: Label) {
self.try_continues.push(try_continue_label);
}
pub(crate) fn push_set_jumps(&mut self, set_jump_label: Label) {
self.set_jumps.push(set_jump_label);
}
}
// `JumpControlInfo` related methods that are implemented on `ByteCompiler`.
@ -198,7 +287,8 @@ impl ByteCompiler<'_, '_> {
///
/// Default `JumpControlInfoKind` is `JumpControlInfoKind::Loop`
pub(crate) fn push_empty_loop_jump_control(&mut self, use_expr: bool) {
let new_info = JumpControlInfo::default().with_loop_flag(true);
let new_info =
JumpControlInfo::new(self.current_open_environments_count).with_loop_flag(true);
self.push_contol_info(new_info, use_expr);
}
@ -206,11 +296,6 @@ impl ByteCompiler<'_, '_> {
self.jump_info.last_mut()
}
pub(crate) fn set_jump_control_start_address(&mut self, start_address: u32) {
let info = self.jump_info.last_mut().expect("jump_info must exist");
info.set_start_address(start_address);
}
pub(crate) fn push_contol_info(&mut self, mut info: JumpControlInfo, use_expr: bool) {
info.flags.set(JumpControlInfoFlags::USE_EXPR, use_expr);
@ -222,6 +307,43 @@ impl ByteCompiler<'_, '_> {
self.jump_info.push(info);
}
/// Pushes an exception [`Handler`].
///
/// Must be patched with [`Self::patch_handler()`].
#[must_use]
pub(crate) fn push_handler(&mut self) -> u32 {
let handler_index = self.handlers.len() as u32;
let start_address = self.next_opcode_location();
// FIXME(HalidOdat): figure out value stack fp value.
let environment_count = self.current_open_environments_count;
self.handlers.push(Handler {
start: start_address,
end: Self::DUMMY_ADDRESS,
stack_count: self.current_stack_value_count,
environment_count,
});
handler_index
}
pub(crate) fn patch_handler(&mut self, handler_index: u32) {
let handler_index = handler_index as usize;
let handler_address = self.next_opcode_location();
assert_eq!(
self.handlers[handler_index].end,
Self::DUMMY_ADDRESS,
"handler already set"
);
assert!(
handler_address >= self.handlers[handler_index].start,
"handler end is before that start"
);
self.handlers[handler_index].end = handler_address;
}
/// Does the jump control info have the `use_expr` flag set to true.
///
/// See [`JumpControlInfoFlags`].
@ -242,7 +364,7 @@ impl ByteCompiler<'_, '_> {
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_labelled_block_flag(true)
.with_label(Some(label))
.with_start_address(start_address);
@ -260,13 +382,11 @@ impl ByteCompiler<'_, '_> {
let info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_labelled());
assert!(info.label().is_some());
for label in info.breaks {
self.patch_jump(label);
}
for label in info.try_continues {
self.patch_jump_with_target(label, info.start_address);
let start_address = info.start_address();
for jump_record in info.jumps {
jump_record.perform_actions(start_address, self);
}
}
// ---- `IterationStatement`'s `JumpControlInfo` methods ---- //
@ -278,7 +398,7 @@ impl ByteCompiler<'_, '_> {
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_loop_flag(true)
.with_label(label)
.with_start_address(start_address);
@ -293,7 +413,7 @@ impl ByteCompiler<'_, '_> {
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_loop_flag(true)
.with_label(label)
.with_start_address(start_address)
@ -308,7 +428,7 @@ impl ByteCompiler<'_, '_> {
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_loop_flag(true)
.with_label(label)
.with_start_address(start_address)
@ -330,12 +450,8 @@ impl ByteCompiler<'_, '_> {
assert!(info.is_loop());
let start_address = info.start_address();
for label in info.try_continues {
self.patch_jump_with_target(label, start_address);
}
for label in info.breaks {
self.patch_jump(label);
for jump_record in info.jumps {
jump_record.perform_actions(start_address, self);
}
}
@ -348,7 +464,7 @@ impl ByteCompiler<'_, '_> {
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_switch_flag(true)
.with_label(label)
.with_start_address(start_address);
@ -367,103 +483,59 @@ impl ByteCompiler<'_, '_> {
assert!(info.is_switch());
for label in info.breaks {
self.patch_jump(label);
let start_address = info.start_address();
for jump_record in info.jumps {
jump_record.perform_actions(start_address, self);
}
}
// ---- `TryStatement`'s `JumpControlInfo` methods ---- //
/// Pushes a `TryStatement`'s `JumpControlInfo` onto the `jump_info` stack.
pub(crate) fn push_try_control_info(
&mut self,
has_finally: bool,
start_address: u32,
use_expr: bool,
) {
let new_info = JumpControlInfo::default()
.with_try_block_flag(true)
.with_start_address(start_address)
.with_has_finally(has_finally);
pub(crate) fn push_try_with_finally_control_info(&mut self, use_expr: bool) {
let new_info = JumpControlInfo::new(self.current_open_environments_count)
.with_try_with_finally_flag(true);
self.push_contol_info(new_info, use_expr);
}
/// Pops and handles the info for a try block's `JumpControlInfo`
/// Pops and handles the info for a try statement with a finally block.
///
/// # Panic
/// - Will panic if `jump_info` is empty.
/// - Will panic if popped `JumpControlInfo` is not for a try block.
pub(crate) fn pop_try_control_info(&mut self, try_end: u32) {
pub(crate) fn pop_try_with_finally_control_info(&mut self, finally_start: u32) {
assert!(!self.jump_info.is_empty());
let mut info = self.jump_info.pop().expect("no jump information found");
let info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_try_block());
assert!(info.is_try_with_finally_block());
// Handle breaks. If there is a finally, breaks should go to the finally
if info.has_finally() {
for label in info.breaks {
self.patch_jump_with_target(label, try_end);
}
} else {
// When there is no finally, search for the break point.
for jump_info in self.jump_info.iter_mut().rev() {
if !jump_info.is_labelled() {
jump_info.breaks.append(&mut info.breaks);
break;
}
}
if info.jumps.is_empty() {
return;
}
// Handle set_jumps
for label in info.set_jumps {
for jump_info in self.jump_info.iter_mut().rev() {
if jump_info.is_loop() || jump_info.is_switch() {
jump_info.breaks.push(label);
break;
}
}
for JumpRecord { label, .. } in &info.jumps {
self.patch_jump_with_target(*label, finally_start);
}
// Pass continues down the stack.
if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.try_continues.append(&mut info.try_continues);
}
}
let (jumps, default) = self.jump_table(info.jumps.len() as u32);
/// Pushes a `TryStatement`'s Finally block `JumpControlInfo` onto the `jump_info` stack.
pub(crate) fn push_init_finally_control_info(&mut self, use_expr: bool) {
let mut new_info = JumpControlInfo::default().with_try_block_flag(true);
// Handle breaks/continue/returns in a finally block
for (i, label) in jumps.iter().enumerate() {
self.patch_jump(*label);
new_info.set_in_finally(true);
let jump_record = info.jumps[i].clone();
jump_record.perform_actions(label.index, self);
}
self.push_contol_info(new_info, use_expr);
self.patch_jump(default);
}
pub(crate) fn pop_finally_control_info(&mut self) {
assert!(!self.jump_info.is_empty());
let mut info = self.jump_info.pop().expect("no jump information found");
assert!(info.in_finally());
// Handle set_jumps
for label in info.set_jumps {
for jump_info in self.jump_info.iter_mut().rev() {
if jump_info.is_loop() || jump_info.is_switch() {
jump_info.breaks.push(label);
break;
}
}
pub(crate) fn jump_info_open_environment_count(&self, index: usize) -> u32 {
let current = &self.jump_info[index];
if let Some(next) = self.jump_info.get(index + 1) {
return next.current_open_environments_count - current.current_open_environments_count;
}
// Handle breaks in a finally block
for label in info.breaks {
self.patch_jump(label);
}
// Pass continues down the stack.
if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.try_continues.append(&mut info.try_continues);
}
self.current_open_environments_count - current.current_open_environments_count
}
}

83
boa_engine/src/bytecompiler/mod.rs

@ -17,7 +17,7 @@ use crate::{
builtins::function::ThisMode,
environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment},
js_string,
vm::{BindingOpcode, CodeBlock, CodeBlockFlags, Opcode},
vm::{BindingOpcode, CodeBlock, CodeBlockFlags, GeneratorResumeKind, Handler, Opcode},
Context, JsBigInt, JsString, JsValue,
};
use boa_ast::{
@ -41,6 +41,7 @@ use rustc_hash::FxHashMap;
pub(crate) use function::FunctionCompiler;
pub(crate) use jump_control::JumpControlInfo;
use thin_vec::ThinVec;
/// Describes how a node has been defined in the source code.
#[derive(Debug, Clone, Copy, PartialEq)]
@ -246,13 +247,16 @@ pub struct ByteCompiler<'ctx, 'host> {
/// The environment that is currently active.
pub(crate) current_environment: Rc<CompileTimeEnvironment>,
pub(crate) code_block_flags: CodeBlockFlags,
current_open_environments_count: u32,
current_stack_value_count: u32,
code_block_flags: CodeBlockFlags,
handlers: ThinVec<Handler>,
literals_map: FxHashMap<Literal, u32>,
names_map: FxHashMap<Identifier, u32>,
bindings_map: FxHashMap<BindingLocator, u32>,
jump_info: Vec<JumpControlInfo>,
in_async_generator: bool,
in_async: bool,
in_generator: bool,
json_parse: bool,
// TODO: remove when we separate scripts from the context
@ -290,13 +294,17 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
this_mode: ThisMode::Global,
params: FormalParameterList::default(),
compile_environments: Vec::default(),
current_open_environments_count: 0,
current_stack_value_count: 0,
code_block_flags,
handlers: ThinVec::default(),
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
in_async_generator: false,
in_async: false,
in_generator: false,
json_parse,
current_environment,
context,
@ -310,6 +318,18 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.code_block_flags.contains(CodeBlockFlags::STRICT)
}
pub(crate) const fn in_async(&self) -> bool {
self.in_async
}
pub(crate) const fn in_generator(&self) -> bool {
self.in_generator
}
pub(crate) const fn in_async_generator(&self) -> bool {
self.in_async() && self.in_generator()
}
pub(crate) fn interner(&self) -> &Interner {
self.context.interner()
}
@ -503,6 +523,30 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined)
}
fn jump_if_not_resume_kind(&mut self, resume_kind: GeneratorResumeKind) -> Label {
let label = self.emit_opcode_with_operand(Opcode::JumpIfNotResumeKind);
self.emit_u8(resume_kind as u8);
label
}
/// Push a jump table with `count` of entries.
///
/// Returns the jump label entries and the default label.
fn jump_table(&mut self, count: u32) -> (Vec<Label>, Label) {
let index = self.next_opcode_location();
self.emit(Opcode::JumpTable, &[count, Self::DUMMY_ADDRESS]);
let default = Label { index: index + 4 };
let mut labels = Vec::with_capacity(count as usize);
for i in 0..count {
labels.push(Label {
index: index + 8 + 4 * i,
});
self.emit_u32(Self::DUMMY_ADDRESS);
}
(labels, default)
}
/// Emit an opcode with a dummy operand.
/// Return the `Label` of the operand.
pub(crate) fn emit_opcode_with_operand(&mut self, opcode: Opcode) -> Label {
@ -519,28 +563,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
(Label { index }, Label { index: index + 4 })
}
/// Emit an opcode with three dummy operands.
/// Return the `Label`s of the three operands.
pub(crate) fn emit_opcode_with_three_operands(
&mut self,
opcode: Opcode,
) -> (Label, Label, Label) {
let index = self.next_opcode_location();
self.emit(
opcode,
&[
Self::DUMMY_ADDRESS,
Self::DUMMY_ADDRESS,
Self::DUMMY_ADDRESS,
],
);
(
Label { index },
Label { index: index + 4 },
Label { index: index + 8 },
)
}
pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) {
const U32_SIZE: usize = std::mem::size_of::<u32>();
@ -1386,13 +1408,17 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
#[inline]
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn finish(self) -> CodeBlock {
pub fn finish(mut self) -> CodeBlock {
// Push return at the end of the function compilation.
self.emit_opcode(Opcode::Return);
let name = self
.context
.interner()
.resolve_expect(self.function_name)
.utf16()
.into();
CodeBlock {
name,
length: self.length,
@ -1404,6 +1430,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
bindings: self.bindings.into_boxed_slice(),
functions: self.functions.into_boxed_slice(),
compile_environments: self.compile_environments.into_boxed_slice(),
handlers: self.handlers,
flags: Cell::new(self.code_block_flags),
}
}

85
boa_engine/src/bytecompiler/statement/break.rs

@ -1,73 +1,42 @@
use crate::{
bytecompiler::{ByteCompiler, JumpControlInfo},
vm::Opcode,
use crate::bytecompiler::{
jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind},
ByteCompiler,
};
use boa_ast::statement::Break;
use boa_interner::Sym;
impl ByteCompiler<'_, '_> {
/// Compile a [`Break`] `boa_ast` node
pub(crate) fn compile_break(&mut self, node: Break, _use_expr: bool) {
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) {
let has_finally_or_is_finally = info.has_finally() || info.in_finally();
let actions = self.break_jump_record_actions(node);
let (break_label, target_jump_label) =
self.emit_opcode_with_two_operands(Opcode::Break);
if let Some(node_label) = node.label() {
let info = self.jump_info_label(node_label);
info.push_break_label(target_jump_label);
if !has_finally_or_is_finally {
info.push_break_label(break_label);
return;
}
} else {
self.jump_info
.last_mut()
.expect("jump_info must exist to reach this point")
.push_set_jumps(target_jump_label);
}
let info = self
.jump_info
.last_mut()
.expect("This try block must exist");
info.push_break_label(break_label);
return;
}
// Emit the break opcode -> (Label, Label)
let (break_label, target_label) = self.emit_opcode_with_two_operands(Opcode::Break);
if let Some(label) = node.label() {
let info = self.jump_info_label(label);
info.push_break_label(break_label);
info.push_break_label(target_label);
return;
}
let info = self.jump_info_non_labelled();
info.push_break_label(break_label);
info.push_break_label(target_label);
JumpRecord::new(JumpRecordKind::Break, actions).perform_actions(Self::DUMMY_ADDRESS, self);
}
fn jump_info_non_labelled(&mut self) -> &mut JumpControlInfo {
for info in self.jump_info.iter_mut().rev() {
if !info.is_labelled() {
return info;
fn break_jump_record_actions(&self, node: Break) -> Vec<JumpRecordAction> {
let mut actions = Vec::default();
for (i, info) in self.jump_info.iter().enumerate().rev() {
let count = self.jump_info_open_environment_count(i);
actions.push(JumpRecordAction::PopEnvironments { count });
if info.is_try_with_finally_block() && !info.in_finally() {
actions.push(JumpRecordAction::HandleFinally {
index: info.jumps.len() as u32,
});
actions.push(JumpRecordAction::Transfer { index: i as u32 });
}
}
panic!("Jump info without label must exist");
}
fn jump_info_label(&mut self, label: Sym) -> &mut JumpControlInfo {
for info in self.jump_info.iter_mut().rev() {
if info.label() == Some(label) {
return info;
if let Some(label) = node.label() {
if info.label() == Some(label) {
actions.push(JumpRecordAction::Transfer { index: i as u32 });
break;
}
} else if info.is_loop() || info.is_switch() {
actions.push(JumpRecordAction::Transfer { index: i as u32 });
break;
}
}
panic!("Jump info with label must exist");
actions.reverse();
actions
}
}

135
boa_engine/src/bytecompiler/statement/continue.rs

@ -1,118 +1,49 @@
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use crate::bytecompiler::{
jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind},
ByteCompiler,
};
use boa_ast::statement::Continue;
impl ByteCompiler<'_, '_> {
#[allow(clippy::unnecessary_wraps)]
pub(crate) fn compile_continue(&mut self, node: Continue, _use_expr: bool) {
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) {
let in_finally = info.in_finally();
let in_finally_or_has_finally = in_finally || info.has_finally();
let actions = self.continue_jump_record_actions(node);
// 1. Handle if node has a label.
if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop());
let mut iterator_closes = Vec::new();
for info in items {
if info.label() == Some(node_label) {
break;
}
if info.iterator_loop() {
iterator_closes.push(info.for_await_of_loop());
}
}
for r#async in iterator_closes {
self.iterator_close(r#async);
}
JumpRecord::new(JumpRecordKind::Continue, actions)
.perform_actions(Self::DUMMY_ADDRESS, self);
}
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
fn continue_jump_record_actions(&self, node: Continue) -> Vec<JumpRecordAction> {
let mut actions = Vec::default();
for (i, info) in self.jump_info.iter().enumerate().rev() {
let count = self.jump_info_open_environment_count(i);
actions.push(JumpRecordAction::PopEnvironments { count });
if info.is_try_with_finally_block() && !info.in_finally() {
actions.push(JumpRecordAction::HandleFinally {
index: info.jumps.len() as u32,
});
actions.push(JumpRecordAction::Transfer { index: i as u32 });
}
let loops = self
.jump_info
.iter_mut()
.rev()
.filter(|info| info.is_loop());
let mut set_continue_as_break = false;
for info in loops {
let found_label = info.label() == Some(node_label);
if found_label && in_finally_or_has_finally {
set_continue_as_break = true;
info.push_try_continue_label(set_label);
break;
} else if found_label && !in_finally_or_has_finally {
info.push_try_continue_label(cont_label);
info.push_try_continue_label(set_label);
break;
}
}
if set_continue_as_break {
self.jump_info
.last_mut()
.expect("no jump information found")
.push_break_label(cont_label);
}
} else {
// TODO: Add has finally or in finally here
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
if in_finally_or_has_finally {
self.jump_info
.last_mut()
.expect("Must exist and be a try block")
.push_break_label(cont_label);
};
let mut items = self
.jump_info
.iter_mut()
.rev()
.filter(|info| info.is_loop());
let jump_info = items.next().expect("continue must be inside loop");
if !in_finally_or_has_finally {
jump_info.push_try_continue_label(cont_label);
};
jump_info.push_try_continue_label(set_label);
};
} else if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop());
let mut iterator_closes = Vec::new();
for info in items {
if info.label() == Some(node_label) {
if let Some(label) = node.label() {
if info.label() == Some(label) {
actions.push(JumpRecordAction::Transfer { index: i as u32 });
break;
}
if info.iterator_loop() {
iterator_closes.push(info.for_await_of_loop());
}
}
for r#async in iterator_closes {
self.iterator_close(r#async);
} else if info.is_loop() {
actions.push(JumpRecordAction::Transfer { index: i as u32 });
break;
}
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
let loops = self
.jump_info
.iter_mut()
.rev()
.filter(|info| info.is_loop());
for info in loops {
if info.label() == Some(node_label) {
info.push_try_continue_label(cont_label);
info.push_try_continue_label(set_label);
}
if info.iterator_loop() {
actions.push(JumpRecordAction::CloseIterator {
r#async: info.for_await_of_loop(),
});
}
} else {
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
let mut items = self
.jump_info
.iter_mut()
.rev()
.filter(|info| info.is_loop());
let jump_info = items.next().expect("continue must be inside loop");
jump_info.push_try_continue_label(cont_label);
jump_info.push_try_continue_label(set_label);
}
actions.reverse();
actions
}
}

9
boa_engine/src/bytecompiler/statement/labelled.rs

@ -1,7 +1,4 @@
use crate::{
bytecompiler::{ByteCompiler, NodeKind},
vm::Opcode,
};
use crate::bytecompiler::{ByteCompiler, NodeKind};
use boa_ast::{
statement::{Labelled, LabelledItem},
Statement,
@ -11,7 +8,6 @@ impl ByteCompiler<'_, '_> {
/// Compile a [`Labelled`] `boa_ast` node
pub(crate) fn compile_labelled(&mut self, labelled: &Labelled, use_expr: bool) {
let labelled_loc = self.next_opcode_location();
let end_label = self.emit_opcode_with_operand(Opcode::LabelledStart);
self.push_labelled_control_info(labelled.label(), labelled_loc, use_expr);
match labelled.item() {
@ -38,9 +34,6 @@ impl ByteCompiler<'_, '_> {
}
}
let labelled_end = self.next_opcode_location();
self.patch_jump_with_target(end_label, labelled_end);
self.pop_labelled_control_info();
self.emit_opcode(Opcode::LabelledEnd);
}
}

60
boa_engine/src/bytecompiler/statement/loop.rs

@ -57,7 +57,6 @@ impl ByteCompiler<'_, '_> {
}
self.push_empty_loop_jump_control(use_expr);
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_jump = self.jump();
let start_address = self.next_opcode_location();
@ -80,8 +79,7 @@ impl ByteCompiler<'_, '_> {
}
}
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::IncrementLoopIteration);
if let Some(final_expr) = for_loop.final_expr() {
self.compile_expr(final_expr, false);
@ -101,9 +99,10 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
if env_labels.is_some() {
self.emit_opcode(Opcode::PopEnvironment);
}
if let Some(env_labels) = env_labels {
let env_index = self.pop_compile_environment();
@ -111,7 +110,6 @@ impl ByteCompiler<'_, '_> {
if let Some(iteration_env_labels) = iteration_env_labels {
self.patch_jump_with_target(iteration_env_labels, env_index);
}
self.emit_opcode(Opcode::PopEnvironment);
}
}
@ -154,12 +152,9 @@ impl ByteCompiler<'_, '_> {
let early_exit = self.jump_if_null_or_undefined();
self.emit_opcode(Opcode::CreateForInIterator);
let (loop_start, exit_label) =
self.emit_opcode_with_two_operands(Opcode::IteratorLoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address, use_expr);
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::IncrementLoopIteration);
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorDone);
@ -233,9 +228,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.patch_jump(exit_label);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.iterator_close(false);
@ -278,15 +271,13 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::GetIterator);
}
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::IteratorLoopStart);
let start_address = self.next_opcode_location();
if for_of_loop.r#await() {
self.push_loop_control_info_for_await_of_loop(label, start_address, use_expr);
} else {
self.push_loop_control_info_for_of_in_loop(label, start_address, use_expr);
}
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::IncrementLoopIteration);
self.emit_opcode(Opcode::IteratorNext);
if for_of_loop.r#await() {
@ -306,6 +297,7 @@ impl ByteCompiler<'_, '_> {
Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment))
};
let mut handler_index = None;
match for_of_loop.initializer() {
IterableLoopInitializer::Identifier(ref ident) => {
match self.set_mutable_binding(*ident) {
@ -323,6 +315,7 @@ impl ByteCompiler<'_, '_> {
}
}
IterableLoopInitializer::Access(access) => {
handler_index = Some(self.push_handler());
self.access_set(
Access::Property { access },
false,
@ -366,10 +359,33 @@ impl ByteCompiler<'_, '_> {
}
},
IterableLoopInitializer::Pattern(pattern) => {
handler_index = Some(self.push_handler());
self.compile_declaration_pattern(pattern, BindingOpcode::SetName);
}
}
// If the left-hand side is not a lexical binding and the assignment produces
// an error, the iterator should be closed and the error forwarded to the
// runtime.
if let Some(handler_index) = handler_index {
let exit = self.jump();
self.patch_handler(handler_index);
self.emit_opcode(Opcode::Exception);
self.current_stack_value_count += 1;
// NOTE: Capture throw of the iterator close and ignore it.
{
let handler_index = self.push_handler();
self.iterator_close(for_of_loop.r#await());
self.patch_handler(handler_index);
}
self.current_stack_value_count -= 1;
self.emit_opcode(Opcode::Throw);
self.patch_jump(exit);
}
self.compile_stmt(for_of_loop.body(), use_expr, true);
if let Some(iteration_environment) = iteration_environment {
@ -381,9 +397,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.iterator_close(for_of_loop.r#await());
}
@ -394,11 +408,9 @@ impl ByteCompiler<'_, '_> {
label: Option<Sym>,
use_expr: bool,
) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.emit_opcode(Opcode::LoopContinue);
self.emit_opcode(Opcode::IncrementLoopIteration);
self.push_loop_control_info(label, start_address, use_expr);
self.patch_jump_with_target(loop_start, start_address);
self.compile_expr(while_loop.condition(), true);
let exit = self.jump_if_false();
@ -408,9 +420,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
}
pub(crate) fn compile_do_while_loop(
@ -419,16 +429,14 @@ impl ByteCompiler<'_, '_> {
label: Option<Sym>,
use_expr: bool,
) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_label = self.jump();
let start_address = self.next_opcode_location();
self.patch_jump_with_target(loop_start, start_address);
self.push_loop_control_info(label, start_address, use_expr);
let condition_label_address = self.next_opcode_location();
self.emit_opcode(Opcode::LoopContinue);
self.emit_opcode(Opcode::IncrementLoopIteration);
self.compile_expr(do_while_loop.cond(), true);
let exit = self.jump_if_false();
@ -438,9 +446,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[condition_label_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
}
}

37
boa_engine/src/bytecompiler/statement/mod.rs

@ -2,6 +2,8 @@ use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::Statement;
use super::jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind};
mod block;
mod r#break;
mod r#continue;
@ -63,7 +65,7 @@ impl ByteCompiler<'_, '_> {
Statement::Return(ret) => {
if let Some(expr) = ret.target() {
self.compile_expr(expr, true);
if self.in_async_generator {
if self.in_async_generator() {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
}
@ -71,7 +73,8 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::SetReturnValue);
self.emit_opcode(Opcode::Return);
self.r#return();
}
Statement::Try(t) => self.compile_try(t, use_expr),
Statement::Expression(expr) => {
@ -84,4 +87,34 @@ impl ByteCompiler<'_, '_> {
Statement::Empty => {}
}
}
pub(crate) fn r#return(&mut self) {
let actions = self.return_jump_record_actions();
JumpRecord::new(JumpRecordKind::Return, actions).perform_actions(Self::DUMMY_ADDRESS, self);
}
fn return_jump_record_actions(&self) -> Vec<JumpRecordAction> {
let mut actions = Vec::default();
for (i, info) in self.jump_info.iter().enumerate().rev() {
let count = self.jump_info_open_environment_count(i);
actions.push(JumpRecordAction::PopEnvironments { count });
if info.is_try_with_finally_block() && !info.in_finally() {
actions.push(JumpRecordAction::HandleFinally {
index: info.jumps.len() as u32,
});
actions.push(JumpRecordAction::Transfer { index: i as u32 });
}
if info.iterator_loop() {
actions.push(JumpRecordAction::CloseIterator {
r#async: info.for_await_of_loop(),
});
}
}
actions.reverse();
actions
}
}

5
boa_engine/src/bytecompiler/statement/switch.rs

@ -11,11 +11,8 @@ impl ByteCompiler<'_, '_> {
self.block_declaration_instantiation(switch);
let (start_label, end_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_switch_control_info(None, start_address, use_expr);
self.patch_jump_with_target(start_label, start_address);
let mut labels = Vec::with_capacity(switch.cases().len());
for case in switch.cases() {
@ -52,8 +49,6 @@ impl ByteCompiler<'_, '_> {
}
self.pop_switch_control_info();
self.patch_jump(end_label);
self.emit_opcode(Opcode::LoopEnd);
let env_index = self.pop_compile_environment();
self.patch_jump_with_target(push_env, env_index);

129
boa_engine/src/bytecompiler/statement/try.rs

@ -1,5 +1,5 @@
use crate::{
bytecompiler::{ByteCompiler, Label},
bytecompiler::{jump_control::JumpControlInfoFlags, ByteCompiler},
vm::{BindingOpcode, Opcode},
};
use boa_ast::{
@ -9,47 +9,107 @@ use boa_ast::{
};
impl ByteCompiler<'_, '_> {
/// Compile try statement.
pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool) {
let try_start = self.next_opcode_location();
let (catch_start, finally_loc) = self.emit_opcode_with_two_operands(Opcode::TryStart);
self.patch_jump_with_target(finally_loc, u32::MAX);
let has_catch = t.catch().is_some();
let has_finally = t.finally().is_some();
// If there is a finally block, initialize the finally control block prior to pushing the try block jump_control
if t.finally().is_some() {
self.push_init_finally_control_info(use_expr);
// stack:
if has_finally {
self.push_try_with_finally_control_info(use_expr);
}
self.push_try_control_info(t.finally().is_some(), try_start, use_expr);
let try_handler = self.push_handler();
// Compile try block
self.compile_block(t.block(), use_expr);
self.emit_opcode(Opcode::TryEnd);
if has_finally {
self.emit_opcode(Opcode::PushZero);
self.emit_opcode(Opcode::PushFalse);
// stack: false, 0
}
let finally = self.jump();
self.patch_jump(catch_start);
self.patch_handler(try_handler);
// If it has a finally but no catch and we are in a generator, then we still need it
// to handle `return()` call on generators.
let catch_handler = if has_finally && (self.in_generator() || has_catch) {
self.current_stack_value_count += 2;
Some(self.push_handler())
} else {
None
};
self.emit_opcode(Opcode::Exception);
if let Some(catch) = t.catch() {
self.compile_catch_stmt(catch, t.finally().is_some(), use_expr);
self.compile_catch_stmt(catch, has_finally, use_expr);
} else {
// Note: implicit !has_catch
if self.in_generator() && has_finally {
// Is this a generator `return()` empty exception?
//
// This is false because when the `Exception` opcode is executed,
// it rethrows the empty exception, so if we reached this section,
// that means it's not an `return()` generator exception.
self.emit_opcode(Opcode::PushFalse);
}
// Should we rethrow the exception?
self.emit_opcode(Opcode::PushTrue);
}
if has_finally {
if has_catch {
self.emit_opcode(Opcode::PushZero);
self.emit_opcode(Opcode::PushFalse);
}
let exit = self.jump();
if let Some(catch_handler) = catch_handler {
self.current_stack_value_count -= 2;
self.patch_handler(catch_handler);
}
// Note: implicit has_finally
if !has_catch && self.in_generator() {
// Is this a generator `return()` empty exception?
self.emit_opcode(Opcode::PushTrue);
}
// Should we rethrow the exception?
self.emit_opcode(Opcode::PushTrue);
self.patch_jump(exit);
}
self.patch_jump(finally);
let finally_start = self.next_opcode_location();
if let Some(finally) = t.finally() {
// Pop and push control loops post FinallyStart, as FinallyStart resets flow control variables.
// Handle finally header operations
let finally_start = self.next_opcode_location();
let finally_end = self.emit_opcode_with_operand(Opcode::FinallyStart);
self.pop_try_control_info(finally_start);
self.set_jump_control_start_address(finally_start);
self.patch_jump_with_target(finally_loc, finally_start);
self.jump_info
.last_mut()
.expect("there should be a try block")
.flags |= JumpControlInfoFlags::IN_FINALLY;
self.current_stack_value_count += 2;
// Compile finally statement body
self.compile_finally_stmt(finally, finally_end);
} else {
let try_end = self.next_opcode_location();
self.pop_try_control_info(try_end);
self.compile_finally_stmt(finally, has_catch);
self.current_stack_value_count -= 2;
}
if has_finally {
self.pop_try_with_finally_control_info(finally_start);
}
}
pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _finally: bool, use_expr: bool) {
pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) {
// stack: exception
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment);
@ -77,14 +137,29 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PopEnvironment);
}
pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, finally_end_label: Label) {
pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, has_catch: bool) {
// TODO: We could probably remove the Get/SetReturnValue if we check that there is no break/continues statements.
self.current_stack_value_count += 1;
self.emit_opcode(Opcode::GetReturnValue);
self.compile_block(finally.block(), true);
self.emit_opcode(Opcode::SetReturnValue);
self.current_stack_value_count -= 1;
// Rethrow error if error happend!
let do_not_throw_exit = self.jump_if_false();
if has_catch {
self.emit_opcode(Opcode::ReThrow);
} else if self.in_generator() {
let is_generator_exit = self.jump_if_true();
self.emit_opcode(Opcode::Throw);
self.patch_jump(is_generator_exit);
self.emit_opcode(Opcode::ReThrow);
} else {
self.emit_opcode(Opcode::Throw);
}
self.pop_finally_control_info();
self.patch_jump(finally_end_label);
self.emit_opcode(Opcode::FinallyEnd);
self.patch_jump(do_not_throw_exit);
}
}

49
boa_engine/src/bytecompiler/utils.rs

@ -1,4 +1,7 @@
use crate::{js_string, vm::Opcode};
use crate::{
js_string,
vm::{GeneratorResumeKind, Opcode},
};
use super::{ByteCompiler, Literal};
@ -47,7 +50,7 @@ impl ByteCompiler<'_, '_> {
let start = self.next_opcode_location();
self.emit_opcode(Opcode::IteratorStackEmpty);
let empty = self.jump_if_true();
self.iterator_close(self.in_async_generator);
self.iterator_close(self.in_async_generator());
self.emit(Opcode::Jump, &[start]);
self.patch_jump(empty);
}
@ -62,7 +65,7 @@ impl ByteCompiler<'_, '_> {
/// [yield]: https://tc39.es/ecma262/#sec-yield
pub(super) fn r#yield(&mut self) {
// 1. Let generatorKind be GetGeneratorKind().
if self.in_async_generator {
if self.in_async() {
// 2. If generatorKind is async, return ? AsyncGeneratorYield(? Await(value)).
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
@ -73,6 +76,7 @@ impl ByteCompiler<'_, '_> {
self.emit_u8(u8::from(false));
self.emit_opcode(Opcode::GeneratorYield);
}
self.emit_opcode(Opcode::GeneratorNext);
}
@ -85,22 +89,33 @@ impl ByteCompiler<'_, '_> {
///
/// [async_yield]: https://tc39.es/ecma262/#sec-asyncgeneratoryield
pub(super) fn async_generator_yield(&mut self) {
// Stack: value
self.emit_opcode(Opcode::AsyncGeneratorYield);
let (normal, throw, r#return) =
self.emit_opcode_with_three_operands(Opcode::GeneratorJumpOnResumeKind);
{
self.patch_jump(r#return);
self.emit_opcode(Opcode::Await);
let (normal, throw, r#return) =
self.emit_opcode_with_three_operands(Opcode::GeneratorJumpOnResumeKind);
self.patch_jump(normal);
self.emit_opcode(Opcode::GeneratorSetReturn);
// Stack: resume_kind, received
let non_return_resume = self.jump_if_not_resume_kind(GeneratorResumeKind::Return);
self.patch_jump(throw);
self.patch_jump(r#return);
}
self.patch_jump(normal);
self.patch_jump(throw);
// Stack: resume_kind(Return), received
self.emit_opcode(Opcode::Pop);
// Stack: received
self.emit_opcode(Opcode::Await);
// Stack: resume_kind, received
let non_normal_resume = self.jump_if_not_resume_kind(GeneratorResumeKind::Normal);
// Stack: resume_kind(Normal), received
self.emit_opcode(Opcode::Pop);
// Stack: received
self.emit_push_integer(GeneratorResumeKind::Return as i32);
// Stack: resume_kind(Return) received
self.patch_jump(non_normal_resume);
// Stack: resume_kind, received
self.patch_jump(non_return_resume);
// Stack: resume_kind, received
}
}

5
boa_engine/src/module/source.rs

@ -1729,7 +1729,8 @@ impl SourceTextModule {
_ => unreachable!("`execute` should only be called for evaluating modules."),
};
let mut callframe = CallFrame::new(codeblock);
let env_fp = environments.len() as u32;
let mut callframe = CallFrame::new(codeblock).with_env_fp(env_fp);
callframe.promise_capability = capability;
// 4. Set the ScriptOrModule of moduleContext to module.
@ -1742,7 +1743,6 @@ impl SourceTextModule {
// 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
std::mem::swap(&mut context.vm.environments, &mut environments);
let stack = std::mem::take(&mut context.vm.stack);
// 2. Set the Function of moduleContext to null.
let function = context.vm.active_function.take();
// 3. Set the Realm of moduleContext to module.[[Realm]].
@ -1762,7 +1762,6 @@ impl SourceTextModule {
let result = context.run();
std::mem::swap(&mut context.vm.environments, &mut environments);
context.vm.stack = stack;
context.vm.active_function = function;
context.vm.active_runnable = active_runnable;
context.swap_realm(&mut realm);

7
boa_engine/src/script.rs

@ -139,12 +139,14 @@ impl Script {
let old_realm = context.enter_realm(self.inner.realm.clone());
let active_function = context.vm.active_function.take();
let stack = std::mem::take(&mut context.vm.stack);
let old_active = context
.vm
.active_runnable
.replace(ActiveRunnable::Script(self.clone()));
context.vm.push_frame(CallFrame::new(codeblock));
let env_fp = context.vm.environments.len() as u32;
context
.vm
.push_frame(CallFrame::new(codeblock).with_env_fp(env_fp));
// TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
@ -152,7 +154,6 @@ impl Script {
let record = context.run();
context.vm.pop_frame();
context.vm.stack = stack;
context.vm.active_function = active_function;
context.vm.active_runnable = old_active;
context.enter_realm(old_realm);

89
boa_engine/src/vm/call_frame/abrupt_record.rs

@ -1,89 +0,0 @@
//! Implements an `AbruptCompletionRecord` struct for `CallFrame` tracking.
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum AbruptKind {
Continue,
Break,
Throw,
Return,
}
/// The `AbruptCompletionRecord` tracks the current `AbruptCompletion` and target address of completion.
#[derive(Clone, Copy, Debug)]
pub(crate) struct AbruptCompletionRecord {
kind: AbruptKind,
target: u32,
}
/// ---- `AbruptCompletionRecord` initialization methods ----
impl AbruptCompletionRecord {
/// Creates an `AbruptCompletionRecord` for an abrupt `Break`.
pub(crate) const fn new_break() -> Self {
Self {
kind: AbruptKind::Break,
target: u32::MAX,
}
}
/// Creates an `AbruptCompletionRecord` for an abrupt `Continue`.
pub(crate) const fn new_continue() -> Self {
Self {
kind: AbruptKind::Continue,
target: u32::MAX,
}
}
/// Creates an `AbruptCompletionRecord` for an abrupt `Throw`.
pub(crate) const fn new_throw() -> Self {
Self {
kind: AbruptKind::Throw,
target: u32::MAX,
}
}
/// Creates an `AbruptCompletionRecord` for an abrupt `Return`.
pub(crate) const fn new_return() -> Self {
Self {
kind: AbruptKind::Return,
target: u32::MAX,
}
}
/// Set the target field of the `AbruptCompletionRecord`.
pub(crate) const fn with_initial_target(mut self, target: u32) -> Self {
self.target = target;
self
}
}
/// ---- `AbruptCompletionRecord` interaction methods ----
impl AbruptCompletionRecord {
/// Returns a boolean value for whether `AbruptCompletionRecord` is a break.
pub(crate) fn is_break(self) -> bool {
self.kind == AbruptKind::Break
}
/// Returns a boolean value for whether `AbruptCompletionRecord` is a continue.
pub(crate) fn is_continue(self) -> bool {
self.kind == AbruptKind::Continue
}
/// Returns a boolean value for whether `AbruptCompletionRecord` is a return.
pub(crate) fn is_return(self) -> bool {
self.kind == AbruptKind::Return
}
/// Returns a boolean value for whether `AbruptCompletionRecord` is a throw.
pub(crate) fn is_throw(self) -> bool {
self.kind == AbruptKind::Throw
}
pub(crate) fn is_throw_with_target(self) -> bool {
self.is_throw() && self.target < u32::MAX
}
/// Returns the value of `AbruptCompletionRecord`'s `target` field.
pub(crate) const fn target(self) -> u32 {
self.target
}
}

201
boa_engine/src/vm/call_frame/env_stack.rs

@ -1,201 +0,0 @@
//! Module for implementing a `CallFrame`'s environment stacks
#[derive(Clone, Debug)]
pub(crate) enum EnvEntryKind {
Global,
Loop {
/// How many iterations a loop has done.
iteration_count: u64,
/// The index of the currently active iterator.
iterator: Option<u32>,
},
Try {
/// The length of the value stack when the try block was entered.
///
/// This is used to pop exact amount values from the stack
/// when a throw happens.
fp: u32,
},
Finally,
Labelled,
}
/// The `EnvStackEntry` tracks the environment count and relevant information for the current environment.
#[derive(Clone, Debug)]
pub(crate) struct EnvStackEntry {
start: u32,
exit: u32,
kind: EnvEntryKind,
env_num: usize,
}
impl Default for EnvStackEntry {
fn default() -> Self {
Self {
start: 0,
exit: u32::MAX,
kind: EnvEntryKind::Global,
env_num: 0,
}
}
}
/// ---- `EnvStackEntry` creation methods ----
impl EnvStackEntry {
/// Creates a new [`EnvStackEntry`] with the supplied start addresses.
pub(crate) const fn new(start_address: u32, exit_address: u32) -> Self {
Self {
start: start_address,
exit: exit_address,
kind: EnvEntryKind::Global,
env_num: 0,
}
}
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Try`].
pub(crate) const fn with_try_flag(mut self, fp: u32) -> Self {
self.kind = EnvEntryKind::Try { fp };
self
}
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Loop`], loop iteration set to zero
/// and iterator index set to `iterator`.
pub(crate) const fn with_iterator_loop_flag(
mut self,
iteration_count: u64,
iterator: u32,
) -> Self {
self.kind = EnvEntryKind::Loop {
iteration_count,
iterator: Some(iterator),
};
self
}
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Loop`].
/// And the loop iteration set to zero.
pub(crate) const fn with_loop_flag(mut self, iteration_count: u64) -> Self {
self.kind = EnvEntryKind::Loop {
iteration_count,
iterator: None,
};
self
}
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Finally`].
pub(crate) const fn with_finally_flag(mut self) -> Self {
self.kind = EnvEntryKind::Finally;
self
}
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Labelled`].
pub(crate) const fn with_labelled_flag(mut self) -> Self {
self.kind = EnvEntryKind::Labelled;
self
}
pub(crate) const fn with_start_address(mut self, start_address: u32) -> Self {
self.start = start_address;
self
}
}
/// ---- `EnvStackEntry` interaction methods ----
impl EnvStackEntry {
/// Returns the `start` field of this `EnvStackEntry`.
pub(crate) const fn start_address(&self) -> u32 {
self.start
}
/// Returns the `exit` field of this `EnvStackEntry`.
pub(crate) const fn exit_address(&self) -> u32 {
self.exit
}
/// Returns the `fp` field of this [`EnvEntryKind::Try`].
///
/// # Panics
///
/// If this [`EnvStackEntry`] is **not** a [`EnvEntryKind::Try`].
pub(crate) fn try_env_frame_pointer(&self) -> u32 {
if let EnvEntryKind::Try { fp } = &self.kind {
return *fp;
}
unreachable!("trying to get frame pointer of a non-try environment")
}
pub(crate) const fn is_global_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Global)
}
/// Returns the loop iteration count if `EnvStackEntry` is a loop.
pub(crate) const fn as_loop_iteration_count(&self) -> Option<u64> {
if let EnvEntryKind::Loop {
iteration_count, ..
} = self.kind
{
return Some(iteration_count);
}
None
}
/// Increases loop iteration count if `EnvStackEntry` is a loop.
pub(crate) fn increase_loop_iteration_count(&mut self) {
if let EnvEntryKind::Loop {
iteration_count, ..
} = &mut self.kind
{
*iteration_count = iteration_count.wrapping_add(1);
}
}
/// Returns the active iterator index if `EnvStackEntry` is an iterator loop.
pub(crate) const fn iterator(&self) -> Option<u32> {
if let EnvEntryKind::Loop { iterator, .. } = self.kind {
return iterator;
}
None
}
/// Returns true if an `EnvStackEntry` is a try block
pub(crate) const fn is_try_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Try { .. })
}
/// Returns true if an `EnvStackEntry` is a labelled block
pub(crate) const fn is_labelled_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Labelled)
}
pub(crate) const fn is_finally_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Finally)
}
pub(crate) const fn is_loop_env(&self) -> bool {
matches!(self.kind, EnvEntryKind::Loop { .. })
}
/// Returns the current environment number for this entry.
pub(crate) const fn env_num(&self) -> usize {
self.env_num
}
pub(crate) fn set_exit_address(&mut self, exit_address: u32) {
self.exit = exit_address;
}
pub(crate) fn clear_env_num(&mut self) {
self.env_num = 0;
}
/// Increments the `env_num` field for current `EnvEntryStack`.
pub(crate) fn inc_env_num(&mut self) {
(self.env_num, _) = self.env_num.overflowing_add(1);
}
/// Decrements the `env_num` field for current `EnvEntryStack`.
pub(crate) fn dec_env_num(&mut self) {
(self.env_num, _) = self.env_num.overflowing_sub(1);
}
}

80
boa_engine/src/vm/call_frame/mod.rs

@ -2,9 +2,6 @@
//!
//! This module will provides everything needed to implement the `CallFrame`
mod abrupt_record;
mod env_stack;
use crate::{
builtins::{iterable::IteratorRecord, promise::PromiseCapability},
environments::BindingLocator,
@ -15,26 +12,16 @@ use crate::{
use boa_gc::{Finalize, Gc, Trace};
use thin_vec::ThinVec;
pub(crate) use abrupt_record::AbruptCompletionRecord;
pub(crate) use env_stack::EnvStackEntry;
/// A `CallFrame` holds the state of a function call.
#[derive(Clone, Debug, Finalize, Trace)]
pub struct CallFrame {
pub(crate) code_block: Gc<CodeBlock>,
pub(crate) pc: u32,
pub(crate) fp: u32,
#[unsafe_ignore_trace]
pub(crate) abrupt_completion: Option<AbruptCompletionRecord>,
#[unsafe_ignore_trace]
pub(crate) r#yield: bool,
pub(crate) env_fp: u32,
// Tracks the number of environments in environment entry.
// On abrupt returns this is used to decide how many environments need to be pop'ed.
#[unsafe_ignore_trace]
pub(crate) env_stack: Vec<EnvStackEntry>,
pub(crate) argument_count: u32,
#[unsafe_ignore_trace]
pub(crate) generator_resume_kind: GeneratorResumeKind,
pub(crate) promise_capability: Option<PromiseCapability>,
// When an async generator is resumed, the generator object is needed
@ -47,6 +34,9 @@ pub struct CallFrame {
// The stack of bindings being updated.
pub(crate) binding_stack: Vec<BindingLocator>,
/// How many iterations a loop has done.
pub(crate) loop_iteration_count: u64,
/// The value that is returned from the function.
//
// TODO(HalidOdat): Remove this and put into the stack, maybe before frame pointer.
@ -67,20 +57,17 @@ impl CallFrame {
impl CallFrame {
/// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new(code_block: Gc<CodeBlock>) -> Self {
let max_length = code_block.bytecode.len() as u32;
Self {
code_block,
pc: 0,
fp: 0,
env_stack: Vec::from([EnvStackEntry::new(0, max_length)]),
abrupt_completion: None,
r#yield: false,
env_fp: 0,
argument_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal,
promise_capability: None,
async_generator: None,
iterators: ThinVec::new(),
binding_stack: Vec::new(),
loop_iteration_count: 0,
return_value: JsValue::undefined(),
}
}
@ -90,6 +77,12 @@ impl CallFrame {
self.argument_count = count;
self
}
/// Updates a `CallFrame`'s `env_fp` field with the value provided.
pub(crate) fn with_env_fp(mut self, env_fp: u32) -> Self {
self.env_fp = env_fp;
self
}
}
/// ---- `CallFrame` stack methods ----
@ -97,32 +90,41 @@ impl CallFrame {
pub(crate) fn set_frame_pointer(&mut self, pointer: u32) {
self.fp = pointer;
}
/// Tracks that one environment has been pushed in the current loop block.
pub(crate) fn inc_frame_env_stack(&mut self) {
self.env_stack
.last_mut()
.expect("environment stack entry must exist")
.inc_env_num();
}
/// Tracks that one environment has been pop'ed in the current loop block.
///
/// Note:
/// - This will check if the env stack has reached 0 and should be popped.
pub(crate) fn dec_frame_env_stack(&mut self) {
self.env_stack
.last_mut()
.expect("environment stack entry must exist")
.dec_env_num();
}
}
/// Indicates how a generator function that has been called/resumed should return.
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[repr(u8)]
pub(crate) enum GeneratorResumeKind {
#[default]
Normal,
Normal = 0,
Throw,
Return,
}
impl From<GeneratorResumeKind> for JsValue {
fn from(value: GeneratorResumeKind) -> Self {
Self::new(value as u8)
}
}
impl JsValue {
/// Convert value to [`GeneratorResumeKind`].
///
/// # Panics
///
/// If not a integer type or not in the range `1..=2`.
#[track_caller]
pub(crate) fn to_generator_resume_kind(&self) -> GeneratorResumeKind {
if let Self::Integer(value) = self {
match *value {
0 => return GeneratorResumeKind::Normal,
1 => return GeneratorResumeKind::Throw,
2 => return GeneratorResumeKind::Return,
_ => unreachable!("generator kind must be a integer between 1..=2, got {value}"),
}
}
unreachable!("generator kind must be a integer type")
}
}

153
boa_engine/src/vm/code_block.rs

@ -87,6 +87,34 @@ unsafe impl Trace for CodeBlockFlags {
empty_trace!();
}
/// This represents a range in the code that handles exception throws.
///
/// When a throw happens, we search for handler in the [`CodeBlock`] using
/// the [`CodeBlock::find_handler()`] method.
///
/// If any exception happens and gets cought by this handler, the `pc` will be set to `end` of the
/// [`Handler`] and remove any environments or stack values that where pushed after the handler.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Handler {
pub(crate) start: u32,
pub(crate) end: u32,
pub(crate) stack_count: u32,
pub(crate) environment_count: u32,
}
impl Handler {
/// Get the handler address.
pub(crate) const fn handler(&self) -> u32 {
self.end
}
/// Check if the provided `pc` is contained in the handler range.
pub(crate) const fn contains(&self, pc: u32) -> bool {
pc < self.end && pc >= self.start
}
}
/// The internal representation of a JavaScript function.
///
/// A `CodeBlock` is generated for each function compiled by the
@ -127,6 +155,10 @@ pub struct CodeBlock {
/// Functions inside this function
pub(crate) functions: Box<[Gc<Self>]>,
/// Exception [`Handler`]s.
#[unsafe_ignore_trace]
pub(crate) handlers: ThinVec<Handler>,
/// Compile time environments in this function.
///
// Safety: Nothing in CompileTimeEnvironment needs tracing, so this is safe.
@ -154,6 +186,7 @@ impl CodeBlock {
length,
this_mode: ThisMode::Global,
params: FormalParameterList::default(),
handlers: ThinVec::default(),
compile_environments: Box::default(),
}
}
@ -217,6 +250,16 @@ impl CodeBlock {
.get()
.contains(CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER)
}
/// Find exception [`Handler`] in the code block given the current program counter (`pc`).
#[inline]
pub(crate) fn find_handler(&self, pc: u32) -> Option<(usize, &Handler)> {
self.handlers
.iter()
.enumerate()
.rev()
.find(|(_, handler)| handler.contains(pc))
}
}
/// ---- `CodeBlock` private API ----
@ -309,8 +352,6 @@ impl CodeBlock {
| Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined
| Opcode::FinallyStart
| Opcode::LabelledStart
| Opcode::Case
| Opcode::Default
| Opcode::LogicalAnd
@ -331,11 +372,6 @@ impl CodeBlock {
format!("{operand}")
}
Opcode::CopyDataProperties
| Opcode::Break
| Opcode::Continue
| Opcode::LoopStart
| Opcode::IteratorLoopStart
| Opcode::TryStart
| Opcode::GeneratorDelegateNext
| Opcode::GeneratorDelegateResume => {
let operand1 = self.read::<u32>(*pc);
@ -430,14 +466,32 @@ impl CodeBlock {
*pc += size_of::<u32>() * (count as usize + 1);
String::new()
}
Opcode::GeneratorJumpOnResumeKind => {
let normal = self.read::<u32>(*pc);
Opcode::JumpTable => {
let count = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let throw = self.read::<u32>(*pc);
let default = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let r#return = self.read::<u32>(*pc);
let mut operands = format!("#{count}: Default: {default:4}");
for i in 1..=count {
let address = self.read::<u32>(*pc);
*pc += size_of::<u32>();
operands += &format!(", {i}: {address}");
}
operands
}
Opcode::JumpIfNotResumeKind => {
let exit = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("n: {normal}, t: {throw}, r: {return}")
let resume_kind = self.read::<u8>(*pc);
*pc += size_of::<u8>();
format!(
"ResumeKind: {:?}, exit: {exit}",
JsValue::new(resume_kind).to_generator_resume_kind()
)
}
Opcode::CreateIteratorResult => {
let done = self.read::<u8>(*pc) != 0;
@ -510,22 +564,21 @@ impl CodeBlock {
| Opcode::ToPropertyKey
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::TryEnd
| Opcode::FinallyEnd
| Opcode::ReThrow
| Opcode::Exception
| Opcode::This
| Opcode::Super
| Opcode::Return
| Opcode::PopEnvironment
| Opcode::LoopEnd
| Opcode::LoopContinue
| Opcode::LabelledEnd
| Opcode::IncrementLoopIteration
| Opcode::CreateForInIterator
| Opcode::GetIterator
| Opcode::GetAsyncIterator
| Opcode::GeneratorResumeReturn
| Opcode::IteratorNext
| Opcode::IteratorNextWithoutPop
| Opcode::IteratorFinishAsyncNext
| Opcode::IteratorValue
| Opcode::IteratorValueWithoutPop
| Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray
@ -543,7 +596,6 @@ impl CodeBlock {
| Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::GeneratorNext
| Opcode::GeneratorSetReturn
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
@ -618,7 +670,15 @@ impl CodeBlock {
| Opcode::Reserved53
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved56
| Opcode::Reserved57
| Opcode::Reserved58
| Opcode::Reserved59
| Opcode::Reserved60
| Opcode::Reserved61
| Opcode::Reserved62
| Opcode::Reserved63
| Opcode::Reserved64 => unreachable!("Reserved opcodes are unrechable"),
}
}
}
@ -634,19 +694,36 @@ impl ToInternedString for CodeBlock {
};
f.push_str(&format!(
"{:-^70}\nLocation Count Opcode Operands\n\n",
"{:-^70}\nLocation Count Handler Opcode Operands\n\n",
format!("Compiled Output: '{}'", name.to_std_string_escaped()),
));
let mut pc = 0;
let mut count = 0;
while pc < self.bytecode.len() {
let opcode: Opcode = self.bytecode[pc].into();
let instruction_start_pc = pc;
let opcode: Opcode = self.bytecode[instruction_start_pc].into();
let opcode = opcode.as_str();
let previous_pc = pc;
let operands = self.instruction_operands(&mut pc, interner);
let handler = if let Some((i, handler)) = self.find_handler(instruction_start_pc as u32)
{
let border_char = if instruction_start_pc as u32 == handler.start {
'>'
} else if pc as u32 == handler.end {
'<'
} else {
' '
};
format!("{border_char}{i:2}: {:04}", handler.handler())
} else {
" none ".to_string()
};
f.push_str(&format!(
"{previous_pc:06} {count:04} {opcode:<27}{operands}\n",
"{previous_pc:06} {count:04} {handler} {opcode:<27}{operands}\n",
));
count += 1;
}
@ -690,6 +767,22 @@ impl ToInternedString for CodeBlock {
}
}
f.push_str("\nHandlers:\n");
if self.handlers.is_empty() {
f.push_str(" <empty>\n");
} else {
for (i, handler) in self.handlers.iter().enumerate() {
f.push_str(&format!(
" {i:04}: Range: [{:04}, {:04}): Handler: {:04}, Stack: {:02}, Environment: {:02}\n",
handler.start,
handler.end,
handler.handler(),
handler.stack_count,
handler.environment_count,
));
}
}
f
}
}
@ -1093,6 +1186,8 @@ impl JsObject {
)
};
let env_fp = context.vm.environments.len() as u32;
let mut last_env = code.compile_environments.len() - 1;
if let Some(class_object) = class_object {
@ -1182,7 +1277,9 @@ impl JsObject {
std::mem::swap(&mut context.vm.stack, &mut stack);
let mut frame = CallFrame::new(code).with_argument_count(argument_count as u32);
let mut 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);
@ -1458,9 +1555,11 @@ impl JsObject {
std::mem::swap(&mut context.vm.active_runnable, &mut script_or_module);
context
.vm
.push_frame(CallFrame::new(code).with_argument_count(argument_count as u32));
context.vm.push_frame(
CallFrame::new(code)
.with_argument_count(argument_count as u32)
.with_env_fp(environments_len as u32),
);
let record = context.run();

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

@ -17,6 +17,7 @@ pub use node::*;
impl CodeBlock {
/// Output the [`CodeBlock`] VM instructions into a [`Graph`].
#[allow(clippy::match_same_arms)]
pub fn to_graph(&self, interner: &Interner, graph: &mut SubGraph) {
// Have to remove any invalid graph chars like `<` or `>`.
let name = if self.name() == utf16!("<main>") {
@ -27,10 +28,6 @@ impl CodeBlock {
graph.set_label(name);
let mut environments = Vec::new();
let mut try_entries = Vec::new();
let mut returns = Vec::new();
let mut pc = 0;
while pc < self.bytecode.len() {
let opcode: Opcode = self.bytecode[pc].into();
@ -131,20 +128,6 @@ impl CodeBlock {
EdgeStyle::Line,
);
}
Opcode::LabelledStart => {
let end_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {end_address}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::LoopStart | Opcode::IteratorLoopStart => {
pc += size_of::<u32>() * 2;
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::TemplateLookup | Opcode::TemplateCreate => {
let start_address = self.read::<u32>(pc);
pc += size_of::<u32>();
@ -155,38 +138,6 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Break => {
let jump_operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let target_operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {jump_operand}, target {target_operand}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(
previous_pc,
jump_operand as usize,
Some("BREAK".into()),
Color::Red,
EdgeStyle::Line,
);
}
Opcode::Continue => {
let jump_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let target_operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {jump_address}, target {target_operand}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(
previous_pc,
jump_address as usize,
Some("CONTINUE".into()),
Color::Red,
EdgeStyle::Line,
);
}
Opcode::LogicalAnd | Opcode::LogicalOr | Opcode::Coalesce => {
let exit = self.read::<u32>(pc);
pc += size_of::<u32>();
@ -283,82 +234,27 @@ impl CodeBlock {
| Opcode::Call
| Opcode::New
| Opcode::SuperCall
| Opcode::ConcatToString
| Opcode::FinallyStart => {
| Opcode::ConcatToString => {
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::GeneratorJumpOnResumeKind => {
let normal = self.read::<u32>(pc);
pc += size_of::<u32>();
let throw = self.read::<u32>(pc);
pc += size_of::<u32>();
let r#return = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(
previous_pc,
NodeShape::Diamond,
opcode_str.into(),
Color::None,
);
graph.add_edge(
previous_pc,
normal as usize,
None,
Color::None,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
throw as usize,
Some("throw".into()),
Color::Red,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
r#return as usize,
Some("return".into()),
Color::Yellow,
EdgeStyle::Line,
);
}
Opcode::TryStart => {
let next_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let finally_address = self.read::<u32>(pc);
Opcode::JumpIfNotResumeKind => {
let exit = self.read::<u32>(pc);
pc += size_of::<u32>();
try_entries.push((
previous_pc,
next_address,
if finally_address == 0 {
None
} else {
Some(finally_address)
},
));
let _resume_kind = self.read::<u8>(pc);
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);
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
graph.add_edge(
previous_pc,
next_address as usize,
Some("NEXT".into()),
Color::None,
exit as usize,
Some("EXIT".into()),
Color::Red,
EdgeStyle::Line,
);
if finally_address != 0 {
graph.add_edge(
previous_pc,
finally_address as usize,
Some("FINALLY".into()),
Color::None,
EdgeStyle::Line,
);
}
graph.add_edge(previous_pc, pc, None, Color::Green, EdgeStyle::Line);
}
Opcode::CopyDataProperties => {
let operand1 = self.read::<u32>(pc);
@ -372,7 +268,6 @@ impl CodeBlock {
}
Opcode::PushDeclarativeEnvironment | Opcode::PushFunctionEnvironment => {
let random = rand::random();
environments.push((previous_pc, random));
pc += size_of::<u32>();
@ -385,22 +280,8 @@ impl CodeBlock {
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::PopEnvironment => {
let (environment_push, random) = environments
.pop()
.expect("There should be a push evironment before");
let color = Color::from_random_number(random);
graph.add_node(previous_pc, NodeShape::None, label.into(), color);
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph
.add_edge(
previous_pc,
environment_push,
None,
color,
EdgeStyle::Dotted,
)
.set_type(EdgeType::None);
}
Opcode::GetArrowFunction
| Opcode::GetAsyncArrowFunction
@ -484,31 +365,30 @@ impl CodeBlock {
}
Opcode::ThrowNewTypeError => {
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() {
if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
graph.add_edge(
previous_pc,
*next as usize,
Some("CAUGHT".into()),
handler.handler() as usize,
Some(format!("Handler {i:2}: CAUGHT").into()),
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
}
}
Opcode::Throw => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() {
Opcode::Throw | Opcode::ReThrow => {
if let Some((i, handler)) = self.find_handler(previous_pc as u32) {
graph.add_node(previous_pc, NodeShape::Record, label.into(), Color::None);
graph.add_edge(
previous_pc,
*next as usize,
Some("CAUGHT".into()),
handler.handler() as usize,
Some(format!("Handler {i:2}: CAUGHT").into()),
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::None);
}
}
Opcode::PushPrivateEnvironment => {
@ -517,6 +397,34 @@ 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::JumpTable => {
let count = self.read::<u32>(pc);
pc += size_of::<u32>();
let default = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(
previous_pc,
default as usize,
Some("DEFAULT".into()),
Color::None,
EdgeStyle::Line,
);
for i in 0..count {
let address = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_edge(
previous_pc,
address as usize,
Some(format!("Index: {i}").into()),
Color::None,
EdgeStyle::Line,
);
}
}
Opcode::Pop
| Opcode::Dup
| Opcode::Swap
@ -582,18 +490,17 @@ impl CodeBlock {
| Opcode::DeleteSuperThrow
| Opcode::ToPropertyKey
| Opcode::ToBoolean
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Super
| Opcode::LoopEnd
| Opcode::LoopContinue
| Opcode::LabelledEnd
| Opcode::IncrementLoopIteration
| Opcode::CreateForInIterator
| Opcode::GetIterator
| Opcode::GetAsyncIterator
| Opcode::IteratorNext
| Opcode::IteratorNextWithoutPop
| Opcode::IteratorFinishAsyncNext
| Opcode::IteratorValue
| Opcode::IteratorValueWithoutPop
| Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray
@ -627,34 +534,15 @@ impl CodeBlock {
| Opcode::PushObjectEnvironment
| Opcode::PopPrivateEnvironment
| Opcode::ImportCall
| Opcode::GeneratorSetReturn
| Opcode::GetReturnValue
| Opcode::SetReturnValue
| Opcode::Exception
| Opcode::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::TryEnd => {
try_entries
.pop()
.expect("there should already be try block");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::Return | Opcode::GeneratorResumeReturn => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, _next, Some(finally))) = try_entries.last() {
graph.add_edge(
previous_pc,
*finally as usize,
None,
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
}
Opcode::Return => {
graph.add_node(previous_pc, NodeShape::Diamond, label.into(), Color::Red);
}
Opcode::Reserved1
| Opcode::Reserved2
@ -711,16 +599,18 @@ impl CodeBlock {
| Opcode::Reserved53
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved56
| Opcode::Reserved57
| Opcode::Reserved58
| Opcode::Reserved59
| Opcode::Reserved60
| Opcode::Reserved61
| Opcode::Reserved62
| Opcode::Reserved63
| Opcode::Reserved64 => unreachable!("Reserved opcodes are unrechable"),
}
}
for ret in returns {
graph.add_edge(ret, pc, None, Color::None, EdgeStyle::Line);
}
graph.add_node(pc, NodeShape::Diamond, "End".into(), Color::Red);
for function in self.functions.as_ref() {
let subgraph = graph.subgraph(String::new());
function.to_graph(interner, subgraph);

94
boa_engine/src/vm/mod.rs

@ -40,7 +40,7 @@ pub(crate) use {
call_frame::GeneratorResumeKind,
code_block::{
create_function_object, create_function_object_fast, create_generator_function_object,
CodeBlockFlags,
CodeBlockFlags, Handler,
},
completion_record::CompletionRecord,
opcode::BindingOpcode,
@ -54,13 +54,23 @@ mod tests;
pub struct Vm {
pub(crate) frames: Vec<CallFrame>,
pub(crate) stack: Vec<JsValue>,
pub(crate) err: Option<JsError>,
/// When an error is thrown, the pending exception is set.
///
/// If we throw an empty exception ([`None`]), this means that `return()` was called on a generator,
/// propagating though the exception handlers and executing the finally code (if any).
///
/// See [`ReThrow`](crate::vm::Opcode::ReThrow) and [`ReThrow`](crate::vm::Opcode::Exception) opcodes.
///
/// This is also used to eliminates [`crate::JsNativeError`] to opaque conversion if not needed.
pub(crate) pending_exception: Option<JsError>,
pub(crate) environments: EnvironmentStack,
#[cfg(feature = "trace")]
pub(crate) trace: bool,
pub(crate) runtime_limits: RuntimeLimits,
pub(crate) active_function: Option<JsObject>,
pub(crate) active_runnable: Option<ActiveRunnable>,
#[cfg(feature = "trace")]
pub(crate) trace: bool,
}
/// Active runnable in the current vm context.
@ -86,12 +96,12 @@ impl Vm {
frames: Vec::with_capacity(16),
stack: Vec::with_capacity(1024),
environments: EnvironmentStack::new(global),
err: None,
#[cfg(feature = "trace")]
trace: false,
pending_exception: None,
runtime_limits: RuntimeLimits::default(),
active_function: None,
active_runnable: None,
#[cfg(feature = "trace")]
trace: false,
}
}
@ -147,6 +157,29 @@ impl Vm {
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
self.frames.pop()
}
/// Handles an exception thrown at position `pc`.
///
/// Returns `true` if the exception was handled, `false` otherwise.
#[inline]
pub(crate) fn handle_exception_at(&mut self, pc: u32) -> bool {
let frame = self.frame_mut();
let Some((_, handler)) = frame.code_block().find_handler(pc) else {
return false;
};
let catch_address = handler.handler();
let environment_sp = frame.env_fp + handler.environment_count;
let sp = frame.fp + handler.stack_count;
// Go to handler location.
frame.pc = catch_address;
self.environments.truncate(environment_sp as usize);
self.stack.truncate(sp as usize);
true
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
@ -154,6 +187,7 @@ pub(crate) enum CompletionType {
Normal,
Return,
Throw,
Yield,
}
impl Context<'_> {
@ -225,16 +259,11 @@ impl Context<'_> {
let promise_capability = self.vm.frame().promise_capability.clone();
let execution_completion = loop {
// 1. Exit the execution loop if program counter ever is equal to or exceeds the amount of instructions
if self.vm.frame().code_block.bytecode.len() <= self.vm.frame().pc as usize {
break CompletionType::Normal;
}
#[cfg(feature = "fuzz")]
{
if self.instructions_remaining == 0 {
let err = JsError::from_native(JsNativeError::no_instructions_remain());
self.vm.err = Some(err);
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
}
self.instructions_remaining -= 1;
@ -290,6 +319,11 @@ impl Context<'_> {
Ok(CompletionType::Throw) => {
break CompletionType::Throw;
}
// Early return immediately.
Ok(CompletionType::Yield) => {
let result = self.vm.pop();
return CompletionRecord::Return(result);
}
Err(err) => {
#[cfg(feature = "fuzz")]
{
@ -297,7 +331,7 @@ impl Context<'_> {
// 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.err = Some(err);
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
}
}
@ -307,15 +341,14 @@ impl Context<'_> {
// 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.err = Some(err);
self.vm.pending_exception = Some(err);
break CompletionType::Throw;
}
}
self.vm.err = Some(err);
self.vm.pending_exception = Some(err);
// If this frame has not evaluated the throw as an AbruptCompletion, then evaluate it
let evaluation = Opcode::Throw
let evaluation = Opcode::ReThrow
.execute(self)
.expect("Opcode::Throw cannot return Err");
@ -328,13 +361,6 @@ impl Context<'_> {
}
};
// Early return immediately after loop.
if self.vm.frame().r#yield {
self.vm.frame_mut().r#yield = false;
let result = self.vm.pop();
return CompletionRecord::Return(result);
}
#[cfg(feature = "trace")]
if self.vm.trace {
println!("\nStack:");
@ -359,11 +385,6 @@ impl Context<'_> {
println!("\n");
}
if execution_completion == CompletionType::Throw
|| execution_completion == CompletionType::Return
{
self.vm.frame_mut().abrupt_completion = None;
}
self.vm.stack.truncate(self.vm.frame().fp as usize);
// Determine the execution result
@ -384,13 +405,18 @@ impl Context<'_> {
.expect("cannot fail per spec");
}
CompletionType::Throw => {
let err = self.vm.err.take().expect("Take must exist on a 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.err = Some(err);
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)
@ -413,7 +439,7 @@ impl Context<'_> {
&next,
Err(self
.vm
.err
.pending_exception
.take()
.expect("err must exist on a Completion::Throw")),
true,
@ -432,7 +458,7 @@ impl Context<'_> {
if execution_completion == CompletionType::Throw {
return CompletionRecord::Throw(
self.vm
.err
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
);

3
boa_engine/src/vm/opcode/await_stm/mod.rs

@ -126,7 +126,6 @@ impl Operation for Await {
);
context.vm.push(JsValue::undefined());
context.vm.frame_mut().r#yield = true;
Ok(CompletionType::Return)
Ok(CompletionType::Yield)
}
}

54
boa_engine/src/vm/opcode/control_flow/break.rs

@ -1,54 +0,0 @@
use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
Context, JsResult,
};
/// `Break` implements the Opcode Operation for `Opcode::Break`
///
/// Operation:
/// - Pop required environments and jump to address.
pub(crate) struct Break;
impl Operation for Break {
const NAME: &'static str = "Break";
const INSTRUCTION: &'static str = "INST - Break";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let jump_address = context.vm.read::<u32>();
let target_address = context.vm.read::<u32>();
// 1. Iterate through Env stack looking for exit address.
let mut envs_to_pop = 0;
let mut found_target = false;
for i in (0..context.vm.frame().env_stack.len()).rev() {
if found_target {
break;
}
let Some(env_entry) = context.vm.frame_mut().env_stack.get_mut(i) else {
break;
};
if jump_address == env_entry.exit_address()
|| (env_entry.is_finally_env() && jump_address == env_entry.start_address())
{
found_target = true;
continue;
}
envs_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
// 2. Register target address in AbruptCompletionRecord.
let new_record = AbruptCompletionRecord::new_break().with_initial_target(target_address);
context.vm.frame_mut().abrupt_completion = Some(new_record);
// 3. Set program counter and finally return fields.
context.vm.frame_mut().pc = jump_address;
Ok(CompletionType::Normal)
}
}

67
boa_engine/src/vm/opcode/control_flow/continue.rs

@ -1,67 +0,0 @@
use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
Context, JsResult,
};
/// `Continue` implements the Opcode Operation for `Opcode::Continue`
///
/// Operands:
/// - Target address
/// - Initial environments to reconcile on continue (will be tracked along with changes to environment stack)
///
/// Operation:
/// - Initializes the `AbruptCompletionRecord` for a delayed continued in a `Opcode::FinallyEnd`
pub(crate) struct Continue;
impl Operation for Continue {
const NAME: &'static str = "Continue";
const INSTRUCTION: &'static str = "INST - Continue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let jump_address = context.vm.read::<u32>();
let target_address = context.vm.read::<u32>();
// 1. Iterate through Env stack looking for exit address.
let mut envs_to_pop = 0;
let mut found_target = false;
for i in (0..context.vm.frame().env_stack.len()).rev() {
if found_target {
break;
}
let Some(env_entry) = context.vm.frame_mut().env_stack.get_mut(i) else {
break;
};
// We check two conditions here where continue actually jumps to a higher address.
// 1. When we have reached a finally env that matches the jump address we are moving to.
// 2. When there is no finally, and we have reached the continue location.
if (env_entry.is_finally_env() && jump_address == env_entry.start_address())
|| (jump_address == target_address && jump_address == env_entry.start_address())
{
found_target = true;
continue;
}
envs_to_pop += env_entry.env_num();
// The below check determines whether we have continued from inside of a finally block.
if jump_address > target_address && jump_address == env_entry.exit_address() {
found_target = true;
context.vm.frame_mut().env_stack.pop();
continue;
}
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
// 2. Register target address in AbruptCompletionRecord.
let new_record = AbruptCompletionRecord::new_continue().with_initial_target(target_address);
context.vm.frame_mut().abrupt_completion = Some(new_record);
// 3. Set program counter and finally return fields.
context.vm.frame_mut().pc = jump_address;
Ok(CompletionType::Normal)
}
}

148
boa_engine/src/vm/opcode/control_flow/finally.rs

@ -1,148 +0,0 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsError, JsResult,
};
/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart`
///
/// Operation:
/// - Start of a finally block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct FinallyStart;
impl Operation for FinallyStart {
const NAME: &'static str = "FinallyStart";
const INSTRUCTION: &'static str = "INST - FinallyStart";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let exit = context.vm.read::<u32>();
let finally_env = context
.vm
.frame_mut()
.env_stack
.last_mut()
.expect("EnvStackEntries must exist");
finally_env.set_exit_address(exit);
Ok(CompletionType::Normal)
}
}
/// `FinallyEnd` implements the Opcode Operation for `Opcode::FinallyEnd`
///
/// Operation:
/// - End of a finally block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct FinallyEnd;
impl Operation for FinallyEnd {
const NAME: &'static str = "FinallyEnd";
const INSTRUCTION: &'static str = "INST - FinallyEnd";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let finally_candidates = context
.vm
.frame()
.env_stack
.iter()
.filter(|env| env.is_finally_env() && context.vm.frame().pc < env.start_address());
let next_finally = match finally_candidates.last() {
Some(env) => env.start_address(),
_ => u32::MAX,
};
let mut envs_to_pop = 0;
match context.vm.frame().abrupt_completion {
Some(record) if next_finally < record.target() => {
context.vm.frame_mut().pc = next_finally;
while let Some(env_entry) = context.vm.frame().env_stack.last() {
if next_finally <= env_entry.exit_address() {
break;
}
envs_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
}
Some(record) if record.is_break() && context.vm.frame().pc < record.target() => {
// handle the continuation of an abrupt break.
context.vm.frame_mut().pc = record.target();
while let Some(env_entry) = context.vm.frame().env_stack.last() {
if record.target() == env_entry.exit_address() {
break;
}
envs_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
context.vm.frame_mut().abrupt_completion = None;
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
}
Some(record) if record.is_continue() && context.vm.frame().pc > record.target() => {
// Handle the continuation of an abrupt continue
context.vm.frame_mut().pc = record.target();
while let Some(env_entry) = context.vm.frame().env_stack.last() {
if env_entry.start_address() == record.target() {
break;
}
envs_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
context.vm.frame_mut().abrupt_completion = None;
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
}
Some(record) if record.is_return() => {
return Ok(CompletionType::Return);
}
Some(record)
if record.is_throw_with_target() && context.vm.frame().pc < record.target() =>
{
context.vm.frame_mut().pc = record.target();
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.start_address() == record.target() {
break;
}
}
context.vm.frame_mut().abrupt_completion = None;
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
}
Some(record) if record.is_throw() && !record.is_throw_with_target() => {
let current_stack = context
.vm
.frame_mut()
.env_stack
.pop()
.expect("Popping current finally stack.");
let env_truncation_len = context
.vm
.environments
.len()
.saturating_sub(current_stack.env_num());
context.vm.environments.truncate(env_truncation_len);
let err = JsError::from_opaque(context.vm.pop());
context.vm.err = Some(err);
return Ok(CompletionType::Throw);
}
_ => {
context.vm.frame_mut().env_stack.pop();
}
}
Ok(CompletionType::Normal)
}
}

55
boa_engine/src/vm/opcode/control_flow/labelled.rs

@ -1,55 +0,0 @@
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType},
Context, JsResult,
};
/// `LabelledStart` implements the Opcode Operation for `Opcode::LabelledStart`
///
/// Operation:
/// - Start of a labelled block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct LabelledStart;
impl Operation for LabelledStart {
const NAME: &'static str = "LabelledStart";
const INSTRUCTION: &'static str = "INST - LabelledStart";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let start = context.vm.frame().pc - 1;
let end = context.vm.read::<u32>();
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, end).with_labelled_flag());
Ok(CompletionType::Normal)
}
}
/// `LabelledEnd` implements the Opcode Operation for `Opcode::LabelledEnd`
///
/// Operation:
/// - Clean up environments at the end of labelled block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct LabelledEnd;
impl Operation for LabelledEnd {
const NAME: &'static str = "LabelledEnd";
const INSTRUCTION: &'static str = "INST - LabelledEnd";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut envs_to_pop = 0_usize;
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.is_labelled_env() {
break;
}
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
Ok(CompletionType::Normal)
}
}

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

@ -1,15 +1,5 @@
pub(crate) mod r#break;
pub(crate) mod r#continue;
pub(crate) mod finally;
pub(crate) mod labelled;
pub(crate) mod r#return;
pub(crate) mod throw;
pub(crate) mod r#try;
pub(crate) use finally::*;
pub(crate) use labelled::*;
pub(crate) use r#break::*;
pub(crate) use r#continue::*;
pub(crate) use r#return::*;
pub(crate) use r#try::*;
pub(crate) use throw::*;

36
boa_engine/src/vm/opcode/control_flow/return.rs

@ -1,5 +1,5 @@
use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
@ -14,39 +14,7 @@ impl Operation for Return {
const NAME: &'static str = "Return";
const INSTRUCTION: &'static str = "INST - Return";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let current_address = context.vm.frame().pc;
let mut env_to_pop = 0;
let mut finally_address = None;
while let Some(env_entry) = context.vm.frame().env_stack.last() {
if env_entry.is_finally_env() {
if env_entry.start_address() < current_address {
finally_address = Some(env_entry.exit_address());
} else {
finally_address = Some(env_entry.start_address());
}
break;
}
env_to_pop += env_entry.env_num();
if env_entry.is_global_env() {
break;
}
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(env_to_pop);
context.vm.environments.truncate(env_truncation_len);
let record = AbruptCompletionRecord::new_return();
context.vm.frame_mut().abrupt_completion = Some(record);
if let Some(finally) = finally_address {
context.vm.frame_mut().pc = finally;
return Ok(CompletionType::Normal);
}
fn execute(_context: &mut Context<'_>) -> JsResult<CompletionType> {
Ok(CompletionType::Return)
}
}

215
boa_engine/src/vm/opcode/control_flow/throw.rs

@ -1,10 +1,6 @@
use crate::{
vm::{
call_frame::{AbruptCompletionRecord, EnvStackEntry},
opcode::Operation,
CompletionType,
},
Context, JsError, JsNativeError, JsResult, JsValue,
vm::{opcode::Operation, CompletionType},
Context, JsError, JsNativeError, JsResult,
};
/// `Throw` implements the Opcode Operation for `Opcode::Throw`
@ -19,168 +15,77 @@ impl Operation for Throw {
const INSTRUCTION: &'static str = "INST - Throw";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let error = if let Some(err) = context.vm.err.take() {
err
} else {
JsError::from_opaque(context.vm.pop())
};
// 1. Find the viable catch and finally blocks
let current_address = context.vm.frame().pc;
let mut envs = context.vm.frame().env_stack.iter();
// Handle catch block
if let Some(idx) =
envs.rposition(|env| env.is_try_env() && env.start_address() != env.exit_address())
{
let active_iterator = context.vm.frame().env_stack[..idx]
.iter()
.filter_map(EnvStackEntry::iterator)
.last();
// Close all iterators that are outside the catch context.
if let Some(active_iterator) = active_iterator {
let inactive = context
.vm
.frame_mut()
.iterators
.split_off(active_iterator as usize + 1);
for iterator in inactive {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
}
context.vm.err.take();
}
let try_env = &context.vm.frame().env_stack[idx];
let try_env_frame_pointer = try_env.try_env_frame_pointer();
context.vm.stack.truncate(try_env_frame_pointer as usize);
let catch_target = context.vm.frame().env_stack[idx].start_address();
let mut env_to_pop = 0;
let mut target_address = u32::MAX;
while context.vm.frame().env_stack.len() > 1 {
let env_entry = context
.vm
.frame_mut()
.env_stack
.last()
.expect("EnvStackEntries must exist");
if env_entry.is_try_env() && env_entry.start_address() < env_entry.exit_address() {
target_address = env_entry.start_address();
env_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
break;
} else if env_entry.is_finally_env() {
if current_address > env_entry.start_address() {
target_address = env_entry.exit_address();
} else {
target_address = env_entry.start_address();
}
break;
}
env_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(env_to_pop);
context.vm.environments.truncate(env_truncation_len);
if target_address == catch_target {
context.vm.frame_mut().pc = catch_target;
} else {
context.vm.frame_mut().pc = target_address;
};
let record = AbruptCompletionRecord::new_throw().with_initial_target(catch_target);
context.vm.frame_mut().abrupt_completion = Some(record);
let err = error.to_opaque(context);
context.vm.push(err);
let error = JsError::from_opaque(context.vm.pop());
context.vm.pending_exception = Some(error);
// Note: -1 because we increment after fetching the opcode.
let pc = context.vm.frame().pc - 1;
if context.vm.handle_exception_at(pc) {
return Ok(CompletionType::Normal);
}
let mut env_to_pop = 0;
let mut target_address = None;
let mut env_stack_to_pop = 0;
for env_entry in context.vm.frame_mut().env_stack.iter_mut().rev() {
if env_entry.is_finally_env() {
if env_entry.start_address() < current_address {
target_address = Some(env_entry.exit_address());
} else {
target_address = Some(env_entry.start_address());
}
break;
};
env_to_pop += env_entry.env_num();
if env_entry.is_global_env() {
env_entry.clear_env_num();
break;
};
env_stack_to_pop += 1;
}
Ok(CompletionType::Throw)
}
}
/// `ReThrow` implements the Opcode Operation for `Opcode::ReThrow`
///
/// Operation:
/// - Rethrow the pending exception.
#[derive(Debug, Clone, Copy)]
pub(crate) struct ReThrow;
let record = AbruptCompletionRecord::new_throw();
context.vm.frame_mut().abrupt_completion = Some(record);
if let Some(address) = target_address {
for _ in 0..env_stack_to_pop {
context.vm.frame_mut().env_stack.pop();
}
let active_iterator = context
.vm
.frame()
.env_stack
.iter()
.filter_map(EnvStackEntry::iterator)
.last();
// Close all iterators that are outside the finally context.
if let Some(active_iterator) = active_iterator {
let inactive = context
.vm
.frame_mut()
.iterators
.split_off(active_iterator as usize + 1);
for iterator in inactive {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
}
context.vm.err.take();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(env_to_pop);
context.vm.environments.truncate(env_truncation_len);
// NOTE: There is could be leftover stack values, but this is fine,
// since we truncate to the call frams's frame pointer on return.
context.vm.frame_mut().pc = address;
let err = error.to_opaque(context);
context.vm.push(err);
impl Operation for ReThrow {
const NAME: &'static str = "ReThrow";
const INSTRUCTION: &'static str = "INST - ReThrow";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// Note: -1 because we increment after fetching the opcode.
let pc = context.vm.frame().pc.saturating_sub(1);
if context.vm.handle_exception_at(pc) {
return Ok(CompletionType::Normal);
}
// Close all iterators that are still open.
for iterator in std::mem::take(&mut context.vm.frame_mut().iterators) {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
// Note: If we are rethowing and there is no pending error,
// this means that return was called on the generator.
//
// Note: If we reached this stage then we there is no handler to handle this,
// so return (only for generators).
if context.vm.pending_exception.is_none() {
return Ok(CompletionType::Return);
}
context.vm.err.take();
context.vm.err = Some(error);
Ok(CompletionType::Throw)
}
}
/// `Exception` implements the Opcode Operation for `Opcode::Exception`
///
/// Operation:
/// - Get the thrown exception and push on the stack.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Exception;
impl Operation for Exception {
const NAME: &'static str = "Exception";
const INSTRUCTION: &'static str = "INST - Exception";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if let Some(error) = context.vm.pending_exception.take() {
let error = error.to_opaque(context);
context.vm.push(error);
return Ok(CompletionType::Normal);
}
// If there is no pending error, this means that `return()` was called
// on the generator, so we rethrow this (empty) error until there is no handler to handle it.
// This is done to run the finally code.
//
// This should be unreachable for regular functions.
ReThrow::execute(context)
}
}
/// `ThrowNewTypeError` implements the Opcode Operation for `Opcode::ThrowNewTypeError`
///
/// Operation:

68
boa_engine/src/vm/opcode/control_flow/try.rs

@ -1,68 +0,0 @@
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType},
Context, JsResult,
};
/// `TryStart` implements the Opcode Operation for `Opcode::TryStart`
///
/// Operation:
/// - Start of a try block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct TryStart;
impl Operation for TryStart {
const NAME: &'static str = "TryStart";
const INSTRUCTION: &'static str = "INST - TryStart";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let catch = context.vm.read::<u32>();
let finally = context.vm.read::<u32>();
// If a finally exists, push the env to the stack before the try.
if finally != u32::MAX {
context.vm.frame_mut().env_stack.push(
EnvStackEntry::default()
.with_finally_flag()
.with_start_address(finally),
);
}
let fp = context.vm.stack.len() as u32;
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(catch, finally).with_try_flag(fp));
Ok(CompletionType::Normal)
}
}
/// `TryEnd` implements the Opcode Operation for `Opcode::TryEnd`
///
/// Operation:
/// - End of a try block
#[derive(Debug, Clone, Copy)]
pub(crate) struct TryEnd;
impl Operation for TryEnd {
const NAME: &'static str = "TryEnd";
const INSTRUCTION: &'static str = "INST - TryEnd";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut envs_to_pop = 0_usize;
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.is_try_env() {
break;
}
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
Ok(CompletionType::Normal)
}
}

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

@ -5,7 +5,7 @@ use crate::{
string::utf16,
vm::{
call_frame::GeneratorResumeKind,
opcode::{control_flow::Return, Operation},
opcode::{Operation, ReThrow},
CompletionType,
},
Context, JsError, JsResult,
@ -27,78 +27,46 @@ impl Operation for GeneratorNext {
const INSTRUCTION: &'static str = "INST - GeneratorNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
match context.vm.frame().generator_resume_kind {
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
match generator_resume_kind {
GeneratorResumeKind::Normal => Ok(CompletionType::Normal),
GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())),
GeneratorResumeKind::Return => {
assert!(context.vm.pending_exception.is_none());
SetReturnValue::execute(context)?;
Return::execute(context)
ReThrow::execute(context)
}
}
}
}
/// `GeneratorJumpOnResumeKind` implements the Opcode Operation for
/// `Opcode::GeneratorJumpOnResumeKind`
/// `JumpIfNotResumeKind` implements the Opcode Operation for `Opcode::JumpIfNotResumeKind`
///
/// Operation:
/// - Jumps to the specified instruction if the current resume kind is `Return`.
/// - Jumps to the specified address if the resume kind is not equal.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorJumpOnResumeKind;
pub(crate) struct JumpIfNotResumeKind;
impl Operation for GeneratorJumpOnResumeKind {
const NAME: &'static str = "GeneratorJumpOnResumeKind";
const INSTRUCTION: &'static str = "INST - GeneratorJumpOnResumeKind";
impl Operation for JumpIfNotResumeKind {
const NAME: &'static str = "JumpIfNotResumeKind";
const INSTRUCTION: &'static str = "INST - JumpIfNotResumeKind";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let normal = context.vm.read::<u32>();
let throw = context.vm.read::<u32>();
let r#return = context.vm.read::<u32>();
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => context.vm.frame_mut().pc = normal,
GeneratorResumeKind::Throw => context.vm.frame_mut().pc = throw,
GeneratorResumeKind::Return => context.vm.frame_mut().pc = r#return,
}
Ok(CompletionType::Normal)
}
}
let exit = context.vm.read::<u32>();
let resume_kind = context.vm.read::<u8>();
/// `GeneratorSetReturn` implements the Opcode Operation for `Opcode::GeneratorSetReturn`
///
/// Operation:
/// - Sets the current generator resume kind to `Return`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorSetReturn;
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
context.vm.push(generator_resume_kind);
impl Operation for GeneratorSetReturn {
const NAME: &'static str = "GeneratorSetReturn";
const INSTRUCTION: &'static str = "INST - GeneratorSetReturn";
if generator_resume_kind as u8 != resume_kind {
context.vm.frame_mut().pc = exit;
}
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
Ok(CompletionType::Normal)
}
}
/// `GeneratorResumeReturn` implements the Opcode Operation for `Opcode::GeneratorResumeReturn`
///
/// Operation:
/// - Resumes a generator with a return completion.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorResumeReturn;
impl Operation for GeneratorResumeReturn {
const NAME: &'static str = "GeneratorResumeReturn";
const INSTRUCTION: &'static str = "INST - GeneratorResumeReturn";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw {
return Err(JsError::from_opaque(context.vm.pop()));
}
Return::execute(context)
}
}
/// `GeneratorDelegateNext` implements the Opcode Operation for `Opcode::GeneratorDelegateNext`
///
/// Operation:
@ -113,6 +81,8 @@ impl Operation for GeneratorDelegateNext {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let throw_method_undefined = context.vm.read::<u32>();
let return_method_undefined = context.vm.read::<u32>();
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
let received = context.vm.pop();
// Preemptively popping removes the iterator from the iterator stack if any operation
@ -124,7 +94,7 @@ impl Operation for GeneratorDelegateNext {
.pop()
.expect("iterator stack should have at least an iterator");
match std::mem::take(&mut context.vm.frame_mut().generator_resume_kind) {
match generator_resume_kind {
GeneratorResumeKind::Normal => {
let result = iterator_record.next_method().call(
&iterator_record.iterator().clone().into(),
@ -133,6 +103,7 @@ impl Operation for GeneratorDelegateNext {
)?;
context.vm.push(false);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
}
GeneratorResumeKind::Throw => {
let throw = iterator_record
@ -146,6 +117,7 @@ impl Operation for GeneratorDelegateNext {
)?;
context.vm.push(false);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
} else {
let error = JsNativeError::typ()
.with_message("iterator does not have a throw method")
@ -166,6 +138,7 @@ impl Operation for GeneratorDelegateNext {
)?;
context.vm.push(true);
context.vm.push(result);
context.vm.push(GeneratorResumeKind::Normal);
} else {
context.vm.push(received);
context.vm.frame_mut().pc = return_method_undefined;
@ -205,10 +178,12 @@ impl Operation for GeneratorDelegateResume {
.pop()
.expect("iterator stack should have at least an iterator");
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
let result = context.vm.pop();
let is_return = context.vm.pop().to_boolean();
if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw {
if generator_resume_kind == GeneratorResumeKind::Throw {
return Err(JsError::from_opaque(result));
}

7
boa_engine/src/vm/opcode/generator/yield_stm.rs

@ -15,9 +15,8 @@ impl Operation for GeneratorYield {
const NAME: &'static str = "GeneratorYield";
const INSTRUCTION: &'static str = "INST - GeneratorYield";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
context.vm.frame_mut().r#yield = true;
Ok(CompletionType::Return)
fn execute(_context: &mut Context<'_>) -> JsResult<CompletionType> {
Ok(CompletionType::Yield)
}
}
@ -75,7 +74,7 @@ impl Operation for AsyncGeneratorYield {
}
};
context.vm.frame_mut().generator_resume_kind = resume_kind;
context.vm.push(resume_kind);
Ok(CompletionType::Normal)
} else {

68
boa_engine/src/vm/opcode/iteration/iterator.rs

@ -34,6 +34,35 @@ impl Operation for IteratorNext {
}
}
/// `IteratorNextWithoutPop` implements the Opcode Operation for `Opcode::IteratorNextWithoutPop`
///
/// Operation:
/// - Calls the `next` method of `iterator`, updating its record with the next value.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorNextWithoutPop;
impl Operation for IteratorNextWithoutPop {
const NAME: &'static str = "IteratorNextWithoutPop";
const INSTRUCTION: &'static str = "INST - IteratorNextWithoutPop";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
let result = iterator.step(context);
context.vm.frame_mut().iterators.push(iterator);
result?;
Ok(CompletionType::Normal)
}
}
/// `IteratorFinishAsyncNext` implements the Opcode Operation for `Opcode::IteratorFinishAsyncNext`.
///
/// Operation:
@ -54,10 +83,11 @@ impl Operation for IteratorFinishAsyncNext {
.pop()
.expect("iterator on the call frame must exist");
if matches!(
context.vm.frame().generator_resume_kind,
GeneratorResumeKind::Throw
) {
let generator_resume_kind = context.vm.pop().to_generator_resume_kind();
if matches!(generator_resume_kind, GeneratorResumeKind::Throw) {
context.vm.push(generator_resume_kind);
// If after awaiting the `next` call the iterator returned an error, it can be considered
// as poisoned, meaning we can remove it from the iterator stack to avoid calling
// cleanup operations on it.
@ -69,6 +99,8 @@ impl Operation for IteratorFinishAsyncNext {
iterator.update_result(next_result, context)?;
context.vm.frame_mut().iterators.push(iterator);
context.vm.push(generator_resume_kind);
Ok(CompletionType::Normal)
}
}
@ -129,6 +161,34 @@ impl Operation for IteratorValue {
}
}
/// `IteratorValueWithoutPop` implements the Opcode Operation for `Opcode::IteratorValueWithoutPop`
///
/// Operation:
/// - Gets the `value` property of the current iterator record.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorValueWithoutPop;
impl Operation for IteratorValueWithoutPop {
const NAME: &'static str = "IteratorValueWithoutPop";
const INSTRUCTION: &'static str = "INST - IteratorValueWithoutPop";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator on the call frame must exist");
let value = iterator.value(context);
context.vm.frame_mut().iterators.push(iterator);
context.vm.push(value?);
Ok(CompletionType::Normal)
}
}
/// `IteratorDone` implements the Opcode Operation for `Opcode::IteratorDone`
///
/// Operation:

117
boa_engine/src/vm/opcode/iteration/loop_ops.rs

@ -1,120 +1,31 @@
use crate::JsNativeError;
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType},
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
/// `IteratorLoopStart` implements the Opcode Operation for `Opcode::IteratorLoopStart`
/// `IncrementLoopIteration` implements the Opcode Operation for `Opcode::IncrementLoopIteration`.
///
/// Operation:
/// - Push iterator loop start marker.
/// - Increment loop itearation count.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorLoopStart;
pub(crate) struct IncrementLoopIteration;
impl Operation for IteratorLoopStart {
const NAME: &'static str = "IteratorLoopStart";
const INSTRUCTION: &'static str = "INST - IteratorLoopStart";
impl Operation for IncrementLoopIteration {
const NAME: &'static str = "IncrementLoopIteration";
const INSTRUCTION: &'static str = "INST - IncrementLoopIteration";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
let previous_iteration_count = context.vm.frame_mut().loop_iteration_count;
// Create and push loop evironment entry.
let entry = EnvStackEntry::new(start, exit)
.with_iterator_loop_flag(1, (context.vm.frame().iterators.len() - 1) as u32);
context.vm.frame_mut().env_stack.push(entry);
Ok(CompletionType::Normal)
}
}
/// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart`
///
/// Operation:
/// - Push loop start marker.
#[derive(Debug, Clone, Copy)]
pub(crate) struct LoopStart;
impl Operation for LoopStart {
const NAME: &'static str = "LoopStart";
const INSTRUCTION: &'static str = "INST - LoopStart";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
// Create and push loop evironment entry.
let entry = EnvStackEntry::new(start, exit).with_loop_flag(1);
context.vm.frame_mut().env_stack.push(entry);
Ok(CompletionType::Normal)
}
}
/// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue`.
///
/// Operation:
/// - Pushes a clean environment onto the frame's `EnvEntryStack`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct LoopContinue;
impl Operation for LoopContinue {
const NAME: &'static str = "LoopContinue";
const INSTRUCTION: &'static str = "INST - LoopContinue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// 1. Clean up the previous environment.
let env = context
.vm
.frame_mut()
.env_stack
.last_mut()
.expect("loop environment must be present");
let env_num = env.env_num();
env.clear_env_num();
if let Some(previous_iteration_count) = env.as_loop_iteration_count() {
env.increase_loop_iteration_count();
let max = context.vm.runtime_limits.loop_iteration_limit();
if previous_iteration_count > max {
let env_truncation_len = context.vm.environments.len().saturating_sub(env_num);
context.vm.environments.truncate(env_truncation_len);
return Err(JsNativeError::runtime_limit()
.with_message(format!("Maximum loop iteration limit {max} exceeded"))
.into());
}
let max = context.vm.runtime_limits.loop_iteration_limit();
if previous_iteration_count > max {
return Err(JsNativeError::runtime_limit()
.with_message(format!("Maximum loop iteration limit {max} exceeded"))
.into());
}
let env_truncation_len = context.vm.environments.len().saturating_sub(env_num);
context.vm.environments.truncate(env_truncation_len);
Ok(CompletionType::Normal)
}
}
/// `LoopEnd` implements the Opcode Operation for `Opcode::LoopEnd`
///
/// Operation:
/// - Clean up environments at the end of a loop.
#[derive(Debug, Clone, Copy)]
pub(crate) struct LoopEnd;
impl Operation for LoopEnd {
const NAME: &'static str = "LoopEnd";
const INSTRUCTION: &'static str = "INST - LoopEnd";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let mut envs_to_pop = 0_usize;
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
envs_to_pop += env_entry.env_num();
if env_entry.is_loop_env() {
break;
}
}
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop);
context.vm.environments.truncate(env_truncation_len);
context.vm.frame_mut().loop_iteration_count = previous_iteration_count.wrapping_add(1);
Ok(CompletionType::Normal)
}
}

39
boa_engine/src/vm/opcode/jump/mod.rs

@ -1,6 +1,6 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsResult,
Context, JsResult, JsValue,
};
/// `Jump` implements the Opcode Operation for `Opcode::Jump`
@ -83,7 +83,7 @@ impl Operation for JumpIfNotUndefined {
}
}
/// `JumpIfUndefined` implements the Opcode Operation for `Opcode::JumpIfUndefined`
/// `JumpIfNullOrUndefined` implements the Opcode Operation for `Opcode::JumpIfNullOrUndefined`
///
/// Operation:
/// - Conditional jump to address.
@ -105,3 +105,38 @@ impl Operation for JumpIfNullOrUndefined {
Ok(CompletionType::Normal)
}
}
/// `JumpTable` implements the Opcode Operation for `Opcode::JumpTable`
///
/// Operation:
/// - Conditional jump to address.
#[derive(Debug, Clone, Copy)]
pub(crate) struct JumpTable;
impl Operation for JumpTable {
const NAME: &'static str = "JumpTable";
const INSTRUCTION: &'static str = "INST - JumpTable";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let count = context.vm.read::<u32>();
let default = context.vm.read::<u32>();
let value = context.vm.pop();
if let JsValue::Integer(value) = &value {
let value = *value as u32;
let mut target = None;
for i in 0..count {
let address = context.vm.read::<u32>();
if i + 1 == value {
target = Some(address);
}
}
context.vm.frame_mut().pc = target.unwrap_or(default);
return Ok(CompletionType::Normal);
}
unreachable!("expected positive integer, got {value:?}")
}
}

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

@ -1117,61 +1117,52 @@ generate_impl! {
/// Stack: value **=>** value
JumpIfNullOrUndefined,
/// Throw exception
/// Jump table that jumps depending on top value of the stack.
///
/// Operands:
/// This is used to handle special cases when we call `continue`, `break` or `return` in a try block,
/// that has finally block.
///
/// Stack: value **=>**
Throw,
/// Throw a new `TypeError` exception
/// Operands: count: `u32`, default: `u32`, address: `u32` * count
///
/// Operands: message: u32
///
/// Stack: **=>**
ThrowNewTypeError,
/// Stack: value: [`i32`] **=>**
JumpTable,
/// Start of a try block.
/// Throw exception.
///
/// Operands: next_address: `u32`, finally_address: `u32`
///
/// Stack: **=>**
TryStart,
/// End of a try block.
/// This sets pending exception and searches for an exception handler.
///
/// Operands:
///
/// Stack: **=>**
TryEnd,
/// Stack: value **=>**
Throw,
/// Start of a finally block.
///
/// Operands:
/// Rethrow thrown exception.
///
/// Stack: **=>**
FinallyStart,
/// End of a finally block.
/// This is also used to handle generator `return()` call, we throw an empty exception, by setting pending exception to [`None`],
/// propagating it and calling finally code until there is no exception handler left, in that case we consume the empty exception and return
/// from the generator.
///
/// Operands:
///
/// Stack: **=>**
FinallyEnd,
ReThrow,
/// Jumps to a target location and pops the environments involved.
/// Get the thrown pending exception and push on the stack.
///
/// Operands: Jump Address: u32, Target address: u32
/// If there is no pending exception, which can happend if we are handling `return()` call on generator,
/// then we rethrow the empty exception. See [`Opcode::ReThrow`].
///
/// Operands:
///
/// Stack: loop_return_value **=>**
Break,
/// Stack: **=>** exception
Exception,
/// Sets the `AbruptCompletionRecord` for a delayed continue
/// Throw a new `TypeError` exception
///
/// Operands: Jump Address: u32, Target address: u32,
/// Operands: message: u32
///
/// Stack: loop_return_value **=>**
Continue,
/// Stack: **=>**
ThrowNewTypeError,
/// Pops value converts it to boolean and pushes it back.
///
@ -1377,42 +1368,14 @@ generate_impl! {
/// Stack: **=>**
PopEnvironment,
/// Push loop start marker.
///
/// Operands:
/// - start: `u32`
/// - exit: `u32`
///
/// Stack: **=>**
LoopStart,
/// Clean up environments when a loop continues.
///
/// Operands:
/// Increment loop itearation count.
///
/// Stack: **=>**
LoopContinue,
/// Clean up environments at the end of a loop and return it's value.
/// Used for limiting the loop iteration.
///
/// Operands:
///
/// Stack: **=>** value
LoopEnd,
/// Push labelled start marker.
///
/// Operands: Exit Address: u32,
///
/// Stack: **=>**
LabelledStart,
/// Clean up environments at the end of a labelled block.
///
/// Operands:
///
/// Stack: **=>**
LabelledEnd,
IncrementLoopIteration,
/// Creates the ForInIterator of an object.
///
@ -1421,15 +1384,6 @@ generate_impl! {
/// Iterator Stack: `iterator`
CreateForInIterator,
/// Push iterator loop start marker.
///
/// Operands:
/// - start: `u32`
/// - exit: `u32`
///
/// Stack: **=>**
IteratorLoopStart,
/// Gets the iterator of an object.
///
/// Operands:
@ -1455,6 +1409,13 @@ generate_impl! {
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorNext,
/// Calls the `next` method of `iterator`, updating its record with the next value.
///
/// Operands:
///
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorNextWithoutPop,
/// Returns `true` if the current iterator is done, or `false` otherwise
///
/// Stack: **=>** done
@ -1467,19 +1428,26 @@ generate_impl! {
///
/// Operands:
///
/// Stack: `next_result` **=>**
/// Stack: `next_result`, `resume_kind` **=>** `resume_kind`
///
/// Iterator Stack: iterator **=>** iterator
IteratorFinishAsyncNext,
/// - Gets the `value` property of the current iterator record.
/// Gets the `value` property of the current iterator record.
///
/// Stack: **=>** `value`
///
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorValue,
/// - Gets the last iteration result of the current iterator record.
/// Gets the `value` property of the current iterator record.
///
/// Stack: **=>** `value`
///
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorValueWithoutPop,
/// Gets the last iteration result of the current iterator record.
///
/// Stack: **=>** `result`
///
@ -1566,22 +1534,18 @@ generate_impl! {
///
/// Operands:
///
/// Stack: value **=>** received
/// Stack: **=>** resume_kind, received
GeneratorYield,
/// Resumes the current generator function.
///
/// Operands:
///
/// Stack: received **=>**
GeneratorNext,
/// Resumes a generator with a return completion.
/// If the `resume_kind` is `Throw`, then the value is poped and thrown, otherwise if `Return`
/// we pop the value, set it as the return value and throw and empty exception. See [`Opcode::ReThrow`].
///
/// Operands:
///
/// Stack: **=>**
GeneratorResumeReturn,
/// Stack: `resume_kind`, value **=>** value
GeneratorNext,
/// Yields from the current async generator execution.
///
@ -1590,22 +1554,12 @@ generate_impl! {
/// Stack: value **=>** received
AsyncGeneratorYield,
/// Jumps to the specified instruction for each resume kind.
///
/// Operands:
/// - normal: u32,
/// - throw: u32,
/// - return: u32,
///
/// Stack:
GeneratorJumpOnResumeKind,
/// Sets the current generator resume kind to `Return`.
/// Jumps to the specified address if the resume kind is not equal.
///
/// Operands:
/// Operands: `exit`: `u32`, `resume_kind`: `u8`.
///
/// Stack:
GeneratorSetReturn,
/// Stack: `resume_kind` **=>** `resume_kind`
JumpIfNotResumeKind,
/// Delegates the current async generator function to another iterator.
///
@ -1796,6 +1750,22 @@ generate_impl! {
Reserved55 => Reserved,
/// Reserved [`Opcode`].
Reserved56 => Reserved,
/// Reserved [`Opcode`].
Reserved57 => Reserved,
/// Reserved [`Opcode`].
Reserved58 => Reserved,
/// Reserved [`Opcode`].
Reserved59 => Reserved,
/// Reserved [`Opcode`].
Reserved60 => Reserved,
/// Reserved [`Opcode`].
Reserved61 => Reserved,
/// Reserved [`Opcode`].
Reserved62 => Reserved,
/// Reserved [`Opcode`].
Reserved63 => Reserved,
/// Reserved [`Opcode`].
Reserved64 => Reserved,
}
}

1
boa_engine/src/vm/opcode/pop/mod.rs

@ -33,7 +33,6 @@ impl Operation for PopEnvironment {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
context.vm.environments.pop();
context.vm.frame_mut().dec_frame_env_stack();
Ok(CompletionType::Normal)
}
}

2
boa_engine/src/vm/opcode/push/environment.rs

@ -22,7 +22,6 @@ impl Operation for PushDeclarativeEnvironment {
[compile_environments_index as usize]
.clone();
context.vm.environments.push_lexical(compile_environment);
context.vm.frame_mut().inc_frame_env_stack();
Ok(CompletionType::Normal)
}
}
@ -67,7 +66,6 @@ impl Operation for PushObjectEnvironment {
let object = object.to_object(context)?;
context.vm.environments.push_object(object);
context.vm.frame_mut().inc_frame_env_stack();
Ok(CompletionType::Normal)
}
}

29
docs/boa_object.md

@ -30,27 +30,30 @@ This function returns the compiled bytecode of a function as a string,
>> $boa.function.bytecode(add)
"
------------------------Compiled Output: 'add'------------------------
Location Count Opcode Operands
000000 0000 DefInitArg 0000: 'a'
000005 0001 DefInitArg 0001: 'b'
000010 0002 RestParameterPop
000011 0003 GetName 0000: 'a'
000016 0004 GetName 0001: 'b'
000021 0005 Add
000022 0006 Return
000023 0007 PushUndefined
000024 0008 Return
Location Count Handler Opcode Operands
000000 0000 none PutLexicalValue 0000: 'x'
000005 0001 none PutLexicalValue 0001: 'y'
000010 0002 none RestParameterPop
000011 0003 none GetName 0000: 'x'
000016 0004 none GetName 0001: 'y'
000021 0005 none Add
000022 0006 none SetReturnValue
000023 0007 none Return
000024 0008 none Return
Literals:
<empty>
Bindings:
0000: a
0001: b
0000: x
0001: y
Functions:
<empty>
Handlers:
<empty>
"
>>
```

27
docs/vm.md

@ -17,12 +17,13 @@ Outputs:
```text
----------------------Compiled Output: '<main>'-----------------------
Location Count Opcode Operands
Location Count Handler Opcode Operands
000001 0000 PushOne
000006 0001 DefInitLet 0000: 'a'
000008 0002 PushInt8 2
000013 0003 DefInitLet 0001: 'b'
000000 0000 none PushOne
000001 0001 none PutLexicalValue 0000: 'a'
000006 0002 none PushInt8 2
000008 0003 none PutLexicalValue 0001: 'b'
000013 0004 none Return
Literals:
<empty>
@ -34,14 +35,18 @@ Bindings:
Functions:
<empty>
Handlers:
<empty>
------------------------------------------ VM Start ------------------------------------------
----------------------------------------- Call Frame -----------------------------------------
Time Opcode Operands Top Of Stack
386μs PushOne 1
6μs DefInitLet 0000: 'a' <empty>
1μs PushInt8 2 2
2μs DefInitLet 0001: 'b' <empty>
6μs PushOne 1
7μs PutLexicalValue 0000: 'a' <empty>
0μs PushInt8 2 2
1μs PutLexicalValue 0001: 'b' <empty>
0μs Return <empty>
Stack:
<empty>
@ -56,11 +61,13 @@ The above output contains the following information:
- `Compiled Output`: The bytecode.
- `Location`: Location of the instruction (instructions are not the same size).
- `Count`: Instruction count.
- `Handler`: Exception handler, if the instruction throws an exception, which handler is responsible for that instruction and where it would jump. Additionally `>` denotes the beggining of a handler and `<` the end.
- `Opcode`: Opcode name.
- `Operands`: The operands of the opcode.
- `Literals`: The literals used by the bytecode (like strings).
- `Bindings`: Binding names used by the bytecode.
- `Functions`: Function names use by the bytecode.
- `Handlers`: Exception handlers use by the bytecode, it contains how many values should be on the stack and evironments (relative to `CallFrame`'s frame pointers).
- The code being executed (marked by `Vm Start` or `Call Frame`).
- `Time`: The amount of time that instruction took to execute.
- `Opcode`: Opcode name.

Loading…
Cancel
Save