Skip to content

Commit

Permalink
Implement local variable optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Sep 16, 2023
1 parent 3c15e59 commit 2d454cb
Show file tree
Hide file tree
Showing 17 changed files with 529 additions and 139 deletions.
67 changes: 67 additions & 0 deletions boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeRef<'a>>,
{
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<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_call(&mut self, node: &'ast crate::expression::Call) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_arrow_function(&mut self, _node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_async_function(&mut self, _node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_async_arrow_function(
&mut self,
_node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_class(&mut self, _node: &'ast Class) -> ControlFlow<Self::BreakTy> {
ControlFlow::Break(())
}

fn visit_pattern(
&mut self,
_node: &'ast crate::pattern::Pattern,
) -> ControlFlow<Self::BreakTy> {
ControlFlow::Break(())
}
}
91 changes: 73 additions & 18 deletions boa_engine/src/bytecompiler/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use boa_interner::Sym;
#[cfg(feature = "annex-b")]
use boa_ast::operations::annex_b_function_declarations_names;

use super::EnvironmentAccess;
use super::Operand;

impl ByteCompiler<'_, '_> {
Expand Down Expand Up @@ -570,9 +571,15 @@ impl ByteCompiler<'_, '_> {

// ii. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = self.initialize_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}
}
}

Expand Down Expand Up @@ -742,10 +749,14 @@ impl ByteCompiler<'_, '_> {
if binding_exists {
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
match self.set_mutable_binding(name) {
Ok(binding) => {
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
Ok(binding) => match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
},
Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]);
Expand All @@ -760,8 +771,14 @@ impl ByteCompiler<'_, '_> {
// 3. Perform ! varEnv.InitializeBinding(fn, fo).
self.create_mutable_binding(name, !strict);
let binding = self.initialize_mutable_binding(name, !strict);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}
}
}
}
Expand All @@ -785,9 +802,15 @@ impl ByteCompiler<'_, '_> {
// 3. Perform ! varEnv.InitializeBinding(vn, undefined).
self.create_mutable_binding(name, !strict);
let binding = self.initialize_mutable_binding(name, !strict);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}
}
}
}
Expand Down Expand Up @@ -919,6 +942,7 @@ impl ByteCompiler<'_, '_> {
// NOTE(HalidOdat): Has been moved up, so "arguments" gets registed as
// the first binding in the environment with index 0.
if arguments_object_needed {
let function_environment_index = self.function_environment_index.take();
// Note: This happens at runtime.
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
Expand All @@ -941,6 +965,13 @@ impl ByteCompiler<'_, '_> {
self.create_mutable_binding(arguments, false);
}

if self.can_optimize_local_variables {
let binding = self.get_binding_value(arguments);
self.get_or_insert_binding(binding);
}

self.function_environment_index = function_environment_index;

self.code_block_flags |= CodeBlockFlags::NEEDS_ARGUMENTS_OBJECT;
}

Expand Down Expand Up @@ -1055,15 +1086,27 @@ impl ByteCompiler<'_, '_> {
else {
// a. Let initialValue be ! env.GetBindingValue(n, false).
let binding = self.get_binding_value(n);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::GetName, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::GetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::GetName, &[Operand::U32(index)]);
}
}
}

// 5. Perform ! varEnv.InitializeBinding(n, initialValue).
let binding = self.initialize_mutable_binding(n, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}

// 6. NOTE: A var with the same name as a formal parameter initially has
// the same value as the corresponding initialized parameter.
Expand All @@ -1088,9 +1131,15 @@ impl ByteCompiler<'_, '_> {

// 3. Perform ! env.InitializeBinding(n, undefined).
let binding = self.initialize_mutable_binding(n, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}
}
}

Expand Down Expand Up @@ -1120,9 +1169,15 @@ impl ByteCompiler<'_, '_> {

// b. Perform ! varEnv.InitializeBinding(F, undefined).
let binding = self.initialize_mutable_binding(f, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::DefInitVar, &[Operand::U32(index)]);
}
}

