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

Active structured mid-end: exact value lanes across finalized mcode

Date: 2026-07-13

Status: isolated performance experiment on codex/perf-active-structured-midend. The exact control and candidate both start at 9313e4ee25b28df4f74744cf735011d704fd2272. Nothing in this document has been merged or pushed.

Result

This experiment makes the structured view of finalized semantic mcode an active, read-only QBE analysis. It gives each ordinary slot definition a value identity, then keeps a narrowly proven numeric value in a QBE d temporary across compiler-created labels and moves. It does not change semantic mcode, the Mach format, the native ABI, frames, actor state, heap objects, suspension metadata, or the scheduler.

On the exact-base control/candidate/control-style bracket used here, spectralnorm changed as follows:

measurementexact-base controlstructured candidatechange
native median75.82 ms44.80 / 44.32 ms-40.9% / -41.5%
QBE IL176,499 B174,029 B-2,470 B (-1.40%)
assembly117,795 B115,720 B-2,075 B (-1.76%)
linked __text31,784 B31,208 B-576 B (-1.81%)
native allocation2,030 KiB2,030 KiBunchanged
checksum1,623,647,0091,623,647,009exact

The large runtime win from a small static reduction is credible. The selected chain is inside the doubly nested 400-by-400 row computation. The baseline repeatedly materializes a divide result into a tagged frame slot, reloads and decodes it after an inline-continuation label and checked array read, then does the same around the following multiply. The active value lane leaves that intermediate in a canonical raw double until the real semantic boundary.

The controls that should not benefit are neutral:

benchmarkexact-base controlcandidateemitted artifact
fannkuch107.47 ms106.83 msidentical 167,625 B IL / 114,287 B assembly / 31,036 B __text
mandelbrot9.21 ms9.21 msidentical 63,628 B IL / 43,809 B assembly / 11,300 B __text

Mach is intentionally unchanged by this experiment. Its timing variation is noise and its instruction counts remain identical.

Active seam and representation

pit-compiler/structured.cm consumes the final output of streamline, before QBE mutates or lowers anything. For each function it builds:

  • basic blocks with normal and exceptional edges;
  • reachability, immediate dominators, and normal-backedge natural loops;
  • dense SSA identities for ordinary frame slots and explicit cells for captured/address-taken slots;
  • stable source origins and unique transform instances;
  • an exhaustive effect record for every finalized mcode opcode; and
  • an exact round trip to the original instruction array.

The mcode array remains authoritative. The view is compiler-temporary and QBE discards it after emission. The schema covers all 94 currently finalized opcodes. A missing future opcode gets the most conservative effect summary and a verifier error; QBE then disables all structured facts for that function.

Dense SSA is deliberately skipped when a function exceeds 512 slots, 1,024 blocks, or 8,192 block-slot state cells. A skipped or non-converged function just receives baseline lowering. This bounds compiler failure risk and never changes target semantics.

Exact optimization contract

A value can enter the active raw-double lane only when all of these are true:

  1. It is the exact SSA result of divide, or of add/subtract/multiply whose exact operand/use facts select the numeric double path.
  2. Its destination is point-typed num or float by the final closed _type_spans interval or immutable _param_types contract.
  3. Every use is numeric arithmetic or a move to another selected raw value.
  4. Every crossed block has exactly one reachable normal predecessor. General joins, loops, cycles, and exceptional entries are rejected by this first active pass.
  5. No crossed instruction may allocate, call, disrupt, mutate/capture a cell, prepare a call frame, or otherwise require a complete tagged continuation.
  6. Every destination of a raw move must also remain raw. One escaping fanout materializes the source and all dependent values.

There is one narrow load exception. An exact point-proven array receiver plus integer key uses the existing checked, no-allocation QBE/Mach array-read contract, so an unrelated raw value may cross it. Text indexing can allocate; record and unknown reads are not this contract. Those all force materialization. Type spans are closed at both endpoints, matching QBE’s existing slot_type_at, and immutable parameter types participate in exactly the same read-specific proof.

Each raw arithmetic result is canonicalized immediately. Finite nan64 values remain doubles; invalid/non-finite results become the internal NaN poison that has sticky Pit null semantics. Materialization uses the existing qbe_new_float64 encoding. Consequently a raw value cannot launder null or a non-number into a later valid number.

This satisfies the actor freeze requirement by construction. Calls, invokes, frame preparation, captured cells, disruption handlers, and scheduler seams always see the ordinary tagged frame as the complete continuation. No raw temporary is required to survive suspension. The pre-existing, separately validated natural-loop phi optimization remains unchanged.

Bugs found during validation

Two mistakes were found before the final gates.

First, the structured type lookup initially treated _type_spans as half-open and did not consult _param_types. QBE’s transported fact contract is a closed [first,last] interval, and spectralnorm’s exact array read occurs at the last instruction of such a span. The mismatch conservatively lost the optimization (75-78 ms, identical to control); it did not miscompile. Matching the existing closed interval and immutable-parameter rule restored the 44 ms result.

