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
matchare 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.