]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
AVR: Outsource code for avr-specific passes to new avr-passes.cc.
authorGeorg-Johann Lay <avr@gjlay.de>
Wed, 28 Aug 2024 09:18:45 +0000 (11:18 +0200)
committerGeorg-Johann Lay <avr@gjlay.de>
Thu, 29 Aug 2024 10:46:42 +0000 (12:46 +0200)
gcc/
* config.gcc (extra_objs) [target=avr]: Add avr-passes.o.
* config/avr/t-avr (avr-passes.o): New rule to make it.
* config/avr/avr.cc (#define INCLUDE_VECTOR): Remove.
(cfganal.h, cfgrtl.h, context.h, tree-pass.h, print-rtl.h): Don't
include them.
(avr_strict_signed_p, avr_strict_unsigned_p, avr_2comparisons_rhs)
(make_avr_pass_recompute_notes, make_avr_pass_casesi)
(make_avr_pass_ifelse, make_avr_pass_pre_proep, avr_split_tiny_move)
(emit_move_ccc, emit_move_ccc_after, reg_seen_between_p)
(avr_maybe_adjust_cfa, avr_redundant_compare_regs)
(avr_parallel_insn_from_insns, avr_is_casesi_sequence)
(avr_optimize_casesi, avr_redundant_compare, make_avr_pass_fuse_add)
(avr_optimize_2ifelse, avr_rest_of_handle_ifelse)
(avr_casei_sequence_check_operands)
Move functions...
(avr_pass_data_fuse_add, avr_pass_data_ifelse)
(avr_pass_data_casesi, avr_pass_data_recompute_notes)
(avr_pass_data_pre_proep): Move objects...
(avr_pass_fuse_add, avr_pass_pre_proep, avr_pass_recompute_notes)
(avr_pass_ifelse, avr_pass_casesi, AVR_LdSt_Props): Move classes...
* config/avr/avr-passes.cc: ... to this new C++ module.
(struct Ranges): Move to...
* config/avr/ranges.h: ...this new file.
* config/avr/avr-protos.h: Adjust comments.

gcc/config.gcc
gcc/config/avr/avr-passes.cc [new file with mode: 0644]
gcc/config/avr/avr-protos.h
gcc/config/avr/avr.cc
gcc/config/avr/ranges.h [new file with mode: 0644]
gcc/config/avr/t-avr

index e887c9c743211623e9f19cc65555f0b8e431678c..08291f4b6e07363eb6b1987c55fede9569ab69eb 100644 (file)
@@ -1662,7 +1662,7 @@ avr-*-*)
        tmake_file="${tmake_file} avr/t-avr avr/t-multilib"
        use_gcc_stdint=wrap
        extra_gcc_objs="driver-avr.o avr-devices.o"
-       extra_objs="avr-devices.o avr-log.o"
+       extra_objs="avr-devices.o avr-log.o avr-passes.o"
        ;;
 bfin*-elf*)
        tm_file="${tm_file} elfos.h newlib-stdint.h bfin/elf.h"
