Skip to content

Scope analysis

arai-a edited this page Apr 24, 2020 · 4 revisions

(work in progress)

This page describes what's done in scope crate.

Goal

Correct scope/binding information used by emitter.

  • which binding is in which scope/environment
  • what kind of binding it is
  • which binding is closed over by inner functions

Approach

Pass

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/scope/src/pass.rs

Currently, we're using separate pass to do scope analysis, than AST building and emitting, so we have 3 passes:

  1. parse script, check early errors, and build AST
  2. traverse the AST and perform scope analysis
  3. traverse the AST and emit bytecode

eventually 2 will be merged into 1, by using grammar extension, so the whole API used in scope analysis is implemented based on callback for certain step in parsing, and currently scope analysis pass is using visitor to call them.

Data collection

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/scope/src/context.rs

Each AST node that will hold bindings (global, block, function, etc) has corresponding *Context struct, that collects information necessary for scope analysis.

Fields in the *Context mostly corresponds to local variable in spec for example, GlobalDeclarationInstantiation has functions_to_initialize, declared_function_names, declared_var_names etc, that corresponds to functionsToInitialize, declaredFunctionNames, declaredVarNames etc in the spec.

https://github.com/mozilla-spidermonkey/jsparagus/blob/b1c798cdf250b3d1a41217c78d2e540843f7afa5/crates/scope/src/context.rs#L283-L307 https://tc39.es/ecma262/#sec-globaldeclarationinstantiation

While visiting sub-nodes inside the node, information is accumulated into those fields, and after that, it performs corresponding spec steps and convert them into ScopeData, that is used by emitter.

*Context is stored in ContextStack, stored in ScopeContext, and it's the interface between visitor (eventually grammar extension) and each context.

Generated data

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/scope/src/data.rs

Each generated data is stored in ScopeDataMap, associated with certain AST node, using AssociatedData. (we use AssociatedData to store data associated to AST node because AST node itself doesn't allow storing extra data)

Bindings are stored in *ScopeData, that corresponds to *Scope::Data in SpiderMonkey. it holds the following information:

  • the list of bindings
  • what kind of binding it is
  • if the binding is closed over by inner functions

https://searchfox.org/mozilla-central/source/js/src/vm/Scope.h

Closed over bindings

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/scope/src/free_name_tracker.rs

If a binding is closed over by inner function, it should live in heap-allocated EnvironmentObject. If not, it can live in call stack (FrameSlot).

FreeNameTracker is used to calculate whether a binding is closed over or not.

FreeNameTracker is allocated for each context, and it collects the following information:

  • the list of names defined in this scope
  • the list of names used by this and inner scope
  • the list of names closed over by inner scope

Each of them are accumulated while visiting nodes inside the context, and propagated to outer scope when leaving the context.

Assumption

Assuming all early errors are handled by *EarlyErrorsContext in early_errors.rs before performing scope analysis, scope analysis doesn't check any error.

SpiderMonkey's scope vs Spec's environment

Scope data is constructed based on SpiderMonkey's scope data, that is slightly different than Spec's environment:

  • In spec, bindings are stored in Environment Record, that is a simple list of bindings, and they're referred by execution context's LexicalEnvironment and VariableEnvironment components, so, single environment record has single kind of bindings
  • In SpiderMonkey, bindings are stored in Scope, that is the list of bindings, and each binding has its kind, so single scope has multiple kinds of bindings

So, creating a new environment in term of spec doesn't necessarily create a new Scope in term of SpiderMonkey.

Consumer

Determine what opcode to use when accessing bindings

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/emitter/src/emitter_scope.rs

While emitting bytecode, scope data is pushed onto EmitterScopeStack, and when emitting bytecode to access bindings, helper structs (reference_op_emitter.rs etc) queries the data.

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/emitter/src/reference_op_emitter.rs

Annotate bytecode with scope range

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/emitter/src/scope_notes.rs

SpiderMonkey VM

https://github.com/mozilla-spidermonkey/jsparagus/blob/master/crates/emitter/src/gcthings.rs

Scope is allocated as GC thing in SpiderMonkey, and data collected while scope analysis is passed there, via EmitResult