On Mon, Jul 6, 2026 at 11:03 AM Uros Bizjak <[email protected]> wrote:
>
> 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.

FP data usually is accompanied with meta that's from analyzing the
data with the actual format known (like whether it's a NaN or denormal).
If such analysis is performed upon loads or with a separate uop for
a "domain/format mismatch" or whether it's "free" (done for every
operation) will determine what to do here.  Optimization manuals
in the last years always talked to match up load format with the
following operations (possibly only to be safe for future uarchs).
I'd definitely not enable this for m_GENERIC.

Richard.

>
> >
> > 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