]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
i386: Split DI<->V2DI patterns before reload with -m32.
authorRoger Sayle <roger@nextmovesoftware.com>
Tue, 21 Jul 2026 08:45:15 +0000 (09:45 +0100)
committerRoger Sayle <roger@nextmovesoftware.com>
Tue, 21 Jul 2026 08:45:15 +0000 (09:45 +0100)
While investigating improvements to x86's stv2 pass (to correctly cost
moves between SI<->V4SI and DI<->V2DI), I noticed that we're currently
relatively inefficient for DI mode transfers on 32-bit targets with
SSE2, where reload ultimately decides to perform these moves via the
stack.  It's possible to do better by making the highpart and lowpart
registers explicit before reload.

Consider the test case below:

typedef long long v2di __attribute__ ((__vector_size__ (16)));

long long foo(v2di x)
{
  return x[0];
}

long long ext();
v2di mem;

void bar()
{
  long long x = ext();
  mem = (v2di){x,0};
}

where foo tests V2DI->DI mode, and bar tests DI->V2DI mode.
Currently -m32 -O2 -msse2 generates:

foo:    subl    $28, %esp
        movq    %xmm0, 8(%esp)
        movl    8(%esp), %eax
        movl    12(%esp), %edx
        addl    $28, %esp
        ret

bar: subl    $28, %esp
        call    ext
        movl    %eax, 8(%esp)
        movl    %edx, 12(%esp)
        movq    8(%esp), %xmm0
        movaps  %xmm0, mem
        addl    $28, %esp
        ret

With this patch, we now avoid going via the stack:

foo: movd    %xmm0, %eax
        pshufd  $225, %xmm0, %xmm0
        movd    %xmm0, %edx
        ret

bar: subl    $12, %esp
        call    ext
        movd    %eax, %xmm0
        movd    %edx, %xmm1
        punpckldq       %xmm1, %xmm0
        movaps  %xmm0, mem
        addl    $12, %esp
        ret

2026-07-21  Roger Sayle  <roger@nextmovesoftware.com>
    Uros Bizjak  <ubizjak@gmail.com>

gcc/ChangeLog
* config/i386/sse.md (define_split): Split *vec_extractv2di_0_sse
before reload on !TARGET_64BIT with TARGET_SSE2.
(define_split): Likewise split *vec_concatv2di_0 before reload
on !TARGET_64BIT with TARGET_SSE2.

gcc/testsuite/ChangeLog
* gcc.target/i386/sse2-stv-7.c: New test case.
* gcc.target/i386/sse4_1-stv-13.c: Likewise.

gcc/config/i386/sse.md
gcc/testsuite/gcc.target/i386/sse2-stv-7.c [new file with mode: 0644]
gcc/testsuite/gcc.target/i386/sse4_1-stv-13.c [new file with mode: 0644]

index 10850189043c863cde412f6e67e923dc69c0b83b..122dfb3676463361be286aaabf1dacac9e8e45d2 100644 (file)
   split_double_mode (DImode, &operands[0], 1, &operands[2], &operands[3]);
 })
 
+;; Split *vec_extractv2di_0_sse before reload with -m32 -msse2 -mno-sse4.1
+;; to avoid going via memory.
+(define_split
+  [(set (match_operand:DI 0 "register_operand")
+       (vec_select:DI
+         (match_operand:V2DI 1 "register_operand")
+         (parallel [(const_int 0)])))]
+  "!TARGET_64BIT && TARGET_SSE2 && !TARGET_SSE4_1
+   && TARGET_INTER_UNIT_MOVES_FROM_VEC
+   && ix86_pre_reload_split ()"
+  [(set (match_dup 2) (match_dup 4))
+   (set (match_dup 5)
+       (vec_select:V4SI
+         (match_dup 6)
+         (parallel [(const_int 1) (const_int 0)
+                    (const_int 2) (const_int 3)])))
+   (set (match_dup 3) (match_dup 7))]
+{
+  operands[4] = gen_lowpart (SImode, operands[1]);
+  operands[5] = gen_reg_rtx (V4SImode);
+  operands[6] = gen_lowpart (V4SImode, operands[1]);
+  operands[7] = gen_lowpart (SImode, operands[5]);
+  split_double_mode (DImode, &operands[0], 1, &operands[2], &operands[3]);
+})
+
 (define_split
   [(set (match_operand:SWI48x 0 "nonimmediate_operand")
        (vec_select:SWI48x
           ]
           (symbol_ref "true")))])
 
