https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78400
--- Comment #4 from GCC Commits <cvs-commit at gcc dot gnu.org> --- The master branch has been updated by Kyrylo Tkachov <[email protected]>: https://gcc.gnu.org/g:21d783552e49650b6dab5e062842f491dbcfb2d6 commit r17-4505-g21d783552e49650b6dab5e062842f491dbcfb2d6 Author: Kyrylo Tkachov <[email protected]> Date: Wed Sep 16 23:55:02 2026 -0700 middle-end: rescan calls after inserting call-used register zeroing [PR127445] gen_call_used_regs_seq adds the registers it zeroes to crtl->must_be_zero_on_return. df_epilogue_uses_p reports those registers as used by the epilogue, so they join the exit block use set. That set in turn decides which registers a sibling call is recorded as clobbering: df_get_call_refs skips the clobber def of any register that the exit block uses. Growing the set therefore invalidates the recorded defs of every sibling call in the function, but pass_zero_call_used_regs only refreshed the artificial refs, through df_update_exit_block_uses. To hit this a function needs one return path that is a sibling call and another that is a plain return. The zeroing sequence goes on the plain return path while the sibling call keeps its stale defs. The next pass to run df_analyze then ICEs in a df checking compiler. On aarch64 that pass is narrow_gp_writes: during RTL pass: narrow_gp_writes internal compiler error: in df_refs_verify, at df-scan.cc:4012 0x87315f df_refs_verify 0xed6b66 df_insn_refs_verify 0xed8b63 df_bb_verify 0xed8ee7 df_scan_verify() 0xec2161 df_verify() For the sibling call insn the recorded chain holds 65 defs while a fresh scan collects 57, the difference being x0 to x7: (call_insn/j:TI 82 147 83 5 (parallel [ (call (mem:DI (symbol_ref:DI ("bar"))) (const_int 0 [0])) (unspec:DI [(const_int 2 [0x2])] UNSPEC_CALLEE_ABI) (return) ]) 110 {*sibcall_insn} recorded defs { 0 1 2 3 4 5 6 7 8 ... 18 32 ... 86 16 17 } fresh defs { 8 ... 18 32 ... 86 16 17 } Call df_update_entry_exit_and_calls once after all return blocks have been handled. This is the same fix that PR rtl-optimization/78400 applied to shrink-wrapping, and thread_prologue_and_epilogue_insns already does it for the same reason. The df_update_exit_block_uses call being replaced came from PR middle-end/100775, which covered only the artificial refs of the exit block. df_compute_regs_ever_live also calls df_update_entry_exit_and_calls whenever regs_ever_live changed, which is why the bug usually hides: when zeroing a register makes it ever-live for the first time, the next df_analyze repairs the refs by accident. The testcase uses -fzero-call-used-regs=used-arg so that every zeroed register is already live. The original report used -finstrument-functions-once, which supplies the two return paths through the tail call to __cyg_profile_func_exit. Bootstrapped and tested on aarch64-none-linux-gnu. gcc/ChangeLog: PR rtl-optimization/127445 * function.cc (gen_call_used_regs_seq): Return whether a zeroing sequence was emitted. Do not update the exit block uses here. (pass_zero_call_used_regs::execute): Call df_update_entry_exit_and_calls once when a sequence was emitted. gcc/testsuite/ChangeLog: PR rtl-optimization/127445 * gcc.target/aarch64/pr127445.c: New test. Signed-off-by: Kyrylo Tkachov <[email protected]>
