| /* Output routines for CR16 processor. |
| Copyright (C) 2012-2015 Free Software Foundation, Inc. |
| Contributed by KPIT Cummins Infosystems Limited. |
| |
| 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 "rtl.h" |
| #include "hash-set.h" |
| #include "machmode.h" |
| #include "vec.h" |
| #include "double-int.h" |
| #include "input.h" |
| #include "alias.h" |
| #include "symtab.h" |
| #include "wide-int.h" |
| #include "inchash.h" |
| #include "tree.h" |
| #include "fold-const.h" |
| #include "stor-layout.h" |
| #include "calls.h" |
| #include "tm_p.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "output.h" |
| #include "insn-codes.h" |
| #include "insn-attr.h" |
| #include "flags.h" |
| #include "except.h" |
| #include "function.h" |
| #include "recog.h" |
| #include "hashtab.h" |
| #include "statistics.h" |
| #include "real.h" |
| #include "fixed-value.h" |
| #include "expmed.h" |
| #include "dojump.h" |
| #include "explow.h" |
| #include "emit-rtl.h" |
| #include "varasm.h" |
| #include "stmt.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "diagnostic-core.h" |
| #include "predict.h" |
| #include "dominance.h" |
| #include "cfg.h" |
| #include "cfgrtl.h" |
| #include "cfganal.h" |
| #include "lcm.h" |
| #include "cfgbuild.h" |
| #include "cfgcleanup.h" |
| #include "basic-block.h" |
| #include "target.h" |
| #include "target-def.h" |
| #include "df.h" |
| #include "builtins.h" |
| |
| /* Definitions. */ |
| |
| /* Maximum number of register used for passing parameters. */ |
| #define MAX_REG_FOR_PASSING_ARGS 6 |
| |
| /* Minimum number register used for passing parameters. */ |
| #define MIN_REG_FOR_PASSING_ARGS 2 |
| |
| /* The maximum count of words supported in the assembly of the architecture in |
| a push/pop instruction. */ |
| #define MAX_COUNT 8 |
| |
| /* Predicate is true if the current function is a 'noreturn' function, |
| i.e. it is qualified as volatile. */ |
| #define FUNC_IS_NORETURN_P(decl) (TREE_THIS_VOLATILE (decl)) |
| |
| /* Predicate that holds when we need to save registers even for 'noreturn' |
| functions, to accommodate for unwinding. */ |
| #define MUST_SAVE_REGS_P() \ |
| (flag_unwind_tables || (flag_exceptions && !UI_SJLJ)) |
| |
| /* Nonzero if the rtx X is a signed const int of n bits. */ |
| #define RTX_SIGNED_INT_FITS_N_BITS(X, n) \ |
| ((GET_CODE (X) == CONST_INT \ |
| && SIGNED_INT_FITS_N_BITS (INTVAL (X), n)) ? 1 : 0) |
| |
| /* Nonzero if the rtx X is an unsigned const int of n bits. */ |
| #define RTX_UNSIGNED_INT_FITS_N_BITS(X, n) \ |
| ((GET_CODE (X) == CONST_INT \ |
| && UNSIGNED_INT_FITS_N_BITS (INTVAL (X), n)) ? 1 : 0) |
| |
| /* Structure for stack computations. */ |
| |
| /* variable definitions in the struture |
| args_size Number of bytes saved on the stack for local |
| variables |
| |
| reg_size Number of bytes saved on the stack for |
| non-scratch registers |
| |
| total_size The sum of 2 sizes: locals vars and padding byte |
| for saving the registers. Used in expand_prologue() |
| and expand_epilogue() |
| |
| last_reg_to_save Will hold the number of the last register the |
| prologue saves, -1 if no register is saved |
| |
| save_regs[16] Each object in the array is a register number. |
| Mark 1 for registers that need to be saved |
| |
| num_regs Number of registers saved |
| |
| initialized Non-zero if frame size already calculated, not |
| used yet |
| |
| function_makes_calls Does the function make calls ? not used yet. */ |
| |
| struct cr16_frame_info |
| { |
| unsigned long var_size; |
| unsigned long args_size; |
| unsigned int reg_size; |
| unsigned long total_size; |
| long last_reg_to_save; |
| long save_regs[FIRST_PSEUDO_REGISTER]; |
| int num_regs; |
| int initialized; |
| int function_makes_calls; |
| }; |
| |
| /* Current frame information calculated by cr16_compute_frame_size. */ |
| static struct cr16_frame_info current_frame_info; |
| |
| /* Static Variables. */ |
| |
| /* Data model that was supplied by user via command line option |
| This will be overridden in case of invalid combination |
| of core and data model options are supplied. */ |
| static enum data_model_type data_model = DM_DEFAULT; |
| |
| /* TARGETM Function Prototypes and forward declarations */ |
| static void cr16_print_operand (FILE *, rtx, int); |
| static void cr16_print_operand_address (FILE *, rtx); |
| |
| /* Stack layout and calling conventions. */ |
| #undef TARGET_STRUCT_VALUE_RTX |
| #define TARGET_STRUCT_VALUE_RTX cr16_struct_value_rtx |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY cr16_return_in_memory |
| |
| /* Target-specific uses of '__attribute__'. */ |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE cr16_attribute_table |
| #undef TARGET_NARROW_VOLATILE_BITFIELD |
| #define TARGET_NARROW_VOLATILE_BITFIELD hook_bool_void_false |
| |
| /* EH related. */ |
| #undef TARGET_UNWIND_WORD_MODE |
| #define TARGET_UNWIND_WORD_MODE cr16_unwind_word_mode |
| |
| /* Override Options. */ |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE cr16_override_options |
| |
| /* Conditional register usuage. */ |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE cr16_conditional_register_usage |
| |
| /* Controlling register spills. */ |
| #undef TARGET_CLASS_LIKELY_SPILLED_P |
| #define TARGET_CLASS_LIKELY_SPILLED_P cr16_class_likely_spilled_p |
| |
| /* Passing function arguments. */ |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG cr16_function_arg |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE cr16_function_arg_advance |
| #undef TARGET_RETURN_POPS_ARGS |
| #define TARGET_RETURN_POPS_ARGS cr16_return_pops_args |
| |
| /* Initialize the GCC target structure. */ |
| #undef TARGET_FRAME_POINTER_REQUIRED |
| #define TARGET_FRAME_POINTER_REQUIRED cr16_frame_pointer_required |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE cr16_can_eliminate |
| #undef TARGET_LEGITIMIZE_ADDRESS |
| #define TARGET_LEGITIMIZE_ADDRESS cr16_legitimize_address |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P cr16_legitimate_constant_p |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P cr16_legitimate_address_p |
| |
| /* Returning function value. */ |
| #undef TARGET_FUNCTION_VALUE |
| #define TARGET_FUNCTION_VALUE cr16_function_value |
| #undef TARGET_LIBCALL_VALUE |
| #define TARGET_LIBCALL_VALUE cr16_libcall_value |
| #undef TARGET_FUNCTION_VALUE_REGNO_P |
| #define TARGET_FUNCTION_VALUE_REGNO_P cr16_function_value_regno_p |
| |
| /* printing the values. */ |
| #undef TARGET_PRINT_OPERAND |
| #define TARGET_PRINT_OPERAND cr16_print_operand |
| #undef TARGET_PRINT_OPERAND_ADDRESS |
| #define TARGET_PRINT_OPERAND_ADDRESS cr16_print_operand_address |
| |
| /* Relative costs of operations. */ |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST cr16_address_cost |
| #undef TARGET_REGISTER_MOVE_COST |
| #define TARGET_REGISTER_MOVE_COST cr16_register_move_cost |
| #undef TARGET_MEMORY_MOVE_COST |
| #define TARGET_MEMORY_MOVE_COST cr16_memory_move_cost |
| |
| /* Table of machine attributes. */ |
| static const struct attribute_spec cr16_attribute_table[] = { |
| /* ISRs have special prologue and epilogue requirements. */ |
| /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler, |
| affects_type_identity }. */ |
| {"interrupt", 0, 0, false, true, true, NULL, false}, |
| {NULL, 0, 0, false, false, false, NULL, false} |
| }; |
| |
| /* TARGET_ASM_UNALIGNED_xx_OP generates .?byte directive |
| .?byte directive along with @c is not understood by assembler. |
| Therefore, make all TARGET_ASM_UNALIGNED_xx_OP same |
| as TARGET_ASM_ALIGNED_xx_OP. */ |
| #undef TARGET_ASM_UNALIGNED_HI_OP |
| #define TARGET_ASM_UNALIGNED_HI_OP TARGET_ASM_ALIGNED_HI_OP |
| #undef TARGET_ASM_UNALIGNED_SI_OP |
| #define TARGET_ASM_UNALIGNED_SI_OP TARGET_ASM_ALIGNED_SI_OP |
| #undef TARGET_ASM_UNALIGNED_DI_OP |
| #define TARGET_ASM_UNALIGNED_DI_OP TARGET_ASM_ALIGNED_DI_OP |
| |
| /* Target hook implementations. */ |
| |
| /* Implements hook TARGET_RETURN_IN_MEMORY. */ |
| static bool |
| cr16_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)); |
| } |
| |
| /* Implement TARGET_CLASS_LIKELY_SPILLED_P. */ |
| static bool |
| cr16_class_likely_spilled_p (reg_class_t rclass) |
| { |
| if ((rclass) == SHORT_REGS || (rclass) == DOUBLE_BASE_REGS |
| || (rclass) == LONG_REGS || (rclass) == GENERAL_REGS) |
| return true; |
| |
| return false; |
| } |
| |
| static int |
| cr16_return_pops_args (tree fundecl ATTRIBUTE_UNUSED, |
| tree funtype ATTRIBUTE_UNUSED, |
| int size ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| /* Returns true if data model selected via command line option |
| is same as function argument. */ |
| bool |
| cr16_is_data_model (enum data_model_type model) |
| { |
| return (model == data_model); |
| } |
| |
| /* Parse relevant options and override. */ |
| static void |
| cr16_override_options (void) |
| { |
| /* Disable -fdelete-null-pointer-checks option for CR16 target. |
| Programs which rely on NULL pointer dereferences _not_ halting the |
| program may not work properly with this option. So disable this |
| option. */ |
| flag_delete_null_pointer_checks = 0; |
| |
| /* FIXME: To avoid spill_failure ICE during exception handling, |
| * disable cse_fllow_jumps. The spill error occurs when compiler |
| * can't find a suitable candidate in GENERAL_REGS class to reload |
| * a 32bit register. |
| * Need to find a better way of avoiding this situation. */ |
| if (flag_exceptions) |
| flag_cse_follow_jumps = 0; |
| |
| /* If -fpic option, data_model == DM_FAR. */ |
| if (flag_pic == NEAR_PIC) |
| { |
| data_model = DM_FAR; |
| } |
| |
| /* The only option we want to examine is data model option. */ |
| if (cr16_data_model) |
| { |
| if (strcmp (cr16_data_model, "medium") == 0) |
| data_model = DM_DEFAULT; |
| else if (strcmp (cr16_data_model, "near") == 0) |
| data_model = DM_NEAR; |
| else if (strcmp (cr16_data_model, "far") == 0) |
| { |
| if (TARGET_CR16CP) |
| data_model = DM_FAR; |
| else |
| error ("data-model=far not valid for cr16c architecture"); |
| } |
| else |
| error ("invalid data model option -mdata-model=%s", cr16_data_model); |
| } |
| else |
| data_model = DM_DEFAULT; |
| } |
| |
| /* Implements the macro TARGET_CONDITIONAL_REGISTER_USAGE. */ |
| static void |
| cr16_conditional_register_usage (void) |
| { |
| if (flag_pic) |
| { |
| fixed_regs[12] = call_used_regs[12] = 1; |
| } |
| } |
| |
| /* Stack layout and calling conventions routines. */ |
| |
| /* Return nonzero if the current function being compiled is an interrupt |
| function as specified by the "interrupt" attribute. */ |
| int |
| cr16_interrupt_function_p (void) |
| { |
| tree attributes; |
| |
| attributes = TYPE_ATTRIBUTES (TREE_TYPE (current_function_decl)); |
| return (lookup_attribute ("interrupt", attributes) != NULL_TREE); |
| } |
| |
| /* Compute values for the array current_frame_info.save_regs and the variable |
| current_frame_info.reg_size. The index of current_frame_info.save_regs |
| is numbers of register, each will get 1 if we need to save it in the |
| current function, 0 if not. current_frame_info.reg_size is the total sum |
| of the registers being saved. */ |
| static void |
| cr16_compute_save_regs (void) |
| { |
| unsigned int regno; |
| |
| /* Initialize here so in case the function is no-return it will be -1. */ |
| current_frame_info.last_reg_to_save = -1; |
| |
| /* Initialize the number of bytes to be saved. */ |
| current_frame_info.reg_size = 0; |
| |
| /* No need to save any registers if the function never returns. */ |
| if (FUNC_IS_NORETURN_P (current_function_decl) && !MUST_SAVE_REGS_P ()) |
| return; |
| |
| for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) |
| { |
| if (fixed_regs[regno]) |
| { |
| current_frame_info.save_regs[regno] = 0; |
| continue; |
| } |
| |
| /* If this reg is used and not call-used (except RA), save it. */ |
| if (cr16_interrupt_function_p ()) |
| { |
| if (!crtl->is_leaf && call_used_regs[regno]) |
| /* This is a volatile reg in a non-leaf interrupt routine - save |
| it for the sake of its sons. */ |
| current_frame_info.save_regs[regno] = 1; |
| else if (df_regs_ever_live_p (regno)) |
| /* This reg is used - save it. */ |
| current_frame_info.save_regs[regno] = 1; |
| else |
| /* This reg is not used, and is not a volatile - don't save. */ |
| current_frame_info.save_regs[regno] = 0; |
| } |
| else |
| { |
| /* If this reg is used and not call-used (except RA), save it. */ |
| if (df_regs_ever_live_p (regno) |
| && (!call_used_regs[regno] || regno == RETURN_ADDRESS_REGNUM)) |
| current_frame_info.save_regs[regno] = 1; |
| else |
| current_frame_info.save_regs[regno] = 0; |
| } |
| } |
| |
| /* Save registers so the exception handler can modify them. */ |
| if (crtl->calls_eh_return) |
| { |
| unsigned int i; |
| |
| for (i = 0;; ++i) |
| { |
| regno = EH_RETURN_DATA_REGNO (i); |
| if (INVALID_REGNUM == regno) |
| break; |
| current_frame_info.save_regs[regno] = 1; |
| } |
| } |
| |
| for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) |
| if (current_frame_info.save_regs[regno] == 1) |
| { |
| current_frame_info.last_reg_to_save = regno; |
| if (regno >= CR16_FIRST_DWORD_REGISTER) |
| current_frame_info.reg_size += CR16_UNITS_PER_DWORD; |
| else |
| current_frame_info.reg_size += UNITS_PER_WORD; |
| } |
| } |
| |
| /* Compute the size of the local area and the size to be adjusted by the |
| prologue and epilogue. */ |
| static void |
| cr16_compute_frame (void) |
| { |
| /* For aligning the local variables. */ |
| int stack_alignment = STACK_BOUNDARY / BITS_PER_UNIT; |
| int padding_locals; |
| |
| /* Padding needed for each element of the frame. */ |
| current_frame_info.var_size = get_frame_size (); |
| |
| /* Align to the stack alignment. */ |
| padding_locals = current_frame_info.var_size % stack_alignment; |
| if (padding_locals) |
| padding_locals = stack_alignment - padding_locals; |
| |
| current_frame_info.var_size += padding_locals; |
| current_frame_info.total_size = current_frame_info.var_size |
| + (ACCUMULATE_OUTGOING_ARGS |
| ? crtl->outgoing_args_size : 0); |
| } |
| |
| /* Implements the macro INITIAL_ELIMINATION_OFFSET, return the OFFSET. */ |
| int |
| cr16_initial_elimination_offset (int from, int to) |
| { |
| /* Compute this since we need to use current_frame_info.reg_size. */ |
| cr16_compute_save_regs (); |
| |
| /* Compute this since we need to use current_frame_info.var_size. */ |
| cr16_compute_frame (); |
| |
| if (((from) == FRAME_POINTER_REGNUM) && ((to) == STACK_POINTER_REGNUM)) |
| return (ACCUMULATE_OUTGOING_ARGS ? crtl->outgoing_args_size : 0); |
| else if (((from) == ARG_POINTER_REGNUM) && ((to) == FRAME_POINTER_REGNUM)) |
| return (current_frame_info.reg_size + current_frame_info.var_size); |
| else if (((from) == ARG_POINTER_REGNUM) && ((to) == STACK_POINTER_REGNUM)) |
| return (current_frame_info.reg_size + current_frame_info.var_size |
| + (ACCUMULATE_OUTGOING_ARGS ? crtl->outgoing_args_size : 0)); |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Register Usage. */ |
| |
| /* Return the class number of the smallest class containing reg number REGNO. |
| This could be a conditional expression or could index an array. */ |
| enum reg_class |
| cr16_regno_reg_class (int regno) |
| { |
| if ((regno >= 0) && (regno < CR16_FIRST_DWORD_REGISTER)) |
| return SHORT_REGS; |
| |
| if ((regno >= CR16_FIRST_DWORD_REGISTER) && (regno < FIRST_PSEUDO_REGISTER)) |
| return LONG_REGS; |
| |
| return NO_REGS; |
| } |
| |
| /* Return 1 if hard register REGNO can hold a value of machine-mode MODE. */ |
| int |
| cr16_hard_regno_mode_ok (int regno, machine_mode mode) |
| { |
| if ((GET_MODE_SIZE (mode) >= 4) && (regno == 11)) |
| return 0; |
| |
| if (mode == DImode || mode == DFmode) |
| { |
| if ((regno > 8) || (regno & 1)) |
| return 0; |
| return 1; |
| } |
| |
| if ((TARGET_INT32) |
| && ((regno >= 12) && (GET_MODE_SIZE (mode) < 4 ))) |
| return 0; |
| |
| /* CC can only hold CCmode values. */ |
| if (GET_MODE_CLASS (mode) == MODE_CC) |
| return 0; |
| return 1; |
| } |
| |
| /* Returns register number for function return value.*/ |
| static inline unsigned int |
| cr16_ret_register (void) |
| { |
| return 0; |
| } |
| |
| /* Implements hook TARGET_STRUCT_VALUE_RTX. */ |
| static rtx |
| cr16_struct_value_rtx (tree fntype ATTRIBUTE_UNUSED, |
| int incoming ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (Pmode, cr16_ret_register ()); |
| } |
| |
| /* Returning function value. */ |
| |
| /* Worker function for TARGET_FUNCTION_VALUE_REGNO_P. */ |
| static bool |
| cr16_function_value_regno_p (const unsigned int regno) |
| { |
| return (regno == cr16_ret_register ()); |
| } |
| |
| /* Create an RTX representing the place where a |
| library function returns a value of mode MODE. */ |
| static rtx |
| cr16_libcall_value (machine_mode mode, |
| const_rtx func ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (mode, cr16_ret_register ()); |
| } |
| |
| /* Create an RTX representing the place where a |
| function returns a value of data type VALTYPE. */ |
| static rtx |
| cr16_function_value (const_tree type, |
| const_tree fn_decl_or_type ATTRIBUTE_UNUSED, |
| bool outgoing ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (TYPE_MODE (type), cr16_ret_register ()); |
| } |
| |
| /* Passing function arguments. */ |
| |
| /* If enough param regs are available for passing the param of type TYPE return |
| the number of registers needed else 0. */ |
| static int |
| enough_regs_for_param (CUMULATIVE_ARGS * cum, const_tree type, |
| machine_mode mode) |
| { |
| int type_size; |
| int remaining_size; |
| |
| if (mode != BLKmode) |
| type_size = GET_MODE_BITSIZE (mode); |
| else |
| type_size = int_size_in_bytes (type) * BITS_PER_UNIT; |
| |
| remaining_size = BITS_PER_WORD * (MAX_REG_FOR_PASSING_ARGS |
| - (MIN_REG_FOR_PASSING_ARGS + cum->ints) + |
| 1); |
| |
| /* Any variable which is too big to pass in two registers, will pass on |
| stack. */ |
| if ((remaining_size >= type_size) && (type_size <= 2 * BITS_PER_WORD)) |
| return (type_size + BITS_PER_WORD - 1) / BITS_PER_WORD; |
| |
| return 0; |
| } |
| |
| /* Implements the macro FUNCTION_ARG defined in cr16.h. */ |
| static rtx |
| cr16_function_arg (cumulative_args_t cum_v, machine_mode mode, |
| const_tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| cum->last_parm_in_reg = 0; |
| |
| /* function_arg () is called with this type just after all the args have |
| had their registers assigned. The rtx that function_arg returns from |
| this type is supposed to pass to 'gen_call' but currently it is not |
| implemented (see macro GEN_CALL). */ |
| if (type == void_type_node) |
| return NULL_RTX; |
| |
| if (targetm.calls.must_pass_in_stack (mode, type) || (cum->ints < 0)) |
| return NULL_RTX; |
| |
| if (mode == BLKmode) |
| { |
| /* Enable structures that need padding bytes at the end to pass to a |
| function in registers. */ |
| if (enough_regs_for_param (cum, type, mode) != 0) |
| { |
| cum->last_parm_in_reg = 1; |
| return gen_rtx_REG (mode, MIN_REG_FOR_PASSING_ARGS + cum->ints); |
| } |
| } |
| |
| if ((MIN_REG_FOR_PASSING_ARGS + cum->ints) > MAX_REG_FOR_PASSING_ARGS) |
| return NULL_RTX; |
| else |
| { |
| if (enough_regs_for_param (cum, type, mode) != 0) |
| { |
| cum->last_parm_in_reg = 1; |
| return gen_rtx_REG (mode, MIN_REG_FOR_PASSING_ARGS + cum->ints); |
| } |
| } |
| |
| return NULL_RTX; |
| } |
| |
| /* Implements the macro INIT_CUMULATIVE_ARGS defined in cr16.h. */ |
| void |
| cr16_init_cumulative_args (CUMULATIVE_ARGS * cum, tree fntype, |
| rtx libfunc ATTRIBUTE_UNUSED) |
| { |
| tree param, next_param; |
| |
| cum->ints = 0; |
| |
| /* Determine if this function has variable arguments. This is indicated by |
| the last argument being 'void_type_mode' if there are no variable |
| arguments. Change here for a different vararg. */ |
| for (param = (fntype) ? TYPE_ARG_TYPES (fntype) : 0; |
| param != NULL_TREE; param = next_param) |
| { |
| next_param = TREE_CHAIN (param); |
| if ((next_param == NULL_TREE) && (TREE_VALUE (param) != void_type_node)) |
| { |
| cum->ints = -1; |
| return; |
| } |
| } |
| } |
| |
| /* Implements the macro FUNCTION_ARG_ADVANCE defined in cr16.h. */ |
| static void |
| cr16_function_arg_advance (cumulative_args_t cum_v, machine_mode mode, |
| const_tree type, bool named ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS * cum = get_cumulative_args (cum_v); |
| |
| /* l holds the number of registers required. */ |
| int l = GET_MODE_BITSIZE (mode) / BITS_PER_WORD; |
| |
| /* If the parameter isn't passed on a register don't advance cum. */ |
| if (!cum->last_parm_in_reg) |
| return; |
| |
| if (targetm.calls.must_pass_in_stack (mode, type) || (cum->ints < 0)) |
| return; |
| |
| if ((mode == SImode) || (mode == HImode) |
| || (mode == QImode) || (mode == DImode)) |
| { |
| if (l <= 1) |
| cum->ints += 1; |
| else |
| cum->ints += l; |
| } |
| else if ((mode == SFmode) || (mode == DFmode)) |
| cum->ints += l; |
| else if ((mode) == BLKmode) |
| { |
| if ((l = enough_regs_for_param (cum, type, mode)) != 0) |
| cum->ints += l; |
| } |
| return; |
| } |
| |
| /* Implements the macro FUNCTION_ARG_REGNO_P defined in cr16.h. |
| Return nonzero if N is a register used for passing parameters. */ |
| int |
| cr16_function_arg_regno_p (int n) |
| { |
| return ((n <= MAX_REG_FOR_PASSING_ARGS) && (n >= MIN_REG_FOR_PASSING_ARGS)); |
| } |
| |
| /* Addressing modes. |
| Following set of function implement the macro GO_IF_LEGITIMATE_ADDRESS |
| defined in cr16.h. */ |
| |
| /* Helper function to check if is a valid base register that can |
| hold address. */ |
| static int |
| cr16_addr_reg_p (rtx addr_reg) |
| { |
| rtx reg; |
| |
| if (REG_P (addr_reg)) |
| reg = addr_reg; |
| else if ((GET_CODE (addr_reg) == SUBREG) |
| && REG_P (SUBREG_REG (addr_reg)) |
| && (GET_MODE_SIZE (GET_MODE (SUBREG_REG (addr_reg))) |
| <= UNITS_PER_WORD)) |
| reg = SUBREG_REG (addr_reg); |
| else |
| return FALSE; |
| |
| if (GET_MODE (reg) != Pmode) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* Helper functions: Created specifically for decomposing operand of CONST |
| Recursively look into expression x for code or data symbol. |
| The function expects the expression to contain combination of |
| SYMBOL_REF, CONST_INT, (PLUS or MINUS) |
| LABEL_REF, CONST_INT, (PLUS or MINUS) |
| SYMBOL_REF |
| LABEL_REF |
| All other combinations will result in code = -1 and data = ILLEGAL_DM |
| code data |
| -1 ILLEGAL_DM The expression did not contain SYMBOL_REF or LABEL_REF |
| 0 DM_FAR SYMBOL_REF was found and it was far data reference. |
| 0 DM_DEFAULT SYMBOL_REF was found and it was medium data reference. |
| 1 ILLEGAL_DM LABEL_REF was found. |
| 2 ILLEGAL_DM SYMBOL_REF was found and it was function reference. */ |
| void |
| cr16_decompose_const (rtx x, int *code, enum data_model_type *data, |
| bool treat_as_const) |
| { |
| *code = -1; |
| *data = ILLEGAL_DM; |
| switch (GET_CODE (x)) |
| { |
| case SYMBOL_REF: |
| *code = SYMBOL_REF_FUNCTION_P (x) ? 2 : 0; |
| /* 2 indicates func sym. */ |
| if (*code == 0) |
| { |
| if (CR16_TARGET_DATA_NEAR) |
| *data = DM_DEFAULT; |
| else if (CR16_TARGET_DATA_MEDIUM) |
| *data = DM_FAR; |
| else if (CR16_TARGET_DATA_FAR) |
| { |
| if (treat_as_const) |
| /* This will be used only for printing |
| the qualifier. This call is (may be) |
| made by cr16_print_operand_address. */ |
| *data = DM_FAR; |
| else |
| /* This call is (may be) made by |
| cr16_legitimate_address_p. */ |
| *data = ILLEGAL_DM; |
| } |
| } |
| return; |
| |
| case LABEL_REF: |
| /* 1 - indicates non-function symbol. */ |
| *code = 1; |
| return; |
| |
| case PLUS: |
| case MINUS: |
| /* Look into the tree nodes. */ |
| if (GET_CODE (XEXP (x, 0)) == CONST_INT) |
| cr16_decompose_const (XEXP (x, 1), code, data, treat_as_const); |
| else if (GET_CODE (XEXP (x, 1)) == CONST_INT) |
| cr16_decompose_const (XEXP (x, 0), code, data, treat_as_const); |
| return; |
| default: |
| return; |
| } |
| } |
| |
| /* Decompose Address |
| This function decomposes the address returns the type of address |
| as defined in enum cr16_addrtype. It also fills the parameter *out. |
| The decomposed address can be used for two purposes. One to |
| check if the address is valid and second to print the address |
| operand. |
| |
| Following tables list valid address supported in CR16C/C+ architectures. |
| Legend: |
| aN : Absoulte address N-bit address |
| R : One 16-bit register |
| RP : Consecutive two 16-bit registers or one 32-bit register |
| I : One 32-bit register |
| dispN : Signed displacement of N-bits |
| |
| ----Code addresses---- |
| Branch operands: |
| disp9 : CR16_ABSOLUTE (disp) |
| disp17 : CR16_ABSOLUTE (disp) |
| disp25 : CR16_ABSOLUTE (disp) |
| RP + disp25 : CR16_REGP_REL (base, disp) |
| |
| Jump operands: |
| RP : CR16_REGP_REL (base, disp=0) |
| a24 : CR16_ABSOLUTE (disp) |
| |
| ----Data addresses---- |
| a20 : CR16_ABSOLUTE (disp) near (1M) |
| a24 : CR16_ABSOLUTE (disp) medium (16M) |
| R + d20 : CR16_REG_REL (base, disp) near (1M+64K) |
| RP + d4 : CR16_REGP_REL (base, disp) far (4G) |
| RP + d16 : CR16_REGP_REL (base, disp) far (4G) |
| RP + d20 : CR16_REGP_REL (base, disp) far (4G) |
| I : *** Valid but port does not support this |
| I + a20 : *** Valid but port does not support this |
| I + RP + d14: CR16_INDEX_REGP_REL (base, index, disp) far (4G) |
| I + RP + d20: CR16_INDEX_REGP_REL (base, index, disp) far (4G) |
| |
| Decomposing Data model in case of absolute address. |
| |
| Target Option Address type Resultant Data ref type |
| ---------------------- ------------ ----------------------- |
| CR16_TARGET_MODEL_NEAR ABS20 DM_DEFAULT |
| CR16_TARGET_MODEL_NEAR IMM20 DM_DEFAULT |
| CR16_TARGET_MODEL_NEAR ABS24 Invalid |
| CR16_TARGET_MODEL_NEAR IMM32 Invalid |
| |
| CR16_TARGET_MODEL_MEDIUM ABS20 DM_DEFAULT |
| CR16_TARGET_MODEL_MEDIUM IMM20 DM_DEFAULT |
| CR16_TARGET_MODEL_MEDIUM ABS24 DM_FAR |
| CR16_TARGET_MODEL_MEDIUM IMM32 Invalid |
| |
| CR16_TARGET_MODEL_FAR ABS20 DM_DEFAULT |
| CR16_TARGET_MODEL_FAR IMM20 DM_DEFAULT |
| CR16_TARGET_MODEL_FAR ABS24 DM_FAR |
| CR16_TARGET_MODEL_FAR IMM32 DM_FAR. */ |
| enum cr16_addrtype |
| cr16_decompose_address (rtx addr, struct cr16_address *out, |
| bool debug_print, bool treat_as_const) |
| { |
| rtx base = NULL_RTX, disp = NULL_RTX, index = NULL_RTX; |
| enum data_model_type data = ILLEGAL_DM; |
| int code = -1; |
| enum cr16_addrtype retval = CR16_INVALID; |
| |
| switch (GET_CODE (addr)) |
| { |
| case CONST_INT: |
| /* Absolute address (known at compile time). */ |
| code = 0; |
| if (debug_print) |
| fprintf (stderr, "\ncode:%d", code); |
| disp = addr; |
| |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| } |
| |
| if (UNSIGNED_INT_FITS_N_BITS (INTVAL (disp), 20)) |
| { |
| data = DM_DEFAULT; |
| if (debug_print) |
| fprintf (stderr, "\ndata:%d", data); |
| retval = CR16_ABSOLUTE; |
| } |
| else if (UNSIGNED_INT_FITS_N_BITS (INTVAL (disp), 24)) |
| { |
| if (!CR16_TARGET_DATA_NEAR) |
| { |
| data = DM_FAR; |
| if (debug_print) |
| fprintf (stderr, "\ndata:%d", data); |
| retval = CR16_ABSOLUTE; |
| } |
| else |
| return CR16_INVALID; /* ABS24 is not support in NEAR model. */ |
| } |
| else |
| return CR16_INVALID; |
| break; |
| |
| case CONST: |
| /* A CONST is an expression of PLUS or MINUS with |
| CONST_INT, SYMBOL_REF or LABEL_REF. This is the |
| result of assembly-time arithmetic computation. */ |
| retval = CR16_ABSOLUTE; |
| disp = addr; |
| /* Call the helper function to check the validity. */ |
| cr16_decompose_const (XEXP (addr, 0), &code, &data, treat_as_const); |
| if ((code == 0) && (data == ILLEGAL_DM)) |
| /* CONST is not valid code or data address. */ |
| return CR16_INVALID; |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| fprintf (stderr, "\ncode:%d", code); |
| fprintf (stderr, "\ndata:%d", data); |
| } |
| break; |
| |
| case LABEL_REF: |
| retval = CR16_ABSOLUTE; |
| disp = addr; |
| /* 1 - indicates non-function symbol. */ |
| code = 1; |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| fprintf (stderr, "\ncode:%d", code); |
| } |
| break; |
| |
| case SYMBOL_REF: |
| /* Absolute address (known at link time). */ |
| retval = CR16_ABSOLUTE; |
| disp = addr; |
| /* This is a code address if symbol_ref is a function. */ |
| /* 2 indicates func sym. */ |
| code = SYMBOL_REF_FUNCTION_P (addr) ? 2 : 0; |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| fprintf (stderr, "\ncode:%d", code); |
| } |
| /* If not function ref then check if valid data ref. */ |
| if (code == 0) |
| { |
| if (CR16_TARGET_DATA_NEAR) |
| data = DM_DEFAULT; |
| else if (CR16_TARGET_DATA_MEDIUM) |
| data = DM_FAR; |
| else if (CR16_TARGET_DATA_FAR) |
| { |
| if (treat_as_const) |
| /* This will be used only for printing the |
| qualifier. This call is (may be) made |
| by cr16_print_operand_address. */ |
| data = DM_FAR; |
| else |
| /* This call is (may be) made by |
| cr16_legitimate_address_p. */ |
| return CR16_INVALID; |
| } |
| else |
| data = DM_DEFAULT; |
| } |
| if (debug_print) |
| fprintf (stderr, "\ndata:%d", data); |
| break; |
| |
| case REG: |
| case SUBREG: |
| /* Register relative address. */ |
| /* Assume REG fits in a single register. */ |
| retval = CR16_REG_REL; |
| if (GET_MODE_BITSIZE (GET_MODE (addr)) > BITS_PER_WORD) |
| if (!LONG_REG_P (REGNO (addr))) |
| /* REG will result in reg pair. */ |
| retval = CR16_REGP_REL; |
| base = addr; |
| if (debug_print) |
| { |
| fprintf (stderr, "\nbase:"); |
| debug_rtx (base); |
| } |
| break; |
| |
| case PLUS: |
| switch (GET_CODE (XEXP (addr, 0))) |
| { |
| case REG: |
| case SUBREG: |
| /* REG + DISP20. */ |
| /* All Reg relative addresses having a displacement needs |
| to fit in 20-bits. */ |
| disp = XEXP (addr, 1); |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| } |
| switch (GET_CODE (XEXP (addr, 1))) |
| { |
| case CONST_INT: |
| /* Shall fit in 20-bits. */ |
| if (!UNSIGNED_INT_FITS_N_BITS (INTVAL (disp), 20)) |
| return CR16_INVALID; |
| code = 0; |
| if (debug_print) |
| fprintf (stderr, "\ncode:%d", code); |
| break; |
| |
| case UNSPEC: |
| switch (XINT (XEXP (addr, 1), 1)) |
| { |
| case UNSPEC_LIBRARY_OFFSET: |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| |
| case LABEL_REF: |
| case SYMBOL_REF: |
| case CONST: |
| /* This is also a valid expression for address. |
| However, we cannot ascertain if the resultant |
| displacement will be valid 20-bit value. Therefore, |
| lets not allow such an expression for now. This will |
| be updated when we find a way to validate this |
| expression as legitimate address. |
| Till then fall through CR16_INVALID. */ |
| default: |
| return CR16_INVALID; |
| } |
| |
| /* Now check if REG can fit into single or pair regs. */ |
| retval = CR16_REG_REL; |
| base = XEXP (addr, 0); |
| if (debug_print) |
| { |
| fprintf (stderr, "\nbase:"); |
| debug_rtx (base); |
| } |
| if (GET_MODE_BITSIZE (GET_MODE ((XEXP (addr, 0)))) > BITS_PER_WORD) |
| { |
| if (!LONG_REG_P (REGNO ((XEXP (addr, 0))))) |
| /* REG will result in reg pair. */ |
| retval = CR16_REGP_REL; |
| } |
| break; |
| |
| case PLUS: |
| /* Valid expr: |
| plus |
| /\ |
| / \ |
| plus idx |
| /\ |
| / \ |
| reg const_int |
| |
| Check if the operand 1 is valid index register. */ |
| data = ILLEGAL_DM; |
| if (debug_print) |
| fprintf (stderr, "\ndata:%d", data); |
| switch (GET_CODE (XEXP (addr, 1))) |
| { |
| case REG: |
| case SUBREG: |
| if (!REG_OK_FOR_INDEX_P (XEXP (addr, 1))) |
| return CR16_INVALID; |
| /* OK. REG is a valid index register. */ |
| index = XEXP (addr, 1); |
| if (debug_print) |
| { |
| fprintf (stderr, "\nindex:"); |
| debug_rtx (index); |
| } |
| break; |
| default: |
| return CR16_INVALID; |
| } |
| /* Check if operand 0 of operand 0 is REGP. */ |
| switch (GET_CODE (XEXP (XEXP (addr, 0), 0))) |
| { |
| case REG: |
| case SUBREG: |
| /* Now check if REG is a REGP and not in LONG regs. */ |
| if (GET_MODE_BITSIZE (GET_MODE (XEXP (XEXP (addr, 0), 0))) |
| > BITS_PER_WORD) |
| { |
| if (REGNO (XEXP (XEXP (addr, 0), 0)) |
| >= CR16_FIRST_DWORD_REGISTER) |
| return CR16_INVALID; |
| base = XEXP (XEXP (addr, 0), 0); |
| if (debug_print) |
| { |
| fprintf (stderr, "\nbase:"); |
| debug_rtx (base); |
| } |
| } |
| else |
| return CR16_INVALID; |
| break; |
| default: |
| return CR16_INVALID; |
| } |
| /* Now check if the operand 1 of operand 0 is const_int. */ |
| if (GET_CODE (XEXP (XEXP (addr, 0), 1)) == CONST_INT) |
| { |
| disp = XEXP (XEXP (addr, 0), 1); |
| if (debug_print) |
| { |
| fprintf (stderr, "\ndisp:"); |
| debug_rtx (disp); |
| } |
| if (!UNSIGNED_INT_FITS_N_BITS (INTVAL (disp), 20)) |
| return CR16_INVALID; |
| } |
| else |
| return CR16_INVALID; |
| retval = CR16_INDEX_REGP_REL; |
| break; |
| default: |
| return CR16_INVALID; |
| } |
| break; |
| |
| default: |
| return CR16_INVALID; |
| } |
| |
| /* Check if the base and index registers are valid. */ |
| if (base && !(cr16_addr_reg_p (base))) |
| return CR16_INVALID; |
| if (base && !(CR16_REG_OK_FOR_BASE_P (base))) |
| return CR16_INVALID; |
| if (index && !(REG_OK_FOR_INDEX_P (index))) |
| return CR16_INVALID; |
| |
| /* Write the decomposition to out parameter. */ |
| out->base = base; |
| out->disp = disp; |
| out->index = index; |
| out->data = data; |
| out->code = code; |
| |
| return retval; |
| } |
| |
| /* Return non-zero value if 'x' is legitimate PIC operand |
| when generating PIC code. */ |
| int |
| legitimate_pic_operand_p (rtx x) |
| { |
| switch (GET_CODE (x)) |
| { |
| case SYMBOL_REF: |
| return 0; |
| break; |
| case LABEL_REF: |
| return 0; |
| break; |
| case CONST: |
| /* REVISIT: Use something like symbol_referenced_p. */ |
| if (GET_CODE (XEXP (x, 0)) == PLUS |
| && (GET_CODE (XEXP (XEXP (x, 0), 0)) == SYMBOL_REF |
| || GET_CODE (XEXP (XEXP (x, 0), 0)) == LABEL_REF) |
| && (GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT)) |
| return 0; |
| break; |
| case MEM: |
| return legitimate_pic_operand_p (XEXP (x, 0)); |
| break; |
| default: |
| break; |
| } |
| return 1; |
| } |
| |
| /* Convert a non-PIC address in `orig' to a PIC address in `reg'. |
| |
| Input Output (-f pic) Output (-f PIC) |
| orig reg |
| |
| C1 symbol symbol@BRO (r12) symbol@GOT (r12) |
| |
| C2 symbol + offset symbol+offset@BRO (r12) symbol+offset@GOT (r12) |
| |
| NOTE: @BRO is added using unspec:BRO |
| NOTE: @GOT is added using unspec:GOT. */ |
| rtx |
| legitimize_pic_address (rtx orig, machine_mode mode ATTRIBUTE_UNUSED, |
| rtx reg) |
| { |
| /* First handle a simple SYMBOL_REF or LABEL_REF. */ |
| if (GET_CODE (orig) == SYMBOL_REF || GET_CODE (orig) == LABEL_REF) |
| { |
| if (reg == 0) |
| reg = gen_reg_rtx (Pmode); |
| |
| if (flag_pic == NEAR_PIC) |
| { |
| /* Unspec to handle -fpic option. */ |
| emit_insn (gen_unspec_bro_addr (reg, orig)); |
| emit_insn (gen_addsi3 (reg, reg, pic_offset_table_rtx)); |
| } |
| else if (flag_pic == FAR_PIC) |
| { |
| /* Unspec to handle -fPIC option. */ |
| emit_insn (gen_unspec_got_addr (reg, orig)); |
| } |
| return reg; |
| } |
| else if (GET_CODE (orig) == CONST) |
| { |
| /* To handle (symbol + offset). */ |
| rtx base, offset; |
| |
| if (GET_CODE (XEXP (orig, 0)) == PLUS |
| && XEXP (XEXP (orig, 0), 0) == pic_offset_table_rtx) |
| return orig; |
| |
| if (reg == 0) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| } |
| |
| gcc_assert (GET_CODE (XEXP (orig, 0)) == PLUS); |
| |
| base = legitimize_pic_address (XEXP (XEXP (orig, 0), 0), Pmode, reg); |
| offset = legitimize_pic_address (XEXP (XEXP (orig, 0), 1), Pmode, |
| base == reg ? 0 : reg); |
| |
| /* REVISIT: Optimize for const-offsets. */ |
| emit_insn (gen_addsi3 (reg, base, offset)); |
| |
| return reg; |
| } |
| return orig; |
| } |
| |
| /* Implementation of TARGET_LEGITIMATE_ADDRESS_P. */ |
| static bool |
| cr16_legitimate_address_p (machine_mode mode ATTRIBUTE_UNUSED, |
| rtx addr, bool strict) |
| { |
| enum cr16_addrtype addrtype; |
| struct cr16_address address; |
| |
| if (TARGET_DEBUG_ADDR) |
| { |
| fprintf (stderr, |
| "\n======\nTARGET_LEGITIMATE_ADDRESS_P, mode = %s, strict = %d", |
| GET_MODE_NAME (mode), strict); |
| debug_rtx (addr); |
| } |
| addrtype = cr16_decompose_address (addr, &address, |
| (TARGET_DEBUG_ADDR ? 1 : 0), FALSE); |
| |
| if (TARGET_DEBUG_ADDR) |
| { |
| const char *typestr; |
| |
| switch (addrtype) |
| { |
| case CR16_INVALID: |
| typestr = "invalid"; |
| break; |
| case CR16_ABSOLUTE: |
| typestr = "absolute"; |
| break; |
| case CR16_REG_REL: |
| typestr = "register relative"; |
| break; |
| case CR16_REGP_REL: |
| typestr = "register pair relative"; |
| break; |
| case CR16_INDEX_REGP_REL: |
| typestr = "index + register pair relative"; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| fprintf (stderr, "\ncr16 address type: %s\n", typestr); |
| } |
| |
| if (addrtype == CR16_INVALID) |
| return FALSE; |
| |
| if (strict) |
| { |
| if (address.base |
| && !REGNO_MODE_OK_FOR_BASE_P (REGNO (address.base), mode)) |
| { |
| if (TARGET_DEBUG_ADDR) |
| fprintf (stderr, "base register not strict\n"); |
| return FALSE; |
| } |
| if (address.index && !REGNO_OK_FOR_INDEX_P (REGNO (address.index))) |
| { |
| if (TARGET_DEBUG_ADDR) |
| fprintf (stderr, "index register not strict\n"); |
| return FALSE; |
| } |
| } |
| |
| /* Return true if addressing mode is register relative. */ |
| if (flag_pic) |
| { |
| if (addrtype == CR16_REG_REL || addrtype == CR16_REGP_REL) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Routines to compute costs. */ |
| |
| /* Return cost of the memory address x. */ |
| static int |
| cr16_address_cost (rtx addr, machine_mode mode ATTRIBUTE_UNUSED, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed ATTRIBUTE_UNUSED) |
| { |
| enum cr16_addrtype addrtype; |
| struct cr16_address address; |
| int cost = 2; |
| |
| addrtype = cr16_decompose_address (addr, &address, 0, FALSE); |
| |
| gcc_assert (addrtype != CR16_INVALID); |
| |
| /* CR16_ABSOLUTE : 3 |
| CR16_REG_REL (disp !=0) : 4 |
| CR16_REG_REL (disp ==0) : 5 |
| CR16_REGP_REL (disp !=0) : 6 |
| CR16_REGP_REL (disp ==0) : 7 |
| CR16_INDEX_REGP_REL (disp !=0) : 8 |
| CR16_INDEX_REGP_REL (disp ==0) : 9. */ |
| switch (addrtype) |
| { |
| case CR16_ABSOLUTE: |
| cost += 1; |
| break; |
| case CR16_REGP_REL: |
| cost += 2; |
| /* Fall through. */ |
| case CR16_REG_REL: |
| cost += 3; |
| if (address.disp) |
| cost -= 1; |
| break; |
| case CR16_INDEX_REGP_REL: |
| cost += 7; |
| if (address.disp) |
| cost -= 1; |
| default: |
| break; |
| } |
| |
| if (TARGET_DEBUG_ADDR) |
| { |
| fprintf (stderr, "\n======\nmacro TARGET_ADDRESS_COST = %d\n", cost); |
| debug_rtx (addr); |
| } |
| |
| return cost; |
| } |
| |
| |
| /* Implement `TARGET_REGISTER_MOVE_COST'. */ |
| static int |
| cr16_register_move_cost (machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t from ATTRIBUTE_UNUSED, reg_class_t to) |
| { |
| return (to != GENERAL_REGS ? 8 : 2); |
| } |
| |
| /* Implement `TARGET_MEMORY_MOVE_COST'. */ |
| |
| /* Return the cost of moving data of mode MODE between a register of class |
| CLASS and memory; IN is zero if the value is to be written to memory, |
| nonzero if it is to be read in. This cost is relative to those in |
| REGISTER_MOVE_COST. */ |
| static int |
| cr16_memory_move_cost (machine_mode mode, |
| reg_class_t rclass ATTRIBUTE_UNUSED, |
| bool in ATTRIBUTE_UNUSED) |
| { |
| /* One LD or ST takes twice the time of a simple reg-reg move. */ |
| if (reg_classes_intersect_p (rclass, GENERAL_REGS)) |
| return (4 * HARD_REGNO_NREGS (0, mode)); |
| else |
| return (100); |
| } |
| |
| /* Instruction output. */ |
| |
| /* Check if a const_double is ok for cr16 store-immediate instructions. */ |
| int |
| cr16_const_double_ok (rtx op) |
| { |
| if (GET_MODE (op) == SFmode) |
| { |
| REAL_VALUE_TYPE r; |
| long l; |
| REAL_VALUE_FROM_CONST_DOUBLE (r, op); |
| REAL_VALUE_TO_TARGET_SINGLE (r, l); |
| return UNSIGNED_INT_FITS_N_BITS (l, 4) ? 1 : 0; |
| } |
| |
| return ((UNSIGNED_INT_FITS_N_BITS (CONST_DOUBLE_LOW (op), 4)) && |
| (UNSIGNED_INT_FITS_N_BITS (CONST_DOUBLE_HIGH (op), 4))) ? 1 : 0; |
| } |
| |
| /* Returns bit position of first 0 or 1 bit. |
| It is safe to assume val as 16-bit wide. */ |
| int |
| cr16_operand_bit_pos (int val, int bitval) |
| { |
| int i; |
| if (bitval == 0) |
| val = ~val; |
| |
| for (i = 0; i < 16; i++) |
| if (val & (1 << i)) |
| break; |
| return i; |
| } |
| |
| /* Implements the macro PRINT_OPERAND defined in cr16.h. */ |
| static void |
| cr16_print_operand (FILE * file, rtx x, int code) |
| { |
| int ptr_dereference = 0; |
| |
| switch (code) |
| { |
| case 'd': |
| { |
| const char *cr16_cmp_str; |
| switch (GET_CODE (x)) |
| { |
| /* MD: compare (reg, reg or imm) but CR16: cmp (reg or imm, reg) |
| -> swap all non symmetric ops. */ |
| case EQ: |
| cr16_cmp_str = "eq"; |
| break; |
| case NE: |
| cr16_cmp_str = "ne"; |
| break; |
| case GT: |
| cr16_cmp_str = "lt"; |
| break; |
| case GTU: |
| cr16_cmp_str = "lo"; |
| break; |
| case LT: |
| cr16_cmp_str = "gt"; |
| break; |
| case LTU: |
| cr16_cmp_str = "hi"; |
| break; |
| case GE: |
| cr16_cmp_str = "le"; |
| break; |
| case GEU: |
| cr16_cmp_str = "ls"; |
| break; |
| case LE: |
| cr16_cmp_str = "ge"; |
| break; |
| case LEU: |
| cr16_cmp_str = "hs"; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| fprintf (file, "%s", cr16_cmp_str); |
| return; |
| } |
| case '$': |
| putc ('$', file); |
| return; |
| |
| case 'p': |
| if (GET_CODE (x) == REG) |
| { |
| /* For Push instructions, we should not print register pairs. */ |
| fprintf (file, "%s", reg_names[REGNO (x)]); |
| return; |
| } |
| break; |
| |
| case 'b': |
| /* Print the immediate address for bal |
| 'b' is used instead of 'a' to avoid compiler calling |
| the GO_IF_LEGITIMATE_ADDRESS which cannot |
| perform checks on const_int code addresses as it |
| assumes all const_int are data addresses. */ |
| fprintf (file, "0x%lx", INTVAL (x)); |
| return; |
| |
| case 'r': |
| /* Print bit position of first 0. */ |
| fprintf (file, "%d", cr16_operand_bit_pos (INTVAL (x), 0)); |
| return; |
| |
| case 's': |
| /* Print bit position of first 1. */ |
| fprintf (file, "%d", cr16_operand_bit_pos (INTVAL (x), 1)); |
| return; |
| case 'g': |
| /* 'g' is used for implicit mem: dereference. */ |
| ptr_dereference = 1; |
| case 'f': |
| case 0: |
| /* default. */ |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| if (GET_MODE_BITSIZE (GET_MODE (x)) > BITS_PER_WORD) |
| { |
| if (LONG_REG_P (REGNO (x))) |
| fprintf (file, "(%s)", reg_names[REGNO (x)]); |
| else |
| fprintf (file, "(%s,%s)", reg_names[REGNO (x) + 1], |
| reg_names[REGNO (x)]); |
| } |
| else |
| fprintf (file, "%s", reg_names[REGNO (x)]); |
| return; |
| |
| case MEM: |
| output_address (XEXP (x, 0)); |
| return; |
| |
| case CONST_DOUBLE: |
| { |
| REAL_VALUE_TYPE r; |
| long l; |
| |
| REAL_VALUE_FROM_CONST_DOUBLE (r, x); |
| REAL_VALUE_TO_TARGET_SINGLE (r, l); |
| |
| fprintf (file, "$0x%lx", l); |
| return; |
| } |
| case CONST_INT: |
| { |
| fprintf (file, "$%ld", INTVAL (x)); |
| return; |
| } |
| case UNSPEC: |
| switch (XINT (x, 1)) |
| { |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| |
| default: |
| if (!ptr_dereference) |
| { |
| putc ('$', file); |
| } |
| cr16_print_operand_address (file, x); |
| return; |
| } |
| default: |
| output_operand_lossage ("invalid %%xn code"); |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| /* Implements the macro PRINT_OPERAND_ADDRESS defined in cr16.h. */ |
| |
| static void |
| cr16_print_operand_address (FILE * file, rtx addr) |
| { |
| enum cr16_addrtype addrtype; |
| struct cr16_address address; |
| |
| /* Decompose the address. Also ask it to treat address as constant. */ |
| addrtype = cr16_decompose_address (addr, &address, 0, TRUE); |
| |
| if (address.disp && GET_CODE (address.disp) == UNSPEC) |
| { |
| debug_rtx (addr); |
| } |
| |
| switch (addrtype) |
| { |
| case CR16_REG_REL: |
| if (address.disp) |
| { |
| if (GET_CODE (address.disp) == UNSPEC) |
| cr16_print_operand (file, address.disp, 0); |
| else |
| output_addr_const (file, address.disp); |
| } |
| else |
| fprintf (file, "0"); |
| fprintf (file, "(%s)", reg_names[REGNO (address.base)]); |
| break; |
| |
| case CR16_ABSOLUTE: |
| if (address.disp) |
| output_addr_const (file, address.disp); |
| else |
| fprintf (file, "0"); |
| break; |
| |
| case CR16_INDEX_REGP_REL: |
| fprintf (file, "[%s]", reg_names[REGNO (address.index)]); |
| /* Fall through. */ |
| case CR16_REGP_REL: |
| if (address.disp) |
| { |
| if (GET_CODE (address.disp) == UNSPEC) |
| cr16_print_operand (file, address.disp, 0); |
| else |
| output_addr_const (file, address.disp); |
| } |
| else |
| fprintf (file, "0"); |
| fprintf (file, "(%s,%s)", reg_names[REGNO (address.base) + 1], |
| reg_names[REGNO (address.base)]); |
| break; |
| default: |
| debug_rtx (addr); |
| gcc_unreachable (); |
| } |
| /* Add qualifiers to the address expression that was just printed. */ |
| if (flag_pic < NEAR_PIC && address.code == 0) |
| { |
| if (address.data == DM_FAR) |
| /* Addr contains SYMBOL_REF & far data ptr. */ |
| fprintf (file, "@l"); |
| else if (address.data == DM_DEFAULT) |
| /* Addr contains SYMBOL_REF & medium data ptr. */ |
| fprintf (file, "@m"); |
| /* Addr contains SYMBOL_REF & medium data ptr. */ |
| else if (address.data == DM_NEAR) |
| /* Addr contains SYMBOL_REF & near data ptr. */ |
| fprintf (file, "@s"); |
| } |
| else if (flag_pic == NEAR_PIC |
| && (address.code == 0) && (address.data == DM_FAR |
| || address.data == DM_DEFAULT |
| || address.data == DM_NEAR)) |
| { |
| fprintf (file, "@l"); |
| } |
| else if (flag_pic == NEAR_PIC && address.code == 2) |
| { |
| fprintf (file, "pic"); |
| } |
| else if (flag_pic == NEAR_PIC && address.code == 1) |
| { |
| fprintf (file, "@cpic"); |
| } |
| |
| else if (flag_pic == FAR_PIC && address.code == 2) |
| { |
| /* REVISIT: cr16 register indirect jump expects a 1-bit right shifted |
| address ! GOTc tells assembler this symbol is a text-address |
| This needs to be fixed in such a way that this offset is done |
| only in the case where an address is being used for indirect jump |
| or call. Determining the potential usage of loadd is of course not |
| possible always. Eventually, this has to be fixed in the |
| processor. */ |
| fprintf (file, "GOT (%s)", reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| } |
| else if (flag_pic == FAR_PIC && address.code == 1) |
| { |
| fprintf (file, "@cGOT (%s)", reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| } |
| |
| else if (flag_pic == FAR_PIC && |
| (address.data == DM_FAR || address.data == DM_DEFAULT |
| || address.data == DM_NEAR)) |
| { |
| fprintf (file, "@GOT (%s)", reg_names[PIC_OFFSET_TABLE_REGNUM]); |
| } |
| } |
| |
| /* Machine description helper functions. */ |
| |
| /* Called from cr16.md. The return value depends on the parameter push_or_pop: |
| When push_or_pop is zero -> string for push instructions of prologue. |
| When push_or_pop is nonzero -> string for pop/popret/retx in epilogue. |
| Relies on the assumptions: |
| 1. RA is the last register to be saved. |
| 2. The maximal value of the counter is MAX_COUNT. */ |
| char * |
| cr16_prepare_push_pop_string (int push_or_pop) |
| { |
| /* j is the number of registers being saved, takes care that there won't be |
| more than 8 in one push/pop instruction. */ |
| |
| /* For the register mask string. */ |
| static char one_inst_str[50]; |
| |
| /* i is the index of current_frame_info.save_regs[], going from 0 until |
| current_frame_info.last_reg_to_save. */ |
| int i, start_reg; |
| int word_cnt; |
| int print_ra; |
| char *return_str; |
| |
| /* For reversing on the push instructions if there are more than one. */ |
| char *temp_str; |
| |
| return_str = (char *) xmalloc (160); |
| temp_str = (char *) xmalloc (160); |
| |
| /* Initialize. */ |
| memset (return_str, 0, 3); |
| |
| i = 0; |
| while (i <= current_frame_info.last_reg_to_save) |
| { |
| /* Prepare mask for one instruction. */ |
| one_inst_str[0] = 0; |
| |
| /* To count number of words in one instruction. */ |
| word_cnt = 0; |
| start_reg = i; |
| print_ra = 0; |
| while ((word_cnt < MAX_COUNT) |
| && (i <= current_frame_info.last_reg_to_save)) |
| { |
| /* For each non consecutive save register, |
| a new instruction shall be generated. */ |
| if (!current_frame_info.save_regs[i]) |
| { |
| /* Move to next reg and break. */ |
| ++i; |
| break; |
| } |
| |
| if (i == RETURN_ADDRESS_REGNUM) |
| print_ra = 1; |
| else |
| { |
| /* Check especially if adding 2 does not cross the MAX_COUNT. */ |
| if ((word_cnt + ((i < CR16_FIRST_DWORD_REGISTER) ? 1 : 2)) |
| >= MAX_COUNT) |
| break; |
| /* Increase word count by 2 for long registers except RA. */ |
| word_cnt += ((i < CR16_FIRST_DWORD_REGISTER) ? 1 : 2); |
| } |
| ++i; |
| } |
| |
| /* No need to generate any instruction as |
| no register or RA needs to be saved. */ |
| if ((word_cnt == 0) && (print_ra == 0)) |
| continue; |
| |
| /* Now prepare the instruction operands. */ |
| if (word_cnt > 0) |
| { |
| sprintf (one_inst_str, "$%d, %s", word_cnt, reg_names[start_reg]); |
| if (print_ra) |
| strcat (one_inst_str, ", ra"); |
| } |
| else |
| strcat (one_inst_str, "ra"); |
| |
| if (push_or_pop == 1) |
| { |
| /* Pop instruction. */ |
| if (print_ra && !cr16_interrupt_function_p () |
| && !crtl->calls_eh_return) |
| /* Print popret if RA is saved and its not a interrupt |
| function. */ |
| strcpy (temp_str, "\n\tpopret\t"); |
| else |
| strcpy (temp_str, "\n\tpop\t"); |
| |
| strcat (temp_str, one_inst_str); |
| |
| /* Add the pop instruction list. */ |
| strcat (return_str, temp_str); |
| } |
| else |
| { |
| /* Push instruction. */ |
| strcpy (temp_str, "\n\tpush\t"); |
| strcat (temp_str, one_inst_str); |
| |
| /* We need to reverse the order of the instructions if there |
| are more than one. (since the pop will not be reversed in |
| the epilogue. */ |
| strcat (temp_str, return_str); |
| strcpy (return_str, temp_str); |
| } |
| } |
| |
| if (push_or_pop == 1) |
| { |
| /* POP. */ |
| if (cr16_interrupt_function_p ()) |
| strcat (return_str, "\n\tretx\n"); |
| else if (crtl->calls_eh_return) |
| { |
| /* Add stack adjustment before returning to exception handler |
| NOTE: EH_RETURN_STACKADJ_RTX must refer to (r5, r4). */ |
| strcat (return_str, "\n\taddd\t (r5, r4), (sp)\t\n"); |
| strcat (return_str, "\n\tjump\t (ra)\n"); |
| |
| /* But before anything else, undo the adjustment addition done in |
| cr16_expand_epilogue (). */ |
| strcpy (temp_str, "\n\tsubd\t (r5, r4), (sp)\t\n"); |
| strcat (temp_str, return_str); |
| strcpy (return_str, temp_str); |
| } |
| else if (!FUNC_IS_NORETURN_P (current_function_decl) |
| && !(current_frame_info.save_regs[RETURN_ADDRESS_REGNUM])) |
| strcat (return_str, "\n\tjump\t (ra)\n"); |
| } |
| |
| /* Skip the newline and the tab in the start of return_str. */ |
| return_str += 2; |
| return return_str; |
| } |
| |
| |
| /* Generate DWARF2 annotation for multi-push instruction. */ |
| static void |
| cr16_create_dwarf_for_multi_push (rtx insn) |
| { |
| rtx dwarf, reg, tmp; |
| int i, j, from, to, word_cnt, dwarf_par_index, inc; |
| machine_mode mode; |
| int num_regs = 0, offset = 0, split_here = 0, total_push_bytes = 0; |
| |
| for (i = 0; i <= current_frame_info.last_reg_to_save; ++i) |
| { |
| if (current_frame_info.save_regs[i]) |
| { |
| ++num_regs; |
| if (i < CR16_FIRST_DWORD_REGISTER) |
| total_push_bytes += 2; |
| else |
| total_push_bytes += 4; |
| } |
| } |
| |
| if (!num_regs) |
| return; |
| |
| dwarf = gen_rtx_SEQUENCE (VOIDmode, rtvec_alloc (num_regs + 1)); |
| dwarf_par_index = num_regs; |
| |
| from = current_frame_info.last_reg_to_save + 1; |
| to = current_frame_info.last_reg_to_save; |
| word_cnt = 0; |
| |
| for (i = current_frame_info.last_reg_to_save; i >= 0;) |
| { |
| if (!current_frame_info.save_regs[i] || 0 == i || split_here) |
| { |
| /* This block of regs is pushed in one instruction. */ |
| if (0 == i && current_frame_info.save_regs[i]) |
| from = 0; |
| |
| for (j = to; j >= from; --j) |
| { |
| if (j < CR16_FIRST_DWORD_REGISTER) |
| { |
| mode = HImode; |
| inc = 1; |
| } |
| else |
| { |
| mode = SImode; |
| inc = 2; |
| } |
| reg = gen_rtx_REG (mode, j); |
| offset += 2 * inc; |
| tmp = gen_rtx_SET (VOIDmode, |
| gen_frame_mem (mode, |
| plus_constant |
| (Pmode, stack_pointer_rtx, |
| total_push_bytes - offset)), |
| reg); |
| RTX_FRAME_RELATED_P (tmp) = 1; |
| XVECEXP (dwarf, 0, dwarf_par_index--) = tmp; |
| } |
| from = i; |
| to = --i; |
| split_here = 0; |
| word_cnt = 0; |
| continue; |
| } |
| |
| if (i != RETURN_ADDRESS_REGNUM) |
| { |
| inc = (i < CR16_FIRST_DWORD_REGISTER) ? 1 : 2; |
| if (word_cnt + inc >= MAX_COUNT || FRAME_POINTER_REGNUM == i) |
| { |
| split_here = 1; |
| from = i; |
| continue; |
| } |
| word_cnt += inc; |
| } |
| |
| from = i--; |
| } |
| |
| tmp = gen_rtx_SET (SImode, stack_pointer_rtx, |
| gen_rtx_PLUS (SImode, stack_pointer_rtx, |
| GEN_INT (-offset))); |
| RTX_FRAME_RELATED_P (tmp) = 1; |
| XVECEXP (dwarf, 0, 0) = tmp; |
| |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, dwarf); |
| } |
| |
| /* |
| CompactRISC CR16 Architecture stack layout: |
| |
| 0 +--------------------- |
| | |
| . |
| . |
| | |
| +==================== Sp (x) = Ap (x+1) |
| A | Args for functions |
| | | called by X and Dynamically |
| | | Dynamic allocations allocated and |
| | | (alloca, variable deallocated |
| Stack | length arrays). |
| grows +-------------------- Fp (x) |
| down| | Local variables of X |
| ward| +-------------------- |
| | | Regs saved for X-1 |
| | +==================== Sp (x-1) = Ap (x) |
| | Args for func X |
| | pushed by X-1 |
| +-------------------- Fp (x-1) |
| | |
| | |
| V |
| */ |
| void |
| cr16_expand_prologue (void) |
| { |
| rtx insn; |
| |
| cr16_compute_frame (); |
| cr16_compute_save_regs (); |
| |
| /* If there is no need in push and adjustment to sp, return. */ |
| if ((current_frame_info.total_size + current_frame_info.reg_size) == 0) |
| return; |
| |
| if (current_frame_info.last_reg_to_save != -1) |
| { |
| /* If there are registers to push. */ |
| insn = emit_insn (gen_push_for_prologue |
| (GEN_INT (current_frame_info.reg_size))); |
| cr16_create_dwarf_for_multi_push (insn); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| |
| if (current_frame_info.total_size > 0) |
| { |
| insn = emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (-current_frame_info.total_size))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| if (frame_pointer_needed) |
| { |
| /* Initialize the frame pointer with the value of the stack pointer |
| pointing now to the locals. */ |
| insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx); |
| } |
| } |
| |
| /* Generate insn that updates the stack for local variables and padding |
| for registers we save. - Generate the appropriate return insn. */ |
| void |
| cr16_expand_epilogue (void) |
| { |
| rtx insn; |
| |
| /* Nonzero if we need to return and pop only RA. This will generate a |
| different insn. This differentiate is for the peepholes for call as |
| last statement in function. */ |
| int only_popret_RA = (current_frame_info.save_regs[RETURN_ADDRESS_REGNUM] |
| && (current_frame_info.reg_size |
| == CR16_UNITS_PER_DWORD)); |
| |
| if (frame_pointer_needed) |
| { |
| /* Restore the stack pointer with the frame pointers value. */ |
| insn = emit_move_insn (stack_pointer_rtx, frame_pointer_rtx); |
| } |
| |
| if (current_frame_info.total_size > 0) |
| { |
| insn = emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (current_frame_info.total_size))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| if (crtl->calls_eh_return) |
| { |
| /* Add this here so that (r5, r4) is actually loaded with the adjustment |
| value; otherwise, the load might be optimized away... |
| NOTE: remember to subtract the adjustment before popping the regs |
| and add it back before returning. */ |
| insn = emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| EH_RETURN_STACKADJ_RTX)); |
| } |
| |
| if (cr16_interrupt_function_p ()) |
| { |
| insn = emit_jump_insn (gen_interrupt_return ()); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| else if (crtl->calls_eh_return) |
| { |
| /* Special case, pop what's necessary, adjust SP and jump to (RA). */ |
| insn = emit_jump_insn (gen_pop_and_popret_return |
| (GEN_INT (current_frame_info.reg_size))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| else if (current_frame_info.last_reg_to_save == -1) |
| /* Nothing to pop. */ |
| /* Don't output jump for interrupt routine, only retx. */ |
| emit_jump_insn (gen_jump_return ()); |
| else if (only_popret_RA) |
| { |
| insn = emit_jump_insn (gen_popret_RA_return ()); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| else |
| { |
| insn = emit_jump_insn (gen_pop_and_popret_return |
| (GEN_INT (current_frame_info.reg_size))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| /* Implements FRAME_POINTER_REQUIRED. */ |
| static bool |
| cr16_frame_pointer_required (void) |
| { |
| return (cfun->calls_alloca || crtl->calls_eh_return |
| || cfun->has_nonlocal_label || crtl->calls_eh_return); |
| } |
| |
| static bool |
| cr16_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| return (to == STACK_POINTER_REGNUM ? !frame_pointer_needed : true); |
| } |
| |
| |
| /* A C compound statement that attempts to replace X with |
| a valid memory address for an operand of mode MODE. WIN |
| will be a C statement label elsewhere in the code. |
| X will always be the result of a call to break_out_memory_refs (), |
| and OLDX will be the operand that was given to that function to |
| produce X. |
| The code generated by this macro should not alter the |
| substructure of X. If it transforms X into a more legitimate form, |
| it should assign X (which will always be a C variable) a new value. */ |
| static rtx |
| cr16_legitimize_address (rtx x, rtx orig_x ATTRIBUTE_UNUSED, |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (flag_pic) |
| return legitimize_pic_address (orig_x, mode, NULL_RTX); |
| else |
| return x; |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P |
| Nonzero if X is a legitimate constant for an immediate |
| operand on the target machine. You can assume that X |
| satisfies CONSTANT_P. In cr16c treat legitimize float |
| constant as an immediate operand. */ |
| static bool |
| cr16_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x ATTRIBUTE_UNUSED) |
| { |
| return 1; |
| } |
| |
| void |
| notice_update_cc (rtx exp) |
| { |
| if (GET_CODE (exp) == SET) |
| { |
| /* Jumps do not alter the cc's. */ |
| if (SET_DEST (exp) == pc_rtx) |
| return; |
| |
| /* Moving register or memory into a register: |
| it doesn't alter the cc's, but it might invalidate |
| the RTX's which we remember the cc's came from. |
| (Note that moving a constant 0 or 1 MAY set the cc's). */ |
| if (REG_P (SET_DEST (exp)) |
| && (REG_P (SET_SRC (exp)) || GET_CODE (SET_SRC (exp)) == MEM)) |
| { |
| return; |
| } |
| |
| /* Moving register into memory doesn't alter the cc's. |
| It may invalidate the RTX's which we remember the cc's came from. */ |
| if (GET_CODE (SET_DEST (exp)) == MEM && REG_P (SET_SRC (exp))) |
| { |
| return; |
| } |
| } |
| |
| CC_STATUS_INIT; |
| return; |
| } |
| |
| static machine_mode |
| cr16_unwind_word_mode (void) |
| { |
| return SImode; |
| } |
| |
| /* Helper function for md file. This function is used to emit arithmetic |
| DI instructions. The argument "num" decides which instruction to be |
| printed. */ |
| const char * |
| cr16_emit_add_sub_di (rtx *operands, enum rtx_code code) |
| { |
| rtx lo_op[2] ; |
| rtx hi0_op[2] ; |
| rtx hi1_op[2] ; |
| |
| lo_op[0] = gen_lowpart (SImode, operands[0]); |
| hi0_op[0] = simplify_gen_subreg (HImode, operands[0], DImode, 4); |
| hi1_op[0] = simplify_gen_subreg (HImode, operands[0], DImode, 6); |
| |
| lo_op[1] = gen_lowpart (SImode, operands[2]); |
| hi0_op[1] = simplify_gen_subreg (HImode, operands[2], DImode, 4); |
| hi1_op[1] = simplify_gen_subreg (HImode, operands[2], DImode, 6); |
| |
| switch (code) |
| { |
| case PLUS: |
| { |
| output_asm_insn ("addd\t%1, %0", lo_op) ; |
| output_asm_insn ("addcw\t%1, %0", hi0_op) ; |
| output_asm_insn ("addcw\t%1, %0", hi1_op) ; |
| break; |
| } |
| case MINUS: |
| { |
| output_asm_insn ("subd\t%1, %0", lo_op) ; |
| output_asm_insn ("subcw\t%1, %0", hi0_op) ; |
| output_asm_insn ("subcw\t%1, %0", hi1_op) ; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| |
| /* Helper function for md file. This function is used to emit logical |
| DI instructions. The argument "num" decides which instruction to be |
| printed. */ |
| const char * |
| cr16_emit_logical_di (rtx *operands, enum rtx_code code) |
| { |
| rtx lo_op[2] ; |
| rtx hi_op[2] ; |
| |
| lo_op[0] = gen_lowpart (SImode, operands[0]); |
| hi_op[0] = simplify_gen_subreg (SImode, operands[0], DImode, 4); |
| |
| lo_op[1] = gen_lowpart (SImode, operands[2]); |
| hi_op[1] = simplify_gen_subreg (SImode, operands[2], DImode, 4); |
| |
| switch (code) |
| { |
| case AND: |
| { |
| output_asm_insn ("andd\t%1, %0", lo_op) ; |
| output_asm_insn ("andd\t%1, %0", hi_op) ; |
| return ""; |
| } |
| case IOR: |
| { |
| output_asm_insn ("ord\t%1, %0", lo_op) ; |
| output_asm_insn ("ord\t%1, %0", hi_op) ; |
| return ""; |
| } |
| case XOR: |
| { |
| output_asm_insn ("xord\t%1, %0", lo_op) ; |
| output_asm_insn ("xord\t%1, %0", hi_op) ; |
| return ""; |
| } |
| default: |
| break; |
| } |
| |
| return ""; |
| } |
| |
| /* Initialize 'targetm' variable which contains pointers to functions |
| and data relating to the target machine. */ |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |