Browse Source

Do not allocate space for local bindings in runtime environments

reduce-environment-allocations
raskad 2 months ago
parent
commit
8df3d007b7
No known key found for this signature in database
  1. 130
      core/ast/src/scope.rs
  2. 73
      core/ast/src/scope_analyzer.rs
  3. 4
      core/ast/src/source.rs
  4. 16
      core/engine/src/builtins/function/arguments.rs
  5. 1
      core/engine/src/bytecompiler/class.rs
  6. 1
      core/engine/src/bytecompiler/function.rs
  7. 8
      core/engine/src/bytecompiler/mod.rs
  8. 4
      core/engine/src/environments/runtime/mod.rs
  9. 2
      core/engine/src/module/synthetic.rs
  10. 5
      core/engine/src/vm/opcode/push/environment.rs

130
core/ast/src/scope.rs

@ -3,12 +3,12 @@
//! Scopes are used to track the bindings of identifiers in the AST.
use boa_string::JsString;
use rustc_hash::FxHashMap;
use std::{cell::RefCell, fmt::Debug, rc::Rc};
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::struct_excessive_bools)]
struct Binding {
name: JsString,
index: u32,
mutable: bool,
lex: bool,
@ -53,7 +53,7 @@ pub(crate) struct Inner {
unique_id: u32,
outer: Option<Scope>,
index: RefCell<u32>,
bindings: RefCell<FxHashMap<JsString, Binding>>,
bindings: RefCell<Vec<Binding>>,
function: bool,
}
@ -94,13 +94,13 @@ impl Scope {
self.inner
.bindings
.borrow()
.values()
.iter()
.all(|binding| !binding.escapes)
}
/// Marks all bindings in this scope as escaping.
pub fn escape_all_bindings(&self) {
for binding in self.inner.bindings.borrow_mut().values_mut() {
for binding in self.inner.bindings.borrow_mut().iter_mut() {
binding.escapes = true;
}
}
@ -111,21 +111,22 @@ impl Scope {
self.inner
.bindings
.borrow()
.get(name)
.iter()
.find(|b| &b.name == name)
.map_or(false, |binding| binding.lex)
}
/// Check if the scope has a binding with the given name.
#[must_use]
pub fn has_binding(&self, name: &JsString) -> bool {
self.inner.bindings.borrow().contains_key(name)
self.inner.bindings.borrow().iter().any(|b| &b.name == name)
}
/// Get the binding locator for a binding with the given name.
/// Fall back to the global scope if the binding is not found.
#[must_use]
pub fn get_identifier_reference(&self, name: JsString) -> IdentifierReference {
if let Some(binding) = self.inner.bindings.borrow().get(&name) {
if let Some(binding) = self.inner.bindings.borrow().iter().find(|b| b.name == name) {
IdentifierReference::new(
BindingLocator::declarative(
name,
@ -150,6 +151,32 @@ impl Scope {
self.inner.bindings.borrow().len() as u32
}
/// Returns the number of bindings in this scope that are not local.
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn num_bindings_non_local(&self) -> u32 {
self.inner
.bindings
.borrow()
.iter()
.filter(|binding| binding.escapes)
.count() as u32
}
/// Adjust the binding indices to exclude local bindings.
pub(crate) fn reorder_binding_indices(&self) {
let mut bindings = self.inner.bindings.borrow_mut();
let mut index = 0;
for binding in bindings.iter_mut() {
if !binding.escapes {
binding.index = 0;
continue;
}
binding.index = index;
index += 1;
}
}
/// Returns the index of this scope.
#[must_use]
pub fn scope_index(&self) -> u32 {
@ -176,7 +203,12 @@ impl Scope {
/// Get the locator for a binding name.
#[must_use]
pub fn get_binding(&self, name: &JsString) -> Option<BindingLocator> {
self.inner.bindings.borrow().get(name).map(|binding| {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
BindingLocator::declarative(
name.clone(),
*self.inner.index.borrow(),
@ -189,7 +221,12 @@ impl Scope {
/// Get the locator for a binding name.
#[must_use]
pub fn get_binding_reference(&self, name: &JsString) -> Option<IdentifierReference> {
self.inner.bindings.borrow().get(name).map(|binding| {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
IdentifierReference::new(
BindingLocator::declarative(
name.clone(),
@ -211,7 +248,13 @@ impl Scope {
let mut crossed_function_border = false;
let mut current = self;
loop {
if let Some(binding) = current.inner.bindings.borrow_mut().get_mut(name) {
if let Some(binding) = current
.inner
.bindings
.borrow_mut()
.iter_mut()
.find(|b| &b.name == name)
{
if crossed_function_border || eval_or_with {
binding.escapes = true;
}
@ -232,17 +275,24 @@ impl Scope {
#[must_use]
#[allow(clippy::cast_possible_truncation)]
pub fn create_mutable_binding(&self, name: JsString, function_scope: bool) -> BindingLocator {
let binding_index = self.inner.bindings.borrow().len() as u32;
self.inner.bindings.borrow_mut().insert(
name.clone(),
Binding {
let mut bindings = self.inner.bindings.borrow_mut();
let binding_index = bindings.len() as u32;
if let Some(binding) = bindings.iter().find(|b| b.name == name) {
return BindingLocator::declarative(
name,
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
);
}
bindings.push(Binding {
name: name.clone(),
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
escapes: self.is_global(),
},
);
});
BindingLocator::declarative(
name,
*self.inner.index.borrow(),
@ -254,17 +304,19 @@ impl Scope {
/// Crate an immutable binding.
#[allow(clippy::cast_possible_truncation)]
pub(crate) fn create_immutable_binding(&self, name: JsString, strict: bool) {
let binding_index = self.inner.bindings.borrow().len() as u32;
self.inner.bindings.borrow_mut().insert(
let mut bindings = self.inner.bindings.borrow_mut();
if bindings.iter().any(|b| b.name == name) {
return;
}
let binding_index = bindings.len() as u32;
bindings.push(Binding {
name,
Binding {
index: binding_index,
mutable: false,
lex: true,
strict,
escapes: self.is_global(),
},
);
});
}
/// Return the binding locator for a mutable binding.
@ -275,7 +327,8 @@ impl Scope {
&self,
name: JsString,
) -> Result<IdentifierReference, BindingLocatorError> {
Ok(match self.inner.bindings.borrow().get(&name) {
Ok(
match self.inner.bindings.borrow().iter().find(|b| b.name == name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
@ -286,7 +339,9 @@ impl Scope {
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(binding) if binding.strict => {
return Err(BindingLocatorError::MutateImmutable)
}
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
@ -298,7 +353,8 @@ impl Scope {
},
|outer| outer.set_mutable_binding(name.clone()),
)?,
})
},
)
}
#[cfg(feature = "annex-b")]
@ -323,7 +379,8 @@ impl Scope {
);
}
Ok(match self.inner.bindings.borrow().get(&name) {
Ok(
match self.inner.bindings.borrow().iter().find(|b| b.name == name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
@ -334,7 +391,9 @@ impl Scope {
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(binding) if binding.strict => {
return Err(BindingLocatorError::MutateImmutable)
}
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
@ -346,7 +405,8 @@ impl Scope {
},
|outer| outer.set_mutable_binding_var(name.clone()),
)?,
})
},
)
}
/// Gets the outer scope of this scope.
@ -538,7 +598,8 @@ impl FunctionScopes {
}
/// Returns the effective paramter scope for this function.
pub(crate) fn parameter_scope(&self) -> Scope {
#[must_use]
pub fn parameter_scope(&self) -> Scope {
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
return parameters_eval_scope.clone();
}
@ -572,6 +633,19 @@ impl FunctionScopes {
lexical_scope.escape_all_bindings();
}
}
pub(crate) fn reorder_binding_indices(&self) {
self.function_scope.reorder_binding_indices();
if let Some(parameters_eval_scope) = &self.parameters_eval_scope {
parameters_eval_scope.reorder_binding_indices();
}
if let Some(parameters_scope) = &self.parameters_scope {
parameters_scope.reorder_binding_indices();
}
if let Some(lexical_scope) = &self.lexical_scope {
lexical_scope.reorder_binding_indices();
}
}
}
#[cfg(feature = "arbitrary")]

73
core/ast/src/scope_analyzer.rs

@ -105,6 +105,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
try_break!(self.visit_statement_list_mut(&mut node.statements));
if let Some(scope) = &mut node.scope {
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
@ -125,6 +126,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
}
if let Some(scope) = &mut node.scope {
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
@ -140,6 +142,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
std::mem::swap(&mut self.scope, &mut node.scope);
try_break!(self.visit_statement_mut(&mut node.statement));
std::mem::swap(&mut self.scope, &mut node.scope);
node.scope.reorder_binding_indices();
self.with = with;
ControlFlow::Continue(())
}
@ -156,6 +159,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
}
try_break!(self.visit_block_mut(&mut node.block));
std::mem::swap(&mut self.scope, &mut node.scope);
node.scope.reorder_binding_indices();
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
}
@ -181,6 +185,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
try_break!(self.visit_statement_mut(&mut node.inner.body));
if let Some(ForLoopInitializer::Lexical(decl)) = &mut node.inner.init {
std::mem::swap(&mut self.scope, &mut decl.scope);
decl.scope.reorder_binding_indices();
}
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
@ -199,6 +204,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
if let Some(scope) = &mut node.target_scope {
self.direct_eval = direct_eval_old;
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
if let Some(scope) = &mut node.scope {
self.direct_eval = node.contains_direct_eval || self.direct_eval;
@ -211,6 +217,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
try_break!(self.visit_statement_mut(&mut node.body));
if let Some(scope) = &mut node.scope {
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
@ -229,6 +236,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
if let Some(scope) = &mut node.iterable_scope {
self.direct_eval = direct_eval_old;
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
if let Some(scope) = &mut node.scope {
self.direct_eval = node.contains_direct_eval || self.direct_eval;
@ -241,6 +249,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
try_break!(self.visit_statement_mut(&mut node.body));
if let Some(scope) = &mut node.scope {
std::mem::swap(&mut self.scope, scope);
scope.reorder_binding_indices();
}
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
@ -253,7 +262,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -265,7 +274,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -277,7 +286,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -289,7 +298,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -301,7 +310,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -313,7 +322,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -325,7 +334,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -337,7 +346,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -349,7 +358,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -361,7 +370,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -382,6 +391,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
try_break!(self.visit_class_element_mut(element));
}
std::mem::swap(&mut self.scope, &mut node.name_scope);
node.name_scope.reorder_binding_indices();
ControlFlow::Continue(())
}
@ -407,6 +417,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
}
if let Some(name_scope) = &mut node.name_scope {
std::mem::swap(&mut self.scope, name_scope);
name_scope.reorder_binding_indices();
}
ControlFlow::Continue(())
}
@ -415,16 +426,42 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
&mut self,
node: &'ast mut ClassElement,
) -> ControlFlow<Self::BreakTy> {
if let ClassElement::MethodDefinition(node) = node {
self.visit_function_like(
match node {
ClassElement::MethodDefinition(node) => self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
} else {
),
ClassElement::FieldDefinition(field) | ClassElement::StaticFieldDefinition(field) => {
try_break!(self.visit_property_name_mut(&mut field.name));
if let Some(e) = &mut field.field {
try_break!(self.visit_expression_mut(e));
}
ControlFlow::Continue(())
}
ClassElement::PrivateFieldDefinition(field) => {
if let Some(e) = &mut field.field {
try_break!(self.visit_expression_mut(e));
}
ControlFlow::Continue(())
}
ClassElement::PrivateStaticFieldDefinition(_, e) => {
if let Some(e) = e {
try_break!(self.visit_expression_mut(e));
}
ControlFlow::Continue(())
}
ClassElement::StaticBlock(node) => {
let contains_direct_eval = contains(node.statements(), ContainsSymbol::DirectEval);
self.visit_function_like(
&mut FormalParameterList::default(),
&mut node.body,
&mut node.scopes,
contains_direct_eval,
)
}
}
}
fn visit_object_method_definition_mut(
@ -435,7 +472,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
self.visit_function_like(
&mut node.parameters,
&mut node.body,
&node.scopes,
&mut node.scopes,
node.contains_direct_eval,
)
}
@ -485,6 +522,7 @@ impl<'ast> VisitorMut<'ast> for BindingEscapeAnalyzer<'_> {
std::mem::swap(&mut self.scope, &mut scope);
try_break!(self.visit_module_item_list_mut(&mut node.items));
std::mem::swap(&mut self.scope, &mut scope);
scope.reorder_binding_indices();
ControlFlow::Continue(())
}
}
@ -494,7 +532,7 @@ impl BindingEscapeAnalyzer<'_> {
&mut self,
parameters: &mut FormalParameterList,
body: &mut FunctionBody,
scopes: &FunctionScopes,
scopes: &mut FunctionScopes,
contains_direct_eval: bool,
) -> ControlFlow<&'static str> {
let direct_eval_old = self.direct_eval;
@ -510,6 +548,7 @@ impl BindingEscapeAnalyzer<'_> {
std::mem::swap(&mut self.scope, &mut scope);
try_break!(self.visit_function_body_mut(body));
std::mem::swap(&mut self.scope, &mut scope);
scopes.reorder_binding_indices();
self.direct_eval = direct_eval_old;
ControlFlow::Continue(())
}

4
core/ast/src/source.rs

@ -91,6 +91,10 @@ impl Script {
if !analyze_binding_escapes(self, true, lexical_scope.clone(), interner) {
return Err(String::from("Failed to analyze scope"));
}
variable_scope.escape_all_bindings();
lexical_scope.escape_all_bindings();
variable_scope.reorder_binding_indices();
lexical_scope.reorder_binding_indices();
optimize_scope_indicies(self, lexical_scope);
Ok(bindings)

16
core/engine/src/builtins/function/arguments.rs

@ -1,4 +1,5 @@
use crate::{
bytecompiler::ToJsString,
environments::DeclarativeEnvironment,
object::{
internal_methods::{
@ -11,8 +12,9 @@ use crate::{
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
Context, JsData, JsResult, JsValue,
};
use boa_ast::{function::FormalParameterList, operations::bound_names};
use boa_ast::{function::FormalParameterList, operations::bound_names, scope::Scope};
use boa_gc::{Finalize, Gc, Trace};
use boa_interner::Interner;
use rustc_hash::FxHashMap;
use thin_vec::{thin_vec, ThinVec};
@ -141,7 +143,11 @@ impl MappedArguments {
}
impl MappedArguments {
pub(crate) fn binding_indices(formals: &FormalParameterList) -> ThinVec<Option<u32>> {
pub(crate) fn binding_indices(
formals: &FormalParameterList,
scope: &Scope,
interner: &Interner,
) -> ThinVec<Option<u32>> {
// Section 17-19 are done first, for easier object creation in 11.
//
// The section 17-19 differs from the spec, due to the way the runtime environments work.
@ -180,8 +186,10 @@ impl MappedArguments {
let mut bindings = FxHashMap::default();
let mut property_index = 0;
for name in bound_names(formals) {
// NOTE(HalidOdat): Offset by +1 to account for the first binding ("argument").
let binding_index = bindings.len() as u32 + 1;
let binding_index = scope
.get_binding(&name.to_js_string(interner))
.expect("binding must exist")
.binding_index();
let entry = bindings
.entry(name)

1
core/engine/src/bytecompiler/class.rs

@ -101,6 +101,7 @@ impl ByteCompiler<'_> {
compiler.length = expr.parameters().length();
compiler.params = expr.parameters().clone();
compiler.parameter_scope = expr.scopes().parameter_scope();
compiler.function_declaration_instantiation(
expr.body(),

1
core/engine/src/bytecompiler/function.rs

@ -195,6 +195,7 @@ impl FunctionCompiler {
compiler.compile_statement_list(body.statement_list(), false, false);
compiler.params = parameters.clone();
compiler.parameter_scope = scopes.parameter_scope();
let code = compiler.finish();

8
core/engine/src/bytecompiler/mod.rs

@ -401,6 +401,9 @@ pub struct ByteCompiler<'ctx> {
/// Parameters passed to this function.
pub(crate) params: FormalParameterList,
/// Scope of the function parameters.
pub(crate) parameter_scope: Scope,
/// Bytecode
pub(crate) bytecode: Vec<u8>,
@ -513,6 +516,7 @@ impl<'ctx> ByteCompiler<'ctx> {
local_binding_registers: FxHashMap::default(),
this_mode: ThisMode::Global,
params: FormalParameterList::default(),
parameter_scope: Scope::default(),
current_open_environments_count: 0,
register_allocator,
@ -1772,7 +1776,9 @@ impl<'ctx> ByteCompiler<'ctx> {
let mapped_arguments_binding_indices = self
.emitted_mapped_arguments_object_opcode
.then(|| MappedArguments::binding_indices(&self.params))
.then(|| {
MappedArguments::binding_indices(&self.params, &self.parameter_scope, self.interner)
})
.unwrap_or_default();
let max_local_binding_register_index =

4
core/engine/src/environments/runtime/mod.rs

@ -186,7 +186,7 @@ impl EnvironmentStack {
/// Push a function environment on the environments stack.
pub(crate) fn push_function(&mut self, scope: Scope, function_slots: FunctionSlots) {
let num_bindings = scope.num_bindings();
let num_bindings = scope.num_bindings_non_local();
let (poisoned, with) = {
// Check if the outer environment is a declarative environment.
@ -214,7 +214,7 @@ impl EnvironmentStack {
/// Push a module environment on the environments stack.
pub(crate) fn push_module(&mut self, scope: Scope) {
let num_bindings = scope.num_bindings();
let num_bindings = scope.num_bindings_non_local();
self.stack.push(Environment::Declarative(Gc::new(
DeclarativeEnvironment::new(DeclarativeEnvironmentKind::Module(
ModuleEnvironment::new(num_bindings, scope),

2
core/engine/src/module/synthetic.rs

@ -307,6 +307,8 @@ impl SyntheticModule {
})
.collect::<Vec<_>>();
module_scope.escape_all_bindings();
let cb = Gc::new(compiler.finish());
let mut envs = EnvironmentStack::new(global_env);

5
core/engine/src/vm/opcode/push/environment.rs

@ -17,7 +17,10 @@ impl PushScope {
#[allow(clippy::unnecessary_wraps)]
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let scope = context.vm.frame().code_block().constant_scope(index);
context.vm.environments.push_lexical(scope.num_bindings());
context
.vm
.environments
.push_lexical(scope.num_bindings_non_local());
Ok(CompletionType::Normal)
}
}

Loading…
Cancel
Save