#[cfg(feature = "regexp")]
use oxc_ast::ast::RegExpFlags;
use oxc_ast::ast::{self, LabelIdentifier, Statement};
use oxc_semantic::ScopeId;
use wtf8::Wtf8Buf;
#[cfg(feature = "regexp")]
use crate::ecmascript::RegExp;
use crate::{
ecmascript::{
Agent, BigInt, CompileFunctionBodyData, Number, ObjectShape, PropertyKey,
PropertyLookupCache, SourceCode, String, Value,
},
engine::{
Executable, FunctionExpression, Instruction, NoGcScope,
bytecode::{
bytecode_compiler::finaliser_stack::{
compile_array_destructuring_exit, compile_if_statement_exit, compile_loop_exit,
compile_stack_variable_exit, compile_sync_iterator_exit,
},
executable::ArrowFunctionExpression,
},
},
};
use super::{
executable_context::ExecutableContext,
finaliser_stack::{
ControlFlowFinallyEntry, ControlFlowStackEntry, compile_async_iterator_exit,
compile_iterator_pop,
},
function_declaration_instantiation,
};
pub(crate) type IndexType = u16;
#[derive(Debug, Clone, Copy)]
pub(crate) enum NamedEvaluationParameter {
Result,
Stack,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub(crate) struct JumpIndex {
pub(crate) index: usize,
}
#[derive(PartialEq, Eq)]
pub(crate) enum GeneratorKind {
Sync,
Async,
}
pub(crate) struct CompileContext<'agent, 'script, 'gc, 'scope> {
executable: ExecutableContext<'agent, 'gc, 'scope>,
source_code: SourceCode<'gc>,
pub(super) name_identifier: Option<NamedEvaluationParameter>,
pub(super) lexical_binding_state: bool,
pub(super) optional_chains: Option<Vec<JumpIndex>>,
pub(super) is_call_optional_chain_this: bool,
control_flow_stack: Vec<ControlFlowStackEntry<'script>>,
stack_variables: Vec<(oxc_semantic::SymbolId, u32)>,
generator_kind: Option<GeneratorKind>,
}
impl<'agent, 'script, 'gc, 'scope> CompileContext<'agent, 'script, 'gc, 'scope> {
pub(crate) fn new(
agent: &'agent mut Agent,
source_code: SourceCode<'gc>,
gc: NoGcScope<'gc, 'scope>,
) -> Self {
Self {
executable: ExecutableContext::new(agent, gc),
source_code,
name_identifier: None,
lexical_binding_state: false,
optional_chains: None,
is_call_optional_chain_this: false,
control_flow_stack: Vec::new(),
stack_variables: Vec::new(),
generator_kind: None,
}
}
pub(crate) fn set_generator_kind(&mut self, kind: GeneratorKind) {
self.generator_kind = Some(kind);
}
pub(crate) fn is_generator(&self) -> bool {
self.generator_kind.is_some()
}
pub(crate) fn is_async_generator(&self) -> bool {
self.generator_kind == Some(GeneratorKind::Async)
}
pub(crate) fn get_agent_and_gc(&mut self) -> (&mut Agent, NoGcScope<'gc, 'scope>) {
self.executable.get_agent_and_gc()
}
pub(crate) fn get_agent(&self) -> &Agent {
self.executable.get_agent()
}
pub(crate) fn get_source_code(&self) -> SourceCode<'gc> {
self.source_code
}
pub(crate) fn scope_contains_direct_eval(&self, scope: ScopeId) -> bool {
let scoping = self.source_code.get_scoping(self.get_agent());
let flags = scoping.scope_flags(scope);
flags.contains_direct_eval()
}
pub(crate) fn get_agent_mut(&mut self) -> &mut Agent {
self.executable.get_agent_mut()
}
pub(crate) fn create_property_lookup_cache(
&mut self,
identifier: PropertyKey<'gc>,
) -> PropertyLookupCache<'gc> {
self.executable.create_property_lookup_cache(identifier)
}
pub(crate) fn create_bigint(&mut self, literal: &str, radix: u32) -> BigInt<'gc> {
self.executable.create_bigint(literal, radix)
}
pub(crate) fn create_number(&mut self, value: f64) -> Number<'gc> {
self.executable.create_number(value)
}
pub(crate) fn create_property_key(&mut self, literal: &str) -> PropertyKey<'gc> {
self.executable.create_property_key(literal)
}
#[cfg(feature = "regexp")]
pub(crate) fn create_regexp(&mut self, literal: &str, flags: RegExpFlags) -> RegExp<'gc> {
self.executable.create_regexp(literal, flags)
}
pub(crate) fn create_string(&mut self, literal: &str) -> String<'gc> {
self.executable.create_string(literal)
}
pub(super) fn create_string_from_owned(&mut self, owned: std::string::String) -> String<'gc> {
self.executable.create_string_from_owned(owned)
}
#[expect(dead_code)]
pub(super) fn create_string_from_wtf8_buf(&mut self, buf: Wtf8Buf) -> String<'gc> {
self.executable.create_string_from_wtf8_buf(buf)
}
pub(super) fn enter_label(
&mut self,
label: &'script LabelIdentifier<'script>,
) -> LabelledStatement {
self.control_flow_stack
.push(ControlFlowStackEntry::LabelledStatement {
label,
incoming_control_flows: None,
});
LabelledStatement
}
pub(super) fn exit_label(&mut self, st: LabelledStatement) {
core::mem::forget(st);
let Some(ControlFlowStackEntry::LabelledStatement {
label: _,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.compile(&mut self.executable);
}
}
pub(super) fn enter_lexical_scope(&mut self) -> LexicalScope {
self.add_instruction(Instruction::EnterDeclarativeEnvironment);
self.control_flow_stack
.push(ControlFlowStackEntry::LexicalScope);
LexicalScope
}
fn exit_lexical_scope(&mut self, scope: LexicalScope) {
core::mem::forget(scope);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::LexicalScope)));
if self.is_unreachable() {
return;
}
self.add_instruction(Instruction::ExitDeclarativeEnvironment);
}
pub(super) fn get_variable_stack_index(&self, symbol: oxc_semantic::SymbolId) -> Option<u32> {
self.stack_variables
.iter()
.find(|(s, _)| *s == symbol)
.map(|(_, i)| *i)
}
pub(super) fn mark_stack_value(&mut self) -> StackValue {
let _ = self.executable.push_stack();
self.control_flow_stack
.push(ControlFlowStackEntry::StackValue);
StackValue
}
fn pop_stack_value(&mut self, var: StackValue) {
core::mem::forget(var);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::StackValue)));
self.executable.pop_stack();
}
pub(super) fn load_to_stack(&mut self) -> StackValue {
self.add_instruction(Instruction::Load);
let _ = self.executable.push_stack();
self.control_flow_stack
.push(ControlFlowStackEntry::StackValue);
StackValue
}
pub(super) fn load_constant_to_stack(&mut self, constant: impl Into<Value<'gc>>) -> StackValue {
self.add_instruction_with_constant(Instruction::LoadConstant, constant);
let _ = self.executable.push_stack();
self.control_flow_stack
.push(ControlFlowStackEntry::StackValue);
StackValue
}
pub(super) fn load_copy_to_stack(&mut self) -> StackValue {
self.add_instruction(Instruction::LoadCopy);
let _ = self.executable.push_stack();
self.control_flow_stack
.push(ControlFlowStackEntry::StackValue);
StackValue
}
pub(super) fn push_stack_variable(
&mut self,
symbol: oxc_semantic::SymbolId,
value_in_result_register: bool,
) -> StackVariable {
if value_in_result_register {
self.add_instruction(Instruction::Load);
} else {
self.add_instruction_with_constant(Instruction::LoadConstant, Value::Undefined);
}
let idx = self.executable.push_stack();
self.stack_variables.push((symbol, idx));
self.control_flow_stack
.push(ControlFlowStackEntry::StackValue);
StackVariable
}
fn pop_stack_variable(&mut self, var: StackVariable) {
core::mem::forget(var);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::StackValue)));
self.stack_variables.pop().unwrap();
self.executable.pop_stack();
if self.is_unreachable() {
return;
}
compile_stack_variable_exit(&mut self.executable);
}
pub(super) fn push_stack_result_value(
&mut self,
value: Option<impl Into<Value<'gc>>>,
) -> StackResultValue {
if let Some(value) = value {
self.add_instruction_with_constant(Instruction::LoadConstant, value.into());
} else {
self.add_instruction(Instruction::Load);
}
let stack_slot = self.executable.push_stack();
self.control_flow_stack
.push(ControlFlowStackEntry::StackResultValue);
StackResultValue { stack_slot }
}
fn pop_stack_result_value(&mut self, result: StackResultValue) {
core::mem::forget(result);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(
entry,
Some(ControlFlowStackEntry::StackResultValue)
));
self.executable.pop_stack();
}
pub(crate) fn reset_stack_depth(&mut self) {
self.executable.add_instruction_with_immediate(
Instruction::TruncateStack,
self.executable.stack_depth(),
);
}
pub(super) fn enter_private_scope(&mut self, private_name_count: usize) -> PrivateScope {
self.add_instruction_with_immediate(
Instruction::EnterPrivateEnvironment,
private_name_count,
);
self.control_flow_stack
.push(ControlFlowStackEntry::PrivateScope);
PrivateScope
}
fn exit_private_scope(&mut self, scope: PrivateScope) {
core::mem::forget(scope);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::PrivateScope)));
if self.is_unreachable() {
return;
}
self.add_instruction(Instruction::ExitPrivateEnvironment);
}
pub(super) fn enter_class_static_block(&mut self) -> ClassStaticBlock {
self.add_instruction(Instruction::EnterClassStaticElementEnvironment);
self.control_flow_stack
.push(ControlFlowStackEntry::LexicalScope);
self.control_flow_stack
.push(ControlFlowStackEntry::VariableScope);
ClassStaticBlock
}
fn exit_class_static_block(&mut self, scope: ClassStaticBlock) {
core::mem::forget(scope);
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::VariableScope)));
let entry = self.control_flow_stack.pop();
debug_assert!(matches!(entry, Some(ControlFlowStackEntry::LexicalScope)));
if self.is_unreachable() {
return;
}
self.add_instruction(Instruction::ExitVariableEnvironment);
self.add_instruction(Instruction::ExitDeclarativeEnvironment);
}
#[must_use]
pub(super) fn enter_try_catch_block(&mut self) -> TryCatchBlock {
let jump_to_catch =
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget);
self.control_flow_stack
.push(ControlFlowStackEntry::CatchBlock);
TryCatchBlock(jump_to_catch)
}
fn exit_try_catch_block(&mut self, block: TryCatchBlock) {
core::mem::forget(block);
let Some(ControlFlowStackEntry::CatchBlock) = self.control_flow_stack.pop() else {
unreachable!()
};
if self.is_unreachable() {
return;
}
self.add_instruction(Instruction::PopExceptionJumpTarget);
}
pub(super) fn enter_try_finally_block(&mut self) -> TryFinallyBlock {
let jump_to_catch =
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget);
self.control_flow_stack
.push(ControlFlowStackEntry::TryFinallyBlock {
jump_to_catch,
incoming_control_flows: None,
});
TryFinallyBlock
}
fn exit_try_finally_block(
&mut self,
b: TryFinallyBlock,
block: &'script ast::BlockStatement<'script>,
jump_over_catch_blocks: Option<JumpIndex>,
) {
core::mem::forget(b);
let Some(ControlFlowStackEntry::TryFinallyBlock {
jump_to_catch,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(jump_over_catch_blocks) = jump_over_catch_blocks {
let jump_to_finally_from_catch_end = if !self.is_unreachable() {
Some(self.add_instruction_with_jump_slot(Instruction::Jump))
} else {
None
};
self.compile_abrupt_finally_blocks(block, jump_to_catch, incoming_control_flows);
self.set_jump_target_here(jump_over_catch_blocks);
if let Some(jump_to_finally_from_catch_end) = jump_to_finally_from_catch_end {
self.set_jump_target_here(jump_to_finally_from_catch_end);
}
self.add_instruction(Instruction::PopExceptionJumpTarget);
let finally_block = self.enter_finally_block(false);
let _result = block.compile(self);
finally_block.exit(self);
} else {
let jump_over_abrupt_completions = if !self.is_unreachable() {
self.add_instruction(Instruction::PopExceptionJumpTarget);
let finally_block = self.enter_finally_block(false);
let _result = block.compile(self);
finally_block.exit(self);
if !self.is_unreachable() {
Some(self.add_instruction_with_jump_slot(Instruction::Jump))
} else {
None
}
} else {
None
};
self.compile_abrupt_finally_blocks(block, jump_to_catch, incoming_control_flows);
if let Some(jump_over_abrupt_completions) = jump_over_abrupt_completions {
self.set_jump_target_here(jump_over_abrupt_completions);
}
}
}
fn compile_abrupt_finally_blocks(
&mut self,
block: &'script ast::BlockStatement<'script>,
jump_to_catch: JumpIndex,
incoming_control_flows: Option<Box<ControlFlowFinallyEntry<'script>>>,
) {
self.set_jump_target_here(jump_to_catch);
self.reset_stack_depth();
let finally_block = self.enter_finally_block(true);
let _result = block.compile(self);
finally_block.exit(self);
let end_of_finally_block_is_unreachable = self.is_unreachable();
if !end_of_finally_block_is_unreachable {
self.add_instruction(Instruction::Throw);
}
if let Some(incoming_control_flows) = incoming_control_flows {
for (break_source, label) in incoming_control_flows.breaks {
self.set_jump_target_here(break_source);
self.add_instruction(Instruction::PopExceptionJumpTarget);
let finally_block = self.enter_finally_block(false);
let _result = block.compile(self);
finally_block.exit(self);
if !end_of_finally_block_is_unreachable {
self.compile_break(label);
}
}
for (continue_source, label) in incoming_control_flows.continues {
self.set_jump_target_here(continue_source);
self.add_instruction(Instruction::PopExceptionJumpTarget);
let finally_block = self.enter_finally_block(false);
let _result = block.compile(self);
finally_block.exit(self);
if !end_of_finally_block_is_unreachable {
self.compile_continue(label);
}
}
if !incoming_control_flows.returns.is_empty() {
for return_source in incoming_control_flows.returns {
self.set_jump_target_here(return_source);
}
self.add_instruction(Instruction::PopExceptionJumpTarget);
let finally_block = self.enter_finally_block(false);
let _result = block.compile(self);
finally_block.exit(self);
if !end_of_finally_block_is_unreachable {
self.compile_return(false);
}
}
}
}
pub(super) fn enter_if_statement(&mut self) -> IfStatement {
self.control_flow_stack
.push(ControlFlowStackEntry::IfStatement);
IfStatement
}
fn exit_if_statement(&mut self, st: IfStatement, has_result: bool) {
core::mem::forget(st);
let Some(ControlFlowStackEntry::IfStatement) = self.control_flow_stack.pop() else {
unreachable!()
};
if !self.is_unreachable() && !has_result {
compile_if_statement_exit(&mut self.executable);
}
}
pub(super) fn enter_finally_block(&mut self, has_result: bool) -> FinallyBlock {
self.control_flow_stack
.push(ControlFlowStackEntry::FinallyBlock);
if !has_result {
self.add_instruction_with_constant(Instruction::LoadConstant, Value::Undefined);
self.add_instruction(Instruction::UpdateEmpty);
}
self.add_instruction(Instruction::Load);
let _ = self.executable.push_stack();
FinallyBlock
}
fn exit_finally_block(&mut self) {
let Some(ControlFlowStackEntry::FinallyBlock) = self.control_flow_stack.pop() else {
unreachable!()
};
self.executable.pop_stack();
if !self.is_unreachable() {
self.add_instruction(Instruction::Store);
}
}
#[must_use]
pub(super) fn enter_loop(
&mut self,
label_set: Option<Vec<&'script LabelIdentifier<'script>>>,
) -> Loop {
self.control_flow_stack.push(ControlFlowStackEntry::Loop {
label_set,
incoming_control_flows: None,
});
Loop::Enumerator(self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget))
}
fn exit_loop(&mut self, continue_target: JumpIndex) {
let Some(ControlFlowStackEntry::Loop {
label_set: _,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.compile(
continue_target,
compile_loop_exit,
&mut self.executable,
);
} else if !self.is_unreachable() {
compile_loop_exit(&mut self.executable);
}
}
pub(super) fn enter_switch(
&mut self,
label_set: Option<Vec<&'script LabelIdentifier<'script>>>,
) -> SwitchBlock {
self.control_flow_stack.push(ControlFlowStackEntry::Switch {
label_set,
incoming_control_flows: None,
});
SwitchBlock
}
fn exit_switch(&mut self, block: SwitchBlock) {
core::mem::forget(block);
let Some(ControlFlowStackEntry::Switch {
label_set: _,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.compile(&mut self.executable);
}
}
pub(super) fn push_enumerator(&mut self) -> IteratorStackEntry {
self.control_flow_stack
.push(ControlFlowStackEntry::IteratorStackEntry);
self.add_instruction(Instruction::EnumerateObjectProperties);
IteratorStackEntry(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
pub(super) fn push_sync_iterator(&mut self) -> IteratorStackEntry {
self.control_flow_stack
.push(ControlFlowStackEntry::IteratorStackEntry);
self.add_instruction(Instruction::GetIteratorSync);
IteratorStackEntry(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
pub(super) fn push_async_iterator(&mut self) -> IteratorStackEntry {
self.control_flow_stack
.push(ControlFlowStackEntry::IteratorStackEntry);
self.add_instruction(Instruction::GetIteratorAsync);
IteratorStackEntry(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
fn pop_iterator_stack(&mut self, it: IteratorStackEntry) {
core::mem::forget(it);
let Some(ControlFlowStackEntry::IteratorStackEntry) = self.control_flow_stack.pop() else {
unreachable!()
};
if !self.is_unreachable() {
compile_iterator_pop(&mut self.executable);
}
}
#[must_use]
pub(super) fn enter_iterator(
&mut self,
label_set: Option<Vec<&'script LabelIdentifier<'script>>>,
) -> Loop {
self.control_flow_stack
.push(ControlFlowStackEntry::Iterator {
label_set,
incoming_control_flows: None,
});
Loop::SyncIterator(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
fn exit_iterator(&mut self, continue_target: JumpIndex) {
let Some(ControlFlowStackEntry::Iterator {
label_set: _,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.compile(
continue_target,
compile_sync_iterator_exit,
&mut self.executable,
);
} else if !self.is_unreachable() {
compile_sync_iterator_exit(&mut self.executable);
}
}
#[must_use]
pub(super) fn enter_array_destructuring(&mut self) -> ArrayDestructuring {
self.control_flow_stack
.push(ControlFlowStackEntry::ArrayDestructuring);
ArrayDestructuring(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
fn exit_array_destructuring(&mut self, d: ArrayDestructuring) {
core::mem::forget(d);
let Some(ControlFlowStackEntry::ArrayDestructuring) = self.control_flow_stack.pop() else {
unreachable!()
};
if !self.is_unreachable() {
compile_array_destructuring_exit(&mut self.executable);
}
}
#[must_use]
pub(super) fn enter_async_iterator(
&mut self,
label_set: Option<Vec<&'script LabelIdentifier<'script>>>,
) -> Loop {
self.control_flow_stack
.push(ControlFlowStackEntry::AsyncIterator {
label_set,
incoming_control_flows: None,
});
Loop::AsyncIterator(
self.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget),
)
}
fn exit_async_iterator(&mut self, continue_target: JumpIndex) {
let Some(ControlFlowStackEntry::AsyncIterator {
label_set: _,
incoming_control_flows,
}) = self.control_flow_stack.pop()
else {
unreachable!()
};
if let Some(incoming_control_flows) = incoming_control_flows {
incoming_control_flows.compile(
continue_target,
compile_async_iterator_exit,
&mut self.executable,
);
} else if !self.is_unreachable() {
compile_async_iterator_exit(&mut self.executable);
}
}
pub(super) fn compile_break(&mut self, label: Option<&'script LabelIdentifier<'script>>) {
let mut has_result = false;
for entry in self.control_flow_stack.iter_mut().rev() {
if entry.is_break_target_for(label) {
let break_source = self
.executable
.add_instruction_with_jump_slot(Instruction::Jump);
entry.add_break_source(label, break_source);
return;
}
entry.compile_exit(&mut self.executable, has_result);
has_result = has_result || entry.sets_result_during_exit();
}
}
pub(super) fn compile_continue(&mut self, label: Option<&'script LabelIdentifier<'script>>) {
let mut has_result = false;
for entry in self.control_flow_stack.iter_mut().rev() {
if entry.is_continue_target_for(label) {
let continue_source = self
.executable
.add_instruction_with_jump_slot(Instruction::Jump);
entry.add_continue_source(label, continue_source);
break;
}
entry.compile_exit(&mut self.executable, has_result);
has_result = has_result || entry.sets_result_during_exit();
}
}
pub(super) fn compile_return(&mut self, has_param: bool) {
if self.is_async_generator() && has_param {
self.add_instruction(Instruction::Await);
}
let (stack_contains_finally_blocks, stack_contains_finalisers) = self
.control_flow_stack
.iter()
.fold((false, false), |acc, entry| {
(
acc.0 || entry.is_return_target(),
acc.1 || entry.requires_return_finalisation(false),
)
});
if !stack_contains_finalisers {
self.add_instruction(Instruction::Return);
return;
} else if !stack_contains_finally_blocks {
for entry in self.control_flow_stack.iter().rev() {
if entry.requires_return_finalisation(true) {
entry.compile_exit(&mut self.executable, true);
}
}
self.add_instruction(Instruction::Return);
return;
}
for entry in self.control_flow_stack.iter_mut().rev() {
if entry.is_return_target() {
let return_source = self
.executable
.add_instruction_with_jump_slot(Instruction::Jump);
entry.add_return_source(return_source);
return;
}
entry.compile_exit(&mut self.executable, true);
}
unreachable!()
}
pub(crate) fn is_unreachable(&self) -> bool {
self.executable.is_unreachable()
}
pub(crate) fn compile_function_body(&mut self, data: CompileFunctionBodyData<'script>) {
if self.executable.agent.options.print_internals {
eprintln!();
eprintln!("=== Compiling Function ===");
eprintln!();
}
let mut stack_variables =
Vec::with_capacity(data.ast.formal_parameters().parameters_count());
function_declaration_instantiation::instantiation(
self,
&mut stack_variables,
data.ast,
data.is_strict,
data.is_lexical,
);
if self.is_generator() {
self.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined);
self.add_instruction(Instruction::Yield);
}
let body: &[Statement] =
unsafe { core::mem::transmute(data.ast.ecmascript_code().statements.as_slice()) };
self.compile_statements(body);
for stack_variable in stack_variables {
stack_variable.exit(self);
}
}
pub(crate) fn compile_statements(&mut self, body: &'script [Statement<'script>]) {
let iter = body.iter();
for stmt in iter {
let result = stmt.compile(self);
if result.is_break() {
break;
}
}
}
pub(crate) fn do_implicit_return(&mut self) {
if !self.is_unreachable() {
if self.is_async_generator() {
self.add_instruction(Instruction::Await);
}
self.add_instruction(Instruction::Return);
}
}
pub(crate) fn finish(self) -> Executable<'gc> {
debug_assert!(
self.control_flow_stack.is_empty(),
"Control flow stack contained: {:?}",
self.control_flow_stack
);
self.executable.finish()
}
pub(super) fn add_instruction(&mut self, instruction: Instruction) {
self.executable.add_instruction(instruction);
}
pub(super) fn add_instruction_with_jump_slot(&mut self, instruction: Instruction) -> JumpIndex {
self.executable.add_instruction_with_jump_slot(instruction)
}
pub(super) fn add_jump_instruction_to_index(
&mut self,
instruction: Instruction,
jump_index: JumpIndex,
) {
self.executable
.add_jump_instruction_to_index(instruction, jump_index);
}
pub(super) fn get_jump_index_to_here(&mut self) -> JumpIndex {
self.executable.get_jump_index_to_here()
}
pub(super) fn add_instruction_with_immediate(
&mut self,
instruction: Instruction,
immediate: usize,
) {
self.executable
.add_instruction_with_immediate(instruction, immediate);
}
pub(super) fn add_instruction_with_constant(
&mut self,
instruction: Instruction,
constant: impl Into<Value<'gc>>,
) {
self.executable
.add_instruction_with_constant(instruction, constant);
}
pub(super) fn add_instruction_with_identifier(
&mut self,
instruction: Instruction,
identifier: PropertyKey<'gc>,
) {
self.executable
.add_instruction_with_identifier(instruction, identifier);
}
pub(super) fn add_instruction_with_cache(
&mut self,
instruction: Instruction,
cache: PropertyLookupCache<'gc>,
) {
self.executable
.add_instruction_with_cache(instruction, cache);
}
pub(super) fn add_instruction_with_identifier_and_cache(
&mut self,
instruction: Instruction,
identifier: String<'gc>,
cache: PropertyLookupCache<'gc>,
) {
self.executable.add_instruction_with_identifier_and_cache(
instruction,
identifier.to_property_key(),
cache,
);
}
pub(super) fn add_instruction_with_identifier_and_constant(
&mut self,
instruction: Instruction,
identifier: String<'gc>,
constant: impl Into<Value<'gc>>,
) {
self.executable
.add_instruction_with_identifier_and_constant(
instruction,
identifier.to_property_key(),
constant,
);
}
pub(super) fn add_instruction_with_identifier_and_immediate(
&mut self,
instruction: Instruction,
identifier: String<'gc>,
immediate: usize,
) {
self.executable
.add_instruction_with_identifier_and_immediate(
instruction,
identifier.to_property_key(),
immediate,
);
}
pub(super) fn add_instruction_with_immediate_and_immediate(
&mut self,
instruction: Instruction,
immediate1: usize,
immediate2: usize,
) {
self.executable
.add_instruction_with_immediate_and_immediate(instruction, immediate1, immediate2);
}
pub(super) fn add_instruction_with_immediate_and_constant(
&mut self,
instruction: Instruction,
immediate: usize,
constant: impl Into<Value<'gc>>,
) {
self.executable.add_instruction_with_immediate_and_constant(
instruction,
immediate,
constant.into(),
);
}
pub(super) fn add_instruction_with_function_expression(
&mut self,
instruction: Instruction,
function_expression: FunctionExpression<'gc>,
) {
self.executable
.add_instruction_with_function_expression(instruction, function_expression);
}
pub(super) fn add_instruction_with_function_expression_and_immediate(
&mut self,
instruction: Instruction,
function_expression: FunctionExpression<'gc>,
immediate: usize,
) -> IndexType {
self.executable
.add_instruction_with_function_expression_and_immediate(
instruction,
function_expression,
immediate,
)
}
pub(super) fn add_instruction_with_shape(
&mut self,
instruction: Instruction,
shape: ObjectShape<'gc>,
) {
self.executable
.add_instruction_with_shape(instruction, shape);
}
pub(super) fn add_arrow_function_expression(
&mut self,
arrow_function_expression: ArrowFunctionExpression,
) {
self.executable
.add_arrow_function_expression(arrow_function_expression);
}
pub(super) fn set_jump_target_here(&mut self, jump: JumpIndex) {
self.executable.set_jump_target_here(jump);
}
pub(super) fn get_next_class_initializer_index(&self) -> IndexType {
self.executable.get_next_class_initializer_index()
}
pub(super) fn set_function_expression_bytecode(
&mut self,
index: IndexType,
executable: Executable<'gc>,
) {
self.executable
.set_function_expression_bytecode(index, executable);
}
pub(super) fn add_class_initializer_bytecode(
&mut self,
executable: Executable<'gc>,
has_constructor_parent: bool,
) {
self.executable
.add_class_initializer_bytecode(executable, has_constructor_parent);
}
pub(super) fn add_class_initializer(&mut self, has_constructor_parent: bool) {
self.executable
.add_class_initializer(has_constructor_parent);
}
}
#[cfg(debug_assertions)]
trait Undroppable {
#[inline(always)]
fn on_drop() {
#[cfg(debug_assertions)]
if !std::thread::panicking() {
panic!(
"Unhandled {}: this type should be explicitly consumed, not dropped",
core::any::type_name::<Self>()
);
}
}
}
#[must_use]
pub(crate) struct LexicalScope;
#[cfg(debug_assertions)]
impl Undroppable for LexicalScope {}
impl LexicalScope {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.exit_lexical_scope(self);
}
}
#[cfg(debug_assertions)]
impl Drop for LexicalScope {
fn drop(&mut self) {
Self::on_drop();
}
}
#[must_use]
pub(crate) struct ClassStaticBlock;
#[cfg(debug_assertions)]
impl Undroppable for ClassStaticBlock {}
impl ClassStaticBlock {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.exit_class_static_block(self);
}
}
#[cfg(debug_assertions)]
impl Drop for ClassStaticBlock {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct StackValue;
#[cfg(debug_assertions)]
impl Undroppable for StackValue {}
impl StackValue {
pub(crate) fn store(self, ctx: &mut CompileContext) {
ctx.pop_stack_value(self);
if ctx.is_unreachable() {
return;
}
ctx.executable.add_instruction(Instruction::Store);
}
pub(crate) fn pop(self, ctx: &mut CompileContext) {
ctx.pop_stack_value(self);
if ctx.is_unreachable() {
return;
}
ctx.executable.add_instruction(Instruction::PopStack);
}
pub(crate) fn update_empty(self, ctx: &mut CompileContext) {
ctx.pop_stack_value(self);
if ctx.is_unreachable() {
return;
}
ctx.executable.add_instruction(Instruction::UpdateEmpty);
}
pub(crate) fn forget(self, ctx: &mut CompileContext) {
ctx.pop_stack_value(self);
}
}
#[cfg(debug_assertions)]
impl Drop for StackValue {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct StackVariable;
#[cfg(debug_assertions)]
impl Undroppable for StackVariable {}
impl StackVariable {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.pop_stack_variable(self);
}
}
#[cfg(debug_assertions)]
impl Drop for StackVariable {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct StackResultValue {
stack_slot: u32,
}
#[cfg(debug_assertions)]
impl Undroppable for StackResultValue {}
impl StackResultValue {
pub(crate) fn read(&self, ctx: &mut CompileContext) {
ctx.add_instruction_with_immediate(
Instruction::GetValueFromIndex,
self.stack_slot as usize,
);
}
pub(crate) fn write(&self, ctx: &mut CompileContext) {
ctx.add_instruction_with_immediate(Instruction::PutValueToIndex, self.stack_slot as usize);
}
#[inline(always)]
pub(crate) fn update_empty(self, ctx: &mut CompileContext) {
ctx.pop_stack_result_value(self);
if ctx.is_unreachable() {
return;
}
ctx.executable.add_instruction(Instruction::UpdateEmpty);
}
#[inline(always)]
pub(crate) fn forget(self, ctx: &mut CompileContext) {
ctx.pop_stack_result_value(self);
}
}
#[cfg(debug_assertions)]
impl Drop for StackResultValue {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) enum BlockEnvPrep {
Var(StackVariable),
Env(LexicalScope),
}
impl BlockEnvPrep {
#[inline(always)]
pub(crate) fn is_env(&self) -> bool {
matches!(self, BlockEnvPrep::Env(_))
}
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
match self {
BlockEnvPrep::Var(var) => var.exit(ctx),
BlockEnvPrep::Env(scope) => scope.exit(ctx),
}
}
}
pub(crate) struct PrivateScope;
#[cfg(debug_assertions)]
impl Undroppable for PrivateScope {}
impl PrivateScope {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.exit_private_scope(self);
}
}
#[cfg(debug_assertions)]
impl Drop for PrivateScope {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct TryCatchBlock(JumpIndex);
#[cfg(debug_assertions)]
impl Undroppable for TryCatchBlock {}
impl TryCatchBlock {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) -> JumpIndex {
let throw_handler = self.0.clone();
ctx.exit_try_catch_block(self);
throw_handler
}
}
#[cfg(debug_assertions)]
impl Drop for TryCatchBlock {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct TryFinallyBlock;
#[cfg(debug_assertions)]
impl Undroppable for TryFinallyBlock {}
impl TryFinallyBlock {
#[inline(always)]
pub(crate) fn exit<'s>(
self,
ctx: &mut CompileContext<'_, 's, '_, '_>,
block: &'s ast::BlockStatement<'s>,
jump_over_catch_blocks: Option<JumpIndex>,
) {
ctx.exit_try_finally_block(self, block, jump_over_catch_blocks);
}
}
#[cfg(debug_assertions)]
impl Drop for TryFinallyBlock {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct IfStatement;
#[cfg(debug_assertions)]
impl Undroppable for IfStatement {}
impl IfStatement {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext, has_result: bool) {
ctx.exit_if_statement(self, has_result);
}
}
#[cfg(debug_assertions)]
impl Drop for IfStatement {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct LabelledStatement;
#[cfg(debug_assertions)]
impl Undroppable for LabelledStatement {}
impl LabelledStatement {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.exit_label(self);
}
}
#[cfg(debug_assertions)]
impl Drop for LabelledStatement {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct FinallyBlock;
#[cfg(debug_assertions)]
impl Undroppable for FinallyBlock {}
impl FinallyBlock {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
core::mem::forget(self);
ctx.exit_finally_block();
}
}
#[cfg(debug_assertions)]
impl Drop for FinallyBlock {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) enum Loop {
Enumerator(JumpIndex),
SyncIterator(JumpIndex),
AsyncIterator(JumpIndex),
}
#[cfg(debug_assertions)]
impl Undroppable for Loop {}
impl Loop {
#[inline(always)]
pub(crate) fn on_abrupt_exit(&self) -> JumpIndex {
match self {
Self::Enumerator(t) | Self::SyncIterator(t) | Self::AsyncIterator(t) => t.clone(),
}
}
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext, continue_target: JumpIndex) {
match self {
Self::Enumerator(_) => {
core::mem::forget(self);
ctx.exit_loop(continue_target)
}
Self::SyncIterator(_) => {
core::mem::forget(self);
ctx.exit_iterator(continue_target)
}
Self::AsyncIterator(_) => {
core::mem::forget(self);
ctx.exit_async_iterator(continue_target)
}
}
}
}
#[cfg(debug_assertions)]
impl Drop for Loop {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct SwitchBlock;
#[cfg(debug_assertions)]
impl Undroppable for SwitchBlock {}
impl SwitchBlock {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) {
ctx.exit_switch(self);
}
}
#[cfg(debug_assertions)]
impl Drop for SwitchBlock {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct IteratorStackEntry(JumpIndex);
#[cfg(debug_assertions)]
impl Undroppable for IteratorStackEntry {}
impl IteratorStackEntry {
#[inline(always)]
pub(crate) fn on_abrupt_exit(&self) -> JumpIndex {
self.0.clone()
}
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) -> JumpIndex {
let throw_handler = self.0.clone();
ctx.pop_iterator_stack(self);
throw_handler
}
}
#[cfg(debug_assertions)]
impl Drop for IteratorStackEntry {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) struct ArrayDestructuring(JumpIndex);
#[cfg(debug_assertions)]
impl Undroppable for ArrayDestructuring {}
impl ArrayDestructuring {
#[inline(always)]
pub(crate) fn exit(self, ctx: &mut CompileContext) -> JumpIndex {
let throw_handler = self.0.clone();
ctx.exit_array_destructuring(self);
throw_handler
}
}
#[cfg(debug_assertions)]
impl Drop for ArrayDestructuring {
fn drop(&mut self) {
Self::on_drop();
}
}
pub(crate) trait CompileEvaluation<'a, 's, 'gc, 'scope> {
type Output;
fn compile(&'s self, ctx: &mut CompileContext<'a, 's, 'gc, 'scope>) -> Self::Output;
}
pub(crate) trait CompileLabelledEvaluation<'a, 's, 'gc, 'scope> {
type Output;
fn compile_labelled(
&'s self,
label_set: Option<&mut Vec<&'s LabelIdentifier<'s>>>,
ctx: &mut CompileContext<'a, 's, 'gc, 'scope>,
) -> Self::Output;
}