[PATCH][RFC]AArch64 SVE: Fix multiple comparison masks on inverted operands
Richard Sandiford
richard.sandiford@arm.com
Mon Jun 14 15:54:35 GMT 2021
Tamar Christina <Tamar.Christina@arm.com> writes:
> Hi Richard,
>> -----Original Message-----
>> From: Richard Sandiford <richard.sandiford@arm.com>
>> Sent: Monday, June 14, 2021 3:50 PM
>> To: Tamar Christina <Tamar.Christina@arm.com>
>> Cc: gcc-patches@gcc.gnu.org; nd <nd@arm.com>; Richard Earnshaw
>> <Richard.Earnshaw@arm.com>; Marcus Shawcroft
>> <Marcus.Shawcroft@arm.com>; Kyrylo Tkachov <Kyrylo.Tkachov@arm.com>
>> Subject: Re: [PATCH][RFC]AArch64 SVE: Fix multiple comparison masks on
>> inverted operands
>>
>> Tamar Christina <tamar.christina@arm.com> writes:
>> > Hi All,
>> >
>> > This RFC is trying to address the following inefficiency when
>> > vectorizing conditional statements with SVE.
>> >
>> > Consider the case
>> >
>> > void f10(double * restrict z, double * restrict w, double * restrict x,
>> > double * restrict y, int n)
>> > {
>> > for (int i = 0; i < n; i++) {
>> > z[i] = (w[i] > 0) ? x[i] + w[i] : y[i] - w[i];
>> > }
>> > }
>> >
>> >
>> > For which we currently generate at -O3:
>> >
>> > f10:
>> > cmp w4, 0
>> > ble .L1
>> > mov x5, 0
>> > whilelo p1.d, wzr, w4
>> > ptrue p3.b, all
>> > .L3:
>> > ld1d z1.d, p1/z, [x1, x5, lsl 3]
>> > fcmgt p2.d, p1/z, z1.d, #0.0
>> > fcmgt p0.d, p3/z, z1.d, #0.0
>> > ld1d z2.d, p2/z, [x2, x5, lsl 3]
>> > bic p0.b, p3/z, p1.b, p0.b
>> > ld1d z0.d, p0/z, [x3, x5, lsl 3]
>> > fsub z0.d, p0/m, z0.d, z1.d
>> > movprfx z0.d, p2/m, z1.d
>> > fadd z0.d, p2/m, z0.d, z2.d
>> > st1d z0.d, p1, [x0, x5, lsl 3]
>> > incd x5
>> > whilelo p1.d, w5, w4
>> > b.any .L3
>> > .L1:
>> > ret
>> >
>> > Notice that the condition for the else branch duplicates the same
>> > predicate as the then branch and then uses BIC to negate the results.
>> >
>> > The reason for this is that during instruction generation in the
>> > vectorizer we emit
>> >
>> > mask__41.11_66 = vect__4.10_64 > vect_cst__65;
>> > vec_mask_and_69 = mask__41.11_66 & loop_mask_63;
>> > vec_mask_and_71 = mask__41.11_66 & loop_mask_63;
>> > mask__43.16_73 = ~mask__41.11_66;
>> > vec_mask_and_76 = mask__43.16_73 & loop_mask_63;
>> > vec_mask_and_78 = mask__43.16_73 & loop_mask_63;
>> >
>> > which ultimately gets optimized to
>> >
>> > mask__41.11_66 = vect__4.10_64 > { 0.0, ... };
>> > vec_mask_and_69 = loop_mask_63 & mask__41.11_66;
>> > mask__43.16_73 = ~mask__41.11_66;
>> > vec_mask_and_76 = loop_mask_63 & mask__43.16_73;
>> >
>> > Notice how the negate is on the operation and not the predicate
>> > resulting from the operation. When this is expanded this turns into
>> > RTL where the negate is on the compare directly. This means the RTL
>> > is different from the one without the negate and so CSE is unable to
>> recognize that they are essentially same operation.
>> >
>> > To fix this my patch changes it so you negate the mask rather than the
>> > operation
>> >
>> > mask__41.13_55 = vect__4.12_53 > { 0.0, ... };
>> > vec_mask_and_58 = loop_mask_52 & mask__41.13_55;
>> > vec_mask_op_67 = ~vec_mask_and_58;
>> > vec_mask_and_65 = loop_mask_52 & vec_mask_op_67;
>>
>> But to me this looks like a pessimisation in gimple terms. We've increased
>> the length of the critical path: vec_mask_and_65 now needs a chain of
>> 4 operations instead of 3.
>
> True, but it should reduce the number of RTL patterns. I would have thought
> RTL is more expensive to handle than gimple.
I think this is only a fair gimple optimisation if gimple does the isel
itself (to a predicated compare and a predicated NOT).
>> We also need to be careful not to pessimise the case in which the
>> comparison is an integer one. At the moment we'll generate opposed
>> conditions, which is the intended behaviour:
>
> This still happens with this patch at `-Ofast` because that flips the conditions,
> So the different representation doesn't harm it.
OK, that's good.
>>
>> .L3:
>> ld1d z1.d, p0/z, [x1, x5, lsl 3]
>> cmpgt p2.d, p0/z, z1.d, #0
>> movprfx z2, z1
>> scvtf z2.d, p3/m, z1.d
>> cmple p1.d, p0/z, z1.d, #0
>> ld1d z0.d, p2/z, [x2, x5, lsl 3]
>> ld1d z1.d, p1/z, [x3, x5, lsl 3]
>> fadd z0.d, p2/m, z0.d, z2.d
>> movprfx z0.d, p1/m, z1.d
>> fsub z0.d, p1/m, z0.d, z2.d
>> st1d z0.d, p0, [x0, x5, lsl 3]
>> add x5, x5, x6
>> whilelo p0.d, w5, w4
>> b.any .L3
>>
>> Could we handle the fcmp case using a 3->2 define_split instead: convert
>>
>> (set res (and (not (fcmp X Y)) Z)) ->
>> (set res (fcmp X Y))
>> (set res (and (not res) Z))
>>
>
> This was the other approach I mentioned. It works, and gives you the neg, but only in the case where the compare is single use.
But in the original example we duplicate the comparison through a
2->2 combine, which leaves the original comparison as a single use.
Isn't that enough?
> e.g. in
>
> void f11(double * restrict z, double * restrict w, double * restrict x, double * restrict y, int n)
> {
> for (int i = 0; i < n; i++) {
> z[i] = (w[i] > 0) ? w[i] : y[i] - w[i];
> }
> }
>
> You have some of the same problem. It generates
>
> ld1d z0.d, p0/z, [x1, x2, lsl 3]
> fcmgt p2.d, p3/z, z0.d, #0.0
> bic p1.b, p3/z, p0.b, p2.b
> ld1d z1.d, p1/z, [x3, x2, lsl 3]
> fsub z1.d, p1/m, z1.d, z0.d
> sel z0.d, p2, z0.d, z1.d
> st1d z0.d, p0, [x0, x2, lsl 3]
> incd x2
> whilelo p0.d, w2, w4
>
> which has two problems. fcmgt doesn't need to be predicated on p3 which is ptrue all,
> it can/should be p0.
>
> With that fixed the splitter won't match because p2 is needed in the sel, so it's not single use and so combine won't
> try to build the RTL so it can be split.
I think having the vectoriser avoid the dual use between the
IFN_MASK_LOAD/STORE and the VEC_COND_EXPR is fair game, since that
is the only pass that has the context to prove that including the loop
mask in the VEC_COND_EXPR condition is correct. We already try to do
that to some extent:
/* See whether another part of the vectorized code applies a loop
mask to the condition, or to its inverse. */
but it would need extending to handle this case.
Thanks,
Richard
More information about the Gcc-patches
mailing list