Browse Source

Implement local variable optimization

pull/3194/head
Haled Odat 1 year ago
parent
commit
e23ec27f83
  1. 60
      boa_ast/src/operations.rs
  2. 16
      boa_engine/src/bytecompiler/class.rs
  3. 83
      boa_engine/src/bytecompiler/declarations.rs
  4. 38
      boa_engine/src/bytecompiler/expression/assign.rs
  5. 10
      boa_engine/src/bytecompiler/expression/mod.rs
  6. 2
      boa_engine/src/bytecompiler/expression/object_literal.rs
  7. 8
      boa_engine/src/bytecompiler/expression/unary.rs
  8. 38
      boa_engine/src/bytecompiler/expression/update.rs
  9. 48
      boa_engine/src/bytecompiler/function.rs
  10. 6
      boa_engine/src/bytecompiler/jump_control.rs
  11. 238
      boa_engine/src/bytecompiler/mod.rs
  12. 8
      boa_engine/src/bytecompiler/statement/block.rs
  13. 82
      boa_engine/src/bytecompiler/statement/loop.rs
  14. 2
      boa_engine/src/bytecompiler/statement/mod.rs
  15. 8
      boa_engine/src/bytecompiler/statement/switch.rs
  16. 17
      boa_engine/src/bytecompiler/statement/try.rs
  17. 4
      boa_engine/src/bytecompiler/utils.rs
  18. 54
      boa_engine/src/environments/compile.rs
  19. 11
      boa_engine/src/environments/runtime/mod.rs
  20. 12
      boa_engine/src/module/source.rs
  21. 34
      boa_engine/src/vm/code_block.rs
  22. 18
      boa_engine/src/vm/flowgraph/mod.rs
  23. 76
      boa_engine/src/vm/opcode/control_flow/return.rs
  24. 45
      boa_engine/src/vm/opcode/delete/mod.rs
  25. 93
      boa_engine/src/vm/opcode/get/name.rs
  26. 54
      boa_engine/src/vm/opcode/mod.rs
  27. 59
      boa_engine/src/vm/opcode/set/name.rs

60
boa_ast/src/operations.rs

@ -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
boa_engine/src/bytecompiler/class.rs

@ -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,
@ -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,
);
@ -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,
);
@ -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,
);
@ -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,
);
@ -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);

83
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);
}
@ -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.
@ -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.
@ -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,
);
}
}
@ -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);

38
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);

10
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);

2
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);

8
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),
}

38
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);

48
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<Sym>,
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: <https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncconcisebody>
// - 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody: <https://tc39.es/ecma262/#sec-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 ): <https://tc39.es/ecma262/#sec-execute-async-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 ): <https://tc39.es/ecma262/#sec-asyncgeneratorstart>
//
// 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());
}

6
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
//

238
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<L, G, S>(
self,
local: L,
global: G,
slow: S,
compiler: &mut ByteCompiler<'_, '_>,
) where
L: Into<Option<Opcode>>,
G: Into<Option<Opcode>>,
S: Into<Option<Opcode>>,
{
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<Handler>,
literals_map: FxHashMap<Literal, u32>,
names_map: FxHashMap<Identifier, u32>,
bindings_map: FxHashMap<BindingLocator, u32>,
bindings_map: FxHashMap<BindingLocator, EnvironmentAccess>,
jump_info: Vec<JumpControlInfo>,
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<u32>,
/// 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<u32>,
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,
}
}

8
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);
}
}
}

82
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);
@ -66,16 +71,28 @@ impl ByteCompiler<'_, '_> {
.expect("jump_control must exist as it was just pushed")
.set_start_address(start_address);
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);
}
if !self.can_optimize_local_variables {
if let Some((let_binding_indices, env_index)) = &let_binding_indices {
for index in let_binding_indices {
index.emit(
Opcode::GetLocal,
Opcode::GetGlobalName,
Opcode::GetName,
self,
);
}
self.emit_opcode(Opcode::PopEnvironment);
self.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, *env_index);
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);
for index in let_binding_indices.iter().rev() {
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)]);

2
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);
}

8
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);
}
}
}

17
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);

4
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);

54
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<BindingLocator> {
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<BindingLocator, BindingLocatorError> {
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<BindingLocator, BindingLocatorError> {
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<Rc<Self>> {
self.outer.clone()
}
pub(crate) const fn environment_index(&self) -> u32 {
self.environment_index
}
}

11
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

12
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);
}

34
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<CompileTimeEnvironment>]>,
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());

18
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"),
}
}

76
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<CompletionType> {
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<CompletionType> {
let offset = context.vm.read::<u8>() as usize;
Self::operation(context, offset)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let offset = context.vm.read::<u16>() as usize;
Self::operation(context, offset)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let offset = context.vm.read::<u32>() 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<CompletionType> {
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<CompletionType> {
let offset = context.vm.read::<u8>() as usize;
Self::operation(context, offset)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let offset = context.vm.read::<u16>() as usize;
Self::operation(context, offset)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let offset = context.vm.read::<u32>() as usize;
Self::operation(context, offset)
}
}

45
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<CompletionType> {
let index = context.vm.read::<u8>();
Self::operation(context, index as usize)
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() 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<CompletionType> {
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<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
@ -109,8 +146,8 @@ impl Operation for DeleteName {
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
Self::operation(context, index as usize)
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

93
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<CompletionType> {
let index = context.vm.read::<u8>();
Self::operation(context, index as usize)
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
@ -43,8 +43,93 @@ impl Operation for GetName {
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
Self::operation(context, index as usize)
let index = context.vm.read::<u32>() 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<CompletionType> {
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<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() 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<CompletionType> {
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<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

54
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.

59
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<CompletionType> {
let index = context.vm.read::<u8>();
Self::operation(context, index as usize)
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
@ -39,8 +39,59 @@ impl Operation for ThrowMutateImmutable {
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>();
Self::operation(context, index as usize)
let index = context.vm.read::<u32>() 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<CompletionType> {
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<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

Loading…
Cancel
Save