ladybird/Libraries/LibJS/Rust/src/bytecode/codegen.rs

9163 lines
367 KiB
Rust
Raw Normal View History

/*
* Copyright (c) 2026-present, the Ladybird developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
//! Bytecode generation from AST.
//!
//! This is the largest module in the parser -- it walks the AST
//! and emits bytecode instructions via the `Generator`.
//!
//! ## Conventions
//!
//! Each AST node's codegen returns `Option<ScopedOperand>`:
//! - `Some(op)` if the node produces a value (expressions)
//! - `None` for statements that don't produce values
//!
//! The `preferred_dst` parameter is a register hint: when the caller
//! already has a destination register (e.g. the LHS of an assignment),
//! codegen writes directly there instead of allocating a temporary.
//!
//! ## File organization
//!
//! The file is organized by AST node type, with section headers:
//!
//! - **Top-level entry points**: `generate_expression`, `generate_statement`
//! - **Literals and identifiers**: numeric, string, boolean, regexp, identifier
//! - **Await/yield**: async/generator control flow helpers
//! - **Operators**: binary, logical, conditional, update, assignment
//! - **Control flow**: if, while, do-while, for, for-in/of, switch, labelled
//! - **Blocks and scopes**: block statements, function bodies, scope children
//! - **Declarations**: variable declarations, using declarations
//! - **Calls**: regular calls, super calls, optional chains, builtin detection
//! - **Templates**: template literals, tagged templates
//! - **Objects and classes**: object expressions, class expressions
//! - **Patterns**: binding pattern destructuring (array and object)
//! - **Try/catch/finally**: try statement codegen
//! - **Functions**: `emit_new_function`, `emit_function_declaration_instantiation`
//! - **Helpers**: constant folding, NaN-boxing, error message utilities
use std::collections::HashSet;
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};
use crate::ast::*;
use crate::lexer::ch;
use crate::u32_from_usize;
use super::ffi::WellKnownSymbolKind;
use super::generator::{
BlockBoundaryType, ConstantValue, FinallyContext, Generator, PendingClassBlueprint, PendingClassElement,
PendingLiteralValueKind, PendingSharedFunctionData, ScopedOperand, choose_dst, constant_to_boolean, parse_bigint,
};
use super::instruction::Instruction;
use super::operand::*;
/// Generate bytecode for an expression.
pub fn generate_expression(
expression: &Expression,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let saved_source_start = generator.current_source_start;
let saved_source_end = generator.current_source_end;
generator.current_source_start = expression.range.start;
generator.current_source_end = expression.range.end;
2026-02-24 08:36:14 -03:00
let result = generate_expression_inner(expression, generator, preferred_dst);
2026-02-24 08:36:14 -03:00
generator.current_source_start = saved_source_start;
generator.current_source_end = saved_source_end;
result
}
fn generate_expression_inner(
expression: &Expression,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// NamedEvaluation: only function/class expressions consume pending_lhs_name.
// Clear it for all other expression types so it doesn't leak through to
// nested function expressions (e.g. IIFEs: `let x = (function() { ... })()`).
if !matches!(expression.inner, ExpressionKind::Function(_) | ExpressionKind::Class(_)) {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = None;
}
2026-02-24 08:36:14 -03:00
match &expression.inner {
// === Literals ===
2026-02-24 08:36:14 -03:00
ExpressionKind::NumericLiteral(value) => Some(generator.add_constant_number(*value)),
2026-02-24 08:36:14 -03:00
ExpressionKind::BooleanLiteral(value) => Some(generator.add_constant_boolean(*value)),
2026-02-24 08:36:14 -03:00
ExpressionKind::NullLiteral => Some(generator.add_constant_null()),
ExpressionKind::StringLiteral(value) => Some(generator.add_constant_string((**value).clone())),
ExpressionKind::BigIntLiteral(value) => {
// The AST stores the raw value including the 'n' suffix; strip it for codegen.
let digits = value.strip_suffix('n').unwrap_or(value.as_str());
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint(digits.to_string()))
}
ExpressionKind::RegExpLiteral(data) => {
2026-02-24 08:36:14 -03:00
let source_index = generator.intern_string(&data.pattern);
let flags_index = generator.intern_string(&data.flags);
let compiled = data.compiled_regex.take();
2026-02-24 08:36:14 -03:00
let regex_index = generator.intern_regex(compiled);
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::NewRegExp {
dst: dst.operand(),
source_index,
flags_index,
regex_index,
});
Some(dst)
}
// === Identifiers ===
ExpressionKind::Identifier(ident) => Some(generate_identifier(ident, generator, preferred_dst)),
// === This ===
ExpressionKind::This => {
// OPTIMIZATION: When function_environment_needed is false, the `this`
// value is inherited from the outer function and already in the register.
2026-02-24 08:36:14 -03:00
if generator.function_environment_needed {
emit_resolve_this_if_needed(generator);
}
2026-02-24 08:36:14 -03:00
Some(generator.this_value())
}
// === Unary ===
ExpressionKind::Unary { op, operand } => generate_unary_expression(generator, *op, operand, preferred_dst),
// === Binary ===
ExpressionKind::Binary(data) => {
generate_binary_expression(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
}
// === Logical (short-circuit) ===
ExpressionKind::Logical(data) => generate_logical(generator, data.op, &data.lhs, &data.rhs, preferred_dst),
// === Conditional (ternary) ===
ExpressionKind::Conditional(data) => {
generate_conditional(generator, &data.test, &data.consequent, &data.alternate, preferred_dst)
}
// === Sequence ===
ExpressionKind::Sequence(expressions) => {
let mut last = None;
for expression in expressions.iter() {
2026-02-24 08:36:14 -03:00
last = generate_expression(expression, generator, None);
if generator.is_current_block_terminated() {
break;
}
}
last
}
// === Function expressions ===
ExpressionKind::Function(function_id) => {
Some(generate_function_expression(generator, *function_id, preferred_dst))
}
// === Array ===
ExpressionKind::Array(elements) => Some(generate_array_expression(generator, elements, preferred_dst)),
// === Member access ===
ExpressionKind::Member(data) => {
generate_member_expression(generator, &data.object, &data.property, data.computed, preferred_dst)
}
// === Call ===
ExpressionKind::Call(data) => generate_call_expression(generator, data, preferred_dst, false),
// === New ===
2026-02-24 08:36:14 -03:00
ExpressionKind::New(data) => generate_call_expression(generator, data, preferred_dst, true),
// === Spread ===
ExpressionKind::Spread(inner) => {
// Spread is handled by the caller (Call, Array, Object)
Some(generate_expression_or_undefined(inner, generator, preferred_dst))
}
// === Yield ===
ExpressionKind::Yield(data) => Some(generate_yield_expression(
generator,
data.argument.as_deref(),
data.is_yield_from,
)),
// === Await ===
ExpressionKind::Await(inner) => {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(inner, generator, None);
// Allocate received_completion registers before the await.
2026-02-24 08:36:14 -03:00
let received_completion = generator.allocate_register();
let received_completion_type = generator.allocate_register();
let received_completion_value = generator.allocate_register();
let acc = generator.accumulator();
generator.emit_mov(&received_completion, &acc);
Some(generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&value,
&received_completion,
&received_completion_type,
&received_completion_value,
))
}
// === MetaProperty ===
ExpressionKind::MetaProperty(MetaPropertyType::NewTarget) => {
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::GetNewTarget { dst: dst.operand() });
Some(dst)
}
ExpressionKind::MetaProperty(MetaPropertyType::ImportMeta) => {
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::GetImportMeta { dst: dst.operand() });
Some(dst)
}
// === ImportCall ===
ExpressionKind::ImportCall(ic_data) => {
let spec = generate_expression(&ic_data.specifier, generator, None)?;
let opts = match &ic_data.options {
2026-02-24 08:36:14 -03:00
Some(o) => generate_expression(o, generator, None)?,
None => generator.add_constant_undefined(),
};
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::ImportCall {
dst: dst.operand(),
specifier: spec.operand(),
options: opts.operand(),
});
Some(dst)
}
// === Update (++/--) ===
ExpressionKind::Update(data) => generate_update_expression(generator, data.op, &data.argument, data.prefixed),
// === Assignment ===
ExpressionKind::Assignment(data) => {
generate_assignment_expression(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
}
// === Template literals ===
ExpressionKind::TemplateLiteral(data) => generate_template_literal(generator, data, preferred_dst),
// === Tagged template literals ===
ExpressionKind::TaggedTemplateLiteral(data) => Some(generate_tagged_template_literal(
generator,
&data.tag,
&data.template_literal,
preferred_dst,
)),
// === Object ===
ExpressionKind::Object(data) => Some(generate_object_expression(generator, data, preferred_dst)),
// === OptionalChain ===
ExpressionKind::OptionalChain(oc_data) => {
// Allocate current_base first, current_value second.
2026-02-24 08:36:14 -03:00
let current_base = generator.allocate_register();
let current_value = choose_dst(generator, preferred_dst);
let undef = generator.add_constant_undefined();
generator.emit_mov(&current_base, &undef);
generate_optional_chain_inner(
generator,
&oc_data.base,
&oc_data.references,
2026-02-24 08:36:14 -03:00
&current_value,
&current_base,
)?;
Some(current_value)
}
// === SuperCall ===
ExpressionKind::SuperCall(data) => {
let arguments = if data.is_synthetic {
// Synthetic constructor: super(...arguments) — single spread argument,
// don't call @@iterator on %Array.prototype%.
assert!(data.arguments.len() == 1 && data.arguments[0].is_spread);
2026-02-24 08:36:14 -03:00
generate_expression_or_undefined(&data.arguments[0].value, generator, None)
} else {
2026-02-24 08:36:14 -03:00
generate_arguments_array(generator, &data.arguments)
};
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::SuperCallWithArgumentArray {
dst: dst.operand(),
arguments: arguments.operand(),
is_synthetic: data.is_synthetic,
});
Some(dst)
}
ExpressionKind::Super => {
// super keyword as an expression (for super.foo, super[foo])
// Returns the home object's prototype
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::ResolveSuperBase { dst: dst.operand() });
Some(dst)
}
ExpressionKind::Class(data) => Some(generate_class_expression(generator, data, preferred_dst)),
ExpressionKind::PrivateIdentifier(_) => {
// Private identifiers are handled by member access codegen
None
}
ExpressionKind::Error => None,
2026-02-24 08:36:14 -03:00
}
}
fn generate_unary_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: UnaryOp,
operand: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// typeof and delete on identifiers need special handling BEFORE
// evaluating the operand to avoid throwing on unresolvable references.
// Allocate dst before evaluating typeof/not operands.
if op == UnaryOp::Typeof {
2026-02-24 08:36:14 -03:00
if let ExpressionKind::Identifier(ident) = &operand.inner
&& !ident.is_local()
{
let dst = choose_dst(generator, preferred_dst);
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::TypeofBinding {
dst: dst.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
let value = generate_expression(operand, generator, None)?;
generator.emit(Instruction::Typeof {
dst: dst.operand(),
src: value.operand(),
});
return Some(dst);
}
if op == UnaryOp::Delete {
2026-02-24 08:36:14 -03:00
return Some(emit_delete_reference(generator, operand));
}
// Allocate dst before operand.
// Also optimize !!x -> ToBoolean(x).
if op == UnaryOp::Not {
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
if let ExpressionKind::Unary {
op: UnaryOp::Not,
operand: inner,
} = &operand.inner
{
2026-02-24 08:36:14 -03:00
let value = generate_expression(inner, generator, None)?;
if let Some(folded) = try_constant_fold_to_boolean(generator, &value) {
return Some(folded);
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ToBoolean {
dst: dst.operand(),
value: value.operand(),
});
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let value = generate_expression(operand, generator, None)?;
if let Some(folded) = try_constant_fold_unary(generator, op, &value) {
return Some(folded);
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Not {
dst: dst.operand(),
src: value.operand(),
});
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let value = generate_expression(operand, generator, None)?;
// OPTIMIZATION: constant fold unary operations on constants.
2026-02-24 08:36:14 -03:00
if let Some(folded) = try_constant_fold_unary(generator, op, &value) {
return Some(folded);
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
match op {
UnaryOp::BitwiseNot => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::BitwiseNot {
dst: dst.operand(),
src: value.operand(),
});
}
UnaryOp::Not => unreachable!("Not is handled by early return above"),
UnaryOp::Plus => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::UnaryPlus {
dst: dst.operand(),
src: value.operand(),
});
}
UnaryOp::Minus => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::UnaryMinus {
dst: dst.operand(),
src: value.operand(),
});
}
UnaryOp::Typeof => unreachable!("Typeof is handled by early return above"),
UnaryOp::Void => {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_undefined());
}
UnaryOp::Delete => unreachable!("Delete is handled by early return above"),
}
Some(dst)
}
fn might_contain_assignment_expression(expression: &Expression) -> bool {
match &expression.inner {
ExpressionKind::NumericLiteral(_)
| ExpressionKind::StringLiteral(_)
| ExpressionKind::BooleanLiteral(_)
| ExpressionKind::NullLiteral
| ExpressionKind::Identifier(_) => false,
ExpressionKind::Unary { op: _, operand } => might_contain_assignment_expression(operand),
ExpressionKind::Binary(data) => {
might_contain_assignment_expression(&data.lhs) || might_contain_assignment_expression(&data.rhs)
}
ExpressionKind::Member(data) => {
might_contain_assignment_expression(&data.object) || might_contain_assignment_expression(&data.property)
}
// Conservatively consider everything else, including assignments themselves as potentially
// assigning.
_ => true,
}
}
fn generate_binary_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: BinaryOp,
lhs: &Expression,
rhs: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// Special case: `#privateId in obj` uses HasPrivateId instead of In.
2026-02-24 08:36:14 -03:00
if op == BinaryOp::In
&& let ExpressionKind::PrivateIdentifier(priv_ident) = &lhs.inner
{
let base = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::HasPrivateId {
dst: dst.operand(),
base: base.operand(),
property: id,
});
return Some(dst);
}
// OPTIMIZATION: Pre-convert numeric literal operands of bitwise
// operations to i32/u32 to avoid runtime conversion.
let lhs_val = match op {
BinaryOp::BitwiseAnd
| BinaryOp::BitwiseOr
| BinaryOp::BitwiseXor
| BinaryOp::LeftShift
| BinaryOp::RightShift
| BinaryOp::UnsignedRightShift => {
if let ExpressionKind::NumericLiteral(n) = &lhs.inner {
2026-02-24 08:36:14 -03:00
generator.add_constant_number(to_int32(*n) as f64)
} else {
2026-02-24 08:36:14 -03:00
generate_expression(lhs, generator, None)?
}
}
2026-02-24 08:36:14 -03:00
_ => generate_expression(lhs, generator, None)?,
};
// OPTIMIZATION: We do need to make a copy of the LHS here in case evaluation of the RHS
// reassigns it. However, binary expressions are a pretty common thing, so doing the copy
// unconditionally is a noticable performance hit, especially because in practice, the copy is
// almost never needed. We add a small heuristic here that detects the most common cases.
// FIXME: This is a pretty narrow optimization. Maybe instead, it would make sense to have a
// more general "remove unnecessary mov-operations" as part of a bytecode optimization pass.
let lhs_val = if might_contain_assignment_expression(rhs) {
generator.copy_if_needed_to_preserve_evaluation_order(&lhs_val)
} else {
lhs_val
};
let rhs_val = match op {
BinaryOp::BitwiseAnd | BinaryOp::BitwiseOr | BinaryOp::BitwiseXor => {
if let ExpressionKind::NumericLiteral(n) = &rhs.inner {
2026-02-24 08:36:14 -03:00
generator.add_constant_number(to_int32(*n) as f64)
} else {
2026-02-24 08:36:14 -03:00
generate_expression(rhs, generator, None)?
}
}
BinaryOp::LeftShift | BinaryOp::RightShift | BinaryOp::UnsignedRightShift => {
if let ExpressionKind::NumericLiteral(n) = &rhs.inner {
2026-02-24 08:36:14 -03:00
generator.add_constant_number(to_u32(*n) as f64)
} else {
2026-02-24 08:36:14 -03:00
generate_expression(rhs, generator, None)?
}
}
2026-02-24 08:36:14 -03:00
_ => generate_expression(rhs, generator, None)?,
};
// OPTIMIZATION: constant folding for binary operations on constants.
2026-02-24 08:36:14 -03:00
if let Some(folded) = try_constant_fold_binary(generator, op, &lhs_val, &rhs_val) {
return Some(folded);
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
emit_binary_op(generator, op, &dst, &lhs_val, &rhs_val);
Some(dst)
}
fn generate_function_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
function_id: FunctionId,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
let data = generator.function_table.take(function_id);
let has_name = data.name.is_some();
// Named function expressions get an intermediate scope so the name
// is visible inside the function body but not outside.
let name_id = if has_name {
2026-02-24 08:36:14 -03:00
let parent = generator
.lexical_environment_register_stack
.last()
.cloned()
2026-02-24 08:36:14 -03:00
.unwrap_or_else(|| generator.add_constant_undefined());
let new_env = generator.allocate_register();
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
generator.emit(Instruction::CreateLexicalEnvironment {
dst: new_env.operand(),
parent: parent.operand(),
capacity: 0,
});
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.push(new_env);
let id = generator.intern_identifier(&data.name.as_ref().expect("function declaration must have a name").name);
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: true,
is_global: false,
is_strict: false,
});
Some(id)
} else {
None
};
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
// For anonymous function expressions, use the pending LHS name
// as the function's .name property.
let lhs_name = if !has_name {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name.take()
} else {
None
};
let lhs_name_str: Option<Utf16String> = lhs_name.map(|index| generator.identifier_table[index.0 as usize].clone());
let name_override = if !has_name { lhs_name_str.as_deref() } else { None };
2026-02-24 08:36:14 -03:00
let shared_function_data_index = emit_new_function(generator, data, name_override);
let home_object = generator.home_objects.last().map(|ho| ho.operand());
generator.emit(Instruction::NewFunction {
dst: dst.operand(),
shared_function_data_index,
lhs_name,
home_object,
});
if has_name {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: name_id.expect("has_name guarantees name_id is set"),
src: dst.operand(),
cache: EnvironmentCoordinate::empty(),
});
2026-02-24 08:36:14 -03:00
generator.end_variable_scope();
}
dst
}
fn generate_array_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
elements: &[Option<Expression>],
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
// If all elements are constant primitives, emit NewPrimitiveArray.
if !elements.is_empty()
&& elements.iter().all(|e| match e {
None => true, // holes
Some(e) => matches!(
e.inner,
ExpressionKind::NumericLiteral(_) | ExpressionKind::BooleanLiteral(_) | ExpressionKind::NullLiteral
),
})
{
let values: Vec<u64> = elements
.iter()
.map(|e| match e {
None => nanboxed_empty(),
Some(e) => match &e.inner {
ExpressionKind::NumericLiteral(n) => nanboxed_number(*n),
ExpressionKind::BooleanLiteral(b) => nanboxed_boolean(*b),
ExpressionKind::NullLiteral => nanboxed_null(),
_ => unreachable!("all elements verified as primitive literals above"),
},
})
.collect();
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit(Instruction::NewPrimitiveArray {
dst: dst.operand(),
element_count: u32_from_usize(values.len()),
elements: values,
});
return dst;
}
// Find the first spread element.
let first_spread = elements
.iter()
.position(|e| matches!(e, Some(element) if matches!(element.inner, ExpressionKind::Spread(_))));
// Collect elements before the first spread into a NewArray.
let pre_spread_count = first_spread.unwrap_or(elements.len());
let mut scoped_arguments: Vec<ScopedOperand> = Vec::with_capacity(pre_spread_count);
for element in &elements[..pre_spread_count] {
match element {
Some(e) => {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(e, generator, None);
scoped_arguments.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
}
None => {
2026-02-24 08:36:14 -03:00
scoped_arguments.push(generator.add_constant_empty());
}
}
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
let arguments: Vec<Operand> = scoped_arguments.iter().map(|s| s.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewArray {
dst: dst.operand(),
element_count: u32_from_usize(arguments.len()),
elements: arguments,
});
// NB: Keep scoped_arguments alive until the end of the expression
// so their registers aren't reused during spread evaluation.
// Append elements after the first spread using ArrayAppend.
if let Some(spread_index) = first_spread {
for element in &elements[spread_index..] {
match element {
None => {
2026-02-24 08:36:14 -03:00
let empty = generator.add_constant_empty();
generator.emit(Instruction::ArrayAppend {
dst: dst.operand(),
src: empty.operand(),
is_spread: false,
});
}
Some(e) => {
let is_spread = matches!(e.inner, ExpressionKind::Spread(_));
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(e, generator, None);
generator.emit(Instruction::ArrayAppend {
dst: dst.operand(),
src: val.operand(),
is_spread,
});
}
}
}
}
dst
}
fn generate_member_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
object: &Expression,
property: &Expression,
computed: bool,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
let is_super = matches!(object.inner, ExpressionKind::Super);
if is_super {
// Per spec, evaluation order for super property access is:
// 1. Resolve this binding
// 2. Evaluate computed property (if any)
// 3. Resolve super base
// 4. Property lookup with this
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let computed_key = if computed {
2026-02-24 08:36:14 -03:00
Some(generate_expression(property, generator, None)?)
} else {
None
};
2026-02-24 08:36:14 -03:00
let super_base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase {
dst: super_base.operand(),
});
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
if let Some(key) = computed_key {
2026-02-24 08:36:14 -03:00
emit_get_by_value_with_this(generator, &dst, &super_base, &key, &this_value);
} else if let ExpressionKind::Identifier(ident) = &property.inner {
emit_get_by_id_with_this(generator, &dst, &super_base, &ident.name, &this_value);
}
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let obj = generate_expression(object, generator, None)?;
let obj = generator.copy_if_needed_to_preserve_evaluation_order(&obj);
2026-02-24 08:36:14 -03:00
let base_id = intern_base_identifier(generator, object);
if computed {
2026-02-24 08:36:14 -03:00
let property = generate_expression(property, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
emit_get_by_value(generator, &dst, &obj, &property, base_id);
return Some(dst);
}
// Non-computed: property must be an Identifier
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
if let ExpressionKind::Identifier(ident) = &property.inner {
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &dst, &obj, &ident.name, base_id);
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::GetPrivateById {
dst: dst.operand(),
base: obj.operand(),
property: id,
});
}
Some(dst)
}
fn generate_yield_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
argument: Option<&Expression>,
is_yield_from: bool,
) -> ScopedOperand {
// Allocate completion registers before evaluating the argument.
2026-02-24 08:36:14 -03:00
let received_completion = generator.allocate_register();
let received_completion_type = generator.allocate_register();
let received_completion_value = generator.allocate_register();
let value = if let Some(argument) = argument {
2026-02-24 08:36:14 -03:00
generate_expression_or_undefined(argument, generator, None)
} else {
2026-02-24 08:36:14 -03:00
generator.add_constant_undefined()
};
if is_yield_from {
return generate_yield_from(
2026-02-24 08:36:14 -03:00
generator,
value,
&received_completion,
&received_completion_type,
&received_completion_value,
);
}
// Create continuation block, call generate_yield, then handle
// completion checking.
2026-02-24 08:36:14 -03:00
let continuation_block = generator.make_block();
let is_in_finalizer = generator.is_in_finalizer();
// Save exception register before yielding if in a finalizer,
// as the act of yielding clears scheduled exceptions.
let saved_exception = if is_in_finalizer {
2026-02-24 08:36:14 -03:00
let reg = generator.allocate_register();
generator.emit_mov_raw(reg.operand(), generator.exception_operand());
Some(reg)
} else {
None
};
generate_yield(
2026-02-24 08:36:14 -03:00
generator,
continuation_block,
&value,
&received_completion,
&received_completion_type,
&received_completion_value,
2026-02-24 08:36:14 -03:00
generator.is_in_async_generator_function(),
);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(continuation_block);
// Restore exception register after resuming.
if let Some(ref saved) = saved_exception {
2026-02-24 08:36:14 -03:00
generator.emit_mov_raw(generator.exception_operand(), saved.operand());
}
2026-02-24 08:36:14 -03:00
let acc = generator.accumulator();
generator.emit_mov(&received_completion, &acc);
generator.emit(Instruction::GetCompletionFields {
type_dst: received_completion_type.operand(),
value_dst: received_completion_value.operand(),
completion: received_completion.operand(),
});
2026-02-24 08:36:14 -03:00
let normal_block = generator.make_block();
let throw_cont = generator.make_block();
let type_is_normal = generator.allocate_register();
let normal_type = generator.add_constant_number(CompletionType::Normal.to_f64());
generator.emit(Instruction::StrictlyEquals {
dst: type_is_normal.operand(),
lhs: received_completion_type.operand(),
rhs: normal_type.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&type_is_normal, normal_block, throw_cont);
2026-02-24 08:36:14 -03:00
let throw_value_block = generator.make_block();
let return_value_block = generator.make_block();
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_cont);
let type_is_throw = generator.allocate_register();
let throw_type = generator.add_constant_number(CompletionType::Throw.to_f64());
generator.emit(Instruction::StrictlyEquals {
dst: type_is_throw.operand(),
lhs: received_completion_type.operand(),
rhs: throw_type.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&type_is_throw, throw_value_block, return_value_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_value_block);
generator.perform_needed_unwinds();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Throw {
src: received_completion_value.operand(),
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_value_block);
generator.generate_return(&received_completion_value);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(normal_block);
received_completion_value
}
/// Generate bytecode for an expression, returning `undefined` if the
/// expression produces no value (e.g. the block was already terminated).
fn generate_expression_or_undefined(
expression: &Expression,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
generate_expression(expression, generator, preferred_dst).unwrap_or_else(|| generator.add_constant_undefined())
}
/// Generate bytecode for a statement.
pub fn generate_statement(
statement: &Statement,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let saved_source_start = generator.current_source_start;
let saved_source_end = generator.current_source_end;
generator.current_source_start = statement.range.start;
generator.current_source_end = statement.range.end;
let result = match &statement.inner {
StatementKind::Empty | StatementKind::Error | StatementKind::ErrorDeclaration => None,
StatementKind::Debugger => None,
// === ExpressionStatement ===
2026-02-24 08:36:14 -03:00
StatementKind::Expression(expression) => generate_expression(expression, generator, None),
// === Block ===
StatementKind::Block(scope) => generate_block_statement(generator, &scope.borrow(), preferred_dst),
// === FunctionBody ===
StatementKind::FunctionBody { scope, .. } => generate_scope_children(generator, &scope.borrow(), preferred_dst),
// === Program ===
// Note: GlobalDeclarationInstantiation (GDI) runs before this bytecode
// executes. GDI hoists top-level function declarations and var bindings
// to the global scope, including Annex B function-in-block hoisting.
2026-02-24 08:36:14 -03:00
StatementKind::Program(data) => {
// Populate annexb_function_names so switch codegen can emit
// GetBinding + SetVariableBinding for AnnexB-hoisted functions
// (Annex B requires switch cases to copy the block-scoped binding
// into the var-scoped binding on each case entry).
let scope = data.scope.borrow();
for name in &scope.annexb_function_names {
2026-02-24 08:36:14 -03:00
generator.annexb_function_names.insert(name.clone());
}
2026-02-24 08:36:14 -03:00
generate_scope_children(generator, &scope, preferred_dst)
}
// === If ===
2026-03-22 15:36:14 -03:00
StatementKind::If(data) => generate_if_statement(
2026-02-24 08:36:14 -03:00
generator,
2026-03-22 15:36:14 -03:00
&data.test,
&data.consequent,
data.alternate.as_deref(),
2026-02-24 08:36:14 -03:00
preferred_dst,
),
// === While ===
StatementKind::While(data) => generate_while_statement(generator, &data.test, &data.body, preferred_dst),
// === DoWhile ===
StatementKind::DoWhile(data) => generate_do_while_statement(generator, &data.test, &data.body, preferred_dst),
// === For ===
2026-03-22 15:41:19 -03:00
StatementKind::For(data) => generate_for_statement(
2026-02-24 08:36:14 -03:00
generator,
2026-03-22 15:41:19 -03:00
data.init.as_ref(),
data.test.as_deref(),
data.update.as_deref(),
&data.body,
preferred_dst,
),
// === Return ===
StatementKind::Return(value) => {
let val = match value {
Some(expression) => {
2026-02-24 08:36:14 -03:00
let v = generate_expression_or_undefined(expression, generator, None);
// Async functions implicitly await an explicit return value.
// Bare `return;` does NOT await (per spec).
2026-02-24 08:36:14 -03:00
if generator.is_in_async_function() {
let received_completion = generator.allocate_register();
let received_completion_type = generator.allocate_register();
let received_completion_value = generator.allocate_register();
generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&v,
&received_completion,
&received_completion_type,
&received_completion_value,
)
} else {
v
}
}
2026-02-24 08:36:14 -03:00
None => generator.add_constant_undefined(),
};
2026-02-24 08:36:14 -03:00
generator.generate_return(&val);
None
}
// === Throw ===
StatementKind::Throw(expression) => {
2026-02-24 08:36:14 -03:00
let val = generate_expression(expression, generator, None)?;
generator.perform_needed_unwinds();
generator.emit(Instruction::Throw { src: val.operand() });
None
}
// === Variable declarations ===
StatementKind::VariableDeclaration(data) => {
generate_variable_declaration(generator, data.kind, &data.declarations);
None
}
// === Break ===
StatementKind::Break { target_label } => {
2026-02-24 08:36:14 -03:00
generator.generate_break(target_label.as_deref());
None
}
// === Continue ===
StatementKind::Continue { target_label } => {
2026-02-24 08:36:14 -03:00
generator.generate_continue(target_label.as_deref());
None
}
// === Labelled ===
StatementKind::Labelled(data) => generate_labelled_statement(generator, &data.label, &data.item, preferred_dst),
// === Switch ===
2026-02-24 08:36:14 -03:00
StatementKind::Switch(data) => generate_switch_statement(generator, data, preferred_dst),
// === Try ===
2026-02-24 08:36:14 -03:00
StatementKind::Try(data) => generate_try_statement(generator, data, preferred_dst),
// === FunctionDeclaration ===
StatementKind::FunctionDeclaration(fd) => {
if fd.is_hoisted.get() {
// Annex B.3.3: Copy the function from the lexical (block) scope
// to the var scope.
if let Some(name_ident) = &fd.name {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name_ident.name);
let value = generator.allocate_register();
generator.emit(Instruction::GetBinding {
dst: value.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetVariableBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
None
}
// === With ===
2026-03-22 15:46:11 -03:00
StatementKind::With(data) => {
let obj = generate_expression(&data.object, generator, None)?;
2026-02-24 08:36:14 -03:00
let object_environment = generator.allocate_register();
generator.emit(Instruction::EnterObjectEnvironment {
dst: object_environment.operand(),
object: obj.operand(),
});
generator.lexical_environment_register_stack.push(object_environment);
2026-02-24 08:36:14 -03:00
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-03-22 15:46:11 -03:00
let result = generate_statement(&data.body, generator, preferred_dst);
2026-02-24 08:36:14 -03:00
generator.end_variable_scope();
// Per spec 13.11.7 step 10: if body completion value is empty,
// return NormalCompletion(undefined).
2026-02-24 08:36:14 -03:00
Some(result.unwrap_or_else(|| generator.add_constant_undefined()))
}
// === ForIn / ForOf / ForAwaitOf ===
StatementKind::ForInOf(data) => {
generate_for_in_of_statement(generator, data.kind, &data.lhs, &data.rhs, &data.body, preferred_dst)
}
// === UsingDeclaration ===
StatementKind::UsingDeclaration(_) => {
// Disposal semantics are not yet implemented.
2026-02-24 08:36:14 -03:00
let error = generator.allocate_register();
let msg = generator.intern_string(utf16!("TODO: UsingDeclaration"));
generator.emit(Instruction::NewTypeError {
dst: error.operand(),
error_string: msg,
});
2026-02-24 08:36:14 -03:00
generator.perform_needed_unwinds();
generator.emit(Instruction::Throw { src: error.operand() });
// Switch to a dead block so subsequent codegen doesn't crash.
2026-02-24 08:36:14 -03:00
let dead = generator.make_block();
generator.switch_to_basic_block(dead);
None
}
// === ClassDeclaration ===
StatementKind::ClassDeclaration(data) => {
2026-02-24 08:36:14 -03:00
let value = generate_class_expression(generator, data, None);
// Bind the class name in the outer scope (classes are lexically scoped).
// Use InitializeLexicalBinding since the name starts in the TDZ
// (temporal dead zone) until this point, matching `let` semantics.
// NB: We do NOT mark the local as initialized here, preserving
// TDZ checks for subsequent uses of the class name.
if let Some(name_ident) = &data.name {
if name_ident.is_local() {
let local =
generator.resolve_local(name_ident.local_index.get(), name_ident.local_type.get().unwrap());
generator.emit_mov(&local, &value);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
None
}
// === Import/Export ===
StatementKind::Import(_) => None, // Handled by module loading
StatementKind::Export(export_data) => {
if !export_data.is_default_export {
// Non-default export: generate code for the wrapped statement.
if let Some(ref child_statement) = export_data.statement {
2026-02-24 08:36:14 -03:00
generate_statement(child_statement, generator, None)
} else {
None
}
} else if let Some(ref child_statement) = export_data.statement {
match &child_statement.inner {
StatementKind::FunctionDeclaration(_) | StatementKind::ClassDeclaration(_) => {
2026-02-24 08:36:14 -03:00
generate_statement(child_statement, generator, None)
}
_ => {
// export default <expression>
// The child_statement wraps an Expression via StatementKind::Expression.
let default_name: Utf16String = Utf16String::from(utf16!("default"));
generator.pending_lhs_name = Some(generator.intern_identifier(&default_name));
2026-02-24 08:36:14 -03:00
let value = generate_statement(child_statement, generator, None);
generator.pending_lhs_name = None;
if let Some(value) = value {
2026-02-24 08:36:14 -03:00
let local_name = generator.intern_identifier(utf16!("*default*"));
generator.emit(Instruction::InitializeLexicalBinding {
identifier: local_name,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
Some(value)
} else {
None
}
}
}
} else {
None
}
}
// === ClassFieldInitializer ===
StatementKind::ClassFieldInitializer(data) => {
// Only set pending_lhs_name for compile-time-known keys (non-empty names).
// For computed keys, field_name is empty and the name is set at runtime.
if !data.field_name.is_empty() {
generator.pending_lhs_name = Some(generator.intern_identifier(&data.field_name));
}
let value = generate_expression_or_undefined(&data.expression, generator, None);
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = None;
generator.emit(Instruction::Return { value: value.operand() });
None
}
};
2026-02-24 08:36:14 -03:00
generator.current_source_start = saved_source_start;
generator.current_source_end = saved_source_end;
result
}
// =============================================================================
// Await helper
// =============================================================================
/// Completion::Type values (ABI-compatible).
#[derive(Clone, Copy)]
#[repr(u32)]
enum CompletionType {
Normal = 1,
Return = 4,
Throw = 5,
}
impl CompletionType {
fn to_f64(self) -> f64 {
self as u32 as f64
}
}
/// Environment binding mode.
#[repr(u32)]
enum EnvironmentMode {
Lexical = 0,
Var = 1,
}
/// Arguments object creation mode.
#[repr(u32)]
enum ArgumentsKind {
Mapped = 0,
Unmapped = 1,
}
/// Class element kind (ABI-compatible with ClassBlueprint::Element::Kind).
#[repr(u8)]
enum ClassElementKind {
Method = 0,
Getter = 1,
Setter = 2,
Field = 3,
StaticInitializer = 4,
}
/// Iterator hint (ABI-compatible).
#[repr(u32)]
enum IteratorHint {
Sync = 0,
Async = 1,
}
/// Like generate_await but uses caller-provided completion registers.
///
/// Returns the received_completion_value on the normal path.
/// Emits a Throw on the throw path.
fn generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
argument: &ScopedOperand,
received_completion: &ScopedOperand,
received_completion_type: &ScopedOperand,
received_completion_value: &ScopedOperand,
) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
let continuation = generator.make_block();
generator.emit(Instruction::Await {
continuation_label: continuation,
argument: argument.operand(),
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(continuation);
2026-02-24 08:36:14 -03:00
let acc = generator.accumulator();
generator.emit_mov(received_completion, &acc);
generator.emit(Instruction::GetCompletionFields {
type_dst: received_completion_type.operand(),
value_dst: received_completion_value.operand(),
completion: received_completion.operand(),
});
2026-02-24 08:36:14 -03:00
let normal_block = generator.make_block();
let throw_block = generator.make_block();
let is_normal = generator.allocate_register();
let normal_type = generator.add_constant_number(CompletionType::Normal.to_f64());
generator.emit(Instruction::StrictlyEquals {
dst: is_normal.operand(),
lhs: received_completion_type.operand(),
rhs: normal_type.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_normal, normal_block, throw_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_block);
generator.perform_needed_unwinds();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Throw {
src: received_completion_value.operand(),
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(normal_block);
received_completion_value.clone()
}
/// Yield* (yield from) delegation.
///
/// Implements the iterator delegation protocol from
/// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation
///
/// The delegating generator forwards next/throw/return to the inner iterator.
fn generate_yield_from(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
value: ScopedOperand,
received_completion: &ScopedOperand,
received_completion_type: &ScopedOperand,
received_completion_value: &ScopedOperand,
) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
let is_async = generator.is_in_async_generator_function();
// 4. Let iteratorRecord be ? GetIterator(value, generatorKind).
2026-02-24 08:36:14 -03:00
let iterator = generator.allocate_register();
let next_method = generator.allocate_register();
let iterator_done_property = generator.allocate_register();
let hint = if is_async {
IteratorHint::Async
} else {
IteratorHint::Sync
} as u32;
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::GetIterator {
dst_iterator_object: iterator.operand(),
dst_iterator_next: next_method.operand(),
dst_iterator_done: iterator_done_property.operand(),
iterable: value.operand(),
hint,
});
// 6. Let received be NormalCompletion(undefined).
2026-02-24 08:36:14 -03:00
let normal_const = generator.add_constant_number(CompletionType::Normal.to_f64());
generator.emit_mov(received_completion_type, &normal_const);
let undef = generator.add_constant_undefined();
generator.emit_mov(received_completion_value, &undef);
// 7. Repeat,
2026-02-24 08:36:14 -03:00
let loop_block = generator.make_block();
let continuation_block = generator.make_block();
let loop_end_block = generator.make_block();
generator.emit(Instruction::Jump { target: loop_block });
generator.switch_to_basic_block(loop_block);
// Branch on received.[[Type]].
2026-02-24 08:36:14 -03:00
let type_is_normal_block = generator.make_block();
let is_type_throw_block = generator.make_block();
let is_normal = generator.allocate_register();
generator.emit(Instruction::StrictlyEquals {
dst: is_normal.operand(),
lhs: received_completion_type.operand(),
rhs: normal_const.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_normal, type_is_normal_block, is_type_throw_block);
// =========================================================================
// a. If received.[[Type]] is normal, then
// =========================================================================
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_normal_block);
// i. Let innerResult be ? Call(next, iterator, « received.[[Value]] »).
2026-02-24 08:36:14 -03:00
let inner_result = generator.allocate_register();
generator.emit(Instruction::Call {
dst: inner_result.operand(),
callee: next_method.operand(),
this_value: iterator.operand(),
argument_count: 1,
expression_string: None,
arguments: vec![received_completion_value.operand()],
});
// ii. If generatorKind is async, set innerResult to ? Await(innerResult).
if is_async {
let awaited = generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&inner_result,
received_completion,
received_completion_type,
received_completion_value,
);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&inner_result, &awaited);
}
// iii. If innerResult is not an Object, throw a TypeError exception.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ThrowIfNotObject {
src: inner_result.operand(),
});
// iv. Let done be ? IteratorComplete(innerResult).
2026-02-24 08:36:14 -03:00
let done = generator.allocate_register();
emit_get_by_id(generator, &done, &inner_result, utf16!("done"), None);
// v. If done is true, then return ? IteratorValue(innerResult).
2026-02-24 08:36:14 -03:00
let type_is_normal_done_block = generator.make_block();
let type_is_normal_not_done_block = generator.make_block();
generator.emit_jump_if(&done, type_is_normal_done_block, type_is_normal_not_done_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_normal_done_block);
let return_value = generator.allocate_register();
emit_get_by_id(generator, &return_value, &inner_result, utf16!("value"), None);
generator.emit(Instruction::Jump { target: loop_end_block });
// vi/vii. Yield IteratorValue(innerResult), receive new completion.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_normal_not_done_block);
{
2026-02-24 08:36:14 -03:00
let current_value = generator.allocate_register();
emit_get_by_id(generator, &current_value, &inner_result, utf16!("value"), None);
generate_yield(
2026-02-24 08:36:14 -03:00
generator,
continuation_block,
&current_value,
received_completion,
received_completion_type,
received_completion_value,
false,
);
}
// =========================================================================
// b. Else if received.[[Type]] is throw, then
// =========================================================================
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(is_type_throw_block);
let type_is_throw_block = generator.make_block();
let type_is_return_block = generator.make_block();
let throw_const = generator.add_constant_number(CompletionType::Throw.to_f64());
let is_throw = generator.allocate_register();
generator.emit(Instruction::StrictlyEquals {
dst: is_throw.operand(),
lhs: received_completion_type.operand(),
rhs: throw_const.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_throw, type_is_throw_block, type_is_return_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_throw_block);
// i. Let throw be ? GetMethod(iterator, "throw").
2026-02-24 08:36:14 -03:00
let throw_method = generator.allocate_register();
let throw_key = generator.intern_property_key(utf16!("throw"));
generator.emit(Instruction::GetMethod {
dst: throw_method.operand(),
object: iterator.operand(),
property: throw_key,
});
// ii. If throw is not undefined, then
2026-02-24 08:36:14 -03:00
let throw_method_defined_block = generator.make_block();
let throw_method_undefined_block = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: throw_method.operand(),
true_target: throw_method_undefined_block,
false_target: throw_method_defined_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_method_defined_block);
// 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »).
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: inner_result.operand(),
callee: throw_method.operand(),
this_value: iterator.operand(),
argument_count: 1,
expression_string: None,
arguments: vec![received_completion_value.operand()],
});
// 2. If generatorKind is async, set innerResult to ? Await(innerResult).
if is_async {
let awaited = generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&inner_result,
received_completion,
received_completion_type,
received_completion_value,
);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&inner_result, &awaited);
}
// 4. If innerResult is not an Object, throw a TypeError exception.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ThrowIfNotObject {
src: inner_result.operand(),
});
// 5. Let done be ? IteratorComplete(innerResult).
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &done, &inner_result, utf16!("done"), None);
// 6. If done is true, return ? IteratorValue(innerResult).
2026-02-24 08:36:14 -03:00
let type_is_throw_done_block = generator.make_block();
let type_is_throw_not_done_block = generator.make_block();
generator.emit_jump_if(&done, type_is_throw_done_block, type_is_throw_not_done_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_throw_done_block);
emit_get_by_id(generator, &return_value, &inner_result, utf16!("value"), None);
generator.emit(Instruction::Jump { target: loop_end_block });
// 7/8. Yield IteratorValue(innerResult), receive new completion.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_throw_not_done_block);
{
2026-02-24 08:36:14 -03:00
let yield_value = generator.allocate_register();
emit_get_by_id(generator, &yield_value, &inner_result, utf16!("value"), None);
generate_yield(
2026-02-24 08:36:14 -03:00
generator,
continuation_block,
&yield_value,
received_completion,
received_completion_type,
received_completion_value,
false,
);
}
// throw is undefined: close iterator, throw TypeError.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_method_undefined_block);
if is_async {
// AsyncIteratorClose: get return method, call it, await, check object.
2026-02-24 08:36:14 -03:00
let return_method = generator.allocate_register();
let return_key = generator.intern_property_key(utf16!("return"));
generator.emit(Instruction::GetMethod {
dst: return_method.operand(),
object: iterator.operand(),
property: return_key,
});
2026-02-24 08:36:14 -03:00
let call_return_block = generator.make_block();
let after_close = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: return_method.operand(),
true_target: after_close,
false_target: call_return_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(call_return_block);
2026-02-24 08:36:14 -03:00
let close_result = generator.allocate_register();
generator.emit(Instruction::Call {
dst: close_result.operand(),
callee: return_method.operand(),
this_value: iterator.operand(),
argument_count: 0,
expression_string: None,
arguments: vec![],
});
let awaited = generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&close_result,
received_completion,
received_completion_type,
received_completion_value,
);
generator.emit(Instruction::ThrowIfNotObject { src: awaited.operand() });
generator.emit(Instruction::Jump { target: after_close });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(after_close);
} else {
// Sync: IteratorClose with Normal completion.
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
generator.emit(Instruction::IteratorClose {
iterator_object: iterator.operand(),
iterator_next: next_method.operand(),
iterator_done: done.operand(),
completion_type: CompletionType::Normal as u32,
completion_value: undef.operand(),
});
}
// Throw a TypeError: iterator does not have a throw method.
2026-02-24 08:36:14 -03:00
let exception = generator.allocate_register();
let error_string = generator.intern_string(utf16!("yield* protocol violation: iterator must have a throw method"));
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewTypeError {
dst: exception.operand(),
error_string,
});
generator.perform_needed_unwinds();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Throw {
src: exception.operand(),
});
// =========================================================================
// c. Else (received.[[Type]] is return)
// =========================================================================
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_return_block);
// ii. Let return be ? GetMethod(iterator, "return").
2026-02-24 08:36:14 -03:00
let return_method = generator.allocate_register();
let return_key = generator.intern_property_key(utf16!("return"));
generator.emit(Instruction::GetMethod {
dst: return_method.operand(),
object: iterator.operand(),
property: return_key,
});
// iii. If return is undefined, then return received.[[Value]].
2026-02-24 08:36:14 -03:00
let return_is_undefined_block = generator.make_block();
let return_is_defined_block = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: return_method.operand(),
true_target: return_is_undefined_block,
false_target: return_is_defined_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_is_undefined_block);
// 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
if is_async {
generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
received_completion_value,
received_completion,
received_completion_type,
received_completion_value,
);
}
// 2. Return received (return completion).
2026-02-24 08:36:14 -03:00
generator.generate_return(received_completion_value);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_is_defined_block);
// iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »).
2026-02-24 08:36:14 -03:00
let inner_return_result = generator.allocate_register();
generator.emit(Instruction::Call {
dst: inner_return_result.operand(),
callee: return_method.operand(),
this_value: iterator.operand(),
argument_count: 1,
expression_string: None,
arguments: vec![received_completion_value.operand()],
});
// v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
if is_async {
let awaited = generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&inner_return_result,
received_completion,
received_completion_type,
received_completion_value,
);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&inner_return_result, &awaited);
}
// vi. If innerReturnResult is not an Object, throw a TypeError exception.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ThrowIfNotObject {
src: inner_return_result.operand(),
});
// vii. Let done be ? IteratorComplete(innerReturnResult).
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &done, &inner_return_result, utf16!("done"), None);
// viii. If done is true, return IteratorValue(innerReturnResult).
2026-02-24 08:36:14 -03:00
let type_is_return_done_block = generator.make_block();
let type_is_return_not_done_block = generator.make_block();
generator.emit_jump_if(&done, type_is_return_done_block, type_is_return_not_done_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_return_done_block);
let inner_return_result_value = generator.allocate_register();
emit_get_by_id(
2026-02-24 08:36:14 -03:00
generator,
&inner_return_result_value,
&inner_return_result,
utf16!("value"),
None,
);
2026-02-24 08:36:14 -03:00
generator.generate_return(&inner_return_result_value);
// ix/x. Yield IteratorValue(innerReturnResult), receive new completion.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(type_is_return_not_done_block);
let received = generator.allocate_register();
emit_get_by_id(generator, &received, &inner_return_result, utf16!("value"), None);
generate_yield(
2026-02-24 08:36:14 -03:00
generator,
continuation_block,
&received,
received_completion,
received_completion_type,
received_completion_value,
false,
);
// =========================================================================
// Continuation block: resume after any yield, extract completion, loop back.
// =========================================================================
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(continuation_block);
let acc = generator.accumulator();
generator.emit_mov(received_completion, &acc);
generator.emit(Instruction::GetCompletionFields {
type_dst: received_completion_type.operand(),
value_dst: received_completion_value.operand(),
completion: received_completion.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: loop_block });
// =========================================================================
// Loop end: return the accumulated return_value.
// =========================================================================
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(loop_end_block);
return_value
}
/// Unified yield function.
///
/// For non-async generators: just emits a Yield instruction.
/// For async generators: optionally awaits the argument first, then yields,
/// then handles AsyncGeneratorUnwrapYieldResumption (check return type,
/// await return value, re-classify).
/// Jumps to continuation_label for the "not return" and "throw after await" paths.
fn generate_yield(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
continuation_label: Label,
argument: &ScopedOperand,
received_completion: &ScopedOperand,
received_completion_type: &ScopedOperand,
received_completion_value: &ScopedOperand,
await_before_yield: bool,
) {
2026-02-24 08:36:14 -03:00
if !generator.is_in_async_generator_function() {
generator.emit(Instruction::Yield {
continuation_label: Some(continuation_label),
value: argument.operand(),
});
return;
}
let argument = if await_before_yield {
generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
argument,
received_completion,
received_completion_type,
received_completion_value,
)
} else {
argument.clone()
};
// Yield, then UnwrapYieldResumption.
2026-02-24 08:36:14 -03:00
let unwrap_block = generator.make_block();
generator.emit(Instruction::Yield {
continuation_label: Some(unwrap_block),
value: argument.operand(),
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(unwrap_block);
2026-02-24 08:36:14 -03:00
let acc = generator.accumulator();
generator.emit_mov(received_completion, &acc);
generator.emit(Instruction::GetCompletionFields {
type_dst: received_completion_type.operand(),
value_dst: received_completion_value.operand(),
completion: received_completion.operand(),
});
// If resumptionValue.[[Type]] is not return, jump to continuation.
2026-02-24 08:36:14 -03:00
let return_block = generator.make_block();
let is_not_return = generator.allocate_register();
let return_type = generator.add_constant_number(CompletionType::Return.to_f64());
generator.emit(Instruction::StrictlyInequals {
dst: is_not_return.operand(),
lhs: received_completion_type.operand(),
rhs: return_type.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_not_return, continuation_label, return_block);
// Return path: Await(resumptionValue.[[Value]]).
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_block);
generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
received_completion_value,
received_completion,
received_completion_type,
received_completion_value,
);
// If awaited.[[Type]] is throw, jump to continuation.
2026-02-24 08:36:14 -03:00
let awaited_normal_block = generator.make_block();
let is_throw = generator.allocate_register();
let throw_type = generator.add_constant_number(CompletionType::Throw.to_f64());
generator.emit(Instruction::StrictlyEquals {
dst: is_throw.operand(),
lhs: received_completion_type.operand(),
rhs: throw_type.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_throw, continuation_label, awaited_normal_block);
// awaited.[[Type]] is normal: set type to Return and jump to continuation.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(awaited_normal_block);
generator.emit(Instruction::SetCompletionType {
completion: received_completion.operand(),
completion_type: CompletionType::Return as u32,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: continuation_label,
});
}
// =============================================================================
// Identifier codegen
// =============================================================================
/// Generate bytecode for an identifier reference.
///
/// Scope analysis determines how the identifier is resolved:
/// - **Local**: direct register/local access (with TDZ check for let/const)
/// - **Global**: GetGlobal instruction (with inline cache)
/// - **Environment**: GetBinding/GetInitializedBinding (with environment coordinate cache)
fn generate_identifier(
ident: &Identifier,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
if ident.is_local() {
let local_index = ident.local_index.get();
2026-02-24 08:36:14 -03:00
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
// Check TDZ for uninitialized bindings.
// Arguments may need TDZ during default parameter evaluation;
// for variable-type locals, only lexically-declared (let/const) need TDZ.
let needs_tdz_check = if ident.local_type.get() == Some(LocalType::Argument) {
2026-02-24 08:36:14 -03:00
!generator.is_argument_initialized(local_index)
} else {
generator.is_local_lexically_declared(local_index) && !generator.is_local_initialized(local_index)
};
if needs_tdz_check {
if ident.local_type.get() == Some(LocalType::Argument) {
// Arguments are initialized to undefined by default, so we
// need to replace the value with the empty sentinel to
// trigger the TDZ check.
2026-02-24 08:36:14 -03:00
let empty = generator.add_constant_empty();
generator.emit_mov(&local, &empty);
}
generator.emit(Instruction::ThrowIfTDZ { src: local.operand() });
}
return local;
}
// OPTIMIZATION: Generate builtin constants (undefined, NaN, Infinity) directly.
2026-02-24 08:36:14 -03:00
if ident.is_global.get()
&& let Some(constant) = maybe_generate_builtin_constant(generator, &ident.name)
{
return constant;
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
if ident.is_global.get() {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
let cache = generator.next_global_variable_cache();
generator.emit(Instruction::GetGlobal {
dst: dst.operand(),
identifier: id,
cache: cache as u64,
});
} else if ident.declaration_kind.get() == Some(DeclarationKind::Var) {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::GetInitializedBinding {
dst: dst.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::GetBinding {
dst: dst.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
}
dst
}
fn maybe_generate_builtin_constant(generator: &mut Generator, name: &[u16]) -> Option<ScopedOperand> {
if name == utf16!("undefined") {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_undefined());
}
if name == utf16!("NaN") {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_number(f64::NAN));
}
if name == utf16!("Infinity") {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_number(f64::INFINITY));
}
2026-02-24 08:36:14 -03:00
if let Some(op) = try_generate_builtin_constant(generator, &name.into()) {
return Some(op);
}
None
}
// =============================================================================
// Binary operator emission
// =============================================================================
fn emit_binary_op(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: BinaryOp,
dst: &ScopedOperand,
lhs: &ScopedOperand,
rhs: &ScopedOperand,
) {
let dst_op = dst.operand();
let lhs_op = lhs.operand();
let rhs_op = rhs.operand();
match op {
2026-02-24 08:36:14 -03:00
BinaryOp::Addition => generator.emit(Instruction::Add {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::Subtraction => generator.emit(Instruction::Sub {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::Multiplication => generator.emit(Instruction::Mul {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::Division => generator.emit(Instruction::Div {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::Modulo => generator.emit(Instruction::Mod {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::Exponentiation => generator.emit(Instruction::Exp {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::StrictlyEquals => generator.emit(Instruction::StrictlyEquals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::StrictlyInequals => generator.emit(Instruction::StrictlyInequals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::LooselyEquals => generator.emit(Instruction::LooselyEquals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::LooselyInequals => generator.emit(Instruction::LooselyInequals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::GreaterThan => generator.emit(Instruction::GreaterThan {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::GreaterThanEquals => generator.emit(Instruction::GreaterThanEquals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::LessThan => generator.emit(Instruction::LessThan {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::LessThanEquals => generator.emit(Instruction::LessThanEquals {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::BitwiseAnd => generator.emit(Instruction::BitwiseAnd {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
BinaryOp::BitwiseOr => {
// OPTIMIZATION: x | 0 == ToInt32(x)
2026-02-24 08:36:14 -03:00
if let Some(ConstantValue::Number(n)) = generator.get_constant(rhs) {
if *n == 0.0 && n.is_sign_positive() {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ToInt32 {
dst: dst_op,
value: lhs_op,
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::BitwiseOr {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
});
}
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::BitwiseOr {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
});
}
}
2026-02-24 08:36:14 -03:00
BinaryOp::BitwiseXor => generator.emit(Instruction::BitwiseXor {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::LeftShift => generator.emit(Instruction::LeftShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
BinaryOp::RightShift => {
// OPTIMIZATION: x >> 0 == ToInt32(x) (matches C++)
if let Some(ConstantValue::Number(n)) = generator.get_constant(rhs) {
if *n == 0.0 && n.is_sign_positive() {
generator.emit(Instruction::ToInt32 {
dst: dst_op,
value: lhs_op,
});
} else {
generator.emit(Instruction::RightShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
});
}
} else {
generator.emit(Instruction::RightShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
});
}
}
2026-02-24 08:36:14 -03:00
BinaryOp::UnsignedRightShift => generator.emit(Instruction::UnsignedRightShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::In => generator.emit(Instruction::In {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
BinaryOp::InstanceOf => generator.emit(Instruction::InstanceOf {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
}
}
// =============================================================================
// Logical expression (short-circuit)
// =============================================================================
fn generate_logical(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: LogicalOp,
lhs: &Expression,
rhs: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let lhs_val = generate_expression(lhs, generator, preferred_dst)?;
// Constant-fold: if LHS is a constant, we can statically determine the branch.
2026-02-24 08:36:14 -03:00
if let Some(constant) = generator.get_constant(&lhs_val) {
let is_nullish = matches!(constant, ConstantValue::Null | ConstantValue::Undefined);
if let Some(is_truthy) = constant_to_boolean(constant) {
let take_rhs = match op {
LogicalOp::And => is_truthy,
LogicalOp::Or => !is_truthy,
LogicalOp::NullishCoalescing => is_nullish,
};
if take_rhs {
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
let rhs_val = generate_expression(rhs, generator, Some(&dst))?;
if rhs_val.operand().is_constant() {
return Some(rhs_val);
}
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, &rhs_val);
return Some(dst);
}
return Some(lhs_val);
}
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
generator.emit_mov(&dst, &lhs_val);
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let end_block = generator.make_block();
match op {
LogicalOp::And => {
// If lhs is falsy, short-circuit to end
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&lhs_val, rhs_block, end_block);
}
LogicalOp::Or => {
// If lhs is truthy, short-circuit to end
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&lhs_val, end_block, rhs_block);
}
LogicalOp::NullishCoalescing => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::JumpNullish {
condition: lhs_val.operand(),
true_target: rhs_block,
false_target: end_block,
});
}
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(rhs_block);
let rhs_val = generate_expression(rhs, generator, Some(&dst));
if let Some(rhs_val) = &rhs_val {
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, rhs_val);
}
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: end_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
Some(dst)
}
// =============================================================================
// Conditional expression (ternary)
// =============================================================================
fn generate_conditional(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
test: &Expression,
consequent: &Expression,
alternate: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let predicate = generate_expression(test, generator, None)?;
// OPTIMIZATION: if the predicate is always true/false, only generate the taken expression.
2026-02-24 08:36:14 -03:00
if let Some(constant) = generator.get_constant(&predicate)
&& let Some(is_truthy) = constant_to_boolean(constant)
{
if is_truthy {
return generate_expression(consequent, generator, preferred_dst);
}
2026-02-24 08:36:14 -03:00
return generate_expression(alternate, generator, preferred_dst);
}
2026-02-24 08:36:14 -03:00
let true_block = generator.make_block();
let false_block = generator.make_block();
let end_block = generator.make_block();
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&predicate, true_block, false_block);
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(true_block);
let cons_val = generate_expression(consequent, generator, None);
if let Some(val) = &cons_val {
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, val);
}
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: end_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(false_block);
let alt_val = generate_expression(alternate, generator, None);
if let Some(val) = &alt_val {
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, val);
}
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: end_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
Some(dst)
}
/// Generate a statement while propagating the completion register.
///
/// Saves and restores `gen.current_completion_register`, and emits a mov
/// from the statement's result to the completion register when appropriate.
fn generate_with_completion(
body: &Statement,
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
completion: Option<&ScopedOperand>,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let saved = generator.current_completion_register.clone();
if let Some(c) = completion {
generator.current_completion_register = Some(c.clone());
}
2026-02-24 08:36:14 -03:00
let result = generate_statement(body, generator, preferred_dst);
if !generator.is_current_block_terminated()
&& let (Some(c), Some(val)) = (completion, &result)
{
generator.emit_mov(c, val);
}
2026-02-24 08:36:14 -03:00
generator.current_completion_register = saved;
result
}
// =============================================================================
// If statement
// =============================================================================
fn generate_if_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
test: &Expression,
consequent: &Statement,
alternate: Option<&Statement>,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let pred = generate_expression_or_undefined(test, generator, None);
2026-02-24 08:36:14 -03:00
let completion = if generator.must_propagate_completion {
let reg = choose_dst(generator, preferred_dst);
let undef = generator.add_constant_undefined();
generator.emit_mov(&reg, &undef);
Some(reg)
} else {
None
};
// OPTIMIZATION: if the predicate is always true/false, only build the taken branch.
2026-02-24 08:36:14 -03:00
if let Some(constant) = generator.get_constant(&pred)
&& let Some(is_truthy) = constant_to_boolean(constant)
{
// Pass the completion register as preferred_dst so nested
// if-statements reuse the same register.
2026-02-24 08:36:14 -03:00
let child_dst = completion.as_ref().or(preferred_dst);
if is_truthy {
generate_with_completion(consequent, generator, completion.as_ref(), child_dst);
2026-02-24 08:36:14 -03:00
} else if let Some(alt) = alternate {
generate_with_completion(alt, generator, completion.as_ref(), child_dst);
}
2026-02-24 08:36:14 -03:00
return completion;
}
2026-02-24 08:36:14 -03:00
let true_block = generator.make_block();
let false_block = generator.make_block();
let has_alternate = alternate.is_some();
let end_block = if has_alternate {
2026-02-24 08:36:14 -03:00
generator.make_block()
} else {
false_block
};
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&pred, true_block, false_block);
// Pass completion as preferred_dst to children so nested if-else chains
// reuse the same completion register.
let child_preferred_dst = completion.as_ref();
// Consequent
2026-02-24 08:36:14 -03:00
let saved_completion = generator.current_completion_register.clone();
{
generator.switch_to_basic_block(true_block);
if let Some(ref c) = completion {
generator.current_completion_register = Some(c.clone());
}
let cons_result = generate_statement(consequent, generator, child_preferred_dst);
if !generator.is_current_block_terminated() {
if let (Some(c), Some(val)) = (&completion, &cons_result) {
generator.emit_mov(c, val);
}
generator.emit(Instruction::Jump { target: end_block });
}
}
2026-02-24 08:36:14 -03:00
generator.current_completion_register = saved_completion.clone();
// Alternate
if let Some(alt) = alternate {
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(false_block);
if let Some(ref c) = completion {
2026-02-24 08:36:14 -03:00
generator.current_completion_register = Some(c.clone());
}
2026-02-24 08:36:14 -03:00
let alt_result = generate_statement(alt, generator, child_preferred_dst);
if !generator.is_current_block_terminated() {
if let (Some(c), Some(val)) = (&completion, &alt_result) {
generator.emit_mov(c, val);
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: end_block });
}
2026-02-24 08:36:14 -03:00
generator.current_completion_register = saved_completion;
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
completion
}
// =============================================================================
// While statement
// =============================================================================
fn generate_while_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
test: &Expression,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let test_block = generator.make_block();
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: test_block });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(test_block);
let test_val = generate_expression_or_undefined(test, generator, None);
// OPTIMIZATION: If predicate is always false, ignore body and exit early.
2026-02-24 08:36:14 -03:00
if let Some(constant) = generator.get_constant(&test_val)
&& constant_to_boolean(constant) == Some(false)
{
return completion;
}
2026-02-24 08:36:14 -03:00
let body_block = generator.make_block();
let end_block = generator.make_block();
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&test_val, body_block, end_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(body_block);
let labels = std::mem::take(&mut generator.pending_labels);
generator.begin_continuable_scope(test_block, labels.clone(), completion.clone());
generator.begin_breakable_scope(end_block, labels, completion.clone());
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
2026-02-24 08:36:14 -03:00
generator.end_breakable_scope();
generator.end_continuable_scope();
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: test_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
completion
}
// =============================================================================
// DoWhile statement
// =============================================================================
fn generate_do_while_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
test: &Expression,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let body_block = generator.make_block();
let test_block = generator.make_block();
let load_result_and_jump_to_end_block = generator.make_block();
let end_block = generator.make_block();
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: body_block });
// Generate test FIRST, keeping the test ScopedOperand alive during
// body generation, consuming a register from the free pool.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(test_block);
let test_val = generate_expression_or_undefined(test, generator, None);
generator.emit_jump_if(&test_val, body_block, load_result_and_jump_to_end_block);
// Generate body SECOND (test_val still alive).
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(body_block);
let labels = std::mem::take(&mut generator.pending_labels);
generator.begin_continuable_scope(test_block, labels.clone(), completion.clone());
generator.begin_breakable_scope(end_block, labels, completion.clone());
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
2026-02-24 08:36:14 -03:00
generator.end_breakable_scope();
generator.end_continuable_scope();
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: test_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(load_result_and_jump_to_end_block);
generator.emit(Instruction::Jump { target: end_block });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
completion
}
// =============================================================================
// For statement
// =============================================================================
fn generate_for_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
init: Option<&ForInit>,
test: Option<&Expression>,
update: Option<&Expression>,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// Check if init is a lexical declaration (let/const) with non-local variables.
// If so, we need to create a lexical environment for the loop variables and
// implement per-iteration copy semantics (CreatePerIterationEnvironment).
let mut has_lexical_environment = false;
let mut per_iteration_binding_names: Vec<Utf16String> = Vec::new();
2026-02-24 08:36:14 -03:00
if let Some(ForInit::Declaration(init)) = init
&& let StatementKind::VariableDeclaration(vd) = &init.inner
&& (vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const)
2026-02-24 08:36:14 -03:00
{
let mut non_local_names: Vec<(Utf16String, bool)> = Vec::new();
for declaration in &vd.declarations {
2026-02-24 08:36:14 -03:00
collect_target_names(&declaration.target, &mut non_local_names);
}
if !non_local_names.is_empty() {
has_lexical_environment = true;
let is_const = vd.kind == DeclarationKind::Const;
// begin_variable_scope: CreateLexicalEnvironment + boundary
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
2026-02-24 08:36:14 -03:00
for (name, _) in &non_local_names {
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: is_const,
is_global: false,
is_strict: false,
});
if !is_const {
per_iteration_binding_names.push(name.clone());
}
}
}
}
// Init
match init {
Some(ForInit::Declaration(decl)) => {
2026-02-24 08:36:14 -03:00
generate_statement(decl, generator, None);
}
Some(ForInit::Expression(expr)) => {
2026-02-24 08:36:14 -03:00
generate_expression(expr, generator, None);
}
None => {}
}
// CreatePerIterationEnvironment after init (first iteration setup).
2026-02-24 08:36:14 -03:00
emit_per_iteration_bindings(generator, &per_iteration_binding_names);
// Block creation order: body → update (if exists) → test (if exists) → end.
// If 'test' is missing, fuse 'test' and 'body' blocks.
// If 'update' is missing, fuse 'body' and 'update' blocks.
2026-02-24 08:36:14 -03:00
let body_block = generator.make_block();
let update_block = if update.is_some() {
2026-02-24 08:36:14 -03:00
generator.make_block()
} else {
body_block
};
let test_block = if test.is_some() {
2026-02-24 08:36:14 -03:00
generator.make_block()
} else {
body_block
};
2026-02-24 08:36:14 -03:00
let end_block = generator.make_block();
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: test_block });
// Test
if let Some(test_expression) = test {
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(test_block);
let test_val = generate_expression_or_undefined(test_expression, generator, None);
// OPTIMIZATION: test value is always falsey, skip body entirely.
2026-02-24 08:36:14 -03:00
if let Some(constant) = generator.get_constant(&test_val)
&& constant_to_boolean(constant) == Some(false)
{
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
if has_lexical_environment {
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
if !generator.is_current_block_terminated() {
let parent = generator.current_lexical_environment();
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
}
}
2026-02-24 08:36:14 -03:00
return completion;
}
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&test_val, body_block, end_block);
}
// Update
if let Some(update_expression) = update {
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(update_block);
generate_expression(update_expression, generator, None);
generator.emit(Instruction::Jump { target: test_block });
}
// Body
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(body_block);
let labels = std::mem::take(&mut generator.pending_labels);
let continue_target = if update.is_some() { update_block } else { test_block };
2026-02-24 08:36:14 -03:00
generator.begin_continuable_scope(continue_target, labels.clone(), completion.clone());
generator.begin_breakable_scope(end_block, labels, completion.clone());
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
2026-02-24 08:36:14 -03:00
generator.end_breakable_scope();
generator.end_continuable_scope();
if !generator.is_current_block_terminated() {
// CreatePerIterationEnvironment at end of each iteration.
2026-02-24 08:36:14 -03:00
emit_per_iteration_bindings(generator, &per_iteration_binding_names);
if update.is_some() {
generator.emit(Instruction::Jump { target: update_block });
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: test_block });
}
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
// end_variable_scope: restore parent environment
if has_lexical_environment {
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
if !generator.is_current_block_terminated() {
let parent = generator.current_lexical_environment();
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
}
}
completion
}
/// Emit CreatePerIterationEnvironment: save current binding values, pop env,
/// push new env, re-create variables, and re-initialize from saved values.
/// This implements per-iteration lexical scoping for `for (let ...)` loops.
2026-02-24 08:36:14 -03:00
fn emit_per_iteration_bindings(generator: &mut Generator, bindings: &[Utf16String]) {
if bindings.is_empty() {
return;
}
// Save current values into registers.
let mut saved: Vec<(ScopedOperand, IdentifierTableIndex)> = Vec::with_capacity(bindings.len());
for name in bindings {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(name);
let reg = generator.allocate_register();
generator.emit(Instruction::GetBinding {
dst: reg.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
saved.push((reg, id));
}
// Pop current environment (end_variable_scope).
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
let parent = generator.current_lexical_environment();
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
// Push new environment (begin_variable_scope).
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
// Re-create variables and initialize from saved values.
for (reg, id) in &saved {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CreateVariable {
identifier: *id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: *id,
src: reg.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
// =============================================================================
// Scope children (Block, FunctionBody, Program)
// =============================================================================
fn generate_scope_children(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
scope: &ScopeData,
_preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
let mut last_result = None;
for child in &scope.children {
2026-02-24 08:36:14 -03:00
let result = generate_statement(child, generator, None);
if generator.must_propagate_completion
&& let Some(ref val) = result
{
last_result = result.clone();
if !generator.is_current_block_terminated()
&& let Some(ref completion_reg) = generator.current_completion_register.clone()
{
generator.emit_mov(completion_reg, val);
}
}
// NB: When must_propagate_completion is false, we intentionally do NOT
// accumulate results into last_result. `result` goes out of scope at
// the end of each loop iteration, freeing any temporary registers
// immediately.
2026-02-24 08:36:14 -03:00
if generator.is_current_block_terminated() {
break;
}
}
last_result
}
/// Generate bytecode for a block statement, creating a lexical environment
/// if the block has non-local lexical declarations (let/const/class).
fn generate_block_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
scope: &ScopeData,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let did_create_env = emit_block_declaration_instantiation(generator, scope);
if did_create_env {
2026-02-24 08:36:14 -03:00
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
}
// The parser wraps for-loop statements in a Block for scope tracking
// (via close_for_loop_scope). When the block doesn't create a lexical
// environment and its only child is a for-loop variant, skip
// generate_scope_children and generate the child directly to avoid
// emitting a redundant completion Mov.
let result = if !did_create_env && scope.children.len() == 1 && is_for_loop(&scope.children[0]) {
2026-02-24 08:36:14 -03:00
generate_statement(&scope.children[0], generator, preferred_dst)
} else {
2026-02-24 08:36:14 -03:00
generate_scope_children(generator, scope, preferred_dst)
};
if did_create_env {
2026-02-24 08:36:14 -03:00
generator.end_variable_scope();
}
result
}
/// Create lexical bindings and instantiate function declarations for a block.
/// For each declaration, creates bindings and immediately instantiates functions
/// (single pass, not two separate passes).
fn emit_lexical_declarations_for_block<'a>(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
environment: &ScopedOperand,
children: impl Iterator<Item = &'a Statement>,
) {
for child in children {
match &child.inner {
StatementKind::VariableDeclaration(vd)
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const =>
{
let is_constant = vd.kind == DeclarationKind::Const;
for declaration in &vd.declarations {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
for (name, _) in &names {
let id = generator.intern_identifier(name);
if is_constant {
generator.emit(Instruction::CreateImmutableBinding {
environment: environment.operand(),
identifier: id,
strict_binding: true,
});
} else {
generator.emit(Instruction::CreateMutableBinding {
environment: environment.operand(),
identifier: id,
can_be_deleted: false,
});
}
}
}
}
StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
for (name, _) in &names {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateImmutableBinding {
environment: environment.operand(),
identifier: id,
strict_binding: true,
});
}
}
}
StatementKind::ClassDeclaration(class_data) => {
2026-02-24 08:36:14 -03:00
if let Some(ref name_ident) = class_data.name
&& !name_ident.is_local()
{
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::CreateMutableBinding {
environment: environment.operand(),
identifier: id,
can_be_deleted: false,
});
}
}
StatementKind::FunctionDeclaration(fd) if fd.name.is_some() => {
let name_ident = fd.name.as_ref().unwrap();
// a. Create binding.
if !name_ident.is_local() {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::CreateMutableBinding {
environment: environment.operand(),
identifier: id,
can_be_deleted: false,
});
}
// b. Instantiate function object.
let function_data = generator.function_table.take(fd.function_id);
2026-02-24 08:36:14 -03:00
let sfd_index = emit_new_function(generator, function_data, None);
let fo = generator.allocate_register();
generator.emit(Instruction::NewFunction {
dst: fo.operand(),
shared_function_data_index: sfd_index,
home_object: None,
lhs_name: None,
});
if name_ident.is_local() {
let local_index = name_ident.local_index.get();
2026-02-24 08:36:14 -03:00
let local = generator.local(local_index);
generator.emit_mov(&local, &fo);
generator.mark_local_initialized(local_index);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: fo.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
_ => {}
}
}
}
2026-02-24 08:36:14 -03:00
fn emit_block_declaration_instantiation(generator: &mut Generator, scope: &ScopeData) -> bool {
if !needs_block_declaration_instantiation(scope) {
return false;
}
2026-02-24 08:36:14 -03:00
let new_env = generator.push_new_lexical_environment(0);
2026-02-24 08:36:14 -03:00
emit_lexical_declarations_for_block(generator, &new_env, scope.children.iter());
true
}
// =============================================================================
// Variable declaration
// =============================================================================
fn generate_variable_declaration(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
kind: DeclarationKind,
declarations: &[VariableDeclarator],
) {
for declaration in declarations {
// OPTIMIZATION: For let/const declarations where the target is a local identifier,
// pass the local as preferred_dst to the initializer. This allows NewArray, NewFunction,
// Add, etc. to write directly to the local instead of temp+Mov.
// NB: Not safe for `var` since var declarations can have duplicates, meaning the
// preferred_dst could be used as input in the initializer.
let init_dst = if kind != DeclarationKind::Var {
if let VariableDeclaratorTarget::Identifier(ident) = &declaration.target {
if ident.is_local() && ident.local_type.get() == Some(LocalType::Variable) {
2026-02-24 08:36:14 -03:00
Some(generator.local(ident.local_index.get()))
} else {
None
}
} else {
None
}
} else {
None
};
// Set pending LHS name for function name inference.
if let VariableDeclaratorTarget::Identifier(ident) = &declaration.target {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
}
let init_value = declaration
.init
.as_ref()
2026-02-24 08:36:14 -03:00
.and_then(|init| generate_expression(init, generator, init_dst.as_ref()));
generator.pending_lhs_name = None;
match &declaration.target {
VariableDeclaratorTarget::Identifier(ident) => {
// var declarations without initializer don't need to assign undefined.
// The FDI already handles initialization for var bindings.
if init_value.is_none() && kind == DeclarationKind::Var {
if ident.is_local() {
2026-02-24 08:36:14 -03:00
generator.mark_local_initialized(ident.local_index.get());
}
continue;
}
2026-02-24 08:36:14 -03:00
let value = init_value.unwrap_or_else(|| generator.add_constant_undefined());
if ident.is_local() {
let local_index = ident.local_index.get();
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
2026-02-24 08:36:14 -03:00
generator.emit_mov(&local, &value);
generator.mark_local_initialized(local_index);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
match kind {
DeclarationKind::Var => {
if ident.is_global.get() {
2026-02-24 08:36:14 -03:00
let cache = generator.next_global_variable_cache();
generator.emit(Instruction::SetGlobal {
identifier: id,
src: value.operand(),
cache: cache as u64,
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
DeclarationKind::Let | DeclarationKind::Const => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
}
VariableDeclaratorTarget::BindingPattern(pattern) => {
if let Some(value) = init_value {
let mode = match kind {
DeclarationKind::Var => BindingMode::Set,
DeclarationKind::Let | DeclarationKind::Const => BindingMode::InitializeLexical,
};
2026-02-24 08:36:14 -03:00
generate_binding_pattern_bytecode(generator, pattern, mode, &value);
}
}
}
}
}
// =============================================================================
// Call expression
// =============================================================================
fn try_generate_builtin_abstract_operation(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &CallExpressionData,
preferred_dst: Option<&ScopedOperand>,
) -> Option<Option<ScopedOperand>> {
2026-02-24 08:36:14 -03:00
if !generator.builtin_abstract_operations_enabled {
return None;
}
let name = match &data.callee.inner {
ExpressionKind::Identifier(ident) => &ident.name,
_ => return None,
};
if data.arguments.iter().any(|a| a.is_spread) {
return None;
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
// Operations that map to dedicated bytecode instructions.
if name == utf16!("IsCallable") {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::IsCallable {
dst: dst.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("IsConstructor") {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::IsConstructor {
dst: dst.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("ToBoolean") {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::ToBoolean {
dst: dst.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("ToObject") {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::ToObject {
dst: dst.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("ToLength") {
2026-02-24 08:36:14 -03:00
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::ToLength {
dst: dst.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("ThrowIfNotObject") {
2026-02-24 08:36:14 -03:00
let src = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::ThrowIfNotObject { src: src.operand() });
return Some(Some(dst));
}
if name == utf16!("ThrowTypeError") {
if let ExpressionKind::StringLiteral(ref s) = data.arguments[0].value.inner {
2026-02-24 08:36:14 -03:00
let message_string = generator.intern_string(s);
let type_error_register = generator.allocate_register();
generator.emit(Instruction::NewTypeError {
dst: type_error_register.operand(),
error_string: message_string,
});
2026-02-24 08:36:14 -03:00
generator.perform_needed_unwinds();
generator.emit(Instruction::Throw {
src: type_error_register.operand(),
});
return Some(Some(dst));
}
return None;
}
if name == utf16!("NewTypeError") {
if let ExpressionKind::StringLiteral(ref s) = data.arguments[0].value.inner {
2026-02-24 08:36:14 -03:00
let message_string = generator.intern_string(s);
generator.emit(Instruction::NewTypeError {
dst: dst.operand(),
error_string: message_string,
});
return Some(Some(dst));
}
return None;
}
if name == utf16!("NewObjectWithNoPrototype") {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewObjectWithNoPrototype { dst: dst.operand() });
return Some(Some(dst));
}
if name == utf16!("NewArrayWithLength") {
2026-02-24 08:36:14 -03:00
let length = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
generator.emit(Instruction::NewArrayWithLength {
dst: dst.operand(),
array_length: length.operand(),
});
return Some(Some(dst));
}
if name == utf16!("CreateAsyncFromSyncIterator") {
2026-02-24 08:36:14 -03:00
let iterator = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
let next_method = generate_expression_or_undefined(&data.arguments[1].value, generator, None);
2026-02-24 08:36:14 -03:00
let done = generate_expression_or_undefined(&data.arguments[2].value, generator, None);
generator.emit(Instruction::CreateAsyncFromSyncIterator {
dst: dst.operand(),
iterator: iterator.operand(),
next_method: next_method.operand(),
done: done.operand(),
});
return Some(Some(dst));
}
if name == utf16!("CreateDataPropertyOrThrow") {
2026-02-24 08:36:14 -03:00
let object = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
let property = generate_expression_or_undefined(&data.arguments[1].value, generator, None);
let value = generate_expression_or_undefined(&data.arguments[2].value, generator, None);
generator.emit(Instruction::CreateDataPropertyOrThrow {
object: object.operand(),
property: property.operand(),
value: value.operand(),
});
return Some(Some(dst));
}
if name == utf16!("Call") {
2026-02-24 08:36:14 -03:00
let callee = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
let this_value = generate_expression_or_undefined(&data.arguments[1].value, generator, None);
let extra_args = &data.arguments[2..];
let mut argument_holders = Vec::with_capacity(extra_args.len());
for argument in extra_args {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&argument.value, generator, None);
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
}
let callee_name =
expression_string_approximation(&data.arguments[0].value).map(|s| generator.intern_string(&s));
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string: callee_name,
arguments,
});
return Some(Some(dst));
}
// Operations that map to intrinsic function calls.
let known_operations: &[&[u16]] = &[
utf16!("AsyncIteratorClose"),
utf16!("GetMethod"),
utf16!("GetIteratorDirect"),
utf16!("GetIteratorFromMethod"),
utf16!("IteratorComplete"),
];
for &op_name in known_operations {
if *name == op_name {
let intrinsic_value = unsafe {
super::ffi::get_abstract_operation_function(generator.vm_ptr, op_name.as_ptr(), op_name.len())
};
2026-02-24 08:36:14 -03:00
let callee = generator.add_constant_raw_value(intrinsic_value);
let undefined = generator.add_constant_undefined();
let expression_string = generator.intern_string(name);
let mut argument_holders = Vec::with_capacity(data.arguments.len());
for argument in &data.arguments {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&argument.value, generator, None);
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
}
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: dst.operand(),
callee: callee.operand(),
this_value: undefined.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string: Some(expression_string),
arguments,
});
return Some(Some(dst));
}
}
None
}
/// Try to generate a builtin constant (e.g. SYMBOL_ITERATOR).
/// Returns Some(operand) if the identifier is a known builtin constant.
fn try_generate_builtin_constant(generator: &mut Generator, name: &Utf16String) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
if !generator.builtin_abstract_operations_enabled {
return None;
}
if *name == utf16!("SYMBOL_ITERATOR") {
let value = unsafe { super::ffi::get_well_known_symbol(generator.vm_ptr, WellKnownSymbolKind::SymbolIterator) };
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_raw_value(value));
}
if *name == utf16!("SYMBOL_ASYNC_ITERATOR") {
let value =
unsafe { super::ffi::get_well_known_symbol(generator.vm_ptr, WellKnownSymbolKind::SymbolAsyncIterator) };
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_raw_value(value));
}
if *name == utf16!("MAX_ARRAY_LIKE_INDEX") {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_number(9007199254740991.0));
}
None
}
/// Generate bytecode for a call expression (`f()`) or new expression (`new C()`).
///
/// Handles several special forms:
/// - Direct `eval()` calls (CallWithArgumentArray with IsDirectEval flag)
/// - Member calls (`obj.f()`) that need to pass `this`
/// - Super calls (`super()`)
/// - Spread arguments (CallWithArgumentArray)
/// - Builtin abstract operation detection for built-in JS files
fn generate_call_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &CallExpressionData,
preferred_dst: Option<&ScopedOperand>,
is_new: bool,
) -> Option<ScopedOperand> {
// Check for builtin abstract operations before anything else.
if !is_new && let Some(result) = try_generate_builtin_abstract_operation(generator, data, preferred_dst) {
2026-02-24 08:36:14 -03:00
return result;
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
// Compute expression_string for error messages (e.g. "true is not a function (evaluated from 'a')").
let expression_string: Option<StringTableIndex> =
2026-02-24 08:36:14 -03:00
expression_string_approximation(&data.callee).map(|s| generator.intern_string(&s));
// Detect direct eval calls: bare identifier "eval" as callee.
let is_direct_eval =
!is_new && matches!(&data.callee.inner, ExpressionKind::Identifier(ident) if ident.name == utf16!("eval"));
// Detect known builtins for member expression callees (e.g. Math.abs).
let builtin: Option<u8> = if !is_new { get_builtin(&data.callee) } else { None };
// For method calls (obj.method()), we need to use the object as `this`.
let (callee, this_value) = if !is_new {
match &data.callee.inner {
ExpressionKind::Member(data) if matches!(data.object.inner, ExpressionKind::Super) => {
// Super member call: super.method() or super[expr]()
// Spec evaluation order:
// 1. ResolveThisBinding
// 2. Evaluate computed property (if any)
// 3. ResolveSuperBase
// 4. GetByIdWithThis / GetByValueWithThis
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let computed_key = if data.computed {
Some(generate_expression_or_undefined(&data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let super_base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase {
dst: super_base.operand(),
});
2026-02-24 08:36:14 -03:00
let method = generator.allocate_register();
if let Some(key) = computed_key {
2026-02-24 08:36:14 -03:00
emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value);
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
emit_get_by_id_with_this(generator, &method, &super_base, &ident.name, &this_value);
}
(method, Some(this_value))
}
ExpressionKind::Member(data) => {
let obj = generate_expression_or_undefined(&data.object, generator, None);
let base_id = intern_base_identifier(generator, &data.object);
2026-02-24 08:36:14 -03:00
let method = generator.allocate_register();
if data.computed {
let property = generate_expression_or_undefined(&data.property, generator, None);
2026-02-24 08:36:14 -03:00
emit_get_by_value(generator, &method, &obj, &property, None);
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &method, &obj, &ident.name, base_id);
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::GetPrivateById {
dst: method.operand(),
base: obj.operand(),
property: id,
});
}
(method, Some(obj))
}
ExpressionKind::Identifier(ident) if ident.is_local() => {
// Local identifier: use the local directly, with ThrowIfTDZ
// if not yet initialized.
let local = generator.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap());
let needs_tdz = if ident.local_type.get() == Some(LocalType::Argument) {
2026-02-24 08:36:14 -03:00
!generator.is_argument_initialized(ident.local_index.get())
} else {
2026-02-24 08:36:14 -03:00
generator.is_local_lexically_declared(ident.local_index.get())
&& !generator.is_local_initialized(ident.local_index.get())
};
if needs_tdz {
generator.emit(Instruction::ThrowIfTDZ { src: local.operand() });
}
(local, None)
}
ExpressionKind::Identifier(ident) if !ident.is_global.get() => {
// Non-local, non-global identifier: use GetCalleeAndThisFromEnvironment
// to properly handle with-statement bindings and eval.
2026-02-24 08:36:14 -03:00
let callee_reg = generator.allocate_register();
let this_reg = generator.allocate_register();
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::GetCalleeAndThisFromEnvironment {
callee: callee_reg.operand(),
this_value: this_reg.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
(callee_reg, Some(this_reg))
}
ExpressionKind::OptionalChain(oc_data) => {
// Allocate callee (current_value) first, this_value
// (current_base) second.
2026-02-24 08:36:14 -03:00
let callee = generator.allocate_register();
let this_value = generator.allocate_register();
generate_optional_chain_inner(generator, &oc_data.base, &oc_data.references, &callee, &this_value)?;
(callee, Some(this_value))
}
_ => {
2026-02-24 08:36:14 -03:00
let callee = generate_expression_or_undefined(&data.callee, generator, None);
(callee, None)
}
}
} else {
2026-02-24 08:36:14 -03:00
let callee = generate_expression_or_undefined(&data.callee, generator, None);
(callee, None)
};
// Copy callee/this into fresh registers so argument evaluation
// cannot mutate them (e.g. `foo.bar(foo = null)`).
// Zero-argument calls do not evaluate anything after the callee/this.
let this_value = if data.arguments.is_empty() {
this_value
} else {
this_value.map(|tv| generator.copy_if_needed_to_preserve_evaluation_order(&tv))
};
let callee = if data.arguments.is_empty() {
callee
} else {
generator.copy_if_needed_to_preserve_evaluation_order(&callee)
};
// Unwrap this_value at function scope so its register lifetime outlives argument temporaries.
2026-02-24 08:36:14 -03:00
let this_value = this_value.unwrap_or_else(|| generator.add_constant_undefined());
let has_spread = data.arguments.iter().any(|a| a.is_spread);
if has_spread {
// Build an arguments array using NewArray + ArrayAppend for spread elements.
2026-02-24 08:36:14 -03:00
let arguments_array = generator.allocate_register();
let first_spread = data.arguments.iter().position(|a| a.is_spread).unwrap_or(0);
let mut pre_holders = Vec::with_capacity(first_spread);
for argument in &data.arguments[..first_spread] {
2026-02-24 08:36:14 -03:00
let reg = generator.allocate_register();
let val = generate_expression_or_undefined(&argument.value, generator, None);
generator.emit_mov(&reg, &val);
pre_holders.push(reg);
}
let pre_arguments: Vec<Operand> = pre_holders.iter().map(|a| a.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewArray {
dst: arguments_array.operand(),
element_count: u32_from_usize(pre_arguments.len()),
elements: pre_arguments,
});
for argument in &data.arguments[first_spread..] {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&argument.value, generator, None);
generator.emit(Instruction::ArrayAppend {
dst: arguments_array.operand(),
src: val.operand(),
is_spread: argument.is_spread,
});
}
if is_new {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CallConstructWithArgumentArray {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
arguments: arguments_array.operand(),
expression_string,
});
} else if is_direct_eval {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CallDirectEvalWithArgumentArray {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
arguments: arguments_array.operand(),
expression_string,
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CallWithArgumentArray {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
arguments: arguments_array.operand(),
expression_string,
});
}
} else {
// Copy local variables into fresh registers so that evaluating
// later arguments cannot mutate earlier argument values (e.g.
// `bar(i, i++)` — the first argument must be the pre-increment value).
let mut argument_holders = Vec::with_capacity(data.arguments.len());
for argument in &data.arguments {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&argument.value, generator, None);
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
}
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
if is_new {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CallConstruct {
dst: dst.operand(),
callee: callee.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string,
arguments,
});
} else if is_direct_eval {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CallDirectEval {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string,
arguments,
});
} else if let Some(builtin) = builtin {
if builtin_argument_count(builtin) == arguments.len() {
emit_builtin_call(
generator,
builtin,
dst.operand(),
callee.operand(),
this_value.operand(),
expression_string,
arguments,
);
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string,
arguments,
});
}
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: dst.operand(),
callee: callee.operand(),
this_value: this_value.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string,
arguments,
});
}
}
Some(dst)
}
// =============================================================================
// Update expression (++/--)
// =============================================================================
/// Emit the increment/decrement operation for an update expression.
/// Returns the result operand: `value` for prefix, a new `dst` for postfix.
fn emit_update_op(generator: &mut Generator, op: UpdateOp, prefixed: bool, value: &ScopedOperand) -> ScopedOperand {
if prefixed {
match op {
UpdateOp::Increment => generator.emit(Instruction::Increment { dst: value.operand() }),
UpdateOp::Decrement => generator.emit(Instruction::Decrement { dst: value.operand() }),
}
value.clone()
} else {
// Always allocate a fresh register for the old value.
let dst = generator.allocate_register();
match op {
2026-02-24 08:36:14 -03:00
UpdateOp::Increment => generator.emit(Instruction::PostfixIncrement {
dst: dst.operand(),
src: value.operand(),
}),
2026-02-24 08:36:14 -03:00
UpdateOp::Decrement => generator.emit(Instruction::PostfixDecrement {
dst: dst.operand(),
src: value.operand(),
}),
}
dst
}
}
fn generate_update_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: UpdateOp,
argument: &Expression,
prefixed: bool,
) -> Option<ScopedOperand> {
// Load the value, keeping track of the base for member expressions
// so we can store back without re-evaluating.
match &argument.inner {
ExpressionKind::Identifier(ident) => {
let value = generate_identifier(ident, generator, None);
let result = emit_update_op(generator, op, prefixed, &value);
2026-02-24 08:36:14 -03:00
emit_set_variable(generator, ident, &value);
Some(result)
}
ExpressionKind::Member(data) => {
let is_super = matches!(data.object.inner, ExpressionKind::Super);
if is_super {
// Per spec, evaluation order for super property access is:
// 1. ResolveThisBinding
// 2. Evaluate computed property (if any)
// 3. ResolveSuperBase
// 4. Property lookup with this
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let computed_key = if data.computed {
Some(generate_expression_or_undefined(&data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase { dst: base.operand() });
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
if let Some(ref key) = computed_key {
2026-02-24 08:36:14 -03:00
emit_get_by_value_with_this(generator, &value, &base, key, &this_value);
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
emit_get_by_id_with_this(generator, &value, &base, &ident.name, &this_value);
}
let result = emit_update_op(generator, op, prefixed, &value);
emit_super_put(
2026-02-24 08:36:14 -03:00
generator,
&base,
&data.property,
data.computed,
&this_value,
&value,
computed_key.as_ref(),
);
Some(result)
} else {
// Non-super member update expression.
let base = generate_expression(&data.object, generator, None)?;
let base_id = intern_base_identifier(generator, &data.object);
if data.computed {
let property = generate_expression(&data.property, generator, None)?;
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
emit_get_by_value(generator, &value, &base, &property, base_id);
let result = emit_update_op(generator, op, prefixed, &value);
emit_put_normal_by_value(generator, &base, &property, &value, None);
Some(result)
} else if let ExpressionKind::Identifier(property_ident) = &data.property.inner {
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
emit_get_by_id(generator, &value, &base, &property_ident.name, base_id);
let key = generator.intern_property_key(&property_ident.name);
let result = emit_update_op(generator, op, prefixed, &value);
2026-02-24 08:36:14 -03:00
let cache2 = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: value.operand(),
cache: cache2 as u64,
base_identifier: None,
kind: 0,
});
Some(result)
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner {
let id = generator.intern_identifier(&priv_ident.name);
let value = generator.allocate_register();
generator.emit(Instruction::GetPrivateById {
dst: value.operand(),
base: base.operand(),
property: id,
});
let result = emit_update_op(generator, op, prefixed, &value);
generator.emit(Instruction::PutPrivateById {
base: base.operand(),
property: id,
src: value.operand(),
});
Some(result)
} else {
// Fallback: just evaluate, no store-back
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
Some(value)
}
}
}
_ => {
// Invalid update target (e.g. foo()++). Per spec, evaluate the
// expression first, then throw ReferenceError.
2026-02-24 08:36:14 -03:00
generate_expression(argument, generator, None);
emit_invalid_lhs_error(generator);
Some(generator.add_constant_undefined())
}
}
}
// =============================================================================
// Assignment expression
// =============================================================================
/// Generate bytecode for all forms of assignment: simple (`=`), compound
/// (`+=`, `-=`, etc.), and logical (`&&=`, `||=`, `??=`).
///
/// Handles identifiers (local, global, environment), member expressions
/// (by-id, by-value, super, private), and destructuring patterns.
fn generate_assignment_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: AssignmentOp,
lhs: &AssignmentLhs,
rhs: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
match lhs {
AssignmentLhs::Expression(lhs_expression) => {
// Simple assignment to identifier
if let ExpressionKind::Identifier(ident) = &lhs_expression.inner {
if op == AssignmentOp::Assignment {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
let rhs_val = generate_expression(rhs, generator, None)?;
generator.pending_lhs_name = None;
if ident.is_local() {
emit_tdz_check_if_needed(generator, ident);
}
2026-02-24 08:36:14 -03:00
emit_set_variable(generator, ident, &rhs_val);
return Some(rhs_val);
}
// Load LHS value first (needed for both compound and logical assignments).
let lhs_val = generate_identifier(ident, generator, None);
let lhs_val = generator.copy_if_needed_to_preserve_evaluation_order(&lhs_val);
let is_logical = matches!(
op,
AssignmentOp::AndAssignment | AssignmentOp::OrAssignment | AssignmentOp::NullishAssignment
);
if is_logical {
// Logical assignments short-circuit: evaluate RHS only if condition met.
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let lhs_block = generator.make_block();
let end_block = generator.make_block();
match op {
AssignmentOp::AndAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&lhs_val, rhs_block, lhs_block);
}
AssignmentOp::OrAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&lhs_val, lhs_block, rhs_block);
}
AssignmentOp::NullishAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::JumpNullish {
condition: lhs_val.operand(),
true_target: rhs_block,
false_target: lhs_block,
});
}
_ => unreachable!("only logical assignment ops reach this branch"),
}
// RHS block: evaluate RHS, assign, jump to end.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(rhs_block);
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
let rhs_val = generate_expression(rhs, generator, None)?;
generator.pending_lhs_name = None;
// Allocate dst after RHS evaluation.
let dst = if lhs_val.operand().is_local() {
lhs_val.clone()
} else {
2026-02-24 08:36:14 -03:00
choose_dst(generator, preferred_dst)
};
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, &rhs_val);
emit_set_variable(generator, ident, &dst);
generator.emit(Instruction::Jump { target: end_block });
// LHS block: keep original value.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(lhs_block);
generator.emit_mov(&dst, &lhs_val);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
return Some(dst);
}
// Regular compound assignment (+=, -=, etc.)
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
// OPTIMIZATION: If LHS is a local, write directly into it.
let dst = if lhs_val.operand().is_local() {
lhs_val.clone()
} else {
2026-02-24 08:36:14 -03:00
choose_dst(generator, preferred_dst)
};
2026-02-24 08:36:14 -03:00
emit_compound_assignment(generator, op, &dst, &lhs_val, &rhs_val);
emit_set_variable(generator, ident, &dst);
return Some(dst);
}
// Member expression LHS (e.g., obj.foo = x, obj[key] = x)
if let ExpressionKind::Member(member_data) = &lhs_expression.inner {
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
if is_super {
// Per spec, evaluation order for super property reference is:
// 1. ResolveThisBinding
// 2. Evaluate computed property (if any)
// 3. ResolveSuperBase
2026-02-24 08:36:14 -03:00
let super_this = emit_resolve_this_binding(generator);
if op == AssignmentOp::Assignment {
let computed_key = if member_data.computed {
Some(generate_expression_or_undefined(&member_data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase { dst: base.operand() });
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
emit_super_put(
2026-02-24 08:36:14 -03:00
generator,
&base,
&member_data.property,
member_data.computed,
&super_this,
&rhs_val,
computed_key.as_ref(),
);
return Some(rhs_val);
}
// Compound/logical assignment: evaluate property, resolve
// super base, then get old value.
let computed_key = if member_data.computed {
Some(generate_expression_or_undefined(&member_data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase { dst: base.operand() });
2026-02-24 08:36:14 -03:00
let old_val = generator.allocate_register();
if let Some(ref key) = computed_key {
2026-02-24 08:36:14 -03:00
emit_get_by_value_with_this(generator, &old_val, &base, key, &super_this);
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
emit_get_by_id_with_this(generator, &old_val, &base, &ident.name, &super_this);
}
let is_logical = matches!(
op,
AssignmentOp::AndAssignment | AssignmentOp::OrAssignment | AssignmentOp::NullishAssignment
);
if is_logical {
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let lhs_block = generator.make_block();
let end_block = generator.make_block();
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
generator.switch_to_basic_block(rhs_block);
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
generator.emit_mov(&dst, &rhs_val);
emit_super_put(
2026-02-24 08:36:14 -03:00
generator,
&base,
&member_data.property,
member_data.computed,
&super_this,
&dst,
computed_key.as_ref(),
);
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(lhs_block);
generator.emit_mov(&dst, &old_val);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
emit_super_put(
2026-02-24 08:36:14 -03:00
generator,
&base,
&member_data.property,
member_data.computed,
&super_this,
&dst,
computed_key.as_ref(),
);
return Some(dst);
}
// Non-super member assignment.
let base_raw = generate_expression(&member_data.object, generator, None)?;
if op == AssignmentOp::Assignment {
2026-02-24 08:36:14 -03:00
let base = generator.copy_if_needed_to_preserve_evaluation_order(&base_raw);
let precomputed_key = if member_data.computed {
let key_val = generate_expression_or_undefined(&member_data.property, generator, None);
2026-02-24 08:36:14 -03:00
Some(generator.copy_if_needed_to_preserve_evaluation_order(&key_val))
} else {
None
};
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
if let Some(key) = precomputed_key {
let base_id = intern_base_identifier(generator, &member_data.object);
2026-02-24 08:36:14 -03:00
emit_put_normal_by_value(generator, &base, &key, &rhs_val, base_id);
} else {
2026-02-24 08:36:14 -03:00
emit_put_to_member(
generator,
&base,
&member_data.property,
2026-02-24 08:36:14 -03:00
false,
&rhs_val,
Some(&member_data.object),
2026-02-24 08:36:14 -03:00
);
}
return Some(rhs_val);
}
// Compound/logical member assignment.
let base = base_raw;
let base_id = intern_base_identifier(generator, &member_data.object);
let is_logical = matches!(
op,
AssignmentOp::AndAssignment | AssignmentOp::OrAssignment | AssignmentOp::NullishAssignment
);
if member_data.computed {
let property = generate_expression(&member_data.property, generator, None)?;
2026-02-24 08:36:14 -03:00
let old_val = generator.allocate_register();
emit_get_by_value(generator, &old_val, &base, &property, base_id);
// Copy property to a fresh register so RHS evaluation
// (which may mutate the variable backing property) doesn't
// affect the store-back index.
2026-02-24 08:36:14 -03:00
let saved_property = generator.allocate_register();
generator.emit_mov(&saved_property, &property);
if is_logical {
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let lhs_block = generator.make_block();
let end_block = generator.make_block();
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
generator.switch_to_basic_block(rhs_block);
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
generator.emit_mov(&dst, &rhs_val);
emit_put_normal_by_value(generator, &base, &saved_property, &dst, None);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(lhs_block);
generator.emit_mov(&dst, &old_val);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
emit_put_normal_by_value(generator, &base, &saved_property, &dst, None);
return Some(dst);
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
2026-02-24 08:36:14 -03:00
let old_val = generator.allocate_register();
emit_get_by_id(generator, &old_val, &base, &ident.name, base_id);
if is_logical {
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let lhs_block = generator.make_block();
let end_block = generator.make_block();
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
generator.switch_to_basic_block(rhs_block);
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
generator.emit_mov(&dst, &rhs_val);
let key = generator.intern_property_key(&ident.name);
let cache2 = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: dst.operand(),
cache: cache2 as u64,
base_identifier: None,
kind: 0,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(lhs_block);
generator.emit_mov(&dst, &old_val);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
let key = generator.intern_property_key(&ident.name);
let cache2 = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: dst.operand(),
cache: cache2 as u64,
base_identifier: None,
kind: 0,
});
return Some(dst);
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &member_data.property.inner {
2026-02-24 08:36:14 -03:00
let old_val = generator.allocate_register();
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::GetPrivateById {
dst: old_val.operand(),
base: base.operand(),
property: id,
});
if is_logical {
2026-02-24 08:36:14 -03:00
let rhs_block = generator.make_block();
let lhs_block = generator.make_block();
let end_block = generator.make_block();
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
generator.switch_to_basic_block(rhs_block);
let rhs_val = generate_expression(rhs, generator, None)?;
// Allocate dst after RHS evaluation.
let dst = choose_dst(generator, preferred_dst);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, &rhs_val);
let id2 = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::PutPrivateById {
base: base.operand(),
property: id2,
src: dst.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(lhs_block);
generator.emit_mov(&dst, &old_val);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(end_block);
return Some(dst);
}
2026-02-24 08:36:14 -03:00
let rhs_val = generate_expression(rhs, generator, None)?;
let dst = choose_dst(generator, preferred_dst);
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
let id2 = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::PutPrivateById {
base: base.operand(),
property: id2,
src: dst.operand(),
});
return Some(dst);
}
}
// LHS is not an identifier or member expression (e.g. a function call).
// Per spec 13.15.2 step 1b, evaluate the LHS, then throw ReferenceError
// before evaluating the RHS.
2026-02-24 08:36:14 -03:00
generate_expression(lhs_expression, generator, None);
emit_invalid_lhs_error(generator);
Some(generator.add_constant_undefined())
}
AssignmentLhs::Pattern(pattern) => {
let rhs_val = generate_expression(rhs, generator, None)?;
2026-02-24 08:36:14 -03:00
generate_binding_pattern_bytecode(generator, pattern, BindingMode::Set, &rhs_val);
Some(rhs_val)
}
}
}
/// Emit ResolveThisBinding (if not already resolved in current block) and return
/// the this value register.
2026-02-24 08:36:14 -03:00
fn emit_resolve_this_binding(generator: &mut Generator) -> ScopedOperand {
emit_resolve_this_if_needed(generator);
generator.this_value()
}
/// Emit ResolveThisBinding only if not already resolved in the current or entry block.
2026-02-24 08:36:14 -03:00
fn emit_resolve_this_if_needed(generator: &mut Generator) {
let index = generator.current_block_index().basic_block_index();
if generator.basic_blocks[index].resolved_this {
return;
}
2026-02-24 08:36:14 -03:00
if generator.basic_blocks[0].resolved_this {
generator.basic_blocks[index].resolved_this = true;
return;
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ResolveThisBinding);
let index = generator.current_block_index().basic_block_index();
generator.basic_blocks[index].resolved_this = true;
}
/// Emit a super property get (uses WithThis variants).
/// For computed access, evaluates the property expression.
/// Returns the evaluated property operand for computed access (so callers
/// can reuse it for a subsequent put).
fn emit_super_get(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
dst: &ScopedOperand,
base: &ScopedOperand,
property: &Expression,
computed: bool,
this_value: &ScopedOperand,
) -> Option<ScopedOperand> {
if computed {
2026-02-24 08:36:14 -03:00
let property = generate_expression_or_undefined(property, generator, None);
emit_get_by_value_with_this(generator, dst, base, &property, this_value);
Some(property)
} else if let ExpressionKind::Identifier(ident) = &property.inner {
emit_get_by_id_with_this(generator, dst, base, &ident.name, this_value);
None
} else {
None
}
}
/// Emit a super property put (uses WithThis variants).
/// For computed access, `computed_key` should be the operand returned by
/// `emit_super_get` so the property is not re-evaluated. If `None` for
/// computed access, the property expression will be evaluated.
fn emit_super_put(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &ScopedOperand,
property: &Expression,
computed: bool,
this_value: &ScopedOperand,
value: &ScopedOperand,
computed_key: Option<&ScopedOperand>,
) {
if computed {
let property = match computed_key {
Some(k) => k.clone(),
2026-02-24 08:36:14 -03:00
None => generate_expression_or_undefined(property, generator, None),
};
2026-02-24 08:36:14 -03:00
emit_put_normal_by_value_with_this(generator, base, &property, this_value, value);
} else if let ExpressionKind::Identifier(ident) = &property.inner {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(&ident.name);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutByIdWithThis {
base: base.operand(),
this_value: this_value.operand(),
property: key,
src: value.operand(),
cache: cache as u64,
kind: 0,
});
}
}
/// Emit a property access by name, using GetLength for the "length" property.
fn emit_get_by_id(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
dst: &ScopedOperand,
base: &ScopedOperand,
property_name: &[u16],
base_identifier: Option<IdentifierTableIndex>,
) {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(property_name);
if property_name == utf16!("length") {
2026-02-24 08:36:14 -03:00
generator.length_identifier = Some(key);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetLength {
dst: dst.operand(),
base: base.operand(),
base_identifier,
cache: cache as u64,
});
} else {
2026-02-24 08:36:14 -03:00
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetById {
dst: dst.operand(),
base: base.operand(),
property: key,
base_identifier,
cache: cache as u64,
});
}
}
/// Emit a property access by name with a this value, using GetLengthWithThis
/// for the "length" property.
fn emit_get_by_id_with_this(
generator: &mut Generator,
dst: &ScopedOperand,
base: &ScopedOperand,
property_name: &[u16],
this_value: &ScopedOperand,
) {
let key = generator.intern_property_key(property_name);
if property_name == utf16!("length") {
generator.length_identifier = Some(key);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetLengthWithThis {
dst: dst.operand(),
base: base.operand(),
this_value: this_value.operand(),
cache: cache as u64,
});
} else {
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetByIdWithThis {
dst: dst.operand(),
base: base.operand(),
property: key,
this_value: this_value.operand(),
cache: cache as u64,
});
}
}
/// Emit a "Invalid left-hand side in assignment" ReferenceError followed by Throw.
2026-02-24 08:36:14 -03:00
fn emit_invalid_lhs_error(generator: &mut Generator) {
let exception = generator.allocate_register();
let error_string = generator.intern_string(utf16!("Invalid left-hand side in assignment"));
generator.emit(Instruction::NewReferenceError {
dst: exception.operand(),
error_string,
});
generator.perform_needed_unwinds();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Throw {
src: exception.operand(),
});
}
/// Check if a UTF-16 string is a canonical array index (non-negative integer < 2^32 - 1).
/// These strings become integer PropertyKeys,
/// not string PropertyKeys, so they must NOT be optimized to GetById/PutById.
pub(crate) fn is_array_index(s: &[u16]) -> bool {
if s.is_empty() || s.len() > 10 {
return false;
}
// Must not have leading zeros (except "0" itself)
if s.len() > 1 && s[0] == ch(b'0') {
return false;
}
let mut value: u64 = 0;
for &c in s {
if c < ch(b'0') || c > ch(b'9') {
return false;
}
value = value * 10 + (c - ch(b'0')) as u64;
}
value <= 0xFFFF_FFFE
}
/// Emit a property read by value, optimizing constant string properties to GetById.
fn emit_get_by_value(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
dst: &ScopedOperand,
base: &ScopedOperand,
property: &ScopedOperand,
base_identifier: Option<IdentifierTableIndex>,
) {
2026-02-24 08:36:14 -03:00
if let Some(key) = generator.try_constant_string_to_property_key(property) {
if generator.property_key_table[key.0 as usize].0 == utf16!("length") {
generator.length_identifier = Some(key);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetLength {
dst: dst.operand(),
base: base.operand(),
base_identifier,
cache: cache as u64,
});
} else {
2026-02-24 08:36:14 -03:00
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetById {
dst: dst.operand(),
base: base.operand(),
property: key,
base_identifier,
cache: cache as u64,
});
}
return;
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::GetByValue {
dst: dst.operand(),
base: base.operand(),
property: property.operand(),
base_identifier,
});
}
/// Emit a property read by value with explicit this, optimizing constant string properties.
fn emit_get_by_value_with_this(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
dst: &ScopedOperand,
base: &ScopedOperand,
property: &ScopedOperand,
this_value: &ScopedOperand,
) {
2026-02-24 08:36:14 -03:00
if let Some(key) = generator.try_constant_string_to_property_key(property) {
if generator.property_key_table[key.0 as usize].0 == utf16!("length") {
generator.length_identifier = Some(key);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetLengthWithThis {
dst: dst.operand(),
base: base.operand(),
this_value: this_value.operand(),
cache: cache as u64,
});
} else {
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::GetByIdWithThis {
dst: dst.operand(),
base: base.operand(),
property: key,
this_value: this_value.operand(),
cache: cache as u64,
});
}
return;
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::GetByValueWithThis {
dst: dst.operand(),
base: base.operand(),
property: property.operand(),
this_value: this_value.operand(),
});
}
/// Emit a normal property write by value, optimizing constant string properties to PutNormalById.
fn emit_put_normal_by_value(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &ScopedOperand,
property: &ScopedOperand,
src: &ScopedOperand,
base_identifier: Option<IdentifierTableIndex>,
) {
2026-02-24 08:36:14 -03:00
if let Some(key) = generator.try_constant_string_to_property_key(property) {
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: src.operand(),
cache: cache as u64,
base_identifier,
kind: 0,
});
return;
}
generator.emit(Instruction::PutByValue {
base: base.operand(),
property: property.operand(),
src: src.operand(),
base_identifier,
kind: 0,
});
}
/// Emit a normal property write by value with explicit this, optimizing constant string properties.
fn emit_put_normal_by_value_with_this(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &ScopedOperand,
property: &ScopedOperand,
this_value: &ScopedOperand,
src: &ScopedOperand,
) {
2026-02-24 08:36:14 -03:00
if let Some(key) = generator.try_constant_string_to_property_key(property) {
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutByIdWithThis {
base: base.operand(),
this_value: this_value.operand(),
property: key,
src: src.operand(),
cache: cache as u64,
kind: 0,
});
return;
}
generator.emit(Instruction::PutByValueWithThis {
base: base.operand(),
property: property.operand(),
this_value: this_value.operand(),
src: src.operand(),
kind: 0,
});
}
enum PutKind {
Own,
Getter,
Setter,
}
/// Emit a property write by value, optimizing constant string properties to the ById variant.
fn emit_put_by_value(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &ScopedOperand,
property: &ScopedOperand,
src: &ScopedOperand,
kind: PutKind,
) {
2026-02-24 08:36:14 -03:00
if let Some(key) = generator.try_constant_string_to_property_key(property) {
let cache = generator.next_property_lookup_cache();
match kind {
PutKind::Own => {
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: src.operand(),
cache: cache as u64,
base_identifier: None,
kind: 4,
});
}
PutKind::Getter => {
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: src.operand(),
cache: cache as u64,
base_identifier: None,
kind: 1,
});
}
PutKind::Setter => {
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: src.operand(),
cache: cache as u64,
base_identifier: None,
kind: 2,
});
}
}
return;
}
match kind {
PutKind::Own => {
generator.emit(Instruction::PutByValue {
base: base.operand(),
property: property.operand(),
src: src.operand(),
base_identifier: None,
kind: 4,
});
}
PutKind::Getter => {
generator.emit(Instruction::PutByValue {
base: base.operand(),
property: property.operand(),
src: src.operand(),
base_identifier: None,
kind: 1,
});
}
PutKind::Setter => {
generator.emit(Instruction::PutByValue {
base: base.operand(),
property: property.operand(),
src: src.operand(),
base_identifier: None,
kind: 2,
});
}
}
}
/// Emit a ThrowIfTDZ check for a local identifier if needed. This is used
/// before assigning to a
/// variable to ensure TDZ semantics for let/const bindings.
fn emit_tdz_check_if_needed(generator: &mut Generator, ident: &Identifier) {
if !ident.is_local() {
return;
}
let local_index = ident.local_index.get();
let needs_tdz_check = if ident.local_type.get() == Some(LocalType::Argument) {
!generator.is_argument_initialized(local_index)
} else {
generator.is_local_lexically_declared(local_index) && !generator.is_local_initialized(local_index)
};
if needs_tdz_check {
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
if ident.local_type.get() == Some(LocalType::Argument) {
let empty = generator.add_constant_empty();
generator.emit_mov(&local, &empty);
}
generator.emit(Instruction::ThrowIfTDZ { src: local.operand() });
}
}
2026-02-24 08:36:14 -03:00
fn emit_set_variable(generator: &mut Generator, ident: &Identifier, value: &ScopedOperand) {
if ident.is_local() {
if ident.declaration_kind.get() == Some(DeclarationKind::Const) {
// The caller is responsible for emitting ThrowIfTDZ before calling
// emit_set_variable().
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ThrowConstAssignment {});
return;
}
let local_index = ident.local_index.get();
2026-02-24 08:36:14 -03:00
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
// Skip self-move entirely.
let is_variable_self_move = ident.local_type.get() == Some(LocalType::Variable)
&& value.operand().is_local()
&& value.operand().index() == local_index;
if is_variable_self_move {
return;
}
// No TDZ check here: the caller is responsible for checking TDZ
// before calling emit_set_variable().
generator.emit(Instruction::Mov {
dst: local.operand(),
src: value.operand(),
});
} else if ident.is_global.get() {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
let cache = generator.next_global_variable_cache();
generator.emit(Instruction::SetGlobal {
identifier: id,
src: value.operand(),
cache: cache as u64,
});
} else {
// Non-local, non-global: use SetLexicalBinding which searches
// the lexical environment chain (important for with-statement support).
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::SetLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
fn emit_put_to_member(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &ScopedOperand,
property: &Expression,
computed: bool,
value: &ScopedOperand,
base_object: Option<&Expression>,
) {
2026-02-24 08:36:14 -03:00
let base_id = base_object.and_then(|obj| intern_base_identifier(generator, obj));
if computed {
2026-02-24 08:36:14 -03:00
let property = generate_expression_or_undefined(property, generator, None);
emit_put_normal_by_value(generator, base, &property, value, base_id);
} else if let ExpressionKind::Identifier(ident) = &property.inner {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(&ident.name);
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: base.operand(),
property: key,
src: value.operand(),
cache: cache as u64,
base_identifier: base_id,
kind: 0,
});
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::PutPrivateById {
base: base.operand(),
property: id,
src: value.operand(),
});
}
}
/// Emit bytecode for `delete <expression>`.
2026-02-24 08:36:14 -03:00
fn emit_delete_reference(generator: &mut Generator, operand: &Expression) -> ScopedOperand {
match &operand.inner {
ExpressionKind::Identifier(ident) => {
if ident.is_local() {
2026-02-24 08:36:14 -03:00
return generator.add_constant_boolean(false);
}
2026-02-24 08:36:14 -03:00
let dst = generator.allocate_register();
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::DeleteVariable {
dst: dst.operand(),
identifier: id,
});
dst
}
ExpressionKind::Member(data) => {
// https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
// Deleting a super property is always a ReferenceError.
if matches!(data.object.inner, ExpressionKind::Super) {
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
// Evaluate computed property for side effects before throwing.
// Per spec, property key evaluation precedes ResolveSuperBase.
let _computed_key = if data.computed {
Some(generate_expression_or_undefined(&data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let super_base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase {
dst: super_base.operand(),
});
2026-02-24 08:36:14 -03:00
let exception = generator.allocate_register();
let error_string = generator.intern_string(utf16!("Can't delete a property on 'super'"));
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewReferenceError {
dst: exception.operand(),
error_string,
});
2026-02-24 08:36:14 -03:00
generator.perform_needed_unwinds();
generator.emit(Instruction::Throw {
src: exception.operand(),
});
2026-02-24 08:36:14 -03:00
let dead_block = generator.make_block();
generator.switch_to_basic_block(dead_block);
let _ = (this_value, _computed_key);
2026-02-24 08:36:14 -03:00
return generator.add_constant_undefined();
}
let base = generate_expression_or_undefined(&data.object, generator, None);
2026-02-24 08:36:14 -03:00
let dst = generator.allocate_register();
if data.computed {
let key = generate_expression_or_undefined(&data.property, generator, None);
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::DeleteByValue {
dst: dst.operand(),
base: base.operand(),
property: key.operand(),
});
} else if let ExpressionKind::Identifier(property_ident) = &data.property.inner {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(&property_ident.name);
generator.emit(Instruction::DeleteById {
dst: dst.operand(),
base: base.operand(),
property: key,
});
} else {
2026-02-24 08:36:14 -03:00
return generator.add_constant_boolean(true);
}
dst
}
_ => {
// delete on non-reference: evaluate for side effects, return true
2026-02-24 08:36:14 -03:00
generate_expression(operand, generator, None);
generator.add_constant_boolean(true)
}
}
}
/// Pre-evaluated reference operands for deferred store.
/// Used when the spec requires evaluating the assignment target reference
/// before performing some other operation (like iterating a spread element).
enum EvaluatedReference {
Member {
base: ScopedOperand,
property: ScopedOperand,
base_identifier: Option<IdentifierTableIndex>,
},
MemberId {
base: ScopedOperand,
property: PropertyKeyTableIndex,
cache: u32,
base_identifier: Option<IdentifierTableIndex>,
},
PrivateMember {
base: ScopedOperand,
property: IdentifierTableIndex,
},
SuperMember {
base: ScopedOperand,
property: ScopedOperand,
this_value: ScopedOperand,
},
SuperMemberId {
base: ScopedOperand,
property: PropertyKeyTableIndex,
cache: u32,
this_value: ScopedOperand,
},
}
/// Evaluate a member expression target to get pre-computed reference operands
/// without performing a load. This implements the "Let lref be ? Evaluation of
/// DestructuringAssignmentTarget" step from the spec.
fn emit_evaluate_member_reference(generator: &mut Generator, target: &Expression) -> EvaluatedReference {
if let ExpressionKind::Member(member_data) = &target.inner {
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
if is_super {
// ResolveThisBinding first, then ResolveSuperBase.
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase { dst: base.operand() });
if member_data.computed {
let property = generate_expression_or_undefined(&member_data.property, generator, None);
// If the computed property is a constant string (e.g. super["minutes"]),
// optimize to SuperMemberId.
if let Some(key) = generator.try_constant_string_to_property_key(&property) {
let cache = generator.next_property_lookup_cache();
EvaluatedReference::SuperMemberId {
base,
property: key,
cache,
this_value,
}
} else {
let saved_property = generator.allocate_register();
generator.emit_mov(&saved_property, &property);
EvaluatedReference::SuperMember {
base,
property: saved_property,
this_value,
}
}
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(&ident.name);
let cache = generator.next_property_lookup_cache();
EvaluatedReference::SuperMemberId {
base,
property: key,
cache,
this_value,
}
} else {
unreachable!("non-computed super member property must be an identifier")
}
} else {
let base = generate_expression_or_undefined(&member_data.object, generator, None);
if member_data.computed {
let property = generate_expression_or_undefined(&member_data.property, generator, None);
// If the computed property is a constant string (e.g. obj["key"]),
// optimize to MemberId.
if let Some(key) = generator.try_constant_string_to_property_key(&property) {
let cache = generator.next_property_lookup_cache();
EvaluatedReference::MemberId {
base,
property: key,
cache,
base_identifier: None,
}
} else {
let saved_property = generator.allocate_register();
generator.emit_mov(&saved_property, &property);
EvaluatedReference::Member {
base,
property: saved_property,
base_identifier: None,
}
}
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
let key = generator.intern_property_key(&ident.name);
let cache = generator.next_property_lookup_cache();
EvaluatedReference::MemberId {
base,
property: key,
cache,
base_identifier: None,
}
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &member_data.property.inner {
let id = generator.intern_identifier(&priv_ident.name);
EvaluatedReference::PrivateMember { base, property: id }
} else {
unreachable!("non-computed member property must be an identifier or private identifier")
}
}
} else {
unreachable!("emit_evaluate_member_reference called on non-member expression")
}
}
/// Store a value to a pre-evaluated reference.
fn emit_store_to_evaluated_reference(generator: &mut Generator, reference: &EvaluatedReference, value: &ScopedOperand) {
match reference {
EvaluatedReference::Member {
base,
property,
base_identifier,
} => {
2026-02-24 08:36:14 -03:00
emit_put_normal_by_value(generator, base, property, value, *base_identifier);
}
EvaluatedReference::MemberId {
base,
property,
cache,
base_identifier,
} => {
generator.emit(Instruction::PutById {
base: base.operand(),
property: *property,
src: value.operand(),
cache: *cache as u64,
base_identifier: *base_identifier,
kind: 0,
});
}
EvaluatedReference::PrivateMember { base, property } => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::PutPrivateById {
base: base.operand(),
property: *property,
src: value.operand(),
});
}
EvaluatedReference::SuperMember {
base,
property,
this_value,
} => {
2026-02-24 08:36:14 -03:00
emit_put_normal_by_value_with_this(generator, base, property, this_value, value);
}
EvaluatedReference::SuperMemberId {
base,
property,
cache,
this_value,
} => {
generator.emit(Instruction::PutByIdWithThis {
base: base.operand(),
this_value: this_value.operand(),
property: *property,
src: value.operand(),
cache: *cache as u64,
kind: 0,
});
}
}
}
2026-02-24 08:36:14 -03:00
fn emit_store_to_reference(generator: &mut Generator, target: &Expression, value: &ScopedOperand) {
match &target.inner {
ExpressionKind::Identifier(ident) => {
2026-02-24 08:36:14 -03:00
emit_set_variable(generator, ident, value);
}
ExpressionKind::Member(data) => {
if matches!(data.object.inner, ExpressionKind::Super) {
// ResolveThisBinding first, then ResolveSuperBase.
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase { dst: base.operand() });
2026-02-24 08:36:14 -03:00
emit_super_put(
generator,
&base,
&data.property,
data.computed,
2026-02-24 08:36:14 -03:00
&this_value,
value,
None,
);
} else {
let base = generate_expression_or_undefined(&data.object, generator, None);
emit_put_to_member(generator, &base, &data.property, data.computed, value, None);
}
}
_ => {
// Evaluate the expression for side effects, then throw ReferenceError.
2026-02-24 08:36:14 -03:00
generate_expression(target, generator, None);
emit_invalid_lhs_error(generator);
}
}
}
/// Emit the conditional jump for a logical assignment (&&=, ||=, ??=).
fn emit_logical_jump(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: AssignmentOp,
condition: &ScopedOperand,
rhs_block: Label,
lhs_block: Label,
) {
match op {
AssignmentOp::AndAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(condition, rhs_block, lhs_block);
}
AssignmentOp::OrAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(condition, lhs_block, rhs_block);
}
AssignmentOp::NullishAssignment => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::JumpNullish {
condition: condition.operand(),
true_target: rhs_block,
false_target: lhs_block,
});
}
_ => unreachable!("only logical assignment ops are passed to emit_logical_jump"),
}
}
fn emit_compound_assignment(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: AssignmentOp,
dst: &ScopedOperand,
lhs: &ScopedOperand,
rhs: &ScopedOperand,
) {
let dst_op = dst.operand();
let lhs_op = lhs.operand();
let rhs_op = rhs.operand();
match op {
2026-02-24 08:36:14 -03:00
AssignmentOp::AdditionAssignment => generator.emit(Instruction::Add {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::SubtractionAssignment => generator.emit(Instruction::Sub {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::MultiplicationAssignment => generator.emit(Instruction::Mul {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::DivisionAssignment => generator.emit(Instruction::Div {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::ModuloAssignment => generator.emit(Instruction::Mod {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::ExponentiationAssignment => generator.emit(Instruction::Exp {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::BitwiseAndAssignment => generator.emit(Instruction::BitwiseAnd {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::BitwiseOrAssignment => generator.emit(Instruction::BitwiseOr {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::BitwiseXorAssignment => generator.emit(Instruction::BitwiseXor {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::LeftShiftAssignment => generator.emit(Instruction::LeftShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::RightShiftAssignment => generator.emit(Instruction::RightShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
}),
2026-02-24 08:36:14 -03:00
AssignmentOp::UnsignedRightShiftAssignment => {
generator.emit(Instruction::UnsignedRightShift {
dst: dst_op,
lhs: lhs_op,
rhs: rhs_op,
});
2026-02-24 08:36:14 -03:00
}
AssignmentOp::AndAssignment | AssignmentOp::OrAssignment | AssignmentOp::NullishAssignment => {
unreachable!("logical assignment in compound path")
}
AssignmentOp::Assignment => unreachable!("plain assignment in compound path"),
}
}
// =============================================================================
// Template literal
// =============================================================================
fn generate_template_literal(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &TemplateLiteralData,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// The parser stores ALL parts (string segments AND interpolated expressions)
// in data.expressions. raw_strings is only populated for tagged templates.
// OPTIMIZATION: Filter out empty string segments.
let segments: Vec<&Expression> = data
.expressions
.iter()
.filter(|e| !matches!(&e.inner, ExpressionKind::StringLiteral(s) if s.is_empty()))
.collect();
if segments.is_empty() {
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_string(Utf16String::new()));
}
// Allocate dst before generating expressions.
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
if segments.len() == 1 {
2026-02-24 08:36:14 -03:00
let val = generate_expression(segments[0], generator, None)?;
// If it's a constant, return directly.
if val.operand().is_constant() {
return Some(val);
}
// Otherwise, emit ToString.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ToString {
dst: dst.operand(),
value: val.operand(),
});
return Some(dst);
}
for (index, expression) in segments.iter().enumerate() {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(expression, generator, None);
if index == 0 {
if matches!(&expression.inner, ExpressionKind::StringLiteral(_)) {
2026-02-24 08:36:14 -03:00
generator.emit_mov(&dst, &val);
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ToString {
dst: dst.operand(),
value: val.operand(),
});
}
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ConcatString {
dst: dst.operand(),
src: val.operand(),
});
}
}
Some(dst)
}
// =============================================================================
// Tagged template literal
// =============================================================================
fn generate_tagged_template_literal(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
tag: &Expression,
template_literal: &Expression,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
// Resolve tag and this_value based on the tag expression type.
let (tag_reg, this_value) = match &tag.inner {
ExpressionKind::Member(member_data) if matches!(member_data.object.inner, ExpressionKind::Super) => {
// super.func`` or super["func"]``
// Per spec, evaluation order: ResolveThisBinding, evaluate
// computed property, then ResolveSuperBase.
2026-02-24 08:36:14 -03:00
let this_value = emit_resolve_this_binding(generator);
let computed_key = if member_data.computed {
Some(generate_expression_or_undefined(&member_data.property, generator, None))
} else {
None
};
2026-02-24 08:36:14 -03:00
let super_base = generator.allocate_register();
generator.emit(Instruction::ResolveSuperBase {
dst: super_base.operand(),
});
2026-02-24 08:36:14 -03:00
let method = generator.allocate_register();
if let Some(key) = computed_key {
emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value);
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
emit_get_by_id_with_this(generator, &method, &super_base, &ident.name, &this_value);
}
(method, Some(this_value))
}
ExpressionKind::Member(member_data) => {
let obj = generate_expression_or_undefined(&member_data.object, generator, None);
2026-02-24 08:36:14 -03:00
let method = generator.allocate_register();
if member_data.computed {
let property = generate_expression_or_undefined(&member_data.property, generator, None);
2026-02-24 08:36:14 -03:00
emit_get_by_value(generator, &method, &obj, &property, None);
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
let base_id = intern_base_identifier(generator, &member_data.object);
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &method, &obj, &ident.name, base_id);
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &member_data.property.inner {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&priv_ident.name);
generator.emit(Instruction::GetPrivateById {
dst: method.operand(),
base: obj.operand(),
property: id,
});
}
(method, Some(obj))
}
ExpressionKind::Identifier(ident) if ident.is_local() || ident.is_global.get() => {
2026-02-24 08:36:14 -03:00
let tag_val = generate_expression_or_undefined(tag, generator, None);
(tag_val, None)
}
ExpressionKind::Identifier(ident) => {
// Non-local, non-global identifier: use GetCalleeAndThisFromEnvironment
// to properly handle with-statement bindings.
2026-02-24 08:36:14 -03:00
let callee_reg = generator.allocate_register();
let this_reg = generator.allocate_register();
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::GetCalleeAndThisFromEnvironment {
callee: callee_reg.operand(),
this_value: this_reg.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
(callee_reg, Some(this_reg))
}
_ => {
2026-02-24 08:36:14 -03:00
let tag_val = generate_expression_or_undefined(tag, generator, None);
(tag_val, None)
}
};
// Build template strings for GetTemplateObject.
// expressions has alternating: string_0, expression_0, string_1, expression_1, ..., string_n
let ExpressionKind::TemplateLiteral(data) = &template_literal.inner else {
unreachable!("TaggedTemplateLiteral template must be TemplateLiteral");
};
// Collect cooked strings (even indices). NullLiteral means invalid escape → undefined.
let mut string_regs = Vec::new();
for i in (0..data.expressions.len()).step_by(2) {
if matches!(&data.expressions[i].inner, ExpressionKind::NullLiteral) {
2026-02-24 08:36:14 -03:00
string_regs.push(generator.add_constant_undefined());
} else {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&data.expressions[i], generator, None);
string_regs.push(val);
}
}
// Append raw strings.
for raw in &data.raw_strings {
2026-02-24 08:36:14 -03:00
let val = generator.add_constant_string(raw.clone());
string_regs.push(val);
}
// Emit GetTemplateObject.
2026-02-24 08:36:14 -03:00
let strings_array = generator.allocate_register();
let string_ops: Vec<Operand> = string_regs.iter().map(|s| s.operand()).collect();
2026-02-24 08:36:14 -03:00
let cache_index = generator.next_template_object_cache();
generator.emit(Instruction::GetTemplateObject {
dst: strings_array.operand(),
strings_count: u32_from_usize(string_ops.len()),
cache: cache_index as u64,
strings: string_ops,
});
// Build arguments: [template_object, ...interpolated_expressions]
let mut argument_regs = vec![strings_array];
for i in (1..data.expressions.len()).step_by(2) {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&data.expressions[i], generator, None);
argument_regs.push(val);
}
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
let this_op = this_value.unwrap_or_else(|| generator.add_constant_undefined());
let arguments: Vec<Operand> = argument_regs.iter().map(|a| a.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Call {
dst: dst.operand(),
callee: tag_reg.operand(),
this_value: this_op.operand(),
argument_count: u32_from_usize(arguments.len()),
expression_string: None,
arguments,
});
dst
}
// =============================================================================
// Switch statement
// =============================================================================
fn generate_switch_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &SwitchStatementData,
_preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
2026-02-24 08:36:14 -03:00
let discriminant = generate_expression(&data.discriminant, generator, None)?;
// Block declaration instantiation: create lexical environment for
// function declarations and let/const across all switch cases.
2026-02-24 08:36:14 -03:00
let did_create_env = emit_switch_block_declaration_instantiation(generator, data);
if did_create_env {
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
}
// Create first test block and jump to it.
2026-02-24 08:36:14 -03:00
let first_test_block = generator.make_block();
generator.emit(Instruction::Jump {
target: first_test_block,
});
// Pre-allocate test blocks for each case with a test expression.
let mut test_blocks: Vec<Label> = Vec::with_capacity(data.cases.len());
for case in &data.cases {
if case.test.is_some() {
2026-02-24 08:36:14 -03:00
test_blocks.push(generator.make_block());
}
}
// Emit comparison chain: for each case, create the case body block,
// switch to the test block, evaluate the test, and emit comparison.
// Test blocks are interleaved with case body blocks.
let mut next_test_block = first_test_block;
let mut case_blocks: Vec<Label> = Vec::with_capacity(data.cases.len());
let mut default_block = None;
let mut test_block_index = 0;
for case in &data.cases {
2026-02-24 08:36:14 -03:00
let case_block = generator.make_block();
if let Some(test) = &case.test {
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(next_test_block);
let test_val = generate_expression(test, generator, None)?;
let cmp = generator.allocate_register();
// NB: test_value is LHS, discriminant is RHS.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::StrictlyEquals {
dst: cmp.operand(),
lhs: test_val.operand(),
rhs: discriminant.operand(),
});
next_test_block = test_blocks[test_block_index];
test_block_index += 1;
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&cmp, case_block, next_test_block);
} else {
default_block = Some(case_block);
}
case_blocks.push(case_block);
}
// Switch to the last test block and create end block.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(next_test_block);
let end_block = generator.make_block();
let labels = std::mem::take(&mut generator.pending_labels);
// Jump to default case or end block.
let fallthrough_target = default_block.unwrap_or(end_block);
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: fallthrough_target,
});
2026-02-24 08:36:14 -03:00
generator.begin_breakable_scope(end_block, labels, completion.clone());
// Emit case bodies (fall-through by default).
for (i, case) in data.cases.iter().enumerate() {
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(case_blocks[i]);
2026-02-24 08:36:14 -03:00
let saved_completion = generator.current_completion_register.clone();
if let Some(ref c) = completion {
2026-02-24 08:36:14 -03:00
generator.current_completion_register = Some(c.clone());
}
let case_scope = case.scope.borrow();
for child in &case_scope.children {
// For function declarations in switch cases: emit AnnexB hoisting
// only if the scope collector approved it (name is in annexb_function_names).
2026-02-24 08:36:14 -03:00
if did_create_env
&& let StatementKind::FunctionDeclaration(ref fd) = child.inner
&& let Some(ref name_ident) = fd.name
&& generator.annexb_function_names.contains(name_ident.name.as_slice())
2026-02-24 08:36:14 -03:00
{
let id = generator.intern_identifier(&name_ident.name);
let value = generator.allocate_register();
generator.emit(Instruction::GetBinding {
dst: value.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
generator.emit(Instruction::SetVariableBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
2026-02-24 08:36:14 -03:00
let result = generate_statement(child, generator, None);
if generator.is_current_block_terminated() {
break;
}
2026-02-24 08:36:14 -03:00
if generator.must_propagate_completion
&& let (Some(c), Some(val)) = (&completion, &result)
{
generator.emit_mov(c, val);
}
}
2026-02-24 08:36:14 -03:00
generator.current_completion_register = saved_completion;
// Fall through to next case
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() && i + 1 < case_blocks.len() {
generator.emit(Instruction::Jump {
target: case_blocks[i + 1],
});
2026-02-24 08:36:14 -03:00
} else if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: end_block });
}
}
2026-02-24 08:36:14 -03:00
generator.end_breakable_scope();
generator.switch_to_basic_block(end_block);
if did_create_env {
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
if !generator.is_current_block_terminated() {
let parent = generator.current_lexical_environment();
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
}
}
completion
}
/// Create block declaration instantiation for switch statements.
/// Function declarations and let/const declarations across all cases
/// share a single lexical environment.
fn emit_switch_block_declaration_instantiation(generator: &mut Generator, data: &SwitchStatementData) -> bool {
// Collect all statements across all cases.
let case_scopes: Vec<_> = data.cases.iter().map(|c| c.scope.borrow()).collect();
let all_children: Vec<&Statement> = case_scopes.iter().flat_map(|scope| scope.children.iter()).collect();
// Check if we need a lexical environment.
// Only needed if there are non-local lexical declarations.
let needs_env = all_children.iter().any(|child| match &child.inner {
StatementKind::FunctionDeclaration(_) => true,
StatementKind::VariableDeclaration(vd)
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const =>
{
vd.declarations.iter().any(|declaration| {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
!names.is_empty()
})
}
StatementKind::VariableDeclaration(_) => false,
StatementKind::ClassDeclaration(class_data) => class_data.name.as_ref().is_some_and(|n| !n.is_local()),
_ => false,
});
if !needs_env {
return false;
}
2026-02-24 08:36:14 -03:00
let new_env = generator.push_new_lexical_environment(0);
2026-02-24 08:36:14 -03:00
emit_lexical_declarations_for_block(generator, &new_env, all_children.iter().copied());
true
}
// =============================================================================
// Object expression
// =============================================================================
/// Generate bytecode for an object literal expression.
///
/// Objects whose shape can be determined at compile time (only simple
/// key-value properties with identifier or non-numeric string keys)
/// get shape caching for faster allocation.
fn generate_object_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
properties: &[ObjectProperty],
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
// Determine if this is a simple object literal (all KeyValue with non-computed
// string/identifier keys that are not numeric indices). Simple literals can
// benefit from shape caching. Numeric string keys like "0" are stored in
// indexed storage rather than shape-based storage, so they can't use the fast path.
//
// NB: The parser treats {["x"]: 1} identically to {"x": 1} at the AST level
// (both produce a StringLiteral key with is_computed=false). The parser keeps
// is_computed=true for bracket-enclosed keys. We normalize here by treating
// StringLiteral keys as non-computed regardless of is_computed.
let is_simple = !properties.is_empty()
&& properties.iter().all(|p| {
if p.property_type != ObjectPropertyType::KeyValue {
return false;
}
match &p.key.inner {
ExpressionKind::Identifier(_) if !p.is_computed => true,
ExpressionKind::StringLiteral(s) => !is_numeric_index_key(s),
_ => false,
}
});
let cache_index = if is_simple {
2026-02-24 08:36:14 -03:00
generator.next_object_shape_cache()
} else {
u32::MAX
};
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewObject {
dst: dst.operand(),
cache: cache_index as u64,
});
if properties.is_empty() {
return dst;
}
for (slot, property) in properties.iter().enumerate() {
if property.property_type == ObjectPropertyType::Spread {
// For spread, the source expression is in `key`, not `value`.
2026-02-24 08:36:14 -03:00
let src = generate_expression_or_undefined(&property.key, generator, None);
generator.emit(Instruction::PutBySpread {
base: dst.operand(),
src: src.operand(),
});
continue;
}
// For non-string keys (computed, numeric, etc.), evaluate key before value
// (spec evaluation order). All non-StringLiteral keys are treated the same:
// generate key → ToPrimitiveWithStringHint → generate value → PutByValue.
//
// NB: StringLiteral keys are always treated as non-computed (see is_simple comment).
let is_string_literal_key = matches!(&property.key.inner, ExpressionKind::StringLiteral(_));
let is_string_key = is_string_literal_key || matches!(&property.key.inner, ExpressionKind::Identifier(_));
let effectively_computed = property.is_computed && !is_string_literal_key;
let computed_key = if effectively_computed || !is_string_key {
2026-02-24 08:36:14 -03:00
let key = generate_expression_or_undefined(&property.key, generator, None);
let key = generator.copy_if_needed_to_preserve_evaluation_order(&key);
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ToPrimitiveWithStringHint {
dst: key.operand(),
value: key.operand(),
});
Some(key)
} else {
None
};
// Set pending LHS name for function name inference on non-computed properties.
// ProtoSetter (__proto__) skips NamedEvaluation per spec.
if !effectively_computed && property.property_type != ObjectPropertyType::ProtoSetter {
let base_name: Option<Utf16String> = match &property.key.inner {
ExpressionKind::StringLiteral(s) => Some((**s).clone()),
ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()),
_ => None,
};
if let Some(name) = base_name {
let full_name: Utf16String = match property.property_type {
ObjectPropertyType::Getter => {
let mut prefixed = Utf16String(utf16!("get ").to_vec());
prefixed.0.extend_from_slice(&name);
prefixed
}
ObjectPropertyType::Setter => {
let mut prefixed = Utf16String(utf16!("set ").to_vec());
prefixed.0.extend_from_slice(&name);
prefixed
}
_ => name,
};
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = Some(generator.intern_identifier(&full_name));
} else {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = None;
}
} else {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = None;
}
// Methods, getters, and setters need the object as their [[HomeObject]]
// so that super property lookups work.
let is_method_like = property.is_method
|| property.property_type == ObjectPropertyType::Getter
|| property.property_type == ObjectPropertyType::Setter;
if is_method_like {
2026-02-24 08:36:14 -03:00
generator.home_objects.push(dst.clone());
}
let value = property
.value
.as_ref()
2026-02-24 08:36:14 -03:00
.and_then(|v| generate_expression(v, generator, None))
.unwrap_or_else(|| generator.add_constant_undefined());
if is_method_like {
2026-02-24 08:36:14 -03:00
generator.home_objects.pop();
}
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = None;
match property.property_type {
ObjectPropertyType::Spread => {
unreachable!("spread properties are handled before this point")
}
ObjectPropertyType::KeyValue => {
if let Some(key_val) = &computed_key {
2026-02-24 08:36:14 -03:00
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Own);
} else if is_simple {
emit_object_property_set_by_key(
2026-02-24 08:36:14 -03:00
generator,
&dst,
&property.key,
&value,
u32_from_usize(slot),
cache_index,
false,
);
} else {
// Non-simple object: use PutOwnById instead of InitObjectLiteralProperty
let property_key = match &property.key.inner {
ExpressionKind::Identifier(ident) => generator.intern_property_key(&ident.name),
2026-02-24 08:36:14 -03:00
ExpressionKind::StringLiteral(s) => generator.intern_property_key(s),
_ => {
emit_object_property_set_by_key(
2026-02-24 08:36:14 -03:00
generator,
&dst,
&property.key,
&value,
u32_from_usize(slot),
cache_index,
false,
);
continue;
}
};
2026-02-24 08:36:14 -03:00
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: dst.operand(),
property: property_key,
src: value.operand(),
cache: cache as u64,
base_identifier: None,
kind: 4,
});
}
}
ObjectPropertyType::Getter => {
if let Some(key_val) = &computed_key {
2026-02-24 08:36:14 -03:00
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Getter);
} else {
emit_object_accessor_by_key(generator, &dst, &property.key, &value, true, false);
}
}
ObjectPropertyType::Setter => {
if let Some(key_val) = &computed_key {
2026-02-24 08:36:14 -03:00
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Setter);
} else {
emit_object_accessor_by_key(generator, &dst, &property.key, &value, false, false);
}
}
ObjectPropertyType::ProtoSetter => {
2026-02-24 08:36:14 -03:00
let key = generator.intern_property_key(utf16!("__proto__"));
let cache = generator.next_property_lookup_cache();
generator.emit(Instruction::PutById {
base: dst.operand(),
property: key,
src: value.operand(),
cache: cache as u64,
base_identifier: None,
kind: 3,
});
}
}
}
if is_simple {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CacheObjectShape {
object: dst.operand(),
cache: cache_index as u64,
});
}
dst
}
/// Emit a property set for an object literal key (static or computed).
fn emit_object_property_set_by_key(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
object: &ScopedOperand,
key: &Expression,
value: &ScopedOperand,
slot: u32,
cache_index: u32,
is_computed: bool,
) {
if is_computed {
2026-02-24 08:36:14 -03:00
let key_val = generate_expression_or_undefined(key, generator, None);
generator.emit(Instruction::PutByValue {
base: object.operand(),
property: key_val.operand(),
src: value.operand(),
base_identifier: None,
kind: 4,
});
return;
}
match &key.inner {
ExpressionKind::Identifier(ident) => {
2026-02-24 08:36:14 -03:00
let property_key = generator.intern_property_key(&ident.name);
generator.emit(Instruction::InitObjectLiteralProperty {
object: object.operand(),
property: property_key,
src: value.operand(),
shape_cache_index: cache_index,
property_slot: slot,
});
}
ExpressionKind::StringLiteral(s) => {
2026-02-24 08:36:14 -03:00
let property_key = generator.intern_property_key(s);
generator.emit(Instruction::InitObjectLiteralProperty {
object: object.operand(),
property: property_key,
src: value.operand(),
shape_cache_index: cache_index,
property_slot: slot,
});
}
ExpressionKind::NumericLiteral(n) => {
2026-02-24 08:36:14 -03:00
let key_val = generator.add_constant_number(*n);
generator.emit(Instruction::PutByValue {
base: object.operand(),
property: key_val.operand(),
src: value.operand(),
base_identifier: None,
kind: 4,
});
}
_ => {
// Computed key
2026-02-24 08:36:14 -03:00
let key_val = generate_expression_or_undefined(key, generator, None);
generator.emit(Instruction::PutByValue {
base: object.operand(),
property: key_val.operand(),
src: value.operand(),
base_identifier: None,
kind: 4,
});
}
}
}
/// Emit a getter/setter for an object literal key.
fn emit_object_accessor_by_key(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
object: &ScopedOperand,
key: &Expression,
value: &ScopedOperand,
is_getter: bool,
is_computed: bool,
) {
2026-02-24 08:36:14 -03:00
let emit_by_id = |generator: &mut Generator, name: &[u16]| {
let property_key = generator.intern_property_key(name);
let cache = generator.next_property_lookup_cache();
if is_getter {
generator.emit(Instruction::PutById {
base: object.operand(),
property: property_key,
src: value.operand(),
cache: cache as u64,
base_identifier: None,
kind: 1,
});
} else {
generator.emit(Instruction::PutById {
base: object.operand(),
property: property_key,
src: value.operand(),
cache: cache as u64,
base_identifier: None,
kind: 2,
});
}
};
2026-02-24 08:36:14 -03:00
let emit_by_value = |generator: &mut Generator, key: &Expression| {
let key_val = generate_expression_or_undefined(key, generator, None);
if is_getter {
generator.emit(Instruction::PutByValue {
base: object.operand(),
property: key_val.operand(),
src: value.operand(),
base_identifier: None,
kind: 1,
});
} else {
generator.emit(Instruction::PutByValue {
base: object.operand(),
property: key_val.operand(),
src: value.operand(),
base_identifier: None,
kind: 2,
});
}
};
if is_computed {
2026-02-24 08:36:14 -03:00
emit_by_value(generator, key);
return;
}
match &key.inner {
2026-02-24 08:36:14 -03:00
ExpressionKind::Identifier(ident) => emit_by_id(generator, &ident.name),
ExpressionKind::StringLiteral(s) => emit_by_id(generator, s),
_ => emit_by_value(generator, key),
}
}
// =============================================================================
// Optional chain
// =============================================================================
/// Generate an optional chain, writing results into pre-allocated current_value
/// and current_base registers.
fn generate_optional_chain_inner(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
base: &Expression,
references: &[OptionalChainReference],
current_value: &ScopedOperand,
current_base: &ScopedOperand,
) -> Option<()> {
// Evaluate base expression.
let new_current_value = match &base.inner {
ExpressionKind::Member(member_data) => {
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
// For super property access, resolve this binding first (before
// ResolveSuperBase) per spec evaluation order.
let this_value = if is_super {
2026-02-24 08:36:14 -03:00
Some(emit_resolve_this_binding(generator))
} else {
None
};
let obj = generate_expression(&member_data.object, generator, None)?;
2026-02-24 08:36:14 -03:00
let val = generator.allocate_register();
if is_super {
let this_value = this_value.unwrap();
emit_super_get(
generator,
&val,
&obj,
&member_data.property,
member_data.computed,
&this_value,
);
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_base, &this_value);
} else if member_data.computed {
let property = generate_expression(&member_data.property, generator, None)?;
2026-02-24 08:36:14 -03:00
emit_get_by_value(generator, &val, &obj, &property, None);
generator.emit_mov(current_base, &obj);
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
let base_id = intern_base_identifier(generator, &member_data.object);
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &val, &obj, &ident.name, base_id);
generator.emit_mov(current_base, &obj);
} else if let ExpressionKind::PrivateIdentifier(name) = &member_data.property.inner {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name.name);
generator.emit(Instruction::GetPrivateById {
dst: val.operand(),
base: obj.operand(),
property: id,
});
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_base, &obj);
} else {
let property = generate_expression(&member_data.property, generator, None)?;
2026-02-24 08:36:14 -03:00
emit_get_by_value(generator, &val, &obj, &property, None);
generator.emit_mov(current_base, &obj);
}
val
}
ExpressionKind::OptionalChain(oc_data) => {
generate_optional_chain_inner(
2026-02-24 08:36:14 -03:00
generator,
&oc_data.base,
&oc_data.references,
current_value,
current_base,
)?;
current_value.clone()
}
2026-02-24 08:36:14 -03:00
_ => generate_expression(base, generator, None)?,
};
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_value, &new_current_value);
// Create shared blocks: load_undefined_block is reused for all optional
// short-circuits.
2026-02-24 08:36:14 -03:00
let load_undefined_block = generator.make_block();
let end_block = generator.make_block();
for reference in references {
let is_optional = match reference {
OptionalChainReference::Call { mode, .. }
| OptionalChainReference::ComputedReference { mode, .. }
| OptionalChainReference::MemberReference { mode, .. }
| OptionalChainReference::PrivateMemberReference { mode, .. } => *mode == OptionalChainMode::Optional,
};
if is_optional {
2026-02-24 08:36:14 -03:00
let not_nullish_block = generator.make_block();
generator.emit(Instruction::JumpNullish {
condition: current_value.operand(),
true_target: load_undefined_block,
false_target: not_nullish_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(not_nullish_block);
}
match reference {
OptionalChainReference::MemberReference { identifier, .. } => {
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_base, current_value);
emit_get_by_id(generator, current_value, current_value, &identifier.name, None);
}
OptionalChainReference::ComputedReference { expression, .. } => {
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_base, current_value);
let property = generate_expression(expression, generator, None)?;
emit_get_by_value(generator, current_value, current_value, &property, None);
}
OptionalChainReference::Call { arguments, .. } => {
2026-02-24 08:36:14 -03:00
let arguments_array = generate_arguments_array(generator, arguments);
generator.emit(Instruction::CallWithArgumentArray {
dst: current_value.operand(),
callee: current_value.operand(),
this_value: current_base.operand(),
arguments: arguments_array.operand(),
expression_string: None,
});
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
generator.emit_mov(current_base, &undef);
}
OptionalChainReference::PrivateMemberReference { private_identifier, .. } => {
2026-02-24 08:36:14 -03:00
generator.emit_mov(current_base, current_value);
let id = generator.intern_identifier(&private_identifier.name);
generator.emit(Instruction::GetPrivateById {
dst: current_value.operand(),
base: current_value.operand(),
property: id,
});
}
}
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: end_block });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(load_undefined_block);
let undef = generator.add_constant_undefined();
generator.emit_mov(current_value, &undef);
generator.emit(Instruction::Jump { target: end_block });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
Some(())
}
/// Convert arguments to an array for CallWithArgumentArray.
fn generate_arguments_array(generator: &mut Generator, arguments: &[CallArgument]) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
let dst = generator.allocate_register();
if arguments.is_empty() {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewArray {
dst: dst.operand(),
element_count: 0,
elements: vec![],
});
return dst;
}
let first_spread = arguments.iter().position(|a| a.is_spread).unwrap_or(arguments.len());
let mut arg_holders = Vec::with_capacity(first_spread);
for argument in &arguments[..first_spread] {
2026-02-24 08:36:14 -03:00
let reg = generator.allocate_register();
let val = generate_expression_or_undefined(&argument.value, generator, None);
generator.emit_mov(&reg, &val);
arg_holders.push(reg);
}
let arg_ops: Vec<Operand> = arg_holders.iter().map(|a| a.operand()).collect();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewArray {
dst: dst.operand(),
element_count: u32_from_usize(arg_ops.len()),
elements: arg_ops,
});
// NB: arg_holders stays alive until function return so their registers
// aren't reused during the spread arguments loop.
for argument in &arguments[first_spread..] {
2026-02-24 08:36:14 -03:00
let val = generate_expression_or_undefined(&argument.value, generator, None);
generator.emit(Instruction::ArrayAppend {
dst: dst.operand(),
src: val.operand(),
is_spread: argument.is_spread,
});
}
drop(arg_holders);
dst
}
// =============================================================================
// Class expression
// =============================================================================
/// Generate bytecode for a class expression or declaration.
///
/// Creates a ClassBlueprint via FFI containing the constructor SFD,
/// class elements (methods, fields, accessors, static initializers),
/// and then emits a NewClass instruction that creates the class at runtime.
fn generate_class_expression(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &ClassData,
preferred_dst: Option<&ScopedOperand>,
) -> ScopedOperand {
let has_super = data.super_class.is_some();
// Always consume pending_lhs_name. Named classes don't use it, but we
// must clear it to prevent it from leaking to nested expressions.
let lhs_name = if data.name.is_none() {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name.take()
} else {
generator.pending_lhs_name = None;
None
};
// Step 2: Save parent environment, create class lexical environment.
2026-02-24 08:36:14 -03:00
let parent_env = generator.current_lexical_environment();
let class_env = generator.allocate_register();
generator.emit(Instruction::CreateLexicalEnvironment {
dst: class_env.operand(),
parent: parent_env.operand(),
capacity: 0,
});
generator.lexical_environment_register_stack.push(class_env.clone());
// Step 3.a: Create binding for the class name in the class environment.
// Only emit when the class has a name, or when there's no lhs_name
// (skip this for anonymous classes with lhs_name).
if data.name.is_some() || lhs_name.is_none() {
let name = if let Some(name_ident) = &data.name {
name_ident.name.to_utf16_string()
} else {
Utf16String::new()
};
2026-02-24 08:36:14 -03:00
let name_id = generator.intern_identifier(&name);
generator.emit(Instruction::CreateVariable {
identifier: name_id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: true,
is_global: false,
is_strict: false,
});
}
// Evaluate super class if present
let super_class = if let Some(super_expression) = &data.super_class {
2026-02-24 08:36:14 -03:00
generate_expression(super_expression, generator, None)
} else {
None
};
// Create private environment for private class elements.
let mut has_private_env = false;
for element_node in &data.elements {
let priv_name = match &element_node.inner {
ClassElement::Method { key, .. } | ClassElement::Field { key, .. } => {
if let ExpressionKind::PrivateIdentifier(ident) = &key.inner {
Some(ident.name.clone())
} else {
None
}
}
ClassElement::StaticInitializer { .. } => None,
};
if let Some(name) = priv_name {
if !has_private_env {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CreatePrivateEnvironment);
has_private_env = true;
}
2026-02-24 08:36:14 -03:00
let name_id = generator.intern_identifier(&name);
generator.emit(Instruction::AddPrivateName { name: name_id });
}
}
// First pass: evaluate all computed property keys.
// This must happen before registering the constructor and method SFDs,
// using a two-pass structure.
let mut element_keys: Vec<Option<ScopedOperand>> = Vec::with_capacity(data.elements.len());
for element_node in &data.elements {
match &element_node.inner {
ClassElement::Method { key, .. } => {
if !is_private_key(key) {
2026-02-24 08:36:14 -03:00
let key_val = generate_expression(key, generator, None);
element_keys.push(key_val);
} else {
element_keys.push(None);
}
}
ClassElement::Field { key, .. } => {
if !is_private_key(key) {
2026-02-24 08:36:14 -03:00
let key_val = generate_expression(key, generator, None);
element_keys.push(key_val);
} else {
element_keys.push(None);
}
}
ClassElement::StaticInitializer { .. } => {
element_keys.push(None);
}
}
}
// Create SharedFunctionInstanceData for constructor
let constructor_sfd_index = if let Some(ctor_expression) = &data.constructor {
// Explicit constructor — extract FunctionData from the expression
if let ExpressionKind::Function(function_id) = &ctor_expression.inner {
2026-02-24 08:36:14 -03:00
let function_data = generator.function_table.take(*function_id);
emit_new_function(generator, function_data, None)
} else {
// Fallback: synthesize a default constructor
2026-02-24 08:36:14 -03:00
emit_default_constructor(generator, has_super)
}
} else {
// No explicit constructor — synthesize a default one
2026-02-24 08:36:14 -03:00
emit_default_constructor(generator, has_super)
};
// Second pass: register method/field SFDs and build element descriptors.
let mut class_elements = Vec::with_capacity(data.elements.len());
for element_node in &data.elements {
match &element_node.inner {
ClassElement::Method {
key,
function,
kind,
is_static,
} => {
let ffi_kind = match kind {
ClassMethodKind::Method => ClassElementKind::Method as u8,
ClassMethodKind::Getter => ClassElementKind::Getter as u8,
ClassMethodKind::Setter => ClassElementKind::Setter as u8,
};
// Create SFD for the method function.
// Don't set the method name here — the runtime's update_function_name
// in construct_class sets it from the evaluated property key, which
// correctly handles computed keys (Symbols, etc).
let sfd_index = if let ExpressionKind::Function(function_id) = &function.inner {
2026-02-24 08:36:14 -03:00
let function_data = generator.function_table.take(*function_id);
Some(emit_new_function(generator, function_data, None))
} else {
None
};
// Handle computed vs static keys
let is_private = is_private_key(key);
class_elements.push(PendingClassElement {
kind: ffi_kind,
is_static: *is_static,
is_private,
private_identifier: get_private_identifier_name(key),
shared_function_data_index: sfd_index,
has_initializer: false,
literal_value_kind: PendingLiteralValueKind::None,
literal_value_number: 0.0,
literal_value_string: None,
});
}
ClassElement::Field {
key,
initializer,
is_static,
} => {
// Detect literal initializers and store the value directly,
// avoiding function creation for simple cases like x = 0.
// This avoids function creation for simple cases.
let mut literal_value_kind = PendingLiteralValueKind::None;
let mut literal_value_number: f64 = 0.0;
let mut literal_value_string = None;
let mut sfd_index = None;
if let Some(init_expression) = initializer {
let is_literal = match &init_expression.inner {
ExpressionKind::NumericLiteral(n) => {
literal_value_kind = PendingLiteralValueKind::Number;
literal_value_number = *n;
true
}
ExpressionKind::BooleanLiteral(b) => {
literal_value_kind = if *b {
PendingLiteralValueKind::BooleanTrue
} else {
PendingLiteralValueKind::BooleanFalse
};
true
}
ExpressionKind::NullLiteral => {
literal_value_kind = PendingLiteralValueKind::Null;
true
}
ExpressionKind::StringLiteral(s) => {
literal_value_kind = PendingLiteralValueKind::String;
literal_value_string = Some((**s).clone());
true
}
ExpressionKind::Unary { op, operand } if *op == UnaryOp::Minus => {
if let ExpressionKind::NumericLiteral(n) = &operand.inner {
literal_value_kind = PendingLiteralValueKind::Number;
literal_value_number = -n;
true
} else {
false
}
}
_ => false,
};
if !is_literal {
// Determine field name for anonymous function naming.
let field_name = match &key.inner {
ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(),
ExpressionKind::StringLiteral(s) => (**s).clone(),
ExpressionKind::PrivateIdentifier(p) => p.name.clone(),
ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n),
ExpressionKind::BigIntLiteral(s) => {
let digits = s.strip_suffix('n').unwrap_or(s.as_str());
Utf16String(digits.encode_utf16().collect())
}
_ => Utf16String::new(),
};
// Wrap the expression in a ClassFieldInitializer statement.
let body_statement = Statement::new(
init_expression.range,
StatementKind::ClassFieldInitializer(Box::new(ClassFieldInitializerData {
expression: Box::new(init_expression.as_ref().clone()),
field_name,
})),
);
let wrapper_body = Statement::new(
init_expression.range,
StatementKind::Block(ScopeData::shared_with_children(vec![body_statement])),
);
// Class bodies are always strict mode.
let function_data = Box::new(FunctionData {
name: None,
source_text_start: init_expression.range.start.offset,
source_text_end: init_expression.range.end.offset,
body: Box::new(wrapper_body),
parameters: Vec::new(),
function_length: 0,
kind: FunctionKind::Normal,
is_strict_mode: true,
is_arrow_function: false,
parsing_insights: FunctionParsingInsights {
uses_this: true,
uses_this_from_environment: true,
..Default::default()
},
// This wrapper is synthesized after parsing, so it
// asks FunctionTable to discover nested functions
// from the wrapped initializer expression.
nested_function_ids: None,
});
let index = emit_new_function(generator, function_data, Some(utf16!("field")));
let key_is_private = is_private_key(key);
let key_name: Utf16String = match &key.inner {
ExpressionKind::PrivateIdentifier(ident) => ident.name.clone(),
ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(),
ExpressionKind::StringLiteral(s) => (**s).clone(),
ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n),
_ => Utf16String::new(),
};
if !key_name.is_empty() {
generator.set_class_field_initializer_name(index, key_name, key_is_private);
}
sfd_index = Some(index);
}
}
let is_private = is_private_key(key);
class_elements.push(PendingClassElement {
kind: ClassElementKind::Field as u8,
is_static: *is_static,
is_private,
private_identifier: get_private_identifier_name(key),
shared_function_data_index: sfd_index,
has_initializer: initializer.is_some(),
literal_value_kind,
literal_value_number,
literal_value_string,
});
}
ClassElement::StaticInitializer { body } => {
// Wrap the static block body in a function.
// Class bodies are always strict mode.
let function_data = Box::new(FunctionData {
name: None,
source_text_start: body.range.start.offset,
source_text_end: body.range.end.offset,
body: body.clone(),
parameters: Vec::new(),
function_length: 0,
kind: FunctionKind::Normal,
is_strict_mode: true,
is_arrow_function: false,
parsing_insights: FunctionParsingInsights {
uses_this: true,
uses_this_from_environment: true,
..Default::default()
},
// Static initializer wrappers are synthesized after parsing;
// keep the structural fallback for nested functions inside
// the wrapped block.
nested_function_ids: None,
});
let sfd_index = Some(emit_new_function(generator, function_data, None));
class_elements.push(PendingClassElement {
kind: ClassElementKind::StaticInitializer as u8,
is_static: true,
is_private: false,
private_identifier: None,
shared_function_data_index: sfd_index,
has_initializer: false,
literal_value_kind: PendingLiteralValueKind::None,
literal_value_number: 0.0,
literal_value_string: None,
});
}
}
}
// Get class name and source text
let has_name = data.name.is_some();
let source_start = data.source_text_start as usize;
let source_end = data.source_text_end as usize;
let source_text_len = source_end - source_start;
let blueprint_index = generator.register_class_blueprint(PendingClassBlueprint {
name: data.name.as_ref().map(|n| n.name.to_utf16_string()),
source_text_offset: source_start,
source_text_length: source_text_len,
constructor_sfd_index,
has_super_class: has_super,
has_name,
elements: class_elements,
});
// Build element_keys operands for the NewClass instruction
let element_key_ops: Vec<Option<Operand>> = element_keys.iter().map(|k| k.as_ref().map(|s| s.operand())).collect();
// Restore parent environment before emitting NewClass.
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent_env.operand(),
});
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
// Allocate dst after element keys.
2026-02-24 08:36:14 -03:00
let dst = choose_dst(generator, preferred_dst);
// Emit NewClass instruction
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewClass {
dst: dst.operand(),
super_class: super_class.as_ref().map(|s| s.operand()),
class_environment: class_env.operand(),
class_blueprint_index: blueprint_index,
lhs_name,
element_keys_count: u32_from_usize(element_key_ops.len()),
element_keys: element_key_ops,
});
if has_private_env {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::LeavePrivateEnvironment);
}
dst
}
/// Synthesize a default constructor SharedFunctionInstanceData.
2026-02-24 08:36:14 -03:00
fn emit_default_constructor(generator: &mut Generator, has_super: bool) -> u32 {
use crate::parser::{Parser, ProgramType};
// Wrap in "function" keyword so it parses as a FunctionDeclaration.
let source: Utf16String = if has_super {
Utf16String::from(utf16!("function constructor(...arguments) { super(...arguments); }"))
} else {
Utf16String::from(utf16!("function constructor() {}"))
};
let mut parser = Parser::new(&source, ProgramType::Script);
if has_super {
parser.flags.allow_super_constructor_call = true;
}
let program = parser.parse_program(false);
parser.scope_collector.analyze(false);
assert!(!parser.has_errors(), "default constructor parse failed");
// Extract FunctionData from the parsed program.
let function_id = if let StatementKind::Program(ref data) = program.inner {
let scope = data.scope.borrow();
scope.children.iter().find_map(|child| {
if let StatementKind::FunctionDeclaration(fd) = &child.inner {
Some(fd.function_id)
} else {
None
}
})
} else {
None
};
let function_id = function_id.expect("default constructor: no FunctionDeclaration found");
let mut function_data = parser.function_table.take(function_id);
// Zero out source text range since this is synthetic source,
// not part of the original source code buffer.
function_data.source_text_start = 0;
function_data.source_text_end = 0;
let subtable = parser.function_table.extract_reachable(&function_data);
generator.register_shared_function_data(PendingSharedFunctionData {
function_data: Some(function_data),
subtable: Some(subtable),
name_override: None,
class_field_initializer_name: None,
})
}
/// Check if a key expression is a private identifier, return (is_private, private_name).
fn is_private_key(key: &Expression) -> bool {
matches!(&key.inner, ExpressionKind::PrivateIdentifier(_))
}
/// Get a pointer directly into the AST's PrivateIdentifier name.
/// The pointer remains valid as long as the AST is alive.
fn get_private_identifier_name(key: &Expression) -> Option<Utf16String> {
if let ExpressionKind::PrivateIdentifier(ident) = &key.inner {
Some(ident.name.clone())
} else {
None
}
}
/// Check if a for-in/for-of LHS is a `let`/`const` declaration with non-local identifiers,
/// meaning we need a per-iteration lexical environment.
fn for_in_of_needs_lexical_env(lhs: &ForInOfLhs) -> bool {
2026-02-24 08:36:14 -03:00
if let ForInOfLhs::Declaration(statement) = lhs
&& let StatementKind::VariableDeclaration(vd) = &statement.inner
&& (vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const)
2026-02-24 08:36:14 -03:00
{
let mut names = Vec::new();
for declaration in &vd.declarations {
2026-02-24 08:36:14 -03:00
collect_target_names(&declaration.target, &mut names);
}
2026-02-24 08:36:14 -03:00
return !names.is_empty();
}
false
}
/// Collect all non-local binding names from a variable declarator target.
fn collect_target_names(target: &VariableDeclaratorTarget, names: &mut Vec<(Utf16String, bool)>) {
match target {
VariableDeclaratorTarget::Identifier(ident) => {
if !ident.is_local() {
names.push((ident.name.to_utf16_string(), false));
}
}
VariableDeclaratorTarget::BindingPattern(pattern) => {
collect_pattern_binding_names(pattern, names);
}
}
}
/// Collect all non-local binding names from a binding pattern (recursive).
fn collect_pattern_binding_names(pattern: &BindingPattern, names: &mut Vec<(Utf16String, bool)>) {
for entry in &pattern.entries {
match &entry.alias {
Some(BindingEntryAlias::Identifier(ident)) => {
if !ident.is_local() {
names.push((ident.name.to_utf16_string(), false));
}
}
Some(BindingEntryAlias::BindingPattern(sub)) => {
collect_pattern_binding_names(sub, names);
}
None => {
2026-02-24 08:36:14 -03:00
if let Some(BindingEntryName::Identifier(ident)) = &entry.name
&& !ident.is_local()
{
names.push((ident.name.to_utf16_string(), false));
}
}
Some(BindingEntryAlias::MemberExpression(_)) => {}
}
}
}
/// Create a per-iteration lexical environment for for-in/for-of `let`/`const` declarations.
/// Returns the parent environment register so we can restore it later.
2026-02-24 08:36:14 -03:00
fn create_for_in_of_lexical_env(generator: &mut Generator, lhs: &ForInOfLhs) -> ScopedOperand {
let parent = generator.current_lexical_environment();
// Collect all binding names to determine capacity.
let mut binding_names: Vec<(Utf16String, bool)> = Vec::new();
let mut is_constant = false;
2026-02-24 08:36:14 -03:00
if let ForInOfLhs::Declaration(statement) = lhs
&& let StatementKind::VariableDeclaration(vd) = &statement.inner
2026-02-24 08:36:14 -03:00
{
is_constant = vd.kind == DeclarationKind::Const;
for declaration in &vd.declarations {
2026-02-24 08:36:14 -03:00
collect_target_names(&declaration.target, &mut binding_names);
}
}
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
// Create variable bindings in the new environment.
for (name, _) in &binding_names {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: is_constant,
is_global: false,
is_strict: is_constant,
});
}
parent
}
// =============================================================================
// For-in statement
// =============================================================================
/// Create a TDZ environment for lexical declarations in for-in/for-of heads.
/// Returns true if a TDZ scope was entered (must call leave_for_in_of_head_tdz after RHS eval).
2026-02-24 08:36:14 -03:00
fn enter_for_in_of_head_tdz(generator: &mut Generator, lhs: &ForInOfLhs) -> bool {
if let ForInOfLhs::Declaration(statement) = lhs
&& let StatementKind::VariableDeclaration(vd) = &statement.inner
&& (vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const)
2026-02-24 08:36:14 -03:00
{
let mut names = Vec::new();
for declaration in &vd.declarations {
2026-02-24 08:36:14 -03:00
collect_target_names(&declaration.target, &mut names);
}
if !names.is_empty() {
generator.push_new_lexical_environment(0);
for (name, _) in &names {
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
}
2026-02-24 08:36:14 -03:00
return true;
}
}
false
}
/// Tear down the TDZ environment after RHS evaluation.
2026-02-24 08:36:14 -03:00
fn leave_for_in_of_head_tdz(generator: &mut Generator) {
generator.lexical_environment_register_stack.pop();
if !generator.is_current_block_terminated() {
let parent = generator.current_lexical_environment();
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
}
}
fn generate_for_in_of_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
kind: ForInOfKind,
lhs: &ForInOfLhs,
rhs: &Expression,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
match kind {
2026-02-24 08:36:14 -03:00
ForInOfKind::ForIn => generate_for_in_statement(generator, lhs, rhs, body, preferred_dst),
ForInOfKind::ForOf => generate_for_of_statement_inner(generator, lhs, rhs, body, preferred_dst, false),
ForInOfKind::ForAwaitOf => generate_for_of_statement_inner(generator, lhs, rhs, body, preferred_dst, true),
}
}
fn generate_for_in_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
lhs: &ForInOfLhs,
rhs: &Expression,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// B.3.5 Initializers in ForIn Statement Heads
// Evaluate the initializer for `for (var x = init in obj)` before the RHS.
2026-02-24 08:36:14 -03:00
if let ForInOfLhs::Declaration(statement) = lhs
&& let StatementKind::VariableDeclaration(vd) = &statement.inner
&& vd.kind == DeclarationKind::Var
&& let Some(declaration) = vd.declarations.first()
&& let (VariableDeclaratorTarget::Identifier(ident), Some(init)) = (&declaration.target, &declaration.init)
2026-02-24 08:36:14 -03:00
{
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
let value = generate_expression_or_undefined(init, generator, None);
generator.pending_lhs_name = None;
emit_set_variable(generator, ident, &value);
}
// Create end_block and update_block first, then nullish_block and
// continuation_block during head evaluation.
2026-02-24 08:36:14 -03:00
let end_block = generator.make_block();
let update_block = generator.make_block();
let needs_lexical_env = for_in_of_needs_lexical_env(lhs);
// B.3.5 Initializers in ForIn Statement Heads: evaluate initializer before RHS.
// Create TDZ for lexical declarations before evaluating the RHS expression.
2026-02-24 08:36:14 -03:00
let entered_tdz = enter_for_in_of_head_tdz(generator, lhs);
// Evaluate RHS into `object`, allocate the internal property iterator
// register, emit the null/undefined check + GetObjectPropertyIterator,
// then let `object` go out of scope so its register is freed before the
// loop body.
let iterator_object = {
let object = generate_expression_or_undefined(rhs, generator, None);
if entered_tdz {
leave_for_in_of_head_tdz(generator);
}
let iterator_object = generator.allocate_register();
// Check for null/undefined
let nullish_block = generator.make_block();
let continue_block = generator.make_block();
generator.emit(Instruction::JumpNullish {
condition: object.operand(),
true_target: nullish_block,
false_target: continue_block,
});
generator.switch_to_basic_block(nullish_block);
generator.emit(Instruction::Jump { target: end_block });
generator.switch_to_basic_block(continue_block);
// Get property iterator
let cache = generator.next_object_property_iterator_cache();
generator.emit(Instruction::GetObjectPropertyIterator {
dst_iterator: iterator_object.operand(),
object: object.operand(),
cache: cache as u64,
});
iterator_object
};
// Body evaluation: completion, then jump to update block.
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
generator.emit(Instruction::Jump { target: update_block });
// Update: get next value
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(update_block);
let next_value = generator.allocate_register();
let done = generator.allocate_register();
generator.emit(Instruction::ObjectPropertyIteratorNext {
dst_value: next_value.operand(),
dst_done: done.operand(),
iterator_object: iterator_object.operand(),
});
2026-02-24 08:36:14 -03:00
let loop_continue_block = generator.make_block();
generator.emit_jump_if(&done, end_block, loop_continue_block);
generator.switch_to_basic_block(loop_continue_block);
// Create per-iteration lexical environment for let/const declarations.
if needs_lexical_env {
2026-02-24 08:36:14 -03:00
create_for_in_of_lexical_env(generator, lhs);
}
// Assign to LHS
2026-02-24 08:36:14 -03:00
assign_to_for_in_of_lhs(generator, lhs, &next_value);
// Body — break/continue handle environment restoration via LeaveLexicalEnvironment boundary.
2026-02-24 08:36:14 -03:00
let labels = std::mem::take(&mut generator.pending_labels);
generator.begin_continuable_scope(update_block, labels.clone(), completion.clone());
generator.begin_breakable_scope(end_block, labels, completion.clone());
if needs_lexical_env {
2026-02-24 08:36:14 -03:00
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
}
if !generator.is_current_block_terminated() {
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
}
if needs_lexical_env {
2026-02-24 08:36:14 -03:00
generator.end_variable_scope();
}
2026-02-24 08:36:14 -03:00
generator.end_breakable_scope();
generator.end_continuable_scope();
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: update_block });
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
completion
}
// =============================================================================
// Labelled statement
// =============================================================================
fn generate_labelled_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
label: &Utf16String,
item: &Statement,
preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
// Collect all labels from nested Labelled statements.
let mut labels = vec![label.clone()];
let mut inner = item;
while let StatementKind::Labelled(labelled_data) = &inner.inner {
labels.push(labelled_data.label.clone());
inner = &labelled_data.item;
}
// For iteration/switch statements, set pending_labels so that
// begin_breakable_scope/begin_continuable_scope pick them up.
// NB: The parser wraps for/for-in/for-of loops in a Block for scope
// management, so we look through single-child Block wrappers.
let block_scope_borrow;
let effective_inner = if let StatementKind::Block(ref scope) = inner.inner {
block_scope_borrow = scope.borrow();
if block_scope_borrow.children.len() == 1 {
&block_scope_borrow.children[0]
} else {
inner
}
} else {
inner
};
let is_iteration_or_switch = matches!(
&effective_inner.inner,
2026-03-22 15:41:19 -03:00
StatementKind::For(_)
| StatementKind::ForInOf(_)
| StatementKind::While(_)
| StatementKind::DoWhile(_)
| StatementKind::Switch(_)
);
if is_iteration_or_switch {
2026-02-24 08:36:14 -03:00
let previous_labels = std::mem::replace(&mut generator.pending_labels, labels);
let result = generate_statement(inner, generator, preferred_dst);
generator.pending_labels = previous_labels;
result
} else {
// Non-iteration: wrap in a breakable scope so `break label;` works.
2026-02-24 08:36:14 -03:00
let end_block = generator.make_block();
generator.begin_breakable_scope(end_block, labels, None);
let result = generate_statement(inner, generator, preferred_dst);
generator.end_breakable_scope();
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Jump { target: end_block });
}
generator.switch_to_basic_block(end_block);
result
}
}
// =============================================================================
// For-of statement
// =============================================================================
/// Shared implementation for for-of and for-await-of with iterator close.
fn generate_for_of_statement_inner(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
lhs: &ForInOfLhs,
rhs: &Expression,
body: &Statement,
preferred_dst: Option<&ScopedOperand>,
is_await: bool,
) -> Option<ScopedOperand> {
// Create end_block and update_block before evaluating the RHS expression.
// This ensures loop blocks get lower block numbers than any blocks created
// during RHS evaluation (e.g. conditional expressions).
let end_block = generator.make_block();
let update_block = generator.make_block();
// Create TDZ for lexical declarations before evaluating the RHS expression.
2026-02-24 08:36:14 -03:00
let entered_tdz = enter_for_in_of_head_tdz(generator, lhs);
let needs_lexical_env = for_in_of_needs_lexical_env(lhs);
2026-02-24 08:36:14 -03:00
let old_handler = generator.current_unwind_handler;
// Evaluate RHS into `object`, allocate iterator registers, and emit
// GetIterator. The block scopes `object` so its register is freed
// before the loop body.
let (iterator_object, iterator_next_method, iterator_done) = {
let object = generate_expression_or_undefined(rhs, generator, None);
if entered_tdz {
leave_for_in_of_head_tdz(generator);
}
let iterator_object = generator.allocate_register();
let iterator_next_method = generator.allocate_register();
let iterator_done = generator.allocate_register();
generator.emit(Instruction::GetIterator {
dst_iterator_object: iterator_object.operand(),
dst_iterator_next: iterator_next_method.operand(),
dst_iterator_done: iterator_done.operand(),
iterable: object.operand(),
hint: if is_await {
IteratorHint::Async
} else {
IteratorHint::Sync
} as u32,
});
(iterator_object, iterator_next_method, iterator_done)
};
2026-02-24 08:36:14 -03:00
let completion = generator.allocate_completion_register();
// Set up iterator close via synthetic FinallyContext.
2026-02-24 08:36:14 -03:00
let close_completion_type = generator.allocate_register();
let close_completion_value = generator.allocate_register();
let exception_preamble_block = generator.make_block();
let iterator_close_body_block = generator.make_block();
let lexical_env_at_entry = generator.lexical_environment_register_stack.last().cloned();
let parent_index = generator.current_finally_context;
generator.push_finally_context(FinallyContext {
completion_type: close_completion_type.clone(),
completion_value: close_completion_value.clone(),
finally_body: iterator_close_body_block,
exception_preamble: exception_preamble_block,
parent_index,
registered_jumps: Vec::new(),
next_jump_index: FinallyContext::FIRST_JUMP_INDEX,
lexical_environment_at_entry: lexical_env_at_entry.clone(),
saved_unwind_handler: None,
});
// Break scope wraps the ReturnToFinally so break hits ReturnToFinally first.
2026-02-24 08:36:14 -03:00
let labels = std::mem::take(&mut generator.pending_labels);
generator.begin_breakable_scope(end_block, labels.clone(), completion.clone());
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
generator.emit(Instruction::Jump { target: update_block });
// Update: get next value
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(update_block);
let next_value = generator.allocate_register();
let done = generator.allocate_register();
if is_await {
// For-await-of: Call iterator.next(), await the result, then unpack.
2026-02-24 08:36:14 -03:00
let next_result = generator.allocate_register();
generator.emit(Instruction::IteratorNext {
dst: next_result.operand(),
iterator_object: iterator_object.operand(),
iterator_next: iterator_next_method.operand(),
iterator_done: iterator_done.operand(),
});
// Await the next result. Pre-allocate completion registers and emit
// Mov(received_completion, accumulator) before Await.
2026-02-24 08:36:14 -03:00
let received_completion = generator.allocate_register();
let received_completion_type = generator.allocate_register();
let received_completion_value = generator.allocate_register();
let acc = generator.accumulator();
generator.emit_mov(&received_completion, &acc);
let awaited = generate_await_with_completions(
2026-02-24 08:36:14 -03:00
generator,
&next_result,
&received_completion,
&received_completion_type,
&received_completion_value,
);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&next_result, &awaited);
// Type check
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::ThrowIfNotObject {
src: next_result.operand(),
});
// IteratorComplete — get .done property
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &done, &next_result, utf16!("done"), None);
2026-02-24 08:36:14 -03:00
let loop_continue_block = generator.make_block();
generator.emit_jump_if(&done, end_block, loop_continue_block);
generator.switch_to_basic_block(loop_continue_block);
// IteratorValue — get .value property
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &next_value, &next_result, utf16!("value"), None);
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::IteratorNextUnpack {
dst_value: next_value.operand(),
dst_done: done.operand(),
iterator_object: iterator_object.operand(),
iterator_next: iterator_next_method.operand(),
iterator_done: iterator_done.operand(),
});
2026-02-24 08:36:14 -03:00
let loop_continue_block = generator.make_block();
generator.emit_jump_if(&done, end_block, loop_continue_block);
generator.switch_to_basic_block(loop_continue_block);
}
// Set up exception handler AFTER iterator-next section.
// Per spec, exceptions from IteratorNext/Await/IteratorComplete/IteratorValue
// propagate directly; only LHS assignment and body exceptions trigger close.
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = Some(exception_preamble_block);
let loop_body_block = generator.make_block();
generator.emit(Instruction::Jump {
target: loop_body_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(loop_body_block);
// Create per-iteration lexical environment for let/const declarations.
let parent_env = if needs_lexical_env {
2026-02-24 08:36:14 -03:00
Some(create_for_in_of_lexical_env(generator, lhs))
} else {
None
};
// Assign to LHS
2026-02-24 08:36:14 -03:00
assign_to_for_in_of_lhs(generator, lhs, &next_value);
// Body
2026-02-24 08:36:14 -03:00
generator.begin_continuable_scope(update_block, labels, completion.clone());
if needs_lexical_env {
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
}
if !generator.is_current_block_terminated() {
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
}
// Restore lexical env before continuing
if needs_lexical_env {
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.pop();
}
2026-02-24 08:36:14 -03:00
generator.end_continuable_scope();
2026-02-24 08:36:14 -03:00
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
generator.end_breakable_scope();
// Pop the FinallyContext.
let finally_ctx_index = generator.current_finally_context.expect("no active finally context");
2026-02-24 08:36:14 -03:00
generator.current_finally_context = generator.finally_contexts[finally_ctx_index].parent_index;
// Restore unwind handler
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = old_handler;
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
if needs_lexical_env {
let parent = parent_env
.as_ref()
.expect("parent_env must be set when restoring lexical environment");
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalEnvironment {
environment: parent.operand(),
});
}
generator.emit(Instruction::Jump { target: update_block });
}
// --- Exception preamble: catch thrown exception, route to iterator close ---
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(exception_preamble_block);
generator.emit(Instruction::Catch {
dst: close_completion_value.operand(),
});
if let Some(env) = &lexical_env_at_entry {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalEnvironment {
environment: env.operand(),
});
}
2026-02-24 08:36:14 -03:00
let throw_const = generator.add_constant_i32(FinallyContext::THROW);
generator.emit_mov(&close_completion_type, &throw_const);
generator.emit(Instruction::Jump {
target: iterator_close_body_block,
});
// --- Iterator close body: dispatch based on completion type ---
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(iterator_close_body_block);
// THROW path
2026-02-24 08:36:14 -03:00
let throw_close_block = generator.make_block();
let non_throw_close_block = generator.make_block();
let throw_check_const = generator.add_constant_i32(FinallyContext::THROW);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: close_completion_type.operand(),
rhs: throw_check_const.operand(),
true_target: throw_close_block,
false_target: non_throw_close_block,
});
// Non-throw close: close the iterator with Normal completion, then dispatch.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(non_throw_close_block);
if is_await {
// For async iterators, inline AsyncIteratorClose using GetMethod+Call+Await
// instead of the synchronous IteratorClose instruction. This avoids spinning
// the event loop inside bytecode execution.
2026-02-24 08:36:14 -03:00
let after_close = generator.make_block();
let return_method = generator.allocate_register();
let return_key = generator.intern_property_key(utf16!("return"));
generator.emit(Instruction::GetMethod {
dst: return_method.operand(),
object: iterator_object.operand(),
property: return_key,
});
2026-02-24 08:36:14 -03:00
let call_return_block = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: return_method.operand(),
true_target: after_close,
false_target: call_return_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(call_return_block);
2026-02-24 08:36:14 -03:00
let inner_result = generator.allocate_register();
generator.emit(Instruction::Call {
dst: inner_result.operand(),
callee: return_method.operand(),
this_value: iterator_object.operand(),
expression_string: None,
argument_count: 0,
arguments: vec![],
});
// Pre-allocate completion registers in this scope so they're freed
// together with return_method and inner_result.
2026-02-24 08:36:14 -03:00
let rc = generator.allocate_register();
let rct = generator.allocate_register();
let rcv = generator.allocate_register();
let awaited = generate_await_with_completions(generator, &inner_result, &rc, &rct, &rcv);
generator.emit(Instruction::ThrowIfNotObject { src: awaited.operand() });
generator.emit(Instruction::Jump { target: after_close });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(after_close);
} else {
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
generator.emit(Instruction::IteratorClose {
iterator_object: iterator_object.operand(),
iterator_next: iterator_next_method.operand(),
iterator_done: iterator_done.operand(),
completion_type: CompletionType::Normal as u32,
completion_value: undef.operand(),
});
}
// Dispatch registered jumps (break/continue targets).
let registered_jumps = std::mem::take(&mut generator.finally_contexts[finally_ctx_index].registered_jumps);
for jump in &registered_jumps {
2026-02-24 08:36:14 -03:00
let after_check = generator.make_block();
let jump_const = generator.add_constant_i32(jump.index);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: close_completion_type.operand(),
rhs: jump_const.operand(),
true_target: jump.target,
false_target: after_check,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(after_check);
}
// RETURN path
2026-02-24 08:36:14 -03:00
let return_block = generator.make_block();
let unreachable_block = generator.make_block();
let return_const = generator.add_constant_i32(FinallyContext::RETURN);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: close_completion_type.operand(),
rhs: return_const.operand(),
true_target: return_block,
false_target: unreachable_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_block);
if let Some(outer_index) = generator.current_finally_context {
let outer_ct = generator.finally_contexts[outer_index].completion_type.clone();
let outer_cv = generator.finally_contexts[outer_index].completion_value.clone();
2026-02-24 08:36:14 -03:00
let outer_fb = generator.finally_contexts[outer_index].finally_body;
generator.emit_mov(&outer_ct, &close_completion_type);
generator.emit_mov(&outer_cv, &close_completion_value);
generator.emit(Instruction::Jump { target: outer_fb });
} else if generator.is_in_generator_function() {
generator.emit(Instruction::Yield {
continuation_label: None,
value: close_completion_value.operand(),
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Return {
value: close_completion_value.operand(),
});
}
// Unreachable default: throw the value.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(unreachable_block);
generator.emit(Instruction::Throw {
src: close_completion_value.operand(),
});
// Throw close: close iterator then rethrow original exception.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(throw_close_block);
if is_await {
// Inline AsyncIteratorClose with exception handler: any error from the close
// steps is discarded and the original exception is rethrown.
2026-02-24 08:36:14 -03:00
let rethrow_block = generator.make_block();
let close_catch_block = generator.make_block();
// Set up an exception handler that catches errors from the close and rethrows original.
2026-02-24 08:36:14 -03:00
let old_close_handler = generator.current_unwind_handler;
generator.current_unwind_handler = Some(close_catch_block);
// Jump to a block created inside the unwind context so that
// GetMethod/Call/Await all have the exception handler set.
2026-02-24 08:36:14 -03:00
let close_try_block = generator.make_block();
generator.emit(Instruction::Jump {
target: close_try_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(close_try_block);
{
let return_method = generator.allocate_register();
let return_key = generator.intern_property_key(utf16!("return"));
generator.emit(Instruction::GetMethod {
dst: return_method.operand(),
object: iterator_object.operand(),
property: return_key,
});
let call_return_block = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: return_method.operand(),
true_target: rethrow_block,
false_target: call_return_block,
});
generator.switch_to_basic_block(call_return_block);
let inner_result = generator.allocate_register();
generator.emit(Instruction::Call {
dst: inner_result.operand(),
callee: return_method.operand(),
this_value: iterator_object.operand(),
expression_string: None,
argument_count: 0,
arguments: vec![],
});
let rc = generator.allocate_register();
let rct = generator.allocate_register();
let rcv = generator.allocate_register();
generate_await_with_completions(generator, &inner_result, &rc, &rct, &rcv);
// Even if close succeeded, rethrow original (spec step 5).
generator.emit(Instruction::Jump { target: rethrow_block });
}
// Exception handler: discard close error, rethrow original.
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = old_close_handler;
generator.switch_to_basic_block(close_catch_block);
let discarded = generator.allocate_register();
generator.emit(Instruction::Catch {
dst: discarded.operand(),
});
generator.emit(Instruction::Jump { target: rethrow_block });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(rethrow_block);
generator.emit(Instruction::Throw {
src: close_completion_value.operand(),
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::IteratorClose {
iterator_object: iterator_object.operand(),
iterator_next: iterator_next_method.operand(),
iterator_done: iterator_done.operand(),
completion_type: CompletionType::Throw as u32,
completion_value: close_completion_value.operand(),
});
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
generator.emit(Instruction::Throw {
src: close_completion_value.operand(),
});
}
}
// Release the FinallyContext's ScopedOperands so their registers
// can be reused.
2026-02-24 08:36:14 -03:00
let dummy = generator.add_constant_undefined();
generator.finally_contexts[finally_ctx_index].completion_type = dummy.clone();
generator.finally_contexts[finally_ctx_index].completion_value = dummy;
generator.finally_contexts[finally_ctx_index].lexical_environment_at_entry = None;
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(end_block);
completion
}
2026-02-24 08:36:14 -03:00
fn assign_to_for_in_of_lhs(generator: &mut Generator, lhs: &ForInOfLhs, value: &ScopedOperand) {
match lhs {
ForInOfLhs::Declaration(statement) => {
// UsingDeclaration: disposal semantics not yet implemented.
// UsingDeclaration is not recognized as a VariableDeclaration
// in for_in_of_head_evaluation, so it is treated as Assignment
// lhs_kind. This produces NewTypeError + Throw for the using
// declaration, followed by NewReferenceError + Throw (dead code).
if matches!(statement.inner, StatementKind::UsingDeclaration(_)) {
2026-02-24 08:36:14 -03:00
generate_statement(statement, generator, None);
let exception = generator.allocate_register();
let error_string = generator.intern_string(utf16!("Invalid left-hand side in assignment"));
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::NewReferenceError {
dst: exception.operand(),
error_string,
});
2026-02-24 08:36:14 -03:00
generator.perform_needed_unwinds();
generator.emit(Instruction::Throw {
src: exception.operand(),
});
return;
}
// The declaration is a VariableDeclaration with a single declarator
if let StatementKind::VariableDeclaration(vd) = &statement.inner
&& let Some(declaration) = vd.declarations.first()
2026-02-24 08:36:14 -03:00
{
// For var: FDI already initialized the binding, so use Set.
// For let/const: per-iteration env created new bindings needing Initialize.
let mode = match vd.kind {
2026-02-24 08:36:14 -03:00
DeclarationKind::Var => BindingMode::Set,
DeclarationKind::Let | DeclarationKind::Const => BindingMode::InitializeLexical,
};
match &declaration.target {
VariableDeclaratorTarget::Identifier(ident) => {
emit_set_variable_with_mode(generator, ident, value, mode);
}
VariableDeclaratorTarget::BindingPattern(pattern) => {
generate_binding_pattern_bytecode(generator, pattern, mode, value);
}
}
}
}
ForInOfLhs::Expression(expression) => {
2026-02-24 08:36:14 -03:00
emit_store_to_reference(generator, expression, value);
}
ForInOfLhs::Pattern(pattern) => {
2026-02-24 08:36:14 -03:00
generate_binding_pattern_bytecode(generator, pattern, BindingMode::Set, value);
}
}
}
// =============================================================================
// Binding pattern destructuring
// =============================================================================
/// Whether we are initializing a new binding or setting an existing one.
#[derive(Clone, Copy)]
enum BindingMode {
/// `const` or `let` declarations: emit InitializeLexicalBinding.
InitializeLexical,
/// Assignment expressions / var iteration: emit SetLexicalBinding or SetGlobal.
Set,
}
2026-02-24 08:36:14 -03:00
fn set_pending_lhs_name_for_entry(generator: &mut Generator, entry: &BindingEntry) {
let name = match &entry.alias {
Some(BindingEntryAlias::Identifier(id)) => Some(&id.name),
None => {
if let Some(BindingEntryName::Identifier(id)) = &entry.name {
Some(&id.name)
} else {
None
}
}
_ => None,
};
if let Some(name) = name {
2026-02-24 08:36:14 -03:00
generator.pending_lhs_name = Some(generator.intern_identifier(name));
}
}
fn generate_binding_pattern_bytecode(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
pattern: &BindingPattern,
mode: BindingMode,
input_value: &ScopedOperand,
) {
match pattern.kind {
BindingPatternKind::Array => {
2026-02-24 08:36:14 -03:00
generate_array_binding_pattern(generator, pattern, mode, input_value);
}
BindingPatternKind::Object => {
2026-02-24 08:36:14 -03:00
generate_object_binding_pattern(generator, pattern, mode, input_value);
}
}
}
fn emit_set_variable_with_mode(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
ident: &Identifier,
value: &ScopedOperand,
mode: BindingMode,
) {
if ident.is_local() {
let local = generator.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap());
2026-02-24 08:36:14 -03:00
generator.emit_mov(&local, value);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
match mode {
BindingMode::InitializeLexical => {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
BindingMode::Set => {
if ident.is_global.get() {
2026-02-24 08:36:14 -03:00
let cache = generator.next_global_variable_cache();
generator.emit(Instruction::SetGlobal {
identifier: id,
src: value.operand(),
cache: cache as u64,
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalBinding {
identifier: id,
src: value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
}
}
fn assign_binding_entry_alias(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
entry: &BindingEntry,
value: &ScopedOperand,
mode: BindingMode,
) {
match &entry.alias {
None => {
// Name IS the binding target (e.g., `{ x }` or array element).
if let Some(BindingEntryName::Identifier(ident)) = &entry.name {
2026-02-24 08:36:14 -03:00
emit_set_variable_with_mode(generator, ident, value, mode);
}
}
Some(BindingEntryAlias::Identifier(ident)) => {
2026-02-24 08:36:14 -03:00
emit_set_variable_with_mode(generator, ident, value, mode);
}
Some(BindingEntryAlias::BindingPattern(sub_pattern)) => {
2026-02-24 08:36:14 -03:00
generate_binding_pattern_bytecode(generator, sub_pattern, mode, value);
}
Some(BindingEntryAlias::MemberExpression(expression)) => {
2026-02-24 08:36:14 -03:00
emit_store_to_reference(generator, expression, value);
}
}
}
fn generate_array_binding_pattern(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
pattern: &BindingPattern,
mode: BindingMode,
input_array: &ScopedOperand,
) {
2026-02-24 08:36:14 -03:00
let is_exhausted = generator.allocate_register();
let false_val = generator.add_constant_boolean(false);
generator.emit_mov(&is_exhausted, &false_val);
let iterator_object = generator.allocate_register();
let iterator_next = generator.allocate_register();
let iterator_done = generator.allocate_register();
generator.emit(Instruction::GetIterator {
dst_iterator_object: iterator_object.operand(),
dst_iterator_next: iterator_next.operand(),
dst_iterator_done: iterator_done.operand(),
iterable: input_array.operand(),
hint: IteratorHint::Sync as u32,
});
for (index, entry) in pattern.entries.iter().enumerate() {
if entry.is_rest {
// 13.15.5.3 AssignmentRestElement: ... DestructuringAssignmentTarget
// Step 1: Evaluate the reference BEFORE iterating remaining elements.
let evaluated_ref = if let Some(BindingEntryAlias::MemberExpression(expression)) = &entry.alias {
Some(emit_evaluate_member_reference(generator, expression))
} else {
None
};
// Rest element: collect remaining into array.
// NB: Allocate register unconditionally, then re-allocate in the
// else branch.
2026-02-24 08:36:14 -03:00
let mut value = generator.allocate_register();
if index == 0 {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::IteratorToArray {
dst: value.operand(),
iterator_object: iterator_object.operand(),
iterator_next_method: iterator_next.operand(),
iterator_done_property: iterator_done.operand(),
});
} else {
2026-02-24 08:36:14 -03:00
let if_exhausted = generator.make_block();
let if_not_exhausted = generator.make_block();
let continuation = generator.make_block();
2026-02-24 08:36:14 -03:00
generator.emit_jump_if(&is_exhausted, if_exhausted, if_not_exhausted);
2026-02-24 08:36:14 -03:00
value = generator.allocate_register();
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_exhausted);
generator.emit(Instruction::NewArray {
dst: value.operand(),
element_count: 0,
elements: Vec::new(),
});
generator.emit(Instruction::Jump { target: continuation });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_not_exhausted);
generator.emit(Instruction::IteratorToArray {
dst: value.operand(),
iterator_object: iterator_object.operand(),
iterator_next_method: iterator_next.operand(),
iterator_done_property: iterator_done.operand(),
});
generator.emit(Instruction::Jump { target: continuation });
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(continuation);
}
if let Some(ref eref) = evaluated_ref {
2026-02-24 08:36:14 -03:00
emit_store_to_evaluated_reference(generator, eref, &value);
} else {
2026-02-24 08:36:14 -03:00
assign_binding_entry_alias(generator, entry, &value, mode);
}
return; // rest consumes the iterator
}
// 13.15.5.5 AssignmentElement: DestructuringAssignmentTarget Initializer(opt)
// Step 1: Evaluate the reference BEFORE calling IteratorStepValue.
let evaluated_ref = if let Some(BindingEntryAlias::MemberExpression(expression)) = &entry.alias {
Some(emit_evaluate_member_reference(generator, expression))
} else {
None
};
// For elisions (name is None), we still advance the iterator
// but don't bind anything.
let is_elision = entry.name.is_none() && entry.alias.is_none();
2026-02-24 08:36:14 -03:00
let exhausted_block = generator.make_block();
if index != 0 {
2026-02-24 08:36:14 -03:00
let not_exhausted_block = generator.make_block();
generator.emit_jump_if(&is_exhausted, exhausted_block, not_exhausted_block);
generator.switch_to_basic_block(not_exhausted_block);
}
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
generator.emit(Instruction::IteratorNextUnpack {
dst_value: value.operand(),
dst_done: is_exhausted.operand(),
iterator_object: iterator_object.operand(),
iterator_next: iterator_next.operand(),
iterator_done: iterator_done.operand(),
});
// Check if iterator got exhausted by this step.
2026-02-24 08:36:14 -03:00
let no_bail_block = generator.make_block();
generator.emit_jump_if(&is_exhausted, exhausted_block, no_bail_block);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(no_bail_block);
let create_binding_block = generator.make_block();
generator.emit(Instruction::Jump {
target: create_binding_block,
});
// Exhausted: load undefined.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(exhausted_block);
let undef = generator.add_constant_undefined();
generator.emit_mov(&value, &undef);
generator.emit(Instruction::Jump {
target: create_binding_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(create_binding_block);
// Handle default initializer.
if let Some(ref initializer) = entry.initializer {
2026-02-24 08:36:14 -03:00
let if_undefined = generator.make_block();
let if_not_undefined = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: value.operand(),
true_target: if_undefined,
false_target: if_not_undefined,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_undefined);
set_pending_lhs_name_for_entry(generator, entry);
if let Some(default_value) = generate_expression(initializer, generator, None) {
generator.emit_mov(&value, &default_value);
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: if_not_undefined,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_not_undefined);
}
if !is_elision {
if let Some(ref eref) = evaluated_ref {
2026-02-24 08:36:14 -03:00
emit_store_to_evaluated_reference(generator, eref, &value);
} else {
2026-02-24 08:36:14 -03:00
assign_binding_entry_alias(generator, entry, &value, mode);
}
}
}
// Close iterator if not exhausted.
2026-02-24 08:36:14 -03:00
let done_block = generator.make_block();
let not_done_block = generator.make_block();
generator.emit_jump_if(&is_exhausted, done_block, not_done_block);
generator.switch_to_basic_block(not_done_block);
let undef = generator.add_constant_undefined();
generator.emit(Instruction::IteratorClose {
iterator_object: iterator_object.operand(),
iterator_next: iterator_next.operand(),
iterator_done: iterator_done.operand(),
completion_type: CompletionType::Normal as u32,
completion_value: undef.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump { target: done_block });
generator.switch_to_basic_block(done_block);
}
fn generate_object_binding_pattern(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
pattern: &BindingPattern,
mode: BindingMode,
object: &ScopedOperand,
) {
generator.emit(Instruction::ThrowIfNullish { src: object.operand() });
let mut excluded_names: Vec<ScopedOperand> = Vec::new();
let has_rest = pattern.entries.last().is_some_and(|e| e.is_rest);
for entry in &pattern.entries {
if entry.is_rest {
// Rest element: copy object excluding already-destructured properties.
2026-02-24 08:36:14 -03:00
let copy = generator.allocate_register();
generator.emit(Instruction::CopyObjectExcludingProperties {
dst: copy.operand(),
from_object: object.operand(),
excluded_names_count: u32_from_usize(excluded_names.len()),
excluded_names: excluded_names.iter().map(|o| o.operand()).collect(),
});
2026-02-24 08:36:14 -03:00
assign_binding_entry_alias(generator, entry, &copy, mode);
return;
}
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
match &entry.name {
Some(BindingEntryName::Identifier(ident)) => {
2026-02-24 08:36:14 -03:00
emit_get_by_id(generator, &value, object, &ident.name, None);
if has_rest {
let name_val = generator.add_constant_string(ident.name.to_utf16_string());
excluded_names.push(name_val);
}
}
Some(BindingEntryName::Expression(expression)) => {
2026-02-24 08:36:14 -03:00
let property_name = generate_expression_or_undefined(expression, generator, None);
if has_rest {
// Only copy to a new register if the property name is a local variable,
// since locals can be reassigned. Registers are temporaries and won't change.
if property_name.operand().is_local() {
2026-02-24 08:36:14 -03:00
let excluded_name = generator.allocate_register();
generator.emit_mov(&excluded_name, &property_name);
excluded_names.push(excluded_name);
} else {
excluded_names.push(property_name.clone());
}
}
2026-02-24 08:36:14 -03:00
emit_get_by_value(generator, &value, object, &property_name, None);
}
None => {
// Should not happen for object patterns
continue;
}
}
// Handle default initializer.
if let Some(ref initializer) = entry.initializer {
2026-02-24 08:36:14 -03:00
let if_undefined = generator.make_block();
let if_not_undefined = generator.make_block();
generator.emit(Instruction::JumpUndefined {
condition: value.operand(),
true_target: if_undefined,
false_target: if_not_undefined,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_undefined);
set_pending_lhs_name_for_entry(generator, entry);
if let Some(default_value) = generate_expression(initializer, generator, None) {
generator.emit_mov(&value, &default_value);
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: if_not_undefined,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_not_undefined);
}
2026-02-24 08:36:14 -03:00
assign_binding_entry_alias(generator, entry, &value, mode);
}
}
// =============================================================================
// Try statement
// =============================================================================
/// Generate bytecode for a try/catch/finally statement.
///
/// The structure is:
/// 1. Set up FinallyContext (if finally block exists)
/// 2. Set up exception handler pointing to catch/exception preamble
/// 3. Generate try body
/// 4. Generate catch block (if present)
/// 5. Generate finally block (if present) with LeaveFinally dispatch
fn generate_try_statement(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
data: &TryStatementData,
_preferred_dst: Option<&ScopedOperand>,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let old_handler = generator.current_unwind_handler;
let saved_block = generator.current_block_index();
// Save lexical environment for restoration in catch/exception handler.
2026-02-24 08:36:14 -03:00
let saved_env = generator.current_lexical_environment();
let mut next_block: Option<Label> = None;
let mut completion: Option<ScopedOperand> = None;
// --- Set up FinallyContext if we have a finalizer ---
let has_finally = data.finalizer.is_some();
let mut finally_body_block: Option<Label> = None;
if has_finally {
2026-02-24 08:36:14 -03:00
let completion_type = generator.allocate_register();
let completion_value = generator.allocate_register();
2026-02-24 08:36:14 -03:00
let exception_preamble_block = generator.make_block();
let fb_block = generator.make_block();
finally_body_block = Some(fb_block);
// Save the parent FinallyContext and install new one.
2026-02-24 08:36:14 -03:00
let parent_index = generator.current_finally_context;
generator.push_finally_context(FinallyContext {
completion_type,
completion_value,
finally_body: fb_block,
exception_preamble: exception_preamble_block,
parent_index,
registered_jumps: Vec::new(),
next_jump_index: FinallyContext::FIRST_JUMP_INDEX,
lexical_environment_at_entry: Some(saved_env.clone()),
saved_unwind_handler: None,
});
// Generate exception preamble block:
// Catch → completion_value
// SetLexicalEnvironment (restore to entry)
// completion_type = THROW
// Jump → finally_body
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(exception_preamble_block);
let ctx_index = generator.current_finally_context.expect("no active finally context");
let cv = generator.finally_contexts[ctx_index].completion_value.clone();
let ct = generator.finally_contexts[ctx_index].completion_type.clone();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Catch { dst: cv.operand() });
generator.emit(Instruction::SetLexicalEnvironment {
environment: saved_env.operand(),
});
2026-02-24 08:36:14 -03:00
let throw_const = generator.add_constant_i32(FinallyContext::THROW);
generator.emit_mov(&ct, &throw_const);
generator.emit(Instruction::Jump { target: fb_block });
// Set exception_preamble as default handler for blocks created below.
// The catch body gets this as its handler (exceptions in catch → finally).
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = Some(exception_preamble_block);
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
}
// --- Generate catch handler block (if present) ---
let mut handler_block: Option<Label> = None;
if let Some(catch) = &data.handler {
2026-02-24 08:36:14 -03:00
let hb = generator.make_block();
handler_block = Some(hb);
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(hb);
2026-02-24 08:36:14 -03:00
let caught_value = generator.allocate_register();
generator.emit(Instruction::Catch {
dst: caught_value.operand(),
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalEnvironment {
environment: saved_env.operand(),
});
// Bind the catch parameter.
let mut created_catch_scope = false;
if let Some(parameter) = &catch.parameter {
match parameter {
CatchBinding::Identifier(ident) => {
if ident.is_local() {
2026-02-24 08:36:14 -03:00
let local = generator.local(ident.local_index.get());
generator.emit_mov(&local, &caught_value);
generator.mark_local_initialized(ident.local_index.get());
} else {
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
created_catch_scope = true;
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: caught_value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
CatchBinding::BindingPattern(pattern) => {
let mut names: Vec<(Utf16String, bool)> = Vec::new();
collect_pattern_binding_names(pattern, &mut names);
if !names.is_empty() {
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
created_catch_scope = true;
for (name, _) in &names {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
}
}
generate_binding_pattern_bytecode(
2026-02-24 08:36:14 -03:00
generator,
pattern,
BindingMode::InitializeLexical,
&caught_value,
);
}
}
}
// Catch body gets its own completion register to prevent
// break/continue inside catch from leaking values.
let mut catch_completion: Option<ScopedOperand> = None;
{
2026-02-24 08:36:14 -03:00
let saved_completion = generator.current_completion_register.clone();
if generator.must_propagate_completion {
let reg = generator.allocate_register();
let undef = generator.add_constant_undefined();
generator.emit_mov(&reg, &undef);
generator.current_completion_register = Some(reg.clone());
catch_completion = Some(reg);
}
2026-02-24 08:36:14 -03:00
generate_statement(&catch.body, generator, None);
generator.current_completion_register = saved_completion;
}
// Save catch completion BEFORE restoring the lexical environment
// from the catch scope.
2026-02-24 08:36:14 -03:00
if generator.must_propagate_completion
&& let Some(ref cc) = catch_completion
&& !generator.is_current_block_terminated()
{
let reg = generator.allocate_register();
generator.emit_mov(&reg, cc);
completion = Some(reg);
}
if created_catch_scope {
2026-02-24 08:36:14 -03:00
generator.end_variable_scope();
}
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
if has_finally {
// Normal exit from catch → completion_type = NORMAL, jump to finally.
let ctx_index = generator.current_finally_context.expect("no active finally context");
let ct = generator.finally_contexts[ctx_index].completion_type.clone();
2026-02-24 08:36:14 -03:00
let fb = generator.finally_contexts[ctx_index].finally_body;
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
generator.emit_mov(&ct, &normal_const);
generator.emit(Instruction::Jump { target: fb });
} else {
if next_block.is_none() {
2026-02-24 08:36:14 -03:00
next_block = Some(generator.make_block());
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: next_block.expect("next_block must be set"),
});
}
}
}
if has_finally {
2026-02-24 08:36:14 -03:00
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
}
// --- Generate try body ---
// Set handler BEFORE creating the try body block, so make_block()
// captures the correct handler for exception routing.
// For try-catch-finally: catch handler is inner (exceptions → catch → exception_preamble → finally).
// For try-catch: catch handler.
// For try-finally: exception_preamble.
if let Some(hb) = handler_block {
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = Some(hb);
} else if has_finally && let Some(ctx_index) = generator.current_finally_context {
let ep = generator.finally_contexts[ctx_index].exception_preamble;
generator.current_unwind_handler = Some(ep);
}
2026-02-24 08:36:14 -03:00
let try_body_block = generator.make_block();
generator.switch_to_basic_block(saved_block);
generator.emit(Instruction::Jump { target: try_body_block });
if has_finally {
2026-02-24 08:36:14 -03:00
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
}
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(try_body_block);
// Try body gets its own completion register to prevent
// break/continue inside try from leaking values.
// NB: try_completion must be declared outside the inner scope so its
// register stays alive during finally body generation.
let mut try_completion: Option<ScopedOperand> = None;
{
2026-02-24 08:36:14 -03:00
let saved_completion = generator.current_completion_register.clone();
if generator.must_propagate_completion {
let reg = generator.allocate_register();
let undef = generator.add_constant_undefined();
generator.emit_mov(&reg, &undef);
generator.current_completion_register = Some(reg.clone());
try_completion = Some(reg);
}
2026-02-24 08:36:14 -03:00
generate_statement(&data.block, generator, None);
generator.current_completion_register = saved_completion;
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated()
&& generator.must_propagate_completion
&& let Some(ref tc) = try_completion
{
let reg = generator.allocate_register();
generator.emit_mov(&reg, tc);
completion = Some(reg);
}
}
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
if has_finally {
// Normal exit from try → completion_type = NORMAL, jump to finally.
let ctx_index = generator.current_finally_context.expect("no active finally context");
let ct = generator.finally_contexts[ctx_index].completion_type.clone();
2026-02-24 08:36:14 -03:00
let fb = generator.finally_contexts[ctx_index].finally_body;
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
generator.emit_mov(&ct, &normal_const);
generator.emit(Instruction::Jump { target: fb });
} else {
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = old_handler;
if next_block.is_none() {
2026-02-24 08:36:14 -03:00
next_block = Some(generator.make_block());
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: next_block.expect("next_block must be set"),
});
}
}
if has_finally {
2026-02-24 08:36:14 -03:00
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
}
// Restore old unwind handler.
2026-02-24 08:36:14 -03:00
generator.current_unwind_handler = old_handler;
// --- Generate finally body and after-finally dispatch ---
if let Some(fb_block) = finally_body_block {
// Pop FinallyContext.
let ctx_index = generator.current_finally_context.expect("no active finally context");
2026-02-24 08:36:14 -03:00
generator.current_finally_context = generator.finally_contexts[ctx_index].parent_index;
// Extract fields needed for dispatch (to avoid borrow conflicts).
let ctx_ct = generator.finally_contexts[ctx_index].completion_type.clone();
let ctx_cv = generator.finally_contexts[ctx_index].completion_value.clone();
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(fb_block);
generator.start_boundary(BlockBoundaryType::LeaveFinally);
// Generate the finally body with a throwaway completion register
// to prevent break/continue in finally from leaking the try/catch
// completion value.
if let Some(finalizer) = &data.finalizer {
2026-02-24 08:36:14 -03:00
let saved_completion = generator.current_completion_register.clone();
if generator.must_propagate_completion {
let finally_completion = generator.allocate_register();
let undef = generator.add_constant_undefined();
generator.emit_mov(&finally_completion, &undef);
generator.current_completion_register = Some(finally_completion);
}
2026-02-24 08:36:14 -03:00
generate_statement(finalizer, generator, None);
generator.current_completion_register = saved_completion;
}
2026-02-24 08:36:14 -03:00
generator.end_boundary(BlockBoundaryType::LeaveFinally);
2026-02-24 08:36:14 -03:00
if !generator.is_current_block_terminated() {
if next_block.is_none() {
2026-02-24 08:36:14 -03:00
next_block = Some(generator.make_block());
}
let nb = next_block.expect("next_block must be set");
// After-finally dispatch chain:
// 1. NORMAL → next block
2026-02-24 08:36:14 -03:00
let after_normal_check = generator.make_block();
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: ctx_ct.operand(),
rhs: normal_const.operand(),
true_target: nb,
false_target: after_normal_check,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(after_normal_check);
// 2. Registered break/continue jumps
let registered_jumps = std::mem::take(&mut generator.finally_contexts[ctx_index].registered_jumps);
for jump in &registered_jumps {
2026-02-24 08:36:14 -03:00
let after_jump_check = generator.make_block();
let jump_const = generator.add_constant_i32(jump.index);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: ctx_ct.operand(),
rhs: jump_const.operand(),
true_target: jump.target,
false_target: after_jump_check,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(after_jump_check);
}
// 3. RETURN → actually return the completion_value
2026-02-24 08:36:14 -03:00
let return_block = generator.make_block();
let rethrow_block = generator.make_block();
let return_const = generator.add_constant_i32(FinallyContext::RETURN);
generator.emit(Instruction::JumpStrictlyEquals {
lhs: ctx_ct.operand(),
rhs: return_const.operand(),
true_target: return_block,
false_target: rethrow_block,
});
// Generate return block.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(return_block);
if let Some(outer_index) = generator.current_finally_context {
// Nested finally: copy completion record to outer and jump to outer finally.
let outer_ct = generator.finally_contexts[outer_index].completion_type.clone();
let outer_cv = generator.finally_contexts[outer_index].completion_value.clone();
2026-02-24 08:36:14 -03:00
let outer_fb = generator.finally_contexts[outer_index].finally_body;
generator.emit_mov(&outer_ct, &ctx_ct);
generator.emit_mov(&outer_cv, &ctx_cv);
generator.emit(Instruction::Jump { target: outer_fb });
} else if generator.is_in_generator_function() {
generator.emit(Instruction::Yield {
continuation_label: None,
value: ctx_cv.operand(),
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Return {
value: ctx_cv.operand(),
});
}
// 4. Default → rethrow the exception.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(rethrow_block);
generator.emit(Instruction::Throw { src: ctx_cv.operand() });
}
2026-02-24 08:36:14 -03:00
generator.finally_contexts[ctx_index].lexical_environment_at_entry = None;
let dummy = generator.add_constant_undefined();
generator.finally_contexts[ctx_index].completion_value = dummy.clone();
generator.finally_contexts[ctx_index].completion_type = dummy;
}
// Switch to the next block for code after the try statement.
// When next_block is None, all paths are terminated; switch back to
// saved_block (which is already terminated) so no dead block is emitted.
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(next_block.unwrap_or(saved_block));
2026-02-24 08:36:14 -03:00
if generator.must_propagate_completion && completion.is_none() {
return Some(generator.add_constant_undefined());
}
completion
}
/// Create a SharedFunctionInstanceData for a function expression/declaration
/// and register it with the generator.
///
/// Returns the shared_function_data_index for use in NewFunction instructions.
fn emit_new_function(generator: &mut Generator, data: Box<FunctionData>, name_override: Option<&[u16]>) -> u32 {
assert!(
2026-02-24 08:36:14 -03:00
data.source_text_end as usize <= generator.source_len,
"Function source range out of bounds: {}..{} (source len {})",
data.source_text_start,
data.source_text_end,
2026-02-24 08:36:14 -03:00
generator.source_len
);
2026-02-24 08:36:14 -03:00
let subtable = generator.function_table.extract_reachable(&data);
generator.register_shared_function_data(PendingSharedFunctionData {
function_data: Some(data),
subtable: Some(subtable),
name_override: name_override.map(Utf16String::from),
class_field_initializer_name: None,
})
}
// =============================================================================
// FunctionDeclarationInstantiation (FDI)
// =============================================================================
/// Emit FDI bytecode for a function body.
///
/// Creates environment bindings, initializes parameters, creates arguments
/// objects, and hoists function declarations.
pub fn emit_function_declaration_instantiation(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
function_data: &FunctionData,
body_scope: &ScopeData,
var_environment_bindings_count: usize,
) {
2026-02-24 08:36:14 -03:00
let strict = function_data.is_strict_mode || generator.strict;
let is_arrow = function_data.is_arrow_function;
// --- Compute FDI metadata ---
// Check for parameter expressions (default values or binding patterns with expressions).
let has_parameter_expressions = function_data.parameters.iter().any(|p| {
p.default_value.is_some()
|| matches!(
p.binding,
FunctionParameterBinding::BindingPattern(ref pat) if pat.contains_expression()
)
});
// Build parameter_names map and check for duplicates.
let mut parameter_names: Vec<FdiParameterName> = Vec::new();
let mut seen_names: HashSet<Utf16String> = HashSet::new();
let mut has_duplicates = false;
for parameter in &function_data.parameters {
match &parameter.binding {
FunctionParameterBinding::Identifier(ident) => {
let name = ident.name.to_utf16_string();
let is_local = ident.is_local();
if !seen_names.insert(name.clone()) {
has_duplicates = true;
} else {
parameter_names.push(FdiParameterName { name, is_local });
}
}
FunctionParameterBinding::BindingPattern(pattern) => {
collect_binding_pattern_names(pattern, &mut parameter_names, &mut seen_names, &mut has_duplicates);
}
}
}
// Determine if arguments object is needed (from parsing insights).
let mut arguments_object_needed = if is_arrow || parameter_names.iter().any(|p| p.name == utf16!("arguments")) {
false
} else {
function_data.parsing_insights.might_need_arguments_object
};
let function_scope_data = body_scope.function_scope_data.as_ref();
if let Some(fsd) = function_scope_data {
if !has_parameter_expressions && fsd.has_function_named_arguments {
arguments_object_needed = false;
}
if !has_parameter_expressions && arguments_object_needed && fsd.has_lexically_declared_arguments {
arguments_object_needed = false;
}
}
// --- Step 1: Parameter scope for parameter expressions ---
if has_parameter_expressions {
let has_non_local_parameters = parameter_names.iter().any(|p| !p.is_local);
if has_non_local_parameters {
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(0);
}
}
// --- Step 2: Create bindings for non-local parameters ---
for param in &parameter_names {
if !param.is_local {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&param.name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
if has_duplicates {
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: undef.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
// --- Step 3: Create arguments object ---
if arguments_object_needed {
// Find local variable index for ArgumentsObject, if any.
2026-02-24 08:36:14 -03:00
let arguments_local_index = generator
.local_variables
.iter()
.position(|lv| lv.name == utf16!("arguments") && !lv.is_lexically_declared);
let dst = arguments_local_index.map(|index| Operand::local(u32_from_usize(index)));
let kind = if strict
|| !function_data.parameters.iter().all(|p| {
!p.is_rest && p.default_value.is_none() && matches!(p.binding, FunctionParameterBinding::Identifier(_))
}) {
ArgumentsKind::Unmapped as u32
} else {
ArgumentsKind::Mapped as u32
};
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CreateArguments {
dst,
kind,
is_immutable: strict,
});
if let Some(index) = arguments_local_index {
2026-02-24 08:36:14 -03:00
generator.mark_local_initialized(u32_from_usize(index));
}
}
// --- Step 4: Bind formal parameters ---
for (parameter_index, parameter) in function_data.parameters.iter().enumerate() {
let parameter_index = u32_from_usize(parameter_index);
if parameter.is_rest {
2026-02-24 08:36:14 -03:00
let dst = generator.scoped_operand(Operand::argument(parameter_index));
generator.emit(Instruction::CreateRestParams {
dst: dst.operand(),
rest_index: parameter_index,
});
} else if parameter.default_value.is_some() {
2026-02-24 08:36:14 -03:00
let if_undefined_block = generator.make_block();
let if_not_undefined_block = generator.make_block();
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::JumpUndefined {
condition: Operand::argument(parameter_index),
true_target: if_undefined_block,
false_target: if_not_undefined_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_undefined_block);
if let Some(value) = generate_expression(
parameter
.default_value
.as_ref()
.expect("guarded by has_default_value check"),
2026-02-24 08:36:14 -03:00
generator,
None,
) {
2026-02-24 08:36:14 -03:00
generator.emit_mov_raw(Operand::argument(parameter_index), value.operand());
}
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::Jump {
target: if_not_undefined_block,
});
2026-02-24 08:36:14 -03:00
generator.switch_to_basic_block(if_not_undefined_block);
}
match &parameter.binding {
FunctionParameterBinding::Identifier(ident) => {
if ident.is_local() {
let local_index = ident.local_index.get();
match ident.local_type.get() {
2026-02-24 08:36:14 -03:00
Some(LocalType::Variable) => generator.mark_local_initialized(local_index),
Some(LocalType::Argument) => {
generator.mark_argument_initialized(local_index);
2026-02-24 08:36:14 -03:00
}
None => {}
}
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&ident.name);
if has_duplicates {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::SetLexicalBinding {
identifier: id,
src: Operand::argument(parameter_index),
cache: EnvironmentCoordinate::empty(),
});
} else {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeLexicalBinding {
identifier: id,
src: Operand::argument(parameter_index),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
FunctionParameterBinding::BindingPattern(pattern) => {
2026-02-24 08:36:14 -03:00
let argument = generator.scoped_operand(Operand::argument(parameter_index));
let mode = if has_duplicates {
BindingMode::Set
} else {
BindingMode::InitializeLexical
};
2026-02-24 08:36:14 -03:00
generate_binding_pattern_bytecode(generator, pattern, mode, &argument);
}
}
}
// --- Step 5: Initialize var bindings ---
if let Some(fsd) = function_scope_data {
if !has_parameter_expressions {
// Simple case: vars share the parameter environment.
for var in &fsd.vars_to_initialize {
if var.is_parameter {
continue;
}
if arguments_object_needed && var.name == utf16!("arguments") {
continue;
}
if let Some(local_binding) = var.local {
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
let local = var_local_operand(generator, local_binding.local_type, local_binding.index);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&local, &undef);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&var.name);
let undef = generator.add_constant_undefined();
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Var as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeVariableBinding {
identifier: id,
src: undef.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
} else {
// Parameter expressions: vars get a separate environment.
let has_non_local_vars = fsd.vars_to_initialize.iter().any(|v| v.local.is_none());
if has_non_local_vars {
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::CreateVariableEnvironment {
capacity: u32_from_usize(var_environment_bindings_count),
});
// After CreateVariableEnvironment, re-read the lexical environment
// (which was also updated) and push it onto the register stack.
// This ensures subsequent CreateLexicalEnvironment instructions
// (e.g. Step 7) use the var environment as their parent, not the
// parameter scope.
2026-02-24 08:36:14 -03:00
let var_env = generator.allocate_register();
generator.emit(Instruction::GetLexicalEnvironment { dst: var_env.operand() });
2026-02-24 08:36:14 -03:00
generator.lexical_environment_register_stack.push(var_env);
}
for var in &fsd.vars_to_initialize {
let is_in_parameter_bindings =
var.is_parameter || (arguments_object_needed && var.name == utf16!("arguments"));
let initial_value = if !is_in_parameter_bindings || var.is_function_name {
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
let undef = generator.add_constant_undefined();
generator.emit_mov(&value, &undef);
value
} else if let Some(local_binding) = var.local {
let local = var_local_operand(generator, local_binding.local_type, local_binding.index);
2026-02-24 08:36:14 -03:00
let value = generator.allocate_register();
generator.emit_mov(&value, &local);
value
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&var.name);
let value = generator.allocate_register();
generator.emit(Instruction::GetBinding {
dst: value.operand(),
identifier: id,
cache: EnvironmentCoordinate::empty(),
});
value
};
if let Some(local_binding) = var.local {
let local = var_local_operand(generator, local_binding.local_type, local_binding.index);
2026-02-24 08:36:14 -03:00
generator.emit_mov(&local, &initial_value);
} else {
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&var.name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Var as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
2026-02-24 08:36:14 -03:00
generator.emit(Instruction::InitializeVariableBinding {
identifier: id,
src: initial_value.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
}
// --- Step 6: AnnexB function name bindings (non-strict only) ---
if !strict {
let var_names = function_scope_data.map(|fsd| &fsd.var_names);
for name in &body_scope.annexb_function_names {
2026-02-24 08:36:14 -03:00
generator.annexb_function_names.insert(name.clone());
// Skip creating a var binding if this name is already declared as a var.
if var_names.is_some_and(|names| names.contains(name)) {
continue;
}
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Var as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
2026-02-24 08:36:14 -03:00
let undef = generator.add_constant_undefined();
generator.emit(Instruction::InitializeVariableBinding {
identifier: id,
src: undef.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
// --- Step 7: Lexical environment for non-local declarations ---
// Note: this counts only let/const/class declarations (not function declarations,
// which are var-hoisted in function bodies).
let lex_bindings_count = count_non_local_lexical_bindings(body_scope);
if !strict && lex_bindings_count > 0 {
2026-02-24 08:36:14 -03:00
generator.push_new_lexical_environment(lex_bindings_count);
}
// --- Step 8: Create lexical bindings ---
for child in &body_scope.children {
match &child.inner {
StatementKind::VariableDeclaration(vd)
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const =>
{
let is_constant = vd.kind == DeclarationKind::Const;
for declaration in &vd.declarations {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
for (name, _) in names {
let id = generator.intern_identifier(&name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: is_constant,
is_global: false,
is_strict: is_constant,
});
}
}
}
StatementKind::ClassDeclaration(class_data) => {
// Class declarations are lexically scoped (like const).
2026-02-24 08:36:14 -03:00
if let Some(ref name_ident) = class_data.name
&& !name_ident.is_local()
{
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::CreateVariable {
identifier: id,
mode: EnvironmentMode::Lexical as u32,
is_immutable: false,
is_global: false,
is_strict: false,
});
}
}
_ => {}
}
}
// --- Step 9: Initialize hoisted function declarations ---
if let Some(fsd) = function_scope_data {
for function_to_init in &fsd.functions_to_initialize {
let child = &body_scope.children[function_to_init.child_index];
if let StatementKind::FunctionDeclaration(ref fd) = child.inner {
let inner_function_data = generator.function_table.take(fd.function_id);
2026-02-24 08:36:14 -03:00
let sfd_index = emit_new_function(generator, inner_function_data, None);
// Check if the function name identifier is local.
if let Some(name_ident) = &fd.name {
if name_ident.is_local() {
let local_index = name_ident.local_index.get();
2026-02-24 08:36:14 -03:00
let local = generator.local(local_index);
generator.emit(Instruction::NewFunction {
dst: local.operand(),
shared_function_data_index: sfd_index,
home_object: None,
lhs_name: None,
});
2026-02-24 08:36:14 -03:00
generator.mark_local_initialized(local_index);
} else {
2026-02-24 08:36:14 -03:00
let function_reg = generator.allocate_register();
generator.emit(Instruction::NewFunction {
dst: function_reg.operand(),
shared_function_data_index: sfd_index,
home_object: None,
lhs_name: None,
});
2026-02-24 08:36:14 -03:00
let id = generator.intern_identifier(&name_ident.name);
generator.emit(Instruction::SetVariableBinding {
identifier: id,
src: function_reg.operand(),
cache: EnvironmentCoordinate::empty(),
});
}
}
}
}
}
}
/// Check if a statement is a for-loop variant (for, for-in, for-of, for-await-of).
fn is_for_loop(statement: &Statement) -> bool {
matches!(statement.inner, StatementKind::For(_) | StatementKind::ForInOf(_))
}
/// Check if a block needs block declaration instantiation.
/// True when the block has function declarations or non-local let/const/class.
fn needs_block_declaration_instantiation(scope: &ScopeData) -> bool {
for child in &scope.children {
match &child.inner {
StatementKind::FunctionDeclaration(_) => {
return true;
}
StatementKind::VariableDeclaration(vd)
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const =>
{
for declaration in &vd.declarations {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
if !names.is_empty() {
return true;
}
}
}
StatementKind::ClassDeclaration(class_data) => {
2026-02-24 08:36:14 -03:00
if let Some(ref name_ident) = class_data.name
&& !name_ident.is_local()
{
return true;
}
}
StatementKind::UsingDeclaration(declarations) => {
for declaration in declarations.iter() {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
if !names.is_empty() {
return true;
}
}
}
_ => {}
}
}
false
}
/// Count non-local lexical bindings in a function body scope.
fn count_non_local_lexical_bindings(scope: &ScopeData) -> u32 {
let mut count = 0u32;
for child in &scope.children {
match &child.inner {
StatementKind::VariableDeclaration(vd)
if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const =>
{
for declaration in &vd.declarations {
let mut names = Vec::new();
collect_target_names(&declaration.target, &mut names);
count += u32_from_usize(names.len());
}
}
StatementKind::ClassDeclaration(class_data) if class_data.name.as_ref().is_some_and(|n| !n.is_local()) => {
count += 1;
}
_ => {}
}
}
count
}
/// Create a ScopedOperand for a VarToInit's local variable (argument or variable).
fn var_local_operand(generator: &mut Generator, local_type: LocalType, index: u32) -> ScopedOperand {
2026-02-24 08:36:14 -03:00
generator.resolve_local(index, local_type)
}
/// Collect bound names from a binding pattern into the parameter_names list.
fn collect_binding_pattern_names(
pattern: &BindingPattern,
parameter_names: &mut Vec<FdiParameterName>,
seen_names: &mut HashSet<Utf16String>,
has_duplicates: &mut bool,
) {
for entry in &pattern.entries {
// The bound name can be in the alias (for object patterns) or name (for array patterns).
match &entry.alias {
Some(BindingEntryAlias::Identifier(ident)) => {
let name = ident.name.to_utf16_string();
let is_local = ident.is_local();
if !seen_names.insert(name.clone()) {
*has_duplicates = true;
} else {
parameter_names.push(FdiParameterName { name, is_local });
}
}
Some(BindingEntryAlias::BindingPattern(sub_pattern)) => {
collect_binding_pattern_names(sub_pattern, parameter_names, seen_names, has_duplicates);
}
None => {
// No alias — the name itself is the binding.
if let Some(BindingEntryName::Identifier(ident)) = &entry.name {
let name = ident.name.to_utf16_string();
let is_local = ident.is_local();
if !seen_names.insert(name.clone()) {
*has_duplicates = true;
} else {
parameter_names.push(FdiParameterName { name, is_local });
}
}
}
Some(BindingEntryAlias::MemberExpression(_)) => {}
}
}
}
/// A parameter name with its locality (used during FDI).
struct FdiParameterName {
name: Utf16String,
is_local: bool,
}
// Builtin IDs matching JS_ENUMERATE_BUILTINS in Builtins.h.
const BUILTIN_MATH_ABS: u8 = 0;
const BUILTIN_MATH_LOG: u8 = 1;
const BUILTIN_MATH_POW: u8 = 2;
const BUILTIN_MATH_EXP: u8 = 3;
const BUILTIN_MATH_CEIL: u8 = 4;
const BUILTIN_MATH_FLOOR: u8 = 5;
const BUILTIN_MATH_IMUL: u8 = 6;
const BUILTIN_MATH_RANDOM: u8 = 7;
const BUILTIN_MATH_ROUND: u8 = 8;
const BUILTIN_MATH_SQRT: u8 = 9;
const BUILTIN_MATH_SIN: u8 = 10;
const BUILTIN_MATH_COS: u8 = 11;
const BUILTIN_MATH_TAN: u8 = 12;
const BUILTIN_REGEXP_PROTOTYPE_EXEC: u8 = 13;
const BUILTIN_REGEXP_PROTOTYPE_REPLACE: u8 = 14;
const BUILTIN_REGEXP_PROTOTYPE_SPLIT: u8 = 15;
const BUILTIN_ORDINARY_HAS_INSTANCE: u8 = 16;
const BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT: u8 = 17;
const BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT: u8 = 18;
const BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT: u8 = 19;
const BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT: u8 = 20;
const BUILTIN_STRING_FROM_CHAR_CODE: u8 = 21;
const BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT: u8 = 22;
const BUILTIN_STRING_PROTOTYPE_CHAR_AT: u8 = 23;
/// Detect known builtin methods from a callee expression (e.g. Math.abs).
/// Returns the Builtin enum value as u8, matching Builtins.h ordering.
fn get_builtin(callee: &Expression) -> Option<u8> {
let ExpressionKind::Member(member_data) = &callee.inner else {
return None;
};
if member_data.computed {
return None;
}
let ExpressionKind::Identifier(property_ident) = &member_data.property.inner else {
return None;
};
if property_ident.name == utf16!("charAt") {
return Some(BUILTIN_STRING_PROTOTYPE_CHAR_AT);
}
if property_ident.name == utf16!("charCodeAt") {
return Some(BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT);
}
let ExpressionKind::Identifier(base_ident) = &member_data.object.inner else {
return None;
};
// Must match JS_ENUMERATE_BUILTINS order in Builtins.h.
static BUILTINS: &[(&[u16], &[u16], u8)] = &[
(utf16!("Math"), utf16!("abs"), BUILTIN_MATH_ABS),
(utf16!("Math"), utf16!("log"), BUILTIN_MATH_LOG),
(utf16!("Math"), utf16!("pow"), BUILTIN_MATH_POW),
(utf16!("Math"), utf16!("exp"), BUILTIN_MATH_EXP),
(utf16!("Math"), utf16!("ceil"), BUILTIN_MATH_CEIL),
(utf16!("Math"), utf16!("floor"), BUILTIN_MATH_FLOOR),
(utf16!("Math"), utf16!("imul"), BUILTIN_MATH_IMUL),
(utf16!("Math"), utf16!("random"), BUILTIN_MATH_RANDOM),
(utf16!("Math"), utf16!("round"), BUILTIN_MATH_ROUND),
(utf16!("Math"), utf16!("sqrt"), BUILTIN_MATH_SQRT),
(utf16!("Math"), utf16!("sin"), BUILTIN_MATH_SIN),
(utf16!("Math"), utf16!("cos"), BUILTIN_MATH_COS),
(utf16!("Math"), utf16!("tan"), BUILTIN_MATH_TAN),
(utf16!("RegExpPrototype"), utf16!("exec"), BUILTIN_REGEXP_PROTOTYPE_EXEC),
(
utf16!("RegExpPrototype"),
utf16!("replace"),
BUILTIN_REGEXP_PROTOTYPE_REPLACE,
),
(
utf16!("RegExpPrototype"),
utf16!("split"),
BUILTIN_REGEXP_PROTOTYPE_SPLIT,
),
(
utf16!("InternalBuiltin"),
utf16!("ordinary_has_instance"),
BUILTIN_ORDINARY_HAS_INSTANCE,
),
(
utf16!("ArrayIteratorPrototype"),
utf16!("next"),
BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT,
),
(
utf16!("MapIteratorPrototype"),
utf16!("next"),
BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT,
),
(
utf16!("SetIteratorPrototype"),
utf16!("next"),
BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT,
),
(
utf16!("StringIteratorPrototype"),
utf16!("next"),
BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT,
),
(utf16!("String"), utf16!("fromCharCode"), BUILTIN_STRING_FROM_CHAR_CODE),
];
for &(base, property, id) in BUILTINS {
if base_ident.name == base && property_ident.name == property {
return Some(id);
}
}
None
}
fn builtin_argument_count(builtin: u8) -> usize {
// Must match JS_ENUMERATE_BUILTINS argument counts in Builtins.h.
match builtin {
BUILTIN_MATH_ABS => 1,
BUILTIN_MATH_LOG => 1,
BUILTIN_MATH_POW => 2,
BUILTIN_MATH_EXP => 1,
BUILTIN_MATH_CEIL => 1,
BUILTIN_MATH_FLOOR => 1,
BUILTIN_MATH_IMUL => 2,
BUILTIN_MATH_RANDOM => 0,
BUILTIN_MATH_ROUND => 1,
BUILTIN_MATH_SQRT => 1,
BUILTIN_MATH_SIN => 1,
BUILTIN_MATH_COS => 1,
BUILTIN_MATH_TAN => 1,
BUILTIN_REGEXP_PROTOTYPE_EXEC => 1,
BUILTIN_REGEXP_PROTOTYPE_REPLACE => 2,
BUILTIN_REGEXP_PROTOTYPE_SPLIT => 2,
BUILTIN_ORDINARY_HAS_INSTANCE => 1,
BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT => 0,
BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT => 0,
BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT => 0,
BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT => 0,
BUILTIN_STRING_FROM_CHAR_CODE => 1,
BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT => 1,
BUILTIN_STRING_PROTOTYPE_CHAR_AT => 1,
_ => usize::MAX,
}
}
fn emit_builtin_call(
generator: &mut Generator,
builtin: u8,
dst: Operand,
callee: Operand,
this_value: Operand,
expression_string: Option<StringTableIndex>,
arguments: Vec<Operand>,
) {
macro_rules! emit_nullary_builtin_instruction {
($instruction:ident) => {
generator.emit(Instruction::$instruction {
dst,
callee,
this_value,
expression_string,
})
};
}
macro_rules! emit_unary_builtin_instruction {
($instruction:ident) => {
generator.emit(Instruction::$instruction {
dst,
callee,
this_value,
argument: arguments[0],
expression_string,
})
};
}
macro_rules! emit_binary_builtin_instruction {
($instruction:ident) => {
generator.emit(Instruction::$instruction {
dst,
callee,
this_value,
argument0: arguments[0],
argument1: arguments[1],
expression_string,
})
};
}
match builtin {
BUILTIN_MATH_ABS => emit_unary_builtin_instruction!(CallBuiltinMathAbs),
BUILTIN_MATH_LOG => emit_unary_builtin_instruction!(CallBuiltinMathLog),
BUILTIN_MATH_POW => emit_binary_builtin_instruction!(CallBuiltinMathPow),
BUILTIN_MATH_EXP => emit_unary_builtin_instruction!(CallBuiltinMathExp),
BUILTIN_MATH_CEIL => emit_unary_builtin_instruction!(CallBuiltinMathCeil),
BUILTIN_MATH_FLOOR => emit_unary_builtin_instruction!(CallBuiltinMathFloor),
BUILTIN_MATH_IMUL => emit_binary_builtin_instruction!(CallBuiltinMathImul),
BUILTIN_MATH_RANDOM => emit_nullary_builtin_instruction!(CallBuiltinMathRandom),
BUILTIN_MATH_ROUND => emit_unary_builtin_instruction!(CallBuiltinMathRound),
BUILTIN_MATH_SQRT => emit_unary_builtin_instruction!(CallBuiltinMathSqrt),
BUILTIN_MATH_SIN => emit_unary_builtin_instruction!(CallBuiltinMathSin),
BUILTIN_MATH_COS => emit_unary_builtin_instruction!(CallBuiltinMathCos),
BUILTIN_MATH_TAN => emit_unary_builtin_instruction!(CallBuiltinMathTan),
BUILTIN_REGEXP_PROTOTYPE_EXEC => {
emit_unary_builtin_instruction!(CallBuiltinRegExpPrototypeExec);
}
BUILTIN_REGEXP_PROTOTYPE_REPLACE => {
emit_binary_builtin_instruction!(CallBuiltinRegExpPrototypeReplace);
}
BUILTIN_REGEXP_PROTOTYPE_SPLIT => {
emit_binary_builtin_instruction!(CallBuiltinRegExpPrototypeSplit);
}
BUILTIN_ORDINARY_HAS_INSTANCE => {
emit_unary_builtin_instruction!(CallBuiltinOrdinaryHasInstance);
}
BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT => {
emit_nullary_builtin_instruction!(CallBuiltinArrayIteratorPrototypeNext);
}
BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT => {
emit_nullary_builtin_instruction!(CallBuiltinMapIteratorPrototypeNext);
}
BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT => {
emit_nullary_builtin_instruction!(CallBuiltinSetIteratorPrototypeNext);
}
BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT => {
emit_nullary_builtin_instruction!(CallBuiltinStringIteratorPrototypeNext);
}
BUILTIN_STRING_FROM_CHAR_CODE => {
emit_unary_builtin_instruction!(CallBuiltinStringFromCharCode);
}
BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT => {
emit_unary_builtin_instruction!(CallBuiltinStringPrototypeCharCodeAt);
}
BUILTIN_STRING_PROTOTYPE_CHAR_AT => {
emit_unary_builtin_instruction!(CallBuiltinStringPrototypeCharAt);
}
_ => unreachable!(),
}
}
/// JS ToInt32 conversion (ECMA-262 7.1.6).
fn to_int32(n: f64) -> i32 {
if n.is_nan() || n.is_infinite() || n == 0.0 {
return 0;
}
let int_val = n.signum() * n.abs().floor();
let int32bit = int_val % 4294967296.0; // 2^32
let int32bit = if int32bit < 0.0 {
int32bit + 4294967296.0
} else {
int32bit
};
if int32bit >= 2147483648.0 {
(int32bit - 4294967296.0) as i32
} else {
int32bit as i32
}
}
/// JS ToUint32 conversion (ECMA-262 7.1.7).
fn to_u32(n: f64) -> u32 {
if n.is_nan() || n.is_infinite() || n == 0.0 {
return 0;
}
let int_val = n.signum() * n.abs().floor();
let int32bit = int_val % 4294967296.0; // 2^32
if int32bit < 0.0 {
(int32bit + 4294967296.0) as u32
} else {
int32bit as u32
}
}
/// Check if a string key is a numeric index (valid u32 < u32::MAX).
/// Numeric indices are stored in indexed storage rather than shape-based storage.
fn is_numeric_index_key(key: &[u16]) -> bool {
if key.is_empty() {
return false;
}
// Exclude leading zeros in multi-digit strings (e.g., "01")
if key[0] == ch(b'0') && key.len() > 1 {
return false;
}
// Must be all ASCII digits
if !key.iter().all(|&c| c >= ch(b'0') && c <= ch(b'9')) {
return false;
}
// Parse as u64 first to avoid overflow, then check < u32::MAX
let mut n: u64 = 0;
for &c in key {
n = n * 10 + (c - ch(b'0')) as u64;
if n > u32::MAX as u64 {
return false;
}
}
(n as u32) < u32::MAX
}
// =============================================================================
// Constant folding
// =============================================================================
/// Try to constant-fold a unary operation when the operand is a constant.
fn try_constant_fold_unary(generator: &mut Generator, op: UnaryOp, operand: &ScopedOperand) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let constant = generator.get_constant(operand)?;
match op {
UnaryOp::Minus => {
let n = constant_to_number(constant)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(-n))
}
UnaryOp::Plus => {
let n = constant_to_number(constant)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(n))
}
UnaryOp::BitwiseNot => {
if let ConstantValue::BigInt(s) = constant {
let n = parse_bigint(&s.clone())?;
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_bigint((-(n + BigInt::one())).to_string()));
}
let n = constant_to_number(constant)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((!to_int32(n)) as f64))
}
UnaryOp::Not => {
let as_bool = constant_to_boolean(constant)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(!as_bool))
}
_ => None,
}
}
/// Constant-fold !!x when x is a constant, returning Boolean(x).
fn try_constant_fold_to_boolean(generator: &mut Generator, operand: &ScopedOperand) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let constant = generator.get_constant(operand)?;
let as_bool = constant_to_boolean(constant)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(as_bool))
}
/// Implement IsLooselyEqual for constant values.
/// https://tc39.es/ecma262/#sec-islooselyequal
fn try_constant_loosely_equals(lhs: &ConstantValue, rhs: &ConstantValue) -> Option<bool> {
// Same type: use strict equality rules.
match (lhs, rhs) {
(ConstantValue::Null | ConstantValue::Undefined, ConstantValue::Null | ConstantValue::Undefined) => {
return Some(true);
}
(ConstantValue::Null | ConstantValue::Undefined, _) | (_, ConstantValue::Null | ConstantValue::Undefined) => {
return Some(false);
}
(ConstantValue::Number(a), ConstantValue::Number(b)) => return Some(a == b),
(ConstantValue::String(a), ConstantValue::String(b)) => return Some(a == b),
(ConstantValue::Boolean(a), ConstantValue::Boolean(b)) => return Some(a == b),
(ConstantValue::BigInt(a), ConstantValue::BigInt(b)) => return Some(a == b),
_ => {}
}
// Cross-type comparisons: Boolean → Number first, then retry.
match (lhs, rhs) {
(ConstantValue::Boolean(b), _) => {
let coerced = ConstantValue::Number(if *b { 1.0 } else { 0.0 });
return try_constant_loosely_equals(&coerced, rhs);
}
(_, ConstantValue::Boolean(b)) => {
let coerced = ConstantValue::Number(if *b { 1.0 } else { 0.0 });
return try_constant_loosely_equals(lhs, &coerced);
}
_ => {}
}
// Number == String → compare ToNumber(string) to number.
// String == Number → compare number to ToNumber(string).
match (lhs, rhs) {
(ConstantValue::Number(n), ConstantValue::String(s)) | (ConstantValue::String(s), ConstantValue::Number(n)) => {
Some(*n == string_to_number(s))
}
// BigInt == Number or Number == BigInt: compare mathematical values.
(ConstantValue::BigInt(b), ConstantValue::Number(n)) | (ConstantValue::Number(n), ConstantValue::BigInt(b)) => {
if n.is_nan() || n.is_infinite() {
return Some(false);
}
if n.fract() != 0.0 {
return Some(false);
}
let bi = parse_bigint(b)?;
// Compare: the number must be a safe integer that equals the BigInt.
// Only fold if the f64 value fits in i64 range for lossless conversion.
if *n > i64::MAX as f64 || *n < i64::MIN as f64 {
return None;
}
let n_i64 = *n as i64;
if n_i64 as f64 != *n {
return None;
}
Some(BigInt::from(n_i64) == bi)
}
// BigInt == String or String == BigInt: parse string as BigInt per StringToBigInt.
// If the string cannot be parsed, the result is false (not equal).
(ConstantValue::BigInt(b), ConstantValue::String(s)) | (ConstantValue::String(s), ConstantValue::BigInt(b)) => {
match string_to_bigint(s) {
Some(s_bi) => {
let bi = parse_bigint(b)?;
Some(bi == s_bi)
}
None => Some(false),
}
}
_ => None,
}
}
#[derive(Clone, Copy)]
enum NonDecimalRadix {
Binary,
Octal,
Hexadecimal,
}
impl NonDecimalRadix {
fn as_u32(self) -> u32 {
match self {
Self::Binary => 2,
Self::Octal => 8,
Self::Hexadecimal => 16,
}
}
}
fn strip_non_decimal_prefix(text: &str) -> Option<(NonDecimalRadix, &str)> {
// Detect an ASCII non-decimal prefix without slicing at a UTF-8-invalid boundary.
// Empty suffixes ("0x", "0o", "0b") remain invalid and are rejected here.
// Callers validate suffix digits before delegating conversion to numeric parsers.
if let Some(rest) = text.strip_prefix("0b").or_else(|| text.strip_prefix("0B")) {
return (!rest.is_empty()).then_some((NonDecimalRadix::Binary, rest));
}
if let Some(rest) = text.strip_prefix("0o").or_else(|| text.strip_prefix("0O")) {
return (!rest.is_empty()).then_some((NonDecimalRadix::Octal, rest));
}
if let Some(rest) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) {
return (!rest.is_empty()).then_some((NonDecimalRadix::Hexadecimal, rest));
}
None
}
fn is_valid_non_decimal_digits(text: &str, radix: NonDecimalRadix) -> bool {
// Keep this JS-specific precheck even though parse_bytes() also validates:
// num-bigint accepts forms that are invalid in JS StringToNumber/StringToBigInt
// non-decimal parsing (for example a leading '+' and '_' separators).
// strip_non_decimal_prefix() guarantees that this suffix is non-empty.
debug_assert!(!text.is_empty());
match radix {
NonDecimalRadix::Binary => text.bytes().all(|b| matches!(b, b'0' | b'1')),
NonDecimalRadix::Octal => text.bytes().all(|b| matches!(b, b'0'..=b'7')),
NonDecimalRadix::Hexadecimal => text.bytes().all(|b| b.is_ascii_hexdigit()),
}
}
/// Implements StringToBigInt per https://tc39.es/ecma262/#sec-stringtobigint.
/// Trims whitespace, handles optional sign (decimal only), and handles
/// 0b/0o/0x prefixes. Returns None if the string is not a valid
/// StringIntegerLiteral.
fn string_to_bigint(s: &Utf16String) -> Option<BigInt> {
use num_bigint::BigInt;
let s_utf8: String = char::decode_utf16(s.0.iter().copied())
.map(|r| r.unwrap_or('\u{FFFD}'))
.collect();
let s_trimmed = s_utf8.trim();
if s_trimmed.is_empty() {
return Some(BigInt::from(0));
}
// Check for non-decimal prefixes (no sign allowed).
if let Some((radix, rest)) = strip_non_decimal_prefix(s_trimmed) {
if !is_valid_non_decimal_digits(rest, radix) {
return None;
}
// Convert validated suffix digits using the parser.
return BigInt::parse_bytes(rest.as_bytes(), radix.as_u32());
}
// Decimal with optional sign. Only allow digits (no dots, no exponents).
let (is_negative, digits) = if let Some(rest) = s_trimmed.strip_prefix('-') {
(true, rest)
} else if let Some(rest) = s_trimmed.strip_prefix('+') {
(false, rest)
} else {
(false, s_trimmed)
};
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
let bi = BigInt::parse_bytes(digits.as_bytes(), 10)?;
Some(if is_negative { -bi } else { bi })
}
/// Constant-fold a binary operation on two BigInt operands.
fn try_constant_fold_bigint_binary(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: BinaryOp,
a_str: &str,
b_str: &str,
) -> Option<ScopedOperand> {
let a = parse_bigint(a_str)?;
let b = parse_bigint(b_str)?;
match op {
// Arithmetic operations: produce a BigInt result.
2026-02-24 08:36:14 -03:00
BinaryOp::Addition => Some(generator.add_constant_bigint((&a + &b).to_string())),
BinaryOp::Subtraction => Some(generator.add_constant_bigint((&a - &b).to_string())),
BinaryOp::Multiplication => Some(generator.add_constant_bigint((&a * &b).to_string())),
BinaryOp::Division => {
if b.is_zero() {
return None; // Division by zero throws at runtime.
}
use num_integer::Integer;
let (quotient, _) = a.div_rem(&b);
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint(quotient.to_string()))
}
BinaryOp::Modulo => {
if b.is_zero() {
return None; // Modulo by zero throws at runtime.
}
// JS BigInt remainder has the sign of the dividend (truncated division).
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint((&a % &b).to_string()))
}
BinaryOp::Exponentiation => {
if b.is_negative() {
return None; // Negative exponent throws at runtime.
}
let exp = b.to_u32()?;
// Only fold small exponents to avoid huge results.
if exp > 1000 {
return None;
}
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint(num_traits::pow::pow(a, exp as usize).to_string()))
}
// Comparison operations: produce a Boolean result.
BinaryOp::StrictlyEquals | BinaryOp::LooselyEquals => Some(generator.add_constant_boolean(a == b)),
BinaryOp::StrictlyInequals | BinaryOp::LooselyInequals => Some(generator.add_constant_boolean(a != b)),
2026-02-24 08:36:14 -03:00
BinaryOp::LessThan => Some(generator.add_constant_boolean(a < b)),
BinaryOp::LessThanEquals => Some(generator.add_constant_boolean(a <= b)),
BinaryOp::GreaterThan => Some(generator.add_constant_boolean(a > b)),
BinaryOp::GreaterThanEquals => Some(generator.add_constant_boolean(a >= b)),
// Bitwise operations on BigInt.
2026-02-24 08:36:14 -03:00
BinaryOp::BitwiseAnd => Some(generator.add_constant_bigint((&a & &b).to_string())),
BinaryOp::BitwiseOr => Some(generator.add_constant_bigint((&a | &b).to_string())),
BinaryOp::BitwiseXor => Some(generator.add_constant_bigint((&a ^ &b).to_string())),
BinaryOp::LeftShift => {
let shift = b.to_u64()?;
if shift > 512 {
return None;
}
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint((&a << shift as usize).to_string()))
}
BinaryOp::RightShift => {
let shift = b.to_u64()?;
// BigInt right shift for negative numbers floors toward negative infinity.
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_bigint(bigint_right_shift(&a, shift as usize).to_string()))
}
// UnsignedRightShift throws TypeError for BigInt.
_ => None,
}
}
/// BigInt arithmetic right shift that floors toward negative infinity
/// (matching JS spec 6.1.6.2.9 BigInt::signedRightShift).
fn bigint_right_shift(value: &BigInt, shift: usize) -> BigInt {
if !value.is_negative() || shift == 0 {
return value >> shift;
}
// For negative values, we need floor division behavior.
// Check if any of the shifted-out bits are set.
let divisor = BigInt::one() << shift;
use num_integer::Integer;
value.div_floor(&divisor)
}
/// Try to constant-fold a binary operation when both operands are constants.
// 6.1.6.1.3 Number::exponentiate ( base, exponent )
// https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate
// Rust's f64::powf follows C's pow() which returns 1.0 for pow(±1, ±∞),
// but JS specifies NaN when abs(base) is 1 and exponent is ±∞.
fn js_exponentiate(base: f64, exponent: f64) -> f64 {
if exponent.is_infinite() && base.abs() == 1.0 {
return f64::NAN;
}
base.powf(exponent)
}
/// Parse a non-decimal integer string via BigInt to f64, matching
/// UnsignedBigInteger::from_base() + to_double(). This avoids u64 overflow
/// for large literals like 0x10000000000000000.
fn bigint_string_to_f64(s: &str, radix: u32) -> f64 {
use num_bigint::BigUint;
match BigUint::parse_bytes(s.as_bytes(), radix) {
Some(n) => {
use num_traits::ToPrimitive;
n.to_f64().unwrap_or(f64::INFINITY)
}
None => f64::NAN,
}
}
// 7.1.4.1.1 StringToNumber ( str ), https://tc39.es/ecma262/#sec-stringtonumber
fn string_to_number(s: &Utf16String) -> f64 {
let text: String = char::decode_utf16(s.0.iter().copied())
.map(|r| r.unwrap_or('\u{FFFD}'))
.collect();
let trimmed = text.trim();
if trimmed.is_empty() {
return 0.0;
}
if trimmed == "Infinity" || trimmed == "+Infinity" {
return f64::INFINITY;
}
if trimmed == "-Infinity" {
return f64::NEG_INFINITY;
}
if let Some((radix, rest)) = strip_non_decimal_prefix(trimmed) {
if !is_valid_non_decimal_digits(rest, radix) {
return f64::NAN;
}
// Convert validated suffix digits using the parser.
return bigint_string_to_f64(rest, radix.as_u32());
}
if !trimmed
.bytes()
.all(|b| b.is_ascii_digit() || b == b'.' || b == b'e' || b == b'E' || b == b'+' || b == b'-')
{
return f64::NAN;
}
trimmed.parse::<f64>().unwrap_or(f64::NAN)
}
/// Convert a constant value to a JS number for constant folding purposes.
fn constant_to_number(val: &ConstantValue) -> Option<f64> {
match val {
ConstantValue::Number(n) => Some(*n),
ConstantValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
ConstantValue::Null => Some(0.0),
ConstantValue::Undefined => Some(f64::NAN),
ConstantValue::String(s) => Some(string_to_number(s)),
_ => None,
}
}
/// Convert a constant value to a JS string (ToString) for string concatenation.
/// Returns None for types where ToString requires runtime (e.g. objects).
fn constant_to_string(val: &ConstantValue) -> Option<Utf16String> {
match val {
ConstantValue::String(s) => Some(s.clone()),
ConstantValue::Number(n) => Some(super::ffi::js_number_to_utf16(*n)),
ConstantValue::Boolean(b) => Some(if *b {
Utf16String(utf16!("true").to_vec())
} else {
Utf16String(utf16!("false").to_vec())
}),
ConstantValue::Null => Some(Utf16String(utf16!("null").to_vec())),
ConstantValue::Undefined => Some(Utf16String(utf16!("undefined").to_vec())),
ConstantValue::BigInt(s) => {
// BigInt.prototype.toString() returns the decimal string representation.
Some(Utf16String(s.encode_utf16().collect()))
}
_ => None,
}
}
/// Compare a BigInt and a Number using Abstract Relational Comparison semantics.
/// Returns None if comparison cannot be folded (non-safe integer, etc).
/// Returns Some(None) for NaN (undefined result - all comparisons return false).
/// Returns Some(Some(Ordering)) for the BigInt relative to the Number.
fn compare_bigint_and_number(bigint_str: &str, number: f64) -> Option<Option<std::cmp::Ordering>> {
use std::cmp::Ordering;
if number.is_nan() {
return Some(None); // Comparisons with NaN return undefined → false.
}
if number == f64::INFINITY {
return Some(Some(Ordering::Less)); // Any BigInt < +Infinity
}
if number == f64::NEG_INFINITY {
return Some(Some(Ordering::Greater)); // Any BigInt > -Infinity
}
let bigint = parse_bigint(bigint_str)?;
// Only fold if the f64 value is a safe integer for lossless comparison.
if number.fract() != 0.0 {
// The number has a fractional part, so we can still compare:
// floor(number) < bigint means bigint > number, etc.
let floored = number.floor();
if floored > i64::MAX as f64 || floored < i64::MIN as f64 {
return None;
}
let floored_i64 = floored as i64;
let floored_bigint = BigInt::from(floored_i64);
if bigint <= floored_bigint {
// bigint <= floor(number) means bigint < number
return Some(Some(Ordering::Less));
} else {
// bigint > floor(number) means bigint > number (since number = floor + fract where fract > 0)
return Some(Some(Ordering::Greater));
}
}
if number > i64::MAX as f64 || number < i64::MIN as f64 {
return None;
}
let number_i64 = number as i64;
if number_i64 as f64 != number {
return None;
}
let number_bigint = BigInt::from(number_i64);
Some(Some(bigint.cmp(&number_bigint)))
}
fn try_constant_fold_binary(
2026-02-24 08:36:14 -03:00
generator: &mut Generator,
op: BinaryOp,
lhs: &ScopedOperand,
rhs: &ScopedOperand,
) -> Option<ScopedOperand> {
2026-02-24 08:36:14 -03:00
let lhs_const = generator.get_constant(lhs)?;
let rhs_const = generator.get_constant(rhs)?;
// BigInt constant folding: if both operands are BigInt, handle separately.
// Clone strings to release the immutable borrow on gen before the mutable call.
if let (ConstantValue::BigInt(a), ConstantValue::BigInt(b)) = (lhs_const, rhs_const) {
let a = a.clone();
let b = b.clone();
2026-02-24 08:36:14 -03:00
return try_constant_fold_bigint_binary(generator, op, &a, &b);
}
match op {
BinaryOp::Addition => {
// If either operand is a string, do string concatenation using ToString.
if matches!(lhs_const, ConstantValue::String(_)) || matches!(rhs_const, ConstantValue::String(_)) {
let a = constant_to_string(lhs_const)?;
let b = constant_to_string(rhs_const)?;
let mut result = a;
result.0.extend_from_slice(&b);
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_string(result));
}
// Numeric addition: both operands coerced to number
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(a + b))
}
BinaryOp::Subtraction => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(a - b))
}
BinaryOp::Multiplication => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(a * b))
}
BinaryOp::Division => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(a / b))
}
BinaryOp::Modulo => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(a % b))
}
BinaryOp::Exponentiation => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number(js_exponentiate(a, b)))
}
BinaryOp::StrictlyEquals | BinaryOp::StrictlyInequals => {
let equal = match (lhs_const, rhs_const) {
(ConstantValue::Number(a), ConstantValue::Number(b)) => a == b,
(ConstantValue::String(a), ConstantValue::String(b)) => a == b,
(ConstantValue::Boolean(a), ConstantValue::Boolean(b)) => a == b,
(ConstantValue::Null, ConstantValue::Null) => true,
(ConstantValue::Undefined, ConstantValue::Undefined) => true,
// Different types are never strictly equal.
_ => false,
};
let result = if op == BinaryOp::StrictlyInequals {
!equal
} else {
equal
};
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(result))
}
BinaryOp::GreaterThan | BinaryOp::GreaterThanEquals | BinaryOp::LessThan | BinaryOp::LessThanEquals => {
// String-string comparison is lexicographic (by UTF-16 code units).
if let (ConstantValue::String(a), ConstantValue::String(b)) = (lhs_const, rhs_const) {
let result = match op {
BinaryOp::GreaterThan => a.0 > b.0,
BinaryOp::GreaterThanEquals => a.0 >= b.0,
BinaryOp::LessThan => a.0 < b.0,
BinaryOp::LessThanEquals => a.0 <= b.0,
_ => unreachable!("outer match arm only matches comparison operators"),
};
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_boolean(result));
}
// BigInt-Number/Boolean cross-type comparison.
// Per spec, Booleans are converted to Number first.
let lhs_for_cmp = match lhs_const {
ConstantValue::Boolean(b) => &ConstantValue::Number(if *b { 1.0 } else { 0.0 }),
other => other,
};
let rhs_for_cmp = match rhs_const {
ConstantValue::Boolean(b) => &ConstantValue::Number(if *b { 1.0 } else { 0.0 }),
other => other,
};
// BigInt vs Number comparison using Abstract Relational Comparison.
let bigint_ord = match (lhs_for_cmp, rhs_for_cmp) {
(ConstantValue::BigInt(b), ConstantValue::Number(n)) => compare_bigint_and_number(b, *n),
(ConstantValue::Number(n), ConstantValue::BigInt(b)) => {
compare_bigint_and_number(b, *n).map(|o| o.map(|o| o.reverse()))
}
// BigInt vs String: per spec, parse the string as a BigInt
// using StringToBigInt, then compare the two BigInts.
// If the string is not a valid StringIntegerLiteral,
// the result is undefined (= false for all comparisons).
(ConstantValue::BigInt(b), ConstantValue::String(s)) => match string_to_bigint(s) {
Some(rhs_bi) => {
let lhs_bi = parse_bigint(b)?;
Some(Some(lhs_bi.cmp(&rhs_bi)))
}
None => Some(None),
},
(ConstantValue::String(s), ConstantValue::BigInt(b)) => match string_to_bigint(s) {
Some(lhs_bi) => {
let rhs_bi = parse_bigint(b)?;
Some(Some(lhs_bi.cmp(&rhs_bi)))
}
None => Some(None),
},
_ => None,
};
if let Some(ord) = bigint_ord {
let result = match op {
BinaryOp::GreaterThan => matches!(ord, Some(std::cmp::Ordering::Greater)),
BinaryOp::GreaterThanEquals => {
matches!(ord, Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal))
}
BinaryOp::LessThan => matches!(ord, Some(std::cmp::Ordering::Less)),
BinaryOp::LessThanEquals => {
matches!(ord, Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal))
}
_ => unreachable!("outer match arm only matches comparison operators"),
};
2026-02-24 08:36:14 -03:00
return Some(generator.add_constant_boolean(result));
}
let a = constant_to_number(lhs_for_cmp)?;
let b = constant_to_number(rhs_for_cmp)?;
let result = match op {
BinaryOp::GreaterThan => a > b,
BinaryOp::GreaterThanEquals => a >= b,
BinaryOp::LessThan => a < b,
BinaryOp::LessThanEquals => a <= b,
_ => unreachable!("outer match arm only matches comparison operators"),
};
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(result))
}
BinaryOp::LooselyEquals => {
let result = try_constant_loosely_equals(lhs_const, rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(result))
}
BinaryOp::LooselyInequals => {
let result = try_constant_loosely_equals(lhs_const, rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_boolean(!result))
}
BinaryOp::BitwiseAnd => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_int32(a) & to_int32(b)) as f64))
}
BinaryOp::BitwiseOr => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_int32(a) | to_int32(b)) as f64))
}
BinaryOp::BitwiseXor => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_int32(a) ^ to_int32(b)) as f64))
}
BinaryOp::LeftShift => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_int32(a) << (to_u32(b) & 0x1f)) as f64))
}
BinaryOp::RightShift => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_int32(a) >> (to_u32(b) & 0x1f)) as f64))
}
BinaryOp::UnsignedRightShift => {
let a = constant_to_number(lhs_const)?;
let b = constant_to_number(rhs_const)?;
2026-02-24 08:36:14 -03:00
Some(generator.add_constant_number((to_u32(a) >> (to_u32(b) & 0x1f)) as f64))
}
_ => None,
}
}
// =============================================================================
// NaN-boxing helpers
// =============================================================================
// NanBoxed Value encoding helpers (ABI-compatible with GC::NanBoxedValue).
// Used by NewPrimitiveArray to encode constant primitive values inline.
const NANBOX_TAG_SHIFT: u64 = 48;
const NANBOX_BASE_TAG: u64 = 0x7FF8;
const NANBOX_INT32_TAG: u64 = 0b010 | NANBOX_BASE_TAG;
const NANBOX_BOOLEAN_TAG: u64 = 0b001 | NANBOX_BASE_TAG;
const NANBOX_NULL_TAG: u64 = 0b111 | NANBOX_BASE_TAG;
const NANBOX_EMPTY_TAG: u64 = 0b011 | NANBOX_BASE_TAG;
const NEGATIVE_ZERO_BITS: u64 = 1u64 << 63;
fn nanboxed_number(value: f64) -> u64 {
let is_negative_zero = value.to_bits() == NEGATIVE_ZERO_BITS;
if value >= i32::MIN as f64 && value <= i32::MAX as f64 && value.trunc() == value && !is_negative_zero {
(NANBOX_INT32_TAG << NANBOX_TAG_SHIFT) | ((value as i32 as u32) as u64)
} else if value.is_nan() {
// Canon NaN
0x7FF8_0000_0000_0000u64
} else {
value.to_bits()
}
}
fn nanboxed_boolean(value: bool) -> u64 {
(NANBOX_BOOLEAN_TAG << NANBOX_TAG_SHIFT) | (value as u64)
}
fn nanboxed_null() -> u64 {
NANBOX_NULL_TAG << NANBOX_TAG_SHIFT
}
fn nanboxed_empty() -> u64 {
NANBOX_EMPTY_TAG << NANBOX_TAG_SHIFT
}
// =============================================================================
// Error message utilities
// =============================================================================
/// Intern the base expression as an identifier for error messages like
/// "Cannot access property X on null object Y".
fn intern_base_identifier(generator: &mut Generator, base: &Expression) -> Option<IdentifierTableIndex> {
2026-02-24 08:36:14 -03:00
expression_identifier(base).map(|s| generator.intern_identifier(&s))
}
/// Try to produce a human-readable name for an expression (for error messages).
/// Returns None for expressions that have no meaningful name.
fn expression_identifier(expression: &Expression) -> Option<Utf16String> {
match &expression.inner {
ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()),
ExpressionKind::StringLiteral(s) => {
let mut result = Utf16String(utf16!("'").to_vec());
result.0.extend_from_slice(s);
result.0.extend_from_slice(utf16!("'"));
Some(result)
}
ExpressionKind::NumericLiteral(n) => Some(super::ffi::js_number_to_utf16(*n)),
ExpressionKind::This => Some(Utf16String(utf16!("this").to_vec())),
ExpressionKind::Member(data) => {
let mut s = Utf16String::new();
if let Some(obj_id) = expression_identifier(&data.object) {
s.0.extend_from_slice(&obj_id);
}
if let Some(property_id) = expression_identifier(&data.property) {
if data.computed {
s.0.extend_from_slice(utf16!("["));
s.0.extend_from_slice(&property_id);
s.0.extend_from_slice(utf16!("]"));
} else {
s.0.extend_from_slice(utf16!("."));
s.0.extend_from_slice(&property_id);
}
}
Some(s)
}
_ => None,
}
}
/// Produce a human-readable string for call expression error messages.
/// Unlike expression_identifier, this always produces output for known types
/// (using "<object>" for unrecognized sub-expressions).
fn expression_string_approximation(expression: &Expression) -> Option<Utf16String> {
match &expression.inner {
ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()),
ExpressionKind::Member(_) => Some(member_to_string_approximation(expression)),
_ => None,
}
}
fn member_to_string_approximation(expression: &Expression) -> Utf16String {
match &expression.inner {
ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(),
ExpressionKind::Member(data) => {
let mut s = member_to_string_approximation(&data.object);
let property_str = member_to_string_approximation(&data.property);
if data.computed {
s.0.extend_from_slice(utf16!("["));
s.0.extend_from_slice(&property_str);
s.0.extend_from_slice(utf16!("]"));
} else {
s.0.extend_from_slice(utf16!("."));
s.0.extend_from_slice(&property_str);
}
s
}
ExpressionKind::StringLiteral(s) => {
let mut result = Utf16String(utf16!("'").to_vec());
result.0.extend_from_slice(s);
result.0.extend_from_slice(utf16!("'"));
result
}
ExpressionKind::NumericLiteral(n) => {
let s = format_double_for_display(*n);
s.encode_utf16().collect()
}
ExpressionKind::This => Utf16String(utf16!("this").to_vec()),
ExpressionKind::PrivateIdentifier(ident) => ident.name.clone(),
_ => Utf16String(utf16!("<object>").to_vec()),
}
}
/// Format a double matching AK's `Utf16String::formatted("{}", double)`.
/// Uses ECMA-262 rules: scientific notation when the decimal exponent n
/// satisfies n < -5 or n > 21, otherwise regular decimal notation.
fn format_double_for_display(n: f64) -> String {
if n.is_nan() {
return "NaN".to_string();
}
if n.is_infinite() {
return if n > 0.0 { "Infinity" } else { "-Infinity" }.to_string();
}
if n == 0.0 {
return "0".to_string();
}
// Get the scientific notation representation to extract the exponent.
let e_str = format!("{n:e}");
if let Some(e_pos) = e_str.find('e') {
let exp_str = &e_str[e_pos + 1..];
let displayed_exponent = exp_str.parse::<i32>().unwrap_or(0);
// AK uses: n < -5 || n > 21 where n = displayed_exponent + 1.
// Equivalently: displayed_exponent < -6 || displayed_exponent > 20.
if !(-6..=20).contains(&displayed_exponent) {
let mantissa_part = &e_str[..e_pos];
if displayed_exponent < 0 {
return format!("{mantissa_part}e{displayed_exponent}");
} else {
return format!("{mantissa_part}e+{displayed_exponent}");
}
}
}
format!("{n}")
}