Files
BFM-decomp/cookbook/C0148.md
T

4.5 KiB
Raw Blame History

§137 — REGALLOC-PERM is a TWO-COMPILE ARITHMETIC PROBLEM, not a permuter job

Symptom key: match_one reports REGALLOC-PERM — a clean swap of two registers, everything else byte-identical ($t8/$t9, $s0/$s1, …). Historically this class went to the permuter or was ledgered "unsteerable". It is neither: gcc-2.7.2 decides it by an arithmetic priority you can read out of the compiler's own dumps and then target deliberately.

The mechanism. global.c:allocno_compare ranks by

pri = (int)( floor_log2(R) * R / L * 1e4 * size )

where R = times the register is used and L = the live-range length in insns. Both come out of cc1's own dumps:

cc1 -dl -dg …          # t.i.lreg : "Register N used R times across L insns"
                       # t.i.greg : ";; Register dispositions"

The method (measured on func_801833F0, 328 ins):

  1. Compile with -dl -dg and read R and L for BOTH contenders and their ranked neighbours.
  2. Evaluate pri for each ⇒ you get the exact admissible priority WINDOW that flips the pair. Here the two contenders were ONE unit apart — vtx 1297 vs the giv 1296 — with window (1228, 1296).
  3. R and L are both forced by the emitted code, so source reordering does not move them (measured twice: moving a load's source position changed nothing, because L is recomputed post-sched1). This is why source-level levers are a dead end for this class.
  4. Place a zero-byte __asm__ __volatile__("" :: "r"(v)) (§17/§21 primitive) at the source point that makes L land inside the window. Five probed placements gave L = 190/194/196/219/233/258; only L=219 → pri 1232 worked.

Why this matters: it converts a class we have been routing to the permuter (a random search that this session banked 0 from) into a deterministic two-compile calculation. Try it before the permuter on any clean 2-register swap.

(Companion finding, same wave, func_8017BFEC: a 5-instruction head rotation that was invariant across every legal statement permutation — 8 head orderings × 2 store forms all identical — is the diagnostic signature of priority / birthing-boost, not LUID order. Fix: make the pseudo single-set by splitting a variable reused in two blocks into two locals (sched.md §1.7/§S2, sched.c:2469/2490). An earlier agent had ledgered this exact function DIFF/7 "not steerable from this decomposition" after ~70 source variants — refuted. Invariance under source permutation is information: it says the lever is not in the source order.)

§137a — A gate verdict has a TIMESTAMP; re-check it against the draft's mtime

A redraft agent this wave found its handed-over "genuine byte-DIFF" was stale by 28 minutes: the gate ran at 14:53, the draft was rewritten at 15:22 by an earlier lane whose result had been cached, and it was never re-gated. The agent ran match_one on the file as it stood (D3), got MATCH, and spent its budget proving the file would BANK instead of re-deriving a function that was already done.

The rule: before acting on any failure verdict — yours or a prior wave's — compare the verdict's time against the draft file's mtime. If the file is newer, re-verify before redrafting. This is the same family as the Phase-29 finding that 77% of stored drafts had decayed, but the opposite direction: a stored verdict can be stale because the draft got better, not just worse.

Two offline oracles that agent built, both worth reusing (they close the gap match_one's relocation mask leaves open, without running make):

  1. Full relocation RESOLVE — resolve your .o's relocations against the target .s (D_<addr> / func_<addr> symbol values, HI16/LO16 paired addends) and compare all words. This sees the class the mask hides: a wrong jal target or a wrong %hi/%lo symbol or addend. Result there: 0/227 diffs including every relocation.
  2. Collateral check — objdump the whole TU with and without the splice and require every OTHER function to emit identical words, allowing only .text-relative j addends shifted by exactly your function's size (0x38C there). That catches file-scope declaration damage the target function's own bytes cannot show.

Symptom line for the index: "match_one/rtu_match say MATCH but the whole-overlay SHA still DIFFs" has exactly three causes — a stale verdict, a wrong jal/%hi/%lo target the mask hides, or collateral from file-scope decls — and the two oracles above discriminate all three offline.