This patch improves TImode moves on x86_64 between SSE and general
registers with SSE2.  This is a partial solution for PR target/114544.
Consider the following simple function:

typedef __int128 v1ti __attribute__((vector_size(16)));
__int128 foo(v1ti x) { return x[0]; }

Currently, with default -O2 (-msse2) the 128-bit value is moved
via the stack:

foo:    movaps  %xmm0, -24(%rsp)        // 5 bytes
        movq    -24(%rsp), %rax         // 5 bytes
        movq    -16(%rsp), %rdx         // 5 bytes
        ret

Though with -O2 -msse4.1, the transfers can be done directly.

foo:    movq    %xmm0, %rax
        pextrq  $1, %xmm0, %rdx
        ret

With this patch, we make use of SSE2's shuffle functionality
to avoid the transfers to and from memory:

foo:    movq    %xmm0, %rax             // 5 bytes
        shufpd  $1, %xmm0, %xmm0        // 5 bytes
        movq    %xmm0, %rdx             // 5 bytes
        ret

Technically, to avoid using a second register we swap/rotate the
high and low lanes between movq instructions, and (not shown above)
also swap/rotate the lanes back again (so the source register remains
unchanged if the value is still live).  This second (restoring) shufpd
is eliminated by later RTL passes if it's discovered to be dead code.

To make this possible, the Yd constraint that enables allows
*movti_internal to match on SSE4_1, is tweaked to form a new YD constrain
that allows *movti_internal to match on SSE2.  In theory, this could
also check TARGET_INTER_UNIT_MOVES_FROM_VEC (when SSE2 but not SSE4_1)
but I'm not sure that the possible benefit is worth the added complexity.


This patch has been tested on x86_64-pc-linux-gnu with make bootstrap
and make -k check, both with and without --target_board=unix{-m32}
with no new failures.  Ok for mainline?


2026-09-10  Roger Sayle  <[email protected]>

gcc/ChangeLog
        PR target/114544
        * config/i386/constraints.md (YD): New constraint, like Yd but
        allows SSE_REGS on TARGET_SSE2.
        * config/i386/i386.md (*movti_internal): Change Yd constraint to YD.
        (define_split): On TARGET_64BIT && TARGET_SSE2 && !TARGET_SSE4_1,
        split *movti_internal into movq+shufpd+movq+shufpd.

gcc/testsuite/ChangeLog
        * gcc.target/i386/pr126293-15.c: Update test case.
        * gcc.target/i386/pr126293-5.c: Likewise.


Thanks,
Roger
--

diff --git a/gcc/config/i386/constraints.md b/gcc/config/i386/constraints.md
index e7dec9cc0c0..af7ebb7670b 100644
--- a/gcc/config/i386/constraints.md
+++ b/gcc/config/i386/constraints.md
@@ -98,6 +98,8 @@
 ;;  z  First SSE register.
 ;;  d  any EVEX encodable SSE register for AVX512DQ target or
 ;;     any SSE register for SSE4_1 target.
+;;  D  any EVEX encodable SSE register for AVX512DQ target or
+;;     any SSE register for SSE2 target.
 ;;  p  Integer register when TARGET_PARTIAL_REG_STALL is disabled
 ;;  a  Integer register when zero extensions with AND are disabled
 ;;  b  Any register that can be used as the GOT base when calling
@@ -121,6 +123,10 @@
  "TARGET_AVX512DQ ? ALL_SSE_REGS : TARGET_SSE4_1 ? SSE_REGS : NO_REGS"
  "@internal Any EVEX encodable SSE register (@code{%xmm0-%xmm31}) for AVX512DQ 
target or any SSE register for SSE4_1 target.")
 
