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 Oct 4, 2023
1 parent a51581b commit e23ec27
Show file tree
Hide file tree
Showing 27 changed files with 904 additions and 220 deletions.
60 changes: 60 additions & 0 deletions boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2156,3 +2156,63 @@ 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(())
}
}
16 changes: 10 additions & 6 deletions boa_engine/src/bytecompiler/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ impl ByteCompiler<'_, '_> {
Some(name) if class.has_binding_identifier() => {
let env_index = self.push_compile_environment(false);
self.create_immutable_binding(name, true);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
if !self.can_optimize_local_variables {
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
}
true
}
_ => false,
Expand All @@ -39,7 +41,7 @@ impl ByteCompiler<'_, '_> {
let mut compiler = ByteCompiler::new(
class_name,
true,
self.json_parse,
self.json_parse(),
self.current_environment.clone(),
self.context,
);
Expand Down Expand Up @@ -276,7 +278,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,
);
Expand Down Expand Up @@ -310,7 +312,7 @@ impl ByteCompiler<'_, '_> {
let mut field_compiler = ByteCompiler::new(
class_name,
true,
self.json_parse,
self.json_parse(),
self.current_environment.clone(),
self.context,
);
Expand Down Expand Up @@ -354,7 +356,7 @@ impl ByteCompiler<'_, '_> {
let mut field_compiler = ByteCompiler::new(
class_name,
true,
self.json_parse,
self.json_parse(),
self.current_environment.clone(),
self.context,
);
Expand Down Expand Up @@ -591,7 +593,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);
Expand Down
83 changes: 63 additions & 20 deletions boa_engine/src/bytecompiler/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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,
);
}
}
}
Expand All @@ -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,
);
}
}
}
Expand Down Expand Up @@ -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).
Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -1031,7 +1056,9 @@ impl ByteCompiler<'_, '_> {
// b. Let varEnv be NewDeclarativeEnvironment(env).
// c. Set the VariableEnvironment of calleeContext to varEnv.
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);
}
env_label = true;

// d. Let instantiatedVarNames be a new empty List.
Expand All @@ -1056,15 +1083,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.
Expand All @@ -1089,9 +1124,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,
);
}
}

Expand Down Expand Up @@ -1121,9 +1160,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);
Expand Down
38 changes: 26 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,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));
Expand All @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions boa_engine/src/bytecompiler/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
}
Expand All @@ -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);
Expand Down
Loading

0 comments on commit e23ec27

Please sign in to comment.