// c. Append F to instantiatedVarNames.
instantiated_var_names.push(f);
Expand Down
36 changes: 24 additions & 12 deletions boa_engine/src/bytecompiler/expression/assign.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
bytecompiler::{Access, ByteCompiler, Operand},
bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand},
environments::BindingLocatorError,
vm::{BindingOpcode, Opcode},
};
Expand Down Expand Up @@ -56,14 +56,22 @@ impl ByteCompiler<'_, '_> {
match access {
Access::Variable { name } => {
let binding = self.get_binding_value(name);
let index = self.get_or_insert_binding(binding);
let lex = self.current_environment.is_lex_binding(name);

if lex {
self.emit(Opcode::GetName, &[Operand::U32(index)]);
} else {
self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]);
}
let is_fast = match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::GetLocal, &[Operand::U32(index)]);
true
}
EnvironmentAccess::Slow { index } => {
if lex {
self.emit(Opcode::GetName, &[Operand::U32(index)]);
} else {
self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]);
}
false
}
};

if short_circuit {
early_exit = Some(self.emit_opcode_with_operand(opcode));
Expand All @@ -75,12 +83,16 @@ impl ByteCompiler<'_, '_> {
if use_expr {
self.emit_opcode(Opcode::Dup);
}
if lex {
if lex || is_fast {
match self.set_mutable_binding(name) {
Ok(binding) => {
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
Ok(binding) => match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
},
Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]);
Expand Down
12 changes: 9 additions & 3 deletions boa_engine/src/bytecompiler/expression/unary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use boa_ast::{
};

use crate::{
bytecompiler::{Access, ByteCompiler, Operand},
bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand},
vm::Opcode,
};

Expand All @@ -28,8 +28,14 @@ impl ByteCompiler<'_, '_> {
match unary.target().flatten() {
Expression::Identifier(identifier) => {
let binding = self.get_binding_value(*identifier);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::GetNameOrUndefined, &[Operand::U32(index)]);
match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::GetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::GetNameOrUndefined, &[Operand::U32(index)]);
}
}
}
expr => self.compile_expr(expr, true),
}
Expand Down
36 changes: 24 additions & 12 deletions boa_engine/src/bytecompiler/expression/update.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
bytecompiler::{Access, ByteCompiler, Operand},
bytecompiler::{Access, ByteCompiler, EnvironmentAccess, Operand},
environments::BindingLocatorError,
vm::Opcode,
};
Expand All @@ -24,14 +24,22 @@ impl ByteCompiler<'_, '_> {
match Access::from_update_target(update.target()) {
Access::Variable { name } => {
let binding = self.get_binding_value(name);
let index = self.get_or_insert_binding(binding);
let lex = self.current_environment.is_lex_binding(name);

if lex {
self.emit(Opcode::GetName, &[Operand::U32(index)]);
} else {
self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]);
}
let is_fast = match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::GetLocal, &[Operand::U32(index)]);
true
}
EnvironmentAccess::Slow { index } => {
if lex {
self.emit(Opcode::GetName, &[Operand::U32(index)]);
} else {
self.emit(Opcode::GetNameAndLocator, &[Operand::U32(index)]);
}
false
}
};

self.emit_opcode(opcode);
if post {
Expand All @@ -40,12 +48,16 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Dup);
}

if lex {
if lex || is_fast {
match self.set_mutable_binding(name) {
Ok(binding) => {
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
Ok(binding) => match self.get_or_insert_binding(binding) {
EnvironmentAccess::Fast { index } => {
self.emit(Opcode::SetLocal, &[Operand::U32(index)]);
}
EnvironmentAccess::Slow { index } => {
self.emit(Opcode::SetName, &[Operand::U32(index)]);
}
},
Err(BindingLocatorError::MutateImmutable) => {
let index = self.get_or_insert_name(name);
self.emit(Opcode::ThrowMutateImmutable, &[Operand::U32(index)]);
Expand Down
Loading

0 comments on commit 2d454cb

Please sign in to comment.