diff --git a/gcc/config/avr/avr-passes.cc b/gcc/config/avr/avr-passes.cc
new file mode 100644 (file)
index 0000000..8b018ff
--- /dev/null
@@ -0,0 +1,1931 @@
+/* Functions and methods for avr specific passes registered in avr-passes.def.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GCC; see the file COPYING3.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+#define IN_TARGET_CODE 1
+
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "intl.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "target.h"
+#include "rtl.h"
+#include "tree.h"
+#include "cfghooks.h"
+#include "cfganal.h"
+#include "df.h"
+#include "memmodel.h"
+#include "tm_p.h"
+#include "optabs.h"
+#include "regs.h"
+#include "emit-rtl.h"
+#include "recog.h"
+#include "explow.h"
+#include "cfgrtl.h"
+#include "context.h"
+#include "tree-pass.h"
+
+namespace
+{
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Try to replace 2 cbranch insns with 1 comparison and 2 branches.
+
+static const pass_data avr_pass_data_ifelse =
+{
+  RTL_PASS,      // type
+  "",            // name (will be patched)
+  OPTGROUP_NONE, // optinfo_flags
+  TV_DF_SCAN,    // tv_id
+  0,             // properties_required
+  0,             // properties_provided
+  0,             // properties_destroyed
+  0,             // todo_flags_start
+  TODO_df_finish | TODO_df_verify // todo_flags_finish
+};
+
+class avr_pass_ifelse : public rtl_opt_pass
+{
+public:
+  avr_pass_ifelse (gcc::context *ctxt, const char *name)
+    : rtl_opt_pass (avr_pass_data_ifelse, ctxt)
+  {
+    this->name = name;
+  }
+
+  bool gate (function *) final override
+  {
+    return optimize > 0;
+  }
+
+  unsigned int execute (function *func) final override;
+}; // avr_pass_ifelse
+
+
+/* Return TRUE iff comparison code CODE is explicitly signed.  */
+
+static bool
+avr_strict_signed_p (rtx_code code)
+{
+  return code == GT || code == GE || code == LT || code == LE;
+}
+
+
+/* Return TRUE iff comparison code CODE is explicitly unsigned.  */
+
+static bool
+avr_strict_unsigned_p (rtx_code code)
+{
+  return code == GTU || code == GEU || code == LTU || code == LEU;
+}
+
+#include "config/avr/ranges.h"
+
+/* Suppose the inputs represent a code like
+
+      if (x <CMP1> XVAL1)  goto way1;
+      if (x <CMP2> XVAL2)  goto way2;
+      way3:;
+
+   with two integer mode comparisons where XVAL1 and XVAL2 are CONST_INT.
+   When this can be rewritten in the form
+
+      if (x <cond1> xval)  goto way1;
+      if (x <cond2> xval)  goto way2;
+      way3:;
+
+  then set CMP1 = cond1, CMP2 = cond2, and return xval.  Else return NULL_RTX.
+  When SWAPT is returned true, then way1 and way2 must be swapped.
+  When the incomping SWAPT is false, the outgoing one will be false, too.  */
+
+static rtx
+avr_2comparisons_rhs (rtx_code &cmp1, rtx xval1,
+                     rtx_code &cmp2, rtx xval2,
+                     machine_mode mode, bool &swapt)
+{
+  const bool may_swapt = swapt;
+  swapt = false;
+
+  //////////////////////////////////////////////////////////////////
+  // Step 0: Decide about signedness, map xval1/2 to the range
+  //         of [un]signed machine mode.
+
+  const bool signed1_p = avr_strict_signed_p (cmp1);
+  const bool signed2_p = avr_strict_signed_p (cmp2);
+  const bool unsigned1_p = avr_strict_unsigned_p (cmp1);
+  const bool unsigned2_p = avr_strict_unsigned_p (cmp2);
+  const bool signed_p = signed1_p || signed2_p;
+  bool unsigned_p = unsigned1_p || unsigned2_p;
+
+  using T = Ranges::scalar_type;
+  T val1 = INTVAL (xval1);
+  T val2 = INTVAL (xval2);
+
+  if (signed_p + unsigned_p > 1)
+    {
+      // Don't go down that rabbit hole.  When the RHSs are the
+      // same, we can still save one comparison.
+      return val1 == val2 ? xval1 : NULL_RTX;
+    }
+
+  // Decide about signedness.  When no explicit signedness is present,
+  // then cases that are close to the unsigned boundary like  EQ 0, EQ 1
+  // can also be optimized.
+  if (unsigned_p
+      || (! signed_p && IN_RANGE (val1, -2, 2)))
+    {
+      unsigned_p = true;
+      val1 = UINTVAL (xval1) & GET_MODE_MASK (mode);
+      val2 = UINTVAL (xval2) & GET_MODE_MASK (mode);
+    }
+
+  // No way we can decompose the domain in a usable manner when the
+  // RHSes are too far apart.
+  if (! IN_RANGE (val1 - val2, -2, 2))
+    return NULL_RTX;
+
+  //////////////////////////////////////////////////////////////////
+  // Step 1: Represent the input conditions as truth Ranges.  This
+  //         establishes a decomposition / coloring of the domain.
+
+  Ranges dom = Ranges::NBitsRanges (GET_MODE_BITSIZE (mode), unsigned_p,
+                                   Ranges::ALL);
+  Ranges r[4] = { dom, dom.truth (cmp1, val1), dom.truth (cmp2, val2), dom };
+
+  // r[1] shadows r[2] shadows r[3].  r[0] is just for nice indices.
+  r[3].minus (r[2]);
+  r[3].minus (r[1]);
+  r[2].minus (r[1]);
+
+  //////////////////////////////////////////////////////////////////
+  // Step 2: Filter for cases where the domain decomposes into three
+  //         intervals:  One to the left, one to the right, and one
+  //         in the middle where the latter holds exactly one value.
+
+  for (int i = 1; i <= 3; ++i)
+    {
+      // Keep track of which Ranges is which.
+      r[i].tag = i;
+
+      gcc_assert (r[i].check ());
+
+      // Filter for proper intervals.  Also return for the empty set,
+      // since cases where [m_min, m_max] decomposes into two intervals
+      // or less have been sorted out by the generic optimizers already,
+      // and hence should not be seen here.  And more than two intervals
+      // at a time cannot be optimized of course.
+      if (r[i].size () != 1)
+       return NULL_RTX;
+    }
+
+  // Bubble-sort the three intervals such that:
+  // [1] is the left interval, i.e. the one taken by LT[U].
+  // [2] is the middle interval, i.e. the one taken by EQ.
+  // [3] is the right interval, i.e. the one taken by GT[U].
+  Ranges::sort2 (r[1], r[3]);
+  Ranges::sort2 (r[2], r[3]);
+  Ranges::sort2 (r[1], r[2]);
+
+  if (dump_file)
+    fprintf (dump_file,
+            ";; Decomposed: .%d=[%ld, %ld] .%d=[%ld, %ld] .%d=[%ld, %ld]\n",
+            r[1].tag, (long) r[1].ranges[0].lo, (long) r[1].ranges[0].hi,
+            r[2].tag, (long) r[2].ranges[0].lo, (long) r[2].ranges[0].hi,
+            r[3].tag, (long) r[3].ranges[0].lo, (long) r[3].ranges[0].hi);
+
+  // EQ / NE can handle only one value.
+  if (r[2].cardinality (0) != 1)
+    return NULL_RTX;
+
+  // Success! This is the sought for xval.
+  const T val = r[2].ranges[0].lo;
+
+  //////////////////////////////////////////////////////////////////
+  // Step 3: Work out which label gets which condition, trying to
+  //         avoid the expensive codes GT[U] and LE[U] if possible.
+  //         Avoiding expensive codes is always possible when labels
+  //         way1 and way2 may be swapped.
+
+  // The xx1 ways have an expensive GT for cmp1 which can be avoided
+  // by swapping way1 with way2.
+  swapt = may_swapt && r[3].tag == 1;
+  if (swapt)
+    std::swap (r[3], r[2].tag == 2 ? r[2] : r[1]);
+
+  // 6 = 3! ways to assign LT, EQ, GT to the three labels.
+  const int way = 100 * r[1].tag + 10 * r[2].tag + r[3].tag;
+
+  if (dump_file)
+    fprintf (dump_file, ";; Success: unsigned=%d, swapt=%d, way=%d, rhs=%ld\n",
+            unsigned_p, swapt, way, (long) val);
+
+#define WAY(w, c1, c2)                                 \
+  case w:                                              \
+    cmp1 = unsigned_p ? unsigned_condition (c1) : c1;  \
+    cmp2 = unsigned_p ? unsigned_condition (c2) : c2;  \
+    break;
+
+  switch (way)
+    {
+    default:
+      gcc_unreachable();
+
+      // cmp1 gets the LT, avoid difficult branches for cmp2.
+      WAY (123, LT, EQ);
+      WAY (132, LT, NE);
+
+      // cmp1 gets the EQ, avoid difficult branches for cmp2.
+      WAY (213, EQ, LT);
+      WAY (312, EQ, GE);
+
+      // cmp1 gets the difficult GT, unavoidable as we may not swap way1/2.
+      WAY (231, GT, NE);
+      WAY (321, GT, EQ);
+    }
+
+#undef WAY
+
+  return gen_int_mode (val, mode);
+}
+
+
+/* A helper for the next method.  Suppose we have two conditional branches
+   with REG and CONST_INT operands
+
+      if (reg <cond1> xval1) goto label1;
+      if (reg <cond2> xval2) goto label2;
+
+   If the second comparison is redundant and there are codes <cmp1>
+   and <cmp2> such that the sequence can be performed as
+
+      REG_CC = compare (reg, xval);
+      if (REG_CC <cmp1> 0) goto label1;
+      if (REG_CC <cmp2> 0) goto label2;
+
+   then set COND1 to cmp1, COND2 to cmp2, SWAPT to true when the branch
+   targets have to be swapped, and return XVAL.  Otherwise, return NULL_RTX.
+   This function may clobber COND1 and COND2 even when it returns NULL_RTX.
+
+   REVERSE_COND1 can be set to reverse condition COND1.  This is useful
+   when the second comparison does not follow the first one, but is
+   located after label1 like in:
+
+      if (reg <cond1> xval1) goto label1;
+      ...
+      label1:
+      if (reg <cond2> xval2) goto label2;
+
+   In such a case we cannot swap the labels, and we may end up with a
+   difficult branch -- though one comparison can still be optimized out.
+   Getting rid of such difficult branches would require to reorder blocks. */
+
+static rtx
+avr_redundant_compare (rtx xreg1, rtx_code &cond1, rtx xval1,
+                      rtx xreg2, rtx_code &cond2, rtx xval2,
+                      bool reverse_cond1, bool &swapt)
+{
+  // Make sure we have two REG <cond> CONST_INT comparisons with the same reg.
+  if (! rtx_equal_p (xreg1, xreg2)
+      || ! CONST_INT_P (xval1)
+      || ! CONST_INT_P (xval2))
+    return NULL_RTX;
+
+  if (reverse_cond1)
+    cond1 = reverse_condition (cond1);
+
+  // Allow swapping label1 <-> label2 only when ! reverse_cond1.
+  swapt = ! reverse_cond1;
+  rtx_code c1 = cond1;
+  rtx_code c2 = cond2;
+  rtx xval = avr_2comparisons_rhs (c1, xval1,
+                                  c2, xval2, GET_MODE (xreg1), swapt);
+  if (! xval)
+    return NULL_RTX;
+
+  if (dump_file)
+    {
+      rtx_code a1 = reverse_cond1 ? reverse_condition (cond1) : cond1;
+      rtx_code b1 = reverse_cond1 ? reverse_condition (c1) : c1;
+      const char *s_rev1 = reverse_cond1 ? " reverse_cond1" : "";
+      avr_dump (";; cond1: %C %r%s\n", a1, xval1, s_rev1);
+      avr_dump (";; cond2: %C %r\n", cond2, xval2);
+      avr_dump (";; => %C %d\n", b1, (int) INTVAL (xval));
+      avr_dump (";; => %C %d\n", c2, (int) INTVAL (xval));
+    }
+
+  cond1 = c1;
+  cond2 = c2;
+
+  return xval;
+}
+
+
+/* Similar to the function above, but assume that
+
+      if (xreg1 <cond1> xval1) goto label1;
+      if (xreg2 <cond2> xval2) goto label2;
+
+   are two subsequent REG-REG comparisons.  When this can be represented as
+
+      REG_CC = compare (reg, xval);
+      if (REG_CC <cmp1> 0) goto label1;
+      if (REG_CC <cmp2> 0) goto label2;
+
+   then set XREG1 to reg, COND1 and COND2 accordingly, and return xval.
+   Otherwise, return NULL_RTX.  This optmization can be performed
+   when { xreg1, xval1 } and { xreg2, xval2 } are equal as sets.
+   It can be done in such a way that no difficult branches occur.  */
+
+static rtx
+avr_redundant_compare_regs (rtx &xreg1, rtx_code &cond1, rtx &xval1,
+                           rtx &xreg2, rtx_code &cond2, rtx &xval2,
+                           bool reverse_cond1)
+{
+  bool swapped;
+
+  if (! REG_P (xval1))
+    return NULL_RTX;
+  else if (rtx_equal_p (xreg1, xreg2)
+          && rtx_equal_p (xval1, xval2))
+    swapped = false;
+  else if (rtx_equal_p (xreg1, xval2)
+          && rtx_equal_p (xreg2, xval1))
+    swapped = true;
+  else
+    return NULL_RTX;
+
+  // Found a redundant REG-REG comparison.  Assume that the incoming
+  // representation has been canonicalized by CANONICALIZE_COMPARISON.
+  // We can always represent this using only one comparison and in such
+  // a way that no difficult branches are required.
+
+  if (dump_file)
+    {
+      const char *s_rev1 = reverse_cond1 ? " reverse_cond1" : "";
+      avr_dump (";; %r %C %r%s\n", xreg1, cond1, xval1, s_rev1);
+      avr_dump (";; %r %C %r\n", xreg2, cond2, xval2);
+    }
+
+  if (reverse_cond1)
+    cond1 = reverse_condition (cond1);
+
+  if (swapped)
+    {
+      if (cond1 == EQ || cond1 == NE)
+       {
+         avr_dump (";; case #21\n");
+         std::swap (xreg1, xval1);
+       }
+      else
+       {
+         std::swap (xreg2, xval2);
+         cond2 = swap_condition (cond2);
+
+         // The swap may have introduced a difficult comparison.
+         // In order to get of it, only a few cases need extra care.
+         if ((cond1 == LT && cond2 == GT)
+             || (cond1 == LTU && cond2 == GTU))
+           {
+             avr_dump (";; case #22\n");
+             cond2 = NE;
+           }
+         else
+           avr_dump (";; case #23\n");
+       }
+    }
+  else
+    avr_dump (";; case #20\n");
+
+  return xval1;
+}
+
+
+/* INSN1 and INSN2 are two cbranch insns for the same integer mode.
+   When FOLLOW_LABEL1 is false, then INSN2 is located in the fallthrough
+   path of INSN1.  When FOLLOW_LABEL1 is true, then INSN2 is located at
+   the true edge of INSN1, INSN2 is preceded by a barrier, and no other
+   edge leads to the basic block of INSN2.
+
+   Try to replace INSN1 and INSN2 by a compare insn and two branch insns.
+   When such a replacement has been performed, then return the insn where the
+   caller should continue scanning the insn stream.  Else, return nullptr.  */
+
+static rtx_insn *
+avr_optimize_2ifelse (rtx_jump_insn *insn1,
+                     rtx_jump_insn *insn2, bool follow_label1)
+{
+  avr_dump (";; Investigating jump_insn %d and jump_insn %d.\n",
+           INSN_UID (insn1), INSN_UID (insn2));
+
+  // Extract the operands of the insns:
+  // $0 = comparison operator ($1, $2)
+  // $1 = reg
+  // $2 = reg or const_int
+  // $3 = code_label
+  // $4 = optional SCRATCH for HI, PSI, SI cases.
+
+  const auto &op = recog_data.operand;
+
+  extract_insn (insn1);
+  rtx xop1[5] = { op[0], op[1], op[2], op[3], op[4] };
+  int n_operands = recog_data.n_operands;
+
+  extract_insn (insn2);
+  rtx xop2[5] = { op[0], op[1], op[2], op[3], op[4] };
+
+  rtx_code code1 = GET_CODE (xop1[0]);
+  rtx_code code2 = GET_CODE (xop2[0]);
+  bool swap_targets = false;
+
+  // Search redundant REG-REG comparison.
+  rtx xval = avr_redundant_compare_regs (xop1[1], code1, xop1[2],
+                                        xop2[1], code2, xop2[2],
+                                        follow_label1);
+
+  // Search redundant REG-CONST_INT comparison.
+  if (! xval)
+    xval = avr_redundant_compare (xop1[1], code1, xop1[2],
+                                 xop2[1], code2, xop2[2],
+                                 follow_label1, swap_targets);
+  if (! xval)
+    {
+      avr_dump (";; Nothing found for jump_insn %d and jump_insn %d.\n",
+               INSN_UID (insn1), INSN_UID (insn2));
+      return nullptr;
+    }
+
+  if (follow_label1)
+    code1 = reverse_condition (code1);
+
+  //////////////////////////////////////////////////////
+  // Found a replacement.
+
+  if (dump_file)
+    {
+      avr_dump (";; => %C %r\n", code1, xval);
+      avr_dump (";; => %C %r\n", code2, xval);
+
+      fprintf (dump_file, "\n;; Found chain of jump_insn %d and"
+              " jump_insn %d, follow_label1=%d:\n",
+              INSN_UID (insn1), INSN_UID (insn2), follow_label1);
+      print_rtl_single (dump_file, PATTERN (insn1));
+      print_rtl_single (dump_file, PATTERN (insn2));
+    }
+
+  rtx_insn *next_insn
+    = next_nonnote_nondebug_insn (follow_label1 ? insn1 : insn2);
+
+  // Pop the new branch conditions and the new comparison.
+  // Prematurely split into compare + branch so that we can drop
+  // the 2nd comparison.  The following pass, split2, splits all
+  // insns for REG_CC, and it should still work as usual even when
+  // there are already some REG_CC insns around.
+
+  rtx xcond1 = gen_rtx_fmt_ee (code1, VOIDmode, cc_reg_rtx, const0_rtx);
+  rtx xcond2 = gen_rtx_fmt_ee (code2, VOIDmode, cc_reg_rtx, const0_rtx);
+  rtx xpat1 = gen_branch (xop1[3], xcond1);
+  rtx xpat2 = gen_branch (xop2[3], xcond2);
+  rtx xcompare = NULL_RTX;
+  machine_mode mode = GET_MODE (xop1[1]);
+
+  if (mode == QImode)
+    {
+      gcc_assert (n_operands == 4);
+      xcompare = gen_cmpqi3 (xop1[1], xval);
+    }
+  else
+    {
+      gcc_assert (n_operands == 5);
+      rtx scratch = GET_CODE (xop1[4]) == SCRATCH ? xop2[4] : xop1[4];
+      rtx (*gen_cmp)(rtx,rtx,rtx)
+       = mode == HImode  ? gen_gen_comparehi
+       : mode == PSImode ? gen_gen_comparepsi
+       : gen_gen_comparesi; // SImode
+      xcompare = gen_cmp (xop1[1], xval, scratch);
+    }
+
+  // Emit that stuff.
+
+  rtx_insn *cmp = emit_insn_before (xcompare, insn1);
+  rtx_jump_insn *branch1 = emit_jump_insn_after (xpat1, insn1);
+  rtx_jump_insn *branch2 = emit_jump_insn_after (xpat2, insn2);
+
+  JUMP_LABEL (branch1) = xop1[3];
+  JUMP_LABEL (branch2) = xop2[3];
+  // delete_insn() decrements LABEL_NUSES when deleting a JUMP_INSN,
+  // but when we pop a new JUMP_INSN, do it by hand.
+  ++LABEL_NUSES (xop1[3]);
+  ++LABEL_NUSES (xop2[3]);
+
+  delete_insn (insn1);
+  delete_insn (insn2);
+
+  if (swap_targets)
+    {
+      gcc_assert (! follow_label1);
+
+      basic_block to1 = BLOCK_FOR_INSN (xop1[3]);
+      basic_block to2 = BLOCK_FOR_INSN (xop2[3]);
+      edge e1 = find_edge (BLOCK_FOR_INSN (branch1), to1);
+      edge e2 = find_edge (BLOCK_FOR_INSN (branch2), to2);
+      gcc_assert (e1);
+      gcc_assert (e2);
+      redirect_edge_and_branch (e1, to2);
+      redirect_edge_and_branch (e2, to1);
+    }
+
+  // As a side effect, also recog the new insns.
+  gcc_assert (valid_insn_p (cmp));
+  gcc_assert (valid_insn_p (branch1));
+  gcc_assert (valid_insn_p (branch2));
+
+  return next_insn;
+}
+
+
+/* Sequences like
+
+      SREG = compare (reg, 1 + val);
+         if (SREG >= 0)  goto label1;
+      SREG = compare (reg, val);
+         if (SREG == 0)  goto label2;
+
+   can be optimized to
+
+      SREG = compare (reg, val);
+         if (SREG == 0)  goto label2;
+         if (SREG >= 0)  goto label1;
+
+   Almost all cases where one of the comparisons is redundant can
+   be transformed in such a way that only one comparison is required
+   and no difficult branches are needed.  */
+
+unsigned int
+avr_pass_ifelse::execute (function *)
+{
+  rtx_insn *next_insn;
+
+  for (rtx_insn *insn = get_insns(); insn; insn = next_insn)
+    {
+      next_insn = next_nonnote_nondebug_insn (insn);
+
+      if (! next_insn)
+       break;
+
+      // Search for two cbranch insns.  The first one is a cbranch.
+      // Filter for "cbranch<mode>4_insn" with mode in QI, HI, PSI, SI.
+
+      if (! JUMP_P (insn))
+       continue;
+
+      int icode1 = recog_memoized (insn);
+
+      if (icode1 != CODE_FOR_cbranchqi4_insn
+         && icode1 != CODE_FOR_cbranchhi4_insn
+         && icode1 != CODE_FOR_cbranchpsi4_insn
+         && icode1 != CODE_FOR_cbranchsi4_insn)
+       continue;
+
+      rtx_jump_insn *insn1 = as_a<rtx_jump_insn *> (insn);
+
+      // jmp[0]: We can optimize cbranches that follow cbranch insn1.
+      rtx_insn *jmp[2] = { next_insn, nullptr };
+
+      // jmp[1]: A cbranch following the label of cbranch insn1.
+      if (LABEL_NUSES (JUMP_LABEL (insn1)) == 1)
+       {
+         rtx_insn *code_label1 = JUMP_LABEL_AS_INSN (insn1);
+         rtx_insn *barrier = prev_nonnote_nondebug_insn (code_label1);
+
+         // When the target label of insn1 is used exactly once and is
+         // not a fallthrough, i.e. is preceded by a barrier, then
+         // consider the insn following that label.
+         if (barrier && BARRIER_P (barrier))
+           jmp[1] = next_nonnote_nondebug_insn (code_label1);
+      }
+
+      // With almost certainty, only one of the two possible jumps can
+      // be optimized with insn1, but it's hard to tell which one a priori.
+      // Just try both.  In the unlikely case where both could be optimized,
+      // prefer jmp[0] because eliminating difficult branches is impeded
+      // by following label1.
+
+      for (int j = 0; j < 2; ++j)
+       if (jmp[j] && JUMP_P (jmp[j])
+           && recog_memoized (jmp[j]) == icode1)
+         {
+           rtx_insn *next
+             = avr_optimize_2ifelse (insn1, as_a<rtx_jump_insn *> (jmp[j]),
+                                     j == 1 /* follow_label1 */);
+           if (next)
+             {
+               next_insn = next;
+               break;
+             }
+         }
+
+    } // loop insns
+
+  return 0;
+}
+
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Optimize results of the casesi expander for modes < SImode.
+
+static const pass_data avr_pass_data_casesi =
+{
+  RTL_PASS,      // type
+  "",            // name (will be patched)
+  OPTGROUP_NONE, // optinfo_flags
+  TV_DF_SCAN,    // tv_id
+  0,             // properties_required
+  0,             // properties_provided
+  0,             // properties_destroyed
+  0,             // todo_flags_start
+  0              // todo_flags_finish
+};
+
+class avr_pass_casesi : public rtl_opt_pass
+{
+public:
+  avr_pass_casesi (gcc::context *ctxt, const char *name)
+    : rtl_opt_pass (avr_pass_data_casesi, ctxt)
+  {
+    this->name = name;
+  }
+
+  bool gate (function *) final override
+  {
+    return optimize > 0;
+  }
+
+  unsigned int execute (function *) final override;
+}; // avr_pass_casesi
+
+
+/* Make one parallel insn with all the patterns from insns i[0]..i[5].  */
+
+static rtx_insn *
+avr_parallel_insn_from_insns (rtx_insn *i[5])
+{
+  rtvec vec = gen_rtvec (5, PATTERN (i[0]), PATTERN (i[1]), PATTERN (i[2]),
+                        PATTERN (i[3]), PATTERN (i[4]));
+  start_sequence();
+  emit (gen_rtx_PARALLEL (VOIDmode, vec));
+  rtx_insn *insn = get_insns();
+  end_sequence();
+
+  return insn;
+}
+
+
+/* Return true if we see an insn stream generated by casesi expander together
+   with an extension to SImode of the switch value.
+
+   If this is the case, fill in the insns from casesi to INSNS[1..5] and
+   the SImode extension to INSNS[0].  Moreover, extract the operands of
+   pattern casesi_<mode>_sequence forged from the sequence to recog_data.  */
+
+static bool
+avr_is_casesi_sequence (basic_block bb, rtx_insn *insn, rtx_insn *insns[5])
+{
+  rtx set_4, set_0;
+
+  /* A first and quick test for a casesi sequences.  As a side effect of
+     the test, harvest respective insns to INSNS[0..4].  */
+
+  if (!(JUMP_P (insns[4] = insn)
+       // casesi is the only insn that comes up with UNSPEC_INDEX_JMP,
+       // hence the following test ensures that we are actually dealing
+       // with code from casesi.
+       && (set_4 = single_set (insns[4]))
+       && UNSPEC == GET_CODE (SET_SRC (set_4))
+       && UNSPEC_INDEX_JMP == XINT (SET_SRC (set_4), 1)
+
+       && (insns[3] = prev_real_insn (insns[4]))
+       && (insns[2] = prev_real_insn (insns[3]))
+       && (insns[1] = prev_real_insn (insns[2]))
+
+       // Insn prior to casesi.
+       && (insns[0] = prev_real_insn (insns[1]))
+       && (set_0 = single_set (insns[0]))
+       && extend_operator (SET_SRC (set_0), SImode)))
+    {
+      return false;
+    }
+
+  if (dump_file)
+    {
+      fprintf (dump_file, ";; Sequence from casesi in "
+              "[bb %d]:\n\n", bb->index);
+      for (int i = 0; i < 5; i++)
+       print_rtl_single (dump_file, insns[i]);
+    }
+
+  /* We have to deal with quite some operands.  Extracting them by hand
+     would be tedious, therefore wrap the insn patterns into a parallel,
+     run recog against it and then use insn extract to get the operands. */
+
+  rtx_insn *xinsn = avr_parallel_insn_from_insns (insns);
+
+  INSN_CODE (xinsn) = recog (PATTERN (xinsn), xinsn, NULL /* num_clobbers */);
+
+  /* Failing to recognize means that someone changed the casesi expander or
+     that some passes prior to this one performed some unexpected changes.
+     Gracefully drop such situations instead of aborting.  */
+
+  if (INSN_CODE (xinsn) < 0)
+    {
+      if (dump_file)
+       fprintf (dump_file, ";; Sequence not recognized, giving up.\n\n");
+
+      return false;
+    }
+
+  gcc_assert (CODE_FOR_casesi_qi_sequence == INSN_CODE (xinsn)
+             || CODE_FOR_casesi_hi_sequence == INSN_CODE (xinsn));
+
+  extract_insn (xinsn);
+
+  // Assert on the anatomy of xinsn's operands we are going to work with.
+
+  gcc_assert (recog_data.n_operands == 11);
+  gcc_assert (recog_data.n_dups == 4);
+
+  if (dump_file)
+    {
+      fprintf (dump_file, ";; Operands extracted:\n");
+      for (int i = 0; i < recog_data.n_operands; i++)
+       avr_fdump (dump_file, ";; $%d = %r\n", i, recog_data.operand[i]);
+      fprintf (dump_file, "\n");
+    }
+
+  return true;
+}
+
+
+/* INSNS[1..4] is a sequence as generated by casesi and INSNS[0] is an
+   extension of an 8-bit or 16-bit integer to SImode.  XOP contains the
+   operands of INSNS as extracted by insn_extract from pattern
+   casesi_<mode>_sequence:
+
+      $0: SImode reg switch value as result of $9.
+      $1: Negative of smallest index in switch.
+      $2: Number of entries in switch.
+      $3: Label to table.
+      $4: Label if out-of-bounds.
+      $5: $0 + $1.
+      $6: 3-byte PC: subreg:HI ($5) + label_ref ($3)
+         2-byte PC: subreg:HI ($5)
+      $7: HI reg index into table (Z or pseudo)
+      $8: R24 or const0_rtx (to be clobbered)
+      $9: Extension to SImode of an 8-bit or 16-bit integer register $10.
+      $10: QImode or HImode register input of $9.
+
+   Try to optimize this sequence, i.e. use the original HImode / QImode
+   switch value instead of SImode.  */
+
+static void
+avr_optimize_casesi (rtx_insn *insns[5], rtx *xop)
+{
+  // Original mode of the switch value; this is QImode or HImode.
+  machine_mode mode = GET_MODE (xop[10]);
+
+  // How the original switch value was extended to SImode; this is
+  // SIGN_EXTEND or ZERO_EXTEND.
+  enum rtx_code code = GET_CODE (xop[9]);
+
+  // Lower index, upper index (plus one) and range of case calues.
+  HOST_WIDE_INT low_idx = -INTVAL (xop[1]);
+  HOST_WIDE_INT num_idx = INTVAL (xop[2]);
+  HOST_WIDE_INT hig_idx = low_idx + num_idx;
+
+  // Maximum ranges of (un)signed QImode resp. HImode.
+  unsigned umax = QImode == mode ? 0xff : 0xffff;
+  int imax = QImode == mode ? 0x7f : 0x7fff;
+  int imin = -imax - 1;
+
+  // Testing the case range and whether it fits into the range of the
+  // (un)signed mode.  This test should actually always pass because it
+  // makes no sense to have case values outside the mode range.  Notice
+  // that case labels which are unreachable because they are outside the
+  // mode of the switch value (e.g. "case -1" for uint8_t) have already
+  // been thrown away by the middle-end.
+
+  if (SIGN_EXTEND == code
+      && low_idx >= imin
+      && hig_idx <= imax)
+    {
+      // ok
+    }
+  else if (ZERO_EXTEND == code
+          && low_idx >= 0
+          && (unsigned) hig_idx <= umax)
+    {
+      // ok
+    }
+  else
+    {
+      if (dump_file)
+       fprintf (dump_file, ";; Case ranges too big, giving up.\n\n");
+      return;
+    }
+
+  // Do normalization of switch value $10 and out-of-bound check in its
+  // original mode instead of in SImode.  Use a newly created pseudo.
+  // This will replace insns[1..2].
+
+  start_sequence();
+
+  rtx reg = copy_to_mode_reg (mode, xop[10]);
+
+  rtx (*gen_add)(rtx,rtx,rtx) = QImode == mode ? gen_addqi3 : gen_addhi3;
+  rtx (*gen_cbranch)(rtx,rtx,rtx,rtx)
+    = QImode == mode ? gen_cbranchqi4 : gen_cbranchhi4;
+
+  emit_insn (gen_add (reg, reg, gen_int_mode (-low_idx, mode)));
+  rtx op0 = reg; rtx op1 = gen_int_mode (num_idx, mode);
+  rtx labelref = copy_rtx (xop[4]);
+  rtx xbranch = gen_cbranch (gen_rtx_fmt_ee (GTU, VOIDmode, op0, op1),
+                            op0, op1, labelref);
+  rtx_insn *cbranch = emit_jump_insn (xbranch);
+  JUMP_LABEL (cbranch) = xop[4];
+  ++LABEL_NUSES (xop[4]);
+
+  rtx_insn *seq1 = get_insns();
+  rtx_insn *last1 = get_last_insn();
+  end_sequence();
+
+  emit_insn_after (seq1, insns[2]);
+
+  // After the out-of-bounds test and corresponding branch, use a
+  // 16-bit index.  If QImode is used, extend it to HImode first.
+  // This will replace insns[4].
+
+  start_sequence();
+
+  if (QImode == mode)
+    reg = force_reg (HImode, gen_rtx_fmt_e (code, HImode, reg));
+
+  rtx pat_4 = AVR_3_BYTE_PC
+    ? gen_movhi (xop[7], reg)
+    : gen_addhi3 (xop[7], reg, gen_rtx_LABEL_REF (VOIDmode, xop[3]));
+
+  emit_insn (pat_4);
+
+  rtx_insn *seq2 = get_insns();
+  rtx_insn *last2 = get_last_insn();
+  end_sequence();
+
+  emit_insn_after (seq2, insns[3]);
+
+  if (dump_file)
+    {
+      fprintf (dump_file, ";; New insns: ");
+
+      for (rtx_insn *insn = seq1; ; insn = NEXT_INSN (insn))
+       {
+         fprintf (dump_file, "%d, ", INSN_UID (insn));
+         if (insn == last1)
+           break;
+       }
+      for (rtx_insn *insn = seq2; ; insn = NEXT_INSN (insn))
+       {
+         fprintf (dump_file, "%d%s", INSN_UID (insn),
+                  insn == last2 ? ".\n\n" : ", ");
+         if (insn == last2)
+           break;
+       }
+
+      fprintf (dump_file, ";; Deleting insns: %d, %d, %d.\n\n",
+              INSN_UID (insns[1]), INSN_UID (insns[2]), INSN_UID (insns[3]));
+    }
+
+  // Pseudodelete the SImode and subreg of SImode insns.  We don't care
+  // about the extension insns[0]: Its result is now unused and other
+  // passes will clean it up.
+
+  SET_INSN_DELETED (insns[1]);
+  SET_INSN_DELETED (insns[2]);
+  SET_INSN_DELETED (insns[3]);
+}
+
+
+unsigned int
+avr_pass_casesi::execute (function *func)
+{
+  basic_block bb;
+
+  FOR_EACH_BB_FN (bb, func)
+    {
+      rtx_insn *insn, *insns[5];
+
+      FOR_BB_INSNS (bb, insn)
+       {
+         if (avr_is_casesi_sequence (bb, insn, insns))
+           {
+             avr_optimize_casesi (insns, recog_data.operand);
+           }
+       }
+    }
+
+  return 0;
+}
+
+} // anonymous namespace
+
+/* Perform some extra checks on operands of casesi_<mode>_sequence.
+   Not all operand dependencies can be described by means of predicates.
+   This function performs left over checks and should always return true.
+   Returning false means that someone changed the casesi expander but did
+   not adjust casesi_<mode>_sequence.  */
+
+bool
+avr_casei_sequence_check_operands (rtx *xop)
+{
+  rtx sub_5 = NULL_RTX;
+
+  if (AVR_HAVE_EIJMP_EICALL
+      // The last clobber op of the tablejump.
+      && xop[8] == all_regs_rtx[REG_24])
+    {
+      // $6 is: (subreg:SI ($5) 0)
+      sub_5 = xop[6];
+    }
+
+  if (!AVR_HAVE_EIJMP_EICALL
+      // $6 is: (plus:HI (subreg:SI ($5) 0)
+      //                (label_ref ($3)))
+      && PLUS == GET_CODE (xop[6])
+      && LABEL_REF == GET_CODE (XEXP (xop[6], 1))
+      && rtx_equal_p (xop[3], XEXP (XEXP (xop[6], 1), 0))
+      // The last clobber op of the tablejump.
+      && xop[8] == const0_rtx)
+    {
+      sub_5 = XEXP (xop[6], 0);
+    }
+
+  if (sub_5
+      && SUBREG_P (sub_5)
+      && SUBREG_BYTE (sub_5) == 0
+      && rtx_equal_p (xop[5], SUBREG_REG (sub_5)))
+    return true;
+
+  if (dump_file)
+    fprintf (dump_file, "\n;; Failed condition for casesi_<mode>_sequence\n\n");
+
+  return false;
+}
+
+namespace
+{
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Find more POST_INC and PRE_DEC cases.
+
+static const pass_data avr_pass_data_fuse_add =
+{
+  RTL_PASS,        // type
+  "",              // name (will be patched)
+  OPTGROUP_NONE,    // optinfo_flags
+  TV_DF_SCAN,      // tv_id
+  0,               // properties_required
+  0,               // properties_provided
+  0,               // properties_destroyed
+  0,               // todo_flags_start
+  TODO_df_finish    // todo_flags_finish
+};
+
+class avr_pass_fuse_add : public rtl_opt_pass
+{
+public:
+  avr_pass_fuse_add (gcc::context *ctxt, const char *name)
+    : rtl_opt_pass (avr_pass_data_fuse_add, ctxt)
+  {
+    this->name = name;
+  }
+
+  bool gate (function *) final override
+  {
+    return optimize && avr_fuse_add > 0;
+  }
+
+  unsigned int execute (function *) final override;
+
+  struct Some_Insn
+  {
+    rtx_insn *insn = nullptr;
+    rtx dest, src;
+    bool valid () const { return insn != nullptr; }
+    void set_deleted ()
+    {
+      gcc_assert (insn);
+      SET_INSN_DELETED (insn);
+      insn = nullptr;
+    }
+  };
+
+  // If .insn is not NULL, then this is a  reg:HI += const_int
+  // of an address register.
+  struct Add_Insn : Some_Insn
+  {
+    rtx addend;
+    int regno;
+    Add_Insn () {}
+    Add_Insn (rtx_insn *insn);
+  };
+
+  // If .insn is not NULL, then this sets an address register
+  // to a constant value.
+  struct Ldi_Insn : Some_Insn
+  {
+    int regno;
+    Ldi_Insn () {}
+    Ldi_Insn (rtx_insn *insn);
+  };
+
+  // If .insn is not NULL, then this is a load or store insn where the
+  // address is REG or POST_INC with an address register.
+  struct Mem_Insn : Some_Insn
+  {
+    rtx reg_or_0, mem, addr, addr_reg;
+    int addr_regno;
+    enum rtx_code addr_code;
+    machine_mode mode;
+    addr_space_t addr_space;
+    bool store_p, volatile_p;
+    Mem_Insn () {}
+    Mem_Insn (rtx_insn *insn);
+  };
+
+  rtx_insn *fuse_ldi_add (Ldi_Insn &prev_ldi, Add_Insn &add);
+  rtx_insn *fuse_add_add (Add_Insn &prev_add, Add_Insn &add);
+  rtx_insn *fuse_add_mem (Add_Insn &prev_add, Mem_Insn &mem);
+  rtx_insn *fuse_mem_add (Mem_Insn &prev_mem, Add_Insn &add);
+}; // avr_pass_fuse_add
+
+
+/* Describe properties of AVR's indirect load and store instructions
+   LD, LDD, ST, STD, LPM, ELPM depending on register number, volatility etc.
+   Rules for "volatile" accesses are:
+
+        | Xmega           |  non-Xmega
+   ------+-----------------+----------------
+   load  | read LSB first  | read LSB first
+   store | write LSB first | write MSB first
+*/
+
+struct AVR_LdSt_Props
+{
+  bool has_postinc, has_predec, has_ldd;
+  // The insn printers will use POST_INC or PRE_DEC addressing, no matter
+  // what adressing modes we are feeding into them.
+  bool want_postinc, want_predec;
+
+  AVR_LdSt_Props (int regno, bool store_p, bool volatile_p, addr_space_t as)
+  {
+    bool generic_p = ADDR_SPACE_GENERIC_P (as);
+    bool flashx_p = ! generic_p && as != ADDR_SPACE_MEMX;
+    has_postinc = generic_p || (flashx_p && regno == REG_Z);
+    has_predec = generic_p;
+    has_ldd = ! AVR_TINY && generic_p && (regno == REG_Y || regno == REG_Z);
+    want_predec  = volatile_p && generic_p && ! AVR_XMEGA && store_p;
+    want_postinc = volatile_p && generic_p && (AVR_XMEGA || ! store_p);
+    want_postinc |= flashx_p && regno == REG_Z;
+  }
+
+  AVR_LdSt_Props (const avr_pass_fuse_add::Mem_Insn &m)
+    : AVR_LdSt_Props (m.addr_regno, m.store_p, m.volatile_p, m.addr_space)
+  {
+    gcc_assert (m.valid ());
+  }
+};
+
+
+/* Emit a single_set that clobbers REG_CC.  */
+
+static rtx_insn *
+emit_move_ccc (rtx dest, rtx src)
+{
+  return emit_insn (gen_gen_move_clobbercc (dest, src));
+}
+
+
+/* Emit a single_set that clobbers REG_CC after insn AFTER.  */
+
+static rtx_insn *
+emit_move_ccc_after (rtx dest, rtx src, rtx_insn *after)
+{
+  return emit_insn_after (gen_gen_move_clobbercc (dest, src), after);
+}
+
+static bool
+reg_seen_between_p (const_rtx reg, const rtx_insn *from, const rtx_insn *to)
+{
+  return (reg_used_between_p (reg, from, to)
+         || reg_set_between_p (reg, from, to));
+}
+
+
+static void
+avr_maybe_adjust_cfa (rtx_insn *insn, rtx reg, int addend)
+{
+  if (addend
+      && frame_pointer_needed
+      && REGNO (reg) == FRAME_POINTER_REGNUM
+      && avr_fuse_add == 3)
+    {
+      rtx plus = plus_constant (Pmode, reg, addend);
+      RTX_FRAME_RELATED_P (insn) = 1;
+      add_reg_note (insn, REG_CFA_ADJUST_CFA, gen_rtx_SET (reg, plus));
+    }
+}
+
+
+// If successful, this represents a SET of a pointer register to a constant.
+avr_pass_fuse_add::Ldi_Insn::Ldi_Insn (rtx_insn *insn)
+{
+  rtx set = single_set (insn);
+  if (!set)
+    return;
+
+  src = SET_SRC (set);
+  dest = SET_DEST (set);
+
+  if (REG_P (dest)
+      && GET_MODE (dest) == Pmode
+      && IN_RANGE (regno = REGNO (dest), REG_X, REG_Z)
+      && CONSTANT_P (src))
+    {
+      this->insn = insn;
+    }
+}
+
+// If successful, this represents a PLUS with CONST_INT of a pointer
+// register X, Y or Z.  Otherwise, the object is not valid().
+avr_pass_fuse_add::Add_Insn::Add_Insn (rtx_insn *insn)
+{
+  rtx set = single_set (insn);
+  if (!set)
+    return;
+
+  src = SET_SRC (set);
+  dest = SET_DEST (set);
+  if (REG_P (dest)
+      // We are only interested in PLUSes that change address regs.
+      && GET_MODE (dest) == Pmode
+      && IN_RANGE (regno = REGNO (dest), REG_X, REG_Z)
+      && PLUS == GET_CODE (src)
+      && rtx_equal_p (XEXP (src, 0), dest)
+      && CONST_INT_P (XEXP (src, 1)))
+    {
+      // This is reg:HI += const_int.
+      addend = XEXP (src, 1);
+      this->insn = insn;
+    }
+}
+
+// If successful, this represents a load or store insn where the addressing
+// mode uses pointer register X, Y or Z.  Otherwise, the object is not valid().
+avr_pass_fuse_add::avr_pass_fuse_add::Mem_Insn::Mem_Insn (rtx_insn *insn)
+{
+  rtx set = single_set (insn);
+  if (!set)
+    return;
+
+  src = SET_SRC (set);
+  dest = SET_DEST (set);
+  mode = GET_MODE (dest);
+
+  if (MEM_P (dest)
+      && (REG_P (src) || src == CONST0_RTX (mode)))
+    {
+      reg_or_0 = src;
+      mem = dest;
+    }
+  else if (REG_P (dest) && MEM_P (src))
+    {
+      reg_or_0 = dest;
+      mem = src;
+    }
+  else
+    return;
+
+  if (avr_mem_memx_p (mem)
+      || avr_load_libgcc_p (mem))
+    return;
+
+  addr = XEXP (mem, 0);
+  addr_code = GET_CODE (addr);
+
+  if (addr_code == REG)
+    addr_reg = addr;
+  else if (addr_code == POST_INC || addr_code == PRE_DEC)
+    addr_reg = XEXP (addr, 0);
+  else
+    return;
+
+  addr_regno = REGNO (addr_reg);
+
+  if (avr_fuse_add == 2
+      && frame_pointer_needed
+      && addr_regno == FRAME_POINTER_REGNUM)
+    MEM_VOLATILE_P (mem) = 0;
+
+  if (reg_overlap_mentioned_p (reg_or_0, addr) // Can handle CONSTANT_P.
+      || addr_regno > REG_Z
+      || avr_mem_memx_p (mem)
+      // The following optimizations only handle REG and POST_INC,
+      // so that's all what we allow here.
+      || (addr_code != REG && addr_code != POST_INC))
+    return;
+
+  addr_space = MEM_ADDR_SPACE (mem);
+  volatile_p = MEM_VOLATILE_P (mem);
+  store_p = MEM_P (dest);
+
+  // Turn this "valid".
+  this->insn = insn;
+}
+
+/* Try to combine a Ldi insn with a PLUS CONST_INT addend to one Ldi insn.
+   If LDI is valid, then it precedes ADD in the same block.
+   When a replacement is found, a new insn is emitted and the old insns
+   are pseudo-deleted.  The returned insn is the point where the calling
+   scanner should continue.  When no replacement is found, nullptr is
+   returned and nothing changed.  */
+
+rtx_insn *
+avr_pass_fuse_add::fuse_ldi_add (Ldi_Insn &ldi, Add_Insn &add)
+{
+  if (! ldi.valid ()
+      || reg_seen_between_p (ldi.dest, ldi.insn, add.insn))
+    {
+      // If something is between the Ldi and the current insn, we can
+      // set the Ldi invalid to speed future scans.
+      return ldi.insn = nullptr;
+    }
+
+  // Found a Ldi with const and a PLUS insns in the same BB,
+  // and with no interfering insns between them.
+
+  // Emit new Ldi with the sum of the original offsets after the old Ldi.
+  rtx xval = plus_constant (Pmode, ldi.src, INTVAL (add.addend));
+
+  rtx_insn *insn = emit_move_ccc_after (ldi.dest, xval, ldi.insn);
+  avr_dump (";; new Ldi[%d] insn %d after %d: R%d = %r\n\n", ldi.regno,
+           INSN_UID (insn), INSN_UID (ldi.insn), ldi.regno, xval);
+
+  rtx_insn *next = NEXT_INSN (add.insn);
+  ldi.set_deleted ();
+  add.set_deleted ();
+
+  return next;
+}
+
+/* Try to combine two PLUS insns with CONST_INT addend to one such insn.
+   If PREV_ADD is valid, then it precedes ADD in the same basic block.
+   When a replacement is found, a new insn is emitted and the old insns
+   are pseudo-deleted.  The returned insn is the point where the calling
+   scanner should continue.  When no replacement is found, nullptr is
+   returned and nothing changed.  */
+
+rtx_insn *
+avr_pass_fuse_add::fuse_add_add (Add_Insn &prev_add, Add_Insn &add)
+{
+  if (! prev_add.valid ()
+      || reg_seen_between_p (add.dest, prev_add.insn, add.insn))
+    {
+      // If something is between the previous Add and the current insn,
+      // we can set the previous Add invalid to speed future scans.
+      return prev_add.insn = nullptr;
+    }
+
+  // Found two PLUS insns in the same BB, and with no interfering
+  // insns between them.
+  rtx plus = plus_constant (Pmode, add.src, INTVAL (prev_add.addend));
+
+  rtx_insn *next;
+  if (REG_P (plus))
+    {
+      avr_dump (";; Add[%d] from %d annihilates %d\n\n", add.regno,
+               INSN_UID (prev_add.insn), INSN_UID (add.insn));
+      next = NEXT_INSN (add.insn);
+    }
+  else
+    {
+      // Emit after the current insn, so that it will be picked
+      // up as next valid Add insn.
+      next = emit_move_ccc_after (add.dest, plus, add.insn);
+      avr_dump (";; #1 new Add[%d] insn %d after %d: R%d += %d\n\n",
+               add.regno, INSN_UID (next), INSN_UID (add.insn),
+               add.regno, (int) INTVAL (XEXP (plus, 1)));
+      gcc_assert (GET_CODE (plus) == PLUS);
+    }
+
+  add.set_deleted ();
+  prev_add.set_deleted ();
+
+  return next;
+}
+
+/* Try to combine a PLUS of the address register with a load or store insn.
+   If ADD is valid, then it precedes MEM in the same basic block.
+   When a replacement is found, a new insn is emitted and the old insns
+   are pseudo-deleted.  The returned insn is the point where the calling
+   scanner should continue.  When no replacement is found, nullptr is
+   returned and nothing changed.  */
+
+rtx_insn *
+avr_pass_fuse_add::fuse_add_mem (Add_Insn &add, Mem_Insn &mem)
+{
+  if (! add.valid ()
+      || reg_seen_between_p (add.dest, add.insn, mem.insn))
+    {
+      // If something is between the Add and the current insn, we can
+      // set the Add invalid to speed future scans.
+      return add.insn = nullptr;
+    }
+
+  AVR_LdSt_Props ap { mem };
+
+  int msize = GET_MODE_SIZE (mem.mode);
+
+  // The mem insn really wants PRE_DEC.
+  bool case1 = ((mem.addr_code == REG || mem.addr_code == POST_INC)
+               && msize > 1 && ap.want_predec && ! ap.has_ldd);
+
+  // The offset can be consumed by a PRE_DEC.
+  bool case2 = (- INTVAL (add.addend) == msize
+               && (mem.addr_code == REG || mem.addr_code == POST_INC)
+               && ap.has_predec && ! ap.want_postinc);
+
+  if (! case1 && ! case2)
+    return nullptr;
+
+  // Change from REG or POST_INC to PRE_DEC.
+  rtx xmem = change_address (mem.mem, mem.mode,
+                            gen_rtx_PRE_DEC (Pmode, mem.addr_reg));
+  rtx dest = mem.store_p ? xmem : mem.reg_or_0;
+  rtx src  = mem.store_p ? mem.reg_or_0 : xmem;
+
+  rtx_insn *next = emit_move_ccc_after (dest, src, mem.insn);
+  add_reg_note (next, REG_INC, mem.addr_reg);
+  avr_dump (";; new Mem[%d] insn %d after %d: %r = %r\n\n", mem.addr_regno,
+           INSN_UID (next), INSN_UID (mem.insn), dest, src);
+
+  // Changing REG or POST_INC -> PRE_DEC means that the addend before
+  // the memory access must be increased by the size of the access,
+  rtx plus = plus_constant (Pmode, add.src, msize);
+  if (! REG_P (plus))
+    {
+      rtx_insn *insn = emit_move_ccc_after (add.dest, plus, add.insn);
+      avr_dump (";; #2 new Add[%d] insn %d after %d: R%d += %d\n\n",
+               add.regno, INSN_UID (insn), INSN_UID (add.insn),
+               add.regno, (int) INTVAL (XEXP (plus, 1)));
+      gcc_assert (GET_CODE (plus) == PLUS);
+    }
+  else
+    avr_dump (";; Add[%d] insn %d consumed into %d\n\n",
+             add.regno, INSN_UID (add.insn), INSN_UID (next));
+
+  // Changing POST_INC -> PRE_DEC means that the addend after the mem has to be
+  // the size of the access.  The hope is that this new add insn may be unused.
+  if (mem.addr_code == POST_INC)
+    {
+      plus = plus_constant (Pmode, add.dest, msize);
+      rtx_insn *next2 = emit_move_ccc_after (add.dest, plus, next);
+      avr_dump (";; #3 new Add[%d] insn %d after %d: R%d += %d\n\n", add.regno,
+               INSN_UID (next2), INSN_UID (next), add.regno, msize);
+      next = next2;
+    }
+
+  add.set_deleted ();
+  mem.set_deleted ();
+
+  return next;
+}
+
+/* Try to combine a load or store insn with a PLUS of the address register.
+   If MEM is valid, then it precedes ADD in the same basic block.
+   When a replacement is found, a new insn is emitted and the old insns
+   are pseudo-deleted.  The returned insn is the point where the calling
+   scanner should continue.  When no replacement is found, nullptr is
+   returned and nothing changed.  */
+
+rtx_insn *
+avr_pass_fuse_add::fuse_mem_add (Mem_Insn &mem, Add_Insn &add)
+{
+  if (! mem.valid ()
+      || reg_seen_between_p (add.dest, mem.insn, add.insn))
+    {
+      // If something is between the Mem and the current insn, we can
+      // set the Mem invalid to speed future scans.
+      return mem.insn = nullptr;
+    }
+
+  AVR_LdSt_Props ap { mem };
+
+  int msize = GET_MODE_SIZE (mem.mode);
+
+  // The add insn can be consumed by a POST_INC.
+  bool case1 = (mem.addr_code == REG
+               && INTVAL (add.addend) == msize
+               && ap.has_postinc && ! ap.want_predec);
+
+  // There are cases where even a partial consumption of the offset is better.
+  // This are the cases where no LD+offset addressing is available, because
+  // the address register is obviously used after the mem insn, and a mem insn
+  // with REG addressing mode will have to restore the address.
+  bool case2 = (mem.addr_code == REG
+               && msize > 1 && ap.want_postinc && ! ap.has_ldd);
+
+  if (! case1 && ! case2)
+    return nullptr;
+
+  // Change addressing mode from REG to POST_INC.
+  rtx xmem = change_address (mem.mem, mem.mode,
+                            gen_rtx_POST_INC (Pmode, mem.addr_reg));
+  rtx dest = mem.store_p ? xmem : mem.reg_or_0;
+  rtx src  = mem.store_p ? mem.reg_or_0 : xmem;
+
+  rtx_insn *insn = emit_move_ccc_after (dest, src, mem.insn);
+  add_reg_note (insn, REG_INC, mem.addr_reg);
+  avr_dump (";; new Mem[%d] insn %d after %d: %r = %r\n\n", add.regno,
+           INSN_UID (insn), INSN_UID (mem.insn), dest, src);
+
+  rtx_insn *next = NEXT_INSN (add.insn);
+
+  // Changing REG -> POST_INC means that the post addend must be
+  // decreased by the size of the access.
+  rtx plus = plus_constant (Pmode, add.src, -msize);
+  if (! REG_P (plus))
+    {
+      next = emit_move_ccc_after (mem.addr_reg, plus, add.insn);
+      avr_dump (";; #4 new Add[%d] insn %d after %d: R%d += %d\n\n",
+               add.regno, INSN_UID (next), INSN_UID (add.insn),
+               add.regno, (int) INTVAL (XEXP (plus, 1)));
+      gcc_assert (GET_CODE (plus) == PLUS);
+    }
+  else
+    avr_dump (";; Add[%d] insn %d consumed into %d\n\n",
+             add.regno, INSN_UID (add.insn), INSN_UID (insn));
+
+  add.set_deleted ();
+  mem.set_deleted ();
+
+  return next;
+}
+
+/* Try to post-reload combine PLUS with CONST_INt of pointer registers with:
+   - Sets to a constant address.
+   - PLUS insn of that kind.
+   - Indirect loads and stores.
+   In almost all cases, combine opportunities arise from the preparation
+   done by `avr_split_tiny_move', but in some rare cases combinations are
+   found for the ordinary cores, too.
+      As we consider at most one Mem insn per try, there may still be missed
+   optimizations like  POST_INC + PLUS + POST_INC  might be performed
+   as  PRE_DEC + PRE_DEC  for two adjacent locations.  */
+
+unsigned int
+avr_pass_fuse_add::execute (function *func)
+{
+  df_note_add_problem ();
+  df_analyze ();
+
+  int n_add = 0, n_mem = 0, n_ldi = 0;
+  basic_block bb;
+
+  FOR_EACH_BB_FN (bb, func)
+    {
+      Ldi_Insn prev_ldi_insns[REG_32];
+      Add_Insn prev_add_insns[REG_32];
+      Mem_Insn prev_mem_insns[REG_32];
+      rtx_insn *insn, *curr;
+
+      avr_dump ("\n;; basic block %d\n\n", bb->index);
+
+      FOR_BB_INSNS_SAFE (bb, insn, curr)
+       {
+         rtx_insn *next = nullptr;
+         Ldi_Insn ldi_insn { insn };
+         Add_Insn add_insn { insn };
+         Mem_Insn mem_insn { insn };
+
+         if (add_insn.valid ())
+           {
+             // Found reg:HI += const_int
+             avr_dump (";; insn %d: Add[%d]: R%d += %d\n\n",
+                       INSN_UID (add_insn.insn), add_insn.regno,
+                       add_insn.regno, (int) INTVAL (add_insn.addend));
+             Ldi_Insn &prev_ldi_insn = prev_ldi_insns[add_insn.regno];
+             Add_Insn &prev_add_insn = prev_add_insns[add_insn.regno];
+             Mem_Insn &prev_mem_insn = prev_mem_insns[add_insn.regno];
+             if ((next = fuse_ldi_add (prev_ldi_insn, add_insn)))
+               curr = next, n_ldi += 1;
+             else if ((next = fuse_add_add (prev_add_insn, add_insn)))
+               curr = next, n_add += 1;
+             else if ((next = fuse_mem_add (prev_mem_insn, add_insn)))
+               curr = next, n_mem += 1;
+             else
+               prev_add_insn = add_insn;
+           }
+         else if (mem_insn.valid ())
+           {
+             int addr_regno = REGNO (mem_insn.addr_reg);
+             avr_dump (";; insn %d: Mem[%d]: %r = %r\n\n",
+                       INSN_UID (mem_insn.insn), addr_regno,
+                       mem_insn.dest, mem_insn.src);
+             Add_Insn &prev_add_insn = prev_add_insns[addr_regno];
+             if ((next = fuse_add_mem (prev_add_insn, mem_insn)))
+               curr = next, n_mem += 1;
+             else
+               prev_mem_insns[addr_regno] = mem_insn;
+           }
+         else if (ldi_insn.valid ())
+           {
+             if (! CONST_INT_P (ldi_insn.src))
+               avr_dump (";; insn %d: Ldi[%d]: R%d = %r\n\n",
+                         INSN_UID (ldi_insn.insn), ldi_insn.regno,
+                         ldi_insn.regno, ldi_insn.src);
+             prev_ldi_insns[ldi_insn.regno] = ldi_insn;
+           }
+       } // for insns
+    } // for BBs
+
+  avr_dump (";; Function %f: Found %d changes: %d ldi, %d add, %d mem.\n",
+           n_ldi + n_add + n_mem, n_ldi, n_add, n_mem);
+
+  return 0;
+}
+
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Determine whether an ISR may use the __gcc_isr pseudo-instruction.
+
+static const pass_data avr_pass_data_pre_proep =
+{
+  RTL_PASS,        // type
+  "",              // name (will be patched)
+  OPTGROUP_NONE,    // optinfo_flags
+  TV_DF_SCAN,      // tv_id
+  0,               // properties_required
+  0,               // properties_provided
+  0,               // properties_destroyed
+  0,               // todo_flags_start
+  0                // todo_flags_finish
+};
+
+class avr_pass_pre_proep : public rtl_opt_pass
+{
+public:
+  avr_pass_pre_proep (gcc::context *ctxt, const char *name)
+    : rtl_opt_pass (avr_pass_data_pre_proep, ctxt)
+  {
+    this->name = name;
+  }
+
+  void compute_maybe_gasisr (function *);
+
+  unsigned int execute (function *fun) final override
+  {
+    if (avr_gasisr_prologues
+       // Whether this function is an ISR worth scanning at all.
+       && !fun->machine->is_no_gccisr
+       && (fun->machine->is_interrupt
+           || fun->machine->is_signal)
+       && !cfun->machine->is_naked
+       // Paranoia: Non-local gotos and labels that might escape.
+       && !cfun->calls_setjmp
+       && !cfun->has_nonlocal_label
+       && !cfun->has_forced_label_in_static)
+      {
+       compute_maybe_gasisr (fun);
+      }
+
+    return 0;
+  }
+
+}; // avr_pass_pre_proep
+
+
+/* Set fun->machine->gasisr.maybe provided we don't find anything that
+   prohibits GAS generating parts of ISR prologues / epilogues for us.  */
+
+void
+avr_pass_pre_proep::compute_maybe_gasisr (function *fun)
+{
+  // Don't use BB iterators so that we see JUMP_TABLE_DATA.
+
+  for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
+    {
+      // Transparent calls always use [R]CALL and are filtered out by GAS.
+      // ISRs don't use -mcall-prologues, hence what remains to be filtered
+      // out are open coded (tail) calls.
+
+      if (CALL_P (insn))
+       return;
+
+      // __tablejump2__ clobbers something and is targeted by JMP so
+      // that GAS won't see its usage.
+
+      if (AVR_HAVE_JMP_CALL
+         && JUMP_TABLE_DATA_P (insn))
+       return;
+
+      // Non-local gotos not seen in *FUN.
+
+      if (JUMP_P (insn)
+         && find_reg_note (insn, REG_NON_LOCAL_GOTO, NULL_RTX))
+       return;
+    }
+
+  fun->machine->gasisr.maybe = 1;
+}
+
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Late recomputation of notes so we can use `reg_unused_after()' and friends.
+
+static const pass_data avr_pass_data_recompute_notes =
+{
+  RTL_PASS,      // type
+  "",            // name (will be patched)
+  OPTGROUP_NONE, // optinfo_flags
+  TV_DF_SCAN,    // tv_id
+  0,             // properties_required
+  0,             // properties_provided
+  0,             // properties_destroyed
+  0,             // todo_flags_start
+  TODO_df_finish | TODO_df_verify // todo_flags_finish
+};
+
+class avr_pass_recompute_notes : public rtl_opt_pass
+{
+public:
+  avr_pass_recompute_notes (gcc::context *ctxt, const char *name)
+    : rtl_opt_pass (avr_pass_data_recompute_notes, ctxt)
+  {
+    this->name = name;
+  }
+
+  unsigned int execute (function *) final override
+  {
+    df_note_add_problem ();
+    df_analyze ();
+
+    return 0;
+  }
+}; // avr_pass_recompute_notes
+
+} // anonymous namespace
+
+
+\f
+//////////////////////////////////////////////////////////////////////////////
+// Function visible and used outside this module.
+
+/* During reload, we allow much more addresses than Reduced Tiny actually
+   supports.  Split them after reload in order to get closer to the
+   core's capabilities.  This sets the stage for pass .avr-fuse-add.  */
+
+bool
+avr_split_tiny_move (rtx_insn * /*insn*/, rtx *xop)
+{
+  bool store_p = false;
+  rtx mem, reg_or_0;
+
+  if (REG_P (xop[0]) && MEM_P (xop[1]))
+    {
+      reg_or_0 = xop[0];
+      mem = xop[1];
+    }
+  else if (MEM_P (xop[0])
+          && (REG_P (xop[1])
+              || xop[1] == CONST0_RTX (GET_MODE (xop[0]))))
+    {
+      mem = xop[0];
+      reg_or_0 = xop[1];
+      store_p = true;
+    }
+  else
+    return false;
+
+  machine_mode mode = GET_MODE (mem);
+  rtx base, addr = XEXP (mem, 0);
+  enum rtx_code addr_code = GET_CODE (addr);
+
+  if (REG_P (reg_or_0)
+      && reg_overlap_mentioned_p (reg_or_0, addr))
+    return false;
+  else if (addr_code == PLUS || addr_code == PRE_DEC || addr_code == POST_INC)
+    base = XEXP (addr, 0);
+  else if (addr_code == REG)
+    base = addr;
+  else
+    return false;
+
+  if (REGNO (base) > REG_Z)
+    return false;
+
+  if (! AVR_TINY
+      // Only keep base registers that can't do PLUS addressing.
+      && ((REGNO (base) != REG_X
+          && ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (mem)))
+         || avr_load_libgcc_p (mem)
+         || avr_mem_memx_p (mem)))
+    return false;
+
+  bool volatile_p = MEM_VOLATILE_P (mem);
+  bool mem_volatile_p = false;
+  if (frame_pointer_needed
+      && REGNO (base) == FRAME_POINTER_REGNUM)
+    {
+      if (avr_fuse_add < 2
+         // Be a projection (we always split PLUS).
+         || (avr_fuse_add == 2 && volatile_p && addr_code != PLUS))
+       return false;
+
+      // Changing the frame pointer locally may confuse later passes
+      // like .dse2 which don't track changes of FP, not even when
+      // respective CFA notes are present.  An example is pr22141-1.c.
+      if (avr_fuse_add == 2)
+       mem_volatile_p = true;
+    }
+
+  enum rtx_code new_code = UNKNOWN;
+  HOST_WIDE_INT add = 0, sub = 0;
+  int msize = GET_MODE_SIZE (mode);
+
+  AVR_LdSt_Props ap { REGNO (base), store_p, volatile_p, ADDR_SPACE_GENERIC };
+
+  switch (addr_code)
+    {
+    default:
+      return false;
+
+    case PLUS:
+      add = INTVAL (XEXP (addr, 1));
+      if (msize == 1)
+       {
+         new_code = REG;
+         sub = -add;
+       }
+      else if (ap.want_predec)
+       {
+         // volatile stores prefer PRE_DEC (MSB first)
+         sub = -add;
+         add += msize;
+         new_code = PRE_DEC;
+       }
+      else
+       {
+         new_code = POST_INC;
+         sub = -add - msize;
+       }
+      break;
+
+    case POST_INC:
+      // volatile stores prefer PRE_DEC (MSB first)
+      if (msize > 1 && ap.want_predec)
+       {
+         add = msize;
+         new_code = PRE_DEC;
+         sub = msize;
+         break;
+       }
+      return false;
+
+    case PRE_DEC:
+      // volatile loads prefer POST_INC (LSB first)
+      if (msize > 1 && ap.want_postinc)
+       {
+         add = -msize;
+         new_code = POST_INC;
+         sub = -msize;
+         break;
+       }
+      return false;
+
+    case REG:
+      if (msize == 1)
+       return false;
+
+      if (ap.want_predec)
+       {
+         add = msize;
+         new_code = PRE_DEC;
+         sub = 0;
+       }
+      else
+       {
+         add = 0;
+         new_code = POST_INC;
+         sub = -msize;
+       }
+      break;
+    } // switch addr_code
+
+  rtx_insn *insn;
+
+  if (add)
+    {
+      insn = emit_move_ccc (base, plus_constant (Pmode, base, add));
+      avr_maybe_adjust_cfa (insn, base, add);
+    }
+
+  rtx new_addr = new_code == REG
+    ? base
+    : gen_rtx_fmt_e (new_code, Pmode, base);
+
+  rtx new_mem = change_address (mem, mode, new_addr);
+  if (mem_volatile_p)
+    MEM_VOLATILE_P (new_mem) = 1;
+
+  insn = emit_move_ccc (store_p ? new_mem : reg_or_0,
+                       store_p ? reg_or_0 : new_mem);
+  if (auto_inc_p (new_addr))
+    {
+      add_reg_note (insn, REG_INC, base);
+      int off = new_code == POST_INC ? msize : -msize;
+      avr_maybe_adjust_cfa (insn, base, off);
+    }
+
+  if (sub)
+    {
+      insn = emit_move_ccc (base, plus_constant (Pmode, base, sub));
+      avr_maybe_adjust_cfa (insn, base, sub);
+    }
+
+  return true;
+}
+
+
+\f
+// Functions  make_<pass-name> (gcc::context*)  where <pass-name> is
+// according to the pass declaration in avr-passes.def.  GCC's pass
+// manager uses these function to create the respective pass object.
+
+// Optimize results of the casesi expander for modes < SImode.
+
+rtl_opt_pass *
+make_avr_pass_casesi (gcc::context *ctxt)
+{
+  return new avr_pass_casesi (ctxt, "avr-casesi");
+}
+
+// Try to replace 2 cbranch insns with 1 comparison and 2 branches.
+
+rtl_opt_pass *
+make_avr_pass_ifelse (gcc::context *ctxt)
+{
+  return new avr_pass_ifelse (ctxt, "avr-ifelse");
+}
+
+// Determine whether an ISR may use the __gcc_isr pseudo-instruction.
+
+rtl_opt_pass *
+make_avr_pass_pre_proep (gcc::context *ctxt)
+{
+  return new avr_pass_pre_proep (ctxt, "avr-pre-proep");
+}
+
+// Find more POST_INC and PRE_DEC cases.
+
+rtl_opt_pass *
+make_avr_pass_fuse_add (gcc::context *ctxt)
+{
+  return new avr_pass_fuse_add (ctxt, "avr-fuse-add");
+}
+
+// Late recomputation of notes so we can use `reg_unused_after()' and friends.
+
+rtl_opt_pass *
+make_avr_pass_recompute_notes (gcc::context *ctxt)
+{
+  return new avr_pass_recompute_notes (ctxt, "avr-notes-free-cfg");
+}
index 34298b976a7f9c526cfcff924cd7d8be16c052db..dec48a55e81720ef3e23dd8a5e4189b190776fb5 100644 (file)
@@ -91,7 +91,6 @@ extern void avr_expand_epilogue (bool);
 extern bool avr_emit_cpymemhi (rtx*);
 extern void avr_emit_xior_with_shift (rtx_insn*, rtx*, int);
 extern int avr_epilogue_uses (int regno);
-extern bool avr_split_tiny_move (rtx_insn *insn, rtx *operands);
 
 extern void avr_output_addr_vec (rtx_insn*, rtx);
 extern const char *avr_out_sbxx_branch (rtx_insn *insn, rtx operands[]);
@@ -134,7 +133,6 @@ extern bool avr_mem_memx_p (rtx);
 extern bool avr_load_libgcc_p (rtx);
 extern bool avr_xload_libgcc_p (machine_mode);
 extern rtx avr_eval_addr_attrib (rtx x);
-extern bool avr_casei_sequence_check_operands (rtx *xop);
 
 extern bool avr_float_lib_compare_returns_bool (machine_mode, enum rtx_code);
 
@@ -163,6 +161,8 @@ extern void asm_output_float (FILE *file, REAL_VALUE_TYPE n);
 
 extern bool avr_have_dimode;
 
+/* From avr-passes.cc */
+
 namespace gcc { class context; }
 class rtl_opt_pass;
 
@@ -171,6 +171,10 @@ extern rtl_opt_pass *make_avr_pass_pre_proep (gcc::context *);
 extern rtl_opt_pass *make_avr_pass_recompute_notes (gcc::context *);
 extern rtl_opt_pass *make_avr_pass_casesi (gcc::context *);
 extern rtl_opt_pass *make_avr_pass_ifelse (gcc::context *);
+#ifdef RTX_CODE
+extern bool avr_casei_sequence_check_operands (rtx *xop);
+extern bool avr_split_tiny_move (rtx_insn *insn, rtx *operands);
+#endif /* RTX_CODE */
 
 /* From avr-log.cc */
 
index 774d2c9978902e97277488b9d20024f9276ce0f7..4a255d15567fd1795d1afd6794d000362b5d93d9 100644 (file)
@@ -20,7 +20,6 @@
 
 #define IN_TARGET_CODE 1
 
-#define INCLUDE_VECTOR
 #include "config.h"
 #include "system.h"
 #include "intl.h"
@@ -34,7 +33,6 @@
 #include "cgraph.h"
 #include "c-family/c-common.h"
 #include "cfghooks.h"
-#include "cfganal.h"
 #include "df.h"
 #include "memmodel.h"
 #include "tm_p.h"
 #include "explow.h"
 #include "expr.h"
 #include "langhooks.h"
-#include "cfgrtl.h"
 #include "builtins.h"
-#include "context.h"
-#include "tree-pass.h"
-#include "print-rtl.h"
 #include "rtl-iter.h"
 
 /* This file should be included last.  */
@@ -339,2762 +333,827 @@ ra_in_progress ()
 }
 
 
