diff --git a/core/ast/src/scope.rs b/core/ast/src/scope.rs index 621189ee79..dc1fb84430 100644 --- a/core/ast/src/scope.rs +++ b/core/ast/src/scope.rs @@ -50,8 +50,9 @@ impl<'a> arbitrary::Arbitrary<'a> for Scope { #[derive(Debug, PartialEq)] pub(crate) struct Inner { + unique_id: u32, outer: Option, - index: u32, + index: RefCell, bindings: RefCell>, function: bool, } @@ -62,8 +63,9 @@ impl Scope { pub fn new_global() -> Self { Self { inner: Rc::new(Inner { + unique_id: 0, outer: None, - index: 0, + index: RefCell::default(), bindings: RefCell::default(), function: true, }), @@ -73,17 +75,29 @@ impl Scope { /// Creates a new scope. #[must_use] pub fn new(parent: Self, function: bool) -> Self { - let index = parent.inner.index + 1; + let index = *parent.inner.index.borrow() + 1; Self { inner: Rc::new(Inner { + unique_id: index, outer: Some(parent), - index, + index: RefCell::new(index), bindings: RefCell::default(), function, }), } } + /// Checks if the scope has only local bindings. + #[must_use] + pub fn all_bindings_local(&self) -> bool { + // if self.inner.function && self.inn + self.inner + .bindings + .borrow() + .values() + .all(|binding| !binding.escapes) + } + /// Marks all bindings in this scope as escaping. pub fn escape_all_bindings(&self) { for binding in self.inner.bindings.borrow_mut().values_mut() { @@ -113,7 +127,12 @@ impl Scope { pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference { if let Some(binding) = self.inner.bindings.borrow().get(&name) { IdentifierReference::new( - BindingLocator::declarative(name, self.inner.index, binding.index), + BindingLocator::declarative( + name, + *self.inner.index.borrow(), + binding.index, + self.inner.unique_id, + ), binding.lex, binding.escapes, ) @@ -134,7 +153,12 @@ impl Scope { /// Returns the index of this scope. #[must_use] pub fn scope_index(&self) -> u32 { - self.inner.index + *self.inner.index.borrow() + } + + /// Set the index of this scope. + pub(crate) fn set_index(&self, index: u32) { + *self.inner.index.borrow_mut() = index; } /// Check if the scope is a function scope. @@ -153,7 +177,12 @@ impl Scope { #[must_use] pub fn get_binding(&self, name: &JsString) -> Option { self.inner.bindings.borrow().get(name).map(|binding| { - BindingLocator::declarative(name.clone(), self.inner.index, binding.index) + BindingLocator::declarative( + name.clone(), + *self.inner.index.borrow(), + binding.index, + self.inner.unique_id, + ) }) } @@ -162,7 +191,12 @@ impl Scope { pub fn get_binding_reference(&self, name: &JsString) -> Option { self.inner.bindings.borrow().get(name).map(|binding| { IdentifierReference::new( - BindingLocator::declarative(name.clone(), self.inner.index, binding.index), + BindingLocator::declarative( + name.clone(), + *self.inner.index.borrow(), + binding.index, + self.inner.unique_id, + ), binding.lex, binding.escapes, ) @@ -209,7 +243,12 @@ impl Scope { escapes: self.is_global(), }, ); - BindingLocator::declarative(name, self.inner.index, binding_index) + BindingLocator::declarative( + name, + *self.inner.index.borrow(), + binding_index, + self.inner.unique_id, + ) } /// Crate an immutable binding. @@ -238,7 +277,12 @@ impl Scope { ) -> Result { Ok(match self.inner.bindings.borrow().get(&name) { Some(binding) if binding.mutable => IdentifierReference::new( - BindingLocator::declarative(name, self.inner.index, binding.index), + BindingLocator::declarative( + name, + *self.inner.index.borrow(), + binding.index, + self.inner.unique_id, + ), binding.lex, binding.escapes, ), @@ -281,7 +325,12 @@ impl Scope { Ok(match self.inner.bindings.borrow().get(&name) { Some(binding) if binding.mutable => IdentifierReference::new( - BindingLocator::declarative(name, self.inner.index, binding.index), + BindingLocator::declarative( + name, + *self.inner.index.borrow(), + binding.index, + self.inner.unique_id, + ), binding.lex, binding.escapes, ), @@ -358,15 +407,23 @@ pub struct BindingLocator { /// Index of the binding in the scope. binding_index: u32, + + unique_scope_id: u32, } impl BindingLocator { /// Creates a new declarative binding locator that has knows indices. - pub(crate) const fn declarative(name: JsString, scope_index: u32, binding_index: u32) -> Self { + pub(crate) const fn declarative( + name: JsString, + scope_index: u32, + binding_index: u32, + unique_scope_id: u32, + ) -> Self { Self { name, scope: scope_index + 1, binding_index, + unique_scope_id, } } @@ -376,6 +433,7 @@ impl BindingLocator { name, scope: 0, binding_index: 0, + unique_scope_id: 0, } } diff --git a/core/ast/src/scope_analyzer.rs b/core/ast/src/scope_analyzer.rs index e40534e67a..78c65256e5 100644 --- a/core/ast/src/scope_analyzer.rs +++ b/core/ast/src/scope_analyzer.rs @@ -1149,6 +1149,423 @@ impl BindingCollectorVisitor<'_> { } } +/// Optimize scope indicies when scopes only contain local bindings. +pub(crate) fn optimize_scope_indicies<'a, N>(node: &'a mut N, scope: &Scope) +where + &'a mut N: Into>, +{ + let mut visitor = ScopeIndexVisitor { + index: scope.scope_index(), + }; + visitor.visit(node.into()); +} + +struct ScopeIndexVisitor { + index: u32, +} + +impl<'ast> VisitorMut<'ast> for ScopeIndexVisitor { + type BreakTy = (); + + fn visit_function_declaration_mut( + &mut self, + node: &'ast mut FunctionDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_generator_declaration_mut( + &mut self, + node: &'ast mut GeneratorDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_async_function_declaration_mut( + &mut self, + node: &'ast mut AsyncFunctionDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_async_generator_declaration_mut( + &mut self, + node: &'ast mut AsyncGeneratorDeclaration, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_function_expression_mut( + &mut self, + node: &'ast mut FunctionExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut node.name_scope, + ) + } + + fn visit_generator_expression_mut( + &mut self, + node: &'ast mut GeneratorExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut node.name_scope, + ) + } + + fn visit_async_function_expression_mut( + &mut self, + node: &'ast mut AsyncFunctionExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut node.name_scope, + ) + } + + fn visit_async_generator_expression_mut( + &mut self, + node: &'ast mut AsyncGeneratorExpression, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut node.name_scope, + ) + } + + fn visit_arrow_function_mut( + &mut self, + node: &'ast mut ArrowFunction, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_async_arrow_function_mut( + &mut self, + node: &'ast mut AsyncArrowFunction, + ) -> ControlFlow { + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_class_declaration_mut( + &mut self, + node: &'ast mut ClassDeclaration, + ) -> ControlFlow { + let index = self.index; + if !node.name_scope.all_bindings_local() { + self.index += 1; + } + node.name_scope.set_index(self.index); + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_class_expression_mut( + &mut self, + node: &'ast mut ClassExpression, + ) -> ControlFlow { + let index = self.index; + if let Some(scope) = &node.name_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + if let Some(super_ref) = &mut node.super_ref { + try_break!(self.visit_expression_mut(super_ref)); + } + if let Some(constructor) = &mut node.constructor { + try_break!(self.visit_function_expression_mut(constructor)); + } + for element in &mut *node.elements { + try_break!(self.visit_class_element_mut(element)); + } + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_class_element_mut( + &mut self, + node: &'ast mut ClassElement, + ) -> ControlFlow { + match node { + ClassElement::MethodDefinition(node) => self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ), + ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => { + try_break!(self.visit_property_name_mut(&mut field.name)); + let index = self.index; + self.index += 1; + field.scope.set_index(self.index); + if let Some(e) = &mut field.field { + try_break!(self.visit_expression_mut(e)); + } + self.index = index; + ControlFlow::Continue(()) + } + ClassElement::PrivateFieldDefinition(field) => { + let index = self.index; + self.index += 1; + field.scope.set_index(self.index); + if let Some(e) = &mut field.field { + try_break!(self.visit_expression_mut(e)); + } + self.index = index; + ControlFlow::Continue(()) + } + ClassElement::PrivateStaticFieldDefinition(_, e) => { + if let Some(e) = e { + try_break!(self.visit_expression_mut(e)); + } + ControlFlow::Continue(()) + } + ClassElement::StaticBlock(node) => self.visit_function_like( + &mut node.body, + &mut FormalParameterList::default(), + &mut node.scopes, + &mut None, + ), + } + } + + fn visit_object_method_definition_mut( + &mut self, + node: &'ast mut ObjectMethodDefinition, + ) -> ControlFlow { + match &mut node.name { + PropertyName::Literal(_) => {} + PropertyName::Computed(name) => { + try_break!(self.visit_expression_mut(name)); + } + } + self.visit_function_like( + &mut node.body, + &mut node.parameters, + &mut node.scopes, + &mut None, + ) + } + + fn visit_block_mut(&mut self, node: &'ast mut Block) -> ControlFlow { + let index = self.index; + if let Some(scope) = &node.scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_statement_list_mut(&mut node.statements)); + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_switch_mut(&mut self, node: &'ast mut Switch) -> ControlFlow { + let index = self.index; + try_break!(self.visit_expression_mut(&mut node.val)); + if let Some(scope) = &node.scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + for case in &mut *node.cases { + try_break!(self.visit_case_mut(case)); + } + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_with_mut(&mut self, node: &'ast mut With) -> ControlFlow { + let index = self.index; + try_break!(self.visit_expression_mut(&mut node.expression)); + self.index += 1; + node.scope.set_index(self.index); + try_break!(self.visit_statement_mut(&mut node.statement)); + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_catch_mut(&mut self, node: &'ast mut Catch) -> ControlFlow { + let index = self.index; + if !node.scope.all_bindings_local() { + self.index += 1; + } + node.scope.set_index(self.index); + if let Some(binding) = &mut node.parameter { + try_break!(self.visit_binding_mut(binding)); + } + try_break!(self.visit_block_mut(&mut node.block)); + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_for_loop_mut(&mut self, node: &'ast mut ForLoop) -> ControlFlow { + let index = self.index; + if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init { + if !decl.scope.all_bindings_local() { + self.index += 1; + } + decl.scope.set_index(self.index); + } + if let Some(fli) = &mut node.inner.init { + try_break!(self.visit_for_loop_initializer_mut(fli)); + } + if let Some(expr) = &mut node.inner.condition { + try_break!(self.visit_expression_mut(expr)); + } + if let Some(expr) = &mut node.inner.final_expr { + try_break!(self.visit_expression_mut(expr)); + } + self.visit_statement_mut(&mut node.inner.body); + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_for_in_loop_mut(&mut self, node: &'ast mut ForInLoop) -> ControlFlow { + { + let index = self.index; + if let Some(scope) = &node.target_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_expression_mut(&mut node.target)); + self.index = index; + } + let index = self.index; + if let Some(scope) = &node.scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.initializer)); + try_break!(self.visit_statement_mut(&mut node.body)); + self.index = index; + ControlFlow::Continue(()) + } + + fn visit_for_of_loop_mut(&mut self, node: &'ast mut ForOfLoop) -> ControlFlow { + { + let index = self.index; + if let Some(scope) = &node.iterable_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_expression_mut(&mut node.iterable)); + self.index = index; + } + let index = self.index; + if let Some(scope) = &node.scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_iterable_loop_initializer_mut(&mut node.init)); + try_break!(self.visit_statement_mut(&mut node.body)); + self.index = index; + ControlFlow::Continue(()) + } +} + +impl ScopeIndexVisitor { + fn visit_function_like( + &mut self, + body: &mut FunctionBody, + parameters: &mut FormalParameterList, + scopes: &mut FunctionScopes, + name_scope: &mut Option, + ) -> ControlFlow<()> { + let index = self.index; + if let Some(scope) = name_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + self.index += 1; + scopes.function_scope.set_index(self.index); + if let Some(scope) = &scopes.parameters_eval_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_formal_parameter_list_mut(parameters)); + if let Some(scope) = &scopes.parameters_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + if let Some(scope) = &scopes.lexical_scope { + if !scope.all_bindings_local() { + self.index += 1; + } + scope.set_index(self.index); + } + try_break!(self.visit_function_body_mut(body)); + self.index = index; + ControlFlow::Continue(()) + } +} + /// `GlobalDeclarationInstantiation ( script, env )` /// /// More information: diff --git a/core/ast/src/source.rs b/core/ast/src/source.rs index f1220a7621..0584653c90 100644 --- a/core/ast/src/source.rs +++ b/core/ast/src/source.rs @@ -7,7 +7,7 @@ use crate::{ scope::Scope, scope_analyzer::{ analyze_binding_escapes, collect_bindings, eval_declaration_instantiation_scope, - EvalDeclarationBindings, + optimize_scope_indicies, EvalDeclarationBindings, }, visitor::{VisitWith, Visitor, VisitorMut}, ModuleItemList, StatementList, @@ -56,7 +56,11 @@ impl Script { if !collect_bindings(self, self.strict(), false, scope, interner) { return false; } - analyze_binding_escapes(self, false, scope.clone(), interner) + if !analyze_binding_escapes(self, false, scope.clone(), interner) { + return false; + } + optimize_scope_indicies(self, scope); + true } /// Analyze the scope of the script in eval mode. @@ -84,10 +88,10 @@ impl Script { if !collect_bindings(self, strict, true, lexical_scope, interner) { return Err(String::from("Failed to analyze scope")); } - if !analyze_binding_escapes(self, true, lexical_scope.clone(), interner) { return Err(String::from("Failed to analyze scope")); } + optimize_scope_indicies(self, lexical_scope); Ok(bindings) } @@ -158,7 +162,11 @@ impl Module { if !collect_bindings(self, true, false, scope, interner) { return false; } - analyze_binding_escapes(self, false, scope.clone(), interner) + if !analyze_binding_escapes(self, false, scope.clone(), interner) { + return false; + } + optimize_scope_indicies(self, &self.scope.clone()); + true } } diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index bfc6900229..4e9795c9d1 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -79,14 +79,7 @@ impl ByteCompiler<'_> { .map_or(Sym::EMPTY_STRING, Identifier::sym) .to_js_string(self.interner()); - let outer_scope = if let Some(name_scope) = class.name_scope { - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(name_scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - Some(outer_scope) - } else { - None - }; + let outer_scope = self.push_declarative_scope(class.name_scope); let mut compiler = ByteCompiler::new( class_name.clone(), @@ -181,9 +174,11 @@ impl ByteCompiler<'_> { let mut static_elements = Vec::new(); let mut static_field_name_count = 0; - if outer_scope.is_some() { + if let Some(scope) = class.name_scope { + let binding = scope.get_identifier_reference(class_name.clone()); + let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::Dup); - self.emit_binding(BindingOpcode::InitLexical, class_name.clone()); + self.emit_binding_access(Opcode::PutLexicalValue, &index); } for element in class.elements { @@ -474,12 +469,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Pop); - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } - + self.pop_declarative_scope(outer_scope); self.emit_opcode(Opcode::PopPrivateEnvironment); if !expression { diff --git a/core/engine/src/bytecompiler/declarations.rs b/core/engine/src/bytecompiler/declarations.rs index e040c99a5d..fcfce89fcc 100644 --- a/core/engine/src/bytecompiler/declarations.rs +++ b/core/engine/src/bytecompiler/declarations.rs @@ -963,10 +963,7 @@ impl ByteCompiler<'_> { } // 19-20 - if let Some(scope) = scopes.parameters_eval_scope() { - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - } + drop(self.push_declarative_scope(scopes.parameters_eval_scope())); let scope = self.lexical_scope.clone(); @@ -1057,8 +1054,7 @@ impl ByteCompiler<'_> { // visibility of declarations in the function body. // b. Let varEnv be NewDeclarativeEnvironment(env). // c. Set the VariableEnvironment of calleeContext to varEnv. - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); + drop(self.push_declarative_scope(Some(scope))); let mut variable_scope = self.lexical_scope.clone(); @@ -1177,10 +1173,7 @@ impl ByteCompiler<'_> { } // 30-31 - if let Some(scope) = scopes.lexical_scope() { - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - } + drop(self.push_declarative_scope(scopes.lexical_scope())); // 35. Let privateEnv be the PrivateEnvironment of calleeContext. // 36. For each Parse Node f of functionsToInitialize, do diff --git a/core/engine/src/bytecompiler/env.rs b/core/engine/src/bytecompiler/env.rs index 59b65184e3..1fddff71ac 100644 --- a/core/engine/src/bytecompiler/env.rs +++ b/core/engine/src/bytecompiler/env.rs @@ -1,5 +1,7 @@ use boa_ast::scope::Scope; +use crate::vm::{Constant, Opcode}; + use super::ByteCompiler; impl ByteCompiler<'_> { @@ -9,8 +11,7 @@ impl ByteCompiler<'_> { self.current_open_environments_count += 1; let index = self.constants.len() as u32; - self.constants - .push(crate::vm::Constant::Scope(scope.clone())); + self.constants.push(Constant::Scope(scope.clone())); if scope.is_function() { self.variable_scope = scope.clone(); @@ -21,6 +22,33 @@ impl ByteCompiler<'_> { index } + /// Push a declarative scope. + /// + /// Returns the outer scope. + #[must_use] + pub(crate) fn push_declarative_scope(&mut self, scope: Option<&Scope>) -> Option { + let mut scope = scope?.clone(); + if !scope.all_bindings_local() { + self.current_open_environments_count += 1; + let index = self.constants.len() as u32; + self.constants.push(Constant::Scope(scope.clone())); + self.emit_with_varying_operand(Opcode::PushScope, index); + } + std::mem::swap(&mut self.lexical_scope, &mut scope); + Some(scope) + } + + /// Pop a declarative scope. + pub(crate) fn pop_declarative_scope(&mut self, scope: Option) { + if let Some(mut scope) = scope { + std::mem::swap(&mut self.lexical_scope, &mut scope); + if !scope.all_bindings_local() { + self.current_open_environments_count -= 1; + self.emit_opcode(Opcode::PopEnvironment); + } + } + } + /// Pops the top scope. pub(crate) fn pop_scope(&mut self) { self.current_open_environments_count -= 1; diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index 3fef0a612d..5a2db2ce6a 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -129,8 +129,10 @@ impl FunctionCompiler { } if let Some(scope) = self.name_scope { - compiler.code_block_flags |= CodeBlockFlags::HAS_BINDING_IDENTIFIER; - let _ = compiler.push_scope(&scope); + if !scope.all_bindings_local() { + compiler.code_block_flags |= CodeBlockFlags::HAS_BINDING_IDENTIFIER; + let _ = compiler.push_scope(&scope); + } } // Function environment let _ = compiler.push_scope(scopes.function_scope()); diff --git a/core/engine/src/bytecompiler/statement/block.rs b/core/engine/src/bytecompiler/statement/block.rs index f4fa8bdb63..99da202362 100644 --- a/core/engine/src/bytecompiler/statement/block.rs +++ b/core/engine/src/bytecompiler/statement/block.rs @@ -1,25 +1,12 @@ -use crate::{bytecompiler::ByteCompiler, vm::Opcode}; +use crate::bytecompiler::ByteCompiler; use boa_ast::statement::Block; impl ByteCompiler<'_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { - let outer_scope = if let Some(scope) = block.scope() { - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - Some(outer_scope) - } else { - None - }; - + let scope = self.push_declarative_scope(block.scope()); self.block_declaration_instantiation(block); self.compile_statement_list(block.statement_list(), use_expr, true); - - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } + self.pop_declarative_scope(scope); } } diff --git a/core/engine/src/bytecompiler/statement/loop.rs b/core/engine/src/bytecompiler/statement/loop.rs index e0589efd7d..b02c93bcc1 100644 --- a/core/engine/src/bytecompiler/statement/loop.rs +++ b/core/engine/src/bytecompiler/statement/loop.rs @@ -22,6 +22,7 @@ impl ByteCompiler<'_> { use_expr: bool, ) { let mut let_binding_indices = None; + let mut outer_scope_local = None; let mut outer_scope = None; if let Some(init) = for_loop.init() { @@ -31,9 +32,16 @@ impl ByteCompiler<'_> { self.compile_var_decl(decl); } ForLoopInitializer::Lexical(decl) => { - outer_scope = Some(self.lexical_scope.clone()); - let scope_index = self.push_scope(decl.scope()); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); + let scope_index = if decl.scope().all_bindings_local() { + outer_scope_local = Some(self.lexical_scope.clone()); + self.lexical_scope = decl.scope().clone(); + None + } else { + outer_scope = Some(self.lexical_scope.clone()); + let scope_index = self.push_scope(decl.scope()); + self.emit_with_varying_operand(Opcode::PushScope, scope_index); + Some(scope_index) + }; let names = bound_names(decl.declaration()); if decl.declaration().is_const() { @@ -41,8 +49,8 @@ impl ByteCompiler<'_> { let mut indices = Vec::new(); for name in &names { let name = name.to_js_string(self.interner()); - let binding = self - .lexical_scope + let binding = decl + .scope() .get_binding_reference(&name) .expect("binding must exist"); let index = self.get_or_insert_binding(binding); @@ -62,8 +70,10 @@ impl ByteCompiler<'_> { self.emit_binding_access(Opcode::GetName, index); } - self.emit_opcode(Opcode::PopEnvironment); - self.emit_with_varying_operand(Opcode::PushScope, *scope_index); + if let Some(index) = scope_index { + self.emit_opcode(Opcode::PopEnvironment); + self.emit_with_varying_operand(Opcode::PushScope, *index); + } for index in let_binding_indices.iter().rev() { self.emit_binding_access(Opcode::PutLexicalValue, index); @@ -85,8 +95,10 @@ impl ByteCompiler<'_> { self.emit_binding_access(Opcode::GetName, index); } - self.emit_opcode(Opcode::PopEnvironment); - self.emit_with_varying_operand(Opcode::PushScope, *scope_index); + if let Some(index) = scope_index { + self.emit_opcode(Opcode::PopEnvironment); + self.emit_with_varying_operand(Opcode::PushScope, *index); + } for index in let_binding_indices.iter().rev() { self.emit_binding_access(Opcode::PutLexicalValue, index); @@ -115,11 +127,10 @@ impl ByteCompiler<'_> { self.patch_jump(exit); self.pop_loop_control_info(); - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); + if let Some(outer_scope_local) = outer_scope_local { + self.lexical_scope = outer_scope_local; } + self.pop_declarative_scope(outer_scope); } pub(crate) fn compile_for_in_loop( @@ -138,17 +149,9 @@ impl ByteCompiler<'_> { } } } - if let Some(scope) = for_in_loop.target_scope() { - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - self.compile_expr(for_in_loop.target(), true); - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } else { - self.compile_expr(for_in_loop.target(), true); - } + let outer_scope = self.push_declarative_scope(for_in_loop.target_scope()); + self.compile_expr(for_in_loop.target(), true); + self.pop_declarative_scope(outer_scope); let early_exit = self.jump_if_null_or_undefined(); self.emit_opcode(Opcode::CreateForInIterator); @@ -163,13 +166,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::IteratorValue); - let mut outer_scope = None; - - if let Some(scope) = for_in_loop.scope() { - outer_scope = Some(self.lexical_scope.clone()); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - } + let outer_scope = self.push_declarative_scope(for_in_loop.scope()); match for_in_loop.initializer() { IterableLoopInitializer::Identifier(ident) => { @@ -208,12 +205,7 @@ impl ByteCompiler<'_> { } self.compile_stmt(for_in_loop.body(), use_expr, true); - - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } + self.pop_declarative_scope(outer_scope); self.emit(Opcode::Jump, &[Operand::U32(start_address)]); @@ -234,17 +226,9 @@ impl ByteCompiler<'_> { label: Option, use_expr: bool, ) { - if let Some(scope) = for_of_loop.iterable_scope() { - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - self.compile_expr(for_of_loop.iterable(), true); - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } else { - self.compile_expr(for_of_loop.iterable(), true); - } + let outer_scope = self.push_declarative_scope(for_of_loop.iterable_scope()); + self.compile_expr(for_of_loop.iterable(), true); + self.pop_declarative_scope(outer_scope); if for_of_loop.r#await() { self.emit_opcode(Opcode::GetAsyncIterator); @@ -271,14 +255,7 @@ impl ByteCompiler<'_> { let exit = self.jump_if_true(); self.emit_opcode(Opcode::IteratorValue); - let mut outer_scope = None; - - if let Some(scope) = for_of_loop.scope() { - outer_scope = Some(self.lexical_scope.clone()); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - } - + let outer_scope = self.push_declarative_scope(for_of_loop.scope()); let handler_index = self.push_handler(); match for_of_loop.initializer() { @@ -354,12 +331,7 @@ impl ByteCompiler<'_> { self.patch_jump(exit); } - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } - + self.pop_declarative_scope(outer_scope); self.emit(Opcode::Jump, &[Operand::U32(start_address)]); self.patch_jump(exit); diff --git a/core/engine/src/bytecompiler/statement/switch.rs b/core/engine/src/bytecompiler/statement/switch.rs index d1bb4b8d38..3a53d9cc39 100644 --- a/core/engine/src/bytecompiler/statement/switch.rs +++ b/core/engine/src/bytecompiler/statement/switch.rs @@ -5,15 +5,7 @@ impl ByteCompiler<'_> { /// Compile a [`Switch`] `boa_ast` node pub(crate) fn compile_switch(&mut self, switch: &Switch, use_expr: bool) { self.compile_expr(switch.val(), true); - - let outer_scope = if let Some(scope) = switch.scope() { - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(scope); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); - Some(outer_scope) - } else { - None - }; + let outer_scope = self.push_declarative_scope(switch.scope()); self.block_declaration_instantiation(switch); @@ -55,11 +47,6 @@ impl ByteCompiler<'_> { } self.pop_switch_control_info(); - - if let Some(outer_scope) = outer_scope { - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); - } + self.pop_declarative_scope(outer_scope); } } diff --git a/core/engine/src/bytecompiler/statement/try.rs b/core/engine/src/bytecompiler/statement/try.rs index ad0b626c5a..98073bdf32 100644 --- a/core/engine/src/bytecompiler/statement/try.rs +++ b/core/engine/src/bytecompiler/statement/try.rs @@ -108,11 +108,7 @@ impl ByteCompiler<'_> { } pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) { - // stack: exception - - let outer_scope = self.lexical_scope.clone(); - let scope_index = self.push_scope(catch.scope()); - self.emit_with_varying_operand(Opcode::PushScope, scope_index); + let outer_scope = self.push_declarative_scope(Some(catch.scope())); if let Some(binding) = catch.parameter() { match binding { @@ -130,9 +126,7 @@ impl ByteCompiler<'_> { self.compile_catch_finally_block(catch.block(), use_expr); - self.pop_scope(); - self.lexical_scope = outer_scope; - self.emit_opcode(Opcode::PopEnvironment); + self.pop_declarative_scope(outer_scope); } pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, has_catch: bool) {