On a STRICT_ALIGNMENT target store merging never widens a group below its
alignment, even where movmisalign<mode> is one unaligned str or strh.  Let such
targets use the aligned/unaligned store-count comparison for each size whose
movmisalign expansion, like every smaller one split_group may shrink it to,
costs no more for size than the aligned accesses it replaces.  arm's unaligned
strh and str pass.  Alpha without BWX (EV5) merges and shrinks; alpha with BWX,
whose byte stores are cheaper, is unchanged.

Pi1MHz firmware (arm1176, -O2, LTO): .text -416 bytes; a merged strh takes 30%
fewer cycles than the two strb it replaces.

Bootstrapped and regression tested on x86_64-pc-linux-gnu.  Regression tested
on arm-none-eabi (arm.exp, dg.exp, tree-ssa.exp; compile-only Cortex-A15 Arm and
Thumb, Cortex-M0, Armv8-A); execute.exp, dg-torture.exp and dg.exp are unchanged
on qemu arm1176 with the MMU on.

Assisted-by: Claude Opus 5 (Anthropic)

gcc/ChangeLog:

        * gimple-ssa-store-merging.cc: Include memmodel.h, emit-rtl.h and
        optabs.h.
        (max_access_size): New function.
        (imm_store_chain_info::try_coalesce_bswap): Use it for the store and
        the load alignment checks.
        (split_group): Use it to limit unaligned stores on STRICT_ALIGNMENT
        targets and loads below their alignment.
        (imm_store_chain_info::output_merged_store): Allow unaligned stores
        on STRICT_ALIGNMENT targets but not unaligned loads.  Adjust the
        function comment.

gcc/testsuite/ChangeLog:

        * gcc.dg/torture/store-merging-misaligned.c: New test.
        * gcc.target/arm/store-merge-unaligned.c: New test.
        * gcc.target/arm/store-merge-unaligned-2.c: New test.
        * gcc.target/arm/store-merge-unaligned-3.c: New test.
        * gcc.target/arm/store-merge-unaligned-4.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
Changes in v2: no new hook (Andrea Pinski).  Store merging asks about its own
transform (Richard Biener): below the alignment it uses movmisalign<mode> only
where that costs no more than the aligned accesses it replaces.  The arm patch
is gone; the tests check function bodies, with SLP off where it merged first.

Building RTL at GIMPLE time for costs has precedent (ivopts' computation_cost,
tree-ssa-address, reassoc and switch conversion); here it asks the target only
about the one sequence the pass would emit.

OK for trunk?

 gcc/gimple-ssa-store-merging.cc               | 56 +++++++++--
 .../gcc.dg/torture/store-merging-misaligned.c | 43 ++++++++
 .../gcc.target/arm/store-merge-unaligned-2.c  | 22 +++++
 .../gcc.target/arm/store-merge-unaligned-3.c  | 20 ++++
 .../gcc.target/arm/store-merge-unaligned-4.c  | 20 ++++
 .../gcc.target/arm/store-merge-unaligned.c    | 99 +++++++++++++++++++
 6 files changed, 253 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/torture/store-merging-misaligned.c
 create mode 100644 gcc/testsuite/gcc.target/arm/store-merge-unaligned-2.c
 create mode 100644 gcc/testsuite/gcc.target/arm/store-merge-unaligned-3.c
 create mode 100644 gcc/testsuite/gcc.target/arm/store-merge-unaligned-4.c
 create mode 100644 gcc/testsuite/gcc.target/arm/store-merge-unaligned.c

diff --git a/gcc/gimple-ssa-store-merging.cc b/gcc/gimple-ssa-store-merging.cc
index 39ef59ab9a6..7f086f1fb35 100644
--- a/gcc/gimple-ssa-store-merging.cc
+++ b/gcc/gimple-ssa-store-merging.cc
@@ -166,7 +166,10 @@
 #include "target.h"
 #include "gimplify-me.h"
 #include "rtl.h"
+#include "memmodel.h"
+#include "emit-rtl.h"
 #include "expr.h"      /* For get_bit_range.  */
+#include "optabs.h"
 #include "optabs-tree.h"
 #include "dbgcnt.h"
 #include "selftest.h"
@@ -2952,6 +2955,44 @@ check_no_overlap (const vec<store_immediate_info *> 
&m_store_info,
   return true;
 }
 
