Browse Source

Try-catch-block control flow fix/refactor (#2568)

<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->

This Pull Request is meant to address #1900. While working on it, there was a decent amount of refactoring/restructuring. Initially, I had kept with the current approach of just keeping track of a kind and counter on the environment stack, especially because that kept the size of each stack entry to a minimum. 

I did, however, make a switch to having the opcode create the `EnvStackEntry` with a start address and exit address for a bit more precision.

It changes the following:

- Consolidates `loop_env_stack` and `try_env_stack` into one `EnvStackEntry` struct.
- Changes `Continue`, `Break`, `LoopStart`, `LoopContinue`, `FinallyStart`, `FinallyEnd` and various others. Most of this primarily revolves around the creating of `EnvStackEntry` and interacting with the `env_stack`. 
- Changes/updates the try-catch-finally, break and continue statement compilations as necessary
- Adds an `AbruptCompletionRecord` in place of the `finally_jump` vector.
- Adds some tests for try-catch-finally blocks with breaks.
pull/2588/head
Kevin 2 years ago
parent
commit
c2809ef6f1
  1. 234
      boa_engine/src/bytecompiler/jump_control.rs
  2. 6
      boa_engine/src/bytecompiler/statement/block.rs
  3. 104
      boa_engine/src/bytecompiler/statement/break.rs
  4. 149
      boa_engine/src/bytecompiler/statement/continue.rs
  5. 5
      boa_engine/src/bytecompiler/statement/labelled.rs
  6. 54
      boa_engine/src/bytecompiler/statement/loop.rs
  7. 5
      boa_engine/src/bytecompiler/statement/switch.rs
  8. 96
      boa_engine/src/bytecompiler/statement/try.rs
  9. 134
      boa_engine/src/tests.rs
  10. 75
      boa_engine/src/vm/call_frame/abrupt_record.rs
  11. 143
      boa_engine/src/vm/call_frame/env_stack.rs
  12. 113
      boa_engine/src/vm/call_frame/mod.rs
  13. 17
      boa_engine/src/vm/code_block.rs
  14. 42
      boa_engine/src/vm/flowgraph/mod.rs
  15. 135
      boa_engine/src/vm/mod.rs
  16. 102
      boa_engine/src/vm/opcode/control_flow/break.rs
  17. 97
      boa_engine/src/vm/opcode/control_flow/catch.rs
  18. 178
      boa_engine/src/vm/opcode/control_flow/finally.rs
  19. 55
      boa_engine/src/vm/opcode/control_flow/labelled.rs
  20. 11
      boa_engine/src/vm/opcode/control_flow/mod.rs
  21. 83
      boa_engine/src/vm/opcode/control_flow/try.rs
  22. 8
      boa_engine/src/vm/opcode/generator/mod.rs
  23. 3
      boa_engine/src/vm/opcode/iteration/for_await.rs
  24. 3
      boa_engine/src/vm/opcode/iteration/for_in.rs
  25. 72
      boa_engine/src/vm/opcode/iteration/loop_ops.rs
  26. 44
      boa_engine/src/vm/opcode/jump/break.rs
  27. 3
      boa_engine/src/vm/opcode/jump/mod.rs
  28. 43
      boa_engine/src/vm/opcode/mod.rs
  29. 11
      boa_engine/src/vm/opcode/pop/mod.rs
  30. 80
      boa_engine/src/vm/opcode/promise/mod.rs
  31. 3
      boa_engine/src/vm/opcode/push/environment.rs
  32. 58
      boa_engine/src/vm/opcode/return_stm/mod.rs
  33. 4
      boa_engine/src/vm/opcode/throw/mod.rs
  34. 164
      boa_engine/src/vm/opcode/try_catch/mod.rs

234
boa_engine/src/bytecompiler/jump_control.rs

@ -9,24 +9,19 @@
//! [try spec]: https://tc39.es/ecma262/#sec-try-statement //! [try spec]: https://tc39.es/ecma262/#sec-try-statement
//! [labelled spec]: https://tc39.es/ecma262/#sec-labelled-statements //! [labelled spec]: https://tc39.es/ecma262/#sec-labelled-statements
use crate::{ use crate::bytecompiler::{ByteCompiler, Label};
bytecompiler::{ByteCompiler, Label},
vm::Opcode,
};
use bitflags::bitflags; use bitflags::bitflags;
use boa_interner::Sym; use boa_interner::Sym;
use std::mem::size_of;
/// Boa's `ByteCompiler` jump information tracking struct. /// Boa's `ByteCompiler` jump information tracking struct.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct JumpControlInfo { pub(crate) struct JumpControlInfo {
label: Option<Sym>, label: Option<Sym>,
start_address: u32, start_address: u32,
decl_envs: u32,
flags: JumpControlInfoFlags, flags: JumpControlInfoFlags,
set_jumps: Vec<Label>,
breaks: Vec<Label>, breaks: Vec<Label>,
try_continues: Vec<Label>, try_continues: Vec<Label>,
finally_start: Option<Label>,
} }
bitflags! { bitflags! {
@ -37,8 +32,9 @@ bitflags! {
const TRY_BLOCK = 0b0000_0100; const TRY_BLOCK = 0b0000_0100;
const LABELLED = 0b0000_1000; const LABELLED = 0b0000_1000;
const IN_CATCH = 0b0001_0000; const IN_CATCH = 0b0001_0000;
const HAS_FINALLY = 0b0010_0000; const IN_FINALLY = 0b0010_0000;
const FOR_OF_IN_LOOP = 0b0100_0000; const HAS_FINALLY = 0b0100_0000;
const FOR_OF_IN_LOOP = 0b1000_0000;
} }
} }
@ -53,17 +49,15 @@ impl Default for JumpControlInfo {
Self { Self {
label: None, label: None,
start_address: u32::MAX, start_address: u32::MAX,
decl_envs: 0,
flags: JumpControlInfoFlags::default(), flags: JumpControlInfoFlags::default(),
set_jumps: Vec::new(),
breaks: Vec::new(), breaks: Vec::new(),
try_continues: Vec::new(), try_continues: Vec::new(),
finally_start: None,
} }
} }
} }
// ---- `JumpControlInfo` Creation Methods ---- // /// ---- `JumpControlInfo` Creation Methods ----
impl JumpControlInfo { impl JumpControlInfo {
pub(crate) const fn with_label(mut self, label: Option<Sym>) -> Self { pub(crate) const fn with_label(mut self, label: Option<Sym>) -> Self {
self.label = label; self.label = label;
@ -106,8 +100,7 @@ impl JumpControlInfo {
} }
} }
// ---- `JumpControlInfo` const fn methods ---- // /// ---- `JumpControlInfo` const fn methods ----
impl JumpControlInfo { impl JumpControlInfo {
pub(crate) const fn label(&self) -> Option<Sym> { pub(crate) const fn label(&self) -> Option<Sym> {
self.label self.label
@ -137,23 +130,20 @@ impl JumpControlInfo {
self.flags.contains(JumpControlInfoFlags::IN_CATCH) self.flags.contains(JumpControlInfoFlags::IN_CATCH)
} }
pub(crate) const fn has_finally(&self) -> bool { pub(crate) const fn in_finally(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::HAS_FINALLY) self.flags.contains(JumpControlInfoFlags::IN_FINALLY)
} }
pub(crate) const fn finally_start(&self) -> Option<Label> { pub(crate) const fn has_finally(&self) -> bool {
self.finally_start self.flags.contains(JumpControlInfoFlags::HAS_FINALLY)
} }
pub(crate) const fn for_of_in_loop(&self) -> bool { pub(crate) const fn for_of_in_loop(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::FOR_OF_IN_LOOP) self.flags.contains(JumpControlInfoFlags::FOR_OF_IN_LOOP)
} }
pub(crate) const fn decl_envs(&self) -> u32 {
self.decl_envs
}
} }
/// ---- `JumpControlInfo` interaction methods ----
impl JumpControlInfo { impl JumpControlInfo {
/// Sets the `label` field of `JumpControlInfo`. /// Sets the `label` field of `JumpControlInfo`.
pub(crate) fn set_label(&mut self, label: Option<Sym>) { pub(crate) fn set_label(&mut self, label: Option<Sym>) {
@ -171,19 +161,9 @@ impl JumpControlInfo {
self.flags.set(JumpControlInfoFlags::IN_CATCH, value); self.flags.set(JumpControlInfoFlags::IN_CATCH, value);
} }
/// Sets the `finally_start` field of `JumpControlInfo`. /// Set the `in_finally` field of `JumpControlInfo`.
pub(crate) fn set_finally_start(&mut self, label: Label) { pub(crate) fn set_in_finally(&mut self, value: bool) {
self.finally_start = Some(label); self.flags.set(JumpControlInfoFlags::IN_FINALLY, value);
}
/// Increments the `decl_env` field of `JumpControlInfo`.
pub(crate) fn inc_decl_envs(&mut self) {
self.decl_envs += 1;
}
/// Decrements the `decl_env` field of `JumpControlInfo`.
pub(crate) fn dec_decl_envs(&mut self) {
self.decl_envs -= 1;
} }
/// Pushes a `Label` onto the `break` vector of `JumpControlInfo`. /// Pushes a `Label` onto the `break` vector of `JumpControlInfo`.
@ -195,6 +175,10 @@ impl JumpControlInfo {
pub(crate) fn push_try_continue_label(&mut self, try_continue_label: Label) { pub(crate) fn push_try_continue_label(&mut self, try_continue_label: Label) {
self.try_continues.push(try_continue_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`. // `JumpControlInfo` related methods that are implemented on `ByteCompiler`.
@ -211,18 +195,23 @@ impl ByteCompiler<'_, '_> {
self.jump_info.last_mut() self.jump_info.last_mut()
} }
pub(crate) fn set_jump_control_finally_start(&mut self, start: Label) { 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 set_jump_control_in_finally(&mut self, value: bool) {
if !self.jump_info.is_empty() { if !self.jump_info.is_empty() {
let info = self let info = self
.jump_info .jump_info
.last_mut() .last_mut()
.expect("must have try control label"); .expect("must have try control label");
assert!(info.is_try_block()); assert!(info.is_try_block());
info.set_finally_start(start); info.set_in_finally(value);
} }
} }
pub(crate) fn set_jump_control_catch_start(&mut self, value: bool) { pub(crate) fn set_jump_control_in_catch(&mut self, value: bool) {
if !self.jump_info.is_empty() { if !self.jump_info.is_empty() {
let info = self let info = self
.jump_info .jump_info
@ -233,28 +222,39 @@ impl ByteCompiler<'_, '_> {
} }
} }
/// Emits the `PushDeclarativeEnvironment` and updates the current jump info to track environments. // ---- Labelled Statement JumpControlInfo methods ---- //
pub(crate) fn emit_and_track_decl_env(&mut self) -> (Label, Label) {
let pushed_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); /// Pushes a `LabelledStatement`'s `JumpControlInfo` onto the `jump_info` stack.
if !self.jump_info.is_empty() { pub(crate) fn push_labelled_control_info(&mut self, label: Sym, start_address: u32) {
let current_jump_info = self let new_info = JumpControlInfo::default()
.jump_info .with_labelled_block_flag(true)
.last_mut() .with_label(Some(label))
.expect("Jump info must exist as the vector is not empty"); .with_start_address(start_address);
current_jump_info.inc_decl_envs(); self.jump_info.push(new_info);
} }
pushed_env
/// Pops and handles the info for a label's `JumpControlInfo`
///
/// # Panic
/// - Will panic if `jump_info` stack is empty.
/// - Will panic if popped `JumpControlInfo` is not for a `LabelledStatement`.
pub(crate) fn pop_labelled_control_info(&mut self) {
assert!(!self.jump_info.is_empty());
let info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_labelled());
for label in info.breaks {
self.patch_jump(label);
} }
/// Emits the `PopEnvironment` Opcode and updates the current jump that the env is removed. for label in info.try_continues {
pub(crate) fn emit_and_track_pop_env(&mut self) { self.patch_jump_with_target(label, info.start_address);
self.emit_opcode(Opcode::PopEnvironment);
if !self.jump_info.is_empty() {
let current_info = self.jump_info.last_mut().expect("JumpInfo must exist");
current_info.dec_decl_envs();
} }
} }
// ---- `IterationStatement`'s `JumpControlInfo` methods ---- //
/// Pushes an `WhileStatement`, `ForStatement` or `DoWhileStatement`'s `JumpControlInfo` on to the `jump_info` stack.
pub(crate) fn push_loop_control_info(&mut self, label: Option<Sym>, start_address: u32) { pub(crate) fn push_loop_control_info(&mut self, label: Option<Sym>, start_address: u32) {
let new_info = JumpControlInfo::default() let new_info = JumpControlInfo::default()
.with_loop_flag(true) .with_loop_flag(true)
@ -263,6 +263,7 @@ impl ByteCompiler<'_, '_> {
self.jump_info.push(new_info); self.jump_info.push(new_info);
} }
/// Pushes a `ForInOfStatement`'s `JumpControlInfo` on to the `jump_info` stack.
pub(crate) fn push_loop_control_info_for_of_in_loop( pub(crate) fn push_loop_control_info_for_of_in_loop(
&mut self, &mut self,
label: Option<Sym>, label: Option<Sym>,
@ -276,20 +277,30 @@ impl ByteCompiler<'_, '_> {
self.jump_info.push(new_info); self.jump_info.push(new_info);
} }
/// Pops and handles the info for a loop control block's `JumpControlInfo`
///
/// # Panic
/// - Will panic if `jump_info` stack is empty.
/// - Will panic if popped `JumpControlInfo` is not for a loop block.
pub(crate) fn pop_loop_control_info(&mut self) { pub(crate) fn pop_loop_control_info(&mut self) {
let loop_info = self.jump_info.pop().expect("no jump information found"); assert!(!self.jump_info.is_empty());
let info = self.jump_info.pop().expect("no jump information found");
assert!(loop_info.is_loop()); assert!(info.is_loop());
for label in loop_info.breaks { let start_address = info.start_address();
self.patch_jump(label); for label in info.try_continues {
self.patch_jump_with_target(label, start_address);
} }
for label in loop_info.try_continues { for label in info.breaks {
self.patch_jump_with_target(label, loop_info.start_address); self.patch_jump(label);
} }
} }
// ---- `SwitchStatement` `JumpControlInfo` methods ---- //
/// Pushes a `SwitchStatement`'s `JumpControlInfo` on to the `jump_info` stack.
pub(crate) fn push_switch_control_info(&mut self, label: Option<Sym>, start_address: u32) { pub(crate) fn push_switch_control_info(&mut self, label: Option<Sym>, start_address: u32) {
let new_info = JumpControlInfo::default() let new_info = JumpControlInfo::default()
.with_switch_flag(true) .with_switch_flag(true)
@ -298,7 +309,13 @@ impl ByteCompiler<'_, '_> {
self.jump_info.push(new_info); self.jump_info.push(new_info);
} }
/// Pops and handles the info for a switch block's `JumpControlInfo`
///
/// # Panic
/// - Will panic if `jump_info` stack is empty.
/// - Will panic if popped `JumpControlInfo` is not for a switch block.
pub(crate) fn pop_switch_control_info(&mut self) { pub(crate) fn pop_switch_control_info(&mut self) {
assert!(!self.jump_info.is_empty());
let info = self.jump_info.pop().expect("no jump information found"); let info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_switch()); assert!(info.is_switch());
@ -308,14 +325,10 @@ impl ByteCompiler<'_, '_> {
} }
} }
pub(crate) fn push_try_control_info(&mut self, has_finally: bool) { // ---- `TryStatement`'s `JumpControlInfo` methods ---- //
if !self.jump_info.is_empty() {
let start_address = self
.jump_info
.last()
.expect("no jump information found")
.start_address();
/// 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) {
let new_info = JumpControlInfo::default() let new_info = JumpControlInfo::default()
.with_try_block_flag(true) .with_try_block_flag(true)
.with_start_address(start_address) .with_start_address(start_address)
@ -323,65 +336,82 @@ impl ByteCompiler<'_, '_> {
self.jump_info.push(new_info); self.jump_info.push(new_info);
} }
}
pub(crate) fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) { /// Pops and handles the info for a try block's `JumpControlInfo`
if !self.jump_info.is_empty() { ///
/// # 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) {
assert!(!self.jump_info.is_empty());
let mut info = self.jump_info.pop().expect("no jump information found"); let mut info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_try_block()); assert!(info.is_try_block());
let mut breaks = Vec::with_capacity(info.breaks.len()); // Handle breaks. If there is a finally, breaks should go to the finally
if info.has_finally() {
if let Some(finally_start_address) = finally_start_address { for label in info.breaks {
for label in info.try_continues { self.patch_jump_with_target(label, try_end);
if label.index < finally_start_address { }
self.patch_jump_with_target(label, finally_start_address);
} else { } else {
self.patch_jump_with_target(label, info.start_address); // 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;
}
} }
} }
for label in info.breaks { // Handle set_jumps
if label.index < finally_start_address { for label in info.set_jumps {
self.patch_jump_with_target(label, finally_start_address); for jump_info in self.jump_info.iter_mut().rev() {
let Label { mut index } = label; if jump_info.is_loop() || jump_info.is_switch() {
index -= size_of::<Opcode>() as u32; jump_info.breaks.push(label);
index -= size_of::<u32>() as u32; break;
breaks.push(Label { index });
} else {
breaks.push(label);
} }
} }
if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.breaks.append(&mut breaks);
} }
} else if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.breaks.append(&mut info.breaks); // Pass continues down the stack.
if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.try_continues.append(&mut info.try_continues); jump_info.try_continues.append(&mut info.try_continues);
} }
} }
}
pub(crate) fn push_labelled_control_info(&mut self, label: Sym, start_address: u32) { /// Pushes a `TryStatement`'s Finally block `JumpControlInfo` onto the `jump_info` stack.
let new_info = JumpControlInfo::default() pub(crate) fn push_init_finally_control_info(&mut self) {
.with_labelled_block_flag(true) let mut new_info = JumpControlInfo::default().with_try_block_flag(true);
.with_label(Some(label))
.with_start_address(start_address); new_info.set_in_finally(true);
self.jump_info.push(new_info); self.jump_info.push(new_info);
} }
pub(crate) fn pop_labelled_control_info(&mut self) { pub(crate) fn pop_finally_control_info(&mut self) {
let info = self.jump_info.pop().expect("no jump information found"); assert!(!self.jump_info.is_empty());
let mut info = self.jump_info.pop().expect("no jump information found");
assert!(info.is_labelled()); 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;
}
}
}
// Handle breaks in a finally block
for label in info.breaks { for label in info.breaks {
self.patch_jump(label); self.patch_jump(label);
} }
for label in info.try_continues { // Pass continues down the stack.
self.patch_jump_with_target(label, info.start_address); if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.try_continues.append(&mut info.try_continues);
} }
} }
} }

6
boa_engine/src/bytecompiler/statement/block.rs

@ -1,4 +1,4 @@
use crate::{bytecompiler::ByteCompiler, JsResult}; use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult};
use boa_ast::statement::Block; use boa_ast::statement::Block;
@ -11,7 +11,7 @@ impl ByteCompiler<'_, '_> {
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_and_track_decl_env(); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_script_decls(block.statement_list(), configurable_globals); self.create_script_decls(block.statement_list(), configurable_globals);
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?; self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?;
@ -21,7 +21,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump_with_target(push_env.0, num_bindings as u32); self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_and_track_pop_env(); self.emit_opcode(Opcode::PopEnvironment);
Ok(()) Ok(())
} }

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

@ -1,37 +1,88 @@
use boa_ast::statement::Break; use boa_ast::statement::Break;
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsNativeError, JsResult}; use crate::{
bytecompiler::{ByteCompiler, Label},
vm::Opcode,
JsNativeError, JsResult,
};
use boa_interner::Sym;
impl ByteCompiler<'_, '_> { impl ByteCompiler<'_, '_> {
/// Compile a [`Break`] `boa_ast` node /// Compile a [`Break`] `boa_ast` node
pub(crate) fn compile_break(&mut self, node: Break) -> JsResult<()> { pub(crate) fn compile_break(&mut self, node: Break) -> JsResult<()> {
let next = self.next_opcode_location();
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) { if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) {
let in_finally = if let Some(finally_start) = info.finally_start() { let in_finally = info.in_finally();
next >= finally_start.index let in_catch_no_finally = info.in_catch() && !info.has_finally();
} else { let has_finally_or_is_finally = info.has_finally() || info.in_finally();
false
};
let in_catch_no_finally = !info.has_finally() && info.in_catch();
if in_finally { if in_finally {
self.emit_opcode(Opcode::PopIfThrown); self.emit_opcode(Opcode::PopIfThrown);
} }
if in_finally || in_catch_no_finally { if in_finally || in_catch_no_finally {
self.emit_opcode(Opcode::CatchEnd2); self.emit_opcode(Opcode::CatchEnd2);
}
let (break_label, target_jump_label) =
self.emit_opcode_with_two_operands(Opcode::Break);
if let Some(node_label) = node.label() {
self.search_jump_info_label(target_jump_label, node_label)?;
if !has_finally_or_is_finally {
self.search_jump_info_label(break_label, node_label)?;
return Ok(());
}
} else { } else {
self.emit_opcode(Opcode::TryEnd); self.jump_info
.last_mut()
.expect("jump_info must exist to reach this point")
.push_set_jumps(target_jump_label);
} }
self.emit(Opcode::FinallySetJump, &[u32::MAX]);
let info = self
.jump_info
.last_mut()
// TODO: Currently would allow unlabelled breaks in try_blocks with no
// loops or switch. This can prevented by the early error referenced in line 66.
.expect("This try block must exist");
info.push_break_label(break_label);
return Ok(());
} }
let (break_label, envs_to_pop) = self.emit_opcode_with_two_operands(Opcode::Break);
if let Some(label_name) = node.label() { // Emit the break opcode -> (Label, Label)
let (break_label, target_label) = self.emit_opcode_with_two_operands(Opcode::Break);
if node.label().is_some() {
self.search_jump_info_label(
break_label,
node.label().expect("must exist in this block"),
)?;
self.search_jump_info_label(target_label, node.label().expect("must exist"))?;
return Ok(());
};
let info = self
.jump_info
.last_mut()
// TODO: promote to an early error.
.ok_or_else(|| {
JsNativeError::syntax()
.with_message("unlabeled break must be inside loop or switch")
})?;
info.push_break_label(break_label);
info.push_break_label(target_label);
Ok(())
}
fn search_jump_info_label(&mut self, address: Label, node_label: Sym) -> JsResult<()> {
let mut found = false; let mut found = false;
let mut total_envs: u32 = 0;
for info in self.jump_info.iter_mut().rev() { for info in self.jump_info.iter_mut().rev() {
total_envs += info.decl_envs(); if info.label() == Some(node_label) {
if info.label() == Some(label_name) { info.push_break_label(address);
info.push_break_label(break_label);
found = true; found = true;
break; break;
} }
@ -41,29 +92,10 @@ impl ByteCompiler<'_, '_> {
return Err(JsNativeError::syntax() return Err(JsNativeError::syntax()
.with_message(format!( .with_message(format!(
"Cannot use the undeclared label '{}'", "Cannot use the undeclared label '{}'",
self.interner().resolve_expect(label_name) self.interner().resolve_expect(node_label)
)) ))
.into()); .into());
} }
self.patch_jump_with_target(envs_to_pop, total_envs);
} else {
let envs = self
.jump_info
.last()
// TODO: promote to an early error.
.ok_or_else(|| {
JsNativeError::syntax()
.with_message("unlabeled break must be inside loop or switch")
})?
.decl_envs();
self.patch_jump_with_target(envs_to_pop, envs);
self.jump_info
.last_mut()
.expect("cannot throw error as last access would have thrown")
.push_break_label(break_label);
}
Ok(()) Ok(())
} }

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

@ -4,14 +4,9 @@ use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsNativeError, JsResult};
impl ByteCompiler<'_, '_> { impl ByteCompiler<'_, '_> {
pub(crate) fn compile_continue(&mut self, node: Continue) -> JsResult<()> { pub(crate) fn compile_continue(&mut self, node: Continue) -> JsResult<()> {
let next = self.next_opcode_location();
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) { if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) {
let start_address = info.start_address(); let in_finally = info.in_finally();
let in_finally = if let Some(finally_start) = info.finally_start() { let in_finally_or_has_finally = in_finally || info.has_finally();
next > finally_start.index
} else {
false
};
let in_catch_no_finally = !info.has_finally() && info.in_catch(); let in_catch_no_finally = !info.has_finally() && info.in_catch();
if in_finally { if in_finally {
@ -19,63 +14,147 @@ impl ByteCompiler<'_, '_> {
} }
if in_finally || in_catch_no_finally { if in_finally || in_catch_no_finally {
self.emit_opcode(Opcode::CatchEnd2); self.emit_opcode(Opcode::CatchEnd2);
} else { }
self.emit_opcode(Opcode::TryEnd); // 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 emit_for_of_in_exit = 0_u32;
let mut loop_info = None;
for info in items {
if info.label() == Some(node_label) {
loop_info = Some(info);
break;
} }
self.emit(Opcode::FinallySetJump, &[start_address]); if info.for_of_in_loop() {
emit_for_of_in_exit += 1;
}
}
let label = self.jump(); // TODO: promote to an early error.
loop_info.ok_or_else(|| {
JsNativeError::syntax().with_message(format!(
"Cannot use the undeclared label '{}'",
self.context.interner().resolve_expect(node_label)
))
})?;
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
}
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());
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 self.jump_info
.last_mut() .last_mut()
.expect("no jump information found") .expect("no jump information found")
.push_try_continue_label(label); .push_break_label(cont_label);
}
} else { } else {
let mut items = self.jump_info.iter().rev().filter(|info| info.is_loop()); // TODO: Add has finally or in finally here
let address = if let Some(label_name) = node.label() { let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
let mut num_loops = 0; if in_finally_or_has_finally {
let mut emit_for_of_in_exit = 0; self.jump_info
let mut address_info = None; .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()
// TODO: promote to an early error.
.ok_or_else(|| {
JsNativeError::syntax().with_message("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);
};
return Ok(());
} else if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop());
let mut emit_for_of_in_exit = 0_u32;
let mut loop_info = None;
for info in items { for info in items {
if info.label() == node.label() { if info.label() == Some(node_label) {
address_info = Some(info); loop_info = Some(info);
break; break;
} }
num_loops += 1;
if info.for_of_in_loop() { if info.for_of_in_loop() {
emit_for_of_in_exit += 1; emit_for_of_in_exit += 1;
} }
} }
// TODO: promote to an early error. // TODO: promote to an early error.
let address = address_info loop_info.ok_or_else(|| {
.ok_or_else(|| {
JsNativeError::syntax().with_message(format!( JsNativeError::syntax().with_message(format!(
"Cannot use the undeclared label '{}'", "Cannot use the undeclared label '{}'",
self.context.interner().resolve_expect(label_name) self.context.interner().resolve_expect(node_label)
)) ))
})? })?;
.start_address();
for _ in 0..emit_for_of_in_exit { for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
for _ in 0..num_loops {
self.emit_opcode(Opcode::LoopEnd); 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);
}
} }
address
} else { } else {
items 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() .next()
// TODO: promote to an early error. // TODO: promote to an early error.
.ok_or_else(|| { .ok_or_else(|| {
JsNativeError::syntax().with_message("continue must be inside loop") JsNativeError::syntax().with_message("continue must be inside loop")
})? })?;
.start_address() jump_info.push_try_continue_label(cont_label);
}; jump_info.push_try_continue_label(set_label);
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::LoopStart);
self.emit(Opcode::Jump, &[address]);
} }
Ok(()) Ok(())

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

@ -5,6 +5,7 @@ use boa_ast::{
use crate::{ use crate::{
bytecompiler::{ByteCompiler, NodeKind}, bytecompiler::{ByteCompiler, NodeKind},
vm::Opcode,
JsResult, JsResult,
}; };
@ -17,6 +18,7 @@ impl ByteCompiler<'_, '_> {
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
let labelled_loc = self.next_opcode_location(); 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); self.push_labelled_control_info(labelled.label(), labelled_loc);
match labelled.item() { match labelled.item() {
@ -59,7 +61,10 @@ impl ByteCompiler<'_, '_> {
} }
} }
let labelled_end = self.next_opcode_location();
self.patch_jump_with_target(end_label, labelled_end);
self.pop_labelled_control_info(); self.pop_labelled_control_info();
self.emit_opcode(Opcode::LabelledEnd);
Ok(()) Ok(())
} }

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

@ -22,7 +22,7 @@ impl ByteCompiler<'_, '_> {
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_and_track_decl_env(); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.push_empty_loop_jump_control(); self.push_empty_loop_jump_control();
if let Some(init) = for_loop.init() { if let Some(init) = for_loop.init() {
@ -39,10 +39,10 @@ impl ByteCompiler<'_, '_> {
} }
} }
self.emit_opcode(Opcode::LoopStart); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_jump = self.jump(); let initial_jump = self.jump();
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.current_jump_control_mut() self.current_jump_control_mut()
.expect("jump_control must exist as it was just pushed") .expect("jump_control must exist as it was just pushed")
.set_label(label); .set_label(label);
@ -50,7 +50,11 @@ impl ByteCompiler<'_, '_> {
.expect("jump_control must exist as it was just pushed") .expect("jump_control must exist as it was just pushed")
.set_start_address(start_address); .set_start_address(start_address);
self.emit_opcode(Opcode::LoopContinue); let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);
if let Some(final_expr) = for_loop.final_expr() { if let Some(final_expr) = for_loop.final_expr() {
self.compile_expr(final_expr, false)?; self.compile_expr(final_expr, false)?;
} }
@ -74,9 +78,11 @@ impl ByteCompiler<'_, '_> {
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.patch_jump(exit); self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_and_track_pop_env(); self.emit_opcode(Opcode::PopEnvironment);
Ok(()) Ok(())
} }
@ -108,10 +114,13 @@ impl ByteCompiler<'_, '_> {
let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator); let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator);
self.emit_opcode(Opcode::LoopStart); let (loop_start, exit_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address); self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue); let (continue_label, cont_exit_label) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(continue_label, start_address);
self.patch_jump_with_target(loop_start, start_address);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
@ -187,6 +196,8 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit); self.patch_jump(exit);
self.patch_jump(exit_label);
self.patch_jump(cont_exit_label);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose); self.emit_opcode(Opcode::IteratorClose);
@ -226,10 +237,13 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::InitIterator);
} }
self.emit_opcode(Opcode::LoopStart); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address); self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue); let (cont_label, cont_exit_label) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(cont_label, start_address);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
@ -312,6 +326,8 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit); self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(cont_exit_label);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose); self.emit_opcode(Opcode::IteratorClose);
@ -324,17 +340,22 @@ impl ByteCompiler<'_, '_> {
label: Option<Sym>, label: Option<Sym>,
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
self.emit_opcode(Opcode::LoopStart); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.push_loop_control_info(label, start_address); self.push_loop_control_info(label, start_address);
self.emit_opcode(Opcode::LoopContinue); self.patch_jump_with_target(loop_start, start_address);
self.patch_jump_with_target(continue_start, start_address);
self.compile_expr(while_loop.condition(), true)?; self.compile_expr(while_loop.condition(), true)?;
let exit = self.jump_if_false(); let exit = self.jump_if_false();
self.compile_stmt(while_loop.body(), false, configurable_globals)?; self.compile_stmt(while_loop.body(), false, configurable_globals)?;
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
Ok(()) Ok(())
@ -346,12 +367,15 @@ impl ByteCompiler<'_, '_> {
label: Option<Sym>, label: Option<Sym>,
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
self.emit_opcode(Opcode::LoopStart); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_label = self.jump(); let initial_label = self.jump();
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
let (continue_start, continue_exit) =
self.emit_opcode_with_two_operands(Opcode::LoopContinue);
self.patch_jump_with_target(continue_start, start_address);
self.patch_jump_with_target(loop_start, start_address);
self.push_loop_control_info(label, start_address); self.push_loop_control_info(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
let condition_label_address = self.next_opcode_location(); let condition_label_address = self.next_opcode_location();
self.compile_expr(do_while_loop.cond(), true)?; self.compile_expr(do_while_loop.cond(), true)?;
@ -362,6 +386,8 @@ impl ByteCompiler<'_, '_> {
self.compile_stmt(do_while_loop.body(), false, configurable_globals)?; self.compile_stmt(do_while_loop.body(), false, configurable_globals)?;
self.emit(Opcode::Jump, &[condition_label_address]); self.emit(Opcode::Jump, &[condition_label_address]);
self.patch_jump(exit); self.patch_jump(exit);
self.patch_jump(loop_exit);
self.patch_jump(continue_exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);

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

@ -14,10 +14,11 @@ impl ByteCompiler<'_, '_> {
for case in switch.cases() { for case in switch.cases() {
self.create_script_decls(case.body(), configurable_globals); self.create_script_decls(case.body(), configurable_globals);
} }
self.emit_opcode(Opcode::LoopStart); let (start_label, end_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_switch_control_info(None, start_address); self.push_switch_control_info(None, start_address);
self.patch_jump_with_target(start_label, start_address);
self.compile_expr(switch.val(), true)?; self.compile_expr(switch.val(), true)?;
let mut labels = Vec::with_capacity(switch.cases().len()); let mut labels = Vec::with_capacity(switch.cases().len());
@ -40,7 +41,7 @@ impl ByteCompiler<'_, '_> {
} }
self.pop_switch_control_info(); self.pop_switch_control_info();
self.patch_jump(end_label);
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();

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

@ -1,4 +1,8 @@
use boa_ast::{declaration::Binding, operations::bound_names, statement::Try}; use boa_ast::{
declaration::Binding,
operations::bound_names,
statement::{Finally, Try},
};
use crate::{ use crate::{
bytecompiler::{ByteCompiler, Label}, bytecompiler::{ByteCompiler, Label},
@ -13,9 +17,16 @@ impl ByteCompiler<'_, '_> {
use_expr: bool, use_expr: bool,
configurable_globals: bool, configurable_globals: bool,
) -> JsResult<()> { ) -> JsResult<()> {
self.push_try_control_info(t.finally().is_some());
let try_start = self.next_opcode_location(); let try_start = self.next_opcode_location();
self.emit(Opcode::TryStart, &[ByteCompiler::DUMMY_ADDRESS, 0]); let (catch_start, finally_loc) = self.emit_opcode_with_two_operands(Opcode::TryStart);
self.patch_jump_with_target(finally_loc, u32::MAX);
// 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();
}
self.push_try_control_info(t.finally().is_some(), try_start);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
@ -30,17 +41,47 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::TryEnd); self.emit_opcode(Opcode::TryEnd);
let finally = self.jump(); let finally = self.jump();
self.patch_jump(Label { index: try_start }); self.patch_jump(catch_start);
if t.catch().is_some() {
self.compile_catch_stmt(t, use_expr, configurable_globals)?;
}
self.patch_jump(finally);
if let Some(catch) = t.catch() { if let Some(finally) = t.finally() {
self.set_jump_control_catch_start(true); // Pop and push control loops post FinallyStart, as FinallyStart resets flow control variables.
let catch_start = if t.finally().is_some() { // Handle finally header operations
Some(self.emit_opcode_with_operand(Opcode::CatchStart)) 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);
// Compile finally statement body
self.compile_finally_stmt(finally, finally_end, configurable_globals)?;
} else { } else {
None let try_end = self.next_opcode_location();
}; self.pop_try_control_info(try_end);
}
Ok(())
}
pub(crate) fn compile_catch_stmt(
&mut self,
parent_try: &Try,
use_expr: bool,
configurable_globals: bool,
) -> JsResult<()> {
let catch = parent_try
.catch()
.expect("Catch must exist for compile_catch_stmt to have been invoked");
self.set_jump_control_in_catch(true);
let catch_end = self.emit_opcode_with_operand(Opcode::CatchStart);
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
if let Some(binding) = catch.parameter() { if let Some(binding) = catch.parameter() {
match binding { match binding {
Binding::Identifier(ident) => { Binding::Identifier(ident) => {
@ -70,29 +111,24 @@ impl ByteCompiler<'_, '_> {
self.patch_jump_with_target(push_env.0, num_bindings as u32); self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
if let Some(catch_start) = catch_start { if parent_try.finally().is_some() {
self.emit_opcode(Opcode::CatchEnd); self.emit_opcode(Opcode::CatchEnd);
self.patch_jump(catch_start);
} else { } else {
self.emit_opcode(Opcode::CatchEnd2); self.emit_opcode(Opcode::CatchEnd2);
} }
}
self.patch_jump(finally); self.patch_jump(catch_end);
self.set_jump_control_in_finally(false);
if let Some(finally) = t.finally() { Ok(())
self.emit_opcode(Opcode::FinallyStart); }
let finally_start_address = self.next_opcode_location();
self.set_jump_control_finally_start(Label {
index: finally_start_address,
});
self.patch_jump_with_target(
Label {
index: try_start + 4,
},
finally_start_address,
);
pub(crate) fn compile_finally_stmt(
&mut self,
finally: &Finally,
finally_end_label: Label,
configurable_globals: bool,
) -> JsResult<()> {
self.context.push_compile_time_environment(false); self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
@ -107,13 +143,11 @@ impl ByteCompiler<'_, '_> {
let index_compile_environment = self.push_compile_environment(compile_environment); let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32); self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::PopEnvironment);
self.pop_finally_control_info();
self.patch_jump(finally_end_label);
self.emit_opcode(Opcode::FinallyEnd); self.emit_opcode(Opcode::FinallyEnd);
self.pop_try_control_info(Some(finally_start_address));
} else {
self.pop_try_control_info(None);
}
Ok(()) Ok(())
} }

134
boa_engine/src/tests.rs

@ -2189,7 +2189,7 @@ fn break_environment_gauntlet() {
} }
{ {
result = ""; let result = "";
lab_block: { lab_block: {
try { try {
result = "try_block"; result = "try_block";
@ -2995,3 +2995,135 @@ fn unary_operations_on_this() {
assert!(string.contains(pos)); assert!(string.contains(pos));
} }
} }
#[test]
fn try_break_finally_edge_cases() {
let scenario = r#"
var a;
var b;
{
while (true) {
try {
try {
break;
} catch(a) {
} finally {
}
} finally {
}
}
}
{
while (true) {
try {
try {
throw "b";
} catch (b) {
break;
} finally {
a = "foo"
}
} finally {
}
}
}
{
while (true) {
try {
try {
} catch (c) {
} finally {
b = "bar"
break;
}
} finally {
}
}
}
a + b
"#;
assert_eq!(&exec(scenario), "\"foobar\"");
}
#[test]
fn try_break_labels() {
let scenario = r#"
{
var str = '';
outer: {
foo: {
bar: {
while (true) {
try {
try {
break;
} catch(f) {
} finally {
str = "fin";
break foo;
str += "This won't execute";
}
} finally {
str = str + "ally!"
break bar;
}
}
str += " oh no";
}
str += " :)";
}
}
str
}
"#;
assert_eq!(&exec(scenario), "\"finally! :)\"");
}
#[test]
fn break_nested_labels_loops_and_try() {
let scenario = r#"
{
let nestedLabels = (x) => {
let str = "";
foo: {
spacer: {
bar: {
while(true) {
try {
try {
break spacer;
} finally {
str = "foo";
}
} catch(h) {} finally {
str += "bar"
if (x === true) {
break foo;
} else {
break bar;
}
}
}
str += " broke-while"
}
str += " broke-bar"
}
str += " broke-spacer"
}
str += " broke-foo";
return str
}
nestedLabels(true) + " != " + nestedLabels(false)
}
"#;
assert_eq!(
&exec(scenario),
"\"foobar broke-foo != foobar broke-bar broke-spacer broke-foo\""
);
}

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

@ -0,0 +1,75 @@
//! Implements an `AbruptCompletionRecord` struct for `CallFrame` tracking.
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum AbruptKind {
Continue,
Break,
Throw,
}
/// 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,
}
}
/// 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 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
}
}

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

@ -0,0 +1,143 @@
//! Module for implementing a `CallFrame`'s environment stacks
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum EnvEntryKind {
Global,
Loop,
Try,
Catch,
Finally,
Labelled,
}
/// The `EnvStackEntry` tracks the environment count and relavant information for the current environment.
#[derive(Copy, 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 `Try`.
pub(crate) const fn with_try_flag(mut self) -> Self {
self.kind = EnvEntryKind::Try;
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `Loop`.
pub(crate) const fn with_loop_flag(mut self) -> Self {
self.kind = EnvEntryKind::Loop;
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `Catch`.
pub(crate) const fn with_catch_flag(mut self) -> Self {
self.kind = EnvEntryKind::Catch;
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `Finally`.
pub(crate) const fn with_finally_flag(mut self) -> Self {
self.kind = EnvEntryKind::Finally;
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `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
}
pub(crate) fn is_global_env(&self) -> bool {
self.kind == EnvEntryKind::Global
}
/// Returns true if an `EnvStackEntry` is a loop
pub(crate) fn is_loop_env(&self) -> bool {
self.kind == EnvEntryKind::Loop
}
/// Returns true if an `EnvStackEntry` is a try block
pub(crate) fn is_try_env(&self) -> bool {
self.kind == EnvEntryKind::Try
}
/// Returns true if an `EnvStackEntry` is a labelled block
pub(crate) fn is_labelled_env(&self) -> bool {
self.kind == EnvEntryKind::Labelled
}
/// Returns true if an `EnvStackEntry` is a catch block
pub(crate) fn is_catch_env(&self) -> bool {
self.kind == EnvEntryKind::Catch
}
pub(crate) fn is_finally_env(&self) -> bool {
self.kind == EnvEntryKind::Finally
}
/// 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 += 1;
}
/// Decrements the `env_num` field for current `EnvEntryStack`.
pub(crate) fn dec_env_num(&mut self) {
self.env_num -= 1;
}
}

113
boa_engine/src/vm/call_frame.rs → boa_engine/src/vm/call_frame/mod.rs

@ -5,35 +5,33 @@
use crate::{object::JsObject, vm::CodeBlock}; use crate::{object::JsObject, vm::CodeBlock};
use boa_gc::{Finalize, Gc, Trace}; use boa_gc::{Finalize, Gc, Trace};
mod abrupt_record;
mod env_stack;
pub(crate) use abrupt_record::AbruptCompletionRecord;
pub(crate) use env_stack::EnvStackEntry;
/// A `CallFrame` holds the state of a function call. /// A `CallFrame` holds the state of a function call.
#[derive(Clone, Debug, Finalize, Trace)] #[derive(Clone, Debug, Finalize, Trace)]
pub struct CallFrame { pub struct CallFrame {
pub(crate) code_block: Gc<CodeBlock>, pub(crate) code_block: Gc<CodeBlock>,
pub(crate) pc: usize, pub(crate) pc: usize,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) catch: Vec<CatchAddresses>, pub(crate) try_catch: Vec<FinallyAddresses>,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) finally_return: FinallyReturn, pub(crate) finally_return: FinallyReturn,
pub(crate) finally_jump: Vec<Option<u32>>, #[unsafe_ignore_trace]
pub(crate) abrupt_completion: Option<AbruptCompletionRecord>,
pub(crate) pop_on_return: usize, pub(crate) pop_on_return: usize,
// Tracks the number of environments in environment entry.
// Tracks the number of environments in the current loop block.
// On abrupt returns this is used to decide how many environments need to be pop'ed.
pub(crate) loop_env_stack: Vec<usize>,
// Tracks the number of environments in the current try-catch-finally block.
// On abrupt returns this is used to decide how many environments need to be pop'ed. // On abrupt returns this is used to decide how many environments need to be pop'ed.
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) try_env_stack: Vec<TryStackEntry>, pub(crate) env_stack: Vec<EnvStackEntry>,
pub(crate) param_count: usize, pub(crate) param_count: usize,
pub(crate) arg_count: usize, pub(crate) arg_count: usize,
#[unsafe_ignore_trace] #[unsafe_ignore_trace]
pub(crate) generator_resume_kind: GeneratorResumeKind, pub(crate) generator_resume_kind: GeneratorResumeKind,
// Indicate that the last try block has thrown an exception.
pub(crate) thrown: bool,
// When an async generator is resumed, the generator object is needed // When an async generator is resumed, the generator object is needed
// to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart). // to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart).
pub(crate) async_generator: Option<JsObject>, pub(crate) async_generator: Option<JsObject>,
@ -43,22 +41,18 @@ pub struct CallFrame {
impl CallFrame { impl CallFrame {
/// Creates a new `CallFrame` with the provided `CodeBlock`. /// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new(code_block: Gc<CodeBlock>) -> Self { pub(crate) fn new(code_block: Gc<CodeBlock>) -> Self {
let max_length = code_block.bytecode.len() as u32;
Self { Self {
code_block, code_block,
pc: 0, pc: 0,
catch: Vec::new(), try_catch: Vec::new(),
finally_return: FinallyReturn::None, finally_return: FinallyReturn::None,
finally_jump: Vec::new(),
pop_on_return: 0, pop_on_return: 0,
loop_env_stack: Vec::from([0]), env_stack: Vec::from([EnvStackEntry::new(0, max_length)]),
try_env_stack: Vec::from([TryStackEntry { abrupt_completion: None,
num_env: 0,
num_loop_stack_entries: 0,
}]),
param_count: 0, param_count: 0,
arg_count: 0, arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false,
async_generator: None, async_generator: None,
} }
} }
@ -79,70 +73,43 @@ impl CallFrame {
/// ---- `CallFrame` stack methods ---- /// ---- `CallFrame` stack methods ----
impl CallFrame { impl CallFrame {
/// Tracks that one environment has been pushed in the current loop block. /// Tracks that one environment has been pushed in the current loop block.
pub(crate) fn loop_env_stack_inc(&mut self) { pub(crate) fn inc_frame_env_stack(&mut self) {
*self self.env_stack
.loop_env_stack
.last_mut() .last_mut()
.expect("loop environment stack entry must exist") += 1; .expect("environment stack entry must exist")
.inc_env_num();
} }
/// Tracks that one environment has been pop'ed in the current loop block. /// Tracks that one environment has been pop'ed in the current loop block.
pub(crate) fn loop_env_stack_dec(&mut self) { ///
*self /// Note:
.loop_env_stack /// - This will check if the env stack has reached 0 and should be popped.
.last_mut() pub(crate) fn dec_frame_env_stack(&mut self) {
.expect("loop environment stack entry must exist") -= 1; self.env_stack
}
/// Tracks that one environment has been pushed in the current try-catch-finally block.
pub(crate) fn try_env_stack_inc(&mut self) {
self.try_env_stack
.last_mut()
.expect("try environment stack entry must exist")
.num_env += 1;
}
/// Tracks that one environment has been pop'ed in the current try-catch-finally block.
pub(crate) fn try_env_stack_dec(&mut self) {
self.try_env_stack
.last_mut()
.expect("try environment stack entry must exist")
.num_env -= 1;
}
/// Tracks that one loop has started in the current try-catch-finally block.
pub(crate) fn try_env_stack_loop_inc(&mut self) {
self.try_env_stack
.last_mut()
.expect("try environment stack entry must exist")
.num_loop_stack_entries += 1;
}
/// Tracks that one loop has finished in the current try-catch-finally block.
pub(crate) fn try_env_stack_loop_dec(&mut self) {
self.try_env_stack
.last_mut() .last_mut()
.expect("try environment stack entry must exist") .expect("environment stack entry must exist")
.num_loop_stack_entries -= 1; .dec_env_num();
} }
} }
/// Tracks the number of environments in the current try-catch-finally block. /// Tracks the address that should be jumped to when an error is caught.
/// ///
/// Because of the interactions between loops and try-catch-finally blocks, /// Additionally the address of a finally block is tracked, to allow for special handling if it exists.
/// the number of loop blocks in the try-catch-finally block also needs to be tracked.
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub(crate) struct TryStackEntry { pub(crate) struct FinallyAddresses {
pub(crate) num_env: usize, finally: Option<u32>,
pub(crate) num_loop_stack_entries: usize,
} }
/// Tracks the address that should be jumped to when an error is caught. impl FinallyAddresses {
/// Additionally the address of a finally block is tracked, to allow for special handling if it exists. pub(crate) const fn new(finally_address: Option<u32>) -> Self {
#[derive(Copy, Clone, Debug)] Self {
pub(crate) struct CatchAddresses { finally: finally_address,
pub(crate) next: u32, }
pub(crate) finally: Option<u32>, }
pub(crate) const fn finally(self) -> Option<u32> {
self.finally
}
} }
/// Indicates if a function should return or throw at the end of a finally block. /// Indicates if a function should return or throw at the end of a finally block.

17
boa_engine/src/vm/code_block.rs

@ -233,7 +233,8 @@ impl CodeBlock {
| Opcode::JumpIfNotUndefined | Opcode::JumpIfNotUndefined
| Opcode::JumpIfNullOrUndefined | Opcode::JumpIfNullOrUndefined
| Opcode::CatchStart | Opcode::CatchStart
| Opcode::FinallySetJump | Opcode::FinallyStart
| Opcode::LabelledStart
| Opcode::Case | Opcode::Case
| Opcode::Default | Opcode::Default
| Opcode::LogicalAnd | Opcode::LogicalAnd
@ -252,11 +253,15 @@ impl CodeBlock {
*pc += size_of::<u32>(); *pc += size_of::<u32>();
result result
} }
Opcode::TryStart
| Opcode::PushDeclarativeEnvironment Opcode::PushDeclarativeEnvironment
| Opcode::PushFunctionEnvironment | Opcode::PushFunctionEnvironment
| Opcode::CopyDataProperties | Opcode::CopyDataProperties
| Opcode::Break => { | Opcode::Break
| Opcode::Continue
| Opcode::LoopContinue
| Opcode::LoopStart
| Opcode::TryStart => {
let operand1 = self.read::<u32>(*pc); let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc); let operand2 = self.read::<u32>(*pc);
@ -401,15 +406,13 @@ impl CodeBlock {
| Opcode::TryEnd | Opcode::TryEnd
| Opcode::CatchEnd | Opcode::CatchEnd
| Opcode::CatchEnd2 | Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Super | Opcode::Super
| Opcode::Return | Opcode::Return
| Opcode::PopEnvironment | Opcode::PopEnvironment
| Opcode::LoopStart
| Opcode::LoopContinue
| Opcode::LoopEnd | Opcode::LoopEnd
| Opcode::LabelledEnd
| Opcode::InitIterator | Opcode::InitIterator
| Opcode::InitIteratorAsync | Opcode::InitIteratorAsync
| Opcode::IteratorNext | Opcode::IteratorNext

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

@ -129,13 +129,31 @@ impl CodeBlock {
EdgeStyle::Line, 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::LoopContinue | Opcode::LoopStart => {
let start_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let end_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {start_address}, {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::Break => { Opcode::Break => {
let jump_operand = self.read::<u32>(pc); let jump_operand = self.read::<u32>(pc);
pc += size_of::<u32>(); pc += size_of::<u32>();
let envs_operand = self.read::<u32>(pc); let target_operand = self.read::<u32>(pc);
pc += size_of::<u32>(); pc += size_of::<u32>();
let label = format!("{opcode_str} {jump_operand}, pop {envs_operand}"); let label = format!("{opcode_str} {jump_operand}, target {target_operand}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge( graph.add_edge(
previous_pc, previous_pc,
@ -145,6 +163,22 @@ impl CodeBlock {
EdgeStyle::Line, 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 => { Opcode::LogicalAnd | Opcode::LogicalOr | Opcode::Coalesce => {
let exit = self.read::<u32>(pc); let exit = self.read::<u32>(pc);
pc += size_of::<u32>(); pc += size_of::<u32>();
@ -212,7 +246,6 @@ impl CodeBlock {
); );
} }
Opcode::CatchStart Opcode::CatchStart
| Opcode::FinallySetJump
| Opcode::CallEval | Opcode::CallEval
| Opcode::Call | Opcode::Call
| Opcode::New | Opcode::New
@ -452,9 +485,8 @@ impl CodeBlock {
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Super | Opcode::Super
| Opcode::LoopStart
| Opcode::LoopContinue
| Opcode::LoopEnd | Opcode::LoopEnd
| Opcode::LabelledEnd
| Opcode::InitIterator | Opcode::InitIterator
| Opcode::InitIteratorAsync | Opcode::InitIteratorAsync
| Opcode::IteratorNext | Opcode::IteratorNext

135
boa_engine/src/vm/mod.rs

@ -6,7 +6,7 @@
use crate::{ use crate::{
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
vm::{call_frame::CatchAddresses, code_block::Readable}, vm::{call_frame::AbruptCompletionRecord, code_block::Readable},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
#[cfg(feature = "fuzz")] #[cfg(feature = "fuzz")]
@ -25,7 +25,7 @@ pub mod flowgraph;
pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
pub(crate) use { pub(crate) use {
call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry}, call_frame::{FinallyReturn, GeneratorResumeKind},
code_block::{create_function_object, create_generator_function_object}, code_block::{create_function_object, create_generator_function_object},
opcode::BindingOpcode, opcode::BindingOpcode,
}; };
@ -289,46 +289,112 @@ impl Context<'_> {
return Err(e); return Err(e);
} }
} }
if let Some(address) = self.vm.frame().catch.last() { // 1. Find the viable catch and finally blocks
let address = address.next; let current_address = self.vm.frame().pc;
let try_stack_entry = self let viable_catch_candidates =
.vm self.vm.frame().env_stack.iter().filter(|env| {
.frame_mut() env.is_try_env() && env.start_address() < env.exit_address()
.try_env_stack });
.last_mut()
.expect("must exist"); if let Some(candidate) = viable_catch_candidates.last() {
let try_stack_entry_copy = *try_stack_entry; let catch_target = candidate.start_address();
try_stack_entry.num_env = 0;
try_stack_entry.num_loop_stack_entries = 0; let mut env_to_pop = 0;
for _ in 0..try_stack_entry_copy.num_env { let mut target_address = u32::MAX;
self.realm.environments.pop(); while self.vm.frame().env_stack.len() > 1 {
} let env_entry = self
let mut num_env = try_stack_entry_copy.num_env;
for _ in 0..try_stack_entry_copy.num_loop_stack_entries {
num_env -= self
.vm .vm
.frame_mut() .frame_mut()
.loop_env_stack .env_stack
.pop() .last()
.expect("must exist"); .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();
self.vm.frame_mut().env_stack.pop();
break;
} else if env_entry.is_finally_env() {
if current_address > (env_entry.start_address() as usize) {
target_address = env_entry.exit_address();
} else {
target_address = env_entry.start_address();
} }
*self break;
.vm }
.frame_mut() env_to_pop += env_entry.env_num();
.loop_env_stack self.vm.frame_mut().env_stack.pop();
.last_mut() }
.expect("must exist") -= num_env;
self.vm.frame_mut().try_env_stack.pop().expect("must exist"); let env_truncation_len =
self.realm.environments.len().saturating_sub(env_to_pop);
self.realm.environments.truncate(env_truncation_len);
if target_address == catch_target {
self.vm.frame_mut().pc = catch_target as usize;
} else {
self.vm.frame_mut().pc = target_address as usize;
};
for _ in 0..self.vm.frame().pop_on_return { for _ in 0..self.vm.frame().pop_on_return {
self.vm.pop(); self.vm.pop();
} }
self.vm.frame_mut().pop_on_return = 0;
let record =
AbruptCompletionRecord::new_throw().with_initial_target(catch_target);
self.vm.frame_mut().abrupt_completion = Some(record);
self.vm.frame_mut().finally_return = FinallyReturn::Err;
let err = e.to_opaque(self);
self.vm.push(err);
} else {
let mut env_to_pop = 0;
let mut target_address = None;
let mut env_stack_to_pop = 0;
for env_entry in self.vm.frame_mut().env_stack.iter_mut().rev() {
if env_entry.is_finally_env() {
if (env_entry.start_address() as usize) < current_address {
target_address = Some(env_entry.exit_address() as usize);
} else {
target_address = Some(env_entry.start_address() as usize);
}
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;
}
if let Some(address) = target_address {
for _ in 0..env_stack_to_pop {
self.vm.frame_mut().env_stack.pop();
}
let env_truncation_len =
self.realm.environments.len().saturating_sub(env_to_pop);
self.realm.environments.truncate(env_truncation_len);
let previous_stack_size = self
.vm
.stack
.len()
.saturating_sub(self.vm.frame().pop_on_return);
self.vm.stack.truncate(previous_stack_size);
self.vm.frame_mut().pop_on_return = 0; self.vm.frame_mut().pop_on_return = 0;
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().catch.pop(); let record = AbruptCompletionRecord::new_throw();
self.vm.frame_mut().abrupt_completion = Some(record);
self.vm.frame_mut().pc = address;
self.vm.frame_mut().finally_return = FinallyReturn::Err; self.vm.frame_mut().finally_return = FinallyReturn::Err;
self.vm.frame_mut().thrown = true; let err = e.to_opaque(self);
let e = e.to_opaque(self); self.vm.push(err);
self.vm.push(e);
} else { } else {
self.vm.stack.truncate(start_stack_size); self.vm.stack.truncate(start_stack_size);
@ -368,6 +434,7 @@ impl Context<'_> {
} }
} }
} }
}
if self.vm.trace { if self.vm.trace {
println!("\nStack:"); println!("\nStack:");

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

@ -0,0 +1,102 @@
use crate::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, FinallyReturn, ShouldExit},
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<ShouldExit> {
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;
while let Some(env_entry) = context.vm.frame().env_stack.last() {
if (jump_address == env_entry.exit_address())
|| (env_entry.is_finally_env() && jump_address == env_entry.start_address())
{
break;
}
// Checks for the break if we have jumped from inside of a finally block
if jump_address == env_entry.exit_address() {
break;
}
envs_to_pop += env_entry.env_num();
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.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 as usize;
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
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;
while let Some(env_entry) = context.vm.frame_mut().env_stack.last() {
// 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())
{
break;
}
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() {
context.vm.frame_mut().env_stack.pop();
break;
}
context.vm.frame_mut().env_stack.pop();
}
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.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 as usize;
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}

97
boa_engine/src/vm/opcode/control_flow/catch.rs

@ -0,0 +1,97 @@
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, FinallyReturn, ShouldExit},
Context, JsResult,
};
/// `CatchStart` implements the Opcode Operation for `Opcode::CatchStart`
///
/// Operation:
/// - Start of a catch block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchStart;
impl Operation for CatchStart {
const NAME: &'static str = "CatchStart";
const INSTRUCTION: &'static str = "INST - CatchStart";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let start = context.vm.frame().pc as u32 - 1;
let finally = context.vm.read::<u32>();
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, finally - 1).with_catch_flag());
context.vm.frame_mut().abrupt_completion = None;
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}
/// `CatchEnd` implements the Opcode Operation for `Opcode::CatchEnd`
///
/// Operation:
/// - End of a catch block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchEnd;
impl Operation for CatchEnd {
const NAME: &'static str = "CatchEnd";
const INSTRUCTION: &'static str = "INST - CatchEnd";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
context.vm.frame_mut().try_catch.pop();
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_catch_env() {
break;
}
}
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}
/// `CatchEnd2` implements the Opcode Operation for `Opcode::CatchEnd2`
///
/// Operation:
/// - End of a catch block
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchEnd2;
impl Operation for CatchEnd2 {
const NAME: &'static str = "CatchEnd2";
const INSTRUCTION: &'static str = "INST - CatchEnd2";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
if let Some(catch_entry) = context
.vm
.frame()
.env_stack
.last()
.filter(|entry| entry.is_catch_env())
{
let env_truncation_len = context
.realm
.environments
.len()
.saturating_sub(catch_entry.env_num());
context.realm.environments.truncate(env_truncation_len);
context.vm.frame_mut().env_stack.pop();
}
if context.vm.frame_mut().finally_return == FinallyReturn::Err {
context.vm.frame_mut().finally_return = FinallyReturn::None;
}
Ok(ShouldExit::False)
}
}

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