-/* Return TRUE iff comparison code CODE is explicitly signed.  */
+/* Set `avr_arch' as specified by `-mmcu='.
+   Return true on success.  */
 
 static bool
-avr_strict_signed_p (rtx_code code)
+avr_set_core_architecture (void)
 {
-  return code == GT || code == GE || code == LT || code == LE;
-}
-
-
-/* Return TRUE iff comparison code CODE is explicitly unsigned.  */
+  /* Search for mcu core architecture.  */
 
-static bool
-avr_strict_unsigned_p (rtx_code code)
-{
-  return code == GTU || code == GEU || code == LTU || code == LEU;
-}
-
-
-/* A class that represents the union of finitely many intervals.
-   The domain over which the intervals are defined is a finite integer
-   interval [m_min, m_max], usually the range of some [u]intN_t.
-   Supported operations are:
-   - Complement w.r.t. the domain (invert)
-   - Union (union_)
-   - Intersection (intersect)
-   - Difference / Setminus (minus).
-   Ranges is closed under all operations:  The result of all operations
-   is a Ranges over the same domain.  (As opposed to value-range.h which
-   may ICE for some operations, see below).
-
-   The representation is unique in the sense that when we have two
-   Ranges A and B, then
-   1)  A == B  <==>  A.size == B.size  &&  Ai == Bi for all i.
-
-   The representation is normalized:
-   2)  Ai != {}          ;; There are no empty intervals.
-   3)  Ai.hi < A{i+1}.lo ;; The Ai's are in increasing order and separated
-                        ;; by at least one value (non-adjacent).
-   The sub-intervals Ai are maintained as a std::vector.
-   The computation of union and intersection scales like  A.size * B.size
-   i.e. Ranges is only eligible for GCC when size() has a fixed upper
-   bound independent of the program being compiled (or there are other
-   means to guarantee that the complexity is linearistic).
-   In the context of avr.cc, we have size() <= 3.
-
-   The reason why we don't use value-range.h's irange or int_range is that
-   these use the integers Z as their domain, which makes computations like
-   invert() quite nasty as they may ICE for common cases.  Doing all
-   these special cases (like one sub-interval touches the domain bounds)
-   makes using value-range.h more laborious (and instable) than using our
-   own mini Ranger.  */
-
-struct Ranges
-{
-  // This is good enough as it covers (un)signed SImode.
-  using T = HOST_WIDE_INT;
-  typedef T scalar_type;
-
-  // Non-empty ranges.  Empty sets are only used transiently;
-  // Ranges.ranges[] doesn't use them.
-  struct SubRange
-  {
-    // Lower and upper bound, inclusively.
-    T lo, hi;
+  if (!avr_mmcu)
+    avr_mmcu = AVR_MMCU_DEFAULT;
 
-    SubRange intersect (const SubRange &r) const
-    {
-      if (lo >= r.lo && hi <= r.hi)
-       return *this;
-      else if (r.lo >= lo && r.hi <= hi)
-       return r;
-      else if (lo > r.hi || hi < r.lo)
-       return SubRange { 1, 0 };
-      else
-       return SubRange { std::max (lo, r.lo), std::min (hi, r.hi) };
-    }
+  avr_arch = &avr_arch_types[0];
 
-    T cardinality () const
+  for (const avr_mcu_t *mcu = avr_mcu_types; ; mcu++)
     {
-      return std::max<T> (0, hi - lo + 1);
-    }
-  };
+      if (mcu->name == NULL)
+       {
+         /* Reached the end of `avr_mcu_types'.  This should actually never
+            happen as options are provided by device-specs.  It could be a
+            typo in a device-specs or calling the compiler proper directly
+            with -mmcu=<device>. */
 
-  // Finitely many intervals over [m_min, m_max] that are normalized:
-  // No empty sets, increasing order, separated by at least one value.
-  T m_min, m_max;
-  std::vector<SubRange> ranges;
+         error ("unknown core architecture %qs specified with %qs",
+                avr_mmcu, "-mmcu=");
+         avr_inform_core_architectures ();
+         break;
+       }
+      else if (strcmp (mcu->name, avr_mmcu) == 0
+              // Is this a proper architecture ?
+              && mcu->macro == NULL)
+       {
+         avr_arch = &avr_arch_types[mcu->arch_id];
+         avr_arch_index = mcu->arch_id;
+         if (avr_n_flash < 0)
+           avr_n_flash = 1 + (mcu->flash_size - 1) / 0x10000;
 
-  // Not used anywhere in Ranges; can be used elsewhere.
-  // May be clobbered by set operations.
-  int tag = -1;
+         return true;
+       }
+    }
 
-  enum initial_range { EMPTY, ALL };
+  return false;
+}
 
-  Ranges (T mi, T ma, initial_range ir)
-    : m_min (mi), m_max (ma)
-  {
-    if (ir == ALL)
-      push (mi, ma);
-  }
 
-  // Domain is the range of some [u]intN_t.
-  static Ranges NBitsRanges (int n_bits, bool unsigned_p, initial_range ir)
-  {
-    T mask = ((T) 1 << n_bits) - 1;
-    gcc_assert (mask > 0);
-    T ma = mask >> ! unsigned_p;
-    return Ranges (unsigned_p ? 0 : -ma - 1, ma, ir);
-  }
+/* Implement `TARGET_OPTION_OVERRIDE'.  */
 
