/* Support for avr-passes.cc for AVR 8-bit microcontrollers. 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 . */ /* FIXME: The documentation in hard-reg-set.h is wrong in that it states that HARD_REG_SET is a scalar iff HARD_REG_SET is a macro. This is not the case: HARD_REG_SET is a typedef no matter what. So in order to get the lower 32 bits (and maybe more) as a scalar we have to invoke type traits as we can't #ifdef HARD_REG_SET */ template::value> struct elt0_getter; // All hard regs fit in one HARD_REG_ELT_TYPE: T === ELT. template struct elt0_getter { static inline const ELT &get (const T &t) { return t; } }; // HARD_REG_SET is not a scalar but a composite with HARD_REG_ELT_TYPE elts[]. template struct elt0_getter { static inline const ELT &get (const T &t) { return t.elts[0]; } }; // To track known values held in General Purpose Registers R2 ... R31. struct memento_t { // One bit for each GPR. gprmask_t known = 0; std::array values; static gprmask_t fixed_regs_mask; void apply (const ply_t &); void apply_insn (rtx_insn *insn, bool unused) { apply_insn1 (insn, unused); known &= ~memento_t::fixed_regs_mask; } private: void apply_insn1 (rtx_insn *, bool); public: bool knows (int rno, int n = 1) const { gcc_checking_assert (gpr_regno_p (rno, n)); const gprmask_t mask = regmask (rno, n); return (known & mask) == mask; } uint8_t operator[] (int rno) const { gcc_checking_assert (gpr_regno_p (rno)); return values[rno]; } // Set the 8-bit register number DEST as known to hold value VAL. void set_value (int dest, int val) { gcc_checking_assert (gpr_regno_p (dest, 1)); values[dest] = (uint8_t) val; set_known (dest); } void copy_value (int dest, int src) { gcc_checking_assert (gpr_regno_p (dest, 1)); gcc_checking_assert (gpr_regno_p (src, 1)); values[dest] = values[src]; set_known (dest, knows (src)); } void copy_values (int dest, int src, int n_bytes) { gcc_checking_assert (gpr_regno_p (dest, n_bytes)); gcc_checking_assert (gpr_regno_p (src, n_bytes)); if (dest < src) for (int n = 0; n < n_bytes; ++n) copy_value (n + dest, n + src); else if (dest > src) for (int n = n_bytes - 1; n >= 0; --n) copy_value (n + dest, n + src); } // Get the value as a CONST_INT or NULL_RTX when any byte is unknown. rtx get_value_as_const_int (int regno, int n_bytes) const { gcc_checking_assert (gpr_regno_p (regno, n_bytes)); if (! knows (regno, n_bytes)) return NULL_RTX; const machine_mode mode = size_to_mode (n_bytes); uint64_t val = 0; for (int i = n_bytes - 1; i >= 0; --i) val = 256 * val + values[regno + i]; return gen_int_mode (val, mode); } // Copy the known state and the value (provided it is known) from // register SRC to register DEST. void copy_values (rtx dest, rtx src) { if (REG_P (dest) && REG_P (src) && GET_MODE_SIZE (GET_MODE (src)) <= GET_MODE_SIZE (GET_MODE (dest))) { int n_bytes = std::min (GET_MODE_SIZE (GET_MODE (src)), GET_MODE_SIZE (GET_MODE (dest))); copy_values (REGNO (dest), REGNO (src), n_bytes); } } void set_values (rtx dest, rtx src) { gcc_assert (REG_P (dest) && CONST_INT_P (src)); int regno = REGNO (dest); for (int i = 0; i < GET_MODE_SIZE (GET_MODE (dest)); ++i) set_value (regno + i, avr_uint8 (src, i)); } // Value >= 0 of the i-th reg or -1 if unknown. int value (int i) const { gcc_checking_assert (gpr_regno_p (i)); return knows (i) ? (int) values[i] : -1; } // Value >= 0 of the rno-th reg[n] or -1 if unknown. int64_t value (int rno, int n, bool strict = true) const { gcc_assert (n <= 4); gcc_checking_assert (gpr_regno_p (rno, n)); if (! knows (rno, n)) { if (! strict) return -1; gcc_unreachable (); } uint64_t val = 0; for (int r = rno + n - 1; r >= rno; --r) val = 256 * val + values[r]; return val; } void set_known (int r, bool kno = true) { gcc_checking_assert (gpr_regno_p (r)); known = kno ? known | (1u << r) : known & ~(1u << r); } void set_unknown (int r) { gcc_checking_assert (gpr_regno_p (r)); set_known (r, false); } int n_known () const { return popcount_hwi (known); } // Hamming byte distance of R[n] to VAL. int hamming (int r, int n, uint64_t val) const { gcc_assert (n <= 8); gcc_checking_assert (gpr_regno_p (r, n)); int ham = 0; for (int i = 0; i < n; ++i) ham += value (r + i) != (uint8_t) (val >> (8 * i)); return ham; } // Calculate the Hamming byte distance, ignoring regs in IGNORES. int distance_to (const memento_t &that, gprmask_t ignores = 0) const { int d = 0; for (int r = FIRST_GPR; r < REG_32; ++r) if (! (ignores & (1u << r))) d += value (r) != that.value (r); return d; } // Return true when *this and THAT are the same, with the only allowed // exceptions as of mask IGNORES. bool equals (const memento_t &that, gprmask_t ignores) const { if ((known & ~ignores) != (that.known & ~ignores)) return false; for (int r = FIRST_GPR; r < REG_32; ++r) if (! (ignores & (1u << r))) if (value (r) != that.value (r)) return false; return true; } // Return TRUE iff the N_BYTES registers starting at REGNO are known // to contain VAL. bool have_value (int rno, int n_bytes, int val) const { gcc_assert (n_bytes <= 4); for (int i = rno; i < rno + n_bytes; ++i) if (value (i) != (uint8_t) val) return false; else val >>= 8; return true; } // The regno of a d-reg that has a known value, or 0 if none found. int known_dregno (void) const { const gprmask_t dregs = known & 0xffff0000 & ~memento_t::fixed_regs_mask; return dregs ? clz_hwi (1) - clz_hwi (dregs) : 0; } // Return a regno for a register that contains value VAL8 and that does // not overlap with the registers mentioned in EXCLUDES. Else return 0. int regno_with_value (uint8_t val8, gprmask_t excludes) const { for (int r = REG_31; r >= FIRST_GPR; --r) if (value (r) == val8 && ! (regmask (r, 1) & excludes)) return r; return 0; } // Return a regno for a 16-bit reg that contains value HI8:LO8 and that does // not overlap with the registers mentioned in EXCLUDES. Else return 0. int reg16_with_value (uint8_t lo8, uint8_t hi8, gprmask_t excludes) const { for (int r = REG_30; r >= FIRST_GPR; r -= 2) if (! (regmask (r, 2) & excludes) && value (r) == lo8 && value (r + 1) == hi8) return r; return 0; } void operator&= (const HARD_REG_SET &hrs) { known &= elt0_getter::get (hrs); } // Coalesce register knowledge about *this and THAT. void coalesce (const memento_t &that) { known &= that.known; for (int i = FIRST_GPR; i < REG_32; ++i) if (values[i] != that.values[i]) set_unknown (i); } void dump (const char *msg = nullptr, FILE *f = dump_file) const { if (f) { msg = msg && msg[0] ? msg : "%s\n"; const char *const xs = strstr (msg, "%s"); gcc_assert (xs); fprintf (f, "%.*s", (int) (xs - msg), msg); fprintf (f, " (%d known): ", n_known ()); for (int i = FIRST_GPR; i < REG_32; ++i) if (knows (i)) fprintf (f, " r%d=%02x", i, values[i]); fprintf (f, "%s", xs + strlen ("%s")); } } }; // memento_t // In avr-fuse-move, a possible step towards an optimal code sequence // to load a compile-time constant. A ply_t represents one or two // instructions. There are cases where there is no 1-to-1 correspondence // between a ply_t and an insn; but a sequence of ply_ts can be mapped to // a sequence of insns; though there are cases where 2 or more ply_ts map // to a single insn and vice versa. struct ply_t { // The destination register with .size in { 1, 2 }. int regno; int size; // The performed operation where .arg represents an optional source operand. // .code may be one of: SET (ldi, clr, ldi+mov), REG (mov, movw), NEG (neg), // NOT (com), PRE_INC (inc), PRE_DEC (dec), ROTATE (swap), ASHIFT (lsl), // LSHIFTRT (lsr), ASHIFTRT (asr), PLUS (add), MINUS (sub), AND (and), // IOR (or), XOR (eor), SS_PLUS (adiw, sbiw), MOD (set+bld, clt+bld, bld). rtx_code code; int arg; // Code size in terms of words / instructions. Extra costs for, say // a CLT prior to a sequence of BLDs, are added to the 1st element. int cost; // We only consider ply_ts that reduce the Hamming distance by 0, 1 or 2. // There are exotic cases where the Hamming distance temporarily increases, // but we don't consider them. (They may fall out of the algorithm anyways, // for example when a "set_some" insn is used that restores its scratch. int dhamming = 1; // Whether this is a SET that's intended for insn "set_some"'s payload. bool in_set_some = false; // 0 or an upper scratch register. One needed for SETs of a lower reg. // SETs in a set_some don't need a scratch. int scratch = 0; // Statistics. static int n_ply_ts; static int max_n_ply_ts; gprmask_t mask_dest () const { return regmask (regno, size); } gprmask_t mask_src () const { if (code == SET) return 0; else if (code == REG) return regmask (arg, size); else if (code == PLUS || code == MINUS || code == AND || code == IOR || code == XOR) return regmask (arg, size) | mask_dest (); else return mask_dest (); } bool is_movw () const { return size == 2 && code == REG; } bool is_adiw () const { return size == 2 && code == SS_PLUS; } bool is_bld () const { return code == MOD; } // A BLD setting one bit. bool is_setbld () const { return is_bld () && popcount_hwi (arg) == 1; } // A BLD clearing one bit. bool is_cltbld () const { return is_bld () && popcount_hwi (arg) == 7; } rtx_code bld_rtx_code () const { return select() : is_setbld () ? IOR : is_cltbld () ? AND : UNKNOWN; } // Is *P a BLD of the same kind? bool is_same_bld (const ply_t *p) const { gcc_assert (is_bld ()); return p && bld_rtx_code () == p->bld_rtx_code (); } int bld_bitno () const { gcc_assert (is_bld ()); int bit = exact_log2 (popcount_hwi (arg) == 1 ? arg : 0xff ^ arg); gcc_assert (IN_RANGE (bit, 0, 7)); return bit; } bool needs_scratch () const { return code == SET && AVRasm::ldi_needs_scratch (regno, arg); } // Return true when *this modifies (changes *AND* uses) the result // generated by *P. bool changes_result_of (const ply_t *p) const { return code != REG && code != SET && (mask_dest() & p->mask_dest()); } bool overrides (const ply_t *p) const { return code == REG || code == SET ? mask_dest () & p->mask_dest () : false; } bool commutes_with (const ply_t *p, int scratch = 0) const { if (code == SET || p->code == SET) { // SETs will be emit as a group where they commute. if (code == SET && p->code == SET) return true; // Grant more flexibility to move around expensive SETs. if (! scratch && (needs_scratch () || p->needs_scratch ())) return false; } if (is_bld () || p->is_bld ()) { // BLD requires a previous SET or CLT which means that like // BLDs should occur as a contiguous sequence. This limits // re-ordering for the purpose of canonicalization of instruction // ordering. return ((is_cltbld () && p->is_cltbld ()) || (is_setbld () && p->is_setbld ())); } gprmask_t msrc = 1u << scratch; gprmask_t m1 = mask_dest() | mask_src(); gprmask_t m2 = p->mask_dest() | p->mask_src(); return (m1 & m2) == 0 && ((m1 | m2) & msrc) == 0; } // Expected insn name; used in dumps. const char *insn_name () const { if (code == SET) return select() : in_set_some ? "set_some" : scratch && needs_scratch () ? "*reload_inqi" : "movqi_insn"; return "???"; } void dump (int level = 0, FILE *f = dump_file) const { if (f) { if (level) avr_fdump (f, ";; .%d ply_t R%d[%d] = %C", level, regno, size, code); else avr_fdump (f, ";; ply_t R%d[%d] = %C", regno, size, code); if (code == REG || is_adiw ()) fprintf (f, " %d", arg); else if (code == PLUS || code == MINUS || code == AND || code == IOR || code == XOR) fprintf (f, " R%d", arg); else if (is_setbld ()) fprintf (f, " BLD |= 0x%02x", arg); else if (is_cltbld ()) fprintf (f, " BLD &= 0x%02x", arg); else fprintf (f, " 0x%x = %d", arg, arg); const char *const name = insn_name (); fprintf (f, ", cost=%d, dhamm=%d", cost, dhamming); if (name && name[0] != '?') fprintf (f, ", \"%s\"", name); fprintf (f, "\n"); } } // Helper for dump_plys: Value of the destination. int dest_value (const memento_t &memo) const { return memo.value (regno, size); } // Helper for dump_plys: Value of 1st source arg provided it is a register. int src1_value (const memento_t &memo) const { int rsrc = regno; switch (code) { default: return -1; case REG: gcc_assert (size == 1 || size == 2); rsrc = arg; break; case SS_PLUS: gcc_assert (size == 2); break; case NEG: case NOT: case PRE_DEC: case PRE_INC: case ASHIFT: case LSHIFTRT: case ASHIFTRT: case ROTATE: case AND: case IOR: case XOR: case MOD: case PLUS: case MINUS: gcc_assert (size == 1); break; } return memo.value (rsrc, size); } // Helper for dump_plys: Value of 2nd source argument. int src2_value (const memento_t &memo) const { switch (code) { default: break; case AND: case IOR: case XOR: case PLUS: case MINUS: gcc_assert (size == 1); return memo.value (arg, 1); } return -1; } // Dumping a solution (or parts of it) is tedious because when // their specific action should be displayed. static void dump_plys (FILE *f, int level, int len, const ply_t *const ps[], const memento_t &m0) { if (f) { memento_t memo = m0; for (int i = 0; i < len; ++i) ps[i]->dump (level, memo, f); } } void dump (int level, memento_t &memo, FILE *f = dump_file) const { if (! f) return; const ply_t &p = *this; // Keep track of chars in the current line for neat alignment. int cs = level > 0 ? fprintf (f, ";; .%d ", level) : fprintf (f, ";; "); cs += fprintf (f, "ply_t %-4s R%d[%d] = ", p.mnemonic (), p.regno, p.size); const int x = p.src1_value (memo); const int y = p.src2_value (memo); memo.apply (p); const int z = p.dest_value (memo); switch (p.code) { default: fprintf (f, "%s ???", rtx_name[p.code]); gcc_unreachable (); break; case REG: cs += fprintf (f, "R%d = 0x%0*x", p.arg, 2 * p.size, x); break; case SET: cs += fprintf (f, "0x%02x = %d, \"%s\"", p.arg, p.arg, insn_name ()); break; case PRE_DEC: case PRE_INC: case ASHIFT: case LSHIFTRT: case ASHIFTRT: case ROTATE: cs += fprintf (f, "R%d %s = 0x%02x = 0x%02x %s", p.regno, p.op_str (), z, x, p.op_str ()); break; case NEG: case NOT: cs += fprintf (f, "%sR%d = 0x%02x = %s0x%02x", p.op_str (), p.regno, z, p.op_str (), x); break; case PLUS: case MINUS: case AND: case IOR: case XOR: cs += fprintf (f, "R%d %s R%d = 0x%02x = 0x%02x %s 0x%02x", p.regno, p.op_str (), p.arg, z, x, p.op_str (), y); break; case SS_PLUS: // ADIW / SBIW { int arg = (int16_t) p.arg; char op = arg < 0 ? '-' : '+'; cs += fprintf (f, "R%d %c %d = 0x%04x = 0x%04x %c %d", p.regno, op, std::abs (arg), z, x, op, std::abs (arg)); } break; case MOD: // BLD { const char opc = "&|" [p.is_setbld ()]; cs += fprintf (f, "R%d %c 0x%02x = 0x%02x = 0x%02x %c bit%d", p.regno, opc, p.arg, z, x, opc, p.bld_bitno ()); } break; } cs += fprintf (f, ", "); while (cs++ < 56) fputc (' ', f); fprintf (f, "cost=%d, dhamm=%d\n", p.cost, p.dhamming); } // AVR mnemnic; used in dumps. const char *mnemonic () const { if (is_bld ()) { static char s_bld[] = "BLD*"; s_bld[3] = '0' + bld_bitno (); return s_bld; } return select() : code == LSHIFTRT ? "LSR" : code == ASHIFTRT ? "ASR" : code == ASHIFT ? "LSL" : code == ROTATE ? "SWAP" : code == PRE_DEC ? "DEC" : code == PRE_INC ? "INC" : code == MINUS ? "SUB" : code == PLUS ? "ADD" : code == NEG ? "NEG" : code == NOT ? "COM" : code == AND ? "AND" : code == IOR ? "OR" : code == XOR ? "EOR" : code == REG ? size == 1 ? "MOV" : "MOVW" : code == SET ? arg == 0 ? "CLR" : "LDI" : code == SS_PLUS ? arg < 0 ? "SBIW" : "ADIW" : rtx_name[code]; } // Return a string of length 1 for CODE, or "?". static const char *code_name_str1 (rtx_code code) { return select() : code == NEG ? "-" : code == NOT ? "~" : code == AND ? "&" : code == IOR ? "|" : code == XOR ? "^" : code == PLUS ? "+" : code == MINUS ? "-" : "?"; } // Short semantics representation used in dumps. const char *op_str () const { return select() : code == LSHIFTRT ? ">> 1" : code == ASHIFTRT ? ">> 1" : code == ASHIFT ? "<< 1" : code == ROTATE ? ">>> 4" : code == PRE_DEC ? "- 1" : code == PRE_INC ? "+ 1" : code == SS_PLUS ? "+" : *(ply_t::code_name_str1 (code)) != '?' ? ply_t::code_name_str1 (code) : rtx_name[code]; } }; // ply_t // A set of ply_t's. We prefer std:array (with some expected upper // bound for the number of ply_t's as generated by bbinfo_t::get_plies()) // over std::vector. That way, all plies_t are only allocated once as // elements of avr_pass_fuse_move::BInfo. struct plies_t { int n_plies; std::array plies; int emit_insns (const insninfo_t &, const memento_t &) const; int emit_sets (const insninfo_t&, int &n_insns, const memento_t&, int) const; int emit_blds (const insninfo_t &, int &n_insns, int i0) const; void add_plies_movw (int regno, int size, uint64_t, int, const memento_t &); void reset () { n_plies = 0; } void add (const ply_t &ply) { if (n_plies < (int) plies.size ()) { plies[n_plies++] = ply; ply_t::n_ply_ts += 1; } else avr_dump (";; WARNING: plies_t is full\n"); } void add (ply_t, const ply_t *prev, const memento_t &, bool maybe_set_some); plies_t () {} plies_t (int n, const ply_t *const ps[]) { gcc_assert (n <= (int) plies.size ()); for (int i = 0; i < n; ++i) plies[i] = *ps[i]; n_plies = n; } static int max_n_plies; }; // plies_t // An 8-bit value leaf of absint_byte_t. // May be known to equal an 8-bit value. // May be known to equal the content of an 8-bit GPR. struct absint_val_t { int16_t val8 = -1; int8_t regno = 0; absint_val_t () {} bool knows_val8 () const { gcc_assert (IN_RANGE (val8, -1, 0xff)); return val8 >= 0; } bool knows_regno () const { gcc_assert (IN_RANGE (regno, 0, REG_31)); return regno; } bool clueless () const { return ! knows_val8 () && ! knows_regno (); } gprmask_t reg_mask () const { return regno ? regmask (regno, 1) : 0; } void dump (FILE *f = dump_file) const { if (f) { if (knows_regno ()) fprintf (f, "r%d%s", regno, knows_val8 () ? "=" : ""); if (knows_val8 ()) fprintf (f, "%02x", val8); else if (! knows_regno ()) fprintf (f, "--"); } } }; // absint_val_t // One byte in AbsInt. class absint_byte_t { // "SET": the value is .x0. rtx_code code = UNKNOWN; absint_val_t x0; absint_val_t x1; public: const absint_val_t &arg (int i) const { gcc_assert (IN_RANGE (i, 0, arity () - 1)); return i == 1 ? x1 : x0; } rtx_code get_code () const { return code; } absint_byte_t () {} absint_byte_t (absint_val_t x) : code(x.clueless () ? UNKNOWN : SET), x0(x) {} // new = A0 where CODE is a unary operation. absint_byte_t (rtx_code c, const absint_byte_t &a0) : code(c) { switch (code) { default: gcc_unreachable (); case NOT: if (a0.can (CONST_INT)) init_val8 (absint_byte_t::eval (code, a0.val8 ())); else if (a0.can (REG)) x0 = a0.x0; else if (a0.can (NOT)) init_regno (a0.regno ()); else code = UNKNOWN; break; case SIGN_EXTEND: if (a0.can (CONST_INT)) init_val8 (absint_byte_t::eval (code, a0.val8 ())); else if (a0.can (REG)) x0 = a0.x0; else code = UNKNOWN; break; } } // new = A0 A1 where CODE is a binary operation. absint_byte_t (rtx_code c, const absint_byte_t &a0, const absint_byte_t &a1) : code(c) { gcc_assert (c == AND || c == IOR || c == XOR || code == PLUS); if (a1.is_image1 (c)) *this = a1; else if (a0.is_image1 (c)) *this = a0; else if (a1.is_neutral (c)) *this = a0; else if (a0.is_neutral (c)) *this = a1; else if (a0.can (CONST_INT) && a1.can (CONST_INT)) init_val8 (absint_byte_t::eval (code, a0.val8 (), a1.val8 ())); else if (a0.can (REG) && a1.can (CONST_INT)) { x0 = a0.x0; x1 = a1.x0; if (code == XOR && a1.val8 () == 0xff) code = NOT; } else if (a0.can (CONST_INT) && a1.can (REG)) { x0 = a1.x0; x1 = a0.x0; if (code == XOR && a0.val8 () == 0xff) code = NOT; } else if (a0.can (REG) && a1.can (REG)) { x0.regno = std::min (a0.regno (), a1.regno ()); x1.regno = std::max (a0.regno (), a1.regno ()); } else code = UNKNOWN; } int arity () const { return select() : code == UNKNOWN ? 0 : code == SET || code == NOT || code == SIGN_EXTEND ? 1 : code == AND || code == IOR || code == XOR || code == PLUS ? 2 : bad_case (); } // Return a byte with 8 signs according to code CODE. absint_byte_t get_signs (rtx_code ext) const { return select() : ext == ZERO_EXTEND ? absint_byte_t::from_val8 (0) : ext == SIGN_EXTEND ? absint_byte_t (SIGN_EXTEND, *this) : ext == LSHIFTRT ? absint_byte_t::from_val8 (0) : ext == ASHIFTRT ? absint_byte_t (SIGN_EXTEND, *this) : bad_case (); } gprmask_t reg_mask () const { return select() : code == SET ? x0.reg_mask () : arity () == 1 ? x0.reg_mask () : arity () == 2 ? x0.reg_mask () | x1.reg_mask () : bad_case (); } bool check () const { return select() : arity () >= 1 && x0.clueless () ? false : arity () == 2 && x1.clueless () ? false : true; } static inline uint8_t eval (rtx_code code, uint8_t x) { return select() : code == NOT ? ~x : code == SIGN_EXTEND ? (x >= 0x80 ? 0xff : 0x00) : bad_case (); } static inline uint8_t eval (rtx_code code, uint8_t x, uint8_t y) { return select() : code == AND ? x & y : code == IOR ? x | y : code == XOR ? x ^ y : code == PLUS ? x + y : bad_case (); } bool is_neutral (rtx_code c) const { return can (CONST_INT) && val8 () == AVRasm::neutral_val (c); } bool is_image1 (rtx_code c) const { return can (CONST_INT) && val8 () == AVRasm::image1_val (c); } bool can (rtx_code c) const { if (code == SET) gcc_assert (IN_RANGE (x0.val8, 0, 0xff) || gpr_regno_p (x0.regno)); if (c == CONST_INT) return code == SET && x0.knows_val8 (); else if (c == REG) return code == SET && x0.knows_regno (); else if (c == VALUE) return code != UNKNOWN; else if (c == UNKNOWN || c == SET || c == NOT || c == SIGN_EXTEND || c == AND || c == IOR || c == XOR || c == PLUS) return code == c; gcc_unreachable (); } // Return the known byte value in 0...0xff, or -1 if unknown and ! STRICT. int val8 (bool strict = true) const { gcc_assert (! strict || code == SET); gcc_assert (! strict || can (CONST_INT)); return can (CONST_INT) ? x0.val8 : -1; } int regno (bool strict = true) const { gcc_assert (! strict || code == SET); gcc_assert (! strict || can (REG)); return can (REG) ? x0.regno : 0; } void init_val8 (int v) { gcc_assert (IN_RANGE (v, 0, 0xff)); x0.val8 = v; x0.regno = 0; code = SET; } void init_regno (int r) { gcc_assert (gpr_regno_p (r)); x0.val8 = -1; x0.regno = r; code = SET; } void learn_val8 (int v) { gcc_assert (IN_RANGE (v, 0, 0xff)); gcc_assert (code == SET || code == UNKNOWN); x0.val8 = v; code = SET; } void learn_regno (int r) { gcc_assert (gpr_regno_p (r)); gcc_assert (code == SET || code == UNKNOWN); x0.regno = r; code = SET; } static inline absint_byte_t from_val8 (int val, bool strict = true) { gcc_assert (IN_RANGE (val, -1, 0xff)); gcc_assert (! strict || val >= 0); absint_byte_t b; if (val >= 0) b.init_val8 (val); return b; } // Return a SET rtx that can replace the set_src of INSN. // Returns BINARY_P or NULL_RTX. absint_byte_t find_alternative_binary (const memento_t &memo) const { gprmask_t excludes = x1.knows_regno () ? regmask (x1.regno, 1) : 0; absint_byte_t alt = *this; if (arity () == 2 && x0.knows_regno () && x1.knows_val8 () && (! x1.knows_regno () || x0.regno != x1.regno) && (alt.x1.regno = memo.regno_with_value (x1.val8, excludes))) { if (dump_flags & TDF_FOLDING) { alt.dump (";; AI.alternative AI=[%s]"); dump (" can replace AI=[%s]\n"); } return alt; } return absint_byte_t {}; } rtx to_rtx () const { if (arity () == 2) { gcc_assert (x0.knows_regno ()); gcc_assert (x1.knows_regno ()); rtx op0 = gen_rtx_REG (QImode, x0.regno); rtx op1 = gen_rtx_REG (QImode, x1.regno); return gen_rtx_fmt_ee (code, QImode, op0, op1); } gcc_unreachable (); } void dump (const char *msg = nullptr, FILE *f = dump_file) const { if (f) { msg = msg && msg[0] ? msg : "%s"; const char *const xs = strstr (msg, "%s"); gcc_assert (xs); fprintf (f, "%.*s", (int) (xs - msg), msg); if (code == UNKNOWN) fprintf (f, "--"); else if (code == SET) x0.dump (f); else if (code == NOT) { fprintf (f, "~"); x0.dump (f); } else if (code == SIGN_EXTEND) { fprintf (f, "signs("); x0.dump (f); fprintf (f, ")"); } else if (arity () == 2) { x0.dump (f); fprintf (f, "%s", ply_t::code_name_str1 (code)); x1.dump (f); } else gcc_unreachable (); fprintf (f, "%s", xs + strlen ("%s")); } } }; // absint_byte_t struct bbinfo_t { // All BBs of the current function. static bbinfo_t *bb_info; // bbinfo_t holds additional information for this basic block. basic_block bb; // Known values held in GPRs. memento_t regs; // Represents the "time" when the value was set. When we have the choice // between several registers to copy from, we use the first (oldest) set. // This can avoid copy-chains. std::array ticks; static int tick; // Whether according BB is done and optimized. bool done; static void optimize_one_function (function *func); void optimize_one_block (bool &changed); void enter (); void leave (); // Used when finding a best plies_t. This object is only needed // once and can be shared between all basic blocks. struct find_plies_data_t { // These are used by [run_]find_plies() const ply_t *ply_stack[N_BEST_PLYS]; plies_t plies[N_BEST_PLYS]; plies_t solution; // Register knowledge at start of recursive algo. memento_t regs0; int max_ply_cost; int movmode_cost; int n_best_plys; int n_get_plies; // Only for bookkeeping / statistics. }; // find_plies_data_t static find_plies_data_t *fpd; static bool try_fuse_p; static bool try_mem0_p; static bool try_bin_arg1_p; static bool try_simplify_p; static bool try_split_ldi_p; static bool try_split_any_p; static bool use_arith_p; static bool use_set_some_p; static void get_plies (plies_t &, const insninfo_t &, const memento_t &, const ply_t *); static void find_plies (int depth, const insninfo_t &, const memento_t &); bool run_find_plies (const insninfo_t &, const memento_t &) const; }; // bbinfo_t