@ -0,0 +1,178 @@
use crate::{
vm::{opcode::Operation, FinallyReturn, ShouldExit},
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<ShouldExit> {
let exit = context.vm.read::<u32>();
context.vm.frame_mut().try_catch.pop();
let finally_env = context
.vm
.frame_mut()
.env_stack
.last_mut()
.expect("EnvStackEntries must exist");
finally_env.set_exit_address(exit);
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
let finally_candidates = context.vm.frame().env_stack.iter().filter(|env| {
env.is_finally_env() && context.vm.frame().pc < (env.start_address() as usize)
});
let next_finally = match finally_candidates.last() {
Some(env) => env.start_address(),
_ => u32::MAX,
};
let abrupt_record = context.vm.frame_mut().abrupt_completion;
match context.vm.frame_mut().finally_return {
FinallyReturn::None => {
// Check if there is an `AbruptCompletionRecord`.
if let Some(record) = abrupt_record {
let mut envs_to_pop = 0;
if next_finally < record.target() {
context.vm.frame_mut().pc = next_finally as usize;
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();
}
} else if record.is_break() && context.vm.frame().pc < record.target() as usize
{
// handle the continuation of an abrupt break.
context.vm.frame_mut().pc = record.target() as usize;
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;
} else if record.is_continue()
&& context.vm.frame().pc > record.target() as usize
{
// Handle the continuation of an abrupt continue
context.vm.frame_mut().pc = record.target() as usize;
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.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
} else {
context.vm.frame_mut().env_stack.pop();
}
Ok(ShouldExit::False)
}
FinallyReturn::Ok => Ok(ShouldExit::True),
FinallyReturn::Err => {
if let Some(record) = abrupt_record {
let mut envs_to_pop = 0;
if next_finally < record.target() {
context.vm.frame_mut().pc = next_finally as usize;
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();
}
} else if record.is_throw_with_target()
&& context.vm.frame().pc < record.target() as usize
{
context.vm.frame_mut().pc = record.target() as usize;
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;
} else if !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
.realm
.environments
.len()
.saturating_sub(current_stack.env_num());
context.realm.environments.truncate(env_truncation_len);
return Err(JsError::from_opaque(context.vm.pop()));
}
let env_truncation_len =
context.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
return Ok(ShouldExit::False);
}
let current_stack = context
.vm
.frame_mut()
.env_stack
.pop()
.expect("Popping current finally stack.");
let env_truncation_len = context
.realm
.environments
.len()
.saturating_sub(current_stack.env_num());
context.realm.environments.truncate(env_truncation_len);
Err(JsError::from_opaque(context.vm.pop()))
}
}
}
}

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

