--- /dev/null
+/* 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");
+}
#define IN_TARGET_CODE 1
-#define INCLUDE_VECTOR
#include "config.h"
#include "system.h"
#include "intl.h"
#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. */
}
-/* 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;
}
}
-/* 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. */