ssa-opt: phi-branch-threading repairs downstream PHIs on redirected edges

phi-branch-threading rewrites \`pred → block (with PHI+br-cond) →
{then,else}' into \`pred → then-or-else' based on a const-or-cmp PHI
input. It removed the threaded input from BLOCK's PHI but left
downstream PHIs in {then,else} carrying stale incoming labels that
named the now-bypassed BLOCK. The next prune-stale-phi-args pass
saw \`(VAL :label BLOCK)' in those PHIs, BLOCK no longer in preds,
and pruned it — collapsing a 2-input PHI to a single-input MOV.

When the dropped arg was the value carried on the threaded path,
all downstream uses ended up reading the OTHER input — leaving R7
(or whatever physical reg the PHI mapped to) undefined on the
threaded edge and the verifier rejecting with \`R7 !read_ok'.

Hit by has_key.bt and any \`if (has_key(@m, k)) { ++\$count; }'-style
script: \`++\$count' compiles to \$count = phi(\$count_init,
\$count_after_inc), the inner has_key folds via branch threading,
and the outer PHI gets eaten before its value is realised.

Fix: in phi-thread-one-input, after redirecting PRED → NEW-TARGET,
walk PHIs in NEW-TARGET and add a parallel \`(VAL :label PRED)' entry
for any arg that pointed at BLOCK. Same VAL — the threading is
value-preserving on that path. prune-stale-phi-args then drops the
stale BLOCK arg but keeps the new PRED arg, so the PHI's input
count and meaning are preserved.
Anthony Green2026-05-30 16:10:42 -0400 (�application/grpc grpc-encodinggzip9c0685ad4cfacd7241f7199253bf4e04eac8997f