On Sun, 12 Jul 2026, Dominic P wrote:
> store_fixed_bit_field_1 implements a bit-field store as a read-modify-
> write: it reads the destination storage unit into a register, masks out
> the field's bits and ors in the new value, then writes it back. When the
> field occupies the whole unit there are no surrounding bits to preserve,
> yet the read is still emitted. For a non-volatile destination the read is
> dead and later removed, but a volatile read cannot be removed and survives
> as a spurious extra memory access.
>
> On a strict-alignment target a misaligned volatile store is decomposed
> into per-unit stores, so e.g.
>
> struct __attribute__((packed)) {
> unsigned char pad; volatile unsigned v;
> } *p;
> p->v = x;
>
> emits a dead volatile load before every byte/half-word store of the value.
> For a memory-mapped I/O register with read side effects (read-to-clear,
> FIFO pop, W1C) this is a wrong-code bug.
>
> When OP0 is in memory and the store fills the whole unit, store VALUE
> directly with no read. This is deliberately restricted to memory: for a
> register destination the read-modify-write is how a lowpart insertion is
> expressed, which a target may match with a dedicated pattern (e.g. x86
> bswaphisi2_lowpart), and the redundant read is eliminated later anyway.
>
> Genuine volatile bit-fields are unaffected. Under
> -fstrict-volatile-bitfields (the Arm default) a bit-field that fills its
> container is written through the container-width access path in
> store_bit_field_1 and never reaches this code, so it already stores once
> with no read. This was verified to give byte-identical output for byte,
> half-word and word volatile bit-fields, both aligned and in a packed
> struct, with and without -fstrict-volatile-bitfields. The only stores
> this shortcut changes are the decomposed per-unit writes of a misaligned
> volatile scalar -- the motivating case -- which are not bit-fields and
> carry no container-access contract.
>
> The fix is size-neutral -- it removes only the dead volatile read a rare
> whole-unit volatile bit-field store would emit. On five freestanding /
> bare-metal code bases at -O2, .text over the translation units that build
> with both an unpatched and a patched compiler:
>
> Linux kernel (arch/arm) 408 TUs 2,696,101 B -28 (-0.001%)
> SQLite amalgamation (arm1176) 1 TUs 853,260 B -48 (-0.006%)
> Pico SDK (Cortex-M0+) 74 TUs 62,682 B +0 (+0.000%)
> Circle (arm1176, C++) 77 TUs 140,537 B -12 (-0.009%)
> PiTubeDirect (arm1176) 88 TUs 966,021 B -20 (-0.002%)
>
> Bootstrapped and regression-tested on x86_64-pc-linux-gnu with no new
> failures (this is a generic expmed.cc change); also cross-tested on
> arm-none-eabi, where gcc.target/arm (arm.exp) is unchanged against master
> and the gcc.c-torture execute suite gives identical results under QEMU
> between an unpatched and a patched compiler. The new test
> gcc.target/arm/pr71048.c passes.
>
> gcc/ChangeLog:
>
> PR middle-end/71048
> * expmed.cc (store_fixed_bit_field_1): When OP0 is a memory
> reference that the field fills entirely, store VALUE directly
> instead of doing a read-modify-write, so that no read of OP0 is
> emitted.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/arm/pr71048.c: New test.
> * gcc.dg/torture/pr71048-1.c: New test.
>
> Signed-off-by: Dominic P <[email protected]>
> ---
>
> This patch was prepared with the assistance of an AI coding tool. Every line
> of
> code, every test and every measurement was written, reviewed and verified by
> the
> author, who takes responsibility for the patch; the Signed-off-by above
> certifies
> the Developer Certificate of Origin.
>
> gcc/expmed.cc | 17 +++++++++++++
> gcc/testsuite/gcc.dg/torture/pr71048-1.c | 36 ++++++++++++++++++++++++
> gcc/testsuite/gcc.target/arm/pr71048.c | 19 +++++++++++++
> 3 files changed, 72 insertions(+)
> create mode 100644 gcc/testsuite/gcc.dg/torture/pr71048-1.c
> create mode 100644 gcc/testsuite/gcc.target/arm/pr71048.c
>
> diff --git a/gcc/expmed.cc b/gcc/expmed.cc
> index da1b5b632..87b03278f 100644
> --- a/gcc/expmed.cc
> +++ b/gcc/expmed.cc
> @@ -1344,6 +1344,23 @@ store_fixed_bit_field_1 (rtx op0, scalar_int_mode mode,
> if (reverse)
> value = flip_storage_order (mode, value);
>
> + /* If OP0 is a memory reference and the field fills the whole of OP0, there
> + are no surrounding bits to preserve, so store VALUE directly instead of
> + doing a read-modify-write. This does not read OP0 at all: for a
> volatile
> + MEM (such as a memory-mapped I/O register) that read cannot be removed
> by
> + later passes, so it would be a spurious extra access with observable
> side
> + effects (PR71048). This is restricted to memory: for a register
> + destination the read-modify-write is how a lowpart insertion is
> expressed,
> + which a target may match with a dedicated pattern, and the redundant
> read
> + is eliminated later anyway. */
> + if (MEM_P (op0) && bitnum == 0 && bitsize == GET_MODE_BITSIZE (mode))
> + {
> + /* Copy OP0 so the store does not share the caller's MEM rtx. */
> + op0 = copy_rtx (op0);
> + emit_move_insn (op0, value);
I'm not sure if we should have catched this upthread, but also I think
you need to
value = force_reg (mode, value);
before storing the value, both becuase the move might otherwise
be not recognizable and because it might be a MEM overlapping with OP0,
creating a correctness issue?
Richard.
> + return;
> + }
> +
> /* Now clear the chosen bits in OP0,
> except that if VALUE is -1 we need not bother. */
> /* We keep the intermediates in registers to allow CSE to combine
> diff --git a/gcc/testsuite/gcc.dg/torture/pr71048-1.c
> b/gcc/testsuite/gcc.dg/torture/pr71048-1.c
> new file mode 100644
> index 000000000..f2dc83a1b
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/pr71048-1.c
> @@ -0,0 +1,36 @@
> +/* PR middle-end/71048: storing a value that fills a whole memory unit must
> go
> + in without a read-modify-write. This checks, target-independently, that
> the
> + store-directly path produces the correct value for volatile destinations
> of
> + several widths, including the misaligned (packed) case that a
> strict-alignment
> + target decomposes into per-unit stores. */
> +/* { dg-do run } */
> +
> +extern void abort (void);
> +
> +struct __attribute__((packed)) Sp { unsigned char pad; volatile unsigned w;
> };
> +struct Sa { volatile unsigned w; };
> +struct Sh { volatile unsigned short h; };
> +struct Sb { volatile unsigned char b; };
> +
> +static struct Sp sp;
> +static struct Sa sa;
> +static struct Sh sh;
> +static struct Sb sb;
> +
> +static const unsigned vals[] =
> + { 0u, 1u, 0xffu, 0x1234u, 0xdeadbeefu, 0x80000000u, 0xffffffffu,
> 0xa5a5a5a5u };
> +
> +int
> +main (void)
> +{
> + for (unsigned i = 0; i < sizeof (vals) / sizeof (vals[0]); i++)
> + {
> + unsigned v = vals[i];
> +
> + sp.w = v; if (sp.w != v) abort ();
> + sa.w = v; if (sa.w != v) abort ();
> + sh.h = (unsigned short) v; if (sh.h != (unsigned short) v) abort ();
> + sb.b = (unsigned char) v; if (sb.b != (unsigned char) v) abort ();
> + }
> + return 0;
> +}
> diff --git a/gcc/testsuite/gcc.target/arm/pr71048.c
> b/gcc/testsuite/gcc.target/arm/pr71048.c
> new file mode 100644
> index 000000000..9cd243635
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/arm/pr71048.c
> @@ -0,0 +1,19 @@
> +/* PR middle-end/71048: a write-only store to a misaligned volatile object
> must
> + not read the destination back. The store is decomposed into byte/halfword
> + accesses on a strict-alignment target; each unit is fully written, so no
> + read-modify-write (and in particular no spurious volatile read) is
> needed. */
> +/* { dg-do compile } */
> +/* { dg-options "-O2" } */
> +
> +struct __attribute__((packed)) S { unsigned char pad; volatile unsigned val;
> };
> +
> +void
> +wr (struct S *m, unsigned x)
> +{
> + m->val = x;
> +}
> +
> +/* No load through the destination pointer (r0) should be emitted -- that is
> the
> + spurious volatile read the fix removes. Matching only loads based on r0
> (not
> + any incidental ldr, such as a constant-pool load) keeps this robust to
> tuning. */
> +/* { dg-final { scan-assembler-not {ldr[bh]?\t[^\n]*\[r0} } } */
>
--
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald; (HRB 36809, AG Nuernberg)