On Armv6 in Arm state a byte-aligned 16- or 32-bit field of a packed
structure, or such a bit-field, is accessed a byte at a time although
unaligned_access is the default: extv/extzv are disabled without Thumb-2
and insv FAILs for a MEM.  Add ext<z>vmisalignsi, used only when
extv/extzv are disabled, and let insv use its unaligned store for a MEM
less aligned than the field.  Thumb-1, Armv6T2 and later, and
-mno-unaligned-access are unaffected.

.text on trunk: bcm2835_defconfig vmlinux (its -Os -march=armv6k
-mtune=arm1136j-s) -33832 bytes (0.37%), 601 functions smaller and 6
larger by 32 bytes in total (KCFLAGS=-O2: -41040 bytes, 611 smaller, 7
larger by 28 bytes); Pi1MHz firmware (-O2 -mcpu=arm1176jzf-s) -5872
bytes (0.87%), none larger; of 5450 gcc.c-torture, gcc.dg/torture and
gcc.target/arm files at -march=armv6zk, 34 change at -O2 (-1588 bytes)
and 41 at -Os (-5352), none grows.

Tested on arm-none-eabi, with arm.exp, simd.exp, mve.exp,
advsimd-intrinsics.exp, acle.exp and cmse.exp on the default board and
on a -mthumb -mfloat-abi=hard -mtune=cortex-a9 -march=armv7-a
-mfpu=neon-fp-armv8 board, and with arm.exp, execute.exp and
dg-torture.exp on an Armv6 qemu board with the MMU on: no regressions.

Assisted-by: Claude Opus 5 (Anthropic)

        PR target/55218

gcc/ChangeLog:

        * config/arm/arm.md (insv): Without Thumb-2, use the unaligned
        store path for a MEM less aligned than the field.
        (ext<z>vmisalignsi): New expander.
        * config/arm/iterators.md (any_extract): New code iterator.
        (su): Handle sign_extract and zero_extract.
        (z): New code attribute.

gcc/testsuite/ChangeLog:

        * gcc.target/arm/pr55218-1.c: New test.
        * gcc.target/arm/pr55218-2.c: New test.
        * gcc.target/arm/pr55218-3.c: New test.
        * gcc.target/arm/pr55218-4.c: New test.
        * gcc.target/arm/pr55218-5.c: New test.
        * gcc.target/arm/pr55218-6.c: New test.

Signed-off-by: Dominic P <[email protected]>
---
Measured on trunk (not on silicon): see the numbers in the message above.
They are code sizes only; I make no cycle claim.
Only insv checks MEM_ALIGN: an aligned load is expanded as a plain load
(simple_mem_bitfield_p) before ext<z>vmisalignsi is tried, but an aligned
store to a generic-vector element can reach insv, and it keeps its old
expansion (pr55218-6.c).
A probe reading a packed short as (unsigned char) now gets ldrsh+uxtb
instead of ldrb, as Armv6T2 already does.
OK for trunk?

 gcc/config/arm/arm.md                    |  24 ++++-
 gcc/config/arm/iterators.md              |   9 +-
 gcc/testsuite/gcc.target/arm/pr55218-1.c | 101 +++++++++++++++++++
 gcc/testsuite/gcc.target/arm/pr55218-2.c |  17 ++++
 gcc/testsuite/gcc.target/arm/pr55218-3.c |  11 +++
 gcc/testsuite/gcc.target/arm/pr55218-4.c |  64 ++++++++++++
 gcc/testsuite/gcc.target/arm/pr55218-5.c | 120 +++++++++++++++++++++++
 gcc/testsuite/gcc.target/arm/pr55218-6.c |  16 +++
 8 files changed, 360 insertions(+), 2 deletions(-)
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-1.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-2.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-3.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-4.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-5.c
 create mode 100644 gcc/testsuite/gcc.target/arm/pr55218-6.c

diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index febff17df0b..fc156620f1e 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -3290,7 +3290,8 @@
     HOST_WIDE_INT mask = (HOST_WIDE_INT_1 << width) - 1;
     rtx target, subtarget;
 
-    if (arm_arch_thumb2)
+    if (arm_arch_thumb2
+       || (MEM_P (operands[0]) && MEM_ALIGN (operands[0]) < (unsigned) width))
       {
         if (unaligned_access && MEM_P (operands[0])
            && s_register_operand (operands[3], GET_MODE (operands[3]))
@@ -4965,6 +4966,27 @@
   FAIL;
 })
 
