Archive — history, not state. Kept for its reasoning and its evidence; its plan is closed.
Compiler optimization campaign: final decisions and tradeoffs
Date: 2026-07-14
Branch: compiler_optimizing
Status: final review index for the contained experiment. Nothing here was pushed or merged upstream. Measurements are from the clean-daemon integrated artifact unless a row explicitly says it is an isolated trial.
Outcome
The campaign substantially improved generated programs without changing Pit’s language guarantees or adding a semantic mcode operation. From the fresh contained baseline to the final composed artifact, native changed as follows:
| workload | baseline | final | change |
|---|---|---|---|
| mandelbrot | 28.01 ms | 9.21 ms | -67.1% |
| spectralnorm | 172.10 ms | 44.55 ms | -74.1% |
| fannkuch | 241.13 ms | 111.35 ms | -53.8% |
| binarytrees | 60.71 ms | 50.06 ms | -17.5% |
| closure | 30.43 ms | 26.24 ms | -13.8% |
| Fibonacci | 28.79 ms | 13.61 ms | -52.7% |
| self tail call | 30.30 ms | 4.98 ms | -83.6% |
Mach improved 17-45% on the four original shootouts and 12.4% on the dynamic
closure row. Scalar native arithmetic is now broadly competitive with OCaml
and .NET. The remaining large gaps are representation and dynamic-boundary
costs: boxed arrays, dynamic calls/closures, record access misses, allocation,
and GC. nbody at 150.79 ms native versus 1.58 ms OCaml is the clearest
combined stress case.
Retained production mechanisms
Exact compiler facts and native numeric lowering
- Constants are point/use-site facts, not function-global slot facts. This fixes slot-reuse wrong code while preserving constant division, literals, range analysis, and later exact consumers.
- The active structured QBE value lane keeps narrowly proven doubles raw across safe finalized-mcode regions. It moved spectralnorm from about 76 to 44-45 ms native with 576 fewer linked text bytes and no target heap/frame cost. Compiler-only cost on spectralnorm was +2.41 MB allocation (+22.7%), about 20 versus 8 ms QBE emission, and an 8-to-16 MiB old-block peak. Compiler speed was not a campaign gate; generated target memory remains unchanged.
- Native positive-power-of-two divide/idiv/mod/remainder specialization is retained. The final canary was 15.14 versus 15.43 ms and 1,522 versus 1,551 QBE IL lines; the exhaustive edge fixture emitted 77,267 versus 77,837 lines. It has no runtime metadata or heap cost.
- Wide whole literals now distinguish language
is_integerfrom the current signed-int32 immediate lane. This is correctness-only and adds no cost to ordinary programs.
Records and PGO
- Dense static record shapes reuse the record header’s
Ibit andcap56; there is no new per-record word. A three-field record is 56 rather than 144 bytes. One immutable descriptor costs24 + 8 * key_countbytes in shared CT memory. Existing-field writes stay dense; add/delete converts that record one-way to the unchanged generic representation. - Final
record_newwas 21.18/14.85 ms Mach/native with 16.817 MB allocation and 20/11 collections. The generic-template canary allocated 43.217 MB. - Dense shapes have one explicit caveat. In the final
binarytreescanary they were 74.79/47.25 ms versus generic-template 83.63/44.86 (repeat 80.34/44.73) and no-shape 80.37/51.66. Thus dense is a large memory/Mach win and faster than no shapes, but about 5% slower native than generic-template. It remains retained because allocation falls from 53.96 to 43.25 MB, common records are 61% smaller, and no-shape native is worse. The native gap is follow-up work. - Program-scoped PGO is explicit (
--pgo_collect,--pgo_auto), portable, and keyed to exact executable semantic identity. It collects record-load sites only, caps memory/sites, and consumes only sites with at least 1,024 samples and 90% valid top-four shape coverage. - Final record-load PGO moved 53.875/30.147 to 45.954/22.895 ms Mach/native; collection was 56.283 ms. The profile is 7,088 bytes. The first consumed 64-bit Mach PIC costs about 144 bytes (function wrapper, code metadata, site row); later sites are about 64 bytes. Records gain zero bytes and existing shape descriptors are reused.
Calls and modules
- Exact static
useexports are cached in actor-local frame slots. The direct fixture improved Mach 16.81 to 13.90 ms and native 10.87 to 9.14 ms. The cost is one 8-byte live top-frame slot per cached export; module identity and dynamic export behavior remain intact. - Proven synchronous C leaves bypass a temporary Mach activation while retaining the C-module non-suspending contract. Generic and cached paths improved about 28-34% in the original gate. Runtime text grew about 992 bytes; there is no per-call heap object.
- The fused Mach outer-frame cache combines the first depth-1 captured access with binding. Final canary: 51.42 versus 56.18 ms (-8.5%). It costs one scanned 8-byte slot per live eligible activation, zero serialized bytecode bytes, and preserves GC, frozen stacks, disruption, recursion, depth-2, and the captured write barrier. Native is unchanged.
- Calls still use ordinary freezeable Pit frames. No optimization may hide a live continuation in an unscanned native or C-only side structure.
Artifact and tooling contracts
- Semantic mcode is fact-free. A strict
pit.mcode.lowering@1sidecar carries finalized target facts and hydrates a private lowering view. Canonical units arepit.mcode.unit@2; cache identity ismcode-unit-v4-strict-lowering. Missing, malformed, unknown, mismatched, or inline facts fail closed; no compatibility fallback remains. - Final artifacts contain 65 strictly validated mcode modules:
boot.qop74,842,189 bytes,boot/firmware67,509,488, andboot/boot1,573,945. Combined size fell 1,095,242 bytes versus the immediate pre-audit campaign artifacts; the engine grew 126,372 bytes for strict validation/hydration. Their final SHA-256 identities are respectivelya58afba087c65d9d04b31c10ade9c16400fc3766b3a3f8a8295749462eeb7d57,797db28fc9b1bec61db89da36a5656049db9da3389ce72a6f0dc61c12b175e89, and6fff594fcb6f130c9c2ab5db5c1f595081320ec56d1c7f6f11ea1ebb4bf17174. - Bare CLI source execution is typed one-shot request/response; the implicit two-second help probe is gone. PGO flags are program-scoped. GC benchmark detail is opt-in. These are workflow correctness improvements, not language speed claims.
Rejected and deleted experiments
| experiment | measured result | decision |
|---|---|---|
| activation-frame cache | 31.37 ms baseline; 40.46 eager scrub; 36.74 stale-scanned | bookkeeping exceeded bump/pop; delete |
| PGO check-only dynamic call | 58.768 to 59.241 ms | predicate/arity checks are not the call bottleneck; delete consumer |
| fused native call delivery | call0 -0.9%, closure flat, call_hot +4.2% | failed controls; delete |
| local array bounds proof | Mach -2.5%, native +2.1% noise | checks were not dominant; delete |
| sealed boxed-int array raw load | 0.09% | boxing/representation still dominated; delete |
| PGO record-store PIC | Mach -2.8%, native +0.2% | ordinary shaped store already cheap; delete |
| exact linked constant-leaf substitution | Mach about 13% faster; native 0.93% faster, below 25%/50% gates | did not remove enough activation work; fully delete |
| eager closure outer bind | 57.71 to 65.00 ms | one added dispatch per call; delete |
| native closure outer-base CSE | below 5%, noise-sized | delete metadata/code |
| default nursery | some allocation rows improved, native neutral/slightly worse, per-actor reserve cost | keep mechanism opt-in; default remains off |
| record-per-node structured IR | about 24x retained graph, sampled 512 MiB compiler heap | reject representation, retain structured-value thesis |
| linked semantic clone/materialization arms | disruption/provenance failures or no executable gate | delete production integration |
The PGO dynamic-call collector was also deleted because it had no surviving consumer yet could consume all 512 profile sites and roughly 32.7 KiB of call descriptors plus about 24.5 KiB of collector spine/counters. The profile schema remains extensible; there is no dormant call collection fallback.
Research-only code deliberately preserved
compact_spine.cmand its probe/tests are an isolated packed value/origin/effect representation. It retains roughly 0.91x raw mcode and is not imported by streamline, Mach, QBE, the linker, or runtime.cross_unit_inline_experiment.cmis likewise a research experiment, not a production pass. Failed pairers, linked-call sidecars, pure-leaf integration, and executable fixtures were deleted.- The active structured QBE lane is production code; the earlier record-heavy structured representation is not.
Memory interpretation
The final benchmark actor numbers above are meaningful only on a restarted node. Clean boot mapped 64 MiB with 4 MiB actor heaps, 1 MiB node constants, about 20 MiB shared Mach, a roughly 2/8 MiB shop, and a 59/256 KiB builder. After running the aggregate and full compiler/test fleet without restart, the development daemon reached 704 MiB mapped, 278 MiB actor heaps, 37 MiB node constants, and a 242/256 MiB builder. Allocation rows then slowed by 2-6x; restart restored them. This is development compiler/cache retention, not the generated program’s target footprint, but it is a real tooling problem for repeatable benchmarking.
The nursery was off in the final matrix. Dense shapes are the campaign’s main runtime-memory win. PGO and closure caching add bounded bytes only where used; numeric/compiler proofs generally add no target memory. A future 32-bit PitValue/Playdate build remains separate work and was not produced here.
Final validation
make seed: passed.- Meson: 7/7; focused compiler: 145/145.
- Isolated VM: 1,086/1,086; warmed full suite: 1,921/1,921.
- Fuzz: 3,733/3,733, 500 programs, seed 20260713.
- Aggregate: 68/68 exact Mach/native results.
- One cold full-suite attempt passed 835 tests then charged realization to a
vm_suitereply timeout; isolated and warmed reruns passed. It is not counted as a language failure or silently omitted.
Best next work
- Attack boxed-array representation, not another bounds-check proof. Static or
profiled element kinds need a persistent specialized backing store before
array_read, fannkuch, spectralnorm, or nbody can approach native peers. - Redesign dynamic call/return work around freezeable ordinary frames. The rejected frame cache and check-only PIC show that activation/continuation delivery, not predicates, is the prize.
- Explain and remove the dense-shape native
binarytreesgap while preserving the 56-byte record. Generated QBE/assembly and allocation/GC interaction are the first evidence to inspect. - Extend the semantic linked graph only when a consumer removes a large cost. Constant-leaf substitution was too small; direct known Pit-function entry or a larger cross-unit inline must include activation/return and provenance.
- Keep PGO conservative. The proven record-load PIC is useful; store-only PIC and call-check PIC are not. New consumers need honest polymorphism, bounded metadata, and predeclared Mach/native gates.
Source: plans/archive/perf-2026-07/perf-campaign-final-decisions.md