kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 0b33acede42ae594f52d5ee544446332bc8aa772
parent bcdbeba368da56abbac093222d97d19298491466
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 09:47:15 -0700

doc(perf): PERF-IDEAL.md — touch-once/classify-once rewrite roadmap + WAVE 1-2 landings

Diffstat:
Adoc/plan/PERF-IDEAL.md | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 416 insertions(+), 0 deletions(-)

diff --git a/doc/plan/PERF-IDEAL.md b/doc/plan/PERF-IDEAL.md @@ -0,0 +1,416 @@ +# Performance by ideal flow — rewrite to the conservation law, don't patch around it + +Companion to `PERF.md` (standings + methodology), `PERF-TCC-GAP.md` (structural +diagnosis), and `PERF-TCC-SLIM.md` (the patch-oriented slimming campaign). This +doc reframes the whole effort around a single discipline: + +> **For each entity the compiler handles, name the work that should happen +> _exactly once_ per entity — the conservation law / ideal flow — then rewrite the +> subsystem to obey it, instead of memoizing or short-cutting around a structure +> that re-does the work N times.** + +This is tcc's secret stated as invariants: *each source byte is touched ~once, +each token is an `int`, each operator is a `vtop` adjustment plus ≤1 four-byte +store, and there is no second copy of the program anywhere.* kit is ~3.1× tcc in +**retired instructions** (the metric — load-independent, Linux callgrind `Ir`); +kit's IPC already exceeds tcc's, so the gap is **instructions, not cache**. Each +violated conservation law below _is_ a slice of that 3×. + +## One refinement up front: the objective is minimum *instructions*, not minimum *passes* + +"Touch each byte exactly once" is a proxy for "do the minimum work," and usually +the two agree — but not always. The splice pre-scan (§A) is the case that +separates them: the literal one-pass ideal (fuse `\<newline>` handling into the +tokenizer, tcc-style) would reintroduce a per-byte splice test into the hot +tokenizer loops that the raw-cursor rewrite deliberately made splice-free — +**taxing every token to save a pass.** A separate SIMD-cheap pre-sweep (`memchr`, +~1 instruction per 16–32 bytes) does the same job for fewer total instructions. +So the law we optimize is **minimum retired instructions per entity**; "touch +once" is how we reason about it, not a goal in itself. Where the two diverge, +instructions win — and we say so explicitly. + +## The fresh profile (the evidence base) + +`kit cc -c sqlite3.c` (the real benchmark), callgrind at HEAD `de8c3b62`, **2.04 B +`Ir` total**. Self-time buckets (trust self/exclusive — inclusive is garbage here +because lex↔pp↔parse form one mutual-recursion cycle callgrind double-counts): + +| area | self % | conservation law it violates | +|---|--:|---| +| **types / CG-drive** | **20.7%** | each type decoded/classified once; all ancillary info cached on the id | +| lexer scan (`lex_next`+`finish_ident`) | 16.3% | each byte classified once into one token | +| pp pump + relay | 11.6% | a token lives in one place, never copied frame-to-frame | +| **splice pre-scan** (`lex_open_mem`) | 10.6% | bytes not destined for a token are not walked by a scalar pass | +| intern (`pool_intern_slice`) | 4.2% | each lexeme hashed once, then it is an int forever | +| native emit (NDT path) | 3.9% | each machine byte written once into the final image | +| other (parse/sema/objwrite long tail) | 26.3% | — | + +Artifacts: `build/linux-prof/cg.annot.txt` (flat self-`Ir`), +`build/linux-prof/cg.tree.txt` (caller/callee + call counts). Recipe + gotchas: +`PERF.md` §6. Every rewrite gates byte-identical +(`scripts/perf_identity_gate.sh`) unless it deliberately changes emitted bytes +(then: run-correctness + determinism). + +--- + +## §A — Source bytes: *classify once; never scalar-walk a byte that isn't becoming a token* + +**Ideal flow.** A source byte is loaded, classified via `cclass[256]`, and +consumed into exactly one token body — once. Bytes that exist only to be skipped +(whitespace, the rare line-continuation) cost a table test, not a separate scalar +pass. + +**Violation (10.6%, `lex_open_mem`).** Every opened buffer is walked **twice**. +`lex_fold_splices` (`lang/cpp/lex/lex.c:149`, inlined into `lex_open_mem:281`) +runs a scalar, 2×-unrolled byte loop (`lex.c:156-157`, +`for(i;i+1<len;++i) if(src[i]=='\\' && src[i+1]=='\n') ++nspl;`) over the *entire* +buffer to count `\<newline>` splices — which are almost always zero — and then +`lex_next` walks every byte again to tokenize. Confirmed by disassembly of the +profiled binary: the count loop is `ldurb;cmp #0x5c;ldrb;cmp #0xa;cset;...`, +~4–5 instr/byte, **no NEON**. Across the ~3,140 file opens of the sqlite +amalgamation's transitive header closure (`file_register → lex_open_mem`, +`cg.tree.txt:56`) this is tens of millions of bytes scalar-walked for nothing. +(`PERF.md:278-279`'s claim that the splice-free path "pays nothing" is true of +`lex_next` during tokenization but **false for the open-time pre-scan** — the doc +overlooked this counting pass.) + +**The rewrite.** +1. **`memchr`-driven splice sweep** (the instruction-ideal — see the refinement + above). Replace the scalar count loop with `memchr(p,'\\',...)` to skip + non-backslash spans at glibc-NEON speed; backslash is rare in C, so the sweep + consumes the buffer at ~1 instruction per 16–32 bytes. Byte-identical (same + `nspl`, same fold table; the fold path is entered only when `nspl>0`). + **215M → ~20–40M `Ir` ≈ −8.5…9.5% of the whole compile.** This is the single + biggest lever in the profile and near-zero risk. The full tcc-style fuse is + explicitly **rejected**: it taxes the splice-free hot loops (`lex_next`, + `skip_ws_fast`, `scan_ident_run`, `scan_pp_number`, header-name scan) with a + per-byte test and would force spelling reconstruction for spliced tokens. +2. **Delete the per-lexer `Pool`.** `lex_open_mem` mints `l->pool = c_pool_new(c)` + (`lex.c:288`) — a 64 B `Pool` + a `kit_arena_new` — on each of ~6,000 opens, + but the lexer touches `l->pool` only as `l->pool->c` (the Compiler) at three + sites, and `l->c` already holds it. Drop the field; replace `l->pool->c` → + `l->c`. **~20–30M `Ir`**, byte-identical, clean cutover. (Identifier interning + is unaffected — it already targets the shared `c->global` symtab via + `kit_sym_intern`, _not_ the per-lexer pool; the pool was dead weight.) +3. **Hoist the punctuator-spelling cache to per-compile.** `struct Lexer` is + 1216 B, ~1184 of it `Sym punct_sym[]`, `memset`-zeroed per open and re-interned + cold each time — so the same ~60 punctuators are re-interned across ~6,000 + lexers (the "classify once" law: a punctuator spelling should be interned + **once per compilation**, not once per lexer). Move `punct_sym[]` to a + per-compile `LexShared`; `Lexer` shrinks to ~32 B, its per-open `memset` + collapses, and punctuators are interned ~60 times total. **~5–15M `Ir`** + + relief on the 2% `memset`. More invasive (threads a shared ctx into + `lex_open_mem`); preserve the digraph-bypass and the "entry 0 = not-yet-seen" + sentinel. Do third. + +**Floor.** The irreducible work is one classified pass over each distinct +buffer's bytes (the tokenize pass) + a near-free SIMD splice sweep. The rewrite +reaches it. **Area est.: ~10–11% of the whole compile**, all byte-identical +(gate + the line-splice battery: mid-token / mid-string / leading / trailing / +consecutive continuations + a diagnostic line across a splice). + +--- + +## §B — Identifiers: *each lexeme hashed once, then it is an integer forever* + +**Ideal flow.** The first time a spelling is scanned it is hashed → `Sym` (a small +int); every later occurrence is *already* that int. Macro/keyword/symbol +resolution is a load off the interned token, never a re-hash. (tcc: the token +leaves the scanner as an int.) + +**Violation (4.2%, `pool_intern_slice`).** Every occurrence of every +identifier/number re-runs FNV-1a + an open-addressed probe — ~628K calls × +~135 `Ir` — on a token stream with **22.4× reuse** (measured on real sqlite3.c: +440,942 id+num occurrences, 19,700 distinct). The probe's `sym_eq` +(`src/core/pool.h`) loads a hash-scattered `ents[sym]` line per occupied slot +tested. *(Note: the interner is already per-compile-shared — `c->global`, +`api/core.c:222`. The per-lexer `Pool` from `c_pool_new` is the **frontend** pool +(arena + type/abi cache), a different type, not a string interner — so there is no +per-buffer intern fragmentation to recover; the cache below belongs on +`c->global`.)* + +**The rewrite — a direct-mapped, byte-verified intern cache inside +`pool_intern_slice`.** An array of `{u32 keyhash; Sym sym}`, N a power of two, +keyed on a *cheap* fold of `len` + the first ≤4 bytes (**not** full FNV). On +lookup: index `= fold & (N-1)`; if the slot is occupied, **byte-verify** the +candidate via `pool_slice(sym)` (the same compare the probe's `sym_eq` does) — on +match return, having skipped FNV *and* the probe. On miss, fall through to the +existing FNV+probe and populate the slot. Placing it inside `pool_intern_slice` +(not the lexer) serves **frontend and codegen** interns alike — e.g. `aa_plan_call` +re-interns ABI helper names (`__floatditf`, …) 19,455× — which a lexer-local cache +would miss. Pure accelerator: every hit byte-verifies, so it can never return a +wrong `Sym` (digraph-safe by construction — `<:` and `[` differ in bytes and fail +verify); store only `{keyhash, sym}`, never a borrowed `in.s` pointer (it aims at +transient lexer buffers). + +Measured hit rates on real sqlite3.c: **256-slot ≤4-byte-fold = 78.7%**, 1024-slot += 85.3%. This is explicitly **not** PERF-TCC-SLIM's A1 single "last-spelling" slot +— that measured **3.4%** (real C alternates: `sqlite3 . pVfs -> xOpen (`), so a +1-slot cache never hits. It is also distinct from the documented dead ends (the +*entry-side* inline-prefix cache and the word-at-a-time hash, which don't remove +the per-occurrence hash+probe): this removes both the hash and the probe for ~79% +of calls. + +**Floor.** Steady state = one cache load + a ~6-byte verify per occurrence (~20–25 +`Ir`); one FNV+probe per distinct lexeme + collisions. At 78.7% × ~564K × ~110 `Ir` +saved minus the cache's own overhead: **−30…45M (1.5–2.2%)** — the single biggest +lever in this area. Byte-identical (gate + a digraph spelling check: +`<: :> <% %> %: %:%:` must stay distinct). + +**Deferred — _not_ an `Ir` lever:** the self-sufficient `{hash, sym}` probe slot +(PERF-TCC-SLIM A2 / `PERF-TCC-GAP` Track 3). Measured ~0 on instructions — it +trades an `ents[sym].hash` load for a `slot.hash` load (same instruction, better +locality), so it is a **D1-miss / wall-clock** play the `Ir` metric cannot see, +and the cache above already absorbs ~79% of probes. Revisit only under a dedicated +cache-stall investigation. *(This corrects PERF-TCC-SLIM's framing of A2 as an +instruction lever.)* + +--- + +## §C — Tokens: *produce only tokens the parser consumes — never build one that's always dropped* + +**Ideal flow.** The pump emits, into a fixed out-pointer slot, only tokens the +parser will read; a token that is always discarded downstream is never built or +relayed. The "lives in one place, never copied" half of the ideal is **already +met** — and the work to do here is *removing a token class*, not restructuring the +relay. + +**The copy half is already at the ideal (measured — relay rewrite is a confirmed +dead end).** The lex→pp→parse relay already uses out-pointer (`_into`) forms +(`pp.c:72`, `pp_expand.c:935`) precisely to kill inter-frame sret copies. The +~206M `Ir` in `src_next_raw_into` / `pp_next_raw_into` / `fetch_tok` / `advance` is +**per-token macro / directive / include-guard / `#line` logic, not copy +bandwidth** — it survives any relay restructuring (`PERF.md`'s "fused pull pipeline += ~5% dead end", reconfirmed). Likewise **`Tok` 24→16 B is a dead end**: `loc.line` +is read/written on the *hot* PP path (`#line` delta `pp.c:123`, `__LINE__` +`pp_expand.c:1021`), so an inline-packed position forces a per-token decode that +costs more than the copy it saves — the `LocId` side-table regression, +reconfirmed. + +**The real violation — 51% of produced tokens are discarded.** `lex_next` is +called **1,424,699×** but only **684,790** tokens reach the parser (`pp_next`): +~740K (51%) are dropped inside PP, overwhelmingly `TOK_NEWLINE` — each built by +`lex_next` (24 B), sret-relayed through `src_next_raw_into` + `pp_next_raw_into`, +then `continue`'d in `pp_next`. `TOK_NEWLINE` cannot be removed outright (the +directive scanner needs line boundaries: `#define` / `#if` / `read_directive_line` +scan to it), but the non-directive ~95% need not be materialized as parser-facing +`Tok`s. + +**The rewrite.** +- **§C.3a (low risk):** absorb the newline-drop one frame lower so a non-directive + newline never pays the `pp_next` sret round-trip (`pp.c:191`, + `pp_expand.c:935`). **−5…12M.** Byte-identical (-E newline placement is the + critical gate). +- **§C.3b (structural follow-up):** don't materialize non-directive newlines as + `Tok`s at all — the lexer sets `TF_AT_BOL` on the next content token (already + tracked) and emits an explicit end-of-line marker only *inside* directive + context. **−15…25M**, med-high risk: must prove every `TOK_NEWLINE` consumer is + directive-internal or expressible via `TF_AT_BOL` (`#if`/`#define` + line-termination is the hazard). Map the directive-scanner contract first. + +**Floor.** The pump's irreducible cost is the per-token macro/directive/guard logic +over the *content* tokens only; newlines collapse to a flag on the next token. +**Area est.: −5…12M now (3a), −15…25M follow-up (3b).** Gate: byte-identical -E + +the directive corpus (`test/pp/`) + the line-splice battery. + +--- + +## §D — Types: *decode/classify once; all ancillary info cached on the id* + +**Ideal flow.** At construction, a type is classified once and carries — on a +stable id — its size, align, regclass, predicate bitset +(`is_int/float/ptr/void/record/aggregate/i128`), unaliased id, lowered `CgId`, and +record layout. Every later query is **one load**. There is one type +representation the whole pipeline reads, not a `Type*`→`CgId`→`CgType` re-crossing +per operand. + +**Violation (20.7% — the single largest area).** Predicates re-derive recursively +through `cg_type_get` ↔ `api_unalias_type` ↔ `abi_cg_type_info`: `cg_type_get` +recurses on itself **610K×**, `api_unalias_type` is called **~660K×**, +`cg_type_is_aggregate` 31M self over ~550K calls — each re-decoding the id through +a two-level segvec index. Root causes the prior "F1 memo" missed (it memoized +`abi_cg_type_info`, which sat on already-flat field reads — hence its measured +−2.1M): +- **`c_abi_record_layout` is _not_ memoized and is rebuilt O(M·N)** (M member + accesses × N fields). `lang/c/abi/c_abi.c:85` `arena_znew`s a fresh layout and + loops every field calling `kit_cg_type_record_field` + `kit_cg_type_size` per + field — on every call. Its exact CG twin `abi_cg_record_layout` + (`src/abi/abi.c:150`) **is** memoized via `AbiRecLayoutMap`. *(Both verified.)* + This is the direct source of the `kit_cg_type_record_field` (1.39M calls) / + `kit_cg_type_size` (648K) cluster. +- **The parser's value stack carries `const Type*`** (`cg_adapter.h:65`), so every + CG-driving op re-crosses the bridge via `type_cg_id_in_pool` (>1.2M calls), and + `type_cg_lower` runs a `t->kind` switch *before* checking its own `t->cg_id` + cache (`type.c:840-846`). +- **Two parallel type-info memos** (`c_abi_type_info` keyed on `Type*`, + `abi_cg_type_info` keyed on `CgId`) wrap the same facts; a `sizeof` probes one, + crosses the bridge, then reads the other. + +**The rewrite.** +1. **Memoize `c_abi_record_layout`** (highest leverage, lowest risk) — mirror the + `AbiInfoMap` pattern (`c_abi.c:22`) or, better, cache the layout pointer on the + `Type` node itself (it already carries a mutable `cg_id` slot, `type.h:94`). + The O(M·N) rebuild collapses to one build per distinct record. **−40…80M.** + Byte-identical. +2. **A flat per-id descriptor** — extend the proven `cached_class` byte + (`type.c:33`, with its `pad[3]`) to a packed `{unalias_is_self, pred_bitset, + unaliased_id}` stamped once (eagerly at construction for builtins/records). + Predicates become builtin-fast-path OR one `api_type_from_id` + a byte test; + `api_unalias_type` returns `e->unaliased`. **−15…30M.** Byte-identical. +3. **Carry the lowered `CgId` on the `PcgSlot`** alongside `const Type*`, filled + once at push — operands stop re-crossing the bridge. Plus hoist the `t->cg_id` + cache check above the `type_cg_lower` switch. **−15…30M** (P3a byte-identical; + P3b re-stamp `cg_id` whenever `slot->type` is written; couples with the + `PcgSlot`/`ApiSValue` shrink so net size is neutral). +4. **Collapse `c_abi_type_info` into a thin reader** off the descriptor once (2) + and (3) land — delete the `AbiInfoMap` probe. **−8…14M.** Byte-identical. + +**Floor.** The irreducible work is one decode + classify + layout per *distinct* +type, then O(1) loads. The rewrite reaches it. **Area est.: ~80…140M (−4…7%)** — +the first credible double-digit-M win in this campaign, because (1) attacks genuine +recomputation, not a constant factor. Sequence: (1) first and independent; (2) and +(3a) independent; (4) after (2)+(3b). Re-profile after (1). + +--- + +## §E — Operands & emit (the -O0 NDT path): *pop, pop, emit — one real dispatch, one byte-store* + +**Ideal flow.** An operand is a small fixed stack record; an operator is +`pop,pop,emit` with exactly **one** indirect call (the real per-arch +`NativeTarget` dispatch) and ≤1 four-byte store *into the final section image*. No +marshalling round-trip, no fake vtable, no mini register-allocator on the hot +path. The fold/strength-reduce ladder stays (it removes emitted bytes — the +multiplicative codesize lever); only its *decision cost* is slimmed. + +**Already at the ideal (verified — do not re-propose).** `F2` (MCEmitter +devirtualized to direct extern calls; the `static inline` form regressed +21M and +is documented out-of-line), `D6` (relocations indexed by section — the +O(n_sections × n_relocs) rescan is gone, the latent superlinear axis closed), and +the value-stack residency + coalescing + lazy-home work (object now 1.09× tcc) are +**done**. + +**Violation that remains.** +- **A per-instruction libc `memcpy` in the byte sink.** `aa_emit32` + (`aa64/native.c:329`) stages a word into `u8 b[4]` then calls the out-of-line + `mc_emit_bytes(mc,b,4)` → `buf_write` → `memcpy(t->data+t->used, data, n)` + (`buf.h:55`). Because the call boundary blocks const-propagating `n=4`, the + memcpy lowers to a **libc call: 6.94M `Ir` over 434K calls** — one per emitted + word, the dominant memcpy in the whole compile. *(Verified.)* +- **The arch re-queries types the NDT already knew.** `loc_is_64` /`type_size32` + (`aa64/native.c:374`) call `cg_type_size` + `cg_type_is_ptr` on `loc.type` for + every binop/move/convert/cmp — ~6.9M of `cg_type_size`'s callers are backend + re-derivation of a width the NDT had when it built the `NativeLoc`. +- **A 48 B `NativeLoc` marshalling round-trip** — `nd_binop` passes 3×48 B by + value; the arch immediately unwraps each to a register number. + +**The rewrite.** +1. **Typed-store byte sink** (the "F2 second half," never built): add direct + `mc_emit32(MCEmitter*, u32)` / `mc_emit_word(…, u64, n)` against a `{cur,end}` + cursor cached on the base — `if (cur+4<=end){ wr_u32_le(cur,word); cur+=4; } + else slow()`. `aa_emit32`/rv64 call it directly: **one 4-byte store, no + staging, no libc call.** **~5–6M**, compounds with codesize (per emitted word). + Risk: cursor↔`Buf::used` coherence across the slow path / `buf_patch` / + `mc_pos` (sync before any random-access read). Byte-identical + a `-g` check. +2. **Stamp `is64`/`size_log2` on the `NativeLoc` pad** (`native_target.h:261` has + free `pad[2]`) at the `native_loc_reg` choke point; the arch reads the bits + instead of `loc_is_64`/`type_size32`. **~4–6M**, byte-identical, low risk + (falls back to `loc.type` where unset). The arch-side elimination is + independent of §D's descriptor. +3. **Narrow register-only `NativeTarget` hooks** (`binop_rr` etc., ~16–24 B with + the imm rider vs 144 B/op) for the post-materialization ops; keep the fat + vtable for the opt replay path, gate on `opt_level==0`. **~3–5M**, additive + across all arches; synergistic with (2) (the hook carries exactly what the + arch reads). Byte-identical for the rr refactor; defer the x64 fused + mem-operand sub-case (changes selection → run-correctness gate). + +**Explicitly dropped** (measured/judged not worth it): D2 round-robin victim (the +LRU is inlined, invisible to `Ir`, and coarser eviction risks the codesize track — +instead a policy-neutral free-register bitmap for the `nd_cache_alloc` free-pool +scan is ~1–2M, byte-identical); C3 `ApiSValue` shrink (Round-7-neutral; +`api_sv_adjust_refs` at 0.67% is the load-bearing liveness refcount that *drives* +coalescing, not copy bandwidth); D5 zero-copy section write (`buf_flatten` is only +0.25M `Ir` — invisible to the metric). + +**Floor.** This subsystem's micro-levers each land single-digit-M (as +`PERF-TCC-SLIM` §10b warned); items 1–3 + the bitmap total **~13–19M (−0.6…0.9%)**. +The real emit-phase lever is the **codesize track** (fewer emitted bytes → +multiplicatively less byte-sink/objwrite/assemble), already most-realized by the +value-stack work. Item 1 leads because it compounds with codesize. **Do not touch +the fold ladder** (`arith.c:48-99`) — `PERF-TCC-SLIM` §8 holds. + +--- + +## §F — Emitted bytes / objwrite: *each machine byte written once into the final image* + +**Ideal flow.** Machine code is built once in the final section buffer; symbols, +strings, relocations are each bucketed once. No flatten-to-temp second copy, no +O(n²) rescan. + +**Status.** The two historical O(n²) violations are **closed**: `obj_strtab_add` +(shared content-hashed `ObjStrtab`) and the reloc-by-section rescan (`D6`, +`obj_reloc_index_ensure`, a counting sort — verified). The residual `buf_flatten` +second copy of all machine code at ELF/Mach-O write is real but only ~0.25M `Ir` +(one big 2.2 MB copy, cheap per byte) — **below the metric's noise; not worth the +Writer-API surface change** unless a future wall-clock/cache pass resurfaces it. +The full tcc-shape ideal (`mc_emit32` stores into one contiguous geometric section +buffer = the final image, no chunked `Buf` at all) is the §E-item-1 cursor taken +to its conclusion; revisit only if §E-1's chunked cursor still shows the spill +path hot. + +--- + +## Sequencing by leverage (re-profile after each — the frontier moves) + +``` +WAVE 1 (biggest, cleanest, all byte-identical): + §A.1 memchr splice sweep ~ -8.5…9.5% (largest single lever) + §D.1 memoize c_abi_record_layout ~ -40…80M (the F1-missed recomputation) + §B.1 direct-mapped intern cache ~ -30…45M (79% of interns are redundant) + +WAVE 2 (high-leverage, byte-identical): + §A.2 delete per-lexer (frontend) Pool ~ -20…30M + §D.2 flat per-id type descriptor ~ -15…30M + §D.3 CgId on PcgSlot + cg_id hoist ~ -15…30M + §E.1 typed-store byte sink ~ -5…6M + §E.2 is64/size_log2 on NativeLoc ~ -4…6M + §C.3a newline drop one frame lower ~ -5…12M + +WAVE 3 (more invasive / structural follow-ups): + §A.3 per-compile punct table ~ -5…15M + §D.4 collapse c_abi_type_info ~ -8…14M + §E.3 narrow register-only NT hooks ~ -3…5M + §C.3b non-directive newlines never Tok ~ -15…25M + +DROPPED / not Ir levers (measured — do not pursue): + §B self-sufficient {hash,sym} slot (~0 Ir, D1-only) · relay-copy rewrite (dead) · + Tok 24→16B (hot-path line decode) · §E D2 round-robin / C3 ApiSValue / D5 zero-copy +``` + +**WAVE 1 alone is ~13–15% off the 2.04 B compile** (moving kit from ~3.1× toward +~2.7× tcc) for three low-risk, byte-identical rewrites. All three attack **genuine +recomputation** — an unmemoized O(M·N) layout rebuild (§D.1), a redundant scalar +pass over every byte (§A.1), and 79%-redundant per-occurrence hashing (§B.1) — not +constant factors. Those are the rewrites-to-the-ideal; WAVE 2–3 compound. Re-profile +after each landing — the frontier moves, and several WAVE-2 items shrink once their +WAVE-1 neighbor lands. + +## Appendix — claims verified against source (not docs) + +- §A scalar splice loop + no NEON: `lex.c:156-157`, disasm of the profiled binary. ✓ +- §A per-lexer pool dead except `->c`: `lex.c:288,340,597,784`; ident intern hits + shared `c->global` via `kit_sym_intern` (`src/api/core.c:220`). ✓ +- §B two distinct `Pool` types — `c_pool_new` makes the **frontend** pool (arena + + type/abi cache, `cpp_support.h:33`), *not* the string interner (`c->global`, + `src/core/pool.h`); intern is already per-compile-shared. ✓ +- §B/§C measured on real sqlite3.c (re-tokenized): 440,942 id+num occurrences / + 19,700 distinct (22.4× reuse); 256-slot intern cache 78.7% hit, single-slot 3.4%; + `lex_next` 1,424,699 calls vs 684,790 tokens to the parser (51% dropped in PP). ✓ +- §D `c_abi_record_layout` unmemoized vs `abi_cg_record_layout` memoized: + `lang/c/abi/c_abi.c:85-118` (fresh `arena_znew` + per-field loop, no cache) vs + `src/abi/abi.c:150-161` (`AbiRecLayoutMap_get/set`). ✓ +- §D parser stack carries `Type*`: `lang/c/parse/cg_adapter.h:65`. ✓ +- §E byte-sink libc memcpy: `aa64/native.c:329` (`b[4]`+`mc_emit_bytes`) → + `mc.c:242` (`buf_write`) → `buf.h:55` (`memcpy(...,n)`), n=4 not const-propagated + across the out-of-line call. ✓ +- §E F2/D6 already landed: `mc.h:48-82` (no fn-ptrs), `obj.c:1140-1196` + (`obj_reloc_index_ensure` counting sort). ✓