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 8fa5cb8
Show file tree
Hide file tree
Showing 27 changed files with 901 additions and 214 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(())
}
}
12 changes: 7 additions & 5 deletions boa_engine/src/bytecompiler/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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);
Expand Down
79 changes: 60 additions & 19 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 Down Expand Up @@ -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.
Expand All @@ -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,
);
}
}

Expand Down Expand Up @@ -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);
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
2 changes: 1 addition & 1 deletion boa_engine/src/bytecompiler/expression/object_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 8fa5cb8

Please sign in to comment.