Archive — history, not state. Kept for its reasoning and its evidence; its plan is closed.

Linked optimization, shapes, and PGO arc

Date started: 2026-07-13

Branch: compiler_optimizing

Starting commit: da2a46b5901ac3f56ae9ba34b0550b832c90fa1f

This is the append-only experiment ledger for the optimization arc after the first contained compiler campaign. The objective is faster generated Mach and native programs. Compiler time is not an acceptance criterion; compiler memory, generated-code size, actor memory, allocation traffic, and runtime metadata are recorded because the target range includes 4 MiB-class machines and may include a reduced GBA profile.

The restart context and earlier accepted/rejected work remain in compiler-optimizing-handoff.md and perf-campaign-2026-07.md. This file does not overwrite that evidence.

Fixed semantic constraints

  • A Pit call can suspend and its continuation must remain freezeable and relocating-GC-walkable. C-module execution is the exception while inside C.
  • Parameters and def are immutable; functions have at most four parameters.
  • Missing arguments null-fill and excess dynamic arguments disrupt.
  • Literal top-level use() imports are static, actor-local, and stone.
  • Records retain current mutation, deletion, enumeration, stone, C API, and serialization semantics. A dense shaped record converts to the existing generic representation if its key set changes; no shape-transition graph is planned in v1.
  • Profile evidence is a hint. Static proof remains authoritative, and honest dynamic cases require a guard and correct generic fallback.
  • No semantic mcode operation is added without designer review. Facts, lowering choices, and target-private Mach/native forms can be experimented with freely.

Arc sequence

On 2026-07-14 the designer authorized this complete campaign to proceed without intermediate approval. Implementation choices are made locally, successful mechanisms remain reviewable as separate commits, and every losing arm is deleted and recorded here. The ten work items are:

  1. Establish and finally repeat a composed, artifact-identified evidence matrix across micro, shootout, allocation, Mach, native, and comparison runtimes.
  2. Add compact persistent linked identities for units, module values, exports, functions, origins, and external/native providers; extend them to cross-unit effects, escape, pruning, and selective inlining.
  3. Bind literal use() exports and exact C leaves directly, measuring module lookup removal separately from calling-protocol specialization.
  4. Implement static dense record shapes v1 and measure access, construction, GC, instance size, conversion, serialization, and C API behavior.
  5. Build a compact structured value/origin/effect spine. It must not repeat the rejected record-per-node representation whose retained memory reached roughly 24x raw mcode and whose sampled compiler heap reached 512 MiB.
  6. Complete PGO identity, collection, bounded shop-local persistence, portable import/export, executable identity, and explicit CLI/build consumption.
  7. Consume static/profile facts for honest dynamic record layout sites.
  8. Consume static/profile facts for array receiver, length stability, bounds, and boxed element kind; an unboxed backing-store change remains a separate representation decision rather than an assumed part of this campaign.
  9. Specialize honest dynamic closure targets and experiment with a thinner freeze-safe activation/return convention, without retrying the rejected activation-frame cache, uniform four-slot arg region, check-only call opcode, or resolved-native-symbol-only arms.
  10. After shapes, sweep nursery sizes and remaining point-specific constant, type, carrier-slot, and lowering wins under the 4 MiB-class memory target.

Decision gates

PGO persistence and build integration

The schema and identity needs will be measured before choosing a user-facing storage policy. The eventual decision must cover:

  • whether profiles live beside the shop’s content-addressed executable cache, beside a package/project, or in an explicitly named portable file;
  • whether multiple runs merge automatically or only through an explicit command;
  • how an instrumented Mach executable is requested and how a subsequent Mach or cross-target native executable selects the profile;
  • staleness behavior when unit hashes, linked graph, compiler salt, target profile, or semantics change;
  • whether profile artifacts are reproducible build inputs and therefore part of executable identity;
  • bounded profile size, counter saturation, privacy, and deletion.

No persistence policy will be silently embedded in the compiler. Once the first semantic profile artifact exists, concrete alternatives and their CLI effects return to the designer for approval.

Proposed policy sent for designer review:

  • instrumented Mach automatically merges bounded, saturating counters into a shop-local profile keyed by linked semantic source identity and compiler semantic salt;
  • collection and consumption remain explicit and program-scoped: --pgo_collect realizes instrumented Mach, --pgo_auto consumes the exact matching local artifact, --pgo_from <file> consumes a portable artifact, and no flag produces an unprofiled reproducible build; these outer flags are removed before typed program arguments are packed;
  • the consumed profile content hash is part of Mach/native executable identity;
  • pit pgo status, export, import, and reset expose and control the otherwise automatic collection; this distinct namespace preserves the existing sampling/log profiler’s pit profile API;
  • stale source/compiler/link identities are ineligible, profile storage is capped, and old identities are evicted.

This separates actor-fleet self-improvement during development from hidden, nondeterministic release builds. --pgo_auto fails actionably when no exact eligible artifact exists rather than silently producing an unprofiled binary. Native build commands expose corresponding typed options for the program being built, not for the build actor. Status: approved for implementation.

Starting state

The starting branch already contains the accepted scalar/numeric, array, record-template, structured-value, call-correctness, Mach-fusion, and native suspension work indexed by compiler-optimizing-handoff.md. It does not contain dense record shapes, PGO, a linked semantic value graph, cross-module direct export calls, or a general thin dynamic-call convention.

The existing executable builder resolves a real module graph and records:

  • content-addressed unit hashes;
  • canonical module bindings and initialization order;
  • static versus dynamically loaded native-module providers;
  • executable content identity.

However, compilation consumes each unit before resolved canonical bindings and provider information are known, and the executable drops export-level meaning. Thus use('math/radians').sqrt, for example, becomes an actor-context module load, record property lookup, and generic dynamic call even though the linked executable knows the module provider exactly.

Trial log

T0: benchmark/node startup contamination

Before the first baseline, this worktree had a live stale daemon identity but no usable endpoint. Only the exact worktree process was identified and stopped; ./pit ps then booted normally. This is tooling contamination, not a language-performance result. Future matrices must begin with a confirmed warm listener and record whether a stale-process recovery occurred.

A later composed validation exposed a second, more expensive failure mode. record_shapes.cm accidentally declared a six-argument helper even though Pit functions are capped at four parameters. The shop logged the precise compile diagnostic, but ./pit ps did not return after listener bootstrap had become impossible: the daemon continued at roughly one CPU and compiled unrelated graph units, reaching a 256 MiB compiler heap and more than 1.3 GiB of sampled allocation traffic before the failure was noticed in observe.jsonl. Recovery was to stop only that exact worktree PID and repair the source.

Tooling proposal: a boot-critical realization failure must cancel the remaining build graph, terminate the not-ready daemon, and make the waiting CLI fail immediately with the first compiler diagnostic. Continuing expensive work after listener readiness is impossible wastes developer time and makes a syntax error look like a runtime hang. This is not charged to executable performance, but it materially slows the experiment loop.

T1: existing native_call row is not a linked-call benchmark

Command: ./pit aot_bench/vs.ce native_call

Result: disrupted with not a function before timing. The dual-lane harness tokenizes/folds/lowers the fixture as an isolated source string. It neither passes the executable builder’s resolved static bindings into the compiler nor installs the imported module in the benchmark execution context. Historical comments acknowledge the same limitation for nbody.

Disposition: repaired. The harness now declares its native dependency in its own linked executable and passes the fixture a real static binding plan. The generic linked baseline is therefore quotable:

armMach msnative msMach instructionsallocation KiBGC
ordinary linked module record22.549.797,500,04510

The row remains split into the following mechanisms:

  1. ordinary static module record + generic property lookup + generic C call;
  2. exact export binding while retaining the generic C call;
  3. exact export binding plus a specialized known-C call path.

This is required evidence infrastructure for linked optimization, not a benchmark-specific shortcut.

T2: cache exact statically used module exports in actor-local slots

The compiler records the main-scope members used from each literal use() import. Immediately after loading the immutable module value, main mcode guards it once as a record and caches each proven member in a normal slot. Existing function-proxy/record/null guards remain at the actual use; only the record-path property probe becomes a slot move. No semantic mcode operation was added, and no loader/runtime-context change is required.

The same-process A/B uses identical source, module, call protocol, seven-run medians, and result oracle:

armMach msnative msMach instructionsQBE IL lines
generic module-record property lookup22.549.797,500,0452,623
exact export cached in a normal slot19.278.736,000,0482,616
change-14.5%-10.8%-1,499,997-7

The fixture makes 500,000 calls, so the mechanism removes exactly three Mach instructions per call. Both lanes returned the independent golden value 235701906. Allocation remained 1 KiB with zero collections.

This is general for repeated direct reads or calls of a statically named export from an immutable literal use() binding; it is not keyed to sqrt or the benchmark loop. The retained target cost is one main-frame PitValue slot per main-used export (8 bytes now, 4 bytes under nan32), plus a one-time record guard/load. There is no actor-context property or executable metadata record. Nested-only uses are tracked separately and do not allocate useless ordinary main slots. A future nested arm can deliberately make the hidden slot captured and reuse the existing outer-frame get machinery, with the corresponding retained-frame cost measured separately.

Semantics retained by the first arm:

  • record exports, missing/null exports, and function-valued exports preserve the ordinary record path;
  • a module whose exported value is itself a function still takes the existing function-proxy path and ignores the null cache entry;
  • dynamic imports and dynamic/bracket member names remain generic;
  • the module object and normal binding remain present for identity, enumeration, and any honest dynamic use.

