6.4 KiB
§148 — The loop.c hoisting THRESHOLD is arithmetic you can compute, and the ?: clamp that folds to MIN_EXPR (P30 S42, func_8017C6F4, 947 ins)
Serial run #2. Reached NEAR(63) of 947 (frame 0x120 exact, vars=232 exact, all opcodes,
immediates, stack offsets and branch targets correct; residual is one register rotation). Did not
bank. The levers below are the yield.
A. move_movables hoists iff threshold × savings × lifetime ≥ insn_count — and you can read it
loop.c:1631 sets threshold = 58 for this MIPS config and decrements it by 3 per movable
already moved (threshold -= 3, loop.c:1719/1904). This is the first quantitative handle we have
on gcc-2.7.2 invariant motion.
Measured here: the draft's prim loop was 513 RTL insns, so &g.flag (savings 3, lifetime 3 →
58×3×3 = 522 ≥ 513) got hoisted into a preheader register that the target recomputes inline —
costing a callee-saved register and cascading into 2 extra spills and +96 instructions.
Duplicating the if (za < g.sz2) za = g.sz2; tail into both arms of the sz0>sz1 test (which the
target's cross-jump reveals) pushed the loop to 523 insns → 522 < 523 → not hoisted → the
register came back.
Symptom → lever: an address (&x) hoisted into a loop-preheader register that the target
recomputes inline ⇒ raise the loop's RTL insn count by ~10, or lower the movable's ref count.
Read it directly: cc1 -dL writes <file>.i.loop, which prints
Loop from A to B: N real insns and, per movable, Insn K: regno R (life L), savings S moved/not desirable. Stop guessing which invariant moved — the dump names it.
B. (v < 0x40) ? v : 0x3F is folded to MIN_EXPR and expands to the WRONG SHAPE
fold-const.c:4948 — A < C1 ? A : C2 with C1 == C2+1 becomes MIN(A,C2), which expands as
copy-then-conditionally-overwrite (move t,v; slti; bnez; li). A <= C ? A : C folds too (the
A op B ? A : B rule at 4907).
(v < 0x40) ? v : 0x3F -> MIN_EXPR -> move/slti/bnez/li WRONG
(v > 0x3F) ? 0x3F : v -> no fold -> jumpifnot/store/j/store RIGHT
Same slti $v0,$v,0x40 is emitted either way — but the second keeps gcc's canonical branchy form.
This single respelling took the draft from close 827 to close 63 and fixed all four clamps
instruction-for-instruction. Corollary: (v < 0) ? 0 : X is safe — fold's "swap if arg1 is simpler"
rewrites it to (v >= 0) ? X : 0, which is exactly the target's bltz.
C. A zero-byte ALLOCNO-PRIORITY slider
__asm__ ("" :: "r"(a), "r"(b)); /* emits nothing */
Priority is floor_log2(n_refs)·n_refs·size / live_length, and REG_N_REFS is incremented by loop
depth — so an empty asm with "r" inputs inside a loop adds depth references per operand while
emitting no code. Used here it flipped {rowptr,y} ↔ {cx1,cy1} for the last two callee-saved
registers. This is the counterpart to §47's live-length slider: that one moves the DENOMINATOR,
this one moves the NUMERATOR.
D. Reproduce the original's BUGS verbatim
This variant's F3 arm bbox-tests the packet through PolyFT3 offsets (pkt+8/+0x10/+0x18, stride 8)
while writing F3 xy at stride 4; the FT4 arm reads tmpxy[3].vx where .vy is meant. Both are
original-source copy/paste bugs. Matching means reproducing them.
⚠️ E. A "these are all the same function" claim needs the DRAFT test, not a diff
The run reported all sixteen 947-ins instances as one identical body (→ "one crack banks 15,152 ins").
Checked and it does not hold: the draft scores 63 on ov_SC03_126 but 340 on
func_8017C59C and func_8017CF90 — with an identical first diff on both, i.e. those two match
each other but not the cracked one. That is consistent with the h_norm clustering (947×3, 947×2, plus
singletons): several multi-instance groups, not one group of 16. A normalized-stream diff can say
"same shape"; only running the actual draft against the sibling's asm says "same body". Cheap
test, do it before scaling a ×N claim (sed s/func_A/func_B/ the draft and match_one it).
Tooling note
permuter_ils cannot be aimed at this draft: run_masked reports "Function … not found in
base.c" because the gte_* #define block defeats make_base_c. Demacroize first — worth doing
generally, since any GTE-using draft hits it.
✅ FIXED, and the diagnosis above was WRONG (P30 S43, R14/R35). Nothing about the draft or the macro block defeats
make_base_c—cpp_expand_macrosexists precisely to handle it, and its detector fires correctly here. The real cause:make_base_cran the cpp expansion before#includelines were dropped, somipsel-linux-gnu-cpp -P -nostdinc -died on#include "common.h"(rc=1, empty stdout) and areturn cfallback handed back the unexpanded draft — silently re-creating the exact failure the function was written to prevent. No demacroizing is needed by hand. Fixes: strip#includeinsidecpp_expand_macros(byte-neutral — they are dropped downstream anyway) and RAISE on cpp failure; plusdefines_fn(), an R32 assertion insetup()that the function's definition survives intobase.c, which catches every swallow cause rather than this one (validated: 0 false alarms over 388 stored drafts). Blast radius: 63 stored drafts carried both a#define … __asm__and an#include, including the behemoth renderer drafts — the permuter lane was silently dead on the highest-byte-weight targets on the board. After the fix this draft loads at base score 65 and iterates.The class, third sighting (§G unterminated comment · §133 default-filter · here): a prep step that silently returns its input on failure turns "the tool broke" into "the search found nothing", and the two are indistinguishable at the call site. A permuter run that reports no match in ~0 seconds is a TOOLING verdict until proven otherwise — check
base.cbefore believing the floor. Corollary for §147/§148's hand-search floors: the ~40 probes on this function were run with the permuter unavailable, so "the permuter also plateaus" was never actually measured here.
Symptom lines for the index: "an &address hoisted into a loop preheader" · "a clamp expands as move-then-overwrite" · "MIN_EXPR" · "two callee-saved registers swapped" · "permuter says function not found in base.c".