[avr] PR115830: Improve code by using more condition code

Message ID 5ee3499e-7557-48e1-b23b-44eed5aabc9c@gjlay.de
State New
Headers
Series [avr] PR115830: Improve code by using more condition code |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed

Commit Message

Georg-Johann Lay July 9, 2024, 10:03 a.m. UTC
  Hi Jeff,

This patch adds peephole2s and insns to make better use of
instructions that set condition code (SREG) as a byproduct.

Of course with cc0 all this was *much* simpler... so here we go;
adding CCNmode and CCZNmode, and extra insns that do arith + CC.

No new regressions.

Ok for master?

Johann

--

AVR: target/115830 - Make better use of SREG.N and SREG.Z.

This patch adds new CC modes CCN and CCZN for operations that
set SREG.N, resp. SREG.Z and SREG.N.  Add a bunch of peephole2
patterns to generate new compute + branch insns that make use
of the Z and N flags.  Most of these patterns need their own
asm output routines that don't do all the micro-optimizations
that the ordinary outputs may perform, as the latter have no
requirement to set CC in a usable way.  Pass peephole2 is run
a second time so all patterns get a chance to match.

	PR target/115830
gcc/
	* config/avr/avr-modes.def (CCN, CCZN): New CC_MODEs.
	* config/avr/avr-protos.h (ret_cond_branch): Adjust.
	(avr_out_plus_set_N, avr_op8_ZN_operator,
	avr_out_op8_set_ZN, avr_len_op8_set_ZN): New protos.
	* config/avr/avr.cc (ret_cond_branch): Remove "reverse"
	argument (was always false) and respective code.
	Pass cc_overflow_unusable as an argument.
  	(cond_string): Add bool cc_overflow_unusable argument.
	(avr_print_operand) ['L']: Like 'j' but overflow unusable.
	['K']: Like 'k' but overflow unusable.
	(avr_out_plus_set_ZN): Also support adding -2 and +2.
	(avr_out_plus_set_N, avr_op8_ZN_operator): New functions.
	(avr_out_op8_set_ZN, avr_len_op8_set_ZN): New functions.
	(avr_adjust_insn_length) [ADJUST_LEN_ADD_SET_N]: Hande case.
	(avr_class_max_nregs): All MODE_CCs occupy one hard reg.
	(avr_hard_regno_nregs): Same.
	(avr_hard_regno_mode_ok) [REG_CC]: Allow all MODE_CC.
	(pass_manager.h): Include it.
	(avr_option_override): Run peephole2 a second time.
	* config/avr/avr.md (adjust_len) [add_set_N]: New.
	(ALLCC, CCN_CCZN): New mode iterators.
	(CCname): New mode attribute.
	(eqnegtle, cmp_signed, op8_ZN): New code iterators.
	(swap, SWAP, tstMSB): New code attributes.
	(branch): Handle CCNmode and CCZNmode.  Assimilate...
	(difficult_branch): ...this insn.
	(p1m1): Turn into p2m2.
	(gen_add_for_<code>_<mode>): Adjust to CCNmode and CCZNmode.
	Extend peephole2s that produce them.
	(*add.for.eqne.<mode>): Extend to *add.for.<CCN_CCZN:mode>.<QISI:mode>.
	(*ashift.for.ccn.<mode>): New insns and peephole2s to make them.
	(*op8.for.cczn.<code>): New insns and peephole2s to make them.
	* config/avr/predicates.md (const_1_to_3_operand)
	(abs1_abs2_operand, signed_comparison_operator)
	(op8_ZN_operator): New predicates.
gcc/testsuite/
	* gcc.target/avr/pr115830-add-c.c: New test.
	* gcc.target/avr/pr115830-add-i.c: New test.
	* gcc.target/avr/pr115830-and.c: New test.
	* gcc.target/avr/pr115830-asl.c: New test.
	* gcc.target/avr/pr115830-asr.c: New test.
	* gcc.target/avr/pr115830-ior.c: New test.
	* gcc.target/avr/pr115830-lsr.c: New test.
	* gcc.target/avr/pr115830-asl32.c: New test.
  

Comments

Jeff Law July 9, 2024, 11:17 p.m. UTC | #1
On 7/9/24 4:03 AM, Georg-Johann Lay wrote:
> Hi Jeff,
> 
> This patch adds peephole2s and insns to make better use of
> instructions that set condition code (SREG) as a byproduct.
> 
> Of course with cc0 all this was *much* simpler... so here we go;
> adding CCNmode and CCZNmode, and extra insns that do arith + CC.
> 
> No new regressions.
> 
> Ok for master?
> 
> Johann
> 
> -- 
> 
> AVR: target/115830 - Make better use of SREG.N and SREG.Z.
> 
> This patch adds new CC modes CCN and CCZN for operations that
> set SREG.N, resp. SREG.Z and SREG.N.  Add a bunch of peephole2
> patterns to generate new compute + branch insns that make use
> of the Z and N flags.  Most of these patterns need their own
> asm output routines that don't do all the micro-optimizations
> that the ordinary outputs may perform, as the latter have no
> requirement to set CC in a usable way.  Pass peephole2 is run
> a second time so all patterns get a chance to match.
> 
>      PR target/115830
> gcc/
>      * config/avr/avr-modes.def (CCN, CCZN): New CC_MODEs.
>      * config/avr/avr-protos.h (ret_cond_branch): Adjust.
>      (avr_out_plus_set_N, avr_op8_ZN_operator,
>      avr_out_op8_set_ZN, avr_len_op8_set_ZN): New protos.
>      * config/avr/avr.cc (ret_cond_branch): Remove "reverse"
>      argument (was always false) and respective code.
>      Pass cc_overflow_unusable as an argument.
>       (cond_string): Add bool cc_overflow_unusable argument.
>      (avr_print_operand) ['L']: Like 'j' but overflow unusable.
>      ['K']: Like 'k' but overflow unusable.
>      (avr_out_plus_set_ZN): Also support adding -2 and +2.
>      (avr_out_plus_set_N, avr_op8_ZN_operator): New functions.
>      (avr_out_op8_set_ZN, avr_len_op8_set_ZN): New functions.
>      (avr_adjust_insn_length) [ADJUST_LEN_ADD_SET_N]: Hande case.
>      (avr_class_max_nregs): All MODE_CCs occupy one hard reg.
>      (avr_hard_regno_nregs): Same.
>      (avr_hard_regno_mode_ok) [REG_CC]: Allow all MODE_CC.
>      (pass_manager.h): Include it.
>      (avr_option_override): Run peephole2 a second time.
>      * config/avr/avr.md (adjust_len) [add_set_N]: New.
>      (ALLCC, CCN_CCZN): New mode iterators.
>      (CCname): New mode attribute.
>      (eqnegtle, cmp_signed, op8_ZN): New code iterators.
>      (swap, SWAP, tstMSB): New code attributes.
>      (branch): Handle CCNmode and CCZNmode.  Assimilate...
>      (difficult_branch): ...this insn.
>      (p1m1): Turn into p2m2.
>      (gen_add_for_<code>_<mode>): Adjust to CCNmode and CCZNmode.
>      Extend peephole2s that produce them.
>      (*add.for.eqne.<mode>): Extend to 
> *add.for.<CCN_CCZN:mode>.<QISI:mode>.
>      (*ashift.for.ccn.<mode>): New insns and peephole2s to make them.
>      (*op8.for.cczn.<code>): New insns and peephole2s to make them.
>      * config/avr/predicates.md (const_1_to_3_operand)
>      (abs1_abs2_operand, signed_comparison_operator)
>      (op8_ZN_operator): New predicates.
> gcc/testsuite/
>      * gcc.target/avr/pr115830-add-c.c: New test.
>      * gcc.target/avr/pr115830-add-i.c: New test.
>      * gcc.target/avr/pr115830-and.c: New test.
>      * gcc.target/avr/pr115830-asl.c: New test.
>      * gcc.target/avr/pr115830-asr.c: New test.
>      * gcc.target/avr/pr115830-ior.c: New test.
>      * gcc.target/avr/pr115830-lsr.c: New test.
>      * gcc.target/avr/pr115830-asl32.c: New test.
I was going to throw this into my tester, but the avr.md part of the 
patch failed.  I'm guessing the patch needs minor updates due to some 
kind of changes on the trunk.


It looks like avr exposes the CC register early, creating references to 
it during expansion to RTL.  Presumably this means you've got a 
reasonbale way to reload values, particularly address arithmetic without 
impacting the CC state?

It looks like you're relying heavily on peep2 patterns.  Did you explore 
using cmpelim?



> 
> pr115830-1.diff
> 
> diff --git a/gcc/config/avr/avr-modes.def b/gcc/config/avr/avr-modes.def
> index e0633d680d5..77b05dd8509 100644
> --- a/gcc/config/avr/avr-modes.def
> +++ b/gcc/config/avr/avr-modes.def
> @@ -18,6 +18,11 @@
>   
>   FRACTIONAL_INT_MODE (PSI, 24, 3);
>   
> +/* Used when the N (and Z) flag(s) of SREG are set.
> +   The N flag indicates  whether the value is negative.  */
> +CC_MODE (CCN);
> +CC_MODE (CCZN);
N and Z are probably the easiest to model.  C is usually worth modeling 
as well.  But obviously that could be a follow-up.


> +      if (ival >= +1) avr_asm_len ("inc %0", xop, plen, 1);
> +      if (ival == +2) avr_asm_len ("inc %0", xop, plen, 1);
> +      if (ival <= -1) avr_asm_len ("dec %0", xop, plen, 1);
> +      if (ival == -2) avr_asm_len ("dec %0", xop, plen, 1);
Formatting issue.  Bring the avr_asm_len calls down to their own lines, 
indented appropriately.  However, if this is a common style in avr.cc, 
particularly in that same function then it's OK to leave as-is.


Not really ready to ACK or NAK at this point.

jeff
  