Targeted raw-mcode regression: there is exactly one module-property load at cache initialization and no repeated hot-path load. Requirements regressions also distinguish use scope from definition scope, exclude a shadowing nested parameter, union duplicate import aliases, and exclude nested-only uses from main cache slots. tests/compile.cm passes 127/127.

The completed nested arm publishes all identity-checked statically used exports from the real link plan, then lets mcode choose a normal or captured cache location. Duplicate aliases of one request/member share one hidden cache and one initialization. Nested uses read it through the existing outer-frame get, preserving the ordinary GC/freeze/closure machinery:

500,000-call casegenericcachedchange
direct Mach, integrated16.81 ms13.90 ms-17.3%
direct native, integrated10.87 ms9.14 ms-15.9%
direct Mach instructions7,500,0456,000,048-20.0%
nested Mach, four-run aggregate median24.96 ms24.39 ms-2.3% (noisy)
nested native, four-run aggregate median17.665 ms15.855 ms-10.3%
nested Mach instructions9,000,0468,000,049-11.1%

The measured nested main frame grew 22 to 23 total slots while its retained closure prefix stayed at four because compression filled an existing prefix gap. The worst case is one additional retained PitValue per nested-used export. Allocation remained 1 KiB/zero GC. Full validation was 129/129 compiler, 8/8 runtime semantics, 1/1 real nested link-plan, and 1,919/1,919 full tests. A fresh-build test twice observed executable completion before its cached policy decision was visible; the later full run passed. Tooling should make completion/cache-hit delivery atomic with policy-cache publication rather than require immediate filesystem visibility.

Bootstrap migration trial

The discarded actor-context version exposed a two-generation bootstrap seam: an old resident builder compiled application modules that expected new context keys while the newly packed loader had already removed their publisher. Restoring a known-old seed was initially insufficient because .pit/objects retained content-addressed artifacts from the incompatible intermediate compiler generation. Clearing .pit/build, .pit/cache, and .pit/realize did not clear those objects. Recovery required clearing .pit/objects, then running two seeds: generation one installed the new compiler while emitting old-style applications; generation two recompiled applications with the new compiler salt. The final local-cache design needs no compatibility path and boots strictly.

Tooling proposal: make seed should provide an explicit two-generation mode for compiler-output/schema changes and report which compiler salt produced each boot unit. Its clean-recovery command must include .pit/objects, or offer a compiler-artifact-only purge, because the current documented cache purge can leave a worktree permanently reusing incompatible units.

B0: composed starting baseline

Command, after the T0 listener recovery:

./pit aot_bench/vs.ce call_hot closure array_read record_field record_new \
  mandelbrot fannkuch spectralnorm binarytrees

Seven timed samples per lane, median reported:

benchmarkMach msnative msMach instructionsallocation KiBGC
call_hot14.518.1218,000,04210
closure53.9826.6638,000,08820
array_read23.4023.9727,021,048170
record_field109.8953.3082,000,37520
record_new33.0118.2710,505,17042,20465
mandelbrot91.599.8136,900,64510
fannkuch221.37107.44168,026,47610
spectralnorm177.1046.01121,886,482250
binarytrees83.4752.6822,241,52252,70534

Every result matched across lanes and every checked golden oracle passed. Native measurement-thunk instruction counts are the fixed 32-instruction observer wrapper and are not target instruction counts. Allocation/GC values shown are the Mach measured run; native allocation was equal within 0-11 KiB on these rows.

The baseline confirms the next targets rather than a scalar regression:

  • native scalar code is already strong on mandelbrot;
  • exact array access remains expensive enough that native array_read is slightly slower than Mach;
  • generic record access remains the clearest dispatch/helper problem;
  • record construction and binary trees remain dominated by 42-53 MiB of allocation traffic despite existing capacity and template work;
  • dynamic captured closure calls remain much slower than resolved calls;
  • fannkuch and spectralnorm retain large Mach dispatch and native materialization/call/control costs after the accepted numeric work.

T3: invoke synchronous C leaves without a temporary Mach frame

MACH_CALL previously treated a C leaf like a Pit activation: allocate and initialize a temporary frame, copy the named caller slots into it, then have the shared invoke tail copy those values into rooted C-call storage and release the frame before entering C. A C leaf is synchronous and cannot retain or freeze a Pit activation. The retained arm therefore reads the existing MACH_CALLARGS payload directly from the caller, copies those values into a rooted scratch array, and enters the existing C/C_DATA call machinery. It adds no opcode and changes no semantic or Mach instruction count.

Fresh candidate samples against the previously recorded same-source arms:

linked export armold Mach msnew Mach mschangeMach instructions
generic module record21.4315.40-28.1%7,500,045
exact cached export18.2912.14-33.6%6,000,048

Candidate samples were 15.72/15.19/15.40 ms and 12.05/12.14/12.14 ms respectively. Native remained approximately 8-10 ms, as expected from a Mach-only protocol change. The shipped runtime’s padded Mach-O __TEXT segment stayed at 1,212,416 bytes, while an equal-flag object comparison attributes 992 bytes of real machine text, 27 bytes of C strings, and 64 bytes of compact-unwind data to the arm. make, a VALIDATE_GC build, 56/56 focused Mach tests, and 1,919/1,919 full tests passed. New coverage disrupts inside a C module call, forces a moving GC, then proves the caller and a subsequent C call remain usable; existing allocating C-call and C-callback-into-Mach coverage also passed.

The win applies to all synchronous C and C_DATA values reached through an ordinary Mach call, whether or not the compiler knows their identity. It is therefore a general call-protocol improvement, not an export or sqrt specialization. The remaining C entry still performs its own arity-padding copy/root because the public C ABI hands callees stable argument pointers. That second copy is a possible later target, but is intentionally outside this arm.

The analogous native helper already knows the runtime function kind before its general fallback. Entering the existing C dispatcher directly instead of re-entering Pit_CallInternal produced a smaller but general win:

500,000-call native caseoldcandidatechange
generic linked C export, first arm9.57 ms9.16 ms-4.3%
cached linked C export, first arm8.25 ms7.88 ms-4.5%
C export behind Pit closure, generic15.25 ms15.51 ms+1.7% (comparison tax/noise)
C export behind Pit closure, cached14.38 ms14.53 ms+1.0% (comparison tax/noise)

All results matched at 1 KiB/zero GC. Actual runtime __text was 60 bytes smaller and the mapped segment was unchanged. make check and the subsequent 1,919/1,919 full suite passed. The retained follow-up moved the C/C_DATA test after the existing native-kind arm so ordinary native functions preserve their exact previous first comparison. Three-process median-of-seven controls were within noise: call0_plain -0.8%, closure +0.2%, and call_hot +1.7%. The reordered C path retained -3.5% on generic linked C calls and -1.8% on cached linked C calls; its helper object was 64 text bytes smaller. Focused 56/56 Mach, 1,086/1,086 VM, and 1,919/1,919 full tests passed. True symbol-level export calls remain a separate project: the current linker knows module initializer symbols, while individual C export functions are commonly static and have no provider manifest identity.

T4: static dense-record shape gate (audit)

The current record is an inline open-addressed hash table with a 32-byte base and 16 bytes per hash slot. Existing right-sizing and native templates improve capacity choice and construction, but every instance still repeats its keys and slack: one/two fields are 80 bytes, three through five are 144 bytes, and six through eleven are 272 bytes.

The proposed v1 keeps OBJ_RECORD and uses its record-unused I flag to mark a dense instance. Its aligned immutable descriptor pointer divided by eight is encoded in the existing 56-bit header payload; the unchanged 32-byte record base is followed by one PitValue per field. This avoids an extra per-instance shape word and yields 40/48/56/64/72/80 bytes for one through six fields. The descriptor is shared code/runtime-constant data. A future nan32 profile can replace the isolated pointer encoding with a compact shape ID.

Existing-key writes stay dense. Addition or deletion converts through the current allocation-and-forwarding mechanism to the ordinary hash record, preserving aliases, record identity, class/opaque metadata, stone behavior, enumeration, and C API semantics. The first falsification should accept only unique compile-time immediate-ASCII keys with proven non-null construction values, matching the existing native-template safety envelope.

Predicted instance-only savings are 61% for the three-field record_new fixture (144 to 56 bytes, about 26.4 MB over 300,000 iterations) and 40% for two-field binary-tree nodes (80 to 48 bytes). Retention gates are at least 15% Mach/10% native on record_new, material improvement in both binarytrees lanes, at least 40% lower relevant allocation traffic, and no more than 2% regression in generic record_field. Shapes alone do not give that dynamic eight-layout benchmark a direct offset; static finite-shape flow or PGO still must supply the guarded descriptor/offset specialization.

Designer ruling and implemented scope: the header/I-bit scheme is accepted. Every compile-time constant text key is eligible regardless of length or Unicode content, except a text containing U+0000. Such a record remains on the generic representation because the existing native and Mach descriptor bridges are C-string based. Widening both bridges to length+pointer pairs would add eight bytes per descriptor key for a case with no demonstrated workload; this is a shape-eligibility restriction, not a record-semantic restriction.

The provisional implementation now includes runtime-local interned immutable descriptors, dense construction, direct proven loads in QBE and Mach, one-way add/delete conversion, property/C APIs, GC scanning and forwarding, actor memory diagnostics, and Nota/Wota support. The first controlled median-of- seven A/B is:

