diff --git a/Libraries/LibJS/Rust/src/ast.rs b/Libraries/LibJS/Rust/src/ast.rs index 1ae90fa398..4a33d17134 100644 --- a/Libraries/LibJS/Rust/src/ast.rs +++ b/Libraries/LibJS/Rust/src/ast.rs @@ -640,6 +640,10 @@ impl FunctionTable { pub struct FunctionPayload { pub data: FunctionData, pub function_table: FunctionTable, + /// Shared access to the program-wide identifier/scope/string tables. + /// Each lazy-compile SFD carries an Arc clone so it can resolve its + /// identifier IDs without depending on a parent generator. + pub arena: Arc, } // ============================================================================= @@ -1037,14 +1041,14 @@ pub struct FunctionParameter { #[derive(Clone, Debug)] pub enum FunctionParameterBinding { - Identifier(Rc), + Identifier(IdentifierId), BindingPattern(BindingPattern), } /// Shared data for FunctionDeclaration and FunctionExpression. #[derive(Debug)] pub struct FunctionData { - pub name: Option>, + pub name: Option, pub source_text_start: u32, pub source_text_end: u32, pub body: Box, @@ -1069,7 +1073,7 @@ pub struct FunctionData { /// Shared data for ClassDeclaration and ClassExpression. #[derive(Clone, Debug)] pub struct ClassData { - pub name: Option>, + pub name: Option, pub source_text_start: u32, pub source_text_end: u32, pub constructor: Option>, @@ -1152,7 +1156,7 @@ pub struct BindingEntry { /// - `Expression`: computed property key (`{ [expression]: x }`) #[derive(Clone, Debug)] pub enum BindingEntryName { - Identifier(Rc), + Identifier(IdentifierId), Expression(Box), } @@ -1163,7 +1167,7 @@ pub enum BindingEntryName { /// - `MemberExpression`: assignment target (`{ x: obj.property }`) #[derive(Clone, Debug)] pub enum BindingEntryAlias { - Identifier(Rc), + Identifier(IdentifierId), BindingPattern(Box), MemberExpression(Box), } @@ -1181,7 +1185,7 @@ pub struct VariableDeclarator { #[derive(Clone, Debug)] pub enum VariableDeclaratorTarget { - Identifier(Rc), + Identifier(IdentifierId), BindingPattern(BindingPattern), } @@ -1254,7 +1258,7 @@ pub enum OptionalChainReference { mode: OptionalChainMode, }, MemberReference { - identifier: Rc, + identifier: IdentifierId, mode: OptionalChainMode, }, PrivateMemberReference { @@ -1349,7 +1353,7 @@ pub struct CatchClause { #[derive(Clone, Debug)] pub enum CatchBinding { - Identifier(Rc), + Identifier(IdentifierId), BindingPattern(BindingPattern), } @@ -1646,7 +1650,7 @@ pub enum ExpressionKind { RegExpLiteral(Box), // Identifiers - Identifier(Rc), + Identifier(IdentifierId), PrivateIdentifier(Box), // Operators @@ -1754,7 +1758,7 @@ pub struct VariableDeclarationData { #[derive(Clone, Debug)] pub struct FunctionDeclarationData { pub function_id: FunctionId, - pub name: Option>, + pub name: Option, pub kind: FunctionKind, pub is_hoisted: Cell, } diff --git a/Libraries/LibJS/Rust/src/ast_dump.rs b/Libraries/LibJS/Rust/src/ast_dump.rs index 8cf4e8fac1..e742a17ce6 100644 --- a/Libraries/LibJS/Rust/src/ast_dump.rs +++ b/Libraries/LibJS/Rust/src/ast_dump.rs @@ -68,12 +68,16 @@ struct DumpState<'a> { use_color: bool, output: Option<&'a RefCell>, function_table: &'a FunctionTable, + identifiers: &'a crate::ast::IdentifierArena, } impl DumpState<'_> { fn function_table(&self) -> &FunctionTable { self.function_table } + fn identifier(&self, id: crate::ast::IdentifierId) -> &crate::ast::Identifier { + &self.identifiers[id] + } } fn print_node(state: &DumpState, text: &str) { @@ -118,6 +122,7 @@ fn child_state<'a>(state: &DumpState<'a>, is_last: bool) -> DumpState<'a> { use_color: state.use_color, output: state.output, function_table: state.function_table, + identifiers: state.identifiers, } } @@ -303,7 +308,12 @@ fn dump_labeled_statement(label: &str, statement: &Statement, is_last: bool, sta // Entry point // ============================================================================ -pub fn dump_program(program: &Statement, use_color: bool, function_table: &FunctionTable) { +pub fn dump_program( + program: &Statement, + use_color: bool, + function_table: &FunctionTable, + identifiers: &crate::ast::IdentifierArena, +) { let state = DumpState { prefix: String::new(), is_last: false, @@ -311,12 +321,17 @@ pub fn dump_program(program: &Statement, use_color: bool, function_table: &Funct use_color, output: None, function_table, + identifiers, }; dump_statement(program, &state); println!(); } -pub fn dump_program_to_string(program: &Statement, function_table: &FunctionTable) -> String { +pub fn dump_program_to_string( + program: &Statement, + function_table: &FunctionTable, + identifiers: &crate::ast::IdentifierArena, +) -> String { let output = RefCell::new(String::new()); let state = DumpState { prefix: String::new(), @@ -325,6 +340,7 @@ pub fn dump_program_to_string(program: &Statement, function_table: &FunctionTabl use_color: false, output: Some(&output), function_table, + identifiers, }; dump_statement(program, &state); output.into_inner() @@ -682,7 +698,7 @@ fn dump_expression(expression: &Expression, state: &DumpState) { } ExpressionKind::Identifier(ident) => { - dump_identifier(ident, &expression.range, state); + dump_identifier(state.identifier(*ident), &expression.range, state); } ExpressionKind::PrivateIdentifier(ident) => { @@ -798,7 +814,7 @@ fn dump_expression(expression: &Expression, state: &DumpState) { } OptionalChainReference::MemberReference { identifier, mode } => { print_node(&ref_state, &format!("MemberReference({})", optional_mode_str(*mode))); - dump_identifier(identifier, &identifier.range, &child_state(&ref_state, true)); + dump_identifier_id(*identifier, &child_state(&ref_state, true)); } OptionalChainReference::PrivateMemberReference { private_identifier, @@ -942,6 +958,12 @@ fn dump_expression(expression: &Expression, state: &DumpState) { // Identifier dumper // ============================================================================ +fn dump_identifier_id(id: crate::ast::IdentifierId, state: &DumpState) { + let ident = state.identifier(id); + let range = ident.range; + dump_identifier(ident, &range, state); +} + fn dump_identifier(ident: &Identifier, range: &SourceRange, state: &DumpState) { let mut desc = color_node_name(state, "Identifier"); desc.push_str(&format!(" {}", color_string_utf16(state, &ident.name))); @@ -990,8 +1012,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR if is_generator { desc.push('*'); } - let name_str = match &function_data.name { - Some(ident) => utf16_to_string(&ident.name), + let name_str = match function_data.name { + Some(id) => utf16_to_string(&state.identifier(id).name), None => String::new(), }; desc.push_str(&format!(" {}", color_string(state, &name_str))); @@ -1025,8 +1047,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR if parameter.is_rest { print_node(¶meter_state, &color_label(state, "rest")); match ¶meter.binding { - FunctionParameterBinding::Identifier(ident) => { - dump_identifier(ident, &ident.range, &child_state(¶meter_state, !has_default)); + FunctionParameterBinding::Identifier(id) => { + dump_identifier_id(*id, &child_state(¶meter_state, !has_default)); } FunctionParameterBinding::BindingPattern(pattern) => { dump_binding_pattern(pattern, &child_state(¶meter_state, !has_default), state); @@ -1034,10 +1056,9 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR } } else { match ¶meter.binding { - FunctionParameterBinding::Identifier(ident) => { - dump_identifier( - ident, - &ident.range, + FunctionParameterBinding::Identifier(id) => { + dump_identifier_id( + *id, &child_state(¶meters_state, i == function_data.parameters.len() - 1), ); } @@ -1065,8 +1086,8 @@ fn dump_function(function_data: &FunctionData, class_name: &str, range: &SourceR } fn dump_class(class_data: &ClassData, range: &SourceRange, state: &DumpState, root_state: &DumpState) { - let name_str = match &class_data.name { - Some(ident) => utf16_to_string(&ident.name), + let name_str = match class_data.name { + Some(id) => utf16_to_string(&state.identifier(id).name), None => String::new(), }; print_node( @@ -1205,9 +1226,8 @@ fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state: &child_state(&entry_state, !has_alias && !has_initializer), &color_label(root_state, "name"), ); - dump_identifier( - ident, - &ident.range, + dump_identifier_id( + *ident, &child_state(&child_state(&entry_state, !has_alias && !has_initializer), true), ); } @@ -1232,11 +1252,7 @@ fn dump_binding_pattern(pattern: &BindingPattern, state: &DumpState, root_state: ); match alias { BindingEntryAlias::Identifier(ident) => { - dump_identifier( - ident, - &ident.range, - &child_state(&child_state(&entry_state, !has_initializer), true), - ); + dump_identifier_id(*ident, &child_state(&child_state(&entry_state, !has_initializer), true)); } BindingEntryAlias::BindingPattern(sub) => { dump_binding_pattern( @@ -1283,7 +1299,7 @@ fn dump_variable_declarator(declaration: &VariableDeclarator, state: &DumpState, let has_init = declaration.init.is_some(); match &declaration.target { VariableDeclaratorTarget::Identifier(ident) => { - dump_identifier(ident, &ident.range, &child_state(state, !has_init)); + dump_identifier_id(*ident, &child_state(state, !has_init)); } VariableDeclaratorTarget::BindingPattern(pattern) => { dump_binding_pattern(pattern, &child_state(state, !has_init), root_state); @@ -1337,7 +1353,7 @@ fn dump_catch_clause(clause: &CatchClause, state: &DumpState, root_state: &DumpS match parameter { CatchBinding::Identifier(ident) => { print_node(&child_state(state, false), &color_label(root_state, "parameter")); - dump_identifier(ident, &ident.range, &child_state(&child_state(state, false), true)); + dump_identifier_id(*ident, &child_state(&child_state(state, false), true)); } CatchBinding::BindingPattern(pattern) => { print_node(&child_state(state, false), &color_label(root_state, "parameter")); diff --git a/Libraries/LibJS/Rust/src/bytecode/codegen.rs b/Libraries/LibJS/Rust/src/bytecode/codegen.rs index ad8e946e85..e18e153063 100644 --- a/Libraries/LibJS/Rust/src/bytecode/codegen.rs +++ b/Libraries/LibJS/Rust/src/bytecode/codegen.rs @@ -117,7 +117,7 @@ fn generate_expression_inner( } // === Identifiers === - ExpressionKind::Identifier(ident) => Some(generate_identifier(ident, generator, preferred_dst)), + ExpressionKind::Identifier(ident) => Some(generate_identifier(*ident, generator, preferred_dst)), // === This === ExpressionKind::This => { @@ -324,11 +324,12 @@ fn generate_unary_expression( // evaluating the operand to avoid throwing on unresolvable references. // Allocate dst before evaluating typeof/not operands. if op == UnaryOp::Typeof { + let arena = generator.arena.clone(); if let ExpressionKind::Identifier(ident) = &operand.inner - && !ident.is_local() + && !arena.identifiers[*ident].is_local() { let dst = choose_dst(generator, preferred_dst); - let id = generator.intern_identifier(&ident.name); + let id = generator.intern_identifier(&arena.identifiers[*ident].name); generator.emit(Instruction::TypeofBinding { dst: dst.operand(), identifier: id, @@ -538,7 +539,9 @@ fn generate_function_expression( }); generator.lexical_environment_register_stack.push(new_env); - let id = generator.intern_identifier(&data.name.as_ref().expect("function declaration must have a name").name); + let name_id = data.name.expect("function declaration must have a name"); + let arena = generator.arena.clone(); + let id = generator.intern_identifier(&arena.identifiers[name_id].name); generator.emit(Instruction::CreateVariable { identifier: id, mode: EnvironmentMode::Lexical as u32, @@ -711,7 +714,14 @@ fn generate_member_expression( if let Some(key) = computed_key { emit_get_by_value_with_this(generator, &dst, &super_base, &key, &this_value); } else if let ExpressionKind::Identifier(ident) = &property.inner { - emit_get_by_id_with_this(generator, &dst, &super_base, &ident.name, &this_value); + let arena = generator.arena.clone(); + emit_get_by_id_with_this( + generator, + &dst, + &super_base, + &arena.identifiers[*ident].name, + &this_value, + ); } return Some(dst); } @@ -727,7 +737,8 @@ fn generate_member_expression( // Non-computed: property must be an Identifier let dst = choose_dst(generator, preferred_dst); if let ExpressionKind::Identifier(ident) = &property.inner { - emit_get_by_id(generator, &dst, &obj, &ident.name, base_id); + let arena = generator.arena.clone(); + emit_get_by_id(generator, &dst, &obj, &arena.identifiers[*ident].name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &property.inner { let id = generator.intern_identifier(&priv_ident.name); generator.emit(Instruction::GetPrivateById { @@ -985,8 +996,9 @@ pub fn generate_statement( if fd.is_hoisted.get() { // Annex B.3.3: Copy the function from the lexical (block) scope // to the var scope. - if let Some(name_ident) = &fd.name { - let id = generator.intern_identifier(&name_ident.name); + if let Some(name_ident) = fd.name { + let arena = generator.arena.clone(); + let id = generator.intern_identifier(&arena.identifiers[name_ident].name); let value = generator.allocate_register(); generator.emit(Instruction::GetBinding { dst: value.operand(), @@ -1052,7 +1064,9 @@ pub fn generate_statement( // (temporal dead zone) until this point, matching `let` semantics. // NB: We do NOT mark the local as initialized here, preserving // TDZ checks for subsequent uses of the class name. - if let Some(name_ident) = &data.name { + if let Some(name_ident_id) = data.name { + let arena = generator.arena.clone(); + let name_ident = &arena.identifiers[name_ident_id]; if name_ident.is_local() { let local = generator.resolve_local(name_ident.local_index.get(), name_ident.local_type.get().unwrap()); @@ -1730,10 +1744,12 @@ fn generate_yield( /// - **Global**: GetGlobal instruction (with inline cache) /// - **Environment**: GetBinding/GetInitializedBinding (with environment coordinate cache) fn generate_identifier( - ident: &Identifier, + id: crate::ast::IdentifierId, generator: &mut Generator, preferred_dst: Option<&ScopedOperand>, ) -> ScopedOperand { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[id]; if ident.is_local() { let local_index = ident.local_index.get(); let local = generator.resolve_local(local_index, ident.local_type.get().unwrap()); @@ -2328,7 +2344,7 @@ fn generate_for_statement( { let mut non_local_names: Vec<(Utf16String, bool)> = Vec::new(); for declaration in &vd.declarations { - collect_target_names(&declaration.target, &mut non_local_names); + collect_target_names(&declaration.target, &mut non_local_names, &generator.arena.identifiers); } if !non_local_names.is_empty() { has_lexical_environment = true; @@ -2588,7 +2604,7 @@ fn emit_lexical_declarations_for_block<'a>( let is_constant = vd.kind == DeclarationKind::Const; for declaration in &vd.declarations { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, &generator.arena.identifiers); for (name, _) in &names { let id = generator.intern_identifier(name); if is_constant { @@ -2610,7 +2626,7 @@ fn emit_lexical_declarations_for_block<'a>( StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, &generator.arena.identifiers); for (name, _) in &names { let id = generator.intern_identifier(name); generator.emit(Instruction::CreateImmutableBinding { @@ -2622,19 +2638,23 @@ fn emit_lexical_declarations_for_block<'a>( } } StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name_ident) = class_data.name - && !name_ident.is_local() - { - let id = generator.intern_identifier(&name_ident.name); - generator.emit(Instruction::CreateMutableBinding { - environment: environment.operand(), - identifier: id, - can_be_deleted: false, - }); + if let Some(name_ident_id) = class_data.name { + let arena = generator.arena.clone(); + let name_ident = &arena.identifiers[name_ident_id]; + if !name_ident.is_local() { + let id = generator.intern_identifier(&name_ident.name); + generator.emit(Instruction::CreateMutableBinding { + environment: environment.operand(), + identifier: id, + can_be_deleted: false, + }); + } } } StatementKind::FunctionDeclaration(fd) if fd.name.is_some() => { - let name_ident = fd.name.as_ref().unwrap(); + let name_ident_id = fd.name.unwrap(); + let arena = generator.arena.clone(); + let name_ident = &arena.identifiers[name_ident_id]; // a. Create binding. if !name_ident.is_local() { let id = generator.intern_identifier(&name_ident.name); @@ -2674,7 +2694,7 @@ fn emit_lexical_declarations_for_block<'a>( } fn emit_block_declaration_instantiation(generator: &mut Generator, scope: &ScopeData) -> bool { - if !needs_block_declaration_instantiation(scope) { + if !needs_block_declaration_instantiation(scope, &generator.arena.identifiers) { return false; } @@ -2695,13 +2715,15 @@ fn generate_variable_declaration( declarations: &[VariableDeclarator], ) { for declaration in declarations { + let arena = generator.arena.clone(); // 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 let VariableDeclaratorTarget::Identifier(id) = &declaration.target { + let ident = &arena.identifiers[*id]; if ident.is_local() && ident.local_type.get() == Some(LocalType::Variable) { Some(generator.local(ident.local_index.get())) } else { @@ -2715,7 +2737,8 @@ fn generate_variable_declaration( }; // Set pending LHS name for function name inference. - if let VariableDeclaratorTarget::Identifier(ident) = &declaration.target { + if let VariableDeclaratorTarget::Identifier(id) = &declaration.target { + let ident = &arena.identifiers[*id]; generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name)); } let init_value = declaration @@ -2725,7 +2748,8 @@ fn generate_variable_declaration( generator.pending_lhs_name = None; match &declaration.target { - VariableDeclaratorTarget::Identifier(ident) => { + VariableDeclaratorTarget::Identifier(id) => { + let ident = &arena.identifiers[*id]; // 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 { @@ -2794,8 +2818,9 @@ fn try_generate_builtin_abstract_operation( if !generator.builtin_abstract_operations_enabled { return None; } + let arena = generator.arena.clone(); let name = match &data.callee.inner { - ExpressionKind::Identifier(ident) => &ident.name, + ExpressionKind::Identifier(ident) => &arena.identifiers[*ident].name, _ => return None, }; if data.arguments.iter().any(|a| a.is_spread) { @@ -2921,8 +2946,9 @@ fn try_generate_builtin_abstract_operation( let val = generate_expression_or_undefined(&argument.value, generator, None); argument_holders.push(generator.copy_if_needed_to_preserve_evaluation_order(&val)); } - let callee_name = - expression_string_approximation(&data.arguments[0].value).map(|s| generator.intern_string(&s)); + let arena_clone = generator.arena.clone(); + let callee_name = expression_string_approximation(&data.arguments[0].value, &arena_clone.identifiers) + .map(|s| generator.intern_string(&s)); let arguments: Vec = argument_holders.iter().map(|a| a.operand()).collect(); generator.emit(Instruction::Call { dst: dst.operand(), @@ -3019,15 +3045,20 @@ fn generate_call_expression( let dst = choose_dst(generator, preferred_dst); // Compute expression_string for error messages (e.g. "true is not a function (evaluated from 'a')"). + let arena = generator.arena.clone(); let expression_string: Option = - expression_string_approximation(&data.callee).map(|s| generator.intern_string(&s)); + expression_string_approximation(&data.callee, &arena.identifiers).map(|s| generator.intern_string(&s)); // Detect direct eval calls: bare identifier "eval" as callee. - let is_direct_eval = - !is_new && matches!(&data.callee.inner, ExpressionKind::Identifier(ident) if ident.name == utf16!("eval")); + let is_direct_eval = !is_new + && matches!(&data.callee.inner, ExpressionKind::Identifier(ident) if arena.identifiers[*ident].name == utf16!("eval")); // Detect known builtins for member expression callees (e.g. Math.abs). - let builtin: Option = if !is_new { get_builtin(&data.callee) } else { None }; + let builtin: Option = if !is_new { + get_builtin(&data.callee, &generator.arena.identifiers) + } else { + None + }; // For method calls (obj.method()), we need to use the object as `this`. let (callee, this_value) = if !is_new { @@ -3053,7 +3084,13 @@ fn generate_call_expression( if let Some(key) = computed_key { emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value); } else if let ExpressionKind::Identifier(ident) = &data.property.inner { - emit_get_by_id_with_this(generator, &method, &super_base, &ident.name, &this_value); + emit_get_by_id_with_this( + generator, + &method, + &super_base, + &arena.identifiers[*ident].name, + &this_value, + ); } (method, Some(this_value)) } @@ -3065,7 +3102,7 @@ fn generate_call_expression( let property = generate_expression_or_undefined(&data.property, generator, None); emit_get_by_value(generator, &method, &obj, &property, None); } else if let ExpressionKind::Identifier(ident) = &data.property.inner { - emit_get_by_id(generator, &method, &obj, &ident.name, base_id); + emit_get_by_id(generator, &method, &obj, &arena.identifiers[*ident].name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &data.property.inner { let id = generator.intern_identifier(&priv_ident.name); generator.emit(Instruction::GetPrivateById { @@ -3076,27 +3113,28 @@ fn generate_call_expression( } (method, Some(obj)) } - ExpressionKind::Identifier(ident) if ident.is_local() => { + ExpressionKind::Identifier(ident) if arena.identifiers[*ident].is_local() => { + let id_data = &arena.identifiers[*ident]; // Local identifier: use the local directly, with ThrowIfTDZ // if not yet initialized. - let local = generator.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap()); - let needs_tdz = if ident.local_type.get() == Some(LocalType::Argument) { - !generator.is_argument_initialized(ident.local_index.get()) + let local = generator.resolve_local(id_data.local_index.get(), id_data.local_type.get().unwrap()); + let needs_tdz = if id_data.local_type.get() == Some(LocalType::Argument) { + !generator.is_argument_initialized(id_data.local_index.get()) } else { - generator.is_local_lexically_declared(ident.local_index.get()) - && !generator.is_local_initialized(ident.local_index.get()) + generator.is_local_lexically_declared(id_data.local_index.get()) + && !generator.is_local_initialized(id_data.local_index.get()) }; if needs_tdz { generator.emit(Instruction::ThrowIfTDZ { src: local.operand() }); } (local, None) } - ExpressionKind::Identifier(ident) if !ident.is_global.get() => { + ExpressionKind::Identifier(ident) if !arena.identifiers[*ident].is_global.get() => { // Non-local, non-global identifier: use GetCalleeAndThisFromEnvironment // to properly handle with-statement bindings and eval. let callee_reg = generator.allocate_register(); let this_reg = generator.allocate_register(); - let id = generator.intern_identifier(&ident.name); + let id = generator.intern_identifier(&arena.identifiers[*ident].name); generator.emit(Instruction::GetCalleeAndThisFromEnvironment { callee: callee_reg.operand(), this_value: this_reg.operand(), @@ -3298,9 +3336,9 @@ fn generate_update_expression( // so we can store back without re-evaluating. match &argument.inner { ExpressionKind::Identifier(ident) => { - let value = generate_identifier(ident, generator, None); + let value = generate_identifier(*ident, generator, None); let result = emit_update_op(generator, op, prefixed, &value); - emit_set_variable(generator, ident, &value); + emit_set_variable(generator, *ident, &value); Some(result) } ExpressionKind::Member(data) => { @@ -3324,7 +3362,8 @@ fn generate_update_expression( if let Some(ref key) = computed_key { emit_get_by_value_with_this(generator, &value, &base, key, &this_value); } else if let ExpressionKind::Identifier(ident) = &data.property.inner { - emit_get_by_id_with_this(generator, &value, &base, &ident.name, &this_value); + let arena = generator.arena.clone(); + emit_get_by_id_with_this(generator, &value, &base, &arena.identifiers[*ident].name, &this_value); } let result = emit_update_op(generator, op, prefixed, &value); emit_super_put( @@ -3350,8 +3389,10 @@ fn generate_update_expression( Some(result) } else if let ExpressionKind::Identifier(property_ident) = &data.property.inner { let value = generator.allocate_register(); - emit_get_by_id(generator, &value, &base, &property_ident.name, base_id); - let key = generator.intern_property_key(&property_ident.name); + let arena = generator.arena.clone(); + let property_name = &arena.identifiers[*property_ident].name; + emit_get_by_id(generator, &value, &base, property_name, base_id); + let key = generator.intern_property_key(property_name); let result = emit_update_op(generator, op, prefixed, &value); let cache2 = generator.next_property_lookup_cache(); generator.emit(Instruction::PutById { @@ -3414,20 +3455,23 @@ fn generate_assignment_expression( match lhs { AssignmentLhs::Expression(lhs_expression) => { // Simple assignment to identifier - if let ExpressionKind::Identifier(ident) = &lhs_expression.inner { + if let ExpressionKind::Identifier(id) = &lhs_expression.inner { + let id = *id; + let arena = generator.arena.clone(); + let ident = &arena.identifiers[id]; if op == AssignmentOp::Assignment { generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name)); let rhs_val = generate_expression(rhs, generator, None)?; generator.pending_lhs_name = None; if ident.is_local() { - emit_tdz_check_if_needed(generator, ident); + emit_tdz_check_if_needed(generator, id); } - emit_set_variable(generator, ident, &rhs_val); + emit_set_variable(generator, id, &rhs_val); return Some(rhs_val); } // Load LHS value first (needed for both compound and logical assignments). - let lhs_val = generate_identifier(ident, generator, None); + let lhs_val = generate_identifier(id, generator, None); let lhs_val = generator.copy_if_needed_to_preserve_evaluation_order(&lhs_val); let is_logical = matches!( @@ -3457,7 +3501,7 @@ fn generate_assignment_expression( } // RHS block: evaluate RHS, assign, jump to end. generator.switch_to_basic_block(rhs_block); - generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name)); + generator.pending_lhs_name = Some(generator.intern_identifier(&arena.identifiers[id].name)); let rhs_val = generate_expression(rhs, generator, None)?; generator.pending_lhs_name = None; // Allocate dst after RHS evaluation. @@ -3467,7 +3511,7 @@ fn generate_assignment_expression( choose_dst(generator, preferred_dst) }; generator.emit_mov(&dst, &rhs_val); - emit_set_variable(generator, ident, &dst); + emit_set_variable(generator, id, &dst); generator.emit(Instruction::Jump { target: end_block }); // LHS block: keep original value. generator.switch_to_basic_block(lhs_block); @@ -3486,7 +3530,7 @@ fn generate_assignment_expression( choose_dst(generator, preferred_dst) }; emit_compound_assignment(generator, op, &dst, &lhs_val, &rhs_val); - emit_set_variable(generator, ident, &dst); + emit_set_variable(generator, id, &dst); return Some(dst); } // Member expression LHS (e.g., obj.foo = x, obj[key] = x) @@ -3534,7 +3578,14 @@ fn generate_assignment_expression( if let Some(ref key) = computed_key { emit_get_by_value_with_this(generator, &old_val, &base, key, &super_this); } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { - emit_get_by_id_with_this(generator, &old_val, &base, &ident.name, &super_this); + let arena = generator.arena.clone(); + emit_get_by_id_with_this( + generator, + &old_val, + &base, + &arena.identifiers[*ident].name, + &super_this, + ); } let is_logical = matches!( op, @@ -3649,7 +3700,9 @@ fn generate_assignment_expression( return Some(dst); } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { let old_val = generator.allocate_register(); - emit_get_by_id(generator, &old_val, &base, &ident.name, base_id); + let arena = generator.arena.clone(); + let property_name = arena.identifiers[*ident].name.clone(); + emit_get_by_id(generator, &old_val, &base, &property_name, base_id); if is_logical { let rhs_block = generator.make_block(); let lhs_block = generator.make_block(); @@ -3659,7 +3712,7 @@ fn generate_assignment_expression( 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 key = generator.intern_property_key(&property_name); let cache2 = generator.next_property_lookup_cache(); generator.emit(Instruction::PutById { base: base.operand(), @@ -3679,7 +3732,7 @@ fn generate_assignment_expression( 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 key = generator.intern_property_key(&property_name); let cache2 = generator.next_property_lookup_cache(); generator.emit(Instruction::PutById { base: base.operand(), @@ -3787,7 +3840,8 @@ fn emit_super_get( emit_get_by_value_with_this(generator, dst, base, &property, this_value); Some(property) } else if let ExpressionKind::Identifier(ident) = &property.inner { - emit_get_by_id_with_this(generator, dst, base, &ident.name, this_value); + let arena = generator.arena.clone(); + emit_get_by_id_with_this(generator, dst, base, &arena.identifiers[*ident].name, this_value); None } else { None @@ -3814,7 +3868,8 @@ fn emit_super_put( }; emit_put_normal_by_value_with_this(generator, base, &property, this_value, value); } else if let ExpressionKind::Identifier(ident) = &property.inner { - let key = generator.intern_property_key(&ident.name); + let arena = generator.arena.clone(); + let key = generator.intern_property_key(&arena.identifiers[*ident].name); let cache = generator.next_property_lookup_cache(); generator.emit(Instruction::PutByIdWithThis { base: base.operand(), @@ -4141,7 +4196,9 @@ fn emit_put_by_value( /// Emit a ThrowIfTDZ check for a local identifier if needed. This is used /// before assigning to a /// variable to ensure TDZ semantics for let/const bindings. -fn emit_tdz_check_if_needed(generator: &mut Generator, ident: &Identifier) { +fn emit_tdz_check_if_needed(generator: &mut Generator, id: crate::ast::IdentifierId) { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[id]; if !ident.is_local() { return; } @@ -4161,7 +4218,9 @@ fn emit_tdz_check_if_needed(generator: &mut Generator, ident: &Identifier) { } } -fn emit_set_variable(generator: &mut Generator, ident: &Identifier, value: &ScopedOperand) { +fn emit_set_variable(generator: &mut Generator, id: crate::ast::IdentifierId, value: &ScopedOperand) { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[id]; if ident.is_local() { if ident.declaration_kind.get() == Some(DeclarationKind::Const) { // The caller is responsible for emitting ThrowIfTDZ before calling @@ -4217,7 +4276,8 @@ fn emit_put_to_member( let property = generate_expression_or_undefined(property, generator, None); emit_put_normal_by_value(generator, base, &property, value, base_id); } else if let ExpressionKind::Identifier(ident) = &property.inner { - let key = generator.intern_property_key(&ident.name); + let arena = generator.arena.clone(); + let key = generator.intern_property_key(&arena.identifiers[*ident].name); let cache = generator.next_property_lookup_cache(); generator.emit(Instruction::PutById { base: base.operand(), @@ -4241,11 +4301,13 @@ fn emit_put_to_member( fn emit_delete_reference(generator: &mut Generator, operand: &Expression) -> ScopedOperand { match &operand.inner { ExpressionKind::Identifier(ident) => { - if ident.is_local() { + let arena = generator.arena.clone(); + let identifier = &arena.identifiers[*ident]; + if identifier.is_local() { return generator.add_constant_boolean(false); } let dst = generator.allocate_register(); - let id = generator.intern_identifier(&ident.name); + let id = generator.intern_identifier(&identifier.name); generator.emit(Instruction::DeleteVariable { dst: dst.operand(), identifier: id, @@ -4293,7 +4355,8 @@ fn emit_delete_reference(generator: &mut Generator, operand: &Expression) -> Sco property: key.operand(), }); } else if let ExpressionKind::Identifier(property_ident) = &data.property.inner { - let key = generator.intern_property_key(&property_ident.name); + let arena = generator.arena.clone(); + let key = generator.intern_property_key(&arena.identifiers[*property_ident].name); generator.emit(Instruction::DeleteById { dst: dst.operand(), base: base.operand(), @@ -4378,7 +4441,8 @@ fn emit_evaluate_member_reference(generator: &mut Generator, target: &Expression } } } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { - let key = generator.intern_property_key(&ident.name); + let arena = generator.arena.clone(); + let key = generator.intern_property_key(&arena.identifiers[*ident].name); let cache = generator.next_property_lookup_cache(); EvaluatedReference::SuperMemberId { base, @@ -4413,7 +4477,8 @@ fn emit_evaluate_member_reference(generator: &mut Generator, target: &Expression } } } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { - let key = generator.intern_property_key(&ident.name); + let arena = generator.arena.clone(); + let key = generator.intern_property_key(&arena.identifiers[*ident].name); let cache = generator.next_property_lookup_cache(); EvaluatedReference::MemberId { base, @@ -4493,7 +4558,7 @@ fn emit_store_to_evaluated_reference(generator: &mut Generator, reference: &Eval fn emit_store_to_reference(generator: &mut Generator, target: &Expression, value: &ScopedOperand) { match &target.inner { ExpressionKind::Identifier(ident) => { - emit_set_variable(generator, ident, value); + emit_set_variable(generator, *ident, value); } ExpressionKind::Member(data) => { if matches!(data.object.inner, ExpressionKind::Super) { @@ -4718,14 +4783,22 @@ fn generate_tagged_template_literal( dst: super_base.operand(), }); let method = generator.allocate_register(); + let arena = generator.arena.clone(); if let Some(key) = computed_key { emit_get_by_value_with_this(generator, &method, &super_base, &key, &this_value); } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { - emit_get_by_id_with_this(generator, &method, &super_base, &ident.name, &this_value); + emit_get_by_id_with_this( + generator, + &method, + &super_base, + &arena.identifiers[*ident].name, + &this_value, + ); } (method, Some(this_value)) } ExpressionKind::Member(member_data) => { + let arena = generator.arena.clone(); let obj = generate_expression_or_undefined(&member_data.object, generator, None); let method = generator.allocate_register(); if member_data.computed { @@ -4733,7 +4806,7 @@ fn generate_tagged_template_literal( emit_get_by_value(generator, &method, &obj, &property, None); } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { let base_id = intern_base_identifier(generator, &member_data.object); - emit_get_by_id(generator, &method, &obj, &ident.name, base_id); + emit_get_by_id(generator, &method, &obj, &arena.identifiers[*ident].name, base_id); } else if let ExpressionKind::PrivateIdentifier(priv_ident) = &member_data.property.inner { let id = generator.intern_identifier(&priv_ident.name); generator.emit(Instruction::GetPrivateById { @@ -4744,7 +4817,10 @@ fn generate_tagged_template_literal( } (method, Some(obj)) } - ExpressionKind::Identifier(ident) if ident.is_local() || ident.is_global.get() => { + ExpressionKind::Identifier(ident) + if generator.arena.identifiers[*ident].is_local() + || generator.arena.identifiers[*ident].is_global.get() => + { let tag_val = generate_expression_or_undefined(tag, generator, None); (tag_val, None) } @@ -4753,7 +4829,8 @@ fn generate_tagged_template_literal( // to properly handle with-statement bindings. let callee_reg = generator.allocate_register(); let this_reg = generator.allocate_register(); - let id = generator.intern_identifier(&ident.name); + let arena = generator.arena.clone(); + let id = generator.intern_identifier(&arena.identifiers[*ident].name); generator.emit(Instruction::GetCalleeAndThisFromEnvironment { callee: callee_reg.operand(), this_value: this_reg.operand(), @@ -4915,10 +4992,13 @@ fn generate_switch_statement( // only if the scope collector approved it (name is in annexb_function_names). if did_create_env && let StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name - && generator.annexb_function_names.contains(name_ident.name.as_slice()) + && let Some(name_ident_id) = fd.name + && generator + .annexb_function_names + .contains(generator.arena.identifiers[name_ident_id].name.as_slice()) { - let id = generator.intern_identifier(&name_ident.name); + let arena = generator.arena.clone(); + let id = generator.intern_identifier(&arena.identifiers[name_ident_id].name); let value = generator.allocate_register(); generator.emit(Instruction::GetBinding { dst: value.operand(), @@ -4987,12 +5067,14 @@ fn emit_switch_block_declaration_instantiation(generator: &mut Generator, data: { vd.declarations.iter().any(|declaration| { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, &generator.arena.identifiers); !names.is_empty() }) } StatementKind::VariableDeclaration(_) => false, - StatementKind::ClassDeclaration(class_data) => class_data.name.as_ref().is_some_and(|n| !n.is_local()), + StatementKind::ClassDeclaration(class_data) => class_data + .name + .is_some_and(|n| !generator.arena.identifiers[n].is_local()), _ => false, }); @@ -5094,7 +5176,7 @@ fn generate_object_expression( if !effectively_computed && property.property_type != ObjectPropertyType::ProtoSetter { let base_name: Option = match &property.key.inner { ExpressionKind::StringLiteral(s) => Some((**s).clone()), - ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()), + ExpressionKind::Identifier(ident) => Some(generator.arena.identifiers[*ident].name.to_utf16_string()), _ => None, }; if let Some(name) = base_name { @@ -5155,8 +5237,11 @@ fn generate_object_expression( ); } else { // Non-simple object: use PutOwnById instead of InitObjectLiteralProperty + let arena = generator.arena.clone(); let property_key = match &property.key.inner { - ExpressionKind::Identifier(ident) => generator.intern_property_key(&ident.name), + ExpressionKind::Identifier(ident) => { + generator.intern_property_key(&arena.identifiers[*ident].name) + } ExpressionKind::StringLiteral(s) => generator.intern_property_key(s), _ => { emit_object_property_set_by_key( @@ -5244,7 +5329,8 @@ fn emit_object_property_set_by_key( } match &key.inner { ExpressionKind::Identifier(ident) => { - let property_key = generator.intern_property_key(&ident.name); + let arena = generator.arena.clone(); + let property_key = generator.intern_property_key(&arena.identifiers[*ident].name); generator.emit(Instruction::InitObjectLiteralProperty { object: object.operand(), property: property_key, @@ -5347,7 +5433,11 @@ fn emit_object_accessor_by_key( } match &key.inner { - ExpressionKind::Identifier(ident) => emit_by_id(generator, &ident.name), + ExpressionKind::Identifier(ident) => { + let arena = generator.arena.clone(); + let name = arena.identifiers[*ident].name.clone(); + emit_by_id(generator, &name); + } ExpressionKind::StringLiteral(s) => emit_by_id(generator, s), _ => emit_by_value(generator, key), } @@ -5396,7 +5486,8 @@ fn generate_optional_chain_inner( generator.emit_mov(current_base, &obj); } else if let ExpressionKind::Identifier(ident) = &member_data.property.inner { let base_id = intern_base_identifier(generator, &member_data.object); - emit_get_by_id(generator, &val, &obj, &ident.name, base_id); + let arena = generator.arena.clone(); + emit_get_by_id(generator, &val, &obj, &arena.identifiers[*ident].name, base_id); generator.emit_mov(current_base, &obj); } else if let ExpressionKind::PrivateIdentifier(name) = &member_data.property.inner { let id = generator.intern_identifier(&name.name); @@ -5454,7 +5545,14 @@ fn generate_optional_chain_inner( match reference { OptionalChainReference::MemberReference { identifier, .. } => { generator.emit_mov(current_base, current_value); - emit_get_by_id(generator, current_value, current_value, &identifier.name, None); + let arena = generator.arena.clone(); + emit_get_by_id( + generator, + current_value, + current_value, + &arena.identifiers[*identifier].name, + None, + ); } OptionalChainReference::ComputedReference { expression, .. } => { generator.emit_mov(current_base, current_value); @@ -5578,8 +5676,8 @@ fn generate_class_expression( // Only emit when the class has a name, or when there's no lhs_name // (skip this for anonymous classes with lhs_name). if data.name.is_some() || lhs_name.is_none() { - let name = if let Some(name_ident) = &data.name { - name_ident.name.to_utf16_string() + let name = if let Some(name_ident_id) = data.name { + generator.arena.identifiers[name_ident_id].name.to_utf16_string() } else { Utf16String::new() }; @@ -5760,8 +5858,9 @@ fn generate_class_expression( if !is_literal { // Determine field name for anonymous function naming. + let arena = generator.arena.clone(); let field_name = match &key.inner { - ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(), + ExpressionKind::Identifier(ident) => arena.identifiers[*ident].name.to_utf16_string(), ExpressionKind::StringLiteral(s) => (**s).clone(), ExpressionKind::PrivateIdentifier(p) => p.name.clone(), ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n), @@ -5811,7 +5910,7 @@ fn generate_class_expression( let key_is_private = is_private_key(key); let key_name: Utf16String = match &key.inner { ExpressionKind::PrivateIdentifier(ident) => ident.name.clone(), - ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(), + ExpressionKind::Identifier(ident) => arena.identifiers[*ident].name.to_utf16_string(), ExpressionKind::StringLiteral(s) => (**s).clone(), ExpressionKind::NumericLiteral(n) => super::ffi::js_number_to_utf16(*n), _ => Utf16String::new(), @@ -5885,8 +5984,9 @@ fn generate_class_expression( let source_end = data.source_text_end as usize; let source_text_len = source_end - source_start; + let class_name = data.name.map(|n| generator.arena.identifiers[n].name.to_utf16_string()); let blueprint_index = generator.register_class_blueprint(PendingClassBlueprint { - name: data.name.as_ref().map(|n| n.name.to_utf16_string()), + name: class_name, source_text_offset: source_start, source_text_length: source_text_len, constructor_sfd_index, @@ -5941,7 +6041,7 @@ fn emit_default_constructor(generator: &mut Generator, has_super: bool) -> u32 { parser.flags.allow_super_constructor_call = true; } let program = parser.parse_program(false); - parser.scope_collector.analyze(false); + parser.scope_collector.analyze(false, &parser.arena.identifiers); assert!(!parser.has_errors(), "default constructor parse failed"); @@ -5995,14 +6095,14 @@ fn get_private_identifier_name(key: &Expression) -> Option { /// 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 { +fn for_in_of_needs_lexical_env(lhs: &ForInOfLhs, identifiers: &crate::ast::IdentifierArena) -> bool { if let ForInOfLhs::Declaration(statement) = lhs && let StatementKind::VariableDeclaration(vd) = &statement.inner && (vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const) { let mut names = Vec::new(); for declaration in &vd.declarations { - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, identifiers); } return !names.is_empty(); } @@ -6010,36 +6110,47 @@ fn for_in_of_needs_lexical_env(lhs: &ForInOfLhs) -> bool { } /// Collect all non-local binding names from a variable declarator target. -fn collect_target_names(target: &VariableDeclaratorTarget, names: &mut Vec<(Utf16String, bool)>) { +fn collect_target_names( + target: &VariableDeclaratorTarget, + names: &mut Vec<(Utf16String, bool)>, + identifiers: &crate::ast::IdentifierArena, +) { match target { - VariableDeclaratorTarget::Identifier(ident) => { + VariableDeclaratorTarget::Identifier(id) => { + let ident = &identifiers[*id]; if !ident.is_local() { names.push((ident.name.to_utf16_string(), false)); } } VariableDeclaratorTarget::BindingPattern(pattern) => { - collect_pattern_binding_names(pattern, names); + collect_pattern_binding_names(pattern, names, identifiers); } } } /// Collect all non-local binding names from a binding pattern (recursive). -fn collect_pattern_binding_names(pattern: &BindingPattern, names: &mut Vec<(Utf16String, bool)>) { +fn collect_pattern_binding_names( + pattern: &BindingPattern, + names: &mut Vec<(Utf16String, bool)>, + identifiers: &crate::ast::IdentifierArena, +) { for entry in &pattern.entries { match &entry.alias { - Some(BindingEntryAlias::Identifier(ident)) => { + Some(BindingEntryAlias::Identifier(id)) => { + let ident = &identifiers[*id]; if !ident.is_local() { names.push((ident.name.to_utf16_string(), false)); } } Some(BindingEntryAlias::BindingPattern(sub)) => { - collect_pattern_binding_names(sub, names); + collect_pattern_binding_names(sub, names, identifiers); } None => { - if let Some(BindingEntryName::Identifier(ident)) = &entry.name - && !ident.is_local() - { - names.push((ident.name.to_utf16_string(), false)); + if let Some(BindingEntryName::Identifier(id)) = &entry.name { + let ident = &identifiers[*id]; + if !ident.is_local() { + names.push((ident.name.to_utf16_string(), false)); + } } } Some(BindingEntryAlias::MemberExpression(_)) => {} @@ -6060,7 +6171,7 @@ fn create_for_in_of_lexical_env(generator: &mut Generator, lhs: &ForInOfLhs) -> { is_constant = vd.kind == DeclarationKind::Const; for declaration in &vd.declarations { - collect_target_names(&declaration.target, &mut binding_names); + collect_target_names(&declaration.target, &mut binding_names, &generator.arena.identifiers); } } @@ -6094,7 +6205,7 @@ fn enter_for_in_of_head_tdz(generator: &mut Generator, lhs: &ForInOfLhs) -> bool { let mut names = Vec::new(); for declaration in &vd.declarations { - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, &generator.arena.identifiers); } if !names.is_empty() { generator.push_new_lexical_environment(0); @@ -6153,19 +6264,20 @@ fn generate_for_in_statement( && let StatementKind::VariableDeclaration(vd) = &statement.inner && vd.kind == DeclarationKind::Var && let Some(declaration) = vd.declarations.first() - && let (VariableDeclaratorTarget::Identifier(ident), Some(init)) = (&declaration.target, &declaration.init) + && let (VariableDeclaratorTarget::Identifier(ident_id), Some(init)) = (&declaration.target, &declaration.init) { - generator.pending_lhs_name = Some(generator.intern_identifier(&ident.name)); + let arena = generator.arena.clone(); + generator.pending_lhs_name = Some(generator.intern_identifier(&arena.identifiers[*ident_id].name)); let value = generate_expression_or_undefined(init, generator, None); generator.pending_lhs_name = None; - emit_set_variable(generator, ident, &value); + emit_set_variable(generator, *ident_id, &value); } // Create end_block and update_block first, then nullish_block and // continuation_block during head evaluation. let end_block = generator.make_block(); let update_block = generator.make_block(); - let needs_lexical_env = for_in_of_needs_lexical_env(lhs); + let needs_lexical_env = for_in_of_needs_lexical_env(lhs, &generator.arena.identifiers); // B.3.5 Initializers in ForIn Statement Heads: evaluate initializer before RHS. // Create TDZ for lexical declarations before evaluating the RHS expression. @@ -6343,7 +6455,7 @@ fn generate_for_of_statement_inner( // Create TDZ for lexical declarations before evaluating the RHS expression. let entered_tdz = enter_for_in_of_head_tdz(generator, lhs); - let needs_lexical_env = for_in_of_needs_lexical_env(lhs); + let needs_lexical_env = for_in_of_needs_lexical_env(lhs, &generator.arena.identifiers); let old_handler = generator.current_unwind_handler; // Evaluate RHS into `object`, allocate iterator registers, and emit @@ -6780,7 +6892,7 @@ fn assign_to_for_in_of_lhs(generator: &mut Generator, lhs: &ForInOfLhs, value: & }; match &declaration.target { VariableDeclaratorTarget::Identifier(ident) => { - emit_set_variable_with_mode(generator, ident, value, mode); + emit_set_variable_with_mode(generator, *ident, value, mode); } VariableDeclaratorTarget::BindingPattern(pattern) => { generate_binding_pattern_bytecode(generator, pattern, mode, value); @@ -6811,11 +6923,12 @@ enum BindingMode { } fn set_pending_lhs_name_for_entry(generator: &mut Generator, entry: &BindingEntry) { + let arena = generator.arena.clone(); let name = match &entry.alias { - Some(BindingEntryAlias::Identifier(id)) => Some(&id.name), + Some(BindingEntryAlias::Identifier(id)) => Some(arena.identifiers[*id].name.clone()), None => { if let Some(BindingEntryName::Identifier(id)) = &entry.name { - Some(&id.name) + Some(arena.identifiers[*id].name.clone()) } else { None } @@ -6823,7 +6936,7 @@ fn set_pending_lhs_name_for_entry(generator: &mut Generator, entry: &BindingEntr _ => None, }; if let Some(name) = name { - generator.pending_lhs_name = Some(generator.intern_identifier(name)); + generator.pending_lhs_name = Some(generator.intern_identifier(&name)); } } @@ -6845,10 +6958,12 @@ fn generate_binding_pattern_bytecode( fn emit_set_variable_with_mode( generator: &mut Generator, - ident: &Identifier, + id: crate::ast::IdentifierId, value: &ScopedOperand, mode: BindingMode, ) { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[id]; if ident.is_local() { let local = generator.resolve_local(ident.local_index.get(), ident.local_type.get().unwrap()); generator.emit_mov(&local, value); @@ -6892,11 +7007,11 @@ fn assign_binding_entry_alias( None => { // Name IS the binding target (e.g., `{ x }` or array element). if let Some(BindingEntryName::Identifier(ident)) = &entry.name { - emit_set_variable_with_mode(generator, ident, value, mode); + emit_set_variable_with_mode(generator, *ident, value, mode); } } Some(BindingEntryAlias::Identifier(ident)) => { - emit_set_variable_with_mode(generator, ident, value, mode); + emit_set_variable_with_mode(generator, *ident, value, mode); } Some(BindingEntryAlias::BindingPattern(sub_pattern)) => { generate_binding_pattern_bytecode(generator, sub_pattern, mode, value); @@ -7110,9 +7225,11 @@ fn generate_object_binding_pattern( match &entry.name { Some(BindingEntryName::Identifier(ident)) => { - emit_get_by_id(generator, &value, object, &ident.name, None); + let arena = generator.arena.clone(); + let name = arena.identifiers[*ident].name.clone(); + emit_get_by_id(generator, &value, object, &name, None); if has_rest { - let name_val = generator.add_constant_string(ident.name.to_utf16_string()); + let name_val = generator.add_constant_string(name.to_utf16_string()); excluded_names.push(name_val); } } @@ -7255,7 +7372,9 @@ fn generate_try_statement( let mut created_catch_scope = false; if let Some(parameter) = &catch.parameter { match parameter { - CatchBinding::Identifier(ident) => { + CatchBinding::Identifier(ident_id) => { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[*ident_id]; if ident.is_local() { let local = generator.local(ident.local_index.get()); generator.emit_mov(&local, &caught_value); @@ -7282,7 +7401,7 @@ fn generate_try_statement( } CatchBinding::BindingPattern(pattern) => { let mut names: Vec<(Utf16String, bool)> = Vec::new(); - collect_pattern_binding_names(pattern, &mut names); + collect_pattern_binding_names(pattern, &mut names, &generator.arena.identifiers); if !names.is_empty() { generator.push_new_lexical_environment(0); @@ -7617,9 +7736,11 @@ pub fn emit_function_declaration_instantiation( let mut seen_names: HashSet = HashSet::new(); let mut has_duplicates = false; + let arena = generator.arena.clone(); for parameter in &function_data.parameters { match ¶meter.binding { - FunctionParameterBinding::Identifier(ident) => { + FunctionParameterBinding::Identifier(ident_id) => { + let ident = &arena.identifiers[*ident_id]; let name = ident.name.to_utf16_string(); let is_local = ident.is_local(); if !seen_names.insert(name.clone()) { @@ -7629,7 +7750,13 @@ pub fn emit_function_declaration_instantiation( } } FunctionParameterBinding::BindingPattern(pattern) => { - collect_binding_pattern_names(pattern, &mut parameter_names, &mut seen_names, &mut has_duplicates); + collect_binding_pattern_names( + pattern, + &mut parameter_names, + &mut seen_names, + &mut has_duplicates, + &arena.identifiers, + ); } } } @@ -7768,7 +7895,9 @@ pub fn emit_function_declaration_instantiation( } match ¶meter.binding { - FunctionParameterBinding::Identifier(ident) => { + FunctionParameterBinding::Identifier(ident_id) => { + let arena = generator.arena.clone(); + let ident = &arena.identifiers[*ident_id]; if ident.is_local() { let local_index = ident.local_index.get(); match ident.local_type.get() { @@ -7935,7 +8064,7 @@ pub fn emit_function_declaration_instantiation( // --- Step 7: Lexical environment for non-local declarations --- // Note: this counts only let/const/class declarations (not function declarations, // which are var-hoisted in function bodies). - let lex_bindings_count = count_non_local_lexical_bindings(body_scope); + let lex_bindings_count = count_non_local_lexical_bindings(body_scope, &generator.arena.identifiers); if !strict && lex_bindings_count > 0 { generator.push_new_lexical_environment(lex_bindings_count); @@ -7951,7 +8080,7 @@ pub fn emit_function_declaration_instantiation( let is_constant = vd.kind == DeclarationKind::Const; for declaration in &vd.declarations { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, &generator.arena.identifiers); for (name, _) in names { let id = generator.intern_identifier(&name); generator.emit(Instruction::CreateVariable { @@ -7966,17 +8095,19 @@ pub fn emit_function_declaration_instantiation( } StatementKind::ClassDeclaration(class_data) => { // Class declarations are lexically scoped (like const). - 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, - }); + if let Some(name_ident_id) = class_data.name { + let arena = generator.arena.clone(); + let name_ident = &arena.identifiers[name_ident_id]; + if !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, + }); + } } } _ => {} @@ -7993,7 +8124,9 @@ pub fn emit_function_declaration_instantiation( let sfd_index = emit_new_function(generator, inner_function_data, None); // Check if the function name identifier is local. - if let Some(name_ident) = &fd.name { + if let Some(name_ident_id) = fd.name { + let arena = generator.arena.clone(); + let name_ident = &arena.identifiers[name_ident_id]; if name_ident.is_local() { let local_index = name_ident.local_index.get(); let local = generator.local(local_index); @@ -8032,7 +8165,7 @@ fn is_for_loop(statement: &Statement) -> bool { /// 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 { +fn needs_block_declaration_instantiation(scope: &ScopeData, identifiers: &crate::ast::IdentifierArena) -> bool { for child in &scope.children { match &child.inner { StatementKind::FunctionDeclaration(_) => { @@ -8043,15 +8176,15 @@ fn needs_block_declaration_instantiation(scope: &ScopeData) -> bool { { for declaration in &vd.declarations { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, identifiers); if !names.is_empty() { return true; } } } StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name_ident) = class_data.name - && !name_ident.is_local() + if let Some(name_ident) = class_data.name + && !identifiers[name_ident].is_local() { return true; } @@ -8059,7 +8192,7 @@ fn needs_block_declaration_instantiation(scope: &ScopeData) -> bool { StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, identifiers); if !names.is_empty() { return true; } @@ -8072,7 +8205,7 @@ fn needs_block_declaration_instantiation(scope: &ScopeData) -> bool { } /// Count non-local lexical bindings in a function body scope. -fn count_non_local_lexical_bindings(scope: &ScopeData) -> u32 { +fn count_non_local_lexical_bindings(scope: &ScopeData, identifiers: &crate::ast::IdentifierArena) -> u32 { let mut count = 0u32; for child in &scope.children { match &child.inner { @@ -8081,11 +8214,13 @@ fn count_non_local_lexical_bindings(scope: &ScopeData) -> u32 { { for declaration in &vd.declarations { let mut names = Vec::new(); - collect_target_names(&declaration.target, &mut names); + collect_target_names(&declaration.target, &mut names, identifiers); count += u32_from_usize(names.len()); } } - StatementKind::ClassDeclaration(class_data) if class_data.name.as_ref().is_some_and(|n| !n.is_local()) => { + StatementKind::ClassDeclaration(class_data) + if class_data.name.is_some_and(|n| !identifiers[n].is_local()) => + { count += 1; } _ => {} @@ -8105,11 +8240,13 @@ fn collect_binding_pattern_names( parameter_names: &mut Vec, seen_names: &mut HashSet, has_duplicates: &mut bool, + identifiers: &crate::ast::IdentifierArena, ) { 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)) => { + Some(BindingEntryAlias::Identifier(ident_id)) => { + let ident = &identifiers[*ident_id]; let name = ident.name.to_utf16_string(); let is_local = ident.is_local(); if !seen_names.insert(name.clone()) { @@ -8119,11 +8256,12 @@ fn collect_binding_pattern_names( } } Some(BindingEntryAlias::BindingPattern(sub_pattern)) => { - collect_binding_pattern_names(sub_pattern, parameter_names, seen_names, has_duplicates); + collect_binding_pattern_names(sub_pattern, parameter_names, seen_names, has_duplicates, identifiers); } None => { // No alias — the name itself is the binding. - if let Some(BindingEntryName::Identifier(ident)) = &entry.name { + if let Some(BindingEntryName::Identifier(ident_id)) = &entry.name { + let ident = &identifiers[*ident_id]; let name = ident.name.to_utf16_string(); let is_local = ident.is_local(); if !seen_names.insert(name.clone()) { @@ -8172,7 +8310,7 @@ const BUILTIN_STRING_PROTOTYPE_CHAR_AT: u8 = 23; /// Detect known builtin methods from a callee expression (e.g. Math.abs). /// Returns the Builtin enum value as u8, matching Builtins.h ordering. -fn get_builtin(callee: &Expression) -> Option { +fn get_builtin(callee: &Expression, identifiers: &crate::ast::IdentifierArena) -> Option { let ExpressionKind::Member(member_data) = &callee.inner else { return None; }; @@ -8182,15 +8320,17 @@ fn get_builtin(callee: &Expression) -> Option { let ExpressionKind::Identifier(property_ident) = &member_data.property.inner else { return None; }; - if property_ident.name == utf16!("charAt") { + let property_name = &identifiers[*property_ident].name; + if property_name == utf16!("charAt") { return Some(BUILTIN_STRING_PROTOTYPE_CHAR_AT); } - if property_ident.name == utf16!("charCodeAt") { + if property_name == utf16!("charCodeAt") { return Some(BUILTIN_STRING_PROTOTYPE_CHAR_CODE_AT); } let ExpressionKind::Identifier(base_ident) = &member_data.object.inner else { return None; }; + let base_name = &identifiers[*base_ident].name; // Must match JS_ENUMERATE_BUILTINS order in Builtins.h. static BUILTINS: &[(&[u16], &[u16], u8)] = &[ (utf16!("Math"), utf16!("abs"), BUILTIN_MATH_ABS), @@ -8245,7 +8385,7 @@ fn get_builtin(callee: &Expression) -> Option { (utf16!("String"), utf16!("fromCharCode"), BUILTIN_STRING_FROM_CHAR_CODE), ]; for &(base, property, id) in BUILTINS { - if base_ident.name == base && property_ident.name == property { + if base_name == base && property_name == property { return Some(id); } } @@ -9078,14 +9218,15 @@ fn nanboxed_empty() -> u64 { /// Intern the base expression as an identifier for error messages like /// "Cannot access property X on null object Y". fn intern_base_identifier(generator: &mut Generator, base: &Expression) -> Option { - expression_identifier(base).map(|s| generator.intern_identifier(&s)) + let arena = generator.arena.clone(); + expression_identifier(base, &arena.identifiers).map(|s| generator.intern_identifier(&s)) } /// Try to produce a human-readable name for an expression (for error messages). /// Returns None for expressions that have no meaningful name. -fn expression_identifier(expression: &Expression) -> Option { +fn expression_identifier(expression: &Expression, identifiers: &crate::ast::IdentifierArena) -> Option { match &expression.inner { - ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()), + ExpressionKind::Identifier(ident) => Some(identifiers[*ident].name.to_utf16_string()), ExpressionKind::StringLiteral(s) => { let mut result = Utf16String(utf16!("'").to_vec()); result.0.extend_from_slice(s); @@ -9096,10 +9237,10 @@ fn expression_identifier(expression: &Expression) -> Option { ExpressionKind::This => Some(Utf16String(utf16!("this").to_vec())), ExpressionKind::Member(data) => { let mut s = Utf16String::new(); - if let Some(obj_id) = expression_identifier(&data.object) { + if let Some(obj_id) = expression_identifier(&data.object, identifiers) { s.0.extend_from_slice(&obj_id); } - if let Some(property_id) = expression_identifier(&data.property) { + if let Some(property_id) = expression_identifier(&data.property, identifiers) { if data.computed { s.0.extend_from_slice(utf16!("[")); s.0.extend_from_slice(&property_id); @@ -9118,20 +9259,23 @@ fn expression_identifier(expression: &Expression) -> Option { /// Produce a human-readable string for call expression error messages. /// Unlike expression_identifier, this always produces output for known types /// (using "" for unrecognized sub-expressions). -fn expression_string_approximation(expression: &Expression) -> Option { +fn expression_string_approximation( + expression: &Expression, + identifiers: &crate::ast::IdentifierArena, +) -> Option { match &expression.inner { - ExpressionKind::Identifier(ident) => Some(ident.name.to_utf16_string()), - ExpressionKind::Member(_) => Some(member_to_string_approximation(expression)), + ExpressionKind::Identifier(ident) => Some(identifiers[*ident].name.to_utf16_string()), + ExpressionKind::Member(_) => Some(member_to_string_approximation(expression, identifiers)), _ => None, } } -fn member_to_string_approximation(expression: &Expression) -> Utf16String { +fn member_to_string_approximation(expression: &Expression, identifiers: &crate::ast::IdentifierArena) -> Utf16String { match &expression.inner { - ExpressionKind::Identifier(ident) => ident.name.to_utf16_string(), + ExpressionKind::Identifier(ident) => identifiers[*ident].name.to_utf16_string(), ExpressionKind::Member(data) => { - let mut s = member_to_string_approximation(&data.object); - let property_str = member_to_string_approximation(&data.property); + let mut s = member_to_string_approximation(&data.object, identifiers); + let property_str = member_to_string_approximation(&data.property, identifiers); if data.computed { s.0.extend_from_slice(utf16!("[")); s.0.extend_from_slice(&property_str); diff --git a/Libraries/LibJS/Rust/src/bytecode/ffi.rs b/Libraries/LibJS/Rust/src/bytecode/ffi.rs index 5e80621f4a..303f05e022 100644 --- a/Libraries/LibJS/Rust/src/bytecode/ffi.rs +++ b/Libraries/LibJS/Rust/src/bytecode/ffi.rs @@ -322,6 +322,7 @@ pub unsafe fn create_shared_function_data( source_code_ptr: *const c_void, is_strict: bool, name_override: Option<&[u16]>, + arena: std::sync::Arc, ) -> *mut c_void { unsafe { use crate::ast::FunctionParameterBinding; @@ -332,8 +333,9 @@ pub unsafe fn create_shared_function_data( let (name_ptr, name_len) = if let Some(name) = name_override { (name.as_ptr(), name.len()) - } else if let Some(ref name_ident) = function_data.name { - (name_ident.name.as_ptr(), name_ident.name.len()) + } else if let Some(name_ident) = function_data.name { + let name = &arena.identifiers[name_ident].name; + (name.as_ptr(), name.len()) } else { (std::ptr::null(), 0) }; @@ -347,8 +349,8 @@ pub unsafe fn create_shared_function_data( .parameters .iter() .map(|p| { - if let FunctionParameterBinding::Identifier(ref id) = p.binding { - FFIUtf16Slice::from(id.name.as_ref()) + if let FunctionParameterBinding::Identifier(id) = p.binding { + FFIUtf16Slice::from(arena.identifiers[id].name.as_ref()) } else { unreachable!("has_simple_parameter_list guarantees all bindings are identifiers") } @@ -369,6 +371,7 @@ pub unsafe fn create_shared_function_data( let payload = Box::new(crate::ast::FunctionPayload { data: *function_data, function_table: subtable, + arena, }); let rust_ast_ptr = Box::into_raw(payload) as *mut c_void; @@ -410,8 +413,9 @@ pub unsafe fn create_sfd_for_gdi( vm_ptr: *mut c_void, source_code_ptr: *const c_void, is_strict: bool, + arena: std::sync::Arc, ) -> *mut c_void { - unsafe { create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None) } + unsafe { create_shared_function_data(function_data, subtable, vm_ptr, source_code_ptr, is_strict, None, arena) } } unsafe fn materialize_shared_function_data( @@ -437,6 +441,7 @@ unsafe fn materialize_shared_function_data( source_code_ptr, generator.strict, pending.name_override.as_ref().map(|name| name.as_slice()), + generator.arena.clone(), ); if let Some((name, is_private)) = &pending.class_field_initializer_name { rust_sfd_set_class_field_initializer_name(sfd_ptr, name.as_ptr(), name.len(), *is_private); diff --git a/Libraries/LibJS/Rust/src/bytecode/generator.rs b/Libraries/LibJS/Rust/src/bytecode/generator.rs index cf3e79455b..177dc947a3 100644 --- a/Libraries/LibJS/Rust/src/bytecode/generator.rs +++ b/Libraries/LibJS/Rust/src/bytecode/generator.rs @@ -17,8 +17,9 @@ use super::basic_block::{BasicBlock, SourceMapEntry}; use super::ffi::{AbstractOperationKind, WellKnownSymbolKind}; use super::instruction::Instruction; use super::operand::*; -use crate::ast::{FunctionData, FunctionId, FunctionTable, LocalType, Position, Utf16String}; +use crate::ast::{AstArena, FunctionData, FunctionId, FunctionTable, IdentifierId, LocalType, Position, Utf16String}; use crate::u32_from_usize; +use std::sync::Arc; /// Identifies an operand that auto-frees its register when the last /// clone is dropped. @@ -300,6 +301,20 @@ pub struct Generator { // Side table owning all FunctionData from the parser. Codegen // takes ownership of individual entries via `take()`. pub function_table: crate::ast::FunctionTable, + + // --- AST arena --- + // Shared (read-only post-parse) storage for identifiers, scopes, and + // interned strings. Cloning is a refcount bump — multiple generators + // (top-level + nested IIFE + lazy children) share the same arena. + pub arena: Arc, +} + +impl Generator { + /// Convenience: look up an identifier by ID in this generator's arena. + #[inline] + pub fn identifier(&self, id: IdentifierId) -> &crate::ast::Identifier { + &self.arena.identifiers[id] + } } macro_rules! singleton_constant { @@ -425,6 +440,7 @@ impl Generator { source_code_ptr: std::ptr::null(), source_len: 0, function_table: crate::ast::FunctionTable::new(), + arena: Arc::new(AstArena::new()), free_register_pool, } } diff --git a/Libraries/LibJS/Rust/src/lib.rs b/Libraries/LibJS/Rust/src/lib.rs index c802a9a440..8fa37ba3aa 100644 --- a/Libraries/LibJS/Rust/src/lib.rs +++ b/Libraries/LibJS/Rust/src/lib.rs @@ -4,6 +4,13 @@ * SPDX-License-Identifier: BSD-2-Clause */ +// AstArena currently transitively contains `Cell<>` fields on `Identifier` (set +// by the scope collector during parse). Once those cells are removed, the +// arena will be naturally `Send + Sync` and this allow can go away. For now +// we hand-wave it the same way we already do with `unsafe impl Send for +// ParsedProgram` -- the arena is move-only-across-threads at the API layer. +#![allow(clippy::arc_with_non_send_sync)] + //! # LibJS Parser //! //! A JavaScript parser that produces an AST. @@ -111,6 +118,7 @@ use std::rc::Rc; pub struct ParsedProgram { program: ast::Statement, function_table: ast::FunctionTable, + arena: std::sync::Arc, scope_ref: Rc>, is_strict_mode: bool, has_top_level_await: bool, @@ -176,6 +184,7 @@ fn abort_on_panic R, R>(f: F) -> R { unsafe fn write_ast_dump_output( program: &ast::Statement, function_table: &ast::FunctionTable, + identifiers: &ast::IdentifierArena, output_ptr: *mut *mut u8, output_len: *mut usize, ) { @@ -183,7 +192,7 @@ unsafe fn write_ast_dump_output( if output_ptr.is_null() || output_len.is_null() { return; } - let dump_string = ast_dump::dump_program_to_string(program, function_table); + let dump_string = ast_dump::dump_program_to_string(program, function_table, identifiers); let mut boxed = dump_string.into_bytes().into_boxed_slice(); *output_ptr = boxed.as_mut_ptr(); *output_len = boxed.len(); @@ -336,11 +345,13 @@ fn precompile_eager_functions(generator: &mut bytecode::generator::Generator) { let payload = ast::FunctionPayload { data: *function_data, function_table: subtable, + arena: generator.arena.clone(), }; let (function_data, precompiled) = compile_function_payload_to_bytecode( payload, generator.source_len, generator.builtin_abstract_operations_enabled, + generator.arena.clone(), ); pending.function_data = Some(function_data); @@ -422,7 +433,9 @@ pub unsafe extern "C" fn rust_compile_program( return std::ptr::null_mut(); } - parser.scope_collector.analyze(initiated_by_eval); + parser + .scope_collector + .analyze(initiated_by_eval, &parser.arena.identifiers); let scope_ref = if let StatementKind::Program(ref data) = program.inner { data.scope.clone() @@ -432,6 +445,8 @@ pub unsafe extern "C" fn rust_compile_program( let mut generator = new_program_generator(starts_in_strict_mode, vm_ptr, source_code_ptr, source_len); generator.function_table = std::mem::take(&mut parser.function_table); + generator.arena = std::sync::Arc::new(std::mem::take(&mut parser.arena)); + // Make a clone of the Arc so we can keep using it after generator is consumed. compile_program_body(&mut generator, &program, &scope_ref, vm_ptr, source_code_ptr) }) } @@ -487,12 +502,12 @@ pub unsafe extern "C" fn rust_parse_program( } if errors.is_empty() { - parser.scope_collector.analyze(false); + parser.scope_collector.analyze(false, &parser.arena.identifiers); } // Dump AST if requested (after scope analysis). if dump_ast && errors.is_empty() { - ast_dump::dump_program(&program, use_color, &parser.function_table); + ast_dump::dump_program(&program, use_color, &parser.function_table, &parser.arena.identifiers); } let (scope_ref, is_strict, has_tla) = if errors.is_empty() { @@ -510,6 +525,7 @@ pub unsafe extern "C" fn rust_parse_program( let parsed = ParsedProgram { program, function_table: std::mem::take(&mut parser.function_table), + arena: std::sync::Arc::new(std::mem::take(&mut parser.arena)), scope_ref, is_strict_mode: is_strict, has_top_level_await: has_tla, @@ -584,8 +600,10 @@ pub unsafe extern "C" fn rust_compile_parsed_program_off_thread( } let mut parsed = Box::from_raw(parsed); + let arena_arc = parsed.arena.clone(); let bytecode = if parsed.has_top_level_await { let mut generator = new_module_async_generator(source_len, std::mem::take(&mut parsed.function_table)); + generator.arena = arena_arc; generator.eager_compile_direct_iifes = true; let assembled = compile_module_as_async_to_bytecode(&parsed.program, &parsed.scope_ref, &mut generator); precompile_eager_functions(&mut generator); @@ -597,6 +615,7 @@ pub unsafe extern "C" fn rust_compile_parsed_program_off_thread( std::ptr::null(), source_len, ); + generator.arena = arena_arc; generator.eager_compile_direct_iifes = true; generator.function_table = std::mem::take(&mut parsed.function_table); let assembled = compile_program_body_to_bytecode(&mut generator, &parsed.program, &parsed.scope_ref); @@ -641,7 +660,8 @@ pub unsafe extern "C" fn rust_parsed_program_ast_dump( unsafe { let parsed = &mut *parsed; let dump = parsed.ast_dump.get_or_insert_with(|| { - ast_dump::dump_program_to_string(&parsed.program, &parsed.function_table).into_bytes() + ast_dump::dump_program_to_string(&parsed.program, &parsed.function_table, &parsed.arena.identifiers) + .into_bytes() }); *output_ptr = dump.as_ptr(); *output_len = dump.len(); @@ -673,6 +693,7 @@ pub unsafe extern "C" fn rust_compile_parsed_script( let mut generator = new_program_generator(parsed.is_strict_mode, vm_ptr, source_code_ptr, source_len); generator.function_table = std::mem::take(&mut parsed.function_table); + generator.arena = parsed.arena.clone(); let exec_ptr = compile_program_body( &mut generator, &parsed.program, @@ -691,6 +712,7 @@ pub unsafe extern "C" fn rust_compile_parsed_script( source_code_ptr, gdi_context, &mut generator.function_table, + &parsed.arena, ); exec_ptr @@ -735,6 +757,7 @@ pub unsafe extern "C" fn rust_materialize_compiled_script( source_code_ptr, gdi_context, &mut bytecode.generator.function_table, + &compiled.parsed.arena, ); exec_ptr @@ -793,9 +816,15 @@ pub unsafe extern "C" fn rust_compile_eval( return std::ptr::null_mut(); } - parser.scope_collector.analyze(true); + parser.scope_collector.analyze(true, &parser.arena.identifiers); - write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len); + write_ast_dump_output( + &program, + &parser.function_table, + &parser.arena.identifiers, + ast_dump_output, + ast_dump_output_len, + ); let (scope_ref, is_strict) = if let StatementKind::Program(ref data) = program.inner { (data.scope.clone(), data.is_strict_mode) @@ -803,8 +832,10 @@ pub unsafe extern "C" fn rust_compile_eval( return std::ptr::null_mut(); }; + let arena_arc = std::sync::Arc::new(std::mem::take(&mut parser.arena)); let mut generator = new_program_generator(is_strict, vm_ptr, source_code_ptr, source_len); generator.function_table = std::mem::take(&mut parser.function_table); + generator.arena = arena_arc.clone(); let exec_ptr = compile_program_body(&mut generator, &program, &scope_ref, vm_ptr, source_code_ptr); if exec_ptr.is_null() { return std::ptr::null_mut(); @@ -817,6 +848,7 @@ pub unsafe extern "C" fn rust_compile_eval( source_code_ptr, gdi_context, &mut generator.function_table, + &arena_arc, ); exec_ptr @@ -968,7 +1000,9 @@ pub unsafe extern "C" fn rust_compile_dynamic_function( // Run scope analysis. Use analyze_as_dynamic_function() to suppress // marking identifiers as global, matching the C++ path which parses // as a FunctionExpression (no Program scope for globals to bind to). - parser.scope_collector.analyze_as_dynamic_function(); + parser + .scope_collector + .analyze_as_dynamic_function(&parser.arena.identifiers); if parser.scope_collector.has_errors() { if let Some(cb) = error_callback { @@ -980,7 +1014,13 @@ pub unsafe extern "C" fn rust_compile_dynamic_function( return std::ptr::null_mut(); } - write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len); + write_ast_dump_output( + &program, + &parser.function_table, + &parser.arena.identifiers, + ast_dump_output, + ast_dump_output_len, + ); // Extract the FunctionExpression from the program. // The program should contain a single ExpressionStatement wrapping a FunctionExpression. @@ -1017,8 +1057,9 @@ pub unsafe extern "C" fn rust_compile_dynamic_function( let is_strict = function_data.is_strict_mode; let subtable = parser.function_table.extract_reachable(&function_data); + let arena = std::sync::Arc::new(std::mem::take(&mut parser.arena)); - bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict) + bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict, arena) }) } } @@ -1071,9 +1112,15 @@ pub unsafe extern "C" fn rust_compile_builtin_file( panic!("Parse errors in builtin file: {}", errors.join("; ")); } - parser.scope_collector.analyze(false); + parser.scope_collector.analyze(false, &parser.arena.identifiers); - write_ast_dump_output(&program, &parser.function_table, ast_dump_output, ast_dump_output_len); + write_ast_dump_output( + &program, + &parser.function_table, + &parser.arena.identifiers, + ast_dump_output, + ast_dump_output_len, + ); let scope_ref = if let StatementKind::Program(ref data) = program.inner { data.scope.clone() @@ -1081,6 +1128,7 @@ pub unsafe extern "C" fn rust_compile_builtin_file( return; }; + let arena = std::sync::Arc::new(std::mem::take(&mut parser.arena)); let scope = scope_ref.borrow(); for child in &scope.children { if let StatementKind::FunctionDeclaration(ref fd) = child.inner { @@ -1092,11 +1140,13 @@ pub unsafe extern "C" fn rust_compile_builtin_file( vm_ptr, source_code_ptr, true, // strict + arena.clone(), ); if !sfd_ptr.is_null() - && let Some(name_ident) = &fd.name + && let Some(name_ident) = fd.name { - push_function(ctx, sfd_ptr, name_ident.name.as_ptr(), name_ident.name.len()); + let name = &arena.identifiers[name_ident].name; + push_function(ctx, sfd_ptr, name.as_ptr(), name.len()); } } } @@ -1151,6 +1201,7 @@ pub unsafe extern "C" fn rust_compile_parsed_module( module_context, cb, &mut parsed.function_table, + &parsed.arena, ); // 4. Compute requested modules (sorted by source offset). @@ -1177,6 +1228,7 @@ pub unsafe extern "C" fn rust_compile_parsed_module( } let mut generator = new_program_generator(true, vm_ptr, source_code_ptr, source_len); generator.function_table = std::mem::take(&mut parsed.function_table); + generator.arena = parsed.arena.clone(); compile_program_body( &mut generator, &parsed.program, @@ -1228,6 +1280,7 @@ pub unsafe extern "C" fn rust_materialize_compiled_module( module_context, cb, &mut bytecode.generator.function_table, + &compiled.parsed.arena, ); extract_requested_modules(&compiled.parsed.scope_ref.borrow(), module_context, cb); @@ -1422,9 +1475,11 @@ pub unsafe extern "C" fn rust_compile_module( return std::ptr::null_mut(); } + let parsed_ref = &*parsed; write_ast_dump_output( - &(*parsed).program, - &(*parsed).function_table, + &parsed_ref.program, + &parsed_ref.function_table, + &parsed_ref.arena.identifiers, ast_dump_output, ast_dump_output_len, ); @@ -1589,7 +1644,9 @@ unsafe fn extract_module_declarations( ctx: *mut c_void, cb: &ModuleCallbacks, function_table: &mut ast::FunctionTable, + arena: &std::sync::Arc, ) { + let identifiers = &arena.identifiers; unsafe { use ast::StatementKind; @@ -1597,7 +1654,7 @@ unsafe fn extract_module_declarations( // Var declared names (walk all nesting levels). for child in &scope.children { - collect_module_var_names(&child.inner, ctx, cb.push_var_name); + collect_module_var_names(&child.inner, ctx, cb.push_var_name, identifiers); } // Lexical bindings and functions to initialize. @@ -1616,19 +1673,25 @@ unsafe fn extract_module_declarations( match declaration { StatementKind::FunctionDeclaration(fd) => { - let is_default = is_exported && fd.name.as_ref().is_some_and(|n| n.name == default_name); + let is_default = is_exported && fd.name.is_some_and(|n| identifiers[n].name == default_name); let function_data = function_table.take(fd.function_id); let subtable = function_table.extract_reachable(&function_data); - let sfd_ptr = - bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, true); + let sfd_ptr = bytecode::ffi::create_sfd_for_gdi( + function_data, + subtable, + vm_ptr, + source_code_ptr, + true, + arena.clone(), + ); if sfd_ptr.is_null() { continue; } // Get the binding name from the AST (e.g., "*default*" for anonymous defaults). - let binding_name = if let Some(name_ident) = &fd.name { - name_ident.name.to_utf16_string() + let binding_name = if let Some(name_ident) = fd.name { + identifiers[name_ident].name.to_utf16_string() } else { continue; }; @@ -1651,21 +1714,22 @@ unsafe fn extract_module_declarations( (cb.push_lexical_binding)(ctx, binding_name.as_ptr(), binding_name.len(), false, function_index); } StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name_ident) = class_data.name { - (cb.push_lexical_binding)(ctx, name_ident.name.as_ptr(), name_ident.name.len(), false, -1); + if let Some(name_ident) = class_data.name { + let name = &identifiers[name_ident].name; + (cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), false, -1); } } StatementKind::VariableDeclaration(vd) if vd.kind != ast::DeclarationKind::Var => { let is_constant = vd.kind == ast::DeclarationKind::Const; for declaration in &vd.declarations { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { (cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), is_constant, -1); }); } } StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { (cb.push_lexical_binding)(ctx, name.as_ptr(), name.len(), false, -1); }); } @@ -1681,24 +1745,25 @@ unsafe fn collect_module_var_names( statement: &ast::StatementKind, ctx: *mut c_void, push_var_name: ModuleNameCallback, + identifiers: &ast::IdentifierArena, ) { unsafe { match statement { ast::StatementKind::VariableDeclaration(vd) if vd.kind == ast::DeclarationKind::Var => { for declaration in &vd.declarations { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { push_var_name(ctx, name.as_ptr(), name.len()); }); } } ast::StatementKind::Export(export_data) => { if let Some(ref stmt) = export_data.statement { - collect_module_var_names(&stmt.inner, ctx, push_var_name); + collect_module_var_names(&stmt.inner, ctx, push_var_name, identifiers); } } _ => { for_each_child_statement(statement, &mut |child| { - collect_module_var_names(child, ctx, push_var_name); + collect_module_var_names(child, ctx, push_var_name, identifiers); }); } } @@ -1843,16 +1908,20 @@ unsafe extern "C" { /// Recursively collect var-declared names from a statement and all nested /// statements, excluding function/class bodies (which create new var scopes). -fn collect_var_names_recursive(statement: &ast::StatementKind, push_name: &mut dyn FnMut(&[u16])) { +fn collect_var_names_recursive( + statement: &ast::StatementKind, + identifiers: &ast::IdentifierArena, + push_name: &mut dyn FnMut(&[u16]), +) { match statement { ast::StatementKind::VariableDeclaration(vd) if vd.kind == ast::DeclarationKind::Var => { for declaration in &vd.declarations { - for_each_bound_name(&declaration.target, push_name); + for_each_bound_name(&declaration.target, identifiers, push_name); } } _ => { for_each_child_statement(statement, &mut |child| { - collect_var_names_recursive(child, push_name); + collect_var_names_recursive(child, identifiers, push_name); }); } } @@ -1875,16 +1944,18 @@ fn extract_gdi_common( push_annex_b_name: &mut dyn FnMut(&[u16]), push_lexical_binding: &mut dyn FnMut(&[u16], bool), function_table: &mut ast::FunctionTable, + arena: &std::sync::Arc, ) { + let identifiers = &arena.identifiers; use ast::{DeclarationKind, StatementKind}; // Var names (var declarations at any nesting level + top-level function declarations) for child in &scope.children { - collect_var_names_recursive(&child.inner, push_var_name); + collect_var_names_recursive(&child.inner, identifiers, push_var_name); if let StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name + && let Some(name_ident) = fd.name { - push_var_name(&name_ident.name); + push_var_name(&identifiers[name_ident].name); } } @@ -1894,29 +1965,36 @@ fn extract_gdi_common( let mut last_position: std::collections::HashMap = std::collections::HashMap::new(); for (i, child) in scope.children.iter().enumerate() { if let StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name + && let Some(name_ident) = fd.name { - last_position.insert(name_ident.name.clone(), i); + last_position.insert(identifiers[name_ident].name.clone(), i); } } for (i, child) in scope.children.iter().enumerate() { if let StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name - && last_position.get(&name_ident.name).copied() == Some(i) + && let Some(name_ident) = fd.name + && last_position.get(&identifiers[name_ident].name).copied() == Some(i) { let function_data = function_table.take(fd.function_id); let subtable = function_table.extract_reachable(&function_data); let sfd_ptr = unsafe { - bytecode::ffi::create_sfd_for_gdi(function_data, subtable, vm_ptr, source_code_ptr, is_strict) + bytecode::ffi::create_sfd_for_gdi( + function_data, + subtable, + vm_ptr, + source_code_ptr, + is_strict, + arena.clone(), + ) }; assert!(!sfd_ptr.is_null(), "create_sfd_for_gdi returned null"); - push_function(sfd_ptr, name_ident.name.as_slice()); + push_function(sfd_ptr, identifiers[name_ident].name.as_slice()); } } // Var-scoped names (var VariableDeclaration names, excluding function declarations) for child in &scope.children { - collect_var_names_recursive(&child.inner, push_var_scoped_name); + collect_var_names_recursive(&child.inner, identifiers, push_var_scoped_name); } for name in &scope.annexb_function_names { @@ -1928,21 +2006,21 @@ fn extract_gdi_common( StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => { let is_constant = vd.kind == DeclarationKind::Const; for declaration in &vd.declarations { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { push_lexical_binding(name, is_constant); }); } } StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { push_lexical_binding(name, false); }); } } StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name) = class_data.name { - push_lexical_binding(&name.name, false); + if let Some(name) = class_data.name { + push_lexical_binding(&identifiers[name].name, false); } } _ => {} @@ -1959,6 +2037,7 @@ unsafe fn extract_eval_gdi( source_code_ptr: *const c_void, ctx: *mut c_void, function_table: &mut ast::FunctionTable, + arena: &std::sync::Arc, ) { unsafe { use bytecode::ffi::{ @@ -1981,6 +2060,7 @@ unsafe fn extract_eval_gdi( eval_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const); }, function_table, + arena, ); } } @@ -1994,7 +2074,9 @@ unsafe fn extract_script_gdi( source_code_ptr: *const c_void, ctx: *mut c_void, function_table: &mut ast::FunctionTable, + arena: &std::sync::Arc, ) { + let identifiers = &arena.identifiers; unsafe { use ast::{DeclarationKind, StatementKind}; use bytecode::ffi::{ @@ -2007,21 +2089,22 @@ unsafe fn extract_script_gdi( match &child.inner { StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => { for declaration in &vd.declarations { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { script_gdi_push_lexical_name(ctx, name.as_ptr(), name.len()); }); } } StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { - for_each_bound_name(&declaration.target, &mut |name| { + for_each_bound_name(&declaration.target, identifiers, &mut |name| { script_gdi_push_lexical_name(ctx, name.as_ptr(), name.len()); }); } } StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name) = class_data.name { - script_gdi_push_lexical_name(ctx, name.name.as_ptr(), name.name.len()); + if let Some(name) = class_data.name { + let n = &identifiers[name].name; + script_gdi_push_lexical_name(ctx, n.as_ptr(), n.len()); } } _ => {} @@ -2041,6 +2124,7 @@ unsafe fn extract_script_gdi( script_gdi_push_lexical_binding(ctx, name.as_ptr(), name.len(), is_const); }, function_table, + arena, ); } } @@ -2107,26 +2191,34 @@ fn for_each_child_statement(statement: &ast::StatementKind, f: &mut dyn FnMut(&a } } -fn for_each_bound_name(target: &ast::VariableDeclaratorTarget, f: &mut dyn FnMut(&[u16])) { +fn for_each_bound_name( + target: &ast::VariableDeclaratorTarget, + identifiers: &ast::IdentifierArena, + f: &mut dyn FnMut(&[u16]), +) { match target { - ast::VariableDeclaratorTarget::Identifier(id) => f(&id.name), + ast::VariableDeclaratorTarget::Identifier(id) => f(&identifiers[*id].name), ast::VariableDeclaratorTarget::BindingPattern(pattern) => { - for_each_bound_name_in_pattern(pattern, f); + for_each_bound_name_in_pattern(pattern, identifiers, f); } } } -fn for_each_bound_name_in_pattern(pattern: &ast::BindingPattern, f: &mut dyn FnMut(&[u16])) { +fn for_each_bound_name_in_pattern( + pattern: &ast::BindingPattern, + identifiers: &ast::IdentifierArena, + f: &mut dyn FnMut(&[u16]), +) { for entry in &pattern.entries { match &entry.alias { None => { if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name { - f(&id.name); + f(&identifiers[*id].name); } } - Some(ast::BindingEntryAlias::Identifier(id)) => f(&id.name), + Some(ast::BindingEntryAlias::Identifier(id)) => f(&identifiers[*id].name), Some(ast::BindingEntryAlias::BindingPattern(inner)) => { - for_each_bound_name_in_pattern(inner, f); + for_each_bound_name_in_pattern(inner, identifiers, f); } Some(ast::BindingEntryAlias::MemberExpression(_)) => {} } @@ -2197,8 +2289,9 @@ pub unsafe extern "C" fn rust_compile_function( return std::ptr::null_mut(); } let payload = Box::from_raw(rust_function_ast as *mut ast::FunctionPayload); + let arena = payload.arena.clone(); let (_function_data, mut precompiled) = - compile_function_payload_to_bytecode(*payload, source_len, builtin_abstract_operations_enabled); + compile_function_payload_to_bytecode(*payload, source_len, builtin_abstract_operations_enabled, arena); precompiled.generator.vm_ptr = vm_ptr; precompiled.generator.source_code_ptr = source_code_ptr; @@ -2219,6 +2312,7 @@ fn compile_function_payload_to_bytecode( payload: ast::FunctionPayload, source_len: usize, builtin_abstract_operations_enabled: bool, + arena: std::sync::Arc, ) -> (Box, Box) { let function_data = Box::new(payload.data); @@ -2230,9 +2324,10 @@ fn compile_function_payload_to_bytecode( // Compute SFD metadata before codegen so the generator can optimize // direct `this` access when it does not need environment resolution. - let sfd_metadata = compute_sfd_metadata(&function_data); + let sfd_metadata = compute_sfd_metadata(&function_data, &arena.identifiers); let mut generator = bytecode::generator::Generator::new(); + generator.arena = arena; generator.strict = function_data.is_strict_mode; generator.this_value_needs_environment_resolution = sfd_metadata.this_value_needs_environment_resolution; generator.builtin_abstract_operations_enabled = builtin_abstract_operations_enabled; @@ -2340,7 +2435,10 @@ struct BodyScopeInfo { /// Compute FDI runtime metadata matching the C++ SharedFunctionInstanceData /// constructor (ECMA-262 §10.2.11). -fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generator::FunctionSfdMetadata { +fn compute_sfd_metadata( + function_data: &ast::FunctionData, + identifiers: &ast::IdentifierArena, +) -> bytecode::generator::FunctionSfdMetadata { let body_scope = match &function_data.body.inner { ast::StatementKind::FunctionBody { scope, .. } => Some(scope), _ => None, @@ -2401,13 +2499,14 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato let mut parameters_in_environment: usize = 0; for parameter in &function_data.parameters { match ¶meter.binding { - ast::FunctionParameterBinding::Identifier(ident) => { + ast::FunctionParameterBinding::Identifier(id) => { + let ident = &identifiers[*id]; if parameter_names.insert(ident.name.to_utf16_string()) && !ident.is_local() { parameters_in_environment += 1; } } ast::FunctionParameterBinding::BindingPattern(pattern) => { - for_each_binding_pattern_identifier(pattern, &mut |ident| { + for_each_binding_pattern_identifier(pattern, identifiers, &mut |ident| { if parameter_names.insert(ident.name.to_utf16_string()) && !ident.is_local() { parameters_in_environment += 1; } @@ -2467,7 +2566,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato } // §10.2.11 step 30: lexical environment. - let non_local_lex_count = count_non_local_lex_declarations(body_scope); + let non_local_lex_count = count_non_local_lex_declarations(body_scope, identifiers); if strict { // Lex env == var env == function env. function_environment_bindings_count += non_local_lex_count; @@ -2489,7 +2588,7 @@ fn compute_sfd_metadata(function_data: &ast::FunctionData) -> bytecode::generato } } - let non_local_lex_count = count_non_local_lex_declarations(body_scope); + let non_local_lex_count = count_non_local_lex_declarations(body_scope, identifiers); if strict { // Lex env == var env. var_environment_bindings_count += non_local_lex_count; @@ -2542,7 +2641,7 @@ unsafe fn write_sfd_metadata(sfd_ptr: *mut c_void, metadata: &bytecode::generato /// Count non-local lexically-declared identifiers in a function body scope. /// Returns the count (used for environment sizing in the function_environment_needed /// computation). -fn count_non_local_lex_declarations(scope: &Rc>) -> usize { +fn count_non_local_lex_declarations(scope: &Rc>, identifiers: &ast::IdentifierArena) -> usize { let sd = scope.borrow(); let mut count = 0; for child in &sd.children { @@ -2551,18 +2650,18 @@ fn count_non_local_lex_declarations(scope: &Rc>) -> usiz use parser::DeclarationKind; if vd.kind == DeclarationKind::Let || vd.kind == DeclarationKind::Const { for declaration in &vd.declarations { - count_non_local_names_in_target(&declaration.target, &mut count); + count_non_local_names_in_target(&declaration.target, &mut count, identifiers); } } } ast::StatementKind::UsingDeclaration(declarations) => { for declaration in declarations.iter() { - count_non_local_names_in_target(&declaration.target, &mut count); + count_non_local_names_in_target(&declaration.target, &mut count, identifiers); } } ast::StatementKind::ClassDeclaration(class_data) => { - if let Some(ref name_ident) = class_data.name - && !name_ident.is_local() + if let Some(name_ident) = class_data.name + && !identifiers[name_ident].is_local() { count += 1; } @@ -2573,33 +2672,41 @@ fn count_non_local_lex_declarations(scope: &Rc>) -> usiz count } -fn count_non_local_names_in_target(target: &ast::VariableDeclaratorTarget, count: &mut usize) { +fn count_non_local_names_in_target( + target: &ast::VariableDeclaratorTarget, + count: &mut usize, + identifiers: &ast::IdentifierArena, +) { match target { - ast::VariableDeclaratorTarget::Identifier(ident) => { - if !ident.is_local() { + ast::VariableDeclaratorTarget::Identifier(id) => { + if !identifiers[*id].is_local() { *count += 1; } } ast::VariableDeclaratorTarget::BindingPattern(pattern) => { - count_non_local_names_in_binding_pattern(pattern, count); + count_non_local_names_in_binding_pattern(pattern, count, identifiers); } } } -fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count: &mut usize) { +fn count_non_local_names_in_binding_pattern( + pattern: &ast::BindingPattern, + count: &mut usize, + identifiers: &ast::IdentifierArena, +) { for entry in &pattern.entries { match &entry.alias { - Some(ast::BindingEntryAlias::Identifier(ident)) => { - if !ident.is_local() { + Some(ast::BindingEntryAlias::Identifier(id)) => { + if !identifiers[*id].is_local() { *count += 1; } } Some(ast::BindingEntryAlias::BindingPattern(sub)) => { - count_non_local_names_in_binding_pattern(sub, count); + count_non_local_names_in_binding_pattern(sub, count, identifiers); } None => { - if let Some(ast::BindingEntryName::Identifier(ident)) = &entry.name - && !ident.is_local() + if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name + && !identifiers[*id].is_local() { *count += 1; } @@ -2609,16 +2716,20 @@ fn count_non_local_names_in_binding_pattern(pattern: &ast::BindingPattern, count } } -fn for_each_binding_pattern_identifier(pattern: &ast::BindingPattern, callback: &mut dyn FnMut(&Rc)) { +fn for_each_binding_pattern_identifier( + pattern: &ast::BindingPattern, + identifiers: &ast::IdentifierArena, + callback: &mut dyn FnMut(&ast::Identifier), +) { for entry in &pattern.entries { match &entry.alias { - Some(ast::BindingEntryAlias::Identifier(ident)) => callback(ident), + Some(ast::BindingEntryAlias::Identifier(id)) => callback(&identifiers[*id]), Some(ast::BindingEntryAlias::BindingPattern(sub)) => { - for_each_binding_pattern_identifier(sub, callback); + for_each_binding_pattern_identifier(sub, identifiers, callback); } None => { - if let Some(ast::BindingEntryName::Identifier(ident)) = &entry.name { - callback(ident); + if let Some(ast::BindingEntryName::Identifier(id)) = &entry.name { + callback(&identifiers[*id]); } } Some(ast::BindingEntryAlias::MemberExpression(_)) => {} diff --git a/Libraries/LibJS/Rust/src/parser.rs b/Libraries/LibJS/Rust/src/parser.rs index 456a6681bb..3453620ace 100644 --- a/Libraries/LibJS/Rust/src/parser.rs +++ b/Libraries/LibJS/Rust/src/parser.rs @@ -34,11 +34,10 @@ use std::collections::{HashMap, HashSet}; -use std::rc::Rc; - use crate::ast::{ - BindingPattern, Expression, ExpressionKind, FunctionData, FunctionId, FunctionParameter, FunctionTable, Identifier, - PrivateIdentifier, ProgramData, ScopeData, SharedUtf16String, SourceRange, Statement, StatementKind, Utf16String, + AstArena, BindingPattern, Expression, ExpressionKind, FunctionData, FunctionId, FunctionParameter, FunctionTable, + Identifier, IdentifierId, PrivateIdentifier, ProgramData, ScopeData, SharedUtf16String, SourceRange, Statement, + StatementKind, Utf16String, }; use crate::lexer::{Lexer, ch}; use crate::scope_collector::{ScopeCollector, ScopeCollectorState}; @@ -74,7 +73,7 @@ pub struct ParamInfo { pub name: Utf16String, pub is_rest: bool, pub is_from_pattern: bool, - pub identifier: Option>, + pub identifier: Option, } /// Result of parsing a property key (object literal or class element). @@ -251,7 +250,7 @@ pub struct Parser<'a> { /// Caller drains this after calling parse_binding_pattern. /// Each entry is (name, identifier) — allows scope analysis to annotate /// binding pattern identifiers with local variable info. - pub(crate) pattern_bound_names: Vec<(SharedUtf16String, Rc)>, + pub(crate) pattern_bound_names: Vec<(SharedUtf16String, IdentifierId)>, /// Set during synthesize_binding_pattern to allow MemberExpressions as binding targets. allow_member_expressions: bool, @@ -285,6 +284,10 @@ pub struct Parser<'a> { /// Side table owning all FunctionData produced during parsing. pub function_table: FunctionTable, + /// Bulk storage for identifiers, scopes, and interned strings. Replaces + /// the old per-node `Rc` heap allocations. + pub arena: AstArena, + /// Stack of nested function ids discovered while parsing each active function. /// /// When the parser finishes a function, the top list becomes that @@ -339,6 +342,7 @@ impl<'a> Parser<'a> { scope_collector: ScopeCollector::new(), exported_names: HashSet::new(), function_table: FunctionTable::new(), + arena: AstArena::new(), function_context_stack: Vec::new(), arrow_function_failed_positions: HashSet::new(), } @@ -382,8 +386,9 @@ impl<'a> Parser<'a> { function_id } - pub(crate) fn make_identifier(&self, start: Position, name: impl Into) -> Rc { - Rc::new(Identifier::new(self.range_from(start), name.into())) + pub(crate) fn make_identifier(&mut self, start: Position, name: impl Into) -> IdentifierId { + let identifier = Identifier::new(self.range_from(start), name.into()); + self.arena.identifiers.insert(identifier) } pub(crate) fn token_identifier_name(&self, token: &Token) -> SharedUtf16String { @@ -417,11 +422,15 @@ impl<'a> Parser<'a> { info_index += 1; (pi.name.clone(), pi.is_rest, pi.is_from_pattern) } else { - (id.name.to_utf16_string(), parameter.is_rest, false) + ( + self.arena.identifiers[*id].name.to_utf16_string(), + parameter.is_rest, + false, + ) }; entries.push(ParameterEntry { name, - identifier: Some(id.clone()), + identifier: Some(*id), is_rest, is_from_pattern, is_first_from_pattern: false, @@ -445,7 +454,7 @@ impl<'a> Parser<'a> { let pi = ¶meter_info[info_index]; entries.push(ParameterEntry { name: pi.name.clone(), - identifier: pi.identifier.clone(), + identifier: pi.identifier, is_rest: pi.is_rest, is_from_pattern: true, is_first_from_pattern: false, @@ -455,8 +464,10 @@ impl<'a> Parser<'a> { } } } - self.scope_collector - .set_function_parameters(&entries, has_parameter_expressions); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.set_function_parameters(&entries, has_parameter_expressions, &arena.identifiers); } // === Token access === @@ -1097,7 +1108,7 @@ impl<'a> Parser<'a> { // Collect all declared names at module level. let mut declared_names: HashSet = HashSet::new(); for child in children { - collect_module_declared_names(child, &mut declared_names); + collect_module_declared_names(child, &mut declared_names, &self.arena.identifiers); } // Check each export's local bindings. @@ -1352,32 +1363,40 @@ fn is_use_strict(raw: &[u16]) -> bool { } /// Collect all binding names introduced by a variable declarator target. -fn collect_binding_names(target: &crate::ast::VariableDeclaratorTarget, names: &mut HashSet) { +fn collect_binding_names( + target: &crate::ast::VariableDeclaratorTarget, + names: &mut HashSet, + identifiers: &crate::ast::IdentifierArena, +) { match target { crate::ast::VariableDeclaratorTarget::Identifier(identifier) => { - names.insert(identifier.name.to_utf16_string()); + names.insert(identifiers[*identifier].name.to_utf16_string()); } crate::ast::VariableDeclaratorTarget::BindingPattern(pattern) => { - collect_binding_pattern_names(pattern, names); + collect_binding_pattern_names(pattern, names, identifiers); } } } /// Collect all binding names from a binding pattern (object or array destructuring). -fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &mut HashSet) { +fn collect_binding_pattern_names( + pattern: &crate::ast::BindingPattern, + names: &mut HashSet, + identifiers: &crate::ast::IdentifierArena, +) { for entry in &pattern.entries { if let Some(ref alias) = entry.alias { match alias { crate::ast::BindingEntryAlias::Identifier(identifier) => { - names.insert(identifier.name.to_utf16_string()); + names.insert(identifiers[*identifier].name.to_utf16_string()); } crate::ast::BindingEntryAlias::BindingPattern(nested) => { - collect_binding_pattern_names(nested, names); + collect_binding_pattern_names(nested, names, identifiers); } crate::ast::BindingEntryAlias::MemberExpression(_) => {} } } else if let Some(crate::ast::BindingEntryName::Identifier(identifier)) = &entry.name { - names.insert(identifier.name.to_utf16_string()); + names.insert(identifiers[*identifier].name.to_utf16_string()); } } } @@ -1386,23 +1405,27 @@ fn collect_binding_pattern_names(pattern: &crate::ast::BindingPattern, names: &m /// This includes lexical declarations (let/const), function/class declarations, imports, /// and also `var` declarations which hoist to module scope even when nested inside /// blocks, loops, if/else, etc. -fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut HashSet) { +fn collect_module_declared_names( + statement: &crate::ast::Statement, + names: &mut HashSet, + identifiers: &crate::ast::IdentifierArena, +) { use crate::ast::*; match &statement.inner { StatementKind::VariableDeclaration(data) => { // All top-level declarations (var, let, const) are module-scoped. for decl in &data.declarations { - collect_binding_names(&decl.target, names); + collect_binding_names(&decl.target, names, identifiers); } } StatementKind::FunctionDeclaration(data) => { - if let Some(ref name) = data.name { - names.insert(name.name.to_utf16_string()); + if let Some(name) = data.name { + names.insert(identifiers[name].name.to_utf16_string()); } } StatementKind::ClassDeclaration(data) => { - if let Some(ref name) = data.name { - names.insert(name.name.to_utf16_string()); + if let Some(name) = data.name { + names.insert(identifiers[name].name.to_utf16_string()); } } StatementKind::Import(data) => { @@ -1412,12 +1435,12 @@ fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut } StatementKind::Export(data) => { if let Some(ref stmt) = data.statement { - collect_module_declared_names(stmt, names); + collect_module_declared_names(stmt, names, identifiers); } } // For any other statement, recurse to find hoisted var declarations. _ => { - collect_var_declared_names(statement, names); + collect_var_declared_names(statement, names, identifiers); } } } @@ -1426,61 +1449,65 @@ fn collect_module_declared_names(statement: &crate::ast::Statement, names: &mut /// `var` declarations are hoisted to the enclosing function/module scope, /// so we must walk into blocks, loops, if/else, switch, try/catch, etc. /// We do NOT walk into function bodies since `var` does not hoist out of functions. -fn collect_var_declared_names(statement: &crate::ast::Statement, names: &mut HashSet) { +fn collect_var_declared_names( + statement: &crate::ast::Statement, + names: &mut HashSet, + identifiers: &crate::ast::IdentifierArena, +) { use crate::ast::*; match &statement.inner { StatementKind::VariableDeclaration(data) if matches!(data.kind, DeclarationKind::Var) => { for decl in &data.declarations { - collect_binding_names(&decl.target, names); + collect_binding_names(&decl.target, names, identifiers); } } StatementKind::Block(scope) => { for child in &scope.borrow().children { - collect_var_declared_names(child, names); + collect_var_declared_names(child, names, identifiers); } } StatementKind::If(data) => { - collect_var_declared_names(&data.consequent, names); + collect_var_declared_names(&data.consequent, names, identifiers); if let Some(ref alt) = data.alternate { - collect_var_declared_names(alt, names); + collect_var_declared_names(alt, names, identifiers); } } StatementKind::While(data) | StatementKind::DoWhile(data) => { - collect_var_declared_names(&data.body, names); + collect_var_declared_names(&data.body, names, identifiers); } StatementKind::With(data) => { - collect_var_declared_names(&data.body, names); + collect_var_declared_names(&data.body, names, identifiers); } StatementKind::For(data) => { if let Some(ForInit::Declaration(ref decl)) = data.init { - collect_var_declared_names(decl, names); + collect_var_declared_names(decl, names, identifiers); } - collect_var_declared_names(&data.body, names); + collect_var_declared_names(&data.body, names, identifiers); } StatementKind::ForInOf(data) => { if let ForInOfLhs::Declaration(ref decl) = data.lhs { - collect_var_declared_names(decl, names); + collect_var_declared_names(decl, names, identifiers); } - collect_var_declared_names(&data.body, names); + collect_var_declared_names(&data.body, names, identifiers); } StatementKind::Switch(data) => { for case in &data.cases { for child in &case.scope.borrow().children { - collect_var_declared_names(child, names); + collect_var_declared_names(child, names, identifiers); } } } StatementKind::Try(data) => { - collect_var_declared_names(&data.block, names); + collect_var_declared_names(&data.block, names, identifiers); if let Some(ref handler) = data.handler { - collect_var_declared_names(&handler.body, names); + collect_var_declared_names(&handler.body, names, identifiers); } if let Some(ref finalizer) = data.finalizer { - collect_var_declared_names(finalizer, names); + collect_var_declared_names(finalizer, names, identifiers); } } StatementKind::Labelled(data) => { - collect_var_declared_names(&data.item, names); + collect_var_declared_names(&data.item, names, identifiers); } // Don't recurse into functions (var doesn't hoist out of functions). // Don't recurse into let/const (they are block-scoped, not hoisted). diff --git a/Libraries/LibJS/Rust/src/parser/declarations.rs b/Libraries/LibJS/Rust/src/parser/declarations.rs index b42a298248..ff683ca688 100644 --- a/Libraries/LibJS/Rust/src/parser/declarations.rs +++ b/Libraries/LibJS/Rust/src/parser/declarations.rs @@ -8,7 +8,6 @@ use std::cell::Cell; use std::collections::{HashMap, HashSet}; -use std::rc::Rc; use crate::ast::*; use crate::lexer::ch; @@ -18,7 +17,7 @@ use crate::parser::{ }; use crate::token::TokenType; -fn expression_into_identifier(expression: Expression) -> Rc { +fn expression_into_identifier(expression: Expression) -> IdentifierId { match expression.inner { ExpressionKind::Identifier(id) => id, _ => unreachable!("expected Identifier expression"), @@ -26,12 +25,12 @@ fn expression_into_identifier(expression: Expression) -> Rc { } /// Extract bound names from a declaration for export statements. -fn get_declaration_export_names(statement: &Statement) -> Vec { +fn get_declaration_export_names(statement: &Statement, identifiers: &IdentifierArena) -> Vec { match &statement.inner { StatementKind::VariableDeclaration(vd) => { let mut names = Vec::new(); for declaration in &vd.declarations { - collect_declarator_names(&declaration.target, &mut names); + collect_declarator_names(&declaration.target, &mut names, identifiers); } names } @@ -39,21 +38,21 @@ fn get_declaration_export_names(statement: &Statement) -> Vec { let mut names = Vec::new(); for declaration in declarations.iter() { if let VariableDeclaratorTarget::Identifier(id) = &declaration.target { - names.push(id.name.to_utf16_string()); + names.push(identifiers[*id].name.to_utf16_string()); } } names } StatementKind::FunctionDeclaration(fd) => { - if let Some(ref name) = fd.name { - vec![name.name.to_utf16_string()] + if let Some(name) = fd.name { + vec![identifiers[name].name.to_utf16_string()] } else { Vec::new() } } StatementKind::ClassDeclaration(class) => { - if let Some(ref name) = class.name { - vec![name.name.to_utf16_string()] + if let Some(name) = class.name { + vec![identifiers[name].name.to_utf16_string()] } else { Vec::new() } @@ -62,24 +61,28 @@ fn get_declaration_export_names(statement: &Statement) -> Vec { } } -fn collect_declarator_names(target: &VariableDeclaratorTarget, names: &mut Vec) { +fn collect_declarator_names( + target: &VariableDeclaratorTarget, + names: &mut Vec, + identifiers: &IdentifierArena, +) { match target { - VariableDeclaratorTarget::Identifier(id) => names.push(id.name.to_utf16_string()), - VariableDeclaratorTarget::BindingPattern(pat) => collect_pattern_names(pat, names), + VariableDeclaratorTarget::Identifier(id) => names.push(identifiers[*id].name.to_utf16_string()), + VariableDeclaratorTarget::BindingPattern(pat) => collect_pattern_names(pat, names, identifiers), } } -fn collect_pattern_names(pat: &BindingPattern, names: &mut Vec) { +fn collect_pattern_names(pat: &BindingPattern, names: &mut Vec, identifiers: &IdentifierArena) { for entry in &pat.entries { match &entry.alias { - Some(BindingEntryAlias::Identifier(id)) => names.push(id.name.to_utf16_string()), - Some(BindingEntryAlias::BindingPattern(nested)) => collect_pattern_names(nested, names), + Some(BindingEntryAlias::Identifier(id)) => names.push(identifiers[*id].name.to_utf16_string()), + Some(BindingEntryAlias::BindingPattern(nested)) => collect_pattern_names(nested, names, identifiers), _ => {} } if entry.alias.is_none() && let Some(BindingEntryName::Identifier(id)) = &entry.name { - names.push(id.name.to_utf16_string()); + names.push(identifiers[*id].name.to_utf16_string()); } } } @@ -150,11 +153,15 @@ impl Parser<'_> { let id = self.make_identifier(declaration_start, name.clone()); if kind == DeclarationKind::Var { - self.scope_collector.add_var_declaration( - &[(name.as_slice(), Some(id.clone()))], + let Self { + scope_collector, arena, .. + } = self; + scope_collector.add_var_declaration( + &[(name.as_slice(), Some(id))], declaration_line, declaration_column, Some(DeclarationKind::Var), + &arena.identifiers, ); } else { self.scope_collector.add_lexical_declaration( @@ -162,7 +169,10 @@ impl Parser<'_> { declaration_line, declaration_column, ); - self.scope_collector.register_identifier(id.clone(), Some(kind)); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, Some(kind), &arena.identifiers); } VariableDeclaratorTarget::Identifier(id) @@ -188,14 +198,20 @@ impl Parser<'_> { // Register bound names with scope collector. if kind == DeclarationKind::Var { - let entries: Vec<(&[u16], Option>)> = bound_names - .iter() - .map(|(n, id)| (n.as_slice(), Some(id.clone()))) - .collect(); + let entries: Vec<(&[u16], Option)> = + bound_names.iter().map(|(n, id)| (n.as_slice(), Some(*id))).collect(); // NOTE: Binding pattern identifiers don't get declaration_kind, // matching C++ behavior where only simple identifiers do. - self.scope_collector - .add_var_declaration(&entries, declaration_line, declaration_column, None); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.add_var_declaration( + &entries, + declaration_line, + declaration_column, + None, + &arena.identifiers, + ); } else { let refs: Vec<&[u16]> = bound_names.iter().map(|(n, _)| n.as_slice()).collect(); self.scope_collector @@ -204,8 +220,11 @@ impl Parser<'_> { // so they get is_local() annotations. // NOTE: C++ does not pass declaration_kind for binding pattern identifiers, // only for simple identifier declarations. + let Self { + scope_collector, arena, .. + } = self; for (_name, id) in &bound_names { - self.scope_collector.register_identifier(id.clone(), None); + scope_collector.register_identifier(*id, None, &arena.identifiers); } } @@ -301,7 +320,10 @@ impl Parser<'_> { .add_lexical_declaration(&[name.as_slice()], declaration_line, declaration_column); // C++ calls parse_lexical_binding() without declaration_kind for using, // so we pass None to match. - self.scope_collector.register_identifier(id.clone(), None); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); let init = if self.match_token(TokenType::Equals) { self.consume(); @@ -378,13 +400,18 @@ impl Parser<'_> { self.last_function_kind = kind; // Register function declaration in parent scope (before opening function scope). - self.scope_collector.add_function_declaration( + let strict = self.flags.strict_mode; + let Self { + scope_collector, arena, .. + } = self; + scope_collector.add_function_declaration( &fn_name, - name.clone(), + name, kind, - self.flags.strict_mode, + strict, declaration_line, declaration_column, + &arena.identifiers, ); let fn_name_for_scope = if fn_name.is_empty() { @@ -404,7 +431,7 @@ impl Parser<'_> { start, saved_might_need_arguments, ); - let decl_name = fd.name.clone(); + let decl_name = fd.name; let decl_kind = fd.kind; let function_id = self.insert_function_data(fd); self.statement( @@ -452,8 +479,11 @@ impl Parser<'_> { // This must happen before open_function_scope so that the identifier group // exists with declaration_kind=None, preventing later var declarations // with the same name from setting a spurious declaration_kind. - if let Some(ref id) = name { - self.scope_collector.register_identifier(id.clone(), None); + if let Some(id) = name { + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); } // Open function scope (function expression name is bound within its own scope). @@ -482,7 +512,7 @@ impl Parser<'_> { #[allow(clippy::too_many_arguments)] fn parse_function_common( &mut self, - name: Option>, + name: Option, fn_name: &[u16], kind: FunctionKind, is_async: bool, @@ -704,13 +734,14 @@ impl Parser<'_> { // The inner class scope (opened/closed inside parse_class_expression) // binds the name for self-reference. The outer scope needs the name // registered as a lexical declaration so it's visible to sibling code. - if let Some(ref name_ident) = data.name { - self.scope_collector.add_lexical_declaration( - &[&name_ident.name as &[u16]], - start.line, - start.column, - ); - self.scope_collector.register_identifier(name_ident.clone(), None); + if let Some(name_ident) = data.name { + let name_slice = self.arena.identifiers[name_ident].name.clone(); + self.scope_collector + .add_lexical_declaration(&[name_slice.as_slice()], start.line, start.column); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(name_ident, None, &arena.identifiers); } self.statement(start, StatementKind::ClassDeclaration(data)) } @@ -735,7 +766,10 @@ impl Parser<'_> { if has_super { let arguments_name = Utf16String::from(utf16!("args")); - let arguments_ref = Rc::new(Identifier::new(self.range_from(start), arguments_name.clone().into())); + let arguments_ref = self + .arena + .identifiers + .insert(Identifier::new(self.range_from(start), arguments_name.clone().into())); let arguments_expression = self.expression(start, ExpressionKind::Identifier(arguments_ref)); let super_call = self.expression( @@ -754,7 +788,10 @@ impl Parser<'_> { StatementKind::Block(ScopeData::shared_with_children(vec![return_statement])), ); - let arguments_binding = Rc::new(Identifier::new(self.range_from(start), arguments_name.into())); + let arguments_binding = self + .arena + .identifiers + .insert(Identifier::new(self.range_from(start), arguments_name.into())); let parameters = vec![FunctionParameter { binding: FunctionParameterBinding::Identifier(arguments_binding), default_value: None, @@ -1238,15 +1275,16 @@ impl Parser<'_> { if !is_arrow { self.check_identifier_name_for_assignment_validity(&value, false); } - let id = Rc::new(Identifier::new( - self.range_from(formal_parameters_start), - self.token_identifier_name(&token), - )); + let name = self.token_identifier_name(&token); + let id = self + .arena + .identifiers + .insert(Identifier::new(self.range_from(formal_parameters_start), name)); parameter_info.push(ParamInfo { name: value, is_rest: rest, is_from_pattern: false, - identifier: Some(id.clone()), + identifier: Some(id), }); (FunctionParameterBinding::Identifier(id), false) } else if self.match_token(TokenType::CurlyOpen) || self.match_token(TokenType::BracketOpen) { @@ -1263,7 +1301,7 @@ impl Parser<'_> { } else { self.expected("parameter name"); self.consume(); - let id = Rc::new(Identifier::new( + let id = self.arena.identifiers.insert(Identifier::new( self.range_from(parameter_start), Utf16String::default().into(), )); @@ -1444,7 +1482,10 @@ impl Parser<'_> { let token = self.consume_property_key_token(); let (value, _has_octal) = self.parse_string_value(&token); let id = self.make_identifier(entry_start, value); - self.scope_collector.register_identifier(id.clone(), None); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); entry_name = Some(BindingEntryName::Identifier(id)); } else if self.match_token(TokenType::BigIntLiteral) { let token = self.consume_property_key_token(); @@ -1455,14 +1496,20 @@ impl Parser<'_> { value.to_vec() }; let id = self.make_identifier(entry_start, name_value); - self.scope_collector.register_identifier(id.clone(), None); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); entry_name = Some(BindingEntryName::Identifier(id)); } else { let token = self.consume_property_key_token(); let name = self.token_identifier_name(&token); entry_name_value = name.clone(); let id = self.make_identifier(entry_start, name); - self.scope_collector.register_identifier(id.clone(), None); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); entry_name = Some(BindingEntryName::Identifier(id)); } } else if self.match_token(TokenType::BracketOpen) { @@ -1504,7 +1551,7 @@ impl Parser<'_> { let token = self.consume(); let name = self.token_identifier_name(&token); let id = self.make_identifier(alias_start, name.clone()); - self.pattern_bound_names.push((name, id.clone())); + self.pattern_bound_names.push((name, id)); entry_alias = Some(BindingEntryAlias::Identifier(id)); } else { self.expected("identifier or binding pattern"); @@ -1518,8 +1565,8 @@ impl Parser<'_> { if entry_is_keyword { self.syntax_error("Binding pattern target may not be a reserved word"); } - if let Some(BindingEntryName::Identifier(ref id)) = entry_name { - self.pattern_bound_names.push((entry_name_value, id.clone())); + if let Some(BindingEntryName::Identifier(id)) = entry_name { + self.pattern_bound_names.push((entry_name_value, id)); } } } @@ -1537,7 +1584,8 @@ impl Parser<'_> { entry_alias = Some(BindingEntryAlias::MemberExpression(Box::new(expression))); } else if Self::is_identifier(&expression) { let id = expression_into_identifier(expression); - self.pattern_bound_names.push((id.name.clone(), id.clone())); + let name = self.arena.identifiers[id].name.clone(); + self.pattern_bound_names.push((name, id)); entry_alias = Some(BindingEntryAlias::Identifier(id)); } else { self.syntax_error("Invalid destructuring assignment target"); @@ -1551,7 +1599,7 @@ impl Parser<'_> { let token = self.consume(); let name = self.token_identifier_name(&token); let id = self.make_identifier(alias_start, name.clone()); - self.pattern_bound_names.push((name, id.clone())); + self.pattern_bound_names.push((name, id)); entry_alias = Some(BindingEntryAlias::Identifier(id)); } else { self.expected("identifier or binding pattern"); @@ -1792,9 +1840,9 @@ impl Parser<'_> { let declaration = self.parse_function_declaration_for_export(has_default_name); if !has_default_name && let StatementKind::FunctionDeclaration(ref fd) = declaration.inner - && let Some(ref name_id) = fd.name + && let Some(name_id) = fd.name { - local_name = Some(name_id.name.to_utf16_string()); + local_name = Some(self.arena.identifiers[name_id].name.to_utf16_string()); } statement = Some(Box::new(declaration)); } else if self.match_token(TokenType::Class) { @@ -1802,9 +1850,9 @@ impl Parser<'_> { if next.token_type != TokenType::CurlyOpen && next.token_type != TokenType::Extends { let declaration = self.parse_class_declaration(); if let StatementKind::ClassDeclaration(ref class) = declaration.inner - && let Some(ref name_id) = class.name + && let Some(name_id) = class.name { - local_name = Some(name_id.name.to_utf16_string()); + local_name = Some(self.arena.identifiers[name_id].name.to_utf16_string()); } statement = Some(Box::new(declaration)); } else { @@ -1877,7 +1925,7 @@ impl Parser<'_> { check_for_from = FromSpecifier::Required; } else if self.match_declaration() { let declaration = self.parse_declaration(); - let names = get_declaration_export_names(&declaration); + let names = get_declaration_export_names(&declaration, &self.arena.identifiers); for name in &names { entries.push(ExportEntry { kind: ExportEntryKind::NamedExport, @@ -1888,7 +1936,7 @@ impl Parser<'_> { statement = Some(Box::new(declaration)); } else if self.match_token(TokenType::Var) { let var_declaration = self.parse_variable_declaration(false); - let names = get_declaration_export_names(&var_declaration); + let names = get_declaration_export_names(&var_declaration, &self.arena.identifiers); for name in &names { entries.push(ExportEntry { kind: ExportEntryKind::NamedExport, diff --git a/Libraries/LibJS/Rust/src/parser/expressions.rs b/Libraries/LibJS/Rust/src/parser/expressions.rs index b8f8347bbf..122299631f 100644 --- a/Libraries/LibJS/Rust/src/parser/expressions.rs +++ b/Libraries/LibJS/Rust/src/parser/expressions.rs @@ -7,7 +7,6 @@ //! Expression parsing: primary, secondary (binary/postfix), unary, and //! precedence climbing. -use std::rc::Rc; use std::sync::Arc; use crate::ast::*; @@ -178,16 +177,19 @@ impl Parser<'_> { // C++ checks for freestanding `arguments` references here (after // parse_primary_expression), NOT during consume(). This avoids // falsely flagging parameter names like `function f(arguments)`. - if let ExpressionKind::Identifier(ref id) = expression.inner - && id.name == utf16!("arguments") + if let ExpressionKind::Identifier(id) = expression.inner + && self.arena.identifiers[id].name == utf16!("arguments") { // https://tc39.es/ecma262/#sec-class-static-initialization-blocks // It is a Syntax Error if ContainsArguments of ClassStaticBlockBody is true. if self.flags.in_class_static_init_block { self.syntax_error("'arguments' is not allowed in class static initialization blocks"); - } else if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&id.name) { - self.scope_collector - .set_contains_access_to_arguments_object_in_non_strict_mode(); + } else { + let name = self.arena.identifiers[id].name.clone(); + if !self.flags.strict_mode && !self.scope_collector.has_declaration_in_current_function(&name) { + self.scope_collector + .set_contains_access_to_arguments_object_in_non_strict_mode(); + } } } @@ -447,8 +449,12 @@ impl Parser<'_> { return (arrow, false); } let token = self.consume_and_check_identifier(); - let id = self.make_identifier(start, self.token_identifier_name(&token)); - self.scope_collector.register_identifier(id.clone(), None); + let name = self.token_identifier_name(&token); + let id = self.make_identifier(start, name); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); (self.expression(start, ExpressionKind::Identifier(id)), true) } @@ -583,14 +589,22 @@ impl Parser<'_> { self.syntax_error("'yield' is not allowed as an identifier in this context"); } let token = self.consume_and_check_identifier(); - let id = self.make_identifier(start, self.token_identifier_name(&token)); - self.scope_collector.register_identifier(id.clone(), None); + let name = self.token_identifier_name(&token); + let id = self.make_identifier(start, name); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); (self.expression(start, ExpressionKind::Identifier(id)), true) } else if self.match_token(TokenType::EscapedKeyword) { self.syntax_error("Keyword must not contain escaped characters"); let token = self.consume_and_check_identifier(); - let id = self.make_identifier(start, self.token_identifier_name(&token)); - self.scope_collector.register_identifier(id.clone(), None); + let name = self.token_identifier_name(&token); + let id = self.make_identifier(start, name); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); (self.expression(start, ExpressionKind::Identifier(id)), true) } else { self.expected("primary expression"); @@ -801,9 +815,14 @@ impl Parser<'_> { // Register synthesized identifiers with the scope collector so // they get resolved as locals during analyze(). let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect(); - for (name, id) in &bound_names { + for (name, _id) in &bound_names { self.check_identifier_name_for_assignment_validity(name, false); - self.scope_collector.register_identifier(id.clone(), None); + } + let Self { + scope_collector, arena, .. + } = self; + for (_name, id) in &bound_names { + scope_collector.register_identifier(*id, None, &arena.identifiers); } self.pattern_bound_names = saved_bound_names; self.consume(); @@ -829,8 +848,9 @@ impl Parser<'_> { if !Self::is_simple_assignment_target(&lhs, allow_call, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in assignment"); } - if let ExpressionKind::Identifier(ref id) = lhs.inner { - self.check_identifier_name_for_assignment_validity(&id.name, false); + if let ExpressionKind::Identifier(id) = lhs.inner { + let name = self.arena.identifiers[id].name.clone(); + self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); let rhs = self.parse_expression(min_precedence, Associativity::Right, forbidden); @@ -966,8 +986,9 @@ impl Parser<'_> { if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in postfix operation"); } - if let ExpressionKind::Identifier(ref id) = lhs.inner { - self.check_identifier_name_for_assignment_validity(&id.name, false); + if let ExpressionKind::Identifier(id) = lhs.inner { + let name = self.arena.identifiers[id].name.clone(); + self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); ( @@ -986,8 +1007,9 @@ impl Parser<'_> { if !Self::is_simple_assignment_target(&lhs, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in postfix operation"); } - if let ExpressionKind::Identifier(ref id) = lhs.inner { - self.check_identifier_name_for_assignment_validity(&id.name, false); + if let ExpressionKind::Identifier(id) = lhs.inner { + let name = self.arena.identifiers[id].name.clone(); + self.check_identifier_name_for_assignment_validity(&name, false); } self.consume(); ( @@ -1021,8 +1043,9 @@ impl Parser<'_> { if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in prefix operation"); } - if let ExpressionKind::Identifier(ref id) = expression.inner { - self.check_identifier_name_for_assignment_validity(&id.name, false); + if let ExpressionKind::Identifier(id) = expression.inner { + let name = self.arena.identifiers[id].name.clone(); + self.check_identifier_name_for_assignment_validity(&name, false); } self.expression( start, @@ -1039,8 +1062,9 @@ impl Parser<'_> { if !Self::is_simple_assignment_target(&expression, true, self.flags.strict_mode) { self.syntax_error("Invalid left-hand side in prefix operation"); } - if let ExpressionKind::Identifier(ref id) = expression.inner { - self.check_identifier_name_for_assignment_validity(&id.name, false); + if let ExpressionKind::Identifier(id) = expression.inner { + let name = self.arena.identifiers[id].name.clone(); + self.check_identifier_name_for_assignment_validity(&name, false); } self.expression( start, @@ -1188,8 +1212,8 @@ impl Parser<'_> { let arguments = self.parse_arguments(); // Check the actual callee expression kind, matching C++ which does // is(callee) && callee.string() == "eval". - if let ExpressionKind::Identifier(ref id) = callee.inner - && id.name == utf16!("eval") + if let ExpressionKind::Identifier(id) = callee.inner + && self.arena.identifiers[id].name == utf16!("eval") { self.scope_collector.set_contains_direct_call_to_eval(); self.scope_collector.set_uses_this(); @@ -1596,7 +1620,12 @@ impl Parser<'_> { && let Some(kv) = &key_value { let id = self.make_identifier(obj_start, kv.clone()); - self.scope_collector.register_identifier(id.clone(), None); + { + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); + } let value = self.expression(obj_start, ExpressionKind::Identifier(id)); self.consume(); // consume '=' // NB: Add a syntax error for CoverInitializedName. This error will @@ -1627,7 +1656,12 @@ impl Parser<'_> { self.syntax_error(&format!("'{name_str}' is a reserved keyword")); } let id = self.make_identifier(obj_start, kv); - self.scope_collector.register_identifier(id.clone(), None); + { + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); + } let value = self.expression(obj_start, ExpressionKind::Identifier(id)); return ObjectProperty { range: self.range_from(obj_start), @@ -2230,10 +2264,13 @@ impl Parser<'_> { self.syntax_error("'await' is a reserved identifier in async functions"); } // C++ uses rule_start (arrow function start, which is `async` for async arrows). - let binding = Rc::new(Identifier::new(self.range_from(start), value.clone().into())); + let binding = self + .arena + .identifiers + .insert(Identifier::new(self.range_from(start), value.clone().into())); parsed = ParsedParameters { parameters: vec![FunctionParameter { - binding: FunctionParameterBinding::Identifier(binding.clone()), + binding: FunctionParameterBinding::Identifier(binding), default_value: None, is_rest: false, }], diff --git a/Libraries/LibJS/Rust/src/parser/statements.rs b/Libraries/LibJS/Rust/src/parser/statements.rs index f414819efc..23dbc9e9ff 100644 --- a/Libraries/LibJS/Rust/src/parser/statements.rs +++ b/Libraries/LibJS/Rust/src/parser/statements.rs @@ -7,7 +7,6 @@ //! Statement parsing: if, for, while, switch, try, etc. use std::collections::HashSet; -use std::rc::Rc; use crate::ast::*; use crate::parser::{Associativity, ForbiddenTokens, PRECEDENCE_COMMA, Parser, Position}; @@ -463,16 +462,16 @@ impl Parser<'_> { // are still valid here. if init_starts_with_async_keyword && let LocalForInit::Expression(ref expression) = init - && let ExpressionKind::Identifier(ref ident) = expression.inner - && ident.name == utf16!("async") + && let ExpressionKind::Identifier(ident) = expression.inner + && self.arena.identifiers[ident].name == utf16!("async") { self.syntax_error("for-of statement may not use 'async' as the left-hand side"); } // https://tc39.es/ecma262/#sec-for-in-and-for-of-statements if let LocalForInit::Expression(ref expression) = init && let ExpressionKind::Member(ref data) = expression.inner - && let ExpressionKind::Identifier(ref ident) = data.object.inner - && ident.name == utf16!("let") + && let ExpressionKind::Identifier(ident) = data.object.inner + && self.arena.identifiers[ident].name == utf16!("let") { self.syntax_error("For of statement may not start with let."); } @@ -735,8 +734,12 @@ impl Parser<'_> { self.scope_collector.add_catch_parameter_pattern(&bound_names); // Register each binding pattern identifier for scope analysis // so they get is_local() annotations (matching variable declarations). - for (_name, id) in &self.pattern_bound_names { - self.scope_collector.register_identifier(id.clone(), None); + let pattern_bound_ids: Vec = self.pattern_bound_names.iter().map(|(_, id)| *id).collect(); + let Self { + scope_collector, arena, .. + } = self; + for id in pattern_bound_ids { + scope_collector.register_identifier(id, None, &arena.identifiers); } Some(CatchBinding::BindingPattern(pattern)) } else if self.match_identifier() { @@ -744,12 +747,16 @@ impl Parser<'_> { let token = self.consume(); let value = self.token_value(&token).to_vec(); self.check_identifier_name_for_assignment_validity(&value, false); - let id = Rc::new(Identifier::new( - self.range_from(parameter_start), - self.token_identifier_name(&token), - )); - self.scope_collector.register_identifier(id.clone(), None); - self.scope_collector.add_catch_parameter_identifier(&value, id.clone()); + let name = self.token_identifier_name(&token); + let id = self + .arena + .identifiers + .insert(Identifier::new(self.range_from(parameter_start), name)); + let Self { + scope_collector, arena, .. + } = self; + scope_collector.register_identifier(id, None, &arena.identifiers); + scope_collector.add_catch_parameter_identifier(&value, id); Some(CatchBinding::Identifier(id)) } else { self.expected("catch parameter"); @@ -763,7 +770,7 @@ impl Parser<'_> { // Collect catch parameter names for post-body validation. let catch_names: Vec = match ¶meter { - Some(CatchBinding::Identifier(id)) => vec![id.name.clone()], + Some(CatchBinding::Identifier(id)) => vec![self.arena.identifiers[*id].name.clone()], Some(CatchBinding::BindingPattern(_)) => self.pattern_bound_names.iter().map(|(n, _)| n.clone()).collect(), None => Vec::new(), }; @@ -780,9 +787,10 @@ impl Parser<'_> { match &child.inner { StatementKind::VariableDeclaration(vd) if vd.kind != DeclarationKind::Var => { for decl in &vd.declarations { - if let VariableDeclaratorTarget::Identifier(ref id) = decl.target { + if let VariableDeclaratorTarget::Identifier(id) = decl.target { + let id_name = self.arena.identifiers[id].name.clone(); for cn in &catch_names { - if cn.as_slice() == id.name.as_slice() { + if cn.as_slice() == id_name.as_slice() { let n = String::from_utf16_lossy(cn); self.syntax_error(&format!( "Identifier '{n}' already declared as catch parameter" @@ -793,18 +801,20 @@ impl Parser<'_> { } } StatementKind::FunctionDeclaration(fd) if fd.name.is_some() => { - let id = fd.name.as_ref().unwrap(); + let id = fd.name.unwrap(); + let id_name = self.arena.identifiers[id].name.clone(); for cn in &catch_names { - if cn.as_slice() == id.name.as_slice() { + if cn.as_slice() == id_name.as_slice() { let n = String::from_utf16_lossy(cn); self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter")); } } } StatementKind::ClassDeclaration(data) => { - if let Some(ref id) = data.name { + if let Some(id) = data.name { + let id_name = self.arena.identifiers[id].name.clone(); for cn in &catch_names { - if cn.as_slice() == id.name.as_slice() { + if cn.as_slice() == id_name.as_slice() { let n = String::from_utf16_lossy(cn); self.syntax_error(&format!("Identifier '{n}' already declared as catch parameter")); } @@ -986,9 +996,14 @@ impl Parser<'_> { let pattern = self.synthesize_binding_pattern(init_start); let bound_names: Vec<_> = self.pattern_bound_names.drain(..).collect(); - for (name, id) in &bound_names { + for (name, _id) in &bound_names { self.check_identifier_name_for_assignment_validity(name, false); - self.scope_collector.register_identifier(id.clone(), None); + } + let Self { + scope_collector, arena, .. + } = self; + for (_name, id) in &bound_names { + scope_collector.register_identifier(*id, None, &arena.identifiers); } ForInOfLhs::Pattern(pattern) } else { diff --git a/Libraries/LibJS/Rust/src/scope_collector.rs b/Libraries/LibJS/Rust/src/scope_collector.rs index 9cc12407d8..26f569c104 100644 --- a/Libraries/LibJS/Rust/src/scope_collector.rs +++ b/Libraries/LibJS/Rust/src/scope_collector.rs @@ -55,8 +55,8 @@ use std::collections::HashMap; use std::rc::Rc; use crate::ast::{ - FunctionScopeData, Identifier, LocalBinding, LocalVarKind, LocalVariable, ScopeData, SharedUtf16String, - Utf16String, VarToInit, + FunctionScopeData, IdentifierArena, IdentifierId, LocalBinding, LocalVarKind, LocalVariable, ScopeData, + SharedUtf16String, Utf16String, VarToInit, }; use crate::parser::{DeclarationKind, FunctionKind, ParseError, ProgramType}; use crate::u32_from_usize; @@ -139,7 +139,7 @@ struct ScopeVariable { flags: VarFlags, /// The Identifier AST node for the `var` declaration (used to build /// FunctionScopeData). None if not a var. - var_identifier: Option>, + var_identifier: Option, } /// Groups all Identifier AST nodes that share the same name within a scope. @@ -154,7 +154,7 @@ struct IdentifierGroup { /// (prevents local variable optimization since `with` can shadow anything). used_inside_with_statement: bool, /// All Identifier AST nodes with this name in this scope. - identifiers: Vec>, + identifiers: Vec, /// If this name was declared (var/let/const), tracks the declaration kind /// so we can annotate each Identifier AST node. declaration_kind: Option, @@ -178,7 +178,7 @@ struct ParameterName { /// Entry describing a single parameter binding for scope analysis. pub struct ParameterEntry { pub name: Utf16String, - pub identifier: Option>, + pub identifier: Option, pub is_rest: bool, pub is_from_pattern: bool, pub is_first_from_pattern: bool, @@ -525,17 +525,18 @@ impl ScopeCollector { // class static initializer). pub fn add_var_declaration( &mut self, - bound_names: &[(&[u16], Option>)], + bound_names: &[(&[u16], Option)], declaration_line: u32, declaration_column: u32, declaration_kind: Option, + identifiers: &IdentifierArena, ) { let index = self.current.expect("no current scope"); for (name, identifier) in bound_names { // Register the declaration identifier so it participates in scope analysis. if let Some(id) = identifier { - self.register_identifier(id.clone(), declaration_kind); + self.register_identifier(*id, declaration_kind, identifiers); } let mut scope_index = index; @@ -546,7 +547,7 @@ impl ScopeCollector { } let var = self.records[scope_index].variable(name); var.flags |= VarFlags::VAR; - var.var_identifier = identifier.clone(); + var.var_identifier = *identifier; if self.records[scope_index].is_top_level() { break; } @@ -562,21 +563,23 @@ impl ScopeCollector { // a `var` binding to the enclosing function scope. // In strict mode (or for async/generator functions), block-scoped function // declarations are treated as lexical bindings and are NOT Annex-B hoisted. + #[allow(clippy::too_many_arguments)] pub fn add_function_declaration( &mut self, name: &[u16], - name_identifier: Option>, + name_identifier: Option, function_kind: FunctionKind, strict_mode: bool, declaration_line: u32, declaration_column: u32, + identifiers: &IdentifierArena, ) { let index = self.current.expect("no current scope"); let scope_level = self.records[index].scope_level; // Register the name identifier so it participates in scope analysis. - if let Some(ref id) = name_identifier { - self.register_identifier(id.clone(), None); + if let Some(id) = name_identifier { + self.register_identifier(id, None, identifiers); } if scope_level != ScopeLevel::NotTopLevel && scope_level != ScopeLevel::ModuleTopLevel { @@ -628,7 +631,7 @@ impl ScopeCollector { } } - pub fn add_catch_parameter_identifier(&mut self, name: &[u16], identifier: Rc) { + pub fn add_catch_parameter_identifier(&mut self, name: &[u16], identifier: IdentifierId) { let index = self.current.expect("no current scope"); let var = self.records[index].variable(name); var.flags |= VarFlags::VAR | VarFlags::BOUND | VarFlags::CATCH_PARAMETER; @@ -637,13 +640,19 @@ impl ScopeCollector { // === Identifier registration === - pub fn register_identifier(&mut self, id: Rc, declaration_kind: Option) { + pub fn register_identifier( + &mut self, + id: IdentifierId, + declaration_kind: Option, + identifiers: &IdentifierArena, + ) { let index = self.current.expect("no current scope"); + let name = identifiers[id].name.clone(); self.records[index] .identifier_groups - .entry(id.name.clone()) + .entry(name) .and_modify(|group| { - group.identifiers.push(id.clone()); + group.identifiers.push(id); if declaration_kind.is_some() && group.declaration_kind.is_none() { group.declaration_kind = declaration_kind; } @@ -658,7 +667,12 @@ impl ScopeCollector { // === Function parameters === - pub fn set_function_parameters(&mut self, entries: &[ParameterEntry], has_parameter_expressions: bool) { + pub fn set_function_parameters( + &mut self, + entries: &[ParameterEntry], + has_parameter_expressions: bool, + identifiers: &IdentifierArena, + ) { let index = self.current.expect("no current scope"); self.records[index].has_function_parameters = true; self.records[index].has_parameter_expressions = has_parameter_expressions; @@ -681,8 +695,8 @@ impl ScopeCollector { is_rest: entry.is_rest, }); } - if let Some(ref id) = entry.identifier { - self.register_identifier(id.clone(), None); + if let Some(id) = entry.identifier { + self.register_identifier(id, None, identifiers); } let var = self.records[index].variables.entry(entry.name.clone()).or_default(); var.flags |= VarFlags::PARAMETER_CANDIDATE | VarFlags::FORBIDDEN_LEXICAL; @@ -845,32 +859,38 @@ impl ScopeCollector { // === Post-parse analysis === - pub fn analyze(&mut self, initiated_by_eval: bool) { - self.analyze_inner(initiated_by_eval, false); + pub fn analyze(&mut self, initiated_by_eval: bool, identifiers: &IdentifierArena) { + self.analyze_inner(initiated_by_eval, false, identifiers); } /// Like analyze(), but suppresses marking identifiers as global. /// Used for dynamic functions (new Function(...)) where the source is /// parsed as a Script but identifiers must not use GetGlobal/SetGlobal, /// matching the C++ path which parses as a FunctionExpression. - pub fn analyze_as_dynamic_function(&mut self) { - self.analyze_inner(false, true); + pub fn analyze_as_dynamic_function(&mut self, identifiers: &IdentifierArena) { + self.analyze_inner(false, true, identifiers); } - fn analyze_inner(&mut self, initiated_by_eval: bool, suppress_globals: bool) { + fn analyze_inner(&mut self, initiated_by_eval: bool, suppress_globals: bool, identifiers: &IdentifierArena) { if !self.records.is_empty() { - self.analyze_recursive(0, initiated_by_eval, suppress_globals); + self.analyze_recursive(0, initiated_by_eval, suppress_globals, identifiers); } } /// Analyze a scope and all its descendants, bottom-up. /// Children are analyzed first so that unresolved identifiers bubble up /// to their parent, and eval poisoning propagates outward. - fn analyze_recursive(&mut self, index: usize, initiated_by_eval: bool, suppress_globals: bool) { + fn analyze_recursive( + &mut self, + index: usize, + initiated_by_eval: bool, + suppress_globals: bool, + identifiers: &IdentifierArena, + ) { // Process children first (bottom-up traversal). let children = std::mem::take(&mut self.records[index].children); for child_index in children { - self.analyze_recursive(child_index, initiated_by_eval, suppress_globals); + self.analyze_recursive(child_index, initiated_by_eval, suppress_globals, identifiers); } // Steps 1-3 must run even for scopes without scope_data (e.g. catch @@ -881,9 +901,15 @@ impl ScopeCollector { // 1. Propagate eval() flags from children to parent. Self::propagate_eval_poisoning(&mut self.records, index); // 2. Match identifier references to declarations; optimize as locals. - Self::resolve_identifiers(&mut self.records, index, initiated_by_eval, suppress_globals); + Self::resolve_identifiers( + &mut self.records, + index, + initiated_by_eval, + suppress_globals, + identifiers, + ); // 3. Annex B: hoist block-scoped functions to enclosing function scope. - Self::hoist_functions(&mut self.records, index); + Self::hoist_functions(&mut self.records, index, identifiers); // 4. For function-like scopes, build the var declaration list that // the bytecode generator uses to initialize function-scoped variables. @@ -893,7 +919,7 @@ impl ScopeCollector { || st == ScopeType::ClassStaticInit || st == ScopeType::ClassField; if needs_fsd { - Self::build_function_scope_data(&self.records, index); + Self::build_function_scope_data(&self.records, index, identifiers); } } } @@ -931,7 +957,13 @@ impl ScopeCollector { /// - It's NOT captured by a nested function /// - It's NOT used inside a `with` statement /// - The scope chain is NOT poisoned by `eval()` - fn resolve_identifiers(records: &mut [ScopeRecord], index: usize, initiated_by_eval: bool, suppress_globals: bool) { + fn resolve_identifiers( + records: &mut [ScopeRecord], + index: usize, + initiated_by_eval: bool, + suppress_globals: bool, + identifiers: &IdentifierArena, + ) { // identifier_groups is an IndexMap, so iteration is in source order // of first reference. Local variable indices follow that order. let groups = std::mem::take(&mut records[index].identifier_groups); @@ -941,7 +973,7 @@ impl ScopeCollector { // so the bytecode generator knows how to handle TDZ checks, etc. if let Some(dk) = group.declaration_kind { for id in &group.identifiers { - id.declaration_kind.set(Some(dk)); + identifiers[*id].declaration_kind.set(Some(dk)); } } @@ -1018,7 +1050,7 @@ impl ScopeCollector { && var_flags.intersects(VarFlags::BOUND) { for id in &group.identifiers { - id.is_inside_scope_with_eval.set(true); + identifiers[*id].is_inside_scope_with_eval.set(true); } } @@ -1046,8 +1078,9 @@ impl ScopeCollector { let can_use_global = !(suppress_globals || group.used_inside_with_statement || initiated_by_eval); if can_use_global { for id in &group.identifiers { - if !id.is_inside_scope_with_eval.get() { - id.is_global.set(true); + let identifier = &identifiers[*id]; + if !identifier.is_inside_scope_with_eval.get() { + identifier.is_global.set(true); } } } @@ -1100,8 +1133,9 @@ impl ScopeCollector { let argument_index = records[ls].get_parameter_index(&name); if let Some(ai) = argument_index { for id in &group.identifiers { - id.local_index.set(ai); - id.local_type.set(Some(crate::ast::LocalType::Argument)); + let identifier = &identifiers[*id]; + identifier.local_index.set(ai); + identifier.local_type.set(Some(crate::ast::LocalType::Argument)); } } else { let lvi = u32_from_usize(sd.local_variables.len()); @@ -1110,8 +1144,9 @@ impl ScopeCollector { kind: LocalVarKind::Var, }); for id in &group.identifiers { - id.local_index.set(lvi); - id.local_type.set(Some(crate::ast::LocalType::Variable)); + let identifier = &identifiers[*id]; + identifier.local_index.set(lvi); + identifier.local_type.set(Some(crate::ast::LocalType::Variable)); } } } else { @@ -1122,8 +1157,9 @@ impl ScopeCollector { kind, }); for id in &group.identifiers { - id.local_index.set(lvi); - id.local_type.set(Some(crate::ast::LocalType::Variable)); + let identifier = &identifiers[*id]; + identifier.local_index.set(lvi); + identifier.local_type.set(Some(crate::ast::LocalType::Variable)); } } } @@ -1142,7 +1178,7 @@ impl ScopeCollector { if records[index].eval_in_current_function { for id in &group.identifiers { - id.is_inside_scope_with_eval.set(true); + identifiers[*id].is_inside_scope_with_eval.set(true); } } @@ -1172,7 +1208,7 @@ impl ScopeCollector { // - vars_to_initialize: var-declared names and their local variable indices // - functions_to_initialize: function declarations to instantiate (in reverse order) // - arguments object metadata (has_argument_parameter, has_function_named_arguments, etc.) - fn build_function_scope_data(records: &[ScopeRecord], index: usize) { + fn build_function_scope_data(records: &[ScopeRecord], index: usize, identifiers: &IdentifierArena) { let record = &records[index]; let Some(ref scope_data) = record.scope_data else { return; @@ -1202,15 +1238,15 @@ impl ScopeCollector { let sd = scope_data.borrow(); for (i, child) in sd.children.iter().enumerate() { if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name + && let Some(name_ident) = fd.name { - last_position.insert(name_ident.name.clone(), i); + last_position.insert(identifiers[name_ident].name.clone(), i); } } for (i, child) in sd.children.iter().enumerate() { if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner - && let Some(ref name_ident) = fd.name - && last_position.get(&name_ident.name).copied() == Some(i) + && let Some(name_ident) = fd.name + && last_position.get(&identifiers[name_ident].name).copied() == Some(i) { functions_to_initialize.push(crate::ast::FunctionToInit { child_index: i }); } @@ -1227,7 +1263,8 @@ impl ScopeCollector { let is_parameter = var.flags.intersects(VarFlags::FORBIDDEN_LEXICAL); let is_function_name = last_position.contains_key(name.as_slice()); - let local_info = if let Some(ref ident) = var.var_identifier { + let local_info = if let Some(ident_id) = var.var_identifier { + let ident = &identifiers[ident_id]; if ident.is_local() { Some(LocalBinding { local_type: ident.local_type.get().expect("is_local() implies local_type is Some"), @@ -1311,7 +1348,7 @@ impl ScopeCollector { /// The function propagates upward through block scopes until it reaches /// a function/program scope (top level) or is blocked by an existing /// lexical or function declaration with the same name. - fn hoist_functions(records: &mut [ScopeRecord], index: usize) { + fn hoist_functions(records: &mut [ScopeRecord], index: usize, identifiers: &IdentifierArena) { let functions = std::mem::take(&mut records[index].functions_to_hoist); for function in functions { @@ -1346,7 +1383,7 @@ impl ScopeCollector { let bs = block_scope.borrow(); for child in &bs.children { if let crate::ast::StatementKind::FunctionDeclaration(ref fd) = child.inner - && fd.name.as_ref().is_some_and(|n| n.name == function.name) + && fd.name.is_some_and(|n| identifiers[n].name == function.name) { fd.is_hoisted.set(true); }