2026-02-23 07:50:46 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
//! Bytecode generation from AST.
|
|
|
|
|
//!
|
|
|
|
|
//! This is the largest module in the parser -- it walks the AST
|
|
|
|
|
//! and emits bytecode instructions via the `Generator`.
|
|
|
|
|
//!
|
|
|
|
|
//! ## Conventions
|
|
|
|
|
//!
|
|
|
|
|
//! Each AST node's codegen returns `Option<ScopedOperand>`:
|
|
|
|
|
//! - `Some(op)` if the node produces a value (expressions)
|
|
|
|
|
//! - `None` for statements that don't produce values
|
|
|
|
|
//!
|
|
|
|
|
//! The `preferred_dst` parameter is a register hint: when the caller
|
|
|
|
|
//! already has a destination register (e.g. the LHS of an assignment),
|
|
|
|
|
//! codegen writes directly there instead of allocating a temporary.
|
|
|
|
|
//!
|
|
|
|
|
//! ## File organization
|
|
|
|
|
//!
|
|
|
|
|
//! The file is organized by AST node type, with section headers:
|
|
|
|
|
//!
|
|
|
|
|
//! - **Top-level entry points**: `generate_expression`, `generate_statement`
|
|
|
|
|
//! - **Literals and identifiers**: numeric, string, boolean, regexp, identifier
|
|
|
|
|
//! - **Await/yield**: async/generator control flow helpers
|
|
|
|
|
//! - **Operators**: binary, logical, conditional, update, assignment
|
|
|
|
|
//! - **Control flow**: if, while, do-while, for, for-in/of, switch, labelled
|
|
|
|
|
//! - **Blocks and scopes**: block statements, function bodies, scope children
|
|
|
|
|
//! - **Declarations**: variable declarations, using declarations
|
|
|
|
|
//! - **Calls**: regular calls, super calls, optional chains, builtin detection
|
|
|
|
|
//! - **Templates**: template literals, tagged templates
|
|
|
|
|
//! - **Objects and classes**: object expressions, class expressions
|
|
|
|
|
//! - **Patterns**: binding pattern destructuring (array and object)
|
|
|
|
|
//! - **Try/catch/finally**: try statement codegen
|
|
|
|
|
//! - **Functions**: `emit_new_function`, `emit_function_declaration_instantiation`
|
|
|
|
|
//! - **Helpers**: constant folding, NaN-boxing, error message utilities
|
|
|
|
|
|
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
|
|
|
|
use num_bigint::BigInt;
|
|
|
|
|
use num_traits::{One, Signed, ToPrimitive, Zero};
|
|
|
|
|
|
|
|
|
|
use crate::ast::*;
|
|
|
|
|
use crate::lexer::ch;
|
|
|
|
|
use crate::u32_from_usize;
|
|
|
|
|
|
2026-03-18 14:55:08 -03:00
|
|
|
use super::ffi::{LiteralValueKind, WellKnownSymbolKind};
|
2026-02-24 06:40:18 -03:00
|
|
|
use super::generator::{
|
2026-02-24 08:36:14 -03:00
|
|
|
BlockBoundaryType, ConstantValue, FinallyContext, Generator, ScopedOperand, choose_dst,
|
|
|
|
|
constant_to_boolean, parse_bigint,
|
2026-02-24 06:40:18 -03:00
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
use super::instruction::Instruction;
|
|
|
|
|
use super::operand::*;
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for an expression.
|
|
|
|
|
pub fn generate_expression(
|
|
|
|
|
expression: &Expression,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_source_start = generator.current_source_start;
|
|
|
|
|
let saved_source_end = generator.current_source_end;
|
|
|
|
|
generator.current_source_start = expression.range.start.offset;
|
|
|
|
|
generator.current_source_end = expression.range.end.offset;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let result = generate_expression_inner(expression, generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_source_start = saved_source_start;
|
|
|
|
|
generator.current_source_end = saved_source_end;
|
2026-02-23 07:50:46 -03:00
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_expression_inner(
|
|
|
|
|
expression: &Expression,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// NamedEvaluation: only function/class expressions consume pending_lhs_name.
|
|
|
|
|
// Clear it for all other expression types so it doesn't leak through to
|
|
|
|
|
// nested function expressions (e.g. IIFEs: `let x = (function() { ... })()`).
|
2026-02-24 06:40:18 -03:00
|
|
|
if !matches!(
|
|
|
|
|
expression.inner,
|
|
|
|
|
ExpressionKind::Function(_) | ExpressionKind::Class(_)
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
match &expression.inner {
|
2026-02-23 07:50:46 -03:00
|
|
|
// === Literals ===
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::NumericLiteral(value) => Some(generator.add_constant_number(*value)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::BooleanLiteral(value) => Some(generator.add_constant_boolean(*value)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::NullLiteral => Some(generator.add_constant_null()),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-22 14:37:12 -03:00
|
|
|
ExpressionKind::StringLiteral(value) => {
|
|
|
|
|
Some(generator.add_constant_string((**value).clone()))
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
ExpressionKind::BigIntLiteral(value) => {
|
|
|
|
|
// The AST stores the raw value including the 'n' suffix; strip it for codegen.
|
2026-03-22 14:38:20 -03:00
|
|
|
let digits = value.strip_suffix('n').unwrap_or(value.as_str());
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint(digits.to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExpressionKind::RegExpLiteral(data) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let source_index = generator.intern_string(&data.pattern);
|
|
|
|
|
let flags_index = generator.intern_string(&data.flags);
|
2026-02-23 07:50:46 -03:00
|
|
|
let compiled = data.compiled_regex.take();
|
2026-02-24 08:36:14 -03:00
|
|
|
let regex_index = generator.intern_regex(compiled);
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::NewRegExp {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
source_index,
|
|
|
|
|
flags_index,
|
|
|
|
|
regex_index,
|
|
|
|
|
});
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Identifiers ===
|
2026-02-27 06:18:31 -03:00
|
|
|
ExpressionKind::Identifier(ident) => {
|
|
|
|
|
Some(generate_identifier(ident, generator, preferred_dst))
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === This ===
|
|
|
|
|
ExpressionKind::This => {
|
|
|
|
|
// OPTIMIZATION: When function_environment_needed is false, the `this`
|
|
|
|
|
// value is inherited from the outer function and already in the register.
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.function_environment_needed {
|
|
|
|
|
emit_resolve_this_if_needed(generator);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.this_value())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Unary ===
|
|
|
|
|
ExpressionKind::Unary { op, operand } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_unary_expression(generator, *op, operand, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Binary ===
|
2026-03-22 14:54:18 -03:00
|
|
|
ExpressionKind::Binary(data) => {
|
|
|
|
|
generate_binary_expression(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Logical (short-circuit) ===
|
2026-03-22 14:56:01 -03:00
|
|
|
ExpressionKind::Logical(data) => {
|
|
|
|
|
generate_logical(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Conditional (ternary) ===
|
2026-03-22 15:04:48 -03:00
|
|
|
ExpressionKind::Conditional(data) => generate_conditional(
|
|
|
|
|
generator,
|
|
|
|
|
&data.test,
|
|
|
|
|
&data.consequent,
|
|
|
|
|
&data.alternate,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Sequence ===
|
|
|
|
|
ExpressionKind::Sequence(expressions) => {
|
|
|
|
|
let mut last = None;
|
2026-03-22 14:47:08 -03:00
|
|
|
for expression in expressions.iter() {
|
2026-02-24 08:36:14 -03:00
|
|
|
last = generate_expression(expression, generator, None);
|
|
|
|
|
if generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
last
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Function expressions ===
|
2026-02-27 06:18:31 -03:00
|
|
|
ExpressionKind::Function(function_id) => Some(generate_function_expression(
|
|
|
|
|
generator,
|
|
|
|
|
*function_id,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Array ===
|
2026-02-27 06:18:31 -03:00
|
|
|
ExpressionKind::Array(elements) => Some(generate_array_expression(
|
|
|
|
|
generator,
|
|
|
|
|
elements,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Member access ===
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => generate_member_expression(
|
|
|
|
|
generator,
|
|
|
|
|
&data.object,
|
|
|
|
|
&data.property,
|
|
|
|
|
data.computed,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Call ===
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::Call(data) => {
|
|
|
|
|
generate_call_expression(generator, data, preferred_dst, false)
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === New ===
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::New(data) => generate_call_expression(generator, data, preferred_dst, true),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Spread ===
|
|
|
|
|
ExpressionKind::Spread(inner) => {
|
|
|
|
|
// Spread is handled by the caller (Call, Array, Object)
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
inner,
|
|
|
|
|
generator,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Yield ===
|
|
|
|
|
ExpressionKind::Yield {
|
|
|
|
|
argument,
|
|
|
|
|
is_yield_from,
|
2026-02-27 06:18:31 -03:00
|
|
|
} => Some(generate_yield_expression(
|
|
|
|
|
generator,
|
|
|
|
|
argument.as_deref(),
|
|
|
|
|
*is_yield_from,
|
|
|
|
|
)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Await ===
|
|
|
|
|
ExpressionKind::Await(inner) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(inner, generator, None);
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate received_completion registers before the await.
|
2026-02-24 08:36:14 -03:00
|
|
|
let received_completion = generator.allocate_register();
|
|
|
|
|
let received_completion_type = generator.allocate_register();
|
|
|
|
|
let received_completion_value = generator.allocate_register();
|
|
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(&received_completion, &acc);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&value,
|
|
|
|
|
&received_completion,
|
|
|
|
|
&received_completion_type,
|
|
|
|
|
&received_completion_value,
|
2026-02-23 07:50:46 -03:00
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === MetaProperty ===
|
|
|
|
|
ExpressionKind::MetaProperty(MetaPropertyType::NewTarget) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::GetNewTarget { dst: dst.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExpressionKind::MetaProperty(MetaPropertyType::ImportMeta) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::GetImportMeta { dst: dst.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === ImportCall ===
|
|
|
|
|
ExpressionKind::ImportCall { specifier, options } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let spec = generate_expression(specifier, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
let opts = match options {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(o) => generate_expression(o, generator, None)?,
|
|
|
|
|
None => generator.add_constant_undefined(),
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::ImportCall {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
specifier: spec.operand(),
|
|
|
|
|
options: opts.operand(),
|
|
|
|
|
});
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Update (++/--) ===
|
2026-03-22 15:00:22 -03:00
|
|
|
ExpressionKind::Update(data) => {
|
|
|
|
|
generate_update_expression(generator, data.op, &data.argument, data.prefixed)
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Assignment ===
|
2026-03-22 15:02:14 -03:00
|
|
|
ExpressionKind::Assignment(data) => {
|
|
|
|
|
generate_assignment_expression(generator, data.op, &data.lhs, &data.rhs, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Template literals ===
|
|
|
|
|
ExpressionKind::TemplateLiteral(data) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_template_literal(generator, data, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Tagged template literals ===
|
2026-03-22 15:24:36 -03:00
|
|
|
ExpressionKind::TaggedTemplateLiteral(data) => Some(generate_tagged_template_literal(
|
2026-02-27 06:18:31 -03:00
|
|
|
generator,
|
2026-03-22 15:24:36 -03:00
|
|
|
&data.tag,
|
|
|
|
|
&data.template_literal,
|
2026-02-27 06:18:31 -03:00
|
|
|
preferred_dst,
|
|
|
|
|
)),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Object ===
|
2026-02-27 06:18:31 -03:00
|
|
|
ExpressionKind::Object(data) => {
|
|
|
|
|
Some(generate_object_expression(generator, data, preferred_dst))
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === OptionalChain ===
|
2026-03-22 15:16:49 -03:00
|
|
|
ExpressionKind::OptionalChain(oc_data) => {
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate current_base first, current_value second.
|
2026-02-24 08:36:14 -03:00
|
|
|
let current_base = generator.allocate_register();
|
|
|
|
|
let current_value = choose_dst(generator, preferred_dst);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(¤t_base, &undef);
|
|
|
|
|
generate_optional_chain_inner(
|
|
|
|
|
generator,
|
2026-03-22 15:16:49 -03:00
|
|
|
&oc_data.base,
|
|
|
|
|
&oc_data.references,
|
2026-02-24 08:36:14 -03:00
|
|
|
¤t_value,
|
|
|
|
|
¤t_base,
|
|
|
|
|
)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(current_value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === SuperCall ===
|
|
|
|
|
ExpressionKind::SuperCall(data) => {
|
|
|
|
|
let arguments = if data.is_synthetic {
|
|
|
|
|
// Synthetic constructor: super(...arguments) — single spread argument,
|
|
|
|
|
// don't call @@iterator on %Array.prototype%.
|
|
|
|
|
assert!(data.arguments.len() == 1 && data.arguments[0].is_spread);
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression_or_undefined(&data.arguments[0].value, generator, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_arguments_array(generator, &data.arguments)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::SuperCallWithArgumentArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
arguments: arguments.operand(),
|
|
|
|
|
is_synthetic: data.is_synthetic,
|
|
|
|
|
});
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExpressionKind::Super => {
|
|
|
|
|
// super keyword as an expression (for super.foo, super[foo])
|
|
|
|
|
// Returns the home object's prototype
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase { dst: dst.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:18:31 -03:00
|
|
|
ExpressionKind::Class(data) => {
|
|
|
|
|
Some(generate_class_expression(generator, data, preferred_dst))
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
ExpressionKind::PrivateIdentifier(_) => {
|
|
|
|
|
// Private identifiers are handled by member access codegen
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExpressionKind::Error => None,
|
2026-02-24 08:36:14 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_unary_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: UnaryOp,
|
|
|
|
|
operand: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// typeof and delete on identifiers need special handling BEFORE
|
|
|
|
|
// evaluating the operand to avoid throwing on unresolvable references.
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst before evaluating typeof/not operands.
|
2026-02-23 07:50:46 -03:00
|
|
|
if op == UnaryOp::Typeof {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ExpressionKind::Identifier(ident) = &operand.inner
|
|
|
|
|
&& !ident.is_local()
|
|
|
|
|
{
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::TypeofBinding {
|
|
|
|
|
dst: dst.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
let value = generate_expression(operand, generator, None)?;
|
|
|
|
|
generator.emit(Instruction::Typeof {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
if op == UnaryOp::Delete {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(emit_delete_reference(generator, operand));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst before operand.
|
2026-02-23 07:50:46 -03:00
|
|
|
// Also optimize !!x -> ToBoolean(x).
|
|
|
|
|
if op == UnaryOp::Not {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-24 06:40:18 -03:00
|
|
|
if let ExpressionKind::Unary {
|
|
|
|
|
op: UnaryOp::Not,
|
|
|
|
|
operand: inner,
|
|
|
|
|
} = &operand.inner
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression(inner, generator, None)?;
|
|
|
|
|
if let Some(folded) = try_constant_fold_to_boolean(generator, &value) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(folded);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ToBoolean {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression(operand, generator, None)?;
|
|
|
|
|
if let Some(folded) = try_constant_fold_unary(generator, op, &value) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(folded);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Not {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression(operand, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// OPTIMIZATION: constant fold unary operations on constants.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(folded) = try_constant_fold_unary(generator, op, &value) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(folded);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
match op {
|
|
|
|
|
UnaryOp::BitwiseNot => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::BitwiseNot {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
UnaryOp::Not => unreachable!("Not is handled by early return above"),
|
|
|
|
|
UnaryOp::Plus => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::UnaryPlus {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
UnaryOp::Minus => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::UnaryMinus {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
UnaryOp::Typeof => unreachable!("Typeof is handled by early return above"),
|
|
|
|
|
UnaryOp::Void => {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
UnaryOp::Delete => unreachable!("Delete is handled by early return above"),
|
|
|
|
|
}
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-08 07:47:18 -03:00
|
|
|
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),
|
2026-03-22 14:54:18 -03:00
|
|
|
ExpressionKind::Binary(data) => {
|
|
|
|
|
might_contain_assignment_expression(&data.lhs)
|
|
|
|
|
|| might_contain_assignment_expression(&data.rhs)
|
2026-03-08 07:47:18 -03:00
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
|
|
|
|
might_contain_assignment_expression(&data.object)
|
|
|
|
|
|| might_contain_assignment_expression(&data.property)
|
2026-03-08 07:47:18 -03:00
|
|
|
}
|
|
|
|
|
// Conservatively consider everything else, including assignments themselves as potentially
|
|
|
|
|
// assigning.
|
|
|
|
|
_ => true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
fn generate_binary_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: BinaryOp,
|
|
|
|
|
lhs: &Expression,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// Special case: `#privateId in obj` uses HasPrivateId instead of In.
|
2026-02-24 08:36:14 -03:00
|
|
|
if op == BinaryOp::In
|
|
|
|
|
&& let ExpressionKind::PrivateIdentifier(priv_ident) = &lhs.inner
|
|
|
|
|
{
|
|
|
|
|
let base = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::HasPrivateId {
|
|
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// OPTIMIZATION: Pre-convert numeric literal operands of bitwise
|
2026-03-20 00:26:33 -03:00
|
|
|
// operations to i32/u32 to avoid runtime conversion.
|
2026-02-23 07:50:46 -03:00
|
|
|
let lhs_val = match op {
|
2026-02-24 06:40:18 -03:00
|
|
|
BinaryOp::BitwiseAnd
|
|
|
|
|
| BinaryOp::BitwiseOr
|
|
|
|
|
| BinaryOp::BitwiseXor
|
|
|
|
|
| BinaryOp::LeftShift
|
|
|
|
|
| BinaryOp::RightShift
|
|
|
|
|
| BinaryOp::UnsignedRightShift => {
|
2026-02-23 07:50:46 -03:00
|
|
|
if let ExpressionKind::NumericLiteral(n) = &lhs.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.add_constant_number(to_int32(*n) as f64)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(lhs, generator, None)?
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
_ => generate_expression(lhs, generator, None)?,
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-03-08 07:47:18 -03:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
let rhs_val = match op {
|
|
|
|
|
BinaryOp::BitwiseAnd | BinaryOp::BitwiseOr | BinaryOp::BitwiseXor => {
|
|
|
|
|
if let ExpressionKind::NumericLiteral(n) = &rhs.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.add_constant_number(to_int32(*n) as f64)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(rhs, generator, None)?
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BinaryOp::LeftShift | BinaryOp::RightShift | BinaryOp::UnsignedRightShift => {
|
|
|
|
|
if let ExpressionKind::NumericLiteral(n) = &rhs.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.add_constant_number(to_u32(*n) as f64)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(rhs, generator, None)?
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
_ => generate_expression(rhs, generator, None)?,
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
// OPTIMIZATION: constant folding for binary operations on constants.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(folded) = try_constant_fold_binary(generator, op, &lhs_val, &rhs_val) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(folded);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_binary_op(generator, op, &dst, &lhs_val, &rhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_function_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
function_id: FunctionId,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-24 08:36:14 -03:00
|
|
|
let data = generator.function_table.take(function_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
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.
|
2026-02-27 06:31:34 -03:00
|
|
|
let name_id = if has_name {
|
2026-02-24 08:36:14 -03:00
|
|
|
let parent = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.lexical_environment_register_stack
|
|
|
|
|
.last()
|
|
|
|
|
.cloned()
|
2026-02-24 08:36:14 -03:00
|
|
|
.unwrap_or_else(|| generator.add_constant_undefined());
|
|
|
|
|
let new_env = generator.allocate_register();
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
|
|
|
|
generator.emit(Instruction::CreateLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: new_env.operand(),
|
|
|
|
|
parent: parent.operand(),
|
|
|
|
|
capacity: 0,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.push(new_env);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(
|
2026-02-24 06:40:18 -03:00
|
|
|
&data
|
|
|
|
|
.name
|
|
|
|
|
.as_ref()
|
|
|
|
|
.expect("function declaration must have a name")
|
|
|
|
|
.name,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: true,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-27 06:31:34 -03:00
|
|
|
Some(id)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
// For anonymous function expressions, use the pending LHS name
|
|
|
|
|
// as the function's .name property.
|
2026-02-24 06:40:18 -03:00
|
|
|
let lhs_name = if !has_name {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name.take()
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
let lhs_name_str: Option<Utf16String> =
|
2026-02-24 08:36:14 -03:00
|
|
|
lhs_name.map(|index| generator.identifier_table[index.0 as usize].clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
let name_override = if !has_name {
|
|
|
|
|
lhs_name_str.as_deref()
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let shared_function_data_index = emit_new_function(generator, data, name_override);
|
|
|
|
|
let home_object = generator.home_objects.last().map(|ho| ho.operand());
|
|
|
|
|
generator.emit(Instruction::NewFunction {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
shared_function_data_index,
|
|
|
|
|
lhs_name,
|
|
|
|
|
home_object,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if has_name {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: name_id.expect("has_name guarantees name_id is set"),
|
|
|
|
|
src: dst.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_variable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_array_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
elements: &[Option<Expression>],
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-23 07:50:46 -03:00
|
|
|
// If all elements are constant primitives, emit NewPrimitiveArray.
|
2026-02-24 06:40:18 -03:00
|
|
|
if !elements.is_empty()
|
|
|
|
|
&& elements.iter().all(|e| match e {
|
|
|
|
|
None => true, // holes
|
|
|
|
|
Some(e) => matches!(
|
|
|
|
|
e.inner,
|
|
|
|
|
ExpressionKind::NumericLiteral(_)
|
|
|
|
|
| ExpressionKind::BooleanLiteral(_)
|
|
|
|
|
| ExpressionKind::NullLiteral
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
{
|
|
|
|
|
let values: Vec<u64> = elements
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|e| match e {
|
|
|
|
|
None => nanboxed_empty(),
|
|
|
|
|
Some(e) => match &e.inner {
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => nanboxed_number(*n),
|
|
|
|
|
ExpressionKind::BooleanLiteral(b) => nanboxed_boolean(*b),
|
|
|
|
|
ExpressionKind::NullLiteral => nanboxed_null(),
|
|
|
|
|
_ => unreachable!("all elements verified as primitive literals above"),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit(Instruction::NewPrimitiveArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
element_count: u32_from_usize(values.len()),
|
|
|
|
|
elements: values,
|
|
|
|
|
});
|
2026-02-27 06:18:31 -03:00
|
|
|
return dst;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find the first spread element.
|
2026-02-24 06:40:18 -03:00
|
|
|
let first_spread = elements.iter().position(
|
|
|
|
|
|e| matches!(e, Some(element) if matches!(element.inner, ExpressionKind::Spread(_))),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Collect elements before the first spread into a NewArray.
|
|
|
|
|
let pre_spread_count = first_spread.unwrap_or(elements.len());
|
|
|
|
|
let mut scoped_arguments: Vec<ScopedOperand> = Vec::with_capacity(pre_spread_count);
|
|
|
|
|
for element in &elements[..pre_spread_count] {
|
|
|
|
|
match element {
|
|
|
|
|
Some(e) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(e, generator, None);
|
|
|
|
|
scoped_arguments.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
None => {
|
2026-02-24 08:36:14 -03:00
|
|
|
scoped_arguments.push(generator.add_constant_empty());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
let arguments: Vec<Operand> = scoped_arguments.iter().map(|s| s.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
element_count: u32_from_usize(arguments.len()),
|
|
|
|
|
elements: arguments,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// NB: Keep scoped_arguments alive until the end of the expression
|
2026-03-20 00:26:33 -03:00
|
|
|
// so their registers aren't reused during spread evaluation.
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Append elements after the first spread using ArrayAppend.
|
|
|
|
|
if let Some(spread_index) = first_spread {
|
|
|
|
|
for element in &elements[spread_index..] {
|
|
|
|
|
match element {
|
|
|
|
|
None => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let empty = generator.add_constant_empty();
|
|
|
|
|
generator.emit(Instruction::ArrayAppend {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: empty.operand(),
|
|
|
|
|
is_spread: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Some(e) => {
|
|
|
|
|
let is_spread = matches!(e.inner, ExpressionKind::Spread(_));
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(e, generator, None);
|
|
|
|
|
generator.emit(Instruction::ArrayAppend {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: val.operand(),
|
|
|
|
|
is_spread,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_member_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
object: &Expression,
|
|
|
|
|
property: &Expression,
|
|
|
|
|
computed: bool,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
let is_super = matches!(object.inner, ExpressionKind::Super);
|
|
|
|
|
if is_super {
|
|
|
|
|
// Per spec, evaluation order for super property access is:
|
|
|
|
|
// 1. Resolve this binding
|
|
|
|
|
// 2. Evaluate computed property (if any)
|
|
|
|
|
// 3. Resolve super base
|
|
|
|
|
// 4. Property lookup with this
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-02-23 07:50:46 -03:00
|
|
|
let computed_key = if computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generate_expression(property, generator, None)?)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let super_base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: super_base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(key) = computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value_with_this(generator, &dst, &super_base, &key, &this_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(generator, &dst, &super_base, &ident.name, &this_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let obj = generate_expression(object, generator, None)?;
|
2026-03-21 09:02:49 -03:00
|
|
|
let obj = generator.copy_if_needed_to_preserve_evaluation_order(&obj);
|
2026-02-24 08:36:14 -03:00
|
|
|
let base_id = intern_base_identifier(generator, object);
|
2026-02-23 07:50:46 -03:00
|
|
|
if computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property = generate_expression(property, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_get_by_value(generator, &dst, &obj, &property, base_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
// Non-computed: property must be an Identifier
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
if let ExpressionKind::Identifier(ident) = &property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &dst, &obj, &ident.name, base_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: obj.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_yield_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
argument: Option<&Expression>,
|
|
|
|
|
is_yield_from: bool,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate completion registers before evaluating the argument.
|
2026-02-24 08:36:14 -03:00
|
|
|
let received_completion = generator.allocate_register();
|
|
|
|
|
let received_completion_type = generator.allocate_register();
|
|
|
|
|
let received_completion_value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let value = if let Some(argument) = argument {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression_or_undefined(argument, generator, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.add_constant_undefined()
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if is_yield_from {
|
2026-02-27 06:18:31 -03:00
|
|
|
return generate_yield_from(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
value,
|
|
|
|
|
&received_completion,
|
|
|
|
|
&received_completion_type,
|
|
|
|
|
&received_completion_value,
|
2026-02-27 06:18:31 -03:00
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Create continuation block, call generate_yield, then handle
|
|
|
|
|
// completion checking.
|
2026-02-24 08:36:14 -03:00
|
|
|
let continuation_block = generator.make_block();
|
|
|
|
|
let is_in_finalizer = generator.is_in_finalizer();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Save exception register before yielding if in a finalizer,
|
|
|
|
|
// as the act of yielding clears scheduled exceptions.
|
|
|
|
|
let saved_exception = if is_in_finalizer {
|
2026-02-24 08:36:14 -03:00
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
generator.emit_mov_raw(reg.operand(), generator.exception_operand());
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(reg)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
generate_yield(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_block,
|
|
|
|
|
&value,
|
|
|
|
|
&received_completion,
|
|
|
|
|
&received_completion_type,
|
|
|
|
|
&received_completion_value,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.is_in_async_generator_function(),
|
2026-02-23 07:50:46 -03:00
|
|
|
);
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(continuation_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Restore exception register after resuming.
|
|
|
|
|
if let Some(ref saved) = saved_exception {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov_raw(generator.exception_operand(), saved.operand());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(&received_completion, &acc);
|
|
|
|
|
generator.emit(Instruction::GetCompletionFields {
|
2026-02-23 07:50:46 -03:00
|
|
|
type_dst: received_completion_type.operand(),
|
|
|
|
|
value_dst: received_completion_value.operand(),
|
|
|
|
|
completion: received_completion.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let normal_block = generator.make_block();
|
|
|
|
|
let throw_cont = generator.make_block();
|
|
|
|
|
let type_is_normal = generator.allocate_register();
|
|
|
|
|
let normal_type = generator.add_constant_number(CompletionType::Normal.to_f64());
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: type_is_normal.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: normal_type.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&type_is_normal, normal_block, throw_cont);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_value_block = generator.make_block();
|
|
|
|
|
let return_value_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_cont);
|
|
|
|
|
let type_is_throw = generator.allocate_register();
|
|
|
|
|
let throw_type = generator.add_constant_number(CompletionType::Throw.to_f64());
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: type_is_throw.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: throw_type.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&type_is_throw, throw_value_block, return_value_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_value_block);
|
2026-03-01 10:19:10 -03:00
|
|
|
generator.perform_needed_unwinds();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: received_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_value_block);
|
|
|
|
|
generator.generate_return(&received_completion_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(normal_block);
|
2026-02-27 06:18:31 -03:00
|
|
|
received_completion_value
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for an expression, returning `undefined` if the
|
|
|
|
|
/// expression produces no value (e.g. the block was already terminated).
|
|
|
|
|
fn generate_expression_or_undefined(
|
|
|
|
|
expression: &Expression,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> ScopedOperand {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(expression, generator, preferred_dst)
|
|
|
|
|
.unwrap_or_else(|| generator.add_constant_undefined())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for a statement.
|
|
|
|
|
pub fn generate_statement(
|
|
|
|
|
statement: &Statement,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_source_start = generator.current_source_start;
|
|
|
|
|
let saved_source_end = generator.current_source_end;
|
|
|
|
|
generator.current_source_start = statement.range.start.offset;
|
|
|
|
|
generator.current_source_end = statement.range.end.offset;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let result = match &statement.inner {
|
|
|
|
|
StatementKind::Empty | StatementKind::Error | StatementKind::ErrorDeclaration => None,
|
|
|
|
|
StatementKind::Debugger => None,
|
|
|
|
|
|
|
|
|
|
// === ExpressionStatement ===
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::Expression(expression) => generate_expression(expression, generator, None),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Block ===
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::Block(scope) => {
|
|
|
|
|
generate_block_statement(generator, &scope.borrow(), preferred_dst)
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === FunctionBody ===
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::FunctionBody { scope, .. } => {
|
|
|
|
|
generate_scope_children(generator, &scope.borrow(), preferred_dst)
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Program ===
|
|
|
|
|
// Note: GlobalDeclarationInstantiation (GDI) runs before this bytecode
|
|
|
|
|
// executes. GDI hoists top-level function declarations and var bindings
|
|
|
|
|
// to the global scope, including Annex B function-in-block hoisting.
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::Program(data) => {
|
2026-02-23 07:50:46 -03:00
|
|
|
// Populate annexb_function_names so switch codegen can emit
|
|
|
|
|
// GetBinding + SetVariableBinding for AnnexB-hoisted functions
|
|
|
|
|
// (Annex B requires switch cases to copy the block-scoped binding
|
|
|
|
|
// into the var-scoped binding on each case entry).
|
|
|
|
|
let scope = data.scope.borrow();
|
|
|
|
|
for name in &scope.annexb_function_names {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.annexb_function_names.insert(name.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_scope_children(generator, &scope, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === If ===
|
|
|
|
|
StatementKind::If {
|
|
|
|
|
test,
|
|
|
|
|
consequent,
|
|
|
|
|
alternate,
|
2026-02-24 08:36:14 -03:00
|
|
|
} => generate_if_statement(
|
|
|
|
|
generator,
|
|
|
|
|
test,
|
|
|
|
|
consequent,
|
|
|
|
|
alternate.as_deref(),
|
|
|
|
|
preferred_dst,
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === While ===
|
|
|
|
|
StatementKind::While { test, body } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_while_statement(generator, test, body, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === DoWhile ===
|
|
|
|
|
StatementKind::DoWhile { test, body } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_do_while_statement(generator, test, body, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === For ===
|
|
|
|
|
StatementKind::For {
|
|
|
|
|
init,
|
|
|
|
|
test,
|
|
|
|
|
update,
|
|
|
|
|
body,
|
2026-02-24 06:40:18 -03:00
|
|
|
} => generate_for_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
init.as_ref(),
|
|
|
|
|
test.as_deref(),
|
|
|
|
|
update.as_deref(),
|
|
|
|
|
body,
|
|
|
|
|
preferred_dst,
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Return ===
|
|
|
|
|
StatementKind::Return(value) => {
|
|
|
|
|
let val = match value {
|
|
|
|
|
Some(expression) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let v = generate_expression_or_undefined(expression, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
// Async functions implicitly await an explicit return value.
|
2026-03-20 00:26:33 -03:00
|
|
|
// Bare `return;` does NOT await (per spec).
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.is_in_async_function() {
|
|
|
|
|
let received_completion = generator.allocate_register();
|
|
|
|
|
let received_completion_type = generator.allocate_register();
|
|
|
|
|
let received_completion_value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&v,
|
|
|
|
|
&received_completion,
|
|
|
|
|
&received_completion_type,
|
|
|
|
|
&received_completion_value,
|
2026-02-23 07:50:46 -03:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
v
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
None => generator.add_constant_undefined(),
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.generate_return(&val);
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Throw ===
|
|
|
|
|
StatementKind::Throw(expression) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression(expression, generator, None)?;
|
|
|
|
|
generator.perform_needed_unwinds();
|
|
|
|
|
generator.emit(Instruction::Throw { src: val.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Variable declarations ===
|
|
|
|
|
StatementKind::VariableDeclaration { kind, declarations } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_variable_declaration(generator, *kind, declarations);
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Break ===
|
|
|
|
|
StatementKind::Break { target_label } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.generate_break(target_label.as_deref());
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Continue ===
|
|
|
|
|
StatementKind::Continue { target_label } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.generate_continue(target_label.as_deref());
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Labelled ===
|
|
|
|
|
StatementKind::Labelled { label, item } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_labelled_statement(generator, label, item, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Switch ===
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::Switch(data) => generate_switch_statement(generator, data, preferred_dst),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === Try ===
|
2026-02-24 08:36:14 -03:00
|
|
|
StatementKind::Try(data) => generate_try_statement(generator, data, preferred_dst),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === FunctionDeclaration ===
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::FunctionDeclaration {
|
2026-02-24 08:36:14 -03:00
|
|
|
name, is_hoisted, ..
|
2026-02-24 06:40:18 -03:00
|
|
|
} => {
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_hoisted.get() {
|
|
|
|
|
// Annex B.3.3: Copy the function from the lexical (block) scope
|
|
|
|
|
// to the var scope.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(name_ident) = name {
|
|
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetVariableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === With ===
|
|
|
|
|
StatementKind::With { object, body } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let obj = generate_expression(object, generator, None)?;
|
|
|
|
|
let object_environment = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::EnterObjectEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: object_environment.operand(),
|
|
|
|
|
object: obj.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator
|
|
|
|
|
.lexical_environment_register_stack
|
2026-02-24 06:40:18 -03:00
|
|
|
.push(object_environment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let result = generate_statement(body, generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_variable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
// Per spec 13.11.7 step 10: if body completion value is empty,
|
|
|
|
|
// return NormalCompletion(undefined).
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(result.unwrap_or_else(|| generator.add_constant_undefined()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === ForIn / ForOf / ForAwaitOf ===
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::ForInOf {
|
|
|
|
|
kind,
|
|
|
|
|
lhs,
|
|
|
|
|
rhs,
|
|
|
|
|
body,
|
2026-02-24 08:36:14 -03:00
|
|
|
} => generate_for_in_of_statement(generator, *kind, lhs, rhs, body, preferred_dst),
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// === UsingDeclaration ===
|
|
|
|
|
StatementKind::UsingDeclaration { .. } => {
|
|
|
|
|
// Disposal semantics are not yet implemented.
|
2026-02-24 08:36:14 -03:00
|
|
|
let error = generator.allocate_register();
|
|
|
|
|
let msg = generator.intern_string(utf16!("TODO: UsingDeclaration"));
|
|
|
|
|
generator.emit(Instruction::NewTypeError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: error.operand(),
|
|
|
|
|
error_string: msg,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.perform_needed_unwinds();
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: error.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
// Switch to a dead block so subsequent codegen doesn't crash.
|
2026-02-24 08:36:14 -03:00
|
|
|
let dead = generator.make_block();
|
|
|
|
|
generator.switch_to_basic_block(dead);
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === ClassDeclaration ===
|
|
|
|
|
StatementKind::ClassDeclaration(data) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_class_expression(generator, data, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
// 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.
|
2026-03-20 00:26:33 -03:00
|
|
|
// NB: We do NOT mark the local as initialized here, preserving
|
|
|
|
|
// TDZ checks for subsequent uses of the class name.
|
2026-02-27 06:18:31 -03:00
|
|
|
if let Some(name_ident) = &data.name {
|
2026-02-23 07:50:46 -03:00
|
|
|
if name_ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.resolve_local(
|
2026-02-24 06:40:18 -03:00
|
|
|
name_ident.local_index.get(),
|
|
|
|
|
name_ident.local_type.get().unwrap(),
|
|
|
|
|
);
|
2026-02-27 06:18:31 -03:00
|
|
|
generator.emit_mov(&local, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
2026-02-27 06:18:31 -03:00
|
|
|
src: value.operand(),
|
2026-02-23 07:50:46 -03:00
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Import/Export ===
|
|
|
|
|
StatementKind::Import(_) => None, // Handled by module loading
|
|
|
|
|
StatementKind::Export(export_data) => {
|
|
|
|
|
if !export_data.is_default_export {
|
|
|
|
|
// Non-default export: generate code for the wrapped statement.
|
|
|
|
|
if let Some(ref child_statement) = export_data.statement {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(child_statement, generator, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else if let Some(ref child_statement) = export_data.statement {
|
|
|
|
|
match &child_statement.inner {
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::FunctionDeclaration { .. }
|
|
|
|
|
| StatementKind::ClassDeclaration(_) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(child_statement, generator, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// export default <expression>
|
|
|
|
|
// The child_statement wraps an Expression via StatementKind::Expression.
|
|
|
|
|
let default_name: Utf16String = Utf16String::from(utf16!("default"));
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name =
|
|
|
|
|
Some(generator.intern_identifier(&default_name));
|
|
|
|
|
let value = generate_statement(child_statement, generator, None);
|
|
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(value) = value {
|
2026-02-24 08:36:14 -03:00
|
|
|
let local_name = generator.intern_identifier(utf16!("*default*"));
|
|
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: local_name,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
Some(value)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === ClassFieldInitializer ===
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::ClassFieldInitializer {
|
|
|
|
|
expression,
|
|
|
|
|
field_name,
|
|
|
|
|
} => {
|
2026-03-03 18:23:09 -03:00
|
|
|
// 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));
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(expression, generator, None);
|
|
|
|
|
generator.pending_lhs_name = None;
|
|
|
|
|
generator.emit(Instruction::Return {
|
2026-02-23 07:50:46 -03:00
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_source_start = saved_source_start;
|
|
|
|
|
generator.current_source_end = saved_source_end;
|
2026-02-23 07:50:46 -03:00
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Await helper
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Completion::Type values (ABI-compatible).
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
#[repr(u32)]
|
|
|
|
|
enum CompletionType {
|
|
|
|
|
Normal = 1,
|
|
|
|
|
Return = 4,
|
|
|
|
|
Throw = 5,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CompletionType {
|
|
|
|
|
fn to_f64(self) -> f64 {
|
|
|
|
|
self as u32 as f64
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Environment binding mode.
|
|
|
|
|
#[repr(u32)]
|
|
|
|
|
enum EnvironmentMode {
|
|
|
|
|
Lexical = 0,
|
|
|
|
|
Var = 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Arguments object creation mode.
|
|
|
|
|
#[repr(u32)]
|
|
|
|
|
enum ArgumentsKind {
|
|
|
|
|
Mapped = 0,
|
|
|
|
|
Unmapped = 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Class element kind (ABI-compatible with ClassBlueprint::Element::Kind).
|
|
|
|
|
#[repr(u8)]
|
|
|
|
|
enum ClassElementKind {
|
|
|
|
|
Method = 0,
|
|
|
|
|
Getter = 1,
|
|
|
|
|
Setter = 2,
|
|
|
|
|
Field = 3,
|
|
|
|
|
StaticInitializer = 4,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Iterator hint (ABI-compatible).
|
|
|
|
|
#[repr(u32)]
|
|
|
|
|
enum IteratorHint {
|
|
|
|
|
Sync = 0,
|
|
|
|
|
Async = 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Like generate_await but uses caller-provided completion registers.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the received_completion_value on the normal path.
|
|
|
|
|
/// Emits a Throw on the throw path.
|
|
|
|
|
fn generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
argument: &ScopedOperand,
|
|
|
|
|
received_completion: &ScopedOperand,
|
|
|
|
|
received_completion_type: &ScopedOperand,
|
|
|
|
|
received_completion_value: &ScopedOperand,
|
|
|
|
|
) -> ScopedOperand {
|
2026-02-24 08:36:14 -03:00
|
|
|
let continuation = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Await {
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: continuation,
|
|
|
|
|
argument: argument.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(continuation);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(received_completion, &acc);
|
|
|
|
|
generator.emit(Instruction::GetCompletionFields {
|
2026-02-23 07:50:46 -03:00
|
|
|
type_dst: received_completion_type.operand(),
|
|
|
|
|
value_dst: received_completion_value.operand(),
|
|
|
|
|
completion: received_completion.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let normal_block = generator.make_block();
|
|
|
|
|
let throw_block = generator.make_block();
|
|
|
|
|
let is_normal = generator.allocate_register();
|
|
|
|
|
let normal_type = generator.add_constant_number(CompletionType::Normal.to_f64());
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: is_normal.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: normal_type.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_normal, normal_block, throw_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_block);
|
2026-03-01 10:19:10 -03:00
|
|
|
generator.perform_needed_unwinds();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: received_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(normal_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
received_completion_value.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Yield* (yield from) delegation.
|
|
|
|
|
///
|
|
|
|
|
/// Implements the iterator delegation protocol from
|
|
|
|
|
/// https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation
|
|
|
|
|
///
|
|
|
|
|
/// The delegating generator forwards next/throw/return to the inner iterator.
|
|
|
|
|
fn generate_yield_from(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
value: ScopedOperand,
|
|
|
|
|
received_completion: &ScopedOperand,
|
|
|
|
|
received_completion_type: &ScopedOperand,
|
|
|
|
|
received_completion_value: &ScopedOperand,
|
|
|
|
|
) -> ScopedOperand {
|
2026-02-24 08:36:14 -03:00
|
|
|
let is_async = generator.is_in_async_generator_function();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// 4. Let iteratorRecord be ? GetIterator(value, generatorKind).
|
2026-02-24 08:36:14 -03:00
|
|
|
let iterator = generator.allocate_register();
|
|
|
|
|
let next_method = generator.allocate_register();
|
|
|
|
|
let iterator_done_property = generator.allocate_register();
|
2026-02-24 06:40:18 -03:00
|
|
|
let hint = if is_async {
|
|
|
|
|
IteratorHint::Async
|
|
|
|
|
} else {
|
|
|
|
|
IteratorHint::Sync
|
|
|
|
|
} as u32;
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::GetIterator {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst_iterator_object: iterator.operand(),
|
|
|
|
|
dst_iterator_next: next_method.operand(),
|
|
|
|
|
dst_iterator_done: iterator_done_property.operand(),
|
|
|
|
|
iterable: value.operand(),
|
|
|
|
|
hint,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 6. Let received be NormalCompletion(undefined).
|
2026-02-24 08:36:14 -03:00
|
|
|
let normal_const = generator.add_constant_number(CompletionType::Normal.to_f64());
|
|
|
|
|
generator.emit_mov(received_completion_type, &normal_const);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(received_completion_value, &undef);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// 7. Repeat,
|
2026-02-24 08:36:14 -03:00
|
|
|
let loop_block = generator.make_block();
|
|
|
|
|
let continuation_block = generator.make_block();
|
|
|
|
|
let loop_end_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Jump { target: loop_block });
|
|
|
|
|
generator.switch_to_basic_block(loop_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Branch on received.[[Type]].
|
2026-02-24 08:36:14 -03:00
|
|
|
let type_is_normal_block = generator.make_block();
|
|
|
|
|
let is_type_throw_block = generator.make_block();
|
|
|
|
|
let is_normal = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: is_normal.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: normal_const.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_normal, type_is_normal_block, is_type_throw_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// a. If received.[[Type]] is normal, then
|
|
|
|
|
// =========================================================================
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_normal_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// i. Let innerResult be ? Call(next, iterator, « received.[[Value]] »).
|
2026-02-24 08:36:14 -03:00
|
|
|
let inner_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: inner_result.operand(),
|
|
|
|
|
callee: next_method.operand(),
|
|
|
|
|
this_value: iterator.operand(),
|
|
|
|
|
argument_count: 1,
|
|
|
|
|
expression_string: None,
|
|
|
|
|
arguments: vec![received_completion_value.operand()],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ii. If generatorKind is async, set innerResult to ? Await(innerResult).
|
|
|
|
|
if is_async {
|
|
|
|
|
let awaited = generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
&inner_result,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&inner_result, &awaited);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// iii. If innerResult is not an Object, throw a TypeError exception.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: inner_result.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// iv. Let done be ? IteratorComplete(innerResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
let done = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(generator, &done, &inner_result, utf16!("done"), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// v. If done is true, then return ? IteratorValue(innerResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
let type_is_normal_done_block = generator.make_block();
|
|
|
|
|
let type_is_normal_not_done_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(
|
2026-02-24 06:40:18 -03:00
|
|
|
&done,
|
|
|
|
|
type_is_normal_done_block,
|
|
|
|
|
type_is_normal_not_done_block,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_normal_done_block);
|
|
|
|
|
let return_value = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
&return_value,
|
|
|
|
|
&inner_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: loop_end_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// vi/vii. Yield IteratorValue(innerResult), receive new completion.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_normal_not_done_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let current_value = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
¤t_value,
|
|
|
|
|
&inner_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
generate_yield(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_block,
|
|
|
|
|
¤t_value,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// b. Else if received.[[Type]] is throw, then
|
|
|
|
|
// =========================================================================
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(is_type_throw_block);
|
|
|
|
|
let type_is_throw_block = generator.make_block();
|
|
|
|
|
let type_is_return_block = generator.make_block();
|
|
|
|
|
let throw_const = generator.add_constant_number(CompletionType::Throw.to_f64());
|
|
|
|
|
let is_throw = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: is_throw.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: throw_const.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_throw, type_is_throw_block, type_is_return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_throw_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// i. Let throw be ? GetMethod(iterator, "throw").
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_method = generator.allocate_register();
|
|
|
|
|
let throw_key = generator.intern_property_key(utf16!("throw"));
|
|
|
|
|
generator.emit(Instruction::GetMethod {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: throw_method.operand(),
|
|
|
|
|
object: iterator.operand(),
|
|
|
|
|
property: throw_key,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ii. If throw is not undefined, then
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_method_defined_block = generator.make_block();
|
|
|
|
|
let throw_method_undefined_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: throw_method.operand(),
|
|
|
|
|
true_target: throw_method_undefined_block,
|
|
|
|
|
false_target: throw_method_defined_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_method_defined_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »).
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: inner_result.operand(),
|
|
|
|
|
callee: throw_method.operand(),
|
|
|
|
|
this_value: iterator.operand(),
|
|
|
|
|
argument_count: 1,
|
|
|
|
|
expression_string: None,
|
|
|
|
|
arguments: vec![received_completion_value.operand()],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 2. If generatorKind is async, set innerResult to ? Await(innerResult).
|
|
|
|
|
if is_async {
|
|
|
|
|
let awaited = generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
&inner_result,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&inner_result, &awaited);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. If innerResult is not an Object, throw a TypeError exception.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: inner_result.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 5. Let done be ? IteratorComplete(innerResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &done, &inner_result, utf16!("done"), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// 6. If done is true, return ? IteratorValue(innerResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
let type_is_throw_done_block = generator.make_block();
|
|
|
|
|
let type_is_throw_not_done_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(
|
2026-02-24 06:40:18 -03:00
|
|
|
&done,
|
|
|
|
|
type_is_throw_done_block,
|
|
|
|
|
type_is_throw_not_done_block,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_throw_done_block);
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
&return_value,
|
|
|
|
|
&inner_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: loop_end_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 7/8. Yield IteratorValue(innerResult), receive new completion.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_throw_not_done_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let yield_value = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
&yield_value,
|
|
|
|
|
&inner_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
generate_yield(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_block,
|
|
|
|
|
&yield_value,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// throw is undefined: close iterator, throw TypeError.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_method_undefined_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_async {
|
|
|
|
|
// AsyncIteratorClose: get return method, call it, await, check object.
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_method = generator.allocate_register();
|
|
|
|
|
let return_key = generator.intern_property_key(utf16!("return"));
|
|
|
|
|
generator.emit(Instruction::GetMethod {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: return_method.operand(),
|
|
|
|
|
object: iterator.operand(),
|
|
|
|
|
property: return_key,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let call_return_block = generator.make_block();
|
|
|
|
|
let after_close = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: return_method.operand(),
|
|
|
|
|
true_target: after_close,
|
|
|
|
|
false_target: call_return_block,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(call_return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let close_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: close_result.operand(),
|
|
|
|
|
callee: return_method.operand(),
|
|
|
|
|
this_value: iterator.operand(),
|
|
|
|
|
argument_count: 0,
|
|
|
|
|
expression_string: None,
|
|
|
|
|
arguments: vec![],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let awaited = generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
&close_result,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: awaited.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: after_close,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(after_close);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
// Sync: IteratorClose with Normal completion.
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::IteratorClose {
|
2026-02-23 07:50:46 -03:00
|
|
|
iterator_object: iterator.operand(),
|
|
|
|
|
iterator_next: next_method.operand(),
|
|
|
|
|
iterator_done: done.operand(),
|
|
|
|
|
completion_type: CompletionType::Normal as u32,
|
|
|
|
|
completion_value: undef.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Throw a TypeError: iterator does not have a throw method.
|
2026-02-24 08:36:14 -03:00
|
|
|
let exception = generator.allocate_register();
|
|
|
|
|
let error_string = generator.intern_string(utf16!(
|
2026-02-24 06:40:18 -03:00
|
|
|
"yield* protocol violation: iterator must have a throw method"
|
|
|
|
|
));
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewTypeError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: exception.operand(),
|
|
|
|
|
error_string,
|
|
|
|
|
});
|
2026-03-01 10:19:10 -03:00
|
|
|
generator.perform_needed_unwinds();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: exception.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// c. Else (received.[[Type]] is return)
|
|
|
|
|
// =========================================================================
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// ii. Let return be ? GetMethod(iterator, "return").
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_method = generator.allocate_register();
|
|
|
|
|
let return_key = generator.intern_property_key(utf16!("return"));
|
|
|
|
|
generator.emit(Instruction::GetMethod {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: return_method.operand(),
|
|
|
|
|
object: iterator.operand(),
|
|
|
|
|
property: return_key,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// iii. If return is undefined, then return received.[[Value]].
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_is_undefined_block = generator.make_block();
|
|
|
|
|
let return_is_defined_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: return_method.operand(),
|
|
|
|
|
true_target: return_is_undefined_block,
|
|
|
|
|
false_target: return_is_defined_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_is_undefined_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
// 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
|
|
|
|
|
if is_async {
|
|
|
|
|
generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
received_completion_value,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// 2. Return received (return completion).
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.generate_return(received_completion_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_is_defined_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »).
|
2026-02-24 08:36:14 -03:00
|
|
|
let inner_return_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: inner_return_result.operand(),
|
|
|
|
|
callee: return_method.operand(),
|
|
|
|
|
this_value: iterator.operand(),
|
|
|
|
|
argument_count: 1,
|
|
|
|
|
expression_string: None,
|
|
|
|
|
arguments: vec![received_completion_value.operand()],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
|
|
|
|
|
if is_async {
|
|
|
|
|
let awaited = generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
&inner_return_result,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&inner_return_result, &awaited);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// vi. If innerReturnResult is not an Object, throw a TypeError exception.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: inner_return_result.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// vii. Let done be ? IteratorComplete(innerReturnResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &done, &inner_return_result, utf16!("done"), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// viii. If done is true, return IteratorValue(innerReturnResult).
|
2026-02-24 08:36:14 -03:00
|
|
|
let type_is_return_done_block = generator.make_block();
|
|
|
|
|
let type_is_return_not_done_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(
|
2026-02-24 06:40:18 -03:00
|
|
|
&done,
|
|
|
|
|
type_is_return_done_block,
|
|
|
|
|
type_is_return_not_done_block,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_return_done_block);
|
|
|
|
|
let inner_return_result_value = generator.allocate_register();
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_get_by_id(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&inner_return_result_value,
|
|
|
|
|
&inner_return_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.generate_return(&inner_return_result_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// ix/x. Yield IteratorValue(innerReturnResult), receive new completion.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(type_is_return_not_done_block);
|
|
|
|
|
let received = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
&received,
|
|
|
|
|
&inner_return_result,
|
|
|
|
|
utf16!("value"),
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
generate_yield(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_block,
|
|
|
|
|
&received,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
false,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// Continuation block: resume after any yield, extract completion, loop back.
|
|
|
|
|
// =========================================================================
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(continuation_block);
|
|
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(received_completion, &acc);
|
|
|
|
|
generator.emit(Instruction::GetCompletionFields {
|
2026-02-23 07:50:46 -03:00
|
|
|
type_dst: received_completion_type.operand(),
|
|
|
|
|
value_dst: received_completion_value.operand(),
|
|
|
|
|
completion: received_completion.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: loop_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// =========================================================================
|
|
|
|
|
// Loop end: return the accumulated return_value.
|
|
|
|
|
// =========================================================================
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(loop_end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return_value
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
/// Unified yield function.
|
2026-02-23 07:50:46 -03:00
|
|
|
///
|
|
|
|
|
/// For non-async generators: just emits a Yield instruction.
|
|
|
|
|
/// For async generators: optionally awaits the argument first, then yields,
|
|
|
|
|
/// then handles AsyncGeneratorUnwrapYieldResumption (check return type,
|
|
|
|
|
/// await return value, re-classify).
|
|
|
|
|
/// Jumps to continuation_label for the "not return" and "throw after await" paths.
|
|
|
|
|
fn generate_yield(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: Label,
|
|
|
|
|
argument: &ScopedOperand,
|
|
|
|
|
received_completion: &ScopedOperand,
|
|
|
|
|
received_completion_type: &ScopedOperand,
|
|
|
|
|
received_completion_value: &ScopedOperand,
|
|
|
|
|
await_before_yield: bool,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_in_async_generator_function() {
|
|
|
|
|
generator.emit(Instruction::Yield {
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: Some(continuation_label),
|
|
|
|
|
value: argument.operand(),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let argument = if await_before_yield {
|
|
|
|
|
generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
argument,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
2026-02-23 07:50:46 -03:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
argument.clone()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Yield, then UnwrapYieldResumption.
|
2026-02-24 08:36:14 -03:00
|
|
|
let unwrap_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Yield {
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: Some(unwrap_block),
|
|
|
|
|
value: argument.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(unwrap_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(received_completion, &acc);
|
|
|
|
|
generator.emit(Instruction::GetCompletionFields {
|
2026-02-23 07:50:46 -03:00
|
|
|
type_dst: received_completion_type.operand(),
|
|
|
|
|
value_dst: received_completion_value.operand(),
|
|
|
|
|
completion: received_completion.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If resumptionValue.[[Type]] is not return, jump to continuation.
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_block = generator.make_block();
|
|
|
|
|
let is_not_return = generator.allocate_register();
|
|
|
|
|
let return_type = generator.add_constant_number(CompletionType::Return.to_f64());
|
|
|
|
|
generator.emit(Instruction::StrictlyInequals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: is_not_return.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: return_type.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_not_return, continuation_label, return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Return path: Await(resumptionValue.[[Value]]).
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
received_completion_value,
|
|
|
|
|
received_completion,
|
|
|
|
|
received_completion_type,
|
|
|
|
|
received_completion_value,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// If awaited.[[Type]] is throw, jump to continuation.
|
2026-02-24 08:36:14 -03:00
|
|
|
let awaited_normal_block = generator.make_block();
|
|
|
|
|
let is_throw = generator.allocate_register();
|
|
|
|
|
let throw_type = generator.add_constant_number(CompletionType::Throw.to_f64());
|
|
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: is_throw.operand(),
|
|
|
|
|
lhs: received_completion_type.operand(),
|
|
|
|
|
rhs: throw_type.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_throw, continuation_label, awaited_normal_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// awaited.[[Type]] is normal: set type to Return and jump to continuation.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(awaited_normal_block);
|
|
|
|
|
generator.emit(Instruction::SetCompletionType {
|
2026-02-23 07:50:46 -03:00
|
|
|
completion: received_completion.operand(),
|
|
|
|
|
completion_type: CompletionType::Return as u32,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: continuation_label,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Identifier codegen
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for an identifier reference.
|
|
|
|
|
///
|
|
|
|
|
/// Scope analysis determines how the identifier is resolved:
|
|
|
|
|
/// - **Local**: direct register/local access (with TDZ check for let/const)
|
|
|
|
|
/// - **Global**: GetGlobal instruction (with inline cache)
|
|
|
|
|
/// - **Environment**: GetBinding/GetInitializedBinding (with environment coordinate cache)
|
|
|
|
|
fn generate_identifier(
|
|
|
|
|
ident: &Identifier,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-23 07:50:46 -03:00
|
|
|
if ident.is_local() {
|
|
|
|
|
let local_index = ident.local_index.get();
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
|
2026-02-23 07:50:46 -03:00
|
|
|
// Check TDZ for uninitialized bindings.
|
|
|
|
|
// Arguments may need TDZ during default parameter evaluation;
|
|
|
|
|
// for variable-type locals, only lexically-declared (let/const) need TDZ.
|
|
|
|
|
let needs_tdz_check = if ident.local_type.get() == Some(LocalType::Argument) {
|
2026-02-24 08:36:14 -03:00
|
|
|
!generator.is_argument_initialized(local_index)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.is_local_lexically_declared(local_index)
|
|
|
|
|
&& !generator.is_local_initialized(local_index)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
if needs_tdz_check {
|
|
|
|
|
if ident.local_type.get() == Some(LocalType::Argument) {
|
|
|
|
|
// Arguments are initialized to undefined by default, so we
|
|
|
|
|
// need to replace the value with the empty sentinel to
|
|
|
|
|
// trigger the TDZ check.
|
2026-02-24 08:36:14 -03:00
|
|
|
let empty = generator.add_constant_empty();
|
|
|
|
|
generator.emit_mov(&local, &empty);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfTDZ {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: local.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-27 06:18:31 -03:00
|
|
|
return local;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OPTIMIZATION: Generate builtin constants (undefined, NaN, Infinity) directly.
|
2026-02-24 08:36:14 -03:00
|
|
|
if ident.is_global.get()
|
|
|
|
|
&& let Some(constant) = maybe_generate_builtin_constant(generator, &ident.name)
|
|
|
|
|
{
|
2026-02-27 06:18:31 -03:00
|
|
|
return constant;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
if ident.is_global.get() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
let cache = generator.next_global_variable_cache();
|
|
|
|
|
generator.emit(Instruction::GetGlobal {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
identifier: id,
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else if ident.declaration_kind.get() == Some(DeclarationKind::Var) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::GetInitializedBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::GetBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn maybe_generate_builtin_constant(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
name: &[u16],
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-23 07:50:46 -03:00
|
|
|
if name == utf16!("undefined") {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
if name == utf16!("NaN") {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_number(f64::NAN));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
if name == utf16!("Infinity") {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_number(f64::INFINITY));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(op) = try_generate_builtin_constant(generator, &name.into()) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(op);
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Binary operator emission
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn emit_binary_op(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: BinaryOp,
|
|
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
lhs: &ScopedOperand,
|
|
|
|
|
rhs: &ScopedOperand,
|
|
|
|
|
) {
|
|
|
|
|
let dst_op = dst.operand();
|
|
|
|
|
let lhs_op = lhs.operand();
|
|
|
|
|
let rhs_op = rhs.operand();
|
|
|
|
|
match op {
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Addition => generator.emit(Instruction::Add {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Subtraction => generator.emit(Instruction::Sub {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Multiplication => generator.emit(Instruction::Mul {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Division => generator.emit(Instruction::Div {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Modulo => generator.emit(Instruction::Mod {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Exponentiation => generator.emit(Instruction::Exp {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::StrictlyEquals => generator.emit(Instruction::StrictlyEquals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::StrictlyInequals => generator.emit(Instruction::StrictlyInequals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LooselyEquals => generator.emit(Instruction::LooselyEquals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LooselyInequals => generator.emit(Instruction::LooselyInequals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::GreaterThan => generator.emit(Instruction::GreaterThan {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::GreaterThanEquals => generator.emit(Instruction::GreaterThanEquals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LessThan => generator.emit(Instruction::LessThan {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LessThanEquals => generator.emit(Instruction::LessThanEquals {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::BitwiseAnd => generator.emit(Instruction::BitwiseAnd {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-23 07:50:46 -03:00
|
|
|
BinaryOp::BitwiseOr => {
|
2026-03-20 00:26:33 -03:00
|
|
|
// OPTIMIZATION: x | 0 == ToInt32(x)
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(ConstantValue::Number(n)) = generator.get_constant(rhs) {
|
2026-02-23 07:50:46 -03:00
|
|
|
if *n == 0.0 && n.is_sign_positive() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ToInt32 {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
value: lhs_op,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::BitwiseOr {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::BitwiseOr {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::BitwiseXor => generator.emit(Instruction::BitwiseXor {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LeftShift => generator.emit(Instruction::LeftShift {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-03-18 17:30:34 -03:00
|
|
|
BinaryOp::RightShift => {
|
|
|
|
|
// OPTIMIZATION: x >> 0 == ToInt32(x) (matches C++)
|
|
|
|
|
if let Some(ConstantValue::Number(n)) = generator.get_constant(rhs) {
|
|
|
|
|
if *n == 0.0 && n.is_sign_positive() {
|
|
|
|
|
generator.emit(Instruction::ToInt32 {
|
|
|
|
|
dst: dst_op,
|
|
|
|
|
value: lhs_op,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
generator.emit(Instruction::RightShift {
|
|
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
generator.emit(Instruction::RightShift {
|
|
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::UnsignedRightShift => generator.emit(Instruction::UnsignedRightShift {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::In => generator.emit(Instruction::In {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::InstanceOf => generator.emit(Instruction::InstanceOf {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Logical expression (short-circuit)
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_logical(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: LogicalOp,
|
|
|
|
|
lhs: &Expression,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let lhs_val = generate_expression(lhs, generator, preferred_dst)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Constant-fold: if LHS is a constant, we can statically determine the branch.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(constant) = generator.get_constant(&lhs_val) {
|
2026-02-23 07:50:46 -03:00
|
|
|
let is_nullish = matches!(constant, ConstantValue::Null | ConstantValue::Undefined);
|
|
|
|
|
if let Some(is_truthy) = constant_to_boolean(constant) {
|
|
|
|
|
let take_rhs = match op {
|
|
|
|
|
LogicalOp::And => is_truthy,
|
|
|
|
|
LogicalOp::Or => !is_truthy,
|
|
|
|
|
LogicalOp::NullishCoalescing => is_nullish,
|
|
|
|
|
};
|
|
|
|
|
if take_rhs {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, Some(&dst))?;
|
2026-02-23 07:50:46 -03:00
|
|
|
if rhs_val.operand().is_constant() {
|
|
|
|
|
return Some(rhs_val);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
return Some(lhs_val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit_mov(&dst, &lhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
match op {
|
|
|
|
|
LogicalOp::And => {
|
|
|
|
|
// If lhs is falsy, short-circuit to end
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&lhs_val, rhs_block, end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
LogicalOp::Or => {
|
|
|
|
|
// If lhs is truthy, short-circuit to end
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&lhs_val, end_block, rhs_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
LogicalOp::NullishCoalescing => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::JumpNullish {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: lhs_val.operand(),
|
|
|
|
|
true_target: rhs_block,
|
|
|
|
|
false_target: end_block,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, Some(&dst));
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(rhs_val) = &rhs_val {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, rhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Conditional expression (ternary)
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_conditional(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
test: &Expression,
|
|
|
|
|
consequent: &Expression,
|
|
|
|
|
alternate: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let predicate = generate_expression(test, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// OPTIMIZATION: if the predicate is always true/false, only generate the taken expression.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(constant) = generator.get_constant(&predicate)
|
|
|
|
|
&& let Some(is_truthy) = constant_to_boolean(constant)
|
|
|
|
|
{
|
|
|
|
|
if is_truthy {
|
|
|
|
|
return generate_expression(consequent, generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
return generate_expression(alternate, generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let true_block = generator.make_block();
|
|
|
|
|
let false_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&predicate, true_block, false_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(true_block);
|
|
|
|
|
let cons_val = generate_expression(consequent, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(val) = &cons_val {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(false_block);
|
|
|
|
|
let alt_val = generate_expression(alternate, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(val) = &alt_val {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate a statement while propagating the completion register.
|
|
|
|
|
///
|
|
|
|
|
/// Saves and restores `gen.current_completion_register`, and emits a mov
|
|
|
|
|
/// from the statement's result to the completion register when appropriate.
|
|
|
|
|
fn generate_with_completion(
|
|
|
|
|
body: &Statement,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-27 06:37:00 -03:00
|
|
|
completion: Option<&ScopedOperand>,
|
2026-02-23 07:50:46 -03:00
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved = generator.current_completion_register.clone();
|
|
|
|
|
if let Some(c) = completion {
|
|
|
|
|
generator.current_completion_register = Some(c.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let result = generate_statement(body, generator, preferred_dst);
|
|
|
|
|
if !generator.is_current_block_terminated()
|
|
|
|
|
&& let (Some(c), Some(val)) = (completion, &result)
|
|
|
|
|
{
|
|
|
|
|
generator.emit_mov(c, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = saved;
|
2026-02-23 07:50:46 -03:00
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// If statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_if_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
test: &Expression,
|
|
|
|
|
consequent: &Statement,
|
|
|
|
|
alternate: Option<&Statement>,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let pred = generate_expression_or_undefined(test, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = if generator.must_propagate_completion {
|
|
|
|
|
let reg = choose_dst(generator, preferred_dst);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(®, &undef);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(reg)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// OPTIMIZATION: if the predicate is always true/false, only build the taken branch.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(constant) = generator.get_constant(&pred)
|
|
|
|
|
&& let Some(is_truthy) = constant_to_boolean(constant)
|
|
|
|
|
{
|
|
|
|
|
// Pass the completion register as preferred_dst so nested
|
2026-03-20 00:26:33 -03:00
|
|
|
// if-statements reuse the same register.
|
2026-02-24 08:36:14 -03:00
|
|
|
let child_dst = completion.as_ref().or(preferred_dst);
|
|
|
|
|
if is_truthy {
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(consequent, generator, completion.as_ref(), child_dst);
|
2026-02-24 08:36:14 -03:00
|
|
|
} else if let Some(alt) = alternate {
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(alt, generator, completion.as_ref(), child_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
return completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let true_block = generator.make_block();
|
|
|
|
|
let false_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
let has_alternate = alternate.is_some();
|
2026-02-24 06:40:18 -03:00
|
|
|
let end_block = if has_alternate {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.make_block()
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
false_block
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&pred, true_block, false_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Pass completion as preferred_dst to children so nested if-else chains
|
2026-03-20 00:26:33 -03:00
|
|
|
// reuse the same completion register.
|
2026-02-23 07:50:46 -03:00
|
|
|
let child_preferred_dst = completion.as_ref();
|
|
|
|
|
|
|
|
|
|
// Consequent
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_completion = generator.current_completion_register.clone();
|
2026-03-20 00:26:33 -03:00
|
|
|
{
|
|
|
|
|
generator.switch_to_basic_block(true_block);
|
|
|
|
|
if let Some(ref c) = completion {
|
|
|
|
|
generator.current_completion_register = Some(c.clone());
|
|
|
|
|
}
|
|
|
|
|
let cons_result = generate_statement(consequent, generator, child_preferred_dst);
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
if let (Some(c), Some(val)) = (&completion, &cons_result) {
|
|
|
|
|
generator.emit_mov(c, val);
|
|
|
|
|
}
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = saved_completion.clone();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Alternate
|
|
|
|
|
if let Some(alt) = alternate {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(false_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(ref c) = completion {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = Some(c.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let alt_result = generate_statement(alt, generator, child_preferred_dst);
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
if let (Some(c), Some(val)) = (&completion, &alt_result) {
|
|
|
|
|
generator.emit_mov(c, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = saved_completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// While statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_while_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
test: &Expression,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let test_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(test_block);
|
|
|
|
|
let test_val = generate_expression_or_undefined(test, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// OPTIMIZATION: If predicate is always false, ignore body and exit early.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(constant) = generator.get_constant(&test_val)
|
|
|
|
|
&& constant_to_boolean(constant) == Some(false)
|
|
|
|
|
{
|
|
|
|
|
return completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let body_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&test_val, body_block, end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(body_block);
|
|
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
|
|
|
|
generator.begin_continuable_scope(test_block, labels.clone(), completion.clone());
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels, completion.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
generator.end_continuable_scope();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// DoWhile statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_do_while_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
test: &Expression,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let body_block = generator.make_block();
|
|
|
|
|
let test_block = generator.make_block();
|
|
|
|
|
let load_result_and_jump_to_end_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: body_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Generate test FIRST, keeping the test ScopedOperand alive during
|
|
|
|
|
// body generation, consuming a register from the free pool.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(test_block);
|
|
|
|
|
let test_val = generate_expression_or_undefined(test, generator, None);
|
|
|
|
|
generator.emit_jump_if(&test_val, body_block, load_result_and_jump_to_end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Generate body SECOND (test_val still alive).
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(body_block);
|
|
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
|
|
|
|
generator.begin_continuable_scope(test_block, labels.clone(), completion.clone());
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels, completion.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
generator.end_continuable_scope();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(load_result_and_jump_to_end_block);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// For statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_for_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
init: Option<&ForInit>,
|
|
|
|
|
test: Option<&Expression>,
|
|
|
|
|
update: Option<&Expression>,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// Check if init is a lexical declaration (let/const) with non-local variables.
|
|
|
|
|
// If so, we need to create a lexical environment for the loop variables and
|
|
|
|
|
// implement per-iteration copy semantics (CreatePerIterationEnvironment).
|
|
|
|
|
let mut has_lexical_environment = false;
|
|
|
|
|
let mut per_iteration_binding_names: Vec<Utf16String> = Vec::new();
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(ForInit::Declaration(init)) = init
|
|
|
|
|
&& let StatementKind::VariableDeclaration { 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;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-01 09:36:50 -03:00
|
|
|
// begin_variable_scope: CreateLexicalEnvironment + boundary
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
for (name, _) in &non_local_names {
|
|
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
|
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: is_const,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
|
|
|
|
if !is_const {
|
|
|
|
|
per_iteration_binding_names.push(name.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Init
|
|
|
|
|
match init {
|
2026-02-24 06:40:18 -03:00
|
|
|
Some(ForInit::Declaration(decl)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(decl, generator, None);
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
|
|
|
|
Some(ForInit::Expression(expr)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(expr, generator, None);
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
None => {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CreatePerIterationEnvironment after init (first iteration setup).
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_per_iteration_bindings(generator, &per_iteration_binding_names);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Block creation order: body → update (if exists) → test (if exists) → end.
|
2026-02-23 07:50:46 -03:00
|
|
|
// If 'test' is missing, fuse 'test' and 'body' blocks.
|
|
|
|
|
// If 'update' is missing, fuse 'body' and 'update' blocks.
|
2026-02-24 08:36:14 -03:00
|
|
|
let body_block = generator.make_block();
|
2026-02-24 06:40:18 -03:00
|
|
|
let update_block = if update.is_some() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.make_block()
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
body_block
|
|
|
|
|
};
|
|
|
|
|
let test_block = if test.is_some() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.make_block()
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
body_block
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Test
|
|
|
|
|
if let Some(test_expression) = test {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(test_block);
|
|
|
|
|
let test_val = generate_expression_or_undefined(test_expression, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// OPTIMIZATION: test value is always falsey, skip body entirely.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(constant) = generator.get_constant(&test_val)
|
|
|
|
|
&& constant_to_boolean(constant) == Some(false)
|
|
|
|
|
{
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
|
|
|
|
if has_lexical_environment {
|
2026-03-01 09:36:50 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
|
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
return completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&test_val, body_block, end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update
|
|
|
|
|
if let Some(update_expression) = update {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(update_block);
|
|
|
|
|
generate_expression(update_expression, generator, None);
|
|
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Body
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(body_block);
|
|
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
2026-02-24 06:40:18 -03:00
|
|
|
let continue_target = if update.is_some() {
|
|
|
|
|
update_block
|
|
|
|
|
} else {
|
|
|
|
|
test_block
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.begin_continuable_scope(continue_target, labels.clone(), completion.clone());
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels, completion.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
generator.end_continuable_scope();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
// CreatePerIterationEnvironment at end of each iteration.
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_per_iteration_bindings(generator, &per_iteration_binding_names);
|
2026-02-23 07:50:46 -03:00
|
|
|
if update.is_some() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: update_block,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: test_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// end_variable_scope: restore parent environment
|
|
|
|
|
if has_lexical_environment {
|
2026-03-01 09:36:50 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit CreatePerIterationEnvironment: save current binding values, pop env,
|
|
|
|
|
/// push new env, re-create variables, and re-initialize from saved values.
|
|
|
|
|
/// This implements per-iteration lexical scoping for `for (let ...)` loops.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_per_iteration_bindings(generator: &mut Generator, bindings: &[Utf16String]) {
|
2026-02-23 07:50:46 -03:00
|
|
|
if bindings.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save current values into registers.
|
|
|
|
|
let mut saved: Vec<(ScopedOperand, IdentifierTableIndex)> = Vec::with_capacity(bindings.len());
|
|
|
|
|
for name in bindings {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: reg.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
saved.push((reg, id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pop current environment (end_variable_scope).
|
2026-03-01 09:36:50 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Push new environment (begin_variable_scope).
|
2026-03-01 09:36:50 -03:00
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Re-create variables and initialize from saved values.
|
|
|
|
|
for (reg, id) in &saved {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: *id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: *id,
|
|
|
|
|
src: reg.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Scope children (Block, FunctionBody, Program)
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_scope_children(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
scope: &ScopeData,
|
|
|
|
|
_preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
let mut last_result = None;
|
|
|
|
|
for child in &scope.children {
|
2026-02-24 08:36:14 -03:00
|
|
|
let result = generate_statement(child, generator, None);
|
|
|
|
|
if generator.must_propagate_completion
|
|
|
|
|
&& let Some(ref val) = result
|
|
|
|
|
{
|
|
|
|
|
last_result = result.clone();
|
|
|
|
|
if !generator.is_current_block_terminated()
|
|
|
|
|
&& let Some(ref completion_reg) = generator.current_completion_register.clone()
|
|
|
|
|
{
|
|
|
|
|
generator.emit_mov(completion_reg, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// NB: When must_propagate_completion is false, we intentionally do NOT
|
2026-03-20 00:26:33 -03:00
|
|
|
// accumulate results into last_result. `result` goes out of scope at
|
|
|
|
|
// the end of each loop iteration, freeing any temporary registers
|
|
|
|
|
// immediately.
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
last_result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for a block statement, creating a lexical environment
|
|
|
|
|
/// if the block has non-local lexical declarations (let/const/class).
|
|
|
|
|
fn generate_block_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
scope: &ScopeData,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let did_create_env = emit_block_declaration_instantiation(generator, scope);
|
2026-02-23 07:50:46 -03:00
|
|
|
if did_create_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-03-20 00:26:33 -03:00
|
|
|
// emitting a redundant completion Mov.
|
2026-02-24 06:40:18 -03:00
|
|
|
let result = if !did_create_env && scope.children.len() == 1 && is_for_loop(&scope.children[0])
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(&scope.children[0], generator, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_scope_children(generator, scope, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if did_create_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_variable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
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).
|
2026-02-24 06:40:18 -03:00
|
|
|
fn emit_lexical_declarations_for_block<'a>(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: &ScopedOperand,
|
|
|
|
|
children: impl Iterator<Item = &'a Statement>,
|
|
|
|
|
) {
|
2026-02-23 07:50:46 -03:00
|
|
|
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 {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_constant {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateImmutableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: environment.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
strict_binding: true,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateMutableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
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 {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateImmutableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: environment.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
strict_binding: true,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
StatementKind::ClassDeclaration(class_data) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(ref name_ident) = class_data.name
|
|
|
|
|
&& !name_ident.is_local()
|
|
|
|
|
{
|
|
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::CreateMutableBinding {
|
|
|
|
|
environment: environment.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
can_be_deleted: false,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::FunctionDeclaration {
|
|
|
|
|
function_id,
|
2026-02-24 08:36:14 -03:00
|
|
|
name: Some(name_ident),
|
2026-02-24 06:40:18 -03:00
|
|
|
..
|
|
|
|
|
} => {
|
2026-02-23 07:50:46 -03:00
|
|
|
// a. Create binding.
|
|
|
|
|
if !name_ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::CreateMutableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: environment.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
can_be_deleted: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
// b. Instantiate function object.
|
2026-02-24 08:36:14 -03:00
|
|
|
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 {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: fo.operand(),
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
home_object: None,
|
|
|
|
|
lhs_name: None,
|
|
|
|
|
});
|
|
|
|
|
if name_ident.is_local() {
|
|
|
|
|
let local_index = name_ident.local_index.get();
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.local(local_index);
|
|
|
|
|
generator.emit_mov(&local, &fo);
|
|
|
|
|
generator.mark_local_initialized(local_index);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: fo.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_block_declaration_instantiation(generator: &mut Generator, scope: &ScopeData) -> bool {
|
2026-02-23 07:50:46 -03:00
|
|
|
if !needs_block_declaration_instantiation(scope) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let new_env = generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_lexical_declarations_for_block(generator, &new_env, scope.children.iter());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Variable declaration
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_variable_declaration(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
kind: DeclarationKind,
|
|
|
|
|
declarations: &[VariableDeclarator],
|
|
|
|
|
) {
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
// OPTIMIZATION: For let/const declarations where the target is a local identifier,
|
|
|
|
|
// pass the local as preferred_dst to the initializer. This allows NewArray, NewFunction,
|
|
|
|
|
// Add, etc. to write directly to the local instead of temp+Mov.
|
|
|
|
|
// NB: Not safe for `var` since var declarations can have duplicates, meaning the
|
|
|
|
|
// preferred_dst could be used as input in the initializer.
|
|
|
|
|
let init_dst = if kind != DeclarationKind::Var {
|
|
|
|
|
if let VariableDeclaratorTarget::Identifier(ident) = &declaration.target {
|
|
|
|
|
if ident.is_local() && ident.local_type.get() == Some(LocalType::Variable) {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.local(ident.local_index.get()))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set pending LHS name for function name inference.
|
|
|
|
|
if let VariableDeclaratorTarget::Identifier(ident) = &declaration.target {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
let init_value = declaration
|
|
|
|
|
.init
|
|
|
|
|
.as_ref()
|
2026-02-24 08:36:14 -03:00
|
|
|
.and_then(|init| generate_expression(init, generator, init_dst.as_ref()));
|
|
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
match &declaration.target {
|
|
|
|
|
VariableDeclaratorTarget::Identifier(ident) => {
|
|
|
|
|
// var declarations without initializer don't need to assign undefined.
|
|
|
|
|
// The FDI already handles initialization for var bindings.
|
|
|
|
|
if init_value.is_none() && kind == DeclarationKind::Var {
|
|
|
|
|
if ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.mark_local_initialized(ident.local_index.get());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = init_value.unwrap_or_else(|| generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
if ident.is_local() {
|
|
|
|
|
let local_index = ident.local_index.get();
|
2026-02-24 08:36:14 -03:00
|
|
|
let local =
|
|
|
|
|
generator.resolve_local(local_index, ident.local_type.get().unwrap());
|
|
|
|
|
generator.emit_mov(&local, &value);
|
|
|
|
|
generator.mark_local_initialized(local_index);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
2026-02-23 07:50:46 -03:00
|
|
|
match kind {
|
|
|
|
|
DeclarationKind::Var => {
|
|
|
|
|
if ident.is_global.get() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache = generator.next_global_variable_cache();
|
|
|
|
|
generator.emit(Instruction::SetGlobal {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DeclarationKind::Let | DeclarationKind::Const => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
VariableDeclaratorTarget::BindingPattern(pattern) => {
|
|
|
|
|
if let Some(value) = init_value {
|
|
|
|
|
let mode = match kind {
|
|
|
|
|
DeclarationKind::Var => BindingMode::Set,
|
|
|
|
|
DeclarationKind::Let | DeclarationKind::Const => {
|
|
|
|
|
BindingMode::InitializeLexical
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_binding_pattern_bytecode(generator, pattern, mode, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Call expression
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn try_generate_builtin_abstract_operation(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &CallExpressionData,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<Option<ScopedOperand>> {
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.builtin_abstract_operations_enabled {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let name = match &data.callee.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => &ident.name,
|
|
|
|
|
_ => return None,
|
|
|
|
|
};
|
|
|
|
|
if data.arguments.iter().any(|a| a.is_spread) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Operations that map to dedicated bytecode instructions.
|
|
|
|
|
if name == utf16!("IsCallable") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::IsCallable {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("IsConstructor") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::IsConstructor {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("ToBoolean") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ToBoolean {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("ToObject") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ToObject {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("ToLength") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ToLength {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("ThrowIfNotObject") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let src = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ThrowIfNotObject { src: src.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("ThrowTypeError") {
|
|
|
|
|
if let ExpressionKind::StringLiteral(ref s) = data.arguments[0].value.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let message_string = generator.intern_string(s);
|
|
|
|
|
let type_error_register = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::NewTypeError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: type_error_register.operand(),
|
|
|
|
|
error_string: message_string,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.perform_needed_unwinds();
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: type_error_register.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("NewTypeError") {
|
|
|
|
|
if let ExpressionKind::StringLiteral(ref s) = data.arguments[0].value.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let message_string = generator.intern_string(s);
|
|
|
|
|
generator.emit(Instruction::NewTypeError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
error_string: message_string,
|
|
|
|
|
});
|
|
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("NewObjectWithNoPrototype") {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewObjectWithNoPrototype { dst: dst.operand() });
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("NewArrayWithLength") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let length = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::NewArrayWithLength {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
array_length: length.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("CreateAsyncFromSyncIterator") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let iterator = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
let next_method =
|
|
|
|
|
generate_expression_or_undefined(&data.arguments[1].value, generator, None);
|
|
|
|
|
let done = generate_expression_or_undefined(&data.arguments[2].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::CreateAsyncFromSyncIterator {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
iterator: iterator.operand(),
|
|
|
|
|
next_method: next_method.operand(),
|
|
|
|
|
done: done.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("CreateDataPropertyOrThrow") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let object = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
let property = generate_expression_or_undefined(&data.arguments[1].value, generator, None);
|
|
|
|
|
let value = generate_expression_or_undefined(&data.arguments[2].value, generator, None);
|
|
|
|
|
generator.emit(Instruction::CreateDataPropertyOrThrow {
|
2026-02-23 07:50:46 -03:00
|
|
|
object: object.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
value: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(Some(dst));
|
|
|
|
|
}
|
|
|
|
|
if name == utf16!("Call") {
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee = generate_expression_or_undefined(&data.arguments[0].value, generator, None);
|
|
|
|
|
let this_value =
|
|
|
|
|
generate_expression_or_undefined(&data.arguments[1].value, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
let extra_args = &data.arguments[2..];
|
|
|
|
|
let mut argument_holders = Vec::with_capacity(extra_args.len());
|
|
|
|
|
for argument in extra_args {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let callee_name = expression_string_approximation(&data.arguments[0].value)
|
2026-02-24 08:36:14 -03:00
|
|
|
.map(|s| generator.intern_string(&s));
|
2026-02-23 07:50:46 -03:00
|
|
|
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
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 {
|
2026-02-24 06:40:18 -03:00
|
|
|
super::ffi::get_abstract_operation_function(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.vm_ptr,
|
2026-02-24 06:40:18 -03:00
|
|
|
op_name.as_ptr(),
|
|
|
|
|
op_name.len(),
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee = generator.add_constant_raw_value(intrinsic_value);
|
|
|
|
|
let undefined = generator.add_constant_undefined();
|
|
|
|
|
let expression_string = generator.intern_string(name);
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut argument_holders = Vec::with_capacity(data.arguments.len());
|
|
|
|
|
for argument in &data.arguments {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
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.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn try_generate_builtin_constant(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
name: &Utf16String,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
if !generator.builtin_abstract_operations_enabled {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
if *name == utf16!("SYMBOL_ITERATOR") {
|
2026-03-18 14:55:08 -03:00
|
|
|
let value = unsafe {
|
|
|
|
|
super::ffi::get_well_known_symbol(generator.vm_ptr, WellKnownSymbolKind::SymbolIterator)
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_raw_value(value));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
if *name == utf16!("SYMBOL_ASYNC_ITERATOR") {
|
2026-03-18 14:55:08 -03:00
|
|
|
let value = unsafe {
|
|
|
|
|
super::ffi::get_well_known_symbol(
|
|
|
|
|
generator.vm_ptr,
|
|
|
|
|
WellKnownSymbolKind::SymbolAsyncIterator,
|
|
|
|
|
)
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_raw_value(value));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
if *name == utf16!("MAX_ARRAY_LIKE_INDEX") {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_number(9007199254740991.0));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for a call expression (`f()`) or new expression (`new C()`).
|
|
|
|
|
///
|
|
|
|
|
/// Handles several special forms:
|
|
|
|
|
/// - Direct `eval()` calls (CallWithArgumentArray with IsDirectEval flag)
|
|
|
|
|
/// - Member calls (`obj.f()`) that need to pass `this`
|
|
|
|
|
/// - Super calls (`super()`)
|
|
|
|
|
/// - Spread arguments (CallWithArgumentArray)
|
|
|
|
|
/// - Builtin abstract operation detection for built-in JS files
|
|
|
|
|
fn generate_call_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &CallExpressionData,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
is_new: bool,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// Check for builtin abstract operations before anything else.
|
2026-02-24 08:36:14 -03:00
|
|
|
if !is_new
|
|
|
|
|
&& let Some(result) =
|
|
|
|
|
try_generate_builtin_abstract_operation(generator, data, preferred_dst)
|
|
|
|
|
{
|
|
|
|
|
return result;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Compute expression_string for error messages (e.g. "true is not a function (evaluated from 'a')").
|
|
|
|
|
let expression_string: Option<StringTableIndex> =
|
2026-02-24 08:36:14 -03:00
|
|
|
expression_string_approximation(&data.callee).map(|s| generator.intern_string(&s));
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Detect direct eval calls: bare identifier "eval" as callee.
|
|
|
|
|
let is_direct_eval = !is_new
|
|
|
|
|
&& matches!(&data.callee.inner, ExpressionKind::Identifier(ident) if ident.name == utf16!("eval"));
|
|
|
|
|
|
|
|
|
|
// Detect known builtins for member expression callees (e.g. Math.abs).
|
|
|
|
|
let builtin: Option<u8> = if !is_new {
|
|
|
|
|
get_builtin(&data.callee)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// For method calls (obj.method()), we need to use the object as `this`.
|
|
|
|
|
let (callee, this_value) = if !is_new {
|
|
|
|
|
match &data.callee.inner {
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) if matches!(data.object.inner, ExpressionKind::Super) => {
|
2026-02-23 07:50:46 -03:00
|
|
|
// Super member call: super.method() or super[expr]()
|
2026-03-20 00:26:33 -03:00
|
|
|
// Spec evaluation order:
|
2026-02-23 07:50:46 -03:00
|
|
|
// 1. ResolveThisBinding
|
|
|
|
|
// 2. Evaluate computed property (if any)
|
|
|
|
|
// 3. ResolveSuperBase
|
|
|
|
|
// 4. GetByIdWithThis / GetByValueWithThis
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-22 15:14:03 -03:00
|
|
|
let computed_key = if data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let super_base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: super_base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let method = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(key) = computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(
|
|
|
|
|
generator,
|
|
|
|
|
&method,
|
|
|
|
|
&super_base,
|
|
|
|
|
&ident.name,
|
|
|
|
|
&this_value,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
(method, Some(this_value))
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
|
|
|
|
let obj = generate_expression_or_undefined(&data.object, generator, None);
|
|
|
|
|
let base_id = intern_base_identifier(generator, &data.object);
|
2026-02-24 08:36:14 -03:00
|
|
|
let method = generator.allocate_register();
|
2026-03-22 15:14:03 -03:00
|
|
|
if data.computed {
|
|
|
|
|
let property =
|
|
|
|
|
generate_expression_or_undefined(&data.property, generator, None);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value(generator, &method, &obj, &property, None);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &method, &obj, &ident.name, base_id);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
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
|
2026-03-20 00:26:33 -03:00
|
|
|
// if not yet initialized.
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator
|
|
|
|
|
.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap());
|
2026-02-23 07:50:46 -03:00
|
|
|
let needs_tdz = if ident.local_type.get() == Some(LocalType::Argument) {
|
2026-02-24 08:36:14 -03:00
|
|
|
!generator.is_argument_initialized(ident.local_index.get())
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.is_local_lexically_declared(ident.local_index.get())
|
|
|
|
|
&& !generator.is_local_initialized(ident.local_index.get())
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
if needs_tdz {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfTDZ {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: local.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
(local, None)
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::Identifier(ident) if !ident.is_global.get() => {
|
|
|
|
|
// Non-local, non-global identifier: use GetCalleeAndThisFromEnvironment
|
|
|
|
|
// to properly handle with-statement bindings and eval.
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee_reg = generator.allocate_register();
|
|
|
|
|
let this_reg = generator.allocate_register();
|
|
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::GetCalleeAndThisFromEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
callee: callee_reg.operand(),
|
|
|
|
|
this_value: this_reg.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
(callee_reg, Some(this_reg))
|
|
|
|
|
}
|
2026-03-22 15:16:49 -03:00
|
|
|
ExpressionKind::OptionalChain(oc_data) => {
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate callee (current_value) first, this_value
|
|
|
|
|
// (current_base) second.
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee = generator.allocate_register();
|
|
|
|
|
let this_value = generator.allocate_register();
|
2026-03-22 15:16:49 -03:00
|
|
|
generate_optional_chain_inner(
|
|
|
|
|
generator,
|
|
|
|
|
&oc_data.base,
|
|
|
|
|
&oc_data.references,
|
|
|
|
|
&callee,
|
|
|
|
|
&this_value,
|
|
|
|
|
)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
(callee, Some(this_value))
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee = generate_expression_or_undefined(&data.callee, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(callee, None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee = generate_expression_or_undefined(&data.callee, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(callee, None)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Copy callee/this into fresh registers so argument evaluation
|
|
|
|
|
// cannot mutate them (e.g. `foo.bar(foo = null)`).
|
2026-02-24 08:36:14 -03:00
|
|
|
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);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Unwrap this_value at function scope so its register lifetime outlives argument temporaries.
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = this_value.unwrap_or_else(|| generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let has_spread = data.arguments.iter().any(|a| a.is_spread);
|
|
|
|
|
|
|
|
|
|
if has_spread {
|
|
|
|
|
// Build an arguments array using NewArray + ArrayAppend for spread elements.
|
2026-02-24 08:36:14 -03:00
|
|
|
let arguments_array = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
let first_spread = data.arguments.iter().position(|a| a.is_spread).unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
let mut pre_holders = Vec::with_capacity(first_spread);
|
|
|
|
|
for argument in &data.arguments[..first_spread] {
|
2026-02-24 08:36:14 -03:00
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
generator.emit_mov(®, &val);
|
2026-02-23 07:50:46 -03:00
|
|
|
pre_holders.push(reg);
|
|
|
|
|
}
|
|
|
|
|
let pre_arguments: Vec<Operand> = pre_holders.iter().map(|a| a.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: arguments_array.operand(),
|
|
|
|
|
element_count: u32_from_usize(pre_arguments.len()),
|
|
|
|
|
elements: pre_arguments,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for argument in &data.arguments[first_spread..] {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ArrayAppend {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: arguments_array.operand(),
|
|
|
|
|
src: val.operand(),
|
|
|
|
|
is_spread: argument.is_spread,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if is_new {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallConstructWithArgumentArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
arguments: arguments_array.operand(),
|
|
|
|
|
expression_string,
|
|
|
|
|
});
|
|
|
|
|
} else if is_direct_eval {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallDirectEvalWithArgumentArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
arguments: arguments_array.operand(),
|
|
|
|
|
expression_string,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallWithArgumentArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
arguments: arguments_array.operand(),
|
|
|
|
|
expression_string,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Copy local variables into fresh registers so that evaluating
|
|
|
|
|
// later arguments cannot mutate earlier argument values (e.g.
|
|
|
|
|
// `bar(i, i++)` — the first argument must be the pre-increment value).
|
|
|
|
|
let mut argument_holders = Vec::with_capacity(data.arguments.len());
|
|
|
|
|
for argument in &data.arguments {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let arguments: Vec<Operand> = argument_holders.iter().map(|a| a.operand()).collect();
|
|
|
|
|
|
|
|
|
|
if is_new {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallConstruct {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
argument_count: u32_from_usize(arguments.len()),
|
|
|
|
|
expression_string,
|
|
|
|
|
arguments,
|
|
|
|
|
});
|
|
|
|
|
} else if is_direct_eval {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallDirectEval {
|
2026-02-23 07:50:46 -03:00
|
|
|
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() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CallBuiltin {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
argument_count: u32_from_usize(arguments.len()),
|
|
|
|
|
builtin: b,
|
|
|
|
|
expression_string,
|
|
|
|
|
arguments,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: callee.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
argument_count: u32_from_usize(arguments.len()),
|
|
|
|
|
expression_string,
|
|
|
|
|
arguments,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
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(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: UpdateOp,
|
|
|
|
|
prefixed: bool,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
) -> ScopedOperand {
|
|
|
|
|
if prefixed {
|
|
|
|
|
match op {
|
2026-02-24 08:36:14 -03:00
|
|
|
UpdateOp::Increment => generator.emit(Instruction::Increment {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
UpdateOp::Decrement => generator.emit(Instruction::Decrement {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
}),
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
value.clone()
|
|
|
|
|
} else {
|
2026-03-20 00:26:33 -03:00
|
|
|
// Always allocate a fresh register for the old value.
|
2026-03-01 10:05:28 -03:00
|
|
|
let dst = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
match op {
|
2026-02-24 08:36:14 -03:00
|
|
|
UpdateOp::Increment => generator.emit(Instruction::PostfixIncrement {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
UpdateOp::Decrement => generator.emit(Instruction::PostfixDecrement {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
dst
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_update_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: UpdateOp,
|
|
|
|
|
argument: &Expression,
|
|
|
|
|
prefixed: bool,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// Load the value, keeping track of the base for member expressions
|
|
|
|
|
// so we can store back without re-evaluating.
|
|
|
|
|
match &argument.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => {
|
2026-02-27 06:18:31 -03:00
|
|
|
let value = generate_identifier(ident, generator, None);
|
2026-03-01 10:05:28 -03:00
|
|
|
let result = emit_update_op(generator, op, prefixed, &value);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_set_variable(generator, ident, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(result)
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
|
|
|
|
let is_super = matches!(data.object.inner, ExpressionKind::Super);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_super {
|
|
|
|
|
// Per spec, evaluation order for super property access is:
|
|
|
|
|
// 1. ResolveThisBinding
|
|
|
|
|
// 2. Evaluate computed property (if any)
|
|
|
|
|
// 3. ResolveSuperBase
|
|
|
|
|
// 4. Property lookup with this
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-22 15:14:03 -03:00
|
|
|
let computed_key = if data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(ref key) = computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value_with_this(generator, &value, &base, key, &this_value);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &data.property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(generator, &value, &base, &ident.name, &this_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-03-01 10:05:28 -03:00
|
|
|
let result = emit_update_op(generator, op, prefixed, &value);
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_super_put(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&data.property,
|
|
|
|
|
data.computed,
|
2026-02-24 06:40:18 -03:00
|
|
|
&this_value,
|
|
|
|
|
&value,
|
|
|
|
|
computed_key.as_ref(),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(result)
|
|
|
|
|
} else {
|
|
|
|
|
// Non-super member update expression.
|
2026-03-22 15:14:03 -03:00
|
|
|
let base = generate_expression(&data.object, generator, None)?;
|
|
|
|
|
let base_id = intern_base_identifier(generator, &data.object);
|
|
|
|
|
if data.computed {
|
|
|
|
|
let property = generate_expression(&data.property, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
emit_get_by_value(generator, &value, &base, &property, base_id);
|
2026-03-01 10:05:28 -03:00
|
|
|
let result = emit_update_op(generator, op, prefixed, &value);
|
2026-03-20 00:26:33 -03:00
|
|
|
emit_put_normal_by_value(generator, &base, &property, &value, None);
|
2026-02-24 06:40:18 -03:00
|
|
|
Some(result)
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(property_ident) = &data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(generator, &value, &base, &property_ident.name, base_id);
|
|
|
|
|
let key = generator.intern_property_key(&property_ident.name);
|
2026-03-01 10:05:28 -03:00
|
|
|
let result = emit_update_op(generator, op, prefixed, &value);
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache2 = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache2 as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
Some(result)
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner {
|
2026-03-01 12:43:53 -03:00
|
|
|
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)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
// Fallback: just evaluate, no store-back
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// Invalid update target (e.g. foo()++). Per spec, evaluate the
|
|
|
|
|
// expression first, then throw ReferenceError.
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(argument, generator, None);
|
|
|
|
|
emit_invalid_lhs_error(generator);
|
|
|
|
|
Some(generator.add_constant_undefined())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Assignment expression
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for all forms of assignment: simple (`=`), compound
|
|
|
|
|
/// (`+=`, `-=`, etc.), and logical (`&&=`, `||=`, `??=`).
|
|
|
|
|
///
|
|
|
|
|
/// Handles identifiers (local, global, environment), member expressions
|
|
|
|
|
/// (by-id, by-value, super, private), and destructuring patterns.
|
|
|
|
|
fn generate_assignment_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: AssignmentOp,
|
|
|
|
|
lhs: &AssignmentLhs,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
match lhs {
|
|
|
|
|
AssignmentLhs::Expression(lhs_expression) => {
|
|
|
|
|
// Simple assignment to identifier
|
|
|
|
|
if let ExpressionKind::Identifier(ident) = &lhs_expression.inner {
|
|
|
|
|
if op == AssignmentOp::Assignment {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
generator.pending_lhs_name = None;
|
2026-03-04 10:02:52 -03:00
|
|
|
if ident.is_local() {
|
|
|
|
|
emit_tdz_check_if_needed(generator, ident);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_set_variable(generator, ident, &rhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(rhs_val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load LHS value first (needed for both compound and logical assignments).
|
2026-02-27 06:18:31 -03:00
|
|
|
let lhs_val = generate_identifier(ident, generator, None);
|
2026-03-08 07:47:17 -03:00
|
|
|
let lhs_val = generator.copy_if_needed_to_preserve_evaluation_order(&lhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let is_logical = matches!(
|
|
|
|
|
op,
|
|
|
|
|
AssignmentOp::AndAssignment
|
|
|
|
|
| AssignmentOp::OrAssignment
|
|
|
|
|
| AssignmentOp::NullishAssignment
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_logical {
|
|
|
|
|
// Logical assignments short-circuit: evaluate RHS only if condition met.
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let lhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
match op {
|
|
|
|
|
AssignmentOp::AndAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&lhs_val, rhs_block, lhs_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
AssignmentOp::OrAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&lhs_val, lhs_block, rhs_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
AssignmentOp::NullishAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::JumpNullish {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: lhs_val.operand(),
|
|
|
|
|
true_target: rhs_block,
|
|
|
|
|
false_target: lhs_block,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("only logical assignment ops reach this branch"),
|
|
|
|
|
}
|
|
|
|
|
// RHS block: evaluate RHS, assign, jump to end.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
generator.pending_lhs_name = None;
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst after RHS evaluation.
|
2026-02-23 07:50:46 -03:00
|
|
|
let dst = if lhs_val.operand().is_local() {
|
|
|
|
|
lhs_val.clone()
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
choose_dst(generator, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
|
|
|
|
emit_set_variable(generator, ident, &dst);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
// LHS block: keep original value.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(lhs_block);
|
|
|
|
|
generator.emit_mov(&dst, &lhs_val);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Regular compound assignment (+=, -=, etc.)
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
// OPTIMIZATION: If LHS is a local, write directly into it.
|
|
|
|
|
let dst = if lhs_val.operand().is_local() {
|
|
|
|
|
lhs_val.clone()
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
choose_dst(generator, preferred_dst)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_compound_assignment(generator, op, &dst, &lhs_val, &rhs_val);
|
|
|
|
|
emit_set_variable(generator, ident, &dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
// Member expression LHS (e.g., obj.foo = x, obj[key] = x)
|
2026-03-22 15:14:03 -03:00
|
|
|
if let ExpressionKind::Member(member_data) = &lhs_expression.inner {
|
|
|
|
|
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_super {
|
|
|
|
|
// Per spec, evaluation order for super property reference is:
|
|
|
|
|
// 1. ResolveThisBinding
|
|
|
|
|
// 2. Evaluate computed property (if any)
|
|
|
|
|
// 3. ResolveSuperBase
|
2026-02-24 08:36:14 -03:00
|
|
|
let super_this = emit_resolve_this_binding(generator);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if op == AssignmentOp::Assignment {
|
2026-03-22 15:14:03 -03:00
|
|
|
let computed_key = if member_data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&member_data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_super_put(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&member_data.property,
|
|
|
|
|
member_data.computed,
|
2026-02-24 06:40:18 -03:00
|
|
|
&super_this,
|
|
|
|
|
&rhs_val,
|
|
|
|
|
computed_key.as_ref(),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(rhs_val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compound/logical assignment: evaluate property, resolve
|
|
|
|
|
// super base, then get old value.
|
2026-03-22 15:14:03 -03:00
|
|
|
let computed_key = if member_data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&member_data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_val = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(ref key) = computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value_with_this(generator, &old_val, &base, key, &super_this);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(
|
|
|
|
|
generator,
|
|
|
|
|
&old_val,
|
|
|
|
|
&base,
|
|
|
|
|
&ident.name,
|
|
|
|
|
&super_this,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
let is_logical = matches!(
|
|
|
|
|
op,
|
|
|
|
|
AssignmentOp::AndAssignment
|
|
|
|
|
| AssignmentOp::OrAssignment
|
|
|
|
|
| AssignmentOp::NullishAssignment
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_logical {
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let lhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
|
|
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_super_put(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&member_data.property,
|
|
|
|
|
member_data.computed,
|
2026-02-24 06:40:18 -03:00
|
|
|
&super_this,
|
|
|
|
|
&dst,
|
|
|
|
|
computed_key.as_ref(),
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(lhs_block);
|
|
|
|
|
generator.emit_mov(&dst, &old_val);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_super_put(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&member_data.property,
|
|
|
|
|
member_data.computed,
|
2026-02-24 06:40:18 -03:00
|
|
|
&super_this,
|
|
|
|
|
&dst,
|
|
|
|
|
computed_key.as_ref(),
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Non-super member assignment.
|
2026-03-22 15:14:03 -03:00
|
|
|
let base_raw = generate_expression(&member_data.object, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if op == AssignmentOp::Assignment {
|
2026-02-24 08:36:14 -03:00
|
|
|
let base = generator.copy_if_needed_to_preserve_evaluation_order(&base_raw);
|
2026-03-22 15:14:03 -03:00
|
|
|
let precomputed_key = if member_data.computed {
|
|
|
|
|
let key_val = generate_expression_or_undefined(
|
|
|
|
|
&member_data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.copy_if_needed_to_preserve_evaluation_order(&key_val))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(key) = precomputed_key {
|
2026-03-22 15:14:03 -03:00
|
|
|
let base_id = intern_base_identifier(generator, &member_data.object);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_normal_by_value(generator, &base, &key, &rhs_val, base_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_to_member(
|
|
|
|
|
generator,
|
|
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&member_data.property,
|
2026-02-24 08:36:14 -03:00
|
|
|
false,
|
|
|
|
|
&rhs_val,
|
2026-03-22 15:14:03 -03:00
|
|
|
Some(&member_data.object),
|
2026-02-24 08:36:14 -03:00
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
return Some(rhs_val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compound/logical member assignment.
|
|
|
|
|
let base = base_raw;
|
2026-03-22 15:14:03 -03:00
|
|
|
let base_id = intern_base_identifier(generator, &member_data.object);
|
2026-02-24 06:40:18 -03:00
|
|
|
let is_logical = matches!(
|
|
|
|
|
op,
|
|
|
|
|
AssignmentOp::AndAssignment
|
|
|
|
|
| AssignmentOp::OrAssignment
|
|
|
|
|
| AssignmentOp::NullishAssignment
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-22 15:14:03 -03:00
|
|
|
if member_data.computed {
|
|
|
|
|
let property = generate_expression(&member_data.property, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_val = generator.allocate_register();
|
|
|
|
|
emit_get_by_value(generator, &old_val, &base, &property, base_id);
|
2026-03-20 00:26:33 -03:00
|
|
|
// Copy property to a fresh register so RHS evaluation
|
|
|
|
|
// (which may mutate the variable backing property) doesn't
|
|
|
|
|
// affect the store-back index.
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_property = generator.allocate_register();
|
|
|
|
|
generator.emit_mov(&saved_property, &property);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_logical {
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let lhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
|
|
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
|
|
|
|
emit_put_normal_by_value(generator, &base, &saved_property, &dst, None);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(lhs_block);
|
|
|
|
|
generator.emit_mov(&dst, &old_val);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
|
|
|
|
|
emit_put_normal_by_value(generator, &base, &saved_property, &dst, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_val = generator.allocate_register();
|
|
|
|
|
emit_get_by_id(generator, &old_val, &base, &ident.name, base_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_logical {
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let lhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
|
|
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
|
|
|
|
let key = generator.intern_property_key(&ident.name);
|
|
|
|
|
let cache2 = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: dst.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache2 as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(lhs_block);
|
|
|
|
|
generator.emit_mov(&dst, &old_val);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
|
|
|
|
|
let key = generator.intern_property_key(&ident.name);
|
|
|
|
|
let cache2 = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: dst.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache2 as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
return Some(dst);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) =
|
|
|
|
|
&member_data.property.inner
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_val = generator.allocate_register();
|
|
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: old_val.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
|
|
|
|
if is_logical {
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_block = generator.make_block();
|
|
|
|
|
let lhs_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
emit_logical_jump(generator, op, &old_val, rhs_block, lhs_block);
|
|
|
|
|
generator.switch_to_basic_block(rhs_block);
|
|
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst after RHS evaluation.
|
2026-03-03 18:25:05 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, &rhs_val);
|
|
|
|
|
let id2 = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::PutPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: id2,
|
|
|
|
|
src: dst.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(lhs_block);
|
|
|
|
|
generator.emit_mov(&dst, &old_val);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
return Some(dst);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
|
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
emit_compound_assignment(generator, op, &dst, &old_val, &rhs_val);
|
|
|
|
|
let id2 = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::PutPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: id2,
|
|
|
|
|
src: dst.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// LHS is not an identifier or member expression (e.g. a function call).
|
|
|
|
|
// Per spec 13.15.2 step 1b, evaluate the LHS, then throw ReferenceError
|
|
|
|
|
// before evaluating the RHS.
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(lhs_expression, generator, None);
|
|
|
|
|
emit_invalid_lhs_error(generator);
|
|
|
|
|
Some(generator.add_constant_undefined())
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
AssignmentLhs::Pattern(pattern) => {
|
2026-03-01 13:21:28 -03:00
|
|
|
let rhs_val = generate_expression(rhs, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_binding_pattern_bytecode(generator, pattern, BindingMode::Set, &rhs_val);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(rhs_val)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit ResolveThisBinding (if not already resolved in current block) and return
|
|
|
|
|
/// the this value register.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_resolve_this_binding(generator: &mut Generator) -> ScopedOperand {
|
|
|
|
|
emit_resolve_this_if_needed(generator);
|
|
|
|
|
generator.this_value()
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit ResolveThisBinding only if not already resolved in the current or entry block.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_resolve_this_if_needed(generator: &mut Generator) {
|
|
|
|
|
let index = generator.current_block_index().basic_block_index();
|
|
|
|
|
if generator.basic_blocks[index].resolved_this {
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.basic_blocks[0].resolved_this {
|
|
|
|
|
generator.basic_blocks[index].resolved_this = true;
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ResolveThisBinding);
|
|
|
|
|
let index = generator.current_block_index().basic_block_index();
|
|
|
|
|
generator.basic_blocks[index].resolved_this = true;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a super property get (uses WithThis variants).
|
|
|
|
|
/// For computed access, evaluates the property expression.
|
|
|
|
|
/// Returns the evaluated property operand for computed access (so callers
|
|
|
|
|
/// can reuse it for a subsequent put).
|
|
|
|
|
fn emit_super_get(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &Expression,
|
|
|
|
|
computed: bool,
|
|
|
|
|
this_value: &ScopedOperand,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
if computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property = generate_expression_or_undefined(property, generator, None);
|
|
|
|
|
emit_get_by_value_with_this(generator, dst, base, &property, this_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(property)
|
|
|
|
|
} else if let ExpressionKind::Identifier(ident) = &property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(generator, dst, base, &ident.name, this_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a super property put (uses WithThis variants).
|
|
|
|
|
/// For computed access, `computed_key` should be the operand returned by
|
|
|
|
|
/// `emit_super_get` so the property is not re-evaluated. If `None` for
|
|
|
|
|
/// computed access, the property expression will be evaluated.
|
|
|
|
|
fn emit_super_put(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &Expression,
|
|
|
|
|
computed: bool,
|
|
|
|
|
this_value: &ScopedOperand,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
computed_key: Option<&ScopedOperand>,
|
|
|
|
|
) {
|
|
|
|
|
if computed {
|
|
|
|
|
let property = match computed_key {
|
|
|
|
|
Some(k) => k.clone(),
|
2026-02-24 08:36:14 -03:00
|
|
|
None => generate_expression_or_undefined(property, generator, None),
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_normal_by_value_with_this(generator, base, &property, this_value, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(&ident.name);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByIdWithThis {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a property access by name, using GetLength for the "length" property.
|
|
|
|
|
fn emit_get_by_id(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property_name: &[u16],
|
|
|
|
|
base_identifier: Option<IdentifierTableIndex>,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(property_name);
|
2026-02-23 07:50:46 -03:00
|
|
|
if property_name == utf16!("length") {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.length_identifier = Some(key);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
|
|
|
|
generator.emit(Instruction::GetLength {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
base_identifier,
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache = generator.next_property_lookup_cache();
|
|
|
|
|
generator.emit(Instruction::GetById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
base_identifier,
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-01 09:19:43 -03:00
|
|
|
/// 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(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-01 09:19:43 -03:00
|
|
|
});
|
|
|
|
|
} 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(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-01 09:19:43 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-01 09:18:03 -03:00
|
|
|
/// Emit a "Invalid left-hand side in assignment" ReferenceError followed by Throw.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_invalid_lhs_error(generator: &mut Generator) {
|
|
|
|
|
let exception = generator.allocate_register();
|
|
|
|
|
let error_string = generator.intern_string(utf16!("Invalid left-hand side in assignment"));
|
|
|
|
|
generator.emit(Instruction::NewReferenceError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: exception.operand(),
|
|
|
|
|
error_string,
|
|
|
|
|
});
|
2026-03-01 10:19:10 -03:00
|
|
|
generator.perform_needed_unwinds();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: exception.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a UTF-16 string is a canonical array index (non-negative integer < 2^32 - 1).
|
2026-03-20 00:26:33 -03:00
|
|
|
/// These strings become integer PropertyKeys,
|
2026-02-23 07:50:46 -03:00
|
|
|
/// not string PropertyKeys, so they must NOT be optimized to GetById/PutById.
|
|
|
|
|
pub(crate) fn is_array_index(s: &[u16]) -> bool {
|
|
|
|
|
if s.is_empty() || s.len() > 10 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Must not have leading zeros (except "0" itself)
|
|
|
|
|
if s.len() > 1 && s[0] == ch(b'0') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
let mut value: u64 = 0;
|
|
|
|
|
for &c in s {
|
|
|
|
|
if c < ch(b'0') || c > ch(b'9') {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
value = value * 10 + (c - ch(b'0')) as u64;
|
|
|
|
|
}
|
|
|
|
|
value <= 0xFFFF_FFFE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a property read by value, optimizing constant string properties to GetById.
|
|
|
|
|
fn emit_get_by_value(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &ScopedOperand,
|
|
|
|
|
base_identifier: Option<IdentifierTableIndex>,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(key) = generator.try_constant_string_to_property_key(property) {
|
|
|
|
|
if generator.property_key_table[key.0 as usize].0 == utf16!("length") {
|
|
|
|
|
generator.length_identifier = Some(key);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
|
|
|
|
generator.emit(Instruction::GetLength {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
base_identifier,
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache = generator.next_property_lookup_cache();
|
|
|
|
|
generator.emit(Instruction::GetById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
base_identifier,
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::GetByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
base_identifier,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a property read by value with explicit this, optimizing constant string properties.
|
|
|
|
|
fn emit_get_by_value_with_this(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &ScopedOperand,
|
|
|
|
|
this_value: &ScopedOperand,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(key) = generator.try_constant_string_to_property_key(property) {
|
2026-03-01 09:19:43 -03:00
|
|
|
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(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-01 09:19:43 -03:00
|
|
|
});
|
|
|
|
|
} 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(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-01 09:19:43 -03:00
|
|
|
});
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::GetByValueWithThis {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a normal property write by value, optimizing constant string properties to PutNormalById.
|
|
|
|
|
fn emit_put_normal_by_value(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &ScopedOperand,
|
|
|
|
|
src: &ScopedOperand,
|
|
|
|
|
base_identifier: Option<IdentifierTableIndex>,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(key) = generator.try_constant_string_to_property_key(property) {
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: src.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
src: src.operand(),
|
|
|
|
|
base_identifier,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a normal property write by value with explicit this, optimizing constant string properties.
|
|
|
|
|
fn emit_put_normal_by_value_with_this(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &ScopedOperand,
|
|
|
|
|
this_value: &ScopedOperand,
|
|
|
|
|
src: &ScopedOperand,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(key) = generator.try_constant_string_to_property_key(property) {
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByIdWithThis {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: src.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValueWithThis {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
src: src.operand(),
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum PutKind {
|
|
|
|
|
Own,
|
|
|
|
|
Getter,
|
|
|
|
|
Setter,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a property write by value, optimizing constant string properties to the ById variant.
|
|
|
|
|
fn emit_put_by_value(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &ScopedOperand,
|
|
|
|
|
src: &ScopedOperand,
|
|
|
|
|
kind: PutKind,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(key) = generator.try_constant_string_to_property_key(property) {
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-02-23 07:50:46 -03:00
|
|
|
match kind {
|
|
|
|
|
PutKind::Own => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: src.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PutKind::Getter => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: src.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 1,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PutKind::Setter => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: src.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 2,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
match kind {
|
|
|
|
|
PutKind::Own => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
src: src.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PutKind::Getter => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
src: src.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 1,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
PutKind::Setter => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: property.operand(),
|
|
|
|
|
src: src.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 2,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
/// Emit a ThrowIfTDZ check for a local identifier if needed. This is used
|
|
|
|
|
/// before assigning to a
|
2026-03-04 10:02:52 -03:00
|
|
|
/// variable to ensure TDZ semantics for let/const bindings.
|
|
|
|
|
fn emit_tdz_check_if_needed(generator: &mut Generator, ident: &Identifier) {
|
|
|
|
|
if !ident.is_local() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let local_index = ident.local_index.get();
|
|
|
|
|
let needs_tdz_check = if ident.local_type.get() == Some(LocalType::Argument) {
|
|
|
|
|
!generator.is_argument_initialized(local_index)
|
|
|
|
|
} else {
|
|
|
|
|
generator.is_local_lexically_declared(local_index)
|
|
|
|
|
&& !generator.is_local_initialized(local_index)
|
|
|
|
|
};
|
|
|
|
|
if needs_tdz_check {
|
|
|
|
|
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
|
|
|
|
|
if ident.local_type.get() == Some(LocalType::Argument) {
|
|
|
|
|
let empty = generator.add_constant_empty();
|
|
|
|
|
generator.emit_mov(&local, &empty);
|
|
|
|
|
}
|
|
|
|
|
generator.emit(Instruction::ThrowIfTDZ {
|
|
|
|
|
src: local.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_set_variable(generator: &mut Generator, ident: &Identifier, value: &ScopedOperand) {
|
2026-02-23 07:50:46 -03:00
|
|
|
if ident.is_local() {
|
|
|
|
|
if ident.declaration_kind.get() == Some(DeclarationKind::Const) {
|
2026-03-03 18:22:35 -03:00
|
|
|
// The caller is responsible for emitting ThrowIfTDZ before calling
|
2026-03-20 00:26:33 -03:00
|
|
|
// emit_set_variable().
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowConstAssignment {});
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let local_index = ident.local_index.get();
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.resolve_local(local_index, ident.local_type.get().unwrap());
|
2026-03-20 00:26:33 -03:00
|
|
|
// Skip self-move entirely.
|
2026-03-01 13:32:52 -03:00
|
|
|
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;
|
|
|
|
|
}
|
2026-03-03 18:22:35 -03:00
|
|
|
// No TDZ check here: the caller is responsible for checking TDZ
|
2026-03-20 00:26:33 -03:00
|
|
|
// before calling emit_set_variable().
|
2026-03-01 13:32:52 -03:00
|
|
|
generator.emit(Instruction::Mov {
|
|
|
|
|
dst: local.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if ident.is_global.get() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
let cache = generator.next_global_variable_cache();
|
|
|
|
|
generator.emit(Instruction::SetGlobal {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// Non-local, non-global: use SetLexicalBinding which searches
|
|
|
|
|
// the lexical environment chain (important for with-statement support).
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::SetLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn emit_put_to_member(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &ScopedOperand,
|
|
|
|
|
property: &Expression,
|
|
|
|
|
computed: bool,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
base_object: Option<&Expression>,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let base_id = base_object.and_then(|obj| intern_base_identifier(generator, obj));
|
2026-02-23 07:50:46 -03:00
|
|
|
if computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property = generate_expression_or_undefined(property, generator, None);
|
|
|
|
|
emit_put_normal_by_value(generator, base, &property, value, base_id);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(&ident.name);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: base_id,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::PutPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit bytecode for `delete <expression>`.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_delete_reference(generator: &mut Generator, operand: &Expression) -> ScopedOperand {
|
2026-02-23 07:50:46 -03:00
|
|
|
match &operand.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => {
|
|
|
|
|
if ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
return generator.add_constant_boolean(false);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = generator.allocate_register();
|
|
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::DeleteVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
});
|
|
|
|
|
dst
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
2026-02-23 07:50:46 -03:00
|
|
|
// https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
|
|
|
|
|
// Deleting a super property is always a ReferenceError.
|
2026-03-22 15:14:03 -03:00
|
|
|
if matches!(data.object.inner, ExpressionKind::Super) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-01 09:18:25 -03:00
|
|
|
// Evaluate computed property for side effects before throwing.
|
|
|
|
|
// Per spec, property key evaluation precedes ResolveSuperBase.
|
2026-03-22 15:14:03 -03:00
|
|
|
let _computed_key = if data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-03-01 09:18:25 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let super_base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: super_base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let exception = generator.allocate_register();
|
|
|
|
|
let error_string =
|
|
|
|
|
generator.intern_string(utf16!("Can't delete a property on 'super'"));
|
|
|
|
|
generator.emit(Instruction::NewReferenceError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: exception.operand(),
|
|
|
|
|
error_string,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.perform_needed_unwinds();
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: exception.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let dead_block = generator.make_block();
|
|
|
|
|
generator.switch_to_basic_block(dead_block);
|
2026-03-01 09:18:25 -03:00
|
|
|
let _ = (this_value, _computed_key);
|
2026-02-24 08:36:14 -03:00
|
|
|
return generator.add_constant_undefined();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
let base = generate_expression_or_undefined(&data.object, generator, None);
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = generator.allocate_register();
|
2026-03-22 15:14:03 -03:00
|
|
|
if data.computed {
|
|
|
|
|
let key = generate_expression_or_undefined(&data.property, generator, None);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::DeleteByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: key.operand(),
|
|
|
|
|
});
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(property_ident) = &data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(&property_ident.name);
|
|
|
|
|
generator.emit(Instruction::DeleteById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
base: base.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
return generator.add_constant_boolean(true);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
dst
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// delete on non-reference: evaluate for side effects, return true
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(operand, generator, None);
|
|
|
|
|
generator.add_constant_boolean(true)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pre-evaluated reference operands for deferred store.
|
|
|
|
|
/// Used when the spec requires evaluating the assignment target reference
|
|
|
|
|
/// before performing some other operation (like iterating a spread element).
|
|
|
|
|
enum EvaluatedReference {
|
|
|
|
|
Member {
|
|
|
|
|
base: ScopedOperand,
|
|
|
|
|
property: ScopedOperand,
|
|
|
|
|
base_identifier: Option<IdentifierTableIndex>,
|
|
|
|
|
},
|
|
|
|
|
MemberId {
|
|
|
|
|
base: ScopedOperand,
|
|
|
|
|
property: PropertyKeyTableIndex,
|
|
|
|
|
cache: u32,
|
|
|
|
|
base_identifier: Option<IdentifierTableIndex>,
|
|
|
|
|
},
|
|
|
|
|
PrivateMember {
|
|
|
|
|
base: ScopedOperand,
|
|
|
|
|
property: IdentifierTableIndex,
|
|
|
|
|
},
|
|
|
|
|
SuperMember {
|
|
|
|
|
base: ScopedOperand,
|
|
|
|
|
property: ScopedOperand,
|
|
|
|
|
this_value: ScopedOperand,
|
|
|
|
|
},
|
|
|
|
|
SuperMemberId {
|
|
|
|
|
base: ScopedOperand,
|
|
|
|
|
property: PropertyKeyTableIndex,
|
|
|
|
|
cache: u32,
|
|
|
|
|
this_value: ScopedOperand,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Evaluate a member expression target to get pre-computed reference operands
|
|
|
|
|
/// without performing a load. This implements the "Let lref be ? Evaluation of
|
|
|
|
|
/// DestructuringAssignmentTarget" step from the spec.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_evaluate_member_reference(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
target: &Expression,
|
|
|
|
|
) -> EvaluatedReference {
|
2026-03-22 15:14:03 -03:00
|
|
|
if let ExpressionKind::Member(member_data) = &target.inner {
|
|
|
|
|
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_super {
|
2026-03-20 00:26:33 -03:00
|
|
|
// ResolveThisBinding first, then ResolveSuperBase.
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-03 18:26:42 -03:00
|
|
|
let base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
|
|
|
|
dst: base.operand(),
|
|
|
|
|
});
|
2026-03-22 15:14:03 -03:00
|
|
|
if member_data.computed {
|
|
|
|
|
let property =
|
|
|
|
|
generate_expression_or_undefined(&member_data.property, generator, None);
|
2026-03-03 18:24:24 -03:00
|
|
|
// If the computed property is a constant string (e.g. super["minutes"]),
|
2026-03-20 00:26:33 -03:00
|
|
|
// optimize to SuperMemberId.
|
2026-03-03 18:24:24 -03:00
|
|
|
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,
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(&ident.name);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-02-24 06:40:18 -03:00
|
|
|
EvaluatedReference::SuperMemberId {
|
|
|
|
|
base,
|
|
|
|
|
property: key,
|
|
|
|
|
cache,
|
|
|
|
|
this_value,
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
unreachable!("non-computed super member property must be an identifier")
|
|
|
|
|
}
|
2026-03-03 18:26:42 -03:00
|
|
|
} else {
|
2026-03-22 15:14:03 -03:00
|
|
|
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);
|
2026-03-03 18:26:42 -03:00
|
|
|
// If the computed property is a constant string (e.g. obj["key"]),
|
2026-03-20 00:26:33 -03:00
|
|
|
// optimize to MemberId.
|
2026-03-03 18:26:42 -03:00
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
2026-03-03 18:26:42 -03:00
|
|
|
let key = generator.intern_property_key(&ident.name);
|
2026-03-03 18:24:24 -03:00
|
|
|
let cache = generator.next_property_lookup_cache();
|
|
|
|
|
EvaluatedReference::MemberId {
|
|
|
|
|
base,
|
|
|
|
|
property: key,
|
|
|
|
|
cache,
|
|
|
|
|
base_identifier: None,
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) =
|
|
|
|
|
&member_data.property.inner
|
|
|
|
|
{
|
2026-03-03 18:26:42 -03:00
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
EvaluatedReference::PrivateMember { base, property: id }
|
2026-03-03 18:24:24 -03:00
|
|
|
} else {
|
2026-03-03 18:26:42 -03:00
|
|
|
unreachable!(
|
|
|
|
|
"non-computed member property must be an identifier or private identifier"
|
|
|
|
|
)
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
unreachable!("emit_evaluate_member_reference called on non-member expression")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Store a value to a pre-evaluated reference.
|
2026-02-24 06:40:18 -03:00
|
|
|
fn emit_store_to_evaluated_reference(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
reference: &EvaluatedReference,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
) {
|
2026-02-23 07:50:46 -03:00
|
|
|
match reference {
|
2026-02-24 06:40:18 -03:00
|
|
|
EvaluatedReference::Member {
|
|
|
|
|
base,
|
|
|
|
|
property,
|
|
|
|
|
base_identifier,
|
|
|
|
|
} => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_normal_by_value(generator, base, property, value, *base_identifier);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
EvaluatedReference::MemberId {
|
|
|
|
|
base,
|
|
|
|
|
property,
|
|
|
|
|
cache,
|
|
|
|
|
base_identifier,
|
|
|
|
|
} => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: *property,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: *cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: *base_identifier,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
EvaluatedReference::PrivateMember { base, property } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::PutPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
property: *property,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
EvaluatedReference::SuperMember {
|
|
|
|
|
base,
|
|
|
|
|
property,
|
|
|
|
|
this_value,
|
|
|
|
|
} => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_normal_by_value_with_this(generator, base, property, this_value, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
EvaluatedReference::SuperMemberId {
|
|
|
|
|
base,
|
|
|
|
|
property,
|
|
|
|
|
cache,
|
|
|
|
|
this_value,
|
|
|
|
|
} => {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByIdWithThis {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: base.operand(),
|
|
|
|
|
this_value: this_value.operand(),
|
|
|
|
|
property: *property,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: *cache as u64,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 0,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_store_to_reference(generator: &mut Generator, target: &Expression, value: &ScopedOperand) {
|
2026-02-23 07:50:46 -03:00
|
|
|
match &target.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_set_variable(generator, ident, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
|
|
|
|
if matches!(data.object.inner, ExpressionKind::Super) {
|
2026-03-20 00:26:33 -03:00
|
|
|
// ResolveThisBinding first, then ResolveSuperBase.
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-03 18:26:42 -03:00
|
|
|
let base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
|
|
|
|
dst: base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_super_put(
|
|
|
|
|
generator,
|
|
|
|
|
&base,
|
2026-03-22 15:14:03 -03:00
|
|
|
&data.property,
|
|
|
|
|
data.computed,
|
2026-02-24 08:36:14 -03:00
|
|
|
&this_value,
|
|
|
|
|
value,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-03-22 15:14:03 -03:00
|
|
|
let base = generate_expression_or_undefined(&data.object, generator, None);
|
|
|
|
|
emit_put_to_member(generator, &base, &data.property, data.computed, value, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// Evaluate the expression for side effects, then throw ReferenceError.
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(target, generator, None);
|
|
|
|
|
emit_invalid_lhs_error(generator);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit the conditional jump for a logical assignment (&&=, ||=, ??=).
|
2026-02-24 06:40:18 -03:00
|
|
|
fn emit_logical_jump(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
op: AssignmentOp,
|
|
|
|
|
condition: &ScopedOperand,
|
|
|
|
|
rhs_block: Label,
|
|
|
|
|
lhs_block: Label,
|
|
|
|
|
) {
|
2026-02-23 07:50:46 -03:00
|
|
|
match op {
|
|
|
|
|
AssignmentOp::AndAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(condition, rhs_block, lhs_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
AssignmentOp::OrAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(condition, lhs_block, rhs_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
AssignmentOp::NullishAssignment => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::JumpNullish {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: condition.operand(),
|
|
|
|
|
true_target: rhs_block,
|
|
|
|
|
false_target: lhs_block,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("only logical assignment ops are passed to emit_logical_jump"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn emit_compound_assignment(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: AssignmentOp,
|
|
|
|
|
dst: &ScopedOperand,
|
|
|
|
|
lhs: &ScopedOperand,
|
|
|
|
|
rhs: &ScopedOperand,
|
|
|
|
|
) {
|
|
|
|
|
let dst_op = dst.operand();
|
|
|
|
|
let lhs_op = lhs.operand();
|
|
|
|
|
let rhs_op = rhs.operand();
|
|
|
|
|
match op {
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::AdditionAssignment => generator.emit(Instruction::Add {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::SubtractionAssignment => generator.emit(Instruction::Sub {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::MultiplicationAssignment => generator.emit(Instruction::Mul {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::DivisionAssignment => generator.emit(Instruction::Div {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::ModuloAssignment => generator.emit(Instruction::Mod {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::ExponentiationAssignment => generator.emit(Instruction::Exp {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::BitwiseAndAssignment => generator.emit(Instruction::BitwiseAnd {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::BitwiseOrAssignment => generator.emit(Instruction::BitwiseOr {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::BitwiseXorAssignment => generator.emit(Instruction::BitwiseXor {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::LeftShiftAssignment => generator.emit(Instruction::LeftShift {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::RightShiftAssignment => generator.emit(Instruction::RightShift {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
|
|
|
|
}),
|
2026-02-24 08:36:14 -03:00
|
|
|
AssignmentOp::UnsignedRightShiftAssignment => {
|
|
|
|
|
generator.emit(Instruction::UnsignedRightShift {
|
|
|
|
|
dst: dst_op,
|
|
|
|
|
lhs: lhs_op,
|
|
|
|
|
rhs: rhs_op,
|
2026-02-27 05:54:44 -03:00
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
AssignmentOp::AndAssignment
|
|
|
|
|
| AssignmentOp::OrAssignment
|
|
|
|
|
| AssignmentOp::NullishAssignment => {
|
2026-02-23 07:50:46 -03:00
|
|
|
unreachable!("logical assignment in compound path")
|
|
|
|
|
}
|
|
|
|
|
AssignmentOp::Assignment => unreachable!("plain assignment in compound path"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Template literal
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_template_literal(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &TemplateLiteralData,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// The parser stores ALL parts (string segments AND interpolated expressions)
|
|
|
|
|
// in data.expressions. raw_strings is only populated for tagged templates.
|
|
|
|
|
|
|
|
|
|
// OPTIMIZATION: Filter out empty string segments.
|
2026-02-24 06:40:18 -03:00
|
|
|
let segments: Vec<&Expression> = data
|
|
|
|
|
.expressions
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|e| !matches!(&e.inner, ExpressionKind::StringLiteral(s) if s.is_empty()))
|
|
|
|
|
.collect();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if segments.is_empty() {
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_string(Utf16String::new()));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst before generating expressions.
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if segments.len() == 1 {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression(segments[0], generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
// If it's a constant, return directly.
|
|
|
|
|
if val.operand().is_constant() {
|
|
|
|
|
return Some(val);
|
|
|
|
|
}
|
|
|
|
|
// Otherwise, emit ToString.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ToString {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: val.operand(),
|
|
|
|
|
});
|
|
|
|
|
return Some(dst);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (index, expression) in segments.iter().enumerate() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(expression, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if index == 0 {
|
|
|
|
|
if matches!(&expression.inner, ExpressionKind::StringLiteral(_)) {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&dst, &val);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ToString {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
value: val.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ConcatString {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: val.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Some(dst)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Tagged template literal
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_tagged_template_literal(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
tag: &Expression,
|
|
|
|
|
template_literal: &Expression,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-23 07:50:46 -03:00
|
|
|
// Resolve tag and this_value based on the tag expression type.
|
|
|
|
|
let (tag_reg, this_value) = match &tag.inner {
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(member_data)
|
|
|
|
|
if matches!(member_data.object.inner, ExpressionKind::Super) =>
|
|
|
|
|
{
|
2026-02-23 07:50:46 -03:00
|
|
|
// super.func`` or super["func"]``
|
2026-03-01 09:18:53 -03:00
|
|
|
// Per spec, evaluation order: ResolveThisBinding, evaluate
|
|
|
|
|
// computed property, then ResolveSuperBase.
|
2026-02-24 08:36:14 -03:00
|
|
|
let this_value = emit_resolve_this_binding(generator);
|
2026-03-22 15:14:03 -03:00
|
|
|
let computed_key = if member_data.computed {
|
|
|
|
|
Some(generate_expression_or_undefined(
|
|
|
|
|
&member_data.property,
|
|
|
|
|
generator,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-03-01 09:18:53 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let super_base = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::ResolveSuperBase {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: super_base.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let method = generator.allocate_register();
|
2026-03-01 09:18:53 -03:00
|
|
|
if let Some(key) = computed_key {
|
|
|
|
|
emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
2026-03-01 09:19:43 -03:00
|
|
|
emit_get_by_id_with_this(generator, &method, &super_base, &ident.name, &this_value);
|
2026-03-01 09:18:53 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
(method, Some(this_value))
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(member_data) => {
|
|
|
|
|
let obj = generate_expression_or_undefined(&member_data.object, generator, None);
|
2026-02-24 08:36:14 -03:00
|
|
|
let method = generator.allocate_register();
|
2026-03-22 15:14:03 -03:00
|
|
|
if member_data.computed {
|
|
|
|
|
let property =
|
|
|
|
|
generate_expression_or_undefined(&member_data.property, generator, None);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value(generator, &method, &obj, &property, None);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
|
|
|
|
let base_id = intern_base_identifier(generator, &member_data.object);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &method, &obj, &ident.name, base_id);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(priv_ident) =
|
|
|
|
|
&member_data.property.inner
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&priv_ident.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: method.operand(),
|
|
|
|
|
base: obj.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
(method, Some(obj))
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::Identifier(ident) if ident.is_local() || ident.is_global.get() => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let tag_val = generate_expression_or_undefined(tag, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(tag_val, None)
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::Identifier(ident) => {
|
|
|
|
|
// Non-local, non-global identifier: use GetCalleeAndThisFromEnvironment
|
|
|
|
|
// to properly handle with-statement bindings.
|
2026-02-24 08:36:14 -03:00
|
|
|
let callee_reg = generator.allocate_register();
|
|
|
|
|
let this_reg = generator.allocate_register();
|
|
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::GetCalleeAndThisFromEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
callee: callee_reg.operand(),
|
|
|
|
|
this_value: this_reg.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
(callee_reg, Some(this_reg))
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let tag_val = generate_expression_or_undefined(tag, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
(tag_val, None)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Build template strings for GetTemplateObject.
|
|
|
|
|
// expressions has alternating: string_0, expression_0, string_1, expression_1, ..., string_n
|
2026-02-27 06:03:16 -03:00
|
|
|
let ExpressionKind::TemplateLiteral(data) = &template_literal.inner else {
|
|
|
|
|
unreachable!("TaggedTemplateLiteral template must be TemplateLiteral");
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Collect cooked strings (even indices). NullLiteral means invalid escape → undefined.
|
|
|
|
|
let mut string_regs = Vec::new();
|
|
|
|
|
for i in (0..data.expressions.len()).step_by(2) {
|
|
|
|
|
if matches!(&data.expressions[i].inner, ExpressionKind::NullLiteral) {
|
2026-02-24 08:36:14 -03:00
|
|
|
string_regs.push(generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&data.expressions[i], generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
string_regs.push(val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append raw strings.
|
|
|
|
|
for raw in &data.raw_strings {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generator.add_constant_string(raw.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
string_regs.push(val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Emit GetTemplateObject.
|
2026-02-24 08:36:14 -03:00
|
|
|
let strings_array = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
let string_ops: Vec<Operand> = string_regs.iter().map(|s| s.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache_index = generator.next_template_object_cache();
|
|
|
|
|
generator.emit(Instruction::GetTemplateObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: strings_array.operand(),
|
|
|
|
|
strings_count: u32_from_usize(string_ops.len()),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache_index as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
strings: string_ops,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Build arguments: [template_object, ...interpolated_expressions]
|
|
|
|
|
let mut argument_regs = vec![strings_array];
|
|
|
|
|
for i in (1..data.expressions.len()).step_by(2) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&data.expressions[i], generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
argument_regs.push(val);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
|
|
|
|
let this_op = this_value.unwrap_or_else(|| generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
let arguments: Vec<Operand> = argument_regs.iter().map(|a| a.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
callee: tag_reg.operand(),
|
|
|
|
|
this_value: this_op.operand(),
|
|
|
|
|
argument_count: u32_from_usize(arguments.len()),
|
|
|
|
|
expression_string: None,
|
|
|
|
|
arguments,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Switch statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_switch_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &SwitchStatementData,
|
|
|
|
|
_preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let discriminant = generate_expression(&data.discriminant, generator, None)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Block declaration instantiation: create lexical environment for
|
|
|
|
|
// function declarations and let/const across all switch cases.
|
2026-02-24 08:36:14 -03:00
|
|
|
let did_create_env = emit_switch_block_declaration_instantiation(generator, data);
|
2026-03-01 12:56:44 -03:00
|
|
|
if did_create_env {
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Create first test block and jump to it.
|
2026-02-24 08:36:14 -03:00
|
|
|
let first_test_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: first_test_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Pre-allocate test blocks for each case with a test expression.
|
|
|
|
|
let mut test_blocks: Vec<Label> = Vec::with_capacity(data.cases.len());
|
|
|
|
|
for case in &data.cases {
|
|
|
|
|
if case.test.is_some() {
|
2026-02-24 08:36:14 -03:00
|
|
|
test_blocks.push(generator.make_block());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Emit comparison chain: for each case, create the case body block,
|
|
|
|
|
// switch to the test block, evaluate the test, and emit comparison.
|
2026-03-20 00:26:33 -03:00
|
|
|
// Test blocks are interleaved with case body blocks.
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut next_test_block = first_test_block;
|
|
|
|
|
let mut case_blocks: Vec<Label> = Vec::with_capacity(data.cases.len());
|
|
|
|
|
let mut default_block = None;
|
|
|
|
|
let mut test_block_index = 0;
|
|
|
|
|
|
|
|
|
|
for case in &data.cases {
|
2026-02-24 08:36:14 -03:00
|
|
|
let case_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(test) = &case.test {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(next_test_block);
|
|
|
|
|
let test_val = generate_expression(test, generator, None)?;
|
|
|
|
|
let cmp = generator.allocate_register();
|
2026-03-20 00:26:33 -03:00
|
|
|
// NB: test_value is LHS, discriminant is RHS.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::StrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: cmp.operand(),
|
|
|
|
|
lhs: test_val.operand(),
|
|
|
|
|
rhs: discriminant.operand(),
|
|
|
|
|
});
|
|
|
|
|
next_test_block = test_blocks[test_block_index];
|
|
|
|
|
test_block_index += 1;
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&cmp, case_block, next_test_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
default_block = Some(case_block);
|
|
|
|
|
}
|
|
|
|
|
case_blocks.push(case_block);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Switch to the last test block and create end block.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(next_test_block);
|
|
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Jump to default case or end block.
|
|
|
|
|
let fallthrough_target = default_block.unwrap_or(end_block);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: fallthrough_target,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.begin_breakable_scope(end_block, labels, completion.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Emit case bodies (fall-through by default).
|
|
|
|
|
for (i, case) in data.cases.iter().enumerate() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(case_blocks[i]);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_completion = generator.current_completion_register.clone();
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(ref c) = completion {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = Some(c.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let case_scope = case.scope.borrow();
|
|
|
|
|
for child in &case_scope.children {
|
|
|
|
|
// For function declarations in switch cases: emit AnnexB hoisting
|
|
|
|
|
// only if the scope collector approved it (name is in annexb_function_names).
|
2026-02-24 08:36:14 -03:00
|
|
|
if did_create_env
|
|
|
|
|
&& let StatementKind::FunctionDeclaration {
|
2026-02-24 06:40:18 -03:00
|
|
|
name: Some(ref name_ident),
|
|
|
|
|
..
|
|
|
|
|
} = child.inner
|
2026-02-24 08:36:14 -03:00
|
|
|
&& generator.annexb_function_names.contains(&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(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let result = generate_statement(child, generator, None);
|
|
|
|
|
if generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
break;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.must_propagate_completion
|
|
|
|
|
&& let (Some(c), Some(val)) = (&completion, &result)
|
|
|
|
|
{
|
|
|
|
|
generator.emit_mov(c, val);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_completion_register = saved_completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Fall through to next case
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() && i + 1 < case_blocks.len() {
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: case_blocks[i + 1],
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
} else if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if did_create_env {
|
2026-03-01 12:56:44 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create block declaration instantiation for switch statements.
|
|
|
|
|
/// Function declarations and let/const declarations across all cases
|
|
|
|
|
/// share a single lexical environment.
|
|
|
|
|
fn emit_switch_block_declaration_instantiation(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &SwitchStatementData,
|
|
|
|
|
) -> bool {
|
|
|
|
|
// Collect all statements across all cases.
|
|
|
|
|
let case_scopes: Vec<_> = data.cases.iter().map(|c| c.scope.borrow()).collect();
|
2026-02-24 06:40:18 -03:00
|
|
|
let all_children: Vec<&Statement> = case_scopes
|
|
|
|
|
.iter()
|
2026-02-23 07:50:46 -03:00
|
|
|
.flat_map(|scope| scope.children.iter())
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// Check if we need a lexical environment.
|
|
|
|
|
// Only needed if there are non-local lexical declarations.
|
|
|
|
|
let needs_env = all_children.iter().any(|child| match &child.inner {
|
|
|
|
|
StatementKind::FunctionDeclaration { .. } => true,
|
|
|
|
|
StatementKind::VariableDeclaration { kind, declarations } => {
|
|
|
|
|
if *kind == DeclarationKind::Let || *kind == DeclarationKind::Const {
|
|
|
|
|
declarations.iter().any(|declaration| {
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
|
|
|
|
!names.is_empty()
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
StatementKind::ClassDeclaration(class_data) => {
|
|
|
|
|
class_data.name.as_ref().is_some_and(|n| !n.is_local())
|
|
|
|
|
}
|
|
|
|
|
_ => false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if !needs_env {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let new_env = generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_lexical_declarations_for_block(generator, &new_env, all_children.iter().copied());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Object expression
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for an object literal expression.
|
|
|
|
|
///
|
|
|
|
|
/// Objects whose shape can be determined at compile time (only simple
|
|
|
|
|
/// key-value properties with identifier or non-numeric string keys)
|
|
|
|
|
/// get shape caching for faster allocation.
|
|
|
|
|
fn generate_object_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
properties: &[ObjectProperty],
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Determine if this is a simple object literal (all KeyValue with non-computed
|
|
|
|
|
// string/identifier keys that are not numeric indices). Simple literals can
|
|
|
|
|
// benefit from shape caching. Numeric string keys like "0" are stored in
|
|
|
|
|
// indexed storage rather than shape-based storage, so they can't use the fast path.
|
|
|
|
|
//
|
2026-03-20 00:26:33 -03:00
|
|
|
// NB: The parser treats {["x"]: 1} identically to {"x": 1} at the AST level
|
2026-02-23 07:50:46 -03:00
|
|
|
// (both produce a StringLiteral key with is_computed=false). The parser keeps
|
|
|
|
|
// is_computed=true for bracket-enclosed keys. We normalize here by treating
|
|
|
|
|
// StringLiteral keys as non-computed regardless of is_computed.
|
2026-02-24 06:40:18 -03:00
|
|
|
let is_simple = !properties.is_empty()
|
|
|
|
|
&& properties.iter().all(|p| {
|
|
|
|
|
if p.property_type != ObjectPropertyType::KeyValue {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
match &p.key.inner {
|
|
|
|
|
ExpressionKind::Identifier(_) if !p.is_computed => true,
|
|
|
|
|
ExpressionKind::StringLiteral(s) => !is_numeric_index_key(s),
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let cache_index = if is_simple {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.next_object_shape_cache()
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
u32::MAX
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache_index as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if properties.is_empty() {
|
2026-02-27 06:18:31 -03:00
|
|
|
return dst;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (slot, property) in properties.iter().enumerate() {
|
|
|
|
|
if property.property_type == ObjectPropertyType::Spread {
|
|
|
|
|
// For spread, the source expression is in `key`, not `value`.
|
2026-02-24 08:36:14 -03:00
|
|
|
let src = generate_expression_or_undefined(&property.key, generator, None);
|
|
|
|
|
generator.emit(Instruction::PutBySpread {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: dst.operand(),
|
|
|
|
|
src: src.operand(),
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For non-string keys (computed, numeric, etc.), evaluate key before value
|
2026-03-20 00:26:33 -03:00
|
|
|
// (spec evaluation order). All non-StringLiteral keys are treated the same:
|
2026-02-23 07:50:46 -03:00
|
|
|
// generate key → ToPrimitiveWithStringHint → generate value → PutByValue.
|
|
|
|
|
//
|
|
|
|
|
// NB: StringLiteral keys are always treated as non-computed (see is_simple comment).
|
|
|
|
|
let is_string_literal_key = matches!(&property.key.inner, ExpressionKind::StringLiteral(_));
|
2026-02-24 06:40:18 -03:00
|
|
|
let is_string_key =
|
|
|
|
|
is_string_literal_key || matches!(&property.key.inner, ExpressionKind::Identifier(_));
|
2026-02-23 07:50:46 -03:00
|
|
|
let effectively_computed = property.is_computed && !is_string_literal_key;
|
|
|
|
|
let computed_key = if effectively_computed || !is_string_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generate_expression_or_undefined(&property.key, generator, None);
|
2026-03-08 07:47:16 -03:00
|
|
|
let key = generator.copy_if_needed_to_preserve_evaluation_order(&key);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ToPrimitiveWithStringHint {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: key.operand(),
|
|
|
|
|
value: key.operand(),
|
|
|
|
|
});
|
|
|
|
|
Some(key)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Set pending LHS name for function name inference on non-computed properties.
|
|
|
|
|
// ProtoSetter (__proto__) skips NamedEvaluation per spec.
|
|
|
|
|
if !effectively_computed && property.property_type != ObjectPropertyType::ProtoSetter {
|
|
|
|
|
let base_name: Option<Utf16String> = match &property.key.inner {
|
2026-03-22 14:37:12 -03:00
|
|
|
ExpressionKind::StringLiteral(s) => Some((**s).clone()),
|
2026-02-23 07:50:46 -03:00
|
|
|
ExpressionKind::Identifier(ident) => Some(ident.name.clone()),
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(name) = base_name {
|
|
|
|
|
let full_name: Utf16String = match property.property_type {
|
|
|
|
|
ObjectPropertyType::Getter => {
|
|
|
|
|
let mut prefixed = Utf16String(utf16!("get ").to_vec());
|
|
|
|
|
prefixed.0.extend_from_slice(&name);
|
|
|
|
|
prefixed
|
|
|
|
|
}
|
|
|
|
|
ObjectPropertyType::Setter => {
|
|
|
|
|
let mut prefixed = Utf16String(utf16!("set ").to_vec());
|
|
|
|
|
prefixed.0.extend_from_slice(&name);
|
|
|
|
|
prefixed
|
|
|
|
|
}
|
|
|
|
|
_ => name,
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(&full_name));
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// Methods, getters, and setters need the object as their [[HomeObject]]
|
|
|
|
|
// so that super property lookups work.
|
|
|
|
|
let is_method_like = property.is_method
|
|
|
|
|
|| property.property_type == ObjectPropertyType::Getter
|
|
|
|
|
|| property.property_type == ObjectPropertyType::Setter;
|
|
|
|
|
if is_method_like {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.home_objects.push(dst.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
let value = property
|
|
|
|
|
.value
|
|
|
|
|
.as_ref()
|
2026-02-24 08:36:14 -03:00
|
|
|
.and_then(|v| generate_expression(v, generator, None))
|
|
|
|
|
.unwrap_or_else(|| generator.add_constant_undefined());
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_method_like {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.home_objects.pop();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
match property.property_type {
|
2026-02-24 06:40:18 -03:00
|
|
|
ObjectPropertyType::Spread => {
|
|
|
|
|
unreachable!("spread properties are handled before this point")
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
ObjectPropertyType::KeyValue => {
|
|
|
|
|
if let Some(key_val) = &computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Own);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else if is_simple {
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_object_property_set_by_key(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&dst,
|
|
|
|
|
&property.key,
|
|
|
|
|
&value,
|
|
|
|
|
u32_from_usize(slot),
|
|
|
|
|
cache_index,
|
|
|
|
|
false,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
// Non-simple object: use PutOwnById instead of InitObjectLiteralProperty
|
|
|
|
|
let property_key = match &property.key.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::Identifier(ident) => {
|
|
|
|
|
generator.intern_property_key(&ident.name)
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::StringLiteral(s) => generator.intern_property_key(s),
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => {
|
2026-02-24 06:40:18 -03:00
|
|
|
emit_object_property_set_by_key(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&dst,
|
|
|
|
|
&property.key,
|
|
|
|
|
&value,
|
|
|
|
|
u32_from_usize(slot),
|
|
|
|
|
cache_index,
|
|
|
|
|
false,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: dst.operand(),
|
|
|
|
|
property: property_key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ObjectPropertyType::Getter => {
|
|
|
|
|
if let Some(key_val) = &computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Getter);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_object_accessor_by_key(
|
|
|
|
|
generator,
|
|
|
|
|
&dst,
|
|
|
|
|
&property.key,
|
|
|
|
|
&value,
|
|
|
|
|
true,
|
|
|
|
|
false,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ObjectPropertyType::Setter => {
|
|
|
|
|
if let Some(key_val) = &computed_key {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_put_by_value(generator, &dst, key_val, &value, PutKind::Setter);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_object_accessor_by_key(
|
|
|
|
|
generator,
|
|
|
|
|
&dst,
|
|
|
|
|
&property.key,
|
|
|
|
|
&value,
|
|
|
|
|
false,
|
|
|
|
|
false,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ObjectPropertyType::ProtoSetter => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key = generator.intern_property_key(utf16!("__proto__"));
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: dst.operand(),
|
|
|
|
|
property: key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 3,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if is_simple {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CacheObjectShape {
|
2026-02-23 07:50:46 -03:00
|
|
|
object: dst.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache_index as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a property set for an object literal key (static or computed).
|
|
|
|
|
fn emit_object_property_set_by_key(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
object: &ScopedOperand,
|
|
|
|
|
key: &Expression,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
slot: u32,
|
|
|
|
|
cache_index: u32,
|
|
|
|
|
is_computed: bool,
|
|
|
|
|
) {
|
|
|
|
|
if is_computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key_val = generate_expression_or_undefined(key, generator, None);
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: key_val.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
match &key.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property_key = generator.intern_property_key(&ident.name);
|
|
|
|
|
generator.emit(Instruction::InitObjectLiteralProperty {
|
2026-02-23 07:50:46 -03:00
|
|
|
object: object.operand(),
|
|
|
|
|
property: property_key,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
shape_cache_index: cache_index,
|
|
|
|
|
property_slot: slot,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::StringLiteral(s) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property_key = generator.intern_property_key(s);
|
|
|
|
|
generator.emit(Instruction::InitObjectLiteralProperty {
|
2026-02-23 07:50:46 -03:00
|
|
|
object: object.operand(),
|
|
|
|
|
property: property_key,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
shape_cache_index: cache_index,
|
|
|
|
|
property_slot: slot,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key_val = generator.add_constant_number(*n);
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: key_val.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
// Computed key
|
2026-02-24 08:36:14 -03:00
|
|
|
let key_val = generate_expression_or_undefined(key, generator, None);
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: key_val.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 4,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Emit a getter/setter for an object literal key.
|
|
|
|
|
fn emit_object_accessor_by_key(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
object: &ScopedOperand,
|
|
|
|
|
key: &Expression,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
is_getter: bool,
|
|
|
|
|
is_computed: bool,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let emit_by_id = |generator: &mut Generator, name: &[u16]| {
|
|
|
|
|
let property_key = generator.intern_property_key(name);
|
|
|
|
|
let cache = generator.next_property_lookup_cache();
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_getter {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: property_key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 1,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutById {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: property_key,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 2,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let emit_by_value = |generator: &mut Generator, key: &Expression| {
|
|
|
|
|
let key_val = generate_expression_or_undefined(key, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_getter {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: key_val.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 1,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-03-04 06:33:38 -03:00
|
|
|
generator.emit(Instruction::PutByValue {
|
2026-02-23 07:50:46 -03:00
|
|
|
base: object.operand(),
|
|
|
|
|
property: key_val.operand(),
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
base_identifier: None,
|
2026-03-04 06:33:38 -03:00
|
|
|
kind: 2,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if is_computed {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_by_value(generator, key);
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match &key.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
ExpressionKind::Identifier(ident) => emit_by_id(generator, &ident.name),
|
|
|
|
|
ExpressionKind::StringLiteral(s) => emit_by_id(generator, s),
|
|
|
|
|
_ => emit_by_value(generator, key),
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Optional chain
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate an optional chain, writing results into pre-allocated current_value
|
|
|
|
|
/// and current_base registers.
|
|
|
|
|
fn generate_optional_chain_inner(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
base: &Expression,
|
|
|
|
|
references: &[OptionalChainReference],
|
|
|
|
|
current_value: &ScopedOperand,
|
|
|
|
|
current_base: &ScopedOperand,
|
|
|
|
|
) -> Option<()> {
|
|
|
|
|
// Evaluate base expression.
|
|
|
|
|
let new_current_value = match &base.inner {
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(member_data) => {
|
|
|
|
|
let is_super = matches!(member_data.object.inner, ExpressionKind::Super);
|
2026-02-23 07:50:46 -03:00
|
|
|
// For super property access, resolve this binding first (before
|
2026-03-20 00:26:33 -03:00
|
|
|
// ResolveSuperBase) per spec evaluation order.
|
2026-02-24 06:40:18 -03:00
|
|
|
let this_value = if is_super {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(emit_resolve_this_binding(generator))
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-03-22 15:14:03 -03:00
|
|
|
let obj = generate_expression(&member_data.object, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if is_super {
|
|
|
|
|
let this_value = this_value.unwrap();
|
2026-03-22 15:14:03 -03:00
|
|
|
emit_super_get(
|
|
|
|
|
generator,
|
|
|
|
|
&val,
|
|
|
|
|
&obj,
|
|
|
|
|
&member_data.property,
|
|
|
|
|
member_data.computed,
|
|
|
|
|
&this_value,
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_base, &this_value);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if member_data.computed {
|
|
|
|
|
let property = generate_expression(&member_data.property, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value(generator, &val, &obj, &property, None);
|
|
|
|
|
generator.emit_mov(current_base, &obj);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::Identifier(ident) = &member_data.property.inner {
|
|
|
|
|
let base_id = intern_base_identifier(generator, &member_data.object);
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &val, &obj, &ident.name, base_id);
|
|
|
|
|
generator.emit_mov(current_base, &obj);
|
2026-03-22 15:14:03 -03:00
|
|
|
} else if let ExpressionKind::PrivateIdentifier(name) = &member_data.property.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: val.operand(),
|
|
|
|
|
base: obj.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_base, &obj);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-03-22 15:14:03 -03:00
|
|
|
let property = generate_expression(&member_data.property, generator, None)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value(generator, &val, &obj, &property, None);
|
|
|
|
|
generator.emit_mov(current_base, &obj);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
val
|
|
|
|
|
}
|
2026-03-22 15:16:49 -03:00
|
|
|
ExpressionKind::OptionalChain(oc_data) => {
|
2026-02-24 06:40:18 -03:00
|
|
|
generate_optional_chain_inner(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-03-22 15:16:49 -03:00
|
|
|
&oc_data.base,
|
|
|
|
|
&oc_data.references,
|
2026-02-24 06:40:18 -03:00
|
|
|
current_value,
|
|
|
|
|
current_base,
|
|
|
|
|
)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
current_value.clone()
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
_ => generate_expression(base, generator, None)?,
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_value, &new_current_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Create shared blocks: load_undefined_block is reused for all optional
|
2026-03-20 00:26:33 -03:00
|
|
|
// short-circuits.
|
2026-02-24 08:36:14 -03:00
|
|
|
let load_undefined_block = generator.make_block();
|
|
|
|
|
let end_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
for reference in references {
|
|
|
|
|
let is_optional = match reference {
|
|
|
|
|
OptionalChainReference::Call { mode, .. }
|
|
|
|
|
| OptionalChainReference::ComputedReference { mode, .. }
|
|
|
|
|
| OptionalChainReference::MemberReference { mode, .. }
|
|
|
|
|
| OptionalChainReference::PrivateMemberReference { mode, .. } => {
|
|
|
|
|
*mode == OptionalChainMode::Optional
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if is_optional {
|
2026-02-24 08:36:14 -03:00
|
|
|
let not_nullish_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpNullish {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: current_value.operand(),
|
|
|
|
|
true_target: load_undefined_block,
|
|
|
|
|
false_target: not_nullish_block,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(not_nullish_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match reference {
|
|
|
|
|
OptionalChainReference::MemberReference { identifier, .. } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_base, current_value);
|
|
|
|
|
emit_get_by_id(
|
|
|
|
|
generator,
|
|
|
|
|
current_value,
|
|
|
|
|
current_value,
|
|
|
|
|
&identifier.name,
|
|
|
|
|
None,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
OptionalChainReference::ComputedReference { expression, .. } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_base, current_value);
|
|
|
|
|
let property = generate_expression(expression, generator, None)?;
|
|
|
|
|
emit_get_by_value(generator, current_value, current_value, &property, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
OptionalChainReference::Call { arguments, .. } => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let arguments_array = generate_arguments_array(generator, arguments);
|
|
|
|
|
generator.emit(Instruction::CallWithArgumentArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: current_value.operand(),
|
|
|
|
|
callee: current_value.operand(),
|
|
|
|
|
this_value: current_base.operand(),
|
|
|
|
|
arguments: arguments_array.operand(),
|
|
|
|
|
expression_string: None,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(current_base, &undef);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
OptionalChainReference::PrivateMemberReference {
|
|
|
|
|
private_identifier, ..
|
|
|
|
|
} => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(current_base, current_value);
|
|
|
|
|
let id = generator.intern_identifier(&private_identifier.name);
|
|
|
|
|
generator.emit(Instruction::GetPrivateById {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: current_value.operand(),
|
|
|
|
|
base: current_value.operand(),
|
|
|
|
|
property: id,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(load_undefined_block);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(current_value, &undef);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
Some(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
/// Convert arguments to an array for CallWithArgumentArray.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn generate_arguments_array(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
arguments: &[CallArgument],
|
|
|
|
|
) -> ScopedOperand {
|
|
|
|
|
let dst = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if arguments.is_empty() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
element_count: 0,
|
|
|
|
|
elements: vec![],
|
|
|
|
|
});
|
|
|
|
|
return dst;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let first_spread = arguments
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|a| a.is_spread)
|
|
|
|
|
.unwrap_or(arguments.len());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let mut arg_holders = Vec::with_capacity(first_spread);
|
|
|
|
|
for argument in &arguments[..first_spread] {
|
2026-02-24 08:36:14 -03:00
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
generator.emit_mov(®, &val);
|
2026-02-23 07:50:46 -03:00
|
|
|
arg_holders.push(reg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let arg_ops: Vec<Operand> = arg_holders.iter().map(|a| a.operand()).collect();
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
element_count: u32_from_usize(arg_ops.len()),
|
|
|
|
|
elements: arg_ops,
|
|
|
|
|
});
|
2026-03-20 00:26:33 -03:00
|
|
|
// NB: arg_holders stays alive until function return so their registers
|
|
|
|
|
// aren't reused during the spread arguments loop.
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
for argument in &arguments[first_spread..] {
|
2026-02-24 08:36:14 -03:00
|
|
|
let val = generate_expression_or_undefined(&argument.value, generator, None);
|
|
|
|
|
generator.emit(Instruction::ArrayAppend {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
src: val.operand(),
|
|
|
|
|
is_spread: argument.is_spread,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 18:25:40 -03:00
|
|
|
drop(arg_holders);
|
2026-02-23 07:50:46 -03:00
|
|
|
dst
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Class expression
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for a class expression or declaration.
|
|
|
|
|
///
|
|
|
|
|
/// Creates a ClassBlueprint via FFI containing the constructor SFD,
|
|
|
|
|
/// class elements (methods, fields, accessors, static initializers),
|
|
|
|
|
/// and then emits a NewClass instruction that creates the class at runtime.
|
|
|
|
|
fn generate_class_expression(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &ClassData,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
2026-02-27 06:18:31 -03:00
|
|
|
) -> ScopedOperand {
|
2026-02-23 07:50:46 -03:00
|
|
|
let has_super = data.super_class.is_some();
|
2026-03-03 18:23:43 -03:00
|
|
|
// Always consume pending_lhs_name. Named classes don't use it, but we
|
|
|
|
|
// must clear it to prevent it from leaking to nested expressions.
|
2026-02-24 06:40:18 -03:00
|
|
|
let lhs_name = if data.name.is_none() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name.take()
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
2026-03-03 18:23:43 -03:00
|
|
|
generator.pending_lhs_name = None;
|
2026-02-24 06:40:18 -03:00
|
|
|
None
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Step 2: Save parent environment, create class lexical environment.
|
2026-02-24 08:36:14 -03:00
|
|
|
let parent_env = generator.current_lexical_environment();
|
|
|
|
|
let class_env = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::CreateLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: class_env.operand(),
|
|
|
|
|
parent: parent_env.operand(),
|
|
|
|
|
capacity: 0,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator
|
|
|
|
|
.lexical_environment_register_stack
|
2026-02-24 06:40:18 -03:00
|
|
|
.push(class_env.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Step 3.a: Create binding for the class name in the class environment.
|
|
|
|
|
// Only emit when the class has a name, or when there's no lhs_name
|
2026-03-20 00:26:33 -03:00
|
|
|
// (skip this for anonymous classes with lhs_name).
|
2026-02-23 07:50:46 -03:00
|
|
|
if data.name.is_some() || lhs_name.is_none() {
|
|
|
|
|
let name = if let Some(name_ident) = &data.name {
|
|
|
|
|
name_ident.name.clone()
|
|
|
|
|
} else {
|
|
|
|
|
Utf16String::new()
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
let name_id = generator.intern_identifier(&name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: name_id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: true,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Evaluate super class if present
|
|
|
|
|
let super_class = if let Some(super_expression) = &data.super_class {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_expression(super_expression, generator, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create private environment for private class elements.
|
|
|
|
|
let mut has_private_env = false;
|
|
|
|
|
for element_node in &data.elements {
|
|
|
|
|
let priv_name = match &element_node.inner {
|
|
|
|
|
ClassElement::Method { key, .. } | ClassElement::Field { key, .. } => {
|
|
|
|
|
if let ExpressionKind::PrivateIdentifier(ident) = &key.inner {
|
|
|
|
|
Some(ident.name.clone())
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClassElement::StaticInitializer { .. } => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(name) = priv_name {
|
|
|
|
|
if !has_private_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreatePrivateEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
has_private_env = true;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let name_id = generator.intern_identifier(&name);
|
|
|
|
|
generator.emit(Instruction::AddPrivateName { name: name_id });
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First pass: evaluate all computed property keys.
|
|
|
|
|
// This must happen before registering the constructor and method SFDs,
|
2026-03-20 00:26:33 -03:00
|
|
|
// using a two-pass structure.
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut element_keys: Vec<Option<ScopedOperand>> = Vec::with_capacity(data.elements.len());
|
|
|
|
|
for element_node in &data.elements {
|
|
|
|
|
match &element_node.inner {
|
|
|
|
|
ClassElement::Method { key, .. } => {
|
|
|
|
|
if !is_private_key(key) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key_val = generate_expression(key, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
element_keys.push(key_val);
|
|
|
|
|
} else {
|
|
|
|
|
element_keys.push(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClassElement::Field { key, .. } => {
|
|
|
|
|
if !is_private_key(key) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let key_val = generate_expression(key, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
element_keys.push(key_val);
|
|
|
|
|
} else {
|
|
|
|
|
element_keys.push(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClassElement::StaticInitializer { .. } => {
|
|
|
|
|
element_keys.push(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create SharedFunctionInstanceData for constructor
|
|
|
|
|
let constructor_sfd_index = if let Some(ctor_expression) = &data.constructor {
|
|
|
|
|
// Explicit constructor — extract FunctionData from the expression
|
|
|
|
|
if let ExpressionKind::Function(function_id) = &ctor_expression.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let function_data = generator.function_table.take(*function_id);
|
|
|
|
|
emit_new_function(generator, function_data, None)
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
// Fallback: synthesize a default constructor
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_default_constructor(generator, has_super)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// No explicit constructor — synthesize a default one
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_default_constructor(generator, has_super)
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Second pass: register method/field SFDs and build element descriptors.
|
|
|
|
|
let mut ffi_elements = Vec::with_capacity(data.elements.len());
|
|
|
|
|
// Keep literal string data alive until FFI call.
|
|
|
|
|
let mut literal_string_storage: Vec<Utf16String> = Vec::new();
|
|
|
|
|
|
|
|
|
|
for element_node in &data.elements {
|
|
|
|
|
match &element_node.inner {
|
|
|
|
|
ClassElement::Method {
|
|
|
|
|
key,
|
|
|
|
|
function,
|
|
|
|
|
kind,
|
|
|
|
|
is_static,
|
|
|
|
|
} => {
|
|
|
|
|
let ffi_kind = match kind {
|
|
|
|
|
ClassMethodKind::Method => ClassElementKind::Method as u8,
|
|
|
|
|
ClassMethodKind::Getter => ClassElementKind::Getter as u8,
|
|
|
|
|
ClassMethodKind::Setter => ClassElementKind::Setter as u8,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Create SFD for the method function.
|
|
|
|
|
// Don't set the method name here — the runtime's update_function_name
|
|
|
|
|
// in construct_class sets it from the evaluated property key, which
|
|
|
|
|
// correctly handles computed keys (Symbols, etc).
|
|
|
|
|
let sfd_index = if let ExpressionKind::Function(function_id) = &function.inner {
|
2026-02-24 08:36:14 -03:00
|
|
|
let function_data = generator.function_table.take(*function_id);
|
|
|
|
|
super::ffi::FFIOptionalU32::some(emit_new_function(
|
|
|
|
|
generator,
|
|
|
|
|
function_data,
|
|
|
|
|
None,
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
super::ffi::FFIOptionalU32::none()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Handle computed vs static keys
|
|
|
|
|
let is_private = is_private_key(key);
|
|
|
|
|
|
|
|
|
|
// Point directly into the AST's PrivateIdentifier name (stable address).
|
|
|
|
|
let (priv_ptr, priv_len) = get_private_identifier_ptr(key);
|
|
|
|
|
|
|
|
|
|
ffi_elements.push(super::ffi::FFIClassElement {
|
2026-03-04 10:02:52 -03:00
|
|
|
kind: ffi_kind,
|
2026-02-23 07:50:46 -03:00
|
|
|
is_static: *is_static,
|
|
|
|
|
is_private,
|
|
|
|
|
private_identifier: priv_ptr,
|
|
|
|
|
private_identifier_len: priv_len,
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
has_initializer: false,
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind: LiteralValueKind::None,
|
2026-02-23 07:50:46 -03:00
|
|
|
literal_value_number: 0.0,
|
|
|
|
|
literal_value_string: std::ptr::null(),
|
|
|
|
|
literal_value_string_len: 0,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ClassElement::Field {
|
|
|
|
|
key,
|
|
|
|
|
initializer,
|
|
|
|
|
is_static,
|
|
|
|
|
} => {
|
|
|
|
|
// Detect literal initializers and store the value directly,
|
|
|
|
|
// avoiding function creation for simple cases like x = 0.
|
2026-03-20 00:26:33 -03:00
|
|
|
// This avoids function creation for simple cases.
|
2026-03-18 14:29:57 -03:00
|
|
|
let mut literal_value_kind = LiteralValueKind::None;
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut literal_value_number: f64 = 0.0;
|
|
|
|
|
let mut literal_value_string = Utf16String::new();
|
|
|
|
|
let mut sfd_index = super::ffi::FFIOptionalU32::none();
|
|
|
|
|
|
|
|
|
|
if let Some(init_expression) = initializer {
|
|
|
|
|
let is_literal = match &init_expression.inner {
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => {
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind = LiteralValueKind::Number;
|
2026-02-23 07:50:46 -03:00
|
|
|
literal_value_number = *n;
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::BooleanLiteral(b) => {
|
|
|
|
|
literal_value_kind = if *b {
|
2026-03-18 14:29:57 -03:00
|
|
|
LiteralValueKind::BooleanTrue
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-03-18 14:29:57 -03:00
|
|
|
LiteralValueKind::BooleanFalse
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::NullLiteral => {
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind = LiteralValueKind::Null;
|
2026-02-23 07:50:46 -03:00
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::StringLiteral(s) => {
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind = LiteralValueKind::String;
|
2026-03-22 14:37:12 -03:00
|
|
|
literal_value_string = (**s).clone();
|
2026-02-23 07:50:46 -03:00
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::Unary { op, operand } if *op == UnaryOp::Minus => {
|
|
|
|
|
if let ExpressionKind::NumericLiteral(n) = &operand.inner {
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind = LiteralValueKind::Number;
|
2026-02-23 07:50:46 -03:00
|
|
|
literal_value_number = -n;
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if !is_literal {
|
|
|
|
|
// Determine field name for anonymous function naming.
|
|
|
|
|
let field_name = match &key.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => ident.name.clone(),
|
2026-03-22 14:37:12 -03:00
|
|
|
ExpressionKind::StringLiteral(s) => (**s).clone(),
|
2026-02-23 07:50:46 -03:00
|
|
|
ExpressionKind::PrivateIdentifier(p) => p.name.clone(),
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n),
|
|
|
|
|
ExpressionKind::BigIntLiteral(s) => {
|
2026-03-22 14:38:20 -03:00
|
|
|
let digits = s.strip_suffix('n').unwrap_or(s.as_str());
|
2026-02-23 07:50:46 -03:00
|
|
|
Utf16String(digits.encode_utf16().collect())
|
|
|
|
|
}
|
|
|
|
|
_ => Utf16String::new(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Wrap the expression in a ClassFieldInitializer statement.
|
|
|
|
|
let body_statement = Statement::new(
|
|
|
|
|
init_expression.range,
|
|
|
|
|
StatementKind::ClassFieldInitializer {
|
|
|
|
|
expression: Box::new(init_expression.as_ref().clone()),
|
|
|
|
|
field_name,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let wrapper_body = Statement::new(
|
|
|
|
|
init_expression.range,
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::Block(ScopeData::shared_with_children(vec![
|
|
|
|
|
body_statement,
|
|
|
|
|
])),
|
2026-02-23 07:50:46 -03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Class bodies are always strict mode.
|
|
|
|
|
let function_data = Box::new(FunctionData {
|
|
|
|
|
name: None,
|
|
|
|
|
source_text_start: init_expression.range.start.offset,
|
|
|
|
|
source_text_end: init_expression.range.end.offset,
|
|
|
|
|
body: Box::new(wrapper_body),
|
|
|
|
|
parameters: Vec::new(),
|
|
|
|
|
function_length: 0,
|
|
|
|
|
kind: FunctionKind::Normal,
|
|
|
|
|
is_strict_mode: true,
|
|
|
|
|
is_arrow_function: false,
|
|
|
|
|
parsing_insights: FunctionParsingInsights {
|
|
|
|
|
uses_this: true,
|
|
|
|
|
uses_this_from_environment: true,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let index =
|
|
|
|
|
emit_new_function(generator, function_data, Some(utf16!("field")));
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Set class_field_initializer_name on the SFD.
|
2026-02-24 08:36:14 -03:00
|
|
|
let sfd_ptr = generator.shared_function_data[index as usize];
|
2026-02-23 07:50:46 -03:00
|
|
|
let key_is_private = is_private_key(key);
|
|
|
|
|
let key_name: Utf16String = match &key.inner {
|
|
|
|
|
ExpressionKind::PrivateIdentifier(ident) => ident.name.clone(),
|
|
|
|
|
ExpressionKind::Identifier(ident) => ident.name.clone(),
|
2026-03-22 14:37:12 -03:00
|
|
|
ExpressionKind::StringLiteral(s) => (**s).clone(),
|
2026-02-24 06:40:18 -03:00
|
|
|
ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n),
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => Utf16String::new(),
|
|
|
|
|
};
|
|
|
|
|
if !key_name.is_empty() {
|
|
|
|
|
unsafe {
|
|
|
|
|
super::ffi::rust_sfd_set_class_field_initializer_name(
|
|
|
|
|
sfd_ptr,
|
|
|
|
|
key_name.as_ptr(),
|
|
|
|
|
key_name.len(),
|
|
|
|
|
key_is_private,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sfd_index = super::ffi::FFIOptionalU32::some(index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let is_private = is_private_key(key);
|
|
|
|
|
|
|
|
|
|
let (priv_ptr, priv_len) = get_private_identifier_ptr(key);
|
|
|
|
|
|
|
|
|
|
// Keep literal string data alive until FFI call.
|
|
|
|
|
let (str_ptr, str_len) = if !literal_value_string.is_empty() {
|
|
|
|
|
literal_string_storage.push(literal_value_string);
|
2026-02-24 06:40:18 -03:00
|
|
|
let s = literal_string_storage
|
|
|
|
|
.last()
|
|
|
|
|
.expect("just pushed an element");
|
2026-02-23 07:50:46 -03:00
|
|
|
(s.as_ptr(), s.len())
|
|
|
|
|
} else {
|
|
|
|
|
(std::ptr::null(), 0)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ffi_elements.push(super::ffi::FFIClassElement {
|
2026-03-04 10:02:52 -03:00
|
|
|
kind: ClassElementKind::Field as u8,
|
2026-02-23 07:50:46 -03:00
|
|
|
is_static: *is_static,
|
|
|
|
|
is_private,
|
|
|
|
|
private_identifier: priv_ptr,
|
|
|
|
|
private_identifier_len: priv_len,
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
has_initializer: initializer.is_some(),
|
|
|
|
|
literal_value_kind,
|
|
|
|
|
literal_value_number,
|
|
|
|
|
literal_value_string: str_ptr,
|
|
|
|
|
literal_value_string_len: str_len,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
ClassElement::StaticInitializer { body } => {
|
|
|
|
|
// Wrap the static block body in a function.
|
|
|
|
|
// Class bodies are always strict mode.
|
|
|
|
|
let function_data = Box::new(FunctionData {
|
|
|
|
|
name: None,
|
|
|
|
|
source_text_start: body.range.start.offset,
|
|
|
|
|
source_text_end: body.range.end.offset,
|
|
|
|
|
body: body.clone(),
|
|
|
|
|
parameters: Vec::new(),
|
|
|
|
|
function_length: 0,
|
|
|
|
|
kind: FunctionKind::Normal,
|
|
|
|
|
is_strict_mode: true,
|
|
|
|
|
is_arrow_function: false,
|
|
|
|
|
parsing_insights: FunctionParsingInsights {
|
|
|
|
|
uses_this: true,
|
|
|
|
|
uses_this_from_environment: true,
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let sfd_index = super::ffi::FFIOptionalU32::some(emit_new_function(
|
|
|
|
|
generator,
|
|
|
|
|
function_data,
|
|
|
|
|
None,
|
|
|
|
|
));
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
ffi_elements.push(super::ffi::FFIClassElement {
|
2026-03-04 10:02:52 -03:00
|
|
|
kind: ClassElementKind::StaticInitializer as u8,
|
2026-02-23 07:50:46 -03:00
|
|
|
is_static: true,
|
|
|
|
|
is_private: false,
|
|
|
|
|
private_identifier: std::ptr::null(),
|
|
|
|
|
private_identifier_len: 0,
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
has_initializer: false,
|
2026-03-18 14:29:57 -03:00
|
|
|
literal_value_kind: LiteralValueKind::None,
|
2026-02-23 07:50:46 -03:00
|
|
|
literal_value_number: 0.0,
|
|
|
|
|
literal_value_string: std::ptr::null(),
|
|
|
|
|
literal_value_string_len: 0,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get class name and source text
|
|
|
|
|
let class_name: Option<&[u16]> = data.name.as_ref().map(|n| &*n.name);
|
|
|
|
|
let has_name = data.name.is_some();
|
|
|
|
|
let (name_ptr, name_len) = class_name
|
|
|
|
|
.map(|n| (n.as_ptr(), n.len()))
|
|
|
|
|
.unwrap_or((std::ptr::null(), 0));
|
|
|
|
|
|
|
|
|
|
let source_start = data.source_text_start as usize;
|
|
|
|
|
let source_end = data.source_text_end as usize;
|
|
|
|
|
let source_text_len = source_end - source_start;
|
|
|
|
|
|
|
|
|
|
// Create the ClassBlueprint via FFI
|
|
|
|
|
let bp_ptr = unsafe {
|
|
|
|
|
super::ffi::rust_create_class_blueprint(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.vm_ptr,
|
|
|
|
|
generator.source_code_ptr,
|
2026-02-23 07:50:46 -03:00
|
|
|
name_ptr,
|
|
|
|
|
name_len,
|
|
|
|
|
source_start,
|
|
|
|
|
source_text_len,
|
|
|
|
|
constructor_sfd_index,
|
|
|
|
|
has_super,
|
|
|
|
|
has_name,
|
|
|
|
|
ffi_elements.as_ptr(),
|
|
|
|
|
ffi_elements.len(),
|
|
|
|
|
)
|
|
|
|
|
};
|
2026-02-24 06:40:18 -03:00
|
|
|
assert!(
|
|
|
|
|
!bp_ptr.is_null(),
|
|
|
|
|
"rust_create_class_blueprint returned null"
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
let blueprint_index = generator.register_class_blueprint(bp_ptr);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Build element_keys operands for the NewClass instruction
|
|
|
|
|
let element_key_ops: Vec<Option<Operand>> = element_keys
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|k| k.as_ref().map(|s| s.operand()))
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
// Restore parent environment before emitting NewClass.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent_env.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Allocate dst after element keys.
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = choose_dst(generator, preferred_dst);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Emit NewClass instruction
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::NewClass {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
super_class: super_class.as_ref().map(|s| s.operand()),
|
|
|
|
|
class_environment: class_env.operand(),
|
|
|
|
|
class_blueprint_index: blueprint_index,
|
|
|
|
|
lhs_name,
|
|
|
|
|
element_keys_count: u32_from_usize(element_key_ops.len()),
|
|
|
|
|
element_keys: element_key_ops,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if has_private_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::LeavePrivateEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:18:31 -03:00
|
|
|
dst
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Synthesize a default constructor SharedFunctionInstanceData.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn emit_default_constructor(generator: &mut Generator, has_super: bool) -> u32 {
|
2026-02-23 07:50:46 -03:00
|
|
|
use crate::parser::{Parser, ProgramType};
|
|
|
|
|
|
|
|
|
|
// Wrap in "function" keyword so it parses as a FunctionDeclaration.
|
|
|
|
|
let source: Utf16String = if has_super {
|
2026-02-24 06:40:18 -03:00
|
|
|
Utf16String::from(utf16!(
|
|
|
|
|
"function constructor(...arguments) { super(...arguments); }"
|
|
|
|
|
))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
Utf16String::from(utf16!("function constructor() {}"))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new(&source, ProgramType::Script);
|
|
|
|
|
if has_super {
|
|
|
|
|
parser.flags.allow_super_constructor_call = true;
|
|
|
|
|
}
|
|
|
|
|
let program = parser.parse_program(false);
|
|
|
|
|
parser.scope_collector.analyze(false);
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
assert!(!parser.has_errors(), "default constructor parse failed");
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Extract FunctionData from the parsed program.
|
|
|
|
|
let function_id = if let StatementKind::Program(ref data) = program.inner {
|
|
|
|
|
let scope = data.scope.borrow();
|
|
|
|
|
scope.children.iter().find_map(|child| {
|
|
|
|
|
if let StatementKind::FunctionDeclaration { function_id, .. } = &child.inner {
|
|
|
|
|
Some(*function_id)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let function_id = function_id.expect("default constructor: no FunctionDeclaration found");
|
|
|
|
|
let mut function_data = parser.function_table.take(function_id);
|
|
|
|
|
|
|
|
|
|
// Zero out source text range since this is synthetic source,
|
|
|
|
|
// not part of the original source code buffer.
|
|
|
|
|
function_data.source_text_start = 0;
|
|
|
|
|
function_data.source_text_end = 0;
|
|
|
|
|
|
|
|
|
|
let subtable = parser.function_table.extract_reachable(&function_data);
|
|
|
|
|
let sfd_ptr = unsafe {
|
|
|
|
|
super::ffi::create_shared_function_data(
|
|
|
|
|
function_data,
|
|
|
|
|
subtable,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.vm_ptr,
|
|
|
|
|
generator.source_code_ptr,
|
|
|
|
|
generator.strict,
|
2026-02-23 07:50:46 -03:00
|
|
|
None,
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
assert!(
|
|
|
|
|
!sfd_ptr.is_null(),
|
|
|
|
|
"default constructor creation returned null"
|
|
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.register_shared_function_data(sfd_ptr)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a key expression is a private identifier, return (is_private, private_name).
|
|
|
|
|
fn is_private_key(key: &Expression) -> bool {
|
|
|
|
|
matches!(&key.inner, ExpressionKind::PrivateIdentifier(_))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a pointer directly into the AST's PrivateIdentifier name.
|
|
|
|
|
/// The pointer remains valid as long as the AST is alive.
|
|
|
|
|
fn get_private_identifier_ptr(key: &Expression) -> (*const u16, usize) {
|
|
|
|
|
if let ExpressionKind::PrivateIdentifier(ident) = &key.inner {
|
|
|
|
|
(ident.name.as_ptr(), ident.name.len())
|
|
|
|
|
} else {
|
|
|
|
|
(std::ptr::null(), 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a for-in/for-of LHS is a `let`/`const` declaration with non-local identifiers,
|
|
|
|
|
/// meaning we need a per-iteration lexical environment.
|
|
|
|
|
fn for_in_of_needs_lexical_env(lhs: &ForInOfLhs) -> bool {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ForInOfLhs::Declaration(statement) = lhs
|
|
|
|
|
&& let StatementKind::VariableDeclaration { kind, declarations } = &statement.inner
|
|
|
|
|
&& (*kind == DeclarationKind::Let || *kind == DeclarationKind::Const)
|
|
|
|
|
{
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
return !names.is_empty();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Collect all non-local binding names from a variable declarator target.
|
|
|
|
|
fn collect_target_names(target: &VariableDeclaratorTarget, names: &mut Vec<(Utf16String, bool)>) {
|
|
|
|
|
match target {
|
|
|
|
|
VariableDeclaratorTarget::Identifier(ident) => {
|
|
|
|
|
if !ident.is_local() {
|
|
|
|
|
names.push((ident.name.clone(), false));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
VariableDeclaratorTarget::BindingPattern(pattern) => {
|
|
|
|
|
collect_pattern_binding_names(pattern, names);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Collect all non-local binding names from a binding pattern (recursive).
|
|
|
|
|
fn collect_pattern_binding_names(pattern: &BindingPattern, names: &mut Vec<(Utf16String, bool)>) {
|
|
|
|
|
for entry in &pattern.entries {
|
|
|
|
|
match &entry.alias {
|
|
|
|
|
Some(BindingEntryAlias::Identifier(ident)) => {
|
|
|
|
|
if !ident.is_local() {
|
|
|
|
|
names.push((ident.name.clone(), false));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::BindingPattern(sub)) => {
|
|
|
|
|
collect_pattern_binding_names(sub, names);
|
|
|
|
|
}
|
|
|
|
|
None => {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(BindingEntryName::Identifier(ident)) = &entry.name
|
|
|
|
|
&& !ident.is_local()
|
|
|
|
|
{
|
|
|
|
|
names.push((ident.name.clone(), false));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::MemberExpression(_)) => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a per-iteration lexical environment for for-in/for-of `let`/`const` declarations.
|
|
|
|
|
/// Returns the parent environment register so we can restore it later.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn create_for_in_of_lexical_env(generator: &mut Generator, lhs: &ForInOfLhs) -> ScopedOperand {
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Collect all binding names to determine capacity.
|
|
|
|
|
let mut binding_names: Vec<(Utf16String, bool)> = Vec::new();
|
|
|
|
|
let mut is_constant = false;
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ForInOfLhs::Declaration(statement) = lhs
|
|
|
|
|
&& let StatementKind::VariableDeclaration { kind, declarations } = &statement.inner
|
|
|
|
|
{
|
|
|
|
|
is_constant = *kind == DeclarationKind::Const;
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
collect_target_names(&declaration.target, &mut binding_names);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Create variable bindings in the new environment.
|
|
|
|
|
for (name, _) in &binding_names {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: is_constant,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: is_constant,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// For-in statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Create a TDZ environment for lexical declarations in for-in/for-of heads.
|
|
|
|
|
/// Returns true if a TDZ scope was entered (must call leave_for_in_of_head_tdz after RHS eval).
|
2026-02-24 08:36:14 -03:00
|
|
|
fn enter_for_in_of_head_tdz(generator: &mut Generator, lhs: &ForInOfLhs) -> bool {
|
|
|
|
|
if let ForInOfLhs::Declaration(statement) = lhs
|
|
|
|
|
&& let StatementKind::VariableDeclaration { kind, declarations } = &statement.inner
|
|
|
|
|
&& (*kind == DeclarationKind::Let || *kind == DeclarationKind::Const)
|
|
|
|
|
{
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
|
|
|
|
}
|
|
|
|
|
if !names.is_empty() {
|
|
|
|
|
generator.push_new_lexical_environment(0);
|
|
|
|
|
for (name, _) in &names {
|
|
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
|
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
return true;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tear down the TDZ environment after RHS evaluation.
|
2026-02-24 08:36:14 -03:00
|
|
|
fn leave_for_in_of_head_tdz(generator: &mut Generator) {
|
|
|
|
|
generator.lexical_environment_register_stack.pop();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
let parent = generator.current_lexical_environment();
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_for_in_of_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
kind: ForInOfKind,
|
|
|
|
|
lhs: &ForInOfLhs,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
match kind {
|
2026-02-24 08:36:14 -03:00
|
|
|
ForInOfKind::ForIn => generate_for_in_statement(generator, lhs, rhs, body, preferred_dst),
|
2026-02-24 06:40:18 -03:00
|
|
|
ForInOfKind::ForOf => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_for_of_statement_inner(generator, lhs, rhs, body, preferred_dst, false)
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
|
|
|
|
ForInOfKind::ForAwaitOf => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_for_of_statement_inner(generator, lhs, rhs, body, preferred_dst, true)
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_for_in_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: &ForInOfLhs,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// B.3.5 Initializers in ForIn Statement Heads
|
|
|
|
|
// Evaluate the initializer for `for (var x = init in obj)` before the RHS.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let ForInOfLhs::Declaration(statement) = lhs
|
|
|
|
|
&& let StatementKind::VariableDeclaration {
|
2026-03-04 10:02:52 -03:00
|
|
|
kind: DeclarationKind::Var,
|
2026-02-24 06:40:18 -03:00
|
|
|
declarations,
|
|
|
|
|
} = &statement.inner
|
2026-02-24 08:36:14 -03:00
|
|
|
&& let Some(declaration) = declarations.first()
|
|
|
|
|
&& let (VariableDeclaratorTarget::Identifier(ident), Some(init)) =
|
|
|
|
|
(&declaration.target, &declaration.init)
|
|
|
|
|
{
|
|
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name));
|
|
|
|
|
let value = generate_expression_or_undefined(init, generator, None);
|
|
|
|
|
generator.pending_lhs_name = None;
|
|
|
|
|
emit_set_variable(generator, ident, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Create end_block and update_block first, then nullish_block and
|
|
|
|
|
// continuation_block during head evaluation.
|
2026-02-24 08:36:14 -03:00
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
let update_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
let needs_lexical_env = for_in_of_needs_lexical_env(lhs);
|
|
|
|
|
|
|
|
|
|
// B.3.5 Initializers in ForIn Statement Heads: evaluate initializer before RHS.
|
|
|
|
|
// Create TDZ for lexical declarations before evaluating the RHS expression.
|
2026-02-24 08:36:14 -03:00
|
|
|
let entered_tdz = enter_for_in_of_head_tdz(generator, lhs);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Evaluate RHS into `object`, allocate iterator registers, emit the
|
|
|
|
|
// null/undefined check + GetObjectPropertyIterator, then let `object`
|
|
|
|
|
// go out of scope so its register is freed before the loop body.
|
|
|
|
|
let (iterator_object, iterator_next_method, iterator_done) = {
|
|
|
|
|
let object = generate_expression_or_undefined(rhs, generator, None);
|
|
|
|
|
if entered_tdz {
|
|
|
|
|
leave_for_in_of_head_tdz(generator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let iterator_object = generator.allocate_register();
|
|
|
|
|
let iterator_next_method = generator.allocate_register();
|
|
|
|
|
let iterator_done = generator.allocate_register();
|
|
|
|
|
|
|
|
|
|
// Check for null/undefined
|
|
|
|
|
let nullish_block = generator.make_block();
|
|
|
|
|
let continue_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpNullish {
|
|
|
|
|
condition: object.operand(),
|
|
|
|
|
true_target: nullish_block,
|
|
|
|
|
false_target: continue_block,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
generator.switch_to_basic_block(nullish_block);
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
generator.switch_to_basic_block(continue_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Get property iterator
|
|
|
|
|
generator.emit(Instruction::GetObjectPropertyIterator {
|
|
|
|
|
dst_iterator_object: iterator_object.operand(),
|
|
|
|
|
dst_iterator_next: iterator_next_method.operand(),
|
|
|
|
|
dst_iterator_done: iterator_done.operand(),
|
|
|
|
|
object: object.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
(iterator_object, iterator_next_method, iterator_done)
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
// Body evaluation: completion, then jump to update block.
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: update_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update: get next value
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(update_block);
|
|
|
|
|
let next_value = generator.allocate_register();
|
|
|
|
|
let done = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::IteratorNextUnpack {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst_value: next_value.operand(),
|
|
|
|
|
dst_done: done.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next_method.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let loop_continue_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&done, end_block, loop_continue_block);
|
|
|
|
|
generator.switch_to_basic_block(loop_continue_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Create per-iteration lexical environment for let/const declarations.
|
|
|
|
|
if needs_lexical_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
create_for_in_of_lexical_env(generator, lhs);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assign to LHS
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_to_for_in_of_lhs(generator, lhs, &next_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Body — break/continue handle environment restoration via LeaveLexicalEnvironment boundary.
|
2026-02-24 08:36:14 -03:00
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
|
|
|
|
generator.begin_continuable_scope(update_block, labels.clone(), completion.clone());
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels, completion.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
if needs_lexical_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-01 09:36:50 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
|
2026-03-01 09:36:50 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if needs_lexical_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_variable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
generator.end_continuable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-24 06:40:18 -03:00
|
|
|
target: update_block,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Labelled statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
fn generate_labelled_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
label: &Utf16String,
|
|
|
|
|
item: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
// Collect all labels from nested Labelled statements.
|
|
|
|
|
let mut labels = vec![label.clone()];
|
|
|
|
|
let mut inner = item;
|
2026-02-24 06:40:18 -03:00
|
|
|
while let StatementKind::Labelled {
|
|
|
|
|
label: next_label,
|
|
|
|
|
item: next_item,
|
|
|
|
|
} = &inner.inner
|
|
|
|
|
{
|
2026-02-23 07:50:46 -03:00
|
|
|
labels.push(next_label.clone());
|
|
|
|
|
inner = next_item;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// For iteration/switch statements, set pending_labels so that
|
|
|
|
|
// begin_breakable_scope/begin_continuable_scope pick them up.
|
|
|
|
|
// NB: The parser wraps for/for-in/for-of loops in a Block for scope
|
|
|
|
|
// management, so we look through single-child Block wrappers.
|
|
|
|
|
let block_scope_borrow;
|
|
|
|
|
let effective_inner = if let StatementKind::Block(ref scope) = inner.inner {
|
|
|
|
|
block_scope_borrow = scope.borrow();
|
|
|
|
|
if block_scope_borrow.children.len() == 1 {
|
|
|
|
|
&block_scope_borrow.children[0]
|
|
|
|
|
} else {
|
|
|
|
|
inner
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
inner
|
|
|
|
|
};
|
|
|
|
|
let is_iteration_or_switch = matches!(
|
|
|
|
|
&effective_inner.inner,
|
|
|
|
|
StatementKind::For { .. }
|
|
|
|
|
| StatementKind::ForInOf { .. }
|
|
|
|
|
| StatementKind::While { .. }
|
|
|
|
|
| StatementKind::DoWhile { .. }
|
|
|
|
|
| StatementKind::Switch(_)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if is_iteration_or_switch {
|
2026-02-24 08:36:14 -03:00
|
|
|
let previous_labels = std::mem::replace(&mut generator.pending_labels, labels);
|
|
|
|
|
let result = generate_statement(inner, generator, preferred_dst);
|
|
|
|
|
generator.pending_labels = previous_labels;
|
2026-02-23 07:50:46 -03:00
|
|
|
result
|
|
|
|
|
} else {
|
|
|
|
|
// Non-iteration: wrap in a breakable scope so `break label;` works.
|
2026-02-24 08:36:14 -03:00
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels, None);
|
|
|
|
|
let result = generate_statement(inner, generator, preferred_dst);
|
|
|
|
|
generator.end_breakable_scope();
|
|
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Jump { target: end_block });
|
|
|
|
|
}
|
|
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// For-of statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Shared implementation for for-of and for-await-of with iterator close.
|
|
|
|
|
fn generate_for_of_statement_inner(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: &ForInOfLhs,
|
|
|
|
|
rhs: &Expression,
|
|
|
|
|
body: &Statement,
|
|
|
|
|
preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
is_await: bool,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-03-20 00:26:33 -03:00
|
|
|
// Create end_block and update_block before evaluating the RHS expression.
|
|
|
|
|
// This ensures loop blocks get lower block numbers than any blocks created
|
|
|
|
|
// during RHS evaluation (e.g. conditional expressions).
|
2026-03-01 13:44:55 -03:00
|
|
|
let end_block = generator.make_block();
|
|
|
|
|
let update_block = generator.make_block();
|
|
|
|
|
|
2026-02-23 07:50:46 -03:00
|
|
|
// Create TDZ for lexical declarations before evaluating the RHS expression.
|
2026-02-24 08:36:14 -03:00
|
|
|
let entered_tdz = enter_for_in_of_head_tdz(generator, lhs);
|
2026-02-23 07:50:46 -03:00
|
|
|
let needs_lexical_env = for_in_of_needs_lexical_env(lhs);
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_handler = generator.current_unwind_handler;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Evaluate RHS into `object`, allocate iterator registers, and emit
|
|
|
|
|
// GetIterator. The block scopes `object` so its register is freed
|
|
|
|
|
// before the loop body.
|
|
|
|
|
let (iterator_object, iterator_next_method, iterator_done) = {
|
|
|
|
|
let object = generate_expression_or_undefined(rhs, generator, None);
|
|
|
|
|
if entered_tdz {
|
|
|
|
|
leave_for_in_of_head_tdz(generator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let iterator_object = generator.allocate_register();
|
|
|
|
|
let iterator_next_method = generator.allocate_register();
|
|
|
|
|
let iterator_done = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetIterator {
|
|
|
|
|
dst_iterator_object: iterator_object.operand(),
|
|
|
|
|
dst_iterator_next: iterator_next_method.operand(),
|
|
|
|
|
dst_iterator_done: iterator_done.operand(),
|
|
|
|
|
iterable: object.operand(),
|
|
|
|
|
hint: if is_await {
|
|
|
|
|
IteratorHint::Async
|
|
|
|
|
} else {
|
|
|
|
|
IteratorHint::Sync
|
|
|
|
|
} as u32,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
(iterator_object, iterator_next_method, iterator_done)
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion = generator.allocate_completion_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Set up iterator close via synthetic FinallyContext.
|
2026-02-24 08:36:14 -03:00
|
|
|
let close_completion_type = generator.allocate_register();
|
|
|
|
|
let close_completion_value = generator.allocate_register();
|
|
|
|
|
let exception_preamble_block = generator.make_block();
|
|
|
|
|
let iterator_close_body_block = generator.make_block();
|
|
|
|
|
let lexical_env_at_entry = generator.lexical_environment_register_stack.last().cloned();
|
|
|
|
|
|
|
|
|
|
let parent_index = generator.current_finally_context;
|
|
|
|
|
generator.push_finally_context(FinallyContext {
|
2026-02-23 07:50:46 -03:00
|
|
|
completion_type: close_completion_type.clone(),
|
|
|
|
|
completion_value: close_completion_value.clone(),
|
|
|
|
|
finally_body: iterator_close_body_block,
|
|
|
|
|
exception_preamble: exception_preamble_block,
|
|
|
|
|
parent_index,
|
|
|
|
|
registered_jumps: Vec::new(),
|
|
|
|
|
next_jump_index: FinallyContext::FIRST_JUMP_INDEX,
|
|
|
|
|
lexical_environment_at_entry: lexical_env_at_entry.clone(),
|
2026-03-01 09:47:30 -03:00
|
|
|
saved_unwind_handler: None,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Break scope wraps the ReturnToFinally so break hits ReturnToFinally first.
|
2026-02-24 08:36:14 -03:00
|
|
|
let labels = std::mem::take(&mut generator.pending_labels);
|
|
|
|
|
generator.begin_breakable_scope(end_block, labels.clone(), completion.clone());
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: update_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update: get next value
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(update_block);
|
|
|
|
|
let next_value = generator.allocate_register();
|
|
|
|
|
let done = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_await {
|
|
|
|
|
// For-await-of: Call iterator.next(), await the result, then unpack.
|
2026-02-24 08:36:14 -03:00
|
|
|
let next_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::IteratorNext {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: next_result.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next_method.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
});
|
|
|
|
|
// Await the next result. Pre-allocate completion registers and emit
|
2026-03-20 00:26:33 -03:00
|
|
|
// Mov(received_completion, accumulator) before Await.
|
2026-02-24 08:36:14 -03:00
|
|
|
let received_completion = generator.allocate_register();
|
|
|
|
|
let received_completion_type = generator.allocate_register();
|
|
|
|
|
let received_completion_value = generator.allocate_register();
|
|
|
|
|
let acc = generator.accumulator();
|
|
|
|
|
generator.emit_mov(&received_completion, &acc);
|
2026-02-23 07:50:46 -03:00
|
|
|
let awaited = generate_await_with_completions(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
&next_result,
|
|
|
|
|
&received_completion,
|
|
|
|
|
&received_completion_type,
|
|
|
|
|
&received_completion_value,
|
2026-02-23 07:50:46 -03:00
|
|
|
);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov(&next_result, &awaited);
|
2026-02-23 07:50:46 -03:00
|
|
|
// Type check
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: next_result.operand(),
|
|
|
|
|
});
|
|
|
|
|
// IteratorComplete — get .done property
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &done, &next_result, utf16!("done"), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let loop_continue_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&done, end_block, loop_continue_block);
|
|
|
|
|
generator.switch_to_basic_block(loop_continue_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// IteratorValue — get .value property
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &next_value, &next_result, utf16!("value"), None);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::IteratorNextUnpack {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst_value: next_value.operand(),
|
|
|
|
|
dst_done: done.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next_method.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let loop_continue_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&done, end_block, loop_continue_block);
|
|
|
|
|
generator.switch_to_basic_block(loop_continue_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set up exception handler AFTER iterator-next section.
|
|
|
|
|
// Per spec, exceptions from IteratorNext/Await/IteratorComplete/IteratorValue
|
|
|
|
|
// propagate directly; only LHS assignment and body exceptions trigger close.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = Some(exception_preamble_block);
|
|
|
|
|
let loop_body_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: loop_body_block,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(loop_body_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Create per-iteration lexical environment for let/const declarations.
|
|
|
|
|
let parent_env = if needs_lexical_env {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(create_for_in_of_lexical_env(generator, lhs))
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Assign to LHS
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_to_for_in_of_lhs(generator, lhs, &next_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Body
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.begin_continuable_scope(update_block, labels, completion.clone());
|
2026-03-01 13:05:59 -03:00
|
|
|
if needs_lexical_env {
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-01 09:36:50 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-27 06:37:00 -03:00
|
|
|
generate_with_completion(body, generator, completion.as_ref(), preferred_dst);
|
2026-03-01 09:36:50 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Restore lexical env before continuing
|
|
|
|
|
if needs_lexical_env {
|
2026-03-01 13:05:59 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.pop();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_continuable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
|
|
|
|
|
generator.end_breakable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Pop the FinallyContext.
|
2026-02-24 08:36:14 -03:00
|
|
|
let finally_ctx_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.current_finally_context
|
|
|
|
|
.expect("no active finally context");
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_finally_context = generator.finally_contexts[finally_ctx_index].parent_index;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Restore unwind handler
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = old_handler;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
if needs_lexical_env {
|
2026-02-24 06:40:18 -03:00
|
|
|
let parent = parent_env
|
|
|
|
|
.as_ref()
|
|
|
|
|
.expect("parent_env must be set when restoring lexical environment");
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-24 06:40:18 -03:00
|
|
|
environment: parent.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-24 06:40:18 -03:00
|
|
|
target: update_block,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Exception preamble: catch thrown exception, route to iterator close ---
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(exception_preamble_block);
|
|
|
|
|
generator.emit(Instruction::Catch {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: close_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
if let Some(env) = &lexical_env_at_entry {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: env.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_const = generator.add_constant_i32(FinallyContext::THROW);
|
|
|
|
|
generator.emit_mov(&close_completion_type, &throw_const);
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: iterator_close_body_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- Iterator close body: dispatch based on completion type ---
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(iterator_close_body_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// THROW path
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_close_block = generator.make_block();
|
|
|
|
|
let non_throw_close_block = generator.make_block();
|
|
|
|
|
let throw_check_const = generator.add_constant_i32(FinallyContext::THROW);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: close_completion_type.operand(),
|
|
|
|
|
rhs: throw_check_const.operand(),
|
|
|
|
|
true_target: throw_close_block,
|
|
|
|
|
false_target: non_throw_close_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Non-throw close: close the iterator with Normal completion, then dispatch.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(non_throw_close_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_await {
|
|
|
|
|
// For async iterators, inline AsyncIteratorClose using GetMethod+Call+Await
|
|
|
|
|
// instead of the synchronous IteratorClose instruction. This avoids spinning
|
|
|
|
|
// the event loop inside bytecode execution.
|
2026-02-24 08:36:14 -03:00
|
|
|
let after_close = generator.make_block();
|
|
|
|
|
let return_method = generator.allocate_register();
|
|
|
|
|
let return_key = generator.intern_property_key(utf16!("return"));
|
|
|
|
|
generator.emit(Instruction::GetMethod {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: return_method.operand(),
|
|
|
|
|
object: iterator_object.operand(),
|
|
|
|
|
property: return_key,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let call_return_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: return_method.operand(),
|
|
|
|
|
true_target: after_close,
|
|
|
|
|
false_target: call_return_block,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(call_return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let inner_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Call {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: inner_result.operand(),
|
|
|
|
|
callee: return_method.operand(),
|
|
|
|
|
this_value: iterator_object.operand(),
|
|
|
|
|
expression_string: None,
|
|
|
|
|
argument_count: 0,
|
|
|
|
|
arguments: vec![],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Pre-allocate completion registers in this scope so they're freed
|
2026-03-20 00:26:33 -03:00
|
|
|
// together with return_method and inner_result.
|
2026-02-24 08:36:14 -03:00
|
|
|
let rc = generator.allocate_register();
|
|
|
|
|
let rct = generator.allocate_register();
|
|
|
|
|
let rcv = generator.allocate_register();
|
|
|
|
|
let awaited = generate_await_with_completions(generator, &inner_result, &rc, &rct, &rcv);
|
|
|
|
|
generator.emit(Instruction::ThrowIfNotObject {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: awaited.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-24 06:40:18 -03:00
|
|
|
target: after_close,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(after_close);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::IteratorClose {
|
2026-02-23 07:50:46 -03:00
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next_method.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
completion_type: CompletionType::Normal as u32,
|
|
|
|
|
completion_value: undef.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dispatch registered jumps (break/continue targets).
|
2026-02-24 06:40:18 -03:00
|
|
|
let registered_jumps =
|
2026-02-24 08:36:14 -03:00
|
|
|
std::mem::take(&mut generator.finally_contexts[finally_ctx_index].registered_jumps);
|
2026-02-23 07:50:46 -03:00
|
|
|
for jump in ®istered_jumps {
|
2026-02-24 08:36:14 -03:00
|
|
|
let after_check = generator.make_block();
|
|
|
|
|
let jump_const = generator.add_constant_i32(jump.index);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: close_completion_type.operand(),
|
|
|
|
|
rhs: jump_const.operand(),
|
|
|
|
|
true_target: jump.target,
|
|
|
|
|
false_target: after_check,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(after_check);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RETURN path
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_block = generator.make_block();
|
|
|
|
|
let unreachable_block = generator.make_block();
|
|
|
|
|
let return_const = generator.add_constant_i32(FinallyContext::RETURN);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: close_completion_type.operand(),
|
|
|
|
|
rhs: return_const.operand(),
|
|
|
|
|
true_target: return_block,
|
|
|
|
|
false_target: unreachable_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_block);
|
|
|
|
|
if let Some(outer_index) = generator.current_finally_context {
|
|
|
|
|
let outer_ct = generator.finally_contexts[outer_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
let outer_cv = generator.finally_contexts[outer_index]
|
|
|
|
|
.completion_value
|
|
|
|
|
.clone();
|
|
|
|
|
let outer_fb = generator.finally_contexts[outer_index].finally_body;
|
|
|
|
|
generator.emit_mov(&outer_ct, &close_completion_type);
|
|
|
|
|
generator.emit_mov(&outer_cv, &close_completion_value);
|
|
|
|
|
generator.emit(Instruction::Jump { target: outer_fb });
|
|
|
|
|
} else if generator.is_in_generator_function() {
|
|
|
|
|
generator.emit(Instruction::Yield {
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: None,
|
|
|
|
|
value: close_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Return {
|
2026-02-23 07:50:46 -03:00
|
|
|
value: close_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unreachable default: throw the value.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(unreachable_block);
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: close_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Throw close: close iterator then rethrow original exception.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(throw_close_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if is_await {
|
|
|
|
|
// Inline AsyncIteratorClose with exception handler: any error from the close
|
|
|
|
|
// steps is discarded and the original exception is rethrown.
|
2026-02-24 08:36:14 -03:00
|
|
|
let rethrow_block = generator.make_block();
|
|
|
|
|
let close_catch_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Set up an exception handler that catches errors from the close and rethrows original.
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_close_handler = generator.current_unwind_handler;
|
|
|
|
|
generator.current_unwind_handler = Some(close_catch_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Jump to a block created inside the unwind context so that
|
|
|
|
|
// GetMethod/Call/Await all have the exception handler set.
|
2026-02-24 08:36:14 -03:00
|
|
|
let close_try_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-24 06:40:18 -03:00
|
|
|
target: close_try_block,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(close_try_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
{
|
|
|
|
|
let return_method = generator.allocate_register();
|
|
|
|
|
let return_key = generator.intern_property_key(utf16!("return"));
|
|
|
|
|
generator.emit(Instruction::GetMethod {
|
|
|
|
|
dst: return_method.operand(),
|
|
|
|
|
object: iterator_object.operand(),
|
|
|
|
|
property: return_key,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
let call_return_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
|
|
|
|
condition: return_method.operand(),
|
|
|
|
|
true_target: rethrow_block,
|
|
|
|
|
false_target: call_return_block,
|
|
|
|
|
});
|
|
|
|
|
generator.switch_to_basic_block(call_return_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
let inner_result = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Call {
|
|
|
|
|
dst: inner_result.operand(),
|
|
|
|
|
callee: return_method.operand(),
|
|
|
|
|
this_value: iterator_object.operand(),
|
|
|
|
|
expression_string: None,
|
|
|
|
|
argument_count: 0,
|
|
|
|
|
arguments: vec![],
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
let rc = generator.allocate_register();
|
|
|
|
|
let rct = generator.allocate_register();
|
|
|
|
|
let rcv = generator.allocate_register();
|
|
|
|
|
generate_await_with_completions(generator, &inner_result, &rc, &rct, &rcv);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Even if close succeeded, rethrow original (spec step 5).
|
|
|
|
|
generator.emit(Instruction::Jump {
|
|
|
|
|
target: rethrow_block,
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Exception handler: discard close error, rethrow original.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = old_close_handler;
|
|
|
|
|
generator.switch_to_basic_block(close_catch_block);
|
|
|
|
|
let discarded = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Catch {
|
2026-02-24 06:40:18 -03:00
|
|
|
dst: discarded.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-24 06:40:18 -03:00
|
|
|
target: rethrow_block,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(rethrow_block);
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: close_completion_value.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::IteratorClose {
|
2026-02-23 07:50:46 -03:00
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next_method.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
completion_type: CompletionType::Throw as u32,
|
|
|
|
|
completion_value: close_completion_value.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: close_completion_value.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release the FinallyContext's ScopedOperands so their registers
|
2026-03-20 00:26:33 -03:00
|
|
|
// can be reused.
|
2026-02-24 08:36:14 -03:00
|
|
|
let dummy = generator.add_constant_undefined();
|
|
|
|
|
generator.finally_contexts[finally_ctx_index].completion_type = dummy.clone();
|
|
|
|
|
generator.finally_contexts[finally_ctx_index].completion_value = dummy;
|
|
|
|
|
generator.finally_contexts[finally_ctx_index].lexical_environment_at_entry = None;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(end_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn assign_to_for_in_of_lhs(generator: &mut Generator, lhs: &ForInOfLhs, value: &ScopedOperand) {
|
2026-02-23 07:50:46 -03:00
|
|
|
match lhs {
|
|
|
|
|
ForInOfLhs::Declaration(statement) => {
|
|
|
|
|
// UsingDeclaration: disposal semantics not yet implemented.
|
2026-03-20 00:26:33 -03:00
|
|
|
// UsingDeclaration is not recognized as a VariableDeclaration
|
|
|
|
|
// in for_in_of_head_evaluation, so it is treated as Assignment
|
|
|
|
|
// lhs_kind. This produces NewTypeError + Throw for the using
|
|
|
|
|
// declaration, followed by NewReferenceError + Throw (dead code).
|
2026-02-23 07:50:46 -03:00
|
|
|
if matches!(statement.inner, StatementKind::UsingDeclaration { .. }) {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(statement, generator, None);
|
|
|
|
|
let exception = generator.allocate_register();
|
2026-02-24 06:40:18 -03:00
|
|
|
let error_string =
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.intern_string(utf16!("Invalid left-hand side in assignment"));
|
|
|
|
|
generator.emit(Instruction::NewReferenceError {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: exception.operand(),
|
|
|
|
|
error_string,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.perform_needed_unwinds();
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-24 06:40:18 -03:00
|
|
|
src: exception.operand(),
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// The declaration is a VariableDeclaration with a single declarator
|
2026-02-24 08:36:14 -03:00
|
|
|
if let StatementKind::VariableDeclaration { kind, declarations } = &statement.inner
|
|
|
|
|
&& let Some(declaration) = declarations.first()
|
|
|
|
|
{
|
|
|
|
|
// For var: FDI already initialized the binding, so use Set.
|
|
|
|
|
// For let/const: per-iteration env created new bindings needing Initialize.
|
|
|
|
|
let mode = match kind {
|
|
|
|
|
DeclarationKind::Var => BindingMode::Set,
|
|
|
|
|
DeclarationKind::Let | DeclarationKind::Const => BindingMode::InitializeLexical,
|
|
|
|
|
};
|
|
|
|
|
match &declaration.target {
|
|
|
|
|
VariableDeclaratorTarget::Identifier(ident) => {
|
|
|
|
|
emit_set_variable_with_mode(generator, ident, value, mode);
|
|
|
|
|
}
|
|
|
|
|
VariableDeclaratorTarget::BindingPattern(pattern) => {
|
|
|
|
|
generate_binding_pattern_bytecode(generator, pattern, mode, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ForInOfLhs::Expression(expression) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_store_to_reference(generator, expression, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
ForInOfLhs::Pattern(pattern) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_binding_pattern_bytecode(generator, pattern, BindingMode::Set, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Binding pattern destructuring
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Whether we are initializing a new binding or setting an existing one.
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
enum BindingMode {
|
|
|
|
|
/// `const` or `let` declarations: emit InitializeLexicalBinding.
|
|
|
|
|
InitializeLexical,
|
|
|
|
|
/// Assignment expressions / var iteration: emit SetLexicalBinding or SetGlobal.
|
|
|
|
|
Set,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
fn set_pending_lhs_name_for_entry(generator: &mut Generator, entry: &BindingEntry) {
|
2026-02-23 07:50:46 -03:00
|
|
|
let name = match &entry.alias {
|
|
|
|
|
Some(BindingEntryAlias::Identifier(id)) => Some(&id.name),
|
|
|
|
|
None => {
|
|
|
|
|
if let Some(BindingEntryName::Identifier(id)) = &entry.name {
|
|
|
|
|
Some(&id.name)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(name) = name {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.pending_lhs_name = Some(generator.intern_identifier(name));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_binding_pattern_bytecode(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
pattern: &BindingPattern,
|
|
|
|
|
mode: BindingMode,
|
|
|
|
|
input_value: &ScopedOperand,
|
|
|
|
|
) {
|
|
|
|
|
match pattern.kind {
|
|
|
|
|
BindingPatternKind::Array => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_array_binding_pattern(generator, pattern, mode, input_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BindingPatternKind::Object => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_object_binding_pattern(generator, pattern, mode, input_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn emit_set_variable_with_mode(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
ident: &Identifier,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
mode: BindingMode,
|
|
|
|
|
) {
|
|
|
|
|
if ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let local =
|
|
|
|
|
generator.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap());
|
|
|
|
|
generator.emit_mov(&local, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
2026-02-23 07:50:46 -03:00
|
|
|
match mode {
|
|
|
|
|
BindingMode::InitializeLexical => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
BindingMode::Set => {
|
|
|
|
|
if ident.is_global.get() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let cache = generator.next_global_variable_cache();
|
|
|
|
|
generator.emit(Instruction::SetGlobal {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
2026-03-07 18:52:25 -03:00
|
|
|
cache: cache as u64,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn assign_binding_entry_alias(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
entry: &BindingEntry,
|
|
|
|
|
value: &ScopedOperand,
|
|
|
|
|
mode: BindingMode,
|
|
|
|
|
) {
|
|
|
|
|
match &entry.alias {
|
|
|
|
|
None => {
|
|
|
|
|
// Name IS the binding target (e.g., `{ x }` or array element).
|
|
|
|
|
if let Some(BindingEntryName::Identifier(ident)) = &entry.name {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_set_variable_with_mode(generator, ident, value, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::Identifier(ident)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_set_variable_with_mode(generator, ident, value, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::BindingPattern(sub_pattern)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_binding_pattern_bytecode(generator, sub_pattern, mode, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::MemberExpression(expression)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_store_to_reference(generator, expression, value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_array_binding_pattern(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
pattern: &BindingPattern,
|
|
|
|
|
mode: BindingMode,
|
|
|
|
|
input_array: &ScopedOperand,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let is_exhausted = generator.allocate_register();
|
|
|
|
|
let false_val = generator.add_constant_boolean(false);
|
|
|
|
|
generator.emit_mov(&is_exhausted, &false_val);
|
|
|
|
|
|
|
|
|
|
let iterator_object = generator.allocate_register();
|
|
|
|
|
let iterator_next = generator.allocate_register();
|
|
|
|
|
let iterator_done = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetIterator {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst_iterator_object: iterator_object.operand(),
|
|
|
|
|
dst_iterator_next: iterator_next.operand(),
|
|
|
|
|
dst_iterator_done: iterator_done.operand(),
|
|
|
|
|
iterable: input_array.operand(),
|
|
|
|
|
hint: IteratorHint::Sync as u32,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (index, entry) in pattern.entries.iter().enumerate() {
|
|
|
|
|
if entry.is_rest {
|
|
|
|
|
// 13.15.5.3 AssignmentRestElement: ... DestructuringAssignmentTarget
|
|
|
|
|
// Step 1: Evaluate the reference BEFORE iterating remaining elements.
|
2026-02-24 06:40:18 -03:00
|
|
|
let evaluated_ref =
|
|
|
|
|
if let Some(BindingEntryAlias::MemberExpression(expression)) = &entry.alias {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(emit_evaluate_member_reference(generator, expression))
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Rest element: collect remaining into array.
|
|
|
|
|
// NB: Allocate register unconditionally, then re-allocate in the
|
2026-03-20 00:26:33 -03:00
|
|
|
// else branch.
|
2026-02-24 08:36:14 -03:00
|
|
|
let mut value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
if index == 0 {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::IteratorToArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next_method: iterator_next.operand(),
|
|
|
|
|
iterator_done_property: iterator_done.operand(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let if_exhausted = generator.make_block();
|
|
|
|
|
let if_not_exhausted = generator.make_block();
|
|
|
|
|
let continuation = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_jump_if(&is_exhausted, if_exhausted, if_not_exhausted);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_exhausted);
|
|
|
|
|
generator.emit(Instruction::NewArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
element_count: 0,
|
|
|
|
|
elements: Vec::new(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: continuation,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_not_exhausted);
|
|
|
|
|
generator.emit(Instruction::IteratorToArray {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next_method: iterator_next.operand(),
|
|
|
|
|
iterator_done_property: iterator_done.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: continuation,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(continuation);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(ref eref) = evaluated_ref {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_store_to_evaluated_reference(generator, eref, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_binding_entry_alias(generator, entry, &value, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
return; // rest consumes the iterator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 13.15.5.5 AssignmentElement: DestructuringAssignmentTarget Initializer(opt)
|
|
|
|
|
// Step 1: Evaluate the reference BEFORE calling IteratorStepValue.
|
2026-02-24 06:40:18 -03:00
|
|
|
let evaluated_ref =
|
|
|
|
|
if let Some(BindingEntryAlias::MemberExpression(expression)) = &entry.alias {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(emit_evaluate_member_reference(generator, expression))
|
2026-02-24 06:40:18 -03:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// For elisions (name is None), we still advance the iterator
|
|
|
|
|
// but don't bind anything.
|
|
|
|
|
let is_elision = entry.name.is_none() && entry.alias.is_none();
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let exhausted_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
if index != 0 {
|
2026-02-24 08:36:14 -03:00
|
|
|
let not_exhausted_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&is_exhausted, exhausted_block, not_exhausted_block);
|
|
|
|
|
generator.switch_to_basic_block(not_exhausted_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::IteratorNextUnpack {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst_value: value.operand(),
|
|
|
|
|
dst_done: is_exhausted.operand(),
|
|
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Check if iterator got exhausted by this step.
|
2026-02-24 08:36:14 -03:00
|
|
|
let no_bail_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&is_exhausted, exhausted_block, no_bail_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(no_bail_block);
|
|
|
|
|
let create_binding_block = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: create_binding_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Exhausted: load undefined.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(exhausted_block);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(&value, &undef);
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: create_binding_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(create_binding_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Handle default initializer.
|
|
|
|
|
if let Some(ref initializer) = entry.initializer {
|
2026-02-24 08:36:14 -03:00
|
|
|
let if_undefined = generator.make_block();
|
|
|
|
|
let if_not_undefined = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: value.operand(),
|
|
|
|
|
true_target: if_undefined,
|
|
|
|
|
false_target: if_not_undefined,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_undefined);
|
|
|
|
|
set_pending_lhs_name_for_entry(generator, entry);
|
|
|
|
|
if let Some(default_value) = generate_expression(initializer, generator, None) {
|
|
|
|
|
generator.emit_mov(&value, &default_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: if_not_undefined,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_not_undefined);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !is_elision {
|
|
|
|
|
if let Some(ref eref) = evaluated_ref {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_store_to_evaluated_reference(generator, eref, &value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_binding_entry_alias(generator, entry, &value, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close iterator if not exhausted.
|
2026-02-24 08:36:14 -03:00
|
|
|
let done_block = generator.make_block();
|
|
|
|
|
let not_done_block = generator.make_block();
|
|
|
|
|
generator.emit_jump_if(&is_exhausted, done_block, not_done_block);
|
|
|
|
|
generator.switch_to_basic_block(not_done_block);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::IteratorClose {
|
2026-02-23 07:50:46 -03:00
|
|
|
iterator_object: iterator_object.operand(),
|
|
|
|
|
iterator_next: iterator_next.operand(),
|
|
|
|
|
iterator_done: iterator_done.operand(),
|
|
|
|
|
completion_type: CompletionType::Normal as u32,
|
|
|
|
|
completion_value: undef.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump { target: done_block });
|
|
|
|
|
generator.switch_to_basic_block(done_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn generate_object_binding_pattern(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
pattern: &BindingPattern,
|
|
|
|
|
mode: BindingMode,
|
|
|
|
|
object: &ScopedOperand,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::ThrowIfNullish {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: object.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let mut excluded_names: Vec<ScopedOperand> = Vec::new();
|
2026-02-24 06:40:18 -03:00
|
|
|
let has_rest = pattern.entries.last().is_some_and(|e| e.is_rest);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
for entry in &pattern.entries {
|
|
|
|
|
if entry.is_rest {
|
|
|
|
|
// Rest element: copy object excluding already-destructured properties.
|
2026-02-24 08:36:14 -03:00
|
|
|
let copy = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::CopyObjectExcludingProperties {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: copy.operand(),
|
|
|
|
|
from_object: object.operand(),
|
|
|
|
|
excluded_names_count: u32_from_usize(excluded_names.len()),
|
|
|
|
|
excluded_names: excluded_names.iter().map(|o| o.operand()).collect(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_binding_entry_alias(generator, entry, ©, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
match &entry.name {
|
|
|
|
|
Some(BindingEntryName::Identifier(ident)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_id(generator, &value, object, &ident.name, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_rest {
|
2026-02-24 08:36:14 -03:00
|
|
|
let name_val = generator.add_constant_string(ident.name.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
excluded_names.push(name_val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryName::Expression(expression)) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let property_name = generate_expression_or_undefined(expression, generator, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_rest {
|
|
|
|
|
// Only copy to a new register if the property name is a local variable,
|
|
|
|
|
// since locals can be reassigned. Registers are temporaries and won't change.
|
|
|
|
|
if property_name.operand().is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let excluded_name = generator.allocate_register();
|
|
|
|
|
generator.emit_mov(&excluded_name, &property_name);
|
2026-02-23 07:50:46 -03:00
|
|
|
excluded_names.push(excluded_name);
|
|
|
|
|
} else {
|
|
|
|
|
excluded_names.push(property_name.clone());
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
emit_get_by_value(generator, &value, object, &property_name, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
// Should not happen for object patterns
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle default initializer.
|
|
|
|
|
if let Some(ref initializer) = entry.initializer {
|
2026-02-24 08:36:14 -03:00
|
|
|
let if_undefined = generator.make_block();
|
|
|
|
|
let if_not_undefined = generator.make_block();
|
|
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: value.operand(),
|
|
|
|
|
true_target: if_undefined,
|
|
|
|
|
false_target: if_not_undefined,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_undefined);
|
|
|
|
|
set_pending_lhs_name_for_entry(generator, entry);
|
|
|
|
|
if let Some(default_value) = generate_expression(initializer, generator, None) {
|
|
|
|
|
generator.emit_mov(&value, &default_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: if_not_undefined,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_not_undefined);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
assign_binding_entry_alias(generator, entry, &value, mode);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Try statement
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Generate bytecode for a try/catch/finally statement.
|
|
|
|
|
///
|
|
|
|
|
/// The structure is:
|
|
|
|
|
/// 1. Set up FinallyContext (if finally block exists)
|
|
|
|
|
/// 2. Set up exception handler pointing to catch/exception preamble
|
|
|
|
|
/// 3. Generate try body
|
|
|
|
|
/// 4. Generate catch block (if present)
|
|
|
|
|
/// 5. Generate finally block (if present) with LeaveFinally dispatch
|
|
|
|
|
fn generate_try_statement(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: &TryStatementData,
|
|
|
|
|
_preferred_dst: Option<&ScopedOperand>,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let old_handler = generator.current_unwind_handler;
|
|
|
|
|
let saved_block = generator.current_block_index();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Save lexical environment for restoration in catch/exception handler.
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_env = generator.current_lexical_environment();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let mut next_block: Option<Label> = None;
|
|
|
|
|
let mut completion: Option<ScopedOperand> = None;
|
|
|
|
|
|
|
|
|
|
// --- Set up FinallyContext if we have a finalizer ---
|
|
|
|
|
let has_finally = data.finalizer.is_some();
|
|
|
|
|
let mut finally_body_block: Option<Label> = None;
|
|
|
|
|
|
|
|
|
|
if has_finally {
|
2026-02-24 08:36:14 -03:00
|
|
|
let completion_type = generator.allocate_register();
|
|
|
|
|
let completion_value = generator.allocate_register();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let exception_preamble_block = generator.make_block();
|
|
|
|
|
let fb_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
finally_body_block = Some(fb_block);
|
|
|
|
|
|
|
|
|
|
// Save the parent FinallyContext and install new one.
|
2026-02-24 08:36:14 -03:00
|
|
|
let parent_index = generator.current_finally_context;
|
|
|
|
|
generator.push_finally_context(FinallyContext {
|
2026-02-23 07:50:46 -03:00
|
|
|
completion_type,
|
|
|
|
|
completion_value,
|
|
|
|
|
finally_body: fb_block,
|
|
|
|
|
exception_preamble: exception_preamble_block,
|
|
|
|
|
parent_index,
|
|
|
|
|
registered_jumps: Vec::new(),
|
|
|
|
|
next_jump_index: FinallyContext::FIRST_JUMP_INDEX,
|
|
|
|
|
lexical_environment_at_entry: Some(saved_env.clone()),
|
2026-03-01 09:47:30 -03:00
|
|
|
saved_unwind_handler: None,
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Generate exception preamble block:
|
|
|
|
|
// Catch → completion_value
|
|
|
|
|
// SetLexicalEnvironment (restore to entry)
|
|
|
|
|
// completion_type = THROW
|
|
|
|
|
// Jump → finally_body
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(exception_preamble_block);
|
|
|
|
|
let ctx_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.current_finally_context
|
|
|
|
|
.expect("no active finally context");
|
2026-02-24 08:36:14 -03:00
|
|
|
let cv = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_value
|
|
|
|
|
.clone();
|
|
|
|
|
let ct = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
generator.emit(Instruction::Catch { dst: cv.operand() });
|
|
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: saved_env.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let throw_const = generator.add_constant_i32(FinallyContext::THROW);
|
|
|
|
|
generator.emit_mov(&ct, &throw_const);
|
|
|
|
|
generator.emit(Instruction::Jump { target: fb_block });
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Set exception_preamble as default handler for blocks created below.
|
|
|
|
|
// The catch body gets this as its handler (exceptions in catch → finally).
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = Some(exception_preamble_block);
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Generate catch handler block (if present) ---
|
|
|
|
|
let mut handler_block: Option<Label> = None;
|
|
|
|
|
if let Some(catch) = &data.handler {
|
2026-02-24 08:36:14 -03:00
|
|
|
let hb = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
handler_block = Some(hb);
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(hb);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let caught_value = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::Catch {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: caught_value.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
environment: saved_env.operand(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Bind the catch parameter.
|
|
|
|
|
let mut created_catch_scope = false;
|
|
|
|
|
if let Some(parameter) = &catch.parameter {
|
|
|
|
|
match parameter {
|
|
|
|
|
CatchBinding::Identifier(ident) => {
|
|
|
|
|
if ident.is_local() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.local(ident.local_index.get());
|
|
|
|
|
generator.emit_mov(&local, &caught_value);
|
|
|
|
|
generator.mark_local_initialized(ident.local_index.get());
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
created_catch_scope = true;
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: caught_value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CatchBinding::BindingPattern(pattern) => {
|
|
|
|
|
let mut names: Vec<(Utf16String, bool)> = Vec::new();
|
|
|
|
|
collect_pattern_binding_names(pattern, &mut names);
|
|
|
|
|
|
|
|
|
|
if !names.is_empty() {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveLexicalEnvironment);
|
2026-02-23 07:50:46 -03:00
|
|
|
created_catch_scope = true;
|
|
|
|
|
|
|
|
|
|
for (name, _) in &names {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
generate_binding_pattern_bytecode(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
pattern,
|
|
|
|
|
BindingMode::InitializeLexical,
|
|
|
|
|
&caught_value,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Catch body gets its own completion register to prevent
|
|
|
|
|
// break/continue inside catch from leaking values.
|
|
|
|
|
let mut catch_completion: Option<ScopedOperand> = None;
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_completion = generator.current_completion_register.clone();
|
|
|
|
|
if generator.must_propagate_completion {
|
|
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(®, &undef);
|
|
|
|
|
generator.current_completion_register = Some(reg.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
catch_completion = Some(reg);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(&catch.body, generator, None);
|
|
|
|
|
generator.current_completion_register = saved_completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
// Save catch completion BEFORE restoring the lexical environment
|
|
|
|
|
// from the catch scope.
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.must_propagate_completion
|
|
|
|
|
&& let Some(ref cc) = catch_completion
|
|
|
|
|
&& !generator.is_current_block_terminated()
|
|
|
|
|
{
|
|
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
generator.emit_mov(®, cc);
|
|
|
|
|
completion = Some(reg);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if created_catch_scope {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_variable_scope();
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_finally {
|
|
|
|
|
// Normal exit from catch → completion_type = NORMAL, jump to finally.
|
2026-02-24 08:36:14 -03:00
|
|
|
let ctx_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.current_finally_context
|
|
|
|
|
.expect("no active finally context");
|
2026-02-24 08:36:14 -03:00
|
|
|
let ct = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
let fb = generator.finally_contexts[ctx_index].finally_body;
|
|
|
|
|
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
|
|
|
|
|
generator.emit_mov(&ct, &normal_const);
|
|
|
|
|
generator.emit(Instruction::Jump { target: fb });
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
|
|
|
|
if next_block.is_none() {
|
2026-02-24 08:36:14 -03:00
|
|
|
next_block = Some(generator.make_block());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: next_block.expect("next_block must be set"),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if has_finally {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Generate try body ---
|
|
|
|
|
|
|
|
|
|
// Set handler BEFORE creating the try body block, so make_block()
|
|
|
|
|
// captures the correct handler for exception routing.
|
|
|
|
|
// For try-catch-finally: catch handler is inner (exceptions → catch → exception_preamble → finally).
|
|
|
|
|
// For try-catch: catch handler.
|
|
|
|
|
// For try-finally: exception_preamble.
|
|
|
|
|
if let Some(hb) = handler_block {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = Some(hb);
|
|
|
|
|
} else if has_finally && let Some(ctx_index) = generator.current_finally_context {
|
|
|
|
|
let ep = generator.finally_contexts[ctx_index].exception_preamble;
|
|
|
|
|
generator.current_unwind_handler = Some(ep);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let try_body_block = generator.make_block();
|
|
|
|
|
generator.switch_to_basic_block(saved_block);
|
|
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: try_body_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if has_finally {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.start_boundary(BlockBoundaryType::ReturnToFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(try_body_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Try body gets its own completion register to prevent
|
|
|
|
|
// break/continue inside try from leaking values.
|
|
|
|
|
// NB: try_completion must be declared outside the inner scope so its
|
2026-03-20 00:26:33 -03:00
|
|
|
// register stays alive during finally body generation.
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut try_completion: Option<ScopedOperand> = None;
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_completion = generator.current_completion_register.clone();
|
|
|
|
|
if generator.must_propagate_completion {
|
|
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(®, &undef);
|
|
|
|
|
generator.current_completion_register = Some(reg.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
try_completion = Some(reg);
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(&data.block, generator, None);
|
|
|
|
|
generator.current_completion_register = saved_completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated()
|
|
|
|
|
&& generator.must_propagate_completion
|
|
|
|
|
&& let Some(ref tc) = try_completion
|
|
|
|
|
{
|
|
|
|
|
let reg = generator.allocate_register();
|
|
|
|
|
generator.emit_mov(®, tc);
|
|
|
|
|
completion = Some(reg);
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_finally {
|
|
|
|
|
// Normal exit from try → completion_type = NORMAL, jump to finally.
|
2026-02-24 08:36:14 -03:00
|
|
|
let ctx_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.current_finally_context
|
|
|
|
|
.expect("no active finally context");
|
2026-02-24 08:36:14 -03:00
|
|
|
let ct = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
let fb = generator.finally_contexts[ctx_index].finally_body;
|
|
|
|
|
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
|
|
|
|
|
generator.emit_mov(&ct, &normal_const);
|
|
|
|
|
generator.emit(Instruction::Jump { target: fb });
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = old_handler;
|
2026-02-23 07:50:46 -03:00
|
|
|
if next_block.is_none() {
|
2026-02-24 08:36:14 -03:00
|
|
|
next_block = Some(generator.make_block());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: next_block.expect("next_block must be set"),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if has_finally {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::ReturnToFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore old unwind handler.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_unwind_handler = old_handler;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// --- Generate finally body and after-finally dispatch ---
|
|
|
|
|
if let Some(fb_block) = finally_body_block {
|
|
|
|
|
// Pop FinallyContext.
|
2026-02-24 08:36:14 -03:00
|
|
|
let ctx_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.current_finally_context
|
|
|
|
|
.expect("no active finally context");
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.current_finally_context = generator.finally_contexts[ctx_index].parent_index;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Extract fields needed for dispatch (to avoid borrow conflicts).
|
2026-02-24 08:36:14 -03:00
|
|
|
let ctx_ct = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
let ctx_cv = generator.finally_contexts[ctx_index]
|
|
|
|
|
.completion_value
|
|
|
|
|
.clone();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(fb_block);
|
|
|
|
|
generator.start_boundary(BlockBoundaryType::LeaveFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Generate the finally body with a throwaway completion register
|
|
|
|
|
// to prevent break/continue in finally from leaking the try/catch
|
|
|
|
|
// completion value.
|
|
|
|
|
if let Some(finalizer) = &data.finalizer {
|
2026-02-24 08:36:14 -03:00
|
|
|
let saved_completion = generator.current_completion_register.clone();
|
|
|
|
|
if generator.must_propagate_completion {
|
|
|
|
|
let finally_completion = generator.allocate_register();
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(&finally_completion, &undef);
|
|
|
|
|
generator.current_completion_register = Some(finally_completion);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_statement(finalizer, generator, None);
|
|
|
|
|
generator.current_completion_register = saved_completion;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.end_boundary(BlockBoundaryType::LeaveFinally);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if !generator.is_current_block_terminated() {
|
2026-02-23 07:50:46 -03:00
|
|
|
if next_block.is_none() {
|
2026-02-24 08:36:14 -03:00
|
|
|
next_block = Some(generator.make_block());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let nb = next_block.expect("next_block must be set");
|
|
|
|
|
|
|
|
|
|
// After-finally dispatch chain:
|
|
|
|
|
// 1. NORMAL → next block
|
2026-02-24 08:36:14 -03:00
|
|
|
let after_normal_check = generator.make_block();
|
|
|
|
|
let normal_const = generator.add_constant_i32(FinallyContext::NORMAL);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: ctx_ct.operand(),
|
|
|
|
|
rhs: normal_const.operand(),
|
|
|
|
|
true_target: nb,
|
|
|
|
|
false_target: after_normal_check,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(after_normal_check);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// 2. Registered break/continue jumps
|
2026-02-24 06:40:18 -03:00
|
|
|
let registered_jumps =
|
2026-02-24 08:36:14 -03:00
|
|
|
std::mem::take(&mut generator.finally_contexts[ctx_index].registered_jumps);
|
2026-02-23 07:50:46 -03:00
|
|
|
for jump in ®istered_jumps {
|
2026-02-24 08:36:14 -03:00
|
|
|
let after_jump_check = generator.make_block();
|
|
|
|
|
let jump_const = generator.add_constant_i32(jump.index);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: ctx_ct.operand(),
|
|
|
|
|
rhs: jump_const.operand(),
|
|
|
|
|
true_target: jump.target,
|
|
|
|
|
false_target: after_jump_check,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(after_jump_check);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. RETURN → actually return the completion_value
|
2026-02-24 08:36:14 -03:00
|
|
|
let return_block = generator.make_block();
|
|
|
|
|
let rethrow_block = generator.make_block();
|
|
|
|
|
let return_const = generator.add_constant_i32(FinallyContext::RETURN);
|
|
|
|
|
generator.emit(Instruction::JumpStrictlyEquals {
|
2026-02-23 07:50:46 -03:00
|
|
|
lhs: ctx_ct.operand(),
|
|
|
|
|
rhs: return_const.operand(),
|
|
|
|
|
true_target: return_block,
|
|
|
|
|
false_target: rethrow_block,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Generate return block.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(return_block);
|
|
|
|
|
if let Some(outer_index) = generator.current_finally_context {
|
2026-02-23 07:50:46 -03:00
|
|
|
// Nested finally: copy completion record to outer and jump to outer finally.
|
2026-02-24 08:36:14 -03:00
|
|
|
let outer_ct = generator.finally_contexts[outer_index]
|
|
|
|
|
.completion_type
|
|
|
|
|
.clone();
|
|
|
|
|
let outer_cv = generator.finally_contexts[outer_index]
|
|
|
|
|
.completion_value
|
|
|
|
|
.clone();
|
|
|
|
|
let outer_fb = generator.finally_contexts[outer_index].finally_body;
|
|
|
|
|
generator.emit_mov(&outer_ct, &ctx_ct);
|
|
|
|
|
generator.emit_mov(&outer_cv, &ctx_cv);
|
|
|
|
|
generator.emit(Instruction::Jump { target: outer_fb });
|
|
|
|
|
} else if generator.is_in_generator_function() {
|
|
|
|
|
generator.emit(Instruction::Yield {
|
2026-02-23 07:50:46 -03:00
|
|
|
continuation_label: None,
|
|
|
|
|
value: ctx_cv.operand(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Return {
|
2026-02-23 07:50:46 -03:00
|
|
|
value: ctx_cv.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. Default → rethrow the exception.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(rethrow_block);
|
|
|
|
|
generator.emit(Instruction::Throw {
|
2026-02-23 07:50:46 -03:00
|
|
|
src: ctx_cv.operand(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.finally_contexts[ctx_index].lexical_environment_at_entry = None;
|
|
|
|
|
let dummy = generator.add_constant_undefined();
|
|
|
|
|
generator.finally_contexts[ctx_index].completion_value = dummy.clone();
|
|
|
|
|
generator.finally_contexts[ctx_index].completion_type = dummy;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Switch to the next block for code after the try statement.
|
|
|
|
|
// When next_block is None, all paths are terminated; switch back to
|
|
|
|
|
// saved_block (which is already terminated) so no dead block is emitted.
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(next_block.unwrap_or(saved_block));
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
if generator.must_propagate_completion && completion.is_none() {
|
|
|
|
|
return Some(generator.add_constant_undefined());
|
2026-02-24 06:40:18 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
completion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a SharedFunctionInstanceData for a function expression/declaration
|
|
|
|
|
/// and register it with the generator.
|
|
|
|
|
///
|
|
|
|
|
/// Returns the shared_function_data_index for use in NewFunction instructions.
|
|
|
|
|
fn emit_new_function(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
data: Box<FunctionData>,
|
|
|
|
|
name_override: Option<&[u16]>,
|
|
|
|
|
) -> u32 {
|
|
|
|
|
assert!(
|
2026-02-24 08:36:14 -03:00
|
|
|
data.source_text_end as usize <= generator.source_len,
|
2026-02-23 07:50:46 -03:00
|
|
|
"Function source range out of bounds: {}..{} (source len {})",
|
|
|
|
|
data.source_text_start,
|
|
|
|
|
data.source_text_end,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.source_len
|
2026-02-23 07:50:46 -03:00
|
|
|
);
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
let subtable = generator.function_table.extract_reachable(&data);
|
2026-02-23 07:50:46 -03:00
|
|
|
let sfd_ptr = unsafe {
|
|
|
|
|
super::ffi::create_shared_function_data(
|
|
|
|
|
data,
|
|
|
|
|
subtable,
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.vm_ptr,
|
|
|
|
|
generator.source_code_ptr,
|
|
|
|
|
generator.strict,
|
2026-02-23 07:50:46 -03:00
|
|
|
name_override,
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.register_shared_function_data(sfd_ptr)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// FunctionDeclarationInstantiation (FDI)
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Emit FDI bytecode for a function body.
|
|
|
|
|
///
|
|
|
|
|
/// Creates environment bindings, initializes parameters, creates arguments
|
|
|
|
|
/// objects, and hoists function declarations.
|
|
|
|
|
pub fn emit_function_declaration_instantiation(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
function_data: &FunctionData,
|
|
|
|
|
body_scope: &ScopeData,
|
2026-03-01 10:09:36 -03:00
|
|
|
var_environment_bindings_count: usize,
|
2026-02-23 07:50:46 -03:00
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
let strict = function_data.is_strict_mode || generator.strict;
|
2026-02-23 07:50:46 -03:00
|
|
|
let is_arrow = function_data.is_arrow_function;
|
|
|
|
|
|
|
|
|
|
// --- Compute FDI metadata ---
|
|
|
|
|
|
2026-03-01 08:29:48 -03:00
|
|
|
// Check for parameter expressions (default values or binding patterns with expressions).
|
2026-02-23 07:50:46 -03:00
|
|
|
let has_parameter_expressions = function_data.parameters.iter().any(|p| {
|
|
|
|
|
p.default_value.is_some()
|
2026-03-01 08:29:48 -03:00
|
|
|
|| matches!(
|
|
|
|
|
p.binding,
|
|
|
|
|
FunctionParameterBinding::BindingPattern(ref pat) if pat.contains_expression()
|
|
|
|
|
)
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Build parameter_names map and check for duplicates.
|
|
|
|
|
let mut parameter_names: Vec<FdiParameterName> = Vec::new();
|
|
|
|
|
let mut seen_names: HashSet<Utf16String> = HashSet::new();
|
|
|
|
|
let mut has_duplicates = false;
|
|
|
|
|
|
|
|
|
|
for parameter in &function_data.parameters {
|
|
|
|
|
match ¶meter.binding {
|
|
|
|
|
FunctionParameterBinding::Identifier(ident) => {
|
|
|
|
|
let name = ident.name.clone();
|
|
|
|
|
let is_local = ident.is_local();
|
|
|
|
|
if !seen_names.insert(name.clone()) {
|
|
|
|
|
has_duplicates = true;
|
|
|
|
|
} else {
|
|
|
|
|
parameter_names.push(FdiParameterName { name, is_local });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FunctionParameterBinding::BindingPattern(pattern) => {
|
2026-02-24 06:40:18 -03:00
|
|
|
collect_binding_pattern_names(
|
|
|
|
|
pattern,
|
|
|
|
|
&mut parameter_names,
|
|
|
|
|
&mut seen_names,
|
|
|
|
|
&mut has_duplicates,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine if arguments object is needed (from parsing insights).
|
2026-02-27 06:31:34 -03:00
|
|
|
let mut arguments_object_needed = if is_arrow
|
2026-02-24 06:40:18 -03:00
|
|
|
|| parameter_names
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|p| p.name == utf16!("arguments"))
|
|
|
|
|
{
|
2026-02-27 06:31:34 -03:00
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
function_data.parsing_insights.might_need_arguments_object
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let function_scope_data = body_scope.function_scope_data.as_ref();
|
|
|
|
|
|
|
|
|
|
if let Some(fsd) = function_scope_data {
|
|
|
|
|
if !has_parameter_expressions && fsd.has_function_named_arguments {
|
|
|
|
|
arguments_object_needed = false;
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
if !has_parameter_expressions
|
|
|
|
|
&& arguments_object_needed
|
|
|
|
|
&& fsd.has_lexically_declared_arguments
|
2026-02-23 07:50:46 -03:00
|
|
|
{
|
|
|
|
|
arguments_object_needed = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 1: Parameter scope for parameter expressions ---
|
|
|
|
|
|
|
|
|
|
if has_parameter_expressions {
|
|
|
|
|
let has_non_local_parameters = parameter_names.iter().any(|p| !p.is_local);
|
|
|
|
|
if has_non_local_parameters {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(0);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 2: Create bindings for non-local parameters ---
|
|
|
|
|
|
|
|
|
|
for param in ¶meter_names {
|
|
|
|
|
if !param.is_local {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(¶m.name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
|
|
|
|
if has_duplicates {
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: undef.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 3: Create arguments object ---
|
|
|
|
|
|
|
|
|
|
if arguments_object_needed {
|
|
|
|
|
// Find local variable index for ArgumentsObject, if any.
|
2026-02-24 08:36:14 -03:00
|
|
|
let arguments_local_index = generator
|
2026-02-24 06:40:18 -03:00
|
|
|
.local_variables
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|lv| lv.name == utf16!("arguments") && !lv.is_lexically_declared);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
let dst = arguments_local_index.map(|index| Operand::local(u32_from_usize(index)));
|
|
|
|
|
|
2026-02-24 06:40:18 -03:00
|
|
|
let kind = if strict
|
|
|
|
|
|| !function_data.parameters.iter().all(|p| {
|
|
|
|
|
!p.is_rest
|
|
|
|
|
&& p.default_value.is_none()
|
|
|
|
|
&& matches!(p.binding, FunctionParameterBinding::Identifier(_))
|
|
|
|
|
}) {
|
2026-02-23 07:50:46 -03:00
|
|
|
ArgumentsKind::Unmapped as u32
|
|
|
|
|
} else {
|
|
|
|
|
ArgumentsKind::Mapped as u32
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateArguments {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst,
|
|
|
|
|
kind,
|
|
|
|
|
is_immutable: strict,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if let Some(index) = arguments_local_index {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.mark_local_initialized(u32_from_usize(index));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 4: Bind formal parameters ---
|
|
|
|
|
|
|
|
|
|
for (parameter_index, parameter) in function_data.parameters.iter().enumerate() {
|
|
|
|
|
let parameter_index = u32_from_usize(parameter_index);
|
|
|
|
|
|
|
|
|
|
if parameter.is_rest {
|
2026-02-24 08:36:14 -03:00
|
|
|
let dst = generator.scoped_operand(Operand::argument(parameter_index));
|
|
|
|
|
generator.emit(Instruction::CreateRestParams {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: dst.operand(),
|
|
|
|
|
rest_index: parameter_index,
|
|
|
|
|
});
|
|
|
|
|
} else if parameter.default_value.is_some() {
|
2026-02-24 08:36:14 -03:00
|
|
|
let if_undefined_block = generator.make_block();
|
|
|
|
|
let if_not_undefined_block = generator.make_block();
|
2026-02-23 07:50:46 -03:00
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::JumpUndefined {
|
2026-02-23 07:50:46 -03:00
|
|
|
condition: Operand::argument(parameter_index),
|
|
|
|
|
true_target: if_undefined_block,
|
|
|
|
|
false_target: if_not_undefined_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_undefined_block);
|
2026-02-24 06:40:18 -03:00
|
|
|
if let Some(value) = generate_expression(
|
|
|
|
|
parameter
|
|
|
|
|
.default_value
|
|
|
|
|
.as_ref()
|
|
|
|
|
.expect("guarded by has_default_value check"),
|
2026-02-24 08:36:14 -03:00
|
|
|
generator,
|
2026-02-24 06:40:18 -03:00
|
|
|
None,
|
|
|
|
|
) {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit_mov_raw(Operand::argument(parameter_index), value.operand());
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::Jump {
|
2026-02-23 07:50:46 -03:00
|
|
|
target: if_not_undefined_block,
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.switch_to_basic_block(if_not_undefined_block);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match ¶meter.binding {
|
|
|
|
|
FunctionParameterBinding::Identifier(ident) => {
|
|
|
|
|
if ident.is_local() {
|
|
|
|
|
let local_index = ident.local_index.get();
|
|
|
|
|
match ident.local_type.get() {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(LocalType::Variable) => generator.mark_local_initialized(local_index),
|
|
|
|
|
Some(LocalType::Argument) => {
|
2026-02-27 05:54:44 -03:00
|
|
|
generator.mark_argument_initialized(local_index);
|
2026-02-24 08:36:14 -03:00
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
None => {}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&ident.name);
|
2026-02-23 07:50:46 -03:00
|
|
|
if has_duplicates {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::SetLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: Operand::argument(parameter_index),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeLexicalBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: Operand::argument(parameter_index),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
FunctionParameterBinding::BindingPattern(pattern) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
let argument = generator.scoped_operand(Operand::argument(parameter_index));
|
2026-02-23 07:50:46 -03:00
|
|
|
let mode = if has_duplicates {
|
|
|
|
|
BindingMode::Set
|
|
|
|
|
} else {
|
|
|
|
|
BindingMode::InitializeLexical
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
generate_binding_pattern_bytecode(generator, pattern, mode, &argument);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 5: Initialize var bindings ---
|
|
|
|
|
|
|
|
|
|
if let Some(fsd) = function_scope_data {
|
|
|
|
|
if !has_parameter_expressions {
|
|
|
|
|
// Simple case: vars share the parameter environment.
|
|
|
|
|
for var in &fsd.vars_to_initialize {
|
|
|
|
|
if var.is_parameter {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if arguments_object_needed && var.name == utf16!("arguments") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(local_binding) = var.local {
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
2026-02-24 06:40:18 -03:00
|
|
|
let local =
|
2026-02-24 08:36:14 -03:00
|
|
|
var_local_operand(generator, local_binding.local_type, local_binding.index);
|
|
|
|
|
generator.emit_mov(&local, &undef);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&var.name);
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Var as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeVariableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: undef.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Parameter expressions: vars get a separate environment.
|
|
|
|
|
let has_non_local_vars = fsd.vars_to_initialize.iter().any(|v| v.local.is_none());
|
|
|
|
|
|
|
|
|
|
if has_non_local_vars {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::CreateVariableEnvironment {
|
2026-03-01 10:09:36 -03:00
|
|
|
capacity: u32_from_usize(var_environment_bindings_count),
|
2026-02-23 07:50:46 -03:00
|
|
|
});
|
|
|
|
|
// After CreateVariableEnvironment, re-read the lexical environment
|
|
|
|
|
// (which was also updated) and push it onto the register stack.
|
|
|
|
|
// This ensures subsequent CreateLexicalEnvironment instructions
|
|
|
|
|
// (e.g. Step 7) use the var environment as their parent, not the
|
|
|
|
|
// parameter scope.
|
2026-02-24 08:36:14 -03:00
|
|
|
let var_env = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetLexicalEnvironment {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: var_env.operand(),
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.lexical_environment_register_stack.push(var_env);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for var in &fsd.vars_to_initialize {
|
|
|
|
|
let is_in_parameter_bindings = var.is_parameter
|
|
|
|
|
|| (arguments_object_needed && var.name == utf16!("arguments"));
|
|
|
|
|
|
|
|
|
|
let initial_value = if !is_in_parameter_bindings || var.is_function_name {
|
2026-02-24 08:36:14 -03:00
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit_mov(&value, &undef);
|
2026-02-23 07:50:46 -03:00
|
|
|
value
|
|
|
|
|
} else if let Some(local_binding) = var.local {
|
2026-02-24 06:40:18 -03:00
|
|
|
let local =
|
2026-02-24 08:36:14 -03:00
|
|
|
var_local_operand(generator, local_binding.local_type, local_binding.index);
|
|
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
generator.emit_mov(&value, &local);
|
2026-02-23 07:50:46 -03:00
|
|
|
value
|
|
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&var.name);
|
|
|
|
|
let value = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::GetBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: value.operand(),
|
|
|
|
|
identifier: id,
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
value
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Some(local_binding) = var.local {
|
2026-02-24 06:40:18 -03:00
|
|
|
let local =
|
2026-02-24 08:36:14 -03:00
|
|
|
var_local_operand(generator, local_binding.local_type, local_binding.index);
|
|
|
|
|
generator.emit_mov(&local, &initial_value);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&var.name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Var as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.emit(Instruction::InitializeVariableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: initial_value.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 6: AnnexB function name bindings (non-strict only) ---
|
|
|
|
|
if !strict {
|
|
|
|
|
let var_names = function_scope_data.map(|fsd| &fsd.var_names);
|
|
|
|
|
for name in &body_scope.annexb_function_names {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.annexb_function_names.insert(name.clone());
|
2026-02-23 07:50:46 -03:00
|
|
|
// Skip creating a var binding if this name is already declared as a var.
|
|
|
|
|
if var_names.is_some_and(|names| names.contains(name)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Var as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let undef = generator.add_constant_undefined();
|
|
|
|
|
generator.emit(Instruction::InitializeVariableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: undef.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 7: Lexical environment for non-local declarations ---
|
|
|
|
|
// Note: this counts only let/const/class declarations (not function declarations,
|
2026-03-20 00:26:33 -03:00
|
|
|
// which are var-hoisted in function bodies).
|
2026-02-23 07:50:46 -03:00
|
|
|
let lex_bindings_count = count_non_local_lexical_bindings(body_scope);
|
|
|
|
|
|
|
|
|
|
if !strict && lex_bindings_count > 0 {
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.push_new_lexical_environment(lex_bindings_count);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 8: Create lexical bindings ---
|
|
|
|
|
|
|
|
|
|
for child in &body_scope.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 {
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: is_constant,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: is_constant,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
StatementKind::ClassDeclaration(class_data) => {
|
|
|
|
|
// Class declarations are lexically scoped (like const).
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(ref name_ident) = class_data.name
|
|
|
|
|
&& !name_ident.is_local()
|
|
|
|
|
{
|
|
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::CreateVariable {
|
|
|
|
|
identifier: id,
|
|
|
|
|
mode: EnvironmentMode::Lexical as u32,
|
|
|
|
|
is_immutable: false,
|
|
|
|
|
is_global: false,
|
|
|
|
|
is_strict: false,
|
|
|
|
|
});
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Step 9: Initialize hoisted function declarations ---
|
|
|
|
|
|
|
|
|
|
if let Some(fsd) = function_scope_data {
|
|
|
|
|
for function_to_init in &fsd.functions_to_initialize {
|
|
|
|
|
let child = &body_scope.children[function_to_init.child_index];
|
2026-02-24 06:40:18 -03:00
|
|
|
if let StatementKind::FunctionDeclaration {
|
|
|
|
|
function_id,
|
|
|
|
|
ref name,
|
|
|
|
|
..
|
|
|
|
|
} = child.inner
|
|
|
|
|
{
|
2026-02-24 08:36:14 -03:00
|
|
|
let inner_function_data = generator.function_table.take(function_id);
|
|
|
|
|
let sfd_index = emit_new_function(generator, inner_function_data, None);
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// Check if the function name identifier is local.
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(name_ident) = name {
|
2026-02-23 07:50:46 -03:00
|
|
|
if name_ident.is_local() {
|
|
|
|
|
let local_index = name_ident.local_index.get();
|
2026-02-24 08:36:14 -03:00
|
|
|
let local = generator.local(local_index);
|
|
|
|
|
generator.emit(Instruction::NewFunction {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: local.operand(),
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
home_object: None,
|
|
|
|
|
lhs_name: None,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
generator.mark_local_initialized(local_index);
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-24 08:36:14 -03:00
|
|
|
let function_reg = generator.allocate_register();
|
|
|
|
|
generator.emit(Instruction::NewFunction {
|
2026-02-23 07:50:46 -03:00
|
|
|
dst: function_reg.operand(),
|
|
|
|
|
shared_function_data_index: sfd_index,
|
|
|
|
|
home_object: None,
|
|
|
|
|
lhs_name: None,
|
|
|
|
|
});
|
2026-02-24 08:36:14 -03:00
|
|
|
let id = generator.intern_identifier(&name_ident.name);
|
|
|
|
|
generator.emit(Instruction::SetVariableBinding {
|
2026-02-23 07:50:46 -03:00
|
|
|
identifier: id,
|
|
|
|
|
src: function_reg.operand(),
|
|
|
|
|
cache: EnvironmentCoordinate::empty(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a statement is a for-loop variant (for, for-in, for-of, for-await-of).
|
|
|
|
|
fn is_for_loop(statement: &Statement) -> bool {
|
|
|
|
|
matches!(
|
|
|
|
|
statement.inner,
|
2026-02-24 06:40:18 -03:00
|
|
|
StatementKind::For { .. } | StatementKind::ForInOf { .. }
|
2026-02-23 07:50:46 -03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a block needs block declaration instantiation.
|
|
|
|
|
/// True when the block has function declarations or non-local let/const/class.
|
|
|
|
|
fn needs_block_declaration_instantiation(scope: &ScopeData) -> bool {
|
|
|
|
|
for child in &scope.children {
|
|
|
|
|
match &child.inner {
|
|
|
|
|
StatementKind::FunctionDeclaration { .. } => {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
StatementKind::VariableDeclaration { kind, declarations } => {
|
|
|
|
|
if *kind == DeclarationKind::Let || *kind == DeclarationKind::Const {
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
|
|
|
|
if !names.is_empty() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
StatementKind::ClassDeclaration(class_data) => {
|
2026-02-24 08:36:14 -03:00
|
|
|
if let Some(ref name_ident) = class_data.name
|
|
|
|
|
&& !name_ident.is_local()
|
|
|
|
|
{
|
|
|
|
|
return true;
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-03 18:27:21 -03:00
|
|
|
StatementKind::UsingDeclaration { declarations } => {
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
|
|
|
|
if !names.is_empty() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Count non-local lexical bindings in a function body scope.
|
|
|
|
|
fn count_non_local_lexical_bindings(scope: &ScopeData) -> u32 {
|
|
|
|
|
let mut count = 0u32;
|
|
|
|
|
for child in &scope.children {
|
|
|
|
|
match &child.inner {
|
|
|
|
|
StatementKind::VariableDeclaration { kind, declarations } => {
|
|
|
|
|
if *kind == DeclarationKind::Let || *kind == DeclarationKind::Const {
|
|
|
|
|
for declaration in declarations {
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
collect_target_names(&declaration.target, &mut names);
|
|
|
|
|
count += u32_from_usize(names.len());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
StatementKind::ClassDeclaration(class_data) => {
|
|
|
|
|
if class_data.name.as_ref().is_some_and(|n| !n.is_local()) {
|
|
|
|
|
count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a ScopedOperand for a VarToInit's local variable (argument or variable).
|
2026-02-24 08:36:14 -03:00
|
|
|
fn var_local_operand(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
local_type: LocalType,
|
|
|
|
|
index: u32,
|
|
|
|
|
) -> ScopedOperand {
|
|
|
|
|
generator.resolve_local(index, local_type)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Collect bound names from a binding pattern into the parameter_names list.
|
|
|
|
|
fn collect_binding_pattern_names(
|
|
|
|
|
pattern: &BindingPattern,
|
|
|
|
|
parameter_names: &mut Vec<FdiParameterName>,
|
|
|
|
|
seen_names: &mut HashSet<Utf16String>,
|
|
|
|
|
has_duplicates: &mut bool,
|
|
|
|
|
) {
|
|
|
|
|
for entry in &pattern.entries {
|
|
|
|
|
// The bound name can be in the alias (for object patterns) or name (for array patterns).
|
|
|
|
|
match &entry.alias {
|
|
|
|
|
Some(BindingEntryAlias::Identifier(ident)) => {
|
|
|
|
|
let name = ident.name.clone();
|
|
|
|
|
let is_local = ident.is_local();
|
|
|
|
|
if !seen_names.insert(name.clone()) {
|
|
|
|
|
*has_duplicates = true;
|
|
|
|
|
} else {
|
|
|
|
|
parameter_names.push(FdiParameterName { name, is_local });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::BindingPattern(sub_pattern)) => {
|
2026-02-24 06:40:18 -03:00
|
|
|
collect_binding_pattern_names(
|
|
|
|
|
sub_pattern,
|
|
|
|
|
parameter_names,
|
|
|
|
|
seen_names,
|
|
|
|
|
has_duplicates,
|
|
|
|
|
);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
// No alias — the name itself is the binding.
|
|
|
|
|
if let Some(BindingEntryName::Identifier(ident)) = &entry.name {
|
|
|
|
|
let name = ident.name.clone();
|
|
|
|
|
let is_local = ident.is_local();
|
|
|
|
|
if !seen_names.insert(name.clone()) {
|
|
|
|
|
*has_duplicates = true;
|
|
|
|
|
} else {
|
|
|
|
|
parameter_names.push(FdiParameterName { name, is_local });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(BindingEntryAlias::MemberExpression(_)) => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A parameter name with its locality (used during FDI).
|
|
|
|
|
struct FdiParameterName {
|
|
|
|
|
name: Utf16String,
|
|
|
|
|
is_local: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Builtin IDs matching JS_ENUMERATE_BUILTINS in Builtins.h.
|
|
|
|
|
const BUILTIN_MATH_ABS: u8 = 0;
|
|
|
|
|
const BUILTIN_MATH_LOG: u8 = 1;
|
|
|
|
|
const BUILTIN_MATH_POW: u8 = 2;
|
|
|
|
|
const BUILTIN_MATH_EXP: u8 = 3;
|
|
|
|
|
const BUILTIN_MATH_CEIL: u8 = 4;
|
|
|
|
|
const BUILTIN_MATH_FLOOR: u8 = 5;
|
|
|
|
|
const BUILTIN_MATH_IMUL: u8 = 6;
|
|
|
|
|
const BUILTIN_MATH_RANDOM: u8 = 7;
|
|
|
|
|
const BUILTIN_MATH_ROUND: u8 = 8;
|
|
|
|
|
const BUILTIN_MATH_SQRT: u8 = 9;
|
|
|
|
|
const BUILTIN_MATH_SIN: u8 = 10;
|
|
|
|
|
const BUILTIN_MATH_COS: u8 = 11;
|
|
|
|
|
const BUILTIN_MATH_TAN: u8 = 12;
|
|
|
|
|
const BUILTIN_REGEXP_PROTOTYPE_EXEC: u8 = 13;
|
|
|
|
|
const BUILTIN_REGEXP_PROTOTYPE_REPLACE: u8 = 14;
|
|
|
|
|
const BUILTIN_REGEXP_PROTOTYPE_SPLIT: u8 = 15;
|
|
|
|
|
const BUILTIN_ORDINARY_HAS_INSTANCE: u8 = 16;
|
|
|
|
|
const BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT: u8 = 17;
|
|
|
|
|
const BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT: u8 = 18;
|
|
|
|
|
const BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT: u8 = 19;
|
|
|
|
|
const BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT: u8 = 20;
|
|
|
|
|
|
|
|
|
|
/// Detect known builtin methods from a callee expression (e.g. Math.abs).
|
2026-03-20 00:26:33 -03:00
|
|
|
/// Returns the Builtin enum value as u8, matching Builtins.h ordering.
|
2026-02-23 07:50:46 -03:00
|
|
|
fn get_builtin(callee: &Expression) -> Option<u8> {
|
2026-03-22 15:14:03 -03:00
|
|
|
let ExpressionKind::Member(member_data) = &callee.inner else {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
};
|
2026-03-22 15:14:03 -03:00
|
|
|
if member_data.computed {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
let ExpressionKind::Identifier(base_ident) = &member_data.object.inner else {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
};
|
2026-03-22 15:14:03 -03:00
|
|
|
let ExpressionKind::Identifier(property_ident) = &member_data.property.inner else {
|
2026-02-23 07:50:46 -03:00
|
|
|
return None;
|
|
|
|
|
};
|
|
|
|
|
// Must match JS_ENUMERATE_BUILTINS order in Builtins.h.
|
|
|
|
|
static BUILTINS: &[(&[u16], &[u16], u8)] = &[
|
|
|
|
|
(utf16!("Math"), utf16!("abs"), BUILTIN_MATH_ABS),
|
|
|
|
|
(utf16!("Math"), utf16!("log"), BUILTIN_MATH_LOG),
|
|
|
|
|
(utf16!("Math"), utf16!("pow"), BUILTIN_MATH_POW),
|
|
|
|
|
(utf16!("Math"), utf16!("exp"), BUILTIN_MATH_EXP),
|
|
|
|
|
(utf16!("Math"), utf16!("ceil"), BUILTIN_MATH_CEIL),
|
|
|
|
|
(utf16!("Math"), utf16!("floor"), BUILTIN_MATH_FLOOR),
|
|
|
|
|
(utf16!("Math"), utf16!("imul"), BUILTIN_MATH_IMUL),
|
|
|
|
|
(utf16!("Math"), utf16!("random"), BUILTIN_MATH_RANDOM),
|
|
|
|
|
(utf16!("Math"), utf16!("round"), BUILTIN_MATH_ROUND),
|
|
|
|
|
(utf16!("Math"), utf16!("sqrt"), BUILTIN_MATH_SQRT),
|
|
|
|
|
(utf16!("Math"), utf16!("sin"), BUILTIN_MATH_SIN),
|
|
|
|
|
(utf16!("Math"), utf16!("cos"), BUILTIN_MATH_COS),
|
|
|
|
|
(utf16!("Math"), utf16!("tan"), BUILTIN_MATH_TAN),
|
2026-02-24 06:40:18 -03:00
|
|
|
(
|
|
|
|
|
utf16!("RegExpPrototype"),
|
|
|
|
|
utf16!("exec"),
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_EXEC,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("RegExpPrototype"),
|
|
|
|
|
utf16!("replace"),
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_REPLACE,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("RegExpPrototype"),
|
|
|
|
|
utf16!("split"),
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_SPLIT,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("InternalBuiltin"),
|
|
|
|
|
utf16!("ordinary_has_instance"),
|
|
|
|
|
BUILTIN_ORDINARY_HAS_INSTANCE,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("ArrayIteratorPrototype"),
|
|
|
|
|
utf16!("next"),
|
|
|
|
|
BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("MapIteratorPrototype"),
|
|
|
|
|
utf16!("next"),
|
|
|
|
|
BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("SetIteratorPrototype"),
|
|
|
|
|
utf16!("next"),
|
|
|
|
|
BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT,
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
utf16!("StringIteratorPrototype"),
|
|
|
|
|
utf16!("next"),
|
|
|
|
|
BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT,
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
];
|
|
|
|
|
for &(base, property, id) in BUILTINS {
|
|
|
|
|
if base_ident.name == base && property_ident.name == property {
|
|
|
|
|
return Some(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn builtin_argument_count(builtin: u8) -> usize {
|
|
|
|
|
// Must match JS_ENUMERATE_BUILTINS argument counts in Builtins.h.
|
|
|
|
|
match builtin {
|
|
|
|
|
BUILTIN_MATH_ABS => 1,
|
|
|
|
|
BUILTIN_MATH_LOG => 1,
|
|
|
|
|
BUILTIN_MATH_POW => 2,
|
|
|
|
|
BUILTIN_MATH_EXP => 1,
|
|
|
|
|
BUILTIN_MATH_CEIL => 1,
|
|
|
|
|
BUILTIN_MATH_FLOOR => 1,
|
|
|
|
|
BUILTIN_MATH_IMUL => 2,
|
|
|
|
|
BUILTIN_MATH_RANDOM => 0,
|
|
|
|
|
BUILTIN_MATH_ROUND => 1,
|
|
|
|
|
BUILTIN_MATH_SQRT => 1,
|
|
|
|
|
BUILTIN_MATH_SIN => 1,
|
|
|
|
|
BUILTIN_MATH_COS => 1,
|
|
|
|
|
BUILTIN_MATH_TAN => 1,
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_EXEC => 1,
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_REPLACE => 2,
|
|
|
|
|
BUILTIN_REGEXP_PROTOTYPE_SPLIT => 2,
|
|
|
|
|
BUILTIN_ORDINARY_HAS_INSTANCE => 1,
|
|
|
|
|
BUILTIN_ARRAY_ITERATOR_PROTOTYPE_NEXT => 0,
|
|
|
|
|
BUILTIN_MAP_ITERATOR_PROTOTYPE_NEXT => 0,
|
|
|
|
|
BUILTIN_SET_ITERATOR_PROTOTYPE_NEXT => 0,
|
|
|
|
|
BUILTIN_STRING_ITERATOR_PROTOTYPE_NEXT => 0,
|
|
|
|
|
_ => usize::MAX,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// JS ToInt32 conversion (ECMA-262 7.1.6).
|
|
|
|
|
fn to_int32(n: f64) -> i32 {
|
|
|
|
|
if n.is_nan() || n.is_infinite() || n == 0.0 {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
let int_val = n.signum() * n.abs().floor();
|
|
|
|
|
let int32bit = int_val % 4294967296.0; // 2^32
|
2026-02-24 06:40:18 -03:00
|
|
|
let int32bit = if int32bit < 0.0 {
|
|
|
|
|
int32bit + 4294967296.0
|
|
|
|
|
} else {
|
|
|
|
|
int32bit
|
|
|
|
|
};
|
2026-02-23 07:50:46 -03:00
|
|
|
if int32bit >= 2147483648.0 {
|
|
|
|
|
(int32bit - 4294967296.0) as i32
|
|
|
|
|
} else {
|
|
|
|
|
int32bit as i32
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// JS ToUint32 conversion (ECMA-262 7.1.7).
|
|
|
|
|
fn to_u32(n: f64) -> u32 {
|
|
|
|
|
if n.is_nan() || n.is_infinite() || n == 0.0 {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
let int_val = n.signum() * n.abs().floor();
|
|
|
|
|
let int32bit = int_val % 4294967296.0; // 2^32
|
2026-02-24 06:40:18 -03:00
|
|
|
if int32bit < 0.0 {
|
|
|
|
|
(int32bit + 4294967296.0) as u32
|
|
|
|
|
} else {
|
|
|
|
|
int32bit as u32
|
|
|
|
|
}
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a string key is a numeric index (valid u32 < u32::MAX).
|
|
|
|
|
/// Numeric indices are stored in indexed storage rather than shape-based storage.
|
|
|
|
|
fn is_numeric_index_key(key: &[u16]) -> bool {
|
|
|
|
|
if key.is_empty() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Exclude leading zeros in multi-digit strings (e.g., "01")
|
|
|
|
|
if key[0] == ch(b'0') && key.len() > 1 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Must be all ASCII digits
|
|
|
|
|
if !key.iter().all(|&c| c >= ch(b'0') && c <= ch(b'9')) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Parse as u64 first to avoid overflow, then check < u32::MAX
|
|
|
|
|
let mut n: u64 = 0;
|
|
|
|
|
for &c in key {
|
|
|
|
|
n = n * 10 + (c - ch(b'0')) as u64;
|
|
|
|
|
if n > u32::MAX as u64 {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(n as u32) < u32::MAX
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Constant folding
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Try to constant-fold a unary operation when the operand is a constant.
|
|
|
|
|
fn try_constant_fold_unary(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: UnaryOp,
|
|
|
|
|
operand: &ScopedOperand,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let constant = generator.get_constant(operand)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
match op {
|
|
|
|
|
UnaryOp::Minus => {
|
|
|
|
|
let n = constant_to_number(constant)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(-n))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
UnaryOp::Plus => {
|
|
|
|
|
let n = constant_to_number(constant)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(n))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
UnaryOp::BitwiseNot => {
|
|
|
|
|
if let ConstantValue::BigInt(s) = constant {
|
|
|
|
|
let n = parse_bigint(&s.clone())?;
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_bigint((-(n + BigInt::one())).to_string()));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let n = constant_to_number(constant)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((!to_int32(n)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
UnaryOp::Not => {
|
|
|
|
|
let as_bool = constant_to_boolean(constant)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(!as_bool))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constant-fold !!x when x is a constant, returning Boolean(x).
|
|
|
|
|
fn try_constant_fold_to_boolean(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
operand: &ScopedOperand,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let constant = generator.get_constant(operand)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
let as_bool = constant_to_boolean(constant)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(as_bool))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Implement IsLooselyEqual for constant values.
|
|
|
|
|
/// https://tc39.es/ecma262/#sec-islooselyequal
|
|
|
|
|
fn try_constant_loosely_equals(lhs: &ConstantValue, rhs: &ConstantValue) -> Option<bool> {
|
|
|
|
|
// Same type: use strict equality rules.
|
|
|
|
|
match (lhs, rhs) {
|
2026-02-27 06:47:19 -03:00
|
|
|
(
|
|
|
|
|
ConstantValue::Null | ConstantValue::Undefined,
|
|
|
|
|
ConstantValue::Null | ConstantValue::Undefined,
|
|
|
|
|
) => return Some(true),
|
|
|
|
|
(ConstantValue::Null | ConstantValue::Undefined, _)
|
|
|
|
|
| (_, ConstantValue::Null | ConstantValue::Undefined) => return Some(false),
|
2026-02-23 07:50:46 -03:00
|
|
|
(ConstantValue::Number(a), ConstantValue::Number(b)) => return Some(a == b),
|
|
|
|
|
(ConstantValue::String(a), ConstantValue::String(b)) => return Some(a == b),
|
|
|
|
|
(ConstantValue::Boolean(a), ConstantValue::Boolean(b)) => return Some(a == b),
|
|
|
|
|
(ConstantValue::BigInt(a), ConstantValue::BigInt(b)) => return Some(a == b),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
// Cross-type comparisons: Boolean → Number first, then retry.
|
|
|
|
|
match (lhs, rhs) {
|
|
|
|
|
(ConstantValue::Boolean(b), _) => {
|
|
|
|
|
let coerced = ConstantValue::Number(if *b { 1.0 } else { 0.0 });
|
|
|
|
|
return try_constant_loosely_equals(&coerced, rhs);
|
|
|
|
|
}
|
|
|
|
|
(_, ConstantValue::Boolean(b)) => {
|
|
|
|
|
let coerced = ConstantValue::Number(if *b { 1.0 } else { 0.0 });
|
|
|
|
|
return try_constant_loosely_equals(lhs, &coerced);
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
// Number == String → compare ToNumber(string) to number.
|
|
|
|
|
// String == Number → compare number to ToNumber(string).
|
|
|
|
|
match (lhs, rhs) {
|
|
|
|
|
(ConstantValue::Number(n), ConstantValue::String(s))
|
2026-02-24 06:40:18 -03:00
|
|
|
| (ConstantValue::String(s), ConstantValue::Number(n)) => Some(*n == string_to_number(s)),
|
2026-02-23 07:50:46 -03:00
|
|
|
// BigInt == Number or Number == BigInt: compare mathematical values.
|
|
|
|
|
(ConstantValue::BigInt(b), ConstantValue::Number(n))
|
|
|
|
|
| (ConstantValue::Number(n), ConstantValue::BigInt(b)) => {
|
|
|
|
|
if n.is_nan() || n.is_infinite() {
|
|
|
|
|
return Some(false);
|
|
|
|
|
}
|
|
|
|
|
if n.fract() != 0.0 {
|
|
|
|
|
return Some(false);
|
|
|
|
|
}
|
|
|
|
|
let bi = parse_bigint(b)?;
|
|
|
|
|
// Compare: the number must be a safe integer that equals the BigInt.
|
|
|
|
|
// Only fold if the f64 value fits in i64 range for lossless conversion.
|
|
|
|
|
if *n > i64::MAX as f64 || *n < i64::MIN as f64 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let n_i64 = *n as i64;
|
|
|
|
|
if n_i64 as f64 != *n {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
Some(BigInt::from(n_i64) == bi)
|
|
|
|
|
}
|
|
|
|
|
// BigInt == String or String == BigInt: parse string as BigInt per StringToBigInt.
|
|
|
|
|
// If the string cannot be parsed, the result is false (not equal).
|
|
|
|
|
(ConstantValue::BigInt(b), ConstantValue::String(s))
|
2026-02-24 06:40:18 -03:00
|
|
|
| (ConstantValue::String(s), ConstantValue::BigInt(b)) => match string_to_bigint(s) {
|
|
|
|
|
Some(s_bi) => {
|
|
|
|
|
let bi = parse_bigint(b)?;
|
|
|
|
|
Some(bi == s_bi)
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
None => Some(false),
|
|
|
|
|
},
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Implements StringToBigInt per https://tc39.es/ecma262/#sec-stringtobigint.
|
|
|
|
|
/// Trims whitespace, handles optional sign (decimal only), and handles
|
|
|
|
|
/// 0b/0o/0x prefixes. Returns None if the string is not a valid
|
|
|
|
|
/// StringIntegerLiteral.
|
|
|
|
|
fn string_to_bigint(s: &Utf16String) -> Option<BigInt> {
|
|
|
|
|
use num_bigint::BigInt;
|
|
|
|
|
let s_utf8: String = char::decode_utf16(s.0.iter().copied())
|
|
|
|
|
.map(|r| r.unwrap_or('\u{FFFD}'))
|
|
|
|
|
.collect();
|
|
|
|
|
let s_trimmed = s_utf8.trim();
|
|
|
|
|
if s_trimmed.is_empty() {
|
|
|
|
|
return Some(BigInt::from(0));
|
|
|
|
|
}
|
|
|
|
|
// Check for non-decimal prefixes (no sign allowed).
|
|
|
|
|
if s_trimmed.len() > 2 {
|
|
|
|
|
let (prefix, rest) = s_trimmed.split_at(2);
|
|
|
|
|
match prefix {
|
|
|
|
|
"0b" | "0B" => return BigInt::parse_bytes(rest.as_bytes(), 2),
|
|
|
|
|
"0o" | "0O" => return BigInt::parse_bytes(rest.as_bytes(), 8),
|
|
|
|
|
"0x" | "0X" => return BigInt::parse_bytes(rest.as_bytes(), 16),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Decimal with optional sign. Only allow digits (no dots, no exponents).
|
|
|
|
|
let (is_negative, digits) = if let Some(rest) = s_trimmed.strip_prefix('-') {
|
|
|
|
|
(true, rest)
|
|
|
|
|
} else if let Some(rest) = s_trimmed.strip_prefix('+') {
|
|
|
|
|
(false, rest)
|
|
|
|
|
} else {
|
|
|
|
|
(false, s_trimmed)
|
|
|
|
|
};
|
|
|
|
|
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let bi = BigInt::parse_bytes(digits.as_bytes(), 10)?;
|
|
|
|
|
Some(if is_negative { -bi } else { bi })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constant-fold a binary operation on two BigInt operands.
|
|
|
|
|
fn try_constant_fold_bigint_binary(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: BinaryOp,
|
|
|
|
|
a_str: &str,
|
|
|
|
|
b_str: &str,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
|
|
|
|
let a = parse_bigint(a_str)?;
|
|
|
|
|
let b = parse_bigint(b_str)?;
|
|
|
|
|
match op {
|
|
|
|
|
// Arithmetic operations: produce a BigInt result.
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::Addition => Some(generator.add_constant_bigint((&a + &b).to_string())),
|
|
|
|
|
BinaryOp::Subtraction => Some(generator.add_constant_bigint((&a - &b).to_string())),
|
|
|
|
|
BinaryOp::Multiplication => Some(generator.add_constant_bigint((&a * &b).to_string())),
|
2026-02-23 07:50:46 -03:00
|
|
|
BinaryOp::Division => {
|
|
|
|
|
if b.is_zero() {
|
|
|
|
|
return None; // Division by zero throws at runtime.
|
|
|
|
|
}
|
|
|
|
|
use num_integer::Integer;
|
|
|
|
|
let (quotient, _) = a.div_rem(&b);
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint(quotient.to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Modulo => {
|
|
|
|
|
if b.is_zero() {
|
|
|
|
|
return None; // Modulo by zero throws at runtime.
|
|
|
|
|
}
|
|
|
|
|
// JS BigInt remainder has the sign of the dividend (truncated division).
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint((&a % &b).to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Exponentiation => {
|
|
|
|
|
if b.is_negative() {
|
|
|
|
|
return None; // Negative exponent throws at runtime.
|
|
|
|
|
}
|
|
|
|
|
let exp = b.to_u32()?;
|
|
|
|
|
// Only fold small exponents to avoid huge results.
|
|
|
|
|
if exp > 1000 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint(num_traits::pow::pow(a, exp as usize).to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// Comparison operations: produce a Boolean result.
|
|
|
|
|
BinaryOp::StrictlyEquals | BinaryOp::LooselyEquals => {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(a == b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::StrictlyInequals | BinaryOp::LooselyInequals => {
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(a != b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::LessThan => Some(generator.add_constant_boolean(a < b)),
|
|
|
|
|
BinaryOp::LessThanEquals => Some(generator.add_constant_boolean(a <= b)),
|
|
|
|
|
BinaryOp::GreaterThan => Some(generator.add_constant_boolean(a > b)),
|
|
|
|
|
BinaryOp::GreaterThanEquals => Some(generator.add_constant_boolean(a >= b)),
|
2026-02-23 07:50:46 -03:00
|
|
|
// Bitwise operations on BigInt.
|
2026-02-24 08:36:14 -03:00
|
|
|
BinaryOp::BitwiseAnd => Some(generator.add_constant_bigint((&a & &b).to_string())),
|
|
|
|
|
BinaryOp::BitwiseOr => Some(generator.add_constant_bigint((&a | &b).to_string())),
|
|
|
|
|
BinaryOp::BitwiseXor => Some(generator.add_constant_bigint((&a ^ &b).to_string())),
|
2026-02-23 07:50:46 -03:00
|
|
|
BinaryOp::LeftShift => {
|
|
|
|
|
let shift = b.to_u64()?;
|
|
|
|
|
if shift > 512 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint((&a << shift as usize).to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::RightShift => {
|
|
|
|
|
let shift = b.to_u64()?;
|
|
|
|
|
// BigInt right shift for negative numbers floors toward negative infinity.
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_bigint(bigint_right_shift(&a, shift as usize).to_string()))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// UnsignedRightShift throws TypeError for BigInt.
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// BigInt arithmetic right shift that floors toward negative infinity
|
|
|
|
|
/// (matching JS spec 6.1.6.2.9 BigInt::signedRightShift).
|
|
|
|
|
fn bigint_right_shift(value: &BigInt, shift: usize) -> BigInt {
|
|
|
|
|
if !value.is_negative() || shift == 0 {
|
|
|
|
|
return value >> shift;
|
|
|
|
|
}
|
|
|
|
|
// For negative values, we need floor division behavior.
|
|
|
|
|
// Check if any of the shifted-out bits are set.
|
|
|
|
|
let divisor = BigInt::one() << shift;
|
|
|
|
|
use num_integer::Integer;
|
|
|
|
|
value.div_floor(&divisor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Try to constant-fold a binary operation when both operands are constants.
|
|
|
|
|
// 6.1.6.1.3 Number::exponentiate ( base, exponent )
|
|
|
|
|
// https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate
|
|
|
|
|
// Rust's f64::powf follows C's pow() which returns 1.0 for pow(±1, ±∞),
|
|
|
|
|
// but JS specifies NaN when abs(base) is 1 and exponent is ±∞.
|
|
|
|
|
fn js_exponentiate(base: f64, exponent: f64) -> f64 {
|
|
|
|
|
if exponent.is_infinite() && base.abs() == 1.0 {
|
|
|
|
|
return f64::NAN;
|
|
|
|
|
}
|
|
|
|
|
base.powf(exponent)
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 00:26:33 -03:00
|
|
|
/// Parse a non-decimal integer string via BigInt to f64, matching
|
2026-02-23 07:50:46 -03:00
|
|
|
/// UnsignedBigInteger::from_base() + to_double(). This avoids u64 overflow
|
|
|
|
|
/// for large literals like 0x10000000000000000.
|
|
|
|
|
fn bigint_string_to_f64(s: &str, radix: u32) -> f64 {
|
|
|
|
|
use num_bigint::BigUint;
|
|
|
|
|
match BigUint::parse_bytes(s.as_bytes(), radix) {
|
|
|
|
|
Some(n) => {
|
|
|
|
|
use num_traits::ToPrimitive;
|
|
|
|
|
n.to_f64().unwrap_or(f64::INFINITY)
|
|
|
|
|
}
|
|
|
|
|
None => f64::NAN,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 7.1.4.1.1 StringToNumber ( str ), https://tc39.es/ecma262/#sec-stringtonumber
|
|
|
|
|
fn string_to_number(s: &Utf16String) -> f64 {
|
|
|
|
|
let text: String = char::decode_utf16(s.0.iter().copied())
|
|
|
|
|
.map(|r| r.unwrap_or('\u{FFFD}'))
|
|
|
|
|
.collect();
|
|
|
|
|
let trimmed = text.trim();
|
|
|
|
|
if trimmed.is_empty() {
|
|
|
|
|
return 0.0;
|
|
|
|
|
}
|
|
|
|
|
if trimmed == "Infinity" || trimmed == "+Infinity" {
|
|
|
|
|
return f64::INFINITY;
|
|
|
|
|
}
|
|
|
|
|
if trimmed == "-Infinity" {
|
|
|
|
|
return f64::NEG_INFINITY;
|
|
|
|
|
}
|
|
|
|
|
if trimmed.len() > 2 {
|
|
|
|
|
let (prefix, rest) = trimmed.split_at(2);
|
|
|
|
|
match prefix {
|
|
|
|
|
"0b" | "0B" => {
|
|
|
|
|
return if rest.bytes().all(|b| b == b'0' || b == b'1') {
|
|
|
|
|
bigint_string_to_f64(rest, 2)
|
|
|
|
|
} else {
|
|
|
|
|
f64::NAN
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
"0o" | "0O" => {
|
|
|
|
|
return if rest.bytes().all(|b| b.is_ascii_digit() && b < b'8') {
|
|
|
|
|
bigint_string_to_f64(rest, 8)
|
|
|
|
|
} else {
|
|
|
|
|
f64::NAN
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
"0x" | "0X" => {
|
|
|
|
|
return if rest.bytes().all(|b| b.is_ascii_hexdigit()) {
|
|
|
|
|
bigint_string_to_f64(rest, 16)
|
|
|
|
|
} else {
|
|
|
|
|
f64::NAN
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
if !trimmed.bytes().all(|b| {
|
|
|
|
|
b.is_ascii_digit() || b == b'.' || b == b'e' || b == b'E' || b == b'+' || b == b'-'
|
|
|
|
|
}) {
|
2026-02-23 07:50:46 -03:00
|
|
|
return f64::NAN;
|
|
|
|
|
}
|
|
|
|
|
trimmed.parse::<f64>().unwrap_or(f64::NAN)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert a constant value to a JS number for constant folding purposes.
|
|
|
|
|
fn constant_to_number(val: &ConstantValue) -> Option<f64> {
|
|
|
|
|
match val {
|
|
|
|
|
ConstantValue::Number(n) => Some(*n),
|
|
|
|
|
ConstantValue::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
|
|
|
|
|
ConstantValue::Null => Some(0.0),
|
|
|
|
|
ConstantValue::Undefined => Some(f64::NAN),
|
|
|
|
|
ConstantValue::String(s) => Some(string_to_number(s)),
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Convert a constant value to a JS string (ToString) for string concatenation.
|
|
|
|
|
/// Returns None for types where ToString requires runtime (e.g. objects).
|
|
|
|
|
fn constant_to_string(val: &ConstantValue) -> Option<Utf16String> {
|
|
|
|
|
match val {
|
|
|
|
|
ConstantValue::String(s) => Some(s.clone()),
|
|
|
|
|
ConstantValue::Number(n) => Some(super::ffi::js_number_to_utf16(*n)),
|
2026-02-24 06:40:18 -03:00
|
|
|
ConstantValue::Boolean(b) => Some(if *b {
|
|
|
|
|
Utf16String(utf16!("true").to_vec())
|
|
|
|
|
} else {
|
|
|
|
|
Utf16String(utf16!("false").to_vec())
|
|
|
|
|
}),
|
2026-02-23 07:50:46 -03:00
|
|
|
ConstantValue::Null => Some(Utf16String(utf16!("null").to_vec())),
|
|
|
|
|
ConstantValue::Undefined => Some(Utf16String(utf16!("undefined").to_vec())),
|
|
|
|
|
ConstantValue::BigInt(s) => {
|
|
|
|
|
// BigInt.prototype.toString() returns the decimal string representation.
|
|
|
|
|
Some(Utf16String(s.encode_utf16().collect()))
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Compare a BigInt and a Number using Abstract Relational Comparison semantics.
|
|
|
|
|
/// Returns None if comparison cannot be folded (non-safe integer, etc).
|
|
|
|
|
/// Returns Some(None) for NaN (undefined result - all comparisons return false).
|
|
|
|
|
/// Returns Some(Some(Ordering)) for the BigInt relative to the Number.
|
|
|
|
|
fn compare_bigint_and_number(bigint_str: &str, number: f64) -> Option<Option<std::cmp::Ordering>> {
|
|
|
|
|
use std::cmp::Ordering;
|
|
|
|
|
if number.is_nan() {
|
|
|
|
|
return Some(None); // Comparisons with NaN return undefined → false.
|
|
|
|
|
}
|
|
|
|
|
if number == f64::INFINITY {
|
|
|
|
|
return Some(Some(Ordering::Less)); // Any BigInt < +Infinity
|
|
|
|
|
}
|
|
|
|
|
if number == f64::NEG_INFINITY {
|
|
|
|
|
return Some(Some(Ordering::Greater)); // Any BigInt > -Infinity
|
|
|
|
|
}
|
|
|
|
|
let bigint = parse_bigint(bigint_str)?;
|
|
|
|
|
// Only fold if the f64 value is a safe integer for lossless comparison.
|
|
|
|
|
if number.fract() != 0.0 {
|
|
|
|
|
// The number has a fractional part, so we can still compare:
|
|
|
|
|
// floor(number) < bigint means bigint > number, etc.
|
|
|
|
|
let floored = number.floor();
|
|
|
|
|
if floored > i64::MAX as f64 || floored < i64::MIN as f64 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let floored_i64 = floored as i64;
|
|
|
|
|
let floored_bigint = BigInt::from(floored_i64);
|
|
|
|
|
if bigint <= floored_bigint {
|
|
|
|
|
// bigint <= floor(number) means bigint < number
|
|
|
|
|
return Some(Some(Ordering::Less));
|
|
|
|
|
} else {
|
|
|
|
|
// bigint > floor(number) means bigint > number (since number = floor + fract where fract > 0)
|
|
|
|
|
return Some(Some(Ordering::Greater));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if number > i64::MAX as f64 || number < i64::MIN as f64 {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let number_i64 = number as i64;
|
|
|
|
|
if number_i64 as f64 != number {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
let number_bigint = BigInt::from(number_i64);
|
|
|
|
|
Some(Some(bigint.cmp(&number_bigint)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn try_constant_fold_binary(
|
2026-02-24 08:36:14 -03:00
|
|
|
generator: &mut Generator,
|
2026-02-23 07:50:46 -03:00
|
|
|
op: BinaryOp,
|
|
|
|
|
lhs: &ScopedOperand,
|
|
|
|
|
rhs: &ScopedOperand,
|
|
|
|
|
) -> Option<ScopedOperand> {
|
2026-02-24 08:36:14 -03:00
|
|
|
let lhs_const = generator.get_constant(lhs)?;
|
|
|
|
|
let rhs_const = generator.get_constant(rhs)?;
|
2026-02-23 07:50:46 -03:00
|
|
|
|
|
|
|
|
// BigInt constant folding: if both operands are BigInt, handle separately.
|
|
|
|
|
// Clone strings to release the immutable borrow on gen before the mutable call.
|
|
|
|
|
if let (ConstantValue::BigInt(a), ConstantValue::BigInt(b)) = (lhs_const, rhs_const) {
|
|
|
|
|
let a = a.clone();
|
|
|
|
|
let b = b.clone();
|
2026-02-24 08:36:14 -03:00
|
|
|
return try_constant_fold_bigint_binary(generator, op, &a, &b);
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match op {
|
|
|
|
|
BinaryOp::Addition => {
|
|
|
|
|
// If either operand is a string, do string concatenation using ToString.
|
2026-02-24 06:40:18 -03:00
|
|
|
if matches!(lhs_const, ConstantValue::String(_))
|
|
|
|
|
|| matches!(rhs_const, ConstantValue::String(_))
|
|
|
|
|
{
|
2026-02-23 07:50:46 -03:00
|
|
|
let a = constant_to_string(lhs_const)?;
|
|
|
|
|
let b = constant_to_string(rhs_const)?;
|
|
|
|
|
let mut result = a;
|
|
|
|
|
result.0.extend_from_slice(&b);
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_string(result));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// Numeric addition: both operands coerced to number
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(a + b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Subtraction => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(a - b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Multiplication => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(a * b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Division => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(a / b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Modulo => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(a % b))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::Exponentiation => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number(js_exponentiate(a, b)))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::StrictlyEquals | BinaryOp::StrictlyInequals => {
|
|
|
|
|
let equal = match (lhs_const, rhs_const) {
|
|
|
|
|
(ConstantValue::Number(a), ConstantValue::Number(b)) => a == b,
|
|
|
|
|
(ConstantValue::String(a), ConstantValue::String(b)) => a == b,
|
|
|
|
|
(ConstantValue::Boolean(a), ConstantValue::Boolean(b)) => a == b,
|
|
|
|
|
(ConstantValue::Null, ConstantValue::Null) => true,
|
|
|
|
|
(ConstantValue::Undefined, ConstantValue::Undefined) => true,
|
|
|
|
|
// Different types are never strictly equal.
|
2026-02-24 06:40:18 -03:00
|
|
|
_ => false,
|
|
|
|
|
};
|
|
|
|
|
let result = if op == BinaryOp::StrictlyInequals {
|
|
|
|
|
!equal
|
|
|
|
|
} else {
|
|
|
|
|
equal
|
2026-02-23 07:50:46 -03:00
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(result))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::GreaterThan
|
|
|
|
|
| BinaryOp::GreaterThanEquals
|
|
|
|
|
| BinaryOp::LessThan
|
|
|
|
|
| BinaryOp::LessThanEquals => {
|
|
|
|
|
// String-string comparison is lexicographic (by UTF-16 code units).
|
|
|
|
|
if let (ConstantValue::String(a), ConstantValue::String(b)) = (lhs_const, rhs_const) {
|
|
|
|
|
let result = match op {
|
|
|
|
|
BinaryOp::GreaterThan => a.0 > b.0,
|
|
|
|
|
BinaryOp::GreaterThanEquals => a.0 >= b.0,
|
|
|
|
|
BinaryOp::LessThan => a.0 < b.0,
|
|
|
|
|
BinaryOp::LessThanEquals => a.0 <= b.0,
|
|
|
|
|
_ => unreachable!("outer match arm only matches comparison operators"),
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_boolean(result));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
// BigInt-Number/Boolean cross-type comparison.
|
|
|
|
|
// Per spec, Booleans are converted to Number first.
|
|
|
|
|
let lhs_for_cmp = match lhs_const {
|
|
|
|
|
ConstantValue::Boolean(b) => &ConstantValue::Number(if *b { 1.0 } else { 0.0 }),
|
|
|
|
|
other => other,
|
|
|
|
|
};
|
|
|
|
|
let rhs_for_cmp = match rhs_const {
|
|
|
|
|
ConstantValue::Boolean(b) => &ConstantValue::Number(if *b { 1.0 } else { 0.0 }),
|
|
|
|
|
other => other,
|
|
|
|
|
};
|
|
|
|
|
// BigInt vs Number comparison using Abstract Relational Comparison.
|
|
|
|
|
let bigint_ord = match (lhs_for_cmp, rhs_for_cmp) {
|
|
|
|
|
(ConstantValue::BigInt(b), ConstantValue::Number(n)) => {
|
|
|
|
|
compare_bigint_and_number(b, *n)
|
|
|
|
|
}
|
|
|
|
|
(ConstantValue::Number(n), ConstantValue::BigInt(b)) => {
|
|
|
|
|
compare_bigint_and_number(b, *n).map(|o| o.map(|o| o.reverse()))
|
|
|
|
|
}
|
|
|
|
|
// BigInt vs String: per spec, parse the string as a BigInt
|
|
|
|
|
// using StringToBigInt, then compare the two BigInts.
|
|
|
|
|
// If the string is not a valid StringIntegerLiteral,
|
|
|
|
|
// the result is undefined (= false for all comparisons).
|
2026-02-24 06:40:18 -03:00
|
|
|
(ConstantValue::BigInt(b), ConstantValue::String(s)) => match string_to_bigint(s) {
|
|
|
|
|
Some(rhs_bi) => {
|
|
|
|
|
let lhs_bi = parse_bigint(b)?;
|
|
|
|
|
Some(Some(lhs_bi.cmp(&rhs_bi)))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
None => Some(None),
|
|
|
|
|
},
|
|
|
|
|
(ConstantValue::String(s), ConstantValue::BigInt(b)) => match string_to_bigint(s) {
|
|
|
|
|
Some(lhs_bi) => {
|
|
|
|
|
let rhs_bi = parse_bigint(b)?;
|
|
|
|
|
Some(Some(lhs_bi.cmp(&rhs_bi)))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
2026-02-24 06:40:18 -03:00
|
|
|
None => Some(None),
|
|
|
|
|
},
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
if let Some(ord) = bigint_ord {
|
|
|
|
|
let result = match op {
|
|
|
|
|
BinaryOp::GreaterThan => matches!(ord, Some(std::cmp::Ordering::Greater)),
|
2026-02-24 06:40:18 -03:00
|
|
|
BinaryOp::GreaterThanEquals => matches!(
|
|
|
|
|
ord,
|
|
|
|
|
Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
BinaryOp::LessThan => matches!(ord, Some(std::cmp::Ordering::Less)),
|
2026-02-24 06:40:18 -03:00
|
|
|
BinaryOp::LessThanEquals => matches!(
|
|
|
|
|
ord,
|
|
|
|
|
Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
|
|
|
|
|
),
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => unreachable!("outer match arm only matches comparison operators"),
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
return Some(generator.add_constant_boolean(result));
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
let a = constant_to_number(lhs_for_cmp)?;
|
|
|
|
|
let b = constant_to_number(rhs_for_cmp)?;
|
|
|
|
|
let result = match op {
|
|
|
|
|
BinaryOp::GreaterThan => a > b,
|
|
|
|
|
BinaryOp::GreaterThanEquals => a >= b,
|
|
|
|
|
BinaryOp::LessThan => a < b,
|
|
|
|
|
BinaryOp::LessThanEquals => a <= b,
|
|
|
|
|
_ => unreachable!("outer match arm only matches comparison operators"),
|
|
|
|
|
};
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(result))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::LooselyEquals => {
|
|
|
|
|
let result = try_constant_loosely_equals(lhs_const, rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(result))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::LooselyInequals => {
|
|
|
|
|
let result = try_constant_loosely_equals(lhs_const, rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_boolean(!result))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::BitwiseAnd => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_int32(a) & to_int32(b)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::BitwiseOr => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_int32(a) | to_int32(b)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::BitwiseXor => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_int32(a) ^ to_int32(b)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::LeftShift => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_int32(a) << (to_u32(b) & 0x1f)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::RightShift => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_int32(a) >> (to_u32(b) & 0x1f)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
BinaryOp::UnsignedRightShift => {
|
|
|
|
|
let a = constant_to_number(lhs_const)?;
|
|
|
|
|
let b = constant_to_number(rhs_const)?;
|
2026-02-24 08:36:14 -03:00
|
|
|
Some(generator.add_constant_number((to_u32(a) >> (to_u32(b) & 0x1f)) as f64))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// NaN-boxing helpers
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
// NanBoxed Value encoding helpers (ABI-compatible with GC::NanBoxedValue).
|
|
|
|
|
// Used by NewPrimitiveArray to encode constant primitive values inline.
|
|
|
|
|
const NANBOX_TAG_SHIFT: u64 = 48;
|
|
|
|
|
const NANBOX_BASE_TAG: u64 = 0x7FF8;
|
|
|
|
|
const NANBOX_INT32_TAG: u64 = 0b010 | NANBOX_BASE_TAG;
|
|
|
|
|
const NANBOX_BOOLEAN_TAG: u64 = 0b001 | NANBOX_BASE_TAG;
|
|
|
|
|
const NANBOX_NULL_TAG: u64 = 0b111 | NANBOX_BASE_TAG;
|
|
|
|
|
const NANBOX_EMPTY_TAG: u64 = 0b011 | NANBOX_BASE_TAG;
|
|
|
|
|
const NEGATIVE_ZERO_BITS: u64 = 1u64 << 63;
|
|
|
|
|
|
|
|
|
|
fn nanboxed_number(value: f64) -> u64 {
|
|
|
|
|
let is_negative_zero = value.to_bits() == NEGATIVE_ZERO_BITS;
|
|
|
|
|
if value >= i32::MIN as f64
|
|
|
|
|
&& value <= i32::MAX as f64
|
|
|
|
|
&& value.trunc() == value
|
|
|
|
|
&& !is_negative_zero
|
|
|
|
|
{
|
|
|
|
|
(NANBOX_INT32_TAG << NANBOX_TAG_SHIFT) | ((value as i32 as u32) as u64)
|
|
|
|
|
} else if value.is_nan() {
|
|
|
|
|
// Canon NaN
|
|
|
|
|
0x7FF8_0000_0000_0000u64
|
|
|
|
|
} else {
|
|
|
|
|
value.to_bits()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nanboxed_boolean(value: bool) -> u64 {
|
|
|
|
|
(NANBOX_BOOLEAN_TAG << NANBOX_TAG_SHIFT) | (value as u64)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nanboxed_null() -> u64 {
|
|
|
|
|
NANBOX_NULL_TAG << NANBOX_TAG_SHIFT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn nanboxed_empty() -> u64 {
|
|
|
|
|
NANBOX_EMPTY_TAG << NANBOX_TAG_SHIFT
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Error message utilities
|
|
|
|
|
// =============================================================================
|
|
|
|
|
|
|
|
|
|
/// Intern the base expression as an identifier for error messages like
|
|
|
|
|
/// "Cannot access property X on null object Y".
|
2026-02-24 08:36:14 -03:00
|
|
|
fn intern_base_identifier(
|
|
|
|
|
generator: &mut Generator,
|
|
|
|
|
base: &Expression,
|
|
|
|
|
) -> Option<IdentifierTableIndex> {
|
|
|
|
|
expression_identifier(base).map(|s| generator.intern_identifier(&s))
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Try to produce a human-readable name for an expression (for error messages).
|
|
|
|
|
/// Returns None for expressions that have no meaningful name.
|
|
|
|
|
fn expression_identifier(expression: &Expression) -> Option<Utf16String> {
|
|
|
|
|
match &expression.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => Some(ident.name.clone()),
|
|
|
|
|
ExpressionKind::StringLiteral(s) => {
|
|
|
|
|
let mut result = Utf16String(utf16!("'").to_vec());
|
|
|
|
|
result.0.extend_from_slice(s);
|
|
|
|
|
result.0.extend_from_slice(utf16!("'"));
|
|
|
|
|
Some(result)
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => Some(super::ffi::js_number_to_utf16(*n)),
|
|
|
|
|
ExpressionKind::This => Some(Utf16String(utf16!("this").to_vec())),
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
2026-02-23 07:50:46 -03:00
|
|
|
let mut s = Utf16String::new();
|
2026-03-22 15:14:03 -03:00
|
|
|
if let Some(obj_id) = expression_identifier(&data.object) {
|
2026-02-23 07:50:46 -03:00
|
|
|
s.0.extend_from_slice(&obj_id);
|
|
|
|
|
}
|
2026-03-22 15:14:03 -03:00
|
|
|
if let Some(property_id) = expression_identifier(&data.property) {
|
|
|
|
|
if data.computed {
|
2026-02-23 07:50:46 -03:00
|
|
|
s.0.extend_from_slice(utf16!("["));
|
|
|
|
|
s.0.extend_from_slice(&property_id);
|
|
|
|
|
s.0.extend_from_slice(utf16!("]"));
|
|
|
|
|
} else {
|
|
|
|
|
s.0.extend_from_slice(utf16!("."));
|
|
|
|
|
s.0.extend_from_slice(&property_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(s)
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Produce a human-readable string for call expression error messages.
|
|
|
|
|
/// Unlike expression_identifier, this always produces output for known types
|
|
|
|
|
/// (using "<object>" for unrecognized sub-expressions).
|
|
|
|
|
fn expression_string_approximation(expression: &Expression) -> Option<Utf16String> {
|
|
|
|
|
match &expression.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => Some(ident.name.clone()),
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(_) => Some(member_to_string_approximation(expression)),
|
2026-02-23 07:50:46 -03:00
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn member_to_string_approximation(expression: &Expression) -> Utf16String {
|
|
|
|
|
match &expression.inner {
|
|
|
|
|
ExpressionKind::Identifier(ident) => ident.name.clone(),
|
2026-03-22 15:14:03 -03:00
|
|
|
ExpressionKind::Member(data) => {
|
|
|
|
|
let mut s = member_to_string_approximation(&data.object);
|
|
|
|
|
let property_str = member_to_string_approximation(&data.property);
|
|
|
|
|
if data.computed {
|
2026-02-23 07:50:46 -03:00
|
|
|
s.0.extend_from_slice(utf16!("["));
|
|
|
|
|
s.0.extend_from_slice(&property_str);
|
|
|
|
|
s.0.extend_from_slice(utf16!("]"));
|
|
|
|
|
} else {
|
|
|
|
|
s.0.extend_from_slice(utf16!("."));
|
|
|
|
|
s.0.extend_from_slice(&property_str);
|
|
|
|
|
}
|
|
|
|
|
s
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::StringLiteral(s) => {
|
|
|
|
|
let mut result = Utf16String(utf16!("'").to_vec());
|
|
|
|
|
result.0.extend_from_slice(s);
|
|
|
|
|
result.0.extend_from_slice(utf16!("'"));
|
|
|
|
|
result
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::NumericLiteral(n) => {
|
|
|
|
|
let s = format_double_for_display(*n);
|
|
|
|
|
s.encode_utf16().collect()
|
|
|
|
|
}
|
|
|
|
|
ExpressionKind::This => Utf16String(utf16!("this").to_vec()),
|
|
|
|
|
ExpressionKind::PrivateIdentifier(ident) => ident.name.clone(),
|
|
|
|
|
_ => Utf16String(utf16!("<object>").to_vec()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Format a double matching AK's `Utf16String::formatted("{}", double)`.
|
|
|
|
|
/// Uses ECMA-262 rules: scientific notation when the decimal exponent n
|
|
|
|
|
/// satisfies n < -5 or n > 21, otherwise regular decimal notation.
|
|
|
|
|
fn format_double_for_display(n: f64) -> String {
|
|
|
|
|
if n.is_nan() {
|
|
|
|
|
return "NaN".to_string();
|
|
|
|
|
}
|
|
|
|
|
if n.is_infinite() {
|
|
|
|
|
return if n > 0.0 { "Infinity" } else { "-Infinity" }.to_string();
|
|
|
|
|
}
|
|
|
|
|
if n == 0.0 {
|
|
|
|
|
return "0".to_string();
|
|
|
|
|
}
|
|
|
|
|
// Get the scientific notation representation to extract the exponent.
|
2026-02-27 05:50:35 -03:00
|
|
|
let e_str = format!("{n:e}");
|
2026-02-23 07:50:46 -03:00
|
|
|
if let Some(e_pos) = e_str.find('e') {
|
|
|
|
|
let exp_str = &e_str[e_pos + 1..];
|
|
|
|
|
let displayed_exponent = exp_str.parse::<i32>().unwrap_or(0);
|
|
|
|
|
// AK uses: n < -5 || n > 21 where n = displayed_exponent + 1.
|
|
|
|
|
// Equivalently: displayed_exponent < -6 || displayed_exponent > 20.
|
|
|
|
|
if !(-6..=20).contains(&displayed_exponent) {
|
|
|
|
|
let mantissa_part = &e_str[..e_pos];
|
|
|
|
|
if displayed_exponent < 0 {
|
2026-02-27 05:50:35 -03:00
|
|
|
return format!("{mantissa_part}e{displayed_exponent}");
|
2026-02-23 07:50:46 -03:00
|
|
|
} else {
|
2026-02-27 05:50:35 -03:00
|
|
|
return format!("{mantissa_part}e+{displayed_exponent}");
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-27 05:50:35 -03:00
|
|
|
format!("{n}")
|
2026-02-23 07:50:46 -03:00
|
|
|
}
|