benchmarkshapes off Mach/nativeshapes on Mach/nativeallocationGCdisposition
record_new32.04 / 22.70 ms20.39 / 13.78 ms42,204 -> 16,423 KiB61 -> 21provisional win
binarytrees85.06 / 54.34 ms75.24 / 47.12 ms52,707 -> 42,241 KiB38 -> 27provisional win
generic record_field124.09 / 55.57 ms112.19 / 52.78 msunchanged-small0no regression

Thus record_new improved 36.4% in Mach and 39.3% native while allocation fell 61.1% and collections fell 65.6%. binarytrees improved 11.5% Mach and 13.3% native with 19.9% less allocation. Shape selection removed 300,000 Mach operations (2.9%) in record_new; the larger gains are predominantly the denser allocation and direct initialized layout rather than an instruction count trick.

These numbers are not yet retained evidence. The heavy record_template shape-to-generic insertion/deletion stress currently returns the correct value in Mach but crashes generated native code after repeated collections. Earlier audit defects in re-entrant Nota/Wota enumeration, post-escape direct-load facts, Mach class metadata, nursery diagnostics, and root-pop order have been fixed or fenced. The native conversion/GC failure, full suite, fuzz/GC stress, and final generated-code audit remain gates before this trial can land.

The stress investigation found a general native correctness defect below the shape optimization. A growing generic record, or a shaped record converted to generic form, can leave the source PitValue pointing at an OBJ_FORWARD stub. Native bare stone previously ORed the stone bit into whichever header that stale value named. The stone bit overlaps a low bit of the forwarding payload, so this corrupted the encoded destination pointer. The corrected lowering chases the complete forwarding chain, stones the live object, and writes the resolved identity back to the destination slot, matching Mach/runtime semantics. This needs a shapes-disabled grow-then-stone native regression so the correctness repair remains independently reviewable even if dense shapes are later rejected.

That gate now passes. The corrected native shape/conversion stress completed 30/30 invocations with exact result 853729. Focused current-tip validation is compiler 130/130, Mach 57/57, Nota 40/40, Wota 33/33, the native record-capacity fixture, and three cross-lane AOT regressions: generic forward/stone 71250, record-template conversion 853729, and suspended conversion 813729. A diagnostic-disabled daemon then ran the deliberately never-shape-eligible native_stone_forward fixture ({} followed by dynamic insertion) with exact 71250 in both lanes: 16.35 ms Mach, 9.46 ms native, 14,454 KiB allocation, and 43 collections. This proves the forwarding/stone fix on the old generic representation independently of shaped construction. Full/fuzz/nursery and the final repeated performance matrix remain outstanding.

The subsequent warmed default suite passed 1,921/1,921 on the composed current tree. The apparent simultaneous pit test/fuzzer process pair was not two active workloads: the daemon retained the argv of the command that started it, while the completed full-suite client had already exited and the fuzzer was the sole active request. Timing remains deferred until fuzz exits and the node is quiescent.

Deterministic differential fuzz then passed 3,733/3,733 checks at seed 20260713. Production nursery-on (PIT_GC_NURSERY=1, verifier off) reached listener readiness in about eight seconds; its targeted record/GC stress is the remaining production nursery gate. This separates ordinary nursery behavior from the intentionally expensive whole-heap verifier startup that failed to reach a listener inside 80 seconds.

The production nursery gate then passed all three exact record rows (native_stone_forward 71250, record_template 853729, suspended conversion 813729) across 21 timed Mach/native executions. Shape stress collected 36-40 times in Mach and 28 times in native. The isolated native PIT_GC_NURSERY=1 PIT_GC_NURSERY_VERIFY=1 ./build/record_capacity_test also exited zero, so verifier correctness is covered without charging its whole-shop instrumentation cost to listener readiness.

The first quiet final A/B repeats the material wins:

benchmarkshapes enabled Mach/nativedisabled Mach/nativeallocationGC
record_new21.23 / 14.60 ms33.49 / 22.49 ms16,423 vs 42,204 KiB21 vs 61
binarytrees79.47 / 51.30 ms85.70 / 55.86 ms42,241 vs 52,705 KiB27 vs 36

A deliberately conversion-heavy stress exposed the opposite tradeoff: record_template immediately deletes/adds fields on every candidate, so constructing dense and then converting regressed about 8% Mach, 3% native, and 17.5% allocation. This is not an acceptable universal tax. The selector is being tightened to reject directly visible immediate structural mutation; final numbers must be rerun after that conservative policy lands.

The tightened selector did remove the dense-to-generic tax: the stress emitted the same 6,680,746 Mach operations, 8,700 native IL lines, roughly 26,722 KiB, and 30 collections with the shape pass enabled or disabled. Its timings were 35.81/31.03 versus 35.50/29.64 ms and are same-code noise. However, generated- code audit then found that this was not yet regression-free. The diagnostic pass switch also removed the predecessor native ordinary-record template, because the shape implementation had replaced that analyzer and descriptor rather than composing with it. The true composed B0 already contained those templates (record_new 18.27 ms native, binarytrees 52.68 ms), and the older template report measured the conversion stress at about 26.24 ms native. Therefore the shape/no-shape native percentages overstate the dense-only gain, and a vetoed candidate can currently lose the old generic construction win.

Disposition remains open pending a required three-arm result: no construction optimization, the pre-existing ordinary preseeded template, and dense shape. Dense-veto candidates must retain the ordinary template when its independent safety proof qualifies. This is a composition defect, not evidence against the dense representation; Mach had no predecessor native template and its shape measurements remain correctly attributed.

The follow-up correctness audit added three non-negotiable gates. Pit uses null as property deletion, so dense construction may bypass the setter only with an exact point-local non-null initializer proof; slot-global _write_types is not sufficient after physical-slot reuse. The runtime interner must store the chased direct runtime-constant text value, never an accepted but forwarded actor-text handle in an immortal descriptor. Finally, Nota/Wota’s re-entrant value snapshot must handle allocation failure and report its bounded transient bytes rather than dereferencing an unchecked allocation. Moved-null, runtime-null, forwarded-key, and low-memory encoder fixtures belong to the final shape gate.

Validation also exposed a shared-worktree tooling hazard: a full compact-spine compiler-corpus probe consumed a CPU while the nursery-verification shop was booting, making listener readiness indistinguishable from a runtime hang. The probe was stopped by exact PID and the agents now serialize high-CPU corpus measurement with daemon/runtime validation. Future benchmark tooling should offer an explicit isolated-process/shop mode for compiler probes.

T5: PGO artifact and build spine (provisional)

The first implementation deliberately separates semantic identity from target instrumentation. A profile names the exact linked semantic graph and compiler salt; a site is {unit_hash, function, final_semantic_pc, kind}. Observation and target counts are unsigned-32 saturating, profiles contain at most 4,096 sites and four targets per site, and canonical ordering/content hashing makes the portable JSON a reproducible build input. Static proof is never replaced by this evidence.

Shop persistence uses immutable content-addressed profile and index objects behind one atomic pgo:index:v1 catalog replacement. There is no second per-program mutable pointer: a crash before the index rename leaves the old complete generation authoritative and merely orphans the newly written objects. The index retains at most 64 semantic profiles, 4 MiB per profile, 16 MiB total, and 256 KiB of index metadata, evicting oldest generations with deterministic semantic-hash ties. Imported profiles are also immutable objects so a cached cross-target native build can reload the exact bytes its executable identity names.

pit pgo status supplies an explicit count/byte summary and a sorted table of exact semantic, content, and object identities. The first command-path probe successfully completed the shop round trip but left presentation to the generic record renderer and printed nothing useful. Raw status fields remain in the reply, so --json retains the machine-readable store response.

The user-facing grammar is program-scoped and unambiguous:

pit --pgo_collect program.ce args...
pit --pgo_auto program.ce args...
pit --pgo_from profile.json program.ce args...
pit pgo status|export|import|reset ...

Outer flags are consumed only before the program token. The executable cache retains only {semantic hash, compiler salt, profile content/object hashes}. An early version also embedded the full linked semantic graph in every executable; the dirty regenerated firmware grew from about 54.5 MiB to 78.3 MiB and boot.qop from 61.0 MiB to 85.4 MiB. That representation was immediately rejected and deleted. The graph is now transient link-time data; a clean seed must confirm the artifact size returns to its ordinary range.

The content-store atomic-write audit also found that its pre-existing fixed .tmp object name was unsafe under concurrent identical-key compiler workers. Object and catalog publication now use cryptographically unique sibling temp names followed by one rename. Last complete rename wins and no reader can see partial bytes. Concurrency and failure-before-index regressions remain part of the final gate.

The same rule now applies to portable profile export; a fixed profile.json.tmp would have let concurrent export commands truncate or move one another’s staged file. A four-actor stress fixture concurrently replaces one catalog key and exports one profile, then accepts only complete catalog hashes and valid final profile JSON. Execution awaits the current seed, but the fixture and all touched modules parser-check.

A CLI plumbing audit found a real integration hole before runtime testing: the terminal parsed and attached opts.pgo, but engine-lite $start discarded that option before shop realization. It now forwards the typed PGO request and validates the accepted modes. This is why CLI tests must verify the final executable request rather than stopping at argument parsing.

The provisional collector memory contract is collection-only and lazy. An ordinary or consuming runtime keeps a null accumulator pointer and no counter table. Instrumented code identifies only selected sites; observed rows use compact integer code tokens and saturating u32 counts. The initial generous schema bound is about 192 KiB for 4,096 fully populated top-four-target sites, plus sparse-table overhead, per runtime rather than per actor. That worst case is not acceptable as a preallocation or as an implicit small-console cost: allocation must scale with actual selected/observed sites, real workload bytes must be measured, and constrained target profiles must explicitly compile collection out or impose a much smaller cap. Consumption may retain only the guard metadata for sites it actually specializes.

