Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization fast local variables #3194

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions boa_ast/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2156,3 +2156,87 @@
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, strict: bool) -> (bool, bool)
where
&'a N: Into<NodeRef<'a>>,
{
let mut visitor = CanOptimizeLocalVariables::new(strict);
let can_optimize_locals = visitor.visit(node.into()).is_continue();

(can_optimize_locals, visitor.uses_arguments)
}

/// The [`Visitor`] used for [`returns_value`].
#[derive(Debug)]
struct CanOptimizeLocalVariables {
strict: bool,

Check warning on line 2175 in boa_ast/src/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage

field `strict` is never read

Check warning on line 2175 in boa_ast/src/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage

field `strict` is never read

Check warning on line 2175 in boa_ast/src/operations.rs

View workflow job for this annotation

GitHub Actions / Coverage

field `strict` is never read
uses_arguments: bool,
}

impl CanOptimizeLocalVariables {
const fn new(strict: bool) -> Self {
Self {
strict,
uses_arguments: false,
}
}
}

impl<'ast> Visitor<'ast> for CanOptimizeLocalVariables {
type BreakTy = ();

fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow<Self::BreakTy> {
if node.sym() == Sym::ARGUMENTS {
self.uses_arguments = true;
}

ControlFlow::Continue(())
}

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 {
// Most likely a direct 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
98 changes: 76 additions & 22 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 @@ -898,6 +912,14 @@ impl ByteCompiler<'_, '_> {
}
}

if self.can_optimize_local_variables
&& !self
.flags
.contains(super::ByteCompilerFlags::USES_ARGUMENTS)
{
arguments_object_needed = false;
}

// 19. If strict is true or hasParameterExpressions is false, then
// a. NOTE: Only a single Environment Record is needed for the parameters,
// since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval.
Expand All @@ -910,15 +932,25 @@ impl ByteCompiler<'_, '_> {
// c. Let env be NewDeclarativeEnvironment(calleeEnv).
// d. Assert: The VariableEnvironment of calleeContext is calleeEnv.
// e. Set the LexicalEnvironment of calleeContext to env.
let _ = self.push_compile_environment(false);
additional_env = true;

if self.can_optimize_local_variables {
let _ = self.push_compile_environment(false);
additional_env = true;
}
}

// 22. If argumentsObjectNeeded is true, then
//
// 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 +973,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 +1053,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 +1067,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 +1094,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 +1135,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 +1171,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
Loading
Loading