https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120550
--- Comment #6 from Jeffrey A. Law <law at gcc dot gnu.org> ---
So this is an ext-dce bug, it just isn't obvious.
ext-dce removes the extension in this insn:
(insn 26 24 29 3 (set (reg:DI 141 [ pretmp_16 ])
(zero_extend:DI (subreg:QI (reg:DI 160) 0))) "j.c":8:5 126
{*zero_extendqidi2_internal}
(expr_list:REG_DEAD (reg:DI 160)
(expr_list:REG_EQUAL (zero_extend:DI (mem/c:QI (symbol_ref:DI ("u")
[flags 0x86] <var_decl 0x7ffff7202d10 u>) [0 u+0 S1 A8]))
(nil))))
I've traced the values around the CFG and the removal is valid. But there's a
REG_EQUAL note that gets left around. So after ext-dce we have:
(insn 26 24 29 3 (set (reg:DI 141 [ pretmp_16 ])
(reg:DI 160)) "j.c":8:5 277 {*movdi_64bit}
(expr_list:REG_DEAD (reg:DI 160)
(expr_list:REG_EQUAL (zero_extend:DI (mem/c:QI (symbol_ref:DI ("u")
[flags 0x86] <var_decl 0x7ffff7202d10 u>) [0 u+0 S1 A8]))
(nil))))
That's the bug. Combine later kicks in and sees the REG_EQUAL note and adjust
the nonzero bits for (reg 141) in the expected way, but they don't accurately
reflect the value in (reg 141). Combine later uses the incorrect nonzero_bits
and eliminates a different (and necessary) extension.
The fix is simple. When ext-dce makes a change it can just wipe the REG_EQUAL
note. We could try and be selective about the notes we remove, but I doubt
it's worth the effort and analysis to be selective.