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::{ |
||||
statement::{Block, Break, If, Labelled, LabelledItem, Switch}, |
||||
Statement, |
||||
}; |
||||
use boa_interner::Sym; |
||||
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult}; |
||||
|
||||
use crate::{vm::Opcode, JsNativeError, JsResult}; |
||||
|
||||
use super::{ByteCompiler, JumpControlInfoKind, NodeKind}; |
||||
use boa_ast::Statement; |
||||
|
||||
mod block; |
||||
mod r#break; |
||||
mod r#continue; |
||||
mod r#if; |
||||
mod labelled; |
||||
mod r#loop; |
||||
mod switch; |
||||
mod r#try; |
||||
|
||||
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(()) |
||||
} |
||||
|
||||
pub(crate) fn compile_labelled( |
||||
/// Compiles a [`Statement`] `boa_ast` node.
|
||||
pub fn compile_stmt( |
||||
&mut self, |
||||
labelled: &Labelled, |
||||
node: &Statement, |
||||
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)?; |
||||
match node { |
||||
Statement::Var(var) => self.compile_var_decl(var)?, |
||||
Statement::If(node) => self.compile_if(node, configurable_globals)?, |
||||
Statement::ForLoop(for_loop) => { |
||||
self.compile_for_loop(for_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); |
||||
Statement::ForInLoop(for_in_loop) => { |
||||
self.compile_for_in_loop(for_in_loop, None, configurable_globals)?; |
||||
} |
||||
if in_finally || in_catch_no_finally { |
||||
self.emit_opcode(Opcode::CatchEnd2); |
||||
} else { |
||||
self.emit_opcode(Opcode::TryEnd); |
||||
Statement::ForOfLoop(for_of_loop) => { |
||||
self.compile_for_of_loop(for_of_loop, None, configurable_globals)?; |
||||
} |
||||
self.emit(Opcode::FinallySetJump, &[u32::MAX]); |
||||
} |
||||
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; |
||||
} |
||||
Statement::WhileLoop(while_loop) => { |
||||
self.compile_while_loop(while_loop, None, configurable_globals)?; |
||||
} |
||||
// 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()); |
||||
Statement::DoWhileLoop(do_while_loop) => { |
||||
self.compile_do_while_loop(do_while_loop, None, configurable_globals)?; |
||||
} |
||||
} else { |
||||
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") |
||||
})? |
||||
.breaks |
||||
.push(label); |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
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(()) |
||||
} |
||||
|
||||
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(); |
||||
Statement::Block(block) => { |
||||
self.compile_block(block, None, use_expr, configurable_globals)?; |
||||
} |
||||
Statement::Labelled(labelled) => { |
||||
self.compile_labelled(labelled, use_expr, configurable_globals)?; |
||||
} |
||||
Statement::Continue(node) => self.compile_continue(*node)?, |
||||
Statement::Break(node) => self.compile_break(*node)?, |
||||
Statement::Throw(throw) => { |
||||
self.compile_expr(throw.target(), true)?; |
||||
self.emit(Opcode::Throw, &[]); |
||||
} |
||||
Statement::Switch(switch) => { |
||||
self.compile_switch(switch, configurable_globals)?; |
||||
} |
||||
Statement::Return(ret) => { |
||||
if let Some(expr) = ret.target() { |
||||
self.compile_expr(expr, true)?; |
||||
} else { |
||||
self.emit(Opcode::PushUndefined, &[]); |
||||
} |
||||
self.emit(Opcode::Return, &[]); |
||||
} |
||||
Statement::Try(t) => self.compile_try(t, use_expr, configurable_globals)?, |
||||
Statement::Empty => {} |
||||
Statement::Expression(expr) => self.compile_expr(expr, use_expr)?, |
||||
} |
||||
|
||||
self.emit_opcode(Opcode::PopEnvironment); |
||||
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