+;; Split *vec_concatv2di_0 before reload with -m32 -msse2 -mno-sse4.1
+;; to avoid going via memory.  Also helps reload with -m32 -msse4.1.
+(define_split
+  [(set (match_operand:V2DI 0 "register_operand")
+       (vec_concat:V2DI
+         (match_operand:DI 1 "register_operand")
+         (const_int 0)))]
+  "!TARGET_64BIT && TARGET_SSE2
+   && TARGET_INTER_UNIT_MOVES_TO_VEC
+   && ix86_pre_reload_split ()"
+  [(set (match_dup 0) (match_dup 2))]
+{
+  rtx lo, hi;
+  split_double_mode (DImode, &operands[1], 1, &lo, &hi);
+  rtx tmp1 = gen_reg_rtx (V4SImode);
+  emit_insn (gen_vec_setv4si_0 (tmp1, CONST0_RTX (V4SImode), lo));
+  rtx result = gen_reg_rtx (V4SImode);
+  if (TARGET_SSE4_1)
+    emit_insn (gen_sse4_1_pinsrd (result, tmp1, hi, GEN_INT (2)));
+  else
+    {
+      rtx tmp2 = gen_reg_rtx (V4SImode);
+      emit_insn (gen_vec_setv4si_0 (tmp2, CONST0_RTX (V4SImode), hi));
+      emit_insn (gen_vec_interleave_lowv4si (result, tmp1, tmp2));
+    }
+  operands[2] = gen_lowpart (V2DImode, result);
+})
+
 ;; vmovq clears also the higher bits.
 (define_insn "vec_set<mode>_0"
   [(set (match_operand:VI8_AVX_AVX512F 0 "register_operand" "=v,v")
diff --git a/gcc/testsuite/gcc.target/i386/sse2-stv-7.c b/gcc/testsuite/gcc.target/i386/sse2-stv-7.c
new file mode 100644 (file)
index 0000000..a173980
--- /dev/null
@@ -0,0 +1,22 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -msse2 -mno-sse4.1" } */
+
+typedef long long v2di __attribute__ ((__vector_size__ (16)));
+
+long long foo(v2di x)
+{
+  return x[0];
+}
+
+long long ext();
+v2di mem;
+
+void bar()
+{
+  long long x = ext();
+  mem = (v2di){x,0};
+}
+
+/* { dg-final { scan-assembler "pshufd" } } */
+/* { dg-final { scan-assembler "punpckldq" } } */
+/* { dg-final { scan-assembler-not "movq" } } */
diff --git a/gcc/testsuite/gcc.target/i386/sse4_1-stv-13.c b/gcc/testsuite/gcc.target/i386/sse4_1-stv-13.c
new file mode 100644 (file)
index 0000000..d14dfb3
--- /dev/null
@@ -0,0 +1,16 @@
+/* { dg-do compile { target ia32 } } */
+/* { dg-options "-O2 -msse4.1" } */
+
+typedef long long v2di __attribute__ ((__vector_size__ (16)));
+
+long long ext();
+v2di mem;
+
+void bar()
+{
+  long long x = ext();
+  mem = (v2di){x,0};
+}
+
+/* { dg-final { scan-assembler "pinsrd" } } */
+/* { dg-final { scan-assembler-not "movq" } } */