When combine forms an invalid MEM address (plus X C), find_split_point
splits out X, or gives up if X is a register; neither helps when C is
not a valid offset either. Split out C instead in that case, so that
I3 can keep X, for example a scaled index, in the address. A valid
offset stays where it is, since splitting it out needs one more
register. I2DEST receives the constant, so try_combine now checks that
it has the mode of the address.
On arm, ((volatile unsigned int *) 0x20a04000)[i] = v becomes a constant
load and str v, [rC, ri, lsl #2] instead of lsl, add, add and str; on
aarch64 a register plus a mov/movk constant is no longer added first.
A Raspberry Pi firmware (arm1176, -O2 -flto) shrinks by 96 bytes.
Bootstrapped and regression tested on x86_64-pc-linux-gnu, and regression
tested on arm-none-eabi and aarch64-linux-gnu.
Assisted-by: Claude Opus 5 (Anthropic)
gcc/ChangeLog:
* combine.cc (address_constant_mode_mismatch_p): New function.
(try_combine): Do not split out an address constant into an I2DEST
of another mode.
(find_split_point): Split out a MEM address constant that is not a
valid offset.
gcc/testsuite/ChangeLog:
* gcc.dg/rtl/aarch64/split-const-offset-1.c: New test.
* gcc.dg/torture/split-const-offset-1.c: New test.
* gcc.target/aarch64/split-const-offset-1.c: New test.
* gcc.target/arm/split-const-offset-1.c: New test.
* gcc.target/arm/split-const-offset-2.c: New test.
Signed-off-by: Dominic P <[email protected]>
---
OK for trunk?
gcc/combine.cc | 27 +++++++
.../gcc.dg/rtl/aarch64/split-const-offset-1.c | 71 +++++++++++++++++++
.../gcc.dg/torture/split-const-offset-1.c | 51 +++++++++++++
.../gcc.target/aarch64/split-const-offset-1.c | 28 ++++++++
.../gcc.target/arm/split-const-offset-1.c | 31 ++++++++
.../gcc.target/arm/split-const-offset-2.c | 20 ++++++
6 files changed, 228 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/rtl/aarch64/split-const-offset-1.c
create mode 100644 gcc/testsuite/gcc.dg/torture/split-const-offset-1.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/split-const-offset-1.c
create mode 100644 gcc/testsuite/gcc.target/arm/split-const-offset-1.c
create mode 100644 gcc/testsuite/gcc.target/arm/split-const-offset-2.c
diff --git a/gcc/combine.cc b/gcc/combine.cc
index 11427f5a920..255f32581a4 100644
--- a/gcc/combine.cc
+++ b/gcc/combine.cc
@@ -2508,6 +2508,21 @@ count_auto_inc (rtx, rtx, rtx, rtx, rtx, void *arg)
return 0;
}
+/* Return true if LOC is the constant term of a MEM address in X and MODE
+ is not the mode of that address. */
+
+static bool
+address_constant_mode_mismatch_p (rtx x, rtx *loc, machine_mode mode)
+{
+ subrtx_var_iterator::array_type array;
+ FOR_EACH_SUBRTX_VAR (iter, array, x, NONCONST)
+ if (MEM_P (*iter)
+ && GET_CODE (XEXP (*iter, 0)) == PLUS
+ && &XEXP (XEXP (*iter, 0), 1) == loc)
+ return GET_MODE (XEXP (*iter, 0)) != mode;
+ return false;
+}
+
/* Try to combine the insns I0, I1 and I2 into I3.
Here I0, I1 and I2 appear earlier than I3.
I0 and I1 can be zero; then we combine just I2 into I3, or I1 and I2 into
@@ -3738,6 +3753,8 @@ try_combine (rtx_insn *i3, rtx_insn *i2, rtx_insn *i1,
rtx_insn *i0,
|| GET_MODE (*split) == VOIDmode
|| can_change_dest_mode (i2dest, added_sets_2,
GET_MODE (*split)))
+ && !address_constant_mode_mismatch_p (newpat, split,
+ GET_MODE (i2dest))
&& (next_nonnote_nondebug_insn (i2) == i3
|| !modified_between_p (*split, i2, i3))
/* We can't overwrite I2DEST if its value is still used by
@@ -5026,6 +5043,16 @@ find_split_point (rtx *loc, rtx_insn *insn, bool set_src)
XEXP (XEXP (XEXP (x, 0), 0), 1) = tem;
}
+ /* If the constant is not a valid offset, compute it separately.
+ Test with a pseudo: a virtual REG may accept any offset. */
+ machine_mode address_mode = GET_MODE (XEXP (x, 0));
+ rtx base = alloca_raw_REG (address_mode, LAST_VIRTUAL_REGISTER + 1);
+ rtx addr = alloca_rtx_fmt_ee (PLUS, address_mode, base,
+ XEXP (XEXP (x, 0), 1));
+ if (!memory_address_addr_space_p (GET_MODE (x), addr,
+ MEM_ADDR_SPACE (x)))
+ return &XEXP (XEXP (x, 0), 1);
+
/* If that didn't work, perhaps the first operand is complex and
needs to be computed separately, so make a split point there.
This will occur on machines that just support REG + CONST
diff --git a/gcc/testsuite/gcc.dg/rtl/aarch64/split-const-offset-1.c
b/gcc/testsuite/gcc.dg/rtl/aarch64/split-const-offset-1.c
new file mode 100644
index 00000000000..0fa4d011dfd
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/rtl/aarch64/split-const-offset-1.c
@@ -0,0 +1,71 @@
+/* The address constant must not be split out into the narrower I2DEST. */
+/* { dg-do compile { target aarch64*-*-* } } */
+/* { dg-require-effective-target lp64 } */
+/* { dg-options "-O2 -mlittle-endian" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/*
+** f1:
+** mov (x[0-9]+), -2147483648
+** str w[0-9]+, \[x[0-9]+, \1\]
+** ret
+*/
+void __RTL (startwith ("combine")) f1 (char *p, int z)
+{
+(function "f1"
+ (param "p"
+ (DECL_RTL (reg/v:DI <0> [ p ]))
+ (DECL_RTL_INCOMING (reg:DI x0 [ p ])))
+ (param "z"
+ (DECL_RTL (reg/v:SI <1> [ z ]))
+ (DECL_RTL_INCOMING (reg:SI x1 [ z ])))
+ (insn-chain
+ (block 2
+ (edge-from entry (flags "FALLTHRU"))
+ (cnote 5 [bb 2] NOTE_INSN_BASIC_BLOCK)
+ (cinsn 6 (set (reg:DI <2>) (const_int -2147483648)))
+ (cinsn 7 (set (reg:SI <3>) (and:SI (reg:SI <1>) (subreg:SI (reg:DI <2>)
4)))
+ (expr_list:REG_DEAD (reg:SI <1>)))
+ (cinsn 8 (set (mem:SI (plus:DI (reg:DI <0>) (reg:DI <2>)) [1 S4 A32])
+ (reg:SI <3>))
+ (expr_list:REG_DEAD (reg:DI <0>)
+ (expr_list:REG_DEAD (reg:DI <2>)
+ (expr_list:REG_DEAD (reg:SI <3>)))))
+ (edge-to exit (flags "FALLTHRU"))
+ )
+ )
+)
+}
+
+/*
+** f2:
+** mov (x[0-9]+), 4294967296
+** str w[0-9]+, \[x[0-9]+, \1\]
+** ret
+*/
+void __RTL (startwith ("combine")) f2 (char *p, int z)
+{
+(function "f2"
+ (param "p"
+ (DECL_RTL (reg/v:DI <0> [ p ]))
+ (DECL_RTL_INCOMING (reg:DI x0 [ p ])))
+ (param "z"
+ (DECL_RTL (reg/v:SI <1> [ z ]))
+ (DECL_RTL_INCOMING (reg:SI x1 [ z ])))
+ (insn-chain
+ (block 2
+ (edge-from entry (flags "FALLTHRU"))
+ (cnote 5 [bb 2] NOTE_INSN_BASIC_BLOCK)
+ (cinsn 6 (set (reg:DI <2>) (const_int 4294967296)))
+ (cinsn 7 (set (reg:SI <3>) (udiv:SI (reg:SI <1>) (subreg:SI (reg:DI <2>)
4)))
+ (expr_list:REG_DEAD (reg:SI <1>)))
+ (cinsn 8 (set (mem:SI (plus:DI (reg:DI <0>) (reg:DI <2>)) [1 S4 A32])
+ (reg:SI <3>))
+ (expr_list:REG_DEAD (reg:DI <0>)
+ (expr_list:REG_DEAD (reg:DI <2>)
+ (expr_list:REG_DEAD (reg:SI <3>)))))
+ (edge-to exit (flags "FALLTHRU"))
+ )
+ )
+)
+}
diff --git a/gcc/testsuite/gcc.dg/torture/split-const-offset-1.c
b/gcc/testsuite/gcc.dg/torture/split-const-offset-1.c
new file mode 100644
index 00000000000..5e0663e5cc7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/split-const-offset-1.c
@@ -0,0 +1,51 @@
+/* { dg-do run { target mmap } } */
+
+#include <sys/mman.h>
+#ifndef MAP_ANONYMOUS
+#define MAP_ANONYMOUS MAP_ANON
+#endif
+
+#define BASE 0x20000000UL
+#define W ((volatile unsigned int *) (BASE + 0xa04000))
+
+__attribute__ ((noipa)) void
+st (unsigned int i, unsigned int v)
+{
+ W[i >> 1] = v;
+}
+
+__attribute__ ((noipa)) unsigned int
+ld (unsigned int i)
+{
+ return W[i];
+}
+
+__attribute__ ((noipa)) void
+stp (unsigned char *p, unsigned char v)
+{
+ p[0x1234567] = v;
+}
+
+__attribute__ ((noipa)) unsigned char
+ldp (unsigned char *p)
+{
+ return p[0x1234567];
+}
+
+int
+main ()
+{
+ unsigned char *p = mmap ((void *) BASE, 0x2000000, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (p != (unsigned char *) BASE)
+ return 0;
+ for (unsigned int i = 0; i < 64; i++)
+ st (i, i * 3);
+ for (unsigned int i = 0; i < 32; i++)
+ if (ld (i) != (2 * i + 1) * 3)
+ __builtin_abort ();
+ stp (p, 0x5a);
+ if (ldp (p) != 0x5a || p[0x1234567] != 0x5a || p[0x1234566] != 0)
+ __builtin_abort ();
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/aarch64/split-const-offset-1.c
b/gcc/testsuite/gcc.target/aarch64/split-const-offset-1.c
new file mode 100644
index 00000000000..a6cdb95dfc4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split-const-offset-1.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target lp64 } */
+/* { dg-options "-O2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/*
+** st:
+** ...
+** strb w1, \[x0, x[0-9]+\]
+** ret
+*/
+void
+st (char *p, char x)
+{
+ p[0x123456780] = x;
+}
+
+/*
+** ld:
+** ...
+** ldr w0, \[x0, x[0-9]+\]
+** ret
+*/
+int
+ld (int *p)
+{
+ return p[0x12345678];
+}
diff --git a/gcc/testsuite/gcc.target/arm/split-const-offset-1.c
b/gcc/testsuite/gcc.target/arm/split-const-offset-1.c
new file mode 100644
index 00000000000..7c8f73ae997
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/split-const-offset-1.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm32 } */
+/* { dg-options "-O2" } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+#define W ((volatile unsigned int *) 0x20a04000)
+
+/*
+** st:
+** ...
+** str r1, \[r[0-9]+, r[0-9]+, lsl #2\]
+** bx lr
+*/
+void
+st (unsigned int i, unsigned int v)
+{
+ W[i >> 1] = v;
+}
+
+/*
+** ld:
+** ...
+** ldr r0, \[r[0-9]+, r[0-9]+, lsl #2\]
+** bx lr
+*/
+unsigned int
+ld (unsigned int i)
+{
+ return W[i];
+}
diff --git a/gcc/testsuite/gcc.target/arm/split-const-offset-2.c
b/gcc/testsuite/gcc.target/arm/split-const-offset-2.c
new file mode 100644
index 00000000000..1bbbdf292e3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/split-const-offset-2.c
@@ -0,0 +1,20 @@
+/* A valid offset stays in the address. */
+/* { dg-do compile } */
+/* { dg-require-effective-target arm32 } */
+/* { dg-options "-O2" } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+struct s { char pad[0x300]; unsigned int t[256]; };
+
+/*
+** st:
+** ...
+** str r1, \[r[0-9]+, #1024\]
+** bx lr
+*/
+void
+st (unsigned char *q, unsigned int v)
+{
+ ((volatile struct s *) 0x100)->t[*q] = v;
+}
--
2.55.0