-  static void sort2 (Ranges &a, Ranges &b)
-  {
-    if (a.size () && b.size ())
-      if (a.ranges[0].lo > b.ranges[0].lo)
-       std::swap (a, b);
-  }
+static void
+avr_option_override (void)
+{
+  /* caller-save.cc looks for call-clobbered hard registers that are assigned
+     to pseudos that cross calls and tries so save-restore them around calls
+     in order to reduce the number of stack slots needed.
 
-  void print (FILE *file) const
-  {
-    if (file)
-      {
-       fprintf (file, " .tag%d=#%d={", tag, size ());
-       for (const auto &r : ranges)
-         fprintf (file, "[ %ld, %ld ]", (long) r.lo, (long) r.hi);
-       fprintf (file, "}\n");
-      }
-  }
+     This might lead to situations where reload is no more able to cope
+     with the challenge of AVR's very few address registers and fails to
+     perform the requested spills.  */
 
-  // The number of sub-intervals in .ranges.
-  int size () const
-  {
-    return (int) ranges.size ();
-  }
+  if (avr_strict_X)
+    flag_caller_saves = 0;
 
-  // Append [LO, HI] & [m_min, m_max] to .ranges provided the
-  // former is non-empty.
-  void push (T lo, T hi)
-  {
-    lo = std::max (lo, m_min);
-    hi = std::min (hi, m_max);
+  /* Unwind tables currently require a frame pointer for correctness,
+     see toplev.cc:process_options().  */
 
-    if (lo <= hi)
-      ranges.push_back (SubRange { lo, hi });
-  }
+  if ((flag_unwind_tables
+       || flag_non_call_exceptions
+       || flag_asynchronous_unwind_tables)
+      && !ACCUMULATE_OUTGOING_ARGS)
+    {
+      flag_omit_frame_pointer = 0;
+    }
 
-  // Append R to .ranges provided the former is non-empty.
-  void push (const SubRange &r)
-  {
-    push (r.lo, r.hi);
-  }
+  /* Disable flag_delete_null_pointer_checks if zero is a valid address. */
+  if (targetm.addr_space.zero_address_valid (ADDR_SPACE_GENERIC))
+    flag_delete_null_pointer_checks = 0;
 
-  // Cardinality of the n-th interval.
-  T cardinality (int n) const
-  {
-    return n < size () ? ranges[n].cardinality () : 0;
-  }
+  /* PR ipa/92606: Inter-procedural analysis optimizes data across
+     address-spaces and PROGMEM.  As of v14, the PROGMEM part is
+     still not fixed (and there is still no target hook as proposed
+     in PR92932).  Just disable respective bogus optimization.  */
+  flag_ipa_icf_variables = 0;
 
-  // Check that *this is normalized: .ranges are non-empty, non-overlapping,
-  // non-adjacent and increasing.
-  bool check () const
-  {
-    bool bad = size () && (ranges[0].lo < m_min
-                          || ranges[size () - 1].hi > m_max);
+  if (flag_pic == 1)
+    warning (OPT_fpic, "%<-fpic%> is not supported");
+  if (flag_pic == 2)
+    warning (OPT_fPIC, "%<-fPIC%> is not supported");
+  if (flag_pie == 1)
+    warning (OPT_fpie, "%<-fpie%> is not supported");
+  if (flag_pie == 2)
+    warning (OPT_fPIE, "%<-fPIE%> is not supported");
 
-    for (int n = 0; n < size (); ++n)
-      {
-       bad |= ranges[n].lo > ranges[n].hi;
-       bad |= n > 0 && ranges[n - 1].hi >= ranges[n].lo;
-      }
+#if !defined (HAVE_AS_AVR_MGCCISR_OPTION)
+  avr_gasisr_prologues = 0;
+#endif
 
-    if (bad)
-      print (dump_file);
+  if (!avr_set_core_architecture())
+    return;
 
-    return ! bad;
-  }
+  /* Sould be set by avr-common.cc */
+  gcc_assert (avr_long_double >= avr_double && avr_double >= 32);
 
-  // Intersect A and B according to  (U Ai) & (U Bj) = U (Ai & Bj)
-  // This has quadratic complexity, but also the nice property that
-  // when A and B are normalized, then the result is too.
-  void intersect (const Ranges &r)
-  {
-    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+  /* RAM addresses of some SFRs common to all devices in respective arch. */
 
-    if (this == &r)
-      return;
+  /* SREG: Status Register containing flags like I (global IRQ) */
+  avr_addr.sreg = 0x3F + avr_arch->sfr_offset;
 
-    std::vector<SubRange> rs;
-    std::swap (rs, ranges);
+  /* RAMPZ: Address' high part when loading via ELPM */
+  avr_addr.rampz = 0x3B + avr_arch->sfr_offset;
 
-    for (const auto &a : rs)
-      for (const auto &b : r.ranges)
-       push (a.intersect (b));
-  }
+  avr_addr.rampy = 0x3A + avr_arch->sfr_offset;
+  avr_addr.rampx = 0x39 + avr_arch->sfr_offset;
+  avr_addr.rampd = 0x38 + avr_arch->sfr_offset;
+  avr_addr.ccp = (AVR_TINY ? 0x3C : 0x34) + avr_arch->sfr_offset;
 
-  // Complement w.r.t. the domain [m_min, m_max].
-  void invert ()
-  {
-    std::vector<SubRange> rs;
-    std::swap (rs, ranges);
+  /* SP: Stack Pointer (SP_H:SP_L) */
+  avr_addr.sp_l = 0x3D + avr_arch->sfr_offset;
+  avr_addr.sp_h = avr_addr.sp_l + 1;
 
-    if (rs.size () == 0)
-       push (m_min, m_max);
-    else
-      {
-       push (m_min, rs[0].lo - 1);
+  init_machine_status = avr_init_machine_status;
 
-       for (size_t n = 1; n < rs.size (); ++n)
-         push (rs[n - 1].hi + 1, rs[n].lo - 1);
+  avr_log_set_avr_log();
+}
 
-       push (rs[rs.size () - 1].hi + 1, m_max);
-      }
-  }
+/* Function to set up the backend function structure.  */
 
-  // Set-minus.
-  void minus (const Ranges &r)
-  {
-    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+static struct machine_function *
+avr_init_machine_status (void)
+{
+  return ggc_cleared_alloc<machine_function> ();
+}
 
-    Ranges sub = r;
-    sub.invert ();
-    intersect (sub);
-  }
 
-  // Union of sets.  Not needed in avr.cc but added for completeness.
-  // DeMorgan this for simplicity.
-  void union_ (const Ranges &r)
-  {
-    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+/* Implement `INIT_EXPANDERS'.  */
+/* The function works like a singleton.  */
 
-    if (this != &r)
-      {
-       invert ();
-       minus (r);
-       invert ();
-      }
-  }
+void
+avr_init_expanders (void)
+{
+  for (int regno = REG_0; regno < REG_32; regno ++)
+    all_regs_rtx[regno] = gen_rtx_REG (QImode, regno);
 
-  // Get the truth Ranges for  x <cmp> val.  For example,
-  // LT 3  will return  [m_min, 2].
-  Ranges truth (rtx_code cmp, T val, bool strict = true)
-  {
-    if (strict)
-      {
-       if (avr_strict_signed_p (cmp))
-         gcc_assert (m_min == -m_max - 1);
-       else if (avr_strict_unsigned_p (cmp))
-         gcc_assert (m_min == 0);
+  lpm_reg_rtx  = all_regs_rtx[LPM_REGNO];
+  tmp_reg_rtx  = all_regs_rtx[AVR_TMP_REGNO];
+  zero_reg_rtx = all_regs_rtx[AVR_ZERO_REGNO];
 
-       gcc_assert (IN_RANGE (val, m_min, m_max));
-      }
+  cc_reg_rtx  = gen_rtx_REG (CCmode, REG_CC);
 
-    bool rev = cmp == NE || cmp == LTU || cmp == LT || cmp == GTU || cmp == GT;
-    if (rev)
-      cmp = reverse_condition (cmp);
+  lpm_addr_reg_rtx = gen_rtx_REG (HImode, REG_Z);
 
-    T lo = m_min;
-    T hi = m_max;
+  sreg_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.sreg));
+  rampd_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampd));
+  rampx_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampx));
+  rampy_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampy));
+  rampz_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampz));
 
-    if (cmp == EQ)
-      lo = hi = val;
-    else if (cmp == LEU || cmp == LE)
-      hi = val;
-    else if (cmp == GEU || cmp == GE)
-      lo = val;
-    else
-      gcc_unreachable ();
+  xstring_empty = gen_rtx_CONST_STRING (VOIDmode, "");
+  xstring_e = gen_rtx_CONST_STRING (VOIDmode, "e");
 
-    Ranges rs (m_min, m_max, Ranges::EMPTY);
-    rs.push (lo, hi);
+  /* TINY core does not have regs r10-r16, but avr-dimode.md expects them
+     to be present */
+  if (AVR_TINY)
+    avr_have_dimode = false;
+}
 
-    if (rev)
-      rs.invert ();
 
-    return rs;
-  }
-}; // struct Ranges
+/* Implement `REGNO_REG_CLASS'.  */
+/* Return register class for register R.  */
 
+enum reg_class
+avr_regno_reg_class (int r)
+{
+  static const enum reg_class reg_class_tab[] =
+    {
+      R0_REG,
+      /* r1 - r15 */
+      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
+      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
+      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
+      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
+      /* r16 - r23 */
+      SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
+      SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
+      /* r24, r25 */
+      ADDW_REGS, ADDW_REGS,
+      /* X: r26, 27 */
+      POINTER_X_REGS, POINTER_X_REGS,
+      /* Y: r28, r29 */
+      POINTER_Y_REGS, POINTER_Y_REGS,
+      /* Z: r30, r31 */
+      POINTER_Z_REGS, POINTER_Z_REGS,
+      /* SP: SPL, SPH */
+      STACK_REG, STACK_REG
+    };
 
-/* Suppose the inputs represent a code like
+  if (r <= 33)
+    return reg_class_tab[r];
 
-      if (x <CMP1> XVAL1)  goto way1;
-      if (x <CMP2> XVAL2)  goto way2;
-      way3:;
+  if (r == REG_CC)
+    return CC_REG;
 
-   with two integer mode comparisons where XVAL1 and XVAL2 are CONST_INT.
-   When this can be rewritten in the form
+  return ALL_REGS;
+}
 
-      if (x <cond1> xval)  goto way1;
-      if (x <cond2> xval)  goto way2;
-      way3:;
 
-  then set CMP1 = cond1, CMP2 = cond2, and return xval.  Else return NULL_RTX.
-  When SWAPT is returned true, then way1 and way2 must be swapped.
-  When the incomping SWAPT is false, the outgoing one will be false, too.  */
+/* Implement `TARGET_SCALAR_MODE_SUPPORTED_P'.  */
 
-static rtx
-avr_2comparisons_rhs (rtx_code &cmp1, rtx xval1,
-                     rtx_code &cmp2, rtx xval2,
-                     machine_mode mode, bool &swapt)
+static bool
+avr_scalar_mode_supported_p (scalar_mode mode)
 {
-  const bool may_swapt = swapt;
-  swapt = false;
+  if (ALL_FIXED_POINT_MODE_P (mode))
+    return true;
 
-  //////////////////////////////////////////////////////////////////
-  // Step 0: Decide about signedness, map xval1/2 to the range
-  //         of [un]signed machine mode.
+  if (PSImode == mode)
+    return true;
 
-  const bool signed1_p = avr_strict_signed_p (cmp1);
-  const bool signed2_p = avr_strict_signed_p (cmp2);
-  const bool unsigned1_p = avr_strict_unsigned_p (cmp1);
-  const bool unsigned2_p = avr_strict_unsigned_p (cmp2);
-  const bool signed_p = signed1_p || signed2_p;
-  bool unsigned_p = unsigned1_p || unsigned2_p;
+  return default_scalar_mode_supported_p (mode);
+}
 
-  using T = Ranges::scalar_type;
-  T val1 = INTVAL (xval1);
-  T val2 = INTVAL (xval2);
 
-  if (signed_p + unsigned_p > 1)
-    {
-      // Don't go down that rabbit hole.  When the RHSs are the
-      // same, we can still save one comparison.
-      return val1 == val2 ? xval1 : NULL_RTX;
-    }
+/* Return TRUE if DECL is a VAR_DECL located in flash and FALSE, otherwise.  */
 
-  // Decide about signedness.  When no explicit signedness is present,
-  // then cases that are close to the unsigned boundary like  EQ 0, EQ 1
-  // can also be optimized.
-  if (unsigned_p
-      || (! signed_p && IN_RANGE (val1, -2, 2)))
+static bool
+avr_decl_flash_p (tree decl)
+{
+  if (TREE_CODE (decl) != VAR_DECL
+      || TREE_TYPE (decl) == error_mark_node)
     {
-      unsigned_p = true;
-      val1 = UINTVAL (xval1) & GET_MODE_MASK (mode);
-      val2 = UINTVAL (xval2) & GET_MODE_MASK (mode);
+      return false;
     }
 
-  // No way we can decompose the domain in a usable manner when the
-  // RHSes are too far apart.
-  if (! IN_RANGE (val1 - val2, -2, 2))
-    return NULL_RTX;
-
-  //////////////////////////////////////////////////////////////////
-  // Step 1: Represent the input conditions as truth Ranges.  This
-  //         establishes a decomposition / coloring of the domain.
-
-  Ranges dom = Ranges::NBitsRanges (GET_MODE_BITSIZE (mode), unsigned_p,
-                                   Ranges::ALL);
-  Ranges r[4] = { dom, dom.truth (cmp1, val1), dom.truth (cmp2, val2), dom };
+  return !ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (TREE_TYPE (decl)));
+}
 
-  // r[1] shadows r[2] shadows r[3].  r[0] is just for nice indices.
-  r[3].minus (r[2]);
-  r[3].minus (r[1]);
-  r[2].minus (r[1]);
 
-  //////////////////////////////////////////////////////////////////
-  // Step 2: Filter for cases where the domain decomposes into three
-  //         intervals:  One to the left, one to the right, and one
-  //         in the middle where the latter holds exactly one value.
+/* Return TRUE if DECL is a VAR_DECL located in the 24-bit flash
+   address space and FALSE, otherwise.  */
 
-  for (int i = 1; i <= 3; ++i)
+static bool
+avr_decl_memx_p (tree decl)
+{
+  if (TREE_CODE (decl) != VAR_DECL
+      || TREE_TYPE (decl) == error_mark_node)
     {
-      // Keep track of which Ranges is which.
-      r[i].tag = i;
-
-      gcc_assert (r[i].check ());
-
-      // Filter for proper intervals.  Also return for the empty set,
-      // since cases where [m_min, m_max] decomposes into two intervals
-      // or less have been sorted out by the generic optimizers already,
-      // and hence should not be seen here.  And more than two intervals
-      // at a time cannot be optimized of course.
-      if (r[i].size () != 1)
-       return NULL_RTX;
+      return false;
     }
 
-  // Bubble-sort the three intervals such that:
-  // [1] is the left interval, i.e. the one taken by LT[U].
-  // [2] is the middle interval, i.e. the one taken by EQ.
-  // [3] is the right interval, i.e. the one taken by GT[U].
-  Ranges::sort2 (r[1], r[3]);
-  Ranges::sort2 (r[2], r[3]);
-  Ranges::sort2 (r[1], r[2]);
-
-  if (dump_file)
-    fprintf (dump_file,
-            ";; Decomposed: .%d=[%ld, %ld] .%d=[%ld, %ld] .%d=[%ld, %ld]\n",
-            r[1].tag, (long) r[1].ranges[0].lo, (long) r[1].ranges[0].hi,
-            r[2].tag, (long) r[2].ranges[0].lo, (long) r[2].ranges[0].hi,
-            r[3].tag, (long) r[3].ranges[0].lo, (long) r[3].ranges[0].hi);
-
-  // EQ / NE can handle only one value.
-  if (r[2].cardinality (0) != 1)
-    return NULL_RTX;
-
-  // Success! This is the sought for xval.
-  const T val = r[2].ranges[0].lo;
+  return (ADDR_SPACE_MEMX == TYPE_ADDR_SPACE (TREE_TYPE (decl)));
+}
 
-  //////////////////////////////////////////////////////////////////
-  // Step 3: Work out which label gets which condition, trying to
-  //         avoid the expensive codes GT[U] and LE[U] if possible.
-  //         Avoiding expensive codes is always possible when labels
-  //         way1 and way2 may be swapped.
 
-  // The xx1 ways have an expensive GT for cmp1 which can be avoided
-  // by swapping way1 with way2.
-  swapt = may_swapt && r[3].tag == 1;
-  if (swapt)
-    std::swap (r[3], r[2].tag == 2 ? r[2] : r[1]);
+/* Return TRUE if X is a MEM rtx located in flash and FALSE, otherwise.  */
 
-  // 6 = 3! ways to assign LT, EQ, GT to the three labels.
-  const int way = 100 * r[1].tag + 10 * r[2].tag + r[3].tag;
+bool
+avr_mem_flash_p (rtx x)
+{
+  return (MEM_P (x)
+         && !ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (x)));
+}
 
-  if (dump_file)
-    fprintf (dump_file, ";; Success: unsigned=%d, swapt=%d, way=%d, rhs=%ld\n",
-            unsigned_p, swapt, way, (long) val);
 
-#define WAY(w, c1, c2)                                 \
-  case w:                                              \
-    cmp1 = unsigned_p ? unsigned_condition (c1) : c1;  \
-    cmp2 = unsigned_p ? unsigned_condition (c2) : c2;  \
-    break;
+/* Return TRUE if X is a MEM rtx located in the 24-bit flash
+   address space and FALSE, otherwise.  */
 
-  switch (way)
-    {
-    default:
-      gcc_unreachable();
-
-      // cmp1 gets the LT, avoid difficult branches for cmp2.
-      WAY (123, LT, EQ);
-      WAY (132, LT, NE);
-
-      // cmp1 gets the EQ, avoid difficult branches for cmp2.
-      WAY (213, EQ, LT);
-      WAY (312, EQ, GE);
-
-      // cmp1 gets the difficult GT, unavoidable as we may not swap way1/2.
-      WAY (231, GT, NE);
-      WAY (321, GT, EQ);
-    }
-
-#undef WAY
-
-  return gen_int_mode (val, mode);
-}
-
-
-namespace {
-
-static const pass_data avr_pass_data_recompute_notes =
-{
-  RTL_PASS,      // type
-  "",            // name (will be patched)
-  OPTGROUP_NONE, // optinfo_flags
-  TV_DF_SCAN,    // tv_id
-  0,             // properties_required
-  0,             // properties_provided
-  0,             // properties_destroyed
-  0,             // todo_flags_start
-  TODO_df_finish | TODO_df_verify // todo_flags_finish
-};
-
-
-class avr_pass_recompute_notes : public rtl_opt_pass
-{
-public:
-  avr_pass_recompute_notes (gcc::context *ctxt, const char *name)
-    : rtl_opt_pass (avr_pass_data_recompute_notes, ctxt)
-  {
-    this->name = name;
-  }
-
-  virtual unsigned int execute (function *)
-  {
-    df_note_add_problem ();
-    df_analyze ();
-
-    return 0;
-  }
-}; // avr_pass_recompute_notes
-
-static const pass_data avr_pass_data_casesi =
-{
-  RTL_PASS,      // type
-  "",            // name (will be patched)
-  OPTGROUP_NONE, // optinfo_flags
-  TV_DF_SCAN,    // tv_id
-  0,             // properties_required
-  0,             // properties_provided
-  0,             // properties_destroyed
-  0,             // todo_flags_start
-  0              // todo_flags_finish
-};
-
-
-class avr_pass_casesi : public rtl_opt_pass
-{
-public:
-  avr_pass_casesi (gcc::context *ctxt, const char *name)
-    : rtl_opt_pass (avr_pass_data_casesi, ctxt)
-  {
-    this->name = name;
-  }
-
-  void avr_rest_of_handle_casesi (function *);
-
-  virtual bool gate (function *) { return optimize > 0; }
-
-  virtual unsigned int execute (function *func)
-  {
-    avr_rest_of_handle_casesi (func);
-
-    return 0;
-  }
-}; // avr_pass_casesi
-
-
-static const pass_data avr_pass_data_ifelse =
-{
-  RTL_PASS,      // type
-  "",            // name (will be patched)
-  OPTGROUP_NONE, // optinfo_flags
-  TV_DF_SCAN,    // tv_id
-  0,             // properties_required
-  0,             // properties_provided
-  0,             // properties_destroyed
-  0,             // todo_flags_start
-  TODO_df_finish | TODO_df_verify // todo_flags_finish
-};
-
-class avr_pass_ifelse : public rtl_opt_pass
-{
-public:
-  avr_pass_ifelse (gcc::context *ctxt, const char *name)
-    : rtl_opt_pass (avr_pass_data_ifelse, ctxt)
-  {
-    this->name = name;
-  }
-
-  void avr_rest_of_handle_ifelse (function *);
-
-  virtual bool gate (function *) { return optimize > 0; }
-
-  virtual unsigned int execute (function *func)
-  {
-    avr_rest_of_handle_ifelse (func);
-
-    return 0;
-  }
-}; // avr_pass_ifelse
-
-} // anon namespace
-
-rtl_opt_pass *
-make_avr_pass_recompute_notes (gcc::context *ctxt)
-{
-  return new avr_pass_recompute_notes (ctxt, "avr-notes-free-cfg");
-}
-
-rtl_opt_pass *
-make_avr_pass_casesi (gcc::context *ctxt)
-{
-  return new avr_pass_casesi (ctxt, "avr-casesi");
-}
-
-rtl_opt_pass *
-make_avr_pass_ifelse (gcc::context *ctxt)
-{
-  return new avr_pass_ifelse (ctxt, "avr-ifelse");
-}
-
-
-/* Make one parallel insn with all the patterns from insns i[0]..i[5].  */
-
-static rtx_insn *
-avr_parallel_insn_from_insns (rtx_insn *i[5])
-{
-  rtvec vec = gen_rtvec (5, PATTERN (i[0]), PATTERN (i[1]), PATTERN (i[2]),
-                        PATTERN (i[3]), PATTERN (i[4]));
-  start_sequence();
-  emit (gen_rtx_PARALLEL (VOIDmode, vec));
-  rtx_insn *insn = get_insns();
-  end_sequence();
-
-  return insn;
-}
-
-
-/* Return true if we see an insn stream generated by casesi expander together
-   with an extension to SImode of the switch value.
-
-   If this is the case, fill in the insns from casesi to INSNS[1..5] and
-   the SImode extension to INSNS[0].  Moreover, extract the operands of
-   pattern casesi_<mode>_sequence forged from the sequence to recog_data.  */
-
-static bool
-avr_is_casesi_sequence (basic_block bb, rtx_insn *insn, rtx_insn *insns[5])
+bool
+avr_mem_memx_p (rtx x)
 {
-  rtx set_4, set_0;
-
-  /* A first and quick test for a casesi sequences.  As a side effect of
-     the test, harvest respective insns to INSNS[0..4].  */
-
-  if (!(JUMP_P (insns[4] = insn)
-       // casesi is the only insn that comes up with UNSPEC_INDEX_JMP,
-       // hence the following test ensures that we are actually dealing
-       // with code from casesi.
-       && (set_4 = single_set (insns[4]))
-       && UNSPEC == GET_CODE (SET_SRC (set_4))
-       && UNSPEC_INDEX_JMP == XINT (SET_SRC (set_4), 1)
-
-       && (insns[3] = prev_real_insn (insns[4]))
-       && (insns[2] = prev_real_insn (insns[3]))
-       && (insns[1] = prev_real_insn (insns[2]))
-
-       // Insn prior to casesi.
-       && (insns[0] = prev_real_insn (insns[1]))
-       && (set_0 = single_set (insns[0]))
-       && extend_operator (SET_SRC (set_0), SImode)))
-    {
-      return false;
-    }
-
-  if (dump_file)
-    {
-      fprintf (dump_file, ";; Sequence from casesi in "
-              "[bb %d]:\n\n", bb->index);
-      for (int i = 0; i < 5; i++)
-       print_rtl_single (dump_file, insns[i]);
-    }
-
-  /* We have to deal with quite some operands.  Extracting them by hand
-     would be tedious, therefore wrap the insn patterns into a parallel,
-     run recog against it and then use insn extract to get the operands. */
-
-  rtx_insn *xinsn = avr_parallel_insn_from_insns (insns);
-
-  INSN_CODE (xinsn) = recog (PATTERN (xinsn), xinsn, NULL /* num_clobbers */);
-
-  /* Failing to recognize means that someone changed the casesi expander or
-     that some passes prior to this one performed some unexpected changes.
-     Gracefully drop such situations instead of aborting.  */
-
-  if (INSN_CODE (xinsn) < 0)
-    {
-      if (dump_file)
-       fprintf (dump_file, ";; Sequence not recognized, giving up.\n\n");
-
-      return false;
-    }
-
-  gcc_assert (CODE_FOR_casesi_qi_sequence == INSN_CODE (xinsn)
-             || CODE_FOR_casesi_hi_sequence == INSN_CODE (xinsn));
-
-  extract_insn (xinsn);
-
-  // Assert on the anatomy of xinsn's operands we are going to work with.
-
-  gcc_assert (recog_data.n_operands == 11);
-  gcc_assert (recog_data.n_dups == 4);
-
-  if (dump_file)
-    {
-      fprintf (dump_file, ";; Operands extracted:\n");
-      for (int i = 0; i < recog_data.n_operands; i++)
-       avr_fdump (dump_file, ";; $%d = %r\n", i, recog_data.operand[i]);
-      fprintf (dump_file, "\n");
-    }
-
-  return true;
+  return (MEM_P (x)
+         && ADDR_SPACE_MEMX == MEM_ADDR_SPACE (x));
 }
 
 
-/* Perform some extra checks on operands of casesi_<mode>_sequence.
-   Not all operand dependencies can be described by means of predicates.
-   This function performs left over checks and should always return true.
-   Returning false means that someone changed the casesi expander but did
-   not adjust casesi_<mode>_sequence.  */
+/* A helper for the subsequent function attribute used to dig for
+   attribute 'name' in a FUNCTION_DECL or FUNCTION_TYPE */
 
