https://gcc.gnu.org/g:1a8e821577dfb535aa54df311ccb282363a93355
commit r15-9307-g1a8e821577dfb535aa54df311ccb282363a93355 Author: Jakub Jelinek <ja...@redhat.com> Date: Tue Apr 8 11:55:13 2025 +0200 cse: Fix up delete_trivially_dead_insns [PR119594] The following testcase is miscompiled by delete_trivially_dead_insns, latently since r0-6313, actually since r15-1575. The problem is in that r0-6313 change, which made count_reg_usage not count uses of the pseudo which the containing SET sets. That is needed so we can delete those instructions as trivially dead if they are really dead, but has the following problem. After fwprop proper we have: (insn 7 2 8 2 (set (reg/v:DI 101 [ g ]) (const_int -1 [0xffffffffffffffff])) "pr119594.c":8:10 95 {*movdi_internal} (nil)) ... (insn 26 24 27 7 (set (reg:DI 104 [ g ]) (zero_extend:DI (subreg:SI (reg/v:DI 101 [ g ]) 0))) "pr119594.c":11:8 175 {*zero_extendsidi2} (expr_list:REG_EQUAL (const_int 4294967295 [0xffffffff]) (expr_list:REG_DEAD (reg/v:DI 101 [ g ]) (nil)))) (insn 27 26 28 7 (set (reg/v:DI 101 [ g ]) (zero_extend:DI (subreg:SI (reg/v:DI 101 [ g ]) 0))) "pr119594.c":11:8 175 {*zero_extendsidi2} (expr_list:REG_EQUAL (const_int 4294967295 [0xffffffff]) (expr_list:REG_UNUSED (reg/v:DI 101 [ g ]) (nil)))) and nothing else uses or sets the 101 and 104 pseudos. The subpass doesn't look at REG_UNUSED or REG_DEAD notes (correctly, as they aren't guaranteed to be accurate). The last change in the IL was forward propagation of (reg:DI 104 [ g ]) value into the following insn. Now, count_reg_usage doesn't count anything on insn 7, the SET_DEST is a reg, so we don't count that and SET_SRC doesn't contain any regs. On insn 26 it counts one usage of pseudo 101 (so counts[101] = 1) and on insn 27 since r0-6313 doesn't count anything as that insn sets pseudo 101 to something that uses it, it isn't a side-effect instruction and can't throw. Now, after counting reg usages the subpass walks the IL from end to start, sees insn 27, counts[101] is non-zero, so insn_live_p is true, nothing is deleted. Then sees insn 26, counts[104] is zero, insn_live_p is false, we delete the insn and decrease associated counts, in this case counts[101] becomes zero. And finally later we process insn 7, counts[101] is now zero, insn_live_p is false, we delete the insn (and decrease associated counts, which aren't any). Except that this resulted in insn 27 staying in the IL but using a REG which is no longer set (and worse, having a REG_EQUAL note of something we need later in the same bb, so we then assume pseudo 101 contains 0xffffffff, which it no longer does. Now, if insn 26 was after insn 27, this would work just fine, we'd first delete that and then insn 27 and then insn 7, which is why most of the time it happens to work fine. The following patch fixes it by detecting the cases where there are self-references after a pseudo has been used at least once outside of the self-references or just as REG_P SET_DEST and in that case only increases the count for the pseudo, making it not trivially deletable. 2025-04-08 Eric Botcazou <botca...@adacore.com> Jakub Jelinek <ja...@redhat.com> PR rtl-optimization/119594 * cse.cc (count_reg_usage): Count even x == dest regs if they have non-zero counts already and incr is positive. * gcc.dg/pr119594.c: New test. Diff: --- gcc/cse.cc | 15 +++++++++++++-- gcc/testsuite/gcc.dg/pr119594.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/gcc/cse.cc b/gcc/cse.cc index 70d5caac4cac..7bdd3b07b1d1 100644 --- a/gcc/cse.cc +++ b/gcc/cse.cc @@ -6762,7 +6762,18 @@ cse_main (rtx_insn *f ATTRIBUTE_UNUSED, int nregs) modify the liveness of DEST. DEST is set to pc_rtx for a trapping insn, or for an insn with side effects. We must then count uses of a SET_DEST regardless, because the insn can't be - deleted here. */ + deleted here. + Also count uses of a SET_DEST if it has been used by an earlier insn, + but in that case only when incrementing and not when decrementing, effectively + making setters of such a pseudo non-eliminable. This is for cases like + (set (reg x) (expr)) + ... + (set (reg y) (expr (reg (x)))) + ... + (set (reg x) (expr (reg (x)))) + where we can't eliminate the last insn because x is is still used, if y + is unused we can eliminate the middle insn and when considering the first insn + we used to eliminate it despite it being used in the last insn. */ static void count_reg_usage (rtx x, int *counts, rtx dest, int incr) @@ -6778,7 +6789,7 @@ count_reg_usage (rtx x, int *counts, rtx dest, int incr) switch (code = GET_CODE (x)) { case REG: - if (x != dest) + if (x != dest || (incr > 0 && counts[REGNO (x)])) counts[REGNO (x)] += incr; return; diff --git a/gcc/testsuite/gcc.dg/pr119594.c b/gcc/testsuite/gcc.dg/pr119594.c new file mode 100644 index 000000000000..5d1cb3796582 --- /dev/null +++ b/gcc/testsuite/gcc.dg/pr119594.c @@ -0,0 +1,32 @@ +/* PR rtl-optimization/119594 */ +/* { dg-do run } */ +/* { dg-options "-Os -fno-dce -fno-tree-dce -fno-tree-dse" } */ + +int a, b; +static unsigned c = -1; + +void +foo (int e) +{ + a = a ^ e; +} + +void +bar (long e) +{ + foo (e >> 1); +} + +int +main () +{ + int g[2]; + for (int h = 0; h < 2; h++) + g[h] = -1; + for (; b; b++) + ; + g[1] = 0; + bar (c); + if (!a) + __builtin_abort (); +}