On Mon, Aug 17, 2026 at 10:51 AM Hongtao Liu <[email protected]> wrote:
>
> 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'd have expected two splat plus a blend for any "two-valued" CTOR
unless one element occurs only once.

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

There's also "almost constant", aka (v16qi){0, 1, 2, 5, 7, a, 9, 10, 17 ... }
which can be done with load + insert.  I expect there's many
special cases, like uniformity across a larger element mode
(v16qi){a, b, c, d, a, b, c, d, a, b, c, d, a, b, c, d} which could
be handled by building a SImode element (on the GPR side?)
and splatting that.  SLP vectorization induction/reduction initial
values can look like this (with not necessarily power-of-two
uniformity blocks).

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

Reply via email to