-bool
-avr_casei_sequence_check_operands (rtx *xop)
+static inline bool
+avr_lookup_function_attribute1 (const_tree func, const char *name)
 {
-  rtx sub_5 = NULL_RTX;
-
-  if (AVR_HAVE_EIJMP_EICALL
-      // The last clobber op of the tablejump.
-      && xop[8] == all_regs_rtx[REG_24])
+  if (FUNCTION_DECL == TREE_CODE (func))
     {
-      // $6 is: (subreg:SI ($5) 0)
-      sub_5 = xop[6];
-    }
+      if (NULL_TREE != lookup_attribute (name, DECL_ATTRIBUTES (func)))
+       {
+         return true;
+       }
 
-  if (!AVR_HAVE_EIJMP_EICALL
-      // $6 is: (plus:HI (subreg:SI ($5) 0)
-      //                (label_ref ($3)))
-      && PLUS == GET_CODE (xop[6])
-      && LABEL_REF == GET_CODE (XEXP (xop[6], 1))
-      && rtx_equal_p (xop[3], XEXP (XEXP (xop[6], 1), 0))
-      // The last clobber op of the tablejump.
-      && xop[8] == const0_rtx)
-    {
-      sub_5 = XEXP (xop[6], 0);
+      func = TREE_TYPE (func);
     }
 
-  if (sub_5
-      && SUBREG_P (sub_5)
-      && SUBREG_BYTE (sub_5) == 0
-      && rtx_equal_p (xop[5], SUBREG_REG (sub_5)))
-    return true;
-
-  if (dump_file)
-    fprintf (dump_file, "\n;; Failed condition for casesi_<mode>_sequence\n\n");
+  gcc_assert (FUNC_OR_METHOD_TYPE_P (func));
 
-  return false;
+  return NULL_TREE != lookup_attribute (name, TYPE_ATTRIBUTES (func));
 }
 
 
-/* INSNS[1..4] is a sequence as generated by casesi and INSNS[0] is an
-   extension of an 8-bit or 16-bit integer to SImode.  XOP contains the
-   operands of INSNS as extracted by insn_extract from pattern
-   casesi_<mode>_sequence:
-
-      $0: SImode reg switch value as result of $9.
-      $1: Negative of smallest index in switch.
-      $2: Number of entries in switch.
-      $3: Label to table.
-      $4: Label if out-of-bounds.
-      $5: $0 + $1.
-      $6: 3-byte PC: subreg:HI ($5) + label_ref ($3)
-         2-byte PC: subreg:HI ($5)
-      $7: HI reg index into table (Z or pseudo)
-      $8: R24 or const0_rtx (to be clobbered)
-      $9: Extension to SImode of an 8-bit or 16-bit integer register $10.
-      $10: QImode or HImode register input of $9.
-
-   Try to optimize this sequence, i.e. use the original HImode / QImode
-   switch value instead of SImode.  */
+/* Call WORKER on all NAME attributes of function FUNC.  */
 
 static void
-avr_optimize_casesi (rtx_insn *insns[5], rtx *xop)
+avr_foreach_function_attribute (tree func, const char *name,
+                               void (*worker) (tree, tree, void *),
+                               void *cookie)
 {
-  // Original mode of the switch value; this is QImode or HImode.
-  machine_mode mode = GET_MODE (xop[10]);
-
-  // How the original switch value was extended to SImode; this is
-  // SIGN_EXTEND or ZERO_EXTEND.
-  enum rtx_code code = GET_CODE (xop[9]);
-
-  // Lower index, upper index (plus one) and range of case calues.
-  HOST_WIDE_INT low_idx = -INTVAL (xop[1]);
-  HOST_WIDE_INT num_idx = INTVAL (xop[2]);
-  HOST_WIDE_INT hig_idx = low_idx + num_idx;
-
-  // Maximum ranges of (un)signed QImode resp. HImode.
-  unsigned umax = QImode == mode ? 0xff : 0xffff;
-  int imax = QImode == mode ? 0x7f : 0x7fff;
-  int imin = -imax - 1;
-
-  // Testing the case range and whether it fits into the range of the
-  // (un)signed mode.  This test should actually always pass because it
-  // makes no sense to have case values outside the mode range.  Notice
-  // that case labels which are unreachable because they are outside the
-  // mode of the switch value (e.g. "case -1" for uint8_t) have already
-  // been thrown away by the middle-end.
-
-  if (SIGN_EXTEND == code
-      && low_idx >= imin
-      && hig_idx <= imax)
-    {
-      // ok
-    }
-  else if (ZERO_EXTEND == code
-          && low_idx >= 0
-          && (unsigned) hig_idx <= umax)
-    {
-      // ok
-    }
-  else
-    {
-      if (dump_file)
-       fprintf (dump_file, ";; Case ranges too big, giving up.\n\n");
-      return;
-    }
-
-  // Do normalization of switch value $10 and out-of-bound check in its
-  // original mode instead of in SImode.  Use a newly created pseudo.
-  // This will replace insns[1..2].
-
-  start_sequence();
-
-  rtx reg = copy_to_mode_reg (mode, xop[10]);
-
-  rtx (*gen_add)(rtx,rtx,rtx) = QImode == mode ? gen_addqi3 : gen_addhi3;
-  rtx (*gen_cbranch)(rtx,rtx,rtx,rtx)
-    = QImode == mode ? gen_cbranchqi4 : gen_cbranchhi4;
-
-  emit_insn (gen_add (reg, reg, gen_int_mode (-low_idx, mode)));
-  rtx op0 = reg; rtx op1 = gen_int_mode (num_idx, mode);
-  rtx labelref = copy_rtx (xop[4]);
-  rtx xbranch = gen_cbranch (gen_rtx_fmt_ee (GTU, VOIDmode, op0, op1),
-                            op0, op1, labelref);
-  rtx_insn *cbranch = emit_jump_insn (xbranch);
-  JUMP_LABEL (cbranch) = xop[4];
-  ++LABEL_NUSES (xop[4]);
-
-  rtx_insn *seq1 = get_insns();
-  rtx_insn *last1 = get_last_insn();
-  end_sequence();
-
-  emit_insn_after (seq1, insns[2]);
-
-  // After the out-of-bounds test and corresponding branch, use a
-  // 16-bit index.  If QImode is used, extend it to HImode first.
-  // This will replace insns[4].
-
-  start_sequence();
-
-  if (QImode == mode)
-    reg = force_reg (HImode, gen_rtx_fmt_e (code, HImode, reg));
-
-  rtx pat_4 = AVR_3_BYTE_PC
-    ? gen_movhi (xop[7], reg)
-    : gen_addhi3 (xop[7], reg, gen_rtx_LABEL_REF (VOIDmode, xop[3]));
-
-  emit_insn (pat_4);
-
-  rtx_insn *seq2 = get_insns();
-  rtx_insn *last2 = get_last_insn();
-  end_sequence();
+  tree attrs = NULL_TREE;
 
-  emit_insn_after (seq2, insns[3]);
+  if (TREE_CODE (func) == FUNCTION_DECL)
+    attrs = DECL_ATTRIBUTES (func);
+  else if (FUNC_OR_METHOD_TYPE_P (func))
+    attrs = TYPE_ATTRIBUTES (TREE_TYPE (func));
 
-  if (dump_file)
+  while (attrs)
     {
-      fprintf (dump_file, ";; New insns: ");
-
-      for (rtx_insn *insn = seq1; ; insn = NEXT_INSN (insn))
-       {
-         fprintf (dump_file, "%d, ", INSN_UID (insn));
-         if (insn == last1)
-           break;
-       }
-      for (rtx_insn *insn = seq2; ; insn = NEXT_INSN (insn))
+      attrs = lookup_attribute (name, attrs);
+      if (attrs)
        {
-         fprintf (dump_file, "%d%s", INSN_UID (insn),
-                  insn == last2 ? ".\n\n" : ", ");
-         if (insn == last2)
-           break;
+         worker (func, attrs, cookie);
+         attrs = TREE_CHAIN (attrs);
        }
-
-      fprintf (dump_file, ";; Deleting insns: %d, %d, %d.\n\n",
-              INSN_UID (insns[1]), INSN_UID (insns[2]), INSN_UID (insns[3]));
     }
-
-  // Pseudodelete the SImode and subreg of SImode insns.  We don't care
-  // about the extension insns[0]: Its result is now unused and other
-  // passes will clean it up.
-
-  SET_INSN_DELETED (insns[1]);
-  SET_INSN_DELETED (insns[2]);
-  SET_INSN_DELETED (insns[3]);
 }
 
 