+(define_register_constraint "YD"
+ "TARGET_AVX512DQ ? ALL_SSE_REGS : TARGET_SSE2 ? SSE_REGS : NO_REGS"
+ "@internal Any EVEX encodable SSE register (@code{%xmm0-%xmm31}) for AVX512DQ 
target or any SSE register for SSE2 target.")
+
 (define_register_constraint "Yp"
  "TARGET_PARTIAL_REG_STALL ? NO_REGS : GENERAL_REGS"
  "@internal Any integer register when TARGET_PARTIAL_REG_STALL is disabled.")
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 9888644ed91..0d4749e6e09 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -2553,7 +2553,7 @@
 
 (define_insn "*movti_internal"
   [(set (match_operand:TI 0 "nonimmediate_operand" "=!r ,o ,v,v ,v ,m,?jc,?Yd")
-       (match_operand:TI 1 "general_operand"      "riFo,re,C,BC,vm,v,Yd,jc"))]
+       (match_operand:TI 1 "general_operand"      "riFo,re,C,BC,vm,v,YD,jc"))]
   "(TARGET_64BIT
     && !(MEM_P (operands[0]) && MEM_P (operands[1])))
    || (TARGET_SSE
@@ -2831,6 +2831,22 @@
                  gen_lowpart (<MODE>mode, operands[1]));
 })
 
+(define_split
+  [(set (match_operand:TI 0 "general_reg_operand")
+        (match_operand:TI 1 "sse_reg_operand"))]
+  "TARGET_64BIT && TARGET_SSE2 && !TARGET_SSE4_1
+   && reload_completed"
+  [(const_int 0)]
+{
+  rtx lo = gen_lowpart (DImode, operands[1]);
+  rtx tmp = gen_rtx_REG (V2DImode, REGNO (operands[1]));
+  emit_move_insn (gen_lowpart (DImode, operands[0]), lo);
+  emit_insn (gen_sse2_shufpd_v2di (tmp, tmp, tmp, const1_rtx, GEN_INT (2)));
+  emit_move_insn (gen_highpart (DImode, operands[0]), lo);
+  emit_insn (gen_sse2_shufpd_v2di (tmp, tmp, tmp, const1_rtx, GEN_INT (2)));
+  DONE;
+})
+
 (define_split
   [(set (match_operand:DWI 0 "nonimmediate_gr_operand")
         (match_operand:DWI 1 "general_gr_operand"))]
diff --git a/gcc/testsuite/gcc.target/i386/pr126293-15.c 
b/gcc/testsuite/gcc.target/i386/pr126293-15.c
index 058e432058d..b791a7aa2c7 100644
--- a/gcc/testsuite/gcc.target/i386/pr126293-15.c
+++ b/gcc/testsuite/gcc.target/i386/pr126293-15.c
@@ -8,9 +8,9 @@
 **.LFB0:
 **     .cfi_startproc
 **     movdqa  load\(%rip\), %xmm0
-**     movaps  %xmm0, -24\(%rsp\)
-**     movq    -24\(%rsp\), %rax
-**     movq    -16\(%rsp\), %rdx
+**     movq    %xmm0, %rax
+**     shufpd  \$1, %xmm0, %xmm0
+**     movq    %xmm0, %rdx
 **     ret
 **     .cfi_endproc
 **...
diff --git a/gcc/testsuite/gcc.target/i386/pr126293-5.c 
b/gcc/testsuite/gcc.target/i386/pr126293-5.c
index 2f0794132c9..f5e4a57ed28 100644
--- a/gcc/testsuite/gcc.target/i386/pr126293-5.c
+++ b/gcc/testsuite/gcc.target/i386/pr126293-5.c
@@ -8,9 +8,9 @@
 **.LFB0:
 **     .cfi_startproc
 **     movdqa  load\(%rip\), %xmm0
-**     movaps  %xmm0, -24\(%rsp\)
-**     movq    -24\(%rsp\), %rax
-**     movq    -16\(%rsp\), %rdx
+**     movq    %xmm0, %rax
+**     shufpd  \$1, %xmm0, %xmm0
+**     movq    %xmm0, %rdx
 **     ret
 **     .cfi_endproc
 **...

Reply via email to