use oxc_ast::ast::LabelIdentifier;
use crate::{ecmascript::Value, engine::Instruction};
use super::{JumpIndex, executable_context::ExecutableContext};
#[derive(Debug, Clone)]
pub(super) struct ControlFlowFinallyEntry<'a> {
pub(super) continues: Vec<(JumpIndex, Option<&'a LabelIdentifier<'a>>)>,
pub(super) breaks: Vec<(JumpIndex, Option<&'a LabelIdentifier<'a>>)>,
pub(super) returns: Vec<JumpIndex>,
}
#[derive(Debug, Clone)]
pub(super) struct ControlFlowLoopEntry {
continues: Vec<JumpIndex>,
breaks: Vec<JumpIndex>,
}
#[derive(Debug, Clone)]
pub(super) struct ControlFlowSwitchEntry {
breaks: Vec<JumpIndex>,
}
#[derive(Debug, Clone)]
pub(super) enum ControlFlowStackEntry<'a> {
LabelledStatement {
label: &'a LabelIdentifier<'a>,
incoming_control_flows: Option<Box<ControlFlowSwitchEntry>>,
},
LexicalScope,
VariableScope,
PrivateScope,
StackValue,
StackResultValue,
CatchBlock,
IfStatement,
FinallyBlock,
TryFinallyBlock {
jump_to_catch: JumpIndex,
incoming_control_flows: Option<Box<ControlFlowFinallyEntry<'a>>>,
},
Loop {
label_set: Option<Vec<&'a LabelIdentifier<'a>>>,
incoming_control_flows: Option<Box<ControlFlowLoopEntry>>,
},
Switch {
label_set: Option<Vec<&'a LabelIdentifier<'a>>>,
incoming_control_flows: Option<Box<ControlFlowSwitchEntry>>,
},
IteratorStackEntry,
ArrayDestructuring,
Iterator {
label_set: Option<Vec<&'a LabelIdentifier<'a>>>,
incoming_control_flows: Option<Box<ControlFlowLoopEntry>>,
},
AsyncIterator {
label_set: Option<Vec<&'a LabelIdentifier<'a>>>,
incoming_control_flows: Option<Box<ControlFlowLoopEntry>>,
},
}
impl<'a> ControlFlowStackEntry<'a> {
pub(super) fn add_break_source(
&mut self,
label: Option<&'a LabelIdentifier<'a>>,
break_source: JumpIndex,
) {
match self {
ControlFlowStackEntry::TryFinallyBlock {
incoming_control_flows,
..
} => {
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.breaks.push((break_source, label));
} else {
*incoming_control_flows = Some(Box::new(ControlFlowFinallyEntry {
continues: vec![],
breaks: vec![(break_source, label)],
returns: vec![],
}));
}
}
ControlFlowStackEntry::Loop {
incoming_control_flows,
..
}
| ControlFlowStackEntry::Iterator {
incoming_control_flows,
..
}
| ControlFlowStackEntry::AsyncIterator {
incoming_control_flows,
..
} => {
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.breaks.push(break_source);
} else {
*incoming_control_flows = Some(Box::new(ControlFlowLoopEntry {
continues: vec![],
breaks: vec![break_source],
}));
}
}
ControlFlowStackEntry::LabelledStatement {
incoming_control_flows,
..
}
| ControlFlowStackEntry::Switch {
incoming_control_flows,
..
} => {
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.breaks.push(break_source);
} else {
*incoming_control_flows = Some(Box::new(ControlFlowSwitchEntry {
breaks: vec![break_source],
}));
}
}
_ => unreachable!(),
}
}
pub(super) fn add_continue_source(
&mut self,
label: Option<&'a LabelIdentifier<'a>>,
continue_source: JumpIndex,
) {
match self {
ControlFlowStackEntry::TryFinallyBlock {
incoming_control_flows,
..
} => {
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows
.continues
.push((continue_source, label));
} else {
*incoming_control_flows = Some(Box::new(ControlFlowFinallyEntry {
continues: vec![(continue_source, label)],
breaks: vec![],
returns: vec![],
}));
}
}
ControlFlowStackEntry::Loop {
incoming_control_flows,
..
}
| ControlFlowStackEntry::Iterator {
incoming_control_flows,
..
}
| ControlFlowStackEntry::AsyncIterator {
incoming_control_flows,
..
} => {
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.continues.push(continue_source);
} else {
*incoming_control_flows = Some(Box::new(ControlFlowLoopEntry {
continues: vec![continue_source],
breaks: vec![],
}));
}
}
_ => unreachable!(),
}
}
pub(super) fn add_return_source(&mut self, return_source: JumpIndex) {
let ControlFlowStackEntry::TryFinallyBlock {
incoming_control_flows,
..
} = self
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.returns.push(return_source);
} else {
*incoming_control_flows = Some(Box::new(ControlFlowFinallyEntry {
continues: vec![],
breaks: vec![],
returns: vec![return_source],
}));
}
}
pub(super) fn is_break_target_for(&self, label: Option<&'a LabelIdentifier<'a>>) -> bool {
match self {
ControlFlowStackEntry::LabelledStatement { label: l, .. } => {
label.is_some_and(|label| l.name == label.name)
}
ControlFlowStackEntry::LexicalScope
| ControlFlowStackEntry::VariableScope
| ControlFlowStackEntry::PrivateScope
| ControlFlowStackEntry::StackValue
| ControlFlowStackEntry::StackResultValue
| ControlFlowStackEntry::CatchBlock
| ControlFlowStackEntry::IfStatement
| ControlFlowStackEntry::FinallyBlock
| ControlFlowStackEntry::IteratorStackEntry { .. }
| ControlFlowStackEntry::ArrayDestructuring => false,
ControlFlowStackEntry::TryFinallyBlock { .. } => true,
ControlFlowStackEntry::Loop { label_set, .. }
| ControlFlowStackEntry::Switch { label_set, .. }
| ControlFlowStackEntry::Iterator { label_set, .. }
| ControlFlowStackEntry::AsyncIterator { label_set, .. } => {
if let Some(label) = label {
let Some(label_set) = label_set else {
return false;
};
label_set.iter().any(|l| l.name == label.name)
} else {
true
}
}
}
}
pub(super) fn is_continue_target_for(&self, label: Option<&'a LabelIdentifier<'a>>) -> bool {
match self {
ControlFlowStackEntry::LabelledStatement { label: l, .. } => {
label.is_some_and(|label| l.name == label.name)
}
ControlFlowStackEntry::LexicalScope
| ControlFlowStackEntry::VariableScope
| ControlFlowStackEntry::PrivateScope
| ControlFlowStackEntry::StackValue
| ControlFlowStackEntry::StackResultValue
| ControlFlowStackEntry::FinallyBlock
| ControlFlowStackEntry::IfStatement
| ControlFlowStackEntry::CatchBlock { .. }
| ControlFlowStackEntry::Switch { .. }
| ControlFlowStackEntry::IteratorStackEntry
| ControlFlowStackEntry::ArrayDestructuring => false,
ControlFlowStackEntry::TryFinallyBlock { .. } => true,
ControlFlowStackEntry::Loop { label_set, .. }
| ControlFlowStackEntry::Iterator { label_set, .. }
| ControlFlowStackEntry::AsyncIterator { label_set, .. } => {
if let Some(label) = label {
let Some(label_set) = label_set else {
return false;
};
label_set.iter().any(|l| l.name == label.name)
} else {
true
}
}
}
}
pub(super) fn is_return_target(&self) -> bool {
matches!(self, ControlFlowStackEntry::TryFinallyBlock { .. })
}
pub(super) fn requires_return_finalisation(&self, will_perform_other_finalisers: bool) -> bool {
match self {
ControlFlowStackEntry::LabelledStatement { .. }
| ControlFlowStackEntry::LexicalScope
| ControlFlowStackEntry::VariableScope
| ControlFlowStackEntry::PrivateScope
| ControlFlowStackEntry::Loop { .. }
| ControlFlowStackEntry::Switch { .. } => false,
ControlFlowStackEntry::StackValue
| ControlFlowStackEntry::StackResultValue
| ControlFlowStackEntry::IfStatement
| ControlFlowStackEntry::FinallyBlock
| ControlFlowStackEntry::ArrayDestructuring
| ControlFlowStackEntry::Iterator { .. }
| ControlFlowStackEntry::AsyncIterator { .. }
| ControlFlowStackEntry::TryFinallyBlock { .. } => true,
ControlFlowStackEntry::CatchBlock | ControlFlowStackEntry::IteratorStackEntry => {
will_perform_other_finalisers
}
}
}
pub(super) fn sets_result_during_exit(&self) -> bool {
matches!(
self,
ControlFlowStackEntry::IfStatement
| ControlFlowStackEntry::Loop { .. }
| ControlFlowStackEntry::Iterator { .. }
| ControlFlowStackEntry::AsyncIterator { .. }
)
}
pub(super) fn compile_exit(&self, executable: &mut ExecutableContext, has_result: bool) {
match self {
ControlFlowStackEntry::LabelledStatement { .. } => {
}
ControlFlowStackEntry::LexicalScope => {
executable.add_instruction(Instruction::ExitDeclarativeEnvironment);
}
ControlFlowStackEntry::VariableScope => {
executable.add_instruction(Instruction::ExitVariableEnvironment);
}
ControlFlowStackEntry::PrivateScope => {
executable.add_instruction(Instruction::ExitPrivateEnvironment);
}
ControlFlowStackEntry::StackValue => {
compile_stack_variable_exit(executable);
}
ControlFlowStackEntry::StackResultValue => {}
ControlFlowStackEntry::IfStatement => {
if has_result {
return;
}
compile_if_statement_exit(executable);
}
ControlFlowStackEntry::FinallyBlock => {
if has_result {
executable.add_instruction(Instruction::UpdateEmpty);
} else {
compile_if_statement_exit(executable);
executable.add_instruction(Instruction::UpdateEmpty);
}
}
ControlFlowStackEntry::CatchBlock { .. } => {
executable.add_instruction(Instruction::PopExceptionJumpTarget);
}
ControlFlowStackEntry::TryFinallyBlock { .. } => {
unreachable!()
}
ControlFlowStackEntry::Switch { .. } => {
executable.add_instruction(Instruction::UpdateEmpty);
}
ControlFlowStackEntry::IteratorStackEntry => {
compile_iterator_pop(executable);
}
ControlFlowStackEntry::ArrayDestructuring => {
compile_array_destructuring_exit(executable);
}
ControlFlowStackEntry::Loop { .. } => {
compile_loop_exit(executable);
}
ControlFlowStackEntry::Iterator { .. } => {
compile_sync_iterator_exit(executable);
}
ControlFlowStackEntry::AsyncIterator { .. } => {
compile_async_iterator_exit(executable);
}
}
}
}
pub(super) fn compile_iterator_pop(executable: &mut ExecutableContext) {
executable.add_instruction(Instruction::PopExceptionJumpTarget);
executable.add_instruction(Instruction::IteratorPop);
}
pub(super) fn compile_stack_variable_exit(executable: &mut ExecutableContext) {
executable.add_instruction(Instruction::PopStack);
}
pub(super) fn compile_if_statement_exit(executable: &mut ExecutableContext) {
executable.add_instruction_with_constant(Instruction::LoadConstant, Value::Undefined);
executable.add_instruction(Instruction::UpdateEmpty);
}
pub(super) fn compile_loop_exit(executable: &mut ExecutableContext) {
executable.add_instruction(Instruction::PopExceptionJumpTarget);
executable.add_instruction(Instruction::UpdateEmpty);
}
pub(super) fn compile_array_destructuring_exit(executable: &mut ExecutableContext) {
executable.add_instruction(Instruction::PopExceptionJumpTarget);
executable.add_instruction(Instruction::IteratorClose);
}
pub(super) fn compile_sync_iterator_exit(executable: &mut ExecutableContext) {
compile_loop_exit(executable);
executable.add_instruction(Instruction::IteratorClose);
}
pub(super) fn compile_async_iterator_exit(executable: &mut ExecutableContext) {
compile_loop_exit(executable);
executable.add_instruction(Instruction::AsyncIteratorClose);
let error_message = executable.create_string("iterator.return() returned a non-object value");
executable.add_instruction_with_identifier(
Instruction::VerifyIsObject,
error_message.to_property_key(),
);
executable.add_instruction(Instruction::Store);
}
impl ControlFlowSwitchEntry {
pub(super) fn compile(self, ctx: &mut ExecutableContext) {
for break_source in self.breaks.into_iter().rev() {
ctx.set_jump_target_here(break_source);
}
}
}
impl ControlFlowLoopEntry {
pub(super) fn compile(
self,
continue_target: JumpIndex,
compile_break: impl FnOnce(&mut ExecutableContext),
ctx: &mut ExecutableContext,
) {
for continue_source in self.continues {
ctx.set_jump_target(continue_source, continue_target.clone());
}
if ctx.is_unreachable() && self.breaks.is_empty() {
return;
}
for break_source in self.breaks.into_iter().rev() {
ctx.set_jump_target_here(break_source);
}
compile_break(ctx);
}
}