-void
-avr_pass_casesi::avr_rest_of_handle_casesi (function *func)
-{
-  basic_block bb;
-
-  FOR_EACH_BB_FN (bb, func)
-    {
-      rtx_insn *insn, *insns[5];
+/* Return nonzero if FUNC is a naked function.  */
 
-      FOR_BB_INSNS (bb, insn)
-       {
-         if (avr_is_casesi_sequence (bb, insn, insns))
-           {
-             avr_optimize_casesi (insns, recog_data.operand);
-           }
-       }
-    }
+static bool
+avr_naked_function_p (tree func)
+{
+  return avr_lookup_function_attribute1 (func, "naked");
 }
 
+/* Return nonzero if FUNC is a noblock function.  */
 
-/* A helper for the next method.  Suppose we have two conditional branches
-   with REG and CONST_INT operands
-
-      if (reg <cond1> xval1) goto label1;
-      if (reg <cond2> xval2) goto label2;
-
-   If the second comparison is redundant and there are codes <cmp1>
-   and <cmp2> such that the sequence can be performed as
-
-      REG_CC = compare (reg, xval);
-      if (REG_CC <cmp1> 0) goto label1;
-      if (REG_CC <cmp2> 0) goto label2;
-
-   then set COND1 to cmp1, COND2 to cmp2, SWAPT to true when the branch
-   targets have to be swapped, and return XVAL.  Otherwise, return NULL_RTX.
-   This function may clobber COND1 and COND2 even when it returns NULL_RTX.
-
-   REVERSE_COND1 can be set to reverse condition COND1.  This is useful
-   when the second comparison does not follow the first one, but is
-   located after label1 like in:
-
-      if (reg <cond1> xval1) goto label1;
-      ...
-      label1:
-      if (reg <cond2> xval2) goto label2;
-
-   In such a case we cannot swap the labels, and we may end up with a
-   difficult branch -- though one comparison can still be optimized out.
-   Getting rid of such difficult branches would require to reorder blocks. */
-
-static rtx
-avr_redundant_compare (rtx xreg1, rtx_code &cond1, rtx xval1,
-                      rtx xreg2, rtx_code &cond2, rtx xval2,
-                      bool reverse_cond1, bool &swapt)
-{
-  // Make sure we have two REG <cond> CONST_INT comparisons with the same reg.
-  if (! rtx_equal_p (xreg1, xreg2)
-      || ! CONST_INT_P (xval1)
-      || ! CONST_INT_P (xval2))
-    return NULL_RTX;
-
-  if (reverse_cond1)
-    cond1 = reverse_condition (cond1);
-
-  // Allow swapping label1 <-> label2 only when ! reverse_cond1.
-  swapt = ! reverse_cond1;
-  rtx_code c1 = cond1;
-  rtx_code c2 = cond2;
-  rtx xval = avr_2comparisons_rhs (c1, xval1,
-                                  c2, xval2, GET_MODE (xreg1), swapt);
-  if (! xval)
-    return NULL_RTX;
-
-  if (dump_file)
-    {
-      rtx_code a1 = reverse_cond1 ? reverse_condition (cond1) : cond1;
-      rtx_code b1 = reverse_cond1 ? reverse_condition (c1) : c1;
-      const char *s_rev1 = reverse_cond1 ? " reverse_cond1" : "";
-      avr_dump (";; cond1: %C %r%s\n", a1, xval1, s_rev1);
-      avr_dump (";; cond2: %C %r\n", cond2, xval2);
-      avr_dump (";; => %C %d\n", b1, (int) INTVAL (xval));
-      avr_dump (";; => %C %d\n", c2, (int) INTVAL (xval));
-    }
-
-  cond1 = c1;
-  cond2 = c2;
-
-  return xval;
+static bool
+avr_noblock_function_p (tree func)
+{
+  return avr_lookup_function_attribute1 (func, "noblock");
 }
 
+/* Return 1 if FUNC is a function that has a "ATTR_NAME" attribute
+   (and perhaps also "ATTR_NAME(num)" attributes.  Return -1 if FUNC has
+   "ATTR_NAME(num)" attribute(s) but no "ATTR_NAME" attribute.
+   When no form of ATTR_NAME is present, return 0.  */
 
-/* Similar to the function above, but assume that
-
-      if (xreg1 <cond1> xval1) goto label1;
-      if (xreg2 <cond2> xval2) goto label2;
-
-   are two subsequent REG-REG comparisons.  When this can be represented as
-
-      REG_CC = compare (reg, xval);
-      if (REG_CC <cmp1> 0) goto label1;
-      if (REG_CC <cmp2> 0) goto label2;
-
-   then set XREG1 to reg, COND1 and COND2 accordingly, and return xval.
-   Otherwise, return NULL_RTX.  This optmization can be performed
-   when { xreg1, xval1 } and { xreg2, xval2 } are equal as sets.
-   It can be done in such a way that no difficult branches occur.  */
-
-static rtx
-avr_redundant_compare_regs (rtx &xreg1, rtx_code &cond1, rtx &xval1,
-                           rtx &xreg2, rtx_code &cond2, rtx &xval2,
-                           bool reverse_cond1)
+static int
+avr_interrupt_signal_function (tree func, const char *attr_name)
 {
-  bool swapped;
-
-  if (! REG_P (xval1))
-    return NULL_RTX;
-  else if (rtx_equal_p (xreg1, xreg2)
-          && rtx_equal_p (xval1, xval2))
-    swapped = false;
-  else if (rtx_equal_p (xreg1, xval2)
-          && rtx_equal_p (xreg2, xval1))
-    swapped = true;
-  else
-    return NULL_RTX;
-
-  // Found a redundant REG-REG comparison.  Assume that the incoming
-  // representation has been canonicalized by CANONICALIZE_COMPARISON.
-  // We can always represent this using only one comparison and in such
-  // a way that no difficult branches are required.
-
-  if (dump_file)
-    {
-      const char *s_rev1 = reverse_cond1 ? " reverse_cond1" : "";
-      avr_dump (";; %r %C %r%s\n", xreg1, cond1, xval1, s_rev1);
-      avr_dump (";; %r %C %r\n", xreg2, cond2, xval2);
-    }
-
-  if (reverse_cond1)
-    cond1 = reverse_condition (cond1);
+  int res = 0;
 
-  if (swapped)
+  avr_foreach_function_attribute (func, attr_name,
+    [] (tree, tree attr, void *cookie)
     {
-      if (cond1 == EQ || cond1 == NE)
-       {
-         avr_dump (";; case #21\n");
-         std::swap (xreg1, xval1);
-       }
-      else
-       {
-         std::swap (xreg2, xval2);
-         cond2 = swap_condition (cond2);
+      int *pcook = (int *) cookie;
 
-         // The swap may have introduced a difficult comparison.
-         // In order to get of it, only a few cases need extra care.
-         if ((cond1 == LT && cond2 == GT)
-             || (cond1 == LTU && cond2 == GTU))
-           {
-             avr_dump (";; case #22\n");
-             cond2 = NE;
-           }
-         else
-           avr_dump (";; case #23\n");
-       }
-    }
-  else
-    avr_dump (";; case #20\n");
+      *pcook = TREE_VALUE (attr)
+       ? *pcook ? *pcook : -1
+       : 1;
+    }, &res);
 
-  return xval1;
+  return res;
 }
 
 
-/* INSN1 and INSN2 are two cbranch insns for the same integer mode.
-   When FOLLOW_LABEL1 is false, then INSN2 is located in the fallthrough
-   path of INSN1.  When FOLLOW_LABEL1 is true, then INSN2 is located at
-   the true edge of INSN1, INSN2 is preceded by a barrier, and no other
-   edge leads to the basic block of INSN2.
-
-   Try to replace INSN1 and INSN2 by a compare insn and two branch insns.
-   When such a replacement has been performed, then return the insn where the
-   caller should continue scanning the insn stream.  Else, return nullptr.  */
-
-static rtx_insn*
-avr_optimize_2ifelse (rtx_jump_insn *insn1,
-                     rtx_jump_insn *insn2, bool follow_label1)
-{
-  avr_dump (";; Investigating jump_insn %d and jump_insn %d.\n",
-           INSN_UID (insn1), INSN_UID (insn2));
-
-  // Extract the operands of the insns:
-  // $0 = comparison operator ($1, $2)
-  // $1 = reg
-  // $2 = reg or const_int
-  // $3 = code_label
-  // $4 = optional SCRATCH for HI, PSI, SI cases.
-
-  const auto &op = recog_data.operand;
-
-  extract_insn (insn1);
-  rtx xop1[5] = { op[0], op[1], op[2], op[3], op[4] };
-  int n_operands = recog_data.n_operands;
-
-  extract_insn (insn2);
-  rtx xop2[5] = { op[0], op[1], op[2], op[3], op[4] };
-
-  rtx_code code1 = GET_CODE (xop1[0]);
-  rtx_code code2 = GET_CODE (xop2[0]);
-  bool swap_targets = false;
-
-  // Search redundant REG-REG comparison.
-  rtx xval = avr_redundant_compare_regs (xop1[1], code1, xop1[2],
-                                        xop2[1], code2, xop2[2],
-                                        follow_label1);
-
-  // Search redundant REG-CONST_INT comparison.
-  if (! xval)
-    xval = avr_redundant_compare (xop1[1], code1, xop1[2],
-                                 xop2[1], code2, xop2[2],
-                                 follow_label1, swap_targets);
-  if (! xval)
-    {
-      avr_dump (";; Nothing found for jump_insn %d and jump_insn %d.\n",
-               INSN_UID (insn1), INSN_UID (insn2));
-      return nullptr;
-    }
-
-  if (follow_label1)
-    code1 = reverse_condition (code1);
-
-  //////////////////////////////////////////////////////
-  // Found a replacement.
-
-  if (dump_file)
-    {
-      avr_dump (";; => %C %r\n", code1, xval);
-      avr_dump (";; => %C %r\n", code2, xval);
-
-      fprintf (dump_file, "\n;; Found chain of jump_insn %d and"
-              " jump_insn %d, follow_label1=%d:\n",
-              INSN_UID (insn1), INSN_UID (insn2), follow_label1);
-      print_rtl_single (dump_file, PATTERN (insn1));
-      print_rtl_single (dump_file, PATTERN (insn2));
-    }
-
-  rtx_insn *next_insn
-    = next_nonnote_nondebug_insn (follow_label1 ? insn1 : insn2);
-
-  // Pop the new branch conditions and the new comparison.
-  // Prematurely split into compare + branch so that we can drop
-  // the 2nd comparison.  The following pass, split2, splits all
-  // insns for REG_CC, and it should still work as usual even when
-  // there are already some REG_CC insns around.
-
-  rtx xcond1 = gen_rtx_fmt_ee (code1, VOIDmode, cc_reg_rtx, const0_rtx);
-  rtx xcond2 = gen_rtx_fmt_ee (code2, VOIDmode, cc_reg_rtx, const0_rtx);
-  rtx xpat1 = gen_branch (xop1[3], xcond1);
-  rtx xpat2 = gen_branch (xop2[3], xcond2);
-  rtx xcompare = NULL_RTX;
-  machine_mode mode = GET_MODE (xop1[1]);
-
-  if (mode == QImode)
-    {
-      gcc_assert (n_operands == 4);
-      xcompare = gen_cmpqi3 (xop1[1], xval);
-    }
-  else
-    {
-      gcc_assert (n_operands == 5);
-      rtx scratch = GET_CODE (xop1[4]) == SCRATCH ? xop2[4] : xop1[4];
-      rtx (*gen_cmp)(rtx,rtx,rtx)
-       = mode == HImode  ? gen_gen_comparehi
-       : mode == PSImode ? gen_gen_comparepsi
-       : gen_gen_comparesi; // SImode
-      xcompare = gen_cmp (xop1[1], xval, scratch);
-    }
-
-  // Emit that stuff.
-
-  rtx_insn *cmp = emit_insn_before (xcompare, insn1);
-  rtx_jump_insn *branch1 = emit_jump_insn_after (xpat1, insn1);
-  rtx_jump_insn *branch2 = emit_jump_insn_after (xpat2, insn2);
-
-  JUMP_LABEL (branch1) = xop1[3];
-  JUMP_LABEL (branch2) = xop2[3];
-  // delete_insn() decrements LABEL_NUSES when deleting a JUMP_INSN,
-  // but when we pop a new JUMP_INSN, do it by hand.
-  ++LABEL_NUSES (xop1[3]);
-  ++LABEL_NUSES (xop2[3]);
-
-  delete_insn (insn1);
-  delete_insn (insn2);
-
-  if (swap_targets)
-    {
-      gcc_assert (! follow_label1);
-
-      basic_block to1 = BLOCK_FOR_INSN (xop1[3]);
-      basic_block to2 = BLOCK_FOR_INSN (xop2[3]);
-      edge e1 = find_edge (BLOCK_FOR_INSN (branch1), to1);
-      edge e2 = find_edge (BLOCK_FOR_INSN (branch2), to2);
-      gcc_assert (e1);
-      gcc_assert (e2);
-      redirect_edge_and_branch (e1, to2);
-      redirect_edge_and_branch (e2, to1);
-    }
-
-  // As a side effect, also recog the new insns.
-  gcc_assert (valid_insn_p (cmp));
-  gcc_assert (valid_insn_p (branch1));
-  gcc_assert (valid_insn_p (branch2));
-
-  return next_insn;
-}
-
-
-/* Sequences like
-
-      SREG = compare (reg, 1 + val);
-         if (SREG >= 0)  goto label1;
-      SREG = compare (reg, val);
-         if (SREG == 0)  goto label2;
-
-   can be optimized to
-
-      SREG = compare (reg, val);
-         if (SREG == 0)  goto label2;
-         if (SREG >= 0)  goto label1;
-
-   Almost all cases where one of the comparisons is redundant can
-   be transformed in such a way that only one comparison is required
-   and no difficult branches are needed.  */
-
-void
-avr_pass_ifelse::avr_rest_of_handle_ifelse (function *)
-{
-  rtx_insn *next_insn;
-
-  for (rtx_insn *insn = get_insns(); insn; insn = next_insn)
-    {
-      next_insn = next_nonnote_nondebug_insn (insn);
-
-      if (! next_insn)
-       break;
-
-      // Search for two cbranch insns.  The first one is a cbranch.
-      // Filter for "cbranch<mode>4_insn" with mode in QI, HI, PSI, SI.
-
-      if (! JUMP_P (insn))
-       continue;
-
-      int icode1 = recog_memoized (insn);
-
-      if (icode1 != CODE_FOR_cbranchqi4_insn
-         && icode1 != CODE_FOR_cbranchhi4_insn
-         && icode1 != CODE_FOR_cbranchpsi4_insn
-         && icode1 != CODE_FOR_cbranchsi4_insn)
-       continue;
-
-      rtx_jump_insn *insn1 = as_a<rtx_jump_insn *> (insn);
-
-      // jmp[0]: We can optimize cbranches that follow cbranch insn1.
-      rtx_insn *jmp[2] = { next_insn, nullptr };
-
-      // jmp[1]: A cbranch following the label of cbranch insn1.
-      if (LABEL_NUSES (JUMP_LABEL (insn1)) == 1)
-       {
-         rtx_insn *code_label1 = JUMP_LABEL_AS_INSN (insn1);
-         rtx_insn *barrier = prev_nonnote_nondebug_insn (code_label1);
-
-         // When the target label of insn1 is used exactly once and is
-         // not a fallthrough, i.e. is preceded by a barrier, then
-         // consider the insn following that label.
-         if (barrier && BARRIER_P (barrier))
-           jmp[1] = next_nonnote_nondebug_insn (code_label1);
-      }
-
-      // With almost certainty, only one of the two possible jumps can
-      // be optimized with insn1, but it's hard to tell which one a priori.
-      // Just try both.  In the unlikely case where both could be optimized,
-      // prefer jmp[0] because eliminating difficult branches is impeded
-      // by following label1.
-
-      for (int j = 0; j < 2; ++j)
-       if (jmp[j] && JUMP_P (jmp[j])
-           && recog_memoized (jmp[j]) == icode1)
-         {
-           rtx_insn *next
-             = avr_optimize_2ifelse (insn1, as_a<rtx_jump_insn *> (jmp[j]),
-                                     j == 1 /* follow_label1 */);
-           if (next)
-             {
-               next_insn = next;
-               break;
-             }
-         }
-
-    } // loop insns
-}
-
-
-/* Set `avr_arch' as specified by `-mmcu='.
-   Return true on success.  */
-
-static bool
-avr_set_core_architecture (void)
-{
-  /* Search for mcu core architecture.  */
-
-  if (!avr_mmcu)
-    avr_mmcu = AVR_MMCU_DEFAULT;
-
-  avr_arch = &avr_arch_types[0];
-
-  for (const avr_mcu_t *mcu = avr_mcu_types; ; mcu++)
-    {
-      if (mcu->name == NULL)
-       {
-         /* Reached the end of `avr_mcu_types'.  This should actually never
-            happen as options are provided by device-specs.  It could be a
-            typo in a device-specs or calling the compiler proper directly
-            with -mmcu=<device>. */
-
-         error ("unknown core architecture %qs specified with %qs",
-                avr_mmcu, "-mmcu=");
-         avr_inform_core_architectures ();
-         break;
-       }
-      else if (strcmp (mcu->name, avr_mmcu) == 0
-              // Is this a proper architecture ?
-              && mcu->macro == NULL)
-       {
-         avr_arch = &avr_arch_types[mcu->arch_id];
-         avr_arch_index = mcu->arch_id;
-         if (avr_n_flash < 0)
-           avr_n_flash = 1 + (mcu->flash_size - 1) / 0x10000;
-
-         return true;
-       }
-    }
-
-  return false;
-}
-
-
-/* Implement `TARGET_OPTION_OVERRIDE'.  */
-
-static void
-avr_option_override (void)
-{
-  /* caller-save.cc looks for call-clobbered hard registers that are assigned
-     to pseudos that cross calls and tries so save-restore them around calls
-     in order to reduce the number of stack slots needed.
-
-     This might lead to situations where reload is no more able to cope
-     with the challenge of AVR's very few address registers and fails to
-     perform the requested spills.  */
-
-  if (avr_strict_X)
-    flag_caller_saves = 0;
-
-  /* Unwind tables currently require a frame pointer for correctness,
-     see toplev.cc:process_options().  */
-
-  if ((flag_unwind_tables
-       || flag_non_call_exceptions
-       || flag_asynchronous_unwind_tables)
-      && !ACCUMULATE_OUTGOING_ARGS)
-    {
-      flag_omit_frame_pointer = 0;
-    }
-
-  /* Disable flag_delete_null_pointer_checks if zero is a valid address. */
-  if (targetm.addr_space.zero_address_valid (ADDR_SPACE_GENERIC))
-    flag_delete_null_pointer_checks = 0;
-
-  /* PR ipa/92606: Inter-procedural analysis optimizes data across
-     address-spaces and PROGMEM.  As of v14, the PROGMEM part is
-     still not fixed (and there is still no target hook as proposed
-     in PR92932).  Just disable respective bogus optimization.  */
-  flag_ipa_icf_variables = 0;
-
-  if (flag_pic == 1)
-    warning (OPT_fpic, "%<-fpic%> is not supported");
-  if (flag_pic == 2)
-    warning (OPT_fPIC, "%<-fPIC%> is not supported");
-  if (flag_pie == 1)
-    warning (OPT_fpie, "%<-fpie%> is not supported");
-  if (flag_pie == 2)
-    warning (OPT_fPIE, "%<-fPIE%> is not supported");
-
-#if !defined (HAVE_AS_AVR_MGCCISR_OPTION)
-  avr_gasisr_prologues = 0;
-#endif
-
-  if (!avr_set_core_architecture())
-    return;
-
-  /* Sould be set by avr-common.cc */
-  gcc_assert (avr_long_double >= avr_double && avr_double >= 32);
-
-  /* RAM addresses of some SFRs common to all devices in respective arch. */
-
-  /* SREG: Status Register containing flags like I (global IRQ) */
-  avr_addr.sreg = 0x3F + avr_arch->sfr_offset;
-
-  /* RAMPZ: Address' high part when loading via ELPM */
-  avr_addr.rampz = 0x3B + avr_arch->sfr_offset;
-
-  avr_addr.rampy = 0x3A + avr_arch->sfr_offset;
-  avr_addr.rampx = 0x39 + avr_arch->sfr_offset;
-  avr_addr.rampd = 0x38 + avr_arch->sfr_offset;
-  avr_addr.ccp = (AVR_TINY ? 0x3C : 0x34) + avr_arch->sfr_offset;
-
-  /* SP: Stack Pointer (SP_H:SP_L) */
-  avr_addr.sp_l = 0x3D + avr_arch->sfr_offset;
-  avr_addr.sp_h = avr_addr.sp_l + 1;
-
-  init_machine_status = avr_init_machine_status;
-
-  avr_log_set_avr_log();
-}
-
-/* Function to set up the backend function structure.  */
-
-static struct machine_function *
-avr_init_machine_status (void)
-{
-  return ggc_cleared_alloc<machine_function> ();
-}
-
-
-/* Implement `INIT_EXPANDERS'.  */
-/* The function works like a singleton.  */
-
-void
-avr_init_expanders (void)
-{
-  for (int regno = REG_0; regno < REG_32; regno ++)
-    all_regs_rtx[regno] = gen_rtx_REG (QImode, regno);
-
-  lpm_reg_rtx  = all_regs_rtx[LPM_REGNO];
-  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);
-
-  lpm_addr_reg_rtx = gen_rtx_REG (HImode, REG_Z);
-
-  sreg_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.sreg));
-  rampd_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampd));
-  rampx_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampx));
-  rampy_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampy));
-  rampz_rtx = gen_rtx_MEM (QImode, GEN_INT (avr_addr.rampz));
-
-  xstring_empty = gen_rtx_CONST_STRING (VOIDmode, "");
-  xstring_e = gen_rtx_CONST_STRING (VOIDmode, "e");
-
-  /* TINY core does not have regs r10-r16, but avr-dimode.md expects them
-     to be present */
-  if (AVR_TINY)
-    avr_have_dimode = false;
-}
-
-
-/* Implement `REGNO_REG_CLASS'.  */
-/* Return register class for register R.  */
-
-enum reg_class
-avr_regno_reg_class (int r)
-{
-  static const enum reg_class reg_class_tab[] =
-    {
-      R0_REG,
-      /* r1 - r15 */
-      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
-      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
-      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
-      NO_LD_REGS, NO_LD_REGS, NO_LD_REGS, NO_LD_REGS,
-      /* r16 - r23 */
-      SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
-      SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS, SIMPLE_LD_REGS,
-      /* r24, r25 */
-      ADDW_REGS, ADDW_REGS,
-      /* X: r26, 27 */
-      POINTER_X_REGS, POINTER_X_REGS,
-      /* Y: r28, r29 */
-      POINTER_Y_REGS, POINTER_Y_REGS,
-      /* Z: r30, r31 */
-      POINTER_Z_REGS, POINTER_Z_REGS,
-      /* SP: SPL, SPH */
-      STACK_REG, STACK_REG
-    };
-
-  if (r <= 33)
-    return reg_class_tab[r];
-
-  if (r == REG_CC)
-    return CC_REG;
-
-  return ALL_REGS;
-}
-
-
-/* Implement `TARGET_SCALAR_MODE_SUPPORTED_P'.  */
-
-static bool
-avr_scalar_mode_supported_p (scalar_mode mode)
-{
-  if (ALL_FIXED_POINT_MODE_P (mode))
-    return true;
-
-  if (PSImode == mode)
-    return true;
-
-  return default_scalar_mode_supported_p (mode);
-}
-
-
-/* Return TRUE if DECL is a VAR_DECL located in flash and FALSE, otherwise.  */
-
-static bool
-avr_decl_flash_p (tree decl)
-{
-  if (TREE_CODE (decl) != VAR_DECL
-      || TREE_TYPE (decl) == error_mark_node)
-    {
-      return false;
-    }
-
-  return !ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (TREE_TYPE (decl)));
-}
-
-
-/* Return TRUE if DECL is a VAR_DECL located in the 24-bit flash
-   address space and FALSE, otherwise.  */
-
-static bool
-avr_decl_memx_p (tree decl)
-{
-  if (TREE_CODE (decl) != VAR_DECL
-      || TREE_TYPE (decl) == error_mark_node)
-    {
-      return false;
-    }
-
-  return (ADDR_SPACE_MEMX == TYPE_ADDR_SPACE (TREE_TYPE (decl)));
-}
-
-
-/* Return TRUE if X is a MEM rtx located in flash and FALSE, otherwise.  */
-
-bool
-avr_mem_flash_p (rtx x)
-{
-  return (MEM_P (x)
-         && !ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (x)));
-}
-
-
-/* Return TRUE if X is a MEM rtx located in the 24-bit flash
-   address space and FALSE, otherwise.  */
-
-bool
-avr_mem_memx_p (rtx x)
-{
-  return (MEM_P (x)
-         && ADDR_SPACE_MEMX == MEM_ADDR_SPACE (x));
-}
-
-
-/* A helper for the subsequent function attribute used to dig for
-   attribute 'name' in a FUNCTION_DECL or FUNCTION_TYPE */
-
-static inline bool
-avr_lookup_function_attribute1 (const_tree func, const char *name)
-{
-  if (FUNCTION_DECL == TREE_CODE (func))
-    {
-      if (NULL_TREE != lookup_attribute (name, DECL_ATTRIBUTES (func)))
-       {
-         return true;
-       }
-
-      func = TREE_TYPE (func);
-    }
-
-  gcc_assert (FUNC_OR_METHOD_TYPE_P (func));
-
-  return NULL_TREE != lookup_attribute (name, TYPE_ATTRIBUTES (func));
-}
-
-
-/* Call WORKER on all NAME attributes of function FUNC.  */
-
-static void
-avr_foreach_function_attribute (tree func, const char *name,
-                               void (*worker) (tree, tree, void *),
-                               void *cookie)
-{
-  tree attrs = NULL_TREE;
-
-  if (TREE_CODE (func) == FUNCTION_DECL)
-    attrs = DECL_ATTRIBUTES (func);
-  else if (FUNC_OR_METHOD_TYPE_P (func))
-    attrs = TYPE_ATTRIBUTES (TREE_TYPE (func));
-
-  while (attrs)
-    {
-      attrs = lookup_attribute (name, attrs);
-      if (attrs)
-       {
-         worker (func, attrs, cookie);
-         attrs = TREE_CHAIN (attrs);
-       }
-    }
-}
-
-
-/* Return nonzero if FUNC is a naked function.  */
-
-static bool
-avr_naked_function_p (tree func)
-{
-  return avr_lookup_function_attribute1 (func, "naked");
-}
-
-/* Return nonzero if FUNC is a noblock function.  */
-
-static bool
-avr_noblock_function_p (tree func)
-{
-  return avr_lookup_function_attribute1 (func, "noblock");
-}
-
-/* Return 1 if FUNC is a function that has a "ATTR_NAME" attribute
-   (and perhaps also "ATTR_NAME(num)" attributes.  Return -1 if FUNC has
-   "ATTR_NAME(num)" attribute(s) but no "ATTR_NAME" attribute.
-   When no form of ATTR_NAME is present, return 0.  */
-
-static int
-avr_interrupt_signal_function (tree func, const char *attr_name)
-{
-  int res = 0;
-
-  avr_foreach_function_attribute (func, attr_name,
-    [] (tree, tree attr, void *cookie)
-    {
-      int *pcook = (int *) cookie;
-
-      *pcook = TREE_VALUE (attr)
-       ? *pcook ? *pcook : -1
-       : 1;
-    }, &res);
-
-  return res;
-}
-
-
-/* Return 1 if FUNC is an interrupt function that has an "interrupt" attribute
-   (and perhaps also "interrupt(num)" attributes.  Return -1 if FUNC has
-   "interrupt(num)" attribute(s) but no "interrupt" attribute.  */
-
-static int
-avr_interrupt_function (tree func)
-{
-  return avr_interrupt_signal_function (func, "interrupt");
-}
-
-/* Return 1 if FUNC is a signal function that has a "signal" attribute
-   (and perhaps also "signal(num)" attributes.  Return -1 if FUNC has
-   "signal(num)" attribute(s) but no "signal" attribute.  */
-
-static int
-avr_signal_function (tree func)
-{
-  return avr_interrupt_signal_function (func, "signal");
-}
-
-/* Return nonzero if FUNC is an OS_task function.  */
-
-static bool
-avr_OS_task_function_p (tree func)
-{
-  return avr_lookup_function_attribute1 (func, "OS_task");
-}
-
-/* Return nonzero if FUNC is an OS_main function.  */
-
-static bool
-avr_OS_main_function_p (tree func)
-{
-  return avr_lookup_function_attribute1 (func, "OS_main");
-}
-
-
-/* Return nonzero if FUNC is a no_gccisr function as specified
-   by the "no_gccisr" attribute.  */
-
-static bool
-avr_no_gccisr_function_p (tree func)
-{
-  return avr_lookup_function_attribute1 (func, "no_gccisr");
-}
-
-
-/* Implement `TARGET_CAN_INLINE_P'.  */
-/* Some options like -mgas_isr_prologues depend on optimization level,
-   and the inliner might think that due to different options, inlining
-   is not permitted; see PR104327.  */
-
-static bool
-avr_can_inline_p (tree /* caller */, tree /* callee */)
-{
-  // No restrictions whatsoever.
-  return true;
-}
-
-/* Implement `TARGET_SET_CURRENT_FUNCTION'.  */
-/* Sanity cheching for above function attributes.  */
-
-static void
-avr_set_current_function (tree decl)
-{
-  if (decl == NULL_TREE
-      || current_function_decl == NULL_TREE
-      || current_function_decl == error_mark_node
-      || ! cfun->machine
-      || cfun->machine->attributes_checked_p)
-    return;
-
-  location_t loc = DECL_SOURCE_LOCATION (decl);
-
-  cfun->machine->is_naked = avr_naked_function_p (decl);
-  cfun->machine->is_signal = avr_signal_function (decl);
-  cfun->machine->is_interrupt = avr_interrupt_function (decl);
-  cfun->machine->is_noblock = avr_noblock_function_p (decl);
-  cfun->machine->is_OS_task = avr_OS_task_function_p (decl);
-  cfun->machine->is_OS_main = avr_OS_main_function_p (decl);
-  cfun->machine->is_no_gccisr = avr_no_gccisr_function_p (decl);
-
-  const char *isr = cfun->machine->is_interrupt ? "interrupt" : "signal";
-
-  /* Too much attributes make no sense as they request conflicting features. */
-
-  if (cfun->machine->is_OS_task
-      && (cfun->machine->is_signal || cfun->machine->is_interrupt))
-    error_at (loc, "function attributes %qs and %qs are mutually exclusive",
-             "OS_task", isr);
-
-  if (cfun->machine->is_OS_main
-      && (cfun->machine->is_signal || cfun->machine->is_interrupt))
-    error_at (loc, "function attributes %qs and %qs are mutually exclusive",
-             "OS_main", isr);
-
-  if (cfun->machine->is_interrupt || cfun->machine->is_signal)
-    {
-      tree args = TYPE_ARG_TYPES (TREE_TYPE (decl));
-      tree ret = TREE_TYPE (TREE_TYPE (decl));
-      const char *name;
-
-      name = DECL_ASSEMBLER_NAME_SET_P (decl)
-       ? IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl))
-       : IDENTIFIER_POINTER (DECL_NAME (decl));
-
-      /* Skip a leading '*' that might still prefix the assembler name,
-        e.g. in non-LTO runs.  */
-
-      name = default_strip_name_encoding (name);
-
-      /* Interrupt handlers must be  void __vector (void)  functions.  */
-
-      if (args && TREE_CODE (TREE_VALUE (args)) != VOID_TYPE)
-       {
-         error_at (loc, "%qs function cannot have arguments", isr);
-         if (TREE_CODE (TREE_TYPE (decl)) == METHOD_TYPE)
-           inform (loc, "method %qs has an implicit %<this%> argument", name);
-       }
-
-      if (TREE_CODE (ret) != VOID_TYPE)
-       error_at (loc, "%qs function cannot return a value", isr);
-
-#if defined WITH_AVRLIBC
-      /* If the function has the 'signal' or 'interrupt' attribute, ensure
-        that the name of the function is "__vector_NN" so as to catch
-        when the user misspells the vector name.  This check is only
-        required when the "interrupt" resp. "signal" attribute does not
-        have an IRQ-number argument.  */
-
-      if (!startswith (name, "__vector")
-         && (cfun->machine->is_interrupt == 1
-             || cfun->machine->is_signal == 1))
-       warning_at (loc, OPT_Wmisspelled_isr, "%qs appears to be a misspelled "
-                   "%qs handler, missing %<__vector%> prefix", name, isr);
-#endif // AVR-LibC naming conventions
-    }
-  else if (cfun->machine->is_noblock)
-    {
-      warning (OPT_Wattributes, "%qs attribute ignored on non-ISR function",
-              "noblock");
-    }
-
-#if defined WITH_AVRLIBC
-  // Common problem is using "ISR" without first including avr/interrupt.h.
-  const char *name = IDENTIFIER_POINTER (DECL_NAME (decl));
-  name = default_strip_name_encoding (name);
-  if (strcmp ("ISR", name) == 0)
-    {
-      warning_at (loc, OPT_Wmisspelled_isr, "%qs is a reserved identifier"
-                 " in AVR-LibC.  Consider %<#include <avr/interrupt.h>%>"
-                 " before using the %qs macro", name, name);
-    }
-  if (strcmp ("INTERRUPT", name) == 0
-      || strcmp ("SIGNAL", name) == 0)
-    {
-      warning_at (loc, OPT_Wmisspelled_isr, "%qs is a deprecated identifier"
-                 " in AVR-LibC.  Consider %<#include <avr/interrupt.h>%>"
-                 " or %<#include <compat/deprecated.h>%>"
-                 " before using the %qs macro", name, name);
-    }
-#endif // AVR-LibC naming conventions
-
-  /* Don't print the above diagnostics more than once.  */
-
-  cfun->machine->attributes_checked_p = 1;
-}
-
-
-/* Implement `ACCUMULATE_OUTGOING_ARGS'.  */
-
-int
-avr_accumulate_outgoing_args (void)
-{
-  if (!cfun)
-    return TARGET_ACCUMULATE_OUTGOING_ARGS;
-
-  /* FIXME: For setjmp and in avr_builtin_setjmp_frame_value we don't know
-       what offset is correct.  In some cases it is relative to
-       virtual_outgoing_args_rtx and in others it is relative to
-       virtual_stack_vars_rtx.  For example code see
-           gcc.c-torture/execute/built-in-setjmp.c
-           gcc.c-torture/execute/builtins/sprintf-chk.c   */
-
-  return (TARGET_ACCUMULATE_OUTGOING_ARGS
-         && !(cfun->calls_setjmp
-              || cfun->has_nonlocal_label));
-}
-
-
-/* Report contribution of accumulated outgoing arguments to stack size.  */
-
-static inline int
-avr_outgoing_args_size (void)
-{
-  return (ACCUMULATE_OUTGOING_ARGS
-         ? (HOST_WIDE_INT) crtl->outgoing_args_size : 0);
-}
-
-
-/* Implement `TARGET_STARTING_FRAME_OFFSET'.  */
-/* This is the offset from the frame pointer register to the first stack slot
-   that contains a variable living in the frame.  */
-
-static HOST_WIDE_INT
-avr_starting_frame_offset (void)
-{
-  return 1 + avr_outgoing_args_size ();
-}
-
-
-/* Return the number of hard registers to push/pop in the prologue/epilogue
-   of the current function, and optionally store these registers in SET.  */
-
-static int
-avr_regs_to_save (HARD_REG_SET *set)
-{
-  int count = 0;
-  int int_or_sig_p = cfun->machine->is_interrupt || cfun->machine->is_signal;
-
-  if (set)
-    CLEAR_HARD_REG_SET (*set);
-
-  /* No need to save any registers if the function never returns or
-     has the "OS_task" or "OS_main" attribute.  */
-
-  if (TREE_THIS_VOLATILE (current_function_decl)
-      || cfun->machine->is_OS_task
-      || cfun->machine->is_OS_main)
-    return 0;
-
-  for (int reg = REG_0; reg < REG_32; reg++)
-    {
-      /* Do not push/pop __tmp_reg__, __zero_reg__, as well as
-        any global register variables.  */
-
-      if (fixed_regs[reg])
-       continue;
-
-      if ((int_or_sig_p && !crtl->is_leaf && call_used_or_fixed_reg_p (reg))
-         || (df_regs_ever_live_p (reg)
-             && (int_or_sig_p || !call_used_or_fixed_reg_p (reg))
-             /* Don't record frame pointer registers here.  They are treated
-                indivitually in prologue.  */
-             && !(frame_pointer_needed
-                  && (reg == REG_Y || reg == REG_Y + 1))))
-       {
-         if (set)
-           SET_HARD_REG_BIT (*set, reg);
-         count++;
-       }
-    }
-  return count;
-}
-
-
-/* Implement `TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS' */
-
-static bool
-avr_allocate_stack_slots_for_args (void)
-{
-  return !cfun->machine->is_naked;
-}
-
-
-/* Implement `TARGET_CAN_ELIMINATE'.  */
-/* Return true if register FROM can be eliminated via register TO.  */
-
-static bool
-avr_can_eliminate (const int /*from*/, const int to)
-{
-  return ((frame_pointer_needed && to == FRAME_POINTER_REGNUM)
-         || !frame_pointer_needed);
-}
-
-
-/* Implement `TARGET_WARN_FUNC_RETURN'.  */
-
-static bool
-avr_warn_func_return (tree decl)
-{
-  /* Naked functions are implemented entirely in assembly, including the
-     return sequence, so suppress warnings about this.  */
-
-  return !avr_naked_function_p (decl);
-}
-
-
-/* Worker function for `INITIAL_ELIMINATION_OFFSET'.  */
-/* Compute offset between arg_pointer and frame_pointer.  */
-
-int
-avr_initial_elimination_offset (int from, int to)
-{
-  if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
-    return 0;
-  else
-    {
-      int offset = frame_pointer_needed ? 2 : 0;
-      int avr_pc_size = AVR_HAVE_EIJMP_EICALL ? 3 : 2;
-
-      // If FROM is ARG_POINTER_REGNUM, we are not in an ISR as ISRs
-      // might not have arguments.  Hence the following is not affected
-      // by gasisr prologues.
-      offset += avr_regs_to_save (NULL);
-      return (get_frame_size () + avr_outgoing_args_size()
-             + avr_pc_size + 1 + offset);
-    }
-}
-
-
-/* Helper for the function below.  */
-
-static void
-avr_adjust_type_node (tree *node, machine_mode mode, int sat_p)
-{
-  *node = make_node (FIXED_POINT_TYPE);
-  TYPE_SATURATING (*node) = sat_p;
-  TYPE_UNSIGNED (*node) = UNSIGNED_FIXED_POINT_MODE_P (mode);
-  TYPE_IBIT (*node) = GET_MODE_IBIT (mode);
-  TYPE_FBIT (*node) = GET_MODE_FBIT (mode);
-  TYPE_PRECISION (*node) = GET_MODE_BITSIZE (mode);
-  SET_TYPE_ALIGN (*node, 8);
-  SET_TYPE_MODE (*node, mode);
-
-  layout_type (*node);
-}
-
-
-/* Implement `TARGET_BUILD_BUILTIN_VA_LIST'.  */
-
-static tree
-avr_build_builtin_va_list (void)
-{
-  /* avr-modes.def adjusts [U]TA to be 64-bit modes with 48 fractional bits.
-     This is more appropriate for the 8-bit machine AVR than 128-bit modes.
-     The ADJUST_IBIT/FBIT are handled in toplev:init_adjust_machine_modes()
-     which is auto-generated by genmodes, but the compiler assigns [U]DAmode
-     to the long long accum modes instead of the desired [U]TAmode.
-
-     Fix this now, right after node setup in tree.cc:build_common_tree_nodes().
-     This must run before c-cppbuiltin.cc:builtin_define_fixed_point_constants()
-     which built-in defines macros like __ULLACCUM_FBIT__ that are used by
-     libgcc to detect IBIT and FBIT.  */
-
-  avr_adjust_type_node (&ta_type_node, TAmode, 0);
-  avr_adjust_type_node (&uta_type_node, UTAmode, 0);
-  avr_adjust_type_node (&sat_ta_type_node, TAmode, 1);
-  avr_adjust_type_node (&sat_uta_type_node, UTAmode, 1);
-
-  unsigned_long_long_accum_type_node = uta_type_node;
-  long_long_accum_type_node = ta_type_node;
-  sat_unsigned_long_long_accum_type_node = sat_uta_type_node;
-  sat_long_long_accum_type_node = sat_ta_type_node;
-
-  /* Dispatch to the default handler.  */
-
-  return std_build_builtin_va_list ();
-}
-
-
-/* Worker function for `INCOMING_RETURN_ADDR_RTX'.  */
-/* Return contents of MEM at frame pointer + stack size + 1 (+2 if 3-byte PC).
-   This is return address of function.  */
-
-rtx
-avr_return_addr_rtx (int count, rtx tem)
-{
-  rtx r;
-
-  /* Can only return this function's return address. Others not supported.  */
-  if (count)
-    return NULL;
-
-  if (AVR_3_BYTE_PC)
-    {
-      r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+2");
-      warning (0, "%<builtin_return_address%> contains only 2 bytes"
-              " of address");
-    }
-  else
-    r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+1");
-
-  cfun->machine->use_L__stack_usage = 1;
-
-  r = gen_rtx_PLUS (Pmode, tem, r);
-  r = gen_frame_mem (Pmode, memory_address (Pmode, r));
-  r = gen_rtx_ROTATE (HImode, r, GEN_INT (8));
-  return r;
-}
-
-/* Return 1 if the function epilogue is just a single "ret".  */
-
-int
-avr_simple_epilogue (void)
-{
-  return (! frame_pointer_needed
-         && get_frame_size () == 0
-         && avr_outgoing_args_size() == 0
-         && avr_regs_to_save (NULL) == 0
-         && ! cfun->machine->is_interrupt
-         && ! cfun->machine->is_signal
-         && ! cfun->machine->is_naked
-         && ! TREE_THIS_VOLATILE (current_function_decl));
-}
-
-/* This function checks sequence of live registers.  */
+/* Return 1 if FUNC is an interrupt function that has an "interrupt" attribute
+   (and perhaps also "interrupt(num)" attributes.  Return -1 if FUNC has
+   "interrupt(num)" attribute(s) but no "interrupt" attribute.  */
 
 static int
-sequent_regs_live (void)
-{
-  int live_seq = 0;
-  int cur_seq = 0;
-
-  for (int reg = 0; reg <= LAST_CALLEE_SAVED_REG; ++reg)
-    {
-      if (fixed_regs[reg])
-       {
-         /* Don't recognize sequences that contain global register
-            variables.  */
-
-         if (live_seq != 0)
-           return 0;
-         else
-           continue;
-       }
-
-      if (!call_used_or_fixed_reg_p (reg))
-       {
-         if (df_regs_ever_live_p (reg))
-           {
-             ++live_seq;
-             ++cur_seq;
-           }
-         else
-           cur_seq = 0;
-       }
-    }
-
-  if (!frame_pointer_needed)
-    {
-      if (df_regs_ever_live_p (REG_Y))
-       {
-         ++live_seq;
-         ++cur_seq;
-       }
-      else
-       cur_seq = 0;
-
-      if (df_regs_ever_live_p (REG_Y + 1))
-       {
-         ++live_seq;
-         ++cur_seq;
-       }
-      else
-       cur_seq = 0;
-    }
-  else
-    {
-      cur_seq += 2;
-      live_seq += 2;
-    }
-  return (cur_seq == live_seq) ? live_seq : 0;
-}
-
-
-namespace {
-static const pass_data avr_pass_data_fuse_add =
-{
-  RTL_PASS,        // type
-  "",              // name (will be patched)
-  OPTGROUP_NONE,    // optinfo_flags
-  TV_DF_SCAN,      // tv_id
-  0,               // properties_required
-  0,               // properties_provided
-  0,               // properties_destroyed
-  0,               // todo_flags_start
-  TODO_df_finish    // todo_flags_finish
-};
-
-
-class avr_pass_fuse_add : public rtl_opt_pass
-{
-public:
-  avr_pass_fuse_add (gcc::context *ctxt, const char *name)
-    : rtl_opt_pass (avr_pass_data_fuse_add, ctxt)
-  {
-    this->name = name;
-  }
-
-  virtual bool gate (function *) { return optimize && avr_fuse_add > 0; }
-
-  virtual unsigned int execute (function *);
-
-  struct Some_Insn
-  {
-    rtx_insn *insn = nullptr;
-    rtx dest, src;
-    bool valid () const { return insn != nullptr; }
-    void set_deleted ()
-    {
-      gcc_assert (insn);
-      SET_INSN_DELETED (insn);
-      insn = nullptr;
-    }
-  };
-
-  // If .insn is not NULL, then this is a  reg:HI += const_int
-  // of an address register.
-  struct Add_Insn : Some_Insn
-  {
-    rtx addend;
-    int regno;
-    Add_Insn () {}
-    Add_Insn (rtx_insn *insn);
-  };
-
-  // If .insn is not NULL, then this sets an address register
-  // to a constant value.
-  struct Ldi_Insn : Some_Insn
-  {
-    int regno;
-    Ldi_Insn () {}
-    Ldi_Insn (rtx_insn *insn);
-  };
-
-  // If .insn is not NULL, then this is a load or store insn where the
-  // address is REG or POST_INC with an address register.
-  struct Mem_Insn : Some_Insn
-  {
-    rtx reg_or_0, mem, addr, addr_reg;
-    int addr_regno;
-    enum rtx_code addr_code;
-    machine_mode mode;
-    addr_space_t addr_space;
-    bool store_p, volatile_p;
-    Mem_Insn () {}
-    Mem_Insn (rtx_insn *insn);
-  };
-
-  rtx_insn *fuse_ldi_add (Ldi_Insn &prev_ldi, Add_Insn &add);
-  rtx_insn *fuse_add_add (Add_Insn &prev_add, Add_Insn &add);
-  rtx_insn *fuse_add_mem (Add_Insn &prev_add, Mem_Insn &mem);
-  rtx_insn *fuse_mem_add (Mem_Insn &prev_mem, Add_Insn &add);
-}; // avr_pass_fuse_add
-
-} // anon namespace
-
-rtl_opt_pass *
-make_avr_pass_fuse_add (gcc::context *ctxt)
+avr_interrupt_function (tree func)
 {
-  return new avr_pass_fuse_add (ctxt, "avr-fuse-add");
+  return avr_interrupt_signal_function (func, "interrupt");
 }
 