The first shared-collector scaffold was rejected in review before Mach use: it grew/freed rows and updated counts without synchronization, and compared target identity strings on every call despite being shared across concurrently scheduled actors. The replacement hot path uses a nonzero u32 runtime code token, one lazily CAS-installed fixed top-four row per active site, relaxed saturating atomic counters, and CAS insertion into empty target slots. It has no row growth/free race, string comparison, or per-call mutex. On this arm64 host atomic_uint is four bytes and uint/pointer atomics report always lock-free; targets without lock-free atomics must compile collection out.

After adding one shared-budget pointer/state to each collection-only table, measured exact requested memory is 72 bytes for one selected-but-idle site, 40 additional bytes when it first executes (112-byte peak), and 200 bytes for 17 selected-but-idle sites. Eight native threads each recorded 50,000 calls: 400,000 exact executions, four unique target tokens, 100,000 per token, and no drops. A collection-disabled build passes and allocates nothing. Export/free are explicitly quiescent-only.

Status: the current seed is green. The portable profile contract passes 12/12 and the store passes 8/8, including a real immutable profile/index commit and exact reload, object reload, caps, eviction, corruption, failure-before-authority, and atomic replacement. The production executable path also passes its focused from-file/cache/native-handoff regression 1/1: three exact eval executables compile/link, the second consumed-profile build hits the executable cache, the immutable profile identity survives hydration, and the native handoff reloads that exact immutable input. Collection counters exist, but there is still no credited consumer speedup. Profile-consuming builds therefore emit ordinary generic calls with no call-site PGO metadata; stored observations remain available for a future consumer that passes its runtime and target-memory gate.

Runtime review then tightened the collection lifecycle. Snapshotting uses a short in-memory barrier to copy one exact interval and reset live counters; observers wait/retry across that boundary and immediately enter the next interval while persistence proceeds. The detached interval remains a generation-stamped pending snapshot until both the immutable profile write and authoritative index replacement succeed. Failed materialization/persistence keeps that same pending interval for byte-identical retry without blocking or dropping next-interval calls. One runtime-wide atomic byte budget is shared by all simultaneously collected semantic sessions and charges session/code/site descriptor strings, proportional collector spines, lazy rows, and the pending raw interval. Thus multiple code units or executable identities cannot each multiply the 256 KiB host / 16 KiB Playdate cap. --pgo_collect is rejected with detached or native execution in v1 because neither path has an unambiguous completion capture (and QBE does not yet emit collectors). Native profile consumption receives no credit until a real QBE consumer changes code and wins. The initial pre-call counter also observes only immediate non-function/over-arity failure, not a later callee disruption across suspension; that portable fact is named immediate_failures until a continuation-complete observation exists.

The first seed attempt also exposed a correctness defect in the new Mach lowerer’s GC-root cleanup. mcode_lower_func_node pushed roots in the order fobj, PGO calls, instructions, checked-store metadata, then shape metadata, but both success and failure cleanup popped PGO calls before instructions. Dense-shape/Wota compilation merely made the pre-existing bad cleanup path collect often enough to reveal the LIFO violation; they were not the cause. Both paths now pop shapes, checked-store metadata, instructions, PGO calls, then fobj, exactly reversing the push order. Any benchmark or runtime result captured from this PGO/Mach arm before that fix is invalid and receives no performance credit.

Seed validation also exposed two language/tooling contract failures rather than allowing them to become runtime mysteries. New PGO helpers had five parameters even though Pit functions accept at most four; both APIs now take one typed options record ({mode, profile, identity}), and a whole-tree scan finds no other Pit definition over the limit. Separately, the builder warm-up path used to ignore failed/malformed batch replies and per-unit failures, and JSON/cache-write disruption could escape the shop actor without naming the unit. It now validates exact result cardinality and every row, reports the source locator plus diagnostics, aborts the graph build, and settles its callback exactly once.

The native-handoff regression found one legitimate eval/cached-executable gap. compile_native_ir reread src_locator solely to salt its native cache key, so an exact cached eval executable failed with could not read -e even though its mcode and profile were complete. Executable-native lowering now passes the linked semantic hash as source_identity; the cache key still independently hashes the emitted IL. Ordinary direct callers that provide no identity retain the source-read behavior. This avoids embedding eval source, allows a cached executable to remain native-compilable after source removal, and passed tests/pgo_executable.ce after a clean seed.

T6: compact linked value/origin/effect spine (Gate 1)

The existing active QBE raw-value lane already constructs the large record-oriented structured view. A new isolated projection tests whether its durable cross-unit information can instead live in packed tables. It records canonical unit/function/origin identities, operations and effects, CFG edges, dominance, loops, phis, SSA values/operands, and fail-closed cross-unit links in eleven blobs. It is not yet imported by a live pass and retains no copy of semantic mcode or the record view.

Four focused round-trip/API tests pass. Initial retained measurements are:

compiler unitraw mcodeold record viewpacked spinepacked/rawpack-only allocation/time
tokenize.cm1.52 MiB33.33 MiB1.44 MiB0.95x12.37 MiB / 61 ms
fold.cm2.64 MiB59.24 MiB2.65 MiB1.00x20.03 MiB / 98 ms
parse.cm4.59 MiB104.74 MiB4.19 MiB0.91x35.39 MiB / 181 ms

This passes the retained-layout falsification and is roughly 22-25 times smaller than the record view. It does not yet solve temporary peak memory: the measurement deliberately projects the already-built active view. A build_views seam avoids constructing that view twice, but final corpus, incremental peak, and an executable-performance consumer are still required. The first consumer will target cross-unit exact Pit callees/effects and feed the existing resolver/inliner fact rather than duplicating the already-retained export-cache optimization.

That first isolated consumer is now concrete and fail-closed. It recognizes a straight-line provider module initializer that returns a record containing one exact capture-free exported function, accepts only an explicit positive schema of small pure mcode operations, and splices the leaf into a linked caller while retaining caller/provider provenance. Unknown controls, handlers, calls, captures, heap/cell effects, unit-relative constants, malformed instruction tails, and leaves over 24 operations reject. Compact-spine tests pass 4/4 and cross-unit proof/consumer tests pass 4/4.

The provider half is proven from a separately compiled real optimized module. The compiler-side importer descriptor seam is now wired: direct calls through a statically cached use export retain an instruction-external fact with final consumer function/PC, provider request/unit/export, cache slot/level/callee slot, and inline context. It survives insertion, local inlining/cloning, nop compaction, slot compression, and canonical location stripping; focused provenance/serialization tests pass 4/4. An isolated pairer joins that real consumer fact to the real provider proof; pair/tamper/arithmetic/code-size tests pass 4/4. Shop/link orchestration is intentionally not wired while PGO owns those files.

The timing probe still supplies an explicit synthetic exact endpoint over an equivalent array-hidden local closure; it measures the generated-code value of exact-callee splicing, not a completed use() executable pipeline. It invoked the constant-return leaf two million times with exact result 7 in both arms. The first Mach-only run fell from 25.671 ms to 12.772 ms (-50.25%); the full repeated dual-lane run measured 25.369 to 14.672 ms Mach (-42.16%) and 23.407 to 3.907 ms native (-83.31%). This removes the call/frame protocol even though the literal splice grows main semantic mcode from 24 to 29 instructions and the Mach blob from 2,352 to 2,496 bits due to retained labels. Native QBE output grew 27,609 to 28,353 bytes and assembly 25,684 to 26,109 bytes (+425 bytes, about 1.65%). It proves the linked consumer can move executable performance. A third stats run measured Mach 26.342 to 13.923 ms (-47.14%) and native 22.906 to 5.387 ms (-76.48%); baseline/candidate allocation was 720/728 bytes in five allocations with zero GC, so the conservative splice cost one 8-byte live top-frame slot, not per-call memory. The real generated importer/provider constant pair grew finalized main mcode 41 to 46 and Mach 3,408 to 3,536 bits (+3.76%).

The remaining gates are explicit. Generic exported arithmetic still retains dynamic type fallback/context/log/disruption and rejects until linked argument type proof can make that cold path unreachable (or a full semantic splice is separately approved). Record-form canonical provenance costs 173-175 bytes per fact in JSON; a conservative upper-bound boot model measured 0 bytes for engine_lite, 23,274 bytes/113 facts for shop_actor, and 604 bytes/3 facts for policy. Production must encode the live subset into compact transient linker tables and strip it before target artifacts. A composed executable workload must pass before production integration.

That metadata gate now passes in the live compiler seam. Canonical target mcode strips _linked_call_origins; a unit wrapper carries eight logical u32 words per fact, a deduplicated provider-text table, and five words per inline context. The cache is currently JSON, so the words are a flat PitValue array: 64 raw payload bytes/fact on the 64-bit ABI (64/640/6,400 bytes for the 1/10/100-fact fixtures), plus array headers. Packed binary artifacts can store the identical rows as 32/320/3,200 bytes, but placing raw blobs in the JSON artifact is invalid and was rejected after it disrupted seed serialization. The shared provider text table remained 25 bytes, and stripped-unit/provider pairing passes 5/5. This was experiment-local evidence, not a retained optimization. After the pure-leaf production gate failed, an artifact audit found no remaining production consumer for _linked_call_origins or link.linked_calls. Their frontend stamping, optimizer transport, packed sidecar, proof/pairer code, tests, probes, and fixtures were deleted. Static export caching is independent and remains enabled. Inspection of a real exported add(a,b) explains its earlier rejection: the fully generic body is about 170 raw / 156 streamlined operations because it also implements text concatenation and type-error construction, while its numeric arm is only two guards, two branches, one add, and one join jump. A flat-instruction-shape matcher for that arm was tried, rejected at 4/10 focused tests, and deleted. It could not recognize the compiler’s own safe current shape without making correctness depend on incidental frontend lowering, and therefore never reached a timing gate. The next attempt must clone/specialize the exact linked semantic function using proven int/float parameters, then run normal streamline so unreachable text/error arms disappear. Unproven, nullable, mixed, and text callers keep the complete original body.