Georg-Johann Lay July 10, 2024, 9:05 a.m. UTC | #2
Am 10.07.24 um 01:17 schrieb Jeff Law:
> On 7/9/24 4:03 AM, Georg-Johann Lay wrote:
>> Hi Jeff,
>>
>> This patch adds peephole2s and insns to make better use of
>> instructions that set condition code (SREG) as a byproduct.
>>
>> Of course with cc0 all this was *much* simpler... so here we go;
>> adding CCNmode and CCZNmode, and extra insns that do arith + CC.
>>
>> No new regressions.
>>
>> Ok for master?
>>
>> Johann
>>
>> -- 
>>
>> AVR: target/115830 - Make better use of SREG.N and SREG.Z.
>>
>> This patch adds new CC modes CCN and CCZN for operations that
>> set SREG.N, resp. SREG.Z and SREG.N.  Add a bunch of peephole2
>> patterns to generate new compute + branch insns that make use
>> of the Z and N flags.  Most of these patterns need their own
>> asm output routines that don't do all the micro-optimizations
>> that the ordinary outputs may perform, as the latter have no
>> requirement to set CC in a usable way.  Pass peephole2 is run
>> a second time so all patterns get a chance to match.
>>
>>      PR target/115830
>> gcc/
>>      * config/avr/avr-modes.def (CCN, CCZN): New CC_MODEs.
>>      * config/avr/avr-protos.h (ret_cond_branch): Adjust.
>>      (avr_out_plus_set_N, avr_op8_ZN_operator,
>>      avr_out_op8_set_ZN, avr_len_op8_set_ZN): New protos.
>>      * config/avr/avr.cc (ret_cond_branch): Remove "reverse"
>>      argument (was always false) and respective code.
>>      Pass cc_overflow_unusable as an argument.
>>       (cond_string): Add bool cc_overflow_unusable argument.
>>      (avr_print_operand) ['L']: Like 'j' but overflow unusable.
>>      ['K']: Like 'k' but overflow unusable.
>>      (avr_out_plus_set_ZN): Also support adding -2 and +2.
>>      (avr_out_plus_set_N, avr_op8_ZN_operator): New functions.
>>      (avr_out_op8_set_ZN, avr_len_op8_set_ZN): New functions.
>>      (avr_adjust_insn_length) [ADJUST_LEN_ADD_SET_N]: Hande case.
>>      (avr_class_max_nregs): All MODE_CCs occupy one hard reg.
>>      (avr_hard_regno_nregs): Same.
>>      (avr_hard_regno_mode_ok) [REG_CC]: Allow all MODE_CC.
>>      (pass_manager.h): Include it.
>>      (avr_option_override): Run peephole2 a second time.
>>      * config/avr/avr.md (adjust_len) [add_set_N]: New.
>>      (ALLCC, CCN_CCZN): New mode iterators.
>>      (CCname): New mode attribute.
>>      (eqnegtle, cmp_signed, op8_ZN): New code iterators.
>>      (swap, SWAP, tstMSB): New code attributes.
>>      (branch): Handle CCNmode and CCZNmode.  Assimilate...
>>      (difficult_branch): ...this insn.
>>      (p1m1): Turn into p2m2.
>>      (gen_add_for_<code>_<mode>): Adjust to CCNmode and CCZNmode.
>>      Extend peephole2s that produce them.
>>      (*add.for.eqne.<mode>): Extend to 
>> *add.for.<CCN_CCZN:mode>.<QISI:mode>.
>>      (*ashift.for.ccn.<mode>): New insns and peephole2s to make them.
>>      (*op8.for.cczn.<code>): New insns and peephole2s to make them.
>>      * config/avr/predicates.md (const_1_to_3_operand)
>>      (abs1_abs2_operand, signed_comparison_operator)
>>      (op8_ZN_operator): New predicates.
>> gcc/testsuite/
>>      * gcc.target/avr/pr115830-add-c.c: New test.
>>      * gcc.target/avr/pr115830-add-i.c: New test.
>>      * gcc.target/avr/pr115830-and.c: New test.
>>      * gcc.target/avr/pr115830-asl.c: New test.
>>      * gcc.target/avr/pr115830-asr.c: New test.
>>      * gcc.target/avr/pr115830-ior.c: New test.
>>      * gcc.target/avr/pr115830-lsr.c: New test.
>>      * gcc.target/avr/pr115830-asl32.c: New test.
> I was going to throw this into my tester, but the avr.md part of the 
> patch failed.  I'm guessing the patch needs minor updates due to some 
> kind of changes on the trunk.

Hi Jeff,

The previous change to avr.md was several days ago, and should not
interfere with this one.  Anyway, I rebased the patch against
master and attached it below.  The patch is atop the ref in the patch
file name : https://gcc.gnu.org/r15-1935

> It looks like avr exposes the CC register early, creating references to 
> it during expansion to RTL.  Presumably this means you've got a 
> reasonable way to reload values, particularly address arithmetic without 
> impacting the CC state?

No. CC only comes into existence after reload in .split2 and in some
cases in .avr-ifelse.  reloads may clobber CC, so there is no way
to split cbranch insn prior to relaod.

> It looks like you're relying heavily on peep2 patterns.  Did you explore 
> using cmpelim?

Some question I have:

- compare-elim.cc states that the comparisons must look like
   [(set (reg:CC) (compare:CC (reg) (reg_or_immediate)))]
which is not always the case, in particular the patterns
may have clobbers. compare-elim uses single_set, which
is true for the pattern with a clobber / scratch.  However,
presence / absence of a clobber reg has to be taken into
account when deciding whether a transformation into cc
other than CCmode is possible.  compare-elim only supplies
the SET_SRC, but not the scratch_operand to SELECT_CC_MODE.

- The internals says that some optimizations / transforms
will happen prior to reload (e.g. .combine).  This is not
possible since CCmode exists only since .split2.

- Insn combine may have transformed some comparisons, e.g.
sign test are represented as a zero_extract + skip like
in the sbrx_branch insns.  This means that compares might
have a non-canonical form at .cmpelim.  But a CCNmode
compare is better still than an sbrx_branch.  Moreover,
CANONICALIZE_COMPARISON also runs already at insn combine,
so that compares have a form not recognized by cmpelim.

- Comparisons may look like if (0 <code> ref) which is
a form not supported by compare-elim.

- The avr-ifelse pass performs some optimizations on
code as produced by if-else decision trees from switch-case.
I don't see how that would fit into cmpelim because not
all information is provided by compare-elim that's required
to perform the required optimization.  FYI this pass is a
replacement of machine dependent reorg due to PR109650.

So as is appears, the current peep2 approach will yield
the best results?

I would add some notes to the commit message why we do
not use cmpelim?

>> pr115830-1.diff
>>
>> diff --git a/gcc/config/avr/avr-modes.def b/gcc/config/avr/avr-modes.def
>> index e0633d680d5..77b05dd8509 100644
>> --- a/gcc/config/avr/avr-modes.def
>> +++ b/gcc/config/avr/avr-modes.def
>> @@ -18,6 +18,11 @@
>>   FRACTIONAL_INT_MODE (PSI, 24, 3);
>> +/* Used when the N (and Z) flag(s) of SREG are set.
>> +   The N flag indicates  whether the value is negative.  */
>> +CC_MODE (CCN);
>> +CC_MODE (CCZN);
> N and Z are probably the easiest to model.  C is usually worth modeling 
> as well.  But obviously that could be a follow-up.
> 
>> +      if (ival >= +1) avr_asm_len ("inc %0", xop, plen, 1);
>> +      if (ival == +2) avr_asm_len ("inc %0", xop, plen, 1);
>> +      if (ival <= -1) avr_asm_len ("dec %0", xop, plen, 1);
>> +      if (ival == -2) avr_asm_len ("dec %0", xop, plen, 1);
> Formatting issue.  Bring the avr_asm_len calls down to their own lines, 
> indented appropriately.  However, if this is a common style in avr.cc, 
> particularly in that same function then it's OK to leave as-is.

It is not very common.  In some cases I find such code sequences easier
to grasp.  I can use strict formatting of course.

Ok with that change?

> Not really ready to ACK or NAK at this point.
> 
> jeff

So I guess mainly due to the cmpelim (non-)alternative?

Johann
  
Jeff Law July 12, 2024, 4:40 p.m. UTC | #3
On 7/10/24 3:05 AM, Georg-Johann Lay wrote:

> 
> The previous change to avr.md was several days ago, and should not
> interfere with this one.  Anyway, I rebased the patch against
> master and attached it below.  The patch is atop the ref in the patch
> file name : https://gcc.gnu.org/r15-1935
I'll throw this version in.  While avr isn't deeply tested additional 
testing never hurts.

> 
>> It looks like avr exposes the CC register early, creating references 
>> to it during expansion to RTL.  Presumably this means you've got a 
>> reasonable way to reload values, particularly address arithmetic 
>> without impacting the CC state?
> 
> No. CC only comes into existence after reload in .split2 and in some
> cases in .avr-ifelse.  reloads may clobber CC, so there is no way
> to split cbranch insn prior to relaod.
Clearly I missed all those reload_completed checks.  Thanks for clarifying.

> 
>> It looks like you're relying heavily on peep2 patterns.  Did you 
>> explore using cmpelim?
> 
> Some question I have:
> 
> - compare-elim.cc states that the comparisons must look like
>    [(set (reg:CC) (compare:CC (reg) (reg_or_immediate)))]
> which is not always the case, in particular the patterns
> may have clobbers. compare-elim uses single_set, which
> is true for the pattern with a clobber / scratch.  However,
> presence / absence of a clobber reg has to be taken into
> account when deciding whether a transformation into cc
> other than CCmode is possible.  compare-elim only supplies
> the SET_SRC, but not the scratch_operand to SELECT_CC_MODE.
We could probably fix that.  I don't think we've had a target where the 
clobbered object affects CC like this.  Could we perhaps pass in the 
full insn?  That would require fixing the various targets that already 
use the compare-elim infrastructure, but I think it'd be a pretty 
mechanical change.



> 
> - The internals says that some optimizations / transforms
> will happen prior to reload (e.g. .combine).  This is not
> possible since CCmode exists only since .split2.
> 
> - Insn combine may have transformed some comparisons, e.g.
> sign test are represented as a zero_extract + skip like
> in the sbrx_branch insns.  This means that compares might
> have a non-canonical form at .cmpelim.  But a CCNmode
> compare is better still than an sbrx_branch.  Moreover,
> CANONICALIZE_COMPARISON also runs already at insn combine,
> so that compares have a form not recognized by cmpelim.
There's probably going to be some cases we can't necessarily handle due 
to intricate target dependencies.  That's fine.  I'm more concerned 
about whether or not we're utilizing the existing infrastructure.  If we 
can use the generic infrastructure we should.  If we can use the generic 
infrastructure after making some improvements we should.  If the 
problems aren't tractable with the generic infrastructure, then a 
bespoke target solution may be the only path forward.



> 
> - Comparisons may look like if (0 <code> ref) which is
> a form not supported by compare-elim.
That's not canonical RTL.  If the backend is generating that it probably 
needs to be fixed.  Various passes assume the constant will be in the 
other position.


> 
>> Not really ready to ACK or NAK at this point.
>>
>> jeff
> 
> So I guess mainly due to the cmpelim (non-)alternative?
Largely yes.  I'll try to take a look at this now that I've got more 
background info.  Thanks.

jeff
  
Georg-Johann Lay July 12, 2024, 6:20 p.m. UTC | #4
Am 12.07.24 um 18:40 schrieb Jeff Law:
> 
> On 7/10/24 3:05 AM, Georg-Johann Lay wrote:
>>
>> The previous change to avr.md was several days ago, and should not
>> interfere with this one.  Anyway, I rebased the patch against
>> master and attached it below.  The patch is atop the ref in the patch
>> file name : https://gcc.gnu.org/r15-1935
> I'll throw this version in.  While avr isn't deeply tested additional 
> testing never hurts.
> 
>>> It looks like avr exposes the CC register early, creating references 
>>> to it during expansion to RTL.  Presumably this means you've got a 
>>> reasonable way to reload values, particularly address arithmetic 
>>> without impacting the CC state?
>>
>> No. CC only comes into existence after reload in .split2 and in some
>> cases in .avr-ifelse.  reloads may clobber CC, so there is no way
>> to split cbranch insn prior to relaod.
> Clearly I missed all those reload_completed checks.  Thanks for clarifying.
> 
>>> It looks like you're relying heavily on peep2 patterns.  Did you 
>>> explore using cmpelim?
>>
>> Some question I have:
>>
>> - compare-elim.cc states that the comparisons must look like
>>    [(set (reg:CC) (compare:CC (reg) (reg_or_immediate)))]
>> which is not always the case, in particular the patterns
>> may have clobbers. compare-elim uses single_set, which
>> is true for the pattern with a clobber / scratch.  However,
>> presence / absence of a clobber reg has to be taken into
>> account when deciding whether a transformation into cc
>> other than CCmode is possible.  compare-elim only supplies
>> the SET_SRC, but not the scratch_operand to SELECT_CC_MODE.
> We could probably fix that.  I don't think we've had a target where the 
> clobbered object affects CC like this.  Could we perhaps pass in the 
> full insn?  That would require fixing the various targets that already 
> use the compare-elim infrastructure, but I think it'd be a pretty 
> mechanical change.

So basically affect all targets.  I definitely cannot do that,
in particular the required testing.

>> - The internals says that some optimizations / transforms
>> will happen prior to reload (e.g. .combine).  This is not
>> possible since CCmode exists only since .split2.
>>
>> - Insn combine may have transformed some comparisons, e.g.
>> sign test are represented as a zero_extract + skip like
>> in the sbrx_branch insns.  This means that compares might
>> have a non-canonical form at .cmpelim.  But a CCNmode
>> compare is better still than an sbrx_branch.  Moreover,
>> CANONICALIZE_COMPARISON also runs already at insn combine,
>> so that compares have a form not recognized by cmpelim.
> There's probably going to be some cases we can't necessarily handle due 
> to intricate target dependencies.  That's fine.  I'm more concerned 
> about whether or not we're utilizing the existing infrastructure.  If we 
> can use the generic infrastructure we should.  If we can use the generic 
> infrastructure after making some improvements we should.  If the 
> problems aren't tractable with the generic infrastructure, then a 
> bespoke target solution may be the only path forward.

Well, what matters in the end is the quality of the generated code.
Which doesn't mean I think how the code is written does not matter.
After all, peep2 is also an existing infrastructure.  Yes, the code
is a bit verbose, but it gives a very well control over what is
happening, as it runs just a couple of passes after .split2.

Plus, there are cases where peephole 2 may provide a scratch, that's
something cmpelim can't do (to my knowledge).

>> - Comparisons may look like if (0 <code> ref) which is
>> a form not supported by compare-elim.
> That's not canonical RTL.  If the backend is generating that it probably 
> needs to be fixed.  Various passes assume the constant will be in the 
> other position.

It is generated deliberately during CANONICALIZE_COMPARISON.

Reason is that it is possible to swap comparison operands with
zero costs, but that does not apply to branch instructions:
Not all branches are supported natively, for example there is GE,
but GT has to be emulated by 2 instructions.  Which means
swapping conditions may add or remove costs.

This works well since beginning of time.  Maybe it could be supported
with cmpelim by doubling the number of CCmodes, so things are getting
complicated, or at least annoying.

Moreover, as far as I can tell, cmpelim cannot convert code like

cc = compare (a, b)
if (cc == 0) goto x
cc = compare (a, b)
if (cc > 0) goto y

to

cc = compare (a, b)
if (cc == 0) goto x
if (cc >= 0) goto y

notice the 2nd operator changed, which is valid because >= is in the
wake of ==.  cmpelim only provides local information and would come up
with

cc = compare (a, b)
if (cc == 0) goto x
if (cc > 0) goto y // suboptimal, not enough context known.

so cmpelim would require a device that tells which assertions hold
when a comparison is handled.  For example, in the 1st compare there
is no assertion, but in the 2nf we know that always cc != 0 due to the
branch.

>> So I guess mainly due to the cmpelim (non-)alternative?
> Largely yes.  I'll try to take a look at this now that I've got more 
> background info.  Thanks.
> 
> jeff

I must admit that I don't fell good about when you are putting loads
of effort into this...

My preferred solution is still peep2, or at least stuff that's in the
avr realm.  My experience is that when problems are popping up, in
particular on the efficiency side, avr is not important enough and
falls off the cliff...

What about starting a new md file for this stuff? Might be easier
to follow than the current 10000 LOC of avr.md (there's already
3 md's to date, one additional for DImode and one for the fixed-point
stuff).

Johann
  

Patch

diff --git a/gcc/config/avr/avr-modes.def b/gcc/config/avr/avr-modes.def
index e0633d680d5..77b05dd8509 100644
--- a/gcc/config/avr/avr-modes.def
+++ b/gcc/config/avr/avr-modes.def
@@ -18,6 +18,11 @@ 
 
 FRACTIONAL_INT_MODE (PSI, 24, 3);
 
+/* Used when the N (and Z) flag(s) of SREG are set.
+   The N flag indicates  whether the value is negative.  */
+CC_MODE (CCN);
+CC_MODE (CCZN);
+
 /* Make TA and UTA 64 bits wide.
    128 bit wide modes would be insane on a 8-bit machine.
    This needs special treatment in avr.cc and avr-lib.h.  */
diff --git a/gcc/config/avr/avr-protos.h b/gcc/config/avr/avr-protos.h
index dc23cfbf461..664513864fe 100644
--- a/gcc/config/avr/avr-protos.h
+++ b/gcc/config/avr/avr-protos.h
@@ -54,7 +54,7 @@  extern const char *avr_out_tsthi (rtx_insn *, rtx*, int*);
 extern const char *avr_out_tstpsi (rtx_insn *, rtx*, int*);
 extern const char *avr_out_compare (rtx_insn *, rtx*, int*);
 extern const char *avr_out_compare64 (rtx_insn *, rtx*, int*);
-extern const char *ret_cond_branch (rtx x, int len, int reverse);
+extern const char *ret_cond_branch (rtx x, int len, machine_mode);
 extern const char *avr_out_movpsi (rtx_insn *, rtx*, int*);
 extern const char *avr_out_sign_extend (rtx_insn *, rtx*, int*);
 extern const char *avr_out_insert_notbit (rtx_insn *, rtx*, int*);
@@ -62,6 +62,10 @@  extern const char *avr_out_insv (rtx_insn *, rtx*, int*);
 extern const char *avr_out_extr (rtx_insn *, rtx*, int*);
 extern const char *avr_out_extr_not (rtx_insn *, rtx*, int*);
 extern const char *avr_out_plus_set_ZN (rtx*, int*);
+extern const char *avr_out_plus_set_N (rtx*, int*);
+extern const char *avr_out_op8_set_ZN (RTX_CODE, rtx*, int*);
+extern int avr_len_op8_set_ZN (RTX_CODE, rtx*);
+extern bool avr_op8_ZN_operator (rtx);
 extern const char *avr_out_cmp_ext (rtx*, enum rtx_code, int*);
 
 extern const char *ashlqi3_out (rtx_insn *insn, rtx operands[], int *len);
diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc
index d299fceb782..270fa0f49ba 100644
--- a/gcc/config/avr/avr.cc
+++ b/gcc/config/avr/avr.cc
@@ -54,6 +54,7 @@ 
 #include "builtins.h"
 #include "context.h"
 #include "tree-pass.h"
+#include "pass_manager.h"
 #include "print-rtl.h"
 #include "rtl-iter.h"
 
@@ -157,7 +158,6 @@  static const char *out_movsi_mr_r (rtx_insn *, rtx[], int *);
 static int get_sequence_length (rtx_insn *insns);
 static int sequent_regs_live (void);
 static const char *ptrreg_to_str (int);
-static const char *cond_string (enum rtx_code);
 static int avr_num_arg_regs (machine_mode, const_tree);
 static int avr_operand_rtx_cost (rtx, machine_mode, enum rtx_code,
 				 int, bool);
@@ -1185,6 +1185,18 @@  avr_option_override (void)
   init_machine_status = avr_init_machine_status;
 
   avr_log_set_avr_log();
+
+  /* As long as peep2_rescan is not implemented, see
+     http://gcc.gnu.org/ml/gcc-patches/2011-10/msg02819.html
+     we add a second peephole2 run to get best results.  */
+  {
+    opt_pass *extra_peephole2
+      = g->get_passes ()->get_pass_peephole2 ()->clone ();
+    struct register_pass_info peep2_2_info
+      = { extra_peephole2, "peephole2", 1, PASS_POS_INSERT_AFTER };
+
+    register_pass (&peep2_2_info);
+  }
 }
 
 /* Function to set up the backend function structure.  */
@@ -3623,10 +3635,8 @@  ptrreg_to_str (int regno)
    Used in conditional jump constructing  */
 
 static const char *
-cond_string (enum rtx_code code)
+cond_string (enum rtx_code code, bool cc_overflow_unusable)
 {
-  bool cc_overflow_unusable = false;
-
   switch (code)
     {
     case NE:
@@ -3955,10 +3965,11 @@  avr_print_operand (FILE *file, rtx x, int code)
     }
   else if (GET_CODE (x) == CONST_STRING)
     fputs (XSTR (x, 0), file);
-  else if (code == 'j')
-    fputs (cond_string (GET_CODE (x)), file);
-  else if (code == 'k')
-    fputs (cond_string (reverse_condition (GET_CODE (x))), file);
+  else if (code == 'j' || code == 'L')
+    fputs (cond_string (GET_CODE (x), code == 'L'), file);
+  else if (code == 'k' || code == 'K')
+    fputs (cond_string (reverse_condition (GET_CODE (x)), code == 'K'),
+	   file);
   else
     avr_print_operand_address (file, VOIDmode, x);
 }