-/* Describe properties of AVR's indirect load and store instructions
-   LD, LDD, ST, STD, LPM, ELPM depending on register number, volatility etc.
-   Rules for "volatile" accesses are:
-
-         | Xmega           |  non-Xmega
-   ------+-----------------+----------------
-   load  | read LSB first  | read LSB first
-   store | write LSB first | write MSB first
-*/
+/* Return 1 if FUNC is a signal function that has a "signal" attribute
+   (and perhaps also "signal(num)" attributes.  Return -1 if FUNC has
+   "signal(num)" attribute(s) but no "signal" attribute.  */
 
-struct AVR_LdSt_Props
+static int
+avr_signal_function (tree func)
 {
-  bool has_postinc, has_predec, has_ldd;
-  // The insn printers will use POST_INC or PRE_DEC addressing, no matter
-  // what adressing modes we are feeding into them.
-  bool want_postinc, want_predec;
-
-  AVR_LdSt_Props (int regno, bool store_p, bool volatile_p, addr_space_t as)
-  {
-    bool generic_p = ADDR_SPACE_GENERIC_P (as);
-    bool flashx_p = ! generic_p && as != ADDR_SPACE_MEMX;
-    has_postinc = generic_p || (flashx_p && regno == REG_Z);
-    has_predec = generic_p;
-    has_ldd = ! AVR_TINY && generic_p && (regno == REG_Y || regno == REG_Z);
-    want_predec  = volatile_p && generic_p && ! AVR_XMEGA && store_p;
-    want_postinc = volatile_p && generic_p && (AVR_XMEGA || ! store_p);
-    want_postinc |= flashx_p && regno == REG_Z;
-  }
-
-  AVR_LdSt_Props (const avr_pass_fuse_add::Mem_Insn &m)
-    : AVR_LdSt_Props (m.addr_regno, m.store_p, m.volatile_p, m.addr_space)
-  {
-    gcc_assert (m.valid ());
-  }
-};
+  return avr_interrupt_signal_function (func, "signal");
+}
 
-/* Emit a single_set that clobbers REG_CC.  */
+/* Return nonzero if FUNC is an OS_task function.  */
 
-static rtx_insn *
-emit_move_ccc (rtx dest, rtx src)
+static bool
+avr_OS_task_function_p (tree func)
 {
-  return emit_insn (gen_gen_move_clobbercc (dest, src));
+  return avr_lookup_function_attribute1 (func, "OS_task");
 }
 
-/* Emit a single_set that clobbers REG_CC after insn AFTER.  */
+/* Return nonzero if FUNC is an OS_main function.  */
 
-static rtx_insn *
-emit_move_ccc_after (rtx dest, rtx src, rtx_insn *after)
+static bool
+avr_OS_main_function_p (tree func)
 {
-  return emit_insn_after (gen_gen_move_clobbercc (dest, src), after);
+  return avr_lookup_function_attribute1 (func, "OS_main");
 }
 
+
+/* Return nonzero if FUNC is a no_gccisr function as specified
+   by the "no_gccisr" attribute.  */
+
 static bool
-reg_seen_between_p (const_rtx reg, const rtx_insn *from, const rtx_insn *to)
+avr_no_gccisr_function_p (tree func)
 {
-  return (reg_used_between_p (reg, from, to)
-         || reg_set_between_p (reg, from, to));
+  return avr_lookup_function_attribute1 (func, "no_gccisr");
 }
 
 
-static void
-avr_maybe_adjust_cfa (rtx_insn *insn, rtx reg, int addend)
+/* Implement `TARGET_CAN_INLINE_P'.  */
+/* Some options like -mgas_isr_prologues depend on optimization level,
+   and the inliner might think that due to different options, inlining
+   is not permitted; see PR104327.  */
+
+static bool
+avr_can_inline_p (tree /* caller */, tree /* callee */)
 {
-  if (addend
-      && frame_pointer_needed
-      && REGNO (reg) == FRAME_POINTER_REGNUM
-      && avr_fuse_add == 3)
-    {
-      rtx plus = plus_constant (Pmode, reg, addend);
-      RTX_FRAME_RELATED_P (insn) = 1;
-      add_reg_note (insn, REG_CFA_ADJUST_CFA, gen_rtx_SET (reg, plus));
-    }
+  // No restrictions whatsoever.
+  return true;
 }
 
+/* Implement `TARGET_SET_CURRENT_FUNCTION'.  */
+/* Sanity cheching for above function attributes.  */
 
-// If successful, this represents a SET of a pointer register to a constant.
-avr_pass_fuse_add::Ldi_Insn::Ldi_Insn (rtx_insn *insn)
+static void
+avr_set_current_function (tree decl)
 {
-  rtx set = single_set (insn);
-  if (!set)
+  if (decl == NULL_TREE
+      || current_function_decl == NULL_TREE
+      || current_function_decl == error_mark_node
+      || ! cfun->machine
+      || cfun->machine->attributes_checked_p)
     return;
 
-  src = SET_SRC (set);
-  dest = SET_DEST (set);
+  location_t loc = DECL_SOURCE_LOCATION (decl);
 
-  if (REG_P (dest)
-      && GET_MODE (dest) == Pmode
-      && IN_RANGE (regno = REGNO (dest), REG_X, REG_Z)
-      && CONSTANT_P (src))
-    {
-      this->insn = insn;
-    }
-}
+  cfun->machine->is_naked = avr_naked_function_p (decl);
+  cfun->machine->is_signal = avr_signal_function (decl);
+  cfun->machine->is_interrupt = avr_interrupt_function (decl);
+  cfun->machine->is_noblock = avr_noblock_function_p (decl);
+  cfun->machine->is_OS_task = avr_OS_task_function_p (decl);
+  cfun->machine->is_OS_main = avr_OS_main_function_p (decl);
+  cfun->machine->is_no_gccisr = avr_no_gccisr_function_p (decl);
 
-// If successful, this represents a PLUS with CONST_INT of a pointer
-// register X, Y or Z.  Otherwise, the object is not valid().
-avr_pass_fuse_add::Add_Insn::Add_Insn (rtx_insn *insn)
-{
-  rtx set = single_set (insn);
-  if (!set)
-    return;
+  const char *isr = cfun->machine->is_interrupt ? "interrupt" : "signal";
 
-  src = SET_SRC (set);
-  dest = SET_DEST (set);
-  if (REG_P (dest)
-      // We are only interested in PLUSes that change address regs.
-      && GET_MODE (dest) == Pmode
-      && IN_RANGE (regno = REGNO (dest), REG_X, REG_Z)
-      && PLUS == GET_CODE (src)
-      && rtx_equal_p (XEXP (src, 0), dest)
-      && CONST_INT_P (XEXP (src, 1)))
-    {
-      // This is reg:HI += const_int.
-      addend = XEXP (src, 1);
-      this->insn = insn;
-    }
-}
+  /* Too much attributes make no sense as they request conflicting features. */
 
-// If successful, this represents a load or store insn where the addressing
-// mode uses pointer register X, Y or Z.  Otherwise, the object is not valid().
-avr_pass_fuse_add::Mem_Insn::Mem_Insn (rtx_insn *insn)
-{
-  rtx set = single_set (insn);
-  if (!set)
-    return;
+  if (cfun->machine->is_OS_task
+      && (cfun->machine->is_signal || cfun->machine->is_interrupt))
+    error_at (loc, "function attributes %qs and %qs are mutually exclusive",
+             "OS_task", isr);
 
-  src = SET_SRC (set);
-  dest = SET_DEST (set);
-  mode = GET_MODE (dest);
+  if (cfun->machine->is_OS_main
+      && (cfun->machine->is_signal || cfun->machine->is_interrupt))
+    error_at (loc, "function attributes %qs and %qs are mutually exclusive",
+             "OS_main", isr);
 
-  if (MEM_P (dest)
-      && (REG_P (src) || src == CONST0_RTX (mode)))
-    {
-      reg_or_0 = src;
-      mem = dest;
-    }
-  else if (REG_P (dest) && MEM_P (src))
+  if (cfun->machine->is_interrupt || cfun->machine->is_signal)
     {
-      reg_or_0 = dest;
-      mem = src;
-    }
-  else
-    return;
-
-  if (avr_mem_memx_p (mem)
-      || avr_load_libgcc_p (mem))
-    return;
-
-  addr = XEXP (mem, 0);
-  addr_code = GET_CODE (addr);
+      tree args = TYPE_ARG_TYPES (TREE_TYPE (decl));
+      tree ret = TREE_TYPE (TREE_TYPE (decl));
+      const char *name;
 
-  if (addr_code == REG)
-    addr_reg = addr;
-  else if (addr_code == POST_INC || addr_code == PRE_DEC)
-    addr_reg = XEXP (addr, 0);
-  else
-    return;
+      name = DECL_ASSEMBLER_NAME_SET_P (decl)
+       ? IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl))
+       : IDENTIFIER_POINTER (DECL_NAME (decl));
 
-  addr_regno = REGNO (addr_reg);
+      /* Skip a leading '*' that might still prefix the assembler name,
+        e.g. in non-LTO runs.  */
 
-  if (avr_fuse_add == 2
-      && frame_pointer_needed
-      && addr_regno == FRAME_POINTER_REGNUM)
-    MEM_VOLATILE_P (mem) = 0;
+      name = default_strip_name_encoding (name);
 
-  if (reg_overlap_mentioned_p (reg_or_0, addr) // Can handle CONSTANT_P.
-      || addr_regno > REG_Z
-      || avr_mem_memx_p (mem)
-      // The following optimizations only handle REG and POST_INC,
-      // so that's all what we allow here.
-      || (addr_code != REG && addr_code != POST_INC))
-    return;
+      /* Interrupt handlers must be  void __vector (void)  functions.  */
 
-  addr_space = MEM_ADDR_SPACE (mem);
-  volatile_p = MEM_VOLATILE_P (mem);
-  store_p = MEM_P (dest);
+      if (args && TREE_CODE (TREE_VALUE (args)) != VOID_TYPE)
+       {
+         error_at (loc, "%qs function cannot have arguments", isr);
+         if (TREE_CODE (TREE_TYPE (decl)) == METHOD_TYPE)
+           inform (loc, "method %qs has an implicit %<this%> argument", name);
+       }
 
-  // Turn this "valid".
-  this->insn = insn;
-}
+      if (TREE_CODE (ret) != VOID_TYPE)
+       error_at (loc, "%qs function cannot return a value", isr);
 
-/* Try to combine a Ldi insn with a PLUS CONST_INT addend to one Ldi insn.
-   If LDI is valid, then it precedes ADD in the same block.
-   When a replacement is found, a new insn is emitted and the old insns
-   are pseudo-deleted.  The returned insn is the point where the calling
-   scanner should continue.  When no replacement is found, nullptr is
-   returned and nothing changed.  */
+#if defined WITH_AVRLIBC
+      /* If the function has the 'signal' or 'interrupt' attribute, ensure
+        that the name of the function is "__vector_NN" so as to catch
+        when the user misspells the vector name.  This check is only
+        required when the "interrupt" resp. "signal" attribute does not
+        have an IRQ-number argument.  */
 
-rtx_insn *
-avr_pass_fuse_add::fuse_ldi_add (Ldi_Insn &ldi, Add_Insn &add)
-{
-  if (! ldi.valid ()
-      || reg_seen_between_p (ldi.dest, ldi.insn, add.insn))
+      if (!startswith (name, "__vector")
+         && (cfun->machine->is_interrupt == 1
+             || cfun->machine->is_signal == 1))
+       warning_at (loc, OPT_Wmisspelled_isr, "%qs appears to be a misspelled "
+                   "%qs handler, missing %<__vector%> prefix", name, isr);
+#endif // AVR-LibC naming conventions
+    }
+  else if (cfun->machine->is_noblock)
     {
-      // If something is between the Ldi and the current insn, we can
-      // set the Ldi invalid to speed future scans.
-      return ldi.insn = nullptr;
+      warning (OPT_Wattributes, "%qs attribute ignored on non-ISR function",
+              "noblock");
     }
 
-  // Found a Ldi with const and a PLUS insns in the same BB,
-  // and with no interfering insns between them.
-
-  // Emit new Ldi with the sum of the original offsets after the old Ldi.
-  rtx xval = plus_constant (Pmode, ldi.src, INTVAL (add.addend));
-
-  rtx_insn *insn = emit_move_ccc_after (ldi.dest, xval, ldi.insn);
-  avr_dump (";; new Ldi[%d] insn %d after %d: R%d = %r\n\n", ldi.regno,
-           INSN_UID (insn), INSN_UID (ldi.insn), ldi.regno, xval);
+#if defined WITH_AVRLIBC
+  // Common problem is using "ISR" without first including avr/interrupt.h.
+  const char *name = IDENTIFIER_POINTER (DECL_NAME (decl));
+  name = default_strip_name_encoding (name);
+  if (strcmp ("ISR", name) == 0)
+    {
+      warning_at (loc, OPT_Wmisspelled_isr, "%qs is a reserved identifier"
+                 " in AVR-LibC.  Consider %<#include <avr/interrupt.h>%>"
+                 " before using the %qs macro", name, name);
+    }
+  if (strcmp ("INTERRUPT", name) == 0
+      || strcmp ("SIGNAL", name) == 0)
+    {
+      warning_at (loc, OPT_Wmisspelled_isr, "%qs is a deprecated identifier"
+                 " in AVR-LibC.  Consider %<#include <avr/interrupt.h>%>"
+                 " or %<#include <compat/deprecated.h>%>"
+                 " before using the %qs macro", name, name);
+    }
+#endif // AVR-LibC naming conventions
 
-  rtx_insn *next = NEXT_INSN (add.insn);
-  ldi.set_deleted ();
-  add.set_deleted ();
+  /* Don't print the above diagnostics more than once.  */
 
-  return next;
+  cfun->machine->attributes_checked_p = 1;
 }
 
-/* Try to combine two PLUS insns with CONST_INT addend to one such insn.
-   If PREV_ADD is valid, then it precedes ADD in the same basic block.
-   When a replacement is found, a new insn is emitted and the old insns
-   are pseudo-deleted.  The returned insn is the point where the calling
-   scanner should continue.  When no replacement is found, nullptr is
-   returned and nothing changed.  */
 
-rtx_insn *
-avr_pass_fuse_add::fuse_add_add (Add_Insn &prev_add, Add_Insn &add)
+/* Implement `ACCUMULATE_OUTGOING_ARGS'.  */
+
+int
+avr_accumulate_outgoing_args (void)
 {
-  if (! prev_add.valid ()
-      || reg_seen_between_p (add.dest, prev_add.insn, add.insn))
-    {
-      // If something is between the previous Add and the current insn,
-      // we can set the previous Add invalid to speed future scans.
-      return prev_add.insn = nullptr;
-    }
+  if (!cfun)
+    return TARGET_ACCUMULATE_OUTGOING_ARGS;
 
-  // Found two PLUS insns in the same BB, and with no interfering
-  // insns between them.
-  rtx plus = plus_constant (Pmode, add.src, INTVAL (prev_add.addend));
+  /* FIXME: For setjmp and in avr_builtin_setjmp_frame_value we don't know
+       what offset is correct.  In some cases it is relative to
+       virtual_outgoing_args_rtx and in others it is relative to
+       virtual_stack_vars_rtx.  For example code see
+           gcc.c-torture/execute/built-in-setjmp.c
+           gcc.c-torture/execute/builtins/sprintf-chk.c   */
 
-  rtx_insn *next;
-  if (REG_P (plus))
-    {
-      avr_dump (";; Add[%d] from %d annihilates %d\n\n", add.regno,
-               INSN_UID (prev_add.insn), INSN_UID (add.insn));
-      next = NEXT_INSN (add.insn);
-    }
-  else
-    {
-      // Emit after the current insn, so that it will be picked
-      // up as next valid Add insn.
-      next = emit_move_ccc_after (add.dest, plus, add.insn);
-      avr_dump (";; #1 new Add[%d] insn %d after %d: R%d += %d\n\n",
-               add.regno, INSN_UID (next), INSN_UID (add.insn),
-               add.regno, (int) INTVAL (XEXP (plus, 1)));
-      gcc_assert (GET_CODE (plus) == PLUS);
-    }
+  return (TARGET_ACCUMULATE_OUTGOING_ARGS
+         && !(cfun->calls_setjmp
+              || cfun->has_nonlocal_label));
+}
 
-  add.set_deleted ();
-  prev_add.set_deleted ();
 
-  return next;
+/* Report contribution of accumulated outgoing arguments to stack size.  */
+
+static inline int
+avr_outgoing_args_size (void)
+{
+  return (ACCUMULATE_OUTGOING_ARGS
+         ? (HOST_WIDE_INT) crtl->outgoing_args_size : 0);
 }
 
-/* Try to combine a PLUS of the address register with a load or store insn.
-   If ADD is valid, then it precedes MEM in the same basic block.
-   When a replacement is found, a new insn is emitted and the old insns
-   are pseudo-deleted.  The returned insn is the point where the calling
-   scanner should continue.  When no replacement is found, nullptr is
-   returned and nothing changed.  */
 
