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