commit 8199c01966dbfe93f649fe2d817329d1aa426b42
parent 508a6344ac3365fcf0fe738853fa8ab690f54776
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 14 Jun 2026 05:58:09 -0700
doc(plan): FRONTEND-SHAPE §D — commit to Path B (lean hot-path representation), no layer fusion
The §4.3 tiers measured ~1.2% combined; Path A (precompute/cache) spiked marginal
on both levers (per-token loc, classify-once types). Conclusion: the cost is
operation-count + dispatch structure, not per-item work. Decision recorded: halve
instructions only via a lean wholesale hot-path representation (lean token,
int-bitmask type on a lean value slot, bypass the general CG type/value-stack
machinery), keeping lex|pp|parse|CG|emit as distinct streaming modules — a
representation pivot, not control-flow fusion. Next step is a minimal end-to-end
lean prototype to validate the compounding hypothesis before building the lane.
Diffstat:
1 file changed, 95 insertions(+), 0 deletions(-)
diff --git a/doc/plan/FRONTEND-SHAPE.md b/doc/plan/FRONTEND-SHAPE.md
@@ -14,6 +14,101 @@ whose headline finding reframes the work: **most of §4.3 is already landed.** T
one genuine-but-bounded structural change. Each tier below is independently
landable and individually gated.
+> **Superseded for the instruction goal (2026-06-14).** The §4.3 tiers below were
+> built and measured (three parallel worktree branches, all byte-identical) — they
+> are real but **marginal**: Tier 1 (1A+1B) −0.70% on `-c`, Tier 2 −0.49%, Tier 3
+> instruction-neutral; ≈ **−1.2% combined**. They remain worthwhile *cleanup*, but
+> they do not move the real number. The committed direction for the actual target —
+> **halving `-c` instructions (within 1.5× of tcc)** — is **§D** immediately below.
+> Treat §0 and the Tier plan as the landed/landable cleanup record beneath it.
+
+---
+
+## D. Direction (2026-06-14): halving instructions — Path B, no layer fusion
+
+The goal is no longer "reach §4.3's shape" (mostly landed, and marginal). It is
+**halve `-c` retired instructions** — 1.91 B → ~0.95 B, i.e. within 1.5× of tcc
+(0.665 B). This section records what governs that, what was ruled out, and the
+committed bet.
+
+### D.1 The arithmetic that governs everything
+
+| phase | kit `-c` | share | vs tcc |
+|---|--:|--:|--:|
+| **lex + pp** | **~0.93 B** | **49%** | ~2.8× |
+| parse + sema + types + CG-drive | ~0.71 B | 37% | ~3.5× |
+| native emit + objwrite | ~0.27 B | 14% | ~7× |
+| **total** | **1.91 B** | | **2.87×** |
+
+**kit's preprocessor alone (0.93 B) is 1.4× tcc's entire compile (0.665 B).** So
+the target is unreachable by improving parse/types/codegen alone — even zeroing
+emit leaves 1.64 B (2.5×). Halving the total means **roughly halving every phase**;
+the cost is **uniform ~2.9× with no single dominant hot function** (the doc's
+long-standing observation). That uniformity is the whole problem: there is nothing
+to "crush," only a pervasive per-token / per-operation tax to remove everywhere.
+
+### D.2 What was ruled out (measured, not assumed)
+
+- **Micro-opts / seam-preserving copy elimination — exhausted.** The three §4.3
+ tiers move `-c` ~1.2% combined. This *is* the data point: the incremental surface
+ is spent.
+- **Path A (precompute / cache) — DEAD, spiked marginal.** Two validation spikes:
+ *lever 1* — kill per-token `SrcLoc`, recover location lazily — **marginal**;
+ *lever 3* — classify-once type facts (kill the `unalias`/`pred_bits`/`class`/
+ `size` re-derivation), carried inline — **marginal**. The spikes prove the cost
+ is **not the per-item work** (which caching removes) but the **operation count
+ and the dispatch structure itself**. Making each operation cheaper is futile when
+ the number of operations is the tax.
+- **Layer fusion — ruled out by decision** (see D.3). Collapsing lex→pp→parse→emit
+ into one mega-pass would buy the uniform inner-loop win, but at the cost of the
+ architecture's legibility and reuse (the shared cpp backing `cc -E`/`cpp`, the
+ separable parser, the CgTarget seam + seven backends). Not pursued.
+
+### D.3 The committed bet: Path B = a lean hot-path *representation*, not control fusion
+
+**Decision: Path B only, and explicitly without layer fusion.** Path B is a
+**representation pivot, not a control-flow pivot.**
+
+- **Lean wholesale representation.** The C `-O0` hot path stops paying for
+ generality: a **lean token** (no per-token heft/loc-stamp), an **int-bitmask
+ type** carried directly on a **lean value slot**, bypassing the general CG type
+ universe and the public `ApiSValue` value-stack API. The wager is that the lean
+ representation's savings **compound when adopted wholesale** — the inner loop gets
+ uniformly cheaper across *all* phases at once — in a way the incremental spikes
+ (a lean *cache layered on top of* the heavy objects) structurally could not show.
+- **No layer fusion.** The pipeline stays factored: **lexer | preprocessor | parser
+ | CG | emit remain distinct streaming modules**, boundaries intact. The shared
+ cpp still backs `cc -E`/`cpp`; the parser stays separable; the `CgTarget` seam and
+ the seven backends stay. We do **not** merge the stages' control flow. **The win
+ comes from the data that flows, not from merging the loops.**
+
+In one line: **replace the heavy general data objects with lean ones on the hot
+path; leave the modules that pass them around exactly as separated as they are.**
+
+### D.4 The load-bearing risk — validate before building the full lane
+
+The spikes were marginal; Path B bets that **wholesale adoption compounds where
+incremental did not.** That bet is **unproven**, and Path B is expensive (a parallel
+lean lane plus a fast/slow split to gate). So the next step is **not** to build the
+lane — it is to **build a minimal end-to-end lean prototype** (a reduced-grammar
+path carrying lean token + int-type + lean value slot from bytes to emitted bytes)
+and measure whether the compounding actually appears on a representative slice. If
+it does not, Path B is also marginal and the honest conclusion is that **~2× is the
+floor for a modular C compiler** and we stop there. This validation gates the spend.
+
+### D.5 Open design questions for the lean lane (after D.4 validates)
+
+- **Inter-layer handoff without fusion:** how separate modules pass the lean
+ representation cheaply (a shared cursor over a lean token buffer? a leaner pull?)
+ — killing the per-token call/copy tax *without* collapsing the loops.
+- **Int-bitmask type on the hot path:** the encoding, and how it **falls back** to
+ the full `Type` / CG-type universe for what a bitmask can't carry (aggregates,
+ bitfields, VLAs, `_Atomic`, wide/`__int128`).
+- **Lean value slot:** what the parser drives that still resolves to the **kept**
+ `CgTarget` seam for emission (the seam is cheap — indirection was never the cost).
+- **Fast/slow split + gate:** which constructs take the lean lane vs fall back, and
+ how the gate proves the two lanes emit **byte-identical** output.
+
---
## 0. Verified current state (what §4.3 already bought)