| /* Subroutines used for code generation on the Lattice Mico32 architecture. |
| Contributed by Jon Beniston <jon@beniston.com> |
| |
| Copyright (C) 2009-2021 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published |
| by the Free Software Foundation; either version 3, or (at your |
| option) any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "optabs.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "output.h" |
| #include "calls.h" |
| #include "alias.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "tm-constrs.h" |
| #include "builtins.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| struct lm32_frame_info |
| { |
| HOST_WIDE_INT total_size; /* number of bytes of entire frame. */ |
| HOST_WIDE_INT callee_size; /* number of bytes to save callee saves. */ |
| HOST_WIDE_INT pretend_size; /* number of bytes we pretend caller did. */ |
| HOST_WIDE_INT args_size; /* number of bytes for outgoing arguments. */ |
| HOST_WIDE_INT locals_size; /* number of bytes for local variables. */ |
| unsigned int reg_save_mask; /* mask of saved registers. */ |
| }; |
| |
| /* Prototypes for static functions. */ |
| static rtx emit_add (rtx dest, rtx src0, rtx src1); |
| static void expand_save_restore (struct lm32_frame_info *info, int op); |
| static void stack_adjust (HOST_WIDE_INT amount); |
| static bool lm32_in_small_data_p (const_tree); |
| static void lm32_setup_incoming_varargs (cumulative_args_t cum, |
| const function_arg_info &, |
| int *pretend_size, int no_rtl); |
| static bool lm32_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno, |
| int *total, bool speed); |
| static bool lm32_can_eliminate (const int, const int); |
| static bool |
| lm32_legitimate_address_p (machine_mode mode, rtx x, bool strict); |
| static HOST_WIDE_INT lm32_compute_frame_size (int size); |
| static void lm32_option_override (void); |
| static rtx lm32_function_arg (cumulative_args_t, const function_arg_info &); |
| static void lm32_function_arg_advance (cumulative_args_t cum, |
| const function_arg_info &); |
| static bool lm32_hard_regno_mode_ok (unsigned int, machine_mode); |
| static bool lm32_modes_tieable_p (machine_mode, machine_mode); |
| static HOST_WIDE_INT lm32_starting_frame_offset (void); |
| |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE lm32_option_override |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST hook_int_rtx_mode_as_bool_0 |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS lm32_rtx_costs |
| #undef TARGET_IN_SMALL_DATA_P |
| #define TARGET_IN_SMALL_DATA_P lm32_in_small_data_p |
| #undef TARGET_PROMOTE_FUNCTION_MODE |
| #define TARGET_PROMOTE_FUNCTION_MODE default_promote_function_mode_always_promote |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS lm32_setup_incoming_varargs |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG lm32_function_arg |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE lm32_function_arg_advance |
| #undef TARGET_PROMOTE_PROTOTYPES |
| #define TARGET_PROMOTE_PROTOTYPES hook_bool_const_tree_true |
| #undef TARGET_MIN_ANCHOR_OFFSET |
| #define TARGET_MIN_ANCHOR_OFFSET -0x8000 |
| #undef TARGET_MAX_ANCHOR_OFFSET |
| #define TARGET_MAX_ANCHOR_OFFSET 0x7fff |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE lm32_can_eliminate |
| #undef TARGET_LRA_P |
| #define TARGET_LRA_P hook_bool_void_false |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P lm32_legitimate_address_p |
| #undef TARGET_HARD_REGNO_MODE_OK |
| #define TARGET_HARD_REGNO_MODE_OK lm32_hard_regno_mode_ok |
| #undef TARGET_MODES_TIEABLE_P |
| #define TARGET_MODES_TIEABLE_P lm32_modes_tieable_p |
| |
| #undef TARGET_CONSTANT_ALIGNMENT |
| #define TARGET_CONSTANT_ALIGNMENT constant_alignment_word_strings |
| |
| #undef TARGET_STARTING_FRAME_OFFSET |
| #define TARGET_STARTING_FRAME_OFFSET lm32_starting_frame_offset |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| /* Current frame information calculated by lm32_compute_frame_size. */ |
| static struct lm32_frame_info current_frame_info; |
| |
| /* Return non-zero if the given return type should be returned in memory. */ |
| |
| int |
| lm32_return_in_memory (tree type) |
| { |
| HOST_WIDE_INT size; |
| |
| if (!AGGREGATE_TYPE_P (type)) |
| { |
| /* All simple types are returned in registers. */ |
| return 0; |
| } |
| |
| size = int_size_in_bytes (type); |
| if (size >= 0 && size <= UNITS_PER_WORD) |
| { |
| /* If it can fit in one register. */ |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Generate an emit a word sized add instruction. */ |
| |
| static rtx |
| emit_add (rtx dest, rtx src0, rtx src1) |
| { |
| rtx insn; |
| insn = emit_insn (gen_addsi3 (dest, src0, src1)); |
| return insn; |
| } |
| |
| /* Generate the code to compare (and possibly branch) two integer values |
| TEST_CODE is the comparison code we are trying to emulate |
| (or implement directly) |
| RESULT is where to store the result of the comparison, |
| or null to emit a branch |
| CMP0 CMP1 are the two comparison operands |
| DESTINATION is the destination of the branch, or null to only compare |
| */ |
| |
| static void |
| gen_int_relational (enum rtx_code code, |
| rtx result, |
| rtx cmp0, |
| rtx cmp1, |
| rtx destination) |
| { |
| machine_mode mode; |
| int branch_p; |
| |
| mode = GET_MODE (cmp0); |
| if (mode == VOIDmode) |
| mode = GET_MODE (cmp1); |
| |
| /* Is this a branch or compare. */ |
| branch_p = (destination != 0); |
| |
| /* Instruction set doesn't support LE or LT, so swap operands and use |
| GE, GT. */ |
| switch (code) |
| { |
| case LE: |
| case LT: |
| case LEU: |
| case LTU: |
| { |
| rtx temp; |
| |
| code = swap_condition (code); |
| temp = cmp0; |
| cmp0 = cmp1; |
| cmp1 = temp; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| if (branch_p) |
| { |
| rtx insn, cond, label; |
| |
| /* Operands must be in registers. */ |
| if (!register_operand (cmp0, mode)) |
| cmp0 = force_reg (mode, cmp0); |
| if (!register_operand (cmp1, mode)) |
| cmp1 = force_reg (mode, cmp1); |
| |
| /* Generate conditional branch instruction. */ |
| cond = gen_rtx_fmt_ee (code, mode, cmp0, cmp1); |
| label = gen_rtx_LABEL_REF (VOIDmode, destination); |
| insn = gen_rtx_SET (pc_rtx, gen_rtx_IF_THEN_ELSE (VOIDmode, |
| cond, label, pc_rtx)); |
| emit_jump_insn (insn); |
| } |
| else |
| { |
| /* We can't have const_ints in cmp0, other than 0. */ |
| if ((GET_CODE (cmp0) == CONST_INT) && (INTVAL (cmp0) != 0)) |
| cmp0 = force_reg (mode, cmp0); |
| |
| /* If the comparison is against an int not in legal range |
| move it into a register. */ |
| if (GET_CODE (cmp1) == CONST_INT) |
| { |
| switch (code) |
| { |
| case EQ: |
| case NE: |
| case LE: |
| case LT: |
| case GE: |
| case GT: |
| if (!satisfies_constraint_K (cmp1)) |
| cmp1 = force_reg (mode, cmp1); |
| break; |
| case LEU: |
| case LTU: |
| case GEU: |
| case GTU: |
| if (!satisfies_constraint_L (cmp1)) |
| cmp1 = force_reg (mode, cmp1); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Generate compare instruction. */ |
| emit_move_insn (result, gen_rtx_fmt_ee (code, mode, cmp0, cmp1)); |
| } |
| } |
| |
| /* Try performing the comparison in OPERANDS[1], whose arms are OPERANDS[2] |
| and OPERAND[3]. Store the result in OPERANDS[0]. */ |
| |
| void |
| lm32_expand_scc (rtx operands[]) |
| { |
| rtx target = operands[0]; |
| enum rtx_code code = GET_CODE (operands[1]); |
| rtx op0 = operands[2]; |
| rtx op1 = operands[3]; |
| |
| gen_int_relational (code, target, op0, op1, NULL_RTX); |
| } |
| |
| /* Compare OPERANDS[1] with OPERANDS[2] using comparison code |
| CODE and jump to OPERANDS[3] if the condition holds. */ |
| |
| void |
| lm32_expand_conditional_branch (rtx operands[]) |
| { |
| enum rtx_code code = GET_CODE (operands[0]); |
| rtx op0 = operands[1]; |
| rtx op1 = operands[2]; |
| rtx destination = operands[3]; |
| |
| gen_int_relational (code, NULL_RTX, op0, op1, destination); |
| } |
| |
| /* Generate and emit RTL to save or restore callee save registers. */ |
| static void |
| expand_save_restore (struct lm32_frame_info *info, int op) |
| { |
| unsigned int reg_save_mask = info->reg_save_mask; |
| int regno; |
| HOST_WIDE_INT offset; |
| rtx insn; |
| |
| /* Callee saves are below locals and above outgoing arguments. */ |
| offset = info->args_size + info->callee_size; |
| for (regno = 0; regno <= 31; regno++) |
| { |
| if ((reg_save_mask & (1 << regno)) != 0) |
| { |
| rtx offset_rtx; |
| rtx mem; |
| |
| offset_rtx = GEN_INT (offset); |
| if (satisfies_constraint_K (offset_rtx)) |
| { |
| mem = gen_rtx_MEM (word_mode, |
| gen_rtx_PLUS (Pmode, |
| stack_pointer_rtx, |
| offset_rtx)); |
| } |
| else |
| { |
| /* r10 is caller saved so it can be used as a temp reg. */ |
| rtx r10; |
| |
| r10 = gen_rtx_REG (word_mode, 10); |
| insn = emit_move_insn (r10, offset_rtx); |
| if (op == 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| insn = emit_add (r10, r10, stack_pointer_rtx); |
| if (op == 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| mem = gen_rtx_MEM (word_mode, r10); |
| } |
| |
| if (op == 0) |
| insn = emit_move_insn (mem, gen_rtx_REG (word_mode, regno)); |
| else |
| insn = emit_move_insn (gen_rtx_REG (word_mode, regno), mem); |
| |
| /* only prologue instructions which set the sp fp or save a |
| register should be marked as frame related. */ |
| if (op == 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| offset -= UNITS_PER_WORD; |
| } |
| } |
| } |
| |
| static void |
| stack_adjust (HOST_WIDE_INT amount) |
| { |
| rtx insn; |
| |
| if (!IN_RANGE (amount, -32776, 32768)) |
| { |
| /* r10 is caller saved so it can be used as a temp reg. */ |
| rtx r10; |
| r10 = gen_rtx_REG (word_mode, 10); |
| insn = emit_move_insn (r10, GEN_INT (amount)); |
| if (amount < 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| insn = emit_add (stack_pointer_rtx, stack_pointer_rtx, r10); |
| if (amount < 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| else |
| { |
| insn = emit_add (stack_pointer_rtx, |
| stack_pointer_rtx, GEN_INT (amount)); |
| if (amount < 0) |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| |
| /* Create and emit instructions for a functions prologue. */ |
| void |
| lm32_expand_prologue (void) |
| { |
| rtx insn; |
| |
| lm32_compute_frame_size (get_frame_size ()); |
| |
| if (current_frame_info.total_size > 0) |
| { |
| /* Add space on stack new frame. */ |
| stack_adjust (-current_frame_info.total_size); |
| |
| /* Save callee save registers. */ |
| if (current_frame_info.reg_save_mask != 0) |
| expand_save_restore (¤t_frame_info, 0); |
| |
| /* Setup frame pointer if it's needed. */ |
| if (frame_pointer_needed == 1) |
| { |
| /* Move sp to fp. */ |
| insn = emit_move_insn (frame_pointer_rtx, stack_pointer_rtx); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| |
| /* Add offset - Don't use total_size, as that includes pretend_size, |
| which isn't part of this frame? */ |
| insn = emit_add (frame_pointer_rtx, |
| frame_pointer_rtx, |
| GEN_INT (current_frame_info.args_size + |
| current_frame_info.callee_size + |
| current_frame_info.locals_size)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* Prevent prologue from being scheduled into function body. */ |
| emit_insn (gen_blockage ()); |
| } |
| } |
| |
| /* Create an emit instructions for a functions epilogue. */ |
| void |
| lm32_expand_epilogue (void) |
| { |
| rtx ra_rtx = gen_rtx_REG (Pmode, RA_REGNUM); |
| |
| lm32_compute_frame_size (get_frame_size ()); |
| |
| if (current_frame_info.total_size > 0) |
| { |
| /* Prevent stack code from being reordered. */ |
| emit_insn (gen_blockage ()); |
| |
| /* Restore callee save registers. */ |
| if (current_frame_info.reg_save_mask != 0) |
| expand_save_restore (¤t_frame_info, 1); |
| |
| /* Deallocate stack. */ |
| stack_adjust (current_frame_info.total_size); |
| |
| /* Return to calling function. */ |
| emit_jump_insn (gen_return_internal (ra_rtx)); |
| } |
| else |
| { |
| /* Return to calling function. */ |
| emit_jump_insn (gen_return_internal (ra_rtx)); |
| } |
| } |
| |
| /* Return the bytes needed to compute the frame pointer from the current |
| stack pointer. */ |
| static HOST_WIDE_INT |
| lm32_compute_frame_size (int size) |
| { |
| int regno; |
| HOST_WIDE_INT total_size, locals_size, args_size, pretend_size, callee_size; |
| unsigned int reg_save_mask; |
| |
| locals_size = size; |
| args_size = crtl->outgoing_args_size; |
| pretend_size = crtl->args.pretend_args_size; |
| callee_size = 0; |
| reg_save_mask = 0; |
| |
| /* Build mask that actually determines which regsiters we save |
| and calculate size required to store them in the stack. */ |
| for (regno = 1; regno < SP_REGNUM; regno++) |
| { |
| if (df_regs_ever_live_p (regno) && !call_used_or_fixed_reg_p (regno)) |
| { |
| reg_save_mask |= 1 << regno; |
| callee_size += UNITS_PER_WORD; |
| } |
| } |
| if (df_regs_ever_live_p (RA_REGNUM) || ! crtl->is_leaf |
| || !optimize) |
| { |
| reg_save_mask |= 1 << RA_REGNUM; |
| callee_size += UNITS_PER_WORD; |
| } |
| if (!(reg_save_mask & (1 << FP_REGNUM)) && frame_pointer_needed) |
| { |
| reg_save_mask |= 1 << FP_REGNUM; |
| callee_size += UNITS_PER_WORD; |
| } |
| |
| /* Compute total frame size. */ |
| total_size = pretend_size + args_size + locals_size + callee_size; |
| |
| /* Align frame to appropriate boundary. */ |
| total_size = (total_size + 3) & ~3; |
| |
| /* Save computed information. */ |
| current_frame_info.total_size = total_size; |
| current_frame_info.callee_size = callee_size; |
| current_frame_info.pretend_size = pretend_size; |
| current_frame_info.locals_size = locals_size; |
| current_frame_info.args_size = args_size; |
| current_frame_info.reg_save_mask = reg_save_mask; |
| |
| return total_size; |
| } |
| |
| void |
| lm32_print_operand (FILE * file, rtx op, int letter) |
| { |
| enum rtx_code code; |
| |
| code = GET_CODE (op); |
| |
| if (code == SIGN_EXTEND) |
| op = XEXP (op, 0), code = GET_CODE (op); |
| else if (code == REG || code == SUBREG) |
| { |
| int regnum; |
| |
| if (code == REG) |
| regnum = REGNO (op); |
| else |
| regnum = true_regnum (op); |
| |
| fprintf (file, "%s", reg_names[regnum]); |
| } |
| else if (code == HIGH) |
| output_addr_const (file, XEXP (op, 0)); |
| else if (code == MEM) |
| output_address (GET_MODE (op), XEXP (op, 0)); |
| else if (letter == 'z' && GET_CODE (op) == CONST_INT && INTVAL (op) == 0) |
| fprintf (file, "%s", reg_names[0]); |
| else if (GET_CODE (op) == CONST_DOUBLE) |
| { |
| if ((CONST_DOUBLE_LOW (op) != 0) || (CONST_DOUBLE_HIGH (op) != 0)) |
| output_operand_lossage ("only 0.0 can be loaded as an immediate"); |
| else |
| fprintf (file, "0"); |
| } |
| else if (code == EQ) |
| fprintf (file, "e "); |
| else if (code == NE) |
| fprintf (file, "ne "); |
| else if (code == GT) |
| fprintf (file, "g "); |
| else if (code == GTU) |
| fprintf (file, "gu "); |
| else if (code == LT) |
| fprintf (file, "l "); |
| else if (code == LTU) |
| fprintf (file, "lu "); |
| else if (code == GE) |
| fprintf (file, "ge "); |
| else if (code == GEU) |
| fprintf (file, "geu"); |
| else if (code == LE) |
| fprintf (file, "le "); |
| else if (code == LEU) |
| fprintf (file, "leu"); |
| else |
| output_addr_const (file, op); |
| } |
| |
| /* A C compound statement to output to stdio stream STREAM the |
| assembler syntax for an instruction operand that is a memory |
| reference whose address is ADDR. ADDR is an RTL expression. |
| |
| On some machines, the syntax for a symbolic address depends on |
| the section that the address refers to. On these machines, |
| define the macro `ENCODE_SECTION_INFO' to store the information |
| into the `symbol_ref', and then check for it here. */ |
| |
| void |
| lm32_print_operand_address (FILE * file, rtx addr) |
| { |
| switch (GET_CODE (addr)) |
| { |
| case REG: |
| fprintf (file, "(%s+0)", reg_names[REGNO (addr)]); |
| break; |
| |
| case MEM: |
| output_address (VOIDmode, XEXP (addr, 0)); |
| break; |
| |
| case PLUS: |
| { |
| rtx arg0 = XEXP (addr, 0); |
| rtx arg1 = XEXP (addr, 1); |
| |
| if (GET_CODE (arg0) == REG && CONSTANT_P (arg1)) |
| { |
| if (GET_CODE (arg1) == CONST_INT) |
| fprintf (file, "(%s+%ld)", reg_names[REGNO (arg0)], |
| INTVAL (arg1)); |
| else |
| { |
| fprintf (file, "(%s+", reg_names[REGNO (arg0)]); |
| output_addr_const (file, arg1); |
| fprintf (file, ")"); |
| } |
| } |
| else if (CONSTANT_P (arg0) && CONSTANT_P (arg1)) |
| output_addr_const (file, addr); |
| else |
| fatal_insn ("bad operand", addr); |
| } |
| break; |
| |
| case SYMBOL_REF: |
| if (SYMBOL_REF_SMALL_P (addr)) |
| { |
| fprintf (file, "gp("); |
| output_addr_const (file, addr); |
| fprintf (file, ")"); |
| } |
| else |
| fatal_insn ("can't use non gp relative absolute address", addr); |
| break; |
| |
| default: |
| fatal_insn ("invalid addressing mode", addr); |
| break; |
| } |
| } |
| |
| /* Determine where to put an argument to a function. |
| Value is zero to push the argument on the stack, |
| or a hard register in which to store the argument. |
| |
| CUM is a variable of type CUMULATIVE_ARGS which gives info about |
| the preceding args and about the function being called. |
| ARG is a description of the argument. */ |
| |
| static rtx |
| lm32_function_arg (cumulative_args_t cum_v, const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| if (arg.end_marker_p ()) |
| /* Compute operand 2 of the call insn. */ |
| return GEN_INT (0); |
| |
| if (targetm.calls.must_pass_in_stack (arg)) |
| return NULL_RTX; |
| |
| if (!arg.named |
| || *cum + LM32_NUM_REGS2 (arg.mode, arg.type) > LM32_NUM_ARG_REGS) |
| return NULL_RTX; |
| |
| return gen_rtx_REG (arg.mode, *cum + LM32_FIRST_ARG_REG); |
| } |
| |
| static void |
| lm32_function_arg_advance (cumulative_args_t cum, |
| const function_arg_info &arg) |
| { |
| *get_cumulative_args (cum) += LM32_NUM_REGS2 (arg.mode, arg.type); |
| } |
| |
| HOST_WIDE_INT |
| lm32_compute_initial_elimination_offset (int from, int to) |
| { |
| HOST_WIDE_INT offset = 0; |
| |
| switch (from) |
| { |
| case ARG_POINTER_REGNUM: |
| switch (to) |
| { |
| case FRAME_POINTER_REGNUM: |
| offset = 0; |
| break; |
| case STACK_POINTER_REGNUM: |
| offset = |
| lm32_compute_frame_size (get_frame_size ()) - |
| current_frame_info.pretend_size; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| return offset; |
| } |
| |
| static void |
| lm32_setup_incoming_varargs (cumulative_args_t cum_v, |
| const function_arg_info &arg, |
| int *pretend_size, int no_rtl) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| int first_anon_arg; |
| tree fntype; |
| |
| fntype = TREE_TYPE (current_function_decl); |
| |
| if (stdarg_p (fntype)) |
| first_anon_arg = *cum + LM32_FIRST_ARG_REG; |
| else |
| { |
| /* this is the common case, we have been passed details setup |
| for the last named argument, we want to skip over the |
| registers, if any used in passing this named parameter in |
| order to determine which is the first registers used to pass |
| anonymous arguments. */ |
| int size = arg.promoted_size_in_bytes (); |
| |
| first_anon_arg = |
| *cum + LM32_FIRST_ARG_REG + |
| ((size + UNITS_PER_WORD - 1) / UNITS_PER_WORD); |
| } |
| |
| if ((first_anon_arg < (LM32_FIRST_ARG_REG + LM32_NUM_ARG_REGS)) && !no_rtl) |
| { |
| int first_reg_offset = first_anon_arg; |
| int size = LM32_FIRST_ARG_REG + LM32_NUM_ARG_REGS - first_anon_arg; |
| rtx regblock; |
| |
| regblock = gen_rtx_MEM (BLKmode, |
| plus_constant (Pmode, arg_pointer_rtx, |
| FIRST_PARM_OFFSET (0))); |
| move_block_from_reg (first_reg_offset, regblock, size); |
| |
| *pretend_size = size * UNITS_PER_WORD; |
| } |
| } |
| |
| /* Override command line options. */ |
| static void |
| lm32_option_override (void) |
| { |
| /* We must have sign-extend enabled if barrel-shift isn't. */ |
| if (!TARGET_BARREL_SHIFT_ENABLED && !TARGET_SIGN_EXTEND_ENABLED) |
| target_flags |= MASK_SIGN_EXTEND_ENABLED; |
| } |
| |
| /* Return nonzero if this function is known to have a null epilogue. |
| This allows the optimizer to omit jumps to jumps if no stack |
| was created. */ |
| int |
| lm32_can_use_return (void) |
| { |
| if (!reload_completed) |
| return 0; |
| |
| if (df_regs_ever_live_p (RA_REGNUM) || crtl->profile) |
| return 0; |
| |
| if (lm32_compute_frame_size (get_frame_size ()) != 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Support function to determine the return address of the function |
| 'count' frames back up the stack. */ |
| rtx |
| lm32_return_addr_rtx (int count, rtx frame) |
| { |
| rtx r; |
| if (count == 0) |
| { |
| if (!df_regs_ever_live_p (RA_REGNUM)) |
| r = gen_rtx_REG (Pmode, RA_REGNUM); |
| else |
| { |
| r = gen_rtx_MEM (Pmode, |
| gen_rtx_PLUS (Pmode, frame, |
| GEN_INT (-2 * UNITS_PER_WORD))); |
| set_mem_alias_set (r, get_frame_alias_set ()); |
| } |
| } |
| else if (flag_omit_frame_pointer) |
| r = NULL_RTX; |
| else |
| { |
| r = gen_rtx_MEM (Pmode, |
| gen_rtx_PLUS (Pmode, frame, |
| GEN_INT (-2 * UNITS_PER_WORD))); |
| set_mem_alias_set (r, get_frame_alias_set ()); |
| } |
| return r; |
| } |
| |
| /* Return true if EXP should be placed in the small data section. */ |
| |
| static bool |
| lm32_in_small_data_p (const_tree exp) |
| { |
| /* We want to merge strings, so we never consider them small data. */ |
| if (TREE_CODE (exp) == STRING_CST) |
| return false; |
| |
| /* Functions are never in the small data area. Duh. */ |
| if (TREE_CODE (exp) == FUNCTION_DECL) |
| return false; |
| |
| if (TREE_CODE (exp) == VAR_DECL && DECL_SECTION_NAME (exp)) |
| { |
| const char *section = DECL_SECTION_NAME (exp); |
| if (strcmp (section, ".sdata") == 0 || strcmp (section, ".sbss") == 0) |
| return true; |
| } |
| else |
| { |
| HOST_WIDE_INT size = int_size_in_bytes (TREE_TYPE (exp)); |
| |
| /* If this is an incomplete type with size 0, then we can't put it |
| in sdata because it might be too big when completed. */ |
| if (size > 0 && size <= g_switch_value) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Emit straight-line code to move LENGTH bytes from SRC to DEST. |
| Assume that the areas do not overlap. */ |
| |
| static void |
| lm32_block_move_inline (rtx dest, rtx src, HOST_WIDE_INT length, |
| HOST_WIDE_INT alignment) |
| { |
| HOST_WIDE_INT offset, delta; |
| unsigned HOST_WIDE_INT bits; |
| int i; |
| machine_mode mode; |
| rtx *regs; |
| |
| /* Work out how many bits to move at a time. */ |
| switch (alignment) |
| { |
| case 1: |
| bits = 8; |
| break; |
| case 2: |
| bits = 16; |
| break; |
| default: |
| bits = 32; |
| break; |
| } |
| |
| mode = int_mode_for_size (bits, 0).require (); |
| delta = bits / BITS_PER_UNIT; |
| |
| /* Allocate a buffer for the temporary registers. */ |
| regs = XALLOCAVEC (rtx, length / delta); |
| |
| /* Load as many BITS-sized chunks as possible. */ |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| { |
| regs[i] = gen_reg_rtx (mode); |
| emit_move_insn (regs[i], adjust_address (src, mode, offset)); |
| } |
| |
| /* Copy the chunks to the destination. */ |
| for (offset = 0, i = 0; offset + delta <= length; offset += delta, i++) |
| emit_move_insn (adjust_address (dest, mode, offset), regs[i]); |
| |
| /* Mop up any left-over bytes. */ |
| if (offset < length) |
| { |
| src = adjust_address (src, BLKmode, offset); |
| dest = adjust_address (dest, BLKmode, offset); |
| move_by_pieces (dest, src, length - offset, |
| MIN (MEM_ALIGN (src), MEM_ALIGN (dest)), RETURN_BEGIN); |
| } |
| } |
| |
| /* Expand string/block move operations. |
| |
| operands[0] is the pointer to the destination. |
| operands[1] is the pointer to the source. |
| operands[2] is the number of bytes to move. |
| operands[3] is the alignment. */ |
| |
| int |
| lm32_expand_block_move (rtx * operands) |
| { |
| if ((GET_CODE (operands[2]) == CONST_INT) && (INTVAL (operands[2]) <= 32)) |
| { |
| lm32_block_move_inline (operands[0], operands[1], INTVAL (operands[2]), |
| INTVAL (operands[3])); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Return TRUE if X references a SYMBOL_REF or LABEL_REF whose symbol |
| isn't protected by a PIC unspec. */ |
| int |
| nonpic_symbol_mentioned_p (rtx x) |
| { |
| const char *fmt; |
| int i; |
| |
| if (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF |
| || GET_CODE (x) == PC) |
| return 1; |
| |
| /* We don't want to look into the possible MEM location of a |
| CONST_DOUBLE, since we're not going to use it, in general. */ |
| if (GET_CODE (x) == CONST_DOUBLE) |
| return 0; |
| |
| if (GET_CODE (x) == UNSPEC) |
| return 0; |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (x)); |
| for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| int j; |
| |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| if (nonpic_symbol_mentioned_p (XVECEXP (x, i, j))) |
| return 1; |
| } |
| else if (fmt[i] == 'e' && nonpic_symbol_mentioned_p (XEXP (x, i))) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Compute a (partial) cost for rtx X. Return true if the complete |
| cost has been computed, and false if subexpressions should be |
| scanned. In either case, *TOTAL contains the cost result. */ |
| |
| static bool |
| lm32_rtx_costs (rtx x, machine_mode mode, int outer_code, |
| int opno ATTRIBUTE_UNUSED, int *total, bool speed) |
| { |
| int code = GET_CODE (x); |
| bool small_mode; |
| |
| const int arithmetic_latency = 1; |
| const int shift_latency = 1; |
| const int compare_latency = 2; |
| const int multiply_latency = 3; |
| const int load_latency = 3; |
| const int libcall_size_cost = 5; |
| |
| /* Determine if we can handle the given mode size in a single instruction. */ |
| small_mode = (mode == QImode) || (mode == HImode) || (mode == SImode); |
| |
| switch (code) |
| { |
| |
| case PLUS: |
| case MINUS: |
| case AND: |
| case IOR: |
| case XOR: |
| case NOT: |
| case NEG: |
| if (!speed) |
| *total = COSTS_N_INSNS (LM32_NUM_REGS (mode)); |
| else |
| *total = |
| COSTS_N_INSNS (arithmetic_latency + (LM32_NUM_REGS (mode) - 1)); |
| break; |
| |
| case COMPARE: |
| if (small_mode) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (compare_latency); |
| } |
| else |
| { |
| /* FIXME. Guessing here. */ |
| *total = COSTS_N_INSNS (LM32_NUM_REGS (mode) * (2 + 3) / 2); |
| } |
| break; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| if (TARGET_BARREL_SHIFT_ENABLED && small_mode) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (shift_latency); |
| } |
| else if (TARGET_BARREL_SHIFT_ENABLED) |
| { |
| /* FIXME: Guessing here. */ |
| *total = COSTS_N_INSNS (LM32_NUM_REGS (mode) * 4); |
| } |
| else if (small_mode && GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| *total = COSTS_N_INSNS (INTVAL (XEXP (x, 1))); |
| } |
| else |
| { |
| /* Libcall. */ |
| if (!speed) |
| *total = COSTS_N_INSNS (libcall_size_cost); |
| else |
| *total = COSTS_N_INSNS (100); |
| } |
| break; |
| |
| case MULT: |
| if (TARGET_MULTIPLY_ENABLED && small_mode) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (multiply_latency); |
| } |
| else |
| { |
| /* Libcall. */ |
| if (!speed) |
| *total = COSTS_N_INSNS (libcall_size_cost); |
| else |
| *total = COSTS_N_INSNS (100); |
| } |
| break; |
| |
| case DIV: |
| case MOD: |
| case UDIV: |
| case UMOD: |
| if (TARGET_DIVIDE_ENABLED && small_mode) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| { |
| if (GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| int cycles = 0; |
| unsigned HOST_WIDE_INT i = INTVAL (XEXP (x, 1)); |
| |
| while (i) |
| { |
| i >>= 2; |
| cycles++; |
| } |
| if (IN_RANGE (i, 0, 65536)) |
| *total = COSTS_N_INSNS (1 + 1 + cycles); |
| else |
| *total = COSTS_N_INSNS (2 + 1 + cycles); |
| return true; |
| } |
| else if (GET_CODE (XEXP (x, 1)) == REG) |
| { |
| *total = COSTS_N_INSNS (1 + GET_MODE_SIZE (mode) / 2); |
| return true; |
| } |
| else |
| { |
| *total = COSTS_N_INSNS (1 + GET_MODE_SIZE (mode) / 2); |
| return false; |
| } |
| } |
| } |
| else |
| { |
| /* Libcall. */ |
| if (!speed) |
| *total = COSTS_N_INSNS (libcall_size_cost); |
| else |
| *total = COSTS_N_INSNS (100); |
| } |
| break; |
| |
| case HIGH: |
| case LO_SUM: |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (arithmetic_latency); |
| break; |
| |
| case ZERO_EXTEND: |
| if (MEM_P (XEXP (x, 0))) |
| *total = COSTS_N_INSNS (0); |
| else if (small_mode) |
| { |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (arithmetic_latency); |
| } |
| else |
| *total = COSTS_N_INSNS (LM32_NUM_REGS (mode) / 2); |
| break; |
| |
| case CONST_INT: |
| { |
| switch (outer_code) |
| { |
| case HIGH: |
| case LO_SUM: |
| *total = COSTS_N_INSNS (0); |
| return true; |
| |
| case AND: |
| case XOR: |
| case IOR: |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| case ROTATE: |
| case ROTATERT: |
| if (satisfies_constraint_L (x)) |
| *total = COSTS_N_INSNS (0); |
| else |
| *total = COSTS_N_INSNS (2); |
| return true; |
| |
| case SET: |
| case PLUS: |
| case MINUS: |
| case COMPARE: |
| if (satisfies_constraint_K (x)) |
| *total = COSTS_N_INSNS (0); |
| else |
| *total = COSTS_N_INSNS (2); |
| return true; |
| |
| case MULT: |
| if (TARGET_MULTIPLY_ENABLED) |
| { |
| if (satisfies_constraint_K (x)) |
| *total = COSTS_N_INSNS (0); |
| else |
| *total = COSTS_N_INSNS (2); |
| return true; |
| } |
| /* Fall through. */ |
| |
| default: |
| if (satisfies_constraint_K (x)) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| return true; |
| } |
| } |
| |
| case SYMBOL_REF: |
| case CONST: |
| switch (outer_code) |
| { |
| case HIGH: |
| case LO_SUM: |
| *total = COSTS_N_INSNS (0); |
| return true; |
| |
| case MEM: |
| case SET: |
| if (g_switch_value) |
| { |
| *total = COSTS_N_INSNS (0); |
| return true; |
| } |
| break; |
| } |
| /* Fall through. */ |
| |
| case LABEL_REF: |
| case CONST_DOUBLE: |
| *total = COSTS_N_INSNS (2); |
| return true; |
| |
| case SET: |
| *total = COSTS_N_INSNS (1); |
| break; |
| |
| case MEM: |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (load_latency); |
| break; |
| |
| } |
| |
| return false; |
| } |
| |
| /* Implemenent TARGET_CAN_ELIMINATE. */ |
| |
| bool |
| lm32_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| return (to == STACK_POINTER_REGNUM && frame_pointer_needed) ? false : true; |
| } |
| |
| /* Implement TARGET_LEGITIMATE_ADDRESS_P. */ |
| |
| static bool |
| lm32_legitimate_address_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x, bool strict) |
| { |
| /* (rM) */ |
| if (strict && REG_P (x) && STRICT_REG_OK_FOR_BASE_P (x)) |
| return true; |
| if (!strict && REG_P (x) && NONSTRICT_REG_OK_FOR_BASE_P (x)) |
| return true; |
| |
| /* (rM)+literal) */ |
| if (GET_CODE (x) == PLUS |
| && REG_P (XEXP (x, 0)) |
| && ((strict && STRICT_REG_OK_FOR_BASE_P (XEXP (x, 0))) |
| || (!strict && NONSTRICT_REG_OK_FOR_BASE_P (XEXP (x, 0)))) |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && satisfies_constraint_K (XEXP ((x), 1))) |
| return true; |
| |
| /* gp(sym) */ |
| if (GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_SMALL_P (x)) |
| return true; |
| |
| return false; |
| } |
| |
| /* Check a move is not memory to memory. */ |
| |
| bool |
| lm32_move_ok (machine_mode mode, rtx operands[2]) { |
| if (memory_operand (operands[0], mode)) |
| return register_or_zero_operand (operands[1], mode); |
| return true; |
| } |
| |
| /* Implement TARGET_HARD_REGNO_MODE_OK. */ |
| |
| static bool |
| lm32_hard_regno_mode_ok (unsigned int regno, machine_mode) |
| { |
| return G_REG_P (regno); |
| } |
| |
| /* Implement TARGET_MODES_TIEABLE_P. */ |
| |
| static bool |
| lm32_modes_tieable_p (machine_mode mode1, machine_mode mode2) |
| { |
| return (GET_MODE_CLASS (mode1) == MODE_INT |
| && GET_MODE_CLASS (mode2) == MODE_INT |
| && GET_MODE_SIZE (mode1) <= UNITS_PER_WORD |
| && GET_MODE_SIZE (mode2) <= UNITS_PER_WORD); |
| } |
| |
| /* Implement TARGET_STARTING_FRAME_OFFSET. */ |
| |
| static HOST_WIDE_INT |
| lm32_starting_frame_offset (void) |
| { |
| return UNITS_PER_WORD; |
| } |