Re: [x86 SSE PATCH] PR target/126619: Improve V4SF vector initialization.
Richard Biener <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <CAFiYyc14BymnxrVbdpKn-aMzR0h0geAW13+kAoyvain+os43Mg@mail.gmail.com> |
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