That semantic-clone attempt was implemented and rejected rather than carried as dormant code. The exact-call sidecar temporarily packed one point-local numeric signature per site (final type/constant spans only; never the unsafe slot-global write type). The provider function family was privately cloned, the selected clone received immutable parameter facts, ordinary streamline ran over it, and a compact-spine zero-effect/24-op gate admitted only a pure residual. The residual was then materialized with the existing function operation immediately before the exact call and the whole caller was sent through ordinary streamline; no backend rule or new semantic operation was added.

The focused gate remained 2/5 passed, 3/5 failed after a principled metadata repair. Text/mixed callers and malformed signatures correctly failed closed, but the real integer leaf did not reach the bounded specialized residual and the real float leaf remained a call because the existing inliner did not consume the materialized clone. Admitting exact integer constant spans and normalizing stale closure/handler summaries after dead cold arms did not change that result. The agreed stop rule forbade falling back to another custom mcode splicer, so the clone module, tests, and temporary signature extension were deleted. No benchmark number is attributed to this attempt. The durable conclusion is that generic cross-unit arithmetic needs a first-class linked semantic function graph and integration point which owns specialization, function-family identity, closure/handler summary recomputation, and direct call/inlining—not another matcher layered onto finalized flat mcode.

A first production integration slice was then attempted and rejected under that same stop rule. It built a transient packed whole-executable view from real finalized units, exact export edges, SSA value identities, parameter facts, effects, and origin/inline-context rows. Canonical units were immutable; eligible pure leaves were appended only to a private derived consumer, exposed through the existing function operation, and handed to ordinary streamline. The graph tables and exact-edge tests were viable, but the existing inliner left the now-dead closure literal after consuming its call. A proposed generic cleanup counted final slot reads and removed a function literal with zero reads. That criterion is not sound in the current slot/capture model: the targeted graph run reported four pass results plus four disruptions, and the compiler suite changed from 135 pass/0 disruptions to 136 pass/89 disruptions. The cleanup, production graph transform, link-stage integration, and its parameter-table extension were immediately deleted. No benchmark result is credited.

This narrows the durable requirement further. A real linked semantic graph cannot be merely a packed observer followed by re-materialization into the old closure/inliner protocol. It must own stable value lifetimes across slot reuse, explicit closure/cell capture, function-family identity, direct-call edges, and the final specialized body before flat slot mcode is regenerated. The compact research spine, exact stripped-unit origins, and provider pairing remain useful evidence and infrastructure, but are not a production optimizer.

T7: first PGO consumers and falsification targets

Collection by itself receives no performance credit. The first consumers are chosen to distinguish three different kinds of honest dynamic evidence rather than treating every observed PitValue as one generic “type”:

  1. closure has eight distinct closure objects but all eight are created from the same function body. A useful call profile therefore records the stable code identity, not the moving closure/object pointer. A monomorphic-code guard can preserve each closure’s distinct outer frame while selecting one known entry/layout. The acceptance gate is a material improvement in both Mach and native at the existing single dynamic call site, with the same 2,000,000 call oracle, no per-actor profile table, and bounded metadata per selected site. Merely skipping function/arity checks, retrying the rejected resolved-symbol helper, or caching activation frames is not a new mechanism and will be deleted if rediscovered.
  2. record_field presents eight layouts through one site. A record profile records stable runtime-local shape identity plus field offset, never an instance pointer. The current top-four target bound can cover only half of this deliberately uniform fixture, so the first PIC arm must report hit rate and metadata bytes. Static finite-shape flow may prove all eight more cheaply; PGO remains the fallback for shapes arriving through messages or endowments. Every miss and every generic/non-shaped record uses the exact existing helper.
  3. array_read reuses one runtime array whose pointer cannot be a portable PGO identity. Its profile schema instead needs receiver kind, integer-index rate, in-bounds rate, observed length stability/range, and element numeric kind. A guarded consumer can remove redundant receiver/index branches or keep a loaded element raw, but cannot assume the backing store is unboxed. Static exact-array work already proved that cloning the full native control diamond is a ROM-heavy non-win; this arm must reuse compact shared lowering and beat that rejected result.

These are general site properties. No source locator or benchmark name is an optimization input. Portable profiles contain semantic identities and portable target facts; process-local code/shape tokens are resolved only while loading an exact executable.

T7a: guarded record-field load PIC (implementation awaiting timing)

The first honest-dynamic record consumer is implemented for loads only. Stores remain out of scope until the load arm passes its performance gate. Final semantic constant-text load sites are selected only when the static dense-shape facts do not already prove an exact offset. Collection records one execution for every selected site and a target only when the forwarded receiver is a shaped record containing that key. The target is a runtime-local shape token while counters are live; snapshot converts it to RSF1: the complete ordered RS1 UTF-8 key sequence plus one u8 field offset. No pointer enters the portable profile.

Consumption uses up to the bounded top four targets. Mach emits a private three-word region retaining the original loadk + load and one descriptor ordinal. Native interns/deduplicates the same portable key list through the existing AOTRecordTemplate registry and primes each unique selected descriptor once at module entry. Both hot paths perform exactly: chase forwarding, require record, require the I/shaped bit, compare the exact runtime descriptor pointer, then load record + 32 + offset*8. Generic records, unmatched shapes, missing fields, arrays, texts, null, and all other values reach the existing literal-load fallback. Profile evidence is therefore never correctness proof.

Memory contracts:

  • records gain zero bytes;
  • PitRecordShape.reserved becomes a 32-bit atomic observation token without changing descriptor size;
  • native selected shapes reuse the deduplicated 16 + 8*key_count descriptor already used by dense construction, with no separate PIC table;
  • Mach retains one runtime descriptor row per selected site, up to four shape pointers and four offsets; exact byte and code-growth measurements are required with the timing result;
  • portable profiles retain up to 4,096 rows, while one linked collection build deterministically selects at most 512 live Mach sites (entry program first, then canonical module order) under the target-specific byte cap;
  • live descriptors retain the per-code identity once and only numeric semantic pc/kind per site. Portable site JSON is reconstructed during snapshot instead of being repeated in every loaded descriptor.

The serialization marker is deliberately bumped to PGO4; this is an unreleased cache format and no compatibility fallback is retained. If the bounded runtime collector still cannot attach because of allocation pressure, the instrumented operations execute their ordinary semantics and emit an actionable collection- disabled error; profiling metadata must never make valid code unloadable.

The failure-producing ps artifact selected 1,519 sites. PGO3 repeated 204,263 bytes of portable site JSON in its serialized Mach blobs and retained 205,782 bytes including C terminators at runtime, before descriptor structs or collector storage. PGO4 removes those bytes entirely. Its 512-site linked cap also removes 1,007 64-byte runtime descriptor rows and their collector-spine pointers from that collection image. Post-build runtime high-water remains a required verification measurement rather than an estimate.

T8: call-layout experiment boundary

The first production monomorphic-code consumer has been rejected and deleted. It left the actual closure object in the ordinary activation path but lazily resolved the profile’s portable code identity to a process-local PitCodeRegister *, guarded that pointer at each call_mono, and skipped only the generic function-kind and arity checks on a hit. The exact two-million-call pgo_closure fixture retains eight different closure environments from one code body and returned the independent oracle 245453 in every arm.

Three fresh-process baseline medians were 57.9998, 58.7680, and 59.2050 ms (median-of-medians 58.768 ms). Collection took 67.996 ms, saved nine sites, and reported 75,158 bytes peak collector memory: about 15.7% runtime overhead for this deliberately call-dense run. Three consume medians were 59.2410, 59.5727, and 59.0670 ms (median-of-medians 59.241 ms), 0.8% slower than baseline and therefore far below the required 5% improvement. The pointer guard, metadata lookup, and atomic expected-target load cost more than the two checks they replaced, and the arm did not address the measured mach_resolve_outer_frame closure-environment cost. The entire call_mono opcode, consume descriptor, expected-pointer state, and consumer-only fallback fixture were removed. Portable collection, persistence, exact executable binding, and the ordinary generic call path remain; --pgo_auto currently adds no call-site target metadata or runtime overhead. Do not repeat a check-only monomorphic call consumer. The next call experiment must attack outer-frame resolution or the freezeable continuation/activation protocol itself.

The remaining closure cost is not primarily the dynamic function predicate. Current native stack-safe calls still create a normal GC-walkable frame, copy and null-fill its live slots, activate an AOT root, charge the reduction counter, enter the target, refresh the caller, and unwind or preserve the frame on suspension. Mach pays the analogous frame/PC switch and outer-frame work.

The next materially different call experiment may use a compact selected activation layout, but it must remain an ordinary freezeable GC object while live. At a minimum it must retain the selected closure (and therefore its outer environment), precisely identify live tagged slots, preserve a caller continuation when the reduction charge suspends, survive moving GC, disruption, recursion, and mixed Mach/native crossings, and fall back for unknown layouts. The experiment will report bytes per live activation as well as ns/call. It must not reintroduce the rejected prepared-frame cache, four-slot uniform arg region, check-only call opcode, or non-GC scalar continuation stack.

