Module frames

Module frames 

Source
Expand description

How many bindings a function body makes, so that a call can reserve room for them.

A let used to allocate a scope of its own — a vector for the one binding, an Arc around it and an Arc around a clone of the enclosing environment, three allocations for one name. At about 126 ns each that is most of what a function call costs, paid again per binding, and a body of two dozen lets spent more on scopes than on the work (76 is where the number came from).

The count here is what removes them. A lambda’s Core::locals is the number of bindings its body can make, so crate::core::Env::call_frame sizes one frame for the parameters and all of them together and a let writes into a slot that already exists.

Two properties make that sound, and both are why this counts the way it does:

  • Every binding that can be live at once gets its own slot. The count sums what runs in sequence and takes the maximum over what cannot: two arms of a match are exclusive, so they share a reservation, and a slot is still never written twice within one call. That is what makes it safe for a closure to hold the frame — nothing it captured can change underneath it.
  • A nested lambda contributes nothing. Its body runs in a frame of its own, made when it is called, so counting it here would reserve slots nothing writes.

Miscounting is safe in one direction and merely slow in the other: too few slots and the evaluator falls back to chaining a scope, exactly as before this existed. That is also what happens to any program built by something that never runs this pass — a synthesised test body, a splitter’s generated module — which is why the fallback is kept rather than asserted away.

Functions§

locals_of
How many bindings this expression makes before control leaves the frame it runs in.
reserve
The same for one expression, whether or not it is a lambda.
reserve_program
Count and record every lambda’s local bindings, across a whole program.