> -----Original Message-----
> From: Robin Dapp <[email protected]>
> Sent: 24 September 2026 21:42
> To: Tamar Christina <[email protected]>; Robin Dapp
> <[email protected]>; [email protected]
> Cc: nd <[email protected]>; [email protected]
> Subject: Re: [patch 1/14]middle-end: add new vector control flow IFNs and
> optabs
> 
> > Ok, but why would you want to use COUNT_ACTIVE which is there for
> > non-masked and non-len targets instead of COND_LEN_COUNT_ACTIVE
> > which does the exact thing you mentioned above?
> 
> I'm reading some frustration in your message about being misunderstood
> and me not having read/grasped all the proposed concepts.  Point taken,
> apologies :)  I don't think I misunderstood entirely, though, even if I
> mixed some things up.
> (I meant COND_LEN_COUNT_ACTIVE, I didn't want to use COUNT_ACTIVE for
> RVV here.)
> 

Not frustration, confusion :) But think you've now cleared up some of it and I
also now know I should add some code example to the docs how they are
intended to be used.

> >> Now if we mask the reduction (2), there is no need for zeroing
> >> because
> >> it will only look at the active values and the tail stays unobserved.
> >> That way, RVV early-break handling wouldn't need to jump through as
> many
> >> hoops as the SVE approach.
> >
> > That's not quite true. SVE compares are side-effectful in more than just fp
> > exceptions. They also set CC flags. And it's the reason the example code
> > in the cover letter has
> >
> >   cmphi   p6.b, p7/z, z31.b, #5
> >   b.none  .L8
> >   splice  z31.b, p6, z31.b, z31.b
> >   fmov    w0, s31
> >   ret
> >
> > notice the branch after the compare, because the compares themselves
> > set the zero flag.   Without masking, this would never be correct, this is
> > why SVE does not have "unpredicated" compares.  Because GIMPLE does
> > not carry this information, the "cbranch" today re-sets the flags using a
> PTEST.
> 
> Yes, I should have clarified that I wasn't talking about switching SVE
> to masking (2), but just RVV.  Hence my question later regarding SVE and
> what would be missing at the various passes to make an earlier IFN
> emission possible.  But as mentioned, that was a future-facing question.
> 

I see, I still have some questions around here but maybe easier to chat
at cauldron next week :)

> > Also in SVE (1) and (2) are not the same instruction as you say.
> > COUNT_ACTIVE
> > is an mask reduction, And SVE would use COND_COUNT_ACTIVE because
> the
> > SVE instruction CNTP requires both the loop mask and the result mask [1]
> 
> > This is used in the cases where
> >
> > If (<condition>)
> >   return i;
> >
> > Or another computation involving the loop invariant. So (1) and (2) are
> unrelated.
> > The codegen here is
> >
> > cmp_mask = cmp & loop_mask
> > if (cmp_mask != 0)
> >    {
> >      partial_mask = . COND_TAKE_EXCL_ACTIVE (loop_mask, cmp_mask);
> >      index = .COND_COUNT_ACTIVE (loop_mask, partial_mask);
> >      return x + index;
> >    }
> >
> > where x is the scalar IV the vectorizer is maintaining.  COUNT_ACTIVE* and
> friends
> > are about manipulating the loop IV.  Both here and in Alfie's patches.
> 
> I think I was confusing flag-setting and predicate-producing in the
> context of the branch.  So yes, that (1) (2) (3) was more misleading
> than helpful.
> 
> > Now masking (3) is not possible, because Richi has stated repeatedly in the
> past
> > not to want an IFN within a cbranch in GIMPLE.  i.e. VECTOR_BOOLEAN
> semantics
> > on comparisons are the same for every target in GIMPLE.
> 
> Yep, I didn't want to argue for masking (3), just show that the
> intermediate reduction is explicit in RVV and can be masked right away
> to simplify things (for RVV).
> 
> > This was the point of cond_[len]_vec_cbranch_{any/all}, at transition out of
> GIMPLE
> > it would explicitly mask and len your branch.  So I never understood why you
> said it
> > wasn't useful for RVV, you're arguing for the exact semantics here :)
> 
> The reason they were not useful for RVV is not their semantics
> (they are fine) but how we get there.  For RVV we'd combine
> VCOND_MASK_LEN with the vector cbranch but that's already too late
> because VCOND_MASK_LEN has converted the len to a mask with the
> restriction that comes with it.  VCOND_MASK_LEN is the issue, nothing
> else, but in the current scheme I don't see a way of doing anything
> else.  That's why I'm suggesting what I'm suggesting.