T9: exact local-array bounds proof (rejected and deleted)

A general static proof recognized a deliberately narrow counted-construction shape: a fresh local, unaliased, non-escaping array began empty, one +1 induction loop pushed once on every route to its sole latch until a constant bound, and integer range analysis proved a later load key was in bounds. The proof survived the compiler’s finalized ownership-transfer form and selected real source sites. It did not alter semantic mcode. Mach then used one private unchecked-load handler; native emitted forwarding repair plus direct address arithmetic and a tagged element load.

The selected implementation failed its performance gate. Enabled versus disabled medians were:

  • array_bounds_local: Mach 1.25 vs 1.22 ms (2.5% slower); native 0.37 vs 0.34 ms (8.8% slower).
  • array_read: Mach 23.96 vs 23.44 ms (2.2% slower); native 24.46 vs 24.99 ms (2.1% faster, noise-sized and not a material win).
  • spectralnorm and fannkuch: noise; the proof did not select useful sites.

Enabled native output added 20 IL lines in both array_bounds_local and array_read without a dependable speed improvement. The full proof, instruction-range evidence, backend consumers, Mach handler, tests, fixture, and benchmark registration were therefore deleted. No independent correctness fix was involved.

Two earlier experiments point the same way. Cloning the full native array-load control diamond moved array_read by only -0.49% while growing IL about 2.7% and assembly about 1.42%; broad persistent raw array add/sub propagation regressed it about 31%. Bounds-check removal by itself does not pay in the current representation. The next array lever must change persistent element representation/type handling (including selective unboxing) or use guarded PGO evidence for genuinely dynamic sites; do not repeat another boxed-load bounds specialization alone.

T10: fused native dynamic-call delivery (rejected and deleted)

A native-only ABI experiment fused dynamic call execution, caller-frame refresh, normal-result delivery, and suspension/disruption classification into one runtime helper. It added no semantic mcode opcode and no live continuation memory. The enabled and exact legacy artifacts were selected at compile time, so neither hot path contained an A/B branch. A stress oracle covered captured closures, nested dynamic calls, allocation/GC pressure, and caught disruption.

Paired fused-versus-legacy medians were:

  • call0_plain: 42.71 vs 43.08 ms (0.9% faster).
  • closure: 26.78 vs 26.75 ms (0.1% slower; no benefit).
  • call_hot: 7.88 vs 7.56 ms (4.2% slower).
  • stress oracle: 0.14 vs 0.07 ms (2x slower), with the expected caught- disruption panic diagnostics in both arms.

The arm decisively failed its required 5% call0/closure gate and regressed the control. The helper, compile-time switch, fixture, compiler test, and harness row were deleted. Collapsing duplicate refresh/result-delivery plumbing is not the dominant call cost and must not be retried as another fused-return ABI. The next call experiment must materially change closure outer-frame resolution or the freezeable activation/continuation representation while preserving the ordinary moving-GC and suspension contracts.

T11: native positive-power-of-two div/rem/idiv (retained)

The native emitter now recognizes the same exact positive constant divisor, integer result/operand, and instruction-local no-overflow facts already used by the constant-divisor path. For divisors 1, 2, ... 2^30, it replaces the Granlund-Montgomery sequence with target-private IL:

  • floor //: one arithmetic right shift (x itself for divisor one);
  • floored modulo: one mask, with no negative-correction diamond when the dividend is proven int32;
  • truncated %: sign/bias/arithmetic-shift reconstruction preserving the sign of negative dividends (zero for divisor one).

Non-powers retain the existing magic multiply. An unproven dividend retains the existing int32-fit guard and exact 64-bit div/rem slow arm. There is no semantic mcode opcode, runtime helper, allocation, metadata table, or target live-memory cost. native_pow2_divrem is a compile-time pass flag; disabling it restores the previous magic-multiply lowering for every formerly eligible divisor. The compiler IL test requires the default artifact’s _bias =l and, _rm =l and, and _qf =l sar selections, rejects _prod =l mul, and requires the inverse pattern in the disabled artifact.

The edge oracle crosses 19 positive/negative/zero dividends including INT32_MIN/MAX against every power from 1 through 2^30. Expected floor quotient, floored modulo, and truncated remainder are computed independently with exact binary64 division/floor. A three-million-iteration signed microbenchmark covers // 1024, % 8, modulo(...,256), and % 1024. Static diff checks are clean; the semantic lane and both compile-time selection assertions pass.

The first serialized arm was semantically green in both modes but mixed in performance. Three-run native medians (enabled vs exact legacy) were:

  • pow2_divrem: 15.46 vs 15.60 ms (0.9% faster);
  • call0_plain: 41.02 vs 43.82 ms (6.4% faster, stable);
  • switch_chain: 14.56 vs 13.78 ms (5.7% slower);
  • fannkuch: 110.94 vs 110.94 ms (unchanged);
  • closure: one pair 26.81 vs 27.07 ms (provisional 1.0% faster).

record_new was too host-noisy to classify. The switch regression exposed an important missing use of range information: its % 16 dividend is produced by a positive modulo, but the first arm emitted the six-operation signed bias/shift reconstruction. Final range analysis now retains one point-indexed _dividend_nonnegative bit for div/rem/idiv. A proven-nonnegative % 2^k emits the same single mask as modulo; an unproven signed dividend retains the fully checked signed sequence. This compiler-only array adds one boolean cell per instruction only in analyzed integer-loop functions and zero target bytes.

The first compile-time A/B regression used x = x - 3 as its signed dividend. That recurrence is not proven safe from int32 underflow, so x correctly becomes num and all three operations take the generic boxed-double path; the test’s expected integer IL markers were therefore absent. The corrected fixture uses a bounded negative recurrence from -64 to 0. It now proves the actual eligible integer path and requires signed % bias, modulo mask, and arithmetic // shift in the enabled artifact, versus magic multiplication in the disabled artifact. The separate nonnegative fixture requires % 16 to select one mask and no signed-bias reconstruction.

Adding the point fact also exposed a self-hosting limit rather than a generated- program defect: the already-large qbe_emit.compile_fn reached 256 Mach slots after lowering, one beyond the 255-slot encoding limit. Removing redundant compile-function aliases/helpers and reading the instruction-local fact directly restored the limit without changing emitted semantics, runtime state, or target memory. This is a tooling/maintainability warning for future emitter work, not a cost paid by compiled programs.

The coherent refined native pairs were:

armpow2_divremcall0_plainswitch_chainfannkuch
enabled, first15.2240.7016.70107.34
disabled, first15.9043.9514.42107.17
enabled, alternating repeat15.2540.7514.86
disabled, alternating repeat15.4743.7415.14

The mechanism improves pow2_divrem by 1.4-4.3%. call0_plain is not an unrelated control: its hot index expression contains a point-proven nonnegative x % 8, so its repeatable 6.8-7.4% improvement is expected. The first switch_chain pair was host-noisy; the alternating repeat is neutral to 1.8% faster rather than a regression. fannkuch is unchanged. Mach is unaffected because the selector changes only QBE IL.

Retain this arm. It is a pure QBE lowering improvement with no semantic mcode, runtime helper, target metadata, or live-memory cost; it has exhaustive signed edge coverage plus compile assertions for both the signed and proven- nonnegative paths.

T12: nursery sizing sweep (default rejected; opt-in retained)

The final adaptive sweep selected a 1 KiB initial block without a detectable hot-actor penalty, but found no compact cap that is both viable and fast. An 8 KiB cap failed to reach the builder after 60 seconds and 22,102 shop collections; 32 KiB regressed the three allocation rows by 133-529%, and 128 KiB still regressed gc_churn 33%/67% and record_new 31%/52% (Mach/native). A 512 KiB cap improved gc_churn 23.9%/22.0%, record_new 8.9% Mach/−1.0% native, and binarytrees 6.9% Mach/−0.9% native, while closure regressed 3.3%/2.3%. It can retain 512 KiB per hot actor and require another 512 KiB replacement block during a minor. Disposition: nursery-off remains the default/native baseline; retain the verified mechanism and exact telemetry only as an opt-in host/Mach experiment. Detailed exact collection, memory, and timing evidence is in perf-nursery-final-sweep.md.

T13: sealed local integer-array load (rejected and deleted)

A second array experiment deliberately targeted the representation traffic that T9’s bounds-only proof could not remove. A read-only structured-value proof followed aliases from one fresh empty array, accepted only exact int32-bounded pushes, and rejected the entire candidate on any captured cell, store, pop, append, call argument, heap insertion, return, or unknown use. Rejecting the whole lifetime, rather than only operations textually before a load, was required because an apparent later escape can execute first on a loop backedge. The proof selected array_read’s three-million-execution load and changed no semantic mcode or array representation.

Two QBE-only consumers were measured. The first used one shared exact helper, returned an untagged int or a private out-of-bounds token, retained the source numeric/error edge, and gave the other addition operand an honest tag-checked integer/float fallback. It was effectively neutral: native array_read was 23.82 ms enabled versus 23.88 ms disabled (-0.25%); QBE IL was 39,057 versus 39,112 bytes (-55), while assembly grew 33,085 versus 32,866 bytes (+219).

