Re: [x86 SSE PATCH] PR target/126619: Improve V4SF vector initialization.

Hongtao Liu <[email protected]>
Newsgroups gmane.comp.gcc.patches
Message-ID <CAMZc-bzOeNef4pMkax+NFCnP21gwsNEVGyNs6ABYwdE80atoXg@mail.gmail.com>
On Tue, Aug 11, 2026 at 3:09 PM Hongtao Liu <[email protected]> wrote:
>
> 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.

Similar for integer .i.e
v16qi
foo (char a, char b)
{
  return __extension__(v16qi){a,b,a,b,a,b,a,b,a,b,a,b,a,b,a,b};
}

Now it generates 16 sequential movd/pinsrb, but originally just 5
insn{movd,pinsrb,punpcklwd,punpckldq, punpcklqdq}.

I think we should also prefer vector concat for vector integer
initialization in general, only prefer pinsr/movd for some specific
cases(.i.e. many zeros in the vector).

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



-- 
BR,
Hongtao
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.