|
| 1 | +# Inlining and unrolling: the dispatch-count lever |
| 2 | + |
| 3 | +The assessment that function inlining, with loop unrolling as its sibling, is one of the largest |
| 4 | +levers for overall single-core performance. Grounded in the profiling numbers (PROFILING.md) and |
| 5 | +queued as design, not committed work. |
| 6 | + |
| 7 | +## Why it is the big lever in this VM |
| 8 | + |
| 9 | +A hello-world http request executes about 3,745 dispatched instructions. Every user-function call |
| 10 | +pays pure ceremony before any work happens: the `CallFn` function-table lookup and frame push, one |
| 11 | +register move per argument into R1.., the callee prologue moving parameters back out, the `Return` |
| 12 | +frame pop, and the destination move. That is roughly five to ten instructions per call, and the http |
| 13 | +parse path is a mesh of tiny helpers, with dozens of calls per request. Call ceremony alone is |
| 14 | +plausibly ten to twenty percent of all dispatched instructions, before counting what inlining |
| 15 | +unlocks downstream. |
| 16 | + |
| 17 | +Two properties make inlining better here than in native compilation: |
| 18 | + |
| 19 | +- **Interpreters are unusually friendly to it.** Native inlining pays with instruction-cache |
| 20 | + pressure. Here a bigger decoded program changes nothing per dispatch, so the classic size penalty |
| 21 | + mostly does not exist. The genuine constraint is register pressure instead: an inlined body shares |
| 22 | + the caller's register file, so deep inlining raises live-variable counts and triggers spills. The |
| 23 | + heuristics watch that, not code size. |
| 24 | +- **It subsumes reference counting at inlined sites.** Inlining a callee dissolves the whole |
| 25 | + ownership contract at that site: no argument retains, no parameter drops, no marshaling. The |
| 26 | + parameters become local aliases that the existing intra-block move elision already handles |
| 27 | + (RC_ELISION.md). The borrow analysis stays valuable exactly where inlining cannot go, large |
| 28 | + bodies and recursion. |
| 29 | + |
| 30 | +## The synergy chain |
| 31 | + |
| 32 | +Inlining is the enabler for the rest of the optimization stack, not a standalone win: |
| 33 | + |
| 34 | +inline, producing long straight-line runs, then constant-fold across former call boundaries, then |
| 35 | +fuse the runs into single closures (FUSED.md over REWRITE_ENGINE.md), collapsing dispatch count. |
| 36 | +Unrolling is the same story for back-edges: a hot per-character loop spends about a fifth of its |
| 37 | +instructions on the condition and jump, and unrolling both removes that and hands fusion longer |
| 38 | +runs. The chain multiplies, which is why the assessment is that this family, not any single pass, |
| 39 | +is the path to the next large single-core factor. |
| 40 | + |
| 41 | +## Why this compiler is well placed |
| 42 | + |
| 43 | +Whole-program compilation, a static call graph (no first-class functions), and monomorphized typed |
| 44 | +IR make inlining body-splicing with variable renaming, no polymorphism or link-time unknowns. It is |
| 45 | +also backend-neutral: an AOT C transpile (ASSESSMENTS.md) benefits identically from the same IR |
| 46 | +transform, so nothing here bets on the interpreter. |
| 47 | + |
| 48 | +## Design questions for the plan proper |
| 49 | + |
| 50 | +- Heuristics: an instruction-count threshold for inline candidates, leaf-first, no or bounded |
| 51 | + recursion, and a register-pressure guard tied to the allocator's capacity model. |
| 52 | +- Where in the pipeline: on typed IR before register planning, so the allocator and the elision |
| 53 | + analyses see the flattened bodies (and the elision report then measures the post-inline world). |
| 54 | +- Interaction with `--explain` and spans: inlined instructions keep their source spans, so the |
| 55 | + aligned view stays honest across splices. |
| 56 | +- Unrolling scope: small constant trip counts and hot parse loops first, driven by the profiling |
| 57 | + counters rather than guessed. |
0 commit comments