commit 5124ada6d86bf9fec1298bc85bc6df6ad191323d
parent e8f7a220cb0fa548b419b4264542ba463949616f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 17:19:22 -0700
doc(perf): refresh PERF.md standings after o0-codesize Lever 2 + follow-ons
Re-measured on arm64-macOS (best-of-7, build/release/kit): sqlite -c object
1.99 -> 1.79 MB (emitting ~6.8% fewer .text insns writes a smaller object);
-c compile instructions ~flat at 1.91 B (the emit + smaller-objwrite savings
are offset by Lever 2's per-op api_temp_dead deadness checks, which also lift
-fsyntax-only 1.63 -> 1.64 B); tcc bar (0.66 B) and -E (0.93 B) unchanged.
o0-codesize .text now 1.33x -> 1.084x tcc (was 1.16x). Update standings table,
object-size line, and the phase-decomposition prose.
Diffstat:
1 file changed, 23 insertions(+), 16 deletions(-)
diff --git a/doc/plan/PERF.md b/doc/plan/PERF.md
@@ -35,7 +35,7 @@ recent low-load reading.
| compiler | instructions | cycles † | wall † | object |
|---|--:|--:|--:|--:|
| **tcc 0.9.28** | 0.66 B | 0.22 B | 0.06 s | 2.11 MB |
-| **kit (current)** | 1.91 B | — | — | **1.99 MB** |
+| **kit (current)** | 1.91 B | — | — | **1.79 MB** |
| clang 22 | 8.73 B | 2.57 B | 0.81 s | 1.50 MB |
† low-load reading; re-confirm on a quiet machine (`instructions` is
@@ -43,22 +43,27 @@ load-independent and the figure to trust; cycles/wall are pending a quiet-machin
re-read).
kit beats clang on compile speed and is the fastest *general* backend here, but
-**tcc is the bar**: ~2.88× instructions ahead. Closing that is the whole game.
+**tcc is the bar**: ~2.87× instructions ahead. Closing that is the whole game.
The gap was 3.23× when this table was last read; two campaigns since closed it to
-2.88×: the frontend instruction-slimming work on main (Sym-keyed binding cache,
+2.87×: the frontend instruction-slimming work on main (Sym-keyed binding cache,
per-id type decode, single-pass symtab/strtab — `-fsyntax-only` 1.84 → 1.63 B)
-and the **`o0-codesize` track** (next paragraph). kit's **object is now 1.99 MB
-— below tcc's 2.11 MB** (was 2.29 MB), because emitting less machine code also
-writes a smaller object.
+and the **`o0-codesize` track** (next paragraph). kit's **object is now 1.79 MB
+— well below tcc's 2.11 MB** (was 1.99 MB, 2.29 MB before), because emitting less
+machine code also writes a smaller object.
-The `o0-codesize` track cut emitted `-O0` `.text` from **1.33× → 1.16× tcc**
-(455,546 → 398,433 insns) via five landed levers — far-slot positive-scaled
+The `o0-codesize` track cut emitted `-O0` `.text` from **1.33× → 1.084× tcc**
+(455,546 → 371,537 insns): five early levers (far-slot positive-scaled
addressing, a 2-insn epilogue, call-result-in-result-register, copy-into-dest
-materialization, and narrow-load zero-extend elision. Three are arch-neutral
-(also −3.18 % on x86-64). Smaller output is also fewer instructions to emit and
-write, so it shrinks the emit+objwrite slice here too. Full standings, diagnosis,
-and the remaining levers (args-into-arg-regs, spill reduction): **`doc/plan/
-PERF-O0-CODESIZE.md`**.
+materialization, narrow-load zero-extend elision), then **Lever 2** (spill
+reduction — eager dead-operand drop + a branch materialize-before-flush reorder,
+−24,581) and three single-pass follow-ons (switch-selector residency, signed
+load-with-extend, byte/half far slots; −2,315). Several are arch-neutral (the
+shared-NDT levers also help x86-64/rv64). Smaller output is fewer instructions to
+emit and write, so it shrinks the emit+objwrite slice — though Lever 2's
+producer-side `api_temp_dead` checks add a small offsetting CG-layer cost, so the
+`-c` compile-instruction total is ~flat (the win shows in the 10 %-smaller
+object). Full standings, diagnosis, and the remaining levers (args-into-arg-regs
+is now the dominant one): **`doc/plan/PERF-O0-CODESIZE.md`**.
### Reproducing the detailed measurements
@@ -111,9 +116,11 @@ m build/release/kit cc -c -o /tmp/k.o sqlite3.c --sysroot "$SDK" # + codegen
`-fsyntax-only` still drives the full CG value-stack and type lowering (routed to
the no-op check backend), so `(-c) − (-fsyntax-only)` isolates **native emit +
-object write** only. Current binary: `-E` 0.93 B, `-fsyntax-only` 1.63 B, `-c`
-1.91 B → that delta is ~0.28 B / ~15 % — i.e. **codegen+emit is a small slice;
-the frontend is the rest.** Within that frontend, `-E` alone (0.93 B) is just
+object write** only. Current binary: `-E` 0.93 B, `-fsyntax-only` 1.64 B, `-c`
+1.91 B → that delta is ~0.27 B / ~14 % — i.e. **codegen+emit is a small slice;
+the frontend is the rest.** (The `o0-codesize` spill-reduction levers nudged
+`-fsyntax-only` up ~0.01 B via their per-op `api_temp_dead` checks while shrinking
+the emit slice, so the `-c` total held ~flat as the object shrank 10 %.) Within that frontend, `-E` alone (0.93 B) is just
under half, and the scanner (`lex_next`) is the bulk of it — see *Where the time
goes*. (Landings that shrank these: the scanner rewrite cut a flat ~84 M off
every phase — `-E` 1.06 → 0.93 B — the -O0 value-stack residency work took