I see, my understanding for this was that I could undo the conversion by
dissolving .VCOND_MASK_LEN, since It's no longer using the result of the
.VCOND_MASK_LEN.

But are you saying that the inputs to .VCOND_MASK_LEN are already in an
inefficient state?

> 
> > This is again the idea behind the new compare IFNs, to make it so that by 
> > the
> time
> > You leave GIMPLE all early break control statements have an explicit len.
> 
> Yeah, here again, their semantics are fine, but the way we get to them
> is similar to before.  Instead, we need a length-controlled mask
> reduction (be it vfirst or vcpop) right away and I don't think we'd be
> hampered by the same issues SVE is (needing to duplicate for single use
> etc.) if we'd have that.  I'm not saying there won't be any issues, but
> the status quo for RVV is not without issues either.
> 
> >> Ideal RVV code for ncopies = 1 would look like:
> >>
> >>  # ...
> >>  # loop body
> >>  vmslt v0, v2, v3 # v0 = v2 < v3, unmasked
> >>  vfirst a0, v0    # COND_LEN_INDEX_FIRST_ACTIVE, masked
> >>  bltz a0          # branch if < 0
> >>  # scalar "tail" for the mismatched element
> >>  # ...
> >>
> >
> > That's not what the optabs are supposed to be or do,
> COND_LEN_INDEX_FIRST_ACTIVE
> > is an IV extraction optab.
> 
> Yes, but the semantics are very close IMHO.  With the -1 adjustment it
> could do what I intended (see below), so I don't consider the suggestion
> too outrageous :)  I understand you rather not want to go there for the
> reasons you stated and the -1 is useless for the way you anticipate
> COND_LEN_INDEX_FIRST_ACTIVE to be used. So let's just define another one
> for the RVV use?

Sure, that makes sense if there's no way to recover the information from
.VCOND_MASK_LEN as you say :)

> 
> >
> > For instance
> >
> > If (<condition>)
> >   return i;
> >
> > gets transformed into for SVE
> >
> > cmp_mask = cmp & loop_mask
> > if (cmp_mask != 0)
> >   return x + .COND_INDEX_FIRST_ACTIVE (loop_mask, cmp_mask);
> >
> > In SVE2 this maps to FIRSTP,
> >
> > https://support.arm.com/documentation/ddi0487/mb/-Part-C-The-
> AArch64-Instruction-Set/-Chapter-C8-SVE-Instruction-Descriptions/-C8-2-
> Alphabetical-list-of-SVE-instructions/-C8-2-192-FIRSTP
> >
> > and COND_INDEX_FIRST_ACTIVE is an optimization over using
> >
> > partial_mask = . COND_TAKE_EXCL_ACTIVE (loop_mask, cmp_mask);
> > index = .COND_COUNT_ACTIVE (loop_mask, partial_mask);
> >
> > And this is the exact reason that COND_INDEX_FIRST_ACTIVE is undefined
> > when the cmp_mask is empty, vs the combination of TAKE_EXCL +
> COUNT_ACTIVE which is
> > defined to be num_lanes.
> 
> Yeah, firstp and vfirst are almost identical.  For RVV the index of the
> first active element (and -1 if there is none) can double as both input
> for the branch as well as the scalar index for the exit block.
> Of course COND_LEN_COUNT_ACTIVE works as well for the branch, but then
> we'd need an extra "reduction" vfirst = IFN_OTHER_NAME :) in the exit
> block when we could have gotten the value in the loop already.  I
> understand that it can never be -1 under your proposed scheme _in_ the
> exit block because it's the very condition to not reach the exit block.
> 
> (IFN_COND_LEN_OTHER_NAME of course...)

Yeah, my expectation was that dissolving the .VCOND_MASK_LEN would
have been equivalent to not emitting it.  And based on that assumption the
current set of optabs were defined.

