diff --git a/gcc/config/avr/avr.cc b/gcc/config/avr/avr.cc index c520b98a178d2266189879239f76a928dfd989cf..774d2c9978902e97277488b9d20024f9276ce0f7 100644 --- a/gcc/config/avr/avr.cc +++ b/gcc/config/avr/avr.cc @@ -20,6 +20,7 @@ #define IN_TARGET_CODE 1 +#define INCLUDE_VECTOR #include "config.h" #include "system.h" #include "intl.h" @@ -33,6 +34,7 @@ #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" @@ -337,6 +339,452 @@ ra_in_progress () } +/* 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; +} + + +/* 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; + + SubRange intersect (const SubRange &r) const + { + if (lo >= r.lo && hi <= r.hi) + return *this; + else if (r.lo >= lo && r.hi <= hi) + return r; + else if (lo > r.hi || hi < r.lo) + return SubRange { 1, 0 }; + else + return SubRange { std::max (lo, r.lo), std::min (hi, r.hi) }; + } + + T cardinality () const + { + return std::max<T> (0, hi - lo + 1); + } + }; + + // Finitely many intervals over [m_min, m_max] that are normalized: + // No empty sets, increasing order, separated by at least one value. + T m_min, m_max; + std::vector<SubRange> ranges; + + // Not used anywhere in Ranges; can be used elsewhere. + // May be clobbered by set operations. + int tag = -1; + + enum initial_range { EMPTY, ALL }; + + Ranges (T mi, T ma, initial_range ir) + : m_min (mi), m_max (ma) + { + if (ir == ALL) + push (mi, ma); + } + + // Domain is the range of some [u]intN_t. + static Ranges NBitsRanges (int n_bits, bool unsigned_p, initial_range ir) + { + T mask = ((T) 1 << n_bits) - 1; + gcc_assert (mask > 0); + T ma = mask >> ! unsigned_p; + return Ranges (unsigned_p ? 0 : -ma - 1, ma, ir); + } + + static void sort2 (Ranges &a, Ranges &b) + { + if (a.size () && b.size ()) + if (a.ranges[0].lo > b.ranges[0].lo) + std::swap (a, b); + } + + void print (FILE *file) const + { + if (file) + { + fprintf (file, " .tag%d=#%d={", tag, size ()); + for (const auto &r : ranges) + fprintf (file, "[ %ld, %ld ]", (long) r.lo, (long) r.hi); + fprintf (file, "}\n"); + } + } + + // The number of sub-intervals in .ranges. + int size () const + { + return (int) ranges.size (); + } + + // Append [LO, HI] & [m_min, m_max] to .ranges provided the + // former is non-empty. + void push (T lo, T hi) + { + lo = std::max (lo, m_min); + hi = std::min (hi, m_max); + + if (lo <= hi) + ranges.push_back (SubRange { lo, hi }); + } + + // Append R to .ranges provided the former is non-empty. + void push (const SubRange &r) + { + push (r.lo, r.hi); + } + + // Cardinality of the n-th interval. + T cardinality (int n) const + { + return n < size () ? ranges[n].cardinality () : 0; + } + + // Check that *this is normalized: .ranges are non-empty, non-overlapping, + // non-adjacent and increasing. + bool check () const + { + bool bad = size () && (ranges[0].lo < m_min + || ranges[size () - 1].hi > m_max); + + for (int n = 0; n < size (); ++n) + { + bad |= ranges[n].lo > ranges[n].hi; + bad |= n > 0 && ranges[n - 1].hi >= ranges[n].lo; + } + + if (bad) + print (dump_file); + + return ! bad; + } + + // Intersect A and B according to (U Ai) & (U Bj) = U (Ai & Bj) + // This has quadratic complexity, but also the nice property that + // when A and B are normalized, then the result is too. + void intersect (const Ranges &r) + { + gcc_assert (m_min == r.m_min && m_max == r.m_max); + + if (this == &r) + return; + + std::vector<SubRange> rs; + std::swap (rs, ranges); + + for (const auto &a : rs) + for (const auto &b : r.ranges) + push (a.intersect (b)); + } + + // Complement w.r.t. the domain [m_min, m_max]. + void invert () + { + std::vector<SubRange> rs; + std::swap (rs, ranges); + + if (rs.size () == 0) + push (m_min, m_max); + else + { + push (m_min, rs[0].lo - 1); + + for (size_t n = 1; n < rs.size (); ++n) + push (rs[n - 1].hi + 1, rs[n].lo - 1); + + push (rs[rs.size () - 1].hi + 1, m_max); + } + } + + // Set-minus. + void minus (const Ranges &r) + { + gcc_assert (m_min == r.m_min && m_max == r.m_max); + + Ranges sub = r; + sub.invert (); + intersect (sub); + } + + // Union of sets. Not needed in avr.cc but added for completeness. + // DeMorgan this for simplicity. + void union_ (const Ranges &r) + { + gcc_assert (m_min == r.m_min && m_max == r.m_max); + + if (this != &r) + { + invert (); + minus (r); + invert (); + } + } + + // Get the truth Ranges for x <cmp> val. For example, + // LT 3 will return [m_min, 2]. + Ranges truth (rtx_code cmp, T val, bool strict = true) + { + if (strict) + { + if (avr_strict_signed_p (cmp)) + gcc_assert (m_min == -m_max - 1); + else if (avr_strict_unsigned_p (cmp)) + gcc_assert (m_min == 0); + + gcc_assert (IN_RANGE (val, m_min, m_max)); + } + + bool rev = cmp == NE || cmp == LTU || cmp == LT || cmp == GTU || cmp == GT; + if (rev) + cmp = reverse_condition (cmp); + + T lo = m_min; + T hi = m_max; + + if (cmp == EQ) + lo = hi = val; + else if (cmp == LEU || cmp == LE) + hi = val; + else if (cmp == GEU || cmp == GE) + lo = val; + else + gcc_unreachable (); + + Ranges rs (m_min, m_max, Ranges::EMPTY); + rs.push (lo, hi); + + if (rev) + rs.invert (); + + return rs; + } +}; // struct Ranges + + +/* 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); +} + + namespace { static const pass_data avr_pass_data_recompute_notes = @@ -775,134 +1223,315 @@ avr_pass_casesi::avr_rest_of_handle_casesi (function *func) /* 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 is a code <cond> such - that the sequence can be performed as + 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; - REG_CC = compare (reg, xval1); - if (REG_CC <cond1> 0) goto label1; - if (REG_CC <cond> 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. - then return <cond>. Otherwise, return UNKNOWN. - xval1 and xval2 are CONST_INT, and mode is the scalar int mode in which - the comparison will be carried out. reverse_cond1 can be set to reverse - condition cond1. This is useful if the second comparison does not follow - the first one, but is located after label1 like in: + 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; */ + 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; -static enum rtx_code -avr_redundant_compare (enum rtx_code cond1, rtx xval1, - enum rtx_code cond2, rtx xval2, - machine_mode mode, bool reverse_cond1) + 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) { - HOST_WIDE_INT ival1 = INTVAL (xval1); - HOST_WIDE_INT ival2 = INTVAL (xval2); + bool swapped; - unsigned HOST_WIDE_INT mask = GET_MODE_MASK (mode); - unsigned HOST_WIDE_INT uval1 = mask & UINTVAL (xval1); - unsigned HOST_WIDE_INT uval2 = mask & UINTVAL (xval2); + 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 (cond1 == EQ) - { - //////////////////////////////////////////////// - // A sequence like - // if (reg == val) goto label1; - // if (reg > val) goto label2; - // can be re-written using the same, simple comparison like in: - // REG_CC = compare (reg, val) - // if (REG_CC == 0) goto label1; - // if (REG_CC >= 0) goto label2; - if (ival1 == ival2 - && (cond2 == GT || cond2 == GTU)) - return avr_normalize_condition (cond2); - - // Similar, but the input sequence is like - // if (reg == val) goto label1; - // if (reg >= val) goto label2; - if (ival1 == ival2 - && (cond2 == GE || cond2 == GEU)) - return cond2; - - // Similar, but the input sequence is like - // if (reg == val) goto label1; - // if (reg >= val + 1) goto label2; - if ((cond2 == GE && ival2 == 1 + ival1) - || (cond2 == GEU && uval2 == 1 + uval1)) - return cond2; - - // Similar, but the input sequence is like - // if (reg == val) goto label1; - // if (reg > val - 1) goto label2; - if ((cond2 == GT && ival2 == ival1 - 1) - || (cond2 == GTU && uval2 == uval1 - 1)) - return avr_normalize_condition (cond2); - - ///////////////////////////////////////////////////////// - // A sequence like - // if (reg == val) goto label1; - // if (reg < 1 + val) goto label2; - // can be re-written as - // REG_CC = compare (reg, val) - // if (REG_CC == 0) goto label1; - // if (REG_CC < 0) goto label2; - if ((cond2 == LT && ival2 == 1 + ival1) - || (cond2 == LTU && uval2 == 1 + uval1)) - return cond2; - - // Similar, but with an input sequence like - // if (reg == val) goto label1; - // if (reg <= val) goto label2; - if (ival1 == ival2 - && (cond2 == LE || cond2 == LEU)) - return avr_normalize_condition (cond2); - - // Similar, but with an input sequence like - // if (reg == val) goto label1; - // if (reg < val) goto label2; - if (ival1 == ival2 - && (cond2 == LT || cond2 == LTU)) - return cond2; - - // Similar, but with an input sequence like - // if (reg == val) goto label1; - // if (reg <= val - 1) goto label2; - if ((cond2 == LE && ival2 == ival1 - 1) - || (cond2 == LEU && uval2 == uval1 - 1)) - return avr_normalize_condition (cond2); - - } // cond1 == EQ + 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); - return UNKNOWN; + // 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; } -/* If-else decision trees generated for switch / case may produce sequences - like +/* 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, val); - if (SREG == 0) goto label1; SREG = compare (reg, 1 + val); - if (SREG >= 0) goto label2; + if (SREG >= 0) goto label1; + SREG = compare (reg, val); + if (SREG == 0) goto label2; - which can be optimized to + can be optimized to SREG = compare (reg, val); - if (SREG == 0) goto label1; - if (SREG >= 0) goto label2; + if (SREG == 0) goto label2; + if (SREG >= 0) goto label1; - The optimal place for such a pass would be directly after expand, but - it's not possible for a jump insn to target more than one code label. - Hence, run a mini pass right before split2 which introduces REG_CC. */ + 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 *) @@ -931,143 +1560,43 @@ avr_pass_ifelse::avr_rest_of_handle_ifelse (function *) continue; rtx_jump_insn *insn1 = as_a<rtx_jump_insn *> (insn); - rtx_jump_insn *insn2 = nullptr; - bool follow_label1 = false; - - // Extract the operands of the first insn: - // $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; - // For now, we can optimize cbranches that follow an EQ cbranch, - // and cbranches that follow the label of a NE cbranch. + // jmp[0]: We can optimize cbranches that follow cbranch insn1. + rtx_insn *jmp[2] = { next_insn, nullptr }; - if (GET_CODE (xop1[0]) == EQ - && JUMP_P (next_insn) - && recog_memoized (next_insn) == icode1) + // jmp[1]: A cbranch following the label of cbranch insn1. + if (LABEL_NUSES (JUMP_LABEL (insn1)) == 1) { - // The 2nd cbranch insn follows insn1, i.e. is located in the - // fallthrough path of insn1. - - insn2 = as_a<rtx_jump_insn *> (next_insn); - } - else if (GET_CODE (xop1[0]) == NE) - { - // insn1 might branch to a label followed by a cbranch. - - rtx target1 = JUMP_LABEL (insn1); rtx_insn *code_label1 = JUMP_LABEL_AS_INSN (insn1); - rtx_insn *next = next_nonnote_nondebug_insn (code_label1); rtx_insn *barrier = prev_nonnote_nondebug_insn (code_label1); - if (// Target label of insn1 is used exactly once and - // is not a fallthru, i.e. is preceded by a barrier. - LABEL_NUSES (target1) == 1 - && barrier - && BARRIER_P (barrier) - // Following the target label is a cbranch of the same kind. - && next - && JUMP_P (next) - && recog_memoized (next) == icode1) - { - follow_label1 = true; - insn2 = as_a<rtx_jump_insn *> (next); - } - } - - if (! insn2) - continue; - - // Also extract operands of insn2, and filter for REG + CONST_INT - // comparsons against the same register. - - extract_insn (insn2); - rtx xop2[5] = { op[0], op[1], op[2], op[3], op[4] }; - - if (! rtx_equal_p (xop1[1], xop2[1]) - || ! CONST_INT_P (xop1[2]) - || ! CONST_INT_P (xop2[2])) - continue; - - machine_mode mode = GET_MODE (xop1[1]); - enum rtx_code code1 = GET_CODE (xop1[0]); - enum rtx_code code2 = GET_CODE (xop2[0]); - - code2 = avr_redundant_compare (code1, xop1[2], code2, xop2[2], - mode, follow_label1); - if (code2 == UNKNOWN) - continue; - - ////////////////////////////////////////////////////// - // Found a replacement. - - if (dump_file) - { - 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)); - } - - if (! follow_label1) - next_insn = next_nonnote_nondebug_insn (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; - - if (mode == QImode) - { - gcc_assert (n_operands == 4); - xcompare = gen_cmpqi3 (xop1[1], xop1[2]); - } - else - { - gcc_assert (n_operands == 5); - 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], xop1[2], xop1[4]); - } - - // Emit that stuff. - - rtx_insn *cmp = emit_insn_before (xcompare, insn1); - rtx_jump_insn *branch1 = emit_jump_insn_before (xpat1, insn1); - rtx_jump_insn *branch2 = emit_jump_insn_before (xpat2, insn2); + // 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); + } - 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]); + // 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. - delete_insn (insn1); - delete_insn (insn2); + 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; + } + } - // 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)); } // loop insns } diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i16.c new file mode 100644 index 0000000000000000000000000000000000000000..c6d25604ffda6251e1c3ca6566c9e52de7b6babb --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i16 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-c.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i8.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i8.c new file mode 100644 index 0000000000000000000000000000000000000000..24c71a4a6f868be017f5179893d677067637a77b --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-i8.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i8 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-c.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-c-u16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-u16.c new file mode 100644 index 0000000000000000000000000000000000000000..cb7511280854f757b16ba882a7959b18d39c353a --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-c-u16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T u16 +#endif + +#ifndef W +#define W -5ul +#endif + +#include "ifelse-c.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-c.h b/gcc/testsuite/gcc.target/avr/torture/ifelse-c.h new file mode 100644 index 0000000000000000000000000000000000000000..e742415d7fd42d96bfbf9f3a58abd80c324de791 --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-c.h @@ -0,0 +1,99 @@ +/* Testing pass avr-ifelse with REG-CONST_INT comparisons. */ + +#define V ((T) (W)) + +#ifdef __OPTIMIZE__ + +typedef __UINT32_TYPE__ u32; +typedef __uint24 u24; +typedef __UINT16_TYPE__ u16; +typedef __UINT8_TYPE__ u8; + +typedef __INT32_TYPE__ i32; +typedef __int24 i24; +typedef __INT16_TYPE__ i16; +typedef __INT8_TYPE__ i8; + +char volatile cc; + +#define NI __attribute__((noipa)) +#define AI static __inline__ __attribute__((always_inline)) + +NI +void f (char x) +{ + cc += x; +} + +#define MK_FUN(id, cmp1, cmp2) \ +NI void fun_##id (T x) \ +{ \ + if (x cmp1) \ + goto a; \ + if (x cmp2) \ + goto b; \ + f(1); \ + a: \ + f(2); \ + b: \ + f(4); \ +} \ + \ +NI char val_##id (T x) \ +{ \ + char c = 0; \ + if (x cmp1) \ + goto a; \ + __asm ("" : "+r" (x)); \ + if (x cmp2) \ + goto b; \ + c += 1; \ + a: \ + c += 2; \ + b: \ + c += 4; \ + \ + return c; \ +} + +MK_FUN (01, > V, < V) +MK_FUN (02, < V, > V) + +MK_FUN (03, == V, <= V) +MK_FUN (04, == V, >= V) + +MK_FUN (05, == V, < V) +MK_FUN (06, == V, > V) + +MK_FUN (07, > V, == V) +MK_FUN (08, < V, == V) + +MK_FUN (09, > V, != V) +MK_FUN (10, < V, != V) + +void testA (void) +{ + for (T x = (T) (V - 2); x != (T) (V + 2); ++x) + { + cc = 0; fun_01 (x); if (cc != val_01 (x)) __builtin_exit (__LINE__); + cc = 0; fun_02 (x); if (cc != val_02 (x)) __builtin_exit (__LINE__); + cc = 0; fun_03 (x); if (cc != val_03 (x)) __builtin_exit (__LINE__); + cc = 0; fun_04 (x); if (cc != val_04 (x)) __builtin_exit (__LINE__); + cc = 0; fun_05 (x); if (cc != val_05 (x)) __builtin_exit (__LINE__); + cc = 0; fun_06 (x); if (cc != val_06 (x)) __builtin_exit (__LINE__); + cc = 0; fun_07 (x); if (cc != val_07 (x)) __builtin_exit (__LINE__); + cc = 0; fun_08 (x); if (cc != val_08 (x)) __builtin_exit (__LINE__); + cc = 0; fun_09 (x); if (cc != val_09 (x)) __builtin_exit (__LINE__); + cc = 0; fun_10 (x); if (cc != val_10 (x)) __builtin_exit (__LINE__); + } +} +#endif /* OPTIMIZE */ + +int main (void) +{ +#ifdef __OPTIMIZE__ + testA(); +#endif /* OPTIMIZE */ + + return 0; +} diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i16.c new file mode 100644 index 0000000000000000000000000000000000000000..ad13c9e45e1532a8a3617188e39e27da6bed3a0a --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i16 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-d.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i8.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i8.c new file mode 100644 index 0000000000000000000000000000000000000000..bf2cd699ddf3c9415986383765c4ba8fa4f7f1ec --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-i8.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i8 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-d.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-d-u16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-u16.c new file mode 100644 index 0000000000000000000000000000000000000000..6f673be7da41966297d0d6435a147d360f23f99c --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-d-u16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T u16 +#endif + +#ifndef W +#define W -5ul +#endif + +#include "ifelse-d.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-d.h b/gcc/testsuite/gcc.target/avr/torture/ifelse-d.h new file mode 100644 index 0000000000000000000000000000000000000000..3ff2494ea6a21d04c03721c4ae1524d09bed4cff --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-d.h @@ -0,0 +1,82 @@ +/* Testing pass avr-ifelse with REG-CONST_INT comparisons + where the 2nd insn is behind the true edge of insn1 (reverse_cond1). */ + +#define V ((T) (W)) + +#ifdef __OPTIMIZE__ + +typedef __UINT32_TYPE__ u32; +typedef __uint24 u24; +typedef __UINT16_TYPE__ u16; +typedef __UINT8_TYPE__ u8; + +typedef __INT32_TYPE__ i32; +typedef __int24 i24; +typedef __INT16_TYPE__ i16; +typedef __INT8_TYPE__ i8; + +char volatile cc; + +#define NI __attribute__((noipa)) +#define AI static __inline__ __attribute__((always_inline)) + +NI +void f (char x) +{ + cc += x; +} + +#define MK_FUN(id, cmp1, cmp2) \ +NI void fun_##id (T x) \ +{ \ + if (x cmp1) \ + f (1); \ + else if (x cmp2) \ + f (2); \ +} \ + \ +NI char val_##id (T x) \ +{ \ + char c = 0; \ + T x2 = x; \ + __asm ("" : "+r" (x2)); \ + \ + if (x cmp1) \ + c += 1; \ + else if (x2 cmp2) \ + c += 2; \ + \ + return c; \ +} + +MK_FUN (01, > V, == V) +MK_FUN (02, < V, == V) + +MK_FUN (03, == V, < V) +MK_FUN (04, == V, > V) + +MK_FUN (05, > V, != V) +MK_FUN (06, < V, != V) + +void testA (void) +{ + for (T x = (T) (V - 2); x != (T) (V + 2); ++x) + { + cc = 0; fun_01 (x); if (cc != val_01 (x)) __builtin_exit (__LINE__); + cc = 0; fun_02 (x); if (cc != val_02 (x)) __builtin_exit (__LINE__); + cc = 0; fun_03 (x); if (cc != val_03 (x)) __builtin_exit (__LINE__); + cc = 0; fun_04 (x); if (cc != val_04 (x)) __builtin_exit (__LINE__); + cc = 0; fun_05 (x); if (cc != val_05 (x)) __builtin_exit (__LINE__); + cc = 0; fun_06 (x); if (cc != val_06 (x)) __builtin_exit (__LINE__); + } +} +#endif /* OPTIMIZE */ + +int main (void) +{ +#ifdef __OPTIMIZE__ + testA(); +#endif /* OPTIMIZE */ + + return 0; +} diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i16.c new file mode 100644 index 0000000000000000000000000000000000000000..7286de8dde21f6805bbee9c414f06556d4103416 --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i16 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-q.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i8.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i8.c new file mode 100644 index 0000000000000000000000000000000000000000..937e228ab56e68cfb67fc1cba7c6669011c92ada --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-i8.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i8 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-q.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-q-u16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-u16.c new file mode 100644 index 0000000000000000000000000000000000000000..c61d6a6a6ee742a70947b9e9ef13f38303bf0317 --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-q-u16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T u16 +#endif + +#ifndef W +#define W -5ul +#endif + +#include "ifelse-q.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-q.h b/gcc/testsuite/gcc.target/avr/torture/ifelse-q.h new file mode 100644 index 0000000000000000000000000000000000000000..6dbb05990e0664dd2b54aa0b1739fa9862a4bf07 --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-q.h @@ -0,0 +1,92 @@ +/* Testing pass avr-ifelse with REG-REG comparisons + where the 2nd insn is behind the true edge of insn1 (reverse_cond1). */ + +#define V ((T) (W)) + +#ifdef __OPTIMIZE__ + +typedef __UINT32_TYPE__ u32; +typedef __uint24 u24; +typedef __UINT16_TYPE__ u16; +typedef __UINT8_TYPE__ u8; + +typedef __INT32_TYPE__ i32; +typedef __int24 i24; +typedef __INT16_TYPE__ i16; +typedef __INT8_TYPE__ i8; + +char volatile cc; + +#define NI __attribute__((noipa)) +#define AI static __inline__ __attribute__((always_inline)) + +NI +void f (char x) +{ + cc += x; +} + +#define MK_FUN(id, cmp1, cmp2) \ +NI void fun_##id (T x, T y) \ +{ \ + if (x cmp1 y) \ + f (1); \ + else if (x cmp2 y) \ + f (2); \ +} \ + \ +NI char val_##id (T x, T y) \ +{ \ + char c = 0; \ + T x2 = x; \ + __asm ("" : "+r" (x2)); \ + \ + if (x cmp1 y) \ + c += 1; \ + else if (x2 cmp2 y) \ + c += 2; \ + \ + return c; \ +} + +MK_FUN (01, >, <) +MK_FUN (02, <, >) + +MK_FUN (03, ==, <= ) +MK_FUN (04, ==, >= ) + +MK_FUN (05, ==, <) +MK_FUN (06, ==, >) + +MK_FUN (07, >, ==) +MK_FUN (08, <, ==) + +MK_FUN (09, >, !=) +MK_FUN (10, <, !=) + +void testA (void) +{ + for (T x = (T) (V - 2); x != (T) (V + 2); ++x) + { + cc = 0; fun_01 (x,V); if (cc != val_01 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_02 (x,V); if (cc != val_02 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_03 (x,V); if (cc != val_03 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_04 (x,V); if (cc != val_04 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_05 (x,V); if (cc != val_05 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_06 (x,V); if (cc != val_06 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_07 (x,V); if (cc != val_07 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_08 (x,V); if (cc != val_08 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_09 (x,V); if (cc != val_09 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_10 (x,V); if (cc != val_10 (x,V)) __builtin_exit (__LINE__); + } +} +#endif /* OPTIMIZE */ + +int main (void) +{ +#ifdef __OPTIMIZE__ + testA(); +#endif /* OPTIMIZE */ + + return 0; +} diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i16.c new file mode 100644 index 0000000000000000000000000000000000000000..1c0d4a94305ba7da182f5d52ec7896f2127f465f --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i16 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-r.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i8.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i8.c new file mode 100644 index 0000000000000000000000000000000000000000..3a5a902bb692e7cdf4f4da06f4821c7215f00edd --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-i8.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T i8 +#endif + +#ifndef W +#define W -5 +#endif + +#include "ifelse-r.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-r-u16.c b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-u16.c new file mode 100644 index 0000000000000000000000000000000000000000..2d08ebb1ff5bc75aa613229b7926bd557dd1a90f --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-r-u16.c @@ -0,0 +1,12 @@ +/* { dg-do run } */ +/* { dg-additional-options { "-fwrapv" "-std=c99" } } */ + +#ifndef T +#define T u16 +#endif + +#ifndef W +#define W -5ul +#endif + +#include "ifelse-r.h" diff --git a/gcc/testsuite/gcc.target/avr/torture/ifelse-r.h b/gcc/testsuite/gcc.target/avr/torture/ifelse-r.h new file mode 100644 index 0000000000000000000000000000000000000000..19cd1cc67c431f9c07aafbf0178ee3cfc2fb354f --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/torture/ifelse-r.h @@ -0,0 +1,100 @@ +/* Testing pass avr-ifelse with REG-REG comparisons. */ + +#define V ((T) (W)) + +#ifdef __OPTIMIZE__ + +typedef __UINT32_TYPE__ u32; +typedef __uint24 u24; +typedef __UINT16_TYPE__ u16; +typedef __UINT8_TYPE__ u8; + +typedef __INT32_TYPE__ i32; +typedef __int24 i24; +typedef __INT16_TYPE__ i16; +typedef __INT8_TYPE__ i8; + +char volatile cc; + +#define NI __attribute__((noipa)) +#define AI static __inline__ __attribute__((always_inline)) + +NI +void f (char x) +{ + cc += x; +} + +#define MK_FUN(id, cmp1, cmp2) \ +NI void fun_##id (T x, T y) \ +{ \ + if (x cmp1 y) \ + goto a; \ + if (x cmp2 y) \ + goto b; \ + f(1); \ + a: \ + f(2); \ + b: \ + f(4); \ +} \ + \ +NI char val_##id (T x, T y) \ +{ \ + char c = 0; \ + if (x cmp1 y) \ + goto a; \ + __asm ("" : "+r" (x)); \ + __asm ("" : "+r" (y)); \ + if (x cmp2 y) \ + goto b; \ + c += 1; \ + a: \ + c += 2; \ + b: \ + c += 4; \ + \ + return c; \ +} + +MK_FUN (01, >, <) +MK_FUN (02, <, >) + +MK_FUN (03, ==, <= ) +MK_FUN (04, ==, >= ) + +MK_FUN (05, ==, <) +MK_FUN (06, ==, >) + +MK_FUN (07, >, ==) +MK_FUN (08, <, ==) + +MK_FUN (09, >, !=) +MK_FUN (10, <, !=) + +void testA (void) +{ + for (T x = (T) (V - 2); x != (T) (V + 2); ++x) + { + cc = 0; fun_01 (x,V); if (cc != val_01 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_02 (x,V); if (cc != val_02 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_03 (x,V); if (cc != val_03 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_04 (x,V); if (cc != val_04 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_05 (x,V); if (cc != val_05 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_06 (x,V); if (cc != val_06 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_07 (x,V); if (cc != val_07 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_08 (x,V); if (cc != val_08 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_09 (x,V); if (cc != val_09 (x,V)) __builtin_exit (__LINE__); + cc = 0; fun_10 (x,V); if (cc != val_10 (x,V)) __builtin_exit (__LINE__); + } +} +#endif /* OPTIMIZE */ + +int main (void) +{ +#ifdef __OPTIMIZE__ + testA(); +#endif /* OPTIMIZE */ + + return 0; +}