URL
https://opencores.org/ocsvn/openrisc_me/openrisc_me/trunk
Subversion Repositories openrisc_me
[/] [openrisc/] [trunk/] [gnu-src/] [gcc-4.5.1/] [gcc/] [config/] [arc/] [arc.c] - Rev 283
Go to most recent revision | Compare with Previous | Blame | View Log
/* Subroutines used for code generation on the Argonaut ARC cpu. Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 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/>. */ /* ??? This is an old port, and is undoubtedly suffering from bit rot. */ #include "config.h" #include "system.h" #include "coretypes.h" #include "tm.h" #include "tree.h" #include "rtl.h" #include "regs.h" #include "hard-reg-set.h" #include "real.h" #include "insn-config.h" #include "conditions.h" #include "output.h" #include "insn-attr.h" #include "flags.h" #include "function.h" #include "expr.h" #include "recog.h" #include "toplev.h" #include "df.h" #include "tm_p.h" #include "target.h" #include "target-def.h" /* Which cpu we're compiling for. */ int arc_cpu_type; /* Name of mangle string to add to symbols to separate code compiled for each cpu (or NULL). */ const char *arc_mangle_cpu; /* Name of text, data, and rodata sections used in varasm.c. */ const char *arc_text_section; const char *arc_data_section; const char *arc_rodata_section; /* Array of valid operand punctuation characters. */ char arc_punct_chars[256]; /* Variables used by arc_final_prescan_insn to implement conditional execution. */ static int arc_ccfsm_state; static int arc_ccfsm_current_cc; static rtx arc_ccfsm_target_insn; static int arc_ccfsm_target_label; /* The maximum number of insns skipped which will be conditionalised if possible. */ #define MAX_INSNS_SKIPPED 3 /* A nop is needed between a 4 byte insn that sets the condition codes and a branch that uses them (the same isn't true for an 8 byte insn that sets the condition codes). Set by arc_final_prescan_insn. Used by arc_print_operand. */ static int last_insn_set_cc_p; static int current_insn_set_cc_p; static bool arc_handle_option (size_t, const char *, int); static void record_cc_ref (rtx); static void arc_init_reg_tables (void); static int get_arc_condition_code (rtx); static tree arc_handle_interrupt_attribute (tree *, tree, tree, int, bool *); static bool arc_assemble_integer (rtx, unsigned int, int); static void arc_output_function_prologue (FILE *, HOST_WIDE_INT); static void arc_output_function_epilogue (FILE *, HOST_WIDE_INT); static void arc_file_start (void); static void arc_internal_label (FILE *, const char *, unsigned long); static void arc_va_start (tree, rtx); static void arc_setup_incoming_varargs (CUMULATIVE_ARGS *, enum machine_mode, tree, int *, int); static bool arc_rtx_costs (rtx, int, int, int *, bool); static int arc_address_cost (rtx, bool); static void arc_external_libcall (rtx); static bool arc_return_in_memory (const_tree, const_tree); static bool arc_pass_by_reference (CUMULATIVE_ARGS *, enum machine_mode, const_tree, bool); static void arc_trampoline_init (rtx, tree, rtx); /* ARC specific attributs. */ static const struct attribute_spec arc_attribute_table[] = { /* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler } */ { "interrupt", 1, 1, true, false, false, arc_handle_interrupt_attribute }, { NULL, 0, 0, false, false, false, NULL } }; /* Initialize the GCC target structure. */ #undef TARGET_ASM_ALIGNED_HI_OP #define TARGET_ASM_ALIGNED_HI_OP "\t.hword\t" #undef TARGET_ASM_ALIGNED_SI_OP #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" #undef TARGET_ASM_INTEGER #define TARGET_ASM_INTEGER arc_assemble_integer #undef TARGET_ASM_FUNCTION_PROLOGUE #define TARGET_ASM_FUNCTION_PROLOGUE arc_output_function_prologue #undef TARGET_ASM_FUNCTION_EPILOGUE #define TARGET_ASM_FUNCTION_EPILOGUE arc_output_function_epilogue #undef TARGET_ASM_FILE_START #define TARGET_ASM_FILE_START arc_file_start #undef TARGET_ATTRIBUTE_TABLE #define TARGET_ATTRIBUTE_TABLE arc_attribute_table #undef TARGET_ASM_INTERNAL_LABEL #define TARGET_ASM_INTERNAL_LABEL arc_internal_label #undef TARGET_ASM_EXTERNAL_LIBCALL #define TARGET_ASM_EXTERNAL_LIBCALL arc_external_libcall #undef TARGET_HANDLE_OPTION #define TARGET_HANDLE_OPTION arc_handle_option #undef TARGET_RTX_COSTS #define TARGET_RTX_COSTS arc_rtx_costs #undef TARGET_ADDRESS_COST #define TARGET_ADDRESS_COST arc_address_cost #undef TARGET_PROMOTE_FUNCTION_MODE #define TARGET_PROMOTE_FUNCTION_MODE default_promote_function_mode_always_promote #undef TARGET_PROMOTE_PROTOTYPES #define TARGET_PROMOTE_PROTOTYPES hook_bool_const_tree_true #undef TARGET_RETURN_IN_MEMORY #define TARGET_RETURN_IN_MEMORY arc_return_in_memory #undef TARGET_PASS_BY_REFERENCE #define TARGET_PASS_BY_REFERENCE arc_pass_by_reference #undef TARGET_CALLEE_COPIES #define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_mode_tree_bool_true #undef TARGET_SETUP_INCOMING_VARARGS #define TARGET_SETUP_INCOMING_VARARGS arc_setup_incoming_varargs #undef TARGET_EXPAND_BUILTIN_VA_START #define TARGET_EXPAND_BUILTIN_VA_START arc_va_start #undef TARGET_TRAMPOLINE_INIT #define TARGET_TRAMPOLINE_INIT arc_trampoline_init struct gcc_target targetm = TARGET_INITIALIZER; /* Implement TARGET_HANDLE_OPTION. */ static bool arc_handle_option (size_t code, const char *arg, int value ATTRIBUTE_UNUSED) { switch (code) { case OPT_mcpu_: return strcmp (arg, "base") == 0 || ARC_EXTENSION_CPU (arg); default: return true; } } /* Called by OVERRIDE_OPTIONS to initialize various things. */ void arc_init (void) { char *tmp; /* Set the pseudo-ops for the various standard sections. */ arc_text_section = tmp = XNEWVEC (char, strlen (arc_text_string) + sizeof (ARC_SECTION_FORMAT) + 1); sprintf (tmp, ARC_SECTION_FORMAT, arc_text_string); arc_data_section = tmp = XNEWVEC (char, strlen (arc_data_string) + sizeof (ARC_SECTION_FORMAT) + 1); sprintf (tmp, ARC_SECTION_FORMAT, arc_data_string); arc_rodata_section = tmp = XNEWVEC (char, strlen (arc_rodata_string) + sizeof (ARC_SECTION_FORMAT) + 1); sprintf (tmp, ARC_SECTION_FORMAT, arc_rodata_string); arc_init_reg_tables (); /* Initialize array for PRINT_OPERAND_PUNCT_VALID_P. */ memset (arc_punct_chars, 0, sizeof (arc_punct_chars)); arc_punct_chars['#'] = 1; arc_punct_chars['*'] = 1; arc_punct_chars['?'] = 1; arc_punct_chars['!'] = 1; arc_punct_chars['~'] = 1; } /* The condition codes of the ARC, and the inverse function. */ static const char *const arc_condition_codes[] = { "al", 0, "eq", "ne", "p", "n", "c", "nc", "v", "nv", "gt", "le", "ge", "lt", "hi", "ls", "pnz", 0 }; #define ARC_INVERSE_CONDITION_CODE(X) ((X) ^ 1) /* Returns the index of the ARC condition code string in `arc_condition_codes'. COMPARISON should be an rtx like `(eq (...) (...))'. */ static int get_arc_condition_code (rtx comparison) { switch (GET_CODE (comparison)) { case EQ : return 2; case NE : return 3; case GT : return 10; case LE : return 11; case GE : return 12; case LT : return 13; case GTU : return 14; case LEU : return 15; case LTU : return 6; case GEU : return 7; default : gcc_unreachable (); } /*NOTREACHED*/ return (42); } /* Given a comparison code (EQ, NE, etc.) and the first operand of a COMPARE, return the mode to be used for the comparison. */ enum machine_mode arc_select_cc_mode (enum rtx_code op, rtx x ATTRIBUTE_UNUSED, rtx y ATTRIBUTE_UNUSED) { switch (op) { case EQ : case NE : return CCZNmode; default : switch (GET_CODE (x)) { case AND : case IOR : case XOR : case SIGN_EXTEND : case ZERO_EXTEND : return CCZNmode; case ASHIFT : case ASHIFTRT : case LSHIFTRT : return CCZNCmode; default: break; } } return CCmode; } /* Vectors to keep interesting information about registers where it can easily be got. We use to use the actual mode value as the bit number, but there is (or may be) more than 32 modes now. Instead we use two tables: one indexed by hard register number, and one indexed by mode. */ /* The purpose of arc_mode_class is to shrink the range of modes so that they all fit (as bit numbers) in a 32-bit word (again). Each real mode is mapped into one arc_mode_class mode. */ enum arc_mode_class { C_MODE, S_MODE, D_MODE, T_MODE, O_MODE, SF_MODE, DF_MODE, TF_MODE, OF_MODE }; /* Modes for condition codes. */ #define C_MODES (1 << (int) C_MODE) /* Modes for single-word and smaller quantities. */ #define S_MODES ((1 << (int) S_MODE) | (1 << (int) SF_MODE)) /* Modes for double-word and smaller quantities. */ #define D_MODES (S_MODES | (1 << (int) D_MODE) | (1 << DF_MODE)) /* Modes for quad-word and smaller quantities. */ #define T_MODES (D_MODES | (1 << (int) T_MODE) | (1 << (int) TF_MODE)) /* Value is 1 if register/mode pair is acceptable on arc. */ const unsigned int arc_hard_regno_mode_ok[] = { T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, T_MODES, D_MODES, D_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, /* ??? Leave these as S_MODES for now. */ S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, S_MODES, C_MODES }; unsigned int arc_mode_class [NUM_MACHINE_MODES]; enum reg_class arc_regno_reg_class[FIRST_PSEUDO_REGISTER]; static void arc_init_reg_tables (void) { int i; for (i = 0; i < NUM_MACHINE_MODES; i++) { switch (GET_MODE_CLASS (i)) { case MODE_INT: case MODE_PARTIAL_INT: case MODE_COMPLEX_INT: if (GET_MODE_SIZE (i) <= 4) arc_mode_class[i] = 1 << (int) S_MODE; else if (GET_MODE_SIZE (i) == 8) arc_mode_class[i] = 1 << (int) D_MODE; else if (GET_MODE_SIZE (i) == 16) arc_mode_class[i] = 1 << (int) T_MODE; else if (GET_MODE_SIZE (i) == 32) arc_mode_class[i] = 1 << (int) O_MODE; else arc_mode_class[i] = 0; break; case MODE_FLOAT: case MODE_COMPLEX_FLOAT: if (GET_MODE_SIZE (i) <= 4) arc_mode_class[i] = 1 << (int) SF_MODE; else if (GET_MODE_SIZE (i) == 8) arc_mode_class[i] = 1 << (int) DF_MODE; else if (GET_MODE_SIZE (i) == 16) arc_mode_class[i] = 1 << (int) TF_MODE; else if (GET_MODE_SIZE (i) == 32) arc_mode_class[i] = 1 << (int) OF_MODE; else arc_mode_class[i] = 0; break; case MODE_CC: arc_mode_class[i] = 1 << (int) C_MODE; break; default: arc_mode_class[i] = 0; break; } } for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) { if (i < 60) arc_regno_reg_class[i] = GENERAL_REGS; else if (i == 60) arc_regno_reg_class[i] = LPCOUNT_REG; else if (i == 61) arc_regno_reg_class[i] = NO_REGS /* CC_REG: must be NO_REGS */; else arc_regno_reg_class[i] = NO_REGS; } } /* ARC specific attribute support. The ARC has these attributes: interrupt - for interrupt functions */ /* Handle an "interrupt" attribute; arguments as in struct attribute_spec.handler. */ static tree arc_handle_interrupt_attribute (tree *node ATTRIBUTE_UNUSED, tree name, tree args, int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) { tree value = TREE_VALUE (args); if (TREE_CODE (value) != STRING_CST) { warning (OPT_Wattributes, "argument of %qE attribute is not a string constant", name); *no_add_attrs = true; } else if (strcmp (TREE_STRING_POINTER (value), "ilink1") && strcmp (TREE_STRING_POINTER (value), "ilink2")) { warning (OPT_Wattributes, "argument of %qE attribute is not \"ilink1\" or \"ilink2\"", name); *no_add_attrs = true; } return NULL_TREE; } /* Acceptable arguments to the call insn. */ int call_address_operand (rtx op, enum machine_mode mode) { return (symbolic_operand (op, mode) || (GET_CODE (op) == CONST_INT && LEGITIMATE_CONSTANT_P (op)) || (GET_CODE (op) == REG)); } int call_operand (rtx op, enum machine_mode mode) { if (GET_CODE (op) != MEM) return 0; op = XEXP (op, 0); return call_address_operand (op, mode); } /* Returns 1 if OP is a symbol reference. */ int symbolic_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { switch (GET_CODE (op)) { case SYMBOL_REF: case LABEL_REF: case CONST : return 1; default: return 0; } } /* Return truth value of statement that OP is a symbolic memory operand of mode MODE. */ int symbolic_memory_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { if (GET_CODE (op) == SUBREG) op = SUBREG_REG (op); if (GET_CODE (op) != MEM) return 0; op = XEXP (op, 0); return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST || GET_CODE (op) == LABEL_REF); } /* Return true if OP is a short immediate (shimm) value. */ int short_immediate_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { if (GET_CODE (op) != CONST_INT) return 0; return SMALL_INT (INTVAL (op)); } /* Return true if OP will require a long immediate (limm) value. This is currently only used when calculating length attributes. */ int long_immediate_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { switch (GET_CODE (op)) { case SYMBOL_REF : case LABEL_REF : case CONST : return 1; case CONST_INT : return !SMALL_INT (INTVAL (op)); case CONST_DOUBLE : /* These can happen because large unsigned 32-bit constants are represented this way (the multiplication patterns can cause these to be generated). They also occur for SFmode values. */ return 1; default: break; } return 0; } /* Return true if OP is a MEM that when used as a load or store address will require an 8 byte insn. Load and store instructions don't allow the same possibilities but they're similar enough that this one function will do. This is currently only used when calculating length attributes. */ int long_immediate_loadstore_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { if (GET_CODE (op) != MEM) return 0; op = XEXP (op, 0); switch (GET_CODE (op)) { case SYMBOL_REF : case LABEL_REF : case CONST : return 1; case CONST_INT : /* This must be handled as "st c,[limm]". Ditto for load. Technically, the assembler could translate some possibilities to "st c,[limm/2 + limm/2]" if limm/2 will fit in a shimm, but we don't assume that it does. */ return 1; case CONST_DOUBLE : /* These can happen because large unsigned 32-bit constants are represented this way (the multiplication patterns can cause these to be generated). They also occur for SFmode values. */ return 1; case REG : return 0; case PLUS : if (GET_CODE (XEXP (op, 1)) == CONST_INT && !SMALL_INT (INTVAL (XEXP (op, 1)))) return 1; return 0; default: break; } return 0; } /* Return true if OP is an acceptable argument for a single word move source. */ int move_src_operand (rtx op, enum machine_mode mode) { switch (GET_CODE (op)) { case SYMBOL_REF : case LABEL_REF : case CONST : return 1; case CONST_INT : return (LARGE_INT (INTVAL (op))); case CONST_DOUBLE : /* We can handle DImode integer constants in SImode if the value (signed or unsigned) will fit in 32 bits. This is needed because large unsigned 32-bit constants are represented as CONST_DOUBLEs. */ if (mode == SImode) return arc_double_limm_p (op); /* We can handle 32-bit floating point constants. */ if (mode == SFmode) return GET_MODE (op) == SFmode; return 0; case REG : return register_operand (op, mode); case SUBREG : /* (subreg (mem ...) ...) can occur here if the inner part was once a pseudo-reg and is now a stack slot. */ if (GET_CODE (SUBREG_REG (op)) == MEM) return address_operand (XEXP (SUBREG_REG (op), 0), mode); else return register_operand (op, mode); case MEM : return address_operand (XEXP (op, 0), mode); default : return 0; } } /* Return true if OP is an acceptable argument for a double word move source. */ int move_double_src_operand (rtx op, enum machine_mode mode) { switch (GET_CODE (op)) { case REG : return register_operand (op, mode); case SUBREG : /* (subreg (mem ...) ...) can occur here if the inner part was once a pseudo-reg and is now a stack slot. */ if (GET_CODE (SUBREG_REG (op)) == MEM) return move_double_src_operand (SUBREG_REG (op), mode); else return register_operand (op, mode); case MEM : /* Disallow auto inc/dec for now. */ if (GET_CODE (XEXP (op, 0)) == PRE_DEC || GET_CODE (XEXP (op, 0)) == PRE_INC) return 0; return address_operand (XEXP (op, 0), mode); case CONST_INT : case CONST_DOUBLE : return 1; default : return 0; } } /* Return true if OP is an acceptable argument for a move destination. */ int move_dest_operand (rtx op, enum machine_mode mode) { switch (GET_CODE (op)) { case REG : return register_operand (op, mode); case SUBREG : /* (subreg (mem ...) ...) can occur here if the inner part was once a pseudo-reg and is now a stack slot. */ if (GET_CODE (SUBREG_REG (op)) == MEM) return address_operand (XEXP (SUBREG_REG (op), 0), mode); else return register_operand (op, mode); case MEM : return address_operand (XEXP (op, 0), mode); default : return 0; } } /* Return true if OP is valid load with update operand. */ int load_update_operand (rtx op, enum machine_mode mode) { if (GET_CODE (op) != MEM || GET_MODE (op) != mode) return 0; op = XEXP (op, 0); if (GET_CODE (op) != PLUS || GET_MODE (op) != Pmode || !register_operand (XEXP (op, 0), Pmode) || !nonmemory_operand (XEXP (op, 1), Pmode)) return 0; return 1; } /* Return true if OP is valid store with update operand. */ int store_update_operand (rtx op, enum machine_mode mode) { if (GET_CODE (op) != MEM || GET_MODE (op) != mode) return 0; op = XEXP (op, 0); if (GET_CODE (op) != PLUS || GET_MODE (op) != Pmode || !register_operand (XEXP (op, 0), Pmode) || !(GET_CODE (XEXP (op, 1)) == CONST_INT && SMALL_INT (INTVAL (XEXP (op, 1))))) return 0; return 1; } /* Return true if OP is a non-volatile non-immediate operand. Volatile memory refs require a special "cache-bypass" instruction and only the standard movXX patterns are set up to handle them. */ int nonvol_nonimm_operand (rtx op, enum machine_mode mode) { if (GET_CODE (op) == MEM && MEM_VOLATILE_P (op)) return 0; return nonimmediate_operand (op, mode); } /* Accept integer operands in the range -0x80000000..0x7fffffff. We have to check the range carefully since this predicate is used in DImode contexts. */ int const_sint32_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { /* All allowed constants will fit a CONST_INT. */ return (GET_CODE (op) == CONST_INT && (INTVAL (op) >= (-0x7fffffff - 1) && INTVAL (op) <= 0x7fffffff)); } /* Accept integer operands in the range 0..0xffffffff. We have to check the range carefully since this predicate is used in DImode contexts. Also, we need some extra crud to make it work when hosted on 64-bit machines. */ int const_uint32_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { #if HOST_BITS_PER_WIDE_INT > 32 /* All allowed constants will fit a CONST_INT. */ return (GET_CODE (op) == CONST_INT && (INTVAL (op) >= 0 && INTVAL (op) <= 0xffffffffL)); #else return ((GET_CODE (op) == CONST_INT && INTVAL (op) >= 0) || (GET_CODE (op) == CONST_DOUBLE && CONST_DOUBLE_HIGH (op) == 0)); #endif } /* Return 1 if OP is a comparison operator valid for the mode of CC. This allows the use of MATCH_OPERATOR to recognize all the branch insns. Some insns only set a few bits in the condition code. So only allow those comparisons that use the bits that are valid. */ int proper_comparison_operator (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { enum rtx_code code; if (!COMPARISON_P (op)) return 0; code = GET_CODE (op); if (GET_MODE (XEXP (op, 0)) == CCZNmode) return (code == EQ || code == NE); if (GET_MODE (XEXP (op, 0)) == CCZNCmode) return (code == EQ || code == NE || code == LTU || code == GEU || code == GTU || code == LEU); return 1; } /* Misc. utilities. */ /* X and Y are two things to compare using CODE. Return the rtx for the cc reg in the proper mode. */ rtx gen_compare_reg (enum rtx_code code, rtx x, rtx y) { enum machine_mode mode = SELECT_CC_MODE (code, x, y); return gen_rtx_REG (mode, 61); } /* Return 1 if VALUE, a const_double, will fit in a limm (4 byte number). We assume the value can be either signed or unsigned. */ int arc_double_limm_p (rtx value) { HOST_WIDE_INT low, high; gcc_assert (GET_CODE (value) == CONST_DOUBLE); low = CONST_DOUBLE_LOW (value); high = CONST_DOUBLE_HIGH (value); if (low & 0x80000000) { return (((unsigned HOST_WIDE_INT) low <= 0xffffffff && high == 0) || (((low & - (unsigned HOST_WIDE_INT) 0x80000000) == - (unsigned HOST_WIDE_INT) 0x80000000) && high == -1)); } else { return (unsigned HOST_WIDE_INT) low <= 0x7fffffff && high == 0; } } /* Do any needed setup for a variadic function. For the ARC, we must create a register parameter block, and then copy any anonymous arguments in registers to memory. CUM has not been updated for the last named argument which has type TYPE and mode MODE, and we rely on this fact. We do things a little weird here. We're supposed to only allocate space for the anonymous arguments. However we need to keep the stack eight byte aligned. So we round the space up if necessary, and leave it to va_start to compensate. */ static void arc_setup_incoming_varargs (CUMULATIVE_ARGS *cum, enum machine_mode mode, tree type ATTRIBUTE_UNUSED, int *pretend_size, int no_rtl) { int first_anon_arg; /* All BLKmode values are passed by reference. */ gcc_assert (mode != BLKmode); first_anon_arg = *cum + ((GET_MODE_SIZE (mode) + UNITS_PER_WORD - 1) / UNITS_PER_WORD); if (first_anon_arg < MAX_ARC_PARM_REGS && !no_rtl) { /* Note that first_reg_offset < MAX_ARC_PARM_REGS. */ int first_reg_offset = first_anon_arg; /* Size in words to "pretend" allocate. */ int size = MAX_ARC_PARM_REGS - first_reg_offset; /* Extra slop to keep stack eight byte aligned. */ int align_slop = size & 1; rtx regblock; regblock = gen_rtx_MEM (BLKmode, plus_constant (arg_pointer_rtx, FIRST_PARM_OFFSET (0) + align_slop * UNITS_PER_WORD)); set_mem_alias_set (regblock, get_varargs_alias_set ()); set_mem_align (regblock, BITS_PER_WORD); move_block_from_reg (first_reg_offset, regblock, MAX_ARC_PARM_REGS - first_reg_offset); *pretend_size = ((MAX_ARC_PARM_REGS - first_reg_offset + align_slop) * UNITS_PER_WORD); } } /* Cost functions. */ /* 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 arc_rtx_costs (rtx x, int code, int outer_code ATTRIBUTE_UNUSED, int *total, bool speed ATTRIBUTE_UNUSED) { switch (code) { /* Small integers are as cheap as registers. 4 byte values can be fetched as immediate constants - let's give that the cost of an extra insn. */ case CONST_INT: if (SMALL_INT (INTVAL (x))) { *total = 0; return true; } /* FALLTHRU */ case CONST: case LABEL_REF: case SYMBOL_REF: *total = COSTS_N_INSNS (1); return true; case CONST_DOUBLE: { rtx high, low; split_double (x, &high, &low); *total = COSTS_N_INSNS (!SMALL_INT (INTVAL (high)) + !SMALL_INT (INTVAL (low))); return true; } /* Encourage synth_mult to find a synthetic multiply when reasonable. If we need more than 12 insns to do a multiply, then go out-of-line, since the call overhead will be < 10% of the cost of the multiply. */ case ASHIFT: case ASHIFTRT: case LSHIFTRT: if (TARGET_SHIFTER) *total = COSTS_N_INSNS (1); else if (GET_CODE (XEXP (x, 1)) != CONST_INT) *total = COSTS_N_INSNS (16); else *total = COSTS_N_INSNS (INTVAL (XEXP ((x), 1))); return false; default: return false; } } /* Provide the costs of an addressing mode that contains ADDR. If ADDR is not a valid address, its cost is irrelevant. */ static int arc_address_cost (rtx addr, bool speed ATTRIBUTE_UNUSED) { switch (GET_CODE (addr)) { case REG : return 1; case LABEL_REF : case SYMBOL_REF : case CONST : return 2; case PLUS : { register rtx plus0 = XEXP (addr, 0); register rtx plus1 = XEXP (addr, 1); if (GET_CODE (plus0) != REG) break; switch (GET_CODE (plus1)) { case CONST_INT : return SMALL_INT (INTVAL (plus1)) ? 1 : 2; case CONST : case SYMBOL_REF : case LABEL_REF : return 2; default: break; } break; } default: break; } return 4; } /* Function prologue/epilogue handlers. */ /* ARC stack frames look like: Before call After call +-----------------------+ +-----------------------+ | | | | high | local variables, | | local variables, | mem | reg save area, etc. | | reg save area, etc. | | | | | +-----------------------+ +-----------------------+ | | | | | arguments on stack. | | arguments on stack. | | | | | SP+16->+-----------------------+FP+48->+-----------------------+ | 4 word save area for | | reg parm save area, | | return addr, prev %fp | | only created for | SP+0->+-----------------------+ | variable argument | | functions | FP+16->+-----------------------+ | 4 word save area for | | return addr, prev %fp | FP+0->+-----------------------+ | | | local variables | | | +-----------------------+ | | | register save area | | | +-----------------------+ | | | alloca allocations | | | +-----------------------+ | | | arguments on stack | | | SP+16->+-----------------------+ low | 4 word save area for | memory | return addr, prev %fp | SP+0->+-----------------------+ Notes: 1) The "reg parm save area" does not exist for non variable argument fns. The "reg parm save area" can be eliminated completely if we created our own va-arc.h, but that has tradeoffs as well (so it's not done). */ /* Structure to be filled in by arc_compute_frame_size with register save masks, and offsets for the current function. */ struct arc_frame_info { unsigned int total_size; /* # bytes that the entire frame takes up. */ unsigned int extra_size; /* # bytes of extra stuff. */ unsigned int pretend_size; /* # bytes we push and pretend caller did. */ unsigned int args_size; /* # bytes that outgoing arguments take up. */ unsigned int reg_size; /* # bytes needed to store regs. */ unsigned int var_size; /* # bytes that variables take up. */ unsigned int reg_offset; /* Offset from new sp to store regs. */ unsigned int gmask; /* Mask of saved gp registers. */ int initialized; /* Nonzero if frame size already calculated. */ }; /* Current frame information calculated by arc_compute_frame_size. */ static struct arc_frame_info current_frame_info; /* Zero structure to initialize current_frame_info. */ static struct arc_frame_info zero_frame_info; /* Type of function DECL. The result is cached. To reset the cache at the end of a function, call with DECL = NULL_TREE. */ enum arc_function_type arc_compute_function_type (tree decl) { tree a; /* Cached value. */ static enum arc_function_type fn_type = ARC_FUNCTION_UNKNOWN; /* Last function we were called for. */ static tree last_fn = NULL_TREE; /* Resetting the cached value? */ if (decl == NULL_TREE) { fn_type = ARC_FUNCTION_UNKNOWN; last_fn = NULL_TREE; return fn_type; } if (decl == last_fn && fn_type != ARC_FUNCTION_UNKNOWN) return fn_type; /* Assume we have a normal function (not an interrupt handler). */ fn_type = ARC_FUNCTION_NORMAL; /* Now see if this is an interrupt handler. */ for (a = DECL_ATTRIBUTES (current_function_decl); a; a = TREE_CHAIN (a)) { tree name = TREE_PURPOSE (a), args = TREE_VALUE (a); if (name == get_identifier ("__interrupt__") && list_length (args) == 1 && TREE_CODE (TREE_VALUE (args)) == STRING_CST) { tree value = TREE_VALUE (args); if (!strcmp (TREE_STRING_POINTER (value), "ilink1")) fn_type = ARC_FUNCTION_ILINK1; else if (!strcmp (TREE_STRING_POINTER (value), "ilink2")) fn_type = ARC_FUNCTION_ILINK2; else gcc_unreachable (); break; } } last_fn = decl; return fn_type; } #define ILINK1_REGNUM 29 #define ILINK2_REGNUM 30 #define RETURN_ADDR_REGNUM 31 #define FRAME_POINTER_MASK (1 << (FRAME_POINTER_REGNUM)) #define RETURN_ADDR_MASK (1 << (RETURN_ADDR_REGNUM)) /* Tell prologue and epilogue if register REGNO should be saved / restored. The return address and frame pointer are treated separately. Don't consider them here. */ #define MUST_SAVE_REGISTER(regno, interrupt_p) \ ((regno) != RETURN_ADDR_REGNUM && (regno) != FRAME_POINTER_REGNUM \ && (df_regs_ever_live_p (regno) && (!call_used_regs[regno] || interrupt_p))) #define MUST_SAVE_RETURN_ADDR (df_regs_ever_live_p (RETURN_ADDR_REGNUM)) /* Return the bytes needed to compute the frame pointer from the current stack pointer. SIZE is the size needed for local variables. */ unsigned int arc_compute_frame_size (int size /* # of var. bytes allocated. */) { int regno; unsigned int total_size, var_size, args_size, pretend_size, extra_size; unsigned int reg_size, reg_offset; unsigned int gmask; enum arc_function_type fn_type; int interrupt_p; var_size = size; args_size = crtl->outgoing_args_size; pretend_size = crtl->args.pretend_args_size; extra_size = FIRST_PARM_OFFSET (0); total_size = extra_size + pretend_size + args_size + var_size; reg_offset = FIRST_PARM_OFFSET(0) + crtl->outgoing_args_size; reg_size = 0; gmask = 0; /* See if this is an interrupt handler. Call used registers must be saved for them too. */ fn_type = arc_compute_function_type (current_function_decl); interrupt_p = ARC_INTERRUPT_P (fn_type); /* Calculate space needed for registers. ??? We ignore the extension registers for now. */ for (regno = 0; regno <= 31; regno++) { if (MUST_SAVE_REGISTER (regno, interrupt_p)) { reg_size += UNITS_PER_WORD; gmask |= 1 << regno; } } total_size += reg_size; /* If the only space to allocate is the fp/blink save area this is an empty frame. However, if we'll be making a function call we need to allocate a stack frame for our callee's fp/blink save area. */ if (total_size == extra_size && !MUST_SAVE_RETURN_ADDR) total_size = extra_size = 0; total_size = ARC_STACK_ALIGN (total_size); /* Save computed information. */ current_frame_info.total_size = total_size; current_frame_info.extra_size = extra_size; current_frame_info.pretend_size = pretend_size; current_frame_info.var_size = var_size; current_frame_info.args_size = args_size; current_frame_info.reg_size = reg_size; current_frame_info.reg_offset = reg_offset; current_frame_info.gmask = gmask; current_frame_info.initialized = reload_completed; /* Ok, we're done. */ return total_size; } /* Common code to save/restore registers. */ void arc_save_restore (FILE *file, const char *base_reg, unsigned int offset, unsigned int gmask, const char *op) { int regno; if (gmask == 0) return; for (regno = 0; regno <= 31; regno++) { if ((gmask & (1L << regno)) != 0) { fprintf (file, "\t%s %s,[%s,%d]\n", op, reg_names[regno], base_reg, offset); offset += UNITS_PER_WORD; } } } /* Target hook to assemble an integer object. The ARC version needs to emit a special directive for references to labels and function symbols. */ static bool arc_assemble_integer (rtx x, unsigned int size, int aligned_p) { if (size == UNITS_PER_WORD && aligned_p && ((GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_FUNCTION_P (x)) || GET_CODE (x) == LABEL_REF)) { fputs ("\t.word\t%st(", asm_out_file); output_addr_const (asm_out_file, x); fputs (")\n", asm_out_file); return true; } return default_assemble_integer (x, size, aligned_p); } /* Set up the stack and frame pointer (if desired) for the function. */ static void arc_output_function_prologue (FILE *file, HOST_WIDE_INT size) { const char *sp_str = reg_names[STACK_POINTER_REGNUM]; const char *fp_str = reg_names[FRAME_POINTER_REGNUM]; unsigned int gmask = current_frame_info.gmask; enum arc_function_type fn_type = arc_compute_function_type (current_function_decl); /* If this is an interrupt handler, set up our stack frame. ??? Optimize later. */ if (ARC_INTERRUPT_P (fn_type)) { fprintf (file, "\t%s interrupt handler\n", ASM_COMMENT_START); fprintf (file, "\tsub %s,%s,16\n", sp_str, sp_str); } /* This is only for the human reader. */ fprintf (file, "\t%s BEGIN PROLOGUE %s vars= %d, regs= %d, args= %d, extra= %d\n", ASM_COMMENT_START, ASM_COMMENT_START, current_frame_info.var_size, current_frame_info.reg_size / 4, current_frame_info.args_size, current_frame_info.extra_size); size = ARC_STACK_ALIGN (size); size = (! current_frame_info.initialized ? arc_compute_frame_size (size) : current_frame_info.total_size); /* These cases shouldn't happen. Catch them now. */ gcc_assert (size || !gmask); /* Allocate space for register arguments if this is a variadic function. */ if (current_frame_info.pretend_size != 0) fprintf (file, "\tsub %s,%s,%d\n", sp_str, sp_str, current_frame_info.pretend_size); /* The home-grown ABI says link register is saved first. */ if (MUST_SAVE_RETURN_ADDR) fprintf (file, "\tst %s,[%s,%d]\n", reg_names[RETURN_ADDR_REGNUM], sp_str, UNITS_PER_WORD); /* Set up the previous frame pointer next (if we need to). */ if (frame_pointer_needed) { fprintf (file, "\tst %s,[%s]\n", fp_str, sp_str); fprintf (file, "\tmov %s,%s\n", fp_str, sp_str); } /* ??? We don't handle the case where the saved regs are more than 252 bytes away from sp. This can be handled by decrementing sp once, saving the regs, and then decrementing it again. The epilogue doesn't have this problem as the `ld' insn takes reg+limm values (though it would be more efficient to avoid reg+limm). */ /* Allocate the stack frame. */ if (size - current_frame_info.pretend_size > 0) fprintf (file, "\tsub %s,%s," HOST_WIDE_INT_PRINT_DEC "\n", sp_str, sp_str, size - current_frame_info.pretend_size); /* Save any needed call-saved regs (and call-used if this is an interrupt handler). */ arc_save_restore (file, sp_str, current_frame_info.reg_offset, /* The zeroing of these two bits is unnecessary, but leave this in for clarity. */ gmask & ~(FRAME_POINTER_MASK | RETURN_ADDR_MASK), "st"); fprintf (file, "\t%s END PROLOGUE\n", ASM_COMMENT_START); } /* Do any necessary cleanup after a function to restore stack, frame, and regs. */ static void arc_output_function_epilogue (FILE *file, HOST_WIDE_INT size) { rtx epilogue_delay = crtl->epilogue_delay_list; int noepilogue = FALSE; enum arc_function_type fn_type = arc_compute_function_type (current_function_decl); /* This is only for the human reader. */ fprintf (file, "\t%s EPILOGUE\n", ASM_COMMENT_START); size = ARC_STACK_ALIGN (size); size = (!current_frame_info.initialized ? arc_compute_frame_size (size) : current_frame_info.total_size); if (size == 0 && epilogue_delay == 0) { rtx insn = get_last_insn (); /* If the last insn was a BARRIER, we don't have to write any code because a jump (aka return) was put there. */ if (GET_CODE (insn) == NOTE) insn = prev_nonnote_insn (insn); if (insn && GET_CODE (insn) == BARRIER) noepilogue = TRUE; } if (!noepilogue) { unsigned int pretend_size = current_frame_info.pretend_size; unsigned int frame_size = size - pretend_size; int restored, fp_restored_p; int can_trust_sp_p = !cfun->calls_alloca; const char *sp_str = reg_names[STACK_POINTER_REGNUM]; const char *fp_str = reg_names[FRAME_POINTER_REGNUM]; /* ??? There are lots of optimizations that can be done here. EG: Use fp to restore regs if it's closer. Maybe in time we'll do them all. For now, always restore regs from sp, but don't restore sp if we don't have to. */ if (!can_trust_sp_p) { gcc_assert (frame_pointer_needed); fprintf (file,"\tsub %s,%s,%d\t\t%s sp not trusted here\n", sp_str, fp_str, frame_size, ASM_COMMENT_START); } /* Restore any saved registers. */ arc_save_restore (file, sp_str, current_frame_info.reg_offset, /* The zeroing of these two bits is unnecessary, but leave this in for clarity. */ current_frame_info.gmask & ~(FRAME_POINTER_MASK | RETURN_ADDR_MASK), "ld"); if (MUST_SAVE_RETURN_ADDR) fprintf (file, "\tld %s,[%s,%d]\n", reg_names[RETURN_ADDR_REGNUM], frame_pointer_needed ? fp_str : sp_str, UNITS_PER_WORD + (frame_pointer_needed ? 0 : frame_size)); /* Keep track of how much of the stack pointer we've restored. It makes the following a lot more readable. */ restored = 0; fp_restored_p = 0; /* We try to emit the epilogue delay slot insn right after the load of the return address register so that it can execute with the stack intact. Secondly, loads are delayed. */ /* ??? If stack intactness is important, always emit now. */ if (MUST_SAVE_RETURN_ADDR && epilogue_delay != NULL_RTX) { final_scan_insn (XEXP (epilogue_delay, 0), file, 1, 1, NULL); epilogue_delay = NULL_RTX; } if (frame_pointer_needed) { /* Try to restore the frame pointer in the delay slot. We can't, however, if any of these is true. */ if (epilogue_delay != NULL_RTX || !SMALL_INT (frame_size) || pretend_size || ARC_INTERRUPT_P (fn_type)) { /* Note that we restore fp and sp here! */ fprintf (file, "\tld.a %s,[%s,%d]\n", fp_str, sp_str, frame_size); restored += frame_size; fp_restored_p = 1; } } else if (!SMALL_INT (size /* frame_size + pretend_size */) || ARC_INTERRUPT_P (fn_type)) { fprintf (file, "\tadd %s,%s,%d\n", sp_str, sp_str, frame_size); restored += frame_size; } /* These must be done before the return insn because the delay slot does the final stack restore. */ if (ARC_INTERRUPT_P (fn_type)) { if (epilogue_delay) { final_scan_insn (XEXP (epilogue_delay, 0), file, 1, 1, NULL); } } /* Emit the return instruction. */ { static const int regs[4] = { 0, RETURN_ADDR_REGNUM, ILINK1_REGNUM, ILINK2_REGNUM }; /* Update the flags, if returning from an interrupt handler. */ if (ARC_INTERRUPT_P (fn_type)) fprintf (file, "\tj.d.f %s\n", reg_names[regs[fn_type]]); else fprintf (file, "\tj.d %s\n", reg_names[regs[fn_type]]); } /* If the only register saved is the return address, we need a nop, unless we have an instruction to put into it. Otherwise we don't since reloading multiple registers doesn't reference the register being loaded. */ if (ARC_INTERRUPT_P (fn_type)) fprintf (file, "\tadd %s,%s,16\n", sp_str, sp_str); else if (epilogue_delay != NULL_RTX) { gcc_assert (!frame_pointer_needed || fp_restored_p); gcc_assert (restored >= size); final_scan_insn (XEXP (epilogue_delay, 0), file, 1, 1, NULL); } else if (frame_pointer_needed && !fp_restored_p) { gcc_assert (SMALL_INT (frame_size)); /* Note that we restore fp and sp here! */ fprintf (file, "\tld.a %s,[%s,%d]\n", fp_str, sp_str, frame_size); } else if (restored < size) { gcc_assert (SMALL_INT (size - restored)); fprintf (file, "\tadd %s,%s," HOST_WIDE_INT_PRINT_DEC "\n", sp_str, sp_str, size - restored); } else fprintf (file, "\tnop\n"); } /* Reset state info for each function. */ current_frame_info = zero_frame_info; arc_compute_function_type (NULL_TREE); } /* Define the number of delay slots needed for the function epilogue. Interrupt handlers can't have any epilogue delay slots (it's always needed for something else, I think). For normal functions, we have to worry about using call-saved regs as they'll be restored before the delay slot insn. Functions with non-empty frames already have enough choices for the epilogue delay slot so for now we only consider functions with empty frames. */ int arc_delay_slots_for_epilogue (void) { if (arc_compute_function_type (current_function_decl) != ARC_FUNCTION_NORMAL) return 0; if (!current_frame_info.initialized) (void) arc_compute_frame_size (get_frame_size ()); if (current_frame_info.total_size == 0) return 1; return 0; } /* Return true if TRIAL is a valid insn for the epilogue delay slot. Any single length instruction which doesn't reference the stack or frame pointer or any call-saved register is OK. SLOT will always be 0. */ int arc_eligible_for_epilogue_delay (rtx trial, int slot) { gcc_assert (!slot); if (get_attr_length (trial) == 1 /* If registers where saved, presumably there's more than enough possibilities for the delay slot. The alternative is something more complicated (of course, if we expanded the epilogue as rtl this problem would go away). */ /* ??? Note that this will always be true since only functions with empty frames have epilogue delay slots. See arc_delay_slots_for_epilogue. */ && current_frame_info.gmask == 0 && ! reg_mentioned_p (stack_pointer_rtx, PATTERN (trial)) && ! reg_mentioned_p (frame_pointer_rtx, PATTERN (trial))) return 1; return 0; } /* Return true if OP is a shift operator. */ int shift_operator (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) { switch (GET_CODE (op)) { case ASHIFTRT: case LSHIFTRT: case ASHIFT: return 1; default: return 0; } } /* Output the assembler code for doing a shift. We go to a bit of trouble to generate efficient code as the ARC only has single bit shifts. This is taken from the h8300 port. We only have one mode of shifting and can't access individual bytes like the h8300 can, so this is greatly simplified (at the expense of not generating hyper- efficient code). This function is not used if the variable shift insns are present. */ /* ??? We assume the output operand is the same as operand 1. This can be optimized (deleted) in the case of 1 bit shifts. */ /* ??? We use the loop register here. We don't use it elsewhere (yet) and using it here will give us a chance to play with it. */ const char * output_shift (rtx *operands) { rtx shift = operands[3]; enum machine_mode mode = GET_MODE (shift); enum rtx_code code = GET_CODE (shift); const char *shift_one; gcc_assert (mode == SImode); switch (code) { case ASHIFT: shift_one = "asl %0,%0"; break; case ASHIFTRT: shift_one = "asr %0,%0"; break; case LSHIFTRT: shift_one = "lsr %0,%0"; break; default: gcc_unreachable (); } if (GET_CODE (operands[2]) != CONST_INT) { if (optimize) { output_asm_insn ("sub.f 0,%2,0", operands); output_asm_insn ("mov lp_count,%2", operands); output_asm_insn ("bz 2f", operands); } else output_asm_insn ("mov %4,%2", operands); goto shiftloop; } else { int n; /* If the count is negative, make it 0. */ n = INTVAL (operands[2]); if (n < 0) n = 0; /* If the count is too big, truncate it. ANSI says shifts of GET_MODE_BITSIZE are undefined - we choose to do the intuitive thing. */ else if (n > GET_MODE_BITSIZE (mode)) n = GET_MODE_BITSIZE (mode); /* First see if we can do them inline. */ if (n <= 8) { while (--n >= 0) output_asm_insn (shift_one, operands); } /* See if we can use a rotate/and. */ else if (n == BITS_PER_WORD - 1) { switch (code) { case ASHIFT : output_asm_insn ("and %0,%0,1\n\tror %0,%0", operands); break; case ASHIFTRT : /* The ARC doesn't have a rol insn. Use something else. */ output_asm_insn ("asl.f 0,%0\n\tsbc %0,0,0", operands); break; case LSHIFTRT : /* The ARC doesn't have a rol insn. Use something else. */ output_asm_insn ("asl.f 0,%0\n\tadc %0,0,0", operands); break; default: break; } } /* Must loop. */ else { char buf[100]; if (optimize) output_asm_insn ("mov lp_count,%c2", operands); else output_asm_insn ("mov %4,%c2", operands); shiftloop: if (optimize) { if (flag_pic) sprintf (buf, "lr %%4,[status]\n\tadd %%4,%%4,6\t%s single insn loop start", ASM_COMMENT_START); else sprintf (buf, "mov %%4,%%%%st(1f)\t%s (single insn loop start) >> 2", ASM_COMMENT_START); output_asm_insn (buf, operands); output_asm_insn ("sr %4,[lp_start]", operands); output_asm_insn ("add %4,%4,1", operands); output_asm_insn ("sr %4,[lp_end]", operands); output_asm_insn ("nop\n\tnop", operands); if (flag_pic) fprintf (asm_out_file, "\t%s single insn loop\n", ASM_COMMENT_START); else fprintf (asm_out_file, "1:\t%s single insn loop\n", ASM_COMMENT_START); output_asm_insn (shift_one, operands); fprintf (asm_out_file, "2:\t%s end single insn loop\n", ASM_COMMENT_START); } else { fprintf (asm_out_file, "1:\t%s begin shift loop\n", ASM_COMMENT_START); output_asm_insn ("sub.f %4,%4,1", operands); output_asm_insn ("nop", operands); output_asm_insn ("bn.nd 2f", operands); output_asm_insn (shift_one, operands); output_asm_insn ("b.nd 1b", operands); fprintf (asm_out_file, "2:\t%s end shift loop\n", ASM_COMMENT_START); } } } return ""; } /* Nested function support. */ /* Emit RTL insns to initialize the variable parts of a trampoline. FNADDR is an RTX for the address of the function's pure code. CXT is an RTX for the static chain value for the function. */ void arc_initialize_trampoline (rtx tramp ATTRIBUTE_UNUSED, rtx fnaddr ATTRIBUTE_UNUSED, rtx cxt ATTRIBUTE_UNUSED) { } /* Set the cpu type and print out other fancy things, at the top of the file. */ static void arc_file_start (void) { default_file_start (); fprintf (asm_out_file, "\t.cpu %s\n", arc_cpu_string); } /* Print operand X (an rtx) in assembler syntax to file FILE. CODE is a letter or dot (`z' in `%z0') or 0 if no letter was specified. For `%' followed by punctuation, CODE is the punctuation and X is null. */ void arc_print_operand (FILE *file, rtx x, int code) { switch (code) { case '#' : /* Conditional branches. For now these are equivalent. */ case '*' : /* Unconditional branches. Output the appropriate delay slot suffix. */ if (!final_sequence || XVECLEN (final_sequence, 0) == 1) { /* There's nothing in the delay slot. */ fputs (".nd", file); } else { rtx jump = XVECEXP (final_sequence, 0, 0); rtx delay = XVECEXP (final_sequence, 0, 1); if (INSN_ANNULLED_BRANCH_P (jump)) fputs (INSN_FROM_TARGET_P (delay) ? ".jd" : ".nd", file); else fputs (".d", file); } return; case '?' : /* with leading "." */ case '!' : /* without leading "." */ /* This insn can be conditionally executed. See if the ccfsm machinery says it should be conditionalized. */ if (arc_ccfsm_state == 3 || arc_ccfsm_state == 4) { /* Is this insn in a delay slot? */ if (final_sequence && XVECLEN (final_sequence, 0) == 2) { rtx insn = XVECEXP (final_sequence, 0, 1); /* If the insn is annulled and is from the target path, we need to inverse the condition test. */ if (INSN_ANNULLED_BRANCH_P (insn)) { if (INSN_FROM_TARGET_P (insn)) fprintf (file, "%s%s", code == '?' ? "." : "", arc_condition_codes[ARC_INVERSE_CONDITION_CODE (arc_ccfsm_current_cc)]); else fprintf (file, "%s%s", code == '?' ? "." : "", arc_condition_codes[arc_ccfsm_current_cc]); } else { /* This insn is executed for either path, so don't conditionalize it at all. */ ; /* nothing to do */ } } else { /* This insn isn't in a delay slot. */ fprintf (file, "%s%s", code == '?' ? "." : "", arc_condition_codes[arc_ccfsm_current_cc]); } } return; case '~' : /* Output a nop if we're between a set of the condition codes, and a conditional branch. */ if (last_insn_set_cc_p) fputs ("nop\n\t", file); return; case 'd' : fputs (arc_condition_codes[get_arc_condition_code (x)], file); return; case 'D' : fputs (arc_condition_codes[ARC_INVERSE_CONDITION_CODE (get_arc_condition_code (x))], file); return; case 'R' : /* Write second word of DImode or DFmode reference, register or memory. */ if (GET_CODE (x) == REG) fputs (reg_names[REGNO (x)+1], file); else if (GET_CODE (x) == MEM) { fputc ('[', file); /* Handle possible auto-increment. Since it is pre-increment and we have already done it, we can just use an offset of four. */ /* ??? This is taken from rs6000.c I think. I don't think it is currently necessary, but keep it around. */ if (GET_CODE (XEXP (x, 0)) == PRE_INC || GET_CODE (XEXP (x, 0)) == PRE_DEC) output_address (plus_constant (XEXP (XEXP (x, 0), 0), 4)); else output_address (plus_constant (XEXP (x, 0), 4)); fputc (']', file); } else output_operand_lossage ("invalid operand to %%R code"); return; case 'S' : if ((GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_FUNCTION_P (x)) || GET_CODE (x) == LABEL_REF) { fprintf (file, "%%st("); output_addr_const (file, x); fprintf (file, ")"); return; } break; case 'H' : case 'L' : if (GET_CODE (x) == REG) { /* L = least significant word, H = most significant word */ if ((TARGET_BIG_ENDIAN != 0) ^ (code == 'L')) fputs (reg_names[REGNO (x)], file); else fputs (reg_names[REGNO (x)+1], file); } else if (GET_CODE (x) == CONST_INT || GET_CODE (x) == CONST_DOUBLE) { rtx first, second; split_double (x, &first, &second); fprintf (file, "0x%08lx", (long)(code == 'L' ? INTVAL (first) : INTVAL (second))); } else output_operand_lossage ("invalid operand to %%H/%%L code"); return; case 'A' : { char str[30]; gcc_assert (GET_CODE (x) == CONST_DOUBLE && GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT); real_to_decimal (str, CONST_DOUBLE_REAL_VALUE (x), sizeof (str), 0, 1); fprintf (file, "%s", str); return; } case 'U' : /* Output a load/store with update indicator if appropriate. */ if (GET_CODE (x) == MEM) { if (GET_CODE (XEXP (x, 0)) == PRE_INC || GET_CODE (XEXP (x, 0)) == PRE_DEC) fputs (".a", file); } else output_operand_lossage ("invalid operand to %%U code"); return; case 'V' : /* Output cache bypass indicator for a load/store insn. Volatile memory refs are defined to use the cache bypass mechanism. */ if (GET_CODE (x) == MEM) { if (MEM_VOLATILE_P (x)) fputs (".di", file); } else output_operand_lossage ("invalid operand to %%V code"); return; case 0 : /* Do nothing special. */ break; default : /* Unknown flag. */ output_operand_lossage ("invalid operand output code"); } switch (GET_CODE (x)) { case REG : fputs (reg_names[REGNO (x)], file); break; case MEM : fputc ('[', file); if (GET_CODE (XEXP (x, 0)) == PRE_INC) output_address (plus_constant (XEXP (XEXP (x, 0), 0), GET_MODE_SIZE (GET_MODE (x)))); else if (GET_CODE (XEXP (x, 0)) == PRE_DEC) output_address (plus_constant (XEXP (XEXP (x, 0), 0), - GET_MODE_SIZE (GET_MODE (x)))); else output_address (XEXP (x, 0)); fputc (']', file); break; case CONST_DOUBLE : /* We handle SFmode constants here as output_addr_const doesn't. */ if (GET_MODE (x) == SFmode) { REAL_VALUE_TYPE d; long l; REAL_VALUE_FROM_CONST_DOUBLE (d, x); REAL_VALUE_TO_TARGET_SINGLE (d, l); fprintf (file, "0x%08lx", l); break; } /* Fall through. Let output_addr_const deal with it. */ default : output_addr_const (file, x); break; } } /* Print a memory address as an operand to reference that memory location. */ void arc_print_operand_address (FILE *file, rtx addr) { register rtx base, index = 0; int offset = 0; switch (GET_CODE (addr)) { case REG : fputs (reg_names[REGNO (addr)], file); break; case SYMBOL_REF : if (/*???*/ 0 && SYMBOL_REF_FUNCTION_P (addr)) { fprintf (file, "%%st("); output_addr_const (file, addr); fprintf (file, ")"); } else output_addr_const (file, addr); break; case PLUS : if (GET_CODE (XEXP (addr, 0)) == CONST_INT) offset = INTVAL (XEXP (addr, 0)), base = XEXP (addr, 1); else if (GET_CODE (XEXP (addr, 1)) == CONST_INT) offset = INTVAL (XEXP (addr, 1)), base = XEXP (addr, 0); else base = XEXP (addr, 0), index = XEXP (addr, 1); gcc_assert (GET_CODE (base) == REG); fputs (reg_names[REGNO (base)], file); if (index == 0) { if (offset != 0) fprintf (file, ",%d", offset); } else { switch (GET_CODE (index)) { case REG: fprintf (file, ",%s", reg_names[REGNO (index)]); break; case SYMBOL_REF: fputc (',', file), output_addr_const (file, index); break; default: gcc_unreachable (); } } break; case PRE_INC : case PRE_DEC : /* We shouldn't get here as we've lost the mode of the memory object (which says how much to inc/dec by. */ gcc_unreachable (); break; default : output_addr_const (file, addr); break; } } /* Update compare/branch separation marker. */ static void record_cc_ref (rtx insn) { last_insn_set_cc_p = current_insn_set_cc_p; switch (get_attr_cond (insn)) { case COND_SET : case COND_SET_ZN : case COND_SET_ZNC : if (get_attr_length (insn) == 1) current_insn_set_cc_p = 1; else current_insn_set_cc_p = 0; break; default : current_insn_set_cc_p = 0; break; } } /* Conditional execution support. This is based on the ARM port but for now is much simpler. A finite state machine takes care of noticing whether or not instructions can be conditionally executed, and thus decrease execution time and code size by deleting branch instructions. The fsm is controlled by final_prescan_insn, and controls the actions of PRINT_OPERAND. The patterns in the .md file for the branch insns also have a hand in this. */ /* The state of the fsm controlling condition codes are: 0: normal, do nothing special 1: don't output this insn 2: don't output this insn 3: make insns conditional 4: make insns conditional State transitions (state->state by whom, under what condition): 0 -> 1 final_prescan_insn, if insn is conditional branch 0 -> 2 final_prescan_insn, if the `target' is an unconditional branch 1 -> 3 branch patterns, after having not output the conditional branch 2 -> 4 branch patterns, after having not output the conditional branch 3 -> 0 (*targetm.asm_out.internal_label), if the `target' label is reached (the target label has CODE_LABEL_NUMBER equal to arc_ccfsm_target_label). 4 -> 0 final_prescan_insn, if `target' unconditional branch is reached If the jump clobbers the conditions then we use states 2 and 4. A similar thing can be done with conditional return insns. We also handle separating branches from sets of the condition code. This is done here because knowledge of the ccfsm state is required, we may not be outputting the branch. */ void arc_final_prescan_insn (rtx insn, rtx *opvec ATTRIBUTE_UNUSED, int noperands ATTRIBUTE_UNUSED) { /* BODY will hold the body of INSN. */ register rtx body = PATTERN (insn); /* This will be 1 if trying to repeat the trick (i.e.: do the `else' part of an if/then/else), and things need to be reversed. */ int reverse = 0; /* If we start with a return insn, we only succeed if we find another one. */ int seeking_return = 0; /* START_INSN will hold the insn from where we start looking. This is the first insn after the following code_label if REVERSE is true. */ rtx start_insn = insn; /* Update compare/branch separation marker. */ record_cc_ref (insn); /* Allow -mdebug-ccfsm to turn this off so we can see how well it does. We can't do this in macro FINAL_PRESCAN_INSN because its called from final_scan_insn which has `optimize' as a local. */ if (optimize < 2 || TARGET_NO_COND_EXEC) return; /* If in state 4, check if the target branch is reached, in order to change back to state 0. */ if (arc_ccfsm_state == 4) { if (insn == arc_ccfsm_target_insn) { arc_ccfsm_target_insn = NULL; arc_ccfsm_state = 0; } return; } /* If in state 3, it is possible to repeat the trick, if this insn is an unconditional branch to a label, and immediately following this branch is the previous target label which is only used once, and the label this branch jumps to is not too far off. Or in other words "we've done the `then' part, see if we can do the `else' part." */ if (arc_ccfsm_state == 3) { if (simplejump_p (insn)) { start_insn = next_nonnote_insn (start_insn); if (GET_CODE (start_insn) == BARRIER) { /* ??? Isn't this always a barrier? */ start_insn = next_nonnote_insn (start_insn); } if (GET_CODE (start_insn) == CODE_LABEL && CODE_LABEL_NUMBER (start_insn) == arc_ccfsm_target_label && LABEL_NUSES (start_insn) == 1) reverse = TRUE; else return; } else if (GET_CODE (body) == RETURN) { start_insn = next_nonnote_insn (start_insn); if (GET_CODE (start_insn) == BARRIER) start_insn = next_nonnote_insn (start_insn); if (GET_CODE (start_insn) == CODE_LABEL && CODE_LABEL_NUMBER (start_insn) == arc_ccfsm_target_label && LABEL_NUSES (start_insn) == 1) { reverse = TRUE; seeking_return = 1; } else return; } else return; } if (GET_CODE (insn) != JUMP_INSN) return; /* This jump might be paralleled with a clobber of the condition codes, the jump should always come first. */ if (GET_CODE (body) == PARALLEL && XVECLEN (body, 0) > 0) body = XVECEXP (body, 0, 0); if (reverse || (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC && GET_CODE (SET_SRC (body)) == IF_THEN_ELSE)) { int insns_skipped = 0, fail = FALSE, succeed = FALSE; /* Flag which part of the IF_THEN_ELSE is the LABEL_REF. */ int then_not_else = TRUE; /* Nonzero if next insn must be the target label. */ int next_must_be_target_label_p; rtx this_insn = start_insn, label = 0; /* Register the insn jumped to. */ if (reverse) { if (!seeking_return) label = XEXP (SET_SRC (body), 0); } else if (GET_CODE (XEXP (SET_SRC (body), 1)) == LABEL_REF) label = XEXP (XEXP (SET_SRC (body), 1), 0); else if (GET_CODE (XEXP (SET_SRC (body), 2)) == LABEL_REF) { label = XEXP (XEXP (SET_SRC (body), 2), 0); then_not_else = FALSE; } else if (GET_CODE (XEXP (SET_SRC (body), 1)) == RETURN) seeking_return = 1; else if (GET_CODE (XEXP (SET_SRC (body), 2)) == RETURN) { seeking_return = 1; then_not_else = FALSE; } else gcc_unreachable (); /* See how many insns this branch skips, and what kind of insns. If all insns are okay, and the label or unconditional branch to the same label is not too far away, succeed. */ for (insns_skipped = 0, next_must_be_target_label_p = FALSE; !fail && !succeed && insns_skipped < MAX_INSNS_SKIPPED; insns_skipped++) { rtx scanbody; this_insn = next_nonnote_insn (this_insn); if (!this_insn) break; if (next_must_be_target_label_p) { if (GET_CODE (this_insn) == BARRIER) continue; if (GET_CODE (this_insn) == CODE_LABEL && this_insn == label) { arc_ccfsm_state = 1; succeed = TRUE; } else fail = TRUE; break; } scanbody = PATTERN (this_insn); switch (GET_CODE (this_insn)) { case CODE_LABEL: /* Succeed if it is the target label, otherwise fail since control falls in from somewhere else. */ if (this_insn == label) { arc_ccfsm_state = 1; succeed = TRUE; } else fail = TRUE; break; case BARRIER: /* Succeed if the following insn is the target label. Otherwise fail. If return insns are used then the last insn in a function will be a barrier. */ next_must_be_target_label_p = TRUE; break; case CALL_INSN: /* Can handle a call insn if there are no insns after it. IE: The next "insn" is the target label. We don't have to worry about delay slots as such insns are SEQUENCE's inside INSN's. ??? It is possible to handle such insns though. */ if (get_attr_cond (this_insn) == COND_CANUSE) next_must_be_target_label_p = TRUE; else fail = TRUE; break; case JUMP_INSN: /* If this is an unconditional branch to the same label, succeed. If it is to another label, do nothing. If it is conditional, fail. */ /* ??? Probably, the test for the SET and the PC are unnecessary. */ if (GET_CODE (scanbody) == SET && GET_CODE (SET_DEST (scanbody)) == PC) { if (GET_CODE (SET_SRC (scanbody)) == LABEL_REF && XEXP (SET_SRC (scanbody), 0) == label && !reverse) { arc_ccfsm_state = 2; succeed = TRUE; } else if (GET_CODE (SET_SRC (scanbody)) == IF_THEN_ELSE) fail = TRUE; } else if (GET_CODE (scanbody) == RETURN && seeking_return) { arc_ccfsm_state = 2; succeed = TRUE; } else if (GET_CODE (scanbody) == PARALLEL) { if (get_attr_cond (this_insn) != COND_CANUSE) fail = TRUE; } break; case INSN: /* We can only do this with insns that can use the condition codes (and don't set them). */ if (GET_CODE (scanbody) == SET || GET_CODE (scanbody) == PARALLEL) { if (get_attr_cond (this_insn) != COND_CANUSE) fail = TRUE; } /* We can't handle other insns like sequences. */ else fail = TRUE; break; default: break; } } if (succeed) { if ((!seeking_return) && (arc_ccfsm_state == 1 || reverse)) arc_ccfsm_target_label = CODE_LABEL_NUMBER (label); else { gcc_assert (seeking_return || arc_ccfsm_state == 2); while (this_insn && GET_CODE (PATTERN (this_insn)) == USE) { this_insn = next_nonnote_insn (this_insn); gcc_assert (!this_insn || (GET_CODE (this_insn) != BARRIER && GET_CODE (this_insn) != CODE_LABEL)); } if (!this_insn) { /* Oh dear! we ran off the end, give up. */ extract_insn_cached (insn); arc_ccfsm_state = 0; arc_ccfsm_target_insn = NULL; return; } arc_ccfsm_target_insn = this_insn; } /* If REVERSE is true, ARM_CURRENT_CC needs to be inverted from what it was. */ if (!reverse) arc_ccfsm_current_cc = get_arc_condition_code (XEXP (SET_SRC (body), 0)); if (reverse || then_not_else) arc_ccfsm_current_cc = ARC_INVERSE_CONDITION_CODE (arc_ccfsm_current_cc); } /* Restore recog_data. Getting the attributes of other insns can destroy this array, but final.c assumes that it remains intact across this call. */ extract_insn_cached (insn); } } /* Record that we are currently outputting label NUM with prefix PREFIX. It it's the label we're looking for, reset the ccfsm machinery. Called from (*targetm.asm_out.internal_label). */ void arc_ccfsm_at_label (const char *prefix, int num) { if (arc_ccfsm_state == 3 && arc_ccfsm_target_label == num && !strcmp (prefix, "L")) { arc_ccfsm_state = 0; arc_ccfsm_target_insn = NULL_RTX; } } /* See if the current insn, which is a conditional branch, is to be deleted. */ int arc_ccfsm_branch_deleted_p (void) { if (arc_ccfsm_state == 1 || arc_ccfsm_state == 2) return 1; return 0; } /* Record a branch isn't output because subsequent insns can be conditionalized. */ void arc_ccfsm_record_branch_deleted (void) { /* Indicate we're conditionalizing insns now. */ arc_ccfsm_state += 2; /* If the next insn is a subroutine call, we still need a nop between the cc setter and user. We need to undo the effect of calling record_cc_ref for the just deleted branch. */ current_insn_set_cc_p = last_insn_set_cc_p; } static void arc_va_start (tree valist, rtx nextarg) { /* See arc_setup_incoming_varargs for reasons for this oddity. */ if (crtl->args.info < 8 && (crtl->args.info & 1)) nextarg = plus_constant (nextarg, UNITS_PER_WORD); std_expand_builtin_va_start (valist, nextarg); } /* This is how to output a definition of an internal numbered label where PREFIX is the class of label and NUM is the number within the class. */ static void arc_internal_label (FILE *stream, const char *prefix, unsigned long labelno) { arc_ccfsm_at_label (prefix, labelno); default_internal_label (stream, prefix, labelno); } /* Worker function for TARGET_ASM_EXTERNAL_LIBCALL. */ static void arc_external_libcall (rtx fun ATTRIBUTE_UNUSED) { #if 0 /* On the ARC we want to have libgcc's for multiple cpus in one binary. We can't use `assemble_name' here as that will call ASM_OUTPUT_LABELREF and we'll get another suffix added on if -mmangle-cpu. */ if (TARGET_MANGLE_CPU_LIBGCC) { fprintf (FILE, "\t.rename\t_%s, _%s%s\n", XSTR (SYMREF, 0), XSTR (SYMREF, 0), arc_mangle_suffix); } #endif } /* Worker function for TARGET_RETURN_IN_MEMORY. */ static bool arc_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED) { if (AGGREGATE_TYPE_P (type)) return true; else { HOST_WIDE_INT size = int_size_in_bytes (type); return (size == -1 || size > 8); } } /* For ARC, All aggregates and arguments greater than 8 bytes are passed by reference. */ static bool arc_pass_by_reference (CUMULATIVE_ARGS *ca ATTRIBUTE_UNUSED, enum machine_mode mode, const_tree type, bool named ATTRIBUTE_UNUSED) { unsigned HOST_WIDE_INT size; if (type) { if (AGGREGATE_TYPE_P (type)) return true; size = int_size_in_bytes (type); } else size = GET_MODE_SIZE (mode); return size > 8; } /* Trampolines. */ /* ??? This doesn't work yet because GCC will use as the address of a nested function the address of the trampoline. We need to use that address right shifted by 2. It looks like we'll need PSImode after all. :-( ??? The above comment sounds like it's doable via TARGET_TRAMPOLINE_ADJUST_ADDRESS; no PSImode needed. On the ARC, the trampoline is quite simple as we have 32-bit immediate constants. mov r24,STATIC j.nd FUNCTION */ static void arc_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) { rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); rtx mem; mem = adjust_address (m_tramp, SImode, 0); emit_move_insn (mem, GEN_INT (0x631f7c00)); mem = adjust_address (m_tramp, SImode, 4); emit_move_insn (mem, chain_value); mem = adjust_address (m_tramp, SImode, 8); emit_move_insn (mem, GEN_INT (0x381f0000)); mem = adjust_address (m_tramp, SImode, 12); emit_move_insn (mem, fnaddr); emit_insn (gen_flush_icache (m_tramp)); }
Go to most recent revision | Compare with Previous | Blame | View Log