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::{ |
use crate::bytecompiler::{ |
||||||
bytecompiler::{ByteCompiler, JumpControlInfo}, |
jump_control::{JumpRecord, JumpRecordAction, JumpRecordKind}, |
||||||
vm::Opcode, |
ByteCompiler, |
||||||
}; |
}; |
||||||
use boa_ast::statement::Break; |
use boa_ast::statement::Break; |
||||||
use boa_interner::Sym; |
|
||||||
|
|
||||||
impl ByteCompiler<'_, '_> { |
impl ByteCompiler<'_, '_> { |
||||||
/// Compile a [`Break`] `boa_ast` node
|
/// Compile a [`Break`] `boa_ast` node
|
||||||
pub(crate) fn compile_break(&mut self, node: Break, _use_expr: bool) { |
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 actions = self.break_jump_record_actions(node); |
||||||
let has_finally_or_is_finally = info.has_finally() || info.in_finally(); |
|
||||||
|
|
||||||
let (break_label, target_jump_label) = |
JumpRecord::new(JumpRecordKind::Break, actions).perform_actions(Self::DUMMY_ADDRESS, self); |
||||||
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); |
|
||||||
} |
} |
||||||
|
|
||||||
fn jump_info_non_labelled(&mut self) -> &mut JumpControlInfo { |
fn break_jump_record_actions(&self, node: Break) -> Vec<JumpRecordAction> { |
||||||
for info in self.jump_info.iter_mut().rev() { |
let mut actions = Vec::default(); |
||||||
if !info.is_labelled() { |
for (i, info) in self.jump_info.iter().enumerate().rev() { |
||||||
return info; |
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 { |
if let Some(label) = node.label() { |
||||||
for info in self.jump_info.iter_mut().rev() { |
if info.label() == Some(label) { |
||||||
if info.label() == Some(label) { |
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||||
return info; |
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; |
use boa_ast::statement::Continue; |
||||||
|
|
||||||
impl ByteCompiler<'_, '_> { |
impl ByteCompiler<'_, '_> { |
||||||
#[allow(clippy::unnecessary_wraps)] |
#[allow(clippy::unnecessary_wraps)] |
||||||
pub(crate) fn compile_continue(&mut self, node: Continue, _use_expr: bool) { |
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 actions = self.continue_jump_record_actions(node); |
||||||
let in_finally = info.in_finally(); |
|
||||||
let in_finally_or_has_finally = in_finally || info.has_finally(); |
|
||||||
|
|
||||||
// 1. Handle if node has a label.
|
JumpRecord::new(JumpRecordKind::Continue, actions) |
||||||
if let Some(node_label) = node.label() { |
.perform_actions(Self::DUMMY_ADDRESS, self); |
||||||
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); |
|
||||||
} |
|
||||||
|
|
||||||
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 |
if let Some(label) = node.label() { |
||||||
.jump_info |
if info.label() == Some(label) { |
||||||
.iter_mut() |
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||||
.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) { |
|
||||||
break; |
break; |
||||||
} |
} |
||||||
|
} else if info.is_loop() { |
||||||
if info.iterator_loop() { |
actions.push(JumpRecordAction::Transfer { index: i as u32 }); |
||||||
iterator_closes.push(info.for_await_of_loop()); |
break; |
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for r#async in iterator_closes { |
|
||||||
self.iterator_close(r#async); |
|
||||||
} |
} |
||||||
|
|
||||||
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); |
if info.iterator_loop() { |
||||||
let loops = self |
actions.push(JumpRecordAction::CloseIterator { |
||||||
.jump_info |
r#async: info.for_await_of_loop(), |
||||||
.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); |
|
||||||
} |
|
||||||
} |
} |
||||||
} 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 r#return; |
||||||
pub(crate) mod throw; |
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#return::*; |
||||||
pub(crate) use r#try::*; |
|
||||||
pub(crate) use throw::*; |
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::JsNativeError; |
||||||
use crate::{ |
use crate::{ |
||||||
vm::{call_frame::EnvStackEntry, opcode::Operation, CompletionType}, |
vm::{opcode::Operation, CompletionType}, |
||||||
Context, JsResult, |
Context, JsResult, |
||||||
}; |
}; |
||||||
|
|
||||||
/// `IteratorLoopStart` implements the Opcode Operation for `Opcode::IteratorLoopStart`
|
/// `IncrementLoopIteration` implements the Opcode Operation for `Opcode::IncrementLoopIteration`.
|
||||||
///
|
///
|
||||||
/// Operation:
|
/// Operation:
|
||||||
/// - Push iterator loop start marker.
|
/// - Increment loop itearation count.
|
||||||
#[derive(Debug, Clone, Copy)] |
#[derive(Debug, Clone, Copy)] |
||||||
pub(crate) struct IteratorLoopStart; |
pub(crate) struct IncrementLoopIteration; |
||||||
|
|
||||||
impl Operation for IteratorLoopStart { |
impl Operation for IncrementLoopIteration { |
||||||
const NAME: &'static str = "IteratorLoopStart"; |
const NAME: &'static str = "IncrementLoopIteration"; |
||||||
const INSTRUCTION: &'static str = "INST - IteratorLoopStart"; |
const INSTRUCTION: &'static str = "INST - IncrementLoopIteration"; |
||||||
|
|
||||||
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { |
||||||
let start = context.vm.read::<u32>(); |
let previous_iteration_count = context.vm.frame_mut().loop_iteration_count; |
||||||
let exit = context.vm.read::<u32>(); |
|
||||||
|
|
||||||
// Create and push loop evironment entry.
|
let max = context.vm.runtime_limits.loop_iteration_limit(); |
||||||
let entry = EnvStackEntry::new(start, exit) |
if previous_iteration_count > max { |
||||||
.with_iterator_loop_flag(1, (context.vm.frame().iterators.len() - 1) as u32); |
return Err(JsNativeError::runtime_limit() |
||||||
context.vm.frame_mut().env_stack.push(entry); |
.with_message(format!("Maximum loop iteration limit {max} exceeded")) |
||||||
Ok(CompletionType::Normal) |
.into()); |
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// `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 env_truncation_len = context.vm.environments.len().saturating_sub(env_num); |
context.vm.frame_mut().loop_iteration_count = previous_iteration_count.wrapping_add(1); |
||||||
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); |
|
||||||
|
|
||||||
Ok(CompletionType::Normal) |
Ok(CompletionType::Normal) |
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue