On 22/03/2026 18:24, Richard Sandiford wrote:
> Muhammad Kamran <[email protected]> writes:
>> The pattern expands uaddc sum into:
>> cmp openand[4], #1
>> adcs operand[0], operand[2], operand[3]
>> cset operand[1], cs
>>
>> Carry chained uaddc expansion results in a rount trip CC -> register -> CC.
>> Remove that round-trip as CSET-like materialization does not clobber flags,
>> so CC already carries the same bit.
>>
>> gcc/ChangeLog:
>>
>>      * config/aarch64/aarch64.md
>>      (uaddc<mode>5): Expansion pattern for uaddc<m>5
>>      (*aarch64_setc_from_carry_combine_cc_c_<mode>) Post-combine no-op split
>>      pattern for carry chain elimination in CC_Cmode
>>      (*aarch64_setc_from_carry_combine_cc_adc_<mode> Post-combine no-op
>>      split for carry chain elimination in CC_ADCmode
>>
>> gcc/testsuite/ChangeLog:
>>
>>      * gcc.target/aarch64/uaddcm5.c: test chained uaddc expansion
>> ---
>>  gcc/config/aarch64/aarch64.md              | 60 ++++++++++++++++++++++
>>  gcc/testsuite/gcc.target/aarch64/uaddcm5.c | 26 ++++++++++
>>  2 files changed, 86 insertions(+)
>>  create mode 100644 gcc/testsuite/gcc.target/aarch64/uaddcm5.c
>>
>> diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
>> index df5f72ab94c..e875bcaec28 100644
>> --- a/gcc/config/aarch64/aarch64.md
>> +++ b/gcc/config/aarch64/aarch64.md
>> @@ -3390,6 +3390,66 @@ (define_insn "*add<mode>3_carryinC"
>>    [(set_attr "type" "adc_reg")]
>>  )
>>  
>> +(define_expand "uaddc<mode>5"
>> +  [(match_operand:GPI 0 "register_operand")
>> +   (match_operand:GPI 1 "register_operand")
>> +   (match_operand:GPI 2 "register_operand")
>> +   (match_operand:GPI 3 "register_operand")
>> +   (match_operand:GPI 4 "nonmemory_operand")]
>> +  ""
>> +{
>> +  if (operands[4] == const0_rtx)
>> +    {
>> +      emit_insn (gen_add<mode>3_compareC (operands[0], operands[2],
>> +                                      operands[3]));
>> +      rtx cc = gen_rtx_REG (CC_Cmode, CC_REGNUM);
>> +      rtx pat = gen_rtx_LTU (<MODE>mode, cc, const0_rtx);
>> +      emit_insn (gen_rtx_SET (operands[1], pat));
>> +    }
>> +  else
>> +    {
>> +      rtx cin = force_reg (<MODE>mode, operands[4]);
>> +      rtx cc = gen_rtx_REG (CC_Cmode, CC_REGNUM);
>> +      rtx adc_cc = gen_rtx_REG (CC_ADCmode, CC_REGNUM);
>> +      rtx cmp = gen_rtx_COMPARE (CC_Cmode,
>> +                             gen_rtx_PLUS (<MODE>mode, cin, constm1_rtx),
>> +                             cin);
>> +      emit_insn (gen_rtx_SET (cc, cmp));
>> +      emit_insn (gen_add<mode>3_carryinC (operands[0], operands[2],
>> +                                      operands[3]));
>> +      rtx pat = gen_rtx_GEU (<MODE>mode, adc_cc, const0_rtx);
>> +      emit_insn (gen_rtx_SET (operands[1], pat));
>> +    }
>> +  DONE;
>> +})
>> +
>> +;; The uaddc expansion results in a rount trip CC -> register -> CC.  Remove
>> +;; that round-trip as CSET-like materialization does not clobber flags, so 
>> CC
>> +;; already carries the same bit.
>> +(define_insn_and_split "*aarch64_setc_from_carry_combine_cc_c_<mode>"
>> +  [(set (reg:CC_C CC_REGNUM)
>> +    (compare:CC_C
>> +      (neg:GPI
>> +        (geu:GPI (reg:CC_C CC_REGNUM) (const_int 0)))
>> +      (ltu:GPI (reg:CC_C CC_REGNUM) (const_int 0))))]
>> +  ""
>> +  "#"
>> +  "&& 1"
>> +  [(const_int 0)]
>> +  "emit_note (NOTE_INSN_DELETED); DONE;")
> 
> If we want this kind of simplification to be target-specific,
> I think we should add a dedicated mechanism for it, rather than using
> define_insn_and_split.  There are two reasons, one kind of subjective
> and one more practical:
> 
> (1) The instruction above is never really intended to be an instruction
>     and is not needed to preserve RTL semantics (since it's a nop in
>     RTL terms as well as machine terms).  It also adds overhead by
>     taking up insn codes and requiring entries in recog_data etc.
> 
>     In other words, it seems like we're using define_insn_and_split
>     because it's already there, rather than because it's what we would
>     choose if we were starting from scratch.
> 
> (2) As a simplification rule, the above is really:
> 
>       (compare:CC_C (neg:M (geu:M X (const_int 0)))
>                     (ltu:M X (const_int 0)))
> 
>       -> X
> 

Isn't this only true if X also has mode CC_C?  It surely isn't valid when X 
has, say, SImode since the compare outputs a different mode to that.  Perhaps 
you're just assuming that.

I'm trying to get my brain around whether this can be generalized further 
though: is it true in general that

     (compare:ANY_CC (neg:M (geu:M X:ANY_CC (const_int 0)))
                     (ltu:M X:ANY_CC (const_int 0)))
     -> X

>     Expressing this as an instruction pattern is less powerful than
>     using open-coded simplify-rtx.cc rules, again for two reasons:
> 
>     (a) The symbolic rule above works for all X, including the rhs of a
>         (reg:CC_C CC_REGNUM) definition.  The define_insn_and_split only
>         works for (reg:CC_C CC_REGNUM) itself.
> 
>     (b) A simplify-rtx.cc rule would expose the "nopness" straight away,
>         enabling follow-on optimisations in the same RTL pass.
>         The instruction pattern only exposes the nopness at the next
>         split point.
> 
> FWIW, I remember writing a patch once to add simplification rules to md
> files, with the aim of using them to simplify UNSPECs.  Can't remember
> whether I ever posted it though, and it would have been on an Arm
> machine that I no longer have access to.
> 
> That said, the above rule seems like it would work for all CC modes on
> all STORE_FLAG_VALUE==1 targets, unless I'm missing something.  The rule
> below is an inversion by means of a mode change, which could be expressed
> using a target hook to say that CC_ADCmode and CC_Cmode are mutual inverses
> for all operations that are defined on them (that is, LTU and GEU).
> 

I guess a hook would need to return the inverse modes if this generalizes.  

There's probably a dual operation for S_F_V== -1, but I'd need to get my head 
around that as well...

R.

> Thanks,
> Richard
> 
>> +
>> +(define_insn_and_split "*aarch64_setc_from_carry_combine_cc_adc_<mode>"
>> +  [(set (reg:CC_C CC_REGNUM)
>> +    (compare:CC_C
>> +      (neg:GPI
>> +        (ltu:GPI (reg:CC_ADC CC_REGNUM) (const_int 0)))
>> +      (geu:GPI (reg:CC_ADC CC_REGNUM) (const_int 0))))]
>> +  ""
>> +  "#"
>> +  "&& 1"
>> +  [(const_int 0)]
>> +  "emit_note (NOTE_INSN_DELETED); DONE;")
>> +
>>  (define_expand "add<mode>3_carryinV"
>>    [(parallel
>>       [(set (reg:CC_V CC_REGNUM)
>> diff --git a/gcc/testsuite/gcc.target/aarch64/uaddcm5.c 
>> b/gcc/testsuite/gcc.target/aarch64/uaddcm5.c
>> new file mode 100644
>> index 00000000000..39a3508b066
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/aarch64/uaddcm5.c
>> @@ -0,0 +1,26 @@
>> +/* { dg-do compile } */
>> +/* { dg-options "-O2" } */
>> +
>> +static unsigned long
>> +uaddc (unsigned long x, unsigned long y, unsigned long carry_in, unsigned 
>> long *carry_out)
>> +{
>> +  unsigned long r;
>> +  unsigned long c1 = __builtin_add_overflow (x, y, &r);
>> +  unsigned long c2 = __builtin_add_overflow (r, carry_in, &r);
>> +  *carry_out = c1 + c2;
>> +  return r;
>> +}
>> +
>> +void
>> +foo (unsigned long *p, unsigned long *q)
>> +{
>> +  unsigned long c;
>> +  p[0] = uaddc (p[0], q[0], 0, &c);
>> +  p[1] = uaddc (p[1], q[1], c, &c);
>> +  p[2] = uaddc (p[2], q[2], c, &c);
>> +  p[3] = uaddc (p[3], q[3], c, &c);
>> +}
>> +
>> +
>> +/* { dg-final { scan-assembler-times "adcs\\t\[xw\]\[0-9\]+, 
>> \[xw\]\[0-9\]+, \[xw\]\[0-9\]+" 3 } } */
>> +/* { dg-final { scan-assembler-not "cset\\t\[xw\]\[0-9\]+, cs+.*" } } */
>> \ No newline at end of file

Reply via email to