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:
| measurement | exact-base control | structured candidate | change |
|---|---|---|---|
| native median | 75.82 ms | 44.80 / 44.32 ms | -40.9% / -41.5% |
| QBE IL | 176,499 B | 174,029 B | -2,470 B (-1.40%) |
| assembly | 117,795 B | 115,720 B | -2,075 B (-1.76%) |
linked __text | 31,784 B | 31,208 B | -576 B (-1.81%) |
| native allocation | 2,030 KiB | 2,030 KiB | unchanged |
| checksum | 1,623,647,009 | 1,623,647,009 | exact |
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:
| benchmark | exact-base control | candidate | emitted artifact |
|---|---|---|---|
| fannkuch | 107.47 ms | 106.83 ms | identical 167,625 B IL / 114,287 B assembly / 31,036 B __text |
| mandelbrot | 9.21 ms | 9.21 ms | identical 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:
- It is the exact SSA result of
divide, or of add/subtract/multiply whose exact operand/use facts select the numeric double path. - Its destination is point-typed
numorfloatby the final closed_type_spansinterval or immutable_param_typescontract. - Every use is numeric arithmetic or a move to another selected raw value.
- Every crossed block has exactly one reachable normal predecessor. General joins, loops, cycles, and exceptional entries are rejected by this first active pass.
- No crossed instruction may allocate, call, disrupt, mutate/capture a cell, prepare a call frame, or otherwise require a complete tagged continuation.
- 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, andraw_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=1and 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 measurement | control | candidate | change |
|---|---|---|---|
| allocation traffic | 10,654,728 B | 13,068,920 B | +2,414,192 B (+22.7%) |
| allocation count | 50,797 | 73,824 | +23,027 (+45.3%) |
| sampled old-heap peak | 8 MiB | 16 MiB | +8 MiB |
| live old-block delta | 4 MiB | 12 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