From 8fa5cb8f0cb9473abd1fc264c5bc3dc19e90f6c3 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/class.rs | 12 +- boa_engine/src/bytecompiler/declarations.rs | 79 ++++-- .../src/bytecompiler/expression/assign.rs | 38 ++- boa_engine/src/bytecompiler/expression/mod.rs | 10 +- .../bytecompiler/expression/object_literal.rs | 2 +- .../src/bytecompiler/expression/unary.rs | 8 +- .../src/bytecompiler/expression/update.rs | 38 ++- boa_engine/src/bytecompiler/function.rs | 48 +++- boa_engine/src/bytecompiler/jump_control.rs | 6 +- boa_engine/src/bytecompiler/mod.rs | 238 ++++++++++++++---- .../src/bytecompiler/statement/block.rs | 8 +- boa_engine/src/bytecompiler/statement/loop.rs | 74 ++++-- boa_engine/src/bytecompiler/statement/mod.rs | 2 +- .../src/bytecompiler/statement/switch.rs | 8 +- boa_engine/src/bytecompiler/statement/try.rs | 17 +- boa_engine/src/bytecompiler/utils.rs | 4 +- boa_engine/src/environments/compile.rs | 54 ++-- boa_engine/src/environments/runtime/mod.rs | 11 +- boa_engine/src/module/source.rs | 12 +- boa_engine/src/vm/code_block.rs | 34 ++- boa_engine/src/vm/flowgraph/mod.rs | 18 +- .../src/vm/opcode/control_flow/return.rs | 76 ++++++ boa_engine/src/vm/opcode/delete/mod.rs | 45 +++- boa_engine/src/vm/opcode/get/name.rs | 93 ++++++- boa_engine/src/vm/opcode/mod.rs | 54 +++- boa_engine/src/vm/opcode/set/name.rs | 59 ++++- 27 files changed, 901 insertions(+), 214 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/class.rs b/boa_engine/src/bytecompiler/class.rs index 1bc57de76ca..a1a380a7751 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -39,7 +39,7 @@ impl ByteCompiler<'_, '_> { let mut compiler = ByteCompiler::new( class_name, true, - self.json_parse, + self.json_parse(), self.current_environment.clone(), self.context, ); @@ -276,7 +276,7 @@ impl ByteCompiler<'_, '_> { let mut field_compiler = ByteCompiler::new( Sym::EMPTY_STRING, true, - self.json_parse, + self.json_parse(), self.current_environment.clone(), self.context, ); @@ -310,7 +310,7 @@ impl ByteCompiler<'_, '_> { let mut field_compiler = ByteCompiler::new( class_name, true, - self.json_parse, + self.json_parse(), self.current_environment.clone(), self.context, ); @@ -354,7 +354,7 @@ impl ByteCompiler<'_, '_> { let mut field_compiler = ByteCompiler::new( class_name, true, - self.json_parse, + self.json_parse(), self.current_environment.clone(), self.context, ); @@ -591,7 +591,9 @@ impl ByteCompiler<'_, '_> { if class_env { self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } self.emit_opcode(Opcode::PopPrivateEnvironment); diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs index 2ebfdd7a405..49e033ad1c9 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -570,9 +570,13 @@ 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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); } } @@ -742,10 +746,12 @@ 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_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -760,8 +766,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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); } } } @@ -785,9 +795,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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); } } } @@ -919,6 +933,13 @@ 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 { + if !strict { + self.can_optimize_local_variables = false; + } + + let can_optimize_local_variables = self.can_optimize_local_variables; + self.can_optimize_local_variables = false; + // Note: This happens at runtime. // a. If strict is true or simpleParameterList is false, then // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). @@ -941,6 +962,10 @@ impl ByteCompiler<'_, '_> { self.create_mutable_binding(arguments, false); } + let binding = self.get_binding_value(arguments); + self.get_or_insert_binding(binding); + self.can_optimize_local_variables = can_optimize_local_variables; + self.code_block_flags |= CodeBlockFlags::NEEDS_ARGUMENTS_OBJECT; } @@ -1017,7 +1042,7 @@ impl ByteCompiler<'_, '_> { } if generator { - self.emit(Opcode::Generator, &[Operand::U8(self.in_async().into())]); + self.emit(Opcode::Generator, &[Operand::Bool(self.is_async())]); self.emit_opcode(Opcode::Pop); } @@ -1056,15 +1081,23 @@ 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_with_varying_operand(Opcode::GetName, index); + self.get_or_insert_binding(binding).emit( + Opcode::GetLocal, + Opcode::GetGlobalName, + Opcode::GetName, + self, + ); } // 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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); // 6. NOTE: A var with the same name as a formal parameter initially has // the same value as the corresponding initialized parameter. @@ -1089,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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); } } @@ -1121,9 +1158,13 @@ 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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); // 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 9efc49e2b23..0e18241de26 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,26 @@ 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_with_varying_operand(Opcode::GetName, index); - } else { - self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit_with_varying_operand(Opcode::GetLocal, index); + true + } + EnvironmentAccess::Global { index } => { + self.emit_with_varying_operand(Opcode::GetGlobalName, index); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit_with_varying_operand(Opcode::GetName, index); + } else { + self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); + } + false + } + }; if short_circuit { early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -75,12 +87,14 @@ 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_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index a9ae59ce2ed..5a9867ca2d9 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -175,7 +175,7 @@ impl ByteCompiler<'_, '_> { // stack: value if r#yield.delegate() { - if self.in_async() { + if self.is_async() { self.emit_opcode(Opcode::GetAsyncIterator); } else { self.emit_opcode(Opcode::GetIterator); @@ -192,14 +192,14 @@ impl ByteCompiler<'_, '_> { let (throw_method_undefined, return_method_undefined) = self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateNext); - if self.in_async() { + if self.is_async() { self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Await); } let (return_gen, exit) = self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateResume); - if self.in_async() { + if self.is_async() { self.emit_opcode(Opcode::IteratorValue); self.async_generator_yield(); } else { @@ -210,7 +210,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(return_gen); self.patch_jump(return_method_undefined); - if self.in_async() { + if self.is_async() { self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Pop); } @@ -219,7 +219,7 @@ impl ByteCompiler<'_, '_> { self.r#return(true); self.patch_jump(throw_method_undefined); - self.iterator_close(self.in_async()); + self.iterator_close(self.is_async()); self.emit_opcode(Opcode::Throw); self.patch_jump(exit); diff --git a/boa_engine/src/bytecompiler/expression/object_literal.rs b/boa_engine/src/bytecompiler/expression/object_literal.rs index 10dea9d1665..39451470e45 100644 --- a/boa_engine/src/bytecompiler/expression/object_literal.rs +++ b/boa_engine/src/bytecompiler/expression/object_literal.rs @@ -24,7 +24,7 @@ impl ByteCompiler<'_, '_> { PropertyName::Literal(name) => { self.compile_expr(expr, true); let index = self.get_or_insert_name((*name).into()); - if *name == Sym::__PROTO__ && !self.json_parse { + if *name == Sym::__PROTO__ && !self.json_parse() { self.emit_opcode(Opcode::SetPrototype); } else { self.emit_with_varying_operand(Opcode::DefineOwnPropertyByName, index); diff --git a/boa_engine/src/bytecompiler/expression/unary.rs b/boa_engine/src/bytecompiler/expression/unary.rs index 662d403afc5..9efcc20a82c 100644 --- a/boa_engine/src/bytecompiler/expression/unary.rs +++ b/boa_engine/src/bytecompiler/expression/unary.rs @@ -28,8 +28,12 @@ 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_with_varying_operand(Opcode::GetNameOrUndefined, index); + self.get_or_insert_binding(binding).emit( + Opcode::GetLocal, + Opcode::GetGlobalNameOrUndefined, + Opcode::GetNameOrUndefined, + self, + ); } 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 110cc6b4400..8a1b099ccb4 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,26 @@ 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_with_varying_operand(Opcode::GetName, index); - } else { - self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index } => { + self.emit_with_varying_operand(Opcode::GetLocal, index); + true + } + EnvironmentAccess::Global { index } => { + self.emit_with_varying_operand(Opcode::GetGlobalName, index); + true + } + EnvironmentAccess::Slow { index } => { + if lex { + self.emit_with_varying_operand(Opcode::GetName, index); + } else { + self.emit_with_varying_operand(Opcode::GetNameAndLocator, index); + } + false + } + }; self.emit_opcode(opcode); if post { @@ -40,12 +52,14 @@ 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_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index be072a6198a..f4fbdefe820 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -2,12 +2,15 @@ use std::rc::Rc; use crate::{ builtins::function::ThisMode, - bytecompiler::ByteCompiler, + bytecompiler::{ByteCompiler, ByteCompilerFlags}, environments::CompileTimeEnvironment, 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; @@ -21,6 +24,8 @@ pub(crate) struct FunctionCompiler { strict: bool, arrow: bool, binding_identifier: Option, + + can_optimize: bool, } impl FunctionCompiler { @@ -33,6 +38,7 @@ impl FunctionCompiler { strict: false, arrow: false, binding_identifier: None, + can_optimize: true, } } @@ -77,6 +83,12 @@ impl FunctionCompiler { self } + /// Indicate if the function can be optimized. + pub(crate) const fn can_optimize(mut self, can_optimize: bool) -> Self { + self.can_optimize = can_optimize; + self + } + /// Compile a function statement list and it's parameters into bytecode. pub(crate) fn compile( mut self, @@ -91,8 +103,11 @@ impl FunctionCompiler { let mut compiler = ByteCompiler::new(self.name, self.strict, false, outer_env, context); compiler.length = length; - compiler.in_async = self.r#async; - compiler.in_generator = self.generator; + + compiler.flags.set(ByteCompilerFlags::ASYNC, self.r#async); + compiler + .flags + .set(ByteCompilerFlags::GENERATOR, self.generator); if self.arrow { compiler.this_mode = ThisMode::Lexical; @@ -107,6 +122,9 @@ impl FunctionCompiler { // Function environment let _ = 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: @@ -115,7 +133,7 @@ impl FunctionCompiler { // `FunctionDeclarationInstantiation` (so they are propagated). // // See: 15.6.2 Runtime Semantics: EvaluateAsyncGeneratorBody: https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncgeneratorbody - if compiler.in_async() && !compiler.in_generator() { + if compiler.is_async() && !compiler.is_generator() { // 1. Let promiseCapability be ! NewPromiseCapability(%Promise%). // // Note: If the promise capability is already set, then we do nothing. @@ -123,6 +141,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 @@ -132,6 +153,19 @@ 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.is_simple() && self.can_optimize; + + 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, @@ -144,10 +178,10 @@ impl FunctionCompiler { // - 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ): // // Note: We do handle exceptions thrown by generator body in `AsyncGeneratorStart`. - if compiler.in_generator() { + if compiler.is_generator() { assert!(compiler.async_handler.is_none()); - if compiler.in_async() { + if compiler.is_async() { // Patched in `ByteCompiler::finish()`. compiler.async_handler = Some(compiler.push_handler()); } diff --git a/boa_engine/src/bytecompiler/jump_control.rs b/boa_engine/src/bytecompiler/jump_control.rs index 85708c90a98..e0ac73fdbdc 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); } @@ -129,7 +133,7 @@ impl JumpRecord { compiler.emit_opcode(Opcode::SetReturnValue); } - match (compiler.in_async(), compiler.in_generator()) { + match (compiler.is_async(), compiler.is_generator()) { // Taken from: // - 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ): https://tc39.es/ecma262/#sec-asyncgeneratorstart // diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index a8b734a8ada..160da883f00 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -23,6 +23,7 @@ use crate::{ }, Context, JsBigInt, JsString, JsValue, }; +use bitflags::bitflags; use boa_ast::{ declaration::{Binding, LexicalDeclaration, VarDeclaration}, expression::{ @@ -229,6 +230,58 @@ pub(crate) enum Operand { Varying(u32), } +#[derive(Debug, Clone, Copy)] +pub(crate) enum EnvironmentAccess { + Fast { index: u32 }, + Global { index: u32 }, + Slow { index: u32 }, +} + +impl EnvironmentAccess { + pub(crate) fn emit( + self, + local: L, + global: G, + slow: S, + compiler: &mut ByteCompiler<'_, '_>, + ) where + L: Into>, + G: Into>, + S: Into>, + { + let local = local.into(); + let global = global.into(); + let slow = slow.into(); + + match self { + Self::Fast { index } if local.is_some() => { + compiler + .emit_with_varying_operand(local.expect("there should be an opcode"), index); + } + Self::Global { index } if global.is_some() => compiler + .emit_with_varying_operand(global.expect("there should be an opcode"), index), + Self::Slow { index } if slow.is_some() => { + compiler.emit_with_varying_operand(slow.expect("there should be an opcode"), index); + } + _ => {} + } + } +} + +bitflags! { + /// Flags for [`ByteCompiler`]. + #[derive(Clone, Copy, Debug)] + pub(crate) struct ByteCompilerFlags: u8 { + const ASYNC = 0b0000_0001; + const GENERATOR = 0b0000_0010; + const HAS_WITH_STATEMENT = 0b0000_0100; + const IN_WITH_STATEMENT = 0b0000_1000; + const IN_EVAL = 0b0001_0000; + const HAS_EVAL = 0b0010_0000; + const JSON_PARSE = 0b0100_0000; + } +} + /// The [`ByteCompiler`] is used to compile ECMAScript AST from [`boa_ast`] to bytecode. #[derive(Debug)] #[allow(clippy::struct_excessive_bools)] @@ -272,16 +325,19 @@ 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, + + pub(crate) flags: ByteCompilerFlags, + 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. pub(crate) async_handler: Option, - json_parse: bool, // TODO: remove when we separate scripts from the context context: &'ctx mut Context<'host>, @@ -307,6 +363,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { ) -> ByteCompiler<'ctx, 'host> { let mut code_block_flags = CodeBlockFlags::empty(); code_block_flags.set(CodeBlockFlags::STRICT, strict); + + let mut flags = ByteCompilerFlags::empty(); + flags.set(ByteCompilerFlags::JSON_PARSE, json_parse); Self { function_name: name, length: 0, @@ -328,13 +387,15 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), 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, context, + flags, + #[cfg(feature = "annex-b")] annex_b_function_names: Vec::new(), } @@ -344,16 +405,20 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.code_block_flags.contains(CodeBlockFlags::STRICT) } - pub(crate) const fn in_async(&self) -> bool { - self.in_async + pub(crate) const fn is_async(&self) -> bool { + self.flags.contains(ByteCompilerFlags::ASYNC) } - pub(crate) const fn in_generator(&self) -> bool { - self.in_generator + pub(crate) const fn is_generator(&self) -> bool { + self.flags.contains(ByteCompilerFlags::GENERATOR) } - pub(crate) const fn in_async_generator(&self) -> bool { - self.in_async() && self.in_generator() + pub(crate) const fn is_async_generator(&self) -> bool { + self.is_async() && self.is_generator() + } + + pub(crate) const fn json_parse(&self) -> bool { + self.flags.contains(ByteCompilerFlags::JSON_PARSE) } pub(crate) fn interner(&self) -> &Interner { @@ -394,31 +459,55 @@ 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 }; + } + + if binding.is_global() && !binding.is_lex() && self.can_optimize_local_variables { + let index = self.get_or_insert_name(binding.name()); + return EnvironmentAccess::Global { 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_with_varying_operand(Opcode::DefVar, index); + self.get_or_insert_binding(binding) + .emit(None, None, Opcode::DefVar, self); } 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_with_varying_operand(Opcode::DefInitVar, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -429,25 +518,39 @@ 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_with_varying_operand(Opcode::DefInitVar, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ); }; } BindingOpcode::InitLet => { let binding = self.initialize_mutable_binding(name, false); - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::PutLexicalValue, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::PutLexicalValue, + self, + ); } BindingOpcode::InitConst => { let binding = self.initialize_immutable_binding(name); - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::PutLexicalValue, index); + self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::PutLexicalValue, + self, + ); } BindingOpcode::SetName => match self.set_mutable_binding(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -699,8 +802,12 @@ 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_with_varying_operand(Opcode::GetName, index); + self.get_or_insert_binding(binding).emit( + Opcode::GetLocal, + Opcode::GetGlobalName, + Opcode::GetName, + self, + ); } Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { @@ -764,24 +871,32 @@ 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_with_varying_operand(Opcode::GetLocator, index); - } + let is_fast = match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } + | EnvironmentAccess::Global { index: _ } => true, + EnvironmentAccess::Slow { index } => { + if !lex { + self.emit_with_varying_operand(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_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -877,8 +992,15 @@ 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_with_varying_operand(Opcode::DeleteName, index); + match self.get_or_insert_binding(binding) { + EnvironmentAccess::Fast { index: _ } => self.emit_opcode(Opcode::PushFalse), + EnvironmentAccess::Global { index } => { + self.emit_with_varying_operand(Opcode::DeleteGlobalName, index); + } + EnvironmentAccess::Slow { index } => { + self.emit_with_varying_operand(Opcode::DeleteName, index); + } + } } Access::This => { self.emit_opcode(Opcode::PushTrue); @@ -1192,14 +1314,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_with_varying_operand(Opcode::GetName, index); + self.get_or_insert_binding(binding).emit( + Opcode::GetLocal, + Opcode::GetGlobalName, + Opcode::GetName, + self, + ); match self.set_mutable_binding_var(name) { - Ok(binding) => { - let index = self.get_or_insert_binding(binding); - self.emit_with_varying_operand(Opcode::SetName, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::SetName, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(name); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -1249,6 +1377,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { .strict(self.strict()) .arrow(arrow) .binding_identifier(binding_identifier) + .can_optimize(self.can_optimize_local_variables) .compile( parameters, body, @@ -1533,6 +1662,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, @@ -1546,6 +1681,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 5982dfd0731..219fc9492f2 100644 --- a/boa_engine/src/bytecompiler/statement/block.rs +++ b/boa_engine/src/bytecompiler/statement/block.rs @@ -5,12 +5,16 @@ impl ByteCompiler<'_, '_> { /// Compile a [`Block`] `boa_ast` node pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } self.block_declaration_instantiation(block); self.compile_statement_list(block.statement_list(), use_expr, true); self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index 4088a7e3b1c..495d87b679a 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -32,7 +32,12 @@ impl ByteCompiler<'_, '_> { } ForLoopInitializer::Lexical(decl) => { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand( + Opcode::PushDeclarativeEnvironment, + env_index, + ); + } has_lexical_environment_binding = true; let names = bound_names(decl); @@ -68,14 +73,26 @@ impl ByteCompiler<'_, '_> { if let Some((let_binding_indices, env_index)) = &let_binding_indices { for index in let_binding_indices { - self.emit_with_varying_operand(Opcode::GetName, *index); + index.emit( + Opcode::GetLocal, + Opcode::GetGlobalName, + Opcode::GetName, + self, + ); } - self.emit_opcode(Opcode::PopEnvironment); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index); + } for index in let_binding_indices.iter().rev() { - self.emit_with_varying_operand(Opcode::PutLexicalValue, *index); + index.emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::PutLexicalValue, + self, + ); } } @@ -103,7 +120,9 @@ impl ByteCompiler<'_, '_> { if has_lexical_environment_binding { self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } } @@ -131,7 +150,9 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_in_loop.target(), true); } else { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } for name in &initializer_bound_names { self.create_mutable_binding(*name, false); @@ -139,7 +160,9 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_in_loop.target(), true); self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } let early_exit = self.jump_if_null_or_undefined(); @@ -157,7 +180,9 @@ impl ByteCompiler<'_, '_> { if !initializer_bound_names.is_empty() { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } } match for_in_loop.initializer() { @@ -212,7 +237,9 @@ impl ByteCompiler<'_, '_> { if !initializer_bound_names.is_empty() { self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } self.emit(Opcode::Jump, &[Operand::U32(start_address)]); @@ -243,7 +270,9 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_of_loop.iterable(), true); } else { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } for name in &initializer_bound_names { self.create_mutable_binding(*name, false); @@ -251,7 +280,9 @@ impl ByteCompiler<'_, '_> { self.compile_expr(for_of_loop.iterable(), true); self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } if for_of_loop.r#await() { @@ -281,17 +312,22 @@ impl ByteCompiler<'_, '_> { if !initializer_bound_names.is_empty() { let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } }; 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_with_varying_operand(Opcode::DefInitVar, index); - } + Ok(binding) => self.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + self, + ), Err(BindingLocatorError::MutateImmutable) => { let index = self.get_or_insert_name(*ident); self.emit_with_varying_operand(Opcode::ThrowMutateImmutable, index); @@ -377,7 +413,9 @@ impl ByteCompiler<'_, '_> { if !initializer_bound_names.is_empty() { self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } self.emit(Opcode::Jump, &[Operand::U32(start_address)]); diff --git a/boa_engine/src/bytecompiler/statement/mod.rs b/boa_engine/src/bytecompiler/statement/mod.rs index 81f7b3d45aa..91c332ea6a7 100644 --- a/boa_engine/src/bytecompiler/statement/mod.rs +++ b/boa_engine/src/bytecompiler/statement/mod.rs @@ -65,7 +65,7 @@ impl ByteCompiler<'_, '_> { Statement::Return(ret) => { if let Some(expr) = ret.target() { self.compile_expr(expr, true); - if self.in_async_generator() { + if self.is_async_generator() { self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::GeneratorNext); } diff --git a/boa_engine/src/bytecompiler/statement/switch.rs b/boa_engine/src/bytecompiler/statement/switch.rs index fd007840671..bf203e121c8 100644 --- a/boa_engine/src/bytecompiler/statement/switch.rs +++ b/boa_engine/src/bytecompiler/statement/switch.rs @@ -7,7 +7,9 @@ impl ByteCompiler<'_, '_> { self.compile_expr(switch.val(), true); let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } self.block_declaration_instantiation(switch); @@ -51,6 +53,8 @@ impl ByteCompiler<'_, '_> { self.pop_switch_control_info(); self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } } diff --git a/boa_engine/src/bytecompiler/statement/try.rs b/boa_engine/src/bytecompiler/statement/try.rs index 07a6bb1b1c4..888d8b66595 100644 --- a/boa_engine/src/bytecompiler/statement/try.rs +++ b/boa_engine/src/bytecompiler/statement/try.rs @@ -38,7 +38,7 @@ impl ByteCompiler<'_, '_> { // If it has a finally but no catch and we are in a generator, then we still need it // to handle `return()` call on generators. - let catch_handler = if has_finally && (self.in_generator() || has_catch) { + let catch_handler = if has_finally && (self.is_generator() || has_catch) { self.current_stack_value_count += 2; Some(self.push_handler()) } else { @@ -50,7 +50,7 @@ impl ByteCompiler<'_, '_> { self.compile_catch_stmt(catch, has_finally, use_expr); } else { // Note: implicit !has_catch - if self.in_generator() && has_finally { + if self.is_generator() && has_finally { // Is this a generator `return()` empty exception? // // This is false because when the `Exception` opcode is executed, @@ -77,7 +77,7 @@ impl ByteCompiler<'_, '_> { } // Note: implicit has_finally - if !has_catch && self.in_generator() { + if !has_catch && self.is_generator() { // Is this a generator `return()` empty exception? self.emit_opcode(Opcode::PushTrue); } @@ -110,9 +110,10 @@ impl ByteCompiler<'_, '_> { pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, use_expr: bool) { // stack: exception - let env_index = self.push_compile_environment(false); - self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + if !self.can_optimize_local_variables { + self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index); + } if let Some(binding) = catch.parameter() { match binding { @@ -134,7 +135,9 @@ impl ByteCompiler<'_, '_> { self.compile_catch_finally_block(catch.block(), use_expr); self.pop_compile_environment(); - self.emit_opcode(Opcode::PopEnvironment); + if !self.can_optimize_local_variables { + self.emit_opcode(Opcode::PopEnvironment); + } } pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, has_catch: bool) { @@ -150,7 +153,7 @@ impl ByteCompiler<'_, '_> { if has_catch { self.emit_opcode(Opcode::ReThrow); - } else if self.in_generator() { + } else if self.is_generator() { let is_generator_exit = self.jump_if_true(); self.emit_opcode(Opcode::Throw); self.patch_jump(is_generator_exit); diff --git a/boa_engine/src/bytecompiler/utils.rs b/boa_engine/src/bytecompiler/utils.rs index 6ba4335fe2c..8319fb31a42 100644 --- a/boa_engine/src/bytecompiler/utils.rs +++ b/boa_engine/src/bytecompiler/utils.rs @@ -50,7 +50,7 @@ impl ByteCompiler<'_, '_> { let start = self.next_opcode_location(); self.emit_opcode(Opcode::IteratorStackEmpty); let empty = self.jump_if_true(); - self.iterator_close(self.in_async_generator()); + self.iterator_close(self.is_async_generator()); self.emit(Opcode::Jump, &[Operand::U32(start)]); self.patch_jump(empty); } @@ -65,7 +65,7 @@ impl ByteCompiler<'_, '_> { /// [yield]: https://tc39.es/ecma262/#sec-yield pub(super) fn r#yield(&mut self) { // 1. Let generatorKind be GetGeneratorKind(). - if self.in_async() { + if self.is_async() { // 2. If generatorKind is async, return ? AsyncGeneratorYield(? Await(value)). self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::GeneratorNext); diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index f0dad91db2a..d911a322222 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -94,20 +94,19 @@ impl CompileTimeEnvironment { /// Get the locator for a binding name. pub(crate) fn get_binding(&self, name: Identifier) -> Option { - self.bindings - .borrow() - .get(&name) - .map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index)) + self.bindings.borrow().get(&name).map(|binding| { + BindingLocator::declarative(name, self.environment_index, binding.index, binding.lex) + }) } /// Get the locator for a binding name in this and all outer environments. pub(crate) fn get_binding_recursive(&self, name: Identifier) -> BindingLocator { if let Some(binding) = self.bindings.borrow().get(&name) { - BindingLocator::declarative(name, self.environment_index, binding.index) + BindingLocator::declarative(name, self.environment_index, binding.index, binding.lex) } else if let Some(outer) = &self.outer { outer.get_binding_recursive(name) } else { - BindingLocator::global(name) + BindingLocator::global(name, false) } } @@ -223,12 +222,19 @@ impl CompileTimeEnvironment { } self.bindings.borrow().get(&name).map_or_else( || outer.initialize_mutable_binding(name, function_scope), - |binding| BindingLocator::declarative(name, self.environment_index, binding.index), + |binding| { + BindingLocator::declarative( + name, + self.environment_index, + binding.index, + binding.lex, + ) + }, ) } else if let Some(binding) = self.bindings.borrow().get(&name) { - BindingLocator::declarative(name, self.environment_index, binding.index) + BindingLocator::declarative(name, self.environment_index, binding.index, binding.lex) } else { - BindingLocator::global(name) + BindingLocator::global(name, false) } } @@ -240,7 +246,7 @@ impl CompileTimeEnvironment { pub(crate) fn initialize_immutable_binding(&self, name: Identifier) -> BindingLocator { let bindings = self.bindings.borrow(); let binding = bindings.get(&name).expect("binding must exist"); - BindingLocator::declarative(name, self.environment_index, binding.index) + BindingLocator::declarative(name, self.environment_index, binding.index, binding.lex) } /// Return the binding locator for a mutable binding. @@ -249,13 +255,16 @@ impl CompileTimeEnvironment { name: Identifier, ) -> Result { Ok(match self.bindings.borrow().get(&name) { - Some(binding) if binding.mutable => { - BindingLocator::declarative(name, self.environment_index, binding.index) - } + Some(binding) if binding.mutable => BindingLocator::declarative( + name, + self.environment_index, + binding.index, + binding.lex, + ), Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), Some(_) => return Err(BindingLocatorError::Silent), None => self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name)), + || Ok(BindingLocator::global(name, false)), |outer| outer.set_mutable_binding_recursive(name), )?, }) @@ -269,19 +278,22 @@ impl CompileTimeEnvironment { ) -> Result { if !self.is_function() { return self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name)), + || Ok(BindingLocator::global(name, false)), |outer| outer.set_mutable_binding_var_recursive(name), ); } Ok(match self.bindings.borrow().get(&name) { - Some(binding) if binding.mutable => { - BindingLocator::declarative(name, self.environment_index, binding.index) - } + Some(binding) if binding.mutable => BindingLocator::declarative( + name, + self.environment_index, + binding.index, + binding.lex, + ), Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable), Some(_) => return Err(BindingLocatorError::Silent), None => self.outer.as_ref().map_or_else( - || Ok(BindingLocator::global(name)), + || Ok(BindingLocator::global(name, false)), |outer| outer.set_mutable_binding_var_recursive(name), )?, }) @@ -291,4 +303,8 @@ impl CompileTimeEnvironment { pub(crate) fn outer(&self) -> Option> { self.outer.clone() } + + pub(crate) const fn environment_index(&self) -> u32 { + self.environment_index + } } diff --git a/boa_engine/src/environments/runtime/mod.rs b/boa_engine/src/environments/runtime/mod.rs index d89a01a6b58..c0f0d3ccbc0 100644 --- a/boa_engine/src/environments/runtime/mod.rs +++ b/boa_engine/src/environments/runtime/mod.rs @@ -474,6 +474,7 @@ pub(crate) struct BindingLocator { environment_index: u32, binding_index: u32, global: bool, + lex: bool, } unsafe impl Trace for BindingLocator { @@ -486,22 +487,25 @@ impl BindingLocator { name: Identifier, environment_index: u32, binding_index: u32, + lex: bool, ) -> Self { Self { name, environment_index, binding_index, global: false, + lex, } } /// Creates a binding locator that indicates that the binding is on the global object. - pub(super) const fn global(name: Identifier) -> Self { + pub(super) const fn global(name: Identifier, lex: bool) -> Self { Self { name, environment_index: 0, binding_index: 0, global: true, + lex, } } @@ -515,6 +519,11 @@ impl BindingLocator { self.global } + /// Returns if the binding is a lexical binding. + pub(crate) const fn is_lex(&self) -> bool { + self.lex + } + /// Returns the environment index of the binding. pub(crate) const fn environment_index(&self) -> u32 { self.environment_index diff --git a/boa_engine/src/module/source.rs b/boa_engine/src/module/source.rs index 068ba259efa..1d086f29313 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, ByteCompilerFlags, FunctionSpec}, environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack}, module::ModuleKind, object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, @@ -1415,7 +1415,7 @@ impl SourceTextModule { let mut compiler = ByteCompiler::new(Sym::MAIN, true, false, module_compile_env.clone(), context); - compiler.in_async = true; + compiler.flags |= ByteCompilerFlags::ASYNC; compiler.async_handler = Some(compiler.push_handler()); let mut imports = Vec::new(); @@ -1499,9 +1499,13 @@ 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_with_varying_operand(Opcode::DefInitVar, index); + compiler.get_or_insert_binding(binding).emit( + Opcode::SetLocal, + Opcode::SetGlobalName, + Opcode::DefInitVar, + &mut compiler, + ); // 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 df17b441e38..503b8467599 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -163,6 +163,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 ---- @@ -185,6 +187,7 @@ impl CodeBlock { params: FormalParameterList::default(), handlers: ThinVec::default(), compile_environments: Box::default(), + local_variable_count: 0, } } @@ -428,8 +431,6 @@ impl CodeBlock { | Instruction::SetPropertySetterByName { index } | Instruction::DefineClassStaticSetterByName { index } | Instruction::DefineClassSetterByName { index } - | Instruction::InPrivate { index } - | Instruction::ThrowMutateImmutable { index } | Instruction::DeletePropertyByName { index } | Instruction::SetPrivateField { index } | Instruction::DefinePrivateField { index } @@ -440,7 +441,13 @@ impl CodeBlock { | Instruction::PushClassFieldPrivate { index } | Instruction::PushClassPrivateGetter { index } | Instruction::PushClassPrivateSetter { index } - | Instruction::PushClassPrivateMethod { index } => { + | Instruction::PushClassPrivateMethod { index } + | Instruction::InPrivate { index } + | Instruction::ThrowMutateImmutable { index } + | Instruction::GetGlobalName { index } + | Instruction::GetGlobalNameOrUndefined { index } + | Instruction::SetGlobalName { index } + | Instruction::DeleteGlobalName { index } => { format!( "{:04}: '{}'", index.value(), @@ -463,6 +470,9 @@ impl CodeBlock { Instruction::CreateIteratorResult { done } => { format!("done: {done}") } + Instruction::GetLocal { index } | Instruction::SetLocal { index } => { + index.value().to_string() + } Instruction::Pop | Instruction::Dup | Instruction::Swap @@ -637,13 +647,7 @@ impl CodeBlock { | Instruction::Reserved49 | Instruction::Reserved50 | Instruction::Reserved51 - | Instruction::Reserved52 - | Instruction::Reserved53 - | Instruction::Reserved54 - | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved52 => unreachable!("Reserved opcodes are unrechable"), } } } @@ -1123,6 +1127,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, script_or_module, Some(self.clone())) .with_argument_count(argument_count as u32) @@ -1130,6 +1135,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()); @@ -1274,6 +1283,7 @@ impl JsObject { let argument_count = args.len(); let parameters_count = code.params.as_ref().len(); + let local_variable_count = code.local_variable_count; context.vm.push_frame( CallFrame::new(code, script_or_module, Some(self.clone())) @@ -1281,6 +1291,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 5152f1b200d..3ad3deb7798 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -260,7 +260,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); } @@ -287,7 +289,11 @@ impl CodeBlock { | Instruction::PushClassPrivateSetter { .. } | Instruction::PushClassPrivateMethod { .. } | Instruction::InPrivate { .. } - | Instruction::ThrowMutateImmutable { .. } => { + | Instruction::ThrowMutateImmutable { .. } + | Instruction::GetGlobalName { .. } + | Instruction::SetGlobalName { .. } + | Instruction::GetGlobalNameOrUndefined { .. } + | Instruction::DeleteGlobalName { .. } => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } @@ -516,13 +522,7 @@ impl CodeBlock { | Instruction::Reserved49 | Instruction::Reserved50 | Instruction::Reserved51 - | Instruction::Reserved52 - | Instruction::Reserved53 - | Instruction::Reserved54 - | Instruction::Reserved55 - | Instruction::Reserved56 - | Instruction::Reserved57 - | Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved52 => 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..4ecdd2be140 100644 --- a/boa_engine/src/vm/opcode/control_flow/return.rs +++ b/boa_engine/src/vm/opcode/control_flow/return.rs @@ -54,3 +54,79 @@ 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 GetLocal { + #[allow(clippy::unnecessary_wraps)] + fn operation(context: &mut Context<'_>, offset: usize) -> JsResult { + let index = context.vm.frame().fp as usize + offset; + + let value = context.vm.stack[index].clone(); + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +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::() as usize; + Self::operation(context, offset) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::() as usize; + Self::operation(context, offset) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::() as usize; + Self::operation(context, offset) + } +} + +/// `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 SetLocal { + #[allow(clippy::unnecessary_wraps)] + fn operation(context: &mut Context<'_>, offset: usize) -> JsResult { + let index = context.vm.frame().fp as usize + offset; + + let value = context.vm.pop(); + context.vm.stack[index] = value; + Ok(CompletionType::Normal) + } +} + +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::() as usize; + Self::operation(context, offset) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::() as usize; + Self::operation(context, offset) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let offset = context.vm.read::() as usize; + Self::operation(context, offset) + } +} diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs index ddeb0ec1d60..aec52b26ef8 100644 --- a/boa_engine/src/vm/opcode/delete/mod.rs +++ b/boa_engine/src/vm/opcode/delete/mod.rs @@ -99,8 +99,45 @@ impl Operation for DeleteName { const INSTRUCTION: &'static str = "INST - DeleteName"; fn execute(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} + +/// `DeleteGlobalName` implements the Opcode Operation for `Opcode::DeleteGlobalName` +/// +/// Operation: +/// - Deletes a global property. +#[derive(Debug, Clone, Copy)] +pub(crate) struct DeleteGlobalName; + +impl DeleteGlobalName { + fn operation(context: &mut Context<'_>, index: usize) -> JsResult { + let key = context.vm.frame().code_block().names[index].clone().into(); + let deleted = context.global_object().__delete__(&key, context)?; + + context.vm.push(deleted); + Ok(CompletionType::Normal) + } +} + +impl Operation for DeleteGlobalName { + const NAME: &'static str = "DeleteGlobalName"; + const INSTRUCTION: &'static str = "INST - DeleteGlobalName"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { @@ -109,8 +146,8 @@ impl Operation for DeleteName { } fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) } } diff --git a/boa_engine/src/vm/opcode/get/name.rs b/boa_engine/src/vm/opcode/get/name.rs index 31812939706..b269c08321f 100644 --- a/boa_engine/src/vm/opcode/get/name.rs +++ b/boa_engine/src/vm/opcode/get/name.rs @@ -33,8 +33,8 @@ impl Operation for GetName { const INSTRUCTION: &'static str = "INST - GetName"; fn execute(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { @@ -43,8 +43,93 @@ impl Operation for GetName { } fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} + +/// `GetGlobalName` implements the Opcode Operation for `Opcode::GetGlobalName` +/// +/// Operation: +/// - TODO: doc +#[derive(Debug, Clone, Copy)] +pub(crate) struct GetGlobalName; + +impl GetGlobalName { + fn operation(context: &mut Context<'_>, index: usize) -> JsResult { + let key = context.vm.frame().code_block().names[index].clone(); + let global = context.global_object(); + if !global.has_property(key.clone(), context)? { + return Err(JsNativeError::reference() + .with_message(format!("{} is not defined", key.to_std_string_escaped())) + .into()); + } + + let value = global.get(key, context)?; + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +impl Operation for GetGlobalName { + const NAME: &'static str = "GetGlobalName"; + const INSTRUCTION: &'static str = "INST - GetGlobalName"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} + +/// `GetGlobalNameOrUndefined` implements the Opcode Operation for `Opcode::GetGlobalNameOrUndefined` +/// +/// Operation: +/// - TODO: doc +#[derive(Debug, Clone, Copy)] +pub(crate) struct GetGlobalNameOrUndefined; + +impl GetGlobalNameOrUndefined { + fn operation(context: &mut Context<'_>, index: usize) -> JsResult { + let key = context.vm.frame().code_block().names[index].clone(); + let global = context.global_object(); + if !global.has_property(key.clone(), context)? { + context.vm.push(JsValue::undefined()); + return Ok(CompletionType::Normal); + } + + let value = global.get(key, context)?; + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +impl Operation for GetGlobalNameOrUndefined { + const NAME: &'static str = "GetGlobalNameOrUndefined"; + const INSTRUCTION: &'static str = "INST - GetGlobalNameOrUndefined"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) } } diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index c7279be5fb1..52b48579af6 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -2036,6 +2036,48 @@ generate_opcodes! { /// Stack: **=>** PopPrivateEnvironment, + /// Delete global variable. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: **=>** + DeleteGlobalName { index: VaryingOperand }, + + /// Get global variable. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: **=>** value + GetGlobalName { index: VaryingOperand }, + + /// Get global variable or undefined if it does not exist. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: **=>** value + GetGlobalNameOrUndefined { index: VaryingOperand }, + + /// Set global variable. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: value **=>** + SetGlobalName { index: VaryingOperand }, + + /// Get fast local variable. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: **=>** value + GetLocal { index: VaryingOperand }, + + /// Get fast local variable. + /// + /// Operands: index: `VaryingOperand` + /// + /// Stack: value **=>** + SetLocal { index: VaryingOperand }, + /// No-operation instruction, does nothing. /// /// Operands: @@ -2161,18 +2203,6 @@ generate_opcodes! { Reserved51 => Reserved, /// Reserved [`Opcode`]. Reserved52 => Reserved, - /// Reserved [`Opcode`]. - Reserved53 => Reserved, - /// Reserved [`Opcode`]. - Reserved54 => Reserved, - /// Reserved [`Opcode`]. - Reserved55 => Reserved, - /// Reserved [`Opcode`]. - Reserved56 => Reserved, - /// Reserved [`Opcode`]. - Reserved57 => Reserved, - /// Reserved [`Opcode`]. - Reserved58 => Reserved, } /// Specific opcodes for bindings. diff --git a/boa_engine/src/vm/opcode/set/name.rs b/boa_engine/src/vm/opcode/set/name.rs index d292d56bbd8..e8b5cc3f7f0 100644 --- a/boa_engine/src/vm/opcode/set/name.rs +++ b/boa_engine/src/vm/opcode/set/name.rs @@ -29,8 +29,8 @@ impl Operation for ThrowMutateImmutable { const INSTRUCTION: &'static str = "INST - ThrowMutateImmutable"; fn execute(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) } fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { @@ -39,8 +39,59 @@ impl Operation for ThrowMutateImmutable { } fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { - let index = context.vm.read::(); - Self::operation(context, index as usize) + let index = context.vm.read::() as usize; + Self::operation(context, index) + } +} + +/// `SetGlobalName` implements the Opcode Operation for `Opcode::SetGlobalName` +/// +/// Operation: +/// - Set global name. +#[derive(Debug, Clone, Copy)] +pub(crate) struct SetGlobalName; + +impl SetGlobalName { + fn operation(context: &mut Context<'_>, index: usize) -> JsResult { + let value = context.vm.pop(); + + let strict = context.vm.frame().code_block.strict(); + let key = context.vm.frame().code_block().names[index].clone(); + + let global = context.global_object(); + let has_property = global.has_property(key.clone(), context)?; + if !has_property && strict { + return Err(JsNativeError::reference() + .with_message(format!( + "cannot assign to uninitialized global property `{}`", + key.to_std_string_escaped() + )) + .into()); + } + + global.set(key, value, strict, context)?; + + Ok(CompletionType::Normal) + } +} + +impl Operation for SetGlobalName { + const NAME: &'static str = "SetGlobalName"; + const INSTRUCTION: &'static str = "INST - SetGlobalName"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) + } + + fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::() as usize; + Self::operation(context, index) } }