From 8f055b584094bcf2ebedca2dd57536fc170bbfb4 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:19:47 +0200 Subject: [PATCH] Eliminate unneeded environemts --- boa_ast/src/operations.rs | 7 + boa_engine/src/bytecompiler/declarations.rs | 84 +++++++--- .../src/bytecompiler/expression/assign.rs | 36 +++-- .../src/bytecompiler/expression/unary.rs | 12 +- .../src/bytecompiler/expression/update.rs | 36 +++-- boa_engine/src/bytecompiler/function.rs | 10 +- boa_engine/src/bytecompiler/jump_control.rs | 4 + boa_engine/src/bytecompiler/mod.rs | 153 +++++++++++++----- .../src/bytecompiler/statement/block.rs | 10 +- boa_engine/src/bytecompiler/statement/loop.rs | 84 ++++++---- .../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 | 21 ++- 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, 416 insertions(+), 141 deletions(-) diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 09dab5a6745..6901fd25a65 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -2215,4 +2215,11 @@ impl<'ast> Visitor<'ast> for CanOptimizeLocalVariables { 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 dc4ae66241f..9ca37f0f25d 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -23,6 +23,8 @@ use boa_interner::Sym; #[cfg(feature = "annex-b")] use boa_ast::operations::annex_b_function_declarations_names; +use super::EnvironmentAccess; + impl ByteCompiler<'_, '_> { /// `GlobalDeclarationInstantiation ( script, env )` /// @@ -568,9 +570,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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } } } @@ -737,10 +745,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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); @@ -755,8 +767,12 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } } } } @@ -780,9 +796,13 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } } } } @@ -914,6 +934,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). @@ -936,6 +957,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; } @@ -1051,15 +1079,25 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetName, &[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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } // 6. NOTE: A var with the same name as a formal parameter initially has // the same value as the corresponding initialized parameter. @@ -1084,9 +1122,13 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } } } @@ -1116,9 +1158,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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[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 f5f103646e1..4cecb831fe6 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}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess}, 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, &[index]); - } else { - self.emit(Opcode::GetNameAndLocator, &[index]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[index]); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit(Opcode::GetName, &[index]); + } else { + self.emit(Opcode::GetNameAndLocator, &[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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); diff --git a/boa_engine/src/bytecompiler/expression/unary.rs b/boa_engine/src/bytecompiler/expression/unary.rs index ff5d6708f82..beda069b3a6 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}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess}, 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::GetNameOrUndefined, &[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 61d537b0583..12819c00f2b 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}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess}, 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, &[index]); - } else { - self.emit(Opcode::GetNameAndLocator, &[index]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::GetLocal, &[index]); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit(Opcode::GetName, &[index]); + } else { + self.emit(Opcode::GetNameAndLocator, &[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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index 01045f7660a..9832bd12c8f 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -123,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: @@ -153,8 +156,8 @@ impl FunctionCompiler { 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}"); + // println!("Can optimize params: {can_optimize_params}"); + // println!("Can optimize body: {can_optimize_body}"); let can_optimize = can_optimize_params && can_optimize_body @@ -164,6 +167,9 @@ impl FunctionCompiler { && 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 ee4aa2e7dd2..4e998ee5124 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -210,6 +210,12 @@ impl Access<'_> { } } +#[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)] @@ -253,11 +259,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. @@ -311,6 +322,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, @@ -375,31 +389,54 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => {} + EnvironmentAccess::Slow { index } => self.emit(Opcode::DefVar, &[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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); @@ -410,25 +447,37 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + } }; } BindingOpcode::InitLet => { let binding = self.initialize_mutable_binding(name, false); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::PutLexicalValue, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[index]); + } + } } BindingOpcode::InitConst => { let binding = self.initialize_immutable_binding(name); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::PutLexicalValue, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[index]); + } + } } BindingOpcode::SetName => match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => self.emit(Opcode::SetName, &[index]), + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); @@ -592,8 +641,10 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::GetLocal, &[index]), + EnvironmentAccess::Slow { index } => self.emit(Opcode::GetName, &[index]), + } } Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { @@ -658,24 +709,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, &[index]); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => true, + EnvironmentAccess::Slow { index } => { + if !lex { + self.emit(Opcode::GetLocator, &[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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); @@ -771,8 +831,10 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => self.emit_opcode(Opcode::PushFalse), + EnvironmentAccess::Slow { index } => self.emit(Opcode::DeleteName, &[index]), + } } Access::This => { self.emit_opcode(Opcode::PushTrue); @@ -1099,14 +1161,20 @@ 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, &[index]); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::GetLocal, &[index]), + EnvironmentAccess::Slow { index } => self.emit(Opcode::GetName, &[index]), + } match self.set_mutable_binding_var(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::SetName, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::SetName, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit(Opcode::ThrowMutateImmutable, &[index]); @@ -1429,6 +1497,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, @@ -1442,6 +1516,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 38c2f18ecba..7213950eec7 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}, + bytecompiler::{Access, ByteCompiler, EnvironmentAccess}, 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,23 @@ impl ByteCompiler<'_, '_> { if let Some(let_binding_indices) = let_binding_indices { for index in &let_binding_indices { - self.emit(Opcode::GetName, &[*index]); + match *index { + EnvironmentAccess::Fast { index } => self.emit(Opcode::GetLocal, &[index]), + EnvironmentAccess::Slow { index } => self.emit(Opcode::GetName, &[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, &[*index]); + match *index { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::PutLexicalValue, &[index]); + } + } } } @@ -100,7 +112,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 +149,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 +158,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 +177,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 +269,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 +278,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 +309,24 @@ 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, &[index]); - } + Ok(binding) => match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => self.emit(Opcode::SetLocal, &[index]), + EnvironmentAccess::Slow { index } => { + self.emit(Opcode::DefInitVar, &[index]); + } + }, Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(*ident); self.emit(Opcode::ThrowMutateImmutable, &[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 d05501d1774..6e7de959809 100644 --- a/boa_engine/src/bytecompiler/statement/try.rs +++ b/boa_engine/src/bytecompiler/statement/try.rs @@ -111,7 +111,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 { @@ -133,8 +134,10 @@ impl ByteCompiler<'_, '_> { self.compile_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 21e74a9e100..25517f17542 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}, + bytecompiler::{ByteCompiler, EnvironmentAccess, FunctionSpec}, 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, &[index]); + match compiler.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + compiler.emit(Opcode::SetLocal, &[index]); + } + EnvironmentAccess::Slow { index } => { + compiler.emit(Opcode::DefInitVar, &[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 5419f4cdc13..cf63c67d7f4 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, } } @@ -361,7 +364,9 @@ impl CodeBlock { | Opcode::Call | Opcode::New | Opcode::SuperCall - | Opcode::ConcatToString => { + | Opcode::ConcatToString + | Opcode::GetLocal + | Opcode::SetLocal => { let result = self.read::(*pc).to_string(); *pc += size_of::(); result @@ -675,9 +680,7 @@ impl CodeBlock { | Opcode::Reserved54 | Opcode::Reserved55 | Opcode::Reserved56 - | Opcode::Reserved57 - | Opcode::Reserved58 - | Opcode::Reserved59 => unreachable!("Reserved opcodes are unrechable"), + | Opcode::Reserved57 => unreachable!("Reserved opcodes are unrechable"), } } } @@ -1232,6 +1235,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) @@ -1241,6 +1245,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()); @@ -1429,6 +1437,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(); @@ -1440,6 +1449,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 5dcd45fdd49..e0663ec225d 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -240,7 +240,9 @@ impl CodeBlock { | Opcode::Call | Opcode::New | Opcode::SuperCall - | Opcode::ConcatToString => { + | Opcode::ConcatToString + | Opcode::GetLocal + | Opcode::SetLocal => { pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); @@ -610,9 +612,7 @@ impl CodeBlock { | Opcode::Reserved54 | Opcode::Reserved55 | Opcode::Reserved56 - | Opcode::Reserved57 - | Opcode::Reserved58 - | Opcode::Reserved59 => unreachable!("Reserved opcodes are unrechable"), + | Opcode::Reserved57 => 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 eb253d5c2fb..3582578b0e5 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1672,6 +1672,20 @@ generate_impl! { /// Stack: **=>** Nop, + /// Get fast local variable. + /// + /// Operands: index: `u32` + /// + /// Stack: **=>** value + GetLocal, + + /// Get fast local variable. + /// + /// Operands: index: `u32` + /// + /// Stack: value **=>** + SetLocal, + /// Reserved [`Opcode`]. Reserved1 => Reserved, /// Reserved [`Opcode`]. @@ -1786,10 +1800,6 @@ generate_impl! { Reserved56 => Reserved, /// Reserved [`Opcode`]. Reserved57 => Reserved, - /// Reserved [`Opcode`]. - Reserved58 => Reserved, - /// Reserved [`Opcode`]. - Reserved59 => Reserved, } }