On Mon, Jul 6, 2026 at 9:37 AM Andrew Gaul <[email protected]> wrote:
>
> Running x86lint, a linter for suboptimal x86-64 instruction encodings, over
> GCC's own -O2 binaries flags every 128-bit integer load as one byte larger
> than necessary.
>
> TARGET_SSE_TYPELESS_STORES already emits the one-byte-shorter movaps/movups
> for 128-bit SSE stores rather than movdqa/movdqu, which differ only by a 66/F3
> prefix (the saving does not exist under AVX/AVX512, so it is disabled there).
> The identical size win is available for 128-bit loads but has never been 
> taken:
> an integer 128-bit load uses movdqa/movdqu for every -mtune.
>
> Add the symmetric X86_TUNE_SSE_TYPELESS_LOADS, enabled for the same 
> processors,
> and use it in the 128-bit move patterns so a load emits movaps/movups
> (aligned/unaligned respectively).  The substitution is value- and
> alignment-exact -- movaps/movdqa both fault on 16-byte-unaligned memory,
> movups/movdqu neither do -- and the existing AVX guard in the move patterns
> leaves VEX-encoded loads unchanged.  It shrinks every 128-bit struct/array 
> copy
> load by a byte, matching the code clang already generates.
>
> The historical asymmetry appears to be an omission rather than a deliberate
> choice: typeless stores was introduced as a pure size optimization and no
> typeless-loads counterpart was ever proposed; the load mnemonic has otherwise
> only been revisited for correctness (e.g. PR target/92904).  On the targeted
> cores a 128-bit load feeding an integer-domain consumer has no bypass
> ("reformatting") penalty, so as with the store the choice is size-only.

No, the asymmetry is not an omission. The existing
TARGET_SSE_TYPELESS_STORES rationale works because a store is a
one-way trip — data leaves the register file, so whatever "domain"
(int vs. float) the store instruction pretends the data is in doesn't
matter to anything downstream. There's no consumer to suffer a bypass
delay.

A load is different: it populates a register that something else is
going to read. If TARGET_SSE_TYPELESS_LOADS fires unconditionally in
mov<mode>_internal/*movti_internal regardless of what consumes the
loaded value, then a case like

v4si a = *p;
v4si b = a + c;   // padd, integer domain

now loads with movaps and immediately feeds an integer-domain SSE op.
On the classic P6-lineage/Atom-style domain-bypass model, that's
exactly the pattern the movdqa/movaps distinction was invented to
avoid — a real (not just cosmetic) forwarding penalty, not merely an
encoding-size question. Stores can be typeless for free, loads can't
in general because the consumer is unknown at the point this pattern
fires.

>
> gcc/ChangeLog:
>
>         * config/i386/x86-tune.def (X86_TUNE_SSE_TYPELESS_LOADS): New tune
>         flag.
>         * config/i386/i386.h (TARGET_SSE_TYPELESS_LOADS): New macro.
>         * config/i386/sse.md (mov<mode>_internal): Select V4SF mode for a
>         128-bit load when TARGET_SSE_TYPELESS_LOADS, so ix86_output_ssemov
>         emits the shorter movups/movaps.
>         * config/i386/i386.md (*movti_internal): Likewise.
>
> gcc/testsuite/ChangeLog:
>
>         * gcc.target/i386/sse-typeless-loads.c: New test.
>
> Signed-off-by: Andrew Gaul <[email protected]>
> ---
>  gcc/config/i386/i386.h                        |  2 ++
>  gcc/config/i386/i386.md                       |  4 +++
>  gcc/config/i386/sse.md                        |  4 +++
>  gcc/config/i386/x86-tune.def                  |  7 +++++
>  .../gcc.target/i386/sse-typeless-loads.c      | 28 +++++++++++++++++++
>  5 files changed, 45 insertions(+)
>  create mode 100644 gcc/testsuite/gcc.target/i386/sse-typeless-loads.c
>
> diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h
> index 1b8785d7872..d0717e88794 100644
> --- a/gcc/config/i386/i386.h
> +++ b/gcc/config/i386/i386.h
> @@ -394,6 +394,8 @@ extern unsigned char ix86_tune_features[X86_TUNE_LAST];
>  #define TARGET_SSE_SPLIT_REGS  ix86_tune_features[X86_TUNE_SSE_SPLIT_REGS]
>  #define TARGET_SSE_TYPELESS_STORES \
>         ix86_tune_features[X86_TUNE_SSE_TYPELESS_STORES]
> +#define TARGET_SSE_TYPELESS_LOADS \
> +       ix86_tune_features[X86_TUNE_SSE_TYPELESS_LOADS]
>  #define TARGET_SSE_LOAD0_BY_PXOR 
> ix86_tune_features[X86_TUNE_SSE_LOAD0_BY_PXOR]
>  #define TARGET_MEMORY_MISMATCH_STALL \
>         ix86_tune_features[X86_TUNE_MEMORY_MISMATCH_STALL]
> diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
> index f173423b334..524576b180b 100644
> --- a/gcc/config/i386/i386.md
> +++ b/gcc/config/i386/i386.md
> @@ -2577,6 +2577,10 @@
>                (and (eq_attr "alternative" "5")
>                     (match_test "TARGET_SSE_TYPELESS_STORES"))
>                  (const_string "V4SF")
> +              (and (eq_attr "alternative" "4")
> +                   (match_test "MEM_P (operands[1])")
> +                   (match_test "TARGET_SSE_TYPELESS_LOADS"))
> +                (const_string "V4SF")
>                ]
>                (const_string "TI")))
>     (set (attr "preferred_for_speed")
> diff --git a/gcc/config/i386/sse.md b/gcc/config/i386/sse.md
> index 7f652a13c87..7b2e785713a 100644
> --- a/gcc/config/i386/sse.md
> +++ b/gcc/config/i386/sse.md
> @@ -1521,6 +1521,10 @@
>                (and (eq_attr "alternative" "4")
>                     (match_test "TARGET_SSE_TYPELESS_STORES"))
>                  (const_string "V4SF")
> +              (and (eq_attr "alternative" "3")
> +                   (match_test "MEM_P (operands[1])")
> +                   (match_test "TARGET_SSE_TYPELESS_LOADS"))
> +                (const_string "V4SF")
>                (and (eq_attr "alternative" "0")
>                     (match_test "TARGET_SSE_LOAD0_BY_PXOR"))
>                  (const_string "TI")
> diff --git a/gcc/config/i386/x86-tune.def b/gcc/config/i386/x86-tune.def
> index d4c27351ad7..fbc5b352ed5 100644
> --- a/gcc/config/i386/x86-tune.def
> +++ b/gcc/config/i386/x86-tune.def
> @@ -472,6 +472,13 @@ DEF_TUNE (X86_TUNE_SSE_TYPELESS_STORES, 
> "sse_typeless_stores",
>           m_AMD_MULTIPLE | m_ZHAOXIN | m_CORE_ALL | m_TREMONT | m_CORE_HYBRID
>           | m_CORE_ATOM | m_C86_4G | m_GENERIC)
>
> +/* X86_TUNE_SSE_TYPELESS_LOADS: Always movaps/movups for 128bit loads.  As 
> with
> +   typeless stores, the shorter (no 66/F3 prefix) encoding is used; on these
> +   cores a load feeding an integer-domain consumer has no bypass penalty.  */
> +DEF_TUNE (X86_TUNE_SSE_TYPELESS_LOADS, "sse_typeless_loads",
> +         m_AMD_MULTIPLE | m_ZHAOXIN | m_CORE_ALL | m_TREMONT | m_CORE_HYBRID
> +         | m_CORE_ATOM | m_C86_4G | m_GENERIC)

