Archive — history, not state. Kept for its reasoning and its evidence; its plan is closed.
Structured native numeric lanes for self-TCO
Status: contained performance experiment, validated and ready for composition review.
Result
Self-tail recursion already becomes an ordinary backedge in semantic mcode, but
the native emitter treated its parameter slots as tagged frame memory. In
tco_self, five million iterations repeatedly decoded, checked, encoded, and
stored both n and acc. This experiment keeps the loop phis in the native
representation each value actually needs:
nis a raw signed integer. Its external entry is a tagged int and every recursive edge is the guarded recurrencen = n - 1on then > 0path.accis a raw double. It enters as an int butacc + nmay leave int32, so treating it as an integer would be wrong. Normal nan64 canonicalization still applies when the value becomes observable.
The semantic mcode instruction set, Mach encoding, runtime object layout, frame layout, and public ABI are unchanged.
Proof and lowering
The proof is deliberately split at the same seam as the program:
- Resolver parameter inference already rejects functions whose callable value
escapes. It now retains a second optimizer-only type vector containing only
calls from outside the callee. Self calls still participate in the ordinary
parameter merge. For
sumto, the ordinary phi types arenum,num, while the external entries areint,int. - The existing TCO pass records the label of the loop it creates. This is lowering identity, not a new operation.
- QBE builds the compact instruction-index CFG it needs for the candidate. An
integral parameter is accepted only if every backedge assigns it from the
exact recurrence
p - 1, every external entry isint, no other definition exists, and the recurrence is unreachable when the false edge ofp <= 0is removed. That reachability test is the dominance proof forp > 0. - The proven subtraction receives a point-specific no-overflow fact. The parameter register and comparison therefore remain untagged. Other numeric TCO parameters use the existing raw-double/NaN-poison lane.
- All existing spill/reload paths remain authoritative. A suspension at the backedge materializes live raw values into tagged GC-scanned frame cells, stamps the resume segment, and decodes them again after resume. Calls, captures, handlers, and unknown definitions conservatively reject or spill.
This is the smallest useful structured mid-end slice found in the campaign: it creates value identity for an entry/backedge phi and proves one edge predicate, without asking slot-global write types to represent two different values.
Controlled performance
Apple arm64, composed suspension+A6+A7 checkpoint, perf_one.ce, native-only,
three sequential 11-sample arms in an exclusive timing window:
native tco_self arm | median | change from tagged | QBE IL | assembly |
|---|---|---|---|---|
| tagged parameters | 30.561 ms | baseline | 34,066 B | 29,168 B |
| both parameters raw double | 19.600 ms | -35.9% | 33,070 B | 29,714 B |
n raw int, acc raw double | 5.312 ms | -82.6% | 30,283 B | 27,271 B |
All arms returned exactly 12500002500000. The split representation is also
72.9% faster than the all-double arm. It is within about 55% of the recorded
OTP 29 JIT result (3.43 ms), rather than 8.9x slower as the old 30.30 ms row was.
The generated artifact becomes smaller: IL -11.1%, assembly -6.5%.
The broad first prototype promoted numeric parameters in all eligible
functions. It improved TCO but made ordinary recursive fib materially worse
because every real call had to spill/reload promoted arguments. That arm was
deleted. Parameter promotion now requires a loop identity emitted by TCO.
Nonselected macro checks remained on their composed artifact paths and returned
their exact goldens: loop_nested 12.247 ms (27,255 B IL), mandelbrot 8.750 ms
(63,628 B), spectralnorm 116.023 ms (184,086 B), and fannkuch 183.650 ms
(178,984 B). These are no-change checks, not claimed wins.
Memory tradeoff
There is no generated-program heap, frame, actor, context, or per-code metadata cost. The target carries fewer instructions for the selected loop.
Compiler-only retained data adds one parameter-entry type array to a function whose closed-world external calls establish at least one type, plus one TCO entry-label field for a transformed function. For this two-argument fixture on host nan64, the array is 32 bytes (16-byte array header plus two PitValue words); its type strings and entry label already exist as shared compiler texts. The QBE CFG worklists/maps are temporary. Compiler time and temporary memory were not gated in this campaign, but none of this reaches N64/GBA executables.
Correctness gates
- Exact fact regression: ordinary recursive phi type differs from external entry type, and TCO identity survives optimization.
- Exact QBE regression: the decrement phi is
lraw integer while the accumulator phi isdraw double. - Dual-lane golden:
tco_selfmatches Mach at12500002500000. - Forced reduction/GC fixture:
native_suspend_tco_raw.cerepeatedly suspends the selected loop, collects while suspended, and resumes. The final composed runtime returned exactly1250025000across 49 suspensions and 49 forced collections, with maximum observed native depth 2. - Focused compiler tests passed 95/95 after adding rejection cases for a float external entry and for a decrement not dominated by a positive guard.
- The full default suite passed 1,918/1,918. The first sandboxed attempt reached 1,901 passes but denied six local socket binds; the unrestricted rerun was clean, so those failures were environmental rather than compiler regressions.
- Deterministic fuzz passed 3,733/3,733 at seed 20260713.
The final branch also composes the two native suspension follow-ups: resumed Mach disruptions remain on the Mach path, and the common native frame-entry path avoids activation work that only resumed calls need. Neither changes the TCO proof or representation; they make the suspension checkpoint used by this experiment authoritative.
Next extension
The valuable abstraction is not “TCO special casing”; it is a compact phi with entry facts, edge facts, and a representation choice. The next native experiment should reuse this infrastructure for ordinary loop-carried locals and for recursive call arguments that cannot be eliminated into a backedge. Captured/address-taken values and suspension seams must continue to materialize.
Source: plans/archive/perf-2026-07/perf-structured-tco-numeric.md