On Sun, Aug 9, 2026 at 6:42 PM Roger Sayle <[email protected]> wrote:
>
>
> This patch addresses PR target/126619, a performance regression caused by
> my recent update of SSE vector initialization on x86_64.  The new idiom
> attempts to benefit from the implicit zero extension provided by movss
> and movd, but this causes problems for (V4SF) cases were the (SFmode)
> value is already in a register.  Without newer extensions, GCC's approach
> to zero extension (i.e. vec_init of (V4SF){ x, 0, 0, 0 }) is to perform
> an interunit transfer to a general purpose integer register, and then
> transfer the value back again.  Inter-unit moves are expensive, especially
> on older microarchitectures.
>
> The problem is fixed in several ways.  The first is to tweak register
> preferencing in vec_set<mode>_0's define_insn, so that general registers
> are only used if the source/destination is already an integer GPR.
> This changes reload from generating (two instructions):
>
>         movd %xmm0, %eax
>         movd %eax, %xmm0
>
> to instead using:
>
>         pxor %xmm1, %xmm1
>         movss %xmm0, %xmm1
>         movaps %xmm1, %xmm0
>
> which requires 3 instructions, and 1 extra register, but requires
> no inter-unit moves.  This matches what clang/llvm does.
>
> However, it's possible to do better, borrowing an idiom from
> the middle-end's expansion of integer zero-extensions.
>
>         pslldq  $12, %xmm0
>         psrldq  $12, %xmm0
>
> uses two instructions, and doesn't require an extra register.
> Indeed, initializing the vector (V4SF){ 0, 0, 0, x } can be
> done in a single instruction, as it doesn't require a "right"
> shift.
>
> Additionally, for cases such as (V4SF){ a, b, c, d }, where
> there is no benefit from zero extension, we should continue
> using GCC's original CONCAT of V2SF idiom, avoiding any overhead
> of zero extension (Hongtao's suggestion in the Bugzilla PR).
>
> Additionally, there are some additional V4SF initialization
> tweaks.  When loading from memory, where zero extension is
> free "onevar_perm"s should construct { x, 0, 0, 0 } then
> perform a shuffle using shufps, but when the source is a
> register, it should construct { 0, 0, 0, x } (using the
> single shift instruction described above), and perform a
> modified shuffle using shufps from there.
>
> With TARGET_SSE4_1, the first insertps can be used to clear
> (initialize) all the other elements to zero, and the remaining
> non-zero elements can be inserted with regular insertps.
>
I think even with sse4.1 and above we should still use the original
ix86_expand_vector_concat solution since insertps have sequential
dependence with depth of 4, but vec_concat only with depth 2.
so the vector_concat solution should still be faster(including the
4-memory case), maybe for optimization for size, insertps is prefered.

>-  else
>+  else if (TARGET_SSE4_1)

So may just
 else if (TARGET_SSE4_1 && optimize_insn_for_size_p ())
  {
    ......
  }
else
  ix86_expand_vector_init_concat (V4SFmode, target, ops, 4);

>+    {
>+      int i;
>+      rtx tmp = gen_reg_rtx (V4SFmode);
>+      bool first_p = true;
>...
>+  else
>+    ix86_expand_vector_init_concat (V4SFmode, target, ops, 4);
>}

> ;; see comment above inline_secondary_memory_needed function in i386.cc
> (define_insn "vec_set<mode>_0"
>   [(set (match_operand:VI4F_128 0 "nonimmediate_operand"
>-          "=Yr,*x,v,v,v,v,x,x,v,Yr ,?x ,x  ,m ,m   ,m")
>+          "=Yr,*x,v,v,v,!v,x,x,v,Yr ,?x ,x  ,m ,m   ,m")
         (vec_merge:VI4F_128

Add *?* to the corresponding alternative *r* of operands[2] instead of
add *!* to *v* of operands[0]?
my experience is ?r is better to help LRA for register allocation choice.

>+;; Use sse4_1_insertps_v4s[if] to vector_init one non-zero value.
>+(define_insn "sse4_1_insertps_<mode>_init"
>+  [(set (match_operand:VI4F_128 0 "register_operand" "=Yr,*x,v")
>+        (vec_merge:VI4F_128
>+          (vec_duplicate:VI4F_128
>+            (match_operand:<ssescalarmode> 1 "nonimmediate_operand" 
>"Yrjm,*xjm,vm")
>+          (match_operand:VI4F_128 2 "const0_operand")
>+          (match_operand:SI 3 "const248_operand")))]
>+  "TARGET_SSE4_1"
>+{
>+  int op3 = INTVAL (operands[3]);
>+  operands[3] = GEN_INT ((exact_log2 (op3) << 4) + (op3 ^ 15));
>+  switch (which_alternative)
>+    {
>+    case 0:
>+    case 1:
>+      return "insertps\t{%3, %1, %0|%0, %1, %3}";
>+    case 2:
>+      return "vinsertps\t{%3, %1, %0, %0|%0, %0, %1, %3}";

Split alternative 2 into v,m and use {%3, %1, %1, %0|%0, %1, %1, %3}
for alternative *v*, it can avoid false dependence for operands[0].
Also for the new *m* alternative, can we only enable it when not
preferred_for_speed since there's false dependence.
   (set (attr "preferred_for_speed")
    (cond [(eq_attr "alternative" "3")
             (symbol_ref "false")]
          (symbol_ref "true")))

>+    default:
>+      gcc_unreachable ();
>+    }

>+      if (TARGET_SSE4_1)
>+        {
>+          if (!REG_P (var) && !MEM_P (var))
>+            var = force_reg (SFmode, var);

-      rtx tmp1 = gen_reg_rtx (V4SFmode);
-      vars[0] = ops[0];
-      vars[1] = CONST0_RTX (SFmode);
-      vars[2] = CONST0_RTX (SFmode);
-      vars[3] = CONST0_RTX (SFmode);
-      ix86_expand_vector_init_v4sf (tmp1, vars);
+      if (TARGET_SSE4_1)
+        {
+          rtx tmp = gen_reg_rtx (V4SFmode);
+          rtx val = ops[0];
+          if (!REG_P (val) && !MEM_P (val))
+            val = force_reg (SFmode, val);
+          emit_insn (gen_vec_setv4sf_0 (tmp, CONST0_RTX (V4SFmode), val));
+          val = ops[1];
+          if (!REG_P (val) && !MEM_P (val))
+            val = force_reg (SFmode, val);
+          emit_insn (gen_vec_setv4sf_sse4_1 (target, tmp, val, GEN_INT (2)));
+        }

I think we can generate 1 insertps for {a,b,0,0} {a, 0, b,0}, {a, 0,
0, b}, here still generates 2 insertps,  maybe we can directy
gen_sse4_1_insertps_v4sf, use subreg to convert SFmode to V4SFmode?


BR,
Hongtao

Reply via email to