Is this list actually derived from bypass-penalty data, or copy-pasted
from the store side?

The store mask only needs to answer "is the shorter encoding a net win
with no correctness/perf downside," which is basically always true
since there's no consumer to penalize. The load mask needs to answer a
harder, per-microarchitecture question: "does this specific core
charge zero cycles when an integer-domain instruction reads a register
last written by movaps/movups." Those aren't the same claim. The
domain-bypass delay for mixing float-tagged and int-tagged SSE moves
is historically most persistent on the small/in-order-derived Atom
line — Bonnell-generation Atom had a real, documented bypass cost for
this exact pattern.

Uros.

> +
>  /* X86_TUNE_SSE_LOAD0_BY_PXOR: Always use pxor to load0 as opposed to
>     xorps/xorpd and other variants.  */
>  DEF_TUNE (X86_TUNE_SSE_LOAD0_BY_PXOR, "sse_load0_by_pxor",
> diff --git a/gcc/testsuite/gcc.target/i386/sse-typeless-loads.c 
> b/gcc/testsuite/gcc.target/i386/sse-typeless-loads.c
> new file mode 100644
> index 00000000000..ce1b28d2f45
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/i386/sse-typeless-loads.c
> @@ -0,0 +1,28 @@
> +/* Verify X86_TUNE_SSE_TYPELESS_LOADS: on tunings that use typeless 128-bit
> +   stores, a 128-bit load should likewise use the one-byte-shorter 
> movups/movaps
> +   rather than movdqu/movdqa (which differ only by a 66/F3 prefix).  */
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -msse2 -mno-avx -mtune=generic" } */
> +
> +struct S16 { char c[16]; };
> +
> +/* Unaligned copy: movdqu load -> movups (matches the movups store).  */
> +void
> +copy_unaligned (struct S16 *d, const struct S16 *s)
> +{
> +  *d = *s;
> +}
> +
> +typedef int v4si __attribute__ ((vector_size (16)));
> +
> +/* Aligned copy: movdqa load -> movaps (matches the movaps store).  */
> +void
> +copy_aligned (v4si *d, const v4si *s)
> +{
> +  *d = *s;
> +}
> +
> +/* { dg-final { scan-assembler-not "movdqu" } } */
> +/* { dg-final { scan-assembler-not "movdqa" } } */
> +/* { dg-final { scan-assembler "movups" } } */
> +/* { dg-final { scan-assembler "movaps" } } */
> --
> 2.55.0
>

Reply via email to