Skip to content

Commit a5be148

Browse files
committed
Record the inlining and unrolling assessment as the dispatch-count lever
INLINING.md grounds the assessment in the profiling numbers: call ceremony is plausibly ten to twenty percent of dispatched instructions on the http path, an interpreter pays no instruction-cache penalty for inlining (register pressure is the real constraint), inlining subsumes the reference-counting contract at inlined sites (the borrow analysis stays valuable where inlining cannot go), and the synergy chain of inline then fold then fuse over the rewrite engine is the path to the next large single-core factor. Whole-program compilation, the static call graph, and monomorphized IR make it body-splicing. Design questions queued: thresholds, register-pressure guard, pipeline position before register planning, span preservation for explain, and counter-driven unrolling scope.
1 parent e338bee commit a5be148

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

plans/INDEX.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ Mostly current and self-describing. Grouped by theme.
2323
- **Modules and stdlib**: MODULES (compile-time AST imports, BUILT v1), HTTP (std.http, BUILT),
2424
WEB_SERVER_TARGET, ROADMAP_GENERICS_STDLIB, TEST_HARNESS, ASSESSMENTS (the futures ledger).
2525
- **Performance**: PROFILING (HTTP server profiling method and findings, single-core throughput and
26-
memory, the harness in `tools/profiling/`), RC_ELISION (the ownership-oracle model, move elision
27-
BUILT, borrow elision PLANNED with the edge-case ledger).
26+
memory, the harness in `tools/profiling/`), RC_ELISION (the ownership-oracle model, move and
27+
borrow elision BUILT with the edge-case ledger), INLINING (the assessment that inlining plus
28+
unrolling plus fusion is the dispatch-count lever, VISION).
2829

2930
## Runtime foundation (VM side), with build status
3031

plans/INLINING.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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

Comments
 (0)