Investigation of a regression with some RISC-V target changes exposed a clear missed optimization in ext-dce.c

In particular if we mask off bits via a logical AND, then the masked off bits are not live-in for the other input.  Tracking that can in turn allow us to eliminate more extensions.  There's a similar case for logical IOR when it unconditionally turns bits on.

So if we look at this testcase:

typedef long unsigned int size_t;
struct function
{
  unsigned int curr_properties;
  unsigned int last_verified;
};
extern struct function *cfun;

void
execute_function_todo (void *data)
{
  unsigned int flags = (size_t) data;
  if (flags & (1 << 5))
    flags |= (1 << 15);
  (cfun + 0)->last_verified = flags & ((1 << 2) | (1 << 3) | (1 << 4));
}

It currently generates this code for rv64gcbv:

        andi    a5,a0,32
        sext.w  a0,a0
        beq     a5,zero,.L2
        bseti   a0,a0,15
.L2:
        lui     a5,%hi(cfun)
        ld      a5,%lo(cfun)(a5)
        andi    a0,a0,28
        sw      a0,4(a5)
        ret

Note carefully the 2nd andi instruction.  That's unconditionally turning off bits 32..63 (and others).  Thus those bits are not relevant/live for the incoming value in a0.  Walking backwards we find the sext.w which sign extends from bit 31 into bits 32..63. But with bits 32..63 not being live, the sext.w is useless.

After this patch we get:

        andi    a5,a0,32
        beq     a5,zero,.L2
        bseti   a0,a0,15
.L2:
        lui     a5,%hi(cfun)
        ld      a5,%lo(cfun)(a5)
        andi    a0,a0,28
        sw      a0,4(a5)
        ret


It doesn't trigger often based on my quick testing.

Bootstrapped and regression tested on various targets including x86 and riscv.  Also tested on the usual assortment of embedded targets in my tester.  I'll wait for pre-commit CI to give a final verdict.

jeff
gcc/
        * ext-dce.cc (carry_backpropagate): Handle AND and IOR with a constant 
argument.

gcc/testsuite/

        * gcc.target/riscv/ext-dce-2.c: New test.

diff --git a/gcc/ext-dce.cc b/gcc/ext-dce.cc
index d75f7e48574c..d9d136a7b572 100644
--- a/gcc/ext-dce.cc
+++ b/gcc/ext-dce.cc
@@ -966,6 +966,19 @@ carry_backpropagate (unsigned HOST_WIDE_INT mask, enum 
rtx_code code, rtx x)
       /* Recurse into the operand.  */
       return carry_backpropagate (mask, GET_CODE (XEXP (x, 0)), XEXP (x, 0));
 
+    /* If an AND/IOR unconditionally clears/sets bits in the output, then
+       we do not care about the liveness of those bits in the inputs. 
+
+       So AND MASK with the constant for AND and with the complemented constant
+       for IOR.  */
+    case AND:
+    case IOR:
+      if (CONST_INT_P (XEXP (x, 1)))
+       mask &= (code == AND
+                ? UINTVAL (XEXP (x, 1))
+                : ~UINTVAL (XEXP (x, 1)));
+      return mask;
+
     /* We propagate for the shifted operand, but not the shift
        count.  The count is handled specially.  */
     case SS_ASHIFT:
diff --git a/gcc/testsuite/gcc.target/riscv/ext-dce-2.c 
b/gcc/testsuite/gcc.target/riscv/ext-dce-2.c
new file mode 100644
index 000000000000..c73e45f8d301
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/ext-dce-2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile { target rv64 } } */
+/* { dg-options "-O2 -fdump-rtl-ext_dce" } */
+typedef long unsigned int size_t;
+struct function
+{
+  unsigned int curr_properties;
+  unsigned int last_verified;
+};
+extern struct function *cfun;
+
+void
+execute_function_todo (void *data)
+{
+  unsigned int flags = (size_t) data;
+  if (flags & (1 << 5))
+    flags |= (1 << 15);
+  (cfun + 0)->last_verified = flags & ((1 << 2) | (1 << 3) | (1 << 4));
+}
+
+/* { dg-final { scan-rtl-dump "Successfully transformed to:" "ext_dce" } } */
+

Reply via email to