summaryrefslogtreecommitdiffstats
path: root/third_party/rust/jsparagus-emitter/src/emitter_scope.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/jsparagus-emitter/src/emitter_scope.rs
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/jsparagus-emitter/src/emitter_scope.rs')
-rw-r--r--third_party/rust/jsparagus-emitter/src/emitter_scope.rs453
1 files changed, 453 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus-emitter/src/emitter_scope.rs b/third_party/rust/jsparagus-emitter/src/emitter_scope.rs
new file mode 100644
index 0000000000..193ac3f844
--- /dev/null
+++ b/third_party/rust/jsparagus-emitter/src/emitter_scope.rs
@@ -0,0 +1,453 @@
+//! Code for tracking scopes and looking up names as the emitter traverses
+//! the program.
+//!
+//! EmitterScopes exist only while the bytecode emitter is working.
+//! Longer-lived scope information is stored in `ScopeDataMap`.
+
+use crate::emitter::InstructionWriter;
+use ast::source_atom_set::SourceAtomSetIndex;
+use std::collections::HashMap;
+use std::iter::Iterator;
+use stencil::env_coord::{EnvironmentHops, EnvironmentSlot};
+use stencil::frame_slot::FrameSlot;
+use stencil::scope::{BindingKind, GlobalScopeData, LexicalScopeData, ScopeDataMap, ScopeIndex};
+use stencil::scope_notes::ScopeNoteIndex;
+
+/// Result of looking up a name.
+///
+/// Corresponds to js::frontend::NameLocation in
+/// m-c/js/src/frontend/NameAnalysisTypes.h
+#[derive(Debug, Clone)]
+pub enum NameLocation {
+ Dynamic,
+ Global(BindingKind),
+ FrameSlot(FrameSlot, BindingKind),
+ EnvironmentCoord(EnvironmentHops, EnvironmentSlot, BindingKind),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct EmitterScopeDepth {
+ index: usize,
+}
+
+impl EmitterScopeDepth {
+ fn has_parent(&self) -> bool {
+ self.index > 0
+ }
+
+ fn parent(&self) -> Self {
+ debug_assert!(self.has_parent());
+
+ Self {
+ index: self.index - 1,
+ }
+ }
+}
+
+// --- EmitterScope types
+//
+// These types are the variants of enum EmitterScope.
+
+#[derive(Debug)]
+pub struct GlobalEmitterScope {
+ cache: HashMap<SourceAtomSetIndex, NameLocation>,
+}
+
+impl GlobalEmitterScope {
+ fn new(data: &GlobalScopeData) -> Self {
+ let mut cache = HashMap::new();
+ for item in data.iter() {
+ cache.insert(item.name(), NameLocation::Global(item.kind()));
+ }
+ Self { cache }
+ }
+
+ fn lookup_name(&self, name: SourceAtomSetIndex) -> Option<NameLocation> {
+ match self.cache.get(&name) {
+ Some(loc) => Some(loc.clone()),
+ None => Some(NameLocation::Global(BindingKind::Var)),
+ }
+ }
+
+ fn next_frame_slot(&self) -> FrameSlot {
+ FrameSlot::new(0)
+ }
+
+ fn scope_note_index(&self) -> Option<ScopeNoteIndex> {
+ None
+ }
+
+ fn has_environment_object(&self) -> bool {
+ false
+ }
+}
+
+struct LexicalEnvironmentObject {}
+impl LexicalEnvironmentObject {
+ fn first_free_slot() -> u32 {
+ // FIXME: This is the value of
+ // `JSSLOT_FREE(&LexicalEnvironmentObject::class_)`
+ // in SpiderMonkey
+ 2
+ }
+}
+
+#[derive(Debug)]
+pub struct LexicalEmitterScope {
+ cache: HashMap<SourceAtomSetIndex, NameLocation>,
+ next_frame_slot: FrameSlot,
+ needs_environment_object: bool,
+ scope_note_index: Option<ScopeNoteIndex>,
+}
+
+impl LexicalEmitterScope {
+ pub fn new(data: &LexicalScopeData, first_frame_slot: FrameSlot) -> Self {
+ let is_all_bindings_closed_over = data.base.is_all_bindings_closed_over();
+ let mut needs_environment_object = false;
+
+ let mut cache = HashMap::new();
+ let mut frame_slot = first_frame_slot;
+ let mut env_slot = EnvironmentSlot::new(LexicalEnvironmentObject::first_free_slot());
+ for item in data.iter() {
+ if is_all_bindings_closed_over || item.is_closed_over() {
+ cache.insert(
+ item.name(),
+ NameLocation::EnvironmentCoord(EnvironmentHops::new(0), env_slot, item.kind()),
+ );
+ env_slot.next();
+ needs_environment_object = true;
+ } else {
+ cache.insert(
+ item.name(),
+ NameLocation::FrameSlot(frame_slot, item.kind()),
+ );
+ frame_slot.next();
+ }
+ }
+
+ Self {
+ cache,
+ next_frame_slot: frame_slot,
+ needs_environment_object,
+ scope_note_index: None,
+ }
+ }
+
+ fn lookup_name(&self, name: SourceAtomSetIndex) -> Option<NameLocation> {
+ match self.cache.get(&name) {
+ Some(loc) => Some(loc.clone()),
+ None => None,
+ }
+ }
+
+ fn next_frame_slot(&self) -> FrameSlot {
+ self.next_frame_slot
+ }
+
+ fn scope_note_index(&self) -> Option<ScopeNoteIndex> {
+ self.scope_note_index
+ }
+
+ pub fn has_environment_object(&self) -> bool {
+ self.needs_environment_object
+ }
+}
+
+/// The information about a scope needed for emitting bytecode.
+#[derive(Debug)]
+pub enum EmitterScope {
+ Global(GlobalEmitterScope),
+ Lexical(LexicalEmitterScope),
+}
+
+impl EmitterScope {
+ fn lookup_name(&self, name: SourceAtomSetIndex) -> Option<NameLocation> {
+ match self {
+ EmitterScope::Global(scope) => scope.lookup_name(name),
+ EmitterScope::Lexical(scope) => scope.lookup_name(name),
+ }
+ }
+
+ fn next_frame_slot(&self) -> FrameSlot {
+ match self {
+ EmitterScope::Global(scope) => scope.next_frame_slot(),
+ EmitterScope::Lexical(scope) => scope.next_frame_slot(),
+ }
+ }
+
+ pub fn scope_note_index(&self) -> Option<ScopeNoteIndex> {
+ match self {
+ EmitterScope::Global(scope) => scope.scope_note_index(),
+ EmitterScope::Lexical(scope) => scope.scope_note_index(),
+ }
+ }
+
+ fn has_environment_object(&self) -> bool {
+ match self {
+ EmitterScope::Global(scope) => scope.has_environment_object(),
+ EmitterScope::Lexical(scope) => scope.has_environment_object(),
+ }
+ }
+
+ fn is_var_scope(&self) -> bool {
+ match self {
+ EmitterScope::Global(_) => true,
+ EmitterScope::Lexical(_) => false,
+ }
+ }
+}
+
+/// Stack that tracks the current scope chain while emitting bytecode.
+///
+/// Bytecode is emitted by traversing the structure of the program. During this
+/// process there's always a "current position". This stack represents the
+/// scope chain at the current position. It is updated when we enter or leave
+/// any node that has its own scope.
+///
+/// Unlike the C++ impl, this uses an explicit stack struct, since Rust cannot
+/// store a reference to a stack-allocated object in another object that has a
+/// longer lifetime.
+pub struct EmitterScopeStack {
+ scope_stack: Vec<EmitterScope>,
+}
+
+impl EmitterScopeStack {
+ /// Create a new, empty scope stack.
+ pub fn new() -> Self {
+ Self {
+ scope_stack: Vec::new(),
+ }
+ }
+
+ /// The current innermost scope.
+ fn innermost(&self) -> &EmitterScope {
+ self.scope_stack
+ .last()
+ .expect("There should be at least one scope")
+ }
+
+ /// Enter the global scope. Call this once at the beginning of a top-level
+ /// script.
+ ///
+ /// This emits bytecode that implements parts of [ScriptEvaluation][1] and
+ /// [GlobalDeclarationInstantiation][2].
+ ///
+ /// [1]: https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation
+ /// [2]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
+ pub fn enter_global(
+ &mut self,
+ emit: &mut InstructionWriter,
+ scope_data_map: &ScopeDataMap,
+ top_level_function_count: u32,
+ ) {
+ let scope_index = scope_data_map.get_global_index();
+ let scope_data = scope_data_map.get_global_at(scope_index);
+
+ // The outermost scope should be the first item in the GC things list.
+ // Enter global scope here, before emitting any name ops below.
+ emit.enter_global_scope(scope_index);
+
+ if scope_data.base.bindings.len() > 0 {
+ emit.global_or_eval_decl_instantiation(top_level_function_count);
+ }
+
+ emit.switch_to_main();
+
+ let scope = EmitterScope::Global(GlobalEmitterScope::new(scope_data));
+ self.scope_stack.push(scope);
+ }
+
+ /// Leave the global scope. Call this once at the end of a top-level
+ /// script.
+ pub fn leave_global(&mut self, emit: &InstructionWriter) {
+ match self.scope_stack.pop() {
+ Some(EmitterScope::Global(_)) => {}
+ _ => panic!("unmatching scope"),
+ }
+ emit.leave_global_scope();
+ }
+
+ /// Emit bytecode to mark some local lexical variables as uninitialized.
+ fn dead_zone_frame_slot_range(
+ &self,
+ emit: &mut InstructionWriter,
+ slot_start: FrameSlot,
+ slot_end: FrameSlot,
+ ) {
+ if slot_start == slot_end {
+ return;
+ }
+
+ emit.uninitialized();
+ let mut slot = slot_start;
+ while slot < slot_end {
+ emit.init_lexical(slot.into());
+ slot.next();
+ }
+ emit.pop();
+ }
+
+ /// Enter a lexical scope.
+ ///
+ /// A new LexicalEmitterScope based on scope_data_map is pushed to the
+ /// scope stack. Bytecode is emitted to mark the new lexical bindings as
+ /// uninitialized.
+ pub fn enter_lexical(
+ &mut self,
+ emit: &mut InstructionWriter,
+ scope_data_map: &mut ScopeDataMap,
+ scope_index: ScopeIndex,
+ ) {
+ let mut scope_data = scope_data_map.get_lexical_at_mut(scope_index);
+
+ let parent_scope_note_index = self.innermost().scope_note_index();
+
+ let first_frame_slot = self.innermost().next_frame_slot();
+ scope_data.first_frame_slot = first_frame_slot;
+ let mut lexical_scope = LexicalEmitterScope::new(scope_data, first_frame_slot);
+ let next_frame_slot = lexical_scope.next_frame_slot;
+ let index = emit.enter_lexical_scope(
+ scope_index,
+ parent_scope_note_index,
+ next_frame_slot,
+ lexical_scope.needs_environment_object,
+ );
+ lexical_scope.scope_note_index = Some(index);
+
+ let scope = EmitterScope::Lexical(lexical_scope);
+ self.scope_stack.push(scope);
+
+ self.dead_zone_frame_slot_range(emit, first_frame_slot, next_frame_slot);
+ }
+
+ /// Leave a lexical scope.
+ pub fn leave_lexical(&mut self, emit: &mut InstructionWriter) {
+ let lexical_scope = match self.scope_stack.pop() {
+ Some(EmitterScope::Lexical(scope)) => scope,
+ _ => panic!("unmatching scope"),
+ };
+ emit.leave_lexical_scope(
+ lexical_scope
+ .scope_note_index
+ .expect("scope note index should be populated"),
+ lexical_scope.needs_environment_object,
+ );
+ }
+
+ /// Resolve a name by searching the current scope chain.
+ ///
+ /// Implements the parts of [ResolveBinding][1] that can be done at
+ /// emit time.
+ ///
+ /// [1]: https://tc39.es/ecma262/#sec-resolvebinding
+ pub fn lookup_name(&mut self, name: SourceAtomSetIndex) -> NameLocation {
+ let mut hops = EnvironmentHops::new(0);
+
+ for scope in self.scope_stack.iter().rev() {
+ if let Some(loc) = scope.lookup_name(name) {
+ return match loc {
+ NameLocation::EnvironmentCoord(orig_hops, slot, kind) => {
+ debug_assert!(u8::from(orig_hops) == 0u8);
+ NameLocation::EnvironmentCoord(hops, slot, kind)
+ }
+ _ => loc,
+ };
+ }
+ if scope.has_environment_object() {
+ hops.next();
+ }
+ }
+
+ NameLocation::Dynamic
+ }
+
+ /// Just like lookup_name, but only in var scope.
+ pub fn lookup_name_in_var(&mut self, name: SourceAtomSetIndex) -> NameLocation {
+ let mut hops = EnvironmentHops::new(0);
+
+ for scope in self.scope_stack.iter().rev() {
+ if scope.is_var_scope() {
+ if let Some(loc) = scope.lookup_name(name) {
+ return match loc {
+ NameLocation::EnvironmentCoord(orig_hops, slot, kind) => {
+ debug_assert!(u8::from(orig_hops) == 0u8);
+ NameLocation::EnvironmentCoord(hops, slot, kind)
+ }
+ _ => loc,
+ };
+ }
+ }
+
+ if scope.has_environment_object() {
+ hops.next();
+ }
+ }
+
+ NameLocation::Dynamic
+ }
+
+ pub fn current_depth(&self) -> EmitterScopeDepth {
+ EmitterScopeDepth {
+ index: self.scope_stack.len() - 1,
+ }
+ }
+
+ /// Walk the scope stack up to and including `to` depth.
+ /// See EmitterScopeWalker for the details.
+ pub fn walk_up_to_including<'a>(&'a self, to: EmitterScopeDepth) -> EmitterScopeWalker<'a> {
+ EmitterScopeWalker::new(self, to)
+ }
+
+ pub fn get_current_scope_note_index(&self) -> Option<ScopeNoteIndex> {
+ self.innermost().scope_note_index()
+ }
+
+ fn get<'a>(&'a self, index: EmitterScopeDepth) -> &'a EmitterScope {
+ self.scope_stack
+ .get(index.index)
+ .expect("scope should exist")
+ }
+}
+
+/// Walk the scope stack up to `to`, and yields EmitterScopeWalkItem for
+/// each scope.
+///
+/// The first item is `{ outer: parent-of-innermost, inner: innermost }`, and
+/// the last item is `{ outer: to, inner: child-of-to }`.
+pub struct EmitterScopeWalker<'a> {
+ stack: &'a EmitterScopeStack,
+ to: EmitterScopeDepth,
+ current: EmitterScopeDepth,
+}
+
+impl<'a> EmitterScopeWalker<'a> {
+ fn new(stack: &'a EmitterScopeStack, to: EmitterScopeDepth) -> Self {
+ let current = stack.current_depth();
+
+ Self { stack, to, current }
+ }
+}
+
+pub struct EmitterScopeWalkItem<'a> {
+ pub outer: &'a EmitterScope,
+ pub inner: &'a EmitterScope,
+}
+
+impl<'a> Iterator for EmitterScopeWalker<'a> {
+ type Item = EmitterScopeWalkItem<'a>;
+
+ fn next(&mut self) -> Option<EmitterScopeWalkItem<'a>> {
+ if self.current == self.to {
+ return None;
+ }
+
+ let outer_index = self.current.parent();
+ let inner_index = self.current;
+ self.current = outer_index;
+
+ Some(EmitterScopeWalkItem {
+ inner: self.stack.get(inner_index),
+ outer: self.stack.get(outer_index),
+ })
+ }
+}