Skip to main content

Module capture

Module capture 

Source
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_SCOPE clones 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 hoisted function bind into the block; var does 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 an EnvData per pass.
expr_captures
True if evaluating e can create something that outlives it holding the current scope.
stmt_captures
True if evaluating s can create something that outlives it holding the current scope.