Second, raw_numeric_boundaries exposed a real emitted-IL defect. The analyzer could select an exact float multiply after a label, but labels clear QBE’s block-local float-literal cache. Add/subtract/multiply did not treat an exact structured raw destination as a reason to select their double handler. They could therefore emit a tagged/general definition while a later instruction used %sraw_<ValueId>, leaving the raw temporary undefined; QBE rejected the IL with invalid type for first operand. The fix makes the exact raw destination itself force the double handler, and a regression requires an explicit =d definition after a literal-cache-clearing label.

The value analysis also originally stored only one destination for a move. That was unsound for fanout: a later arithmetic destination could overwrite an earlier escaping destination in the analysis map. It now records every move destination and retains the source only when all of them remain raw. A focused escape/call regression covers the case.

Correctness evidence

  • Active compiler tests: 121/121.
  • Structured-view tests: 7/7, including exact round trip, CFG, full exceptional edges, dominance/loops, SSA/cells, stable origins, exhaustive 94-op schemas, and unknown-op failure.
  • New raw-lane tests cover a one-predecessor label (accepted), a real two-path join (rejected), a disruption-handler entry (rejected), exact local-span and immutable-parameter array+integer reads (accepted), text/record/unknown reads (rejected), move fanout, call seams, and explicit double definitions.
  • Nine dual-lane edge fixtures agree exactly: native_suspend_loop_phi, raw_numeric_boundaries, raw_numeric_chain, raw_numeric_poison, raw_numeric_literal_boundaries, raw_numeric_call_seams, raw_numeric_gc_seam, guarded_array_load, and raw_array_numeric.
  • The raw GC fixture allocated 2,075 KiB and collected twice in both Mach and native while preserving result 2.
  • The native suspension runner, with PIT_SUSP_TEST_GC=1 and reduction budget 64, returned exactly 50,000 through 48 suspensions and 48 moving collections; maximum native depth was 1 and no exception occurred.
  • Full suite: 1,918/1,918 outside the filesystem/network sandbox.
  • Deterministic fuzz: 3,733/3,733 checks over 500 programs, seed 20260713.

The first sandboxed full run passed 1,901 and failed six tests only because the sandbox denied local socket binding: the probe-port check, fd_pass, fetch_op, io_reader, the boolean aggregate emitted by those failures, and terminal_actor. The exact rerun with host loopback permission was all green.

Memory and compile-time tradeoff

There is no target-side dynamic-memory tradeoff in the selected programs: frame counts/sizes, heap layouts, native allocation traffic, actor state, continuation state, and persistent code metadata are unchanged. Spectralnorm’s linked code is smaller by 576 bytes, although Mach-O segment alignment keeps the dylib file allocation unchanged. This is suitable for small-console target budgets.

The cost is paid by the compiler. Around QBE emission of spectralnorm, the instrumented exact-control comparison was:

compiler measurementcontrolcandidatechange
allocation traffic10,654,728 B13,068,920 B+2,414,192 B (+22.7%)
allocation count50,79773,824+23,027 (+45.3%)
sampled old-heap peak8 MiB16 MiB+8 MiB
live old-block delta4 MiB12 MiB+8 MiB

The last two counters are buddy/GC block-granularity observations, not exact retained-object sizes. QBE emission was about 20 ms rather than 8 ms in the bracket. Compiler speed was explicitly not a gate for this campaign, but the temporary memory matters if this exact compiler must someday run on a console. A packed-table implementation can preserve the proven transform later. The generated executable does not carry this cost.

Tooling added and friction observed

aot_bench/dumpil.ce now supports DUMP_MODE=structured and an optional DUMP_STATS=1 compiler-allocation line. This made it possible to inspect the final value graph and distinguish compiler-temporary memory from target memory.

All validation used PIT_INPROCESS=1. Repeated daemon boots in isolated worktrees can leave a shop without a listener, and one bare dumpil.ce run printed its result but remained attached for roughly a minute before a terminal stop/coupling error. Cold self-host realization also produced 30-70 second pauses. These are CLI/lifecycle problems, not compiler failures, but they make short controlled experiments substantially slower and less reproducible. The in-process path and explicit benchmark arguments were reliable.

Recommendation

Keep this experiment for review. It is a pure compiler proof/lowering win with smaller code, no target memory cost, exact actor suspension behavior, and a repeatable roughly 41% native spectralnorm improvement. The implementation is larger and compiler-heavier than the eventual production form, but compiler speed was not this experiment’s goal and the executable result answers the architectural question: stable value identity across finalized control flow is a high-value lever.

Do not broaden the current load or join rules casually. The next executable experiments should consume the same identities for trivial same-value acyclic diamonds, then for more arithmetic chains, each with emitted-code and target memory gates. Captures, calls/suspension, exceptional entry, mutation, and general phis should remain explicit reviewed boundaries.

Source: plans/archive/perf-2026-07/perf-active-structured-midend.md