mirror of https://github.com/boa-dev/boa.git
Browse Source
<!--- Thank you for contributing to Boa! Please fill out the template below, and remove or add any information as you feel necessary. ---> Hi all! 😄 This Pull Request addresses #2424. There are also a few changes made to the `ByteCompiler`, the majority of which are involving `JumpControlInfo`. It changes the following: - Adds `Break` Opcode - Shifts `compile_stmt` into the `statement` module. - Moves `JumpControlInfo` to it's own module.pull/2521/head
Kevin
2 years ago
17 changed files with 897 additions and 497 deletions
@ -0,0 +1,389 @@ |
|||||||
|
//! `JumpControlInfo` tracks relevant jump information used during compilation.
|
||||||
|
//!
|
||||||
|
//! Primarily, jump control tracks information related to the compilation of [iteration
|
||||||
|
//! statements][iteration spec], [switch statements][switch spec], [try statements][try spec],
|
||||||
|
//! and [labelled statements][labelled spec].
|
||||||
|
//!
|
||||||
|
//! [iteration spec]: https://tc39.es/ecma262/#sec-iteration-statements
|
||||||
|
//! [switch spec]: https://tc39.es/ecma262/#sec-switch-statement
|
||||||
|
//! [try spec]: https://tc39.es/ecma262/#sec-try-statement
|
||||||
|
//! [labelled spec]: https://tc39.es/ecma262/#sec-labelled-statements
|
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{ByteCompiler, Label}, |
||||||
|
vm::Opcode, |
||||||
|
}; |
||||||
|
use bitflags::bitflags; |
||||||
|
use boa_interner::Sym; |
||||||
|
use std::mem::size_of; |
||||||
|
|
||||||
|
/// Boa's `ByteCompiler` jump information tracking struct.
|
||||||
|
#[derive(Debug, Clone)] |
||||||
|
pub(crate) struct JumpControlInfo { |
||||||
|
label: Option<Sym>, |
||||||
|
start_address: u32, |
||||||
|
decl_envs: u32, |
||||||
|
flags: JumpControlInfoFlags, |
||||||
|
breaks: Vec<Label>, |
||||||
|
try_continues: Vec<Label>, |
||||||
|
finally_start: Option<Label>, |
||||||
|
} |
||||||
|
|
||||||
|
bitflags! { |
||||||
|
/// A bitflag that contains the type flags and relevant booleans for `JumpControlInfo`.
|
||||||
|
pub(crate) struct JumpControlInfoFlags: u8 { |
||||||
|
const LOOP = 0b0000_0001; |
||||||
|
const SWITCH = 0b0000_0010; |
||||||
|
const TRY_BLOCK = 0b0000_0100; |
||||||
|
const LABELLED_BLOCK = 0b0000_1000; |
||||||
|
const IN_CATCH = 0b0001_0000; |
||||||
|
const HAS_FINALLY = 0b0010_0000; |
||||||
|
const FOR_OF_IN_LOOP = 0b0100_0000; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for JumpControlInfoFlags { |
||||||
|
fn default() -> Self { |
||||||
|
JumpControlInfoFlags::empty() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for JumpControlInfo { |
||||||
|
fn default() -> Self { |
||||||
|
Self { |
||||||
|
label: None, |
||||||
|
start_address: u32::MAX, |
||||||
|
decl_envs: 0, |
||||||
|
flags: JumpControlInfoFlags::default(), |
||||||
|
breaks: Vec::new(), |
||||||
|
try_continues: Vec::new(), |
||||||
|
finally_start: None, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ---- `JumpControlInfo` Creation Methods ---- //
|
||||||
|
|
||||||
|
impl JumpControlInfo { |
||||||
|
pub(crate) const fn with_label(mut self, label: Option<Sym>) -> Self { |
||||||
|
self.label = label; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn with_start_address(mut self, address: u32) -> Self { |
||||||
|
self.start_address = address; |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_loop_flag(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::LOOP, value); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_switch_flag(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::SWITCH, value); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_try_block_flag(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::TRY_BLOCK, value); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_labelled_block_flag(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::LABELLED_BLOCK, value); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_has_finally(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::HAS_FINALLY, value); |
||||||
|
self |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn with_for_of_in_loop(mut self, value: bool) -> Self { |
||||||
|
self.flags.set(JumpControlInfoFlags::FOR_OF_IN_LOOP, value); |
||||||
|
self |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// ---- `JumpControlInfo` const fn methods ---- //
|
||||||
|
|
||||||
|
impl JumpControlInfo { |
||||||
|
pub(crate) const fn label(&self) -> Option<Sym> { |
||||||
|
self.label |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn start_address(&self) -> u32 { |
||||||
|
self.start_address |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn is_loop(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::LOOP) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn is_switch(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::SWITCH) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn is_try_block(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::TRY_BLOCK) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn is_labelled_block(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::LABELLED_BLOCK) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn in_catch(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::IN_CATCH) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn has_finally(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::HAS_FINALLY) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn finally_start(&self) -> Option<Label> { |
||||||
|
self.finally_start |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn for_of_in_loop(&self) -> bool { |
||||||
|
self.flags.contains(JumpControlInfoFlags::FOR_OF_IN_LOOP) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) const fn decl_envs(&self) -> u32 { |
||||||
|
self.decl_envs |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JumpControlInfo { |
||||||
|
/// Sets the `label` field of `JumpControlInfo`.
|
||||||
|
pub(crate) fn set_label(&mut self, label: Option<Sym>) { |
||||||
|
assert!(self.label.is_none()); |
||||||
|
self.label = label; |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the `start_address` field of `JumpControlInfo`.
|
||||||
|
pub(crate) fn set_start_address(&mut self, start_address: u32) { |
||||||
|
self.start_address = start_address; |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the `in_catch` field of `JumpControlInfo`.
|
||||||
|
pub(crate) fn set_in_catch(&mut self, value: bool) { |
||||||
|
self.flags.set(JumpControlInfoFlags::IN_CATCH, value); |
||||||
|
} |
||||||
|
|
||||||
|
/// Sets the `finally_start` field of `JumpControlInfo`.
|
||||||
|
pub(crate) fn set_finally_start(&mut self, label: Label) { |
||||||
|
self.finally_start = Some(label); |
||||||
|
} |
||||||
|
|
||||||
|
/// 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`.
|
||||||
|
pub(crate) fn push_break_label(&mut self, break_label: Label) { |
||||||
|
self.breaks.push(break_label); |
||||||
|
} |
||||||
|
|
||||||
|
/// Pushes a `Label` onto the `try_continues` vector of `JumpControlInfo`.
|
||||||
|
pub(crate) fn push_try_continue_label(&mut self, try_continue_label: Label) { |
||||||
|
self.try_continues.push(try_continue_label); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// `JumpControlInfo` related methods that are implemented on `ByteCompiler`.
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
/// Pushes a generic `JumpControlInfo` onto `ByteCompiler`
|
||||||
|
///
|
||||||
|
/// Default `JumpControlInfoKind` is `JumpControlInfoKind::Loop`
|
||||||
|
pub(crate) fn push_empty_loop_jump_control(&mut self) { |
||||||
|
self.jump_info |
||||||
|
.push(JumpControlInfo::default().with_loop_flag(true)); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn current_jump_control_mut(&mut self) -> Option<&mut JumpControlInfo> { |
||||||
|
self.jump_info.last_mut() |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn set_jump_control_finally_start(&mut self, start: Label) { |
||||||
|
if !self.jump_info.is_empty() { |
||||||
|
let info = self |
||||||
|
.jump_info |
||||||
|
.last_mut() |
||||||
|
.expect("must have try control label"); |
||||||
|
assert!(info.is_try_block()); |
||||||
|
info.set_finally_start(start); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn set_jump_control_catch_start(&mut self, value: bool) { |
||||||
|
if !self.jump_info.is_empty() { |
||||||
|
let info = self |
||||||
|
.jump_info |
||||||
|
.last_mut() |
||||||
|
.expect("must have try control label"); |
||||||
|
assert!(info.is_try_block()); |
||||||
|
info.set_in_catch(value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Emits the `PushDeclarativeEnvironment` and updates the current jump info to track environments.
|
||||||
|
pub(crate) fn emit_and_track_decl_env(&mut self) -> (Label, Label) { |
||||||
|
let pushed_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
if !self.jump_info.is_empty() { |
||||||
|
let current_jump_info = self |
||||||
|
.jump_info |
||||||
|
.last_mut() |
||||||
|
.expect("Jump info must exist as the vector is not empty"); |
||||||
|
current_jump_info.inc_decl_envs(); |
||||||
|
} |
||||||
|
pushed_env |
||||||
|
} |
||||||
|
|
||||||
|
/// Emits the `PopEnvironment` Opcode and updates the current jump that the env is removed.
|
||||||
|
pub(crate) fn emit_and_track_pop_env(&mut self) { |
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn push_loop_control_info(&mut self, label: Option<Sym>, start_address: u32) { |
||||||
|
let new_info = JumpControlInfo::default() |
||||||
|
.with_loop_flag(true) |
||||||
|
.with_label(label) |
||||||
|
.with_start_address(start_address); |
||||||
|
self.jump_info.push(new_info); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn push_loop_control_info_for_of_in_loop( |
||||||
|
&mut self, |
||||||
|
label: Option<Sym>, |
||||||
|
start_address: u32, |
||||||
|
) { |
||||||
|
let new_info = JumpControlInfo::default() |
||||||
|
.with_loop_flag(true) |
||||||
|
.with_label(label) |
||||||
|
.with_start_address(start_address) |
||||||
|
.with_for_of_in_loop(true); |
||||||
|
self.jump_info.push(new_info); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn pop_loop_control_info(&mut self) { |
||||||
|
let loop_info = self.jump_info.pop().expect("no jump information found"); |
||||||
|
|
||||||
|
assert!(loop_info.is_loop()); |
||||||
|
|
||||||
|
for label in loop_info.breaks { |
||||||
|
self.patch_jump(label); |
||||||
|
} |
||||||
|
|
||||||
|
for label in loop_info.try_continues { |
||||||
|
self.patch_jump_with_target(label, loop_info.start_address); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn push_switch_control_info(&mut self, label: Option<Sym>, start_address: u32) { |
||||||
|
let new_info = JumpControlInfo::default() |
||||||
|
.with_switch_flag(true) |
||||||
|
.with_label(label) |
||||||
|
.with_start_address(start_address); |
||||||
|
self.jump_info.push(new_info); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn pop_switch_control_info(&mut self) { |
||||||
|
let info = self.jump_info.pop().expect("no jump information found"); |
||||||
|
|
||||||
|
assert!(info.is_switch()); |
||||||
|
|
||||||
|
for label in info.breaks { |
||||||
|
self.patch_jump(label); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn push_try_control_info(&mut self, has_finally: bool) { |
||||||
|
if !self.jump_info.is_empty() { |
||||||
|
let start_address = self |
||||||
|
.jump_info |
||||||
|
.last() |
||||||
|
.expect("no jump information found") |
||||||
|
.start_address(); |
||||||
|
|
||||||
|
let new_info = JumpControlInfo::default() |
||||||
|
.with_try_block_flag(true) |
||||||
|
.with_start_address(start_address) |
||||||
|
.with_has_finally(has_finally); |
||||||
|
|
||||||
|
self.jump_info.push(new_info); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) { |
||||||
|
if !self.jump_info.is_empty() { |
||||||
|
let mut info = self.jump_info.pop().expect("no jump information found"); |
||||||
|
|
||||||
|
assert!(info.is_try_block()); |
||||||
|
|
||||||
|
let mut breaks = Vec::with_capacity(info.breaks.len()); |
||||||
|
|
||||||
|
if let Some(finally_start_address) = finally_start_address { |
||||||
|
for label in info.try_continues { |
||||||
|
if label.index < finally_start_address { |
||||||
|
self.patch_jump_with_target(label, finally_start_address); |
||||||
|
} else { |
||||||
|
self.patch_jump_with_target(label, info.start_address); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for label in info.breaks { |
||||||
|
if label.index < finally_start_address { |
||||||
|
self.patch_jump_with_target(label, finally_start_address); |
||||||
|
let Label { mut index } = label; |
||||||
|
index -= size_of::<Opcode>() as u32; |
||||||
|
index -= size_of::<u32>() as u32; |
||||||
|
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); |
||||||
|
jump_info.try_continues.append(&mut info.try_continues); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn push_labelled_block_control_info(&mut self, label: Sym, start_address: u32) { |
||||||
|
let new_info = JumpControlInfo::default() |
||||||
|
.with_labelled_block_flag(true) |
||||||
|
.with_label(Some(label)) |
||||||
|
.with_start_address(start_address); |
||||||
|
self.jump_info.push(new_info); |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn pop_labelled_block_control_info(&mut self) { |
||||||
|
let info = self.jump_info.pop().expect("no jump information found"); |
||||||
|
|
||||||
|
assert!(info.is_labelled_block()); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
for label in info.breaks { |
||||||
|
self.patch_jump(label); |
||||||
|
} |
||||||
|
|
||||||
|
for label in info.try_continues { |
||||||
|
self.patch_jump_with_target(label, info.start_address); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
use crate::{bytecompiler::ByteCompiler, JsResult}; |
||||||
|
|
||||||
|
use boa_ast::statement::Block; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
/// Compile a [`Block`] `boa_ast` node
|
||||||
|
pub(crate) fn compile_block( |
||||||
|
&mut self, |
||||||
|
block: &Block, |
||||||
|
label: Option<Sym>, |
||||||
|
use_expr: bool, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
if let Some(label) = label { |
||||||
|
let next = self.next_opcode_location(); |
||||||
|
self.push_labelled_block_control_info(label, next); |
||||||
|
} |
||||||
|
|
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_and_track_decl_env(); |
||||||
|
|
||||||
|
self.create_decls(block.statement_list(), configurable_globals); |
||||||
|
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_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.1, index_compile_environment as u32); |
||||||
|
|
||||||
|
if label.is_some() { |
||||||
|
self.pop_labelled_block_control_info(); |
||||||
|
} else { |
||||||
|
self.emit_and_track_pop_env(); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
use boa_ast::statement::Break; |
||||||
|
|
||||||
|
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsNativeError, JsResult}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
/// Compile a [`Break`] `boa_ast` node
|
||||||
|
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()) { |
||||||
|
let in_finally = if let Some(finally_start) = info.finally_start() { |
||||||
|
next >= finally_start.index |
||||||
|
} else { |
||||||
|
false |
||||||
|
}; |
||||||
|
let in_catch_no_finally = !info.has_finally() && info.in_catch(); |
||||||
|
|
||||||
|
if in_finally { |
||||||
|
self.emit_opcode(Opcode::PopIfThrown); |
||||||
|
} |
||||||
|
if in_finally || in_catch_no_finally { |
||||||
|
self.emit_opcode(Opcode::CatchEnd2); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::TryEnd); |
||||||
|
} |
||||||
|
self.emit(Opcode::FinallySetJump, &[u32::MAX]); |
||||||
|
} |
||||||
|
let (break_label, envs_to_pop) = self.emit_opcode_with_two_operands(Opcode::Break); |
||||||
|
if let Some(label_name) = node.label() { |
||||||
|
let mut found = false; |
||||||
|
let mut total_envs: u32 = 0; |
||||||
|
for info in self.jump_info.iter_mut().rev() { |
||||||
|
total_envs += info.decl_envs(); |
||||||
|
if info.label() == Some(label_name) { |
||||||
|
info.push_break_label(break_label); |
||||||
|
found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO: promote to an early error.
|
||||||
|
if !found { |
||||||
|
return Err(JsNativeError::syntax() |
||||||
|
.with_message(format!( |
||||||
|
"Cannot use the undeclared label '{}'", |
||||||
|
self.interner().resolve_expect(label_name) |
||||||
|
)) |
||||||
|
.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(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
use crate::{bytecompiler::ByteCompiler, JsResult}; |
||||||
|
use boa_ast::statement::If; |
||||||
|
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
pub(crate) fn compile_if(&mut self, node: &If, configurable_globals: bool) -> JsResult<()> { |
||||||
|
self.compile_expr(node.cond(), true)?; |
||||||
|
let jelse = self.jump_if_false(); |
||||||
|
|
||||||
|
self.compile_stmt(node.body(), false, configurable_globals)?; |
||||||
|
|
||||||
|
match node.else_node() { |
||||||
|
None => { |
||||||
|
self.patch_jump(jelse); |
||||||
|
} |
||||||
|
Some(else_body) => { |
||||||
|
let exit = self.jump(); |
||||||
|
self.patch_jump(jelse); |
||||||
|
self.compile_stmt(else_body, false, configurable_globals)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
statement::{Labelled, LabelledItem}, |
||||||
|
Statement, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{ByteCompiler, NodeKind}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
/// Compile a [`Labelled`] `boa_ast` node
|
||||||
|
pub(crate) fn compile_labelled( |
||||||
|
&mut self, |
||||||
|
labelled: &Labelled, |
||||||
|
use_expr: bool, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
match labelled.item() { |
||||||
|
LabelledItem::Statement(stmt) => match stmt { |
||||||
|
Statement::ForLoop(for_loop) => { |
||||||
|
self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals)?; |
||||||
|
} |
||||||
|
Statement::ForInLoop(for_in_loop) => { |
||||||
|
self.compile_for_in_loop( |
||||||
|
for_in_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::ForOfLoop(for_of_loop) => { |
||||||
|
self.compile_for_of_loop( |
||||||
|
for_of_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::WhileLoop(while_loop) => { |
||||||
|
self.compile_while_loop( |
||||||
|
while_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::DoWhileLoop(do_while_loop) => { |
||||||
|
self.compile_do_while_loop( |
||||||
|
do_while_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::Block(block) => { |
||||||
|
self.compile_block( |
||||||
|
block, |
||||||
|
Some(labelled.label()), |
||||||
|
use_expr, |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
stmt => self.compile_stmt(stmt, use_expr, configurable_globals)?, |
||||||
|
}, |
||||||
|
LabelledItem::Function(f) => { |
||||||
|
self.function(f.into(), NodeKind::Declaration, false)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -1,228 +1,69 @@ |
|||||||
use boa_ast::{ |
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult}; |
||||||
statement::{Block, Break, If, Labelled, LabelledItem, Switch}, |
|
||||||
Statement, |
|
||||||
}; |
|
||||||
use boa_interner::Sym; |
|
||||||
|
|
||||||
use crate::{vm::Opcode, JsNativeError, JsResult}; |
use boa_ast::Statement; |
||||||
|
|
||||||
use super::{ByteCompiler, JumpControlInfoKind, NodeKind}; |
|
||||||
|
|
||||||
|
mod block; |
||||||
|
mod r#break; |
||||||
mod r#continue; |
mod r#continue; |
||||||
|
mod r#if; |
||||||
|
mod labelled; |
||||||
mod r#loop; |
mod r#loop; |
||||||
|
mod switch; |
||||||
mod r#try; |
mod r#try; |
||||||
|
|
||||||
impl ByteCompiler<'_, '_> { |
impl ByteCompiler<'_, '_> { |
||||||
pub(crate) fn compile_if(&mut self, node: &If, configurable_globals: bool) -> JsResult<()> { |
/// Compiles a [`Statement`] `boa_ast` node.
|
||||||
self.compile_expr(node.cond(), true)?; |
pub fn compile_stmt( |
||||||
let jelse = self.jump_if_false(); |
|
||||||
|
|
||||||
self.compile_stmt(node.body(), false, configurable_globals)?; |
|
||||||
|
|
||||||
match node.else_node() { |
|
||||||
None => { |
|
||||||
self.patch_jump(jelse); |
|
||||||
} |
|
||||||
Some(else_body) => { |
|
||||||
let exit = self.jump(); |
|
||||||
self.patch_jump(jelse); |
|
||||||
self.compile_stmt(else_body, false, configurable_globals)?; |
|
||||||
self.patch_jump(exit); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
pub(crate) fn compile_labelled( |
|
||||||
&mut self, |
&mut self, |
||||||
labelled: &Labelled, |
node: &Statement, |
||||||
use_expr: bool, |
use_expr: bool, |
||||||
configurable_globals: bool, |
configurable_globals: bool, |
||||||
) -> JsResult<()> { |
) -> JsResult<()> { |
||||||
match labelled.item() { |
match node { |
||||||
LabelledItem::Statement(stmt) => match stmt { |
Statement::Var(var) => self.compile_var_decl(var)?, |
||||||
Statement::ForLoop(for_loop) => { |
Statement::If(node) => self.compile_if(node, configurable_globals)?, |
||||||
self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals)?; |
Statement::ForLoop(for_loop) => { |
||||||
} |
self.compile_for_loop(for_loop, None, configurable_globals)?; |
||||||
Statement::ForInLoop(for_in_loop) => { |
|
||||||
self.compile_for_in_loop( |
|
||||||
for_in_loop, |
|
||||||
Some(labelled.label()), |
|
||||||
configurable_globals, |
|
||||||
)?; |
|
||||||
} |
|
||||||
Statement::ForOfLoop(for_of_loop) => { |
|
||||||
self.compile_for_of_loop( |
|
||||||
for_of_loop, |
|
||||||
Some(labelled.label()), |
|
||||||
configurable_globals, |
|
||||||
)?; |
|
||||||
} |
|
||||||
Statement::WhileLoop(while_loop) => { |
|
||||||
self.compile_while_loop( |
|
||||||
while_loop, |
|
||||||
Some(labelled.label()), |
|
||||||
configurable_globals, |
|
||||||
)?; |
|
||||||
} |
|
||||||
Statement::DoWhileLoop(do_while_loop) => { |
|
||||||
self.compile_do_while_loop( |
|
||||||
do_while_loop, |
|
||||||
Some(labelled.label()), |
|
||||||
configurable_globals, |
|
||||||
)?; |
|
||||||
} |
|
||||||
Statement::Block(block) => { |
|
||||||
self.compile_block( |
|
||||||
block, |
|
||||||
Some(labelled.label()), |
|
||||||
use_expr, |
|
||||||
configurable_globals, |
|
||||||
)?; |
|
||||||
} |
|
||||||
stmt => self.compile_stmt(stmt, use_expr, configurable_globals)?, |
|
||||||
}, |
|
||||||
LabelledItem::Function(f) => { |
|
||||||
self.function(f.into(), NodeKind::Declaration, false)?; |
|
||||||
} |
} |
||||||
} |
Statement::ForInLoop(for_in_loop) => { |
||||||
|
self.compile_for_in_loop(for_in_loop, None, configurable_globals)?; |
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
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.kind == JumpControlInfoKind::Try) |
|
||||||
{ |
|
||||||
let in_finally = if let Some(finally_start) = info.finally_start { |
|
||||||
next >= finally_start.index |
|
||||||
} else { |
|
||||||
false |
|
||||||
}; |
|
||||||
let in_catch_no_finally = !info.has_finally && info.in_catch; |
|
||||||
|
|
||||||
if in_finally { |
|
||||||
self.emit_opcode(Opcode::PopIfThrown); |
|
||||||
} |
} |
||||||
if in_finally || in_catch_no_finally { |
Statement::ForOfLoop(for_of_loop) => { |
||||||
self.emit_opcode(Opcode::CatchEnd2); |
self.compile_for_of_loop(for_of_loop, None, configurable_globals)?; |
||||||
} else { |
|
||||||
self.emit_opcode(Opcode::TryEnd); |
|
||||||
} |
} |
||||||
self.emit(Opcode::FinallySetJump, &[u32::MAX]); |
Statement::WhileLoop(while_loop) => { |
||||||
} |
self.compile_while_loop(while_loop, None, configurable_globals)?; |
||||||
let label = self.jump(); |
|
||||||
if let Some(label_name) = node.label() { |
|
||||||
let mut found = false; |
|
||||||
for info in self.jump_info.iter_mut().rev() { |
|
||||||
if info.label == Some(label_name) { |
|
||||||
info.breaks.push(label); |
|
||||||
found = true; |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
} |
||||||
// TODO: promote to an early error.
|
Statement::DoWhileLoop(do_while_loop) => { |
||||||
if !found { |
self.compile_do_while_loop(do_while_loop, None, configurable_globals)?; |
||||||
return Err(JsNativeError::syntax() |
|
||||||
.with_message(format!( |
|
||||||
"Cannot use the undeclared label '{}'", |
|
||||||
self.interner().resolve_expect(label_name) |
|
||||||
)) |
|
||||||
.into()); |
|
||||||
} |
} |
||||||
} else { |
Statement::Block(block) => { |
||||||
self.jump_info |
self.compile_block(block, None, use_expr, configurable_globals)?; |
||||||
.last_mut() |
} |
||||||
// TODO: promote to an early error.
|
Statement::Labelled(labelled) => { |
||||||
.ok_or_else(|| { |
self.compile_labelled(labelled, use_expr, configurable_globals)?; |
||||||
JsNativeError::syntax() |
} |
||||||
.with_message("unlabeled break must be inside loop or switch") |
Statement::Continue(node) => self.compile_continue(*node)?, |
||||||
})? |
Statement::Break(node) => self.compile_break(*node)?, |
||||||
.breaks |
Statement::Throw(throw) => { |
||||||
.push(label); |
self.compile_expr(throw.target(), true)?; |
||||||
} |
self.emit(Opcode::Throw, &[]); |
||||||
|
} |
||||||
Ok(()) |
Statement::Switch(switch) => { |
||||||
} |
self.compile_switch(switch, configurable_globals)?; |
||||||
|
} |
||||||
pub(crate) fn compile_switch( |
Statement::Return(ret) => { |
||||||
&mut self, |
if let Some(expr) = ret.target() { |
||||||
switch: &Switch, |
self.compile_expr(expr, true)?; |
||||||
configurable_globals: bool, |
} else { |
||||||
) -> JsResult<()> { |
self.emit(Opcode::PushUndefined, &[]); |
||||||
self.context.push_compile_time_environment(false); |
} |
||||||
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
self.emit(Opcode::Return, &[]); |
||||||
for case in switch.cases() { |
} |
||||||
self.create_decls(case.body(), configurable_globals); |
Statement::Try(t) => self.compile_try(t, use_expr, configurable_globals)?, |
||||||
} |
Statement::Empty => {} |
||||||
self.emit_opcode(Opcode::LoopStart); |
Statement::Expression(expr) => self.compile_expr(expr, use_expr)?, |
||||||
|
|
||||||
let start_address = self.next_opcode_location(); |
|
||||||
self.push_switch_control_info(None, start_address); |
|
||||||
|
|
||||||
self.compile_expr(switch.val(), true)?; |
|
||||||
let mut labels = Vec::with_capacity(switch.cases().len()); |
|
||||||
for case in switch.cases() { |
|
||||||
self.compile_expr(case.condition(), true)?; |
|
||||||
labels.push(self.emit_opcode_with_operand(Opcode::Case)); |
|
||||||
} |
|
||||||
|
|
||||||
let exit = self.emit_opcode_with_operand(Opcode::Default); |
|
||||||
|
|
||||||
for (label, case) in labels.into_iter().zip(switch.cases()) { |
|
||||||
self.patch_jump(label); |
|
||||||
self.compile_statement_list(case.body(), false, configurable_globals)?; |
|
||||||
} |
|
||||||
|
|
||||||
self.patch_jump(exit); |
|
||||||
if let Some(body) = switch.default() { |
|
||||||
self.create_decls(body, configurable_globals); |
|
||||||
self.compile_statement_list(body, false, configurable_globals)?; |
|
||||||
} |
|
||||||
|
|
||||||
self.pop_switch_control_info(); |
|
||||||
|
|
||||||
self.emit_opcode(Opcode::LoopEnd); |
|
||||||
|
|
||||||
let (num_bindings, compile_environment) = self.context.pop_compile_time_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.1, index_compile_environment as u32); |
|
||||||
self.emit_opcode(Opcode::PopEnvironment); |
|
||||||
|
|
||||||
Ok(()) |
|
||||||
} |
|
||||||
|
|
||||||
pub(crate) fn compile_block( |
|
||||||
&mut self, |
|
||||||
block: &Block, |
|
||||||
label: Option<Sym>, |
|
||||||
use_expr: bool, |
|
||||||
configurable_globals: bool, |
|
||||||
) -> JsResult<()> { |
|
||||||
if let Some(label) = label { |
|
||||||
let next = self.next_opcode_location(); |
|
||||||
self.push_labelled_block_control_info(label, next); |
|
||||||
} |
|
||||||
|
|
||||||
self.context.push_compile_time_environment(false); |
|
||||||
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
|
||||||
self.create_decls(block.statement_list(), configurable_globals); |
|
||||||
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?; |
|
||||||
let (num_bindings, compile_environment) = self.context.pop_compile_time_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.1, index_compile_environment as u32); |
|
||||||
|
|
||||||
if label.is_some() { |
|
||||||
self.pop_labelled_block_control_info(); |
|
||||||
} |
} |
||||||
|
|
||||||
self.emit_opcode(Opcode::PopEnvironment); |
|
||||||
Ok(()) |
Ok(()) |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,54 @@ |
|||||||
|
use boa_ast::statement::Switch; |
||||||
|
|
||||||
|
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_, '_> { |
||||||
|
/// Compile a [`Switch`] `boa_ast` node
|
||||||
|
pub(crate) fn compile_switch( |
||||||
|
&mut self, |
||||||
|
switch: &Switch, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
for case in switch.cases() { |
||||||
|
self.create_decls(case.body(), configurable_globals); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
|
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_switch_control_info(None, start_address); |
||||||
|
|
||||||
|
self.compile_expr(switch.val(), true)?; |
||||||
|
let mut labels = Vec::with_capacity(switch.cases().len()); |
||||||
|
for case in switch.cases() { |
||||||
|
self.compile_expr(case.condition(), true)?; |
||||||
|
labels.push(self.emit_opcode_with_operand(Opcode::Case)); |
||||||
|
} |
||||||
|
|
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::Default); |
||||||
|
|
||||||
|
for (label, case) in labels.into_iter().zip(switch.cases()) { |
||||||
|
self.patch_jump(label); |
||||||
|
self.compile_statement_list(case.body(), false, configurable_globals)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.patch_jump(exit); |
||||||
|
if let Some(body) = switch.default() { |
||||||
|
self.create_decls(body, configurable_globals); |
||||||
|
self.compile_statement_list(body, false, configurable_globals)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.pop_switch_control_info(); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_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.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
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) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue