mirror of https://github.com/boa-dev/boa.git
Browse Source
* Refactor environment, exception handling, generators and jumping in VM * Add helper for JumpIfNotResumeKind opcode * Better handler display for CodeBlock * Update documentaion * Factor exception handling from throw opcodes * Simplify try compilation * Only push try statement jump controll info if needed * Add helper functions checks in async and generator * Run prettier * Remove redundant check for end of bytecode. We always emit a `Return` opcode at the end of a function, so this should never be reached. * Fix doc link * Implement stack_count calculation of handlers * Fix typo * Rename `LoopContinue` to `IncrementLoopIteration` * Fix typo * Remove #[allow(unused)] from Handler fieldpull/3186/head
Haled Odat
1 year ago
committed by
GitHub
46 changed files with 1223 additions and 1917 deletions
@ -1,73 +1,42 @@
|
||||
use crate::{ |
||||
bytecompiler::{ByteCompiler, JumpControlInfo}, |
||||
vm::Opcode, |
||||
use crate::bytecompiler::{ |
||||
jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind}, |
||||
ByteCompiler, |
||||
}; |
||||
use boa_ast::statement::Break; |
||||
use boa_interner::Sym; |
||||
|
||||
impl ByteCompiler<'_, '_> { |
||||
/// Compile a [`Break`] `boa_ast` node
|
||||
pub(crate) fn compile_break(&mut self, node: Break, _use_expr: bool) { |
||||
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) { |
||||
let has_finally_or_is_finally = info.has_finally() || info.in_finally(); |
||||
let actions = self.break_jump_record_actions(node); |
||||
|
||||
let (break_label, target_jump_label) = |
||||
self.emit_opcode_with_two_operands(Opcode::Break); |
||||
|
||||
if let Some(node_label) = node.label() { |
||||
let info = self.jump_info_label(node_label); |
||||
info.push_break_label(target_jump_label); |
||||
|
||||
if !has_finally_or_is_finally { |
||||
info.push_break_label(break_label); |
||||
return; |
||||
} |
||||
} else { |
||||
self.jump_info |
||||
.last_mut() |
||||
.expect("jump_info must exist to reach this point") |
||||
.push_set_jumps(target_jump_label); |
||||
} |
||||
|
||||
let info = self |
||||
.jump_info |
||||
.last_mut() |
||||
.expect("This try block must exist"); |
||||
|
||||
info.push_break_label(break_label); |
||||
|
||||
return; |
||||
} |
||||
|
||||
// Emit the break opcode -> (Label, Label)
|
||||
let (break_label, target_label) = self.emit_opcode_with_two_operands(Opcode::Break); |
||||
if let Some(label) = node.label() { |
||||
let info = self.jump_info_label(label); |
||||
info.push_break_label(break_label); |
||||
info.push_break_label(target_label); |
||||
return; |
||||
} |
||||
|
||||
let info = self.jump_info_non_labelled(); |
||||
info.push_break_label(break_label); |
||||
info.push_break_label(target_label); |
||||
JumpRecord::new(JumpRecordKind::Break, actions).perform_actions(Self::DUMMY_ADDRESS, self); |
||||
} |
||||
|
||||
fn jump_info_non_labelled(&mut self) -> &mut JumpControlInfo { |
||||
for info in self.jump_info.iter_mut().rev() { |
||||
if !info.is_labelled() { |
||||
return info; |
||||
fn break_jump_record_actions(&self, node: Break) -> Vec<JumpRecordAction> { |
||||
let mut actions = Vec::default(); |
||||
for (i, info) in self.jump_info.iter().enumerate().rev() { |
||||
let count = self.jump_info_open_environment_count(i); |
||||
actions.push(JumpRecordAction::PopEnvironments { count }); |
||||
|
||||
if info.is_try_with_finally_block() && !info.in_finally() { |
||||
actions.push(JumpRecordAction::HandleFinally { |
||||
index: info.jumps.len() as u32, |
||||
}); |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
} |
||||
} |
||||
panic!("Jump info without label must exist"); |
||||
} |
||||
|
||||
fn jump_info_label(&mut self, label: Sym) -> &mut JumpControlInfo { |
||||
for info in self.jump_info.iter_mut().rev() { |
||||
if info.label() == Some(label) { |
||||
return info; |
||||
if let Some(label) = node.label() { |
||||
if info.label() == Some(label) { |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
break; |
||||
} |
||||
} else if info.is_loop() || info.is_switch() { |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
break; |
||||
} |
||||
} |
||||
panic!("Jump info with label must exist"); |
||||
|
||||
actions.reverse(); |
||||
actions |
||||
} |
||||
} |
||||
|
@ -1,118 +1,49 @@
|
||||
use crate::{bytecompiler::ByteCompiler, vm::Opcode}; |
||||
use crate::bytecompiler::{ |
||||
jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind}, |
||||
ByteCompiler, |
||||
}; |
||||
use boa_ast::statement::Continue; |
||||
|
||||
impl ByteCompiler<'_, '_> { |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn compile_continue(&mut self, node: Continue, _use_expr: bool) { |
||||
if let Some(info) = self.jump_info.last().filter(|info| info.is_try_block()) { |
||||
let in_finally = info.in_finally(); |
||||
let in_finally_or_has_finally = in_finally || info.has_finally(); |
||||
let actions = self.continue_jump_record_actions(node); |
||||
|
||||
// 1. Handle if node has a label.
|
||||
if let Some(node_label) = node.label() { |
||||
let items = self.jump_info.iter().rev().filter(|info| info.is_loop()); |
||||
let mut iterator_closes = Vec::new(); |
||||
|
||||
for info in items { |
||||
if info.label() == Some(node_label) { |
||||
break; |
||||
} |
||||
|
||||
if info.iterator_loop() { |
||||
iterator_closes.push(info.for_await_of_loop()); |
||||
} |
||||
} |
||||
|
||||
for r#async in iterator_closes { |
||||
self.iterator_close(r#async); |
||||
} |
||||
JumpRecord::new(JumpRecordKind::Continue, actions) |
||||
.perform_actions(Self::DUMMY_ADDRESS, self); |
||||
} |
||||
|
||||
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); |
||||
fn continue_jump_record_actions(&self, node: Continue) -> Vec<JumpRecordAction> { |
||||
let mut actions = Vec::default(); |
||||
for (i, info) in self.jump_info.iter().enumerate().rev() { |
||||
let count = self.jump_info_open_environment_count(i); |
||||
actions.push(JumpRecordAction::PopEnvironments { count }); |
||||
|
||||
if info.is_try_with_finally_block() && !info.in_finally() { |
||||
actions.push(JumpRecordAction::HandleFinally { |
||||
index: info.jumps.len() as u32, |
||||
}); |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
} |
||||
|
||||
let loops = self |
||||
.jump_info |
||||
.iter_mut() |
||||
.rev() |
||||
.filter(|info| info.is_loop()); |
||||
let mut set_continue_as_break = false; |
||||
for info in loops { |
||||
let found_label = info.label() == Some(node_label); |
||||
if found_label && in_finally_or_has_finally { |
||||
set_continue_as_break = true; |
||||
info.push_try_continue_label(set_label); |
||||
break; |
||||
} else if found_label && !in_finally_or_has_finally { |
||||
info.push_try_continue_label(cont_label); |
||||
info.push_try_continue_label(set_label); |
||||
break; |
||||
} |
||||
} |
||||
if set_continue_as_break { |
||||
self.jump_info |
||||
.last_mut() |
||||
.expect("no jump information found") |
||||
.push_break_label(cont_label); |
||||
} |
||||
} else { |
||||
// TODO: Add has finally or in finally here
|
||||
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); |
||||
if in_finally_or_has_finally { |
||||
self.jump_info |
||||
.last_mut() |
||||
.expect("Must exist and be a try block") |
||||
.push_break_label(cont_label); |
||||
}; |
||||
let mut items = self |
||||
.jump_info |
||||
.iter_mut() |
||||
.rev() |
||||
.filter(|info| info.is_loop()); |
||||
let jump_info = items.next().expect("continue must be inside loop"); |
||||
if !in_finally_or_has_finally { |
||||
jump_info.push_try_continue_label(cont_label); |
||||
}; |
||||
jump_info.push_try_continue_label(set_label); |
||||
}; |
||||
} else if let Some(node_label) = node.label() { |
||||
let items = self.jump_info.iter().rev().filter(|info| info.is_loop()); |
||||
let mut iterator_closes = Vec::new(); |
||||
for info in items { |
||||
if info.label() == Some(node_label) { |
||||
if let Some(label) = node.label() { |
||||
if info.label() == Some(label) { |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
break; |
||||
} |
||||
|
||||
if info.iterator_loop() { |
||||
iterator_closes.push(info.for_await_of_loop()); |
||||
} |
||||
} |
||||
|
||||
for r#async in iterator_closes { |
||||
self.iterator_close(r#async); |
||||
} else if info.is_loop() { |
||||
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||
break; |
||||
} |
||||
|
||||
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); |
||||
let loops = self |
||||
.jump_info |
||||
.iter_mut() |
||||
.rev() |
||||
.filter(|info| info.is_loop()); |
||||
|
||||
for info in loops { |
||||
if info.label() == Some(node_label) { |
||||
info.push_try_continue_label(cont_label); |
||||
info.push_try_continue_label(set_label); |
||||
} |
||||
if info.iterator_loop() { |
||||
actions.push(JumpRecordAction::CloseIterator { |
||||
r#async: info.for_await_of_loop(), |
||||
}); |
||||
} |
||||
} else { |
||||
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); |
||||
let mut items = self |
||||
.jump_info |
||||
.iter_mut() |
||||
.rev() |
||||
.filter(|info| info.is_loop()); |
||||
let jump_info = items.next().expect("continue must be inside loop"); |
||||
jump_info.push_try_continue_label(cont_label); |
||||
jump_info.push_try_continue_label(set_label); |
||||
} |
||||
|
||||
actions.reverse(); |
||||
actions |
||||
} |
||||
} |
||||
|
@ -1,89 +0,0 @@
|
||||
//! Implements an `AbruptCompletionRecord` struct for `CallFrame` tracking.
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)] |
||||
pub(crate) enum AbruptKind { |
||||
Continue, |
||||
Break, |
||||
Throw, |
||||
Return, |
||||
} |
||||
|
||||
/// The `AbruptCompletionRecord` tracks the current `AbruptCompletion` and target address of completion.
|
||||
#[derive(Clone, Copy, Debug)] |
||||
pub(crate) struct AbruptCompletionRecord { |
||||
kind: AbruptKind, |
||||
target: u32, |
||||
} |
||||
|
||||
/// ---- `AbruptCompletionRecord` initialization methods ----
|
||||
impl AbruptCompletionRecord { |
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Break`.
|
||||
pub(crate) const fn new_break() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Break, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Continue`.
|
||||
pub(crate) const fn new_continue() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Continue, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Throw`.
|
||||
pub(crate) const fn new_throw() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Throw, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Creates an `AbruptCompletionRecord` for an abrupt `Return`.
|
||||
pub(crate) const fn new_return() -> Self { |
||||
Self { |
||||
kind: AbruptKind::Return, |
||||
target: u32::MAX, |
||||
} |
||||
} |
||||
|
||||
/// Set the target field of the `AbruptCompletionRecord`.
|
||||
pub(crate) const fn with_initial_target(mut self, target: u32) -> Self { |
||||
self.target = target; |
||||
self |
||||
} |
||||
} |
||||
|
||||
/// ---- `AbruptCompletionRecord` interaction methods ----
|
||||
impl AbruptCompletionRecord { |
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a break.
|
||||
pub(crate) fn is_break(self) -> bool { |
||||
self.kind == AbruptKind::Break |
||||
} |
||||
|
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a continue.
|
||||
pub(crate) fn is_continue(self) -> bool { |
||||
self.kind == AbruptKind::Continue |
||||
} |
||||
|
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a return.
|
||||
pub(crate) fn is_return(self) -> bool { |
||||
self.kind == AbruptKind::Return |
||||
} |
||||
|
||||
/// Returns a boolean value for whether `AbruptCompletionRecord` is a throw.
|
||||
pub(crate) fn is_throw(self) -> bool { |
||||
self.kind == AbruptKind::Throw |
||||
} |
||||
|
||||
pub(crate) fn is_throw_with_target(self) -> bool { |
||||
self.is_throw() && self.target < u32::MAX |
||||
} |
||||
|
||||
/// Returns the value of `AbruptCompletionRecord`'s `target` field.
|
||||
pub(crate) const fn target(self) -> u32 { |
||||
self.target |
||||
} |
||||
} |
@ -1,201 +0,0 @@
|
||||
//! Module for implementing a `CallFrame`'s environment stacks
|
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub(crate) enum EnvEntryKind { |
||||
Global, |
||||
Loop { |
||||
/// How many iterations a loop has done.
|
||||
iteration_count: u64, |
||||
|
||||
/// The index of the currently active iterator.
|
||||
iterator: Option<u32>, |
||||
}, |
||||
Try { |
||||
/// The length of the value stack when the try block was entered.
|
||||
///
|
||||
/// This is used to pop exact amount values from the stack
|
||||
/// when a throw happens.
|
||||
fp: u32, |
||||
}, |
||||
Finally, |
||||
Labelled, |
||||
} |
||||
|
||||
/// The `EnvStackEntry` tracks the environment count and relevant information for the current environment.
|
||||
#[derive(Clone, Debug)] |
||||
pub(crate) struct EnvStackEntry { |
||||
start: u32, |
||||
exit: u32, |
||||
kind: EnvEntryKind, |
||||
env_num: usize, |
||||
} |
||||
|
||||
impl Default for EnvStackEntry { |
||||
fn default() -> Self { |
||||
Self { |
||||
start: 0, |
||||
exit: u32::MAX, |
||||
kind: EnvEntryKind::Global, |
||||
env_num: 0, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// ---- `EnvStackEntry` creation methods ----
|
||||
impl EnvStackEntry { |
||||
/// Creates a new [`EnvStackEntry`] with the supplied start addresses.
|
||||
pub(crate) const fn new(start_address: u32, exit_address: u32) -> Self { |
||||
Self { |
||||
start: start_address, |
||||
exit: exit_address, |
||||
kind: EnvEntryKind::Global, |
||||
env_num: 0, |
||||
} |
||||
} |
||||
|
||||
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Try`].
|
||||
pub(crate) const fn with_try_flag(mut self, fp: u32) -> Self { |
||||
self.kind = EnvEntryKind::Try { fp }; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Loop`], loop iteration set to zero
|
||||
/// and iterator index set to `iterator`.
|
||||
pub(crate) const fn with_iterator_loop_flag( |
||||
mut self, |
||||
iteration_count: u64, |
||||
iterator: u32, |
||||
) -> Self { |
||||
self.kind = EnvEntryKind::Loop { |
||||
iteration_count, |
||||
iterator: Some(iterator), |
||||
}; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Loop`].
|
||||
/// And the loop iteration set to zero.
|
||||
pub(crate) const fn with_loop_flag(mut self, iteration_count: u64) -> Self { |
||||
self.kind = EnvEntryKind::Loop { |
||||
iteration_count, |
||||
iterator: None, |
||||
}; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Finally`].
|
||||
pub(crate) const fn with_finally_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Finally; |
||||
self |
||||
} |
||||
|
||||
/// Returns calling [`EnvStackEntry`] with `kind` field of [`EnvEntryKind::Labelled`].
|
||||
pub(crate) const fn with_labelled_flag(mut self) -> Self { |
||||
self.kind = EnvEntryKind::Labelled; |
||||
self |
||||
} |
||||
|
||||
pub(crate) const fn with_start_address(mut self, start_address: u32) -> Self { |
||||
self.start = start_address; |
||||
self |
||||
} |
||||
} |
||||
|
||||
/// ---- `EnvStackEntry` interaction methods ----
|
||||
impl EnvStackEntry { |
||||
/// Returns the `start` field of this `EnvStackEntry`.
|
||||
pub(crate) const fn start_address(&self) -> u32 { |
||||
self.start |
||||
} |
||||
|
||||
/// Returns the `exit` field of this `EnvStackEntry`.
|
||||
pub(crate) const fn exit_address(&self) -> u32 { |
||||
self.exit |
||||
} |
||||
|
||||
/// Returns the `fp` field of this [`EnvEntryKind::Try`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If this [`EnvStackEntry`] is **not** a [`EnvEntryKind::Try`].
|
||||
pub(crate) fn try_env_frame_pointer(&self) -> u32 { |
||||
if let EnvEntryKind::Try { fp } = &self.kind { |
||||
return *fp; |
||||
} |
||||
unreachable!("trying to get frame pointer of a non-try environment") |
||||
} |
||||
|
||||
pub(crate) const fn is_global_env(&self) -> bool { |
||||
matches!(self.kind, EnvEntryKind::Global) |
||||
} |
||||
|
||||
/// Returns the loop iteration count if `EnvStackEntry` is a loop.
|
||||
pub(crate) const fn as_loop_iteration_count(&self) -> Option<u64> { |
||||
if let EnvEntryKind::Loop { |
||||
iteration_count, .. |
||||
} = self.kind |
||||
{ |
||||
return Some(iteration_count); |
||||
} |
||||
None |
||||
} |
||||
|
||||
/// Increases loop iteration count if `EnvStackEntry` is a loop.
|
||||
pub(crate) fn increase_loop_iteration_count(&mut self) { |
||||
if let EnvEntryKind::Loop { |
||||
iteration_count, .. |
||||
} = &mut self.kind |
||||
{ |
||||
*iteration_count = iteration_count.wrapping_add(1); |
||||
} |
||||
} |
||||
|
||||
/// Returns the active iterator index if `EnvStackEntry` is an iterator loop.
|
||||
pub(crate) const fn iterator(&self) -> Option<u32> { |
||||
if let EnvEntryKind::Loop { iterator, .. } = self.kind { |
||||
return iterator; |
||||
} |
||||
None |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a try block
|
||||
pub(crate) const fn is_try_env(&self) -> bool { |
||||
matches!(self.kind, EnvEntryKind::Try { .. }) |
||||
} |
||||
|
||||
/// Returns true if an `EnvStackEntry` is a labelled block
|
||||
pub(crate) const fn is_labelled_env(&self) -> bool { |
||||
matches!(self.kind, EnvEntryKind::Labelled) |
||||
} |
||||
|
||||
pub(crate) const fn is_finally_env(&self) -> bool { |
||||
matches!(self.kind, EnvEntryKind::Finally) |
||||
} |
||||
|
||||
pub(crate) const fn is_loop_env(&self) -> bool { |
||||
matches!(self.kind, EnvEntryKind::Loop { .. }) |
||||
} |
||||
|
||||
/// Returns the current environment number for this entry.
|
||||
pub(crate) const fn env_num(&self) -> usize { |
||||
self.env_num |
||||
} |
||||
|
||||
pub(crate) fn set_exit_address(&mut self, exit_address: u32) { |
||||
self.exit = exit_address; |
||||
} |
||||
|
||||
pub(crate) fn clear_env_num(&mut self) { |
||||
self.env_num = 0; |
||||
} |
||||
|
||||
/// Increments the `env_num` field for current `EnvEntryStack`.
|
||||
pub(crate) fn inc_env_num(&mut self) { |
||||
(self.env_num, _) = self.env_num.overflowing_add(1); |
||||
} |
||||
|
||||
/// Decrements the `env_num` field for current `EnvEntryStack`.
|
||||
pub(crate) fn dec_env_num(&mut self) { |
||||
(self.env_num, _) = self.env_num.overflowing_sub(1); |
||||
} |
||||
} |
@ -1,54 +0,0 @@
|
||||
use crate::{ |
||||
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `Break` implements the Opcode Operation for `Opcode::Break`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Pop required environments and jump to address.
|
||||
pub(crate) struct Break; |
||||
|
||||
impl Operation for Break { |
||||
const NAME: &'static str = "Break"; |
||||
const INSTRUCTION: &'static str = "INST - Break"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let jump_address = context.vm.read::<u32>(); |
||||
let target_address = context.vm.read::<u32>(); |
||||
|
||||
// 1. Iterate through Env stack looking for exit address.
|
||||
let mut envs_to_pop = 0; |
||||
let mut found_target = false; |
||||
for i in (0..context.vm.frame().env_stack.len()).rev() { |
||||
if found_target { |
||||
break; |
||||
} |
||||
|
||||
let Some(env_entry) = context.vm.frame_mut().env_stack.get_mut(i) else { |
||||
break; |
||||
}; |
||||
|
||||
if jump_address == env_entry.exit_address() |
||||
|| (env_entry.is_finally_env() && jump_address == env_entry.start_address()) |
||||
{ |
||||
found_target = true; |
||||
continue; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
// 2. Register target address in AbruptCompletionRecord.
|
||||
let new_record = AbruptCompletionRecord::new_break().with_initial_target(target_address); |
||||
context.vm.frame_mut().abrupt_completion = Some(new_record); |
||||
|
||||
// 3. Set program counter and finally return fields.
|
||||
context.vm.frame_mut().pc = jump_address; |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
@ -1,67 +0,0 @@
|
||||
use crate::{ |
||||
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `Continue` implements the Opcode Operation for `Opcode::Continue`
|
||||
///
|
||||
/// Operands:
|
||||
/// - Target address
|
||||
/// - Initial environments to reconcile on continue (will be tracked along with changes to environment stack)
|
||||
///
|
||||
/// Operation:
|
||||
/// - Initializes the `AbruptCompletionRecord` for a delayed continued in a `Opcode::FinallyEnd`
|
||||
pub(crate) struct Continue; |
||||
|
||||
impl Operation for Continue { |
||||
const NAME: &'static str = "Continue"; |
||||
const INSTRUCTION: &'static str = "INST - Continue"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let jump_address = context.vm.read::<u32>(); |
||||
let target_address = context.vm.read::<u32>(); |
||||
|
||||
// 1. Iterate through Env stack looking for exit address.
|
||||
let mut envs_to_pop = 0; |
||||
let mut found_target = false; |
||||
for i in (0..context.vm.frame().env_stack.len()).rev() { |
||||
if found_target { |
||||
break; |
||||
} |
||||
|
||||
let Some(env_entry) = context.vm.frame_mut().env_stack.get_mut(i) else { |
||||
break; |
||||
}; |
||||
|
||||
// We check two conditions here where continue actually jumps to a higher address.
|
||||
// 1. When we have reached a finally env that matches the jump address we are moving to.
|
||||
// 2. When there is no finally, and we have reached the continue location.
|
||||
if (env_entry.is_finally_env() && jump_address == env_entry.start_address()) |
||||
|| (jump_address == target_address && jump_address == env_entry.start_address()) |
||||
{ |
||||
found_target = true; |
||||
continue; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
// The below check determines whether we have continued from inside of a finally block.
|
||||
if jump_address > target_address && jump_address == env_entry.exit_address() { |
||||
found_target = true; |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
continue; |
||||
} |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
// 2. Register target address in AbruptCompletionRecord.
|
||||
let new_record = AbruptCompletionRecord::new_continue().with_initial_target(target_address); |
||||
context.vm.frame_mut().abrupt_completion = Some(new_record); |
||||
|
||||
// 3. Set program counter and finally return fields.
|
||||
context.vm.frame_mut().pc = jump_address; |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
@ -1,148 +0,0 @@
|
||||
use crate::{ |
||||
vm::{opcode::Operation, CompletionType}, |
||||
Context, JsError, JsResult, |
||||
}; |
||||
|
||||
/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyStart; |
||||
|
||||
impl Operation for FinallyStart { |
||||
const NAME: &'static str = "FinallyStart"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let exit = context.vm.read::<u32>(); |
||||
|
||||
let finally_env = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.last_mut() |
||||
.expect("EnvStackEntries must exist"); |
||||
|
||||
finally_env.set_exit_address(exit); |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `FinallyEnd` implements the Opcode Operation for `Opcode::FinallyEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a finally block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FinallyEnd; |
||||
|
||||
impl Operation for FinallyEnd { |
||||
const NAME: &'static str = "FinallyEnd"; |
||||
const INSTRUCTION: &'static str = "INST - FinallyEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let finally_candidates = context |
||||
.vm |
||||
.frame() |
||||
.env_stack |
||||
.iter() |
||||
.filter(|env| env.is_finally_env() && context.vm.frame().pc < env.start_address()); |
||||
|
||||
let next_finally = match finally_candidates.last() { |
||||
Some(env) => env.start_address(), |
||||
_ => u32::MAX, |
||||
}; |
||||
|
||||
let mut envs_to_pop = 0; |
||||
match context.vm.frame().abrupt_completion { |
||||
Some(record) if next_finally < record.target() => { |
||||
context.vm.frame_mut().pc = next_finally; |
||||
|
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if next_finally <= env_entry.exit_address() { |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
} |
||||
Some(record) if record.is_break() && context.vm.frame().pc < record.target() => { |
||||
// handle the continuation of an abrupt break.
|
||||
context.vm.frame_mut().pc = record.target(); |
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if record.target() == env_entry.exit_address() { |
||||
break; |
||||
} |
||||
|
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
} |
||||
Some(record) if record.is_continue() && context.vm.frame().pc > record.target() => { |
||||
// Handle the continuation of an abrupt continue
|
||||
context.vm.frame_mut().pc = record.target(); |
||||
while let Some(env_entry) = context.vm.frame().env_stack.last() { |
||||
if env_entry.start_address() == record.target() { |
||||
break; |
||||
} |
||||
envs_to_pop += env_entry.env_num(); |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
|
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
} |
||||
Some(record) if record.is_return() => { |
||||
return Ok(CompletionType::Return); |
||||
} |
||||
Some(record) |
||||
if record.is_throw_with_target() && context.vm.frame().pc < record.target() => |
||||
{ |
||||
context.vm.frame_mut().pc = record.target(); |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
if env_entry.start_address() == record.target() { |
||||
break; |
||||
} |
||||
} |
||||
context.vm.frame_mut().abrupt_completion = None; |
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
} |
||||
Some(record) if record.is_throw() && !record.is_throw_with_target() => { |
||||
let current_stack = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.pop() |
||||
.expect("Popping current finally stack."); |
||||
|
||||
let env_truncation_len = context |
||||
.vm |
||||
.environments |
||||
.len() |
||||
.saturating_sub(current_stack.env_num()); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
let err = JsError::from_opaque(context.vm.pop()); |
||||
context.vm.err = Some(err); |
||||
return Ok(CompletionType::Throw); |
||||
} |
||||
_ => { |
||||
context.vm.frame_mut().env_stack.pop(); |
||||
} |
||||
} |
||||
|
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
@ -1,55 +0,0 @@
|
||||
use crate::{ |
||||
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `LabelledStart` implements the Opcode Operation for `Opcode::LabelledStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a labelled block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LabelledStart; |
||||
|
||||
impl Operation for LabelledStart { |
||||
const NAME: &'static str = "LabelledStart"; |
||||
const INSTRUCTION: &'static str = "INST - LabelledStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let start = context.vm.frame().pc - 1; |
||||
let end = context.vm.read::<u32>(); |
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.push(EnvStackEntry::new(start, end).with_labelled_flag()); |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `LabelledEnd` implements the Opcode Operation for `Opcode::LabelledEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Clean up environments at the end of labelled block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LabelledEnd; |
||||
|
||||
impl Operation for LabelledEnd { |
||||
const NAME: &'static str = "LabelledEnd"; |
||||
const INSTRUCTION: &'static str = "INST - LabelledEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_labelled_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
@ -1,15 +1,5 @@
|
||||
pub(crate) mod r#break; |
||||
pub(crate) mod r#continue; |
||||
pub(crate) mod finally; |
||||
pub(crate) mod labelled; |
||||
pub(crate) mod r#return; |
||||
pub(crate) mod throw; |
||||
pub(crate) mod r#try; |
||||
|
||||
pub(crate) use finally::*; |
||||
pub(crate) use labelled::*; |
||||
pub(crate) use r#break::*; |
||||
pub(crate) use r#continue::*; |
||||
pub(crate) use r#return::*; |
||||
pub(crate) use r#try::*; |
||||
pub(crate) use throw::*; |
||||
|
@ -1,68 +0,0 @@
|
||||
use crate::{ |
||||
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `TryStart` implements the Opcode Operation for `Opcode::TryStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Start of a try block.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryStart; |
||||
|
||||
impl Operation for TryStart { |
||||
const NAME: &'static str = "TryStart"; |
||||
const INSTRUCTION: &'static str = "INST - TryStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let catch = context.vm.read::<u32>(); |
||||
let finally = context.vm.read::<u32>(); |
||||
|
||||
// If a finally exists, push the env to the stack before the try.
|
||||
if finally != u32::MAX { |
||||
context.vm.frame_mut().env_stack.push( |
||||
EnvStackEntry::default() |
||||
.with_finally_flag() |
||||
.with_start_address(finally), |
||||
); |
||||
} |
||||
|
||||
let fp = context.vm.stack.len() as u32; |
||||
|
||||
context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.push(EnvStackEntry::new(catch, finally).with_try_flag(fp)); |
||||
|
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `TryEnd` implements the Opcode Operation for `Opcode::TryEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - End of a try block
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct TryEnd; |
||||
|
||||
impl Operation for TryEnd { |
||||
const NAME: &'static str = "TryEnd"; |
||||
const INSTRUCTION: &'static str = "INST - TryEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_try_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
@ -1,120 +1,31 @@
|
||||
use crate::JsNativeError; |
||||
use crate::{ |
||||
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType}, |
||||
vm::{opcode::Operation, CompletionType}, |
||||
Context, JsResult, |
||||
}; |
||||
|
||||
/// `IteratorLoopStart` implements the Opcode Operation for `Opcode::IteratorLoopStart`
|
||||
/// `IncrementLoopIteration` implements the Opcode Operation for `Opcode::IncrementLoopIteration`.
|
||||
///
|
||||
/// Operation:
|
||||
/// - Push iterator loop start marker.
|
||||
/// - Increment loop itearation count.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct IteratorLoopStart; |
||||
pub(crate) struct IncrementLoopIteration; |
||||
|
||||
impl Operation for IteratorLoopStart { |
||||
const NAME: &'static str = "IteratorLoopStart"; |
||||
const INSTRUCTION: &'static str = "INST - IteratorLoopStart"; |
||||
impl Operation for IncrementLoopIteration { |
||||
const NAME: &'static str = "IncrementLoopIteration"; |
||||
const INSTRUCTION: &'static str = "INST - IncrementLoopIteration"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let start = context.vm.read::<u32>(); |
||||
let exit = context.vm.read::<u32>(); |
||||
let previous_iteration_count = context.vm.frame_mut().loop_iteration_count; |
||||
|
||||
// Create and push loop evironment entry.
|
||||
let entry = EnvStackEntry::new(start, exit) |
||||
.with_iterator_loop_flag(1, (context.vm.frame().iterators.len() - 1) as u32); |
||||
context.vm.frame_mut().env_stack.push(entry); |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Push loop start marker.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LoopStart; |
||||
|
||||
impl Operation for LoopStart { |
||||
const NAME: &'static str = "LoopStart"; |
||||
const INSTRUCTION: &'static str = "INST - LoopStart"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let start = context.vm.read::<u32>(); |
||||
let exit = context.vm.read::<u32>(); |
||||
|
||||
// Create and push loop evironment entry.
|
||||
let entry = EnvStackEntry::new(start, exit).with_loop_flag(1); |
||||
context.vm.frame_mut().env_stack.push(entry); |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue`.
|
||||
///
|
||||
/// Operation:
|
||||
/// - Pushes a clean environment onto the frame's `EnvEntryStack`.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LoopContinue; |
||||
|
||||
impl Operation for LoopContinue { |
||||
const NAME: &'static str = "LoopContinue"; |
||||
const INSTRUCTION: &'static str = "INST - LoopContinue"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
// 1. Clean up the previous environment.
|
||||
let env = context |
||||
.vm |
||||
.frame_mut() |
||||
.env_stack |
||||
.last_mut() |
||||
.expect("loop environment must be present"); |
||||
|
||||
let env_num = env.env_num(); |
||||
env.clear_env_num(); |
||||
|
||||
if let Some(previous_iteration_count) = env.as_loop_iteration_count() { |
||||
env.increase_loop_iteration_count(); |
||||
let max = context.vm.runtime_limits.loop_iteration_limit(); |
||||
if previous_iteration_count > max { |
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(env_num); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
return Err(JsNativeError::runtime_limit() |
||||
.with_message(format!("Maximum loop iteration limit {max} exceeded")) |
||||
.into()); |
||||
} |
||||
let max = context.vm.runtime_limits.loop_iteration_limit(); |
||||
if previous_iteration_count > max { |
||||
return Err(JsNativeError::runtime_limit() |
||||
.with_message(format!("Maximum loop iteration limit {max} exceeded")) |
||||
.into()); |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(env_num); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
||||
/// `LoopEnd` implements the Opcode Operation for `Opcode::LoopEnd`
|
||||
///
|
||||
/// Operation:
|
||||
/// - Clean up environments at the end of a loop.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct LoopEnd; |
||||
|
||||
impl Operation for LoopEnd { |
||||
const NAME: &'static str = "LoopEnd"; |
||||
const INSTRUCTION: &'static str = "INST - LoopEnd"; |
||||
|
||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||
let mut envs_to_pop = 0_usize; |
||||
while let Some(env_entry) = context.vm.frame_mut().env_stack.pop() { |
||||
envs_to_pop += env_entry.env_num(); |
||||
|
||||
if env_entry.is_loop_env() { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
let env_truncation_len = context.vm.environments.len().saturating_sub(envs_to_pop); |
||||
context.vm.environments.truncate(env_truncation_len); |
||||
|
||||
context.vm.frame_mut().loop_iteration_count = previous_iteration_count.wrapping_add(1); |
||||
Ok(CompletionType::Normal) |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue