diff mbox series

[PING,avr,v2] PR115830: Make better use of SREG

Message ID cb94e5f8-eff5-4c06-9d3a-86565de6dada@gjlay.de
State New
Headers show
Series [PING,avr,v2] PR115830: Make better use of SREG | expand

Commit Message

Georg-Johann Lay Aug. 19, 2024, 5:51 a.m. UTC
Ping for the patch to male better use of SREG and some
code clean-ups for trunk, no new regressions.

https://gcc.gnu.org/pipermail/gcc-patches/2024-August/659422.html

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 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.

We don't use cmpelim because it cannot provide scratch regs
(which peephole2 can), and some of the patterns require a
scratch reg, whereas the same operations that don't set REG_CC
don't require a scratch.  See the comments in avr.md for details.

The existing add.for.cc* patterns are simplified as they no
more cover QImode, which is handled in a separate QImode case.
Apart from that, it adds 3 patterns for subtractions and one
pattern for shift left, all for multi-byte cases (HI, PSI, SI).

The add.for.cc* patterns now use CC[Z]Nmode, instead of the
formerly abuse of CCmode.

	PR target/115830
gcc/
	* config/avr/avr-modes.def (CCN, CCZN): New CC_MODEs.
	* config/avr/avr-protos.h (avr_cond_branch): New from
	ret_cond_branch.
	(avr_out_plus_set_N, avr_op8_ZN_operator)
	(avr_out_op8_set_ZN, avr_len_op8_set_ZN): New protos.
	(ccn_reg_rtx, cczn_reg_rtx): New declarations.
	* config/avr/avr.cc (avr_cond_branch): New from ret_cond_branch.
	(avr_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): Remove handling of QImode.
	(avr_out_plus_set_N, avr_op8_ZN_operator)
	(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.
	(ccn_reg_rtx, cczn_reg_rtx): New GTY variables.
	(avr_init_expanders): Initialize them.
	(avr_option_override): Run peephole2 a second time.
	* config/avr/avr.md (adjust_len) [add_set_N]: New attr value.
	(ALLCC, HI_SI): New mode iterators.
	(CCname): New mode attribute.
	(eqnegtle, cmp_signed, op8_ZN): New code iterators.
	(branch): Handle CCNmode and CCZNmode.  Assimilate...
	(difficult_branch): ...this insn.
	(p1m1): Remove.
	(gen_add_for_<code>_<mode>): Adjust to CCNmode and CCZNmode. Use
	HISI as mode iterator.  Extend peephole2s that produce them.
	(*add.for.eqne.<mode>): Extend to *add.for.cc[z]n.<mode>.
	(*ashift.for.ccn.<mode>): New insn and peephole2 to make them.
	(*sub.for.cczn.<mode>, "*sub-extend<mode>.for.cczn.<mode>:
	New insns and peephole2s to make them.
	(*op8.for.cczn.<code>): New insn and peephole2 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: New test.
	* 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.
	* gcc.target/avr/pr115830-sub.c: New test.
	* gcc.target/avr/pr115830-sub-ext: New test.
diff mbox series

Patch

diff --git a/gcc/config/avr/avr-modes.def b/gcc/config/avr/avr-modes.def
index e0633d680d5..b756beae9a0 100644
--- a/gcc/config/avr/avr-modes.def
+++ b/gcc/config/avr/avr-modes.def
@@ -18,6 +18,12 @@ 
 
 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.
+   The Z flag indicates  whether the value is zero.  */
+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 7b666f17718..fb7e0dc6a15 100644
--- a/gcc/config/avr/avr-protos.h
+++ b/gcc/config/avr/avr-protos.h
@@ -55,7 +55,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 *avr_cond_branch (rtx_insn *, rtx *);
 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*);
@@ -63,6 +63,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);
@@ -154,6 +158,8 @@  extern rtx zero_reg_rtx;
 extern rtx all_regs_rtx[32];
 extern rtx rampz_rtx;
 extern rtx cc_reg_rtx;
+extern rtx ccn_reg_rtx;
+extern rtx cczn_reg_rtx;
 
 #endif /* RTX_CODE */
 
diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc
index 7229aac747b..0ec3e4103ec 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);
@@ -196,7 +196,11 @@  rtx zero_reg_rtx;
 
 /* Condition Code register RTX (reg:CC REG_CC) */
 extern GTY(()) rtx cc_reg_rtx;
+extern GTY(()) rtx ccn_reg_rtx;
+extern GTY(()) rtx cczn_reg_rtx;
 rtx cc_reg_rtx;
+rtx ccn_reg_rtx;
+rtx cczn_reg_rtx;
 
 /* RTXs for all general purpose registers as QImode */
 extern GTY(()) rtx all_regs_rtx[REG_32];
@@ -1185,6 +1189,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.  */
@@ -1209,7 +1225,9 @@  avr_init_expanders (void)
   tmp_reg_rtx  = all_regs_rtx[AVR_TMP_REGNO];
   zero_reg_rtx = all_regs_rtx[AVR_ZERO_REGNO];
 
-  cc_reg_rtx  = gen_rtx_REG (CCmode, REG_CC);
+  cc_reg_rtx = gen_rtx_REG (CCmode, REG_CC);
+  ccn_reg_rtx = gen_rtx_REG (CCNmode, REG_CC);
+  cczn_reg_rtx = gen_rtx_REG (CCZNmode, REG_CC);
 
   lpm_addr_reg_rtx = gen_rtx_REG (HImode, REG_Z);
 
@@ -3756,14 +3774,13 @@  ptrreg_to_str (int regno)
   return NULL;
 }
 
-/* Return the condition name as a string.
-   Used in conditional jump constructing  */
+
+/* Return the condition name as a string to be used in a BR** instruction.
+   Used in conditional jump constructing.  */
 
 static const char *
-cond_string (enum rtx_code code)
+avr_cond_string (rtx_code code, bool cc_overflow_unusable)
 {
-  bool cc_overflow_unusable = false;
-
   switch (code)
     {
     case NE:
@@ -4092,10 +4109,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 (avr_cond_string (GET_CODE (x), code == 'L'), file);
+  else if (code == 'k' || code == 'K')
+    fputs (avr_cond_string (reverse_condition (GET_CODE (x)), code == 'K'),
+	   file);
   else
     avr_print_operand_address (file, VOIDmode, x);
 }
@@ -4144,14 +4162,24 @@  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.  */
+   LEN in { 1, 2, 3 } is a number returned by `avr_jump_mode()'.
+   CCMODE is the mode of the comparison in { CCmode, CCNmode, CCZNmode }.  */
+
+/* Return the asm code for a conditional branch where XOP[0] is the
+   label and XOP[1] is a comparison operator of REG_CC against 0.
+*/
 
 const char *
-ret_cond_branch (rtx x, int len, int reverse)
+avr_cond_branch (rtx_insn *insn, rtx *xop)
 {
-  RTX_CODE cond = reverse ? reverse_condition (GET_CODE (x)) : GET_CODE (x);
-  bool cc_overflow_unusable = false;
+  machine_mode ccmode = GET_MODE (XEXP (xop[1], 0));
+  rtx_code cond = GET_CODE (xop[1]);
+  bool cc_overflow_unusable = ccmode != CCmode;
+  int len = avr_jump_mode (xop[0], insn);
+
+  if (ccmode == CCNmode)
+    // The N flag can only do < 0 and >= 0.
+    gcc_assert (cond == GE || cond == LT);
 
   switch (cond)
     {
@@ -4213,33 +4241,20 @@  ret_cond_branch (rtx x, int len, int reverse)
 	       "brsh .+4" CR_TAB
 	       "jmp %0"));
     default:
-      if (reverse)
-	{
-	  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)
 	{
-	  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 "";
@@ -8980,6 +8995,34 @@  lshrsi3_out (rtx_insn *insn, rtx operands[], int *len)
 }
 
 
+/* When INSN is a PARALLEL with two SETs, a SET of REG_CC and a SET of a
+   GPR, then return the second SET and set *CCMODE to the first SET's mode.
+   Otherwise, return single_set and set *CCMODE to VOIDmode.  */
+
+static rtx
+avr_cc_set (rtx_insn *insn, machine_mode *ccmode)
+{
+  // single_set() not only depends on the anatomy of an insn but also
+  // on REG_UNUSED notes, thus we have to analyze by hand so that the
+  // result only depends on the pattern.
+
+  rtx pat = PATTERN (insn);
+
+  if (GET_CODE (pat) == PARALLEL
+      && XVECLEN (pat, 0) == 2
+      && GET_CODE (XVECEXP (pat, 0, 0)) == SET
+      && GET_CODE (XVECEXP (pat, 0, 1)) == SET)
+    {
+      rtx ccset = XVECEXP (pat, 0, 0);
+      *ccmode = GET_MODE (SET_DEST (ccset));
+      return XVECEXP (pat, 0, 1);
+    }
+
+  *ccmode = VOIDmode;
+  return single_set (insn);
+}
+
+
 /* Output addition of registers YOP[0] and YOP[1]
 
       YOP[0] += extend (YOP[1])
@@ -8988,8 +9031,11 @@  lshrsi3_out (rtx_insn *insn, rtx operands[], int *len)
 
       YOP[0] -= extend (YOP[2])
 
-   where the integer modes satisfy  SI >= YOP[0].mode > YOP[1/2].mode >= QI,
-   and the extension may be sign- or zero-extend.  Returns "".
+   where the integer modes satisfy  SI >= YOP[0].mode >= YOP[1/2].mode >= QI,
+   and the extension may be sign-extend, zero-extend or reg (no extend).
+   INSN is either a single_set or a true parallel insn.  In the latter case,
+   INSN has two SETs: A SET of REG_CC and a SET like in the single_set case.
+   Returns "".
 
    If PLEN == NULL output the instructions.
    If PLEN != NULL set *PLEN to the length of the sequence in words.  */
@@ -8998,8 +9044,13 @@  const char *
 avr_out_plus_ext (rtx_insn *insn, rtx *yop, int *plen)
 {
   rtx regs[2];
+  machine_mode ccmode;
 
-  const rtx src = SET_SRC (single_set (insn));
+  /* Ouch! Whether or not an insn is a single_set does not only depend
+     on the anatomy of the pattern, but also on REG_UNUSED notes.
+     Hence we have to dig by hand... */
+
+  const rtx src = SET_SRC (avr_cc_set (insn, &ccmode));
   const RTX_CODE add = GET_CODE (src);
   gcc_assert (GET_CODE (src) == PLUS || GET_CODE (src) == MINUS);
 
@@ -9010,7 +9061,7 @@  avr_out_plus_ext (rtx_insn *insn, rtx *yop, int *plen)
   const RTX_CODE ext = GET_CODE (xext);
 
   gcc_assert (REG_P (xreg)
-	      && (ext == ZERO_EXTEND || ext == SIGN_EXTEND));
+	      && (ext == ZERO_EXTEND || ext == SIGN_EXTEND || ext == REG));
 
   const int n_bytes0 = GET_MODE_SIZE (GET_MODE (xop[0]));
   const int n_bytes1 = GET_MODE_SIZE (GET_MODE (xop[1]));
@@ -9030,7 +9081,9 @@  avr_out_plus_ext (rtx_insn *insn, rtx *yop, int *plen)
 
   if (ext == SIGN_EXTEND
       && (n_bytes0 > 1 + n_bytes1
-	  || reg_overlap_mentioned_p (msb1, xop[0])))
+	  || reg_overlap_mentioned_p (msb1, xop[0])
+	  // The insn also wants to set SREG.N and SREG.Z.
+	  || ccmode == CCZNmode))
     {
       // Sign-extending more than one byte: Set tmp_reg to 0 or -1
       // depending on $1.msb. Same for the pathological case where
@@ -9688,10 +9741,51 @@  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 HI, PSI or SI register, and XOP[1] is a register or 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);
+
+  // 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] };
+
+  if (REG_P (xop[1]))
+    {
+      avr_out_plus_1 (NULL_RTX, op, plen, PLUS, UNKNOWN, 0, false);
+    }
+  else
+    {
+      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,
-   then the addition can be performed without a clobber reg.  Return "".
+   result.  The mode is HI, PSI or SI.  XOP[2] might be a d-regs clobber
+   register.  If XOP[2] is SCRATCH, then the addition can be performed
+   without a clobber reg.  Return "".
 
    If PLEN == NULL, then output the instructions.
    If PLEN != NULL, then set *PLEN to the length of the sequence in words. */
@@ -9711,15 +9805,6 @@  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 (INTVAL (xval) == 1)
-	return avr_asm_len ("inc %0", xop, plen, 1);
-
-      if (INTVAL (xval) == -1)
-	return avr_asm_len ("dec %0", xop, plen, 1);
-    }
-
   if (n_bytes == 2
       && avr_adiw_reg_p (xreg)
       && IN_RANGE (INTVAL (xval), 1, 63))
@@ -9804,6 +9889,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 = std::abs (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]:
 
@@ -11308,6 +11523,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;
 
@@ -11496,7 +11712,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);
@@ -14110,7 +14326,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);
@@ -14125,7 +14341,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 fce5349bbe5..1fad2193c9a 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"))
 
@@ -261,6 +261,7 @@  (define_mode_iterator QISI  [QI HI PSI SI])
 (define_mode_iterator QIDI  [QI HI PSI SI DI])
 (define_mode_iterator QIPSI [QI HI PSI])
 (define_mode_iterator HISI  [HI PSI SI])
+(define_mode_iterator HI_SI [HI SI])
 
 ;; Ordered integral and fixed-point modes of specific sizes.
 (define_mode_iterator ALL1 [QI QQ UQQ])
@@ -277,6 +278,10 @@  (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_attr CCname [(CC "") (CCN "_N") (CCZN "_ZN")])
+
 ;; All supported move-modes
 (define_mode_iterator MOVMODE [QI QQ UQQ
                                HI HQ UHQ HA UHA
@@ -320,6 +325,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])
@@ -6885,6 +6893,469 @@  (define_insn_and_split "*cbranch<HISI:mode>.<code><QIPSI:mode>.0"
   })
 
 
+;; Try optimize decrement-and-branch.  When we have an addition followed
+;; 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.
+;; The comparisons are split2 from their cbranch insns and before
+;; peephole2 patterns like for swapped_tst and sbrx_branch have been applied.
+
+;; We do NOT use cmpelim / SELECT_CC_MODE because it has many shortcomings
+;; and is by no means equipollent to the removed cc0 framework -- at least
+;; with regard to the avr backend:  Whether or not the result of a comparison
+;; can be obtained as a byproduct of an operation might depend on the
+;; availability of a scratch register:  There are cases where we need a
+;; scratch register to optimize away a comparison, and where the operation
+;; without a comparison does not require a scratch.  With the peep2 approach
+;; below, we can get a scratch from the peep2 framework without increasing
+;; the register pressure, whereas cmpelim doesn't offer such a feature.
+;;    When no scratch is available, then we just don't perform the optimizaton,
+;; i.e. the comparison against 0 won't be optimized away, which is preferred
+;; over increasing the register pressure -- in many cases without reason --
+;; which might result in additional spills.
+;;    What we definitely do not want is to pop a scratch without need, and
+;; in some arithmetic insn we won't know whether it might also be considered
+;; for CCmode generation, at least not prior to register allocation:
+;; CCmode only comes into existence after register allocation.
+;;    cmpelim has more shortcomings, for example some comparisons may not
+;; be available, and it does not handle several of the forms supported below,
+;; just to mention two.  A solution for the former would be to return VOIDmode
+;; in SELECT_CC_MODE, but cmpelim doesn't handle that.  Anyway, it's pointless
+;; to speculate about how other shortcomings could be fixed when the scratch
+;; problem is unsoved in cmpelim.
+;;    Apart from that, compare-elim.cc lists some demands that are not
+;; compatible with this bachend.  For example, it assumes that when an insn
+;; can set the condition code, it is always of the form compare:CCM, i.e.
+;; all comparisons are supported.  This is not the case for AVR, see the
+;; peep2 conditions below.  There is no way (at least not a documented one)
+;; to express that in SELECT_CC_MODE.
+;;    Apart from that passes running before register allocation (and thus
+;; before split2) have #ifdef SELECT_CC_MODE, and nowhere there is an
+;; explanation on how to handle that.
+;;    Skipping cmpelim is accomplished by not defining TARGET_FLAGS_REGNUM.
+
+;; Note: reload1.cc::do_output_reload() does not support output reloads
+;; for JUMP_INSNs, hence letting combine doing decrement-and-branch might
+;; run into an ICE.  Doing reloads by hand is too painful, hence, stick with
+;; RTL peepholes for now.
+
+(define_expand "gen_add_for_<code>_<mode>"
+  [;; "*add.for.cczn.<mode>"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (plus:HISI (match_operand:HISI 0 "register_operand")
+                                            (match_operand:HISI 1 "const_int_operand"))
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (plus:HISI (match_dup 0)
+                              (match_dup 1)))
+              (clobber (match_operand:QI 3))])
+   ;; "branch_ZN"
+   (set (pc)
+        (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:HISI (match_operand:HISI 0 "register_operand")
+                                           (match_operand:HISI 1 "nonmemory_operand"))
+                                (const_int 0)))
+              (set (match_dup 0)
+                   (plus:HISI (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: Additions without a scratch register.
+(define_peephole2
+  [(parallel [(set (match_operand:HISI 0 "register_operand")
+                   (plus:HISI (match_dup 0)
+                              (match_operand:HISI 1 "nonmemory_operand")))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (match_operand:HISI 3 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))]
+  "// Multi-byte reg-reg additions only set the N flag.
+   (<CODE> == GE || <CODE> == LT || ! REG_P (operands[1]))
+   // Needs a const or a d-reg.
+   && (REG_P (operands[1]) || d_register_operand (operands[0], <MODE>mode))
+   && peep2_regno_dead_p (3, REG_CC)"
+  [(scratch)]
+  {
+    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
+                                         gen_rtx_SCRATCH (QImode)));
+    DONE;
+  })
+
+;; 2/3: Additions with a scratch register from the insn.
+(define_peephole2
+  [(parallel [(set (match_operand:HISI 0 "register_operand")
+                   (plus:HISI (match_dup 0)
+                              (match_operand:HISI 1 "nonmemory_operand")))
+              (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)
+                               (match_operand:HISI 4 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))]
+  "// Multi-byte reg-reg additions only set the N flag.
+   (<CODE> == GE || <CODE> == LT || ! REG_P (operands[1]))
+   && peep2_regno_dead_p (3, REG_CC)"
+  [(scratch)]
+  {
+    rtx scratch = operands[3];
+
+    // We need either a d-register or a scratch register
+    // when $1 is not a register.
+    if (! REG_P (operands[1])
+        && ! REG_P (scratch)
+        && ! d_register_operand (operands[0], <MODE>mode))
+      FAIL;
+
+    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
+                                         scratch));
+    DONE;
+  })
+
+;; 3/3: Additions with a scratch register from peephole2.
+(define_peephole2
+  [(match_scratch:QI 3 "d")
+   (parallel [(set (match_operand:HISI 0 "register_operand")
+                   (plus:HISI (match_dup 0)
+                              (match_operand:HISI 1 "const_int_operand")))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (match_operand:HISI 4 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (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]));
+    DONE;
+  })
+
+;; Result of the above three peepholes is an addition that also
+;; 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".
+
+;; "*add.for.cczn.hi"  "*add.for.cczn.psi"  "*add.for.cczn.si"
+(define_insn "*add.for.cczn.<mode>"
+  [(set (reg:CCZN REG_CC)
+        (compare:CCZN
+         (plus:HISI (match_operand:HISI 3 "register_operand"  "0 ,0")
+                    (match_operand:HISI 1 "const_int_operand" "n ,n"))
+         (const_int 0)))
+   (set (match_operand:HISI 0 "register_operand"             "=d ,r")
+        (plus:HISI (match_operand:HISI 4 "register_operand"   "0 ,0")
+                   (match_operand:HISI 5 "const_int_operand"  "1 ,1")))
+   (clobber (match_scratch:QI 2                              "=X ,&d"))]
+  "reload_completed"
+  {
+    return avr_out_plus_set_ZN (operands, nullptr);
+  }
+  [(set (attr "length")
+        (symbol_ref "<SIZE> * (1 + REG_P (operands[2]))"))
+   (set_attr "adjust_len" "add_set_ZN")])
+
+;; "*add.for.ccn.hi"  "*add.for.ccn.psi"  "*add.for.ccn.si"
+(define_insn "*add.for.ccn.<mode>"
+  [(set (reg:CCN REG_CC)
+        (compare:CCN
+         (plus:HISI (match_operand:HISI 3 "register_operand"  "0 ,0 ,0")
+                    (match_operand:HISI 1 "nonmemory_operand" "n ,n ,r"))
+         (const_int 0)))
+   (set (match_operand:HISI 0 "register_operand"             "=d ,r ,r")
+        (plus:HISI (match_operand:HISI 4 "register_operand"   "0 ,0 ,0")
+                   (match_operand:HISI 5 "nonmemory_operand"  "1 ,1 ,1")))
+   (clobber (match_scratch:QI 2                              "=X ,&d,X"))]
+  "reload_completed"
+  {
+    return avr_out_plus_set_N (operands, nullptr);
+  }
+  [(set (attr "length")
+        (symbol_ref "<SIZE> * (1 + REG_P (operands[2]))"))
+   (set_attr "adjust_len" "add_set_N")])
+
+
+;; 1/3: Subtractions with REG subtrahend set Z and N in a meaningful way.
+;; The QI and PSI cases are handled below because they don't have a scratch:QI.
+(define_peephole2
+  [(parallel [(set (match_operand:HI_SI 0 "register_operand")
+                   (minus:HI_SI (match_dup 0)
+                                (match_operand:HI_SI 1 "register_operand")))
+              (clobber (scratch:QI))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (match_operand:HI_SI 3 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (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)"
+  [;; "*sub.for.cczn.<mode>"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (minus:HI_SI (match_dup 0)
+                                              (match_dup 1))
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (minus:HI_SI (match_dup 0)
+                                (match_dup 1)))])
+   ;; "branch_ZN"
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CCZN REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_dup 2))
+                      (pc)))])
+
+;; 2/3: Subtractions with a PSImode REG: no scratch:QI.
+(define_peephole2
+  [(parallel [(set (match_operand:PSI 0 "register_operand")
+                   (minus:PSI (match_dup 0)
+                              (match_operand:PSI 1 "register_operand")))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (match_operand:PSI 3 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (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)"
+  [;; "*sub.for.cczn.psi"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (minus:PSI (match_dup 0)
+                                            (match_dup 1))
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (minus:PSI (match_dup 0)
+                              (match_dup 1)))])
+   ;; "branch_ZN"
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CCZN REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_dup 2))
+                      (pc)))])
+
+;; 3/3: Subtractions that extend the subtrahend.
+(define_peephole2
+  [(parallel [(set (match_operand:HISI 0 "register_operand")
+                   (minus:HISI (match_dup 0)
+                               (any_extend:HISI (match_operand:QIPSI 1 "register_operand"))))
+              (clobber (reg:CC REG_CC))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (match_operand:HISI 3 "const0_operand")))
+              (clobber (scratch:QI))])
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CC REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_operand 2))
+                      (pc)))]
+  "<HISI:SIZE> > <QIPSI:SIZE>
+   && peep2_regno_dead_p (3, REG_CC)"
+  [;; "*sub-extend<QIPSI:mode>.for.cczn.<HISI:mode>"
+   (parallel [(set (reg:CCZN REG_CC)
+                   (compare:CCZN (minus:HISI (match_dup 0)
+                                             (any_extend:HISI (match_dup 1)))
+                                 (const_int 0)))
+              (set (match_dup 0)
+                   (minus:HISI (match_dup 0)
+                               (any_extend:HISI (match_dup 1))))])
+   ;; "branch_ZN"
+   (set (pc)
+        (if_then_else (cmp_signed (reg:CCZN REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_dup 2))
+                      (pc)))])
+
+;; "*sub.for.cczn.hi"
+;; "*sub.for.cczn.psi"
+;; "*sub.for.cczn.si"
+(define_insn "*sub.for.cczn.<mode>"
+  [(set (reg:CCZN REG_CC)
+        (compare:CCZN (minus:HISI (match_operand:HISI 3 "register_operand" "1")
+                                  (match_operand:HISI 4 "register_operand" "2"))
+                      (const_int 0)))
+   (set (match_operand:HISI 0 "register_operand"             "=r")
+        (minus:HISI (match_operand:HISI 1 "register_operand"  "0")
+                    (match_operand:HISI 2 "register_operand"  "r")))]
+  "reload_completed"
+  {
+    return avr_out_plus_ext (insn, operands, nullptr);
+  }
+  [(set_attr "length" "<SIZE>")])
+
+
+(define_insn "*sub-extend<QIPSI:mode>.for.cczn.<HISI:mode>"
+  [(set (reg:CCZN REG_CC)
+        (compare:CCZN (minus:HISI (match_operand:HISI 3 "register_operand"     "0")
+                                  (any_extend:HISI
+                                   (match_operand:QIPSI 4 "register_operand"   "2")))
+                      (const_int 0)))
+   (set (match_operand:HISI 0 "register_operand"                              "=r")
+        (minus:HISI (match_operand:HISI 1 "register_operand"                   "0")
+                    (any_extend:HISI (match_operand:QIPSI 2 "register_operand" "r"))))]
+  "reload_completed
+   && <HISI:SIZE> > <QIPSI:SIZE>"
+  {
+    return avr_out_plus_ext (insn, operands, nullptr);
+  }
+  [(set (attr "length")
+        (symbol_ref "<HISI:SIZE> + 3 * (<CODE> == SIGN_EXTEND)"))])
+
+
+;; Operations other that PLUS can set the condition code in
+;; a meaningful way, too.
+
+;; 1/1 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))])
+   (parallel [(set (reg:CC REG_CC)
+                   (compare:CC (match_dup 0)
+                               (const_int 0)))
+              (clobber (scratch:QI))])
+   (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)))])
+
+(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"
+  {
+    output_asm_insn ("lsl %A0", operands);
+    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/1 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_dup 0)
+                    (match_operand:QI 4 "const0_operand")))
+   (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 (cmp_signed (reg:CCZN REG_CC)
+                                  (const_int 0))
+                      (label_ref (match_operand 3))
+                      (pc)))])
+
+;; Constraints and predicate for the insn below.  This is what op8_ZN_operator
+;; allows.  Constraints are written in such a way that all cases have two
+;; alternatives (shifts, XOR and MINUS have effectively just one alternative).
+;; Note again that due to nregs, match_dup's won't work.
+(define_code_attr c0_op8
+  [(xor "r,r") (minus "r,r") (ashift "r,r") (ashiftrt "r,r") (lshiftrt "r,r")
+   (and "d,r") (ior "d,r") (plus "d,r")])
+
+(define_code_attr c2_op8
+  [(xor "r,r") (minus "r,r") (and "n,r") (ior "n,r") (plus "n,r P N K Cm2")
+   (ashift "P K,C03") (ashiftrt "P K,C03") (lshiftrt "P K,C03")])
+
+(define_code_attr p2_op8
+  [(ashift "const_1_to_3")  (ashiftrt "const_1_to_3")  (lshiftrt "const_1_to_3")
+   (xor "register")  (minus "register")
+   (plus "nonmemory")  (and "nonmemory")  (ior "nonmemory")])
+
+;; Result of the peephole2 above:  An 8-bit operation that sets Z and N.
+;; The allowed operations are:  PLUS, MINUS, AND, IOR, XOR and SHIFTs
+;; with operands according to op8_ZN_operator.
+(define_insn "*op8.for.cczn.<code>"
+  [(set (reg:CCZN REG_CC)
+        (compare:CCZN (op8_ZN:QI (match_operand:QI 3 "register_operand" "0,0")
+                                 (match_operand:QI 4 "<p2_op8>_operand" "2,2"))
+                      (const_int 0)))
+   (set (match_operand:QI 0 "register_operand"           "=<c0_op8>")
+        (op8_ZN:QI (match_operand:QI 1 "register_operand" "0,0")
+                   (match_operand:QI 2 "<p2_op8>_operand" "<c2_op8>")))]
+  "reload_completed"
+  {
+    return avr_out_op8_set_ZN (<CODE>, operands, nullptr);
+  }
+  [(set (attr "length")
+        (symbol_ref "avr_len_op8_set_ZN (<CODE>, operands)"))])
+
+
 ;; Test a single bit in a QI/HI/SImode register.
 ;; Combine will create zero-extract patterns for single-bit tests.
 ;; Permit any mode in source pattern by using VOIDmode.
@@ -7046,32 +7517,25 @@  (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 avr_cond_branch (insn, operands);
   }
-  [(set_attr "type" "branch1")])
+  [(set (attr "type")
+        (if_then_else
+         (match_test "simple_comparison_operator (operands[1], VOIDmode)")
+         (const_string "branch")
+         (const_string "branch1")))])
 
 
 ;; **************************************************************************
@@ -9551,173 +10015,6 @@  (define_peephole2
               (clobber (reg:CC REG_CC))])])
 
 
-;; Try optimize decrement-and-branch.  When we have an addition followed
-;; 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")
-                        (HI "Yxx") (PSI "Yxx") (SI "Yxx")])
-
-;; FIXME: reload1.cc::do_output_reload() does not support output reloads
-;; for JUMP_INSNs, hence letting combine doing decrement-and-branch like
-;; the following might run into ICE.  Doing reloads by hand is too painful...
-;
-; (define_insn_and_split "*add.for.eqne.<mode>.cbranch"
-;   [(set (pc)
-;         (if_then_else (eqne (match_operand:QISI 1 "register_operand"  "0")
-;                             (match_operand:QISI 2 "const_int_operand" "n"))
-;                       (label_ref (match_operand 4))
-;                       (pc)))
-;    (set (match_operand:QISI 0 "register_operand" "=r")
-;         (plus:QISI (match_dup 1)
-;                    (match_operand:QISI 3 "const_int_operand" "n")))]
-;   ;; No clobber for now as combine might not have one handy.
-;   ;; We pop a scatch in split1.
-;   "!reload_completed
-;    && const0_rtx == simplify_binary_operation (PLUS, <MODE>mode,
-;                                                operands[2], operands[3])"
-;   { gcc_unreachable(); }
-;   "&& 1"
-;   [(parallel [(set (pc)
-;                    (if_then_else (eqne (match_dup 1)
-;                                        (match_dup 2))
-;                                  (label_ref (match_dup 4))
-;                                  (pc)))
-;               (set (match_dup 0)
-;                    (plus:QISI (match_dup 1)
-;                               (match_dup 3)))
-;               (clobber (scratch:QI))])])
-;
-;; ...Hence, stick with RTL peepholes for now.  Unfortunately, there is no
-;; canonical form, and if reload shuffles registers around, we might miss
-;; opportunities to match a decrement-and-branch.
-;; 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)))
-              (set (match_dup 0)
-                   (plus:QISI (match_dup 0)
-                              (match_dup 1)))
-              (clobber (match_operand:QI 3))])
-   ; "branch"
-   (set (pc)
-        (if_then_else (eqne (reg:CC REG_CC)
-                            (const_int 0))
-                      (label_ref (match_dup 2))
-                      (pc)))])
-
-
-;; 1/3: A version without clobber: d-reg or 8-bit adds +/-1.
-(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)))
-   (set (pc)
-        (if_then_else (eqne (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)))"
-  [(scratch)]
-  {
-    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
-          gen_rtx_SCRATCH (QImode)));
-    DONE;
-  })
-
-;; 2/3: A version with clobber from the insn.
-(define_peephole2
-  [(parallel [(set (match_operand:QISI 0 "register_operand")
-                   (plus:QISI (match_dup 0)
-                              (match_operand:QISI 1 "const_int_operand")))
-              (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)))
-              (clobber (match_operand:QI 4 "scratch_or_d_register_operand"))])
-   (set (pc)
-        (if_then_else (eqne (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];
-
-    // 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)))
-      {
-        FAIL;
-      }
-    emit (gen_gen_add_for_<code>_<mode> (operands[0], operands[1], operands[2],
-          scratch));
-    DONE;
-  })
-
-;; 3/3 A version with a clobber from peephole2.
-(define_peephole2
-  [(match_scratch:QI 3 "d")
-   (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)))
-   (set (pc)
-        (if_then_else (eqne (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]));
-    DONE;
-  })
-
-;; Result of the above three peepholes is an addition that also
-;; performs an EQ or NE 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"))
-         (const_int 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"))]
-  "reload_completed"
-  {
-    return avr_out_plus_set_ZN (operands, nullptr);
-  }
-  [(set_attr "adjust_len" "add_set_ZN")])
-
-
 ;; Swapping both comparison and branch condition.  This can turn difficult
 ;; branches to easy ones.  And in some cases, a comparison against one can
 ;; be turned into a comparison against zero.
diff --git a/gcc/config/avr/predicates.md b/gcc/config/avr/predicates.md
index 5b49481ff0f..b21acbe323f 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")
@@ -242,10 +253,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..c1891038cf3
--- /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__((noipa))
+
+#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..70f961347c0
--- /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__((noipa))
+
+#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-add.c b/gcc/testsuite/gcc.target/avr/pr115830-add.c
new file mode 100644
index 00000000000..99ac89cd0a6
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-add.c
@@ -0,0 +1,76 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -Os } } */
+
+typedef __UINT8_TYPE__ u8;
+typedef __INT8_TYPE__  i8;
+typedef __UINT16_TYPE__ u16;
+typedef __INT16_TYPE__  i16;
+typedef __uint24 u24;
+typedef __int24  i24;
+typedef __UINT32_TYPE__ u32;
+typedef __INT32_TYPE__  i32;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noipa))
+
+u8 volatile v;
+u8 v1;
+
+#define MK_FUN(TYP, OP, TST)		\
+NI TYP add1_##TYP##_##OP (TYP a, TYP b)	\
+{					\
+  a += b;				\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI TYP add2_##TYP##_##OP (TYP a, TYP b)	\
+{					\
+  a += b;				\
+  __asm ("" : "+r" (a));		\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI void test_##TYP##_##OP (TYP a, TYP b)\
+{					\
+  v = 1;				\
+  TYP c1 = add1_##TYP##_##OP (a, b);	\
+  v1 = v;				\
+  v = 1;				\
+  TYP c2 = add2_##TYP##_##OP (a, b);	\
+  if (c1 != c2)				\
+    __builtin_abort();			\
+  if (v1 != v)				\
+    __builtin_abort();			\
+}
+
+MK_FUN (i16, ge, >= 0)
+MK_FUN (i24, ge, >= 0)
+MK_FUN (i32, ge, >= 0)
+
+MK_FUN (i16, lt, < 0)
+MK_FUN (i24, lt, < 0)
+MK_FUN (i32, lt, < 0)
+
+
+int main (void)
+{
+  for (i8 a = -5; a <= 5; ++a)
+    {
+      for (i8 b = -5; b <= 5; ++b)
+	{
+	  test_i16_ge (a, b);
+	  test_i24_ge (a, b);
+	  test_i32_ge (a, b);
+
+	  test_i16_lt (a, b);
+	  test_i24_lt (a, b);
+	  test_i32_lt (a, b);
+	}
+    }
+
+  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..7c2c189da27
--- /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__((noipa))
+
+#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..4680c5c3996
--- /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__((noipa))
+
+#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..523dd9e84d9
--- /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__((noipa))
+
+#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..99934cc0dc1
--- /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__((noipa))
+
+#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..0a8b592a906
--- /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__((noipa))
+
+#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..9a4709b2ea8
--- /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__((noipa))
+
+#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;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-sub-ext.c b/gcc/testsuite/gcc.target/avr/pr115830-sub-ext.c
new file mode 100644
index 00000000000..3fac6ddd0df
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-sub-ext.c
@@ -0,0 +1,100 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -Os } } */
+
+typedef __UINT8_TYPE__ u8;
+typedef __INT8_TYPE__  i8;
+typedef __UINT16_TYPE__ u16;
+typedef __INT16_TYPE__  i16;
+typedef __uint24 u24;
+typedef __int24  i24;
+typedef __UINT32_TYPE__ u32;
+typedef __INT32_TYPE__  i32;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noipa))
+
+u8 volatile v;
+u8 v1;
+
+#define MK_FUN(A, B, OP, TST)		\
+NI A sub1_##A##_##B##_##OP (A a, B b)	\
+{					\
+  a -= b;				\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI A sub2_##A##_##B##_##OP (A a, B b)	\
+{					\
+  a -= b;				\
+  __asm ("" : "+r" (a));		\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI void test_##A##_##B##_##OP (A a, B b)\
+{					\
+  v = 1;				\
+  A c1 = sub1_##A##_##B##_##OP (a, b);	\
+  v1 = v;				\
+  v = 1;				\
+  A c2 = sub2_##A##_##B##_##OP (a, b);	\
+  if (c1 != c2)				\
+    __builtin_abort();			\
+  if (v1 != v)				\
+    __builtin_abort();			\
+}
+
+MK_FUN (u16, u8, ne, != 0)
+MK_FUN (u24, u8, eq, == 0)
+MK_FUN (u32, u16, ne, != 0)
+
+MK_FUN (i16, i8, ge, >= 0)
+MK_FUN (i24, i8, ge, >= 0)
+MK_FUN (i32, i16, ge, >= 0)
+
+MK_FUN (i16, i8, lt, < 0)
+MK_FUN (i24, i8, lt, < 0)
+MK_FUN (i32, i16, lt, < 0)
+
+MK_FUN (i16, i8, le, <= 0)
+MK_FUN (i24, i8, le, <= 0)
+MK_FUN (i32, i16, le, <= 0)
+
+MK_FUN (i16, i8, gt, > 0)
+MK_FUN (i24, i8, gt, > 0)
+MK_FUN (i32, i16, gt, > 0)
+
+
+int main (void)
+{
+  for (i8 a = -5; a <= 5; ++a)
+    {
+      for (i8 b = -5; b <= 5; ++b)
+	{
+	  test_u16_u8_ne (a, b);
+	  test_u24_u8_eq (a, b);
+	  test_u32_u16_ne (a, b);
+
+	  test_i16_i8_ge (a, b);
+	  test_i24_i8_ge (a, b);
+	  test_i32_i16_ge (a, b);
+
+	  test_i16_i8_lt (a, b);
+	  test_i24_i8_lt (a, b);
+	  test_i32_i16_lt (a, b);
+
+	  test_i16_i8_gt (a, b);
+	  test_i24_i8_gt (a, b);
+	  test_i32_i16_gt (a, b);
+
+	  test_i16_i8_le (a, b);
+	  test_i24_i8_le (a, b);
+	  test_i32_i16_le (a, b);
+	}
+    }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/pr115830-sub.c b/gcc/testsuite/gcc.target/avr/pr115830-sub.c
new file mode 100644
index 00000000000..ef24e74752d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/pr115830-sub.c
@@ -0,0 +1,100 @@ 
+/* { dg-do run } */
+/* { dg-additional-options { -std=c99 -Os } } */
+
+typedef __UINT8_TYPE__ u8;
+typedef __INT8_TYPE__  i8;
+typedef __UINT16_TYPE__ u16;
+typedef __INT16_TYPE__  i16;
+typedef __uint24 u24;
+typedef __int24  i24;
+typedef __UINT32_TYPE__ u32;
+typedef __INT32_TYPE__  i32;
+
+#define AI static inline __attribute__((always_inline))
+#define NI __attribute__((noipa))
+
+u8 volatile v;
+u8 v1;
+
+#define MK_FUN(TYP, OP, TST)		\
+NI TYP sub1_##TYP##_##OP (TYP a, TYP b)	\
+{					\
+  a -= b;				\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI TYP sub2_##TYP##_##OP (TYP a, TYP b)	\
+{					\
+  a -= b;				\
+  __asm ("" : "+r" (a));		\
+  if (a TST)				\
+    v = 0;				\
+  return a;				\
+}					\
+					\
+NI void test_##TYP##_##OP (TYP a, TYP b)\
+{					\
+  v = 1;				\
+  TYP c1 = sub1_##TYP##_##OP (a, b);	\
+  v1 = v;				\
+  v = 1;				\
+  TYP c2 = sub2_##TYP##_##OP (a, b);	\
+  if (c1 != c2)				\
+    __builtin_abort();			\
+  if (v1 != v)				\
+    __builtin_abort();			\
+}
+
+MK_FUN (i8, ge, >= 0)
+MK_FUN (i16, ge, >= 0)
+MK_FUN (i24, ge, >= 0)
+MK_FUN (i32, ge, >= 0)
+
+MK_FUN (i8, lt, < 0)
+MK_FUN (i16, lt, < 0)
+MK_FUN (i24, lt, < 0)
+MK_FUN (i32, lt, < 0)
+
+MK_FUN (i8, le, <= 0)
+MK_FUN (i16, le, <= 0)
+MK_FUN (i24, le, <= 0)
+MK_FUN (i32, le, <= 0)
+
+MK_FUN (i8, gt, > 0)
+MK_FUN (i16, gt, > 0)
+MK_FUN (i24, gt, > 0)
+MK_FUN (i32, gt, > 0)
+
+
+int main (void)
+{
+  for (i8 a = -5; a <= 5; ++a)
+    {
+      for (i8 b = -5; b <= 5; ++b)
+	{
+	  test_i8_ge (a, b);
+	  test_i16_ge (a, b);
+	  test_i24_ge (a, b);
+	  test_i32_ge (a, b);
+
+	  test_i8_lt (a, b);
+	  test_i16_lt (a, b);
+	  test_i24_lt (a, b);
+	  test_i32_lt (a, b);
+
+	  test_i8_gt (a, b);
+	  test_i16_gt (a, b);
+	  test_i24_gt (a, b);
+	  test_i32_gt (a, b);
+
+	  test_i8_le (a, b);
+	  test_i16_le (a, b);
+	  test_i24_le (a, b);
+	  test_i32_le (a, b);
+	}
+    }
+
+  return 0;
+}