+/* Return the largest power of two of at most SIZE bits that a load (if LOAD)
+   or store at ALIGN bits of alignment may use.  A size above ALIGN needs a
+   movmisalign whose size cost, like that of every smaller such size, is no
+   more than that of the aligned accesses it replaces.  */
+
+static unsigned HOST_WIDE_INT
+max_access_size (unsigned HOST_WIDE_INT size, unsigned HOST_WIDE_INT align,
+                bool load)
+{
+  unsigned HOST_WIDE_INT s = MIN (size, align);
+  if (!param_store_merging_allow_unaligned || s == size)
+    return s;
+  rtx addr = gen_raw_REG (Pmode, LAST_VIRTUAL_REGISTER + 1);
+  rtx mem = gen_rtx_MEM (int_mode_for_size (s, 0).require (), addr);
+  rtx reg = gen_raw_REG (GET_MODE (mem), LAST_VIRTUAL_REGISTER + 2);
+  start_sequence ();
+  emit_move_insn (load ? reg : mem, load ? mem : reg);
+  unsigned aligned_cost = seq_cost (end_sequence (), false);
+  for (; s < size; s *= 2)
+    {
+      scalar_int_mode mode = int_mode_for_size (s * 2, 0).require ();
+      insn_code icode = optab_handler (movmisalign_optab, mode);
+      if (icode == CODE_FOR_nothing)
+       break;
+      mem = gen_rtx_MEM (mode, addr);
+      set_mem_align (mem, align);
+      reg = gen_raw_REG (mode, LAST_VIRTUAL_REGISTER + 2);
+      class expand_operand ops[2];
+      create_fixed_operand (&ops[0], load ? reg : mem);
+      create_fixed_operand (&ops[1], load ? mem : reg);
+      start_sequence ();
+      expand_insn (icode, 2, ops);
+      if (seq_cost (end_sequence (), false) > aligned_cost * (s * 2 / align))
+       break;
+    }
+  return s;
+}
+
 /* Return true if m_store_info[first] and at least one following store
    form a group which store try_size bitsize value which is byte swapped
    from a memory load or some value, or identity from some value.
@@ -3006,7 +3047,7 @@ imm_store_chain_info::try_coalesce_bswap 
(merged_store_group *merged_store,
        = (m_store_info[first]->bitpos - align_base) & (align - 1);
       if (align_bitpos)
        align = least_bit_hwi (align_bitpos);
-      if (align < try_size)
+      if (max_access_size (try_size, align, false) < try_size)
        return false;
     }
 
@@ -3145,7 +3186,7 @@ imm_store_chain_info::try_coalesce_bswap 
(merged_store_group *merged_store,
   if (!allow_unaligned && n.base_addr)
     {
       unsigned int align = get_object_alignment (n.src);
-      if (align < try_size)
+      if (max_access_size (try_size, align, true) < try_size)
        return false;
     }
 
@@ -3933,6 +3974,8 @@ split_group (merged_store_group *group, bool 
allow_unaligned_store,
        align = least_bit_hwi (align_bitpos);
       if (!allow_unaligned_store)
        try_size = MIN (try_size, align);
+      else if (STRICT_ALIGNMENT)
+       try_size = max_access_size (try_size, align, false);
       if (!allow_unaligned_load)
        {
          /* If we can't do or don't want to do unaligned stores
@@ -3956,7 +3999,7 @@ split_group (merged_store_group *group, bool 
allow_unaligned_store,
                    load_align = MIN (load_align, a);
                  }
              }
-         try_size = MIN (try_size, load_align);
+         try_size = max_access_size (try_size, load_align, true);
        }
       store_immediate_info *info
        = find_constituent_stores (group, NULL, &first, try_bitpos, try_size);
@@ -4229,7 +4272,7 @@ invert_op (split_store *split_store, int idx, tree 
int_type, tree &mask)
 /* Given a merged store group GROUP output the widened version of it.
    The store chain is against the base object BASE.
    Try store sizes of at most MAX_STORE_BITSIZE bits wide and don't output
-   unaligned stores for STRICT_ALIGNMENT targets or if it's too expensive.
+   unaligned stores if it's too expensive.
    Make sure that the number of statements output is less than the number of
    original statements.  If a better sequence is possible emit it and
    return true.  */