@@ -4008,13 +4019,17 @@  avr_jump_mode (rtx x, rtx_insn *insn)
 /* Return an AVR condition jump commands.
    X is a comparison RTX.
    LEN is a number returned by avr_jump_mode function.
-   If REVERSE nonzero then condition code in X must be reversed.  */
+   CCMODE is the mode of the comparison in CCmode, CCNmode, CCZNmode.  */
 
 const char *
-ret_cond_branch (rtx x, int len, int reverse)
+ret_cond_branch (rtx x, int len, machine_mode ccmode)
 {
-  RTX_CODE cond = reverse ? reverse_condition (GET_CODE (x)) : GET_CODE (x);
-  bool cc_overflow_unusable = false;
+  bool cc_overflow_unusable = ccmode != CCmode;
+  RTX_CODE cond = GET_CODE (x);
+
+  if (ccmode == CCNmode)
+    // The N flag can only do < 0 and >= 0.
+    gcc_assert (cond == GE || cond == LT);
 
   switch (cond)
     {
@@ -4076,33 +4091,20 @@  ret_cond_branch (rtx x, int len, int reverse)
 	       "brsh .+4" CR_TAB
 	       "jmp %0"));
     default:
-      if (reverse)
+      switch (len)
 	{
-	  switch (len)
-	    {
-	    case 1:
-	      return "br%k1 %0";
-	    case 2:
-	      return ("br%j1 .+2" CR_TAB
-		      "rjmp %0");
-	    default:
-	      return ("br%j1 .+4" CR_TAB
-		      "jmp %0");
-	    }
-	}
-      else
-	{
-	  switch (len)
-	    {
-	    case 1:
-	      return "br%j1 %0";
-	    case 2:
-	      return ("br%k1 .+2" CR_TAB
-		      "rjmp %0");
-	    default:
-	      return ("br%k1 .+4" CR_TAB
-		      "jmp %0");
-	    }
+	case 1:
+	  return cc_overflow_unusable
+	    ? "br%L1 %0"
+	    : "br%j1 %0";
+	case 2:
+	  return cc_overflow_unusable
+	    ? "br%K1 .+2" CR_TAB "rjmp %0"
+	    : "br%k1 .+2" CR_TAB "rjmp %0";
+	default:
+	  return cc_overflow_unusable
+	    ? "br%K1 .+4" CR_TAB "jmp %0"
+	    : "br%k1 .+4" CR_TAB "jmp %0";
 	}
     }
   return "";
@@ -9455,6 +9457,41 @@  avr_out_plus (rtx insn, rtx *xop, int *plen, bool out_label)
 }
 
 
