Archive — history, not state. Kept for its reasoning and its evidence; its plan is closed.
Compiler optimizing experiment: restart handoff
Date: 2026-07-13
Branch: compiler_optimizing
Branch point for the contained performance campaign: 81685c37
Branch tip before this handoff: 7fa49065
This is the restart index for the compiler-performance experiment. It does not replace the detailed reports. It states what is actually present, what was measured at which checkpoint, what was rejected and deleted, what costs memory, and what remains only a proposal. Read this before starting another performance experiment so a discarded arm is not recreated without a new premise.
Final campaign update — 2026-07-14
This document began as the restart handoff and its lower sections preserve that historical checkpoint. The later linked/shapes/PGO arc is now complete. For current authority use, in order:
perf-campaign-final-decisions.mdfor the retained/rejected inventory and tradeoffs;perf-campaign-final-matrix.mdfor clean-daemon final benchmarks and integrated validation; andperf-linked-pgo-arc.mdfor the chronological trial log.
The major work described below as “approved, not landed” did land: dense static
record shapes, program-scoped PGO with a profitable record-load PIC, strict
fact-free semantic artifacts plus versioned lowering sidecars, static export
caching, synchronous Mach C-leaf invocation, native power-of-two division and
remainder, and a Mach closure outer-frame cache. Final validation passed
make seed, 7/7 Meson, 145/145 focused compiler, 1,086/1,086 isolated VM,
1,921/1,921 warmed full suite, and 3,733/3,733 fuzz checks. All 68 final
benchmark rows returned exact Mach/native oracles.
Reading order and authority
- This file is the disposition and restart index.
perf-campaign-2026-07.mdis the detailed experiment ledger through the Mach/native array work and suspension prerequisite.- Later accepted experiments have focused reports:
perf-structured-tco-numeric.md,perf-structured-loop-phis.md,perf-spectral-inline-cleanup.md,perf-native-checked-store-region.md,perf-native-record-templates.md,perf-native-compact-raw-continuation.md,perf-native-fannkuch-loop-pressure.md, andperf-active-structured-midend.md. - Failed call-frame reuse is preserved in
perf-call-activation-cache.md. perf-next.mdis valuable historical context, but its charts predate the final contained composition and must not be treated as the current matrix.
Source and generated-code inspection outrank recollection. A timing in this
file is attributed only to the exact report which measured it. “Latest” below
means the latest exact checkpoint currently recorded for that benchmark, not
a single final matrix run at HEAD.
Scope and fixed constraints
The objective is generated-program speed, first native AOT and then Mach. Compiler speed is not an acceptance criterion. Generated code size, actor memory, heap traffic, continuation memory, and shared runtime text remain acceptance criteria because targets include 4 MiB-class systems and may extend to a deliberately reduced 288 KiB profile.
The experiment preserves these language/runtime constraints:
- at most four parameters; missing arguments null-fill; excessive dynamic arguments disrupt;
- parameters and
defare immutable; use()is top-level, literal, bound todef, and returns an actor-local stone module value;- mutable captures remain cells, not scalar SSA values;
- record/array stone and deletion behavior remains observable;
- numeric overflow, null, non-finite values, signed zero, and disruption behavior remain exact;
- Pit calls retain a reifiable, relocating-GC-walkable continuation and can suspend; generated native code cannot rely on an unfreezable host stack;
- semantic mcode remains the portable language-machine contract.
What is actually on this branch
The branch contains a composed sequence of compiler, Mach, runtime-correctness, benchmark, and tooling commits. The major retained mechanisms are:
Compiler correctness and fact identity
- LICM constants are point/use-span facts, not unsafe function-global claims. Integer ranges and QBE consult the constant only at the exact use. Golden execution tests catch the case where a constant and a runtime divisor reuse one physical slot.
- Integer range analysis is allowed on large functions. The old arbitrary 128-instruction cutoff was removed; a final one-million state-cell product guard now bounds pathological compiler memory while failing closed.
- Exact type, constant, range, call, loop, and inline facts are transported or regenerated after instruction mutation rather than trusted at stale indices.
- Mach refreshes rooted mcode instructions after a collection before it uses them again.
- Native cache identity includes emitted IL, preventing stale binaries from masquerading as compiler results.
Native numeric and structured value work
- Raw numeric regions keep canonical doubles and bounded integers out of tagged slots until an observable, call, capture, GC, disruption, or suspension boundary.
- Immutable parameter inference and final range cleanup preserve exact facts through hoisted declarations and calls.
- Self-TCO phis and selected natural-loop phis get stable value identities and raw representations.
- The active read-only structured view now covers finalized mcode with blocks, normal/exception edges, dominance, loops, values, cells, effects, origins, and exact round trip. QBE consumes a deliberately narrow raw-value lane through safe single-predecessor regions.
These changes are general compiler proofs. They do not recognize benchmark names. The largest scalar wins therefore apply to programs with the same semantic shape, not only to the supplied sources.
Arrays
- Native consumes exact array receiver/index facts, shares exact read handling, keeps selected numeric results raw, and folds compiler-generated checked load/store regions.
- Mach has typed index handlers, local integer-proof consumption, allocation-free existing-capacity stores, an inline pure read helper, and a fused compiler-proven checked-store region.
- Bounds, append/growth, null-on-read, disruption-on-invalid-write, write barriers, and forwarding remain in the selected contracts.
This is not typed-array storage. Arrays still hold ordinary PitValues. PGO or static proofs can remove guards and materialization, but a future unboxed array backing store would be a separate representation decision.
Records
- Known-field records are sized against the table’s actual load limit. On binarytrees this cut allocation traffic by 44.4%, collections from 62 to 33, and time by 12.1% Mach / 12.9% native in the controlled experiment.
- Hot native literal key sets can use shared preseeded descriptors. This makes
construction use fixed-offset stores while instances remain ordinary hash
records. It improved
record_new21.55 -> 14.04 ms (-34.8%) and binarytrees 52.62 -> 44.95 ms (-14.6%).
Shapes are not implemented. Record templates accelerate construction but do not shrink the ordinary three-field instance or make general field access an offset operation.
Calls, frames, and suspension
- Native AOT execution now has cooperative, freeze-safe suspension and exact cross-engine resume/disruption handling. This is correctness infrastructure with a measured normal shootout tax usually around 1-2% and a larger recursive-call tax.
- Self-tail calls remove their dead callee lookup after TCO.
- One narrow raw Fibonacci convention proved that exact continuation layouts
matter: private frames fell from 104 to 56 bytes and native
fib(30)fell 23.820 -> 13.947 ms (-41.4%). Selection remains narrow and did not improvecall_hot. - Generic activation-frame caching was measured, regressed, and deleted. See the rejection ledger below.
Mach
- Residual type and text predicate branches are fused where their exact target and fact spans prove it safe.
- Numeric null remains an absorbing poison in fast arithmetic and is canonicalized when observable.
- Exact facts survive supported inline splices and are consumed by compact typed handlers.
- Array read/store work described above materially reduced fannkuch dispatches.
Tooling retained because it made the evidence trustworthy
- typed one-shot benchmark and dump commands with explicit reply deadlines;
- paired optimizer statistics and fact/IL/assembly dumps;
- independent golden benchmark results rather than Mach/native agreement alone;
- deterministic fuzz progress without corrupting the final reply;
- emitted IL in native cache keys;
- bounded, identity-aware POSIX daemon lifecycle behavior;
- native suspension, nursery barrier, record capacity, and daemon lifecycle native fixtures.
Performance evidence
The fresh campaign baseline at 81685c37 was:
| benchmark | Mach ms | native ms |
|---|---|---|
| mandelbrot | 108.30 | 28.01 |
| fannkuch | 297.85 | 241.13 |
| spectralnorm | 305.97 | 172.10 |
| binarytrees | 97.66 | 60.71 |
| dynamic closure | 58.07 | 30.43 |
| Fibonacci | 54.24 | 28.79 |
| self TCO | 89.13 | 30.30 |
Latest exact checkpoint evidence currently recorded:
| benchmark | latest measured lane | recorded result | scope |
|---|---|---|---|
| mandelbrot | native | 9.21 ms | unchanged control at active structured checkpoint |
| spectralnorm | native | 44.32-44.80 ms | active structured raw-value lanes |
| spectralnorm | Mach | 179.10 ms | preceding inline-cleanup checkpoint; active QBE work does not change Mach |
| fannkuch | native | 106.135 ms | bounded raw-int loop-pressure checkpoint |
| fannkuch | Mach | 227.443 ms | fused checked-store checkpoint |
| binarytrees | native | 46.77 ms | composed record-template checkpoint |
| binarytrees | Mach | 86.42 ms | same checkpoint; templates themselves are native-only |
| self TCO | native | 5.312 ms | structured raw int/double TCO phis |
| Fibonacci | native | 13.947 ms | narrow compact raw-recursion convention |
| record construction | native | 14.04 ms | controlled templates on, 21.55 ms off |
These figures establish large real movement, especially for scalar numeric
programs, array-heavy fannkuch, recursion, and record construction. They are
not a final release chart. Later commits were validated with focused controls,
but at this historical checkpoint the entire branch had not yet been timed in
one quiet fresh-process matrix. That matrix is now complete in
perf-campaign-final-matrix.md.
The central unresolved dynamic rows remain:
- generic dynamic closure calls and captured outer-frame lookup;
- general record reads/writes and instance size;
- unknown/mixed array element access;
- allocation/GC under a validated nursery configuration.
Rejected or superseded experiments
The implementation for every rejected compiler/runtime arm below was deleted. Do not retry it without a materially different proof, representation, or cost model.
| Attempt | Evidence | Disposition |
|---|---|---|
| broad inlining and level-1 capture admission | spectralnorm native 172.10 -> 172.20 ms; IL +16.6%; allocation 2.6 -> 26 KiB | reject size and allocation growth without speed |
| inline exact native array reads at every proven site | array_read only -0.49%; spectralnorm not repeatable; IL/assembly grew | retain shared helper; do not replicate the full control diamond |
| broad raw array add/sub propagation | array_read regressed about 31% | keep only proven immediate multiply/divide arm |
| resolved-call direct native symbol entry | no-inline call_hot 27.06 -> 28.67 ms (+6%) | target identity alone is not the dominant call cost |
| prepared activation-frame cache | 31.37 -> 40.46 ms with eager scrub; 36.74 ms with scanned retention | bump/stamp/pop is cheaper; see perf-call-activation-cache.md |
| all eight fannkuch loop phis | native 105.97 -> 127.45 ms; IL smaller but assembly +6.7% | QBE register/spill pressure defeats broad selection |
| add any raw-double/bool phi to fannkuch’s one bounded int | every tested addition slowed, one to 128.53 ms | retain bounded-int-only overflow policy |
| promote numeric parameters broadly | TCO improved but ordinary recursive Fibonacci materially regressed | require an exact loop/call convention identity |
| Mach arithmetic plus cold-arm jump fusion | dispatches fell 224.4M -> 192.4M, time 276.48 -> 277.30 ms | fewer dispatches alone are not enough |
| instruction-pattern cold block layout | self-realization stalled/failed; poisoned generated cache | retry only from verified CFG blocks |
| full typed Mach checked-store handler | all stores selected but time regressed 1-3% and text grew | current generic allocation-free store is better |
| exact transient spectral inline-chain recognizer | reduced IL, but retry’s boundary oracle failed in Mach before a native timing claim | do not reconstruct pattern walker; active value identities are the replacement path |
| first record-heavy structured IR prototype | exact round trip, but roughly 24x retained graphs and sampled 512 MiB compiler heap | reject storage implementation, not structured architecture |
| separate scalar native continuation stack | correctness/memory model superseded by ordinary GC-walkable Pit frames | keep unified frame representation |
Memory ledger
Target/runtime improvements
- known-field record sizing: allocation traffic -44.4%, collections 62 -> 33 in binarytrees;
- native AOT side-table initial allocations: roots 17,408 -> 544 bytes, key cache 1,024 -> 128 bytes, code cache 3,072 -> 384 bytes;
- compact selected recursion: 48 fewer live frame bytes per private activation;
- accepted native loop/value transforms generally reduce generated code and add no frame, heap, or per-code target metadata.
Target/runtime costs
- native record templates: about 1.25 KiB shared runtime text plus 40-72 bytes per selected key-set descriptor; ordinary instance size is unchanged;
- cooperative native execution: 64 bytes of native state per context and a lazy 136-byte four-entry host chunk, with no extra word per Pit frame;
- compact raw recursion: about 596 bytes shared runtime text/unwind, offset by smaller live frames and slightly smaller selected target code;
- Mach fusions add small shared handler text, generally hundreds of bytes to a few KiB each. A final linked-runtime size comparison is still required; individual reports record exact deltas.
Compiler-only costs
- active structured spectral analysis increased QBE-emission allocation traffic 10.65 -> 13.07 MiB (+22.7%), allocations +45.3%, and sampled old-heap block peak 8 -> 16 MiB. Generated target memory did not increase and linked spectral text became 576 bytes smaller;
- the discarded feasibility prototype retained about 24x raw mcode and reached a sampled 512 MiB compiler heap. Its representation is not present here;
- the final integer-range state-product guard prevents very large self-hosted functions from exhausting the compiler heap while retaining all measured benchmark proofs.
Nursery status
The nursery implementation was repaired and instrumented, including the
missing barrier and oversize pretenure behavior. It is still default-off.
Current-tree performance acceptance is not established because supported
daemon/test lifecycle failures blocked a matched off/on matrix. The current
host policy starts with an 8 KiB nursery block and can grow to 512 KiB; neither
is an accepted compact-target default. Do not quote historical nursery wins
as current results. Resume with the explicit 1/2/4/8 KiB by
8/32/128/512 KiB sweep in perf-nursery-revisit.md.
Playdate nan32 status
The branch includes an accepted design and a standalone host/ARM compile-only
fixture, not a 32-bit Pit runtime. Accepted semantics are raw finite binary32,
signed-22-bit immediate integers with larger integers represented as float32,
and one process-wide bounded runtime arena. Pointer/key representation and a
real constrained-device arena remain implementation work. See
pitvalue-playdate32.md.
Structured mid-end disposition
The first read-only feasibility prototype answered the semantic question: blocks, exceptional edges, dominance, loops, ordinary SSA values, captured cells, effects, origins, and exact mcode round trip can all be modeled. It was rejected as a production representation because record-per-node storage and iterative SSA used excessive compiler memory.
The current branch then implemented a narrower active view after finalized mcode and proved its executable value: spectralnorm native fell about 41% at that checkpoint with smaller generated code and no target memory. Therefore:
- structured value identity is an accepted optimization lever;
- the current active view is a conservative QBE consumer, not yet the sole compiler IR;
- compile time is not a gate, but compiler memory is bounded to prevent failed self-host builds;
- future expansion should use stable values/origins and explicit heap/cell effects rather than more instruction-pattern recognizers;
- Mach must eventually consume the same semantic facts where useful.
Historical PGO and shapes plan (superseded by landed code)
The following subsections record the pre-implementation design. Dense shapes
and the bounded PGO record-load consumer subsequently landed with the same core
contracts; see the final update above and perf-linked-pgo-arc.md.
Static record shapes v1
The agreed first representation experiment is:
- a literal known key set gets one immutable shared layout descriptor;
- an instance stores dense values rather than repeated keys/hash slack;
- updating an existing field remains dense;
- adding a field or deleting one converts that record to the existing generic dictionary representation;
- no transition tree or unbounded hidden-class graph;
- monomorphic/polymorphic describes an access site, not the converted record.
This was expected to reduce construction, lookup, GC scanning/copying, and
instance memory. It is now implemented and retained. A common three-field
record is 56 rather than 144 bytes; the full tradeoff, including the final
binarytrees native caveat, is in perf-record-shapes.md.
PGO spine
The agreed profile is semantic and keyed by stable origin plus exact executable content. Instrumented Mach realizations collect execution counts, branches, receiver/result kinds, record layout IDs, array kinds, callee code IDs, loop trips, and disruption frequency. Static facts remain truth; profile data is a hint requiring guards and a generic fallback.
Profiles can feed both Mach and native and can be collected on a development machine for cross-target AOT builds. Branch/type/callee frequencies are portable; target cost decisions are not. Running actors retain their code generation; future actor starts can use a newer optimized realization. Final target artifacts need not contain counters.
Primary consumers are:
- record layout guard plus dense offset read/write;
- dynamic closure code-identity guard plus a thinner freeze-safe call path;
- array length/element-kind guards and loop versioning;
- branch/cold layout, inlining, and specialization budgets.
PGO cannot make a hash record small or a general call convention cheap by itself. Shapes and the call convention are the fast representations PGO chooses.
LTO: existing seed and required extension
use() is already statically constrained and the builder already resolves a
module graph, execution order, and static bindings. pit-compiler/lto.cm
collects used module export members and contains a conservative export-pruning
transform. The dependency graph aggregates importers, but production
compilation does not currently invoke the pruning transform.
engine_lite still loads each script module in order and caches its stone
actor-local return value. That runtime representation does not block LTO. A
logical linked-program view can preserve unit IDs and module initialization
while enabling:
- known module-export offsets/bindings;
- cross-module resolved callee sets and inlining;
- unused export/function/module removal;
- whole-program escape/effect analysis;
- PGO-guided cross-module layout and specialization.
Do not begin by concatenating source or deleting module boundaries. Qualified
(unit, function, value, origin) identities preserve caching, diagnostics,
profile provenance, initialization order, and actor-local module values. One
physical Mach/native blob can be evaluated later as a lowering/layout choice.
Endowments and native C calls remain opaque external effects. The set of Pit code bodies is closed for a fixed executable, but messages, mutable aggregates, selected closures, actor inputs, and C results remain honest dynamic data.
Historical recommended next sequence
- Run one quiet, fresh-process final micro/shootout/call/memory matrix at this branch and record artifact hashes. Do not infer the final matrix by adding deltas from different checkpoints.
- Implement and falsify static dense record shapes v1. This supplies the memory/access representation needed by PGO and has a strong low-memory thesis even before feedback.
- Add stable origin IDs and the smallest complete PGO artifact loop: instrumented Mach -> profile -> guarded optimization -> ordinary Mach and native. Start with record layout IDs because correctness and payoff are easiest to measure.
- Design and measure a general thin freeze-safe call/return convention. Use PGO to guard closure code identity, retain the actual environment, and keep an explicit actor suspension boundary. Do not reuse prepared frames.
- Extend the current static module graph into logical LTO: export pruning, module-field offsets, resolved cross-unit callees, then cross-unit inlining and escape analysis.
- Use PGO for dynamic arrays: hoisted length/representation guards and loop versions first; consider typed backing stores only if boxed load/decode remains material.
- Re-run the nursery matrix after lifecycle tooling is reliable and after shapes reduce record traffic. Choose a low-memory Pareto point rather than the fastest host-only cap.
Historical validation state at handoff
The accepted experiment reports individually record full-suite, focused, dual-lane, suspension/GC, and deterministic fuzz gates. The latest active structured checkpoint records:
- compiler tests 121/121 at that commit;
- structured tests 7/7;
- default suite 1,918/1,918 outside socket-restricted sandboxing;
- deterministic fuzz 3,733/3,733;
- forced native suspension with 48 moving collections.
After adding the integer-range state-product guard, the current branch passed
124/124 focused compiler tests, 7/7 structured tests, and 1,918/1,918 default
tests. At this checkpoint a final performance matrix was pending; it has since
completed and is recorded in perf-campaign-final-matrix.md.
Generated boot objects and OCaml .cmi/.cmx benchmark products are build
artifacts, not performance source. They should not be committed merely because
validation regenerated them.
Source: plans/archive/compiler-optimizing-handoff.md