@@ -4243,9 +4286,8 @@ imm_store_chain_info::output_merged_store 
(merged_store_group *group)
   if (orig_num_stmts < 2)
     return false;
 
-  bool allow_unaligned_store
-    = !STRICT_ALIGNMENT && param_store_merging_allow_unaligned;
-  bool allow_unaligned_load = allow_unaligned_store;
+  bool allow_unaligned_store = param_store_merging_allow_unaligned;
+  bool allow_unaligned_load = !STRICT_ALIGNMENT && allow_unaligned_store;
   bool bzero_first = false;
   store_immediate_info *store;
   unsigned int num_clobber_stmts = 0;
diff --git a/gcc/testsuite/gcc.dg/torture/store-merging-misaligned.c 
b/gcc/testsuite/gcc.dg/torture/store-merging-misaligned.c
new file mode 100644
index 00000000000..6a8e8791fee
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/store-merging-misaligned.c
@@ -0,0 +1,43 @@
+/* Byte stores merged below their alignment must store the right bytes.  */
+
+/* { dg-do run } */
+
+typedef __UINT8_TYPE__ u8;
+typedef __UINT32_TYPE__ u32;
+
+static u8 buf[24];
+
+static void __attribute__((noipa))
+st_h (u8 *p, u32 v) { p[0] = v; p[1] = v >> 8; }
+
+static void __attribute__((noipa))
+st_w (u8 *p, u32 v)
+{ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
+
+static void __attribute__((noipa))
+st_wbe (u8 *p, u32 v)
+{ p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v; }
+
+int
+main (void)
+{
+  for (int off = 0; off < 8; off++)
+    {
+      __builtin_memset (buf, 0xa5, sizeof (buf));
+      st_h (buf + off, 0x1234);
+      st_w (buf + off + 4, 0x89abcdef);
+      st_wbe (buf + off + 12, 0x89abcdef);
+      if (buf[off] != 0x34 || buf[off + 1] != 0x12)
+       __builtin_abort ();
+      if (buf[off + 4] != 0xef || buf[off + 5] != 0xcd
+         || buf[off + 6] != 0xab || buf[off + 7] != 0x89)
+       __builtin_abort ();
+      if (buf[off + 12] != 0x89 || buf[off + 13] != 0xab
+         || buf[off + 14] != 0xcd || buf[off + 15] != 0xef)
+       __builtin_abort ();
+      if (buf[off + 2] != 0xa5 || buf[off + 3] != 0xa5
+         || buf[off + 8] != 0xa5 || buf[off + 11] != 0xa5)
+       __builtin_abort ();
+    }
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/arm/store-merge-unaligned-2.c 
b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-2.c
new file mode 100644
index 00000000000..3517c9c3c41
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-2.c
@@ -0,0 +1,22 @@
+/* Without unaligned access there is no movmisalign, so nothing merges.  */
+
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-options "-O2 -mno-unaligned-access -fno-tree-slp-vectorize" } */
+/* { dg-add-options arm_arch_v6_arm } */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+void h_le (u8 *p, u32 v) { p[0] = v; p[1] = v >> 8; }
+void w_le (u8 *p, u32 v)
+{ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
+void cph (u8 *__restrict d, const u8 *__restrict q)
+{
+  u8 *p = __builtin_assume_aligned (d, 2);
+  p[0] = q[0]; p[1] = q[1];
+}
+
+/* { dg-final { scan-assembler-not {\tstrh\t} } } */
+/* { dg-final { scan-assembler-not {\tstr\t} } } */
+/* { dg-final { scan-assembler-not {\tldrh\t} } } */
diff --git a/gcc/testsuite/gcc.target/arm/store-merge-unaligned-3.c 
b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-3.c
new file mode 100644
index 00000000000..bd2e2c26ea9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-3.c
@@ -0,0 +1,20 @@
+/* --param=store-merging-allow-unaligned=0 stops the unaligned merges.  */
+
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-options "-O2 -fno-tree-slp-vectorize 
--param=store-merging-allow-unaligned=0" } */
+/* { dg-add-options arm_arch_v6_arm } */
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+
+void h_le (u8 *p, u32 v) { p[0] = v; p[1] = v >> 8; }
+void h_be (u8 *p, u32 v) { p[0] = v >> 8; p[1] = v; }
+void cph (u8 *__restrict d, const u8 *__restrict q)
+{
+  u8 *p = __builtin_assume_aligned (d, 2);
+  p[0] = q[0]; p[1] = q[1];
+}
+
+/* { dg-final { scan-assembler-not {\tstrh\t} } } */
+/* { dg-final { scan-assembler-not {\tldrh\t} } } */
diff --git a/gcc/testsuite/gcc.target/arm/store-merge-unaligned-4.c 
b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-4.c
new file mode 100644
index 00000000000..9cdf9f7bf14
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/store-merge-unaligned-4.c
@@ -0,0 +1,20 @@
+/* The bswap path: a reversed unaligned copy becomes ldr, rev and str.  */
+
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-options "-O2 -fno-tree-slp-vectorize" } */
+/* { dg-add-options arm_arch_v6_arm } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned char u8;
+
+/*
+** copy_be:
+**     ldr     (r[0-9]+), \[r1\]       @ unaligned
+**     rev     \1, \1
+**     str     \1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void copy_be (u8 *__restrict p, const u8 *__restrict q)
+{ p[0] = q[3]; p[1] = q[2]; p[2] = q[1]; p[3] = q[0]; }
diff --git a/gcc/testsuite/gcc.target/arm/store-merge-unaligned.c 
b/gcc/testsuite/gcc.target/arm/store-merge-unaligned.c
new file mode 100644
index 00000000000..c3dd7350dde
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/store-merge-unaligned.c
@@ -0,0 +1,99 @@
+/* Stores below their alignment merge into unaligned strh or str on Armv6.  */
+
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-require-effective-target arm_little_endian } */
+/* { dg-options "-O2 -fno-tree-slp-vectorize" } */
+/* { dg-add-options arm_arch_v6_arm } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" } } */
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+
+/*
+** h_le:
+**     strh    r1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void h_le (u8 *p, u32 v) { p[0] = v; p[1] = v >> 8; }
+
+/*
+** h_be:
+**     rev16   (r[0-9]+), r1
+**     strh    \1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void h_be (u8 *p, u32 v) { p[0] = v >> 8; p[1] = v; }
+
+/*
+** w_le:
+**     str     r1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void w_le (u8 *p, u32 v)
+{ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
+
+/*
+** w_be:
+**     rev     (r[0-9]+), r1
+**     str     \1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void w_be (u8 *p, u32 v)
+{ p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v; }
+
+/*
+** hh:
+**     str     r1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void hh (u16 *p, u32 v) { p[0] = v; p[1] = v >> 16; }
+
+/*
+** t3:
+**     lsr     (r[0-9]+), r1, #16
+**     strh    r1, \[r0\]      @ unaligned
+**     strb    \1, \[r0, #2\]
+**     bx      lr
+*/
+void t3 (u8 *p, u32 v) { p[0] = v; p[1] = v >> 8; p[2] = v >> 16; }
+
+/*
+** cp4:
+**     ldr     (r[0-9]+), \[r1\]       @ unaligned
+**     str     \1, \[r0\]      @ unaligned
+**     bx      lr
+*/
+void cp4 (u8 *__restrict p, const u8 *__restrict q)
+{ p[0] = q[0]; p[1] = q[1]; p[2] = q[2]; p[3] = q[3]; }
+
+/*
+** cpa:
+**     ldr     (r[0-9]+), \[r1\]       @ unaligned
+**     str     \1, \[r0\]
+**     bx      lr
+*/
+void cpa (u8 *__restrict d, const u8 *__restrict q)
+{
+  u8 *p = __builtin_assume_aligned (d, 4);
+  p[0] = q[0]; p[1] = q[1]; p[2] = q[2]; p[3] = q[3];
+}
+
+struct ctx { u32 s[2]; };
+
+static void st (u8 *p, u32 v)
+{ p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; }
+
+/* C and OUT may alias, so only store merging can widen these.  */
+
+/*
+** w:
+**     ldr     (r[0-9]+), \[r0\]
+**     str     \1, \[r1\]      @ unaligned
+**     ldr     (r[0-9]+), \[r0, #4\]
+**     str     \2, \[r1, #4\]  @ unaligned
+**     bx      lr
+*/
+void w (struct ctx *c, u8 *out) { st (out, c->s[0]); st (out + 4, c->s[1]); }
-- 
2.55.0

Reply via email to