Commit Graph
2 Commits
Author SHA1 Message Date
Chris Tate cd2b1e2574 Keep callback loop versions from nesting
A callback loop version emits its loop twice. A loop inside either copy
could be versioned again, so a nest of loops that each call a callback
built just before them (`const g = adder(i)`) doubled at every level: a
three-deep nest was emitted seven times per function.

A loop inside a callback version, or the versioned loop itself emitted
again for each copy, is no longer versioned, so a loop nest is emitted at
most twice. The outer loop keeps its version; inner loops keep their
calls as they are.
2026-10-22 09:55:01 -05:00
Chris Tate 906f9911a1 Keep a callback's captured scalars in registers in loops that call it
A call through a binding that the constant-callback census resolves
keeps a fallback through the closure's code pointer. In a loop that
fallback is an unknown call that takes the closure, so the closure's
code pointer, its captures and a captured `let` were reloaded, and the
`let` stored back, on every iteration of `let s = 0; a.forEach((v) => {
s = (s + v) | 0; })`.

A for, while or do-while loop that calls such a binding now tests once,
before the loop, that the binding's closure has the known code pointer.
The copy where the test holds calls the function directly with no
fallback, and the other copy is the ordinary loop. A closure's code
pointer is set only when the closure is built and the census resolves
only bindings the function never rebinds, so one test covers the loop.
Only bindings declared before the loop qualify, and loops are versioned
up to a size bound.

Number and boolean box payloads get an alias category of their own, so
a store to a captured `let` no longer aliases element loads and other
memory in the loop. With the closure built in the caller, the test folds
after inlining and the captured `let` stays in a register.
2026-10-22 07:45:48 -05:00