From 2d454cb5caf7617a3722b1fb10ea64df952f863a Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:07:26 +0200 Subject: [PATCH] Implement local variable optimization --- boa_ast/src/operations.rs | 67 +++++++ boa_engine/src/bytecompiler/declarations.rs | 91 +++++++-- .../src/bytecompiler/expression/assign.rs | 36 ++-- .../src/bytecompiler/expression/unary.rs | 12 +- .../src/bytecompiler/expression/update.rs | 36 ++-- boa_engine/src/bytecompiler/function.rs | 27 ++- boa_engine/src/bytecompiler/jump_control.rs | 4 + boa_engine/src/bytecompiler/mod.rs | 175 ++++++++++++++---- .../src/bytecompiler/statement/block.rs | 10 +- boa_engine/src/bytecompiler/statement/loop.rs | 92 +++++---- .../src/bytecompiler/statement/switch.rs | 10 +- boa_engine/src/bytecompiler/statement/try.rs | 9 +- boa_engine/src/module/source.rs | 12 +- boa_engine/src/vm/code_block.rs | 18 +- boa_engine/src/vm/flowgraph/mod.rs | 8 +- .../src/vm/opcode/control_flow/return.rs | 43 +++++ boa_engine/src/vm/opcode/mod.rs | 18 +- 17 files changed, 529 insertions(+), 139 deletions(-) diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 09d7634d06b..6901fd25a65 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -2156,3 +2156,70 @@ impl<'ast> Visitor<'ast> for ReturnsValueVisitor { ControlFlow::Continue(()) } } + +/// Returns `true` if the given statement can optimize local variables. +#[must_use] +pub fn can_optimize_local_variables<'a, N>(node: &'a N) -> bool +where + &'a N: Into>, +{ + CanOptimizeLocalVariables.visit(node.into()).is_continue() +} + +/// The [`Visitor`] used for [`returns_value`]. +#[derive(Debug)] +struct CanOptimizeLocalVariables; + +impl<'ast> Visitor<'ast> for CanOptimizeLocalVariables { + type BreakTy = (); + + fn visit_with(&mut self, _node: &'ast crate::statement::With) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_call(&mut self, node: &'ast crate::expression::Call) -> ControlFlow { + if let Expression::Identifier(identifier) = node.function() { + if identifier.sym() == Sym::EVAL { + return ControlFlow::Break(()); + } + } + + try_break!(node.function().visit_with(self)); + + for arg in node.args() { + try_break!(arg.visit_with(self)); + } + + ControlFlow::Continue(()) + } + + fn visit_function(&mut self, _node: &'ast Function) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_arrow_function(&mut self, _node: &'ast ArrowFunction) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_async_function(&mut self, _node: &'ast AsyncFunction) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_async_arrow_function( + &mut self, + _node: &'ast AsyncArrowFunction, + ) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_class(&mut self, _node: &'ast Class) -> ControlFlow { + ControlFlow::Break(()) + } + + fn visit_pattern( + &mut self, + _node: &'ast crate::pattern::Pattern, + ) -> ControlFlow { + ControlFlow::Break(()) + } +} diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs index 60cdc66169b..8f1c691801b 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -23,6 +23,7 @@ use boa_interner::Sym; #[cfg(feature = "annex-b")] use boa_ast::operations::annex_b_function_declarations_names; +use super::EnvironmentAccess; use super::Operand; impl ByteCompiler<'_, '_> { @@ -570,9 +571,15 @@ impl ByteCompiler<'_, '_> { // ii. Perform ! varEnv.InitializeBinding(F, undefined). let binding = self.initialize_mutable_binding(f, true); - let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } } } @@ -742,10 +749,14 @@ impl ByteCompiler<'_, '_> { if binding_exists { // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); @@ -760,8 +771,14 @@ impl ByteCompiler<'_, '_> { // 3. Perform ! varEnv.InitializeBinding(fn, fo). self.create_mutable_binding(name, !strict); let binding = self.initialize_mutable_binding(name, !strict); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } } } } @@ -785,9 +802,15 @@ impl ByteCompiler<'_, '_> { // 3. Perform ! varEnv.InitializeBinding(vn, undefined). self.create_mutable_binding(name, !strict); let binding = self.initialize_mutable_binding(name, !strict); - let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } } } } @@ -919,6 +942,7 @@ impl ByteCompiler<'_, '_> { // NOTE(HalidOdat): Has been moved up, so "arguments" gets registed as // the first binding in the environment with index 0. if arguments_object_needed { + let function_environment_index = self.function_environment_index.take(); // Note: This happens at runtime. // a. If strict is true or simpleParameterList is false, then // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). @@ -941,6 +965,13 @@ impl ByteCompiler<'_, '_> { self.create_mutable_binding(arguments, false); } + if self.can_optimize_local_variables { + let binding = self.get_binding_value(arguments); + self.get_or_insert_binding(binding); + } + + self.function_environment_index = function_environment_index; + self.code_block_flags |= CodeBlockFlags::NEEDS_ARGUMENTS_OBJECT; } @@ -1055,15 +1086,27 @@ impl ByteCompiler<'_, '_> { else { // a. Let initialValue be ! env.GetBindingValue(n, false). let binding = self.get_binding_value(n); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::GetName, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } + } } // 5. Perform ! varEnv.InitializeBinding(n, initialValue). let binding = self.initialize_mutable_binding(n, true); - let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } // 6. NOTE: A var with the same name as a formal parameter initially has // the same value as the corresponding initialized parameter. @@ -1088,9 +1131,15 @@ impl ByteCompiler<'_, '_> { // 3. Perform ! env.InitializeBinding(n, undefined). let binding = self.initialize_mutable_binding(n, true); - let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } } } @@ -1120,9 +1169,15 @@ impl ByteCompiler<'_, '_> { // b. Perform ! varEnv.InitializeBinding(F, undefined). let binding = self.initialize_mutable_binding(f, true); - let index = self.get_or_insert_binding(binding); self.emit_opcode(Opcode::PushUndefined); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } // c. Append F to instantiatedVarNames. instantiated_var_names.push(f); diff --git a/boa_engine/src/bytecompiler/expression/assign.rs b/boa_engine/src/bytecompiler/expression/assign.rs index 8fb8f932a73..aaa6a992271 100644 --- a/boa_engine/src/bytecompiler/expression/assign.rs +++ b/boa_engine/src/bytecompiler/expression/assign.rs @@ -1,5 +1,5 @@ use crate::{ - bytecompiler::{Access, ByteCompiler, Operand}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand}, environments::BindingLocatorError, vm::{BindingOpcode, Opcode}, }; @@ -56,14 +56,22 @@ impl ByteCompiler<'_, '_> { match access { Access::Variable { name } => { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); let lex = self.current_environment.is_lex_binding(name); - if lex { - self.emit(Opcode::GetName, &[Operand::U32(index)]); - } else { - self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } else { + self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]); + } + false + } + }; if short_circuit { early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -75,12 +83,16 @@ impl ByteCompiler<'_, '_> { if use_expr { self.emit_opcode(Opcode::Dup); } - if lex { + if lex || is_fast { match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); diff --git a/boa_engine/src/bytecompiler/expression/unary.rs b/boa_engine/src/bytecompiler/expression/unary.rs index e0dcf9a9374..a82201dc43c 100644 --- a/boa_engine/src/bytecompiler/expression/unary.rs +++ b/boa_engine/src/bytecompiler/expression/unary.rs @@ -4,7 +4,7 @@ use boa_ast::{ }; use crate::{ - bytecompiler::{Access, ByteCompiler, Operand}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand}, vm::Opcode, }; @@ -28,8 +28,14 @@ impl ByteCompiler<'_, '_> { match unary.target().flatten() { Expression::Identifier(identifier) => { let binding = self.get_binding_value(*identifier); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::GetNameOrUndefined, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetNameOrUndefined, &[Operand::U32(index)]); + } + } } expr => self.compile_expr(expr, true), } diff --git a/boa_engine/src/bytecompiler/expression/update.rs b/boa_engine/src/bytecompiler/expression/update.rs index 94fd17dde79..49641764f71 100644 --- a/boa_engine/src/bytecompiler/expression/update.rs +++ b/boa_engine/src/bytecompiler/expression/update.rs @@ -1,5 +1,5 @@ use crate::{ - bytecompiler::{Access, ByteCompiler, Operand}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand}, environments::BindingLocatorError, vm::Opcode, }; @@ -24,14 +24,22 @@ impl ByteCompiler<'_, '_> { match Access::from_update_target(update.target()) { Access::Variable { name } => { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); let lex = self.current_environment.is_lex_binding(name); - if lex { - self.emit(Opcode::GetName, &[Operand::U32(index)]); - } else { - self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } else { + self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]); + } + false + } + }; self.emit_opcode(opcode); if post { @@ -40,12 +48,16 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); } - if lex { + if lex || is_fast { match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index b159f21271e..852fbec0971 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -7,7 +7,10 @@ use crate::{ vm::{CodeBlock, CodeBlockFlags, Opcode}, Context, }; -use boa_ast::function::{FormalParameterList, FunctionBody}; +use boa_ast::{ + function::{FormalParameterList, FunctionBody}, + operations::can_optimize_local_variables, +}; use boa_gc::Gc; use boa_interner::Sym; @@ -120,6 +123,9 @@ impl FunctionCompiler { // Function environment compiler.push_compile_environment(true); + compiler.function_environment_index = + Some(compiler.current_environment.environment_index()); + // Taken from: // - 15.9.3 Runtime Semantics: EvaluateAsyncConciseBody: // - 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody: @@ -136,6 +142,9 @@ impl FunctionCompiler { // ExecuteAsyncModule ( module ): compiler.emit_opcode(Opcode::CreatePromiseCapability); + // Note: We set it to one so we don't pop return value when we return. + compiler.current_stack_value_count += 1; + // 2. Let declResult be Completion(FunctionDeclarationInstantiation(functionObject, argumentsList)). // // Note: We push an exception handler so we catch exceptions that are thrown by the @@ -145,6 +154,22 @@ impl FunctionCompiler { compiler.async_handler = Some(compiler.push_handler()); } + let can_optimize_params = can_optimize_local_variables(parameters); + let can_optimize_body = can_optimize_local_variables(body); + // println!("Can optimize params: {can_optimize_params}"); + // println!("Can optimize body: {can_optimize_body}"); + + let can_optimize = can_optimize_params + && can_optimize_body + && !parameters.has_arguments() + && !parameters.has_duplicates() + && !parameters.has_rest_parameter() + && parameters.is_simple(); + println!("Can optimize function: {can_optimize}"); + + compiler.can_optimize_local_variables = + can_optimize && compiler.function_environment_index.is_some(); + let (env_label, additional_env) = compiler.function_declaration_instantiation( body, parameters, diff --git a/boa_engine/src/bytecompiler/jump_control.rs b/boa_engine/src/bytecompiler/jump_control.rs index 85708c90a98..d1127991396 100644 --- a/boa_engine/src/bytecompiler/jump_control.rs +++ b/boa_engine/src/bytecompiler/jump_control.rs @@ -102,6 +102,10 @@ impl JumpRecord { return; } JumpRecordAction::PopEnvironments { count } => { + if compiler.can_optimize_local_variables { + continue; + } + for _ in 0..count { compiler.emit_opcode(Opcode::PopEnvironment); } diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 1d8c33695ca..69db044ab50 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -225,6 +225,12 @@ pub(crate) enum Operand { U64(u64), } +#[derive(Debug, Clone, Copy)] +pub(crate) enum EnvironmentAccess { + Slow { index: u32 }, + Fast { index: u32 }, +} + /// The [`ByteCompiler`] is used to compile ECMAScript AST from [`boa_ast`] to bytecode. #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] @@ -268,11 +274,16 @@ pub struct ByteCompiler<'ctx, 'host> { handlers: ThinVec, literals_map: FxHashMap, names_map: FxHashMap, - bindings_map: FxHashMap, + bindings_map: FxHashMap, jump_info: Vec, pub(crate) in_async: bool, in_generator: bool, + can_optimize_local_variables: bool, + #[allow(dead_code)] + fast_local_variable_count: u32, + function_environment_index: Option, + /// Used to handle exception throws that escape the async function types. /// /// Async functions and async generator functions, need to be closed and resolved. @@ -326,6 +337,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { jump_info: Vec::new(), in_async: false, in_generator: false, + can_optimize_local_variables: false, + fast_local_variable_count: 0, + function_environment_index: None, async_handler: None, json_parse, current_environment, @@ -390,31 +404,56 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } #[inline] - pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 { + pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> EnvironmentAccess { if let Some(index) = self.bindings_map.get(&binding) { return *index; } + if let Some(function_environment_index) = self.function_environment_index { + if !binding.is_global() + && self.can_optimize_local_variables + && function_environment_index <= binding.environment_index() + { + let index = self.fast_local_variable_count; + self.fast_local_variable_count += 1; + + println!("Fast binding {binding:?} at {index}"); + + self.bindings_map + .insert(binding, EnvironmentAccess::Fast { index }); + return EnvironmentAccess::Fast { index }; + } + } + let index = self.bindings.len() as u32; self.bindings.push(binding); - self.bindings_map.insert(binding, index); - index + self.bindings_map + .insert(binding, EnvironmentAccess::Slow { index }); + EnvironmentAccess::Slow { index } } fn emit_binding(&mut self, opcode: BindingOpcode, name: Identifier) { match opcode { BindingOpcode::Var => { let binding = self.initialize_mutable_binding(name, true); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => {} + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefVar, &[Operand::U32(index)]); + } + } } BindingOpcode::InitVar => { if self.has_binding(name) { match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); @@ -425,25 +464,47 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } } else { let binding = self.initialize_mutable_binding(name, true); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } }; } BindingOpcode::InitLet => { let binding = self.initialize_mutable_binding(name, false); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::PutLexicalValue, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[Operand::U32(index)]); + } + } } BindingOpcode::InitConst => { let binding = self.initialize_immutable_binding(name); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::PutLexicalValue, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[Operand::U32(index)]); + } + } } BindingOpcode::SetName => match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); @@ -654,8 +715,14 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { match access { Access::Variable { name } => { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::GetName, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } + } } Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { @@ -719,24 +786,33 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { match access { Access::Variable { name } => { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); let lex = self.current_environment.is_lex_binding(name); - if !lex { - self.emit(Opcode::GetLocator, &[Operand::U32(index)]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => true, + EnvironmentAccess::Slow { index } => { + if !lex { + self.emit(Opcode::GetLocator, &[Operand::U32(index)]); + } + false + } + }; expr_fn(self, 0); if use_expr { self.emit(Opcode::Dup, &[]); } - if lex { + if lex || is_fast { match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); @@ -832,8 +908,12 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { }, Access::Variable { name } => { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DeleteName, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => self.emit_opcode(Opcode::PushFalse), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DeleteName, &[Operand::U32(index)]); + } + } } Access::This => { self.emit_opcode(Opcode::PushTrue); @@ -1147,14 +1227,24 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { .expect("function declaration must have name"); if self.annex_b_function_names.contains(&name) { let binding = self.get_binding_value(name); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::GetName, &[Operand::U32(index)]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } + } match self.set_mutable_binding_var(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); @@ -1506,6 +1596,12 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { .utf16() .into(); + if self.can_optimize_local_variables { + for handler in &mut self.handlers { + handler.stack_count += self.fast_local_variable_count; + } + } + CodeBlock { name, length: self.length, @@ -1519,6 +1615,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { compile_environments: self.compile_environments.into_boxed_slice(), handlers: self.handlers, flags: Cell::new(self.code_block_flags), + local_variable_count: self.fast_local_variable_count, } } diff --git a/boa_engine/src/bytecompiler/statement/block.rs b/boa_engine/src/bytecompiler/statement/block.rs index 665d996ea02..ff0c190ced4 100644 --- a/boa_engine/src/bytecompiler/statement/block.rs +++ b/boa_engine/src/bytecompiler/statement/block.rs @@ -5,14 +5,18 @@ impl ByteCompiler<'_, '_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { self.push_compile_environment(false); - let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment); + + let push_env = (!self.can_optimize_local_variables) + .then(|| self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); self.block_declaration_instantiation(block); self.compile_statement_list(block.statement_list(), use_expr, true); let env_index = self.pop_compile_environment(); - self.patch_jump_with_target(push_env, env_index); - self.emit_opcode(Opcode::PopEnvironment); + if let Some(push_env) = push_env { + self.patch_jump_with_target(push_env, env_index); + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index fc78ab8ee61..ce28ae73478 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -9,7 +9,7 @@ use boa_ast::{ use boa_interner::Sym; use crate::{ - bytecompiler::{Access, ByteCompiler, Operand}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand}, environments::BindingLocatorError, vm::{BindingOpcode, Opcode}, }; @@ -33,8 +33,10 @@ impl ByteCompiler<'_, '_> { } ForLoopInitializer::Lexical(decl) => { self.push_compile_environment(false); - env_labels = - Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); + if !self.can_optimize_local_variables { + env_labels = + Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); + } let names = bound_names(decl); if decl.is_const() { @@ -69,13 +71,29 @@ impl ByteCompiler<'_, '_> { if let Some(let_binding_indices) = let_binding_indices { for index in &let_binding_indices { - self.emit(Opcode::GetName, &[Operand::U32(*index)]); + match *index { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetName, &[Operand::U32(index)]); + } + } + } + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + iteration_env_labels = + Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); } - self.emit_opcode(Opcode::PopEnvironment); - iteration_env_labels = - Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); for index in let_binding_indices.iter().rev() { - self.emit(Opcode::PutLexicalValue, &[Operand::U32(*index)]); + match *index { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[Operand::U32(index)]); + } + } } } @@ -100,7 +118,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(exit); self.pop_loop_control_info(); - if env_labels.is_some() { + if env_labels.is_some() && !self.can_optimize_local_variables { self.emit_opcode(Opcode::PopEnvironment); } @@ -137,7 +155,8 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_in_loop.target(), true); } else { self.push_compile_environment(false); - let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment); + let push_env = (!self.can_optimize_local_variables) + .then(|| self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); for name in &initializer_bound_names { self.create_mutable_binding(*name, false); @@ -145,8 +164,10 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_in_loop.target(), true); let env_index = self.pop_compile_environment(); - self.patch_jump_with_target(push_env, env_index); - self.emit_opcode(Opcode::PopEnvironment); + if let Some(push_env) = push_env { + self.patch_jump_with_target(push_env, env_index); + self.emit_opcode(Opcode::PopEnvironment); + } } let early_exit = self.jump_if_null_or_undefined(); @@ -162,12 +183,13 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::IteratorValue); - let iteration_environment = if initializer_bound_names.is_empty() { - None - } else { - self.push_compile_environment(false); - Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)) - }; + let iteration_environment = + if initializer_bound_names.is_empty() || self.can_optimize_local_variables { + None + } else { + self.push_compile_environment(false); + Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)) + }; match for_in_loop.initializer() { IterableLoopInitializer::Identifier(ident) => { @@ -253,7 +275,8 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_of_loop.iterable(), true); } else { self.push_compile_environment(false); - let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment); + let push_env = (!self.can_optimize_local_variables) + .then(|| self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); for name in &initializer_bound_names { self.create_mutable_binding(*name, false); @@ -261,8 +284,10 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_of_loop.iterable(), true); let env_index = self.pop_compile_environment(); - self.patch_jump_with_target(push_env, env_index); - self.emit_opcode(Opcode::PopEnvironment); + if let Some(push_env) = push_env { + self.patch_jump_with_target(push_env, env_index); + self.emit_opcode(Opcode::PopEnvironment); + } } if for_of_loop.r#await() { @@ -290,21 +315,26 @@ impl ByteCompiler<'_, '_> { let exit = self.jump_if_true(); self.emit_opcode(Opcode::IteratorValue); - let iteration_environment = if initializer_bound_names.is_empty() { - None - } else { - self.push_compile_environment(false); - Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)) - }; + let iteration_environment = + if initializer_bound_names.is_empty() || self.can_optimize_local_variables { + None + } else { + self.push_compile_environment(false); + Some(self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)) + }; let mut handler_index = None; match for_of_loop.initializer() { IterableLoopInitializer::Identifier(ref ident) => { match self.set_mutable_binding(*ident) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(*ident); self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]); diff --git a/boa_engine/src/bytecompiler/statement/switch.rs b/boa_engine/src/bytecompiler/statement/switch.rs index 46df6c692f9..c94a6e4cbe8 100644 --- a/boa_engine/src/bytecompiler/statement/switch.rs +++ b/boa_engine/src/bytecompiler/statement/switch.rs @@ -7,7 +7,8 @@ impl ByteCompiler<'_, '_> { self.compile_expr(switch.val(), true); self.push_compile_environment(false); - let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment); + let push_env = (!self.can_optimize_local_variables) + .then(|| self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); self.block_declaration_instantiation(switch); @@ -51,7 +52,10 @@ impl ByteCompiler<'_, '_> { self.pop_switch_control_info(); let env_index = self.pop_compile_environment(); - self.patch_jump_with_target(push_env, env_index); - self.emit_opcode(Opcode::PopEnvironment); + + if let Some(push_env) = push_env { + self.patch_jump_with_target(push_env, env_index); + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/boa_engine/src/bytecompiler/statement/try.rs b/boa_engine/src/bytecompiler/statement/try.rs index fcaffc4d089..19bd424ccd1 100644 --- a/boa_engine/src/bytecompiler/statement/try.rs +++ b/boa_engine/src/bytecompiler/statement/try.rs @@ -112,7 +112,8 @@ impl ByteCompiler<'_, '_> { // stack: exception self.push_compile_environment(false); - let push_env = self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment); + let push_env = (!self.can_optimize_local_variables) + .then(|| self.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment)); if let Some(binding) = catch.parameter() { match binding { @@ -134,8 +135,10 @@ impl ByteCompiler<'_, '_> { self.compile_catch_finally_block(catch.block(), use_expr); let env_index = self.pop_compile_environment(); - self.patch_jump_with_target(push_env, env_index); - self.emit_opcode(Opcode::PopEnvironment); + if let Some(push_env) = push_env { + self.patch_jump_with_target(push_env, env_index); + self.emit_opcode(Opcode::PopEnvironment); + } } pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, has_catch: bool) { diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index 16eb4ecfce7..cfae2c4a30a 100644 --- a/boa_engine/src/module/source.rs +++ b/boa_engine/src/module/source.rs @@ -22,7 +22,7 @@ use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use crate::{ builtins::{promise::PromiseCapability, Promise}, - bytecompiler::{ByteCompiler, FunctionSpec, Operand}, + bytecompiler::{ByteCompiler, EnvironmentAccess, FunctionSpec, Operand}, environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack}, module::ModuleKind, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, @@ -1499,9 +1499,15 @@ impl SourceTextModule { compiler.create_mutable_binding(name, false); // 2. Perform ! env.InitializeBinding(dn, undefined). let binding = compiler.initialize_mutable_binding(name, false); - let index = compiler.get_or_insert_binding(binding); compiler.emit_opcode(Opcode::PushUndefined); - compiler.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + match compiler.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + compiler.emit(Opcode::SetLocal, &[Operand::U32(index)]); + } + EnvironmentAccess::Slow { index } => { + compiler.emit(Opcode::DefInitVar, &[Operand::U32(index)]); + } + } // 3. Append dn to declaredVarNames. declared_var_names.push(name); } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index f4bee9bafe9..afe8fb36971 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -161,6 +161,8 @@ pub struct CodeBlock { // TODO(#3034): Maybe changing this to Gc after garbage collection would be better than Rc. #[unsafe_ignore_trace] pub(crate) compile_environments: Box<[Rc]>, + + pub(crate) local_variable_count: u32, } /// ---- `CodeBlock` public API ---- @@ -183,6 +185,7 @@ impl CodeBlock { params: FormalParameterList::default(), handlers: ThinVec::default(), compile_environments: Box::default(), + local_variable_count: 0, } } @@ -451,6 +454,7 @@ impl CodeBlock { Instruction::CreateIteratorResult { done } => { format!("done: {done}") } + Instruction::GetLocal { index } | Instruction::SetLocal { index } => index.to_string(), Instruction::Pop | Instruction::Dup | Instruction::Swap @@ -627,9 +631,7 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"), } } } @@ -1184,6 +1186,7 @@ impl JsObject { let argument_count = args.len(); let parameters_count = code.params.as_ref().len(); + let local_variable_count = code.local_variable_count; let frame = CallFrame::new(code) .with_argument_count(argument_count as u32) @@ -1193,6 +1196,10 @@ impl JsObject { context.vm.push_frame(frame); + for _ in 0..local_variable_count { + context.vm.push(JsValue::undefined()); + } + // Push function arguments to the stack. for _ in argument_count..parameters_count { context.vm.push(JsValue::undefined()); @@ -1381,6 +1388,7 @@ impl JsObject { let argument_count = args.len(); let parameters_count = code.params.as_ref().len(); + let local_variable_count = code.local_variable_count; let has_binding_identifier = code.has_binding_identifier(); @@ -1392,6 +1400,10 @@ impl JsObject { .with_env_fp(environments_len as u32), ); + for _ in 0..local_variable_count { + context.vm.push(JsValue::undefined()); + } + // Push function arguments to the stack. for _ in argument_count..parameters_count { context.vm.push(JsValue::undefined()); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index a6b05f08803..0e3e33e5cbf 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -266,7 +266,9 @@ impl CodeBlock { | Instruction::GetNameAndLocator { .. } | Instruction::GetNameOrUndefined { .. } | Instruction::SetName { .. } - | Instruction::DeleteName { .. } => { + | Instruction::DeleteName { .. } + | Instruction::GetLocal { .. } + | Instruction::SetLocal { .. } => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } @@ -526,9 +528,7 @@ impl CodeBlock { | Instruction::Reserved53 | Instruction::Reserved54 | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"), } } diff --git a/boa_engine/src/vm/opcode/control_flow/return.rs b/boa_engine/src/vm/opcode/control_flow/return.rs index 8db9cab8707..b4d8c4aacd9 100644 --- a/boa_engine/src/vm/opcode/control_flow/return.rs +++ b/boa_engine/src/vm/opcode/control_flow/return.rs @@ -54,3 +54,46 @@ impl Operation for SetReturnValue { Ok(CompletionType::Normal) } } + +/// `GetLocal` implements the Opcode Operation for `Opcode::GetLocal` +/// +/// Operation: +/// - Sets the return value of a function. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GetLocal; + +impl Operation for GetLocal { + const NAME: &'static str = "GetLocal"; + const INSTRUCTION: &'static str = "INST - GetLocal"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::(); + + let index = context.vm.frame().fp + offset; + + let value = context.vm.stack[index as usize].clone(); + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +/// `SetLocal` implements the Opcode Operation for `Opcode::SetLocal` +/// +/// Operation: +/// - Sets the return value of a function. +#[derive(Debug, Clone, Copy)] +pub(crate) struct SetLocal; + +impl Operation for SetLocal { + const NAME: &'static str = "SetLocal"; + const INSTRUCTION: &'static str = "INST - SetLocal"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::(); + let value = context.vm.pop(); + + let index = context.vm.frame().fp + offset; + context.vm.stack[index as usize] = value; + Ok(CompletionType::Normal) + } +} diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index 8dbdb1172ed..ddb995150e8 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1946,6 +1946,20 @@ generate_opcodes! { /// Stack: **=>** PopPrivateEnvironment, + /// Get fast local variable. + /// + /// Operands: index: `u32` + /// + /// Stack: **=>** value + GetLocal { index: u32 }, + + /// Get fast local variable. + /// + /// Operands: index: `u32` + /// + /// Stack: value **=>** + SetLocal { index: u32 }, + /// No-operation instruction, does nothing. /// /// Operands: @@ -2065,10 +2079,6 @@ generate_opcodes! { Reserved55 => Reserved, /// Reserved [`Opcode`]. Reserved56 => Reserved, - /// Reserved [`Opcode`]. - Reserved57 => Reserved, - /// Reserved [`Opcode`]. - Reserved58 => Reserved, } /// Specific opcodes for bindings.