@ -0,0 +1,55 @@
use crate::{
vm::{call_frame::EnvStackEntry, opcode::Operation, ShouldExit},
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<ShouldExit> {
let start = context.vm.frame().pc as u32 - 1;
let end = context.vm.read::<u32>();
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, end).with_labelled_flag());
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
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.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
Ok(ShouldExit::False)
}
}

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

@ -0,0 +1,11 @@
pub(crate) mod r#break;
pub(crate) mod catch;
pub(crate) mod finally;
pub(crate) mod labelled;
pub(crate) mod r#try;
pub(crate) use catch::*;
pub(crate) use finally::*;
pub(crate) use labelled::*;
pub(crate) use r#break::*;
pub(crate) use r#try::*;

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

@ -0,0 +1,83 @@
use crate::{
vm::{
call_frame::{EnvStackEntry, FinallyAddresses},
opcode::Operation,
FinallyReturn, ShouldExit,
},
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<ShouldExit> {
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),
);
}
context.vm.frame_mut().finally_return = FinallyReturn::None;
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(catch, finally).with_try_flag());
let finally = if finally == u32::MAX {
None
} else {
Some(finally)
};
context
.vm
.frame_mut()
.try_catch
.push(FinallyAddresses::new(finally));
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
context.vm.frame_mut().try_catch.pop();
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.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}

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

