Skip to content

Commit

Permalink
Do not allocate space for local bindings in runtime environments
Browse files Browse the repository at this point in the history
  • Loading branch information
raskad committed Sep 22, 2024
1 parent 27ab91a commit 8df3d00
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 122 deletions.
266 changes: 170 additions & 96 deletions core/ast/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -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;
}
}
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -176,31 +203,41 @@ 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| {
BindingLocator::declarative(
name.clone(),
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
)
})
}

/// 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| {
IdentifierReference::new(
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
BindingLocator::declarative(
name.clone(),
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
),
binding.lex,
binding.escapes,
)
})
)
})
}

/// Get the locator for a binding name.
#[must_use]
pub fn get_binding_reference(&self, name: &JsString) -> Option<IdentifierReference> {
self.inner
.bindings
.borrow()
.iter()
.find(|b| &b.name == name)
.map(|binding| {
IdentifierReference::new(
BindingLocator::declarative(
name.clone(),
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
),
binding.lex,
binding.escapes,
)
})
}

/// Simulate a binding access.
Expand All @@ -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;
}
Expand All @@ -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 {
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
escapes: self.is_global(),
},
);
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(),
Expand All @@ -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(),
},
);
index: binding_index,
mutable: false,
lex: true,
strict,
escapes: self.is_global(),
});
}

/// Return the binding locator for a mutable binding.
Expand All @@ -275,30 +327,34 @@ impl Scope {
&self,
name: JsString,
) -> Result<IdentifierReference, BindingLocatorError> {
Ok(match self.inner.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
Ok(
match self.inner.bindings.borrow().iter().find(|b| b.name == name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
),
binding.lex,
binding.escapes,
),
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding(name.clone()),
)?,
})
Some(binding) if binding.strict => {
return Err(BindingLocatorError::MutateImmutable)
}
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding(name.clone()),
)?,
},
)
}

#[cfg(feature = "annex-b")]
Expand All @@ -323,30 +379,34 @@ impl Scope {
);
}

Ok(match self.inner.bindings.borrow().get(&name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
Ok(
match self.inner.bindings.borrow().iter().find(|b| b.name == name) {
Some(binding) if binding.mutable => IdentifierReference::new(
BindingLocator::declarative(
name,
*self.inner.index.borrow(),
binding.index,
self.inner.unique_id,
),
binding.lex,
binding.escapes,
),
binding.lex,
binding.escapes,
),
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding_var(name.clone()),
)?,
})
Some(binding) if binding.strict => {
return Err(BindingLocatorError::MutateImmutable)
}
Some(_) => return Err(BindingLocatorError::Silent),
None => self.inner.outer.as_ref().map_or_else(
|| {
Ok(IdentifierReference::new(
BindingLocator::global(name.clone()),
false,
true,
))
},
|outer| outer.set_mutable_binding_var(name.clone()),
)?,
},
)
}

/// Gets the outer scope of this scope.
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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")]
Expand Down
Loading

0 comments on commit 8df3d00

Please sign in to comment.