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