/* * 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`: //! - `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::{LiteralValueKind, WellKnownSymbolKind}; use super::generator::{ BlockBoundaryType, ConstantValue, FinallyContext, Generator, 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, generator: &mut Generator, preferred_dst: Option<&ScopedOperand>, ) -> Option { let saved_source_start = generator.current_source_start; let saved_source_end = generator.current_source_end; generator.current_source_start = expression.range.start.offset; generator.current_source_end = expression.range.end.offset; let result = generate_expression_inner(expression, generator, preferred_dst); generator.current_source_start = saved_source_start; generator.current_source_end = saved_source_end; result } fn generate_expression_inner( expression: &Expression, generator: &mut Generator, preferred_dst: Option<&ScopedOperand>, ) -> Option { // 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(_) ) { generator.pending_lhs_name = None; } match &expression.inner { // === Literals === ExpressionKind::NumericLiteral(value) => Some(generator.add_constant_number(*value)), ExpressionKind::BooleanLiteral(value) => Some(generator.add_constant_boolean(*value)), 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()); Some(generator.add_constant_bigint(digits.to_string())) } ExpressionKind::RegExpLiteral(data) => { let source_index = generator.intern_string(&data.pattern); let flags_index = generator.intern_string(&data.flags); let compiled = data.compiled_regex.take(); 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. if generator.function_environment_needed { emit_resolve_this_if_needed(generator); } 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() { 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 === 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) => { let value = generate_expression_or_undefined(inner, generator, None); // Allocate received_completion registers before the await. 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( generator, &value, &received_completion, &received_completion_type, &received_completion_value, )) } // === MetaProperty === ExpressionKind::MetaProperty(MetaPropertyType::NewTarget) => { let dst = choose_dst(generator, preferred_dst); generator.emit(Instruction::GetNewTarget { dst: dst.operand() }); Some(dst) } ExpressionKind::MetaProperty(MetaPropertyType::ImportMeta) => { 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 { Some(o) => generate_expression(o, generator, None)?, None => generator.add_constant_undefined(), }; 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. let current_base = generator.allocate_register(); let current_value = choose_dst(generator, preferred_dst); let undef = generator.add_constant_undefined(); generator.emit_mov(¤t_base, &undef); generate_optional_chain_inner( generator, &oc_data.base, &oc_data.references, ¤t_value, ¤t_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); generate_expression_or_undefined(&data.arguments[0].value, generator, None) } else { generate_arguments_array(generator, &data.arguments) }; 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 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, } } fn generate_unary_expression( generator: &mut Generator, op: UnaryOp, operand: &Expression, preferred_dst: Option<&ScopedOperand>, ) -> Option { // 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 { 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); } 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 { return Some(emit_delete_reference(generator, operand)); } // Allocate dst before operand. // Also optimize !!x -> ToBoolean(x). if op == UnaryOp::Not { let dst = choose_dst(generator, preferred_dst); if let ExpressionKind::Unary { op: UnaryOp::Not, operand: inner, } = &operand.inner { let value = generate_expression(inner, generator, None)?; if let Some(folded) = try_constant_fold_to_boolean(generator, &value) { return Some(folded); } generator.emit(Instruction::ToBoolean { dst: dst.operand(), value: value.operand(), }); return Some(dst); } let value = generate_expression(operand, generator, None)?; if let Some(folded) = try_constant_fold_unary(generator, op, &value) { return Some(folded); } generator.emit(Instruction::Not { dst: dst.operand(), src: value.operand(), }); return Some(dst); } let value = generate_expression(operand, generator, None)?; // OPTIMIZATION: constant fold unary operations on constants. if let Some(folded) = try_constant_fold_unary(generator, op, &value) { return Some(folded); } let dst = choose_dst(generator, preferred_dst); match op { UnaryOp::BitwiseNot => { generator.emit(Instruction::BitwiseNot { dst: dst.operand(), src: value.operand(), }); } UnaryOp::Not => unreachable!("Not is handled by early return above"), UnaryOp::Plus => { generator.emit(Instruction::UnaryPlus { dst: dst.operand(), src: value.operand(), }); } UnaryOp::Minus => { generator.emit(Instruction::UnaryMinus { dst: dst.operand(), src: value.operand(), }); } UnaryOp::Typeof => unreachable!("Typeof is handled by early return above"), UnaryOp::Void => { 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( generator: &mut Generator, op: BinaryOp, lhs: &Expression, rhs: &Expression, preferred_dst: Option<&ScopedOperand>, ) -> Option { // Special case: `#privateId in obj` uses HasPrivateId instead of In. 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 { generator.add_constant_number(to_int32(*n) as f64) } else { generate_expression(lhs, generator, None)? } } _ => 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 { generator.add_constant_number(to_int32(*n) as f64) } else { generate_expression(rhs, generator, None)? } } BinaryOp::LeftShift | BinaryOp::RightShift | BinaryOp::UnsignedRightShift => { if let ExpressionKind::NumericLiteral(n) = &rhs.inner { generator.add_constant_number(to_u32(*n) as f64) } else { generate_expression(rhs, generator, None)? } } _ => generate_expression(rhs, generator, None)?, }; // OPTIMIZATION: constant folding for binary operations on constants. if let Some(folded) = try_constant_fold_binary(generator, op, &lhs_val, &rhs_val) { return Some(folded); } let dst = choose_dst(generator, preferred_dst); emit_binary_op(generator, op, &dst, &lhs_val, &rhs_val); Some(dst) } fn generate_function_expression( generator: &mut Generator, function_id: FunctionId, preferred_dst: Option<&ScopedOperand>, ) -> ScopedOperand { 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 { let parent = generator .lexical_environment_register_stack .last() .cloned() .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, }); 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, ); generator.emit(Instruction::CreateVariable { identifier: id, mode: EnvironmentMode::Lexical as u32, is_immutable: true, is_global: false, is_strict: false, }); Some(id) } else { None }; 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 { generator.pending_lhs_name.take() } else { None }; let lhs_name_str: Option = 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 }; 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 { generator.emit(Instruction::InitializeLexicalBinding { identifier: name_id.expect("has_name guarantees name_id is set"), src: dst.operand(), cache: EnvironmentCoordinate::empty(), }); generator.end_variable_scope(); } dst } fn generate_array_expression( generator: &mut Generator, elements: &[Option], 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 = 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(); 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 = Vec::with_capacity(pre_spread_count); for element in &elements[..pre_spread_count] { match element { Some(e) => { let val = generate_expression_or_undefined(e, generator, None); scoped_arguments.push(generator.copy_if_needed_to_preserve_evaluation_order(&val)); } None => { scoped_arguments.push(generator.add_constant_empty()); } } } let dst = choose_dst(generator, preferred_dst); let arguments: Vec = scoped_arguments.iter().map(|s| s.operand()).collect(); 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 => { 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(_)); 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( generator: &mut Generator, object: &Expression, property: &Expression, computed: bool, preferred_dst: Option<&ScopedOperand>, ) -> Option { 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 let this_value = emit_resolve_this_binding(generator); let computed_key = if computed { Some(generate_expression(property, generator, None)?) } else { None }; let super_base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: super_base.operand(), }); let dst = choose_dst(generator, preferred_dst); if let Some(key) = computed_key { 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); } let obj = generate_expression(object, generator, None)?; let obj = generator.copy_if_needed_to_preserve_evaluation_order(&obj); let base_id = intern_base_identifier(generator, object); if computed { 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 let dst = choose_dst(generator, preferred_dst); if let ExpressionKind::Identifier(ident) = &property.inner { emit_get_by_id(generator, &dst, &obj, &ident.name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner { 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( generator: &mut Generator, argument: Option<&Expression>, is_yield_from: bool, ) -> ScopedOperand { // Allocate completion registers before evaluating the argument. 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 { generate_expression_or_undefined(argument, generator, None) } else { generator.add_constant_undefined() }; if is_yield_from { return generate_yield_from( generator, value, &received_completion, &received_completion_type, &received_completion_value, ); } // Create continuation block, call generate_yield, then handle // completion checking. 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 { let reg = generator.allocate_register(); generator.emit_mov_raw(reg.operand(), generator.exception_operand()); Some(reg) } else { None }; generate_yield( generator, continuation_block, &value, &received_completion, &received_completion_type, &received_completion_value, generator.is_in_async_generator_function(), ); generator.switch_to_basic_block(continuation_block); // Restore exception register after resuming. if let Some(ref saved) = saved_exception { generator.emit_mov_raw(generator.exception_operand(), saved.operand()); } 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(), }); 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(), }); generator.emit_jump_if(&type_is_normal, normal_block, throw_cont); let throw_value_block = generator.make_block(); let return_value_block = generator.make_block(); 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(), }); generator.emit_jump_if(&type_is_throw, throw_value_block, return_value_block); generator.switch_to_basic_block(throw_value_block); generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: received_completion_value.operand(), }); generator.switch_to_basic_block(return_value_block); generator.generate_return(&received_completion_value); 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, 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, generator: &mut Generator, preferred_dst: Option<&ScopedOperand>, ) -> Option { let saved_source_start = generator.current_source_start; let saved_source_end = generator.current_source_end; generator.current_source_start = statement.range.start.offset; generator.current_source_end = statement.range.end.offset; let result = match &statement.inner { StatementKind::Empty | StatementKind::Error | StatementKind::ErrorDeclaration => None, StatementKind::Debugger => None, // === ExpressionStatement === 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. 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 { generator.annexb_function_names.insert(name.clone()); } generate_scope_children(generator, &scope, preferred_dst) } // === If === StatementKind::If(data) => generate_if_statement( generator, &data.test, &data.consequent, data.alternate.as_deref(), preferred_dst, ), // === While === StatementKind::While(data) => { generate_while_statement(generator, &data.test, &data.body, preferred_dst) } // === DoWhile === StatementKind::DoWhile { test, body } => { generate_do_while_statement(generator, test, body, preferred_dst) } // === For === StatementKind::For { init, test, update, body, } => generate_for_statement( generator, init.as_ref(), test.as_deref(), update.as_deref(), body, preferred_dst, ), // === Return === StatementKind::Return(value) => { let val = match value { Some(expression) => { let v = generate_expression_or_undefined(expression, generator, None); // Async functions implicitly await an explicit return value. // Bare `return;` does NOT await (per spec). 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( generator, &v, &received_completion, &received_completion_type, &received_completion_value, ) } else { v } } None => generator.add_constant_undefined(), }; generator.generate_return(&val); None } // === Throw === StatementKind::Throw(expression) => { let val = generate_expression(expression, generator, None)?; generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: val.operand() }); None } // === Variable declarations === StatementKind::VariableDeclaration { kind, declarations } => { generate_variable_declaration(generator, *kind, declarations); None } // === Break === StatementKind::Break { target_label } => { generator.generate_break(target_label.as_deref()); None } // === Continue === StatementKind::Continue { target_label } => { generator.generate_continue(target_label.as_deref()); None } // === Labelled === StatementKind::Labelled { label, item } => { generate_labelled_statement(generator, label, item, preferred_dst) } // === Switch === StatementKind::Switch(data) => generate_switch_statement(generator, data, preferred_dst), // === Try === StatementKind::Try(data) => generate_try_statement(generator, data, preferred_dst), // === FunctionDeclaration === StatementKind::FunctionDeclaration { name, is_hoisted, .. } => { if is_hoisted.get() { // Annex B.3.3: Copy the function from the lexical (block) scope // to the var scope. if let Some(name_ident) = name { 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(), }); } } None } // === With === StatementKind::With { object, body } => { let obj = generate_expression(object, generator, None)?; 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); generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment); let result = generate_statement(body, generator, preferred_dst); generator.end_variable_scope(); // Per spec 13.11.7 step 10: if body completion value is empty, // return NormalCompletion(undefined). Some(result.unwrap_or_else(|| generator.add_constant_undefined())) } // === ForIn / ForOf / ForAwaitOf === StatementKind::ForInOf { kind, lhs, rhs, body, } => generate_for_in_of_statement(generator, *kind, lhs, rhs, body, preferred_dst), // === UsingDeclaration === StatementKind::UsingDeclaration { .. } => { // Disposal semantics are not yet implemented. let error = generator.allocate_register(); let msg = generator.intern_string(utf16!("TODO: UsingDeclaration")); generator.emit(Instruction::NewTypeError { dst: error.operand(), error_string: msg, }); generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: error.operand(), }); // Switch to a dead block so subsequent codegen doesn't crash. let dead = generator.make_block(); generator.switch_to_basic_block(dead); None } // === ClassDeclaration === StatementKind::ClassDeclaration(data) => { 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 { 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 { 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(_) => { generate_statement(child_statement, generator, None) } _ => { // export default // 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)); let value = generate_statement(child_statement, generator, None); generator.pending_lhs_name = None; if let Some(value) = value { 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 { expression, field_name, } => { // 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 !field_name.is_empty() { generator.pending_lhs_name = Some(generator.intern_identifier(field_name)); } let value = generate_expression_or_undefined(expression, generator, None); generator.pending_lhs_name = None; generator.emit(Instruction::Return { value: value.operand(), }); None } }; 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( generator: &mut Generator, argument: &ScopedOperand, received_completion: &ScopedOperand, received_completion_type: &ScopedOperand, received_completion_value: &ScopedOperand, ) -> ScopedOperand { let continuation = generator.make_block(); generator.emit(Instruction::Await { continuation_label: continuation, argument: argument.operand(), }); generator.switch_to_basic_block(continuation); 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(), }); 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(), }); generator.emit_jump_if(&is_normal, normal_block, throw_block); generator.switch_to_basic_block(throw_block); generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: received_completion_value.operand(), }); 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( generator: &mut Generator, value: ScopedOperand, received_completion: &ScopedOperand, received_completion_type: &ScopedOperand, received_completion_value: &ScopedOperand, ) -> ScopedOperand { let is_async = generator.is_in_async_generator_function(); // 4. Let iteratorRecord be ? GetIterator(value, generatorKind). 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; 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). 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, 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]]. 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(), }); generator.emit_jump_if(&is_normal, type_is_normal_block, is_type_throw_block); // ========================================================================= // a. If received.[[Type]] is normal, then // ========================================================================= generator.switch_to_basic_block(type_is_normal_block); // i. Let innerResult be ? Call(next, iterator, « received.[[Value]] »). 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( generator, &inner_result, received_completion, received_completion_type, received_completion_value, ); generator.emit_mov(&inner_result, &awaited); } // iii. If innerResult is not an Object, throw a TypeError exception. generator.emit(Instruction::ThrowIfNotObject { src: inner_result.operand(), }); // iv. Let done be ? IteratorComplete(innerResult). 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). 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, ); 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. generator.switch_to_basic_block(type_is_normal_not_done_block); { let current_value = generator.allocate_register(); emit_get_by_id( generator, ¤t_value, &inner_result, utf16!("value"), None, ); generate_yield( generator, continuation_block, ¤t_value, received_completion, received_completion_type, received_completion_value, false, ); } // ========================================================================= // b. Else if received.[[Type]] is throw, then // ========================================================================= 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(), }); generator.emit_jump_if(&is_throw, type_is_throw_block, type_is_return_block); generator.switch_to_basic_block(type_is_throw_block); // i. Let throw be ? GetMethod(iterator, "throw"). 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 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, }); generator.switch_to_basic_block(throw_method_defined_block); // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »). 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( generator, &inner_result, received_completion, received_completion_type, received_completion_value, ); generator.emit_mov(&inner_result, &awaited); } // 4. If innerResult is not an Object, throw a TypeError exception. generator.emit(Instruction::ThrowIfNotObject { src: inner_result.operand(), }); // 5. Let done be ? IteratorComplete(innerResult). emit_get_by_id(generator, &done, &inner_result, utf16!("done"), None); // 6. If done is true, return ? IteratorValue(innerResult). 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, ); 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. generator.switch_to_basic_block(type_is_throw_not_done_block); { let yield_value = generator.allocate_register(); emit_get_by_id( generator, &yield_value, &inner_result, utf16!("value"), None, ); generate_yield( generator, continuation_block, &yield_value, received_completion, received_completion_type, received_completion_value, false, ); } // throw is undefined: close iterator, throw TypeError. generator.switch_to_basic_block(throw_method_undefined_block); if is_async { // AsyncIteratorClose: get return method, call it, await, check object. 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, }); 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, }); generator.switch_to_basic_block(call_return_block); 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( 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, }); generator.switch_to_basic_block(after_close); } else { // Sync: IteratorClose with Normal completion. 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. let exception = generator.allocate_register(); let error_string = generator.intern_string(utf16!( "yield* protocol violation: iterator must have a throw method" )); generator.emit(Instruction::NewTypeError { dst: exception.operand(), error_string, }); generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: exception.operand(), }); // ========================================================================= // c. Else (received.[[Type]] is return) // ========================================================================= generator.switch_to_basic_block(type_is_return_block); // ii. Let return be ? GetMethod(iterator, "return"). 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]]. 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, }); 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( generator, received_completion_value, received_completion, received_completion_type, received_completion_value, ); } // 2. Return received (return completion). generator.generate_return(received_completion_value); generator.switch_to_basic_block(return_is_defined_block); // iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »). 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( generator, &inner_return_result, received_completion, received_completion_type, received_completion_value, ); generator.emit_mov(&inner_return_result, &awaited); } // vi. If innerReturnResult is not an Object, throw a TypeError exception. generator.emit(Instruction::ThrowIfNotObject { src: inner_return_result.operand(), }); // vii. Let done be ? IteratorComplete(innerReturnResult). emit_get_by_id(generator, &done, &inner_return_result, utf16!("done"), None); // viii. If done is true, return IteratorValue(innerReturnResult). 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, ); generator.switch_to_basic_block(type_is_return_done_block); let inner_return_result_value = generator.allocate_register(); emit_get_by_id( generator, &inner_return_result_value, &inner_return_result, utf16!("value"), None, ); generator.generate_return(&inner_return_result_value); // ix/x. Yield IteratorValue(innerReturnResult), receive new completion. 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( generator, continuation_block, &received, received_completion, received_completion_type, received_completion_value, false, ); // ========================================================================= // Continuation block: resume after any yield, extract completion, loop back. // ========================================================================= 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(), }); generator.emit(Instruction::Jump { target: loop_block }); // ========================================================================= // Loop end: return the accumulated return_value. // ========================================================================= 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( generator: &mut Generator, continuation_label: Label, argument: &ScopedOperand, received_completion: &ScopedOperand, received_completion_type: &ScopedOperand, received_completion_value: &ScopedOperand, await_before_yield: bool, ) { 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( generator, argument, received_completion, received_completion_type, received_completion_value, ) } else { argument.clone() }; // Yield, then UnwrapYieldResumption. let unwrap_block = generator.make_block(); generator.emit(Instruction::Yield { continuation_label: Some(unwrap_block), value: argument.operand(), }); generator.switch_to_basic_block(unwrap_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(), }); // If resumptionValue.[[Type]] is not return, jump to continuation. 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(), }); generator.emit_jump_if(&is_not_return, continuation_label, return_block); // Return path: Await(resumptionValue.[[Value]]). generator.switch_to_basic_block(return_block); generate_await_with_completions( generator, received_completion_value, received_completion, received_completion_type, received_completion_value, ); // If awaited.[[Type]] is throw, jump to continuation. 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(), }); generator.emit_jump_if(&is_throw, continuation_label, awaited_normal_block); // awaited.[[Type]] is normal: set type to Return and jump to continuation. generator.switch_to_basic_block(awaited_normal_block); generator.emit(Instruction::SetCompletionType { completion: received_completion.operand(), completion_type: CompletionType::Return as u32, }); 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, generator: &mut Generator, preferred_dst: Option<&ScopedOperand>, ) -> ScopedOperand { if ident.is_local() { let local_index = ident.local_index.get(); 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) { !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. 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. if ident.is_global.get() && let Some(constant) = maybe_generate_builtin_constant(generator, &ident.name) { return constant; } let dst = choose_dst(generator, preferred_dst); if ident.is_global.get() { 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) { let id = generator.intern_identifier(&ident.name); generator.emit(Instruction::GetInitializedBinding { dst: dst.operand(), identifier: id, cache: EnvironmentCoordinate::empty(), }); } else { 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 { if name == utf16!("undefined") { return Some(generator.add_constant_undefined()); } if name == utf16!("NaN") { return Some(generator.add_constant_number(f64::NAN)); } if name == utf16!("Infinity") { return Some(generator.add_constant_number(f64::INFINITY)); } if let Some(op) = try_generate_builtin_constant(generator, &name.into()) { return Some(op); } None } // ============================================================================= // Binary operator emission // ============================================================================= fn emit_binary_op( 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 { BinaryOp::Addition => generator.emit(Instruction::Add { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::Subtraction => generator.emit(Instruction::Sub { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::Multiplication => generator.emit(Instruction::Mul { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::Division => generator.emit(Instruction::Div { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::Modulo => generator.emit(Instruction::Mod { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::Exponentiation => generator.emit(Instruction::Exp { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::StrictlyEquals => generator.emit(Instruction::StrictlyEquals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::StrictlyInequals => generator.emit(Instruction::StrictlyInequals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::LooselyEquals => generator.emit(Instruction::LooselyEquals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::LooselyInequals => generator.emit(Instruction::LooselyInequals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::GreaterThan => generator.emit(Instruction::GreaterThan { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::GreaterThanEquals => generator.emit(Instruction::GreaterThanEquals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::LessThan => generator.emit(Instruction::LessThan { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::LessThanEquals => generator.emit(Instruction::LessThanEquals { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::BitwiseAnd => generator.emit(Instruction::BitwiseAnd { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::BitwiseOr => { // OPTIMIZATION: x | 0 == ToInt32(x) 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::BitwiseOr { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }); } } else { generator.emit(Instruction::BitwiseOr { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }); } } BinaryOp::BitwiseXor => generator.emit(Instruction::BitwiseXor { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), 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, }); } } BinaryOp::UnsignedRightShift => generator.emit(Instruction::UnsignedRightShift { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::In => generator.emit(Instruction::In { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), BinaryOp::InstanceOf => generator.emit(Instruction::InstanceOf { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), } } // ============================================================================= // Logical expression (short-circuit) // ============================================================================= fn generate_logical( generator: &mut Generator, op: LogicalOp, lhs: &Expression, rhs: &Expression, preferred_dst: Option<&ScopedOperand>, ) -> Option { let lhs_val = generate_expression(lhs, generator, preferred_dst)?; // Constant-fold: if LHS is a constant, we can statically determine the branch. 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 { 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); } generator.emit_mov(&dst, &rhs_val); return Some(dst); } return Some(lhs_val); } } let dst = choose_dst(generator, preferred_dst); generator.emit_mov(&dst, &lhs_val); let rhs_block = generator.make_block(); let end_block = generator.make_block(); match op { LogicalOp::And => { // If lhs is falsy, short-circuit to end generator.emit_jump_if(&lhs_val, rhs_block, end_block); } LogicalOp::Or => { // If lhs is truthy, short-circuit to end generator.emit_jump_if(&lhs_val, end_block, rhs_block); } LogicalOp::NullishCoalescing => { generator.emit(Instruction::JumpNullish { condition: lhs_val.operand(), true_target: rhs_block, false_target: end_block, }); } } generator.switch_to_basic_block(rhs_block); let rhs_val = generate_expression(rhs, generator, Some(&dst)); if let Some(rhs_val) = &rhs_val { generator.emit_mov(&dst, rhs_val); } if !generator.is_current_block_terminated() { generator.emit(Instruction::Jump { target: end_block }); } generator.switch_to_basic_block(end_block); Some(dst) } // ============================================================================= // Conditional expression (ternary) // ============================================================================= fn generate_conditional( generator: &mut Generator, test: &Expression, consequent: &Expression, alternate: &Expression, preferred_dst: Option<&ScopedOperand>, ) -> Option { let predicate = generate_expression(test, generator, None)?; // OPTIMIZATION: if the predicate is always true/false, only generate the taken expression. 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); } return generate_expression(alternate, generator, preferred_dst); } let true_block = generator.make_block(); let false_block = generator.make_block(); let end_block = generator.make_block(); generator.emit_jump_if(&predicate, true_block, false_block); let dst = choose_dst(generator, preferred_dst); generator.switch_to_basic_block(true_block); let cons_val = generate_expression(consequent, generator, None); if let Some(val) = &cons_val { generator.emit_mov(&dst, val); } if !generator.is_current_block_terminated() { generator.emit(Instruction::Jump { target: end_block }); } generator.switch_to_basic_block(false_block); let alt_val = generate_expression(alternate, generator, None); if let Some(val) = &alt_val { generator.emit_mov(&dst, val); } if !generator.is_current_block_terminated() { generator.emit(Instruction::Jump { target: end_block }); } 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, generator: &mut Generator, completion: Option<&ScopedOperand>, preferred_dst: Option<&ScopedOperand>, ) -> Option { let saved = generator.current_completion_register.clone(); if let Some(c) = completion { generator.current_completion_register = Some(c.clone()); } 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); } generator.current_completion_register = saved; result } // ============================================================================= // If statement // ============================================================================= fn generate_if_statement( generator: &mut Generator, test: &Expression, consequent: &Statement, alternate: Option<&Statement>, preferred_dst: Option<&ScopedOperand>, ) -> Option { let pred = generate_expression_or_undefined(test, generator, None); let completion = if generator.must_propagate_completion { let reg = choose_dst(generator, preferred_dst); let undef = generator.add_constant_undefined(); generator.emit_mov(®, &undef); Some(reg) } else { None }; // OPTIMIZATION: if the predicate is always true/false, only build the taken branch. 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. let child_dst = completion.as_ref().or(preferred_dst); if is_truthy { generate_with_completion(consequent, generator, completion.as_ref(), child_dst); } else if let Some(alt) = alternate { generate_with_completion(alt, generator, completion.as_ref(), child_dst); } return completion; } let true_block = generator.make_block(); let false_block = generator.make_block(); let has_alternate = alternate.is_some(); let end_block = if has_alternate { generator.make_block() } else { false_block }; 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 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 }); } } generator.current_completion_register = saved_completion.clone(); // Alternate if let Some(alt) = alternate { generator.switch_to_basic_block(false_block); if let Some(ref c) = completion { generator.current_completion_register = Some(c.clone()); } 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); } generator.emit(Instruction::Jump { target: end_block }); } generator.current_completion_register = saved_completion; } generator.switch_to_basic_block(end_block); completion } // ============================================================================= // While statement // ============================================================================= fn generate_while_statement( generator: &mut Generator, test: &Expression, body: &Statement, preferred_dst: Option<&ScopedOperand>, ) -> Option { let test_block = generator.make_block(); let completion = generator.allocate_completion_register(); generator.emit(Instruction::Jump { target: test_block }); 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. if let Some(constant) = generator.get_constant(&test_val) && constant_to_boolean(constant) == Some(false) { return completion; } let body_block = generator.make_block(); let end_block = generator.make_block(); generator.emit_jump_if(&test_val, body_block, end_block); 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); generator.end_breakable_scope(); generator.end_continuable_scope(); if !generator.is_current_block_terminated() { generator.emit(Instruction::Jump { target: test_block }); } generator.switch_to_basic_block(end_block); completion } // ============================================================================= // DoWhile statement // ============================================================================= fn generate_do_while_statement( generator: &mut Generator, test: &Expression, body: &Statement, preferred_dst: Option<&ScopedOperand>, ) -> Option { 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(); let completion = generator.allocate_completion_register(); 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. 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). 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); generator.end_breakable_scope(); generator.end_continuable_scope(); if !generator.is_current_block_terminated() { generator.emit(Instruction::Jump { target: test_block }); } generator.switch_to_basic_block(load_result_and_jump_to_end_block); generator.emit(Instruction::Jump { target: end_block }); generator.switch_to_basic_block(end_block); completion } // ============================================================================= // For statement // ============================================================================= fn generate_for_statement( generator: &mut Generator, init: Option<&ForInit>, test: Option<&Expression>, update: Option<&Expression>, body: &Statement, preferred_dst: Option<&ScopedOperand>, ) -> Option { // 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 = Vec::new(); if let Some(ForInit::Declaration(init)) = init && let StatementKind::VariableDeclaration { kind, declarations } = &init.inner && (*kind == DeclarationKind::Let || *kind == DeclarationKind::Const) { let mut non_local_names: Vec<(Utf16String, bool)> = Vec::new(); for declaration in declarations { collect_target_names(&declaration.target, &mut non_local_names); } if !non_local_names.is_empty() { has_lexical_environment = true; let is_const = *kind == DeclarationKind::Const; // begin_variable_scope: CreateLexicalEnvironment + boundary generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment); generator.push_new_lexical_environment(0); 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)) => { generate_statement(decl, generator, None); } Some(ForInit::Expression(expr)) => { generate_expression(expr, generator, None); } None => {} } // CreatePerIterationEnvironment after init (first iteration setup). 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. let body_block = generator.make_block(); let update_block = if update.is_some() { generator.make_block() } else { body_block }; let test_block = if test.is_some() { generator.make_block() } else { body_block }; let end_block = generator.make_block(); let completion = generator.allocate_completion_register(); generator.emit(Instruction::Jump { target: test_block }); // Test if let Some(test_expression) = test { 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. 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); 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(), }); } } return completion; } generator.emit_jump_if(&test_val, body_block, end_block); } // Update if let Some(update_expression) = update { generator.switch_to_basic_block(update_block); generate_expression(update_expression, generator, None); generator.emit(Instruction::Jump { target: test_block }); } // Body 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 }; 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); generator.end_breakable_scope(); generator.end_continuable_scope(); if !generator.is_current_block_terminated() { // CreatePerIterationEnvironment at end of each iteration. emit_per_iteration_bindings(generator, &per_iteration_binding_names); if update.is_some() { generator.emit(Instruction::Jump { target: update_block, }); } else { generator.emit(Instruction::Jump { target: test_block }); } } generator.switch_to_basic_block(end_block); // end_variable_scope: restore parent environment if has_lexical_environment { generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment); 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. 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 { 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); 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); generator.push_new_lexical_environment(0); // Re-create variables and initialize from saved values. for (reg, id) in &saved { generator.emit(Instruction::CreateVariable { identifier: *id, mode: EnvironmentMode::Lexical as u32, is_immutable: false, is_global: false, is_strict: false, }); generator.emit(Instruction::InitializeLexicalBinding { identifier: *id, src: reg.operand(), cache: EnvironmentCoordinate::empty(), }); } } // ============================================================================= // Scope children (Block, FunctionBody, Program) // ============================================================================= fn generate_scope_children( generator: &mut Generator, scope: &ScopeData, _preferred_dst: Option<&ScopedOperand>, ) -> Option { let mut last_result = None; for child in &scope.children { 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. 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( generator: &mut Generator, scope: &ScopeData, preferred_dst: Option<&ScopedOperand>, ) -> Option { let did_create_env = emit_block_declaration_instantiation(generator, scope); if did_create_env { 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]) { generate_statement(&scope.children[0], generator, preferred_dst) } else { generate_scope_children(generator, scope, preferred_dst) }; if did_create_env { 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>( generator: &mut Generator, environment: &ScopedOperand, children: impl Iterator, ) { for child in children { match &child.inner { StatementKind::VariableDeclaration { kind, declarations } => { if *kind == DeclarationKind::Let || *kind == DeclarationKind::Const { let is_constant = *kind == DeclarationKind::Const; for declaration in 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 { 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::CreateImmutableBinding { environment: environment.operand(), identifier: id, strict_binding: true, }); } } } StatementKind::ClassDeclaration(class_data) => { 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 { function_id, name: Some(name_ident), .. } => { // a. Create binding. if !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, }); } // b. Instantiate function object. let function_data = generator.function_table.take(*function_id); 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(); let local = generator.local(local_index); generator.emit_mov(&local, &fo); generator.mark_local_initialized(local_index); } else { let id = generator.intern_identifier(&name_ident.name); generator.emit(Instruction::InitializeLexicalBinding { identifier: id, src: fo.operand(), cache: EnvironmentCoordinate::empty(), }); } } _ => {} } } } fn emit_block_declaration_instantiation(generator: &mut Generator, scope: &ScopeData) -> bool { if !needs_block_declaration_instantiation(scope) { return false; } let new_env = generator.push_new_lexical_environment(0); emit_lexical_declarations_for_block(generator, &new_env, scope.children.iter()); true } // ============================================================================= // Variable declaration // ============================================================================= fn generate_variable_declaration( 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) { 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 { generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name)); } let init_value = declaration .init .as_ref() .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() { generator.mark_local_initialized(ident.local_index.get()); } continue; } 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()); generator.emit_mov(&local, &value); generator.mark_local_initialized(local_index); } else { let id = generator.intern_identifier(&ident.name); match kind { DeclarationKind::Var => { if ident.is_global.get() { let cache = generator.next_global_variable_cache(); generator.emit(Instruction::SetGlobal { identifier: id, src: value.operand(), cache: cache as u64, }); } else { generator.emit(Instruction::SetLexicalBinding { identifier: id, src: value.operand(), cache: EnvironmentCoordinate::empty(), }); } } DeclarationKind::Let | DeclarationKind::Const => { 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 } }; generate_binding_pattern_bytecode(generator, pattern, mode, &value); } } } } } // ============================================================================= // Call expression // ============================================================================= fn try_generate_builtin_abstract_operation( generator: &mut Generator, data: &CallExpressionData, preferred_dst: Option<&ScopedOperand>, ) -> Option> { 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; } let dst = choose_dst(generator, preferred_dst); // Operations that map to dedicated bytecode instructions. if name == utf16!("IsCallable") { 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") { 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") { 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") { 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") { 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") { 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 { 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, }); 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 { 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") { generator.emit(Instruction::NewObjectWithNoPrototype { dst: dst.operand() }); return Some(Some(dst)); } if name == utf16!("NewArrayWithLength") { 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") { 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); 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") { 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") { 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 { 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 = argument_holders.iter().map(|a| a.operand()).collect(); 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(), ) }; 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 { 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 = argument_holders.iter().map(|a| a.operand()).collect(); 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 { 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) }; 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, ) }; return Some(generator.add_constant_raw_value(value)); } if *name == utf16!("MAX_ARRAY_LIKE_INDEX") { 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( generator: &mut Generator, data: &CallExpressionData, preferred_dst: Option<&ScopedOperand>, is_new: bool, ) -> Option { // Check for builtin abstract operations before anything else. if !is_new && let Some(result) = try_generate_builtin_abstract_operation(generator, data, preferred_dst) { return result; } 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 = 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 = 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 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 }; let super_base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: super_base.operand(), }); 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) = &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); let method = generator.allocate_register(); if data.computed { let property = generate_expression_or_undefined(&data.property, generator, None); emit_get_by_value(generator, &method, &obj, &property, None); } else if let ExpressionKind::Identifier(ident) = &data.property.inner { emit_get_by_id(generator, &method, &obj, &ident.name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner { 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) { !generator.is_argument_initialized(ident.local_index.get()) } else { 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. 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. 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)) } _ => { let callee = generate_expression_or_undefined(&data.callee, generator, None); (callee, None) } } } else { 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)`). let this_value = this_value.map(|tv| generator.copy_if_needed_to_preserve_evaluation_order(&tv)); let callee = generator.copy_if_needed_to_preserve_evaluation_order(&callee); // Unwrap this_value at function scope so its register lifetime outlives argument temporaries. 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. 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] { let reg = generator.allocate_register(); let val = generate_expression_or_undefined(&argument.value, generator, None); generator.emit_mov(®, &val); pre_holders.push(reg); } let pre_arguments: Vec = pre_holders.iter().map(|a| a.operand()).collect(); 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..] { 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 { 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 { generator.emit(Instruction::CallDirectEvalWithArgumentArray { dst: dst.operand(), callee: callee.operand(), this_value: this_value.operand(), arguments: arguments_array.operand(), expression_string, }); } else { 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 { 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 = argument_holders.iter().map(|a| a.operand()).collect(); if is_new { 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 { 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(b) = builtin { if builtin_argument_count(b) == arguments.len() { generator.emit(Instruction::CallBuiltin { dst: dst.operand(), callee: callee.operand(), this_value: this_value.operand(), argument_count: u32_from_usize(arguments.len()), builtin: b, expression_string, arguments, }); } else { 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 { 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 { UpdateOp::Increment => generator.emit(Instruction::PostfixIncrement { dst: dst.operand(), src: value.operand(), }), UpdateOp::Decrement => generator.emit(Instruction::PostfixDecrement { dst: dst.operand(), src: value.operand(), }), } dst } } fn generate_update_expression( generator: &mut Generator, op: UpdateOp, argument: &Expression, prefixed: bool, ) -> Option { // 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); 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 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 }; let base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: base.operand(), }); let value = generator.allocate_register(); if let Some(ref key) = computed_key { 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( 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)?; 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 { 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); 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 let value = generator.allocate_register(); Some(value) } } } _ => { // Invalid update target (e.g. foo()++). Per spec, evaluate the // expression first, then throw ReferenceError. 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( generator: &mut Generator, op: AssignmentOp, lhs: &AssignmentLhs, rhs: &Expression, preferred_dst: Option<&ScopedOperand>, ) -> Option { match lhs { AssignmentLhs::Expression(lhs_expression) => { // Simple assignment to identifier if let ExpressionKind::Identifier(ident) = &lhs_expression.inner { if op == AssignmentOp::Assignment { 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); } 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. let rhs_block = generator.make_block(); let lhs_block = generator.make_block(); let end_block = generator.make_block(); match op { AssignmentOp::AndAssignment => { generator.emit_jump_if(&lhs_val, rhs_block, lhs_block); } AssignmentOp::OrAssignment => { generator.emit_jump_if(&lhs_val, lhs_block, rhs_block); } AssignmentOp::NullishAssignment => { 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. 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 { choose_dst(generator, preferred_dst) }; generator.emit_mov(&dst, &rhs_val); emit_set_variable(generator, ident, &dst); generator.emit(Instruction::Jump { target: end_block }); // LHS block: keep original value. 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.) 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 { choose_dst(generator, preferred_dst) }; 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 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 }; let base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: base.operand(), }); let rhs_val = generate_expression(rhs, generator, None)?; emit_super_put( 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 }; let base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: base.operand(), }); let old_val = generator.allocate_register(); if let Some(ref key) = computed_key { 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 { 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( generator, &base, &member_data.property, member_data.computed, &super_this, &dst, computed_key.as_ref(), ); 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); } 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( 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 { 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, ); Some(generator.copy_if_needed_to_preserve_evaluation_order(&key_val)) } else { None }; let rhs_val = generate_expression(rhs, generator, None)?; if let Some(key) = precomputed_key { let base_id = intern_base_identifier(generator, &member_data.object); emit_put_normal_by_value(generator, &base, &key, &rhs_val, base_id); } else { emit_put_to_member( generator, &base, &member_data.property, false, &rhs_val, Some(&member_data.object), ); } 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)?; 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. let saved_property = generator.allocate_register(); generator.emit_mov(&saved_property, &property); if is_logical { 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); } 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 { let old_val = generator.allocate_register(); emit_get_by_id(generator, &old_val, &base, &ident.name, base_id); if is_logical { 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, }); 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); } 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 { 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 { 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); 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(), }); 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); } 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. 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)?; 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. 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. 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; } if generator.basic_blocks[0].resolved_this { generator.basic_blocks[index].resolved_this = true; return; } 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( generator: &mut Generator, dst: &ScopedOperand, base: &ScopedOperand, property: &Expression, computed: bool, this_value: &ScopedOperand, ) -> Option { if computed { 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( 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(), None => generate_expression_or_undefined(property, generator, None), }; emit_put_normal_by_value_with_this(generator, base, &property, this_value, value); } else if let ExpressionKind::Identifier(ident) = &property.inner { 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( generator: &mut Generator, dst: &ScopedOperand, base: &ScopedOperand, property_name: &[u16], base_identifier: Option, ) { 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::GetLength { dst: dst.operand(), base: base.operand(), base_identifier, cache: cache as u64, }); } else { 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. 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(); 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( generator: &mut Generator, dst: &ScopedOperand, base: &ScopedOperand, property: &ScopedOperand, base_identifier: Option, ) { 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 { 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; } 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( generator: &mut Generator, dst: &ScopedOperand, base: &ScopedOperand, property: &ScopedOperand, this_value: &ScopedOperand, ) { 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; } 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( generator: &mut Generator, base: &ScopedOperand, property: &ScopedOperand, src: &ScopedOperand, base_identifier: Option, ) { 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( generator: &mut Generator, base: &ScopedOperand, property: &ScopedOperand, this_value: &ScopedOperand, src: &ScopedOperand, ) { 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( generator: &mut Generator, base: &ScopedOperand, property: &ScopedOperand, src: &ScopedOperand, kind: PutKind, ) { 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(), }); } } 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(). generator.emit(Instruction::ThrowConstAssignment {}); return; } let local_index = ident.local_index.get(); 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() { 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). let id = generator.intern_identifier(&ident.name); generator.emit(Instruction::SetLexicalBinding { identifier: id, src: value.operand(), cache: EnvironmentCoordinate::empty(), }); } } fn emit_put_to_member( generator: &mut Generator, base: &ScopedOperand, property: &Expression, computed: bool, value: &ScopedOperand, base_object: Option<&Expression>, ) { let base_id = base_object.and_then(|obj| intern_base_identifier(generator, obj)); if computed { 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 { 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 { let id = generator.intern_identifier(&priv_ident.name); generator.emit(Instruction::PutPrivateById { base: base.operand(), property: id, src: value.operand(), }); } } /// Emit bytecode for `delete `. fn emit_delete_reference(generator: &mut Generator, operand: &Expression) -> ScopedOperand { match &operand.inner { ExpressionKind::Identifier(ident) => { if ident.is_local() { return generator.add_constant_boolean(false); } 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) { 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 }; let super_base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: super_base.operand(), }); let exception = generator.allocate_register(); let error_string = generator.intern_string(utf16!("Can't delete a property on 'super'")); generator.emit(Instruction::NewReferenceError { dst: exception.operand(), error_string, }); generator.perform_needed_unwinds(); generator.emit(Instruction::Throw { src: exception.operand(), }); let dead_block = generator.make_block(); generator.switch_to_basic_block(dead_block); let _ = (this_value, _computed_key); return generator.add_constant_undefined(); } let base = generate_expression_or_undefined(&data.object, generator, None); let dst = generator.allocate_register(); if data.computed { let key = generate_expression_or_undefined(&data.property, generator, None); generator.emit(Instruction::DeleteByValue { dst: dst.operand(), base: base.operand(), property: key.operand(), }); } else if let ExpressionKind::Identifier(property_ident) = &data.property.inner { let key = generator.intern_property_key(&property_ident.name); generator.emit(Instruction::DeleteById { dst: dst.operand(), base: base.operand(), property: key, }); } else { return generator.add_constant_boolean(true); } dst } _ => { // delete on non-reference: evaluate for side effects, return true 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, }, MemberId { base: ScopedOperand, property: PropertyKeyTableIndex, cache: u32, base_identifier: Option, }, 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. 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 { 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, } => { 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 } => { generator.emit(Instruction::PutPrivateById { base: base.operand(), property: *property, src: value.operand(), }); } EvaluatedReference::SuperMember { base, property, this_value, } => { 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, }); } } } fn emit_store_to_reference(generator: &mut Generator, target: &Expression, value: &ScopedOperand) { match &target.inner { ExpressionKind::Identifier(ident) => { emit_set_variable(generator, ident, value); } ExpressionKind::Member(data) => { if matches!(data.object.inner, ExpressionKind::Super) { // ResolveThisBinding first, then ResolveSuperBase. let this_value = emit_resolve_this_binding(generator); let base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: base.operand(), }); emit_super_put( generator, &base, &data.property, data.computed, &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. generate_expression(target, generator, None); emit_invalid_lhs_error(generator); } } } /// Emit the conditional jump for a logical assignment (&&=, ||=, ??=). fn emit_logical_jump( generator: &mut Generator, op: AssignmentOp, condition: &ScopedOperand, rhs_block: Label, lhs_block: Label, ) { match op { AssignmentOp::AndAssignment => { generator.emit_jump_if(condition, rhs_block, lhs_block); } AssignmentOp::OrAssignment => { generator.emit_jump_if(condition, lhs_block, rhs_block); } AssignmentOp::NullishAssignment => { 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( 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 { AssignmentOp::AdditionAssignment => generator.emit(Instruction::Add { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::SubtractionAssignment => generator.emit(Instruction::Sub { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::MultiplicationAssignment => generator.emit(Instruction::Mul { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::DivisionAssignment => generator.emit(Instruction::Div { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::ModuloAssignment => generator.emit(Instruction::Mod { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::ExponentiationAssignment => generator.emit(Instruction::Exp { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::BitwiseAndAssignment => generator.emit(Instruction::BitwiseAnd { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::BitwiseOrAssignment => generator.emit(Instruction::BitwiseOr { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::BitwiseXorAssignment => generator.emit(Instruction::BitwiseXor { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::LeftShiftAssignment => generator.emit(Instruction::LeftShift { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::RightShiftAssignment => generator.emit(Instruction::RightShift { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }), AssignmentOp::UnsignedRightShiftAssignment => { generator.emit(Instruction::UnsignedRightShift { dst: dst_op, lhs: lhs_op, rhs: rhs_op, }); } 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( generator: &mut Generator, data: &TemplateLiteralData, preferred_dst: Option<&ScopedOperand>, ) -> Option { // 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() { return Some(generator.add_constant_string(Utf16String::new())); } // Allocate dst before generating expressions. let dst = choose_dst(generator, preferred_dst); if segments.len() == 1 { 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. generator.emit(Instruction::ToString { dst: dst.operand(), value: val.operand(), }); return Some(dst); } for (index, expression) in segments.iter().enumerate() { let val = generate_expression_or_undefined(expression, generator, None); if index == 0 { if matches!(&expression.inner, ExpressionKind::StringLiteral(_)) { generator.emit_mov(&dst, &val); } else { generator.emit(Instruction::ToString { dst: dst.operand(), value: val.operand(), }); } } else { generator.emit(Instruction::ConcatString { dst: dst.operand(), src: val.operand(), }); } } Some(dst) } // ============================================================================= // Tagged template literal // ============================================================================= fn generate_tagged_template_literal( 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. 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 }; let super_base = generator.allocate_register(); generator.emit(Instruction::ResolveSuperBase { dst: super_base.operand(), }); 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); let method = generator.allocate_register(); if member_data.computed { let property = generate_expression_or_undefined(&member_data.property, generator, None); 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); emit_get_by_id(generator, &method, &obj, &ident.name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &member_data.property.inner { 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() => { 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. 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)) } _ => { 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) { string_regs.push(generator.add_constant_undefined()); } else { let val = generate_expression_or_undefined(&data.expressions[i], generator, None); string_regs.push(val); } } // Append raw strings. for raw in &data.raw_strings { let val = generator.add_constant_string(raw.clone()); string_regs.push(val); } // Emit GetTemplateObject. let strings_array = generator.allocate_register(); let string_ops: Vec = string_regs.iter().map(|s| s.operand()).collect(); 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) { let val = generate_expression_or_undefined(&data.expressions[i], generator, None); argument_regs.push(val); } let dst = choose_dst(generator, preferred_dst); let this_op = this_value.unwrap_or_else(|| generator.add_constant_undefined()); let arguments: Vec = argument_regs.iter().map(|a| a.operand()).collect(); 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( generator: &mut Generator, data: &SwitchStatementData, _preferred_dst: Option<&ScopedOperand>, ) -> Option { let completion = generator.allocate_completion_register(); let discriminant = generate_expression(&data.discriminant, generator, None)?; // Block declaration instantiation: create lexical environment for // function declarations and let/const across all switch cases. 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. 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