Expand description
Does a piece of a program hold on to the scope it runs in?
Two lowerings in compiler.rs exist only to serve code that captures an
environment, and both cost a heap allocation every time control passes them:
- a
for (let i = …)head is re-bound per iteration (ForBodyEvaluation’s CreatePerIterationEnvironment), so a closure made in one pass keeps that pass’s value —COPY_SCOPEclones the whole scope on every iteration; - a
{ … }block opens a scope for its lexical declarations.
When nothing in the subtree can make a closure, the per-iteration copy is
unobservable: no one can ever hold a reference to the iteration’s bindings,
so one binding mutated in place gives the same answers. A profile of
for (let i = 0; i < 5_000_000; i++) s += i % 7; spent 17% of its samples in
copy_scope and the EnvData allocate/free traffic under it, for copies
that nothing could observe.
This module answers the question conservatively: it says “captures” for
anything that makes a function, a class (its methods are functions), or a
direct eval (which can both make closures and declare into the caller’s
scope). Every match here is exhaustive — a new AST node has to be classified
deliberately rather than defaulting into the fast path.
Functions§
- block_
needs_ scope - Does this statement list declare anything that needs a block scope of its
own?
let/const/class/ a hoistedfunctionbind into the block;vardoes not (it lands in the enclosing function’s base env). A block that binds nothing needs no scope at all, so{ …; }inside a hot loop stops allocating and freeing anEnvDataper pass. - expr_
captures - True if evaluating
ecan create something that outlives it holding the current scope. - stmt_
captures - True if evaluating
scan create something that outlives it holding the current scope.