aboutsummaryrefslogtreecommitdiff
path: root/gcc/config/avr/avr.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/config/avr/avr.c')
-rw-r--r--gcc/config/avr/avr.c165
1 files changed, 165 insertions, 0 deletions
diff --git a/gcc/config/avr/avr.c b/gcc/config/avr/avr.c
index 9c8b43d..1682aa0 100644
--- a/gcc/config/avr/avr.c
+++ b/gcc/config/avr/avr.c
@@ -4503,6 +4503,167 @@ lshrsi3_out (rtx insn, rtx operands[], int *len)
}
+/* Output addition of register XOP[0] and compile time constant XOP[2]:
+
+ XOP[0] = XOP[0] + XOP[2]
+
+ and return "". If PLEN == NULL, print assembler instructions to perform the
+ addition; otherwise, set *PLEN to the length of the instruction sequence (in
+ words) printed with PLEN == NULL. XOP[3] is an 8-bit scratch register.
+ CODE == PLUS: perform addition by using ADD instructions.
+ CODE == MINUS: perform addition by using SUB instructions. */
+
+static void
+avr_out_plus_1 (rtx *xop, int *plen, enum rtx_code code)
+{
+ /* MODE of the operation. */
+ enum machine_mode mode = GET_MODE (xop[0]);
+
+ /* Number of bytes to operate on. */
+ int i, n_bytes = GET_MODE_SIZE (mode);
+
+ /* Value (0..0xff) held in clobber register op[3] or -1 if unknown. */
+ int clobber_val = -1;
+
+ /* op[0]: 8-bit destination register
+ op[1]: 8-bit const int
+ op[2]: 8-bit scratch register */
+ rtx op[3];
+
+ /* Started the operation? Before starting the operation we may skip
+ adding 0. This is no more true after the operation started because
+ carry must be taken into account. */
+ bool started = false;
+
+ /* Value to add. There are two ways to add VAL: R += VAL and R -= -VAL. */
+ rtx xval = xop[2];
+
+ if (MINUS == code)
+ xval = gen_int_mode (-UINTVAL (xval), mode);
+
+ op[2] = xop[3];
+
+ if (plen)
+ *plen = 0;
+
+ for (i = 0; i < n_bytes; i++)
+ {
+ /* We operate byte-wise on the destination. */
+ rtx reg8 = simplify_gen_subreg (QImode, xop[0], mode, i);
+ rtx xval8 = simplify_gen_subreg (QImode, xval, mode, i);
+
+ /* 8-bit value to operate with this byte. */
+ unsigned int val8 = UINTVAL (xval8) & GET_MODE_MASK (QImode);
+
+ /* Registers R16..R31 can operate with immediate. */
+ bool ld_reg_p = test_hard_reg_class (LD_REGS, reg8);
+
+ op[0] = reg8;
+ op[1] = GEN_INT (val8);
+
+ if (!started && i % 2 == 0
+ && test_hard_reg_class (ADDW_REGS, reg8))
+ {
+ rtx xval16 = simplify_gen_subreg (HImode, xval, mode, i);
+ unsigned int val16 = UINTVAL (xval16) & GET_MODE_MASK (HImode);
+
+ /* Registers R24, X, Y, Z can use ADIW/SBIW with constants < 64
+ i.e. operate word-wise. */
+
+ if (val16 < 64)
+ {
+ if (val16 != 0)
+ {
+ started = true;
+ avr_asm_len (code == PLUS ? "adiw %0,%1" : "sbiw %0,%1",
+ op, plen, 1);
+ }
+
+ i++;
+ continue;
+ }
+ }
+
+ if (val8 == 0)
+ {
+ if (started)
+ avr_asm_len (code == PLUS
+ ? "adc %0,__zero_reg__" : "sbc %0,__zero_reg__",
+ op, plen, 1);
+ continue;
+ }
+
+ switch (code)
+ {
+ case PLUS:
+
+ gcc_assert (plen != NULL || REG_P (op[2]));
+
+ if (clobber_val != (int) val8)
+ avr_asm_len ("ldi %2,%1", op, plen, 1);
+ clobber_val = (int) val8;
+
+ avr_asm_len (started ? "adc %0,%2" : "add %0,%2", op, plen, 1);
+
+ break; /* PLUS */
+
+ case MINUS:
+
+ if (ld_reg_p)
+ avr_asm_len (started ? "sbci %0,%1" : "subi %0,%1", op, plen, 1);
+ else
+ {
+ gcc_assert (plen != NULL || REG_P (op[2]));
+
+ if (clobber_val != (int) val8)
+ avr_asm_len ("ldi %2,%1", op, plen, 1);
+ clobber_val = (int) val8;
+
+ avr_asm_len (started ? "sbc %0,%2" : "sub %0,%2", op, plen, 1);
+ }
+
+ break; /* MINUS */
+
+ default:
+ /* Unknown code */
+ gcc_unreachable();
+ }
+
+ started = true;
+
+ } /* for all sub-bytes */
+}
+
+
+/* Output addition of register XOP[0] and compile time constant XOP[2]:
+
+ XOP[0] = XOP[0] + XOP[2]
+
+ and return "". If PLEN == NULL, print assembler instructions to perform the
+ addition; otherwise, set *PLEN to the length of the instruction sequence (in
+ words) printed with PLEN == NULL. */
+
+const char*
+avr_out_plus (rtx *xop, int *plen)
+{
+ int len_plus, len_minus;
+
+ /* Work out if XOP[0] += XOP[2] is better or XOP[0] -= -XOP[2]. */
+
+ avr_out_plus_1 (xop, &len_plus, PLUS);
+ avr_out_plus_1 (xop, &len_minus, MINUS);
+
+ if (plen)
+ *plen = (len_minus <= len_plus) ? len_minus : len_plus;
+ else if (len_minus <= len_plus)
+ avr_out_plus_1 (xop, NULL, MINUS);
+ else
+ avr_out_plus_1 (xop, NULL, PLUS);
+
+ return "";
+}
+
+
/* Output bit operation (IOR, AND, XOR) with register XOP[0] and compile
time constant XOP[2]:
@@ -4851,6 +5012,10 @@ adjust_insn_length (rtx insn, int len)
avr_out_bitop (insn, op, &len);
break;
+ case ADJUST_LEN_OUT_PLUS:
+ avr_out_plus (op, &len);
+ break;
+
default:
gcc_unreachable();
}