ADR-0042: Fail-Closed Tail and Control-Flow Encoding Policy 1.3
Status: accepted; encoder policy 1.3.0 implemented and locally validated; Gate B remains open
Date: 2026-07-30
Visibility: private NAUX architecture/governance record. This ADR is not public roadmap material.
Supersedes: the encoder-policy version and unconditional two-phase tail-copy rule of ADR-0037 only
Preserves: the R1-S7a target schema, lowering policy, ABI, Machine IR semantics, source provenance, target-plan evaluator relation, numeric policy, and all R1-S7b/R1-S8 authority boundaries
Related: ADR-0037, ADR-0038, ADR-0039, ADR-0040, ADR-0041
Context
ADR-0037 deliberately chose a simple stack-home encoder. Every tail transfer staged the complete argument vector into a non-aliasing outgoing area and then copied it into callee parameter homes. This gave an obvious simultaneous-assignment proof and constant native stack usage, but it made the frozen BranchMix lighthouse spend most of its runtime moving values that were already in their destination homes.
The pre-optimization BranchMix target contained:
functions 121
blocks 139
tail terminators 127
raw target code 43,124 bytes
frame 240 bytes
A static audit of those 127 tail transfers found:
exact-identity arguments 969
machine words occupied by those identities 1,100
non-identity immediate words 17
non-identity Home words 172
non-identity words with a direct schedule 177
non-identity words requiring fallback 12
cyclic transfers 2
The target also contained 28 empty exact-identity tail blocks and nine exact
I64Setcc -> TailJump -> empty Branch shapes. A broader audit found 90
composable tail sites, 189 non-identity hops, and a maximum chain length of
eight.
The fixed Gate B trace confirmed that this was a dynamic problem. Before empty-tail composition it executed 99,410,299 blocks and 86,827,257 tail jumps per measured invocation.
The encoder needs a deterministic policy that removes provably redundant transfers and control edges while retaining simultaneous-assignment semantics. It must remain owned, dependency-free, bounded, replayable, and fail closed.
Decision drivers
- Reduce dominant stack-home and tail-transfer traffic before introducing a general register allocator.
- Preserve exact typed simultaneous assignment, including arrays and cycles.
- Keep Machine IR, target-plan semantics, source origins, and ABI unchanged.
- Make every byte change deterministic and part of target identity.
- Refuse an optimization whenever its structural proof is incomplete.
- Retain a conservative correct path for every already admitted valid program.
- Introduce no assembler, linker, SMT solver, optimizer framework, or other external dependency.
- Accept or reject performance work using exact evidence, not expected speedup.
Options considered
| Option | Benefit | Cost |
|---|---|---|
| Keep encoder policy 1.0 | Smallest proof surface | Gate B remains dominated by redundant tail traffic |
| Change Machine IR or specialization output | Could expose a simpler graph earlier | Expands the semantic boundary before the encoder-local problem is understood |
| Introduce a general register allocator immediately | May remove more memory traffic | Adds liveness, interference, spills, and verification obligations too early |
| Elide identities only | Large safe first reduction | Leaves acyclic transfers and redundant control edges |
| Compose every empty-tail chain | Removes the most jumps | Can turn cheap transfers into an expensive cyclic two-phase copy |
| Adopt layered encoder policy 1.3 | Measurable speedup with conservative fallback | Increases encoder complexity and current code size |
Decision
1. Encoder identity and migration
The accepted encoder policy is:
X64_TARGET_ENCODER_POLICY_VERSION = 1.3.0
The version participates in target-plan and artifact identity. No policy-1.0, experimental 1.1/1.2, or naive-composition artifact is reinterpreted as policy 1.3. R1-S7a, R1-S7b, R1-S8, process, IPC, standalone, and correspondence identities must migrate together and replay independently.
This change affects raw realization only. It does not authorize a change to source Core, Residual Core, SSA, Machine IR, target ABI, effects, numeric semantics, or result semantics.
2. Validate before optimizing
Every tail transfer first validates:
- callee existence;
- exact argument arity;
- argument and parameter types;
- source and destination homes;
- complete outgoing-area extent;
- checked offset arithmetic.
Optimization is never used to make malformed input appear admissible.
3. Exact identity elision
An argument is an identity only when it is a typed Home operand exactly equal
to the corresponding callee parameter home, including slot, offset, width, and
machine type.
Exact identities emit no load, stage, store, or commit. Mere value equivalence, overlapping storage, equal offsets with different types, and partial array overlap do not qualify.
4. Deterministic destructive-copy scheduling
After identities are removed, policy 1.3 attempts a direct parallel-copy schedule.
For every pending move, the encoder expands source and destination homes into 64-bit word locations. It repeatedly chooses the first pending move, in canonical argument order, whose destination words do not occur in any remaining source home. The move is emitted and removed from the pending set.
A direct schedule is accepted only when:
- all destination word sets are unique and non-overlapping;
- every pending move can be removed by the rule;
- the complete schedule is deterministic;
- typed aggregate movement preserves its source before any overlapping write.
F64Array remains one typed {pointer, length} value. Both source words are
loaded before either destination word is stored.
If no candidate exists, the complete direct schedule is refused. The encoder uses the conservative two-phase path for every non-identity argument: stage all sources, then commit all destinations. This preserves swaps, longer cycles, and simultaneous assignment.
5. Exact compare-tail-branch fusion
The encoder may fuse only this closed shape:
- the source block contains exactly one signed
I64Setcc; - its terminator tail-jumps to the declared entry of a callee;
- the callee entry contains no instruction and ends in
BranchRel32; - the branch condition is one callee
Boolparameter; - the compare result is the only non-identity tail argument and maps exactly to that condition parameter;
- every sibling argument is an exact typed identity;
- the comparison is signed less-than or signed greater-or-equal.
The fused template loads both I64 operands, emits cmp, canonicalizes the Bool
with setl/setge plus zero extension, stores it directly in the callee
condition home, and emits the signed conditional branch plus the else jump.
The dead source-result home, tail copy, condition reload, and test are not
emitted. setcc, zero extension, and the store preserve the comparison flags.
Any additional instruction, non-identity sibling, immediate branch condition, type mismatch, different entry route, or unsupported comparison refuses fusion and retains ordinary encoding.
6. Exact no-op target threading
Entry transfer, ordinary branch arms, ordinary tail targets, and fused branch arms may thread transitively through an empty block only when its sole action is an exact typed identity tail transfer to the declared callee entry.
Traversal is deterministic and bounded by the finite target program. Missing labels fail validation. A detected cycle returns the caller’s original target; it never invents a cycle exit.
7. Direct-acyclic empty-tail composition
Policy 1.3 may compose a tail route through transitive empty callee entries. At each hop it substitutes the current caller-side operand for the uniquely matching callee parameter home.
Composition requires:
- exact declared entry labels at every hop;
- empty intermediate entries;
- a tail terminator at every traversed intermediate entry;
- exact arity and type preservation;
- unique parameter-home lookup;
- bounded traversal no longer than the function count;
- no repeated callee, ambiguous home, unbound home, malformed target, or arithmetic failure.
A structurally valid composed route is selected only when its final parallel transfer has a complete direct destructive-copy schedule under section 4. If the final transfer would require two-phase staging, the encoder retains the complete original route.
This final gate is normative. Fewer jumps alone is not sufficient evidence that a route is cheaper.
8. Fail-closed behavior
There are two conservative outcomes:
- malformed target input produces the existing typed raw-encoding error and exposes no artifact;
- a valid target that does not satisfy an optimization proof uses its original encoding or the two-phase copy fallback.
No classifier may weaken type, bounds, numeric, effect, ABI, fixup, or source-provenance validation.
Evidence
Static artifact evidence
The accepted policy-1.3 lighthouse shapes are:
| Field | BranchMix | Bounds |
|---|---|---|
| Functions | 121 | 9 |
| Blocks | 139 | 9 |
| Labels | 142 | 12 |
| Fixups | 162 | 14 |
| Plan bytes | 34,742 | 1,653 |
| Semantic artifact bytes | 44,863 | 2,654 |
| Raw target code bytes | 8,070 | 726 |
| Frame bytes | 240 | 144 |
The BranchMix code-size history was:
| Encoder experiment | Code bytes |
|---|---|
| Original unconditional two-phase encoding | 43,124 |
| Identity elision only | 7,924 |
| Identity elision plus acyclic direct scheduling | 5,092 |
| Compare fusion plus no-op threading | 5,002 |
| Naive unrestricted empty-tail composition, rejected | 10,854 |
| Direct-acyclic composition, accepted policy 1.3 | 8,070 |
Direct-acyclic composition accepts 78 of 90 statically composable sites and 165 of 189 non-identity hops. Twelve sites are conservatively uncomposed. Policy 1.3 is 1.6134x larger than policy 1.2 for BranchMix, but 81.29% smaller than the original target.
Dynamic composition evidence
For the frozen BranchMix Gate B workload:
| Metric | Before composition | Naive composition | Direct-acyclic policy 1.3 |
|---|---|---|---|
| Executed blocks | 99,410,299 | 42,678,512 | 51,067,120 |
| Executed tail jumps | 86,827,257 | 30,095,470 | 38,484,078 |
| Tail copy words | 141,354,178 | 161,590,215 | 128,035,793 |
| Tail memory operations | 265,195,925 | 305,667,998 | 238,559,154 |
Naive composition bypassed more jumps but added 20,236,037 copy words and 40,472,073 tail memory operations. Its native benchmark regressed, so it is rejected.
The direct-acyclic gate produces:
accepted dynamic composition events 21,706,861
non-identity hops bypassed 48,343,179
tail jumps removed 48,343,179
tail copy words removed 13,318,385
tail memory operations removed 26,636,771
Relative to the pre-composition trace, it reduces tail jumps by 55.68%, copy words by 9.42%, and tail memory operations by 10.04%.
Gate B observation history
All values below are exact local median*2 integers from the frozen paired
sampler. Each row has its own paired baseline observation.
| Encoder state | NAUX median x2 (ns) | Baseline median x2 (ns) | Ratio |
|---|---|---|---|
| Original policy 1.0 | 1,051,667,140 | 12,799,043 | 82.1676x |
| Identity-only experiment | 157,223,012 | 12,744,328 | 12.3367x |
| Identity plus direct scheduler | 95,815,435 | 12,742,812 | 7.5192x |
| Policy 1.2 fusion/threading | 83,250,490 | 12,718,235 | 6.5458x |
| Naive composition, rejected | 87,242,874 | 12,754,248 | 6.8403x |
| Accepted policy 1.3 | 75,040,133 | 12,783,674 | 5.869997x |
The accepted policy-1.3 observation additionally records:
NAUX p95 39,549,080 ns
baseline p95 6,472,724 ns
NAUX CV admission true
baseline CV admission true
Gate B <= 2.0x threshold false
Policy 1.3 lowers the NAUX median by 9.862% relative to policy 1.2, a 1.1094x incremental speedup. Relative to the original policy-1.0 observation, the cumulative speedup is 14.0147x.
These are local engineering observations. They do not close Gate B: the ratio is above 2.0x and claim-bearing clean-revision and host-admission requirements remain separate.
Consequences
Positive
- Exact identity traffic disappears without changing Machine IR.
- Most acyclic parallel transfers no longer use outgoing staging.
- Cycles retain the original simultaneous-assignment proof.
- Exact compare/branch continuations avoid redundant materialization.
- Direct-acyclic composition reduces both control and memory traffic.
- The change remains deterministic, bounded, source-replayable, and free of third-party backend dependencies.
- Gate B distance falls from 82.1676x to 5.869997x locally.
Negative
- BranchMix policy-1.3 code is larger than policy 1.2 because composed direct copies are emitted at source sites while bypassed bodies remain encoded.
- The raw encoder has a larger proof and adversarial-test surface.
- Every target, native, IPC, standalone, and ELF identity changes together.
- The direct-acyclic predicate may reject profitable cyclic compositions.
- The measured gain is lighthouse-specific, not a universal performance claim.
Risks and mitigations
- Code growth may increase instruction-cache pressure. Retain exact code-size evidence and make future cost policies account for dynamic traffic and emitted bytes.
- Operand substitution could confuse equal-looking homes. Require unique exact typed-home identity and retain the original route otherwise.
- Destructive copies could clobber a live source. Accept only the deterministic word-dependency schedule; otherwise stage in two phases.
- Performance tuning could bypass provenance. Keep optimization inside the versioned owned encoder and regenerate the complete authority chain.
Next target
Gate B remains open. The next performance target is an interference-safe tail-SCC home-coalescing/superblock policy that attacks the remaining dominant stack-home traffic. Compare materialization and bounds-proof work come after that larger structural bottleneck.
Any change to plan semantics, register authority, reachability rules, or
allocation policy requires a new versioned decision and fresh correspondence
evidence. Gate B may be reported complete only when ADR-0041 admission reaches
<= 2.0x; this ADR makes no such claim.