| /* GCC backend functions for C-SKY targets. |
| Copyright (C) 2018-2021 Free Software Foundation, Inc. |
| Contributed by C-SKY Microsystems and Mentor Graphics. |
| |
| 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 "memmodel.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "cfghooks.h" |
| #include "df.h" |
| #include "tm_p.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "optabs.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "cgraph.h" |
| #include "c-family/c-common.h" |
| #include "cpplib.h" |
| #include "diagnostic-core.h" |
| #include "alias.h" |
| #include "fold-const.h" |
| #include "stor-layout.h" |
| #include "calls.h" |
| #include "varasm.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| #include "flags.h" |
| #include "reload.h" |
| #include "explow.h" |
| #include "expr.h" |
| #include "cfgrtl.h" |
| #include "sched-int.h" |
| #include "common/common-target.h" |
| #include "langhooks.h" |
| #include "intl.h" |
| #include "libfuncs.h" |
| #include "opts.h" |
| #include "dumpfile.h" |
| #include "target-globals.h" |
| #include "builtins.h" |
| #include "tm-constrs.h" |
| #include "rtl-iter.h" |
| #include "pass_manager.h" |
| #include "tree-pass.h" |
| #include "context.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| /* Stack and register size macros. */ |
| |
| #define CSKY_NUM_WORDS(SIZE) \ |
| (((SIZE) + UNITS_PER_WORD - 1) / UNITS_PER_WORD) |
| #define CSKY_NUM_REGS(MODE) \ |
| CSKY_NUM_WORDS (GET_MODE_SIZE (MODE)) |
| #define CSKY_STACK_ALIGN(SIZE) \ |
| (CSKY_NUM_WORDS (SIZE) * UNITS_PER_WORD) |
| |
| /* Offsets and range macros. */ |
| |
| #define CSKY_LD16_MAX_OFFSET(MODE) \ |
| (31 * GET_MODE_SIZE (MODE)) |
| #define CSKY_LD32_MAX_OFFSET(MODE) \ |
| (4095 * GET_MODE_SIZE (MODE)) |
| #define CSKY_LD16_OFFSET_MASK(MODE) \ |
| (CSKY_LD16_MAX_OFFSET (MODE) + GET_MODE_SIZE (MODE) - 1) |
| |
| #define CSKY_ADDI16_MAX_IMM 256 |
| #define CSKY_SUBI16_MAX_IMM 256 |
| |
| #define CSKY_CONSTPOOL_LABEL_PREFIX "LCP" |
| |
| /* Array of the smallest class containing reg number REGNO, indexed by |
| REGNO. Used by REGNO_REG_CLASS. */ |
| enum reg_class regno_reg_class[FIRST_PSEUDO_REGISTER] = |
| { |
| /* Registers r0-r7. */ |
| MINI_REGS, MINI_REGS, MINI_REGS, MINI_REGS, |
| MINI_REGS, MINI_REGS, MINI_REGS, MINI_REGS, |
| /* Registers r8-r15. */ |
| LOW_REGS, LOW_REGS, LOW_REGS, LOW_REGS, |
| LOW_REGS, LOW_REGS, SP_REGS, LOW_REGS, |
| /* Registers r16-r31. */ |
| GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, |
| GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, |
| GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, |
| GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, |
| /* Reserved. */ |
| RESERVE_REGS, |
| /* CC,HI,LO registers. */ |
| C_REGS, HILO_REGS, HILO_REGS, |
| /* Reserved. */ |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| /* Vec registers. */ |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| /* Reserved. */ |
| RESERVE_REGS, RESERVE_REGS, |
| /* Register epc. */ |
| OTHER_REGS, |
| /* Vec registers. */ |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| V_REGS, V_REGS, V_REGS, V_REGS, |
| /* Reserved. */ |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| /* Reserved. */ |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, RESERVE_REGS, |
| |
| RESERVE_REGS, RESERVE_REGS, RESERVE_REGS |
| }; |
| |
| /* Arrays that map GCC register numbers to debugger register numbers, |
| '-1' means that is INVALID_REGNUM. |
| TODO: which rules according to here ? */ |
| const int csky_dbx_regno[FIRST_PSEUDO_REGISTER] = |
| { |
| 0, 1, 2, 3, 4, 5, 6, 7, |
| 8, 9, 10, 11, 12, 13, 14, 15, |
| 16, 17, 18, 19, 20, 21, 22, 23, |
| 24, 25, 26, 27, 28, 29, 30, 31, |
| -1, -1, 36, 37, |
| 75, 79, 83, 87, 91, 95, 99, 103, |
| 107, 111, 115, 119, 123, 127, 131, 135, |
| 74, 78, 82, 86, 90, 94, 98, 102, |
| 106, 110, 114, 118, 122, 126, 130, 134, |
| -1, -1, 72, |
| /* vr: 71 - 86 */ |
| 139, 143, 147, 151, 155, 159, 163, 167, |
| 171, 175, 179, 183, 187, 191, 195, 199, |
| 138, 142, 146, 150, 154, 158, 162, 166, |
| 170, 174, 178, 182, 186, 190, 194, 198, |
| /* resereved */ |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| |
| -1, -1, -1 |
| }; |
| |
| /* Table of machine attributes. */ |
| static tree csky_handle_fndecl_attribute (tree *, tree, tree, int, bool *); |
| static tree csky_handle_isr_attribute (tree *, tree, tree, int, bool *); |
| static const struct attribute_spec csky_attribute_table[] = |
| { |
| /* { name, min_len, max_len, decl_req, type_req, fn_type_req, |
| affects_type_identity, handler, exclude } */ |
| { "naked", 0, 0, true, false, false, false, csky_handle_fndecl_attribute, NULL }, |
| /* Interrupt Service Routines have special prologue and epilogue requirements. */ |
| { "interrupt", 0, 1, false, false, false, false, csky_handle_isr_attribute, NULL }, |
| { "isr", 0, 1, false, false, false, false, csky_handle_isr_attribute, NULL }, |
| { NULL, 0, 0, false, false, false, false, NULL, NULL } |
| }; |
| |
| /* A C structure for machine-specific, per-function data. |
| This is added to the cfun structure. */ |
| typedef struct GTY(()) machine_function |
| { |
| /* Records if LR has to be saved for far jumps. */ |
| int far_jump_used; |
| /* Records the type of the current function. */ |
| unsigned long func_type; |
| /* Record if the function has a variable argument list. */ |
| int uses_anonymous_args; |
| |
| /* Stack frame layout information. If frame_init_p is true, |
| these fields have been initialized and don't need to be |
| recomputed. */ |
| unsigned int reg_mask; /* non-volatile reg saves */ |
| int arg_size; /* stdarg spills (bytes) */ |
| int reg_size; /* non-volatile reg saves (bytes) */ |
| int local_size; /* locals */ |
| int outbound_size; /* arg overflow on calls out */ |
| int frame_size; /* total static size of stack frame */ |
| int local_offset; |
| int reg_offset; |
| int arg_offset; |
| int frame_init_p; |
| |
| } machine_function; |
| |
| /* These macros are for the func_type values above. */ |
| #define CSKY_FT_TYPE_MASK ((1 << 3) - 1) |
| #define CSKY_FT_UNKNOWN 0 /* Type not been determined */ |
| #define CSKY_FT_NORMAL 1 /* Normal function */ |
| #define CSKY_FT_ISR 4 /* Interrupt service routine */ |
| #define CSKY_FT_FIQ 5 /* Fast interrupt service routine */ |
| #define CSKY_FT_EXCEPTION 6 /* Exception handler */ |
| #define CSKY_FT_INTERRUPT (1 << 2) /* overlap CSKY_FT_ISR */ |
| #define CSKY_FT_NAKED (1 << 3) /* No prologue and epilogue */ |
| #define CSKY_FUNCTION_TYPE(t) ((t) & CSKY_FT_TYPE_MASK) |
| #define CSKY_FUNCTION_IS_INTERRUPT(t) ((t) & CSKY_FT_INTERRUPT) |
| #define CSKY_FUNCTION_IS_NAKED(t) ((t) & CSKY_FT_NAKED) |
| |
| struct csky_processors |
| { |
| const char *const name; |
| enum csky_processor_type core; |
| const char *arch; |
| enum csky_base_architecture base_arch; |
| enum csky_isa_feature isa_bits[CSKY_ISA_FEATURE_GET (max)]; |
| }; |
| |
| static struct csky_processors all_cores[] = |
| { |
| #undef CSKY_CORE |
| #define CSKY_CORE(NAME, CORE, X, ARCH, ISA) \ |
| {NAME, TARGET_CPU_##CORE, #ARCH, CSKY_BASE_ARCH_##ARCH, \ |
| {ISA CSKY_ISA_FEATURE_GET (none)}}, |
| #include "csky_cores.def" |
| #undef CSKY_CORE |
| {NULL, TARGET_CPU_csky_none, NULL, CSKY_BASE_ARCH_NONE, \ |
| {CSKY_ISA_FEATURE_GET (none)}} |
| }; |
| |
| static struct csky_processors all_architectures[] = |
| { |
| #undef CSKY_ARCH |
| #define CSKY_ARCH(NAME, CORE, ARCH, ISA) \ |
| {NAME, TARGET_CPU_##CORE, #ARCH, CSKY_BASE_ARCH_##ARCH, \ |
| {ISA CSKY_ISA_FEATURE_GET (none)}}, |
| #include "csky_cores.def" |
| #undef CSKY_ARCH |
| {NULL, TARGET_CPU_csky_none, NULL, CSKY_BASE_ARCH_NONE, \ |
| {CSKY_ISA_FEATURE_GET (none)}} |
| }; |
| |
| struct csky_fpu_desc |
| { |
| const char *name; |
| enum csky_isa_feature isa_bits[CSKY_ISA_FEATURE_GET (max)]; |
| }; |
| |
| static const struct csky_fpu_desc all_fpus[] = |
| { |
| #undef CSKY_FPU |
| #define CSKY_FPU(NAME, CNAME, ISA) \ |
| {NAME, {ISA CSKY_ISA_FEATURE_GET (none)}}, |
| #include "csky_cores.def" |
| #undef CSKY_FPU |
| }; |
| |
| /* Active target architecture. */ |
| struct csky_build_target |
| { |
| /* Name of the target CPU, if known, or NULL if the target CPU was not |
| specified by the user (and inferred from the -march option). */ |
| const char *core_name; |
| /* Name of the target ARCH. NULL if there is a selected CPU. */ |
| const char *arch_name; |
| /* Preprocessor substring (never NULL). */ |
| const char *arch_pp_name; |
| /* CPU identifier for the core we're compiling for (architecturally). */ |
| enum csky_processor_type arch_core; |
| /* The base architecture value. */ |
| enum csky_base_architecture base_arch; |
| /* Bitmap encapsulating the isa_bits for the target environment. */ |
| sbitmap isa; |
| }; |
| |
| struct csky_build_target csky_active_target; |
| |
| /* The following are used in the .md file as equivalents to bits. */ |
| int csky_arch_isa_features[CSKY_ISA_FEATURE_GET (max)] = {0}; |
| |
| /* The highest CSKY architecture version supported by the target. */ |
| enum csky_base_architecture csky_base_arch = CSKY_TARGET_ARCH_GET (NONE); |
| |
| /* Forward definitions of types. */ |
| typedef struct minipool_node Mnode; |
| typedef struct minipool_fixup Mfix; |
| |
| static GTY(()) int tls_labelno; |
| |
| |
| /* Maximum constant offset that can be added/subtracted from SP in a |
| single instruction. For ck801, this is for addsp/subsp, otherwise |
| it is the range of addi/subi. */ |
| #define CSKY_MAX_SP_ADJUST \ |
| (CSKY_TARGET_ARCH (CK801) ? 508 : 4096) |
| |
| |
| /* Implement TARGET_CPU_CPP_BUILTINS. */ |
| |
| #define builtin_define(MACRO) cpp_define (pfile, MACRO) |
| |
| void |
| csky_cpu_cpp_builtins (cpp_reader *pfile) |
| { |
| const char *arch_name = csky_active_target.arch_pp_name; |
| char *pp_name = (char *) alloca (1 + strlen (arch_name) + 4); |
| sprintf (pp_name, "__%s__", arch_name); |
| builtin_define (pp_name); |
| |
| builtin_define ("__csky__=2"); |
| builtin_define ("__CSKY__=2"); |
| builtin_define ("__ckcore__=2"); |
| builtin_define ("__CKCORE__=2"); |
| |
| builtin_define ("__CSKYABIV2__"); |
| builtin_define ("__cskyabiv2__"); |
| builtin_define ("__CSKYABI__=2"); |
| builtin_define ("__cskyabi__=2"); |
| |
| if (TARGET_BIG_ENDIAN) |
| { |
| builtin_define ("__ckcoreBE__"); |
| builtin_define ("__cskyBE__"); |
| builtin_define ("__cskybe__"); |
| builtin_define ("__CSKYBE__"); |
| } |
| else |
| { |
| builtin_define ("__ckcoreLE__"); |
| builtin_define ("__cskyLE__"); |
| builtin_define ("__cskyle__"); |
| builtin_define ("__CSKYLE__"); |
| } |
| |
| if (TARGET_HARD_FLOAT) |
| { |
| builtin_define ("__csky_hard_float__"); |
| builtin_define ("__CSKY_HARD_FLOAT__"); |
| if (TARGET_HARD_FLOAT_ABI) |
| { |
| builtin_define ("__csky_hard_float_abi__"); |
| builtin_define ("__CSKY_HARD_FLOAT_ABI__"); |
| } |
| if (TARGET_SINGLE_FPU) |
| { |
| builtin_define ("__csky_hard_float_fpu_sf__"); |
| builtin_define ("__CSKY_HARD_FLOAT_FPU_SF__"); |
| } |
| } |
| else |
| { |
| builtin_define ("__csky_soft_float__"); |
| builtin_define ("__CSKY_SOFT_FLOAT__"); |
| } |
| |
| if (CSKY_ISA_FEATURE (fpv2_sf)) |
| { |
| builtin_define ("__csky_fpuv2__"); |
| builtin_define ("__CSKY_FPUV2__"); |
| } |
| |
| if (TARGET_SUPPORT_FPV3) |
| { |
| builtin_define ("__csky_fpuv3__"); |
| builtin_define ("__CSKY_FPUV3__"); |
| } |
| |
| if (TARGET_ELRW) |
| { |
| builtin_define ("__csky_elrw__"); |
| builtin_define ("__CSKY_ELRW__"); |
| } |
| if (TARGET_ISTACK) |
| { |
| builtin_define ("__csky_istack__"); |
| builtin_define ("__CSKY_ISTACK__"); |
| } |
| if (TARGET_MP) |
| { |
| builtin_define ("__csky_mp__"); |
| builtin_define ("__CSKY_MP__"); |
| } |
| if (TARGET_CP) |
| { |
| builtin_define ("__csky_cp__"); |
| builtin_define ("__CSKY_CP__"); |
| } |
| if (TARGET_CACHE) |
| { |
| builtin_define ("__csky_cache__"); |
| builtin_define ("__CSKY_CACHE__"); |
| } |
| if (TARGET_SECURITY) |
| { |
| builtin_define ("__csky_security__"); |
| builtin_define ("__CSKY_SECURITY__"); |
| } |
| if (TARGET_TRUST) |
| { |
| builtin_define ("__csky_trust__"); |
| builtin_define ("__CSKY_TRUST__"); |
| } |
| if (TARGET_DSP) |
| { |
| builtin_define ("__csky_dsp__"); |
| builtin_define ("__CSKY_DSP__"); |
| } |
| if (TARGET_EDSP) |
| { |
| builtin_define ("__csky_edsp__"); |
| builtin_define ("__CSKY_EDSP__"); |
| } |
| if (TARGET_VDSP) |
| { |
| builtin_define ("__csky_vdsp__"); |
| builtin_define ("__CSKY_VDSP__"); |
| } |
| } |
| |
| |
| /****************************************************************** |
| * Storage Layout * |
| ******************************************************************/ |
| |
| #undef TARGET_PROMOTE_FUNCTION_MODE |
| #define TARGET_PROMOTE_FUNCTION_MODE \ |
| default_promote_function_mode_always_promote |
| |
| #undef TARGET_CONSTANT_ALIGNMENT |
| #define TARGET_CONSTANT_ALIGNMENT csky_constant_alignment |
| |
| #undef TARGET_MANGLE_TYPE |
| #define TARGET_MANGLE_TYPE csky_mangle_type |
| |
| |
| /****************************************************************** |
| * Stack Layout and Calling Conventions * |
| ******************************************************************/ |
| |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE csky_can_eliminate |
| |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG csky_function_arg |
| |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE csky_function_arg_advance |
| |
| #undef TARGET_FUNCTION_VALUE |
| #define TARGET_FUNCTION_VALUE csky_function_value |
| |
| #undef TARGET_LIBCALL_VALUE |
| #define TARGET_LIBCALL_VALUE csky_libcall_value |
| |
| #undef TARGET_FUNCTION_VALUE_REGNO_P |
| #define TARGET_FUNCTION_VALUE_REGNO_P csky_function_value_regno_p |
| |
| #undef TARGET_SPLIT_COMPLEX_ARG |
| #define TARGET_SPLIT_COMPLEX_ARG hook_bool_const_tree_true |
| |
| #undef TARGET_MUST_PASS_IN_STACK |
| #define TARGET_MUST_PASS_IN_STACK must_pass_in_stack_var_size |
| |
| #undef TARGET_ARG_PARTIAL_BYTES |
| #define TARGET_ARG_PARTIAL_BYTES csky_arg_partial_bytes |
| |
| #undef TARGET_PASS_BY_REFERENCE |
| #define TARGET_PASS_BY_REFERENCE hook_pass_by_reference_must_pass_in_stack |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK csky_output_mi_thunk |
| |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK \ |
| hook_bool_const_tree_hwi_hwi_const_tree_true |
| |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE csky_output_function_prologue |
| |
| #undef TARGET_ASM_FUNCTION_EPILOGUE |
| #define TARGET_ASM_FUNCTION_EPILOGUE csky_output_function_epilogue |
| |
| #undef TARGET_WARN_FUNC_RETURN |
| #define TARGET_WARN_FUNC_RETURN csky_warn_func_return |
| |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY csky_return_in_memory |
| |
| |
| /****************************************************************** |
| * Implementing the Varargs Macros * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_SETUP_INCOMING_VARARGS |
| #define TARGET_SETUP_INCOMING_VARARGS csky_setup_incoming_varargs |
| |
| |
| /****************************************************************** |
| * Implicit Calls to Library Routines * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_INIT_LIBFUNCS |
| #define TARGET_INIT_LIBFUNCS csky_init_libfuncs |
| |
| |
| /****************************************************************** |
| * Dividing the Output into Sections (Texts, Data, . . . ) * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_HAVE_TLS |
| #define TARGET_HAVE_TLS TARGET_CSKY_LINUX |
| |
| |
| /****************************************************************** |
| * Defining target-specific uses of __attribute__ * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE csky_attribute_table |
| |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE csky_option_override |
| |
| |
| /* Implement the BRANCH_COST target macro. */ |
| |
| int |
| csky_default_branch_cost (bool speed_p ATTRIBUTE_UNUSED, |
| bool predictable_p ATTRIBUTE_UNUSED) |
| { |
| return csky_branch_cost; |
| } |
| |
| bool |
| csky_default_logical_op_non_short_circuit (void) |
| { |
| return BRANCH_COST (optimize_function_for_speed_p (cfun), false) >= 2; |
| } |
| |
| /****************************************************************** |
| * Register Usage * |
| ******************************************************************/ |
| |
| #undef TARGET_HARD_REGNO_NREGS |
| #define TARGET_HARD_REGNO_NREGS csky_hard_regno_nregs |
| |
| #undef TARGET_HARD_REGNO_MODE_OK |
| #define TARGET_HARD_REGNO_MODE_OK csky_hard_regno_mode_ok |
| |
| #undef TARGET_MODES_TIEABLE_P |
| #define TARGET_MODES_TIEABLE_P csky_modes_tieable_p |
| |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE csky_conditional_register_usage |
| |
| #undef TARGET_CLASS_LIKELY_SPILLED_P |
| #define TARGET_CLASS_LIKELY_SPILLED_P csky_class_likely_spilled_p |
| |
| #undef TARGET_PREFERRED_RELOAD_CLASS |
| #define TARGET_PREFERRED_RELOAD_CLASS csky_preferred_reload_class |
| |
| #undef TARGET_CLASS_MAX_NREGS |
| #define TARGET_CLASS_MAX_NREGS csky_class_max_nregs |
| |
| #undef TARGET_SECONDARY_RELOAD |
| #define TARGET_SECONDARY_RELOAD csky_secondary_reload |
| |
| #undef TARGET_SPILL_CLASS |
| #define TARGET_SPILL_CLASS csky_spill_class |
| |
| |
| /****************************************************************** |
| * Addressing Modes * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_CANNOT_FORCE_CONST_MEM |
| #define TARGET_CANNOT_FORCE_CONST_MEM csky_cannot_force_const_mem |
| |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P csky_legitimate_constant_p |
| |
| #undef TARGET_LEGITIMIZE_ADDRESS |
| #define TARGET_LEGITIMIZE_ADDRESS csky_legitimize_address |
| |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P csky_legitimate_address_p |
| |
| |
| /****************************************************************** |
| * Others * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_CANNOT_COPY_INSN_P |
| #define TARGET_CANNOT_COPY_INSN_P csky_cannot_copy_insn_p |
| |
| |
| /****************************************************************** |
| * Assembler Format * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_PRINT_OPERAND |
| #define TARGET_PRINT_OPERAND csky_print_operand |
| |
| #undef TARGET_PRINT_OPERAND_ADDRESS |
| #define TARGET_PRINT_OPERAND_ADDRESS csky_print_operand_address |
| |
| #undef TARGET_ASM_UNALIGNED_HI_OP |
| #define TARGET_ASM_UNALIGNED_HI_OP "\t.short\t" |
| |
| #undef TARGET_ASM_UNALIGNED_SI_OP |
| #define TARGET_ASM_UNALIGNED_SI_OP "\t.long\t" |
| |
| #undef TARGET_DWARF_REGISTER_SPAN |
| #define TARGET_DWARF_REGISTER_SPAN csky_dwarf_register_span |
| |
| |
| /****************************************************************** |
| * Miscellaneous Parameters * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG csky_reorg |
| |
| #undef TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS |
| #define TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS csky_allocate_stack_slots_for_args |
| |
| #undef TARGET_HAVE_SPECULATION_SAFE_VALUE |
| #define TARGET_HAVE_SPECULATION_SAFE_VALUE speculation_safe_value_not_needed |
| |
| |
| /****************************************************************** |
| * Trampolines for Nested Functions * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_ASM_TRAMPOLINE_TEMPLATE |
| #define TARGET_ASM_TRAMPOLINE_TEMPLATE csky_asm_trampoline_template |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT csky_trampoline_init |
| |
| /* The low bit is ignored by jsr and jmp instructions so is safe to use. */ |
| #undef TARGET_CUSTOM_FUNCTION_DESCRIPTORS |
| #define TARGET_CUSTOM_FUNCTION_DESCRIPTORS 1 |
| |
| /****************************************************************** |
| * Describing Relative Costs of Operations * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_REGISTER_MOVE_COST |
| #define TARGET_REGISTER_MOVE_COST csky_register_move_cost |
| |
| #undef TARGET_MEMORY_MOVE_COST |
| #define TARGET_MEMORY_MOVE_COST csky_memory_move_cost |
| |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS csky_rtx_costs |
| |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST csky_address_cost |
| |
| |
| /****************************************************************** |
| * Anchor address * |
| ******************************************************************/ |
| |
| |
| /* FIXME: the max offset is related to mode size, the following is |
| defined according to SImode. How to deal with HImode and |
| QImode, and should the min offset be defined? */ |
| #undef TARGET_MAX_ANCHOR_OFFSET |
| #define TARGET_MAX_ANCHOR_OFFSET \ |
| ((TARGET_MINI_REGISTERS && optimize_size) ? 127 : 4095) |
| |
| |
| /****************************************************************** |
| * Condition Code Status * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_FIXED_CONDITION_CODE_REGS |
| #define TARGET_FIXED_CONDITION_CODE_REGS csky_fixed_condition_code_regs |
| |
| |
| /****************************************************************** |
| * Adjusting the Instruction Scheduler * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE csky_sched_issue_rate |
| |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST csky_sched_adjust_cost |
| |
| |
| /****************************************************************** |
| * Builtin * |
| ******************************************************************/ |
| |
| |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS csky_init_builtins |
| |
| |
| /* The declaration of functions. */ |
| static void push_csky_minipool_fix (rtx_insn *, HOST_WIDE_INT, rtx *, |
| machine_mode, rtx); |
| static void csky_print_operand (FILE *stream, rtx x, int code); |
| |
| |
| /* Define a table to map ISR attribute arguments onto function type |
| modifiers. */ |
| |
| typedef struct |
| { |
| const char *const arg; |
| const unsigned long return_value; |
| } isr_attribute_entry; |
| |
| static const isr_attribute_entry isr_attribute_map[] = |
| { |
| {"irq", CSKY_FT_ISR }, |
| {"IRQ", CSKY_FT_ISR }, |
| {"fiq", CSKY_FT_FIQ }, |
| {"FIQ", CSKY_FT_FIQ }, |
| {NULL, CSKY_FT_NORMAL } |
| }; |
| |
| |
| /* Return the function type of the current function, if it has not been |
| determined, return CSKY_FT_UNKNOWN. */ |
| |
| static unsigned long |
| get_csky_isr_type (tree argument) |
| { |
| const isr_attribute_entry *ptr; |
| const char *arg; |
| |
| /* if argument is NULL, set default value ISR. */ |
| if (argument == NULL_TREE) |
| return CSKY_FT_ISR; |
| |
| if (TREE_VALUE (argument) == NULL_TREE |
| || TREE_CODE (TREE_VALUE (argument)) != STRING_CST) |
| return CSKY_FT_UNKNOWN; |
| |
| arg = TREE_STRING_POINTER (TREE_VALUE (argument)); |
| |
| for (ptr = isr_attribute_map; ptr->arg != NULL; ptr++) |
| if (strcmp (arg, ptr->arg) == 0) |
| return ptr->return_value; |
| |
| return CSKY_FT_UNKNOWN; |
| } |
| |
| /* Classify cfun as a normal function or some sort of interrupt |
| handler, and set the corresponding bits in cfun->machine->func_type. */ |
| |
| static unsigned long |
| get_csky_current_func_type (void) |
| { |
| if (CSKY_FUNCTION_TYPE (cfun->machine->func_type) == CSKY_FT_UNKNOWN) |
| { |
| unsigned long type = CSKY_FT_UNKNOWN; |
| tree a; |
| tree attr; |
| |
| gcc_assert (TREE_CODE (current_function_decl) == FUNCTION_DECL); |
| |
| attr = DECL_ATTRIBUTES (current_function_decl); |
| a = lookup_attribute ("naked", attr); |
| if (a != NULL_TREE) |
| type |= CSKY_FT_NAKED; |
| a = lookup_attribute ("isr", attr); |
| if (a == NULL_TREE) |
| a = lookup_attribute ("interrupt", attr); |
| if (a == NULL_TREE) |
| type |= CSKY_FT_NORMAL; |
| else |
| type |= get_csky_isr_type (TREE_VALUE (a)); |
| |
| cfun->machine->func_type = type; |
| } |
| |
| return cfun->machine->func_type; |
| } |
| |
| /* These typedefs are located at the start of this file, so that |
| they can be used in the prototypes there. This comment is to |
| remind readers of that fact so that the following structures |
| can be understood more easily. |
| |
| typedef struct minipool_node Mnode; |
| typedef struct minipool_fixup Mfix; */ |
| |
| struct minipool_node |
| { |
| /* Doubly linked chain of entries. */ |
| Mnode *next; |
| Mnode *prev; |
| /* The maximum offset into the code that this entry can be placed. While |
| pushing fixes for forward references, all entries are sorted in order |
| of increasing max_address. */ |
| HOST_WIDE_INT max_address; |
| /* Similarly for an entry inserted for a backwards ref. */ |
| HOST_WIDE_INT min_address; |
| /* The number of fixes referencing this entry. This can become zero |
| if we "unpush" an entry. In this case we ignore the entry when we |
| come to emit the code. */ |
| int refcount; |
| /* The offset from the start of the minipool. */ |
| HOST_WIDE_INT offset; |
| /* The value in table. */ |
| rtx value; |
| /* The mode of value. */ |
| machine_mode mode; |
| /* The size of the value. */ |
| int fix_size; |
| }; |
| |
| struct minipool_fixup |
| { |
| Mfix *next; |
| rtx_insn *insn; |
| HOST_WIDE_INT address; |
| rtx *loc; |
| machine_mode mode; |
| int fix_size; |
| rtx value; |
| Mnode *minipool; |
| HOST_WIDE_INT forwards; |
| HOST_WIDE_INT backwards; |
| }; |
| |
| static Mnode *minipool_vector_head; |
| static Mnode *minipool_vector_tail; |
| static rtx minipool_vector_label; |
| static HOST_WIDE_INT constpool_label_no = 0; |
| |
| /* Obstack for minipool constant handling. */ |
| static struct obstack minipool_obstack; |
| static char *minipool_startobj; |
| /* The linked list of all minipool fixes required for this function. */ |
| Mfix *minipool_fix_head; |
| Mfix *minipool_fix_tail; |
| /* The fix entry for the current minipool, once it has been placed. */ |
| Mfix *minipool_barrier; |
| |
| /* Allow GC scanning of the minipool obstack. */ |
| |
| static void |
| csky_add_gc_roots (void) |
| { |
| gcc_obstack_init (&minipool_obstack); |
| minipool_startobj = (char *) obstack_alloc (&minipool_obstack, 0); |
| } |
| |
| /* Implement TARGET_CONSTANT_ALIGNMENT. |
| Make strings word-aligned so strcpy from constants will be faster. */ |
| |
| static HOST_WIDE_INT |
| csky_constant_alignment (const_tree exp, HOST_WIDE_INT align) |
| { |
| if (TREE_CODE (exp) == STRING_CST |
| && !optimize_size |
| && align < BITS_PER_WORD) |
| return BITS_PER_WORD; |
| return align; |
| } |
| |
| /* Record that there is a natural barrier in the insn stream at |
| ADDRESS. */ |
| |
| static void |
| push_csky_minipool_barrier (rtx_insn *insn, HOST_WIDE_INT address) |
| { |
| Mfix *fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (*fix)); |
| |
| fix->insn = insn; |
| fix->address = address; |
| |
| fix->next = NULL; |
| if (minipool_fix_head != NULL) |
| minipool_fix_tail->next = fix; |
| else |
| minipool_fix_head = fix; |
| |
| minipool_fix_tail = fix; |
| } |
| |
| /* Compute the size of a vector jump table. */ |
| |
| static HOST_WIDE_INT |
| get_csky_jump_table_size (rtx insn) |
| { |
| /* ADDR_VECs only take room if read-only data does into the text |
| section. */ |
| if (JUMP_TABLES_IN_TEXT_SECTION || readonly_data_section == text_section) |
| { |
| rtx body = PATTERN (insn); |
| int elt = GET_CODE (body) == ADDR_DIFF_VEC ? 1 : 0; |
| HOST_WIDE_INT size; |
| HOST_WIDE_INT modesize; |
| |
| modesize = GET_MODE_SIZE (GET_MODE (body)); |
| size = modesize * XVECLEN (body, elt); |
| switch (modesize) |
| { |
| case 1: |
| /* Round up size of TBB table to a halfword boundary. */ |
| size = (size + 1) & ~(HOST_WIDE_INT)1; |
| break; |
| case 2: |
| /* No padding necessary for TBH. */ |
| break; |
| case 4: |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| return size; |
| } |
| |
| return 0; |
| } |
| |
| |
| /* Scan INSN and note any of its operands that need fixing. |
| If DO_PUSHES is false we do not actually push any of the fixups |
| needed. The function returns TRUE if any fixups were needed/pushed. */ |
| |
| static bool |
| note_csky_invalid_constants (rtx_insn *insn, HOST_WIDE_INT address, |
| int do_pushes) |
| { |
| bool result = false; |
| int opno; |
| |
| extract_constrain_insn (insn); |
| |
| if (recog_data.n_alternatives == 0) |
| return false; |
| |
| /* Fill in recog_op_alt with information about the constraints of |
| this insn. */ |
| preprocess_constraints (insn); |
| |
| const operand_alternative *op_alt = which_op_alt (); |
| for (opno = 0; opno < recog_data.n_operands; opno++) |
| { |
| /* Things we need to fix can only occur in inputs. */ |
| if (recog_data.operand_type[opno] != OP_IN) |
| continue; |
| |
| /* If this alternative is a memory reference, then any mention |
| of constants in this alternative is really to fool reload |
| into allowing us to accept one there. We need to fix them up |
| now so that we output the right code. */ |
| if (op_alt[opno].memory_ok) |
| { |
| rtx op = recog_data.operand[opno]; |
| |
| if (CONSTANT_P (op)) |
| { |
| if (do_pushes) |
| push_csky_minipool_fix (insn, address, |
| recog_data.operand_loc[opno], |
| recog_data.operand_mode[opno], op); |
| result = true; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| /* Add a constant to the minipool for a forward reference. Returns the |
| node added or NULL if the constant will not fit in this pool. */ |
| |
| static Mnode * |
| add_csky_minipool_forward_ref (Mfix *fix) |
| { |
| /* If set, max_mp is the first pool_entry that has a lower |
| constraint than the one we are trying to add. */ |
| Mnode *max_mp = NULL; |
| HOST_WIDE_INT max_address = fix->address + fix->forwards; |
| Mnode *mp; |
| |
| /* If the minipool starts before the end of FIX->INSN then this FIX |
| cannot be placed into the current pool. Furthermore, adding the |
| new constant pool entry may cause the pool to start FIX_SIZE bytes |
| earlier. */ |
| if (minipool_vector_head |
| && (fix->address + get_attr_length (fix->insn) |
| >= minipool_vector_head->max_address - fix->fix_size)) |
| return NULL; |
| |
| /* Scan the pool to see if a constant with the same value has |
| already been added. While we are doing this, also note the |
| location where we must insert the constant if it doesn't already |
| exist. */ |
| for (mp = minipool_vector_head; mp != NULL; mp = mp->next) |
| { |
| if (GET_CODE (fix->value) == GET_CODE (mp->value) |
| && fix->mode == mp->mode |
| && (GET_CODE (fix->value) != CODE_LABEL |
| || (CODE_LABEL_NUMBER (fix->value) |
| == CODE_LABEL_NUMBER (mp->value))) |
| && rtx_equal_p (fix->value, mp->value)) |
| { |
| /* More than one fix references this entry. */ |
| mp->refcount++; |
| return mp; |
| } |
| |
| /* Note the insertion point if necessary. */ |
| if (max_mp == NULL && mp->max_address > max_address) |
| max_mp = mp; |
| } |
| |
| /* The value is not currently in the minipool, so we need to create |
| a new entry for it. If MAX_MP is NULL, the entry will be put on |
| the end of the list since the placement is less constrained than |
| any existing entry. Otherwise, we insert the new fix before |
| MAX_MP and, if necessary, adjust the constraints on the other |
| entries. */ |
| mp = XNEW (Mnode); |
| mp->fix_size = fix->fix_size; |
| mp->mode = fix->mode; |
| mp->value = fix->value; |
| mp->refcount = 1; |
| /* Not yet required for a backwards ref. */ |
| mp->min_address = -65536; |
| |
| if (max_mp == NULL) |
| { |
| mp->max_address = max_address; |
| mp->next = NULL; |
| mp->prev = minipool_vector_tail; |
| |
| if (mp->prev == NULL) |
| { |
| minipool_vector_head = mp; |
| minipool_vector_label |
| = gen_csky_constpool_label (gen_rtx_CONST_INT (VOIDmode, |
| constpool_label_no++)); |
| } |
| else |
| mp->prev->next = mp; |
| |
| minipool_vector_tail = mp; |
| } |
| else |
| { |
| if (max_address > max_mp->max_address - mp->fix_size) |
| mp->max_address = max_mp->max_address - mp->fix_size; |
| else |
| mp->max_address = max_address; |
| |
| mp->next = max_mp; |
| mp->prev = max_mp->prev; |
| max_mp->prev = mp; |
| if (mp->prev != NULL) |
| mp->prev->next = mp; |
| else |
| minipool_vector_head = mp; |
| } |
| |
| /* Save the new entry. */ |
| max_mp = mp; |
| |
| /* Scan over the preceding entries and adjust their addresses as |
| required. */ |
| while (mp->prev != NULL |
| && mp->prev->max_address > mp->max_address - mp->prev->fix_size) |
| { |
| mp->prev->max_address = mp->max_address - mp->prev->fix_size; |
| mp = mp->prev; |
| } |
| |
| return max_mp; |
| } |
| |
| |
| /* Return the cost of forcibly inserting a barrier after INSN. */ |
| |
| static int |
| get_csky_barrier_cost (rtx_insn *insn) |
| { |
| /* Basing the location of the pool on the loop depth is preferable, |
| but at the moment, the basic block information seems to be |
| corrupt by this stage of the compilation. */ |
| int base_cost = 50; |
| rtx next = next_nonnote_insn (insn); |
| |
| if (next != NULL && GET_CODE (next) == CODE_LABEL) |
| base_cost -= 20; |
| |
| switch (GET_CODE (insn)) |
| { |
| case CODE_LABEL: |
| /* It will always be better to place the table before the label, rather |
| than after it. */ |
| return 50; |
| |
| case INSN: |
| case CALL_INSN: |
| return base_cost; |
| |
| case JUMP_INSN: |
| return base_cost - 10; |
| |
| default: |
| return base_cost + 10; |
| } |
| } |
| |
| |
| /* Find the best place in the insn stream in the range |
| (FIX->address,MAX_ADDRESS) to forcibly insert a minipool barrier. |
| Create the barrier by inserting a jump and add a new fix entry for |
| it. */ |
| |
| static Mfix * |
| create_csky_fix_barrier (Mfix *fix, Mfix *fix_next, |
| HOST_WIDE_INT max_address) |
| { |
| rtx_barrier *barrier; |
| rtx_insn *from = (fix ? fix->insn : get_insns ()); |
| /* The instruction after which we will insert the jump. */ |
| rtx_insn *selected = NULL; |
| int selected_cost; |
| /* The address at which the jump instruction will be placed. */ |
| HOST_WIDE_INT selected_address = 0; |
| Mfix *new_fix; |
| HOST_WIDE_INT count = (fix ? fix->address : 0); |
| HOST_WIDE_INT max_count = max_address; |
| rtx_code_label *label = gen_label_rtx (); |
| |
| selected_cost = get_csky_barrier_cost (from); |
| |
| while (from && count < max_count) |
| { |
| int new_cost; |
| rtx_jump_table_data *table; |
| |
| /* Count the length of this insn. */ |
| count += get_attr_length (from); |
| |
| /* If there is a jump table, add its length. */ |
| if (tablejump_p (from, NULL, &table)) |
| { |
| count += get_csky_jump_table_size (table); |
| |
| /* Jump tables aren't in a basic block, so base the cost on |
| the dispatch insn. If we select this location, we will |
| still put the pool after the table. */ |
| new_cost = get_csky_barrier_cost (from); |
| |
| if (count < max_count |
| && (!selected || new_cost <= selected_cost)) |
| { |
| selected = table; |
| selected_cost = new_cost; |
| selected_address = count; |
| } |
| |
| /* Continue after the dispatch table. */ |
| from = NEXT_INSN (table); |
| continue; |
| } |
| |
| new_cost = get_csky_barrier_cost (from); |
| |
| if (count < max_count |
| && (!selected || new_cost <= selected_cost)) |
| { |
| selected = from; |
| selected_cost = new_cost; |
| selected_address = count; |
| } |
| |
| from = NEXT_INSN (from); |
| } |
| |
| /* Make sure that we found a place to insert the jump. */ |
| gcc_assert (selected); |
| |
| /* Create a new JUMP_INSN that branches around a barrier. */ |
| from = emit_jump_insn_after (gen_jump (label), selected); |
| JUMP_LABEL (from) = label; |
| barrier = emit_barrier_after (from); |
| emit_label_after (label, barrier); |
| |
| /* Create a minipool barrier entry for the new barrier. */ |
| new_fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (* new_fix)); |
| new_fix->insn = barrier; |
| new_fix->address = selected_address; |
| if (fix) |
| { |
| new_fix->next = fix->next; |
| fix->next = new_fix; |
| } |
| else |
| new_fix->next = fix_next; |
| |
| return new_fix; |
| } |
| |
| |
| /* Print a symbolic form of the constant X to the dump file F. |
| This is used for dump output for -mconstpool in the target-dependent |
| reorg pass. */ |
| |
| static void |
| print_csky_value (FILE *f, rtx x) |
| { |
| switch (GET_CODE (x)) |
| { |
| case CONST_INT: |
| fprintf (f, HOST_WIDE_INT_PRINT_HEX, INTVAL (x)); |
| return; |
| |
| case CONST_DOUBLE: |
| fprintf (f, "<0x%lx,0x%lx>", (long)XWINT (x, 2), (long)XWINT (x, 3)); |
| return; |
| |
| case CONST_VECTOR: |
| { |
| int i; |
| |
| fprintf (f, "<"); |
| for (i = 0; i < CONST_VECTOR_NUNITS (x); i++) |
| { |
| fprintf (f, HOST_WIDE_INT_PRINT_HEX, |
| INTVAL (CONST_VECTOR_ELT (x, i))); |
| if (i < (CONST_VECTOR_NUNITS (x) - 1)) |
| fputc (',', f); |
| } |
| fprintf (f, ">"); |
| } |
| return; |
| |
| case CONST_STRING: |
| fprintf (f, "\"%s\"", XSTR (x, 0)); |
| return; |
| |
| case SYMBOL_REF: |
| fprintf (f, "`%s'", XSTR (x, 0)); |
| return; |
| |
| case LABEL_REF: |
| fprintf (f, "L%d", INSN_UID (XEXP (x, 0))); |
| return; |
| |
| case CONST: |
| print_csky_value (f, XEXP (x, 0)); |
| return; |
| |
| case PLUS: |
| print_csky_value (f, XEXP (x, 0)); |
| fprintf (f, "+"); |
| print_csky_value (f, XEXP (x, 1)); |
| return; |
| |
| case PC: |
| fprintf (f, "pc"); |
| return; |
| |
| default: |
| fprintf (f, "????"); |
| return; |
| } |
| } |
| |
| |
| /* Record INSN, which will need fixing up to load a value from the |
| minipool. ADDRESS is the offset of the insn since the start of the |
| function; LOC is a pointer to the part of the insn which requires |
| fixing; VALUE is the constant that must be loaded, which is of type |
| MODE. */ |
| |
| static void |
| push_csky_minipool_fix (rtx_insn *insn, HOST_WIDE_INT address, rtx *loc, |
| machine_mode mode, rtx value) |
| { |
| #define CSKY_ELRW16_RANGE 1400 |
| #define CSKY_LRW16_RANGE 700 |
| #define CSKY_CONSTANT_POOL_RANGE (TARGET_ELRW ? CSKY_ELRW16_RANGE \ |
| : CSKY_LRW16_RANGE) |
| |
| /* Fixes less than a word need padding out to a word boundary. */ |
| #define CSKY_MINIPOOL_FIX_SIZE(mode) \ |
| (GET_MODE_SIZE ((mode)) >= 4 ? GET_MODE_SIZE ((mode)) : 4) |
| |
| Mfix *fix = (Mfix *) obstack_alloc (&minipool_obstack, sizeof (*fix)); |
| |
| fix->insn = insn; |
| fix->address = address; |
| fix->loc = loc; |
| fix->mode = mode; |
| fix->fix_size = CSKY_MINIPOOL_FIX_SIZE (mode); |
| fix->value = value; |
| fix->forwards = CSKY_CONSTANT_POOL_RANGE; |
| fix->backwards = 0; |
| fix->minipool = NULL; |
| |
| /* If an insn doesn't have a range defined for it, then it isn't |
| expecting to be reworked by this code. Better to stop now than |
| to generate duff assembly code. */ |
| gcc_assert (fix->forwards || fix->backwards); |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, |
| ";; %smode fixup for i%d; addr %lu, range (%ld,%ld): ", |
| GET_MODE_NAME (mode), |
| INSN_UID (insn), (unsigned long) address, |
| -1 * (long)fix->backwards, (long)fix->forwards); |
| print_csky_value (dump_file, fix->value); |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* Add it to the chain of fixes. */ |
| fix->next = NULL; |
| |
| if (minipool_fix_head != NULL) |
| minipool_fix_tail->next = fix; |
| else |
| minipool_fix_head = fix; |
| |
| minipool_fix_tail = fix; |
| } |
| |
| |
| /* Fill in the offsets for minipool entries. */ |
| |
| static void |
| assign_csky_minipool_offsets (Mfix *barrier) |
| { |
| HOST_WIDE_INT offset = 0; |
| Mnode *mp; |
| |
| minipool_barrier = barrier; |
| |
| for (mp = minipool_vector_head; mp != NULL; mp = mp->next) |
| { |
| mp->offset = offset; |
| |
| if (mp->refcount > 0) |
| offset += mp->fix_size; |
| } |
| } |
| |
| |
| /* Output the literal table. */ |
| |
| static HOST_WIDE_INT |
| dump_csky_minipool (rtx_insn *scan) |
| { |
| Mnode *mp; |
| Mnode *nmp; |
| HOST_WIDE_INT pool_length = 0; |
| |
| if (dump_file) |
| fprintf (dump_file, |
| ";; Emitting minipool after insn %u;\ |
| address %ld; align %d (bytes)\n", |
| INSN_UID (scan), (unsigned long) minipool_barrier->address, 4); |
| |
| scan = emit_insn_after (gen_align_4 (), scan); |
| scan = emit_insn_after (minipool_vector_label, scan); |
| |
| for (mp = minipool_vector_head; mp != NULL; mp = nmp) |
| { |
| if (mp->refcount > 0) |
| { |
| if (dump_file) |
| { |
| fprintf (dump_file, ";; Offset %u, min %ld, max %ld ", |
| (unsigned) mp->offset, (unsigned long) mp->min_address, |
| (unsigned long) mp->max_address); |
| print_csky_value (dump_file, mp->value); |
| fputc ('\n', dump_file); |
| } |
| |
| switch (mp->fix_size) |
| { |
| case 4: |
| scan = emit_insn_after (gen_consttable_4 (mp->value), scan); |
| pool_length += 4; |
| break; |
| case 8: |
| scan = emit_insn_after (gen_consttable_8 (mp->value), scan); |
| pool_length += 8; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| nmp = mp->next; |
| free (mp); |
| } |
| |
| minipool_vector_head = minipool_vector_tail = NULL; |
| scan = emit_barrier_after (scan); |
| |
| return pool_length; |
| } |
| |
| /* Return true if INSN is a minipool load or instruction that will be |
| converted to one. It is assumed that INSN has type attribute "load". */ |
| |
| bool |
| csky_minipool_load_p (rtx_insn *insn) |
| { |
| rtx op1, addr; |
| |
| extract_insn_cached (insn); |
| |
| op1 = recog_data.operand[1]; |
| |
| /* This is a constant that has not yet been turned into |
| a minipool load. */ |
| if (CONSTANT_P (op1)) |
| return true; |
| |
| /* Constant pool loads are label_refs. */ |
| if (GET_CODE (op1) == ZERO_EXTEND || GET_CODE (op1) == SIGN_EXTEND) |
| op1 = XEXP (op1, 0); |
| if (GET_CODE (op1) != MEM) |
| return false; |
| addr = XEXP (op1, 0); |
| if (GET_CODE (addr) == PLUS && CONST_INT_P (XEXP (addr, 1))) |
| addr = XEXP (addr, 0); |
| return GET_CODE (addr) == LABEL_REF; |
| } |
| |
| |
| /* Compute the attribute "length" of push or pop insn, according to |
| the registers it uses. */ |
| |
| int |
| csky_compute_pushpop_length (rtx *operands) |
| { |
| rtx parallel_op = operands[2]; |
| /* Initialize to elements number of PARALLEL. */ |
| unsigned indx = XVECLEN (parallel_op, 0) - 1; |
| unsigned first_indx = 0; |
| unsigned regno = REGNO (operands[1]); |
| |
| if (regno > CSKY_LR_REGNUM) |
| return 4; |
| |
| /* Check each register in the list. */ |
| for (; indx > first_indx; indx--) |
| { |
| regno = REGNO (XEXP (XVECEXP (parallel_op, 0, indx), 0)); |
| /* If a register number higher than 15 is included, a 32-bit insn |
| is used. */ |
| if (regno > CSKY_LR_REGNUM) |
| return 4; |
| } |
| |
| return 2; |
| } |
| |
| /* Emit constant pools for -mconstpool. */ |
| |
| static void |
| csky_emit_constant_pools (void) |
| { |
| rtx_insn *insn; |
| HOST_WIDE_INT address = 0; |
| Mfix *fix; |
| |
| minipool_fix_head = minipool_fix_tail = NULL; |
| |
| /* The first insn must always be a note, or the code below won't |
| scan it properly. */ |
| insn = get_insns (); |
| gcc_assert (NOTE_P (insn)); |
| |
| /* Scan the insns and record the operands that need fixing. */ |
| for (insn = next_nonnote_insn (insn); insn; |
| insn = next_nonnote_insn (insn)) |
| { |
| if (BARRIER_P (insn)) |
| push_csky_minipool_barrier (insn, address); |
| else if (INSN_P (insn)) |
| { |
| rtx_jump_table_data *table; |
| |
| note_csky_invalid_constants (insn, address, true); |
| address += get_attr_length (insn); |
| |
| /* If the insn is a vector jump, add the size of the table |
| and skip the table. */ |
| if (tablejump_p (insn, NULL, &table)) |
| { |
| address += get_csky_jump_table_size (table); |
| insn = table; |
| } |
| } |
| } |
| |
| fix = minipool_fix_head; |
| |
| /* Now scan the fixups and perform the required changes. */ |
| while (fix) |
| { |
| Mfix *ftmp; |
| Mfix *last_added_fix; |
| Mfix *last_barrier = NULL; |
| Mfix *this_fix; |
| Mnode *mp; |
| bool has_pending_const = false; |
| |
| /* Check if there is any pending constant not processed. */ |
| for (mp = minipool_vector_head; mp; mp = mp->next) |
| if (mp->refcount > 0) |
| { |
| has_pending_const = true; |
| break; |
| } |
| |
| /* If no pending constant, skip over barrier insns. */ |
| if (has_pending_const == false) |
| { |
| while (fix && BARRIER_P (fix->insn)) |
| fix = fix->next; |
| if (fix == NULL) |
| break; |
| } |
| |
| last_added_fix = NULL; |
| |
| for (ftmp = fix; ftmp; ftmp = ftmp->next) |
| { |
| if (BARRIER_P (ftmp->insn)) |
| { |
| if (minipool_vector_head |
| && ftmp->address >= minipool_vector_head->max_address) |
| break; |
| |
| last_barrier = ftmp; |
| } |
| else |
| { |
| ftmp->minipool = add_csky_minipool_forward_ref (ftmp); |
| if (ftmp->minipool == NULL) |
| break; |
| } |
| last_added_fix = ftmp; /* Keep track of the last fix added. */ |
| } |
| |
| /* If the last added fix is a barrier, dump minipool after it. */ |
| if (last_added_fix && BARRIER_P (last_added_fix->insn)) |
| ftmp = last_barrier; |
| else |
| { |
| /* ftmp is first fix that we can't fit into this pool. |
| Insert a new barrier in the code somewhere between the previous |
| fix and this one, and arrange to jump around it. */ |
| HOST_WIDE_INT max_address; |
| |
| /* The last item on the list of fixes must be a barrier, so |
| we can never run off the end of the list of fixes without |
| last_barrier being set. */ |
| gcc_assert (ftmp); |
| |
| /* Check that there isn't another fix that is in range that |
| we couldn't fit into this pool because the pool was |
| already too large: we need to put the pool before such an |
| instruction. The pool itself may come just after the |
| fix because create_csky_fix_barrier also allows space for a |
| jump instruction. */ |
| max_address = minipool_vector_head->max_address; |
| if (ftmp->address < max_address) |
| max_address = ftmp->address + 1; |
| last_barrier = create_csky_fix_barrier (last_added_fix, ftmp, |
| max_address); |
| } |
| |
| assign_csky_minipool_offsets (last_barrier); |
| |
| /* Scan over the fixes we have identified for this pool, fixing them |
| up and adding the constants to the pool itself. */ |
| for (this_fix = fix; this_fix && ftmp != this_fix; |
| this_fix = this_fix->next) |
| { |
| if (GET_CODE (this_fix->insn) != BARRIER) |
| { |
| rtx addr |
| = plus_constant (Pmode, |
| gen_rtx_LABEL_REF (VOIDmode, |
| minipool_vector_label), |
| this_fix->minipool->offset); |
| rtx insn_body = PATTERN (this_fix->insn); |
| rtx src = XEXP (insn_body, 1); |
| *this_fix->loc = gen_rtx_MEM (this_fix->mode, addr); |
| if (GET_CODE (this_fix->value) == SYMBOL_REF) |
| emit_insn_after (gen_rtx_UNSPEC_VOLATILE (VOIDmode, |
| gen_rtvec (1, src), |
| VUNSPEC_SYMBOL_REF), |
| this_fix->insn); |
| } |
| } |
| dump_csky_minipool (last_barrier->insn); |
| fix = ftmp; |
| if (fix->next == NULL) |
| break; |
| } |
| |
| /* Free the minipool memory. */ |
| obstack_free (&minipool_obstack, minipool_startobj); |
| } |
| |
| |
| /* Implement TARGET_MACHINE_DEPENDENT_REORG. This handles |
| -mconstpool output. */ |
| |
| static void |
| csky_reorg (void) |
| { |
| if (TARGET_CONSTANT_POOL) |
| csky_emit_constant_pools (); |
| } |
| |
| |
| /* Check to see if the current function contains a branch insn with the |
| far jump attribute set. Such a function uses the LR register. */ |
| |
| static bool |
| csky_far_jump_used_p (void) |
| { |
| rtx_insn *insn; |
| if (cfun->machine->far_jump_used) |
| return true; |
| |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| if (GET_CODE (insn) == JUMP_INSN |
| /* Ignore tablejump patterns. */ |
| && GET_CODE (PATTERN (insn)) != ADDR_VEC |
| && GET_CODE (PATTERN (insn)) != ADDR_DIFF_VEC |
| && get_attr_far_jump (insn) == FAR_JUMP_YES) |
| { |
| cfun->machine->far_jump_used = 1; |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Return the mask of registers used by the current function. Set |
| COUNT to the number of registers used. */ |
| |
| static unsigned int |
| get_csky_live_regs (int *count) |
| { |
| int reg; |
| unsigned int live_regs_mask = 0; |
| |
| *count = 0; |
| for (reg = 0; reg < CSKY_NGPR_REGS; reg++) |
| { |
| bool save = false; |
| |
| /* Ignore unsupported registers. */ |
| if (CSKY_TARGET_ARCH (CK801) && reg > 8 && reg < 13) |
| continue; |
| if ((CSKY_TARGET_ARCH (CK801) |
| || CSKY_TARGET_ARCH (CK802) |
| || CSKY_TARGET_ARCH (CK803)) |
| && reg > 15) |
| break; |
| |
| /* Caller-saved registers marked as used. */ |
| if (df_regs_ever_live_p (reg) && !call_used_regs[reg]) |
| save = true; |
| |
| /* Frame pointer marked used. */ |
| else if (frame_pointer_needed && reg == HARD_FRAME_POINTER_REGNUM) |
| save = true; |
| |
| /* This is required for CK801/802 where FP is a fixed reg, otherwise |
| we end up with no FP value available to the DWARF-2 unwinder. */ |
| else if (crtl->calls_eh_return && reg == HARD_FRAME_POINTER_REGNUM) |
| save = true; |
| |
| /* CK801/802 also need special handling for LR because it's clobbered |
| by far jumps. */ |
| else if ((CSKY_TARGET_ARCH (CK801) || CSKY_TARGET_ARCH (CK802)) |
| && reg == CSKY_LR_REGNUM |
| && (!crtl->is_leaf || csky_far_jump_used_p ())) |
| save = true; |
| |
| /* Register is used for EH data return. */ |
| else if (crtl->calls_eh_return |
| && reg >= CSKY_FIRST_EH_RETDATA_REGNUM |
| && reg <= CSKY_LAST_EH_RETDATA_REGNUM) |
| save = true; |
| |
| /* We need a temporary reg to hold the offset for adjusting the SP |
| for a large stack frame. */ |
| if (reg == CSKY_STACKADJUST_REGNUM |
| && cfun->machine->reg_offset > CSKY_MAX_SP_ADJUST * 2) |
| save = true; |
| |
| /* Add reg to the mask. */ |
| if (save) |
| { |
| (*count)++; |
| live_regs_mask |= (1 << reg); |
| } |
| } |
| return live_regs_mask; |
| } |
| |
| /* Compute the stack frame layout, storing sizes of the various pieces |
| in cfun->machine. |
| |
| Stack frames constructed in the prologue look like: |
| ... caller's frame ... |
| incoming SP -> caller's outbound argument overflow |
| argument spill |
| optional FP -> register save |
| local variables |
| alloca() space |
| adjusted SP -> outbound argument overflow |
| |
| with SP/FP pointing at the base (low address) of the respective area, |
| and each area aligned to a word boundary. */ |
| |
| static void |
| csky_layout_stack_frame (void) |
| { |
| machine_function *infp = cfun->machine; |
| int reg_count; |
| |
| if (infp->frame_init_p) |
| return; |
| |
| /* Get sizes of local variables & outbound arguments. */ |
| infp->outbound_size = CSKY_STACK_ALIGN (crtl->outgoing_args_size); |
| infp->local_offset = infp->outbound_size; |
| infp->local_size = CSKY_STACK_ALIGN (get_frame_size ()); |
| infp->reg_offset = infp->local_offset + infp->local_size; |
| |
| /* Now compute size of argument spill + saved regs. These do not |
| need explicit alignment since they are already word-sized. */ |
| infp->reg_mask = get_csky_live_regs (®_count); |
| infp->reg_size = reg_count * UNITS_PER_WORD; |
| infp->arg_offset = infp->reg_offset + infp->reg_size; |
| infp->arg_size = crtl->args.pretend_args_size; |
| infp->frame_size = infp->arg_offset + infp->arg_size; |
| infp->frame_init_p = reload_completed; |
| } |
| |
| /* Implement TARGET_CAN_ELIMINATE. */ |
| static bool |
| csky_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| if (to == FRAME_POINTER_REGNUM) |
| return from != ARG_POINTER_REGNUM; |
| if (to == STACK_POINTER_REGNUM) |
| return !frame_pointer_needed; |
| return true; |
| } |
| |
| /* Worker function for INITIAL_ELIMINATION_OFFSET macro. |
| Define the offset between two registers, one to be eliminated, and |
| the other its replacement, at the start of a routine. */ |
| |
| HOST_WIDE_INT |
| csky_initial_elimination_offset (int from, int to) |
| { |
| int offset; |
| |
| csky_layout_stack_frame (); |
| |
| /* Set OFFSET to the offset to the initial stack pointer. */ |
| switch (from) |
| { |
| case FRAME_POINTER_REGNUM: |
| case HARD_FRAME_POINTER_REGNUM: |
| offset = cfun->machine->reg_offset; |
| break; |
| |
| case ARG_POINTER_REGNUM: |
| offset = cfun->machine->arg_offset; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* If we are asked for the offset to the frame pointer instead, |
| then subtract the difference between the frame pointer and stack |
| pointer. */ |
| if (to == FRAME_POINTER_REGNUM || to == HARD_FRAME_POINTER_REGNUM) |
| offset -= cfun->machine->reg_offset; |
| return offset; |
| } |
| |
| |
| /* 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 |
| csky_function_arg (cumulative_args_t pcum_v, const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v); |
| int reg = pcum->reg; |
| machine_mode mode = arg.mode; |
| |
| if (FUNCTION_VARG_MODE_P(mode) |
| && !pcum->is_stdarg) |
| { |
| reg = pcum->freg; |
| |
| if (reg < CSKY_NPARM_FREGS) |
| return gen_rtx_REG (mode, CSKY_FIRST_VFP_REGNUM + reg); |
| else |
| return NULL_RTX; |
| } |
| |
| if (reg < CSKY_NPARM_REGS) |
| return gen_rtx_REG (mode, CSKY_FIRST_PARM_REGNUM + reg); |
| |
| return NULL_RTX; |
| } |
| |
| |
| /* Return the number of registers (words) needed to pass an argument of |
| MODE and TYPE. */ |
| |
| static int |
| csky_num_arg_regs (machine_mode mode, const_tree type, bool is_stdarg) |
| { |
| int size; |
| |
| if (type && mode == BLKmode) |
| size = int_size_in_bytes (type); |
| else |
| size = GET_MODE_SIZE (mode); |
| |
| if (TARGET_HARD_FLOAT_ABI |
| && !is_stdarg) |
| { |
| if (CSKY_VREG_MODE_P(mode) |
| && !TARGET_SINGLE_FPU) |
| return ((CSKY_NUM_WORDS (size) + 1) / 2); |
| } |
| |
| return CSKY_NUM_WORDS (size); |
| } |
| |
| |
| /* Implement TARGET_FUNCTION_ARG_ADVANCE. */ |
| |
| static void |
| csky_function_arg_advance (cumulative_args_t pcum_v, |
| const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v); |
| int *reg = &pcum->reg; |
| machine_mode mode = arg.mode; |
| |
| int param_size = csky_num_arg_regs (mode, arg.type, pcum->is_stdarg); |
| int param_regs_nums = CSKY_NPARM_REGS; |
| |
| if (FUNCTION_VARG_MODE_P(mode) |
| && !pcum->is_stdarg) |
| { |
| reg = &pcum->freg; |
| param_regs_nums = CSKY_NPARM_FREGS; |
| } |
| |
| if (*reg + param_size > param_regs_nums) |
| *reg = param_regs_nums; |
| else |
| *reg += param_size; |
| } |
| |
| |
| /* Implement TARGET_FUNCTION_VALUE. */ |
| static rtx |
| csky_function_value (const_tree type, const_tree func, |
| bool outgoing ATTRIBUTE_UNUSED) |
| { |
| machine_mode mode; |
| int unsignedp ATTRIBUTE_UNUSED; |
| int size; |
| |
| mode = TYPE_MODE (type); |
| size = int_size_in_bytes (type); |
| |
| if (FUNCTION_VARG_MODE_P(mode)) |
| { |
| mode = promote_function_mode (type, mode, &unsignedp, func, 1); |
| return gen_rtx_REG (mode, CSKY_FIRST_VFP_REGNUM); |
| } |
| |
| /* Since we promote return types, we must promote the mode here too. */ |
| if (INTEGRAL_TYPE_P (type)) |
| { |
| mode = promote_function_mode (type, mode, &unsignedp, func, 1); |
| return gen_rtx_REG (mode, CSKY_FIRST_RET_REGNUM); |
| } |
| |
| if (mode == BLKmode && size > UNITS_PER_WORD |
| && size <= UNITS_PER_WORD * 2) |
| { |
| rtx ret_regs[2]; |
| ret_regs[0] = gen_rtx_EXPR_LIST (SImode, |
| gen_rtx_REG (SImode, |
| CSKY_FIRST_RET_REGNUM), |
| GEN_INT (0 * UNITS_PER_WORD)); |
| ret_regs[1] = gen_rtx_EXPR_LIST (SImode, |
| gen_rtx_REG (SImode, |
| CSKY_FIRST_RET_REGNUM + 1), |
| GEN_INT (1 * UNITS_PER_WORD)); |
| |
| rtvec vec = gen_rtvec (2, ret_regs[0], ret_regs[1]); |
| |
| return gen_rtx_PARALLEL (mode, vec); |
| } |
| |
| return gen_rtx_REG (mode, CSKY_FIRST_RET_REGNUM); |
| } |
| |
| |
| /* Implement TARGET_LIBCALL_VALUE. */ |
| |
| static rtx |
| csky_libcall_value (machine_mode mode, |
| const_rtx libcall ATTRIBUTE_UNUSED) |
| { |
| if (FUNCTION_VARG_MODE_P(mode)) |
| { |
| return gen_rtx_REG (mode, CSKY_FIRST_VFP_REGNUM); |
| } |
| return gen_rtx_REG (mode, CSKY_FIRST_RET_REGNUM); |
| } |
| |
| |
| /* Implement TARGET_FUNCTION_VALUE_REGNO_P. |
| On C-SKY, only r0 can return results. */ |
| |
| static bool |
| csky_function_value_regno_p (const unsigned int regno) |
| { |
| if (regno == CSKY_FIRST_RET_REGNUM |
| || (TARGET_HARD_FLOAT_ABI |
| && regno == CSKY_FIRST_VFP_REGNUM)) |
| return true; |
| return false; |
| } |
| |
| |
| /* Return an RTX indicating where the return address to the |
| calling function can be found. */ |
| |
| rtx |
| csky_return_addr (int count, rtx frame ATTRIBUTE_UNUSED) |
| { |
| if (count != 0) |
| return NULL_RTX; |
| |
| return get_hard_reg_initial_val (Pmode, CSKY_LR_REGNUM); |
| } |
| |
| |
| /* Implement TARGET_ARG_PARTIAL_BYTES. |
| Return the number of bytes at the beginning of an argument |
| that must be put in registers. The value must be zero for arguments |
| that are passed entirely in registers or |
| that are entirely pushed on the stack. */ |
| |
| static int |
| csky_arg_partial_bytes (cumulative_args_t pcum_v, const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v); |
| int param_size = csky_num_arg_regs (arg.mode, arg.type, pcum->is_stdarg); |
| int reg = pcum->reg; |
| |
| if (FUNCTION_VARG_MODE_P(arg.mode) |
| && !pcum->is_stdarg) |
| return 0; |
| |
| if (reg < CSKY_NPARM_REGS |
| && reg + param_size > CSKY_NPARM_REGS) |
| return (CSKY_NPARM_REGS - reg) * UNITS_PER_WORD; |
| |
| return 0; |
| } |
| |
| |
| /* Implement TARGET_SETUP_INCOMING_VARARGS. |
| On C-Sky the copy from the argument registers to the stack is emitted |
| by the prologue hooks, so here we just have to note how much stack space |
| to save. */ |
| |
| static void |
| csky_setup_incoming_varargs (cumulative_args_t pcum_v, |
| const function_arg_info &arg, |
| int *pretend_size, |
| int second_time ATTRIBUTE_UNUSED) |
| { |
| CUMULATIVE_ARGS *pcum = get_cumulative_args (pcum_v); |
| CUMULATIVE_ARGS local_cum; |
| cumulative_args_t local_cum_v = pack_cumulative_args (&local_cum); |
| int regs_to_push; |
| |
| cfun->machine->uses_anonymous_args = 1; |
| local_cum = *pcum; |
| csky_function_arg_advance (local_cum_v, arg); |
| regs_to_push = CSKY_NPARM_REGS - local_cum.reg; |
| if (regs_to_push) |
| *pretend_size = regs_to_push * UNITS_PER_WORD; |
| } |
| |
| |
| /* Implement TARGET_ASM_OUTPUT_MI_THUNK. |
| Output code to add DELTA to the first argument, and then jump |
| to FUNCTION. Used for C++ multiple inheritance. */ |
| |
| static void |
| csky_output_mi_thunk (FILE *file, tree thunk ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, |
| HOST_WIDE_INT vcall_offset, |
| tree function) |
| { |
| const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk)); |
| const char *thiz = "a0"; |
| const char *reg0 = "t0"; |
| const char *reg1 = "t1"; |
| int maxoff = 4096; /* Constant range for addi/subi. */ |
| |
| assemble_start_function (thunk, fnname); |
| final_start_function (emit_barrier (), file, 1); |
| |
| rtx fnaddr = XEXP (DECL_RTL (function), 0); |
| |
| if (CSKY_TARGET_ARCH (CK801)) |
| { |
| /* CK801 can't use t registers and has only 16-bit addi/subi. */ |
| reg0 = "l0"; |
| reg1 = "l1"; |
| maxoff = 256; |
| if (vcall_offset > maxoff || vcall_offset < -maxoff) |
| fprintf (file, "\tpush\tl0, l1\n"); |
| else if (delta > maxoff || delta < -maxoff) |
| fprintf (file, "\tpush\tl0\n"); |
| } |
| |
| if (aggregate_value_p (TREE_TYPE (TREE_TYPE (function)), function)) |
| thiz = "a1"; |
| |
| /* Add delta to this_rtx. */ |
| if (delta != 0) |
| { |
| if (delta > maxoff || delta < -maxoff) |
| { |
| fprintf (file, "\tlrw\t%s, %ld\n", reg0, (long)delta); |
| fprintf (file, "\taddu\t%s, %s, %s\n", thiz, thiz, reg0); |
| } |
| else |
| fprintf (file, "\t%s\t%s, %s, %ld\n", |
| (delta > 0 ? "addi" : "subi"), thiz, thiz, |
| (long)(delta > 0 ? delta : -delta)); |
| } |
| |
| /* If needed, add *(*this_rtx + vcall_offset) to this_rtx. */ |
| if (vcall_offset != 0) |
| { |
| fprintf (file, "\tld.w\t%s, (%s, 0)\n", reg0, thiz); |
| |
| if (vcall_offset > maxoff || vcall_offset < -maxoff) |
| { |
| fprintf (file, "\tlrw\t%s, %ld\n", reg1, (long)vcall_offset); |
| fprintf (file, "\taddu\t%s, %s, %s\n", reg0, reg0, reg1); |
| } |
| else |
| fprintf (file, "\t%s\t%s, %s, %ld\n", |
| (vcall_offset > 0 ? "addi" : "subi"), reg0, reg0, |
| (long)(vcall_offset > 0 ? vcall_offset : -vcall_offset)); |
| |
| /* Load the offset and add it to this_rtx */ |
| fprintf (file, "\tld.w\t%s, (%s, 0)\n", reg0, reg0); |
| fprintf (file, "\taddu\t%s, %s, %s\n", thiz, thiz, reg0); |
| } |
| |
| /* We must pop the scratch regs individually instead of using the |
| "pop" insn, which also does a return. */ |
| if (CSKY_TARGET_ARCH (CK801)) |
| { |
| if (vcall_offset > maxoff || vcall_offset < -maxoff) |
| { |
| fprintf (file, "\tld.w\tl0, (sp, 0)\n"); |
| fprintf (file, "\tld.w\tl1, (sp, 4)\n"); |
| fprintf (file, "\taddi\t sp, sp, 8\n"); |
| } |
| else if (delta > maxoff || delta < -maxoff) |
| { |
| fprintf (file, "\tld.w\tl0, (sp, 0)\n"); |
| fprintf (file, "\taddi\tsp, sp, 4\n"); |
| } |
| } |
| |
| fprintf (file, "\tjbr\t"); |
| output_addr_const (file, fnaddr); |
| fprintf (file, "\n"); |
| |
| final_end_function (); |
| assemble_end_function (thunk, fnname); |
| } |
| |
| |
| /* Implement TARGET_CONDITIONAL_REGISTER_USAGE. |
| Conditionally modify five variables fixed_regs, call_used_regs, global_regs, |
| reg_names, and reg_class_contents, to take into account any dependence of |
| these register sets on target flags. |
| |
| CK801 has registers r0-r8 and r13-r15. CK802 and CK803 have registers |
| r0-r15 (the "low" registers). Other cpus use registers r0-r31 with |
| -mhigh-registers, otherwise also only r0-r15. |
| |
| CK801 only has 16-bit instructions, most of which can only reference |
| r0-r7 (the "mini" registers). So we mark regs outside that range as |
| fixed. -msmart can be used on other arch variants to force the same |
| behavior because it results in smaller code size. |
| |
| TODO: investigate whether it's beneficial to use r8-r13 as a spill |
| class when TARGET_MINI_REGISTERS instead of making them unusable by |
| the register allocator. */ |
| |
| static void |
| csky_conditional_register_usage (void) |
| { |
| /* Only use mini registers in smart mode or 801. */ |
| if (TARGET_MINI_REGISTERS) |
| { |
| int i; |
| |
| for (i = (CSKY_LAST_MINI_REGNUM + 1); i < 32; i++) |
| { |
| fixed_regs[i] = 1; |
| call_used_regs[i] = 1; |
| } |
| } |
| /* For some targets, the high registers are not supported. |
| CPUs other than ck801/ck802/ck803 use high registers |
| depending on -mhigh-registers option. */ |
| else if (CSKY_TARGET_ARCH (CK802) |
| || CSKY_TARGET_ARCH (CK803) |
| || !TARGET_HIGH_REGISTERS) |
| { |
| int i; |
| |
| for (i = CSKY_FIRST_HIGH_REGNUM; i <= CSKY_LAST_HIGH_REGNUM; i++) |
| { |
| fixed_regs[i] = 1; |
| call_used_regs[i] = 1; |
| } |
| } |
| |
| /* On CK801/CK802 we must mark lr as a fixed register because it is |
| used to implement far jumps. |
| FIXME: perhaps there should be a command-line option controlling |
| use of lr for far jumps on ck802 when !TARGET_MINI_REGS, when |
| you really want lr to be available to the register allocator and |
| you know there are no far jumps in the code. */ |
| if (CSKY_TARGET_ARCH (CK801) || CSKY_TARGET_ARCH (CK802)) |
| { |
| fixed_regs[CSKY_LR_REGNUM] = 1; |
| call_used_regs[CSKY_LR_REGNUM] = 0; |
| } |
| |
| /* The hi/lo registers are only supported in dsp mode. */ |
| if (!TARGET_DSP) |
| { |
| fixed_regs[CSKY_HI_REGNUM] = 1; |
| call_used_regs[CSKY_HI_REGNUM] = 1; |
| |
| fixed_regs[CSKY_LO_REGNUM] = 1; |
| call_used_regs[CSKY_LO_REGNUM] = 1; |
| } |
| |
| /* The V_REGS are only supported in hard float mode. */ |
| if (!TARGET_HARD_FLOAT) |
| { |
| int regno; |
| |
| for (regno = CSKY_FIRST_VFP_REGNUM; |
| regno <= CSKY_LAST_VFP3_REGNUM; regno++) |
| { |
| fixed_regs[regno] = 1; |
| call_used_regs[regno] = 1; |
| } |
| } |
| |
| if (!TARGET_SUPPORT_FPV3) |
| { |
| int regno; |
| |
| for (regno = CSKY_FIRST_VFP3_REGNUM; |
| regno <= CSKY_LAST_VFP3_REGNUM; regno++) |
| { |
| fixed_regs[regno] = 1; |
| call_used_regs[regno] = 1; |
| } |
| } |
| |
| /* In pic mode, the gb register is not available for register |
| allocation. Since gb is not clobbered by function |
| calls, set its call_used_regs to 0. */ |
| if (flag_pic) |
| { |
| fixed_regs[PIC_OFFSET_TABLE_REGNUM] = 1; |
| call_used_regs[PIC_OFFSET_TABLE_REGNUM] = 0; |
| } |
| } |
| |
| /* Implement TARGET_HARD_REGNO_NREGS. */ |
| |
| static unsigned int |
| csky_hard_regno_nregs (unsigned int regno, machine_mode mode) |
| { |
| if (regno >= CSKY_FIRST_VFP_REGNUM && !CSKY_TARGET_ARCH (CK803)) |
| return 1; |
| else |
| return CSKY_NUM_REGS (mode); |
| } |
| |
| /* Implement TARGET_HARD_REGNO_MODE_OK. Return true if REGNO is a |
| valid register for holding a quantity of type MODE. */ |
| |
| static bool |
| csky_hard_regno_mode_ok (unsigned int regno, machine_mode mode) |
| { |
| int nregs = CSKY_NUM_REGS (mode); |
| |
| /* We can't handle more than doubleword sizes for any register. */ |
| if (nregs > 2) |
| return false; |
| |
| /* For general registers, return true if mode is one word size. |
| When the size is larger than one word size, there should |
| be two successive hard registers to put the data. */ |
| if (regno < CSKY_NGPR_REGS) |
| { |
| if (nregs < 2) |
| return true; |
| else if (TARGET_MINI_REGISTERS) |
| return (regno < CSKY_LAST_MINI_REGNUM); |
| else if (CSKY_TARGET_ARCH (CK802) |
| || CSKY_TARGET_ARCH (CK803) |
| || !TARGET_HIGH_REGISTERS) |
| /* Without high register, r15 cannot hold doubleword data. */ |
| return (regno < (CSKY_SP_REGNUM - 1)); |
| else |
| return (regno < (CSKY_SP_REGNUM - 1) |
| || (regno >= CSKY_LR_REGNUM |
| && regno < CSKY_LAST_HIGH_UNFIXED_REGNUM)); |
| } |
| else if (regno == CSKY_CC_REGNUM) |
| return (mode == CCmode); |
| else if (regno == CSKY_HI_REGNUM || regno == CSKY_LO_REGNUM) |
| { |
| /* Don't allocate hi,lo register for float data even |
| if in dsp mode, because it will cause high cost |
| to reload data from hi,lo register. */ |
| if (!TARGET_DSP || mode == SFmode || mode == DFmode) |
| return false; |
| else if (nregs == 2) |
| return (regno == CSKY_HI_REGNUM); |
| else |
| return true; |
| } |
| else if (CSKY_VREG_P (regno) && TARGET_HARD_FLOAT) |
| return true; |
| |
| return false; |
| } |
| |
| /* Implement TARGET_MODES_TIEABLE_P. We can't tie DFmode with other modes |
| when V_REGs might be in use because those registers mess with the stored |
| bits. */ |
| |
| static bool |
| csky_modes_tieable_p (machine_mode mode1, machine_mode mode2) |
| { |
| return !(TARGET_HARD_FLOAT |
| && mode1 != mode2 |
| && (mode1 == DFmode || mode2 == DFmode)); |
| } |
| |
| /* Implement TARGET_CLASS_LIKELY_SPILLED_P. |
| We need to define this for MINI_REGS when we only use r0 - r7. |
| Otherwise we can end up using r0-r4 for function arguments, and don't |
| have enough left over to do doubleword arithmetic. */ |
| |
| static bool |
| csky_class_likely_spilled_p (reg_class_t rclass) |
| { |
| if ((TARGET_MINI_REGISTERS && rclass == MINI_REGS) |
| || rclass == C_REGS) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Implement TARGET_PREFERRED_RELOAD_CLASS. |
| Given an rtx X being reloaded into a reg required to be |
| in class CLASS, return the class of reg to actually use. |
| In general this is just CLASS. */ |
| |
| static reg_class_t |
| csky_preferred_reload_class (rtx x, reg_class_t rclass) |
| { |
| if (TARGET_HARD_FLOAT |
| && CONST_DOUBLE_P (x) |
| && (GET_MODE (x) == DFmode || GET_MODE (x) == SFmode) |
| && rclass == NO_REGS) |
| return GENERAL_REGS; |
| return rclass; |
| } |
| |
| |
| /* Implement TARGET_CLASS_MAX_NREGS. |
| Return the maximum number of consecutive registers of class rclass needed |
| to hold a value of mode mode. |
| On the csky, this is the size of MODE in words, |
| except in the FP regs, where a single reg is always enough. */ |
| |
| static unsigned char |
| csky_class_max_nregs (reg_class_t rclass, machine_mode mode) |
| { |
| if (rclass == V_REGS) |
| return 1; |
| else |
| return CSKY_NUM_REGS (mode); |
| } |
| |
| |
| /* Implement TARGET_SECONDARY_RELOAD. |
| If copying a register of RCLASS from/to X requires an intermediate |
| register, the hook should return the REGISTER_CLASS required for this |
| intermediate register. |
| If no intermediate register is required, it should return NO_REGS. |
| If more than one intermediate register is required, describe the one |
| that is closest in the copy chain to the reload register. */ |
| |
| reg_class_t |
| csky_secondary_reload (bool in_p ATTRIBUTE_UNUSED, rtx x, |
| reg_class_t rclass, |
| machine_mode mode, |
| secondary_reload_info *sri ATTRIBUTE_UNUSED) |
| { |
| int regno = -1; |
| |
| /* Extract the real regno from X. */ |
| if (GET_CODE (x) == SIGN_EXTEND) |
| { |
| int off = 0; |
| |
| x = XEXP (x, 0); |
| |
| if (reg_renumber) |
| regno = true_regnum (x); |
| else |
| { |
| while (GET_CODE (x) == SUBREG) |
| { |
| off += subreg_regno_offset (REGNO (SUBREG_REG (x)), |
| GET_MODE (SUBREG_REG (x)), |
| SUBREG_BYTE (x), GET_MODE (x)); |
| x = SUBREG_REG (x); |
| } |
| |
| if (GET_CODE (x) == REG) |
| regno = REGNO (x) + off; |
| } |
| } |
| else if (GET_CODE (x) == REG || GET_CODE (x) == SUBREG) |
| regno = true_regnum (x); |
| |
| /* We always require a general register when copying anything to |
| HI/LO_REGNUM, except when copying an SImode value from HI/LO_REGNUM |
| to a general register, or when copying from register 0. */ |
| if (rclass == HILO_REGS && !CSKY_GENERAL_REGNO_P (regno)) |
| return GENERAL_REGS; |
| |
| if (rclass == V_REGS && !CSKY_GENERAL_REGNO_P (regno)) |
| { |
| /* Reload between vector reg and memory does not need an |
| intermediate register. */ |
| if (MEM_P (x) && (mode == SFmode || mode == DFmode)) |
| return NO_REGS; |
| else |
| return GENERAL_REGS; |
| } |
| |
| return NO_REGS; |
| } |
| |
| /* Implement TARGET_SPILL_CLASS. |
| Try spilling to a larger register class before spilling to memory. */ |
| |
| static reg_class_t |
| csky_spill_class (reg_class_t rclass, machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if ((rclass == MINI_REGS && !TARGET_MINI_REGISTERS) |
| || (rclass == LOW_REGS && TARGET_HIGH_REGISTERS)) |
| return GENERAL_REGS; |
| return NO_REGS; |
| } |
| |
| /* Convert a static initializer array of feature bits to sbitmap |
| representation. */ |
| |
| static void |
| csky_initialize_isa (sbitmap isa, const enum csky_isa_feature *isa_bits) |
| { |
| bitmap_clear (isa); |
| while (*isa_bits != CSKY_ISA_FEATURE_GET (none)) |
| bitmap_set_bit (isa, *(isa_bits++)); |
| } |
| |
| |
| /* Configure a build target TARGET from the user-specified options OPTS and |
| OPTS_SET. */ |
| |
| static void |
| csky_configure_build_target (struct csky_build_target *target, |
| struct cl_target_option *opts, |
| struct gcc_options *opts_set) |
| { |
| const struct csky_processors *csky_selected_tune = NULL; |
| struct csky_processors *csky_selected_cpu = NULL; |
| struct csky_processors *csky_selected_arch = NULL; |
| sbitmap all_sbits = sbitmap_alloc (CSKY_ISA_FEATURE_GET (max)); |
| bitmap_clear (all_sbits); |
| |
| bitmap_clear (target->isa); |
| target->core_name = NULL; |
| target->arch_name = NULL; |
| |
| if (opts_set->x_csky_arch_option) |
| csky_selected_arch = &all_architectures[opts->x_csky_arch_option]; |
| |
| if (opts_set->x_csky_cpu_option) |
| { |
| csky_selected_cpu = &all_cores[opts->x_csky_cpu_option]; |
| csky_selected_tune = &all_cores[opts->x_csky_cpu_option]; |
| } |
| |
| if (csky_selected_cpu) |
| { |
| /* TODO: support combination of features |
| between different cpu & arch, should based on arch. */ |
| if (csky_selected_arch |
| && (csky_selected_cpu->base_arch != csky_selected_arch->base_arch)) |
| warning (0, "cpu %s is not based on arch %s, ignoring the arch", |
| csky_selected_cpu->name, csky_selected_arch->name); |
| if (!csky_selected_arch) |
| csky_selected_arch = &all_architectures[csky_selected_cpu->base_arch]; |
| csky_initialize_isa (all_sbits, csky_selected_arch->isa_bits); |
| target->core_name = csky_selected_cpu->name; |
| } |
| else if (csky_selected_arch) |
| { |
| csky_selected_cpu = csky_selected_arch; |
| target->arch_name = csky_selected_arch->name; |
| } |
| else /* If the user did not specify a processor, choose one for them. */ |
| { |
| csky_selected_cpu = &all_cores[TARGET_CPU_DEFAULT]; |
| csky_selected_arch = &all_architectures[csky_selected_cpu->base_arch]; |
| csky_initialize_isa (all_sbits, csky_selected_arch->isa_bits); |
| target->core_name = csky_selected_cpu->name; |
| } |
| |
| /* The selected cpu may be an architecture, so lookup tuning by core ID. */ |
| if (!csky_selected_tune) |
| csky_selected_tune = &all_cores[csky_selected_cpu->core]; |
| gcc_assert (csky_selected_tune); |
| |
| gcc_assert (csky_selected_arch); |
| gcc_assert (csky_selected_cpu); |
| csky_initialize_isa (target->isa, csky_selected_cpu->isa_bits); |
| bitmap_ior (target->isa, target->isa, all_sbits); |
| |
| /* Finish initializing the target structure. */ |
| target->arch_pp_name = csky_selected_cpu->arch; |
| target->base_arch = csky_selected_cpu->base_arch; |
| target->arch_core = csky_selected_cpu->core; |
| |
| sbitmap_free (all_sbits); |
| } |
| |
| |
| /* Implement TARGET_OPTION_OVERRIDE. */ |
| |
| static void |
| csky_option_override (void) |
| { |
| csky_active_target.isa = sbitmap_alloc (CSKY_ISA_FEATURE_GET (max)); |
| |
| /* Create the default target_options structure. We need this early |
| to configure the overall build target. */ |
| target_option_default_node = target_option_current_node |
| = build_target_option_node (&global_options, &global_options_set); |
| |
| csky_configure_build_target (&csky_active_target, |
| TREE_TARGET_OPTION (target_option_default_node), |
| &global_options_set); |
| |
| #ifdef SUBTARGET_OVERRIDE_OPTIONS |
| SUBTARGET_OVERRIDE_OPTIONS; |
| #endif |
| |
| csky_base_arch = csky_active_target.base_arch; |
| |
| if (flag_pic && !(CSKY_TARGET_ARCH (CK807) |
| || CSKY_TARGET_ARCH (CK810) |
| || CSKY_TARGET_ARCH (CK860))) |
| { |
| flag_pic = 0; |
| warning (0, "%qs is not supported by arch %s", |
| "-fPIC", csky_active_target.arch_pp_name); |
| } |
| |
| /* Check floating-point options for consistency. */ |
| if (TARGET_HARD_FLOAT) |
| { |
| const struct csky_fpu_desc *csky_selected_fpu = NULL; |
| |
| if (csky_fpu_index == TARGET_FPU_auto) |
| { |
| const char *target_fpu_name; |
| bool ok; |
| int fpu_index; |
| |
| if (csky_active_target.core_name != NULL |
| && !strchr (csky_active_target.core_name, 'f')) |
| target_fpu_name = "auto"; |
| else if (CSKY_TARGET_ARCH (CK803) || !TARGET_DOUBLE_FLOAT) |
| target_fpu_name = "fpv2_sf"; |
| else if (CSKY_TARGET_ARCH (CK860)) |
| target_fpu_name = "fpv3"; |
| else if (TARGET_DOUBLE_FLOAT && TARGET_FDIVDU) |
| target_fpu_name = "fpv2_divd"; |
| else |
| #ifdef CSKY_FPUTYPE_DEFAULT |
| target_fpu_name = CSKY_FPUTYPE_DEFAULT; |
| #else |
| target_fpu_name = "fpv2"; |
| #endif |
| |
| ok = opt_enum_arg_to_value (OPT_mfpu_, target_fpu_name, &fpu_index, |
| CL_TARGET); |
| gcc_assert (ok); |
| csky_fpu_index = (enum csky_fpu_type) fpu_index; |
| } |
| |
| if (CSKY_TARGET_ARCH (CK801) || CSKY_TARGET_ARCH (CK802)) |
| error ("%qs is not supported by arch %s", |
| "-mhard-float", csky_active_target.arch_pp_name); |
| else if (csky_fpu_index == TARGET_FPU_auto) |
| error ("%<-mhard-float%> is not supported by the selected CPU"); |
| else |
| { |
| csky_selected_fpu = &all_fpus[csky_fpu_index]; |
| sbitmap fpu_bits = sbitmap_alloc (CSKY_ISA_FEATURE_GET (max)); |
| csky_initialize_isa (fpu_bits, csky_selected_fpu->isa_bits); |
| |
| bitmap_ior (csky_active_target.isa, csky_active_target.isa, |
| fpu_bits); |
| |
| sbitmap_free (fpu_bits); |
| } |
| } |
| else |
| { |
| if (TARGET_DOUBLE_FLOAT > 0) |
| warning (0, "%<-mdouble-float%> ignored without %<-mhard-float%>"); |
| TARGET_DOUBLE_FLOAT = 0; |
| if (TARGET_FDIVDU > 0) |
| warning (0, "%<-mfdivdu%> ignored without %<-mhard-float%>"); |
| TARGET_FDIVDU = 0; |
| } |
| |
| /* Initialize boolean versions of the architectural flags, for use |
| in the .md file. */ |
| |
| #undef CSKY_ISA |
| #define CSKY_ISA(IDENT, DESC) \ |
| { \ |
| csky_arch_isa_features[CSKY_ISA_FEATURE_GET (IDENT)] = \ |
| bitmap_bit_p (csky_active_target.isa, CSKY_ISA_FEATURE_GET (IDENT)); \ |
| } |
| #include "csky_isa.def" |
| #undef CSKY_ISA |
| |
| /* Extended LRW instructions are enabled by default on CK801, disabled |
| otherwise. */ |
| if (TARGET_ELRW == -1) |
| TARGET_ELRW = CSKY_TARGET_ARCH (CK801); |
| |
| /* DSP is enabled either by the processor feature or -mdsp |
| command-line option. There is no -mno-dsp option as the assembler |
| doesn't take one. */ |
| if (!TARGET_DSP) |
| TARGET_DSP = CSKY_ISA_FEATURE (dsp); |
| |
| /* There's both -mdiv and -mno-div. Take default from processor if |
| neither is specified explicitly. */ |
| if (TARGET_DIV == -1) |
| TARGET_DIV = CSKY_ISA_FEATURE (div); |
| |
| /* TARGET_CONSTANT_POOL is mandatory for CK801 and CK802 and optional |
| for other CPUs. |
| The reason why the compiler has to generate constant pools for CK801/2 |
| instead of deferring to the assembler is that these cores don't have a |
| long branch instruction other than jbsr, which clobbers lr. So for |
| the compiler to correctly save/restore lr it has to know whether there |
| are long branches, which depends on having accurate branch length |
| counts, which in turn depends on having control over where constant |
| pools are placed. */ |
| if ((CSKY_TARGET_ARCH (CK801) || CSKY_TARGET_ARCH (CK802)) |
| && !TARGET_CONSTANT_POOL) |
| error ("%qs is not supported by arch %s", |
| "-mno-constpool", csky_active_target.arch_pp_name); |
| else if (TARGET_CONSTANT_POOL == -1) |
| TARGET_CONSTANT_POOL = (CSKY_TARGET_ARCH (CK801) |
| || CSKY_TARGET_ARCH (CK802)); |
| |
| /* TARGET_MINI_REGISTERS is mandatory for CK801, the default for CK802, |
| and optional for other CPUs. TARGET_HIGH_REGISTERS is incompatible |
| with TARGET_MINI_REGISTERS, is not supported by CK801/802/803, |
| and is the default for other processors. |
| See csky_conditional_register_usage. */ |
| if (TARGET_MINI_REGISTERS > 0 && TARGET_HIGH_REGISTERS > 0) |
| error ("%<-msmart%> is incompatible with %<-mhigh-registers%>"); |
| else if (CSKY_TARGET_ARCH (CK801) |
| || CSKY_TARGET_ARCH (CK802) |
| || CSKY_TARGET_ARCH (CK803)) |
| { |
| if (CSKY_TARGET_ARCH (CK801) |
| || (CSKY_TARGET_ARCH (CK802) && TARGET_MINI_REGISTERS == -1)) |
| TARGET_MINI_REGISTERS = 1; |
| else if (TARGET_MINI_REGISTERS == -1) |
| TARGET_MINI_REGISTERS = 0; |
| if (TARGET_HIGH_REGISTERS > 0) |
| warning (0, "%qs is not supported by arch %s", |
| "-mhigh-registers", csky_active_target.arch_pp_name); |
| TARGET_HIGH_REGISTERS = 0; |
| } |
| else |
| { |
| if (TARGET_MINI_REGISTERS == -1) |
| TARGET_MINI_REGISTERS = 0; |
| if (TARGET_HIGH_REGISTERS == -1) |
| TARGET_HIGH_REGISTERS = !TARGET_MINI_REGISTERS; |
| } |
| |
| /* -mmultiple-stld is the default for everything but CK801, which |
| doesn't support it. */ |
| if (CSKY_TARGET_ARCH (CK801)) |
| { |
| if (TARGET_MULTIPLE_STLD > 0) |
| warning (0, "%qs is not supported by arch %s", |
| "-mmultiple-stld", csky_active_target.arch_pp_name); |
| TARGET_MULTIPLE_STLD = 0; |
| } |
| |
| /* TODO */ |
| |
| /* Resynchronize the saved target options. */ |
| cl_target_option_save (TREE_TARGET_OPTION (target_option_default_node), |
| &global_options, &global_options_set); |
| |
| #ifdef ENABLE_TPF_DEBUG |
| /* Don't emit DWARF4 unless specifically selected. The TPF |
| debuggers do not yet support DWARF 3/4. */ |
| if (!global_options_set.x_dwarf_strict) |
| dwarf_strict = 1; |
| if (!global_options_set.x_dwarf_version) |
| dwarf_version = 3; |
| #endif |
| |
| /* Don't run the scheduler before reload by default, |
| since it tends to increase register pressure. */ |
| if (!global_options_set.x_flag_schedule_insns) |
| flag_schedule_insns = 0; |
| |
| csky_add_gc_roots (); |
| } |
| |
| |
| /* Return TRUE if X contains any references to TLS symbols. */ |
| |
| bool |
| csky_tls_referenced_p (rtx x) |
| { |
| if (!TARGET_TLS) |
| return false; |
| |
| subrtx_iterator::array_type array; |
| FOR_EACH_SUBRTX (iter, array, x, ALL) |
| { |
| const_rtx x = *iter; |
| if (GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_TLS_MODEL (x) != 0) |
| return true; |
| |
| /* Don't recurse into UNSPEC_TLS looking for TLS symbols; these are |
| TLS offsets, not real symbol references. */ |
| if (GET_CODE (x) == UNSPEC && XINT (x, 1) == UNSPEC_TLS) |
| iter.skip_subrtxes (); |
| } |
| return false; |
| } |
| |
| |
| /* Implement TARGET_CANNOT_FORCE_CONST_MEM. |
| Determine if it's legal to put X into the constant pool. This |
| is not possible for the address of thread-local symbols, which |
| is checked above. */ |
| |
| static bool |
| csky_cannot_force_const_mem (machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x) |
| { |
| return csky_tls_referenced_p (x); |
| } |
| |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. Returns nonzero if the |
| constant value X is a legitimate general operand. |
| It is given that X satisfies CONSTANT_P or is a CONST_DOUBLE. */ |
| |
| static bool |
| csky_legitimate_constant_p (machine_mode mode, rtx x) |
| { |
| return (!csky_cannot_force_const_mem (mode, x) |
| && CONSTANT_P (x)); |
| } |
| |
| |
| /* Return true if X is valid as an CSKY addressing register. */ |
| |
| static bool |
| is_csky_address_register_rtx_p (rtx x, int strict_p) |
| { |
| int regno; |
| |
| if (!x) |
| return false; |
| if (!REG_P (x)) |
| return false; |
| |
| regno = REGNO (x); |
| |
| if (strict_p) |
| return (CSKY_GENERAL_REGNO_P (regno) |
| || CSKY_GENERAL_REGNO_P (reg_renumber[regno])); |
| else |
| return CSKY_GENERAL_REGNO_P (regno) || regno >= FIRST_PSEUDO_REGISTER; |
| } |
| |
| |
| /* Return TRUE if X is a thread-local symbol. */ |
| |
| static bool |
| csky_tls_symbol_p (rtx x) |
| { |
| if (!TARGET_TLS) |
| return false; |
| |
| if (GET_CODE (x) != SYMBOL_REF) |
| return false; |
| |
| return SYMBOL_REF_TLS_MODEL (x) != 0; |
| } |
| |
| |
| /* Handle lazy initialization of __tls_get_addr libfunc. */ |
| static GTY(()) rtx tls_get_addr_libfunc; |
| |
| static rtx |
| get_tls_get_addr (void) |
| { |
| if (!tls_get_addr_libfunc) |
| tls_get_addr_libfunc = init_one_libfunc ("__tls_get_addr"); |
| return tls_get_addr_libfunc; |
| } |
| |
| |
| /* Emit a call to __tls_get_addr. */ |
| |
| static rtx_insn * |
| csky_call_tls_get_addr (rtx x, rtx reg, rtx *valuep, int reloc) |
| { |
| rtx label, labelno, unspec, tmp; |
| rtx_insn *insns; |
| |
| start_sequence (); |
| |
| labelno = GEN_INT (tls_labelno++); |
| label = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, labelno), UNSPEC_TLS_LABEL); |
| unspec = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (3, x, GEN_INT (reloc), label), |
| UNSPEC_TLS); |
| tmp = gen_reg_rtx (SImode); |
| emit_move_insn (reg, unspec); |
| emit_move_insn (tmp, label); |
| emit_insn (gen_addsi3 (reg, reg, tmp)); |
| *valuep = emit_library_call_value (get_tls_get_addr (), |
| NULL_RTX, LCT_PURE, /* LCT_CONST? */ |
| Pmode, reg, Pmode); |
| insns = get_insns (); |
| end_sequence (); |
| return insns; |
| } |
| |
| /* Helper function for csky_legitimize_address, to handle the TLS cases. |
| REG is a scratch register and may be null. */ |
| |
| rtx |
| csky_legitimize_tls_address (rtx x, rtx reg) |
| { |
| rtx dest, tp, label, labelno, unspec, ret, eqv, addend, tmp; |
| rtx_insn *insns; |
| unsigned int model = SYMBOL_REF_TLS_MODEL (x); |
| |
| if (!reg) |
| reg = gen_reg_rtx (SImode); |
| |
| switch (model) |
| { |
| case TLS_MODEL_GLOBAL_DYNAMIC: |
| insns = csky_call_tls_get_addr (x, reg, &ret, TLS_GD32); |
| dest = gen_reg_rtx (Pmode); |
| emit_libcall_block (insns, dest, ret, x); |
| return dest; |
| |
| case TLS_MODEL_LOCAL_DYNAMIC: |
| insns = csky_call_tls_get_addr (x, reg, &ret, TLS_LDM32); |
| |
| /* Attach a unique REG_EQUIV, to allow the RTL optimizers to |
| share the LDM result with other LD model accesses. */ |
| eqv = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const1_rtx), UNSPEC_TLS); |
| dest = gen_reg_rtx (Pmode); |
| emit_libcall_block (insns, dest, ret, eqv); |
| |
| /* Load the addend. */ |
| addend = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (2, x, GEN_INT (TLS_LDO32)), |
| UNSPEC_TLS); |
| addend = force_reg (SImode, addend); |
| return gen_rtx_PLUS (Pmode, dest, addend); |
| |
| case TLS_MODEL_INITIAL_EXEC: |
| labelno = GEN_INT (tls_labelno++); |
| label = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, labelno), UNSPEC_TLS_LABEL); |
| unspec = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (3, x, GEN_INT (TLS_IE32), label), |
| UNSPEC_TLS); |
| tmp = gen_reg_rtx (SImode); |
| emit_move_insn (reg, unspec); |
| emit_move_insn (tmp, label); |
| emit_insn (gen_addsi3 (reg, reg, tmp)); |
| emit_move_insn (reg, gen_const_mem (Pmode, reg)); |
| tp = gen_rtx_REG (SImode, CSKY_TLS_REGNUM); |
| return gen_rtx_PLUS (Pmode, tp, reg); |
| |
| case TLS_MODEL_LOCAL_EXEC: |
| unspec = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (2, x, GEN_INT (TLS_LE32)), |
| UNSPEC_TLS); |
| emit_move_insn (reg, unspec); |
| tp = gen_rtx_REG (SImode, CSKY_TLS_REGNUM); |
| return gen_rtx_PLUS (Pmode, tp, reg); |
| |
| default: |
| abort (); |
| } |
| } |
| |
| |
| /* Implement TARGET_LEGITIMIZE_ADDRESS. */ |
| |
| static rtx |
| csky_legitimize_address (rtx x, rtx orig_x ATTRIBUTE_UNUSED, |
| machine_mode mode) |
| { |
| if (csky_tls_symbol_p (x)) |
| return csky_legitimize_tls_address (x, NULL_RTX); |
| |
| if (GET_CODE (x) == PLUS) |
| { |
| rtx xop0 = XEXP (x, 0); |
| rtx xop1 = XEXP (x, 1); |
| |
| if (is_csky_address_register_rtx_p (xop0, 0) |
| && CONST_INT_P (xop1)) |
| { |
| HOST_WIDE_INT offset = INTVAL (xop1); |
| |
| /* Try to replace ld32 rx,(ry, offset), to addi16 rz, oimm8 |
| and ld16 rx,(rz, new_ld_offset) to avoid emitting a |
| 32-bit ld, but this addi has a range limitation. */ |
| if (optimize_size |
| && offset > CSKY_LD16_MAX_OFFSET (mode) |
| && offset <= (CSKY_ADDI16_MAX_IMM |
| + CSKY_LD16_MAX_OFFSET (mode))) |
| { |
| HOST_WIDE_INT new_ld_offset |
| = offset & CSKY_LD16_OFFSET_MASK (mode); |
| |
| xop0 = force_operand (plus_constant (Pmode, xop0, |
| offset - new_ld_offset), |
| NULL_RTX); |
| x = plus_constant (Pmode, xop0, new_ld_offset); |
| } |
| else if (offset < 0 && offset >= (-CSKY_SUBI16_MAX_IMM)) |
| x = force_operand (x, NULL_RTX); |
| else if (offset > CSKY_LD16_MAX_OFFSET (mode) |
| || offset < 0) |
| { |
| /* For the remaining cases, force the constant into a |
| register. */ |
| xop1 = force_reg (SImode, xop1); |
| x = gen_rtx_PLUS (SImode, xop0, xop1); |
| } |
| } |
| |
| /* If the index is store in register, force the |
| base to register. */ |
| if (is_csky_address_register_rtx_p (xop1, 0) |
| && !is_csky_address_register_rtx_p (xop0, 0)) |
| { |
| xop0 = force_operand (xop0, NULL_RTX); |
| x = gen_rtx_PLUS (SImode, xop0, xop1); |
| } |
| } |
| /* Make sure to take full advantage of the pre-indexed addressing mode |
| with absolute addresses which often allows for the base register to |
| be factorized for multiple adjacent memory references, and it might |
| even allows for the mini pool to be avoided entirely. */ |
| else if (CONST_INT_P (x) && optimize > 0) |
| { |
| HOST_WIDE_INT mask, base, index; |
| rtx base_reg; |
| |
| mask = CSKY_LD16_OFFSET_MASK (mode); |
| base = INTVAL (x) & ~mask; |
| index = INTVAL (x) & mask; |
| base_reg = force_reg (SImode, GEN_INT (base)); |
| x = plus_constant (Pmode, base_reg, index); |
| } |
| |
| return x; |
| } |
| |
| |
| /* Return nonzero if INDEX is valid for an address index operand. |
| ck801 use 16 bits ld |
| ck802 use 16 and 32 bits ld |
| others use ld and ldr. */ |
| |
| static int |
| ck801_legitimate_index_p (machine_mode mode, rtx index, |
| int strict_p ATTRIBUTE_UNUSED) |
| { |
| enum rtx_code code = GET_CODE (index); |
| |
| /* When the mode size is larger than 4, we may use two ld instruction |
| to get data, the index and (index+1) should be valid. */ |
| if (GET_MODE_SIZE (mode) >= 8) |
| return (code == CONST_INT |
| && INTVAL (index) < CSKY_LD16_MAX_OFFSET (SImode) |
| && INTVAL (index) >= 0 && (INTVAL (index) & 3) == 0); |
| |
| if (code == CONST_INT && GET_MODE_SIZE (mode) > 0 |
| && INTVAL (index) <= CSKY_LD16_MAX_OFFSET (mode) |
| && INTVAL (index) >= 0) |
| return ((INTVAL (index) % GET_MODE_SIZE (mode)) == 0); |
| |
| return 0; |
| } |
| |
| |
| static int |
| ck802_legitimate_index_p (machine_mode mode, rtx index, |
| int strict_p ATTRIBUTE_UNUSED) |
| { |
| enum rtx_code code = GET_CODE (index); |
| |
| /* When the mode size is larger than 4, we may use two ld instruction |
| to get data, the index and (index+1) should be valid. */ |
| if (GET_MODE_SIZE (mode) >= 8) |
| return (code == CONST_INT |
| && INTVAL (index) < CSKY_LD32_MAX_OFFSET (SImode) |
| && INTVAL (index) >= 0 && (INTVAL (index) & 3) == 0); |
| |
| if (code == CONST_INT && GET_MODE_SIZE (mode) > 0 |
| && INTVAL (index) <= CSKY_LD32_MAX_OFFSET (mode) |
| && INTVAL (index) >= 0) |
| return ((INTVAL (index) % GET_MODE_SIZE (mode)) == 0); |
| |
| return 0; |
| } |
| |
| |
| /* The instruction ldr rz, (rx, ry << i), i can be 0,1,2,3. |
| Check that SHIFT is valid, that the code is MULT, and that |
| the shift is a power of 2. */ |
| |
| static bool |
| is_ldr_shift_p (HOST_WIDE_INT shift, enum rtx_code code) |
| { |
| if (code == ASHIFT) |
| return (shift >= 0 && shift <= 3); |
| else if (code == MULT) |
| return (shift == 1 |
| || shift == 2 |
| || shift == 4 |
| || shift == 8); |
| else |
| return false; |
| } |
| |
| |
| static int |
| ck810_legitimate_index_p (machine_mode mode, rtx index, int strict_p) |
| { |
| enum rtx_code code = GET_CODE (index); |
| |
| if (code == CONST_INT && TARGET_HARD_FLOAT && CSKY_VREG_MODE_P (mode)) |
| return (INTVAL (index) < 1024 && INTVAL (index) >= 0 |
| && (INTVAL (index) & 3) == 0); |
| |
| if (code == CONST_INT) |
| { |
| /* When the mode size is larger than 4, we may use two ld instruction |
| to get data, the index and (index+1) should be valid. */ |
| if (GET_MODE_SIZE (mode) >= 8) |
| return (INTVAL (index) < CSKY_LD32_MAX_OFFSET (SImode) |
| && INTVAL (index) >= 0 && (INTVAL (index) & 3) == 0); |
| |
| if (GET_MODE_SIZE (mode) > 0 |
| && INTVAL (index) <= CSKY_LD32_MAX_OFFSET (mode) |
| && INTVAL (index) >= 0) |
| return ((INTVAL (index) % GET_MODE_SIZE (mode)) == 0); |
| } |
| /* Allow ld.w rx, (gb, sym@got) when -fpic specially. */ |
| else if (code == UNSPEC) |
| return (flag_pic == 1 |
| && (XINT (index, 1) == UNSPEC_PIC_SYMBOL_PLT |
| || XINT (index, 1) == UNSPEC_PIC_SYMBOL_GOT)); |
| /* The follow index is for ldr instruction, the ldr cannot |
| load dword data, so the mode size should not be larger than |
| 4. */ |
| else if (GET_MODE_SIZE (mode) <= 4 |
| || (TARGET_HARD_FLOAT && CSKY_VREG_MODE_P (mode))) |
| { |
| if (is_csky_address_register_rtx_p (index, strict_p)) |
| return 1; |
| else if (code == MULT || code == ASHIFT) |
| { |
| rtx xiop0 = XEXP (index, 0); |
| rtx xiop1 = XEXP (index, 1); |
| |
| /* FIXME can the xiop1 be the reg and xiop0 be the int when mult? */ |
| return (is_csky_address_register_rtx_p (xiop0, strict_p) |
| && CONST_INT_P (xiop1) |
| && is_ldr_shift_p (INTVAL (xiop1), code)); |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| static int |
| csky_legitimate_index_p (machine_mode mode, rtx index, int strict_p) |
| { |
| if (CSKY_TARGET_ARCH (CK801)) |
| return ck801_legitimate_index_p (mode, index, strict_p); |
| else if (CSKY_TARGET_ARCH (CK802)) |
| return ck802_legitimate_index_p (mode, index, strict_p); |
| else |
| return ck810_legitimate_index_p (mode, index, strict_p); |
| } |
| |
| |
| /* Implement TARGET_LEGITIMATE_ADDRESS_P. |
| Recognizes RTL expressions that are valid memory addresses for an |
| instruction. The MODE argument is the machine mode for the MEM |
| expression that wants to use this address. |
| |
| It only recognizes address in canonical form. LEGITIMIZE_ADDRESS should |
| convert common non-canonical forms to canonical form so that they will |
| be recognized. */ |
| |
| static bool |
| csky_legitimate_address_p (machine_mode mode, rtx addr, bool strict_p) |
| { |
| enum rtx_code code = GET_CODE (addr); |
| |
| /* Match the RTX form emitted for constant pool references. |
| After reload constants split into minipools will have addresses |
| from a LABEL_REF. */ |
| if (reload_completed |
| && ((code == LABEL_REF) |
| || (code == CONST |
| && GET_CODE (XEXP (addr, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (addr, 0), 0)) == LABEL_REF |
| && CONST_INT_P (XEXP (XEXP (addr, 0), 1))))) |
| return 1; |
| |
| if (is_csky_address_register_rtx_p (addr, strict_p)) |
| return 1; |
| /* It is a pc-relative load, may be generated for constpool. */ |
| else if (GET_CODE (addr) == LABEL_REF) |
| return 1; |
| |
| if (code == PLUS) |
| { |
| rtx xop0 = XEXP (addr, 0); |
| rtx xop1 = XEXP (addr, 1); |
| |
| return ((is_csky_address_register_rtx_p (xop0, strict_p) |
| && csky_legitimate_index_p (mode, xop1, strict_p)) |
| || (is_csky_address_register_rtx_p (xop1, strict_p) |
| && csky_legitimate_index_p (mode, xop0, strict_p))); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* Functions to save and restore machine-specific function data. */ |
| |
| static struct machine_function * |
| csky_init_machine_status (void) |
| { |
| struct machine_function *machine; |
| |
| machine = ggc_cleared_alloc<machine_function> (); |
| |
| #if CSKY_FT_UNKNOWN != 0 |
| machine->func_type = CSKY_FT_UNKNOWN; |
| #endif |
| return machine; |
| } |
| |
| |
| /* Implement INIT_EXPANDERS. */ |
| |
| void |
| csky_init_expanders (void) |
| { |
| /* Arrange to initialize and mark the machine per-function status. */ |
| init_machine_status = csky_init_machine_status; |
| } |
| |
| |
| /* Implement TARGET_CANNOT_COPY_INSN_P. |
| We must not copy any rtx that uses a pc-relative address. */ |
| |
| static bool |
| csky_cannot_copy_insn_p (rtx_insn *insn) |
| { |
| subrtx_iterator::array_type array; |
| FOR_EACH_SUBRTX (iter, array, PATTERN (insn), ALL) |
| { |
| const_rtx x = *iter; |
| if (GET_CODE (x) == UNSPEC |
| && (XINT (x, 1) == UNSPEC_TLS_LABEL |
| || XINT (x, |