@ -37,16 +37,16 @@ impl Operation for GeneratorNext {
GeneratorResumeKind::Return => { GeneratorResumeKind::Return => {
let mut finally_left = false; let mut finally_left = false;
while let Some(catch_addresses) = context.vm.frame().catch.last() { while let Some(try_addresses) = context.vm.frame().try_catch.last() {
if let Some(finally_address) = catch_addresses.finally { if let Some(finally_address) = try_addresses.finally() {
let frame = context.vm.frame_mut(); let frame = context.vm.frame_mut();
frame.pc = finally_address as usize; frame.pc = finally_address as usize;
frame.finally_return = FinallyReturn::Ok; frame.finally_return = FinallyReturn::Ok;
frame.catch.pop(); frame.try_catch.pop();
finally_left = true; finally_left = true;
break; break;
} }
context.vm.frame_mut().catch.pop(); context.vm.frame_mut().try_catch.pop();
} }
if finally_left { if finally_left {

3
boa_engine/src/vm/opcode/iteration/for_await.rs

@ -58,8 +58,7 @@ impl Operation for ForAwaitOfLoopNext {
if next_result.complete(context)? { if next_result.complete(context)? {
context.vm.frame_mut().pc = address as usize; context.vm.frame_mut().pc = address as usize;
context.vm.frame_mut().loop_env_stack_dec(); context.vm.frame_mut().dec_frame_env_stack();
context.vm.frame_mut().try_env_stack_dec();
context.realm.environments.pop(); context.realm.environments.pop();
context.vm.push(true); context.vm.push(true);
} else { } else {

3
boa_engine/src/vm/opcode/iteration/for_in.rs

@ -74,8 +74,7 @@ impl Operation for ForInLoopNext {
context.vm.push(value); context.vm.push(value);
} else { } else {
context.vm.frame_mut().pc = address as usize; context.vm.frame_mut().pc = address as usize;
context.vm.frame_mut().loop_env_stack_dec(); context.vm.frame_mut().dec_frame_env_stack();
context.vm.frame_mut().try_env_stack_dec();
context.realm.environments.pop(); context.realm.environments.pop();
context.vm.push(iterator.clone()); context.vm.push(iterator.clone());
context.vm.push(next_method); context.vm.push(next_method);

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

@ -1,5 +1,5 @@
use crate::{ use crate::{
vm::{opcode::Operation, ShouldExit}, vm::{call_frame::EnvStackEntry, opcode::Operation, ShouldExit},
Context, JsResult, Context, JsResult,
}; };
@ -15,16 +15,22 @@ impl Operation for LoopStart {
const INSTRUCTION: &'static str = "INST - LoopStart"; const INSTRUCTION: &'static str = "INST - LoopStart";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
context.vm.frame_mut().loop_env_stack.push(0); let start = context.vm.read::<u32>();
context.vm.frame_mut().try_env_stack_loop_inc(); let exit = context.vm.read::<u32>();
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, exit).with_loop_flag());
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }
/// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue` /// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue`.
/// ///
/// Operation: /// Operation:
/// - Clean up environments when a loop continues. /// - Pushes a clean environment onto the frame's `EnvEntryStack`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct LoopContinue; pub(crate) struct LoopContinue;
@ -33,17 +39,34 @@ impl Operation for LoopContinue {
const INSTRUCTION: &'static str = "INST - LoopContinue"; const INSTRUCTION: &'static str = "INST - LoopContinue";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let env_num = context let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
// 1. Clean up the previous environment.
if let Some(entry) = context
.vm .vm
.frame_mut() .frame()
.loop_env_stack .env_stack
.last_mut() .last()
.expect("loop env stack entry must exist"); .filter(|entry| entry.exit_address() == exit)
let env_num_copy = *env_num; {
*env_num = 0; let env_truncation_len = context
for _ in 0..env_num_copy { .realm
context.realm.environments.pop(); .environments
.len()
.saturating_sub(entry.env_num());
context.realm.environments.truncate(env_truncation_len);
context.vm.frame_mut().env_stack.pop();
} }
// 2. Push a new clean EnvStack.
context
.vm
.frame_mut()
.env_stack
.push(EnvStackEntry::new(start, exit).with_loop_flag());
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }
@ -60,17 +83,18 @@ impl Operation for LoopEnd {
const INSTRUCTION: &'static str = "INST - LoopEnd"; const INSTRUCTION: &'static str = "INST - LoopEnd";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let env_num = context let mut envs_to_pop = 0_usize;
.vm while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() {
.frame_mut() envs_to_pop += env_entry.env_num();
.loop_env_stack
.pop() if env_entry.is_loop_env() {
.expect("loop env stack entry must exist"); break;
for _ in 0..env_num { }
context.realm.environments.pop();
context.vm.frame_mut().try_env_stack_dec();
} }
context.vm.frame_mut().try_env_stack_loop_dec();
let env_truncation_len = context.realm.environments.len().saturating_sub(envs_to_pop);
context.realm.environments.truncate(env_truncation_len);
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

44
boa_engine/src/vm/opcode/jump/break.rs

@ -1,44 +0,0 @@
use crate::{
vm::{opcode::Operation, ShouldExit},
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<ShouldExit> {
let address = context.vm.read::<u32>();
let pop_envs = context.vm.read::<u32>();
for _ in 0..pop_envs {
context.realm.environments.pop();
let loop_envs = *context
.vm
.frame()
.loop_env_stack
.last()
.expect("loop env stack must exist");
if loop_envs == 0 {
context
.vm
.frame_mut()
.loop_env_stack
.pop()
.expect("loop env stack must exist");
}
context.vm.frame_mut().loop_env_stack_dec();
context.vm.frame_mut().try_env_stack_dec();
}
context.vm.frame_mut().pc = address as usize;
Ok(ShouldExit::False)
}
}

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

@ -3,9 +3,6 @@ use crate::{
Context, JsResult, Context, JsResult,
}; };
pub(crate) mod r#break;
pub(crate) use r#break::*;
/// `Jump` implements the Opcode Operation for `Opcode::Jump` /// `Jump` implements the Opcode Operation for `Opcode::Jump`
/// ///
/// Operation: /// Operation:

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

@ -8,6 +8,7 @@ mod await_stm;
mod binary_ops; mod binary_ops;
mod call; mod call;
mod concat; mod concat;
mod control_flow;
mod copy; mod copy;
mod define; mod define;
mod delete; mod delete;
@ -20,7 +21,6 @@ mod jump;
mod new; mod new;
mod nop; mod nop;
mod pop; mod pop;
mod promise;
mod push; mod push;
mod require; mod require;
mod rest_parameter; mod rest_parameter;
@ -30,7 +30,6 @@ mod swap;
mod switch; mod switch;
mod throw; mod throw;
mod to; mod to;
mod try_catch;
mod unary_ops; mod unary_ops;
mod value; mod value;
@ -44,6 +43,8 @@ pub(crate) use call::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use concat::*; pub(crate) use concat::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use control_flow::*;
#[doc(inline)]
pub(crate) use copy::*; pub(crate) use copy::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use define::*; pub(crate) use define::*;
@ -68,8 +69,6 @@ pub(crate) use nop::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use pop::*; pub(crate) use pop::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use promise::*;
#[doc(inline)]
pub(crate) use push::*; pub(crate) use push::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use require::*; pub(crate) use require::*;
@ -88,8 +87,6 @@ pub(crate) use throw::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use to::*; pub(crate) use to::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use try_catch::*;
#[doc(inline)]
pub(crate) use unary_ops::*; pub(crate) use unary_ops::*;
#[doc(inline)] #[doc(inline)]
pub(crate) use value::*; pub(crate) use value::*;
@ -1148,12 +1145,19 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
FinallyEnd, FinallyEnd,
/// Set the address for a finally jump. /// Jumps to a target location and pops the environments involved.
/// ///
/// Operands: /// Operands: Jump Address: u32, Target address: u32
///
/// Stack: **=>**
Break,
/// Sets the `AbruptCompletionRecord` for a delayed continue
///
/// Operands: Jump Address: u32, Target address: u32,
/// ///
/// Stack: **=>** /// Stack: **=>**
FinallySetJump, Continue,
/// Pops value converts it to boolean and pushes it back. /// Pops value converts it to boolean and pushes it back.
/// ///
@ -1326,14 +1330,14 @@ generate_impl! {
/// Push loop start marker. /// Push loop start marker.
/// ///
/// Operands: /// Operands: Exit Address: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
LoopStart, LoopStart,
/// Clean up environments when a loop continues. /// Clean up environments when a loop continues.
/// ///
/// Operands: /// Operands: Start Address: `u32`, Exit Address: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
LoopContinue, LoopContinue,
@ -1345,6 +1349,20 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
LoopEnd, LoopEnd,
/// Push labelled start marker.
///
/// Operands: Exit Address: u32,
///
/// Stack: **=>**
LabelledStart,
/// Clean up environments at the end of a labelled block.
///
/// Operands:
///
/// Stack: **=>**
LabelledEnd,
/// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. /// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined.
/// ///
/// Operands: address: `u32` /// Operands: address: `u32`
@ -1494,9 +1512,6 @@ generate_impl! {
/// Stack: promise **=>** /// Stack: promise **=>**
Await, Await,
/// Jumps to a target location and pops the environments involved.
Break,
/// Push the current new target to the stack. /// Push the current new target to the stack.
/// ///
/// Operands: /// Operands:

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

@ -32,11 +32,13 @@ impl Operation for PopIfThrown {
const INSTRUCTION: &'static str = "INST - PopIfThrown"; const INSTRUCTION: &'static str = "INST - PopIfThrown";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let frame = context.vm.frame_mut(); let frame = context.vm.frame();
if frame.thrown { match frame.abrupt_completion {
frame.thrown = false; Some(record) if record.is_throw() => {
context.vm.pop(); context.vm.pop();
} }
_ => {}
};
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }
@ -54,8 +56,7 @@ impl Operation for PopEnvironment {
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
context.realm.environments.pop(); context.realm.environments.pop();
context.vm.frame_mut().loop_env_stack_dec(); context.vm.frame_mut().dec_frame_env_stack();
context.vm.frame_mut().try_env_stack_dec();
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

80
boa_engine/src/vm/opcode/promise/mod.rs

@ -1,80 +0,0 @@
use crate::{
vm::{opcode::Operation, FinallyReturn, ShouldExit},
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<ShouldExit> {
*context
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = None;
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
let address = context
.vm
.frame_mut()
.finally_jump
.pop()
.expect("finally jump must exist here");
match context.vm.frame_mut().finally_return {
FinallyReturn::None => {
if let Some(address) = address {
context.vm.frame_mut().pc = address as usize;
}
Ok(ShouldExit::False)
}
FinallyReturn::Ok => Ok(ShouldExit::True),
FinallyReturn::Err => Err(JsError::from_opaque(context.vm.pop())),
}
}
}
/// `FinallySetJump` implements the Opcode Operation for `Opcode::FinallySetJump`
///
/// Operation:
/// - Set the address for a finally jump.
#[derive(Debug, Clone, Copy)]
pub(crate) struct FinallySetJump;
impl Operation for FinallySetJump {
const NAME: &'static str = "FinallySetJump";
const INSTRUCTION: &'static str = "INST - FinallySetJump";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let address = context.vm.read::<u32>();
*context
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = Some(address);
Ok(ShouldExit::False)
}
}

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

@ -24,8 +24,7 @@ impl Operation for PushDeclarativeEnvironment {
.realm .realm
.environments .environments
.push_declarative(num_bindings as usize, compile_environment); .push_declarative(num_bindings as usize, compile_environment);
context.vm.frame_mut().loop_env_stack_inc(); context.vm.frame_mut().inc_frame_env_stack();
context.vm.frame_mut().try_env_stack_inc();
Ok(ShouldExit::False) Ok(ShouldExit::False)
} }
} }

58
boa_engine/src/vm/opcode/return_stm/mod.rs

@ -15,38 +15,36 @@ impl Operation for Return {
const INSTRUCTION: &'static str = "INST - Return"; const INSTRUCTION: &'static str = "INST - Return";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
if let Some(finally_address) = context.vm.frame().catch.last().and_then(|c| c.finally) { let current_address = context.vm.frame().pc;
let frame = context.vm.frame_mut(); let mut env_to_pop = 0;
frame.pc = finally_address as usize; let mut finally_address = None;
frame.finally_return = FinallyReturn::Ok; while let Some(env_entry) = context.vm.frame().env_stack.last() {
frame.catch.pop(); if env_entry.is_finally_env() {
let try_stack_entry = context if (env_entry.start_address() as usize) < current_address {
.vm finally_address = Some(env_entry.exit_address() as usize);
.frame_mut() } else {
.try_env_stack finally_address = Some(env_entry.start_address() as usize);
.pop()
.expect("must exist");
for _ in 0..try_stack_entry.num_env {
context.realm.environments.pop();
} }
let mut num_env = try_stack_entry.num_env; break;
for _ in 0..try_stack_entry.num_loop_stack_entries {
num_env -= context
.vm
.frame_mut()
.loop_env_stack
.pop()
.expect("must exist");
} }
*context
.vm env_to_pop += env_entry.env_num();
.frame_mut() if env_entry.is_global_env() {
.loop_env_stack break;
.last_mut() }
.expect("must exist") -= num_env;
} else { context.vm.frame_mut().env_stack.pop();
return Ok(ShouldExit::True);
} }
Ok(ShouldExit::False)
let env_truncation_len = context.realm.environments.len().saturating_sub(env_to_pop);
context.realm.environments.truncate(env_truncation_len);
if let Some(finally) = finally_address {
context.vm.frame_mut().pc = finally;
context.vm.frame_mut().finally_return = FinallyReturn::Ok;
return Ok(ShouldExit::False);
}
Ok(ShouldExit::True)
} }
} }

4
boa_engine/src/vm/opcode/throw/mod.rs

@ -15,7 +15,7 @@ impl Operation for Throw {
const INSTRUCTION: &'static str = "INST - Throw"; const INSTRUCTION: &'static str = "INST - Throw";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> { fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let value = context.vm.pop(); let err = context.vm.pop();
Err(JsError::from_opaque(value)) Err(JsError::from_opaque(err))
} }
} }

164
boa_engine/src/vm/opcode/try_catch/mod.rs

@ -1,164 +0,0 @@
use crate::{
vm::{opcode::Operation, CatchAddresses, FinallyReturn, ShouldExit, TryStackEntry},
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<ShouldExit> {
let next = context.vm.read::<u32>();
let finally = context.vm.read::<u32>();
let finally = if finally == 0 { None } else { Some(finally) };
context
.vm
.frame_mut()
.catch
.push(CatchAddresses { next, finally });
context.vm.frame_mut().finally_jump.push(None);
context.vm.frame_mut().finally_return = FinallyReturn::None;
context.vm.frame_mut().try_env_stack.push(TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
});
Ok(ShouldExit::False)
}
}
/// `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<ShouldExit> {
context.vm.frame_mut().catch.pop();
let try_stack_entry = context
.vm
.frame_mut()
.try_env_stack
.pop()
.expect("must exist");
for _ in 0..try_stack_entry.num_env {
context.realm.environments.pop();
}
let mut num_env = try_stack_entry.num_env;
for _ in 0..try_stack_entry.num_loop_stack_entries {
num_env -= context
.vm
.frame_mut()
.loop_env_stack
.pop()
.expect("must exist");
}
*context
.vm
.frame_mut()
.loop_env_stack
.last_mut()
.expect("must exist") -= num_env;
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}
/// `CatchStart` implements the Opcode Operation for `Opcode::CatchStart`
///
/// Operation:
/// - Start of a catch block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchStart;
impl Operation for CatchStart {
const NAME: &'static str = "CatchStart";
const INSTRUCTION: &'static str = "INST - CatchStart";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let finally = context.vm.read::<u32>();
context.vm.frame_mut().catch.push(CatchAddresses {
next: finally,
finally: Some(finally),
});
context.vm.frame_mut().try_env_stack.push(TryStackEntry {
num_env: 0,
num_loop_stack_entries: 0,
});
context.vm.frame_mut().thrown = false;
Ok(ShouldExit::False)
}
}
/// `CatchEnd` implements the Opcode Operation for `Opcode::CatchEnd`
///
/// Operation:
/// - End of a catch block.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchEnd;
impl Operation for CatchEnd {
const NAME: &'static str = "CatchEnd";
const INSTRUCTION: &'static str = "INST - CatchEnd";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
context.vm.frame_mut().catch.pop();
let try_stack_entry = context
.vm
.frame_mut()
.try_env_stack
.pop()
.expect("must exist");
for _ in 0..try_stack_entry.num_env {
context.realm.environments.pop();
}
let mut num_env = try_stack_entry.num_env;
for _ in 0..try_stack_entry.num_loop_stack_entries {
num_env -= context
.vm
.frame_mut()
.loop_env_stack
.pop()
.expect("must exist");
}
*context
.vm
.frame_mut()
.loop_env_stack
.last_mut()
.expect("must exist") -= num_env;
context.vm.frame_mut().finally_return = FinallyReturn::None;
Ok(ShouldExit::False)
}
}
/// `CatchEnd2` implements the Opcode Operation for `Opcode::CatchEnd2`
///
/// Operation:
/// - End of a catch block
#[derive(Debug, Clone, Copy)]
pub(crate) struct CatchEnd2;
impl Operation for CatchEnd2 {
const NAME: &'static str = "CatchEnd2";
const INSTRUCTION: &'static str = "INST - CatchEnd2";
fn execute(context: &mut Context<'_>) -> JsResult<ShouldExit> {
let frame = context.vm.frame_mut();
if frame.finally_return == FinallyReturn::Err {
frame.finally_return = FinallyReturn::None;
}
Ok(ShouldExit::False)
}
}
Loading…
Cancel
Save