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 ();
+}

Reply via email to