The second inlined forwarding/bounds/address arithmetic and bypassed the promoted-index frame spill as well as the helper call. A noisy first pair was 23.67 versus 25.00 ms, but the immediately repeated enabled result was 24.86 ms. The low-noise eleven-sample focused medians settled at 24.893 ms enabled and 24.916 ms disabled, only 0.09%. IL fell from 39,112 to 38,856 bytes and assembly from 32,866 to 32,609 bytes, but the runtime change is not material. The proof, raw-int seam, helper/inline arms, pass flag, and compiler metadata were deleted; focused compiler tests were 136/136 before deletion.

This closes the obvious boxed-array variant too: even after proving every element is a tagged int, eliminating the result frame round trip, halving the dynamic add tag checks, and bypassing the helper/index spill, array_read does not move. Do not repeat bounds hoisting, exact boxed-element typing, or a larger cloned loop over the current PitValue[] backing store. A future material array lever needs a different persistent element representation (specialized/unboxed storage with guarded fallback), or a workload where PGO proves a dynamic receiver/element case that the static compiler cannot already classify.

Acceptance rules

An optimization is retained only if:

  • semantic, focused, full-suite, and appropriate fuzz tests pass;
  • Mach and native results match an independent oracle where practical;
  • the relevant microbenchmark moves for the claimed mechanism;
  • at least one composed or realistic workload with the same semantic shape is checked;
  • generated mcode/facts and Mach/native output are inspected to confirm the intended mechanism actually selected;
  • code, target memory, compiler memory, and metadata costs are stated;
  • rejected arms are deleted and preserved here with measurements.

2026-07-14 — T4 dense static record shapes: final matrix

Final controlled medians (Mach/native ms, allocated KiB, GC), with identical fixtures and only the pass selector changed:

fixturedense/defaultgeneric-template (dense_record_shapes off)no shape (record_shapes off)
record_new21.52 / 18.93, 16,423, 2033.29 / 41.34, 42,204, 6143.21 / 60.87, 42,204, 63
binarytrees78.04 / 56.42, 42,241, 2783.81 / 59.65, 52,707, 38113.76 / 67.59, 52,706, 36
record_template34.91 / 32.49, 26,729, 3548.11 / 36.81, 26,722, 3562.96 / 43.77, 26,730, 42

The generic record_field control stayed inside its 2% gate: the aggregate three-pair median was 116.73/53.54 with dense shapes versus 116.12/53.36 with generic templates (+0.53%/+0.34%). record_template is the robust retention case: allocation is effectively identical and dense/default has the same 35 collections as generic-template, yet it is 27.4% faster in Mach and 11.7% faster native. That isolates a real construction/access win rather than a GC artifact. record_new and binarytrees additionally realize the intended compact-layout benefit.

Against composed B0 record_new (33.01/18.27, 42,204 KiB, 65 GC), the final dense arm is 34.8% faster Mach, 3.6% slower native (rough parity), allocates 61.1% less, and reduces GC from 65 to 20. Retention decision: keep the dense shape implementation. It materially advances Mach and memory density while preserving the composed native baseline; the remaining native work is to turn the controlled lowering advantage into an end-to-end improvement over B0.

Implementation tradeoff is bounded: no new semantic opcode and no additional per-record word. The record header I bit selects whether cap56 contains a shifted runtime-local shape pointer or generic capacity. Structural mutation degrades once to the ordinary representation, so dynamic records retain the existing semantics and measured generic-path performance.

T14: production PGO record-field PIC (retained)

The first useful profile consumer is now validated through the real CLI, profile store, executable builder, Mach loader, and native compiler. The fixture executes one honest-dynamic velocity load 18,000,000 times over an 80% dominant dense shape, four 4% dense shapes, and one 4% record deliberately degraded to generic. Collection persisted exactly the intended top four portable targets: 14,400,000 plus three rows of 720,000. The fifth dense shape and generic record remain on the unchanged fallback, so the benchmark verifies both hit and miss correctness rather than a monomorphic special case.

Median-of-run-medians (each run contains seven timed two-million-read samples):

laneordinaryPGO consumechange
Mach56.10 ms49.89 ms11.1% faster
native30.17 ms23.17 ms23.2% faster

The instrumented Mach collection median was 61.40 ms, about 9.4% over the ordinary run. The portable profile is 4,643 bytes. Consumption adds no bytes to records. It retains one bounded runtime call descriptor for the selected site (four shape pointers and four byte offsets) and reuses the existing shared shape descriptors. Native primes the same four descriptor identities once and emits guarded pointer/offset loads. Collection remains capped executable- wide at 512 sites and by the existing runtime byte budget; Playdate uses the smaller target cap.

Two end-to-end defects were found before this result was accepted:

  1. Slot compression reused the same physical destination for several unrelated record literals. The shape profitability scan treated later definitions as aliases of the first record, so later fields falsely looked like structural mutation and only the final literal was shaped. Value definitions now kill the old identity. This is a general exact-use correctness fix for shape selection, with a three-constructor regression; compiler tests are 138/138.
  2. Canonical cached mcode intentionally strips all optimizer facts. The first PGO second stage reloaded that operand-only artifact and tried to recreate facts by streamlining already-compacted code. This cannot reproduce slot histories or closed-world call relationships, and silently made PGO builds less optimized than ordinary builds. Canonical code and its semantic hash remain unchanged; mcode artifacts now carry a separate finalized lowering- fact sidecar. Mach/native hydrate a private lowering view only when needed. The sidecar costs build-cache bytes plus portable-mcode decode and transient lowering memory; final Mach/native payloads do not retain it. In the deliberately fact-heavy PGO fixture its canonical JSON is 19,019 bytes beside 50,962 bytes of operand mcode (+37.3% in the compiler cache); a future binary fact codec can reduce that without changing the seam. A production artifact regression verifies that six distinct dense shapes and point types survive exact hydration.

Disposition: retain the record PIC, profile plumbing, and lowering sidecar. This is a general optimization for any profiled constant-text record read; it does not recognize this benchmark’s source shape or key name. Do not restore the rejected check-only dynamic-call PIC from T8: the next closure work must remove outer-environment/frame cost rather than merely skip type/arity checks.

T15: PGO profitability and useful-site budget policy

PGO consumption now requires both at least 1,024 observed executions and at least 90% coverage by valid, positive-count record-shape targets. Zero-count, malformed, or wrong-key target rows never consume a PIC guard. The gate is a target-private profitability policy, not correctness evidence; an internal override remains available to benchmark a different threshold without adding a public flag or changing semantic mcode.

The 90% boundary was selected by an honest-dynamic coverage sweep, not by the original 92%-coverage fixture. Five independent constant-text load sites used the same four dense shapes plus generic fallback at 25%, 50%, 75%, 90%, and 95% top-four coverage. With all five PICs forcibly enabled, the fresh Mach medians versus the preceding ordinary run were 41.09 vs 38.55 ms at 25%, 40.18 vs 37.58 ms at 50%, 39.96 vs 37.42 ms at 75%, 35.48 vs 36.00 ms at 90%, and 34.80 vs 35.74 ms at 95%. Native was 27.24 vs 26.77, 26.73 vs 26.30, 26.94 vs 26.14, 22.37 vs 25.97, and 21.74 vs 25.77 ms respectively. Thus 75% and below regress both lanes; 90% is the lowest measured profitable point and has a large native margin. A separate ordinary/90%-gated run measured Mach 32.89 vs 36.00 ms and native 21.98 vs 25.97 ms, demonstrating that run-to-run Mach noise changes the exact win but not the break-even classification.

The memory policy is deliberately conservative for 4 MiB-class shipping targets. On the current 64-bit Mach layout, the first consumed PIC in a function is approximately 144 bytes of runtime metadata (24-byte code meta, 56-byte per-function wrapper, and 64-byte site row); later sites are about 64 bytes, and 32-bit layouts are smaller. Each target also emits up to four guard branches/offsets, while shared shape descriptors and records gain zero bytes. Requiring 1,024 representative executions and 90% hits avoids permanent descriptor/code spend on cold or mostly-fallback sites.

Unsupported dynamic-call collection has been deleted rather than allowed to starve useful record sites. Calls had no retained consumer after T8, yet an early call-dense module could previously consume all 512 executable-wide slots before a later record load. Collection now selects record loads only; a regression places 1,024 opaque calls before one load under a one-site cap and proves the load wins. The obsolete MACH_CALL_COLLECT opcode, VM handler, lowering, code-token identity path, monomorphic hint, and closure collection fixture are gone. On a fully active 512-site host collector this avoids up to roughly 32.7 KiB of 64-byte call descriptors and 24.5 KiB of pointer-spine plus 40-byte counter rows, as well as one atomic observation sequence per dynamic call. The portable profile schema remains kind-extensible, but no compatibility fallback or dormant call collector is retained.

Tooling finding: shared-shop command lease

The local shop is one daemon even when several compiler agents edit independent files. Concurrent make seed, test realization, and aggregate benchmark commands produced bootstrap timeouts and contaminated timings. Performance campaigns must serialize seed/test/benchmark batches through an explicit shared-shop lease while allowing parallel read/edit work. The CLI also has a short post-command interval where the listener can be busy enough that its five-second info probe reports daemon endpoint is temporarily unavailable; waiting one second made every focused run deterministic. Neither symptom is a language-performance result, and no timing from a collided batch is retained.

Production artifact seam audit

The finalized sidecar is now the production contract, not only a PGO reload aid. Both the builder-worker object and canonical unit artifact keep semantic code fact-free and carry explicit pit.mcode.lowering@1 data (including an explicit empty fact set). Hydration returns a private code/function-record view and cannot mutate the cached semantic object. Ordinary Mach and ordinary native hydrate unconditionally; PGO second stages reuse an already-hydrated view. Canonical artifacts are now pit.mcode.unit@2; the internal artifact cache is mcode-unit-v4-strict-lowering. Missing sidecars, non-record fact buckets, unknown fact keys, wrong fact value kinds, function-count mismatches, old unit versions, missing/mismatched hashes, and inline optimizer fields all fail closed. The obsolete pit.simple.mcode.artifact compatibility wrapper was deleted.