+/* Output an addition with a compile-time constant that sets SREG.N:
+
+      XOP[0] += XOP[1]
+
+   where XOP[0] is a QI, HI, PSI or SI register, and XOP[1] is a compile-time
+   constant.  XOP[2] is SCRATCH or a QI clobber reg.  Return "".
+
+   If PLEN == NULL output the instructions.
+   If PLEN != NULL set *PLEN to the length of the sequence in words.  */
+
+const char *
+avr_out_plus_set_N (rtx *xop, int *plen)
+{
+  gcc_assert (xop[1] != const0_rtx);
+
+  if (GET_MODE_SIZE (GET_MODE (xop[0])) == 1
+      && ! test_hard_reg_class (LD_REGS, xop[0]))
+    return avr_out_plus_set_ZN (xop, plen);
+
+  // The output function for vanilla additions, avr_out_plus_1, can be
+  // used because it always issues an operation on the MSB (except when
+  // the addend is zero).
+
+  rtx op[] = { xop[0], xop[0], xop[1], xop[2] };
+  int len_plus, len_minus;
+
+  avr_out_plus_1 (NULL_RTX, op, &len_plus,  PLUS,  UNKNOWN, 0, false);
+  avr_out_plus_1 (NULL_RTX, op, &len_minus, MINUS, UNKNOWN, 0, false);
+
+  avr_out_plus_1 (NULL_RTX, op, plen, len_minus < len_plus ? MINUS : PLUS,
+		  UNKNOWN, 0, false);
+  return "";
+}
+
+
 /* Output an instruction sequence for addition of REG in XOP[0] and CONST_INT
    in XOP[1] in such a way that SREG.Z and SREG.N are set according to the
    result.  XOP[2] might be a d-regs clobber register.  If XOP[2] is SCRATCH,
@@ -9478,13 +9515,17 @@  avr_out_plus_set_ZN (rtx *xop, int *plen)
   // Number of bytes to operate on.
   int n_bytes = GET_MODE_SIZE (mode);
 
-  if (n_bytes == 1)
+  if (n_bytes == 1
+      && ! test_hard_reg_class (LD_REGS, xreg))
     {
-      if (INTVAL (xval) == 1)
-	return avr_asm_len ("inc %0", xop, plen, 1);
+      gcc_assert (abs1_abs2_operand (xval, QImode));
+      int ival = (int) INTVAL (xval);
 
-      if (INTVAL (xval) == -1)
-	return avr_asm_len ("dec %0", xop, plen, 1);
+      if (ival >= +1) avr_asm_len ("inc %0", xop, plen, 1);
+      if (ival == +2) avr_asm_len ("inc %0", xop, plen, 1);
+      if (ival <= -1) avr_asm_len ("dec %0", xop, plen, 1);
+      if (ival == -2) avr_asm_len ("dec %0", xop, plen, 1);
+      return "";
     }
 
   if (n_bytes == 2
@@ -9571,6 +9612,136 @@  avr_out_plus_set_ZN (rtx *xop, int *plen)
 }
 
 
+/* A helper worker for op8_ZN_operator.  Allow
+
+     OP0 <code> op1
+
+  QImode operations that set SREG.N and SREG.Z in a usable way.
+  these are:
+
+  * OP0 is a QImode register, and
+  * OP1 is a QImode register or CONST_INT, and
+
+  the allowed operations is one of:
+
+  * SHIFTs with a const_int offset in { 1, 2, 3 }.
+  * MINUS and XOR with a register operand
+  * IOR and AND with a register operand, or d-reg + const_int
+  * PLUS with a register operand, or d-reg + const_int,
+    or a const_int in { -2, -1, 1, 2 }.  */
+
+bool
+avr_op8_ZN_operator (rtx op)
+{
+  const RTX_CODE code = GET_CODE (op);
+  rtx op0 = XEXP (op, 0);
+  rtx op1 = XEXP (op, 1);
+
+  if (! register_operand (op0, QImode)
+      || ! (register_operand (op1, QImode)
+	    || const_int_operand (op1, QImode)))
+    return false;
+
+  const bool reg1_p = REG_P (op1);
+  const bool ld_reg0_p = test_hard_reg_class (LD_REGS, op0);
+
+  switch (code)
+    {
+    default:
+      break;
+
+    case ASHIFT:
+    case ASHIFTRT:
+    case LSHIFTRT:
+      return const_1_to_3_operand (op1, QImode);
+
+    case MINUS:
+    case XOR:
+      return reg1_p;
+
+    case IOR:
+    case AND:
+      return reg1_p || ld_reg0_p;
+
+    case PLUS:
+      return reg1_p || ld_reg0_p || abs1_abs2_operand (op1, QImode);
+    }
+
+  return false;
+}
+
+
+/* Output a QImode instruction sequence for
+
+      XOP[0] = XOP[0]  <CODE>  XOP[2]
+
+   where XOP[0] is a register, and the possible operands and CODEs
+   are according to  avr_op8_ZN_operator()  from above.  Return "".
+
+   If PLEN == NULL, then output the instructions.
+   If PLEN != NULL, then set *PLEN to the length of the sequence in words.  */
+
+const char *
+avr_out_op8_set_ZN (RTX_CODE code, rtx *xop, int *plen)
+{
+  const bool reg2_p = REG_P (xop[2]);
+  const int ival = CONST_INT_P (xop[2]) ? (int) INTVAL (xop[2]) : 0;
+
+  gcc_assert (op8_ZN_operator (gen_rtx_fmt_ee (code, QImode, xop[0], xop[2]),
+			       QImode));
+  if (plen)
+    *plen = 0;
+
+  const char *tpl = nullptr;
+  int times = 1;
+
+  if (code == ASHIFT)
+    tpl = "lsl %0", times = ival;
+  else if (code == LSHIFTRT)
+    tpl = "lsr %0", times = ival;
+  else if (code == ASHIFTRT)
+    tpl = "asr %0", times = ival;
+  else if (code == MINUS)
+    tpl = "sub %0,%2";
+  else if (code == XOR)
+    tpl = "eor %0,%2";
+  else if (code == AND)
+    tpl = reg2_p ? "and %0,%2" : "andi %0,lo8(%2)";
+  else if (code == IOR)
+    tpl = reg2_p ? "or %0,%2" : "ori %0,lo8(%2)";
+  else if (code == PLUS)
+    {
+      if (ival
+	  && ! test_hard_reg_class (LD_REGS, xop[0]))
+	{
+	  tpl = ival > 0 ? "inc %0" : "dec %0";
+	  times = ival > 0 ? ival : -ival;
+	}
+      else
+	tpl = reg2_p ? "add %0,%2" : "subi %0,lo8(%n2)";
+    }
+  else
+    gcc_unreachable();
+
+  for (int i = 0; i < times; ++i)
+    avr_asm_len (tpl, xop, plen, 1);
+
+  return "";
+}
+
+
+/* Used in the "length" attribute of insn "*op8.for.cczn.<code>".  */
+
+int
+avr_len_op8_set_ZN (RTX_CODE code, rtx *xop)
+{
+  int len;
+  (void) avr_out_op8_set_ZN (code, xop, &len);
+
+  return len;
+}
+
+
 /* Output bit operation (IOR, AND, XOR) with register XOP[0] and compile
    time constant XOP[2]:
 
@@ -11022,6 +11193,7 @@  avr_adjust_insn_length (rtx_insn *insn, int len)
 
     case ADJUST_LEN_INSERT_BITS: avr_out_insert_bits (op, &len); break;
     case ADJUST_LEN_ADD_SET_ZN: avr_out_plus_set_ZN (op, &len); break;
+    case ADJUST_LEN_ADD_SET_N:  avr_out_plus_set_N (op, &len); break;
 
     case ADJUST_LEN_INSV_NOTBIT: avr_out_insert_notbit (insn, op, &len); break;
 
@@ -11210,7 +11382,7 @@  avr_assemble_integer (rtx x, unsigned int size, int aligned_p)
 static unsigned char
 avr_class_max_nregs (reg_class_t rclass, machine_mode mode)
 {
-  if (rclass == CC_REG && mode == CCmode)
+  if (rclass == CC_REG && GET_MODE_CLASS (mode) == MODE_CC)
     return 1;
 
   return CEIL (GET_MODE_SIZE (mode), UNITS_PER_WORD);
@@ -13750,7 +13922,7 @@  jump_over_one_insn_p (rtx_insn *insn, rtx dest)
 static unsigned int
 avr_hard_regno_nregs (unsigned int regno, machine_mode mode)
 {
-  if (regno == REG_CC && mode == CCmode)
+  if (regno == REG_CC && GET_MODE_CLASS (mode) == MODE_CC)
     return 1;
 
   return CEIL (GET_MODE_SIZE (mode), UNITS_PER_WORD);
@@ -13765,7 +13937,7 @@  static bool
 avr_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
 {
   if (regno == REG_CC)
-    return mode == CCmode;
+    return GET_MODE_CLASS (mode) == MODE_CC;
 
   /* NOTE: 8-bit values must not be disallowed for R28 or R29.
 	Disallowing QI et al. in these regs might lead to code like
diff --git a/gcc/config/avr/avr.md b/gcc/config/avr/avr.md
index 2783b8c986f..e4f55fb7f4f 100644
--- a/gcc/config/avr/avr.md
+++ b/gcc/config/avr/avr.md
@@ -171,7 +171,7 @@  (define_attr "adjust_len"
    ashlsi, ashrsi, lshrsi,
    ashlpsi, ashrpsi, lshrpsi,
    insert_bits, insv_notbit, insv,
-   add_set_ZN, cmp_uext, cmp_sext,
+   add_set_ZN, add_set_N, cmp_uext, cmp_sext,
    no"
   (const_string "no"))
 
@@ -277,6 +277,11 @@  (define_mode_iterator ALLs4 [SI SQ SA])
 (define_mode_iterator ALLs234 [HI SI PSI
                                HQ HA SQ SA])
 
+(define_mode_iterator ALLCC [CC CCN CCZN])
+(define_mode_iterator CCN_CCZN [CCN CCZN])
+
+(define_mode_attr CCname [(CC "") (CCN "_N") (CCZN "_ZN")])
+
 ;; All supported move-modes
 (define_mode_iterator MOVMODE [QI QQ UQQ
                                HI HQ UHQ HA UHA
@@ -319,6 +324,9 @@  (define_code_iterator bitop [xor ior and])
 (define_code_iterator xior [xor ior])
 (define_code_iterator eqne [eq ne])
 (define_code_iterator gelt [ge lt])
+(define_code_iterator eqnegtle [eq ne gt le])
+(define_code_iterator cmp_signed [eq ne ge lt gt le])
+(define_code_iterator op8_ZN [plus minus and ior xor ashift ashiftrt lshiftrt])
 
 (define_code_iterator ss_addsub [ss_plus ss_minus])
 (define_code_iterator us_addsub [us_plus us_minus])
@@ -350,6 +358,20 @@  (define_code_attr gelt_eqne
   [(ge "eq")
    (lt "ne")])
 
+(define_code_attr swap
+  [(eq "eq") (ne "ne")
+   (lt "gt") (gt "lt")
+   (le "ge") (ge "le")])
+
+(define_code_attr SWAP
+  [(eq "EQ") (ne "NE")
+   (lt "GT") (gt "LT")
+   (le "GE") (ge "LE")])
+
+;; Convert test of MSB to < 0 resp. >= 0.
+(define_code_attr tstMSB
+  [(eq "ge") (ne "lt")])
+
 ;; Map RTX code to its standard insn name
 (define_code_attr code_stdname
   [(ashift   "ashl")
@@ -7199,32 +7221,26 @@  (define_peephole2 ; "*sbrx_branch<mode>"
 ;;  Compare with 0 (test) jumps
 ;; ************************************************************************
 
-(define_insn "branch"
+;; "branch"
+;; "branch_N"
+;; "branch_ZN"
+(define_insn "branch<CCname>"
   [(set (pc)
-        (if_then_else (match_operator 1 "simple_comparison_operator"
-                        [(reg:CC REG_CC)
+        (if_then_else (match_operator 1 "ordered_comparison_operator"
+                        [(reg:ALLCC REG_CC)
                          (const_int 0)])
                       (label_ref (match_operand 0))
                       (pc)))]
   "reload_completed"
   {
-    return ret_cond_branch (operands[1], avr_jump_mode (operands[0], insn), 0);
-  }
-  [(set_attr "type" "branch")])
-
-
-(define_insn "difficult_branch"
-  [(set (pc)
-        (if_then_else (match_operator 1 "difficult_comparison_operator"
-                        [(reg:CC REG_CC)
-                         (const_int 0)])
-                      (label_ref (match_operand 0 "" ""))
-                      (pc)))]
-  "reload_completed"
-  {
-    return ret_cond_branch (operands[1], avr_jump_mode (operands[0], insn), 0);
+    return ret_cond_branch (operands[1], avr_jump_mode (operands[0], insn),
+                            <MODE>mode);
   }
-  [(set_attr "type" "branch1")])
+  [(set (attr "type")
+        (if_then_else
+         (match_test "simple_comparison_operator (operands[1], VOIDmode)")
+         (const_string "branch")
+         (const_string "branch1")))])
 
 
 ;; **************************************************************************
@@ -9547,8 +9563,8 @@  (define_peephole2
 ;; by a comparison of the result against zero, we can output the addition
 ;; in such a way that SREG.N and SREG.Z are set according to the result.
 
-;; { -1, +1 } for QImode, otherwise the empty set.
-(define_mode_attr p1m1 [(QI "N P")
+;; { -2, -1, +1, +2 } for QImode, otherwise the empty set.
+(define_mode_attr p2m2 [(QI "Cm2 N P K")
                         (HI "Yxx") (PSI "Yxx") (SI "Yxx")])
 
 ;; FIXME: reload1.cc::do_output_reload() does not support output reloads
@@ -9587,46 +9603,72 @@  (define_mode_attr p1m1 [(QI "N P")
 ;; doloop_end doesn't reload either, so doloop_end also won't work.
 
 (define_expand "gen_add_for_<code>_<mode>"
-  ; "*add.for.eqne.<mode>"
-  [(parallel [(set (reg:CC REG_CC)
-                   (compare:CC (plus:QISI (match_operand:QISI 0 "register_operand")
-                                          (match_operand:QISI 1 "const_int_operand"))
-                               (const_int 0)))
+  ; "*add.for.cczn.<mode>"
+  [(parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (plus:QISI (match_operand:QISI 0 "register_operand")
+                                            (match_operand:QISI 1 "const_int_operand"))
+                                 (const_int 0)))
               (set (match_dup 0)
                    (plus:QISI (match_dup 0)
                               (match_dup 1)))
               (clobber (match_operand:QI 3))])
-   ; "branch"
+   ; "branch_ZN"
    (set (pc)
-        (if_then_else (eqne (reg:CC REG_CC)
+        (if_then_else (eqnegtle (reg:CCZN REG_CC)
+                                (const_int 0))
+                      (label_ref (match_dup 2))
+                      (pc)))])
+
+(define_expand "gen_add_for_<code>_<mode>"
+  ; "*add.for.ccn.<mode>"
+  [(parallel [(set (reg:CCN REG_CC)
+                   (compare:CCN (plus:QISI (match_operand:QISI 0 "register_operand")
+                                           (match_operand:QISI 1 "const_int_operand"))
+                                (const_int 0)))
+              (set (match_dup 0)
+                   (plus:QISI (match_dup 0)
+                              (match_dup 1)))
+              (clobber (match_operand:QI 3))])
+   ; "branch_N"
+   (set (pc)
+        (if_then_else (gelt (reg:CCN REG_CC)
                             (const_int 0))
                       (label_ref (match_dup 2))
                       (pc)))])
 
 
-;; 1/3: A version without clobber: d-reg or 8-bit adds +/-1.
+;; 1/3: A version without clobber: d-reg or 8-bit adds -2, -1, 1 or 2.
 (define_peephole2
   [(parallel [(set (match_operand:QISI 0 "register_operand")
                    (plus:QISI (match_dup 0)
                               (match_operand:QISI 1 "const_int_operand")))
               (clobber (reg:CC REG_CC))])
    (set (reg:CC REG_CC)
-        (compare:CC (match_dup 0)
-                    (const_int 0)))
+        (compare:CC (match_operand:QISI 3 "reg_or_0_operand")   ; 0 or $0
+                    (match_operand:QISI 4 "reg_or_0_operand"))) ; 0 or $0
    (set (pc)
-        (if_then_else (eqne (reg:CC REG_CC)
-                            (const_int 0))
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
                       (label_ref (match_operand 2))
                       (pc)))]
   "peep2_regno_dead_p (3, REG_CC)
    && (d_register_operand (operands[0], <MODE>mode)
        || (<MODE>mode == QImode
-           && (INTVAL (operands[1]) == 1
-               || INTVAL (operands[1]) == -1)))"
+           && abs1_abs2_operand (operands[1], QImode)))"
   [(scratch)]
   {
-    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
-          gen_rtx_SCRATCH (QImode)));
+    rtx (*gen)(rtx, rtx, rtx, rtx);
+
+    if (rtx_equal_p (operands[0], operands[3])
+        && const0_operand (operands[4], <MODE>mode))
+      gen = gen_gen_add_for_<code>_<mode>;
+    else if (const0_operand (operands[3], <MODE>mode)
+             && rtx_equal_p (operands[0], operands[4]))
+      gen = gen_gen_add_for_<cmp_signed:swap>_<mode>;
+    else
+      FAIL;
+
+    emit (gen (operands[0], operands[1], operands[2], gen_rtx_SCRATCH (QImode)));
     DONE;
   })
 
@@ -9638,30 +9680,39 @@  (define_peephole2
               (clobber (match_operand:QI 3 "scratch_or_d_register_operand"))
               (clobber (reg:CC REG_CC))])
    (parallel [(set (reg:CC REG_CC)
-                   (compare:CC (match_dup 0)
-                               (const_int 0)))
+                   (compare:CC (match_operand:QISI 5 "reg_or_0_operand")   ; 0 or $0
+                               (match_operand:QISI 6 "reg_or_0_operand"))) ; 0 or $0
               (clobber (match_operand:QI 4 "scratch_or_d_register_operand"))])
    (set (pc)
-        (if_then_else (eqne (reg:CC REG_CC)
-                            (const_int 0))
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
                       (label_ref (match_operand 2))
                       (pc)))]
   "peep2_regno_dead_p (3, REG_CC)"
   [(scratch)]
   {
     rtx scratch = REG_P (operands[3]) ? operands[3] : operands[4];
+    rtx (*gen)(rtx, rtx, rtx, rtx);
 
     // We need either a d-register or a scratch register to clobber.
     if (! REG_P (scratch)
         && ! d_register_operand (operands[0], <MODE>mode)
         && ! (QImode == <MODE>mode
-              && (INTVAL (operands[1]) == 1
-                  || INTVAL (operands[1]) == -1)))
+              && abs1_abs2_operand (operands[1], QImode)))
       {
         FAIL;
       }
-    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
-          scratch));
+
+    if (rtx_equal_p (operands[0], operands[5])
+        && const0_operand (operands[6], <MODE>mode))
+      gen = gen_gen_add_for_<code>_<mode>;
+    else if (const0_operand (operands[5], <MODE>mode)
+             && rtx_equal_p (operands[0], operands[6]))
+      gen = gen_gen_add_for_<cmp_signed:swap>_<mode>;
+    else
+      FAIL;
+
+    emit (gen (operands[0], operands[1], operands[2], scratch));
     DONE;
   })
 
@@ -9673,41 +9724,255 @@  (define_peephole2
                               (match_operand:QISI 1 "const_int_operand")))
               (clobber (reg:CC REG_CC))])
    (set (reg:CC REG_CC)
-        (compare:CC (match_dup 0)
-                    (const_int 0)))
+        (compare:CC (match_operand:QISI 4 "reg_or_0_operand")   ; 0 or $0
+                    (match_operand:QISI 5 "reg_or_0_operand"))) ; 0 or $0
    (set (pc)
-        (if_then_else (eqne (reg:CC REG_CC)
-                            (const_int 0))
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
                       (label_ref (match_operand 2))
                       (pc)))]
   "peep2_regno_dead_p (3, REG_CC)"
   [(scratch)]
   {
-    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
-          operands[3]));
+    rtx (*gen)(rtx, rtx, rtx, rtx);
+
+    if (rtx_equal_p (operands[0], operands[4])
+        && const0_operand (operands[5], <MODE>mode))
+      gen = gen_gen_add_for_<code>_<mode>;
+    else if (const0_operand (operands[4], <MODE>mode)
+             && rtx_equal_p (operands[0], operands[5]))
+      gen = gen_gen_add_for_<cmp_signed:swap>_<mode>;
+    else
+      FAIL;
+
+    emit (gen (operands[0], operands[1], operands[2], operands[3]));
     DONE;
   })
 
 ;; Result of the above three peepholes is an addition that also
-;; performs an EQ or NE comparison (of the result) against zero.
+;; performs a signed comparison (of the result) against zero.
 ;; FIXME: Using (match_dup 0) instead of operands[3/4] makes rnregs
 ;; barf in regrename.cc::merge_overlapping_regs().  For now, use the
 ;; fix from PR50788: Constrain as "0".
-(define_insn "*add.for.eqne.<mode>"
-  [(set (reg:CC REG_CC)
-        (compare:CC
-         (plus:QISI (match_operand:QISI 3 "register_operand"  "0,0     ,0")
-                    (match_operand:QISI 1 "const_int_operand" "n,<p1m1>,n"))
+; "*add.for.ccn.qi"   "*add.for.cczn.qi"
+; "*add.for.ccn.hi"   "*add.for.cczn.hi"
+; "*add.for.ccn.psi"  "*add.for.cczn.psi"
+; "*add.for.ccn.si"   "*add.for.cczn.si"
+(define_insn "*add.for.<CCN_CCZN:mode>.<QISI:mode>"
+  [(set (reg:CCN_CCZN REG_CC)
+        (compare:CCN_CCZN
+         (plus:QISI (match_operand:QISI 3 "register_operand"  "0,0          ,0")
+                    (match_operand:QISI 1 "const_int_operand" "n,<QISI:p2m2>,n"))
          (const_int 0)))
-   (set (match_operand:QISI 0 "register_operand"             "=d,*r    ,r")
-        (plus:QISI (match_operand:QISI 4 "register_operand"   "0,0     ,0")
+   (set (match_operand:QISI 0 "register_operand"             "=d,*r         ,r")
+        (plus:QISI (match_operand:QISI 4 "register_operand"   "0,0          ,0")
                    (match_dup 1)))
-   (clobber (match_scratch:QI 2                              "=X,X     ,&d"))]
+   (clobber (match_scratch:QI 2                              "=X,X          ,&d"))]
+  "reload_completed"
+  {
+    return avr_out_plus_set<CCname> (operands, nullptr);
+  }
+  [(set_attr "adjust_len" "add_set<CCname>")])
+
+
+
+;; Operations other that PLUS can set the condition code in
+;; a meaningful way, too.
+
+;; 1/2 Left shift sets the N bit.
+(define_peephole2
+  [(parallel [(set (match_operand:HISI 0 "register_operand")
+                   (ashift:HISI (match_dup 0)
+                                (const_int 1)))
+              (clobber (match_operand:QI 3 "scratch_operand"))
+              (clobber (reg:CC REG_CC))])
+   (set (reg:CC REG_CC)
+        (compare:CC (match_dup 0)
+                    (const_int 0)))
+   (set (pc)
+        (if_then_else (gelt (reg:CC REG_CC)
+                            (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))]
+  "peep2_regno_dead_p (3, REG_CC)"
+  [; "*ashift.for.ccn.<mode>"
+   (parallel [(set (reg:CCN REG_CC)
+                   (compare:CCN (ashift:HISI (match_dup 0)
+                                             (const_int 1))
+                                (const_int 0)))
+              (set (match_dup 0)
+                   (ashift:HISI (match_dup 0)
+                                (const_int 1)))])
+   ; "branch_N"
+   (set (pc)
+        (if_then_else (gelt (reg:CCN REG_CC)
+                            (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))])
+
+
+;; 2/2 Left shift sets the N bit.  Same like above, but with ZERO_EXTRACT
+(define_peephole2
+  [(parallel [(set (match_operand:HISI 0 "register_operand")
+                   (ashift:HISI (match_dup 0)
+                                (const_int 1)))
+              (clobber (match_operand:QI 3 "scratch_operand"))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (pc)
+                   (if_then_else
+                    (eqne (zero_extract (match_dup 0)
+                                        (const_int 1)
+                                        (match_operand 4 "const<MSB>_operand"))
+                          (const_int 0))
+                    (label_ref (match_operand 2))
+                    (pc)))
+              (clobber (reg:CC REG_CC))])]
+  ""
+  [; "*ashift.for.ccn.<mode>"
+   (parallel [(set (reg:CCN REG_CC)
+                   (compare:CCN (ashift:HISI (match_dup 0)
+                                             (const_int 1))
+                                (const_int 0)))
+              (set (match_dup 0)
+                   (ashift:HISI (match_dup 0)
+                                (const_int 1)))])
+   ; "branch_N"
+   (set (pc)
+        (if_then_else (<tstMSB> (reg:CCN REG_CC)
+                                (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))])
+
+
+(define_insn "*ashift.for.ccn.<mode>"
+  [(set (reg:CCN REG_CC)
+        (compare:CCN (ashift:HISI (match_operand:HISI 2 "register_operand" "0")
+                                  (const_int 1))
+                     (const_int 0)))
+   (set (match_operand:HISI 0 "register_operand"             "=r")
+        (ashift:HISI (match_operand:HISI 1 "register_operand" "0")
+                     (const_int 1)))]
   "reload_completed"
   {
-    return avr_out_plus_set_ZN (operands, nullptr);
+    output_asm_insn ("lsl %A0", operands);
+    if (<SIZE> >= 2) output_asm_insn ("rol %B0", operands);
+    if (<SIZE> >= 3) output_asm_insn ("rol %C0", operands);
+    if (<SIZE> >= 4) output_asm_insn ("rol %D0", operands);
+    return "";
+  }
+  [(set_attr "length" "<SIZE>")])
+
+
+;; 1/2 QImode operations that set Z and N in a meaningful way.
+(define_peephole2
+  [(parallel [(set (match_operand:QI 0 "register_operand")
+                   (match_operator:QI 2 "op8_ZN_operator" [(match_dup 0)
+                                                           (match_operand:QI 1)]))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (pc)
+                   (if_then_else (eqne (zero_extract (match_dup 0)
+                                                     (const_int 1)
+                                                     (const_int 7))
+                                       (const_int 0))
+                                 (label_ref (match_operand 3))
+                                 (pc)))
+              (clobber (reg:CC REG_CC))])]
+  ""
+  [; "*op8.for.cczn.<code>"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (match_op_dup 2 [(match_dup 0)
+                                                  (match_dup 1)])
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (match_op_dup 2 [(match_dup 0)
+                                    (match_dup 1)]))])
+   ; "branch_ZN"
+   (set (pc)
+        (if_then_else (<tstMSB> (reg:CCZN REG_CC)
+                                (const_int 0))
+                      (label_ref (match_operand 3))
+                      (pc)))])
+
+
+;; 2/2 QImode operations that set Z and N in a meaningful way.
+(define_peephole2
+  [(parallel [(set (match_operand:QI 0 "register_operand")
+                   (match_operator:QI 2 "op8_ZN_operator" [(match_dup 0)
+                                                           (match_operand:QI 1)]))
+              (clobber (reg:CC REG_CC))])
+   (set (reg:CC REG_CC)
+        (compare:CC (match_operand:QI 4 "reg_or_0_operand")   ; 0 or $0
+                    (match_operand:QI 5 "reg_or_0_operand"))) ; 0 or $0
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_operand 3))
+                      (pc)))]
+  "peep2_regno_dead_p (3, REG_CC)"
+  [; "*op8.for.cczn.<code>"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (match_op_dup 2 [(match_dup 0)
+                                                  (match_dup 1)])
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (match_op_dup 2 [(match_dup 0)
+                                    (match_dup 1)]))])
+   ; "branch_ZN"
+   (set (pc)
+        (if_then_else (match_dup 6)
+                      (label_ref (match_operand 3))
+                      (pc)))]
+  {
+    RTX_CODE code = UNKNOWN;
+
+    if (rtx_equal_p (operands[4], operands[0])
+        && const0_operand (operands[5], QImode))
+      code = <cmp_signed:CODE>;
+    else if (const0_operand (operands[4], QImode)
+             && rtx_equal_p (operands[5], operands[0]))
+      code = <cmp_signed:SWAP>;
+    else
+      FAIL;
+
+  operands[6] = gen_rtx_fmt_ee (code, VOIDmode,
+                                gen_rtx_REG (CCZNmode, REG_CC), const0_rtx);
+  })
+
+;; Constraints and predicate for the insn below.
+;; This is what op8_ZN_operator allows.
+;; Note again that due to nregs, match_dup's won't work.
+(define_code_attr c0_op8
+  [(minus "r") (xor "r") (and "d,r") (ior "d,r") (plus "d,r") (ashift "r") (ashiftrt "r") (lshiftrt "r")])
+(define_code_attr c1_op8
+  [(minus "0") (xor "0") (and "0,0") (ior "0,0") (plus "0,0") (ashift "0") (ashiftrt "0") (lshiftrt "0")])
+(define_code_attr c4_op8
+  [(minus "2") (xor "2") (and "2,2") (ior "2,2") (plus "2,2") (ashift "2") (ashiftrt "2") (lshiftrt "2")])
+(define_code_attr c2_op8
+  [(minus "r") (xor "r") (and "n,r") (ior "n,r") (plus "n,rPNKCm2")
+   (ashift "PKC03") (ashiftrt "PKC03") (lshiftrt "PKC03")])
+
+(define_code_attr p2_op8
+  [(ashift "const_1_to_3")  (ashiftrt "const_1_to_3")  (lshiftrt "const_1_to_3")
+   (minus "register")  (xor "register")
+   (plus "nonmemory")  (and "nonmemory")  (ior "nonmemory")])
+
+;; Result of the two peephole2's above:  An 8-bit operation that
+;; sets Z and N.  The allowed operations are:
+;; PLUS, MINUS, AND, IOR, XOR and SHIFTs.
+(define_insn "*op8.for.cczn.<code>"
+  [(parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (op8_ZN:QI (match_operand:QI 3 "register_operand" "<c1_op8>")
+                                            (match_operand:QI 4 "<p2_op8>_operand" "<c4_op8>"))
+                                 (const_int 0)))
+              (set (match_operand:QI 0 "register_operand"           "=<c0_op8>")
+                   (op8_ZN:QI (match_operand:QI 1 "register_operand" "<c1_op8>")
+                              (match_operand:QI 2 "<p2_op8>_operand" "<c2_op8>")))])]
+  "reload_completed"
+  {
+    return avr_out_op8_set_ZN (<CODE>, operands, nullptr);
   }
-  [(set_attr "adjust_len" "add_set_ZN")])
+  [(set (attr "length")
+        (symbol_ref ("avr_len_op8_set_ZN (<CODE>, operands)")))])
 
 
 ;; Swapping both comparison and branch condition.  This can turn difficult
diff --git a/gcc/config/avr/predicates.md b/gcc/config/avr/predicates.md
index 12013660ed1..0a35c8c31c1 100644
--- a/gcc/config/avr/predicates.md
+++ b/gcc/config/avr/predicates.md
@@ -147,6 +147,11 @@  (define_predicate "const_2_to_7_operand"
   (and (match_code "const_int")
        (match_test "IN_RANGE (INTVAL (op), 2, 7)")))
 
+;; Return true if OP is constant integer 1..3 for MODE.
+(define_predicate "const_1_to_3_operand"
+  (and (match_code "const_int")
+       (match_test "IN_RANGE (INTVAL (op), 1, 3)")))
+
 ;; Return 1 if OP is constant integer 1..6 for MODE.
 (define_predicate "const_1_to_6_operand"
   (and (match_code "const_int")
@@ -162,6 +167,12 @@  (define_predicate "const_m255_to_m1_operand"
   (and (match_code "const_int")
        (match_test "IN_RANGE (INTVAL (op), -255, -1)")))
 
+;; Return true if OP is a CONST_INT in { -2, -1, 1, 2 }.
+(define_predicate "abs1_abs2_operand"
+  (and (match_code "const_int")
+       (match_test "INTVAL (op) != 0")
+       (match_test "IN_RANGE (INTVAL (op), -2, 2)")))
+
 ;; Returns true if OP is either the constant zero or a register.
 (define_predicate "reg_or_0_operand"
   (ior (match_operand 0 "register_operand")
@@ -228,10 +239,30 @@  (define_predicate "simple_comparison_operator"
   (and (match_operand 0 "comparison_operator")
        (not (match_code "gt,gtu,le,leu"))))
 
+;; True for EQ, NE, GE, LT, GT, LE
+(define_predicate "signed_comparison_operator"
+  (match_code "eq,ne,ge,lt,gt,le"))
+
 ;; True for SIGN_EXTEND, ZERO_EXTEND.
 (define_predicate "extend_operator"
   (match_code "sign_extend,zero_extend"))
 
+;; True for 8-bit operations that set SREG.N and SREG.Z in a
+;; usable way:
+;; * OP0 is a QImode register, and
+;; * OP1 is a QImode register or CONST_INT, and
+;;
+;; the allowed operations is one of:
+;;
+;; * SHIFTs with a const_int offset in { 1, 2, 3 }.
+;; * MINUS and XOR with a register operand
+;; * IOR and AND with a register operand, or d-reg + const_int
+;; * PLUS with a register operand, or d-reg + const_int,
+;;   or a const_int in { -2, -1, 1, 2 }.  */
+(define_predicate "op8_ZN_operator"
+  (and (match_code "plus,minus,ashift,ashiftrt,lshiftrt,and,ior,xor")
+       (match_test "avr_op8_ZN_operator (op)")))
+
 ;; Return true if OP is a valid call operand.
 (define_predicate "call_insn_operand"
   (and (match_code "mem")
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-add-c.c b/gcc/testsuite/gcc.target/avr/pr115830-add-c.c
new file mode 100644
index 00000000000..4f269293590
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-add-c.c
@@ -0,0 +1,79 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (ADD_01, += 1, != 0)
+MK_FUN (ADD_02, += 1, == 0)
+MK_FUN (ADD_03, += 1, >= 0)
+MK_FUN (ADD_04, += 1, <= 0)
+MK_FUN (ADD_05, += 1, > 0)
+MK_FUN (ADD_06, += 1, < 0)
+MK_FUN (ADD_07, -= 2, != 0)
+MK_FUN (ADD_08, -= 2, == 0)
+MK_FUN (ADD_09, -= 2, >= 0)
+MK_FUN (ADD_10, -= 2, <= 0)
+MK_FUN (ADD_11, -= 2, > 0)
+MK_FUN (ADD_12, -= 2, < 0)
+MK_FUN (ADD_13, += 42, != 0)
+MK_FUN (ADD_14, += 42, == 0)
+MK_FUN (ADD_15, += 42, >= 0)
+MK_FUN (ADD_16, += 42, <= 0)
+MK_FUN (ADD_17, += 42, > 0)
+MK_FUN (ADD_18, += 42, < 0)
+	
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_ADD_01 (c) != func2_ADD_01 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_02 (c) != func2_ADD_02 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_03 (c) != func2_ADD_03 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_04 (c) != func2_ADD_04 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_05 (c) != func2_ADD_05 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_06 (c) != func2_ADD_06 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_07 (c) != func2_ADD_07 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_08 (c) != func2_ADD_08 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_09 (c) != func2_ADD_09 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_10 (c) != func2_ADD_10 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_11 (c) != func2_ADD_11 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_12 (c) != func2_ADD_12 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_13 (c) != func2_ADD_13 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_14 (c) != func2_ADD_14 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_15 (c) != func2_ADD_15 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_16 (c) != func2_ADD_16 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_17 (c) != func2_ADD_17 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_18 (c) != func2_ADD_18 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-add-i.c b/gcc/testsuite/gcc.target/avr/pr115830-add-i.c
new file mode 100644
index 00000000000..f3b4df95fd7
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-add-i.c
@@ -0,0 +1,103 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT16_TYPE__ uint16_t;
+typedef __INT16_TYPE__  int16_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int16_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (ADD_01, += 1, != 0)
+MK_FUN (ADD_02, += 1, == 0)
+MK_FUN (ADD_03, += 1, >= 0)
+MK_FUN (ADD_04, += 1, <= 0)
+MK_FUN (ADD_05, += 1, > 0)
+MK_FUN (ADD_06, += 1, < 0)
+MK_FUN (ADD_07, -= 2, != 0)
+MK_FUN (ADD_08, -= 2, == 0)
+MK_FUN (ADD_09, -= 2, >= 0)
+MK_FUN (ADD_10, -= 2, <= 0)
+MK_FUN (ADD_11, -= 2, > 0)
+MK_FUN (ADD_12, -= 2, < 0)
+MK_FUN (ADD_13, += 42, != 0)
+MK_FUN (ADD_14, += 42, == 0)
+MK_FUN (ADD_15, += 42, >= 0)
+MK_FUN (ADD_16, += 42, <= 0)
+MK_FUN (ADD_17, += 42, > 0)
+MK_FUN (ADD_18, += 42, < 0)
+MK_FUN (ADD_19, += 256, != 0)
+MK_FUN (ADD_20, += 256, == 0)
+MK_FUN (ADD_21, += 256, >= 0)
+MK_FUN (ADD_22, += 256, <= 0)
+MK_FUN (ADD_23, += 256, > 0)
+MK_FUN (ADD_24, += 256, < 0)
+MK_FUN (ADD_25, += 512, != 0)
+MK_FUN (ADD_26, += 512, == 0)
+MK_FUN (ADD_27, += 512, >= 0)
+MK_FUN (ADD_28, += 512, <= 0)
+MK_FUN (ADD_29, += 512, > 0)
+MK_FUN (ADD_30, += 512, < 0)
+	
+
+int main (void)
+{
+  uint16_t c = 0;
+  do {
+    if (func1_ADD_01 (c) != func2_ADD_01 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_02 (c) != func2_ADD_02 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_03 (c) != func2_ADD_03 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_04 (c) != func2_ADD_04 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_05 (c) != func2_ADD_05 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_06 (c) != func2_ADD_06 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_07 (c) != func2_ADD_07 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_08 (c) != func2_ADD_08 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_09 (c) != func2_ADD_09 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_10 (c) != func2_ADD_10 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_11 (c) != func2_ADD_11 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_12 (c) != func2_ADD_12 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_13 (c) != func2_ADD_13 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_14 (c) != func2_ADD_14 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_15 (c) != func2_ADD_15 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_16 (c) != func2_ADD_16 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_17 (c) != func2_ADD_17 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_18 (c) != func2_ADD_18 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_19 (c) != func2_ADD_19 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_20 (c) != func2_ADD_20 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_21 (c) != func2_ADD_21 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_22 (c) != func2_ADD_22 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_23 (c) != func2_ADD_23 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_24 (c) != func2_ADD_24 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_25 (c) != func2_ADD_25 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_26 (c) != func2_ADD_26 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_27 (c) != func2_ADD_27 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_28 (c) != func2_ADD_28 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_29 (c) != func2_ADD_29 (c)) __builtin_exit (__LINE__);
+    if (func1_ADD_30 (c) != func2_ADD_30 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-and.c b/gcc/testsuite/gcc.target/avr/pr115830-and.c
new file mode 100644
index 00000000000..317486feb03
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-and.c
@@ -0,0 +1,67 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (AND_01, &= 0x80, != 0)
+MK_FUN (AND_02, &= 0x80, == 0)
+MK_FUN (AND_03, &= 0x80, >= 0)
+MK_FUN (AND_04, &= 0x80, <= 0)
+MK_FUN (AND_05, &= 0x80, > 0)
+MK_FUN (AND_06, &= 0x80, < 0)
+MK_FUN (AND_07, &= 0xef, != 0)
+MK_FUN (AND_08, &= 0xef, == 0)
+MK_FUN (AND_09, &= 0xef, >= 0)
+MK_FUN (AND_10, &= 0xef, <= 0)
+MK_FUN (AND_11, &= 0xef, > 0)
+MK_FUN (AND_12, &= 0xef, < 0)
+	
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_AND_01 (c) != func2_AND_01 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_02 (c) != func2_AND_02 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_03 (c) != func2_AND_03 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_04 (c) != func2_AND_04 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_05 (c) != func2_AND_05 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_06 (c) != func2_AND_06 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_07 (c) != func2_AND_07 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_08 (c) != func2_AND_08 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_09 (c) != func2_AND_09 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_10 (c) != func2_AND_10 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_11 (c) != func2_AND_11 (c)) __builtin_exit (__LINE__);
+    if (func1_AND_12 (c) != func2_AND_12 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-asl.c b/gcc/testsuite/gcc.target/avr/pr115830-asl.c
new file mode 100644
index 00000000000..09a4495caac
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-asl.c
@@ -0,0 +1,78 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (ASL_01, <<= 1, != 0)
+MK_FUN (ASL_02, <<= 2, != 0)
+MK_FUN (ASL_03, <<= 3, != 0)
+MK_FUN (ASL_04, <<= 1, == 0)
+MK_FUN (ASL_05, <<= 2, == 0)
+MK_FUN (ASL_06, <<= 3, == 0)
+MK_FUN (ASL_07, <<= 1, >= 0)
+MK_FUN (ASL_08, <<= 2, >= 0)
+MK_FUN (ASL_09, <<= 3, >= 0)
+MK_FUN (ASL_10, <<= 1, <= 0)
+MK_FUN (ASL_11, <<= 2, <= 0)
+MK_FUN (ASL_12, <<= 3, <= 0)
+MK_FUN (ASL_13, <<= 1, > 0)
+MK_FUN (ASL_14, <<= 2, > 0)
+MK_FUN (ASL_15, <<= 3, > 0)
+MK_FUN (ASL_16, <<= 1, < 0)
+MK_FUN (ASL_17, <<= 2, < 0)
+MK_FUN (ASL_18, <<= 3, < 0)
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_ASL_01 (c) != func2_ASL_01 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_02 (c) != func2_ASL_02 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_03 (c) != func2_ASL_03 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_04 (c) != func2_ASL_04 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_05 (c) != func2_ASL_05 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_06 (c) != func2_ASL_06 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_07 (c) != func2_ASL_07 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_08 (c) != func2_ASL_08 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_09 (c) != func2_ASL_09 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_10 (c) != func2_ASL_10 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_11 (c) != func2_ASL_11 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_12 (c) != func2_ASL_12 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_13 (c) != func2_ASL_13 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_14 (c) != func2_ASL_14 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_15 (c) != func2_ASL_15 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_16 (c) != func2_ASL_16 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_17 (c) != func2_ASL_17 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_18 (c) != func2_ASL_18 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-asl32.c b/gcc/testsuite/gcc.target/avr/pr115830-asl32.c
new file mode 100644
index 00000000000..6e8100f5f58
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-asl32.c
@@ -0,0 +1,57 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT32_TYPE__ uint32_t;
+typedef __INT32_TYPE__  int32_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int32_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 0x77665544;                               \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 0x77665544;                           \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (ASL_03, <<= 1, >= 0)
+MK_FUN (ASL_06, <<= 1, < 0)
+
+NI void test_asl (uint32_t c)
+{
+    if (func1_ASL_03 (c) != func2_ASL_03 (c)) __builtin_exit (__LINE__);
+    if (func1_ASL_06 (c) != func2_ASL_06 (c)) __builtin_exit (__LINE__);
+}
+
+int main (void)
+{
+  test_asl (0);
+  test_asl (0x80000000);
+  test_asl (0x80000001);
+  test_asl (0xc0000000);
+  test_asl (0xc0000001);
+  test_asl (0);
+  test_asl (0xff00ff00);
+  test_asl (0x00ff00ff);
+  test_asl (0xff00ff00 >> 1);
+  test_asl (0x00ff00ff >> 1);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-asr.c b/gcc/testsuite/gcc.target/avr/pr115830-asr.c
new file mode 100644
index 00000000000..1ceafb9a537
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-asr.c
@@ -0,0 +1,78 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (ASR_01, >>= 1, != 0)
+MK_FUN (ASR_02, >>= 2, != 0)
+MK_FUN (ASR_03, >>= 3, != 0)
+MK_FUN (ASR_04, >>= 1, == 0)
+MK_FUN (ASR_05, >>= 2, == 0)
+MK_FUN (ASR_06, >>= 3, == 0)
+MK_FUN (ASR_07, >>= 1, >= 0)
+MK_FUN (ASR_08, >>= 2, >= 0)
+MK_FUN (ASR_09, >>= 3, >= 0)
+MK_FUN (ASR_10, >>= 1, <= 0)
+MK_FUN (ASR_11, >>= 2, <= 0)
+MK_FUN (ASR_12, >>= 3, <= 0)
+MK_FUN (ASR_13, >>= 1, > 0)
+MK_FUN (ASR_14, >>= 2, > 0)
+MK_FUN (ASR_15, >>= 3, > 0)
+MK_FUN (ASR_16, >>= 1, < 0)
+MK_FUN (ASR_17, >>= 2, < 0)
+MK_FUN (ASR_18, >>= 3, < 0)
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_ASR_01 (c) != func2_ASR_01 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_02 (c) != func2_ASR_02 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_03 (c) != func2_ASR_03 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_04 (c) != func2_ASR_04 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_05 (c) != func2_ASR_05 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_06 (c) != func2_ASR_06 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_07 (c) != func2_ASR_07 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_08 (c) != func2_ASR_08 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_09 (c) != func2_ASR_09 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_10 (c) != func2_ASR_10 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_11 (c) != func2_ASR_11 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_12 (c) != func2_ASR_12 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_13 (c) != func2_ASR_13 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_14 (c) != func2_ASR_14 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_15 (c) != func2_ASR_15 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_16 (c) != func2_ASR_16 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_17 (c) != func2_ASR_17 (c)) __builtin_exit (__LINE__);
+    if (func1_ASR_18 (c) != func2_ASR_18 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-ior.c b/gcc/testsuite/gcc.target/avr/pr115830-ior.c
new file mode 100644
index 00000000000..51451b4f32f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-ior.c
@@ -0,0 +1,67 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP int8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (IOR_01, &= 0x8, != 0)
+MK_FUN (IOR_02, &= 0x8, == 0)
+MK_FUN (IOR_03, &= 0x8, >= 0)
+MK_FUN (IOR_04, &= 0x8, <= 0)
+MK_FUN (IOR_05, &= 0x8, > 0)
+MK_FUN (IOR_06, &= 0x8, < 0)
+MK_FUN (IOR_07, &= 0x7f, != 0)
+MK_FUN (IOR_08, &= 0x7f, == 0)
+MK_FUN (IOR_09, &= 0x7f, >= 0)
+MK_FUN (IOR_10, &= 0x7f, <= 0)
+MK_FUN (IOR_11, &= 0x7f, > 0)
+MK_FUN (IOR_12, &= 0x7f, < 0)
+	
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_IOR_01 (c) != func2_IOR_01 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_02 (c) != func2_IOR_02 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_03 (c) != func2_IOR_03 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_04 (c) != func2_IOR_04 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_05 (c) != func2_IOR_05 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_06 (c) != func2_IOR_06 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_07 (c) != func2_IOR_07 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_08 (c) != func2_IOR_08 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_09 (c) != func2_IOR_09 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_10 (c) != func2_IOR_10 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_11 (c) != func2_IOR_11 (c)) __builtin_exit (__LINE__);
+    if (func1_IOR_12 (c) != func2_IOR_12 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-lsr.c b/gcc/testsuite/gcc.target/avr/pr115830-lsr.c
new file mode 100644
index 00000000000..1e027154c73
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-lsr.c
@@ -0,0 +1,60 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -fwrapv -Os } } */
+
+typedef __UINT8_TYPE__ uint8_t;
+typedef __INT8_TYPE__  int8_t;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noinline,noclone,no_icf))
+
+#define TYP uint8_t
+
+TYP volatile v;
+
+#define MK_FUN(ID, OP, TST)                     \
+NI TYP func1_##ID (TYP c)                       \
+{                                               \
+  v = 42;                                       \
+  c OP;                                         \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}                                               \
+                                                \
+NI TYP func2_##ID (TYP c)                       \
+{                                               \
+  TYP v = 42;                                   \
+  c OP;                                         \
+  __asm ("" : "+r" (c));                        \
+  if (c TST)                                    \
+    v = c;                                      \
+  return v;                                     \
+}
+
+MK_FUN (LSR_01, >>= 1, != 0)
+MK_FUN (LSR_02, >>= 2, != 0)
+MK_FUN (LSR_03, >>= 3, != 0)
+MK_FUN (LSR_04, >>= 1, == 0)
+MK_FUN (LSR_05, >>= 2, == 0)
+MK_FUN (LSR_06, >>= 3, == 0)
+MK_FUN (LSR_13, >>= 1, > 0)
+MK_FUN (LSR_14, >>= 2, > 0)
+MK_FUN (LSR_15, >>= 3, > 0)
+
+int main (void)
+{
+  uint8_t c = 0;
+  do {
+    if (func1_LSR_01 (c) != func2_LSR_01 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_02 (c) != func2_LSR_02 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_03 (c) != func2_LSR_03 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_04 (c) != func2_LSR_04 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_05 (c) != func2_LSR_05 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_06 (c) != func2_LSR_06 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_13 (c) != func2_LSR_13 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_14 (c) != func2_LSR_14 (c)) __builtin_exit (__LINE__);
+    if (func1_LSR_15 (c) != func2_LSR_15 (c)) __builtin_exit (__LINE__);
+  } while (++c);
+
+  return 0;
+}