The example I gave before for Adv. SIMD

  cmhi    v31.16b, v30.16b, v29.16b
  addhn   v31.8b, v31.8h, v31.8h
  umov    x0, v31.d[0]
  cbz     x0, .L8
  rbit    x0, x0
  clz     x0, x0

at expansion time is actually

  cmhi    v31.16b, v30.16b, v29.16b
  addhn   v30.8b, v31.8h, v31.8h
  umov    x0, v30.d[0]
  cbz     x0, .L8
  addhn   v31.8b, v31.8h, v31.8h
  umov    x0, v31.d[0]
  rbit    x0, x0
  clz     x0, x0

Because the first compression comes from vec_cbranch_any and the second
sequence comes from INDEX_FIRST_ACTIVE.  The code is then CSE'D into the
final form and the compressed value is pulled from the exit and re-uses the
one from inside the loop.

So this was how it was intended to work.   But yes I get what you're saying that
the .VCOND_MASK_LEN can't be undone. From looking at the docs it doesn't make
it quite clear why not, but I believe you :)

> 
> > . COND_INDEX_FIRST_ACTIVE and friends are an optimization that can only
> be applied
> > by the vectorizer which generates the branch.  The vectorizer knows that
> because the
> > result is used within an early exit edge, that if that branch is taken the 
> > mask
> would never
> > but empty.  You cannot do this in RTL (well not easily, without a new RTL
> pass) because
> > you'd have to walk the loop and identify which exit is an early exit and 
> > which
> is the main
> > exit.  Something you cannot tell anymore after the vectorizer, since the
> vectorizer can pick
> > a main exit *other* than the natural loop latch.  That's the
> EARLY_BREAK_PEELED cases
> > the code deals with.
> >
> > And because operations that map to COND_INDEX_FIRST_ACTIVE typically
> define the empty
> > case to be -1, this also means in RTL you can't blindly combine the mask
> partition + count operation.
> >
> 
> [..]
> 
> > The last 3 operations are dead. The csel condition would never be
> > true.  But it cannot be eliminated
> > by any RTL optimizer.  For that to happen it needs to know that the BB the
> instruction is in
> > is guaranteed that there is always at least one bit set.  This is not the 
> > same as
> nonzero_bits
> > because this is a mask, and we simply don't know which bit may be non-
> zero.  We just know
> > there is one.
> >
> > So I don't understand why -1 makes it easier for RVV, it would quite 
> > literally
> never be used.
> 
> Right, if it's just used in the exit block, indeed, the -1 is
> unreachable.  As above, for RVV I'd like IFN_OTHER_NAME to double as
> sentinel for "nothing found" as well as the index of the match.  Then
> we'd only have the vfirst inside the loop and the exit block could
> re-use the value for the full index calculation.
> 
> So all in all, for RVV, ideally we'd get
>  a < b # unmasked cmp
>  ix = IFN_COND_LEN_OTHER_NAME # returns -1 for no match and index
> otherwise
>  if (ix == -1)
>    goto loop;
>  # do something with ix (or not)
> 
> So purely for RVV, not for SVE which obviously has different tradeoffs:
> 
> Do you see this approach (unmasked comparison, masked reduction) in
> conflict with anything done and suggested for SVE and the other archs?
> IFN_COND_LEN_OTHER_NAME I'd want to emit right away in the vectorizer.
> Obviously we'd need some tweaks here and there and essentially the code
> paths would fork a little.
> 

It should be trivial, we already do something similar for Adv. SIMD,
see vectorizable_early_exit which when the target supports
.VEC_TRUNC_ADD_HIGH uses that to compress bits instead of ORRs.

In your case you'd want to replace the code that generates the compares
for len_loop_p case and emit a scalar cbranch instead of a vector one.

And if you make .COND_LEN_INDEX_FIRST_ACTIVE emit the same RTL then
the two should be CSE'd like Adv. SIMD.

Cheers,
Tamar

> Maybe it would be easier to argue based on the code once I have it.  But
> I didn't want to start before your series is in.  I guess my main point
> still is that I don't think we need the LEN variant of COND_VEC_CMP as
> well as their optimizations.
> 
> --
> Regards
>  Robin

Reply via email to