aboutsummaryrefslogtreecommitdiffstats
path: root/gcc-4.9/gcc/config/rl78/rl78.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc-4.9/gcc/config/rl78/rl78.c')
-rw-r--r--gcc-4.9/gcc/config/rl78/rl78.c3748
1 files changed, 3748 insertions, 0 deletions
diff --git a/gcc-4.9/gcc/config/rl78/rl78.c b/gcc-4.9/gcc/config/rl78/rl78.c
new file mode 100644
index 000000000..b5cd2ad75
--- /dev/null
+++ b/gcc-4.9/gcc/config/rl78/rl78.c
@@ -0,0 +1,3748 @@
+/* Subroutines used for code generation on Renesas RL78 processors.
+ Copyright (C) 2011-2014 Free Software Foundation, Inc.
+ Contributed by Red Hat.
+
+ 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/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "tree.h"
+#include "varasm.h"
+#include "stor-layout.h"
+#include "calls.h"
+#include "rtl.h"
+#include "regs.h"
+#include "hard-reg-set.h"
+#include "insn-config.h"
+#include "conditions.h"
+#include "output.h"
+#include "insn-attr.h"
+#include "flags.h"
+#include "function.h"
+#include "expr.h"
+#include "optabs.h"
+#include "libfuncs.h"
+#include "recog.h"
+#include "diagnostic-core.h"
+#include "toplev.h"
+#include "reload.h"
+#include "df.h"
+#include "ggc.h"
+#include "tm_p.h"
+#include "debug.h"
+#include "target.h"
+#include "target-def.h"
+#include "langhooks.h"
+#include "rl78-protos.h"
+#include "dumpfile.h"
+#include "tree-pass.h"
+#include "context.h"
+#include "tm-constrs.h" /* for satisfies_constraint_*(). */
+#include "insn-flags.h" /* for gen_*(). */
+
+static inline bool is_interrupt_func (const_tree decl);
+static inline bool is_brk_interrupt_func (const_tree decl);
+static void rl78_reorg (void);
+
+
+/* Debugging statements are tagged with DEBUG0 only so that they can
+ be easily enabled individually, by replacing the '0' with '1' as
+ needed. */
+#define DEBUG0 0
+#define DEBUG1 1
+
+/* REGISTER_NAMES has the names for individual 8-bit registers, but
+ these have the names we need to use when referring to 16-bit
+ register pairs. */
+static const char * const word_regnames[] =
+{
+ "ax", "AX", "bc", "BC", "de", "DE", "hl", "HL",
+ "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
+ "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
+ "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
+ "sp", "ap", "psw", "es", "cs"
+};
+
+struct GTY(()) machine_function
+{
+ /* If set, the rest of the fields have been computed. */
+ int computed;
+ /* Which register pairs need to be pushed in the prologue. */
+ int need_to_push [FIRST_PSEUDO_REGISTER / 2];
+
+ /* These fields describe the frame layout... */
+ /* arg pointer */
+ /* 4 bytes for saved PC */
+ int framesize_regs;
+ /* frame pointer */
+ int framesize_locals;
+ int framesize_outgoing;
+ /* stack pointer */
+ int framesize;
+
+ /* If set, recog is allowed to match against the "real" patterns. */
+ int real_insns_ok;
+ /* If set, recog is allowed to match against the "virtual" patterns. */
+ int virt_insns_ok;
+ /* Set if the current function needs to clean up any trampolines. */
+ int trampolines_used;
+};
+
+/* This is our init_machine_status, as set in
+ rl78_option_override. */
+static struct machine_function *
+rl78_init_machine_status (void)
+{
+ struct machine_function *m;
+
+ m = ggc_alloc_cleared_machine_function ();
+ m->virt_insns_ok = 1;
+
+ return m;
+}
+
+/* Returns whether to run the devirtualization pass. */
+static bool
+devirt_gate (void)
+{
+ return true;
+}
+
+/* Runs the devirtualization pass. */
+static unsigned int
+devirt_pass (void)
+{
+ rl78_reorg ();
+ return 0;
+}
+
+/* This pass converts virtual instructions using virtual registers, to
+ real instructions using real registers. Rather than run it as
+ reorg, we reschedule it before vartrack to help with debugging. */
+namespace {
+
+const pass_data pass_data_rl78_devirt =
+{
+ RTL_PASS, /* type */
+ "devirt", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ true, /* has_gate */
+ true, /* has_execute */
+ TV_MACH_DEP, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_rl78_devirt : public rtl_opt_pass
+{
+public:
+ pass_rl78_devirt(gcc::context *ctxt)
+ : rtl_opt_pass(pass_data_rl78_devirt, ctxt)
+ {
+ }
+
+ /* opt_pass methods: */
+ bool gate () { return devirt_gate (); }
+ unsigned int execute () { return devirt_pass (); }
+};
+
+} // anon namespace
+
+rtl_opt_pass *
+make_pass_rl78_devirt (gcc::context *ctxt)
+{
+ return new pass_rl78_devirt (ctxt);
+}
+
+/* Redundant move elimination pass. Must be run after the basic block
+ reordering pass for the best effect. */
+
+static unsigned int
+move_elim_pass (void)
+{
+ rtx insn, ninsn, prev = NULL_RTX;
+
+ for (insn = get_insns (); insn; insn = ninsn)
+ {
+ rtx set;
+
+ ninsn = next_nonnote_nondebug_insn (insn);
+
+ if ((set = single_set (insn)) == NULL_RTX)
+ {
+ prev = NULL_RTX;
+ continue;
+ }
+
+ /* If we have two SET insns in a row (without anything
+ between them) and the source of the second one is the
+ destination of the first one, and vice versa, then we
+ can eliminate the second SET. */
+ if (prev
+ && rtx_equal_p (SET_DEST (prev), SET_SRC (set))
+ && rtx_equal_p (SET_DEST (set), SET_SRC (prev))
+ )
+ {
+ if (dump_file)
+ fprintf (dump_file, " Delete insn %d because it is redundant\n",
+ INSN_UID (insn));
+
+ delete_insn (insn);
+ prev = NULL_RTX;
+ }
+ else
+ prev = set;
+ }
+
+ if (dump_file)
+ print_rtl_with_bb (dump_file, get_insns (), 0);
+
+ return 0;
+}
+
+namespace {
+
+const pass_data pass_data_rl78_move_elim =
+{
+ RTL_PASS, /* type */
+ "move_elim", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ true, /* has_gate */
+ true, /* has_execute */
+ TV_MACH_DEP, /* tv_id */
+ 0, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_rl78_move_elim : public rtl_opt_pass
+{
+public:
+ pass_rl78_move_elim(gcc::context *ctxt)
+ : rtl_opt_pass(pass_data_rl78_move_elim, ctxt)
+ {
+ }
+
+ /* opt_pass methods: */
+ bool gate () { return devirt_gate (); }
+ unsigned int execute () { return move_elim_pass (); }
+};
+
+} // anon namespace
+
+rtl_opt_pass *
+make_pass_rl78_move_elim (gcc::context *ctxt)
+{
+ return new pass_rl78_move_elim (ctxt);
+}
+
+#undef TARGET_ASM_FILE_START
+#define TARGET_ASM_FILE_START rl78_asm_file_start
+
+static void
+rl78_asm_file_start (void)
+{
+ int i;
+
+ if (TARGET_G10)
+ {
+ /* The memory used is 0xffec8 to 0xffedf; real registers are in
+ 0xffee0 to 0xffee7. */
+ for (i = 8; i < 32; i++)
+ fprintf (asm_out_file, "r%d\t=\t0x%x\n", i, 0xffec0 + i);
+ }
+ else
+ {
+ for (i = 0; i < 8; i++)
+ {
+ fprintf (asm_out_file, "r%d\t=\t0x%x\n", 8 + i, 0xffef0 + i);
+ fprintf (asm_out_file, "r%d\t=\t0x%x\n", 16 + i, 0xffee8 + i);
+ fprintf (asm_out_file, "r%d\t=\t0x%x\n", 24 + i, 0xffee0 + i);
+ }
+ }
+
+ opt_pass *rl78_devirt_pass = make_pass_rl78_devirt (g);
+ static struct register_pass_info rl78_devirt_info =
+ {
+ rl78_devirt_pass,
+ "pro_and_epilogue",
+ 1,
+ PASS_POS_INSERT_BEFORE
+ };
+
+ opt_pass *rl78_move_elim_pass = make_pass_rl78_move_elim (g);
+ static struct register_pass_info rl78_move_elim_info =
+ {
+ rl78_move_elim_pass,
+ "bbro",
+ 1,
+ PASS_POS_INSERT_AFTER
+ };
+
+ register_pass (& rl78_devirt_info);
+ register_pass (& rl78_move_elim_info);
+}
+
+
+#undef TARGET_OPTION_OVERRIDE
+#define TARGET_OPTION_OVERRIDE rl78_option_override
+
+static void
+rl78_option_override (void)
+{
+ flag_omit_frame_pointer = 1;
+ flag_no_function_cse = 1;
+ flag_split_wide_types = 0;
+
+ init_machine_status = rl78_init_machine_status;
+
+ if (TARGET_ALLREGS)
+ {
+ int i;
+
+ for (i = 24; i < 32; i++)
+ fixed_regs[i] = 0;
+ }
+}
+
+/* Most registers are 8 bits. Some are 16 bits because, for example,
+ gcc doesn't like dealing with $FP as a register pair (the second
+ half of $fp is also 2 to keep reload happy wrt register pairs, but
+ no register class includes it). This table maps register numbers
+ to size in bytes. */
+static const int register_sizes[] =
+{
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 1, 1, 1
+};
+
+/* Predicates used in the MD patterns. This one is true when virtual
+ insns may be matched, which typically means before (or during) the
+ devirt pass. */
+bool
+rl78_virt_insns_ok (void)
+{
+ if (cfun)
+ return cfun->machine->virt_insns_ok;
+ return true;
+}
+
+/* Predicates used in the MD patterns. This one is true when real
+ insns may be matched, which typically means after (or during) the
+ devirt pass. */
+bool
+rl78_real_insns_ok (void)
+{
+ if (cfun)
+ return cfun->machine->real_insns_ok;
+ return false;
+}
+
+/* Implements HARD_REGNO_NREGS. */
+int
+rl78_hard_regno_nregs (int regno, enum machine_mode mode)
+{
+ int rs = register_sizes[regno];
+ if (rs < 1)
+ rs = 1;
+ return ((GET_MODE_SIZE (mode) + rs - 1) / rs);
+}
+
+/* Implements HARD_REGNO_MODE_OK. */
+int
+rl78_hard_regno_mode_ok (int regno, enum machine_mode mode)
+{
+ int s = GET_MODE_SIZE (mode);
+
+ if (s < 1)
+ return 0;
+ /* These are not to be used by gcc. */
+ if (regno == 23 || regno == ES_REG || regno == CS_REG)
+ return 0;
+ /* $fp can always be accessed as a 16-bit value. */
+ if (regno == FP_REG && s == 2)
+ return 1;
+ if (regno < SP_REG)
+ {
+ /* Since a reg-reg move is really a reg-mem move, we must
+ enforce alignment. */
+ if (s > 1 && (regno % 2))
+ return 0;
+ return 1;
+ }
+ if (s == CC_REGNUM)
+ return (mode == BImode);
+ /* All other registers must be accessed in their natural sizes. */
+ if (s == register_sizes [regno])
+ return 1;
+ return 0;
+}
+
+/* Simplify_gen_subreg() doesn't handle memory references the way we
+ need it to below, so we use this function for when we must get a
+ valid subreg in a "natural" state. */
+static rtx
+rl78_subreg (enum machine_mode mode, rtx r, enum machine_mode omode, int byte)
+{
+ if (GET_CODE (r) == MEM)
+ return adjust_address (r, mode, byte);
+ else
+ return simplify_gen_subreg (mode, r, omode, byte);
+}
+
+/* Used by movsi. Split SImode moves into two HImode moves, using
+ appropriate patterns for the upper and lower halves of symbols. */
+void
+rl78_expand_movsi (rtx *operands)
+{
+ rtx op00, op02, op10, op12;
+
+ op00 = rl78_subreg (HImode, operands[0], SImode, 0);
+ op02 = rl78_subreg (HImode, operands[0], SImode, 2);
+ if (GET_CODE (operands[1]) == CONST
+ || GET_CODE (operands[1]) == SYMBOL_REF)
+ {
+ op10 = gen_rtx_ZERO_EXTRACT (HImode, operands[1], GEN_INT (16), GEN_INT (0));
+ op10 = gen_rtx_CONST (HImode, op10);
+ op12 = gen_rtx_ZERO_EXTRACT (HImode, operands[1], GEN_INT (16), GEN_INT (16));
+ op12 = gen_rtx_CONST (HImode, op12);
+ }
+ else
+ {
+ op10 = rl78_subreg (HImode, operands[1], SImode, 0);
+ op12 = rl78_subreg (HImode, operands[1], SImode, 2);
+ }
+
+ if (rtx_equal_p (operands[0], operands[1]))
+ ;
+ else if (rtx_equal_p (op00, op12))
+ {
+ emit_move_insn (op02, op12);
+ emit_move_insn (op00, op10);
+ }
+ else
+ {
+ emit_move_insn (op00, op10);
+ emit_move_insn (op02, op12);
+ }
+}
+
+/* Generate code to move an SImode value. */
+void
+rl78_split_movsi (rtx *operands)
+{
+ rtx op00, op02, op10, op12;
+
+ op00 = rl78_subreg (HImode, operands[0], SImode, 0);
+ op02 = rl78_subreg (HImode, operands[0], SImode, 2);
+
+ if (GET_CODE (operands[1]) == CONST
+ || GET_CODE (operands[1]) == SYMBOL_REF)
+ {
+ op10 = gen_rtx_ZERO_EXTRACT (HImode, operands[1], GEN_INT (16), GEN_INT (0));
+ op10 = gen_rtx_CONST (HImode, op10);
+ op12 = gen_rtx_ZERO_EXTRACT (HImode, operands[1], GEN_INT (16), GEN_INT (16));
+ op12 = gen_rtx_CONST (HImode, op12);
+ }
+ else
+ {
+ op10 = rl78_subreg (HImode, operands[1], SImode, 0);
+ op12 = rl78_subreg (HImode, operands[1], SImode, 2);
+ }
+
+ if (rtx_equal_p (operands[0], operands[1]))
+ ;
+ else if (rtx_equal_p (op00, op12))
+ {
+ operands[2] = op02;
+ operands[4] = op12;
+ operands[3] = op00;
+ operands[5] = op10;
+ }
+ else
+ {
+ operands[2] = op00;
+ operands[4] = op10;
+ operands[3] = op02;
+ operands[5] = op12;
+ }
+}
+
+/* Used by various two-operand expanders which cannot accept all
+ operands in the "far" namespace. Force some such operands into
+ registers so that each pattern has at most one far operand. */
+int
+rl78_force_nonfar_2 (rtx *operands, rtx (*gen)(rtx,rtx))
+{
+ int did = 0;
+ rtx temp_reg = NULL;
+
+ /* FIXME: in the future, be smarter about only doing this if the
+ other operand is also far, assuming the devirtualizer can also
+ handle that. */
+ if (rl78_far_p (operands[0]))
+ {
+ temp_reg = operands[0];
+ operands[0] = gen_reg_rtx (GET_MODE (operands[0]));
+ did = 1;
+ }
+ if (!did)
+ return 0;
+
+ emit_insn (gen (operands[0], operands[1]));
+ if (temp_reg)
+ emit_move_insn (temp_reg, operands[0]);
+ return 1;
+}
+
+/* Likewise, but for three-operand expanders. */
+int
+rl78_force_nonfar_3 (rtx *operands, rtx (*gen)(rtx,rtx,rtx))
+{
+ int did = 0;
+ rtx temp_reg = NULL;
+
+ /* FIXME: Likewise. */
+ if (rl78_far_p (operands[1]))
+ {
+ rtx temp_reg = gen_reg_rtx (GET_MODE (operands[1]));
+ emit_move_insn (temp_reg, operands[1]);
+ operands[1] = temp_reg;
+ did = 1;
+ }
+ if (rl78_far_p (operands[0]))
+ {
+ temp_reg = operands[0];
+ operands[0] = gen_reg_rtx (GET_MODE (operands[0]));
+ did = 1;
+ }
+ if (!did)
+ return 0;
+
+ emit_insn (gen (operands[0], operands[1], operands[2]));
+ if (temp_reg)
+ emit_move_insn (temp_reg, operands[0]);
+ return 1;
+}
+
+#undef TARGET_CAN_ELIMINATE
+#define TARGET_CAN_ELIMINATE rl78_can_eliminate
+
+static bool
+rl78_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to ATTRIBUTE_UNUSED)
+{
+ return true;
+}
+
+/* Returns true if the given register needs to be saved by the
+ current function. */
+static bool
+need_to_save (unsigned int regno)
+{
+ if (is_interrupt_func (cfun->decl))
+ {
+ /* We don't know what devirt will need */
+ if (regno < 8)
+ return true;
+
+ /* We don't need to save registers that have
+ been reserved for interrupt handlers. */
+ if (regno > 23)
+ return false;
+
+ /* If the handler is a non-leaf function then it may call
+ non-interrupt aware routines which will happily clobber
+ any call_used registers, so we have to preserve them. */
+ if (!crtl->is_leaf && call_used_regs[regno])
+ return true;
+
+ /* Otherwise we only have to save a register, call_used
+ or not, if it is used by this handler. */
+ return df_regs_ever_live_p (regno);
+ }
+
+ if (regno == FRAME_POINTER_REGNUM && frame_pointer_needed)
+ return true;
+ if (fixed_regs[regno])
+ return false;
+ if (crtl->calls_eh_return)
+ return true;
+ if (df_regs_ever_live_p (regno)
+ && !call_used_regs[regno])
+ return true;
+ return false;
+}
+
+/* We use this to wrap all emitted insns in the prologue. */
+static rtx
+F (rtx x)
+{
+ RTX_FRAME_RELATED_P (x) = 1;
+ return x;
+}
+
+/* Compute all the frame-related fields in our machine_function
+ structure. */
+static void
+rl78_compute_frame_info (void)
+{
+ int i;
+
+ cfun->machine->computed = 1;
+ cfun->machine->framesize_regs = 0;
+ cfun->machine->framesize_locals = get_frame_size ();
+ cfun->machine->framesize_outgoing = crtl->outgoing_args_size;
+
+ for (i = 0; i < 16; i ++)
+ if (need_to_save (i * 2) || need_to_save (i * 2 + 1))
+ {
+ cfun->machine->need_to_push [i] = 1;
+ cfun->machine->framesize_regs += 2;
+ }
+ else
+ cfun->machine->need_to_push [i] = 0;
+
+ if ((cfun->machine->framesize_locals + cfun->machine->framesize_outgoing) & 1)
+ cfun->machine->framesize_locals ++;
+
+ cfun->machine->framesize = (cfun->machine->framesize_regs
+ + cfun->machine->framesize_locals
+ + cfun->machine->framesize_outgoing);
+}
+
+/* Returns true if the provided function has the specified attribute. */
+static inline bool
+has_func_attr (const_tree decl, const char * func_attr)
+{
+ if (decl == NULL_TREE)
+ decl = current_function_decl;
+
+ return lookup_attribute (func_attr, DECL_ATTRIBUTES (decl)) != NULL_TREE;
+}
+
+/* Returns true if the provided function has the "interrupt" attribute. */
+static inline bool
+is_interrupt_func (const_tree decl)
+{
+ return has_func_attr (decl, "interrupt") || has_func_attr (decl, "brk_interrupt");
+}
+
+/* Returns true if the provided function has the "brk_interrupt" attribute. */
+static inline bool
+is_brk_interrupt_func (const_tree decl)
+{
+ return has_func_attr (decl, "brk_interrupt");
+}
+
+/* Check "interrupt" attributes. */
+static tree
+rl78_handle_func_attribute (tree * node,
+ tree name,
+ tree args,
+ int flags ATTRIBUTE_UNUSED,
+ bool * no_add_attrs)
+{
+ gcc_assert (DECL_P (* node));
+ gcc_assert (args == NULL_TREE);
+
+ if (TREE_CODE (* node) != FUNCTION_DECL)
+ {
+ warning (OPT_Wattributes, "%qE attribute only applies to functions",
+ name);
+ * no_add_attrs = true;
+ }
+
+ /* FIXME: We ought to check that the interrupt and exception
+ handler attributes have been applied to void functions. */
+ return NULL_TREE;
+}
+
+#undef TARGET_ATTRIBUTE_TABLE
+#define TARGET_ATTRIBUTE_TABLE rl78_attribute_table
+
+/* Table of RL78-specific attributes. */
+const struct attribute_spec rl78_attribute_table[] =
+{
+ /* Name, min_len, max_len, decl_req, type_req, fn_type_req, handler,
+ affects_type_identity. */
+ { "interrupt", 0, 0, true, false, false, rl78_handle_func_attribute,
+ false },
+ { "brk_interrupt", 0, 0, true, false, false, rl78_handle_func_attribute,
+ false },
+ { "naked", 0, 0, true, false, false, rl78_handle_func_attribute,
+ false },
+ { NULL, 0, 0, false, false, false, NULL, false }
+};
+
+
+
+/* Break down an address RTX into its component base/index/addend
+ portions and return TRUE if the address is of a valid form, else
+ FALSE. */
+static bool
+characterize_address (rtx x, rtx *base, rtx *index, rtx *addend)
+{
+ *base = NULL_RTX;
+ *index = NULL_RTX;
+ *addend = NULL_RTX;
+
+ if (GET_CODE (x) == UNSPEC
+ && XINT (x, 1) == UNS_ES_ADDR)
+ x = XVECEXP (x, 0, 1);
+
+ if (GET_CODE (x) == REG)
+ {
+ *base = x;
+ return true;
+ }
+
+ /* We sometimes get these without the CONST wrapper */
+ if (GET_CODE (x) == PLUS
+ && GET_CODE (XEXP (x, 0)) == SYMBOL_REF
+ && GET_CODE (XEXP (x, 1)) == CONST_INT)
+ {
+ *addend = x;
+ return true;
+ }
+
+ if (GET_CODE (x) == PLUS)
+ {
+ *base = XEXP (x, 0);
+ x = XEXP (x, 1);
+
+ if (GET_CODE (*base) != REG
+ && GET_CODE (x) == REG)
+ {
+ rtx tmp = *base;
+ *base = x;
+ x = tmp;
+ }
+
+ if (GET_CODE (*base) != REG)
+ return false;
+
+ if (GET_CODE (x) == ZERO_EXTEND
+ && GET_CODE (XEXP (x, 0)) == REG)
+ {
+ *index = XEXP (x, 0);
+ return false;
+ }
+ }
+
+ switch (GET_CODE (x))
+ {
+ case PLUS:
+ if (GET_CODE (XEXP (x, 0)) == SYMBOL_REF
+ && GET_CODE (XEXP (x, 0)) == CONST_INT)
+ {
+ *addend = x;
+ return true;
+ }
+ /* fall through */
+ case MEM:
+ case REG:
+ return false;
+
+ case CONST:
+ case SYMBOL_REF:
+ case CONST_INT:
+ *addend = x;
+ return true;
+
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+/* Used by the Whb constraint. Match addresses that use HL+B or HL+C
+ addressing. */
+bool
+rl78_hl_b_c_addr_p (rtx op)
+{
+ rtx hl, bc;
+
+ if (GET_CODE (op) != PLUS)
+ return false;
+ hl = XEXP (op, 0);
+ bc = XEXP (op, 1);
+ if (GET_CODE (hl) == ZERO_EXTEND)
+ {
+ rtx tmp = hl;
+ hl = bc;
+ bc = tmp;
+ }
+ if (GET_CODE (hl) != REG)
+ return false;
+ if (GET_CODE (bc) != ZERO_EXTEND)
+ return false;
+ bc = XEXP (bc, 0);
+ if (GET_CODE (bc) != REG)
+ return false;
+ if (REGNO (hl) != HL_REG)
+ return false;
+ if (REGNO (bc) != B_REG && REGNO (bc) != C_REG)
+ return false;
+
+ return true;
+}
+
+#define REG_IS(r, regno) (((r) == (regno)) || ((r) >= FIRST_PSEUDO_REGISTER && !(strict)))
+
+/* Used in various constraints and predicates to match operands in the
+ "far" address space. */
+int
+rl78_far_p (rtx x)
+{
+ if (! MEM_P (x))
+ return 0;
+#if DEBUG0
+ fprintf (stderr, "\033[35mrl78_far_p: "); debug_rtx (x);
+ fprintf (stderr, " = %d\033[0m\n", MEM_ADDR_SPACE (x) == ADDR_SPACE_FAR);
+#endif
+ return MEM_ADDR_SPACE (x) == ADDR_SPACE_FAR;
+}
+
+/* Return the appropriate mode for a named address pointer. */
+#undef TARGET_ADDR_SPACE_POINTER_MODE
+#define TARGET_ADDR_SPACE_POINTER_MODE rl78_addr_space_pointer_mode
+static enum machine_mode
+rl78_addr_space_pointer_mode (addr_space_t addrspace)
+{
+ switch (addrspace)
+ {
+ case ADDR_SPACE_GENERIC:
+ return HImode;
+ case ADDR_SPACE_FAR:
+ return SImode;
+ default:
+ gcc_unreachable ();
+ }
+}
+
+/* Returns TRUE for valid addresses. */
+#undef TARGET_VALID_POINTER_MODE
+#define TARGET_VALID_POINTER_MODE rl78_valid_pointer_mode
+static bool
+rl78_valid_pointer_mode (enum machine_mode m)
+{
+ return (m == HImode || m == SImode);
+}
+
+/* Return the appropriate mode for a named address address. */
+#undef TARGET_ADDR_SPACE_ADDRESS_MODE
+#define TARGET_ADDR_SPACE_ADDRESS_MODE rl78_addr_space_address_mode
+static enum machine_mode
+rl78_addr_space_address_mode (addr_space_t addrspace)
+{
+ switch (addrspace)
+ {
+ case ADDR_SPACE_GENERIC:
+ return HImode;
+ case ADDR_SPACE_FAR:
+ return SImode;
+ default:
+ gcc_unreachable ();
+ }
+}
+
+#undef TARGET_LEGITIMATE_CONSTANT_P
+#define TARGET_LEGITIMATE_CONSTANT_P rl78_is_legitimate_constant
+
+static bool
+rl78_is_legitimate_constant (enum machine_mode mode ATTRIBUTE_UNUSED, rtx x ATTRIBUTE_UNUSED)
+{
+ return true;
+}
+
+#undef TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P
+#define TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P rl78_as_legitimate_address
+
+bool
+rl78_as_legitimate_address (enum machine_mode mode ATTRIBUTE_UNUSED, rtx x,
+ bool strict ATTRIBUTE_UNUSED, addr_space_t as ATTRIBUTE_UNUSED)
+{
+ rtx base, index, addend;
+ bool is_far_addr = false;
+
+ if (GET_CODE (x) == UNSPEC
+ && XINT (x, 1) == UNS_ES_ADDR)
+ {
+ x = XVECEXP (x, 0, 1);
+ is_far_addr = true;
+ }
+
+ if (as == ADDR_SPACE_GENERIC
+ && (GET_MODE (x) == SImode || is_far_addr))
+ return false;
+
+ if (! characterize_address (x, &base, &index, &addend))
+ return false;
+
+ /* We can't extract the high/low portions of a PLUS address
+ involving a register during devirtualization, so make sure all
+ such __far addresses do not have addends. This forces GCC to do
+ the sum separately. */
+ if (addend && base && as == ADDR_SPACE_FAR)
+ return false;
+
+ if (base && index)
+ {
+ int ir = REGNO (index);
+ int br = REGNO (base);
+
+#define OK(test, debug) if (test) { /*fprintf(stderr, "%d: OK %s\n", __LINE__, debug);*/ return true; }
+ OK (REG_IS (br, HL_REG) && REG_IS (ir, B_REG), "[hl+b]");
+ OK (REG_IS (br, HL_REG) && REG_IS (ir, C_REG), "[hl+c]");
+ return false;
+ }
+
+ if (strict && base && GET_CODE (base) == REG && REGNO (base) >= FIRST_PSEUDO_REGISTER)
+ return false;
+
+ if (! cfun->machine->virt_insns_ok && base && GET_CODE (base) == REG
+ && REGNO (base) >= 8 && REGNO (base) <= 31)
+ return false;
+
+ return true;
+}
+
+/* Determine if one named address space is a subset of another. */
+#undef TARGET_ADDR_SPACE_SUBSET_P
+#define TARGET_ADDR_SPACE_SUBSET_P rl78_addr_space_subset_p
+static bool
+rl78_addr_space_subset_p (addr_space_t subset, addr_space_t superset)
+{
+ gcc_assert (subset == ADDR_SPACE_GENERIC || subset == ADDR_SPACE_FAR);
+ gcc_assert (superset == ADDR_SPACE_GENERIC || superset == ADDR_SPACE_FAR);
+
+ if (subset == superset)
+ return true;
+
+ else
+ return (subset == ADDR_SPACE_GENERIC && superset == ADDR_SPACE_FAR);
+}
+
+#undef TARGET_ADDR_SPACE_CONVERT
+#define TARGET_ADDR_SPACE_CONVERT rl78_addr_space_convert
+/* Convert from one address space to another. */
+static rtx
+rl78_addr_space_convert (rtx op, tree from_type, tree to_type)
+{
+ addr_space_t from_as = TYPE_ADDR_SPACE (TREE_TYPE (from_type));
+ addr_space_t to_as = TYPE_ADDR_SPACE (TREE_TYPE (to_type));
+ rtx result;
+
+ gcc_assert (from_as == ADDR_SPACE_GENERIC || from_as == ADDR_SPACE_FAR);
+ gcc_assert (to_as == ADDR_SPACE_GENERIC || to_as == ADDR_SPACE_FAR);
+
+ if (to_as == ADDR_SPACE_GENERIC && from_as == ADDR_SPACE_FAR)
+ {
+ /* This is unpredictable, as we're truncating off usable address
+ bits. */
+
+ result = gen_reg_rtx (HImode);
+ emit_move_insn (result, simplify_subreg (HImode, op, SImode, 0));
+ return result;
+ }
+ else if (to_as == ADDR_SPACE_FAR && from_as == ADDR_SPACE_GENERIC)
+ {
+ /* This always works. */
+ result = gen_reg_rtx (SImode);
+ emit_move_insn (rl78_subreg (HImode, result, SImode, 0), op);
+ emit_move_insn (rl78_subreg (HImode, result, SImode, 2), const0_rtx);
+ return result;
+ }
+ else
+ gcc_unreachable ();
+}
+
+/* Implements REGNO_MODE_CODE_OK_FOR_BASE_P. */
+bool
+rl78_regno_mode_code_ok_for_base_p (int regno, enum machine_mode mode ATTRIBUTE_UNUSED,
+ addr_space_t address_space ATTRIBUTE_UNUSED,
+ int outer_code ATTRIBUTE_UNUSED, int index_code)
+{
+ if (regno <= SP_REG && regno >= 16)
+ return true;
+ if (index_code == REG)
+ return (regno == HL_REG);
+ if (regno == C_REG || regno == B_REG || regno == E_REG || regno == L_REG)
+ return true;
+ return false;
+}
+
+/* Implements MODE_CODE_BASE_REG_CLASS. */
+enum reg_class
+rl78_mode_code_base_reg_class (enum machine_mode mode ATTRIBUTE_UNUSED,
+ addr_space_t address_space ATTRIBUTE_UNUSED,
+ int outer_code ATTRIBUTE_UNUSED,
+ int index_code ATTRIBUTE_UNUSED)
+{
+ return V_REGS;
+}
+
+/* Implements INITIAL_ELIMINATION_OFFSET. The frame layout is
+ described in the machine_Function struct definition, above. */
+int
+rl78_initial_elimination_offset (int from, int to)
+{
+ int rv = 0; /* as if arg to arg */
+
+ rl78_compute_frame_info ();
+
+ switch (to)
+ {
+ case STACK_POINTER_REGNUM:
+ rv += cfun->machine->framesize_outgoing;
+ rv += cfun->machine->framesize_locals;
+ /* Fall through. */
+ case FRAME_POINTER_REGNUM:
+ rv += cfun->machine->framesize_regs;
+ rv += 4;
+ break;
+ default:
+ gcc_unreachable ();
+ }
+
+ switch (from)
+ {
+ case FRAME_POINTER_REGNUM:
+ rv -= 4;
+ rv -= cfun->machine->framesize_regs;
+ case ARG_POINTER_REGNUM:
+ break;
+ default:
+ gcc_unreachable ();
+ }
+
+ return rv;
+}
+
+static int
+rl78_is_naked_func (void)
+{
+ return (lookup_attribute ("naked", DECL_ATTRIBUTES (current_function_decl)) != NULL_TREE);
+}
+
+/* Expand the function prologue (from the prologue pattern). */
+void
+rl78_expand_prologue (void)
+{
+ int i, fs;
+ rtx sp = gen_rtx_REG (HImode, STACK_POINTER_REGNUM);
+ int rb = 0;
+
+ if (rl78_is_naked_func ())
+ return;
+
+ /* Always re-compute the frame info - the register usage may have changed. */
+ rl78_compute_frame_info ();
+
+ if (flag_stack_usage_info)
+ current_function_static_stack_size = cfun->machine->framesize;
+
+ if (is_interrupt_func (cfun->decl) && !TARGET_G10)
+ for (i = 0; i < 4; i++)
+ if (cfun->machine->need_to_push [i])
+ {
+ /* Select Bank 0 if we are using any registers from Bank 0. */
+ emit_insn (gen_sel_rb (GEN_INT (0)));
+ break;
+ }
+
+ for (i = 0; i < 16; i++)
+ if (cfun->machine->need_to_push [i])
+ {
+ if (TARGET_G10)
+ {
+ emit_move_insn (gen_rtx_REG (HImode, 0), gen_rtx_REG (HImode, i*2));
+ F (emit_insn (gen_push (gen_rtx_REG (HImode, 0))));
+ }
+ else
+ {
+ int need_bank = i/4;
+
+ if (need_bank != rb)
+ {
+ emit_insn (gen_sel_rb (GEN_INT (need_bank)));
+ rb = need_bank;
+ }
+ F (emit_insn (gen_push (gen_rtx_REG (HImode, i*2))));
+ }
+ }
+
+ if (rb != 0)
+ emit_insn (gen_sel_rb (GEN_INT (0)));
+
+ if (frame_pointer_needed)
+ {
+ F (emit_move_insn (gen_rtx_REG (HImode, AX_REG),
+ gen_rtx_REG (HImode, STACK_POINTER_REGNUM)));
+ F (emit_move_insn (gen_rtx_REG (HImode, FRAME_POINTER_REGNUM),
+ gen_rtx_REG (HImode, AX_REG)));
+ }
+
+ fs = cfun->machine->framesize_locals + cfun->machine->framesize_outgoing;
+ while (fs > 0)
+ {
+ int fs_byte = (fs > 254) ? 254 : fs;
+ F (emit_insn (gen_subhi3 (sp, sp, GEN_INT (fs_byte))));
+ fs -= fs_byte;
+ }
+}
+
+/* Expand the function epilogue (from the epilogue pattern). */
+void
+rl78_expand_epilogue (void)
+{
+ int i, fs;
+ rtx sp = gen_rtx_REG (HImode, STACK_POINTER_REGNUM);
+ int rb = 0;
+
+ if (rl78_is_naked_func ())
+ return;
+
+ if (frame_pointer_needed)
+ {
+ emit_move_insn (gen_rtx_REG (HImode, AX_REG),
+ gen_rtx_REG (HImode, FRAME_POINTER_REGNUM));
+ emit_move_insn (gen_rtx_REG (HImode, STACK_POINTER_REGNUM),
+ gen_rtx_REG (HImode, AX_REG));
+ }
+ else
+ {
+ fs = cfun->machine->framesize_locals + cfun->machine->framesize_outgoing;
+ while (fs > 0)
+ {
+ int fs_byte = (fs > 254) ? 254 : fs;
+
+ emit_insn (gen_addhi3 (sp, sp, GEN_INT (fs_byte)));
+ fs -= fs_byte;
+ }
+ }
+
+ for (i = 15; i >= 0; i--)
+ if (cfun->machine->need_to_push [i])
+ {
+ if (TARGET_G10)
+ {
+ emit_insn (gen_pop (gen_rtx_REG (HImode, 0)));
+ emit_move_insn (gen_rtx_REG (HImode, i*2), gen_rtx_REG (HImode, 0));
+ }
+ else
+ {
+ int need_bank = i / 4;
+
+ if (need_bank != rb)
+ {
+ emit_insn (gen_sel_rb (GEN_INT (need_bank)));
+ rb = need_bank;
+ }
+ emit_insn (gen_pop (gen_rtx_REG (HImode, i * 2)));
+ }
+ }
+
+ if (rb != 0)
+ emit_insn (gen_sel_rb (GEN_INT (0)));
+
+ if (cfun->machine->trampolines_used)
+ emit_insn (gen_trampoline_uninit ());
+
+ if (is_brk_interrupt_func (cfun->decl))
+ emit_jump_insn (gen_brk_interrupt_return ());
+ else if (is_interrupt_func (cfun->decl))
+ emit_jump_insn (gen_interrupt_return ());
+ else
+ emit_jump_insn (gen_rl78_return ());
+}
+
+/* Likewise, for exception handlers. */
+void
+rl78_expand_eh_epilogue (rtx x ATTRIBUTE_UNUSED)
+{
+ /* FIXME - replace this with an indirect jump with stack adjust. */
+ emit_jump_insn (gen_rl78_return ());
+}
+
+#undef TARGET_ASM_FUNCTION_PROLOGUE
+#define TARGET_ASM_FUNCTION_PROLOGUE rl78_start_function
+
+/* We don't use this to actually emit the function prologue. We use
+ this to insert a comment in the asm file describing the
+ function. */
+static void
+rl78_start_function (FILE *file, HOST_WIDE_INT hwi_local ATTRIBUTE_UNUSED)
+{
+ int i;
+
+ if (cfun->machine->framesize == 0)
+ return;
+ fprintf (file, "\t; start of function\n");
+
+ if (cfun->machine->framesize_regs)
+ {
+ fprintf (file, "\t; push %d:", cfun->machine->framesize_regs);
+ for (i = 0; i < 16; i ++)
+ if (cfun->machine->need_to_push[i])
+ fprintf (file, " %s", word_regnames[i*2]);
+ fprintf (file, "\n");
+ }
+
+ if (frame_pointer_needed)
+ fprintf (file, "\t; $fp points here (r22)\n");
+
+ if (cfun->machine->framesize_locals)
+ fprintf (file, "\t; locals: %d byte%s\n", cfun->machine->framesize_locals,
+ cfun->machine->framesize_locals == 1 ? "" : "s");
+
+ if (cfun->machine->framesize_outgoing)
+ fprintf (file, "\t; outgoing: %d byte%s\n", cfun->machine->framesize_outgoing,
+ cfun->machine->framesize_outgoing == 1 ? "" : "s");
+}
+
+/* Return an RTL describing where a function return value of type RET_TYPE
+ is held. */
+
+#undef TARGET_FUNCTION_VALUE
+#define TARGET_FUNCTION_VALUE rl78_function_value
+
+static rtx
+rl78_function_value (const_tree ret_type,
+ const_tree fn_decl_or_type ATTRIBUTE_UNUSED,
+ bool outgoing ATTRIBUTE_UNUSED)
+{
+ enum machine_mode mode = TYPE_MODE (ret_type);
+
+ return gen_rtx_REG (mode, 8);
+}
+
+#undef TARGET_PROMOTE_FUNCTION_MODE
+#define TARGET_PROMOTE_FUNCTION_MODE rl78_promote_function_mode
+
+static enum machine_mode
+rl78_promote_function_mode (const_tree type ATTRIBUTE_UNUSED,
+ enum machine_mode mode,
+ int *punsignedp ATTRIBUTE_UNUSED,
+ const_tree funtype ATTRIBUTE_UNUSED, int for_return ATTRIBUTE_UNUSED)
+{
+ return mode;
+}
+
+/* Return an RTL expression describing the register holding a function
+ parameter of mode MODE and type TYPE or NULL_RTX if the parameter should
+ be passed on the stack. CUM describes the previous parameters to the
+ function and NAMED is false if the parameter is part of a variable
+ parameter list, or the last named parameter before the start of a
+ variable parameter list. */
+
+#undef TARGET_FUNCTION_ARG
+#define TARGET_FUNCTION_ARG rl78_function_arg
+
+static rtx
+rl78_function_arg (cumulative_args_t cum_v ATTRIBUTE_UNUSED,
+ enum machine_mode mode ATTRIBUTE_UNUSED,
+ const_tree type ATTRIBUTE_UNUSED,
+ bool named ATTRIBUTE_UNUSED)
+{
+ return NULL_RTX;
+}
+
+#undef TARGET_FUNCTION_ARG_ADVANCE
+#define TARGET_FUNCTION_ARG_ADVANCE rl78_function_arg_advance
+
+static void
+rl78_function_arg_advance (cumulative_args_t cum_v, enum machine_mode mode, const_tree type,
+ bool named ATTRIBUTE_UNUSED)
+{
+ int rounded_size;
+ CUMULATIVE_ARGS * cum = get_cumulative_args (cum_v);
+
+ rounded_size = ((mode == BLKmode)
+ ? int_size_in_bytes (type) : GET_MODE_SIZE (mode));
+ if (rounded_size & 1)
+ rounded_size ++;
+ (*cum) += rounded_size;
+}
+
+#undef TARGET_FUNCTION_ARG_BOUNDARY
+#define TARGET_FUNCTION_ARG_BOUNDARY rl78_function_arg_boundary
+
+static unsigned int
+rl78_function_arg_boundary (enum machine_mode mode ATTRIBUTE_UNUSED,
+ const_tree type ATTRIBUTE_UNUSED)
+{
+ return 16;
+}
+
+/* Supported modifier letters:
+
+ A - address of a MEM
+ S - SADDR form of a real register
+ v - real register corresponding to a virtual register
+ m - minus - negative of CONST_INT value.
+ C - inverse of a conditional (NE vs EQ for example)
+ C - complement of an integer
+ z - collapsed conditional
+ s - shift count mod 8
+ S - shift count mod 16
+ r - reverse shift count (8-(count mod 8))
+ B - bit position
+
+ h - bottom HI of an SI
+ H - top HI of an SI
+ q - bottom QI of an HI
+ Q - top QI of an HI
+ e - third QI of an SI (i.e. where the ES register gets values from)
+ E - fourth QI of an SI (i.e. MSB)
+
+*/
+
+/* Implements the bulk of rl78_print_operand, below. We do it this
+ way because we need to test for a constant at the top level and
+ insert the '#', but not test for it anywhere else as we recurse
+ down into the operand. */
+static void
+rl78_print_operand_1 (FILE * file, rtx op, int letter)
+{
+ int need_paren;
+
+ switch (GET_CODE (op))
+ {
+ case MEM:
+ if (letter == 'A')
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ else
+ {
+ if (rl78_far_p (op))
+ {
+ fprintf (file, "es:");
+ op = gen_rtx_MEM (GET_MODE (op), XVECEXP (XEXP (op, 0), 0, 1));
+ }
+ if (letter == 'H')
+ {
+ op = adjust_address (op, HImode, 2);
+ letter = 0;
+ }
+ if (letter == 'h')
+ {
+ op = adjust_address (op, HImode, 0);
+ letter = 0;
+ }
+ if (letter == 'Q')
+ {
+ op = adjust_address (op, QImode, 1);
+ letter = 0;
+ }
+ if (letter == 'q')
+ {
+ op = adjust_address (op, QImode, 0);
+ letter = 0;
+ }
+ if (letter == 'e')
+ {
+ op = adjust_address (op, QImode, 2);
+ letter = 0;
+ }
+ if (letter == 'E')
+ {
+ op = adjust_address (op, QImode, 3);
+ letter = 0;
+ }
+ if (CONSTANT_P (XEXP (op, 0)))
+ {
+ fprintf (file, "!");
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ }
+ else if (GET_CODE (XEXP (op, 0)) == PLUS
+ && GET_CODE (XEXP (XEXP (op, 0), 0)) == SYMBOL_REF)
+ {
+ fprintf (file, "!");
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ }
+ else if (GET_CODE (XEXP (op, 0)) == PLUS
+ && GET_CODE (XEXP (XEXP (op, 0), 0)) == REG
+ && REGNO (XEXP (XEXP (op, 0), 0)) == 2)
+ {
+ rl78_print_operand_1 (file, XEXP (XEXP (op, 0), 1), 'u');
+ fprintf (file, "[");
+ rl78_print_operand_1 (file, XEXP (XEXP (op, 0), 0), 0);
+ fprintf (file, "]");
+ }
+ else
+ {
+ fprintf (file, "[");
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ fprintf (file, "]");
+ }
+ }
+ break;
+
+ case REG:
+ if (letter == 'Q')
+ fprintf (file, "%s", reg_names [REGNO (op) | 1]);
+ else if (letter == 'H')
+ fprintf (file, "%s", reg_names [REGNO (op) + 2]);
+ else if (letter == 'q')
+ fprintf (file, "%s", reg_names [REGNO (op) & ~1]);
+ else if (letter == 'e')
+ fprintf (file, "%s", reg_names [REGNO (op) + 2]);
+ else if (letter == 'E')
+ fprintf (file, "%s", reg_names [REGNO (op) + 3]);
+ else if (letter == 'S')
+ fprintf (file, "0x%x", 0xffef8 + REGNO (op));
+ else if (GET_MODE (op) == HImode
+ && ! (REGNO (op) & ~0xfe))
+ {
+ if (letter == 'v')
+ fprintf (file, "%s", word_regnames [REGNO (op) % 8]);
+ else
+ fprintf (file, "%s", word_regnames [REGNO (op)]);
+ }
+ else
+ fprintf (file, "%s", reg_names [REGNO (op)]);
+ break;
+
+ case CONST_INT:
+ if (letter == 'Q')
+ fprintf (file, "%ld", INTVAL (op) >> 8);
+ else if (letter == 'H')
+ fprintf (file, "%ld", INTVAL (op) >> 16);
+ else if (letter == 'q')
+ fprintf (file, "%ld", INTVAL (op) & 0xff);
+ else if (letter == 'h')
+ fprintf (file, "%ld", INTVAL (op) & 0xffff);
+ else if (letter == 'e')
+ fprintf (file, "%ld", (INTVAL (op) >> 16) & 0xff);
+ else if (letter == 'B')
+ fprintf (file, "%d", exact_log2 (INTVAL (op)));
+ else if (letter == 'E')
+ fprintf (file, "%ld", (INTVAL (op) >> 24) & 0xff);
+ else if (letter == 'm')
+ fprintf (file, "%ld", - INTVAL (op));
+ else if (letter == 's')
+ fprintf (file, "%ld", INTVAL (op) % 8);
+ else if (letter == 'S')
+ fprintf (file, "%ld", INTVAL (op) % 16);
+ else if (letter == 'r')
+ fprintf (file, "%ld", 8 - (INTVAL (op) % 8));
+ else if (letter == 'C')
+ fprintf (file, "%ld", (INTVAL (op) ^ 0x8000) & 0xffff);
+ else
+ fprintf (file, "%ld", INTVAL (op));
+ break;
+
+ case CONST:
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ break;
+
+ case ZERO_EXTRACT:
+ {
+ int bits = INTVAL (XEXP (op, 1));
+ int ofs = INTVAL (XEXP (op, 2));
+ if (bits == 16 && ofs == 0)
+ fprintf (file, "%%lo16(");
+ else if (bits == 16 && ofs == 16)
+ fprintf (file, "%%hi16(");
+ else if (bits == 8 && ofs == 16)
+ fprintf (file, "%%hi8(");
+ else
+ gcc_unreachable ();
+ rl78_print_operand_1 (file, XEXP (op, 0), 0);
+ fprintf (file, ")");
+ }
+ break;
+
+ case ZERO_EXTEND:
+ if (GET_CODE (XEXP (op, 0)) == REG)
+ fprintf (file, "%s", reg_names [REGNO (XEXP (op, 0))]);
+ else
+ print_rtl (file, op);
+ break;
+
+ case PLUS:
+ need_paren = 0;
+ if (letter == 'H')
+ {
+ fprintf (file, "%%hi16(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'h')
+ {
+ fprintf (file, "%%lo16(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'e')
+ {
+ fprintf (file, "%%hi8(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'q' || letter == 'Q')
+ output_operand_lossage ("q/Q modifiers invalid for symbol references");
+
+ if (GET_CODE (XEXP (op, 0)) == ZERO_EXTEND)
+ {
+ rl78_print_operand_1 (file, XEXP (op, 1), letter);
+ fprintf (file, "+");
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ }
+ else
+ {
+ rl78_print_operand_1 (file, XEXP (op, 0), letter);
+ fprintf (file, "+");
+ rl78_print_operand_1 (file, XEXP (op, 1), letter);
+ }
+ if (need_paren)
+ fprintf (file, ")");
+ break;
+
+ case SYMBOL_REF:
+ need_paren = 0;
+ if (letter == 'H')
+ {
+ fprintf (file, "%%hi16(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'h')
+ {
+ fprintf (file, "%%lo16(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'e')
+ {
+ fprintf (file, "%%hi8(");
+ need_paren = 1;
+ letter = 0;
+ }
+ if (letter == 'q' || letter == 'Q')
+ output_operand_lossage ("q/Q modifiers invalid for symbol references");
+
+ output_addr_const (file, op);
+ if (need_paren)
+ fprintf (file, ")");
+ break;
+
+ case CODE_LABEL:
+ case LABEL_REF:
+ output_asm_label (op);
+ break;
+
+ case LTU:
+ if (letter == 'z')
+ fprintf (file, "#comparison eliminated");
+ else
+ fprintf (file, letter == 'C' ? "nc" : "c");
+ break;
+ case LEU:
+ if (letter == 'z')
+ fprintf (file, "br");
+ else
+ fprintf (file, letter == 'C' ? "h" : "nh");
+ break;
+ case GEU:
+ if (letter == 'z')
+ fprintf (file, "br");
+ else
+ fprintf (file, letter == 'C' ? "c" : "nc");
+ break;
+ case GTU:
+ if (letter == 'z')
+ fprintf (file, "#comparison eliminated");
+ else
+ fprintf (file, letter == 'C' ? "nh" : "h");
+ break;
+ case EQ:
+ if (letter == 'z')
+ fprintf (file, "br");
+ else
+ fprintf (file, letter == 'C' ? "nz" : "z");
+ break;
+ case NE:
+ if (letter == 'z')
+ fprintf (file, "#comparison eliminated");
+ else
+ fprintf (file, letter == 'C' ? "z" : "nz");
+ break;
+
+ /* Note: these assume appropriate adjustments were made so that
+ unsigned comparisons, which is all this chip has, will
+ work. */
+ case LT:
+ if (letter == 'z')
+ fprintf (file, "#comparison eliminated");
+ else
+ fprintf (file, letter == 'C' ? "nc" : "c");
+ break;
+ case LE:
+ if (letter == 'z')
+ fprintf (file, "br");
+ else
+ fprintf (file, letter == 'C' ? "h" : "nh");
+ break;
+ case GE:
+ if (letter == 'z')
+ fprintf (file, "br");
+ else
+ fprintf (file, letter == 'C' ? "c" : "nc");
+ break;
+ case GT:
+ if (letter == 'z')
+ fprintf (file, "#comparison eliminated");
+ else
+ fprintf (file, letter == 'C' ? "nh" : "h");
+ break;
+
+ default:
+ fprintf (file, "(%s)", GET_RTX_NAME (GET_CODE (op)));
+ break;
+ }
+}
+
+#undef TARGET_PRINT_OPERAND
+#define TARGET_PRINT_OPERAND rl78_print_operand
+
+static void
+rl78_print_operand (FILE * file, rtx op, int letter)
+{
+ if (CONSTANT_P (op) && letter != 'u' && letter != 's' && letter != 'r' && letter != 'S' && letter != 'B')
+ fprintf (file, "#");
+ rl78_print_operand_1 (file, op, letter);
+}
+
+#undef TARGET_TRAMPOLINE_INIT
+#define TARGET_TRAMPOLINE_INIT rl78_trampoline_init
+
+/* Note that the RL78's addressing makes it very difficult to do
+ trampolines on the stack. So, libgcc has a small pool of
+ trampolines from which one is allocated to this task. */
+static void
+rl78_trampoline_init (rtx m_tramp, tree fndecl, rtx static_chain)
+{
+ rtx mov_addr, thunk_addr;
+ rtx function = XEXP (DECL_RTL (fndecl), 0);
+
+ mov_addr = adjust_address (m_tramp, HImode, 0);
+ thunk_addr = gen_reg_rtx (HImode);
+
+ function = force_reg (HImode, function);
+ static_chain = force_reg (HImode, static_chain);
+
+ emit_insn (gen_trampoline_init (thunk_addr, function, static_chain));
+ emit_move_insn (mov_addr, thunk_addr);
+
+ cfun->machine->trampolines_used = 1;
+}
+
+#undef TARGET_TRAMPOLINE_ADJUST_ADDRESS
+#define TARGET_TRAMPOLINE_ADJUST_ADDRESS rl78_trampoline_adjust_address
+
+static rtx
+rl78_trampoline_adjust_address (rtx m_tramp)
+{
+ rtx x = gen_rtx_MEM (HImode, m_tramp);
+ return x;
+}
+
+/* Expander for cbranchqi4 and cbranchhi4. RL78 is missing some of
+ the "normal" compares, specifically, it only has unsigned compares,
+ so we must synthesize the missing ones. */
+void
+rl78_expand_compare (rtx *operands)
+{
+ if (GET_CODE (operands[2]) == MEM)
+ operands[2] = copy_to_mode_reg (GET_MODE (operands[2]), operands[2]);
+}
+
+
+
+/* Define this to 1 if you are debugging the peephole optimizers. */
+#define DEBUG_PEEP 0
+
+/* Predicate used to enable the peephole2 patterns in rl78-virt.md.
+ The default "word" size is a byte so we can effectively use all the
+ registers, but we want to do 16-bit moves whenever possible. This
+ function determines when such a move is an option. */
+bool
+rl78_peep_movhi_p (rtx *operands)
+{
+ int i;
+ rtx m, a;
+
+ /* (set (op0) (op1))
+ (set (op2) (op3)) */
+
+ if (! rl78_virt_insns_ok ())
+ return false;
+
+#if DEBUG_PEEP
+ fprintf (stderr, "\033[33m");
+ debug_rtx (operands[0]);
+ debug_rtx (operands[1]);
+ debug_rtx (operands[2]);
+ debug_rtx (operands[3]);
+ fprintf (stderr, "\033[0m");
+#endif
+
+ /* You can move a constant to memory as QImode, but not HImode. */
+ if (GET_CODE (operands[0]) == MEM
+ && GET_CODE (operands[1]) != REG)
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: move constant to memory\n");
+#endif
+ return false;
+ }
+
+ if (rtx_equal_p (operands[0], operands[3]))
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: overlapping\n");
+#endif
+ return false;
+ }
+
+ for (i = 0; i < 2; i ++)
+ {
+ if (GET_CODE (operands[i]) != GET_CODE (operands[i+2]))
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: different codes\n");
+#endif
+ return false;
+ }
+ if (GET_MODE (operands[i]) != GET_MODE (operands[i+2]))
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: different modes\n");
+#endif
+ return false;
+ }
+
+ switch (GET_CODE (operands[i]))
+ {
+ case REG:
+ /* LSB MSB */
+ if (REGNO (operands[i]) + 1 != REGNO (operands[i+2])
+ || GET_MODE (operands[i]) != QImode)
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: wrong regnos %d %d %d\n",
+ REGNO (operands[i]), REGNO (operands[i+2]),
+ i);
+#endif
+ return false;
+ }
+ if (! rl78_hard_regno_mode_ok (REGNO (operands[i]), HImode))
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: reg %d not HI\n", REGNO (operands[i]));
+#endif
+ return false;
+ }
+ break;
+
+ case CONST_INT:
+ break;
+
+ case MEM:
+ if (GET_MODE (operands[i]) != QImode)
+ return false;
+ if (MEM_ALIGN (operands[i]) < 16)
+ return false;
+ a = XEXP (operands[i], 0);
+ if (GET_CODE (a) == CONST)
+ a = XEXP (a, 0);
+ if (GET_CODE (a) == PLUS)
+ a = XEXP (a, 1);
+ if (GET_CODE (a) == CONST_INT
+ && INTVAL (a) & 1)
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: misaligned mem %d\n", i);
+ debug_rtx (operands[i]);
+#endif
+ return false;
+ }
+ m = adjust_address (operands[i], QImode, 1);
+ if (! rtx_equal_p (m, operands[i+2]))
+ {
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: wrong mem %d\n", i);
+ debug_rtx (m);
+ debug_rtx (operands[i+2]);
+#endif
+ return false;
+ }
+ break;
+
+ default:
+#if DEBUG_PEEP
+ fprintf (stderr, "no peep: wrong rtx %d\n", i);
+#endif
+ return false;
+ }
+ }
+#if DEBUG_PEEP
+ fprintf (stderr, "\033[32mpeep!\033[0m\n");
+#endif
+ return true;
+}
+
+/* Likewise, when a peephole is activated, this function helps compute
+ the new operands. */
+void
+rl78_setup_peep_movhi (rtx *operands)
+{
+ int i;
+
+ for (i = 0; i < 2; i ++)
+ {
+ switch (GET_CODE (operands[i]))
+ {
+ case REG:
+ operands[i+4] = gen_rtx_REG (HImode, REGNO (operands[i]));
+ break;
+
+ case CONST_INT:
+ operands[i+4] = GEN_INT ((INTVAL (operands[i]) & 0xff) + ((char) INTVAL (operands[i+2])) * 256);
+ break;
+
+ case MEM:
+ operands[i+4] = adjust_address (operands[i], HImode, 0);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ How Devirtualization works in the RL78 GCC port
+
+Background
+
+The RL78 is an 8-bit port with some 16-bit operations. It has 32
+bytes of register space, in four banks, memory-mapped. One bank is
+the "selected" bank and holds the registers used for primary
+operations. Since the registers are memory mapped, often you can
+still refer to the unselected banks via memory accesses.
+
+Virtual Registers
+
+The GCC port uses bank 0 as the "selected" registers (A, X, BC, etc)
+and refers to the other banks via their memory addresses, although
+they're treated as regular registers internally. These "virtual"
+registers are R8 through R23 (bank3 is reserved for asm-based
+interrupt handlers).
+
+There are four machine description files:
+
+rl78.md - common register-independent patterns and definitions
+rl78-expand.md - expanders
+rl78-virt.md - patterns that match BEFORE devirtualization
+rl78-real.md - patterns that match AFTER devirtualization
+
+At least through register allocation and reload, gcc is told that it
+can do pretty much anything - but may only use the virtual registers.
+GCC cannot properly create the varying addressing modes that the RL78
+supports in an efficient way.
+
+Sometime after reload, the RL78 backend "devirtualizes" the RTL. It
+uses the "valloc" attribute in rl78-virt.md for determining the rules
+by which it will replace virtual registers with real registers (or
+not) and how to make up addressing modes. For example, insns tagged
+with "ro1" have a single read-only parameter, which may need to be
+moved from memory/constant/vreg to a suitable real register. As part
+of devirtualization, a flag is toggled, disabling the rl78-virt.md
+patterns and enabling the rl78-real.md patterns. The new patterns'
+constraints are used to determine the real registers used. NOTE:
+patterns in rl78-virt.md essentially ignore the constrains and rely on
+predicates, where the rl78-real.md ones essentially ignore the
+predicates and rely on the constraints.
+
+The devirtualization pass is scheduled via the pass manager (despite
+being called "rl78_reorg") so it can be scheduled prior to var-track
+(the idea is to let gdb know about the new registers). Ideally, it
+would be scheduled right after pro/epilogue generation, so the
+post-reload optimizers could operate on the real registers, but when I
+tried that there were some issues building the target libraries.
+
+During devirtualization, a simple register move optimizer is run. It
+would be better to run a full CSE/propogation pass on it though, but
+that has not yet been attempted.
+
+ */
+#define DEBUG_ALLOC 0
+
+#define OP(x) (*recog_data.operand_loc[x])
+
+/* This array is used to hold knowledge about the contents of the
+ real registers (A ... H), the memory-based registers (r8 ... r31)
+ and the first NUM_STACK_LOCS words on the stack. We use this to
+ avoid generating redundant move instructions.
+
+ A value in the range 0 .. 31 indicates register A .. r31.
+ A value in the range 32 .. 63 indicates stack slot (value - 32).
+ A value of NOT_KNOWN indicates that the contents of that location
+ are not known. */
+
+#define NUM_STACK_LOCS 32
+#define NOT_KNOWN 127
+
+static unsigned char content_memory [32 + NUM_STACK_LOCS];
+
+static unsigned char saved_update_index = NOT_KNOWN;
+static unsigned char saved_update_value;
+static enum machine_mode saved_update_mode;
+
+
+static inline void
+clear_content_memory (void)
+{
+ memset (content_memory, NOT_KNOWN, sizeof content_memory);
+ if (dump_file)
+ fprintf (dump_file, " clear content memory\n");
+ saved_update_index = NOT_KNOWN;
+}
+
+/* Convert LOC into an index into the content_memory array.
+ If LOC cannot be converted, return NOT_KNOWN. */
+
+static unsigned char
+get_content_index (rtx loc)
+{
+ enum machine_mode mode;
+
+ if (loc == NULL_RTX)
+ return NOT_KNOWN;
+
+ if (REG_P (loc))
+ {
+ if (REGNO (loc) < 32)
+ return REGNO (loc);
+ return NOT_KNOWN;
+ }
+
+ mode = GET_MODE (loc);
+
+ if (! rl78_stack_based_mem (loc, mode))
+ return NOT_KNOWN;
+
+ loc = XEXP (loc, 0);
+
+ if (REG_P (loc))
+ /* loc = MEM (SP) */
+ return 32;
+
+ /* loc = MEM (PLUS (SP, INT)). */
+ loc = XEXP (loc, 1);
+
+ if (INTVAL (loc) < NUM_STACK_LOCS)
+ return 32 + INTVAL (loc);
+
+ return NOT_KNOWN;
+}
+
+/* Return a string describing content INDEX in mode MODE.
+ WARNING: Can return a pointer to a static buffer. */
+static const char *
+get_content_name (unsigned char index, enum machine_mode mode)
+{
+ static char buffer [128];
+
+ if (index == NOT_KNOWN)
+ return "Unknown";
+
+ if (index > 31)
+ sprintf (buffer, "stack slot %d", index - 32);
+ else if (mode == HImode)
+ sprintf (buffer, "%s%s",
+ reg_names [index + 1], reg_names [index]);
+ else
+ return reg_names [index];
+
+ return buffer;
+}
+
+#if DEBUG_ALLOC
+
+static void
+display_content_memory (FILE * file)
+{
+ unsigned int i;
+
+ fprintf (file, " Known memory contents:\n");
+
+ for (i = 0; i < sizeof content_memory; i++)
+ if (content_memory[i] != NOT_KNOWN)
+ {
+ fprintf (file, " %s contains a copy of ", get_content_name (i, QImode));
+ fprintf (file, "%s\n", get_content_name (content_memory [i], QImode));
+ }
+}
+#endif
+
+static void
+update_content (unsigned char index, unsigned char val, enum machine_mode mode)
+{
+ unsigned int i;
+
+ gcc_assert (index < sizeof content_memory);
+
+ content_memory [index] = val;
+ if (val != NOT_KNOWN)
+ content_memory [val] = index;
+
+ /* Make the entry in dump_file *before* VAL is increased below. */
+ if (dump_file)
+ {
+ fprintf (dump_file, " %s now contains ", get_content_name (index, mode));
+ if (val == NOT_KNOWN)
+ fprintf (dump_file, "Unknown\n");
+ else
+ fprintf (dump_file, "%s and vice versa\n", get_content_name (val, mode));
+ }
+
+ if (mode == HImode)
+ {
+ val = val == NOT_KNOWN ? val : val + 1;
+
+ content_memory [index + 1] = val;
+ if (val != NOT_KNOWN)
+ {
+ content_memory [val] = index + 1;
+ -- val;
+ }
+ }
+
+ /* Any other places that had INDEX recorded as their contents are now invalid. */
+ for (i = 0; i < sizeof content_memory; i++)
+ {
+ if (i == index
+ || (val != NOT_KNOWN && i == val))
+ {
+ if (mode == HImode)
+ ++ i;
+ continue;
+ }
+
+ if (content_memory[i] == index
+ || (val != NOT_KNOWN && content_memory[i] == val))
+ {
+ content_memory[i] = NOT_KNOWN;
+
+ if (dump_file)
+ fprintf (dump_file, " %s cleared\n", get_content_name (i, mode));
+
+ if (mode == HImode)
+ content_memory[++ i] = NOT_KNOWN;
+ }
+ }
+}
+
+/* Record that LOC contains VALUE.
+ For HImode locations record that LOC+1 contains VALUE+1.
+ If LOC is not a register or stack slot, do nothing.
+ If VALUE is not a register or stack slot, clear the recorded content. */
+
+static void
+record_content (rtx loc, rtx value)
+{
+ enum machine_mode mode;
+ unsigned char index;
+ unsigned char val;
+
+ if ((index = get_content_index (loc)) == NOT_KNOWN)
+ return;
+
+ val = get_content_index (value);
+
+ mode = GET_MODE (loc);
+
+ if (val == index)
+ {
+ if (! optimize)
+ return;
+
+ /* This should not happen when optimizing. */
+#if 1
+ fprintf (stderr, "ASSIGNMENT of location to itself detected! [%s]\n",
+ get_content_name (val, mode));
+ return;
+#else
+ gcc_unreachable ();
+#endif
+ }
+
+ update_content (index, val, mode);
+}
+
+/* Returns TRUE if LOC already contains a copy of VALUE. */
+
+static bool
+already_contains (rtx loc, rtx value)
+{
+ unsigned char index;
+ unsigned char val;
+
+ if ((index = get_content_index (loc)) == NOT_KNOWN)
+ return false;
+
+ if ((val = get_content_index (value)) == NOT_KNOWN)
+ return false;
+
+ if (content_memory [index] != val)
+ return false;
+
+ if (GET_MODE (loc) == HImode)
+ return content_memory [index + 1] == val + 1;
+
+ return true;
+}
+
+bool
+rl78_es_addr (rtx addr)
+{
+ if (GET_CODE (addr) == MEM)
+ addr = XEXP (addr, 0);
+ if (GET_CODE (addr) != UNSPEC)
+ return false;
+ if (XINT (addr, 1) != UNS_ES_ADDR)
+ return false;
+ return true;
+}
+
+rtx
+rl78_es_base (rtx addr)
+{
+ if (GET_CODE (addr) == MEM)
+ addr = XEXP (addr, 0);
+ addr = XVECEXP (addr, 0, 1);
+ if (GET_CODE (addr) == CONST
+ && GET_CODE (XEXP (addr, 0)) == ZERO_EXTRACT)
+ addr = XEXP (XEXP (addr, 0), 0);
+ /* Mode doesn't matter here. */
+ return gen_rtx_MEM (HImode, addr);
+}
+
+/* Rescans an insn to see if it's recognized again. This is done
+ carefully to ensure that all the constraint information is accurate
+ for the newly matched insn. */
+static bool
+insn_ok_now (rtx insn)
+{
+ rtx pattern = PATTERN (insn);
+ int i;
+
+ INSN_CODE (insn) = -1;
+
+ if (recog (pattern, insn, 0) > -1)
+ {
+ extract_insn (insn);
+ if (constrain_operands (1))
+ {
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[32m");
+ debug_rtx (insn);
+ fprintf (stderr, "\033[0m");
+#endif
+ if (SET_P (pattern))
+ record_content (SET_DEST (pattern), SET_SRC (pattern));
+
+ /* We need to detect far addresses that haven't been
+ converted to es/lo16 format. */
+ for (i=0; i<recog_data.n_operands; i++)
+ if (GET_CODE (OP (i)) == MEM
+ && GET_MODE (XEXP (OP (i), 0)) == SImode
+ && GET_CODE (XEXP (OP (i), 0)) != UNSPEC)
+ return false;
+
+ return true;
+ }
+ }
+ else
+ {
+ /* We need to re-recog the insn with virtual registers to get
+ the operands. */
+ cfun->machine->virt_insns_ok = 1;
+ if (recog (pattern, insn, 0) > -1)
+ {
+ extract_insn (insn);
+ if (constrain_operands (0))
+ {
+ cfun->machine->virt_insns_ok = 0;
+ return false;
+ }
+ }
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[41;30m Unrecognized *virtual* insn \033[0m\n");
+ debug_rtx (insn);
+#endif
+ gcc_unreachable ();
+ }
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[31m");
+ debug_rtx (insn);
+ fprintf (stderr, "\033[0m");
+#endif
+ return false;
+}
+
+#if DEBUG_ALLOC
+#define WORKED fprintf (stderr, "\033[48;5;22m Worked at line %d \033[0m\n", __LINE__)
+#define FAILEDSOFAR fprintf (stderr, "\033[48;5;52m FAILED at line %d \033[0m\n", __LINE__)
+#define FAILED fprintf (stderr, "\033[48;5;52m FAILED at line %d \033[0m\n", __LINE__), gcc_unreachable()
+#define MAYBE_OK(insn) if (insn_ok_now (insn)) { WORKED; return; } else { FAILEDSOFAR; }
+#define MUST_BE_OK(insn) if (insn_ok_now (insn)) { WORKED; return; } FAILED
+#else
+#define FAILED gcc_unreachable ()
+#define MAYBE_OK(insn) if (insn_ok_now (insn)) return;
+#define MUST_BE_OK(insn) if (insn_ok_now (insn)) return; FAILED
+#endif
+
+/* Registers into which we move the contents of virtual registers. */
+#define X gen_rtx_REG (QImode, X_REG)
+#define A gen_rtx_REG (QImode, A_REG)
+#define C gen_rtx_REG (QImode, C_REG)
+#define B gen_rtx_REG (QImode, B_REG)
+#define E gen_rtx_REG (QImode, E_REG)
+#define D gen_rtx_REG (QImode, D_REG)
+#define L gen_rtx_REG (QImode, L_REG)
+#define H gen_rtx_REG (QImode, H_REG)
+
+#define AX gen_rtx_REG (HImode, AX_REG)
+#define BC gen_rtx_REG (HImode, BC_REG)
+#define DE gen_rtx_REG (HImode, DE_REG)
+#define HL gen_rtx_REG (HImode, HL_REG)
+
+/* Returns TRUE if R is a virtual register. */
+static inline bool
+is_virtual_register (rtx r)
+{
+ return (GET_CODE (r) == REG
+ && REGNO (r) >= 8
+ && REGNO (r) < 32);
+}
+
+/* In all these alloc routines, we expect the following: the insn
+ pattern is unshared, the insn was previously recognized and failed
+ due to predicates or constraints, and the operand data is in
+ recog_data. */
+
+static int virt_insn_was_frame;
+
+/* Hook for all insns we emit. Re-mark them as FRAME_RELATED if
+ needed. */
+static rtx
+EM2 (int line ATTRIBUTE_UNUSED, rtx r)
+{
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[36m%d: ", line);
+ debug_rtx (r);
+ fprintf (stderr, "\033[0m");
+#endif
+ /*SCHED_GROUP_P (r) = 1;*/
+ if (virt_insn_was_frame)
+ RTX_FRAME_RELATED_P (r) = 1;
+ return r;
+}
+
+#define EM(x) EM2 (__LINE__, x)
+
+/* Return a suitable RTX for the low half of a __far address. */
+static rtx
+rl78_lo16 (rtx addr)
+{
+ rtx r;
+
+ if (GET_CODE (addr) == SYMBOL_REF
+ || GET_CODE (addr) == CONST)
+ {
+ r = gen_rtx_ZERO_EXTRACT (HImode, addr, GEN_INT (16), GEN_INT (0));
+ r = gen_rtx_CONST (HImode, r);
+ }
+ else
+ r = rl78_subreg (HImode, addr, SImode, 0);
+
+ r = gen_es_addr (r);
+
+ return r;
+}
+
+/* Return a suitable RTX for the high half's lower byte of a __far address. */
+static rtx
+rl78_hi8 (rtx addr)
+{
+ if (GET_CODE (addr) == SYMBOL_REF
+ || GET_CODE (addr) == CONST)
+ {
+ rtx r = gen_rtx_ZERO_EXTRACT (QImode, addr, GEN_INT (8), GEN_INT (16));
+ r = gen_rtx_CONST (QImode, r);
+ return r;
+ }
+ return rl78_subreg (QImode, addr, SImode, 2);
+}
+
+static void
+add_postponed_content_update (rtx to, rtx value)
+{
+ unsigned char index;
+
+ if ((index = get_content_index (to)) == NOT_KNOWN)
+ return;
+
+ gcc_assert (saved_update_index == NOT_KNOWN);
+ saved_update_index = index;
+ saved_update_value = get_content_index (value);
+ saved_update_mode = GET_MODE (to);
+}
+
+static void
+process_postponed_content_update (void)
+{
+ if (saved_update_index != NOT_KNOWN)
+ {
+ update_content (saved_update_index, saved_update_value, saved_update_mode);
+ saved_update_index = NOT_KNOWN;
+ }
+}
+
+/* Generate and emit a move of (register) FROM into TO. if WHERE is not NULL
+ then if BEFORE is true then emit the insn before WHERE, otherwise emit it
+ after WHERE. If TO already contains FROM then do nothing. Returns TO if
+ BEFORE is true, FROM otherwise. */
+static rtx
+gen_and_emit_move (rtx to, rtx from, rtx where, bool before)
+{
+ enum machine_mode mode = GET_MODE (to);
+
+ if (optimize && before && already_contains (to, from))
+ {
+#if DEBUG_ALLOC
+ display_content_memory (stderr);
+#endif
+ if (dump_file)
+ {
+ fprintf (dump_file, " Omit move of %s into ",
+ get_content_name (get_content_index (from), mode));
+ fprintf (dump_file, "%s as it already contains this value\n",
+ get_content_name (get_content_index (to), mode));
+ }
+ }
+ else
+ {
+ rtx move = mode == QImode ? gen_movqi (to, from) : gen_movhi (to, from);
+
+ EM (move);
+
+ if (where == NULL_RTX)
+ emit_insn (move);
+ else if (before)
+ emit_insn_before (move, where);
+ else
+ {
+ rtx note = find_reg_note (where, REG_EH_REGION, NULL_RTX);
+
+ /* If necessary move REG_EH_REGION notes forward.
+ cf. compiling gcc.dg/pr44545.c. */
+ if (note != NULL_RTX)
+ {
+ add_reg_note (move, REG_EH_REGION, XEXP (note, 0));
+ remove_note (where, note);
+ }
+
+ emit_insn_after (move, where);
+ }
+
+ if (before)
+ record_content (to, from);
+ else
+ add_postponed_content_update (to, from);
+ }
+
+ return before ? to : from;
+}
+
+/* If M is MEM(REG) or MEM(PLUS(REG,INT)) and REG is virtual then
+ copy it into NEWBASE and return the updated MEM. Otherwise just
+ return M. Any needed insns are emitted before BEFORE. */
+static rtx
+transcode_memory_rtx (rtx m, rtx newbase, rtx before)
+{
+ rtx base, index, addendr;
+ int addend = 0;
+ int need_es = 0;
+
+ if (! MEM_P (m))
+ return m;
+
+ if (GET_MODE (XEXP (m, 0)) == SImode)
+ {
+ rtx new_m;
+ rtx seg = rl78_hi8 (XEXP (m, 0));
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "setting ES:\n");
+ debug_rtx(seg);
+#endif
+ emit_insn_before (EM (gen_movqi (A, seg)), before);
+ emit_insn_before (EM (gen_movqi_es (A)), before);
+ record_content (A, NULL_RTX);
+
+ new_m = gen_rtx_MEM (GET_MODE (m), rl78_lo16 (XEXP (m, 0)));
+ MEM_COPY_ATTRIBUTES (new_m, m);
+ m = new_m;
+ need_es = 1;
+ }
+
+ characterize_address (XEXP (m, 0), & base, & index, & addendr);
+ gcc_assert (index == NULL_RTX);
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[33m"); debug_rtx (m); fprintf (stderr, "\033[0m");
+ debug_rtx (base);
+#endif
+ if (base == NULL_RTX)
+ return m;
+
+ if (addendr && GET_CODE (addendr) == CONST_INT)
+ addend = INTVAL (addendr);
+
+ gcc_assert (REG_P (base));
+ gcc_assert (REG_P (newbase));
+
+ if (REGNO (base) == SP_REG)
+ {
+ if (addend >= 0 && addend <= 255)
+ return m;
+ }
+
+ /* BASE should be a virtual register. We copy it to NEWBASE. If
+ the addend is out of range for DE/HL, we use AX to compute the full
+ address. */
+
+ if (addend < 0
+ || (addend > 255 && REGNO (newbase) != 2)
+ || (addendr && GET_CODE (addendr) != CONST_INT))
+ {
+ /* mov ax, vreg
+ add ax, #imm
+ mov hl, ax */
+ EM (emit_insn_before (gen_movhi (AX, base), before));
+ EM (emit_insn_before (gen_addhi3 (AX, AX, addendr), before));
+ EM (emit_insn_before (gen_movhi (newbase, AX), before));
+ record_content (AX, NULL_RTX);
+ record_content (newbase, NULL_RTX);
+
+ base = newbase;
+ addend = 0;
+ }
+ else
+ {
+ base = gen_and_emit_move (newbase, base, before, true);
+ }
+
+ if (addend)
+ {
+ record_content (base, NULL_RTX);
+ base = gen_rtx_PLUS (HImode, base, GEN_INT (addend));
+ }
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[33m");
+ debug_rtx (m);
+#endif
+ if (need_es)
+ m = change_address (m, GET_MODE (m), gen_es_addr (base));
+ else
+ m = change_address (m, GET_MODE (m), base);
+#if DEBUG_ALLOC
+ debug_rtx (m);
+ fprintf (stderr, "\033[0m");
+#endif
+ return m;
+}
+
+/* Copy SRC to accumulator (A or AX), placing any generated insns
+ before BEFORE. Returns accumulator RTX. */
+static rtx
+move_to_acc (int opno, rtx before)
+{
+ rtx src = OP (opno);
+ enum machine_mode mode = GET_MODE (src);
+
+ if (REG_P (src) && REGNO (src) < 2)
+ return src;
+
+ if (mode == VOIDmode)
+ mode = recog_data.operand_mode[opno];
+
+ return gen_and_emit_move (mode == QImode ? A : AX, src, before, true);
+}
+
+static void
+force_into_acc (rtx src, rtx before)
+{
+ enum machine_mode mode = GET_MODE (src);
+ rtx move;
+
+ if (REG_P (src) && REGNO (src) < 2)
+ return;
+
+ move = mode == QImode ? gen_movqi (A, src) : gen_movhi (AX, src);
+
+ EM (move);
+
+ emit_insn_before (move, before);
+ record_content (AX, NULL_RTX);
+}
+
+/* Copy accumulator (A or AX) to DEST, placing any generated insns
+ after AFTER. Returns accumulator RTX. */
+static rtx
+move_from_acc (unsigned int opno, rtx after)
+{
+ rtx dest = OP (opno);
+ enum machine_mode mode = GET_MODE (dest);
+
+ if (REG_P (dest) && REGNO (dest) < 2)
+ return dest;
+
+ return gen_and_emit_move (dest, mode == QImode ? A : AX, after, false);
+}
+
+/* Copy accumulator (A or AX) to REGNO, placing any generated insns
+ before BEFORE. Returns reg RTX. */
+static rtx
+move_acc_to_reg (rtx acc, int regno, rtx before)
+{
+ enum machine_mode mode = GET_MODE (acc);
+ rtx reg;
+
+ reg = gen_rtx_REG (mode, regno);
+
+ return gen_and_emit_move (reg, acc, before, true);
+}
+
+/* Copy SRC to X, placing any generated insns before BEFORE.
+ Returns X RTX. */
+static rtx
+move_to_x (int opno, rtx before)
+{
+ rtx src = OP (opno);
+ enum machine_mode mode = GET_MODE (src);
+ rtx reg;
+
+ if (mode == VOIDmode)
+ mode = recog_data.operand_mode[opno];
+ reg = (mode == QImode) ? X : AX;
+
+ if (mode == QImode || ! is_virtual_register (OP (opno)))
+ {
+ OP (opno) = move_to_acc (opno, before);
+ OP (opno) = move_acc_to_reg (OP (opno), X_REG, before);
+ return reg;
+ }
+
+ return gen_and_emit_move (reg, src, before, true);
+}
+
+/* Copy OP (opno) to H or HL, placing any generated insns before BEFORE.
+ Returns H/HL RTX. */
+static rtx
+move_to_hl (int opno, rtx before)
+{
+ rtx src = OP (opno);
+ enum machine_mode mode = GET_MODE (src);
+ rtx reg;
+
+ if (mode == VOIDmode)
+ mode = recog_data.operand_mode[opno];
+ reg = (mode == QImode) ? L : HL;
+
+ if (mode == QImode || ! is_virtual_register (OP (opno)))
+ {
+ OP (opno) = move_to_acc (opno, before);
+ OP (opno) = move_acc_to_reg (OP (opno), L_REG, before);
+ return reg;
+ }
+
+ return gen_and_emit_move (reg, src, before, true);
+}
+
+/* Copy OP (opno) to E or DE, placing any generated insns before BEFORE.
+ Returns E/DE RTX. */
+static rtx
+move_to_de (int opno, rtx before)
+{
+ rtx src = OP (opno);
+ enum machine_mode mode = GET_MODE (src);
+ rtx reg;
+
+ if (mode == VOIDmode)
+ mode = recog_data.operand_mode[opno];
+
+ reg = (mode == QImode) ? E : DE;
+
+ if (mode == QImode || ! is_virtual_register (OP (opno)))
+ {
+ OP (opno) = move_to_acc (opno, before);
+ OP (opno) = move_acc_to_reg (OP (opno), E_REG, before);
+ }
+ else
+ {
+ gen_and_emit_move (reg, src, before, true);
+ }
+
+ return reg;
+}
+
+/* Devirtualize an insn of the form (SET (op) (unop (op))). */
+static void
+rl78_alloc_physical_registers_op1 (rtx insn)
+{
+ /* op[0] = func op[1] */
+
+ /* We first try using A as the destination, then copying it
+ back. */
+ if (rtx_equal_p (OP (0), OP (1)))
+ {
+ OP (0) =
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ }
+ else
+ {
+ /* If necessary, load the operands into BC and HL.
+ Check to see if we already have OP (0) in HL
+ and if so, swap the order. */
+ if (MEM_P (OP (0))
+ && already_contains (HL, XEXP (OP (0), 0)))
+ {
+ OP (0) = transcode_memory_rtx (OP (0), HL, insn);
+ OP (1) = transcode_memory_rtx (OP (1), BC, insn);
+ }
+ else
+ {
+ OP (0) = transcode_memory_rtx (OP (0), BC, insn);
+ OP (1) = transcode_memory_rtx (OP (1), HL, insn);
+ }
+ }
+
+ MAYBE_OK (insn);
+
+ OP (0) = move_from_acc (0, insn);
+
+ MAYBE_OK (insn);
+
+ /* Try copying the src to acc first, then. This is for, for
+ example, ZERO_EXTEND or NOT. */
+ OP (1) = move_to_acc (1, insn);
+
+ MUST_BE_OK (insn);
+}
+
+/* Returns true if operand OPNUM contains a constraint of type CONSTRAINT.
+ Assumes that the current insn has already been recognised and hence the
+ constraint data has been filled in. */
+static bool
+has_constraint (unsigned int opnum, enum constraint_num constraint)
+{
+ const char * p = recog_data.constraints[opnum];
+
+ /* No constraints means anything is accepted. */
+ if (p == NULL || *p == 0 || *p == ',')
+ return true;
+
+ do
+ {
+ char c;
+ unsigned int len;
+
+ c = *p;
+ len = CONSTRAINT_LEN (c, p);
+ gcc_assert (len > 0);
+
+ switch (c)
+ {
+ case 0:
+ case ',':
+ return false;
+ default:
+ if (lookup_constraint (p) == constraint)
+ return true;
+ }
+ p += len;
+ }
+ while (1);
+}
+
+/* Devirtualize an insn of the form (SET (op) (binop (op) (op))). */
+static void
+rl78_alloc_physical_registers_op2 (rtx insn)
+{
+ rtx prev;
+ rtx first;
+ bool hl_used;
+ int tmp_id;
+ rtx saved_op1;
+
+ if (rtx_equal_p (OP (0), OP (1)))
+ {
+ OP (0) =
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ OP (2) = transcode_memory_rtx (OP (2), HL, insn);
+ }
+ else if (rtx_equal_p (OP (0), OP (2)))
+ {
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ OP (0) =
+ OP (2) = transcode_memory_rtx (OP (2), HL, insn);
+ }
+ else
+ {
+ OP (0) = transcode_memory_rtx (OP (0), BC, insn);
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ OP (2) = transcode_memory_rtx (OP (2), HL, insn);
+ }
+
+ MAYBE_OK (insn);
+
+ prev = prev_nonnote_nondebug_insn (insn);
+ if (recog_data.constraints[1][0] == '%'
+ && is_virtual_register (OP (1))
+ && ! is_virtual_register (OP (2))
+ && ! CONSTANT_P (OP (2)))
+ {
+ rtx tmp = OP (1);
+ OP (1) = OP (2);
+ OP (2) = tmp;
+ }
+
+ /* Make a note of whether (H)L is being used. It matters
+ because if OP (2) also needs reloading, then we must take
+ care not to corrupt HL. */
+ hl_used = reg_mentioned_p (L, OP (0)) || reg_mentioned_p (L, OP (1));
+
+ /* If HL is not currently being used and dest == op1 then there are
+ some possible optimizations available by reloading one of the
+ operands into HL, before trying to use the accumulator. */
+ if (optimize
+ && ! hl_used
+ && rtx_equal_p (OP (0), OP (1)))
+ {
+ /* If op0 is a Ws1 type memory address then switching the base
+ address register to HL might allow us to perform an in-memory
+ operation. (eg for the INCW instruction).
+
+ FIXME: Adding the move into HL is costly if this optimization is not
+ going to work, so for now, make sure that we know that the new insn will
+ match the requirements of the addhi3_real pattern. Really we ought to
+ generate a candidate sequence, test that, and then install it if the
+ results are good. */
+ if (satisfies_constraint_Ws1 (OP (0))
+ && has_constraint (0, CONSTRAINT_Wh1)
+ && (satisfies_constraint_K (OP (2)) || satisfies_constraint_L (OP (2))))
+ {
+ rtx base, index, addend, newbase;
+
+ characterize_address (XEXP (OP (0), 0), & base, & index, & addend);
+ gcc_assert (index == NULL_RTX);
+ gcc_assert (REG_P (base) && REGNO (base) == SP_REG);
+
+ /* Ws1 addressing allows an offset of 0, Wh1 addressing requires a non-zero offset. */
+ if (addend != NULL_RTX)
+ {
+ newbase = gen_and_emit_move (HL, base, insn, true);
+ record_content (newbase, NULL_RTX);
+ newbase = gen_rtx_PLUS (HImode, newbase, addend);
+
+ OP (0) = OP (1) = change_address (OP (0), VOIDmode, newbase);
+
+ /* We do not want to fail here as this means that
+ we have inserted useless insns into the stream. */
+ MUST_BE_OK (insn);
+ }
+ }
+ else if (REG_P (OP (0))
+ && satisfies_constraint_Ws1 (OP (2))
+ && has_constraint (2, CONSTRAINT_Wh1))
+ {
+ rtx base, index, addend, newbase;
+
+ characterize_address (XEXP (OP (2), 0), & base, & index, & addend);
+ gcc_assert (index == NULL_RTX);
+ gcc_assert (REG_P (base) && REGNO (base) == SP_REG);
+
+ /* Ws1 addressing allows an offset of 0, Wh1 addressing requires a non-zero offset. */
+ if (addend != NULL_RTX)
+ {
+ gen_and_emit_move (HL, base, insn, true);
+
+ if (REGNO (OP (0)) != X_REG)
+ {
+ OP (1) = move_to_acc (1, insn);
+ OP (0) = move_from_acc (0, insn);
+ }
+
+ record_content (HL, NULL_RTX);
+ newbase = gen_rtx_PLUS (HImode, HL, addend);
+
+ OP (2) = change_address (OP (2), VOIDmode, newbase);
+
+ /* We do not want to fail here as this means that
+ we have inserted useless insns into the stream. */
+ MUST_BE_OK (insn);
+ }
+ }
+ }
+
+ OP (0) = move_from_acc (0, insn);
+
+ tmp_id = get_max_insn_count ();
+ saved_op1 = OP (1);
+
+ if (rtx_equal_p (OP (1), OP (2)))
+ OP (2) = OP (1) = move_to_acc (1, insn);
+ else
+ OP (1) = move_to_acc (1, insn);
+
+ MAYBE_OK (insn);
+
+ /* If we omitted the move of OP1 into the accumulator (because
+ it was already there from a previous insn), then force the
+ generation of the move instruction now. We know that we
+ are about to emit a move into HL (or DE) via AX, and hence
+ our optimization to remove the load of OP1 is no longer valid. */
+ if (tmp_id == get_max_insn_count ())
+ force_into_acc (saved_op1, insn);
+
+ /* We have to copy op2 to HL (or DE), but that involves AX, which
+ already has a live value. Emit it before those insns. */
+
+ if (prev)
+ first = next_nonnote_nondebug_insn (prev);
+ else
+ for (first = insn; prev_nonnote_nondebug_insn (first); first = prev_nonnote_nondebug_insn (first))
+ ;
+
+ OP (2) = hl_used ? move_to_de (2, first) : move_to_hl (2, first);
+
+ MUST_BE_OK (insn);
+}
+
+/* Devirtualize an insn of the form SET (PC) (MEM/REG). */
+static void
+rl78_alloc_physical_registers_ro1 (rtx insn)
+{
+ OP (0) = transcode_memory_rtx (OP (0), BC, insn);
+
+ MAYBE_OK (insn);
+
+ OP (0) = move_to_acc (0, insn);
+
+ MUST_BE_OK (insn);
+}
+
+/* Devirtualize a compare insn. */
+static void
+rl78_alloc_physical_registers_cmp (rtx insn)
+{
+ int tmp_id;
+ rtx saved_op1;
+ rtx prev = prev_nonnote_nondebug_insn (insn);
+ rtx first;
+
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ OP (2) = transcode_memory_rtx (OP (2), HL, insn);
+
+ /* HI compares have to have OP (1) in AX, but QI
+ compares do not, so it is worth checking here. */
+ MAYBE_OK (insn);
+
+ /* For an HImode compare, OP (1) must always be in AX.
+ But if OP (1) is a REG (and not AX), then we can avoid
+ a reload of OP (1) if we reload OP (2) into AX and invert
+ the comparison. */
+ if (REG_P (OP (1))
+ && REGNO (OP (1)) != AX_REG
+ && GET_MODE (OP (1)) == HImode
+ && MEM_P (OP (2)))
+ {
+ rtx cmp = XEXP (SET_SRC (PATTERN (insn)), 0);
+
+ OP (2) = move_to_acc (2, insn);
+
+ switch (GET_CODE (cmp))
+ {
+ case EQ:
+ case NE:
+ break;
+ case LTU: cmp = gen_rtx_GTU (HImode, OP (2), OP (1)); break;
+ case GTU: cmp = gen_rtx_LTU (HImode, OP (2), OP (1)); break;
+ case LEU: cmp = gen_rtx_GEU (HImode, OP (2), OP (1)); break;
+ case GEU: cmp = gen_rtx_LEU (HImode, OP (2), OP (1)); break;
+
+ case LT:
+ case GT:
+ case LE:
+ case GE:
+#if DEBUG_ALLOC
+ debug_rtx (insn);
+#endif
+ default:
+ gcc_unreachable ();
+ }
+
+ if (GET_CODE (cmp) == EQ || GET_CODE (cmp) == NE)
+ PATTERN (insn) = gen_cbranchhi4_real (cmp, OP (2), OP (1), OP (3));
+ else
+ PATTERN (insn) = gen_cbranchhi4_real_inverted (cmp, OP (2), OP (1), OP (3));
+
+ MUST_BE_OK (insn);
+ }
+
+ /* Surprisingly, gcc can generate a comparison of a register with itself, but this
+ should be handled by the second alternative of the cbranchhi_real pattern. */
+ if (rtx_equal_p (OP (1), OP (2)))
+ {
+ OP (1) = OP (2) = BC;
+ MUST_BE_OK (insn);
+ }
+
+ tmp_id = get_max_insn_count ();
+ saved_op1 = OP (1);
+
+ OP (1) = move_to_acc (1, insn);
+
+ MAYBE_OK (insn);
+
+ /* If we omitted the move of OP1 into the accumulator (because
+ it was already there from a previous insn), then force the
+ generation of the move instruction now. We know that we
+ are about to emit a move into HL via AX, and hence our
+ optimization to remove the load of OP1 is no longer valid. */
+ if (tmp_id == get_max_insn_count ())
+ force_into_acc (saved_op1, insn);
+
+ /* We have to copy op2 to HL, but that involves the acc, which
+ already has a live value. Emit it before those insns. */
+ if (prev)
+ first = next_nonnote_nondebug_insn (prev);
+ else
+ for (first = insn; prev_nonnote_nondebug_insn (first); first = prev_nonnote_nondebug_insn (first))
+ ;
+ OP (2) = move_to_hl (2, first);
+
+ MUST_BE_OK (insn);
+}
+
+/* Like op2, but AX = A * X. */
+static void
+rl78_alloc_physical_registers_umul (rtx insn)
+{
+ rtx prev = prev_nonnote_nondebug_insn (insn);
+ rtx first;
+ int tmp_id;
+ rtx saved_op1;
+
+ OP (0) = transcode_memory_rtx (OP (0), BC, insn);
+ OP (1) = transcode_memory_rtx (OP (1), DE, insn);
+ OP (2) = transcode_memory_rtx (OP (2), HL, insn);
+
+ MAYBE_OK (insn);
+
+ if (recog_data.constraints[1][0] == '%'
+ && is_virtual_register (OP (1))
+ && !is_virtual_register (OP (2))
+ && !CONSTANT_P (OP (2)))
+ {
+ rtx tmp = OP (1);
+ OP (1) = OP (2);
+ OP (2) = tmp;
+ }
+
+ OP (0) = move_from_acc (0, insn);
+
+ tmp_id = get_max_insn_count ();
+ saved_op1 = OP (1);
+
+ if (rtx_equal_p (OP (1), OP (2)))
+ {
+ gcc_assert (GET_MODE (OP (2)) == QImode);
+ /* The MULU instruction does not support duplicate arguments
+ but we know that if we copy OP (2) to X it will do so via
+ A and thus OP (1) will already be loaded into A. */
+ OP (2) = move_to_x (2, insn);
+ OP (1) = A;
+ }
+ else
+ OP (1) = move_to_acc (1, insn);
+
+ MAYBE_OK (insn);
+
+ /* If we omitted the move of OP1 into the accumulator (because
+ it was already there from a previous insn), then force the
+ generation of the move instruction now. We know that we
+ are about to emit a move into HL (or DE) via AX, and hence
+ our optimization to remove the load of OP1 is no longer valid. */
+ if (tmp_id == get_max_insn_count ())
+ force_into_acc (saved_op1, insn);
+
+ /* We have to copy op2 to X, but that involves the acc, which
+ already has a live value. Emit it before those insns. */
+
+ if (prev)
+ first = next_nonnote_nondebug_insn (prev);
+ else
+ for (first = insn; prev_nonnote_nondebug_insn (first); first = prev_nonnote_nondebug_insn (first))
+ ;
+ OP (2) = move_to_x (2, first);
+
+ MUST_BE_OK (insn);
+}
+
+static void
+rl78_alloc_address_registers_macax (rtx insn)
+{
+ int which, op;
+ bool replace_in_op0 = false;
+ bool replace_in_op1 = false;
+
+ MAYBE_OK (insn);
+
+ /* Two different MEMs are not allowed. */
+ which = 0;
+ for (op = 2; op >= 0; op --)
+ {
+ if (MEM_P (OP (op)))
+ {
+ if (op == 0 && replace_in_op0)
+ continue;
+ if (op == 1 && replace_in_op1)
+ continue;
+
+ switch (which)
+ {
+ case 0:
+ /* If we replace a MEM, make sure that we replace it for all
+ occurrences of the same MEM in the insn. */
+ replace_in_op0 = (op > 0 && rtx_equal_p (OP (op), OP (0)));
+ replace_in_op1 = (op > 1 && rtx_equal_p (OP (op), OP (1)));
+
+ OP (op) = transcode_memory_rtx (OP (op), HL, insn);
+ if (op == 2
+ && MEM_P (OP (op))
+ && ((GET_CODE (XEXP (OP (op), 0)) == REG
+ && REGNO (XEXP (OP (op), 0)) == SP_REG)
+ || (GET_CODE (XEXP (OP (op), 0)) == PLUS
+ && REGNO (XEXP (XEXP (OP (op), 0), 0)) == SP_REG)))
+ {
+ emit_insn_before (gen_movhi (HL, gen_rtx_REG (HImode, SP_REG)), insn);
+ OP (op) = replace_rtx (OP (op), gen_rtx_REG (HImode, SP_REG), HL);
+ }
+ if (replace_in_op0)
+ OP (0) = OP (op);
+ if (replace_in_op1)
+ OP (1) = OP (op);
+ break;
+ case 1:
+ OP (op) = transcode_memory_rtx (OP (op), DE, insn);
+ break;
+ case 2:
+ OP (op) = transcode_memory_rtx (OP (op), BC, insn);
+ break;
+ }
+ which ++;
+ }
+ }
+
+ MUST_BE_OK (insn);
+}
+
+/* Scan all insns and devirtualize them. */
+static void
+rl78_alloc_physical_registers (void)
+{
+ /* During most of the compile, gcc is dealing with virtual
+ registers. At this point, we need to assign physical registers
+ to the vitual ones, and copy in/out as needed. */
+
+ rtx insn, curr;
+ enum attr_valloc valloc_method;
+
+ for (insn = get_insns (); insn; insn = curr)
+ {
+ int i;
+
+ curr = next_nonnote_nondebug_insn (insn);
+
+ if (INSN_P (insn)
+ && (GET_CODE (PATTERN (insn)) == SET
+ || GET_CODE (PATTERN (insn)) == CALL)
+ && INSN_CODE (insn) == -1)
+ {
+ if (GET_CODE (SET_SRC (PATTERN (insn))) == ASM_OPERANDS)
+ continue;
+ i = recog (PATTERN (insn), insn, 0);
+ if (i == -1)
+ {
+ debug_rtx (insn);
+ gcc_unreachable ();
+ }
+ INSN_CODE (insn) = i;
+ }
+ }
+
+ cfun->machine->virt_insns_ok = 0;
+ cfun->machine->real_insns_ok = 1;
+
+ clear_content_memory ();
+
+ for (insn = get_insns (); insn; insn = curr)
+ {
+ rtx pattern;
+
+ curr = insn ? next_nonnote_nondebug_insn (insn) : NULL;
+
+ if (!INSN_P (insn))
+ {
+ if (LABEL_P (insn))
+ clear_content_memory ();
+
+ continue;
+ }
+
+ if (dump_file)
+ fprintf (dump_file, "Converting insn %d\n", INSN_UID (insn));
+
+ pattern = PATTERN (insn);
+ if (GET_CODE (pattern) == PARALLEL)
+ pattern = XVECEXP (pattern, 0, 0);
+ if (JUMP_P (insn) || CALL_P (insn) || GET_CODE (pattern) == CALL)
+ clear_content_memory ();
+ if (GET_CODE (pattern) != SET
+ && GET_CODE (pattern) != CALL)
+ continue;
+ if (GET_CODE (pattern) == SET
+ && GET_CODE (SET_SRC (pattern)) == ASM_OPERANDS)
+ continue;
+
+ valloc_method = get_attr_valloc (insn);
+
+ PATTERN (insn) = copy_rtx_if_shared (PATTERN (insn));
+
+ if (valloc_method == VALLOC_MACAX)
+ {
+ record_content (AX, NULL_RTX);
+ record_content (BC, NULL_RTX);
+ record_content (DE, NULL_RTX);
+ }
+
+ if (insn_ok_now (insn))
+ continue;
+
+ INSN_CODE (insn) = -1;
+
+ if (RTX_FRAME_RELATED_P (insn))
+ virt_insn_was_frame = 1;
+ else
+ virt_insn_was_frame = 0;
+
+ switch (valloc_method)
+ {
+ case VALLOC_OP1:
+ rl78_alloc_physical_registers_op1 (insn);
+ break;
+ case VALLOC_OP2:
+ rl78_alloc_physical_registers_op2 (insn);
+ break;
+ case VALLOC_RO1:
+ rl78_alloc_physical_registers_ro1 (insn);
+ break;
+ case VALLOC_CMP:
+ rl78_alloc_physical_registers_cmp (insn);
+ break;
+ case VALLOC_UMUL:
+ rl78_alloc_physical_registers_umul (insn);
+ break;
+ case VALLOC_MACAX:
+ /* Macro that clobbers AX. */
+ rl78_alloc_address_registers_macax (insn);
+ record_content (AX, NULL_RTX);
+ record_content (BC, NULL_RTX);
+ record_content (DE, NULL_RTX);
+ break;
+ }
+
+ if (JUMP_P (insn) || CALL_P (insn) || GET_CODE (pattern) == CALL)
+ clear_content_memory ();
+ else
+ process_postponed_content_update ();
+ }
+
+#if DEBUG_ALLOC
+ fprintf (stderr, "\033[0m");
+#endif
+}
+
+/* Add REG_DEAD notes using DEAD[reg] for rtx S which is part of INSN.
+ This function scans for uses of registers; the last use (i.e. first
+ encounter when scanning backwards) triggers a REG_DEAD note if the
+ reg was previously in DEAD[]. */
+static void
+rl78_note_reg_uses (char *dead, rtx s, rtx insn)
+{
+ const char *fmt;
+ int i, r;
+ enum rtx_code code;
+
+ if (!s)
+ return;
+
+ code = GET_CODE (s);
+
+ switch (code)
+ {
+ /* Compare registers by number. */
+ case REG:
+ r = REGNO (s);
+ if (dump_file)
+ {
+ fprintf (dump_file, "note use reg %d size %d on insn %d\n",
+ r, GET_MODE_SIZE (GET_MODE (s)), INSN_UID (insn));
+ print_rtl_single (dump_file, s);
+ }
+ if (dead [r])
+ add_reg_note (insn, REG_DEAD, gen_rtx_REG (GET_MODE (s), r));
+ for (i = 0; i < GET_MODE_SIZE (GET_MODE (s)); i ++)
+ dead [r + i] = 0;
+ return;
+
+ /* These codes have no constituent expressions
+ and are unique. */
+ case SCRATCH:
+ case CC0:
+ case PC:
+ return;
+
+ case CONST_INT:
+ case CONST_VECTOR:
+ case CONST_DOUBLE:
+ case CONST_FIXED:
+ /* These are kept unique for a given value. */
+ return;
+
+ default:
+ break;
+ }
+
+ fmt = GET_RTX_FORMAT (code);
+
+ for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--)
+ {
+ if (fmt[i] == 'E')
+ {
+ int j;
+ for (j = XVECLEN (s, i) - 1; j >= 0; j--)
+ rl78_note_reg_uses (dead, XVECEXP (s, i, j), insn);
+ }
+ else if (fmt[i] == 'e')
+ rl78_note_reg_uses (dead, XEXP (s, i), insn);
+ }
+}
+
+/* Like the previous function, but scan for SETs instead. */
+static void
+rl78_note_reg_set (char *dead, rtx d, rtx insn)
+{
+ int r, i;
+
+ if (GET_CODE (d) != REG)
+ return;
+
+ r = REGNO (d);
+ if (dead [r])
+ add_reg_note (insn, REG_UNUSED, gen_rtx_REG (GET_MODE (d), r));
+ if (dump_file)
+ fprintf (dump_file, "note set reg %d size %d\n", r, GET_MODE_SIZE (GET_MODE (d)));
+ for (i = 0; i < GET_MODE_SIZE (GET_MODE (d)); i ++)
+ dead [r + i] = 1;
+}
+
+/* This is a rather crude register death pass. Death status is reset
+ at every jump or call insn. */
+static void
+rl78_calculate_death_notes (void)
+{
+ char dead[FIRST_PSEUDO_REGISTER];
+ rtx insn, p, s, d;
+ int i;
+
+ memset (dead, 0, sizeof (dead));
+
+ for (insn = get_last_insn ();
+ insn;
+ insn = prev_nonnote_nondebug_insn (insn))
+ {
+ if (dump_file)
+ {
+ fprintf (dump_file, "\n--------------------------------------------------");
+ fprintf (dump_file, "\nDead:");
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i ++)
+ if (dead[i])
+ fprintf (dump_file, " %s", reg_names[i]);
+ fprintf (dump_file, "\n");
+ print_rtl_single (dump_file, insn);
+ }
+
+ switch (GET_CODE (insn))
+ {
+ case INSN:
+ p = PATTERN (insn);
+ switch (GET_CODE (p))
+ {
+ case SET:
+ s = SET_SRC (p);
+ d = SET_DEST (p);
+ rl78_note_reg_set (dead, d, insn);
+ rl78_note_reg_uses (dead, s, insn);
+ break;
+
+ case USE:
+ rl78_note_reg_uses (dead, p, insn);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case JUMP_INSN:
+ if (INSN_CODE (insn) == CODE_FOR_rl78_return)
+ {
+ memset (dead, 1, sizeof (dead));
+ /* We expect a USE just prior to this, which will mark
+ the actual return registers. The USE will have a
+ death note, but we aren't going to be modifying it
+ after this pass. */
+ break;
+ }
+ case CALL_INSN:
+ memset (dead, 0, sizeof (dead));
+ break;
+
+ default:
+ break;
+ }
+ if (dump_file)
+ print_rtl_single (dump_file, insn);
+ }
+}
+
+/* Helper function to reset the origins in RP and the age in AGE for
+ all registers. */
+static void
+reset_origins (int *rp, int *age)
+{
+ int i;
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ {
+ rp[i] = i;
+ age[i] = 0;
+ }
+}
+
+/* The idea behind this optimization is to look for cases where we
+ move data from A to B to C, and instead move from A to B, and A to
+ C. If B is a virtual register or memory, this is a big win on its
+ own. If B turns out to be unneeded after this, it's a bigger win.
+ For each register, we try to determine where it's value originally
+ came from, if it's propogated purely through moves (and not
+ computes). The ORIGINS[] array has the regno for the "origin" of
+ the value in the [regno] it's indexed by. */
+static void
+rl78_propogate_register_origins (void)
+{
+ int origins[FIRST_PSEUDO_REGISTER];
+ int age[FIRST_PSEUDO_REGISTER];
+ int i;
+ rtx insn, ninsn = NULL_RTX;
+ rtx pat;
+
+ reset_origins (origins, age);
+
+ for (insn = get_insns (); insn; insn = ninsn)
+ {
+ ninsn = next_nonnote_nondebug_insn (insn);
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "\n");
+ fprintf (dump_file, "Origins:");
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i ++)
+ if (origins[i] != i)
+ fprintf (dump_file, " r%d=r%d", i, origins[i]);
+ fprintf (dump_file, "\n");
+ print_rtl_single (dump_file, insn);
+ }
+
+ switch (GET_CODE (insn))
+ {
+ case CODE_LABEL:
+ case BARRIER:
+ case CALL_INSN:
+ case JUMP_INSN:
+ reset_origins (origins, age);
+ break;
+
+ default:
+ break;
+
+ case INSN:
+ pat = PATTERN (insn);
+
+ if (GET_CODE (pat) == PARALLEL)
+ {
+ rtx clobber = XVECEXP (pat, 0, 1);
+ pat = XVECEXP (pat, 0, 0);
+ if (GET_CODE (clobber) == CLOBBER
+ && GET_CODE (XEXP (clobber, 0)) == REG)
+ {
+ int cr = REGNO (XEXP (clobber, 0));
+ int mb = GET_MODE_SIZE (GET_MODE (XEXP (clobber, 0)));
+ if (dump_file)
+ fprintf (dump_file, "reset origins of %d regs at %d\n", mb, cr);
+ for (i = 0; i < mb; i++)
+ {
+ origins[cr + i] = cr + i;
+ age[cr + i] = 0;
+ }
+ }
+ else
+ break;
+ }
+
+ if (GET_CODE (pat) == SET)
+ {
+ rtx src = SET_SRC (pat);
+ rtx dest = SET_DEST (pat);
+ int mb = GET_MODE_SIZE (GET_MODE (dest));
+
+ if (GET_CODE (dest) == REG)
+ {
+ int dr = REGNO (dest);
+
+ if (GET_CODE (src) == REG)
+ {
+ int sr = REGNO (src);
+ int same = 1;
+ int best_age, best_reg;
+
+ /* See if the copy is not needed. */
+ for (i = 0; i < mb; i ++)
+ if (origins[dr + i] != origins[sr + i])
+ same = 0;
+ if (same)
+ {
+ if (dump_file)
+ fprintf (dump_file, "deleting because dest already has correct value\n");
+ delete_insn (insn);
+ break;
+ }
+
+ if (dr < 8 || sr >= 8)
+ {
+ int ar;
+
+ best_age = -1;
+ best_reg = -1;
+ /* See if the copy can be made from another
+ bank 0 register instead, instead of the
+ virtual src register. */
+ for (ar = 0; ar < 8; ar += mb)
+ {
+ same = 1;
+ for (i = 0; i < mb; i ++)
+ if (origins[ar + i] != origins[sr + i])
+ same = 0;
+
+ /* The chip has some reg-reg move limitations. */
+ if (mb == 1 && dr > 3)
+ same = 0;
+
+ if (same)
+ {
+ if (best_age == -1 || best_age > age[sr + i])
+ {
+ best_age = age[sr + i];
+ best_reg = sr;
+ }
+ }
+ }
+
+ if (best_reg != -1)
+ {
+ /* FIXME: copy debug info too. */
+ SET_SRC (pat) = gen_rtx_REG (GET_MODE (src), best_reg);
+ sr = best_reg;
+ }
+ }
+
+ for (i = 0; i < mb; i++)
+ {
+ origins[dr + i] = origins[sr + i];
+ age[dr + i] = age[sr + i] + 1;
+ }
+ }
+ else
+ {
+ /* The destination is computed, its origin is itself. */
+ if (dump_file)
+ fprintf (dump_file, "resetting origin of r%d for %d byte%s\n",
+ dr, mb, mb == 1 ? "" : "s");
+ for (i = 0; i < mb; i ++)
+ {
+ origins[dr + i] = dr + i;
+ age[dr + i] = 0;
+ }
+ }
+
+ /* Any registers marked with that reg as an origin are reset. */
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ if (origins[i] >= dr && origins[i] < dr + mb)
+ {
+ origins[i] = i;
+ age[i] = 0;
+ }
+ }
+
+ /* Special case - our ADDSI3 macro uses AX and sometimes BC. */
+ if (get_attr_valloc (insn) == VALLOC_MACAX)
+ {
+ if (dump_file)
+ fprintf (dump_file, "Resetting origin of AX/BC for macro.\n");
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ if (i <= 3 || origins[i] <= 3)
+ {
+ origins[i] = i;
+ age[i] = 0;
+ }
+ }
+
+ if (GET_CODE (src) == ASHIFT
+ || GET_CODE (src) == ASHIFTRT
+ || GET_CODE (src) == LSHIFTRT)
+ {
+ rtx count = XEXP (src, 1);
+ if (GET_CODE (count) == REG)
+ {
+ /* Special case - our pattern clobbers the count register. */
+ int r = REGNO (count);
+ if (dump_file)
+ fprintf (dump_file, "Resetting origin of r%d for shift.\n", r);
+ for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
+ if (i == r || origins[i] == r)
+ {
+ origins[i] = i;
+ age[i] = 0;
+ }
+ }
+ }
+ }
+ else if (GET_CODE (pat) == CLOBBER
+ && GET_CODE (XEXP (pat, 0)) == REG)
+ {
+ if (REG_P (XEXP (pat, 0)))
+ {
+ unsigned int reg = REGNO (XEXP (pat, 0));
+
+ origins[reg] = reg;
+ age[reg] = 0;
+ }
+ }
+ }
+ }
+}
+
+/* Remove any SETs where the destination is unneeded. */
+static void
+rl78_remove_unused_sets (void)
+{
+ rtx insn, ninsn = NULL_RTX;
+ rtx dest;
+
+ for (insn = get_insns (); insn; insn = ninsn)
+ {
+ ninsn = next_nonnote_nondebug_insn (insn);
+
+ if ((insn = single_set (insn)) == NULL_RTX)
+ continue;
+
+ dest = SET_DEST (insn);
+
+ if (GET_CODE (dest) != REG || REGNO (dest) > 23)
+ continue;
+
+ if (find_regno_note (insn, REG_UNUSED, REGNO (dest)))
+ delete_insn (insn);
+ }
+}
+
+/* This is the top of the devritualization pass. */
+static void
+rl78_reorg (void)
+{
+ /* split2 only happens when optimizing, but we need all movSIs to be
+ split now. */
+ if (optimize <= 0)
+ split_all_insns ();
+
+ rl78_alloc_physical_registers ();
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "\n================DEVIRT:=AFTER=ALLOC=PHYSICAL=REGISTERS================\n");
+ print_rtl_with_bb (dump_file, get_insns (), 0);
+ }
+
+ rl78_propogate_register_origins ();
+ rl78_calculate_death_notes ();
+
+ if (dump_file)
+ {
+ fprintf (dump_file, "\n================DEVIRT:=AFTER=PROPOGATION=============================\n");
+ print_rtl_with_bb (dump_file, get_insns (), 0);
+ fprintf (dump_file, "\n======================================================================\n");
+ }
+
+ rl78_remove_unused_sets ();
+
+ /* The code after devirtualizing has changed so much that at this point
+ we might as well just rescan everything. Note that
+ df_rescan_all_insns is not going to help here because it does not
+ touch the artificial uses and defs. */
+ df_finish_pass (true);
+ if (optimize > 1)
+ df_live_add_problem ();
+ df_scan_alloc (NULL);
+ df_scan_blocks ();
+
+ if (optimize)
+ df_analyze ();
+}
+
+#undef TARGET_RETURN_IN_MEMORY
+#define TARGET_RETURN_IN_MEMORY rl78_return_in_memory
+
+static bool
+rl78_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
+{
+ const HOST_WIDE_INT size = int_size_in_bytes (type);
+ return (size == -1 || size > 8);
+}
+
+
+#undef TARGET_RTX_COSTS
+#define TARGET_RTX_COSTS rl78_rtx_costs
+
+static bool rl78_rtx_costs (rtx x,
+ int code,
+ int outer_code ATTRIBUTE_UNUSED,
+ int opno ATTRIBUTE_UNUSED,
+ int * total,
+ bool speed ATTRIBUTE_UNUSED)
+{
+ if (code == IF_THEN_ELSE)
+ return COSTS_N_INSNS (10);
+ if (GET_MODE (x) == SImode)
+ {
+ switch (code)
+ {
+ case MULT:
+ if (RL78_MUL_RL78)
+ *total = COSTS_N_INSNS (14);
+ else if (RL78_MUL_G13)
+ *total = COSTS_N_INSNS (29);
+ else
+ *total = COSTS_N_INSNS (500);
+ return true;
+ case PLUS:
+ *total = COSTS_N_INSNS (8);
+ return true;
+ case ASHIFT:
+ case ASHIFTRT:
+ case LSHIFTRT:
+ if (GET_CODE (XEXP (x, 1)) == CONST_INT)
+ {
+ switch (INTVAL (XEXP (x, 1)))
+ {
+ case 0: *total = COSTS_N_INSNS (0); break;
+ case 1: *total = COSTS_N_INSNS (6); break;
+ case 2: case 3: case 4: case 5: case 6: case 7:
+ *total = COSTS_N_INSNS (10); break;
+ case 8: *total = COSTS_N_INSNS (6); break;
+ case 9: case 10: case 11: case 12: case 13: case 14: case 15:
+ *total = COSTS_N_INSNS (10); break;
+ case 16: *total = COSTS_N_INSNS (3); break;
+ case 17: case 18: case 19: case 20: case 21: case 22: case 23:
+ *total = COSTS_N_INSNS (4); break;
+ case 24: *total = COSTS_N_INSNS (4); break;
+ case 25: case 26: case 27: case 28: case 29: case 30: case 31:
+ *total = COSTS_N_INSNS (5); break;
+ }
+ }
+ else
+ *total = COSTS_N_INSNS (10+4*16);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+#undef TARGET_UNWIND_WORD_MODE
+#define TARGET_UNWIND_WORD_MODE rl78_unwind_word_mode
+
+static enum machine_mode
+rl78_unwind_word_mode (void)
+{
+ return HImode;
+}
+
+
+struct gcc_target targetm = TARGET_INITIALIZER;
+
+#include "gt-rl78.h"