3.5 KiB
§49 — The LUID DIAL: a zero-byte SCHEDULING dial (the sched.c analogue of §47) — func_8017A4AC (536 ins ×134), Phase 26 session 8
§47 splits a global.c allocno-priority tie by shifting a live-length. This splits a sched.c
rank_for_schedule tie by shifting an insn's position in the expand stream. Same philosophy: when a tie is
broken by an accident of ordering, change the ordering — without changing a single emitted instruction.
The residual. Two adjacent instructions transposed, registers already identical — a pure emission-order
residual. Not §47: global.c was innocent.
The mechanism (two passes, and the proximate cause is not the root cause).
- sched2 (proximate). gcc-2.7.2 schedules BACKWARD (
.sched2printsT-1= the last insn). At the tie point both candidates measuredpriority = 2and the same class vslast_scheduled_insn, sorank_for_schedulefell through to its final tiebreak —return INSN_LUID (tmp) - INSN_LUID (tmp2);— i.e. the tie is decided purely by position in the.gregstream. - sched1 (root).
adjust_priority→birthing_insn_p(bb_live_regs[dest] && reg_n_sets[dest]==1) hands every register-DEFINING insnLAUNCH_PRIORITY = 0x7f000001(max_priority, sched.c:2574). That boost lets the load chain seize the early backward cycles and sinks the un-boosted insn past its rivals — so the.gregLUID order comes out inverted and sched2's tiebreak then picks the wrong one. (A dead-end store —(set (mem) …), never "birthing", priority 2 — is starved and always floats to the front of its block.)
THE DIAL — materialize a call argument's sign-extension into an explicit s32 temp, placed AFTER the
intervening statement.
case 19: {
s16 a, b; s32 ea, eb;
a = ring[i]; i = (i+1) & 0x1FF; ea = a; /* NOT next to the load — see below */
b = ring[i]; i = (i+1) & 0x1FF; eb = b;
f(ea, eb); /* prototype widened to (s32, s32) */
}
It moves the sll/sra 16 pair EARLIER in the expand stream (lowering its INSN_LUID) while emitting exactly
the same instructions. Two placement rules are load-bearing:
- The prototype must be
(s32, s32)so the call itself adds no conversion. ea = a;must sit AFTER the store, not next to the load. Adjacent to the load,combinefuseslhu+sll+srainto a singlelhand you LOSE 3 instructions. The intervening store blocks that fusion, so thesll/srapair survives — identical bytes, earlier LUID.
The zero-byte dial family is now three (all emit nothing; all steer a tie):
| dial | pass | what it shifts | § |
|---|---|---|---|
live-length slider (asm("") between two volatile asms) |
global.c allocno priority |
live_length ±1 | §47 |
| sink-the-init / sink-the-consumer-call into the arms | global.c / local-alloc |
refs + live-range, or deletes the allocno | §48-A1/A4 |
| LUID dial (materialize a temp, placement-controlled) | sched.c rank_for_schedule |
INSN_LUID (expand-stream position) | §49 |
Method note (this is how it was measured, and it is reusable): -dS -dR on cc1 emits the .sched/.sched2
traces — the ready lists, the computed priorities, and the chosen order. When a residual is "two instructions
swapped, same registers", dump the schedule and read the tie: if the priorities are equal, you are on a LUID
tiebreak and the fix is a placement change, not a register change. Harness: .run/a4ac/dump2.sh.