diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/jsparagus-stencil/src/scope.rs | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus-stencil/src/scope.rs b/third_party/rust/jsparagus-stencil/src/scope.rs new file mode 100644 index 0000000000..9b4fa3a94b --- /dev/null +++ b/third_party/rust/jsparagus-stencil/src/scope.rs @@ -0,0 +1,639 @@ +//! Types for the output of scope analysis. +//! +//! The top-level output of this analysis is the `ScopeDataMap`, which holds +//! the following: +//! * `LexicalScopeData` for each lexial scope in the AST +//! * `GlobalScopeData` for the global scope +//! * `VarScopeData` for extra body var scope in function +//! * `FunctionScopeData` for the function scope +//! +//! Each scope contains a list of bindings (`BindingName`). + +use crate::frame_slot::FrameSlot; +use crate::script::ScriptStencilIndex; +use ast::associated_data::AssociatedData; +use ast::source_atom_set::SourceAtomSetIndex; +use ast::source_location_accessor::SourceLocationAccessor; +use ast::type_id::NodeTypeIdAccessor; + +/// Corresponds to js::BindingKind in m-c/js/src/vm/Scope.h. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BindingKind { + Var, + Let, + Const, +} + +/// Information about a single binding in a JS script. +/// +/// Corresponds to `js::BindingName` in m-c/js/src/vm/Scope.h. +#[derive(Debug)] +pub struct BindingName { + pub name: SourceAtomSetIndex, + pub is_closed_over: bool, + pub is_top_level_function: bool, +} + +impl BindingName { + pub fn new(name: SourceAtomSetIndex, is_closed_over: bool) -> Self { + Self { + name, + is_closed_over, + is_top_level_function: false, + } + } + + pub fn new_top_level_function(name: SourceAtomSetIndex, is_closed_over: bool) -> Self { + Self { + name, + is_closed_over, + is_top_level_function: true, + } + } +} + +/// Corresponds to the accessor part of `js::BindingIter` in +/// m-c/js/src/vm/Scope.h. +pub struct BindingIterItem<'a> { + name: &'a BindingName, + kind: BindingKind, +} + +impl<'a> BindingIterItem<'a> { + fn new(name: &'a BindingName, kind: BindingKind) -> Self { + Self { name, kind } + } + + pub fn name(&self) -> SourceAtomSetIndex { + self.name.name + } + + pub fn is_top_level_function(&self) -> bool { + self.name.is_top_level_function + } + + pub fn is_closed_over(&self) -> bool { + self.name.is_closed_over + } + + pub fn kind(&self) -> BindingKind { + self.kind + } +} + +/// Accessor for both BindingName/Option<BindingName>. +pub trait MaybeBindingName { + fn is_closed_over(&self) -> bool; +} +impl MaybeBindingName for BindingName { + fn is_closed_over(&self) -> bool { + self.is_closed_over + } +} +impl MaybeBindingName for Option<BindingName> { + fn is_closed_over(&self) -> bool { + match self { + Some(b) => b.is_closed_over, + None => false, + } + } +} + +#[derive(Debug)] +pub struct BaseScopeData<BindingItemT> +where + BindingItemT: MaybeBindingName, +{ + /// Corresponds to `*Scope::Data.{length, trailingNames}.` + /// The layout is defined by *ScopeData structs below, that has + /// this struct. + pub bindings: Vec<BindingItemT>, + pub has_eval: bool, + pub has_with: bool, +} + +impl<BindingItemT> BaseScopeData<BindingItemT> +where + BindingItemT: MaybeBindingName, +{ + pub fn new(bindings_count: usize) -> Self { + Self { + bindings: Vec::with_capacity(bindings_count), + has_eval: false, + has_with: false, + } + } + + pub fn is_all_bindings_closed_over(&self) -> bool { + // `with` and direct `eval` can dynamically access any binding in this + // scope. + self.has_eval || self.has_with + } + + /// Returns true if this scope needs to be allocated on heap as + /// EnvironmentObject. + pub fn needs_environment_object(&self) -> bool { + // `with` and direct `eval` can dynamically access bindings in this + // scope. + if self.is_all_bindings_closed_over() { + return true; + } + + // If a binding in this scope is closed over by inner function, + // it can be accessed when this frame isn't on the stack. + for binding in &self.bindings { + if binding.is_closed_over() { + return true; + } + } + + false + } +} + +/// Bindings created in global environment. +/// +/// Maps to js::GlobalScope::Data in m-c/js/src/vm/Scope.h. +#[derive(Debug)] +pub struct GlobalScopeData { + /// Bindings are sorted by kind: + /// + /// * `base.bindings[0..let_start]` - `var`s + /// * `base.bindings[let_start..const_start]` - `let`s + /// * `base.bindings[const_start..]` - `const`s + pub base: BaseScopeData<BindingName>, + + pub let_start: usize, + pub const_start: usize, + + /// The global functions in this script. + pub functions: Vec<ScriptStencilIndex>, +} + +impl GlobalScopeData { + pub fn new( + var_count: usize, + let_count: usize, + const_count: usize, + functions: Vec<ScriptStencilIndex>, + ) -> Self { + let capacity = var_count + let_count + const_count; + + Self { + base: BaseScopeData::new(capacity), + let_start: var_count, + const_start: var_count + let_count, + functions, + } + } + + pub fn iter<'a>(&'a self) -> GlobalBindingIter<'a> { + GlobalBindingIter::new(self) + } +} + +/// Corresponds to the iteration part of js::BindingIter +/// in m-c/js/src/vm/Scope.h. +pub struct GlobalBindingIter<'a> { + data: &'a GlobalScopeData, + i: usize, +} + +impl<'a> GlobalBindingIter<'a> { + fn new(data: &'a GlobalScopeData) -> Self { + Self { data, i: 0 } + } +} + +impl<'a> Iterator for GlobalBindingIter<'a> { + type Item = BindingIterItem<'a>; + + fn next(&mut self) -> Option<BindingIterItem<'a>> { + if self.i == self.data.base.bindings.len() { + return None; + } + + let kind = if self.i < self.data.let_start { + BindingKind::Var + } else if self.i < self.data.const_start { + BindingKind::Let + } else { + BindingKind::Const + }; + + let name = &self.data.base.bindings[self.i]; + + self.i += 1; + + Some(BindingIterItem::new(name, kind)) + } +} + +/// Bindings created in var environment. +/// +/// Maps to js::VarScope::Data in m-c/js/src/vm/Scope.h, +/// and the parameter for js::frontend::ScopeCreationData::create +/// in m-c/js/src/frontend/Stencil.h +#[derive(Debug)] +pub struct VarScopeData { + /// All bindings are `var`. + pub base: BaseScopeData<BindingName>, + + /// The first frame slot of this scope. + /// + /// Unlike VarScope::Data, this struct holds the first frame slot, + /// instead of the next frame slot. + /// + /// This is because ScopeCreationData::create receives the first frame slot + /// and VarScope::Data.nextFrameSlot is calculated there. + pub first_frame_slot: FrameSlot, + + pub function_has_extensible_scope: bool, + + /// ScopeIndex of the enclosing scope. + /// + /// A parameter for ScopeCreationData::create. + pub enclosing: ScopeIndex, +} + +impl VarScopeData { + pub fn new( + var_count: usize, + function_has_extensible_scope: bool, + enclosing: ScopeIndex, + ) -> Self { + let capacity = var_count; + + Self { + base: BaseScopeData::new(capacity), + // Set to the correct value in EmitterScopeStack::enter_lexical. + first_frame_slot: FrameSlot::new(0), + function_has_extensible_scope, + enclosing, + } + } +} + +/// Bindings created in lexical environment. +/// +/// Maps to js::LexicalScope::Data in m-c/js/src/vm/Scope.h, +/// and the parameter for js::frontend::ScopeCreationData::create +/// in m-c/js/src/frontend/Stencil.h +#[derive(Debug)] +pub struct LexicalScopeData { + /// Bindings are sorted by kind: + /// + /// * `base.bindings[0..const_start]` - `let`s + /// * `base.bindings[const_start..]` - `const`s + pub base: BaseScopeData<BindingName>, + + pub const_start: usize, + + /// The first frame slot of this scope. + /// + /// Unlike LexicalScope::Data, this struct holds the first frame slot, + /// instead of the next frame slot. + /// + /// This is because ScopeCreationData::create receives the first frame slot + /// and LexicalScope::Data.nextFrameSlot is calculated there. + pub first_frame_slot: FrameSlot, + + /// ScopeIndex of the enclosing scope. + /// + /// A parameter for ScopeCreationData::create. + pub enclosing: ScopeIndex, + + /// Functions in this scope. + pub functions: Vec<ScriptStencilIndex>, +} + +impl LexicalScopeData { + fn new( + let_count: usize, + const_count: usize, + enclosing: ScopeIndex, + functions: Vec<ScriptStencilIndex>, + ) -> Self { + let capacity = let_count + const_count; + + Self { + base: BaseScopeData::new(capacity), + const_start: let_count, + // Set to the correct value in EmitterScopeStack::enter_lexical. + first_frame_slot: FrameSlot::new(0), + enclosing, + functions, + } + } + + pub fn new_block( + let_count: usize, + const_count: usize, + enclosing: ScopeIndex, + functions: Vec<ScriptStencilIndex>, + ) -> Self { + Self::new(let_count, const_count, enclosing, functions) + } + + pub fn new_named_lambda(enclosing: ScopeIndex) -> Self { + Self::new(0, 1, enclosing, Vec::new()) + } + + pub fn new_function_lexical( + let_count: usize, + const_count: usize, + enclosing: ScopeIndex, + ) -> Self { + Self::new(let_count, const_count, enclosing, Vec::new()) + } + + pub fn iter<'a>(&'a self) -> LexicalBindingIter<'a> { + LexicalBindingIter::new(self) + } +} + +/// Corresponds to the iteration part of js::BindingIter +/// in m-c/js/src/vm/Scope.h. +pub struct LexicalBindingIter<'a> { + data: &'a LexicalScopeData, + i: usize, +} + +impl<'a> LexicalBindingIter<'a> { + fn new(data: &'a LexicalScopeData) -> Self { + Self { data, i: 0 } + } +} + +impl<'a> Iterator for LexicalBindingIter<'a> { + type Item = BindingIterItem<'a>; + + fn next(&mut self) -> Option<BindingIterItem<'a>> { + if self.i == self.data.base.bindings.len() { + return None; + } + + let kind = if self.i < self.data.const_start { + BindingKind::Let + } else { + BindingKind::Const + }; + + let name = &self.data.base.bindings[self.i]; + + self.i += 1; + + Some(BindingIterItem::new(name, kind)) + } +} + +/// Bindings created in function environment. +/// +/// Maps to js::FunctionScope::Data in m-c/js/src/vm/Scope.h, +/// and the parameter for js::frontend::ScopeCreationData::create +/// in m-c/js/src/frontend/Stencil.h +#[derive(Debug)] +pub struct FunctionScopeData { + /// Bindings are sorted by kind: + /// + /// * `base.bindings[0..non_positional_formal_start]` - + /// positional foparameters: + /// - single binding parameter with/without default + /// - single binding rest parameter + /// * `base.bindings[non_positional_formal_start..var_start]` - + /// non positional parameters: + /// - destructuring parameter + /// - destructuring rest parameter + /// * `base.bindings[var_start..]` - `var`s + /// + /// Given positional parameters range can have null slot for destructuring, + /// use Vec of Option<BindingName>, instead of BindingName like others. + pub base: BaseScopeData<Option<BindingName>>, + + pub has_parameter_exprs: bool, + + pub non_positional_formal_start: usize, + pub var_start: usize, + + /// The first frame slot of this scope. + /// + /// Unlike FunctionScope::Data, this struct holds the first frame slot, + /// instead of the next frame slot. + /// + /// This is because ScopeCreationData::create receives the first frame slot + /// and FunctionScope::Data.nextFrameSlot is calculated there. + pub first_frame_slot: FrameSlot, + + /// ScopeIndex of the enclosing scope. + /// + /// A parameter for ScopeCreationData::create. + pub enclosing: ScopeIndex, + + pub function_index: ScriptStencilIndex, + + /// True if the function is an arrow function. + pub is_arrow: bool, +} + +impl FunctionScopeData { + pub fn new( + has_parameter_exprs: bool, + positional_parameter_count: usize, + non_positional_formal_start: usize, + max_var_count: usize, + enclosing: ScopeIndex, + function_index: ScriptStencilIndex, + is_arrow: bool, + ) -> Self { + let capacity = positional_parameter_count + non_positional_formal_start + max_var_count; + + Self { + base: BaseScopeData::new(capacity), + has_parameter_exprs, + non_positional_formal_start: positional_parameter_count, + var_start: positional_parameter_count + non_positional_formal_start, + // Set to the correct value in EmitterScopeStack::enter_function. + first_frame_slot: FrameSlot::new(0), + enclosing, + function_index, + is_arrow, + } + } +} + +#[derive(Debug)] +pub enum ScopeData { + /// No scope should be generated, but this scope becomes an alias to + /// enclosing scope. This is used, for example, when we see a function, + /// and set aside a ScopeData for its lexical bindings, but upon + /// reaching the end of the function body, we find that there were no + /// lexical bindings and the spec actually says not to generate a Lexical + /// Environment when this function is called. + /// + /// In other places, the spec does say to create a Lexical Environment, but + /// it turns out it doesn't have any bindings in it and we can optimize it + /// away. + /// + /// NOTE: Alias can be chained. + Alias(ScopeIndex), + + Global(GlobalScopeData), + Var(VarScopeData), + Lexical(LexicalScopeData), + Function(FunctionScopeData), +} + +/// Index into ScopeDataList.scopes. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ScopeIndex { + index: usize, +} +impl ScopeIndex { + fn new(index: usize) -> Self { + Self { index } + } + + pub fn next(&self) -> Self { + Self { + index: self.index + 1, + } + } +} + +impl From<ScopeIndex> for usize { + fn from(index: ScopeIndex) -> usize { + index.index + } +} + +/// A vector of scopes, incrementally populated during analysis. +/// The goal is to build a `Vec<ScopeData>`. +#[derive(Debug)] +pub struct ScopeDataList { + /// Uses Option to allow `allocate()` and `populate()` to be called + /// separately. + scopes: Vec<Option<ScopeData>>, +} + +impl ScopeDataList { + pub fn new() -> Self { + Self { scopes: Vec::new() } + } + + pub fn push(&mut self, scope: ScopeData) -> ScopeIndex { + let index = self.scopes.len(); + self.scopes.push(Some(scope)); + ScopeIndex::new(index) + } + + pub fn allocate(&mut self) -> ScopeIndex { + let index = self.scopes.len(); + self.scopes.push(None); + ScopeIndex::new(index) + } + + pub fn populate(&mut self, index: ScopeIndex, scope: ScopeData) { + self.scopes[usize::from(index)].replace(scope); + } + + fn get(&self, index: ScopeIndex) -> &ScopeData { + self.scopes[usize::from(index)] + .as_ref() + .expect("Should be populated") + } + + pub fn get_mut(&mut self, index: ScopeIndex) -> &mut ScopeData { + self.scopes[usize::from(index)] + .as_mut() + .expect("Should be populated") + } +} + +impl From<ScopeDataList> for Vec<ScopeData> { + fn from(list: ScopeDataList) -> Vec<ScopeData> { + list.scopes + .into_iter() + .map(|g| g.expect("Should be populated")) + .collect() + } +} + +/// The collection of all scope data associated with bindings and scopes in the +/// AST. +#[derive(Debug)] +pub struct ScopeDataMap { + scopes: ScopeDataList, + global: ScopeIndex, + + /// Associates every AST node that's a scope with an index into `scopes`. + non_global: AssociatedData<ScopeIndex>, +} + +impl ScopeDataMap { + pub fn new( + scopes: ScopeDataList, + global: ScopeIndex, + non_global: AssociatedData<ScopeIndex>, + ) -> Self { + Self { + scopes, + global, + non_global, + } + } + + pub fn get_global_index(&self) -> ScopeIndex { + self.global + } + + pub fn get_global_at(&self, index: ScopeIndex) -> &GlobalScopeData { + match self.scopes.get(index) { + ScopeData::Global(scope) => scope, + _ => panic!("Unexpected scope data for global"), + } + } + + pub fn get_index<NodeT>(&self, node: &NodeT) -> ScopeIndex + where + NodeT: SourceLocationAccessor + NodeTypeIdAccessor, + { + self.non_global + .get(node) + .expect("There should be a scope data associated") + .clone() + } + + pub fn get_lexical_at(&self, index: ScopeIndex) -> &LexicalScopeData { + match self.scopes.get(index) { + ScopeData::Lexical(scope) => scope, + _ => panic!("Unexpected scope data for lexical"), + } + } + + pub fn get_lexical_at_mut(&mut self, index: ScopeIndex) -> &mut LexicalScopeData { + match self.scopes.get_mut(index) { + ScopeData::Lexical(scope) => scope, + _ => panic!("Unexpected scope data for lexical"), + } + } + + pub fn get_function_at_mut(&mut self, index: ScopeIndex) -> &mut FunctionScopeData { + match self.scopes.get_mut(index) { + ScopeData::Function(scope) => scope, + _ => panic!("Unexpected scope data for function"), + } + } + + pub fn is_alias(&mut self, index: ScopeIndex) -> bool { + match self.scopes.get(index) { + ScopeData::Alias(_) => true, + _ => false, + } + } +} + +impl From<ScopeDataMap> for Vec<ScopeData> { + fn from(map: ScopeDataMap) -> Vec<ScopeData> { + map.scopes.into() + } +} |