+(define_expand "ext<z>vmisalignsi"
+  [(set (match_operand:SI 0 "s_register_operand")
+       (any_extract:SI (match_operand:BLK 1 "memory_operand")
+                       (match_operand 2 "const_int_operand")
+                       (match_operand 3 "const_int_operand")))]
+  "TARGET_32BIT && unaligned_access"
+{
+  if (INTVAL (operands[3]) % BITS_PER_UNIT != 0)
+    FAIL;
+  if (INTVAL (operands[2]) == 32)
+    emit_insn (gen_unaligned_loadsi (operands[0],
+                                    adjust_address (operands[1], SImode, 0)));
+  else if (INTVAL (operands[2]) == 16)
+    emit_insn (gen_unaligned_loadhi<su> (operands[0],
+                                        adjust_address (operands[1],
+                                                        HImode, 0)));
+  else
+    FAIL;
+  DONE;
+})
+
 ; Helper to expand register forms of extv with the proper modes.
 
 (define_expand "extv_regsi"
diff --git a/gcc/config/arm/iterators.md b/gcc/config/arm/iterators.md
index c8fd31b52c5..602e874b27f 100644
--- a/gcc/config/arm/iterators.md
+++ b/gcc/config/arm/iterators.md
@@ -377,6 +377,9 @@
 ;; A list of widening operators
 (define_code_iterator SE [sign_extend zero_extend])
 
+;; Bit-field extractions
+(define_code_iterator any_extract [sign_extract zero_extract])
+
 ;; Right shifts
 (define_code_iterator RSHIFTS [ashiftrt lshiftrt])
 
@@ -2104,7 +2107,11 @@
 (define_code_attr su_optab [(fix "") (unsigned_fix "u")])
 
 ;; Sign prefix to use in instruction type suffixes, i.e. s32, u32.
-(define_code_attr su [(fix "s") (unsigned_fix "u")])
+(define_code_attr su [(fix "s") (unsigned_fix "u")
+                     (sign_extract "s") (zero_extract "u")])
+
+;; Infix of the bit-field extraction optab names, i.e. extv, extzv.
+(define_code_attr z [(sign_extract "") (zero_extract "z")])
 
 ;; Right shifts
 (define_code_attr shift [(ashiftrt "ashr") (lshiftrt "lshr")])
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-1.c 
b/gcc/testsuite/gcc.target/arm/pr55218-1.c
new file mode 100644
index 00000000000..384f7ec2fb0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-1.c
@@ -0,0 +1,101 @@
+/* PR target/55218: on Armv6 in Arm state a byte-aligned 16- or 32-bit
+   field of a packed structure is accessed with one unaligned
+   ldr/ldrh/ldrsh/str/strh, as on Armv6T2 and later.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -marm -munaligned-access" } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-add-options arm_arch_v6 } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+struct __attribute__ ((packed)) s
+{
+  char c;
+  short h;
+  unsigned short uh;
+  int w;
+};
+
+/*
+** load_w:
+**     ldr     r0, \[r0, #5\]  @ unaligned
+**     bx      lr
+*/
+int
+load_w (struct s *p)
+{
+  return p->w;
+}
+
+/*
+** load_h:
+**     ldrsh   r0, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+int
+load_h (struct s *p)
+{
+  return p->h;
+}
+
+/*
+** load_uh:
+**     ldrh    r0, \[r0, #3\]  @ unaligned
+**     bx      lr
+*/
+unsigned
+load_uh (struct s *p)
+{
+  return p->uh;
+}
+
+/*
+** store_w:
+**     str     r1, \[r0, #5\]  @ unaligned
+**     bx      lr
+*/
+void
+store_w (struct s *p, int v)
+{
+  p->w = v;
+}
+
+/*
+** store_h:
+**     strh    r1, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+void
+store_h (struct s *p, short v)
+{
+  p->h = v;
+}
+
+/* A byte-aligned 16-bit bit-field of an ordinary structure.  */
+struct b
+{
+  char c;
+  unsigned f : 16;
+};
+
+/*
+** load_bitfield:
+**     ldrh    r0, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+unsigned
+load_bitfield (struct b *p)
+{
+  return p->f;
+}
+
+/*
+** store_bitfield:
+**     strh    r1, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+void
+store_bitfield (struct b *p, unsigned v)
+{
+  p->f = v;
+}
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-2.c 
b/gcc/testsuite/gcc.target/arm/pr55218-2.c
new file mode 100644
index 00000000000..dba97e8118e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-2.c
@@ -0,0 +1,17 @@
+/* PR target/55218: with -mno-unaligned-access packed fields are still
+   accessed a byte at a time.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -marm -mno-unaligned-access" } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-add-options arm_arch_v6 } */
+
+struct __attribute__ ((packed)) s { char c; short h; int w; };
+
+int load_w (struct s *p) { return p->w; }
+int load_h (struct s *p) { return p->h; }
+void store_w (struct s *p, int v) { p->w = v; }
+void store_h (struct s *p, short v) { p->h = v; }
+
+/* { dg-final { scan-assembler-not "@ unaligned" } } */
+/* { dg-final { scan-assembler-not "\tldrh\t" } } */
+/* { dg-final { scan-assembler-not "\tstrh\t" } } */
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-3.c 
b/gcc/testsuite/gcc.target/arm/pr55218-3.c
new file mode 100644
index 00000000000..d65e9ebbf15
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-3.c
@@ -0,0 +1,11 @@
+/* PR target/55218: unaligned_loadhis is 32-bit only, so Thumb-1 with
+   -munaligned-access must not use the unaligned expansion.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -mthumb -munaligned-access" } */
+/* { dg-require-effective-target arm_arch_v6_ok } */
+/* { dg-add-options arm_arch_v6 } */
+
+struct __attribute__ ((packed)) s { char c; short h; int w; };
+
+int load_h (struct s *p) { return p->h; }
+int load_w (struct s *p) { return p->w; }
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-4.c 
b/gcc/testsuite/gcc.target/arm/pr55218-4.c
new file mode 100644
index 00000000000..f7ca15c8522
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-4.c
@@ -0,0 +1,64 @@
+/* PR target/55218: big-endian variant of pr55218-1.c.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -marm -mbig-endian -munaligned-access" } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-add-options arm_arch_v6 } */
+/* { dg-add-options check_function_bodies } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+struct __attribute__ ((packed)) s { char c; short h; unsigned short uh; int w; 
};
+
+/*
+** load_w:
+**     ldr     r0, \[r0, #5\]  @ unaligned
+**     bx      lr
+*/
+int
+load_w (struct s *p)
+{
+  return p->w;
+}
+
+/*
+** load_h:
+**     ldrsh   r0, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+int
+load_h (struct s *p)
+{
+  return p->h;
+}
+
+/*
+** load_uh:
+**     ldrh    r0, \[r0, #3\]  @ unaligned
+**     bx      lr
+*/
+unsigned
+load_uh (struct s *p)
+{
+  return p->uh;
+}
+
+/*
+** store_w:
+**     str     r1, \[r0, #5\]  @ unaligned
+**     bx      lr
+*/
+void
+store_w (struct s *p, int v)
+{
+  p->w = v;
+}
+
+/*
+** store_h:
+**     strh    r1, \[r0, #1\]  @ unaligned
+**     bx      lr
+*/
+void
+store_h (struct s *p, short v)
+{
+  p->h = v;
+}
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-5.c 
b/gcc/testsuite/gcc.target/arm/pr55218-5.c
new file mode 100644
index 00000000000..3adb4e7fbac
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-5.c
@@ -0,0 +1,120 @@
+/* PR target/55218: values read and written through unaligned packed
+   fields on Armv6 in Arm state.  */
+/* { dg-do run } */
+/* { dg-require-effective-target arm_arch_v6_arm_multilib } */
+/* { dg-options "-O2 -munaligned-access" } */
+/* { dg-add-options arm_arch_v6_arm } */
+
+struct __attribute__ ((packed)) s
+{
+  char c;
+  short h;
+  unsigned short uh;
+  int w;
+  unsigned uw;
+  long long d;
+};
+
+struct b
+{
+  char c;
+  unsigned f : 16;
+  int g : 16;
+};
+
+struct __attribute__ ((packed)) c
+{
+  unsigned a : 4, f : 16, g : 12;
+};
+
+union u
+{
+  struct s s;
+  unsigned char bytes[sizeof (struct s)];
+};
+
+union v
+{
+  struct b b;
+  unsigned char bytes[sizeof (struct b)];
+};
+
+union w
+{
+  struct c c;
+  unsigned char bytes[sizeof (struct c)];
+};
+
+#define NI __attribute__ ((noipa))
+
+NI int load_h (struct s *p) { return p->h; }
+NI unsigned load_uh (struct s *p) { return p->uh; }
+NI int load_w (struct s *p) { return p->w; }
+NI unsigned load_uw (struct s *p) { return p->uw; }
+NI long long load_d (struct s *p) { return p->d; }
+NI void store_h (struct s *p, short x) { p->h = x; }
+NI void store_uh (struct s *p, unsigned short x) { p->uh = x; }
+NI void store_w (struct s *p, int x) { p->w = x; }
+NI void store_d (struct s *p, long long x) { p->d = x; }
+NI unsigned load_f (struct b *p) { return p->f; }
+NI int load_g (struct b *p) { return p->g; }
+NI void store_f (struct b *p, unsigned x) { p->f = x; }
+NI void store_g (struct b *p, int x) { p->g = x; }
+NI unsigned load_cf (struct c *p) { return p->f; }
+NI int load_cfs (struct c *p) { return (short) p->f; }
+
+#define AT(T, buf, off) \
+  ({ T t_; __builtin_memcpy (&t_, (buf) + (off), sizeof t_); t_; })
+
+int
+main (void)
+{
+  union u x;
+  for (unsigned i = 0; i < sizeof x.bytes; i++)
+    x.bytes[i] = 0x81 + 3 * i;
+
+  if (load_h (&x.s) != AT (short, x.bytes, 1)
+      || load_uh (&x.s) != AT (unsigned short, x.bytes, 3)
+      || load_w (&x.s) != AT (int, x.bytes, 5)
+      || load_uw (&x.s) != AT (unsigned, x.bytes, 9)
+      || load_d (&x.s) != AT (long long, x.bytes, 13))
+    __builtin_abort ();
+
+  unsigned uw = AT (unsigned, x.bytes, 9);
+  store_h (&x.s, -2);
+  store_uh (&x.s, 0x1234);
+  store_w (&x.s, 0x56789abc);
+  store_d (&x.s, 0x0102030405060708LL);
+  if (x.bytes[0] != 0x81
+      || AT (short, x.bytes, 1) != -2
+      || AT (unsigned short, x.bytes, 3) != 0x1234
+      || AT (int, x.bytes, 5) != 0x56789abc
+      || AT (unsigned, x.bytes, 9) != uw
+      || AT (long long, x.bytes, 13) != 0x0102030405060708LL)
+    __builtin_abort ();
+
+  union v y;
+  for (unsigned i = 0; i < sizeof y.bytes; i++)
+    y.bytes[i] = 0xf0 - i;
+  if (load_f (&y.b) != AT (unsigned short, y.bytes, 1))
+    __builtin_abort ();
+  store_f (&y.b, 0xa55a);
+  store_g (&y.b, -3);
+  if (y.bytes[0] != 0xf0
+      || AT (unsigned short, y.bytes, 1) != 0xa55a
+      || load_f (&y.b) != 0xa55a || load_g (&y.b) != -3)
+    __builtin_abort ();
+
+  union w z;
+  for (unsigned i = 0; i < sizeof z.bytes; i++)
+    z.bytes[i] = 0x9c + 5 * i;
+  unsigned cf;
+  if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+    cf = (z.bytes[0] | z.bytes[1] << 8 | z.bytes[2] << 16) >> 4;
+  else
+    cf = (z.bytes[0] << 16 | z.bytes[1] << 8 | z.bytes[2]) >> 4;
+  cf &= 0xffff;
+  if (load_cf (&z.c) != cf || load_cfs (&z.c) != (short) cf)
+    __builtin_abort ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/arm/pr55218-6.c 
b/gcc/testsuite/gcc.target/arm/pr55218-6.c
new file mode 100644
index 00000000000..d8e7bb4e2fd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr55218-6.c
@@ -0,0 +1,16 @@
+/* PR target/55218: an aligned store to an element of a generic vector
+   is not an unaligned access.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -marm -munaligned-access" } */
+/* { dg-require-effective-target arm_arch_v6_arm_ok } */
+/* { dg-add-options arm_arch_v6 } */
+
+typedef int V __attribute__ ((vector_size (32)));
+
+void
+f (V *p, V *q)
+{
+  *p = (*p ^ *q) == *q;
+}
+
+/* { dg-final { scan-assembler-not "@ unaligned" } } */
-- 
2.56.0

Reply via email to