Generated development seed firmware deliberately replaces the facts with a valid explicit empty sidecar and takes conservative boot-service Mach. There is no missing-sidecar engine fallback. The earlier decoded seed assertion found 67 mcode units. boot/firmware fell from 81,886,664 to 70,075,080 bytes (-11,811,584, -14.4%); boot.qop fell from 89,119,221 to 77,358,871 bytes (-11,760,350, -13.2%). The small engine-side portable-mcode hydrator grew boot/boot by 42,191 bytes. Normal portable user mcode retains and hydrates its sidecar, verified by the mcode-only executable start test.

The post-audit production record PIC remained profitable on exact results: Mach 63.53 -> 54.05 ms (-14.9%), native 33.23 -> 24.14 ms (-27.3%); collection was 65.46 ms (+3.0% over ordinary Mach). These are a fresh validation batch, not replacements for the more stable T14 median-of-run-medians above.

Tooling fix: cold bootstrap repeatedly exceeded the engine’s generic 60-second reply deadline before completing its 67-unit fleet. Only the explicit bootstrap_artifacts request now supplies a bounded 600-second deadline; the generic actor reply timeout is unchanged. The next seed completed under that bound.

The final integrated strict-contract pass also found a self-hosting trap. A known-good daemon runs its embedded old shop_build, so its first regenerated firmware mixed current inline units with unchanged legacy hash-only units. Stripping facts before hydration could not see those units; hydration then reintroduced missing sidecars and inline optimizer fields. Production now hydrates and strictly validates every cached unit first, refreshes derivation catalog mappings from validated current bytes, and only then installs the boot-only empty sidecars. One explicitly temporary engine bridge was used to boot the new builder for one generation and was deleted before the final seed; no legacy or missing-sidecar acceptance remains in source or firmware.

The final decoded firmware contains 65 mcode modules and all 65 pass the exact validator. Relative to the immediate pre-audit campaign state, exact artifact sizes changed as follows:

artifactbeforefinaldelta
boot.qop75,433,09974,842,189-590,910 bytes
boot/firmware68,140,19267,509,488-630,704 bytes
boot/boot1,447,5731,573,945+126,372 bytes
total145,020,864143,925,622-1,095,242 bytes

The final SHA-256 identities are a58afba087c65d9d04b31c10ade9c16400fc3766b3a3f8a8295749462eeb7d57 for boot.qop, 797db28fc9b1bec61db89da36a5656049db9da3389ce72a6f0dc61c12b175e89 for boot/firmware, and 6fff594fcb6f130c9c2ab5db5c1f595081320ec56d1c7f6f11ea1ebb4bf17174 for boot/boot.

The engine increase is the strict portable validator/hydrator. The larger firmware/QOP reduction includes deletion of the unused linked-call provenance modules and transport plus the regenerated explicit-empty seed sidecars. Unit artifact hashes are now BLAKE2 over native JSON encoding with a recursively collected, sorted property-list replacer; ordinary record iteration order is not stable across decode/GC and was proven to change the hash before this fix.

T16: PGO record-field store PIC (rejected and deleted)

The retained load PIC was mirrored as a deliberately narrow store experiment: one target-private Mach head preserved the original constant-key store plus a descriptor ordinal, and native reused the same portable shape descriptors. Hits chased forwarding, required record + dense-shape + mutable + non-null, compared up to four exact shape pointers, wrote record + 32 + offset*8, and ran the existing generational barrier. Null deletion, stone disruption, unmatched shapes, generic records, and invalid receivers all reached the unchanged store fallback. No semantic mcode operation or per-record byte was added.

The honest site performed two million writes per sample over the same 80% dominant shape, four 4% alternate shapes, and 4% deliberately generic record as the load gate. The collected top four therefore covered 92%. Seven-sample medians were:

laneordinaryPGO consumechange
Mach56.93 ms55.34 ms2.8% faster
native32.51 ms32.57 ms0.2% slower

Instrumented collection was 53.56 ms; its direct shaped-write path happened to be faster than ordinary lookup despite counter updates, but that is not a shipping consumer. Consumption missed the predeclared 8% Mach / 15% native gate decisively. The ordinary store helpers already have a rootless shaped lookup, in-place overwrite, and barrier path, so the PIC merely traded a short key scan for shape guards and up to four pointer comparisons. Unlike loads, native stores did not remove a sufficiently expensive helper/read path.

The STORE PIC opcodes, selector, native lowering, tests, and benchmark fixture were deleted. Do not repeat a store-only shape PIC with this layout. A future record-write win needs to remove more work than lookup alone (for example a larger representation/dataflow change) and must be justified independently.

Tooling finding: the first apparent consume run was invalid because pit-compiler/pgo.cm is embedded in the boot-loaded builder fleet; restarting the node after an ordinary package edit did not install the new selector. Exporting the profile exposed that it contained zero record_field_store observations. make seed was required before production-path collection. Future PGO consumer experiments must inspect the exported observation kinds before accepting timings.

T17: fused Mach closure outer-frame cache (retained)

The honest closure row performs two million unpredictable calls among eight capturing closures. Each callee reads and writes its depth-1 captured cell. A first eager BIND_OUTER design was correct but added one VM dispatch per call and regressed 57.71 to 65.00 ms; it was deleted. A native dominance-local outer-base CSE removed a pointer walk but measured only 26.60-27.09 versus 26.96-27.19 ms and was deleted below the 5% gate.

The retained Mach form fuses binding into the first dominating captured access and stores the outer frame in one ordinary scanned frame slot. Later accesses use private cached opcodes; depth-2, disruption, recursion, GC movement, frozen stacks, and the write barrier retain their old paths. It adds 8 bytes per live eligible Mach activation, no serialized bytecode bytes, no persistent side table, and no semantic mcode operation. The isolated median was 53.64 versus 58.86 ms (-8.9%); the final canary was 51.42 versus 56.18 ms (-8.5%). Final aggregate closure was 50.88/26.24 ms Mach/native. Details and the rejected arms are in perf-closure-outer-cache.md.

T18: wide integral literal lane correctness (retained)

The compiler had conflated mathematical whole numbers with values fitting the signed-int32 tagged lane. Literals such as 2147483648 could become invalid tagged integers in native output or undergo an out-of-range C conversion in Mach. numeric_literals.cm now separates language-level is_whole from representation-level fits_int_lane; every optimizer and target consumer uses the appropriate predicate. Wide whole values retain language is_integer(value) == true while materializing through the float lane. This is a general wrong-code repair with no metadata, frame, heap, or normal-code cost; only programs containing wide integral literals pay float-constant materialization. The dual-lane golden result is 127. See perf-wide-integral-literals.md.

T19: exact linked pure-leaf substitution (rejected and fully deleted)

The narrowest whole-program consumer replaced an exact immutable linked Pit leaf returning one numeric literal. It passed focused proof tests but failed the production gate: Mach improved about 13.0% (46.6330 to 40.5768 ms; the control was only one clean sample) and native improved only 0.93% (58.9569 to 58.4113 ms), far short of the required 25%/50%. The active pass, shop integration, flag, provider/origin sidecar, proof/pairer modules, tests, probes, and executable fixtures were deleted. Static export caching is independent and remains enabled. The compact linked spine remains only a documented/compiler research substrate; it is not imported by a production pass. Do not repeat constant-leaf substitution without a consumer that removes materially more call/activation work. Full details are in perf-linked-leaf-substitution.md.

T20: final integration corrections, CLI/tooling, and validation

The final seed and benchmark gate found three correctness/tooling defects that were fixed before accepting any timings:

  1. LICM had treated branch-local/conditional replacement writes as though they dominated joined uses. It now rejects those replacements, covering ternary joins and per-iteration conditional accumulator resets.
  2. QBE’s frame_writes set omitted helper-written results of is_array, is_func, and is_record; promoted slots could reload stale values. Those predicates now participate in the same frame-write contract.
  3. The typed one-shot CLI source_bundle fixture started an actor but did not explicitly reply under the new request/response semantics. It now replies. Benchmark GC detail is also an explicit --gc-detail option and is quiet by default, so measurement output no longer floods the daemon.

Integrated evidence: make seed passed; Meson 7/7; focused compiler 145/145; isolated VM 1,086/1,086; warmed full suite 1,921/1,921; deterministic fuzz 3,733/3,733 over 500 programs with seed 20260713; final aggregate 68/68 rows with exact Mach/native oracles. One cold full run passed 835 tests and timed out only while realization was charged to vm_suite; the isolated and warmed runs passed, so it is recorded as lifecycle/tooling evidence rather than hidden or counted as a language failure.

The final clean-daemon shootouts were mandelbrot 89.15/9.21, fannkuch 227.96/111.35, spectralnorm 167.77/44.55, binarytrees 78.79/50.06, and nbody 258.93/150.79 ms Mach/native. The final record-load PGO production path returned 3,200,000 throughout: ordinary 53.875/30.147, collect Mach 56.283, and auto 45.954/22.895 ms. Its generation-1 profile was 7,088 bytes. Full comparisons, artifact hashes, and clean-versus-contaminated daemon memory are in perf-campaign-final-matrix.md.

Source: plans/archive/perf-2026-07/perf-linked-pgo-arc.md