-rtx_insn *
-avr_pass_fuse_add::fuse_add_mem (Add_Insn &add, Mem_Insn &mem)
-{
-  if (! add.valid ()
-      || reg_seen_between_p (add.dest, add.insn, mem.insn))
-    {
-      // If something is between the Add and the current insn, we can
-      // set the Add invalid to speed future scans.
-      return add.insn = nullptr;
-    }
+/* Implement `TARGET_STARTING_FRAME_OFFSET'.  */
+/* This is the offset from the frame pointer register to the first stack slot
+   that contains a variable living in the frame.  */
 
-  AVR_LdSt_Props ap { mem };
+static HOST_WIDE_INT
+avr_starting_frame_offset (void)
+{
+  return 1 + avr_outgoing_args_size ();
+}
 
-  int msize = GET_MODE_SIZE (mem.mode);
 
-  // The mem insn really wants PRE_DEC.
-  bool case1 = ((mem.addr_code == REG || mem.addr_code == POST_INC)
-               && msize > 1 && ap.want_predec && ! ap.has_ldd);
+/* Return the number of hard registers to push/pop in the prologue/epilogue
+   of the current function, and optionally store these registers in SET.  */
 
-  // The offset can be consumed by a PRE_DEC.
-  bool case2 = (- INTVAL (add.addend) == msize
-               && (mem.addr_code == REG || mem.addr_code == POST_INC)
-               && ap.has_predec && ! ap.want_postinc);
+static int
+avr_regs_to_save (HARD_REG_SET *set)
+{
+  int count = 0;
+  int int_or_sig_p = cfun->machine->is_interrupt || cfun->machine->is_signal;
 
-  if (! case1 && ! case2)
-    return nullptr;
+  if (set)
+    CLEAR_HARD_REG_SET (*set);
 
-  // Change from REG or POST_INC to PRE_DEC.
-  rtx xmem = change_address (mem.mem, mem.mode,
-                            gen_rtx_PRE_DEC (Pmode, mem.addr_reg));
-  rtx dest = mem.store_p ? xmem : mem.reg_or_0;
-  rtx src  = mem.store_p ? mem.reg_or_0 : xmem;
+  /* No need to save any registers if the function never returns or
+     has the "OS_task" or "OS_main" attribute.  */
 
-  rtx_insn *next = emit_move_ccc_after (dest, src, mem.insn);
-  add_reg_note (next, REG_INC, mem.addr_reg);
-  avr_dump (";; new Mem[%d] insn %d after %d: %r = %r\n\n", mem.addr_regno,
-           INSN_UID (next), INSN_UID (mem.insn), dest, src);
+  if (TREE_THIS_VOLATILE (current_function_decl)
+      || cfun->machine->is_OS_task
+      || cfun->machine->is_OS_main)
+    return 0;
 
-  // Changing REG or POST_INC -> PRE_DEC means that the addend before
-  // the memory access must be increased by the size of the access,
-  rtx plus = plus_constant (Pmode, add.src, msize);
-  if (! REG_P (plus))
+  for (int reg = REG_0; reg < REG_32; reg++)
     {
-      rtx_insn *insn = emit_move_ccc_after (add.dest, plus, add.insn);
-      avr_dump (";; #2 new Add[%d] insn %d after %d: R%d += %d\n\n",
-               add.regno, INSN_UID (insn), INSN_UID (add.insn),
-               add.regno, (int) INTVAL (XEXP (plus, 1)));
-      gcc_assert (GET_CODE (plus) == PLUS);
-    }
-  else
-    avr_dump (";; Add[%d] insn %d consumed into %d\n\n",
-             add.regno, INSN_UID (add.insn), INSN_UID (next));
+      /* Do not push/pop __tmp_reg__, __zero_reg__, as well as
+        any global register variables.  */
 
-  // Changing POST_INC -> PRE_DEC means that the addend after the mem has to be
-  // the size of the access.  The hope is that this new add insn may be unused.
-  if (mem.addr_code == POST_INC)
-    {
-      plus = plus_constant (Pmode, add.dest, msize);
-      rtx_insn *next2 = emit_move_ccc_after (add.dest, plus, next);
-      avr_dump (";; #3 new Add[%d] insn %d after %d: R%d += %d\n\n", add.regno,
-               INSN_UID (next2), INSN_UID (next), add.regno, msize);
-      next = next2;
+      if (fixed_regs[reg])
+       continue;
+
+      if ((int_or_sig_p && !crtl->is_leaf && call_used_or_fixed_reg_p (reg))
+         || (df_regs_ever_live_p (reg)
+             && (int_or_sig_p || !call_used_or_fixed_reg_p (reg))
+             /* Don't record frame pointer registers here.  They are treated
+                indivitually in prologue.  */
+             && !(frame_pointer_needed
+                  && (reg == REG_Y || reg == REG_Y + 1))))
+       {
+         if (set)
+           SET_HARD_REG_BIT (*set, reg);
+         count++;
+       }
     }
+  return count;
+}
 
-  add.set_deleted ();
-  mem.set_deleted ();
 
-  return next;
+/* Implement `TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS' */
+
+static bool
+avr_allocate_stack_slots_for_args (void)
+{
+  return !cfun->machine->is_naked;
 }
 
-/* Try to combine a load or store insn with a PLUS of the address register.
-   If MEM is valid, then it precedes ADD in the same basic block.
-   When a replacement is found, a new insn is emitted and the old insns
-   are pseudo-deleted.  The returned insn is the point where the calling
-   scanner should continue.  When no replacement is found, nullptr is
-   returned and nothing changed.  */
 
-rtx_insn *
-avr_pass_fuse_add::fuse_mem_add (Mem_Insn &mem, Add_Insn &add)
+/* Implement `TARGET_CAN_ELIMINATE'.  */
+/* Return true if register FROM can be eliminated via register TO.  */
+
+static bool
+avr_can_eliminate (const int /*from*/, const int to)
 {
-  if (! mem.valid ()
-      || reg_seen_between_p (add.dest, mem.insn, add.insn))
-    {
-      // If something is between the Mem and the current insn, we can
-      // set the Mem invalid to speed future scans.
-      return mem.insn = nullptr;
-    }
+  return ((frame_pointer_needed && to == FRAME_POINTER_REGNUM)
+         || !frame_pointer_needed);
+}
 
-  AVR_LdSt_Props ap { mem };
 
-  int msize = GET_MODE_SIZE (mem.mode);
+/* Implement `TARGET_WARN_FUNC_RETURN'.  */
+
+static bool
+avr_warn_func_return (tree decl)
+{
+  /* Naked functions are implemented entirely in assembly, including the
+     return sequence, so suppress warnings about this.  */
 
-  // The add insn can be consumed by a POST_INC.
-  bool case1 = (mem.addr_code == REG
-               && INTVAL (add.addend) == msize
-               && ap.has_postinc && ! ap.want_predec);
+  return !avr_naked_function_p (decl);
+}
 
-  // There are cases where even a partial consumption of the offset is better.
-  // This are the cases where no LD+offset addressing is available, because
-  // the address register is obviously used after the mem insn, and a mem insn
-  // with REG addressing mode will have to restore the address.
-  bool case2 = (mem.addr_code == REG
-               && msize > 1 && ap.want_postinc && ! ap.has_ldd);
 
-  if (! case1 && ! case2)
-    return nullptr;
+/* Worker function for `INITIAL_ELIMINATION_OFFSET'.  */
+/* Compute offset between arg_pointer and frame_pointer.  */
 
-  // Change addressing mode from REG to POST_INC.
-  rtx xmem = change_address (mem.mem, mem.mode,
-                            gen_rtx_POST_INC (Pmode, mem.addr_reg));
-  rtx dest = mem.store_p ? xmem : mem.reg_or_0;
-  rtx src  = mem.store_p ? mem.reg_or_0 : xmem;
+int
+avr_initial_elimination_offset (int from, int to)
+{
+  if (from == FRAME_POINTER_REGNUM && to == STACK_POINTER_REGNUM)
+    return 0;
+  else
+    {
+      int offset = frame_pointer_needed ? 2 : 0;
+      int avr_pc_size = AVR_HAVE_EIJMP_EICALL ? 3 : 2;
 
-  rtx_insn *insn = emit_move_ccc_after (dest, src, mem.insn);
-  add_reg_note (insn, REG_INC, mem.addr_reg);
-  avr_dump (";; new Mem[%d] insn %d after %d: %r = %r\n\n", add.regno,
-           INSN_UID (insn), INSN_UID (mem.insn), dest, src);
+      // If FROM is ARG_POINTER_REGNUM, we are not in an ISR as ISRs
+      // might not have arguments.  Hence the following is not affected
+      // by gasisr prologues.
+      offset += avr_regs_to_save (NULL);
+      return (get_frame_size () + avr_outgoing_args_size()
+             + avr_pc_size + 1 + offset);
+    }
+}
 
-  rtx_insn *next = NEXT_INSN (add.insn);
 
-  // Changing REG -> POST_INC means that the post addend must be
-  // decreased by the size of the access.
-  rtx plus = plus_constant (Pmode, add.src, -msize);
-  if (! REG_P (plus))
-    {
-      next = emit_move_ccc_after (mem.addr_reg, plus, add.insn);
-      avr_dump (";; #4 new Add[%d] insn %d after %d: R%d += %d\n\n",
-               add.regno, INSN_UID (next), INSN_UID (add.insn),
-               add.regno, (int) INTVAL (XEXP (plus, 1)));
-      gcc_assert (GET_CODE (plus) == PLUS);
-    }
-  else
-    avr_dump (";; Add[%d] insn %d consumed into %d\n\n",
-             add.regno, INSN_UID (add.insn), INSN_UID (insn));
+/* Helper for the function below.  */
 
-  add.set_deleted ();
-  mem.set_deleted ();
+static void
+avr_adjust_type_node (tree *node, machine_mode mode, int sat_p)
+{
+  *node = make_node (FIXED_POINT_TYPE);
+  TYPE_SATURATING (*node) = sat_p;
+  TYPE_UNSIGNED (*node) = UNSIGNED_FIXED_POINT_MODE_P (mode);
+  TYPE_IBIT (*node) = GET_MODE_IBIT (mode);
+  TYPE_FBIT (*node) = GET_MODE_FBIT (mode);
+  TYPE_PRECISION (*node) = GET_MODE_BITSIZE (mode);
+  SET_TYPE_ALIGN (*node, 8);
+  SET_TYPE_MODE (*node, mode);
 
-  return next;
+  layout_type (*node);
 }
 
-/* Try to post-reload combine PLUS with CONST_INt of pointer registers with:
-   - Sets to a constant address.
-   - PLUS insn of that kind.
-   - Indirect loads and stores.
-   In almost all cases, combine opportunities arise from the preparation
-   done by `avr_split_tiny_move', but in some rare cases combinations are
-   found for the ordinary cores, too.
-      As we consider at most one Mem insn per try, there may still be missed
-   optimizations like  POST_INC + PLUS + POST_INC  might be performed
-   as  PRE_DEC + PRE_DEC  for two adjacent locations.  */
 
-unsigned int
-avr_pass_fuse_add::execute (function *func)
-{
-  df_note_add_problem ();
-  df_analyze ();
-
-  int n_add = 0, n_mem = 0, n_ldi = 0;
-  basic_block bb;
+/* Implement `TARGET_BUILD_BUILTIN_VA_LIST'.  */
 
-  FOR_EACH_BB_FN (bb, func)
-    {
-      Ldi_Insn prev_ldi_insns[REG_32];
-      Add_Insn prev_add_insns[REG_32];
-      Mem_Insn prev_mem_insns[REG_32];
-      rtx_insn *insn, *curr;
+static tree
+avr_build_builtin_va_list (void)
+{
+  /* avr-modes.def adjusts [U]TA to be 64-bit modes with 48 fractional bits.
+     This is more appropriate for the 8-bit machine AVR than 128-bit modes.
+     The ADJUST_IBIT/FBIT are handled in toplev:init_adjust_machine_modes()
+     which is auto-generated by genmodes, but the compiler assigns [U]DAmode
+     to the long long accum modes instead of the desired [U]TAmode.
 
-      avr_dump ("\n;; basic block %d\n\n", bb->index);
+     Fix this now, right after node setup in tree.cc:build_common_tree_nodes().
+     This must run before c-cppbuiltin.cc:builtin_define_fixed_point_constants()
+     which built-in defines macros like __ULLACCUM_FBIT__ that are used by
+     libgcc to detect IBIT and FBIT.  */
 
-      FOR_BB_INSNS_SAFE (bb, insn, curr)
-       {
-         rtx_insn *next = nullptr;
-         Ldi_Insn ldi_insn { insn };
-         Add_Insn add_insn { insn };
-         Mem_Insn mem_insn { insn };
+  avr_adjust_type_node (&ta_type_node, TAmode, 0);
+  avr_adjust_type_node (&uta_type_node, UTAmode, 0);
+  avr_adjust_type_node (&sat_ta_type_node, TAmode, 1);
+  avr_adjust_type_node (&sat_uta_type_node, UTAmode, 1);
 
-         if (add_insn.valid ())
-           {
-             // Found reg:HI += const_int
-             avr_dump (";; insn %d: Add[%d]: R%d += %d\n\n",
-                       INSN_UID (add_insn.insn), add_insn.regno,
-                       add_insn.regno, (int) INTVAL (add_insn.addend));
-             Ldi_Insn &prev_ldi_insn = prev_ldi_insns[add_insn.regno];
-             Add_Insn &prev_add_insn = prev_add_insns[add_insn.regno];
-             Mem_Insn &prev_mem_insn = prev_mem_insns[add_insn.regno];
-             if ((next = fuse_ldi_add (prev_ldi_insn, add_insn)))
-               curr = next, n_ldi += 1;
-             else if ((next = fuse_add_add (prev_add_insn, add_insn)))
-               curr = next, n_add += 1;
-             else if ((next = fuse_mem_add (prev_mem_insn, add_insn)))
-               curr = next, n_mem += 1;
-             else
-               prev_add_insn = add_insn;
-           }
-         else if (mem_insn.valid ())
-           {
-             int addr_regno = REGNO (mem_insn.addr_reg);
-             avr_dump (";; insn %d: Mem[%d]: %r = %r\n\n",
-                       INSN_UID (mem_insn.insn), addr_regno,
-                       mem_insn.dest, mem_insn.src);
-             Add_Insn &prev_add_insn = prev_add_insns[addr_regno];
-             if ((next = fuse_add_mem (prev_add_insn, mem_insn)))
-               curr = next, n_mem += 1;
-             else
-               prev_mem_insns[addr_regno] = mem_insn;
-           }
-         else if (ldi_insn.valid ())
-           {
-             if (! CONST_INT_P (ldi_insn.src))
-               avr_dump (";; insn %d: Ldi[%d]: R%d = %r\n\n",
-                         INSN_UID (ldi_insn.insn), ldi_insn.regno,
-                         ldi_insn.regno, ldi_insn.src);
-             prev_ldi_insns[ldi_insn.regno] = ldi_insn;
-           }
-       } // for insns
-    } // for BBs
+  unsigned_long_long_accum_type_node = uta_type_node;
+  long_long_accum_type_node = ta_type_node;
+  sat_unsigned_long_long_accum_type_node = sat_uta_type_node;
+  sat_long_long_accum_type_node = sat_ta_type_node;
 
-  avr_dump (";; Function %f: Found %d changes: %d ldi, %d add, %d mem.\n",
-           n_ldi + n_add + n_mem, n_ldi, n_add, n_mem);
+  /* Dispatch to the default handler.  */
 
-  return 0;
+  return std_build_builtin_va_list ();
 }
 
 
-namespace {
-static const pass_data avr_pass_data_pre_proep =
-{
-  RTL_PASS,      // type
-  "",      // name (will be patched)
-  OPTGROUP_NONE, // optinfo_flags
-  TV_DF_SCAN,    // tv_id
-  0,        // properties_required
-  0,        // properties_provided
-  0,        // properties_destroyed
-  0,        // todo_flags_start
-  0          // todo_flags_finish
-};
-
+/* Worker function for `INCOMING_RETURN_ADDR_RTX'.  */
+/* Return contents of MEM at frame pointer + stack size + 1 (+2 if 3-byte PC).
+   This is return address of function.  */
 
-class avr_pass_pre_proep : public rtl_opt_pass
+rtx
+avr_return_addr_rtx (int count, rtx tem)
 {
-public:
-  avr_pass_pre_proep (gcc::context *ctxt, const char *name)
-    : rtl_opt_pass (avr_pass_data_pre_proep, ctxt)
-  {
-    this->name = name;
-  }
+  rtx r;
 
-  void compute_maybe_gasisr (function *);
+  /* Can only return this function's return address. Others not supported.  */
+  if (count)
+    return NULL;
 
-  virtual unsigned int execute (function *fun)
-  {
-    if (avr_gasisr_prologues
-       // Whether this function is an ISR worth scanning at all.
-       && !fun->machine->is_no_gccisr
-       && (fun->machine->is_interrupt
-           || fun->machine->is_signal)
-       && !cfun->machine->is_naked
-       // Paranoia: Non-local gotos and labels that might escape.
-       && !cfun->calls_setjmp
-       && !cfun->has_nonlocal_label
-       && !cfun->has_forced_label_in_static)
-      {
-       compute_maybe_gasisr (fun);
-      }
+  if (AVR_3_BYTE_PC)
+    {
+      r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+2");
+      warning (0, "%<builtin_return_address%> contains only 2 bytes"
+              " of address");
+    }
+  else
+    r = gen_rtx_SYMBOL_REF (Pmode, ".L__stack_usage+1");
 
-    return 0;
-  }
+  cfun->machine->use_L__stack_usage = 1;
 
-}; // avr_pass_pre_proep
+  r = gen_rtx_PLUS (Pmode, tem, r);
+  r = gen_frame_mem (Pmode, memory_address (Pmode, r));
+  r = gen_rtx_ROTATE (HImode, r, GEN_INT (8));
+  return r;
+}
 
-} // anon namespace
+/* Return 1 if the function epilogue is just a single "ret".  */
 
-rtl_opt_pass *
-make_avr_pass_pre_proep (gcc::context *ctxt)
+int
+avr_simple_epilogue (void)
 {
-  return new avr_pass_pre_proep (ctxt, "avr-pre-proep");
+  return (! frame_pointer_needed
+         && get_frame_size () == 0
+         && avr_outgoing_args_size() == 0
+         && avr_regs_to_save (NULL) == 0
+         && ! cfun->machine->is_interrupt
+         && ! cfun->machine->is_signal
+         && ! cfun->machine->is_naked
+         && ! TREE_THIS_VOLATILE (current_function_decl));
 }
 
+/* This function checks sequence of live registers.  */
 
-/* Set fun->machine->gasisr.maybe provided we don't find anything that
-   prohibits GAS generating parts of ISR prologues / epilogues for us.  */
-
-void
-avr_pass_pre_proep::compute_maybe_gasisr (function *fun)
+static int
+sequent_regs_live (void)
 {
-  // Don't use BB iterators so that we see JUMP_TABLE_DATA.
+  int live_seq = 0;
+  int cur_seq = 0;
 
-  for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
+  for (int reg = 0; reg <= LAST_CALLEE_SAVED_REG; ++reg)
     {
-      // Transparent calls always use [R]CALL and are filtered out by GAS.
-      // ISRs don't use -mcall-prologues, hence what remains to be filtered
-      // out are open coded (tail) calls.
-
-      if (CALL_P (insn))
-       return;
+      if (fixed_regs[reg])
+       {
+         /* Don't recognize sequences that contain global register
+            variables.  */
 
-      // __tablejump2__ clobbers something and is targeted by JMP so
-      // that GAS won't see its usage.
+         if (live_seq != 0)
+           return 0;
+         else
+           continue;
+       }
 
-      if (AVR_HAVE_JMP_CALL
-         && JUMP_TABLE_DATA_P (insn))
-       return;
+      if (!call_used_or_fixed_reg_p (reg))
+       {
+         if (df_regs_ever_live_p (reg))
+           {
+             ++live_seq;
+             ++cur_seq;
+           }
+         else
+           cur_seq = 0;
+       }
+    }
 
-      // Non-local gotos not seen in *FUN.
+  if (!frame_pointer_needed)
+    {
+      if (df_regs_ever_live_p (REG_Y))
+       {
+         ++live_seq;
+         ++cur_seq;
+       }
+      else
+       cur_seq = 0;
 
-      if (JUMP_P (insn)
-         && find_reg_note (insn, REG_NON_LOCAL_GOTO, NULL_RTX))
-       return;
+      if (df_regs_ever_live_p (REG_Y + 1))
+       {
+         ++live_seq;
+         ++cur_seq;
+       }
+      else
+       cur_seq = 0;
     }
-
-  fun->machine->gasisr.maybe = 1;
+  else
+    {
+      cur_seq += 2;
+      live_seq += 2;
+    }
+  return (cur_seq == live_seq) ? live_seq : 0;
 }
 
 
@@ -7315,182 +5374,6 @@ out_movhi_mr_r (rtx_insn *insn, rtx op[], int *plen)
 }
 
 
-/* During reload, we allow much more addresses than Reduced Tiny actually
-   supports.  Split them after reload in order to get closer to the
-   core's capabilities.  This sets the stage for pass .avr-fuse-add.  */
-
-bool
-avr_split_tiny_move (rtx_insn * /*insn*/, rtx *xop)
-{
-  bool store_p = false;
-  rtx mem, reg_or_0;
-
-  if (REG_P (xop[0]) && MEM_P (xop[1]))
-    {
-      reg_or_0 = xop[0];
-      mem = xop[1];
-    }
-  else if (MEM_P (xop[0])
-          && (REG_P (xop[1])
-              || xop[1] == CONST0_RTX (GET_MODE (xop[0]))))
-    {
-      mem = xop[0];
-      reg_or_0 = xop[1];
-      store_p = true;
-    }
-  else
-    return false;
-
-  machine_mode mode = GET_MODE (mem);
-  rtx base, addr = XEXP (mem, 0);
-  enum rtx_code addr_code = GET_CODE (addr);
-
-  if (REG_P (reg_or_0)
-      && reg_overlap_mentioned_p (reg_or_0, addr))
-    return false;
-  else if (addr_code == PLUS || addr_code == PRE_DEC || addr_code == POST_INC)
-    base = XEXP (addr, 0);
-  else if (addr_code == REG)
-    base = addr;
-  else
-    return false;
-
-  if (REGNO (base) > REG_Z)
-    return false;
-
-  if (! AVR_TINY
-      // Only keep base registers that can't do PLUS addressing.
-      && ((REGNO (base) != REG_X
-          && ADDR_SPACE_GENERIC_P (MEM_ADDR_SPACE (mem)))
-         || avr_load_libgcc_p (mem)
-         || avr_mem_memx_p (mem)))
-    return false;
-
-  bool volatile_p = MEM_VOLATILE_P (mem);
-  bool mem_volatile_p = false;
-  if (frame_pointer_needed
-      && REGNO (base) == FRAME_POINTER_REGNUM)
-    {
-      if (avr_fuse_add < 2
-         // Be a projection (we always split PLUS).
-         || (avr_fuse_add == 2 && volatile_p && addr_code != PLUS))
-       return false;
-
-      // Changing the frame pointer locally may confuse later passes
-      // like .dse2 which don't track changes of FP, not even when
-      // respective CFA notes are present.  An example is pr22141-1.c.
-      if (avr_fuse_add == 2)
-       mem_volatile_p = true;
-    }
-
-  enum rtx_code new_code = UNKNOWN;
-  HOST_WIDE_INT add = 0, sub = 0;
-  int msize = GET_MODE_SIZE (mode);
-
-  AVR_LdSt_Props ap { REGNO (base), store_p, volatile_p, ADDR_SPACE_GENERIC };
-
-  switch (addr_code)
-    {
-    default:
-      return false;
-
-    case PLUS:
-      add = INTVAL (XEXP (addr, 1));
-      if (msize == 1)
-       {
-         new_code = REG;
-         sub = -add;
-       }
-      else if (ap.want_predec)
-       {
-         // volatile stores prefer PRE_DEC (MSB first)
-         sub = -add;
-         add += msize;
-         new_code = PRE_DEC;
-       }
-      else
-       {
-         new_code = POST_INC;
-         sub = -add - msize;
-       }
-      break;
-
-    case POST_INC:
-      // volatile stores prefer PRE_DEC (MSB first)
-      if (msize > 1 && ap.want_predec)
-       {
-         add = msize;
-         new_code = PRE_DEC;
-         sub = msize;
-         break;
-       }
-      return false;
-
-    case PRE_DEC:
-      // volatile loads prefer POST_INC (LSB first)
-      if (msize > 1 && ap.want_postinc)
-       {
-         add = -msize;
-         new_code = POST_INC;
-         sub = -msize;
-         break;
-       }
-      return false;
-
-    case REG:
-      if (msize == 1)
-       return false;
-
-      if (ap.want_predec)
-       {
-         add = msize;
-         new_code = PRE_DEC;
-         sub = 0;
-       }
-      else
-       {
-         add = 0;
-         new_code = POST_INC;
-         sub = -msize;
-       }
-      break;
-    } // switch addr_code
-
-  rtx_insn *insn;
-
-  if (add)
-    {
-      insn = emit_move_ccc (base, plus_constant (Pmode, base, add));
-      avr_maybe_adjust_cfa (insn, base, add);
-    }
-
-  rtx new_addr = new_code == REG
-    ? base
-    : gen_rtx_fmt_e (new_code, Pmode, base);
-
-  rtx new_mem = change_address (mem, mode, new_addr);
-  if (mem_volatile_p)
-    MEM_VOLATILE_P (new_mem) = 1;
-
-  insn = emit_move_ccc (store_p ? new_mem : reg_or_0,
-                       store_p ? reg_or_0 : new_mem);
-  if (auto_inc_p (new_addr))
-    {
-      add_reg_note (insn, REG_INC, base);
-      int off = new_code == POST_INC ? msize : -msize;
-      avr_maybe_adjust_cfa (insn, base, off);
-    }
-
-  if (sub)
-    {
-      insn = emit_move_ccc (base, plus_constant (Pmode, base, sub));
-      avr_maybe_adjust_cfa (insn, base, sub);
-    }
-
-  return true;
-}
-
-
 /* Implement `TARGET_FRAME_POINTER_REQUIRED'.  */
 /* Return 1 if frame pointer for current function required.  */
 
diff --git a/gcc/config/avr/ranges.h b/gcc/config/avr/ranges.h
new file mode 100644 (file)
index 0000000..89f6896
--- /dev/null
@@ -0,0 +1,278 @@
+/* Subsets of a finite interval over Z.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+
+   This file is part of GCC.
+
+   GCC is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   GCC is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with GCC; see the file COPYING3.  If not see
+   <http://www.gnu.org/licenses/>.  */
+
+/* A class that represents the union of finitely many intervals.
+   The domain over which the intervals are defined is a finite integer
+   interval [m_min, m_max], usually the range of some [u]intN_t.
+   Supported operations are:
+   - Complement w.r.t. the domain (invert)
+   - Union (union_)
+   - Intersection (intersect)
+   - Difference / Setminus (minus).
+   Ranges is closed under all operations:  The result of all operations
+   is a Ranges over the same domain.  (As opposed to value-range.h which
+   may ICE for some operations, see below).
+
+   The representation is unique in the sense that when we have two
+   Ranges A and B, then
+   1)  A == B  <==>  A.size == B.size  &&  Ai == Bi for all i.
+
+   The representation is normalized:
+   2)  Ai != {}          ;; There are no empty intervals.
+   3)  Ai.hi < A{i+1}.lo ;; The Ai's are in increasing order and separated
+                        ;; by at least one value (non-adjacent).
+   The sub-intervals Ai are maintained as a std::vector.
+   The computation of union and intersection scales like  A.size * B.size
+   i.e. Ranges is only eligible for GCC when size() has a fixed upper
+   bound independent of the program being compiled (or there are other
+   means to guarantee that the complexity is linearistic).
+   In the context of AVR, we have size() <= 3.
+
+   The reason why we don't use value-range.h's irange or int_range is that
+   these use the integers Z as their domain, which makes computations like
+   invert() quite nasty as they may ICE for common cases.  Doing all
+   these special cases (like one sub-interval touches the domain bounds)
+   makes using value-range.h more laborious (and instable) than using our
+   own mini Ranger.  */
+
+struct Ranges
+{
+  // This is good enough as it covers (un)signed SImode.
+  using T = HOST_WIDE_INT;
+  typedef T scalar_type;
+
+  // Non-empty ranges.  Empty sets are only used transiently;
+  // Ranges.ranges[] doesn't use them.
+  struct SubRange
+  {
+    // Lower and upper bound, inclusively.
+    T lo, hi;
+
+    SubRange intersect (const SubRange &r) const
+    {
+      if (lo >= r.lo && hi <= r.hi)
+       return *this;
+      else if (r.lo >= lo && r.hi <= hi)
+       return r;
+      else if (lo > r.hi || hi < r.lo)
+       return SubRange { 1, 0 };
+      else
+       return SubRange { std::max (lo, r.lo), std::min (hi, r.hi) };
+    }
+
+    T cardinality () const
+    {
+      return std::max<T> (0, hi - lo + 1);
+    }
+  };
+
+  // Finitely many intervals over [m_min, m_max] that are normalized:
+  // No empty sets, increasing order, separated by at least one value.
+  T m_min, m_max;
+  std::vector<SubRange> ranges;
+
+  // Not used anywhere in Ranges; can be used elsewhere.
+  // May be clobbered by set operations.
+  int tag = -1;
+
+  enum initial_range { EMPTY, ALL };
+
+  Ranges (T mi, T ma, initial_range ir)
+    : m_min (mi), m_max (ma)
+  {
+    if (ir == ALL)
+      push (mi, ma);
+  }
+
+  // Domain is the range of some [u]intN_t.
+  static Ranges NBitsRanges (int n_bits, bool unsigned_p, initial_range ir)
+  {
+    T mask = ((T) 1 << n_bits) - 1;
+    gcc_assert (mask > 0);
+    T ma = mask >> ! unsigned_p;
+    return Ranges (unsigned_p ? 0 : -ma - 1, ma, ir);
+  }
+
+  static void sort2 (Ranges &a, Ranges &b)
+  {
+    if (a.size () && b.size ())
+      if (a.ranges[0].lo > b.ranges[0].lo)
+       std::swap (a, b);
+  }
+
+  void print (FILE *file) const
+  {
+    if (file)
+      {
+       fprintf (file, " .tag%d=#%d={", tag, size ());
+       for (const auto &r : ranges)
+         fprintf (file, "[ %ld, %ld ]", (long) r.lo, (long) r.hi);
+       fprintf (file, "}\n");
+      }
+  }
+
+  // The number of sub-intervals in .ranges.
+  int size () const
+  {
+    return (int) ranges.size ();
+  }
+
+  // Append [LO, HI] & [m_min, m_max] to .ranges provided the
+  // former is non-empty.
+  void push (T lo, T hi)
+  {
+    lo = std::max (lo, m_min);
+    hi = std::min (hi, m_max);
+
+    if (lo <= hi)
+      ranges.push_back (SubRange { lo, hi });
+  }
+
+  // Append R to .ranges provided the former is non-empty.
+  void push (const SubRange &r)
+  {
+    push (r.lo, r.hi);
+  }
+
+  // Cardinality of the n-th interval.
+  T cardinality (int n) const
+  {
+    return n < size () ? ranges[n].cardinality () : 0;
+  }
+
+  // Check that *this is normalized: .ranges are non-empty, non-overlapping,
+  // non-adjacent and increasing.
+  bool check () const
+  {
+    bool bad = size () && (ranges[0].lo < m_min
+                          || ranges[size () - 1].hi > m_max);
+
+    for (int n = 0; n < size (); ++n)
+      {
+       bad |= ranges[n].lo > ranges[n].hi;
+       bad |= n > 0 && ranges[n - 1].hi >= ranges[n].lo;
+      }
+
+    if (bad)
+      print (dump_file);
+
+    return ! bad;
+  }
+
+  // Intersect A and B according to  (U Ai) & (U Bj) = U (Ai & Bj)
+  // This has quadratic complexity, but also the nice property that
+  // when A and B are normalized, then the result is too.
+  void intersect (const Ranges &r)
+  {
+    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+
+    if (this == &r)
+      return;
+
+    std::vector<SubRange> rs;
+    std::swap (rs, ranges);
+
+    for (const auto &a : rs)
+      for (const auto &b : r.ranges)
+       push (a.intersect (b));
+  }
+
+  // Complement w.r.t. the domain [m_min, m_max].
+  void invert ()
+  {
+    std::vector<SubRange> rs;
+    std::swap (rs, ranges);
+
+    if (rs.size () == 0)
+       push (m_min, m_max);
+    else
+      {
+       push (m_min, rs[0].lo - 1);
+
+       for (size_t n = 1; n < rs.size (); ++n)
+         push (rs[n - 1].hi + 1, rs[n].lo - 1);
+
+       push (rs[rs.size () - 1].hi + 1, m_max);
+      }
+  }
+
+  // Set-minus.
+  void minus (const Ranges &r)
+  {
+    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+
+    Ranges sub = r;
+    sub.invert ();
+    intersect (sub);
+  }
+
+  // Union of sets.  Not needed in avr.cc but added for completeness.
+  // DeMorgan this for simplicity.
+  void union_ (const Ranges &r)
+  {
+    gcc_assert (m_min == r.m_min && m_max == r.m_max);
+
+    if (this != &r)
+      {
+       invert ();
+       minus (r);
+       invert ();
+      }
+  }
+
+  // Get the truth Ranges for  x <cmp> val.  For example,
+  // LT 3  will return  [m_min, 2].
+  Ranges truth (rtx_code cmp, T val, bool strict = true)
+  {
+    if (strict)
+      {
+       if (avr_strict_signed_p (cmp))
+         gcc_assert (m_min == -m_max - 1);
+       else if (avr_strict_unsigned_p (cmp))
+         gcc_assert (m_min == 0);
+
+       gcc_assert (IN_RANGE (val, m_min, m_max));
+      }
+
+    bool rev = cmp == NE || cmp == LTU || cmp == LT || cmp == GTU || cmp == GT;
+    if (rev)
+      cmp = reverse_condition (cmp);
+
+    T lo = m_min;
+    T hi = m_max;
+
+    if (cmp == EQ)
+      lo = hi = val;
+    else if (cmp == LEU || cmp == LE)
+      hi = val;
+    else if (cmp == GEU || cmp == GE)
+      lo = val;
+    else
+      gcc_unreachable ();
+
+    Ranges rs (m_min, m_max, Ranges::EMPTY);
+    rs.push (lo, hi);
+
+    if (rev)
+      rs.invert ();
+
+    return rs;
+  }
+
+}; // struct Ranges
index 449512a3395e4609798df2e77f8db366d3e3daa9..3da13289f332aa28abcb232e40f611918ea2d8c4 100644 (file)
@@ -59,8 +59,14 @@ avr-log.o: $(srcdir)/config/avr/avr-log.cc \
   $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) $(INPUT_H) dumpfile.h
        $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) $<
 
+avr-passes.o: $(srcdir)/config/avr/avr-passes.cc \
+  $(CONFIG_H) $(SYSTEM_H) coretypes.h $(TM_H) $(TREE_H) $(INPUT_H)
+       $(COMPILER) -c $(ALL_COMPILERFLAGS) $(ALL_CPPFLAGS) $(INCLUDES) $<
+
 avr.o avr-c.o: $(srcdir)/config/avr/builtins.def
 
+avr-passes.o: $(srcdir)/config/avr/ranges.h
+
 # This overrides stdfix.h from USER_H which we supply and include
 # in our own stdfix.h as stdfix-gcc.h.