| /* 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, 1) == UNSPEC_PIC_SYMBOL_GOTPC_GRS)) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Extract the parts of an RTL expression that is a valid memory address |
| for an instruction. Return FALSE if it is a invalid memory address. */ |
| |
| struct csky_address |
| { |
| rtx base, index, symbol, label, disp; |
| HOST_WIDE_INT scale; |
| }; |
| |
| static bool |
| decompose_csky_address (rtx addr, struct csky_address *out) |
| { |
| rtx base = NULL_RTX, index = NULL_RTX, disp = NULL_RTX; |
| HOST_WIDE_INT scale = 0; |
| rtx scale_rtx = NULL_RTX; |
| int i; |
| |
| out->base = out->index = out->symbol = out->label = out->disp = NULL_RTX; |
| out->scale = 0; |
| |
| if (REG_P (addr)) |
| { |
| out->base = addr; |
| return true; |
| } |
| |
| if (GET_CODE (addr) == LABEL_REF) |
| { |
| out->label = addr; |
| return true; |
| } |
| |
| if (GET_CODE (addr) == CONST) |
| addr = XEXP (addr, 0); |
| |
| if (GET_CODE (addr) == PLUS) |
| { |
| rtx addends[2], op; |
| |
| addends[0] = XEXP (addr, 0); |
| addends[1] = XEXP (addr, 1); |
| |
| if (GET_CODE (addends[0]) == LABEL_REF && CONST_INT_P (addends[1])) |
| { |
| out->label = addends[0]; |
| out->disp = addends[1]; |
| return true; |
| } |
| |
| if (!REG_P (addends[0])) |
| std::swap (addends[0], addends[1]); |
| |
| for (i = 0; i < 2; ++i) |
| { |
| op = addends[i]; |
| switch (GET_CODE (op)) |
| { |
| case REG: |
| if (!base) |
| base = op; |
| else if (!index) |
| { |
| index = op; |
| scale = 1; |
| } |
| else |
| return false; |
| break; |
| case CONST_INT: |
| case UNSPEC: |
| if (disp) |
| return false; |
| disp = op; |
| break; |
| case MULT: |
| if (index) |
| return false; |
| index = XEXP (op, 0); |
| scale_rtx = XEXP (op, 1); |
| if (!CONST_INT_P (index) && !CONST_INT_P (scale_rtx)) |
| return false; |
| else if (CONST_INT_P (index)) |
| std::swap (index, scale_rtx); |
| scale = INTVAL (scale_rtx); |
| break; |
| case ASHIFT: |
| if (index) |
| return false; |
| index = XEXP (op, 0); |
| scale_rtx = XEXP (op, 1); |
| if (!CONST_INT_P (scale_rtx)) |
| return false; |
| scale = 1 << INTVAL (scale_rtx); |
| break; |
| default: |
| return false; |
| } |
| } |
| } |
| |
| if (!base) |
| return false; |
| |
| out->base = base; |
| out->index = index; |
| out->disp = disp; |
| out->scale = scale; |
| |
| return true; |
| } |
| |
| /* Helper function for the csky_simple_mem_operand predicate. Returns |
| true if OP is an address of the form reg + displacement. */ |
| |
| bool |
| csky_simple_addr_operand_p (rtx op) |
| { |
| struct csky_address addr; |
| |
| if (!decompose_csky_address (op, &addr)) |
| return false; |
| |
| /* FIXME The PIC related code. |
| Check if load the symbol address from got table. */ |
| if (addr.disp && GET_CODE (addr.disp) == UNSPEC) |
| return false; |
| if (!addr.index && !addr.symbol) |
| return true; |
| return false; |
| } |
| |
| |
| /* Print the UNSPEC operand in X to the STREAM. */ |
| |
| static void |
| csky_output_pic_addr_const (FILE *stream, rtx x, int code) |
| { |
| |
| if (GET_CODE (x) != UNSPEC) |
| return; |
| |
| if (UNSPEC_TLS == XINT (x, 1)) |
| { |
| /* FIXME It is not reached */ |
| return; |
| } |
| |
| csky_print_operand (stream, XVECEXP (x, 0, 0), code); |
| |
| switch (XINT (x, 1)) |
| { |
| case UNSPEC_PIC_SYMBOL_GOTOFF: |
| fputs ("@GOTOFF", stream); |
| break; |
| case UNSPEC_PIC_SYMBOL_PLT: |
| fputs ("@PLT", stream); |
| break; |
| case UNSPEC_PIC_SYMBOL_GOT: |
| fputs ("@GOT", stream); |
| break; |
| case UNSPEC_PIC_SYMBOL_GOTPC: |
| fputs ("@GOTPC", stream); |
| break; |
| case UNSPEC_PIC_SYMBOL_BSR: |
| break; |
| default: |
| break; |
| } |
| } |
| |
| |
| /* Output the constpool label according to the rtx expression X. */ |
| |
| static void |
| csky_output_constpool_label (FILE *stream, rtx x) |
| { |
| char buf[15]; |
| |
| gcc_assert (GET_CODE (x) == LABEL_REF); |
| x = XEXP (x, 0); |
| |
| if (GET_CODE (x) == UNSPEC_VOLATILE && XINT (x, 1) == VUNSPEC_POOL_LABEL) |
| { |
| ASM_GENERATE_INTERNAL_LABEL (buf, CSKY_CONSTPOOL_LABEL_PREFIX, |
| INTVAL (XVECEXP (x, 0, 0))); |
| assemble_name (stream, buf); |
| } |
| } |
| |
| |
| /* Implement TARGET_PRINT_OPERAND_ADDRESS. */ |
| |
| static void |
| csky_print_operand_address (FILE *stream, |
| machine_mode mode ATTRIBUTE_UNUSED, |
| rtx x) |
| { |
| |
| struct csky_address addr; |
| |
| decompose_csky_address (x, &addr); |
| |
| if (addr.label && addr.disp && GET_CODE (addr.disp) == CONST_INT) |
| { |
| fprintf (stream, "["); |
| csky_output_constpool_label (stream, addr.label); |
| fprintf (stream, "+%d]", (int) INTVAL (addr.disp)); |
| } |
| else if (addr.label) |
| { |
| fprintf (stream, "["); |
| csky_output_constpool_label (stream, addr.label); |
| fprintf (stream, "]"); |
| } |
| else if (addr.symbol && addr.disp && GET_CODE (addr.disp) == CONST_INT) |
| { |
| fprintf (stream, "["); |
| output_addr_const (stream, addr.symbol); |
| fprintf (stream, "+%d]", (int) INTVAL (addr.disp)); |
| } |
| else if (addr.symbol) |
| { |
| fprintf (stream, "["); |
| output_addr_const (stream, addr.symbol); |
| fprintf (stream, "]"); |
| } |
| else if (addr.disp && GET_CODE (addr.disp) == CONST_INT) |
| fprintf (stream, "(%s, %d)", |
| reg_names[REGNO (addr.base)], (int) INTVAL (addr.disp)); |
| else if (addr.disp && GET_CODE (addr.disp) == UNSPEC) |
| { |
| if (REGNO (addr.base) != CSKY_GB_REGNUM) |
| fprintf (stream, "(%s, ", reg_names[REGNO (addr.base)]); |
| else |
| fprintf (stream, "["); |
| csky_output_pic_addr_const (stream, addr.disp, 0); |
| fprintf (stream, "%s", (REGNO (addr.base) != CSKY_GB_REGNUM) |
| ? ")" : "]"); |
| } |
| else if (addr.index) |
| fprintf (stream, "(%s, %s << %d)", |
| reg_names[REGNO (addr.base)], reg_names[REGNO (addr.index)], |
| exact_log2 ((int) (addr.scale))); |
| else |
| fprintf (stream, "(%s, 0)", reg_names[REGNO (addr.base)]); |
| } |
| |
| |
| /* Implement TARGET_PRINT_OPERAND. |
| Print operand X (an rtx) in assembler syntax to file STREAM |
| according to modifier CODE. |
| |
| 'N' print the log2(X+1), mainly used for bmaski |
| 'P' print the log2(X) |
| 'Q' print the log2(~X) |
| 'O' print a decimal number |
| 'M' print a decimal number as its negative |
| 'R' print the next register or memory location along, i.e. the lsw in |
| a double word value |
| 'H' print the high 16 bits of a constant. */ |
| |
| static void |
| csky_print_operand (FILE *stream, rtx x, int code) |
| { |
| switch (code) |
| { |
| case 'N': |
| if ((INTVAL (x) & 0xffffffff) == 0xffffffff) |
| fprintf (stream, "0"); |
| else |
| fprintf (stream, "%d", |
| (int) exact_log2 ((INTVAL (x) & 0xffffffff) + 1) % 32); |
| break; |
| case 'P': |
| fprintf (stream, "%d", |
| (int) exact_log2 (INTVAL (x) & 0xffffffff)); |
| break; |
| case 'Q': |
| fprintf (stream, "%d", |
| (int) exact_log2 (~INTVAL (x) & 0xffffffff)); |
| break; |
| case 'O': |
| fprintf (stream, "%d", (int) INTVAL (x)); |
| break; |
| case 'M': |
| fprintf (stream, "%d", (int) (-INTVAL (x))); |
| break; |
| case 'R': |
| /* Next location along in memory or register. */ |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| fputs (reg_names[REGNO (x) + 1], stream); |
| break; |
| case MEM: |
| csky_print_operand_address |
| (stream, GET_MODE (x), XEXP (adjust_address (x, SImode, 4), 0)); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case 'H': |
| fprintf (stream, "%ld", (long)((INTVAL (x) & 0xFFFF0000) >> 16)); |
| break; |
| default: |
| switch (GET_CODE (x)) |
| { |
| case REG: |
| fputs (reg_names[REGNO (x)], stream); |
| break; |
| case MEM: |
| output_address (GET_MODE (x), XEXP (x, 0)); |
| break; |
| case UNSPEC: |
| csky_output_pic_addr_const (stream, x, code); |
| break; |
| case CONST_DOUBLE: |
| { |
| char fpstr[20]; |
| real_to_decimal ( fpstr, CONST_DOUBLE_REAL_VALUE (x), |
| sizeof (fpstr), 0, 1); |
| fprintf (stream, "%s", fpstr); |
| } |
| break; |
| default: |
| output_addr_const (stream, x); |
| break; |
| } |
| break; |
| } |
| } |
| |
| |
| |
| /* Implement TARGET_ALLOCATE_STACK_SLOTS_FOR_ARGS. */ |
| |
| static bool |
| csky_allocate_stack_slots_for_args (void) |
| { |
| /* Naked functions should not allocate stack slots for arguments. */ |
| return !CSKY_FUNCTION_IS_NAKED (get_csky_current_func_type ()); |
| } |
| |
| |
| /* Can we generate a constant with a single instruction, without using |
| lrw? */ |
| |
| static int |
| const_ok_for_cskyv2 (HOST_WIDE_INT value) |
| { |
| /* Try exact power of two. It can be generated by bgeni. */ |
| if (CSKY_CONST_OK_FOR_Ub (value)) |
| return 1; |
| |
| /* Try exact power of two - 1. It can be generated by bmaski. */ |
| if (CSKY_CONST_OK_FOR_Uc (value) && value != -1) |
| return 1; |
| |
| /* Try if it can be generated by movi. */ |
| if (CSKY_CONST_OK_FOR_I (value)) |
| return 1; |
| |
| /* The constant can be generated by movih. |
| Notice that movih is a 32-bit instruction. */ |
| if (CSKY_CONST_OK_FOR_MOVIH (value)) |
| return 1; |
| |
| return 0; |
| } |
| |
| |
| /* Tricks for synthesizing constants from values that can be directly |
| manipulated by machine instructions. */ |
| |
| enum csky_inline_const_type |
| { |
| IC_UNINLINABLE = 0, /* Not inlineable */ |
| IC_SINGLE, /* Single instruction */ |
| IC_APPEND_NOT, /* Single instruction followed by a not */ |
| IC_APPEND_ADDI, /* Single insn followed by an addi */ |
| IC_APPEND_SUBI, /* Single insn followed by a subi */ |
| IC_BGENI_ADDI, /* Single insn(bgeni) followed by an addi */ |
| IC_BGENI_SUBI, /* Single insn(bgeni) followed by a subi */ |
| IC_APPEND_BSETI, /* Single insn followed by bseti */ |
| IC_APPEND_MOVI, /* Single insn followed by movi */ |
| IC_APPEND_BCLRI, /* Single insn followed by bclri */ |
| IC_APPEND_ROTLI, /* Single insn followed by rotli */ |
| IC_APPEND_LSLI, /* Single insn followed by lsli */ |
| IC_APPEND_IXH, /* Single insn followed by ixh */ |
| IC_APPEND_IXW /* Single insn followed by ixw */ |
| }; |
| |
| |
| /* Try tricks to load a constant inline and return the trick number if |
| success, or IC_UNINLINABLE. */ |
| |
| static enum csky_inline_const_type |
| try_csky_constant_tricks (HOST_WIDE_INT value, HOST_WIDE_INT *x, |
| HOST_WIDE_INT *y) |
| { |
| HOST_WIDE_INT i, value_invert; |
| unsigned HOST_WIDE_INT bit, shf, rot, lobits, hibits; |
| |
| value &= 0xffffffff; |
| value_invert = ~value & 0xffffffff; |
| |
| if (const_ok_for_cskyv2 (value)) |
| { |
| *x = value; |
| return IC_SINGLE; |
| } |
| |
| /* Since movih is 32 bits, do not use it here, better code may |
| be generated later. */ |
| if (const_ok_for_cskyv2 (value_invert) |
| && !CSKY_CONST_OK_FOR_MOVIH (value_invert)) |
| { |
| *x = value_invert; |
| return IC_APPEND_NOT; |
| } |
| |
| /* One immediate generate instruction, and one 16-bit subi or addi. */ |
| for (i = 1; i <= 32; i++) |
| { |
| if (const_ok_for_cskyv2 (value - i) |
| && !CSKY_CONST_OK_FOR_MOVIH (value - i)) |
| { |
| *x = value - i; |
| *y = i; |
| return IC_APPEND_ADDI; |
| } |
| |
| if (const_ok_for_cskyv2 (value + i) |
| && !CSKY_CONST_OK_FOR_MOVIH (value - i)) |
| { |
| *x = value + i; |
| *y = i; |
| return IC_APPEND_SUBI; |
| } |
| } |
| |
| /* Generate bgeni + addi. */ |
| if (CSKY_CONST_OK_FOR_Ub (value & 0xfffff000)) |
| { |
| *x = (value & 0xfffff000); |
| *y = (value & 0xfff); |
| return IC_BGENI_ADDI; |
| } |
| |
| /* Generate bgeni + subi. */ |
| lobits = value & 0xfff; |
| hibits = (unsigned HOST_WIDE_INT)(value & 0xfffff000) + (1 << 12); |
| if (exact_log2 (hibits) >= 1 |
| && exact_log2 (hibits) <= 30 |
| && lobits != 0) |
| { |
| *x = hibits; |
| *y = (0x1000 - lobits); |
| return IC_BGENI_SUBI; |
| } |
| |
| /* One immediate generate instruction, and one bseti or bclri. */ |
| bit = 0x80000000ULL; |
| for (i = 0; i <= 31; i++) |
| { |
| if (const_ok_for_cskyv2 (value & ~bit) |
| && !CSKY_CONST_OK_FOR_MOVIH (value & ~bit)) |
| { |
| *y = bit; |
| *x = (value & ~bit); |
| return IC_APPEND_BSETI; |
| } |
| |
| if (const_ok_for_cskyv2 (value | bit) |
| && !CSKY_CONST_OK_FOR_MOVIH (value | bit)) |
| { |
| *y = ~bit & 0xffffffff; |
| *x = value | bit; |
| return IC_APPEND_BCLRI; |
| } |
| |
| bit >>= 1; |
| } |
| |
| /* One immediate generate instruction, and one rotli or lsli. */ |
| shf = value; |
| rot = value; |
| for (i = 1; i < 31; i++) |
| { |
| int c; |
| |
| /* Rotate left. */ |
| c = rot << 31; |
| rot >>= 1; |
| rot &= 0x7FFFFFFF; |
| rot |= c; |
| |
| if (const_ok_for_cskyv2 (rot) && !CSKY_CONST_OK_FOR_MOVIH (rot)) |
| { |
| *y = i; |
| *x = rot; |
| return IC_APPEND_ROTLI; |
| } |
| |
| /* Can't use logical shift when low order bit is one. */ |
| if (shf & 1) |
| shf = 0; |
| else |
| shf >>= 1; |
| |
| if (shf != 0 && const_ok_for_cskyv2 (shf) |
| && !CSKY_CONST_OK_FOR_MOVIH (shf)) |
| { |
| *y = i; |
| *x = shf; |
| return IC_APPEND_LSLI; |
| } |
| } |
| |
| /* One immediate generate instruction, and one ixh. */ |
| if (CSKY_ISA_FEATURE (E2) |
| && (value % 3) == 0 |
| && const_ok_for_cskyv2 (value / 3) |
| && !CSKY_CONST_OK_FOR_MOVIH (value / 3)) |
| { |
| *x = value / 3; |
| return IC_APPEND_IXH; |
| } |
| |
| /* One immediate generate instruction, and one ixw. */ |
| if (CSKY_ISA_FEATURE (E2) |
| && (value % 5) == 0 |
| && const_ok_for_cskyv2 (value / 5) |
| && !CSKY_CONST_OK_FOR_MOVIH (value / 5)) |
| { |
| *x = value / 5; |
| return IC_APPEND_IXW; |
| } |
| |
| /* Generate movih + bseti. */ |
| if (CSKY_CONST_OK_FOR_Ub (value & 0xffff)) |
| { |
| *x = value & 0xffff0000; |
| *y = value & 0xffff; |
| return IC_APPEND_BSETI; |
| } |
| |
| /* Generate movih + not. */ |
| if (CSKY_CONST_OK_FOR_MOVIH (value_invert)) |
| { |
| *x = value_invert; |
| return IC_APPEND_NOT; |
| } |
| |
| /* One movih, and one 16bits addi or subi. */ |
| for (i = 1; i <= 32; i++) |
| { |
| if (CSKY_CONST_OK_FOR_MOVIH (value - i)) |
| { |
| *x = value - i; |
| *y = i; |
| return IC_APPEND_ADDI; |
| } |
| |
| if (CSKY_CONST_OK_FOR_MOVIH (value + i)) |
| { |
| *x = value + i; |
| *y = i; |
| return IC_APPEND_SUBI; |
| } |
| } |
| |
| /* One movih, and one bseti or bclri. */ |
| bit = 0x80000000ULL; |
| for (i = 0; i <= 31; i++) |
| { |
| if (CSKY_CONST_OK_FOR_MOVIH (value & ~bit)) |
| { |
| *y = bit; |
| *x = value & ~bit; |
| return IC_APPEND_BSETI; |
| } |
| |
| if (CSKY_CONST_OK_FOR_MOVIH (value | bit)) |
| { |
| *y = ~bit & 0xffffffff; |
| *x = value | bit; |
| return IC_APPEND_BCLRI; |
| } |
| |
| bit >>= 1; |
| } |
| |
| /* One movih, and one rotli or lsli. */ |
| shf = value; |
| rot = value; |
| for (i = 1; i < 31; i++) |
| { |
| int c; |
| |
| /* Rotate left. */ |
| c = rot << 31; |
| rot >>= 1; |
| rot &= 0x7FFFFFFF; |
| rot |= c; |
| |
| if (CSKY_CONST_OK_FOR_MOVIH (rot)) |
| { |
| *y = i; |
| *x = rot; |
| return IC_APPEND_ROTLI; |
| } |
| |
| /* Can't use logical shift when low order bit is one. */ |
| if (shf & 1) |
| shf = 0; |
| else |
| shf >>= 1; |
| |
| if (shf != 0 && CSKY_CONST_OK_FOR_MOVIH (shf)) |
| { |
| *y = i; |
| *x = shf; |
| return IC_APPEND_LSLI; |
| } |
| } |
| |
| return IC_UNINLINABLE; |
| } |
| |
| |
| /* Actually output a constant using a trick. |
| FIXME: I think this would be better handled by a splitter than at the |
| asm output level. */ |
| |
| static const char * |
| csky_output_inline_const (machine_mode mode, rtx operands[]) |
| { |
| HOST_WIDE_INT x = 0, y = 0; |
| enum csky_inline_const_type trick_type; |
| rtx out_operands[3]; |
| char buf[256]; |
| char load_op[128]; |
| const char *dst_fmt; |
| HOST_WIDE_INT value = INTVAL (operands[1]); |
| int ivalue = (int) value; |
| unsigned int uvalue = (unsigned int) value; |
| |
| trick_type = try_csky_constant_tricks (value, &x, &y); |
| /* lrw's are handled separately: Large inlinable constants never get |
| turned into lrw's. Our caller uses try_csky_constant_tricks to back |
| off to an lrw rather than calling this routine. */ |
| gcc_assert (trick_type != IC_UNINLINABLE); |
| |
| /* Operands: 0 = dst, 1 = load immedate., 2 = adjust immedate. */ |
| out_operands[0] = operands[0]; |
| out_operands[1] = GEN_INT (x); |
| if (trick_type != IC_SINGLE && trick_type != IC_APPEND_NOT) |
| out_operands[2] = GEN_INT (y); |
| |
| /* Select dst format based on mode. */ |
| if (mode == DImode && TARGET_BIG_ENDIAN) |
| dst_fmt = "%R0"; |
| else |
| dst_fmt = "%0"; |
| |
| /* Try movi16: 0~31,movi32: 0~65535. */ |
| if (CSKY_CONST_OK_FOR_I (x)) |
| sprintf (load_op, "movi\t%s, %%1", dst_fmt); |
| /* Try exact power of two - 1. */ |
| else if (CSKY_CONST_OK_FOR_Uc (x)) |
| sprintf (load_op, "bmaski\t%s, %%N1", dst_fmt); |
| /* Try movih. */ |
| else if (CSKY_CONST_OK_FOR_MOVIH (x)) |
| sprintf (load_op, "movih\t%s, %%H1", dst_fmt); |
| else |
| { |
| sprintf (load_op, "BADMOVI-inline_const %s, %%1", dst_fmt); |
| gcc_unreachable (); |
| } |
| |
| switch (trick_type) |
| { |
| case IC_SINGLE: |
| strcpy (buf, load_op); |
| break; |
| /* Add instruction 'not'. */ |
| case IC_APPEND_NOT: |
| sprintf (buf, "%s\n\tnot\t%s, %s\t// %d 0x%x", load_op, dst_fmt, |
| dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'addi'. */ |
| case IC_APPEND_ADDI: |
| sprintf (buf, "%s\n\taddi\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'subi'. */ |
| case IC_APPEND_SUBI: |
| sprintf (buf, "%s\n\tsubi\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'addi', the last instruction is bgeni. */ |
| case IC_BGENI_ADDI: |
| sprintf (buf, "%s\n\taddi\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'subi', the last instruction is bgeni. */ |
| case IC_BGENI_SUBI: |
| sprintf (buf, "%s\n\tsubi\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'bseti'. */ |
| case IC_APPEND_BSETI: |
| sprintf (buf, "%s\n\tbseti\t%s, %s, %%P2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'movi'. */ |
| case IC_APPEND_MOVI: |
| sprintf (buf, "%s\n\tmovi\t%s, %%2\t// %d 0x%x", load_op, dst_fmt, |
| ivalue, uvalue); |
| break; |
| /* Add instruction 'bclri'. */ |
| case IC_APPEND_BCLRI: |
| sprintf (buf, "%s\n\tbclri\t%s, %s, %%Q2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'rotli'. */ |
| case IC_APPEND_ROTLI: |
| sprintf (buf, "%s\n\trotli\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'lsli'. */ |
| case IC_APPEND_LSLI: |
| sprintf (buf, "%s\n\tlsli\t%s, %s, %%2\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'ixh'. */ |
| case IC_APPEND_IXH: |
| sprintf (buf, "%s\n\tixh\t%s, %s, %s\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| /* Add instruction 'ixw'. */ |
| case IC_APPEND_IXW: |
| sprintf (buf, "%s\n\tixw\t%s, %s, %s\t// %d 0x%x", load_op, |
| dst_fmt, dst_fmt, dst_fmt, ivalue, uvalue); |
| break; |
| default: |
| return ""; |
| } |
| |
| output_asm_insn (buf, out_operands); |
| |
| return ""; |
| } |
| |
| /* This is a helper function for the Uo constraint for movsi patterns. */ |
| |
| bool |
| csky_inlinable_constant (HOST_WIDE_INT value) |
| { |
| HOST_WIDE_INT x, y; |
| return (!(CSKY_TARGET_ARCH (CK802) || CSKY_TARGET_ARCH (CK801)) |
| && try_csky_constant_tricks (value, &x, &y)); |
| } |
| |
| |
| /* Return true if the constant VAL can be expressed by an 8-bit constant |
| with a shift value, filling in *BASE and *SHIFT. */ |
| |
| bool |
| csky_shifted_imm8_constant (unsigned HOST_WIDE_INT val, |
| unsigned int *base, unsigned int *shift) |
| { |
| unsigned HOST_WIDE_INT mask = 0xff; |
| int i; |
| val = val & (unsigned HOST_WIDE_INT) 0xffffffffu; |
| if (val == 0) |
| return 0; |
| |
| for (i = 0; i < 25; i++) |
| if ((val & (mask << i)) == val) |
| { |
| if (base) |
| *base = (unsigned int) (val >> i); |
| if (shift) |
| *shift = (unsigned int) i; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /* Output a move of a word or less value. */ |
| |
| const char * |
| csky_output_move (rtx insn ATTRIBUTE_UNUSED, rtx operands[], |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| struct csky_address op0, op1; |
| |
| if (REG_P (dst)) |
| { |
| /* The situation mov reg to reg. */ |
| if (REG_P (src)) |
| { |
| int dstreg = REGNO (dst); |
| int srcreg = REGNO (src); |
| |
| /* hilo registers exchange their places, |
| and their order of Dimode as same as other |
| general registers in LITTLE_ENDIAN mode. */ |
| if (TARGET_BIG_ENDIAN) |
| { |
| if (dstreg == CSKY_HI_REGNUM) |
| return "mthi\t%1"; |
| else if (dstreg == CSKY_LO_REGNUM) |
| return "mtlo\t%1"; |
| else if (srcreg == CSKY_HI_REGNUM) |
| return "mfhi\t%0"; |
| else if (srcreg == CSKY_LO_REGNUM) |
| return "mflo\t%0"; |
| } |
| else |
| { |
| if (dstreg == CSKY_HI_REGNUM) |
| return "mtlo\t%1"; |
| else if (dstreg == CSKY_LO_REGNUM) |
| return "mthi\t%1"; |
| else if (srcreg == CSKY_HI_REGNUM) |
| return "mflo\t%0"; |
| else if (srcreg == CSKY_LO_REGNUM) |
| return "mfhi\t%0"; |
| } |
| |
| if (CSKY_VREG_P (dstreg) && CSKY_VREG_P (srcreg)) |
| { |
| if (CSKY_ISA_FEATURE (fpv2_sf)) |
| return "fmovs\t%0, %1"; |
| else if (CSKY_ISA_FEATURE (fpv3_sf)) |
| return "fmov.32\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| if (CSKY_VREG_P (dstreg)) |
| { |
| if (CSKY_ISA_FEATURE (fpv2_sf)) |
| return "fmtvrl\t%0, %1"; |
| else if (CSKY_ISA_FEATURE (fpv3_sf)) |
| return "fmtvr.32.1\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| if (CSKY_VREG_P (srcreg)) |
| { |
| if (CSKY_ISA_FEATURE (fpv2_sf)) |
| return "fmfvrl\t%0, %1"; |
| else if (CSKY_ISA_FEATURE (fpv3_sf)) |
| return "fmfvr.32.1\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| if (REGNO (src) == CSKY_CC_REGNUM) |
| return "mvc\t%0"; |
| else |
| return "mov\t%0, %1"; |
| } |
| /* The situation mov memory to reg. */ |
| else if (GET_CODE (src) == MEM) |
| { |
| decompose_csky_address (XEXP (src, 0), &op1); |
| |
| if (op1.index) |
| switch (GET_MODE (src)) |
| { |
| case E_HImode: |
| case E_HFmode: |
| return "ldr.h\t%0, %1"; |
| case E_QImode: |
| return "ldr.b\t%0, %1"; |
| case E_SImode: |
| case E_SFmode: |
| if (CSKY_VREG_P (REGNO (dst))) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_sf)) |
| return "fldrs\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_sf)) |
| return "fldr.32\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| return "ldr.w\t%0, %1"; |
| default: |
| gcc_unreachable (); |
| } |
| /* Generate lrw rx, [LABEL]. This happens when the compiler |
| generates constant pool references and uses lrw to get the |
| constant into memory. */ |
| else if (op1.label) |
| return "lrw\t%0, %1"; |
| /* Generate lrs.w rx, [symbol@GOT/PLT]. */ |
| else if (flag_pic == 1 && op1.disp && GET_CODE (op1.disp) == UNSPEC) |
| return "lrs.w\t%0, %1"; |
| else |
| switch (GET_MODE (src)) |
| { |
| case E_HImode: |
| case E_HFmode: |
| return "ld.h\t%0, %1"; |
| case E_QImode: |
| return "ld.b\t%0, %1"; |
| case E_SFmode: |
| case E_SImode: |
| if (CSKY_VREG_P (REGNO (dst))) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_sf)) |
| return "flds\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_sf)) |
| return "fld.32\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| return "ld.w\t%0, %1"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| /* The situation mov integer to reg. */ |
| else if (GET_CODE (src) == CONST_INT || |
| (GET_CODE (src) == CONST_DOUBLE && GET_MODE (src) == SFmode)) |
| { |
| HOST_WIDE_INT x, y; |
| const REAL_VALUE_TYPE *d; |
| long l; |
| |
| if (GET_CODE (src) == CONST_DOUBLE && GET_MODE (src) == SFmode) |
| { |
| d = CONST_DOUBLE_REAL_VALUE (src); |
| REAL_VALUE_TO_TARGET_SINGLE (*d, l); |
| operands[1] = GEN_INT (l); |
| src = operands[1]; |
| } |
| |
| if (try_csky_constant_tricks (INTVAL (src), &x, &y)) |
| return csky_output_inline_const (SImode, operands); |
| /* Return '#' to split it. */ |
| else if (CSKY_CONST_OK_FOR_T (INTVAL (src))) |
| return "#"; |
| else |
| return "lrw\t%0, %x1\t"; |
| } |
| else if (TARGET_ANCHOR && GET_CODE (src) == SYMBOL_REF) |
| { |
| if (SYMBOL_REF_FUNCTION_P (src)) |
| return "lrw\t%0, %1@BTEXT"; |
| else |
| return "lrw\t%0, %1@BDATA"; |
| } |
| else if (GET_CODE (src) == UNSPEC |
| && XINT (src, 1) == UNSPEC_PIC_SYMBOL_GRS) |
| return "grs\t%0, %1"; |
| else |
| return "lrw\t%0, %1"; |
| } |
| else if (GET_CODE (dst) == MEM) |
| { |
| decompose_csky_address (XEXP (dst, 0), &op0); |
| |
| if (op0.index) |
| switch (GET_MODE (src)) |
| { |
| case E_HImode: |
| return "str.h\t%1, %0"; |
| case E_QImode: |
| return "str.b\t%1, %0"; |
| case E_SFmode: |
| case E_SImode: |
| if (CSKY_VREG_P (REGNO (src))) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_sf)) |
| return "fstrs\t%1, %0"; |
| else if (CSKY_ISA_FEATURE(fpv3_sf)) |
| return "fstr.32\t%1, %0"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| return "str.w\t%1, %0"; |
| default: |
| gcc_unreachable (); |
| } |
| else |
| switch (GET_MODE (dst)) |
| { |
| case E_HImode: |
| return "st.h\t%1, %0"; |
| case E_QImode: |
| return "st.b\t%1, %0"; |
| case E_SImode: |
| case E_SFmode: |
| if (CSKY_VREG_P (REGNO (src))) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_sf)) |
| return "fsts\t%1, %0"; |
| else if (CSKY_ISA_FEATURE(fpv3_sf)) |
| return "fst.32\t%1, %0"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| return "st.w\t%1, %0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| |
| /* Output a move of a word or less value. Specific for ck801. */ |
| |
| const char * |
| csky_output_ck801_move (rtx insn ATTRIBUTE_UNUSED, rtx operands[], |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| struct csky_address op1; |
| |
| if (REG_P (dst)) |
| { |
| if (REG_P (src)) |
| return "mov\t%0, %1"; |
| else if (GET_CODE (src) == MEM) |
| { |
| decompose_csky_address (XEXP (src, 0), &op1); |
| |
| /* Generate lrw rx, [LABEL]. This happens when the compiler |
| generates constant pool references and uses lrw to get the |
| constant in memory. */ |
| if (op1.label) |
| return "lrw\t%0, %1"; |
| else |
| switch (GET_MODE (src)) |
| { |
| case E_HImode: |
| return "ld.h\t%0, %1"; |
| case E_QImode: |
| return "ld.b\t%0, %1"; |
| case E_SFmode: |
| case E_SImode: |
| return "ld.w\t%0, %1"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| else if (GET_CODE (src) == CONST_INT) |
| { |
| if (REGNO (dst) > 7) |
| return "lrw\t%0, %x1\t"; |
| else if (CSKY_CONST_OK_FOR_N (INTVAL (src) + 1)) |
| return "movi\t%0, %1"; |
| /* Return '#' to split it. */ |
| else if (CSKY_CONST_OK_FOR_T (INTVAL (src))) |
| return "#"; |
| else if (csky_shifted_imm8_constant (INTVAL (src), NULL, NULL)) |
| return "#"; |
| else |
| return "lrw\t%0, %x1\t"; |
| } |
| else if (GET_CODE (src) == CONST_DOUBLE && GET_MODE (src) == SFmode) |
| { |
| const REAL_VALUE_TYPE *d; |
| long l; |
| |
| d = CONST_DOUBLE_REAL_VALUE (src); |
| REAL_VALUE_TO_TARGET_SINGLE (*d, l); |
| operands[1] = GEN_INT (l); |
| src = operands[1]; |
| |
| if (CSKY_CONST_OK_FOR_N (INTVAL (src) + 1)) |
| return "movi\t%0, %1"; |
| else |
| return "lrw\t%0, %x1\t"; |
| } |
| else if (TARGET_ANCHOR && GET_CODE (src) == SYMBOL_REF) |
| { |
| if (SYMBOL_REF_FUNCTION_P (src)) |
| return "lrw\t%0, %1@BTEXT"; |
| else |
| return "lrw\t%0, %1@BDATA"; |
| } |
| else |
| return "lrw\t%0, %1"; |
| } |
| else if (GET_CODE (dst) == MEM) |
| switch (GET_MODE (dst)) |
| { |
| case E_HImode: |
| return "st.h\t%1, %0"; |
| case E_QImode: |
| return "st.b\t%1, %0"; |
| case E_SImode: |
| case E_SFmode: |
| return "st.w\t%1, %0"; |
| default: |
| gcc_unreachable (); |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| |
| /* Return a sequence of instructions to perform DI or DF move. |
| Since the CSKY cannot move a DI or DF in one instruction, we have |
| to take care when we see overlapping source and dest registers. */ |
| |
| const char * |
| csky_output_movedouble (rtx operands[], |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| |
| if (REG_P (dst)) |
| { |
| if (REG_P (src)) |
| { |
| int dstreg = REGNO (dst); |
| int srcreg = REGNO (src); |
| |
| if (CSKY_HILO_REG_P (srcreg)) |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "mfhi\t%0\n\tmflo\t%R0"; |
| else |
| return "mfhi\t%R0\n\tmflo\t%0"; |
| } |
| else if (CSKY_HILO_REG_P (dstreg)) |
| { |
| if (TARGET_BIG_ENDIAN) |
| return "mthi\t%1\n\tmtlo\t%R1"; |
| else |
| return "mthi\t%R1\n\tmtlo\t%1"; |
| } |
| else if (CSKY_VREG_P (srcreg) && CSKY_VREG_P (dstreg)) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fmovd\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fmov.64\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else if (CSKY_VREG_P (srcreg)) |
| { |
| /* Since the vector registers in fpuv2_soft processors |
| like ck803f are 32 bits wide, just one insn is needed |
| to complete the move operation. */ |
| if (TARGET_SOFT_FPU) |
| return "fmfvrl\t%0, %1"; |
| else if (TARGET_BIG_ENDIAN) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fmfvrh\t%0, %1\n\tfmfvrl\t%R0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fmfvr.64\t%R0, %0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fmfvrh\t%R0, %1\n\tfmfvrl\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fmfvr.64\t%0, %R0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| } |
| else if (CSKY_VREG_P (dstreg)) |
| { |
| if (TARGET_SOFT_FPU) |
| return "fmtvrl\t%0, %1"; |
| else if (TARGET_BIG_ENDIAN) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fmtvrh\t%0, %1\n\tfmtvrl\t%0, %R1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fmtvr.64\t%0, %R1, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fmtvrh\t%0, %R1\n\tfmtvrl\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fmtvr.64\t%0, %1, %R1"; |
| else |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Ensure the second source not overwritten. */ |
| if (srcreg + 1 == dstreg) |
| return "mov\t%R0, %R1\n\tmov\t%0, %1"; |
| else |
| return "mov\t%0, %1\n\tmov\t%R0, %R1"; |
| } |
| else if (GET_CODE (src) == MEM) |
| { |
| rtx memexp = XEXP (src, 0); |
| int dstreg = REGNO (dst); |
| int basereg = -1; |
| struct csky_address op0; |
| |
| decompose_csky_address (XEXP (src, 0), &op0); |
| |
| if (GET_CODE (memexp) == LABEL_REF |
| || (GET_CODE (memexp) == CONST |
| && GET_CODE (XEXP (memexp, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (memexp, 0), 0)) == LABEL_REF)) |
| return "lrw\t%0, [%1]\n\tlrw\t%R0, [%R1]"; |
| else if (GET_CODE (memexp) == REG) |
| basereg = REGNO (memexp); |
| else if (GET_CODE (memexp) == PLUS) |
| { |
| if (GET_CODE (XEXP (memexp, 0)) == REG) |
| basereg = REGNO (XEXP (memexp, 0)); |
| else if (GET_CODE (XEXP (memexp, 1)) == REG) |
| basereg = REGNO (XEXP (memexp, 1)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| gcc_unreachable (); |
| |
| |
| /* When FPUV2. */ |
| if (CSKY_VREG_P (dstreg)) |
| { |
| if (op0.index) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fldrd\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fldr.64\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fldd\t%0, %1"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fld.64\t%0, %1"; |
| else |
| gcc_unreachable (); |
| } |
| } |
| /* FIXME length attribute is wrong here. */ |
| if (dstreg == basereg) |
| /* Just load them in reverse order. */ |
| return "ld.w\t%R0, %R1\n\tld.w\t%0, %1"; |
| else |
| return "ld.w\t%0, %1\n\tld.w\t%R0, %R1"; |
| } |
| else if (GET_CODE (src) == CONST_INT || GET_CODE (src) == CONST_DOUBLE) |
| { |
| split_double (src, operands + 2, operands + 3); |
| |
| if (CSKY_CONST_OK_FOR_I (INTVAL (operands[2]))) |
| output_asm_insn ("movi\t%0, %2", operands); |
| else if (CSKY_CONST_OK_FOR_Uc (INTVAL (operands[2]))) |
| output_asm_insn ("bmaski\t%0, %N2", operands); |
| else if (CSKY_CONST_OK_FOR_Ub (INTVAL (operands[2]))) |
| output_asm_insn ("bgeni\t%0, %P2", operands); |
| else |
| output_asm_insn ("lrw\t%0, %2", operands); |
| |
| if (CSKY_CONST_OK_FOR_I (INTVAL (operands[3]))) |
| output_asm_insn ("movi\t%R0, %3", operands); |
| else if (CSKY_CONST_OK_FOR_Uc (INTVAL (operands[3]))) |
| output_asm_insn ("bmaski\t%R0, %N3", operands); |
| |
| else if (CSKY_CONST_OK_FOR_Ub (INTVAL (operands[3]))) |
| output_asm_insn ("bgeni\t%R0, %P3", operands); |
| else |
| output_asm_insn ("lrw\t%R0, %3", operands); |
| |
| return ""; |
| } |
| else |
| gcc_unreachable (); |
| } |
| else if (GET_CODE (dst) == MEM && GET_CODE (src) == REG) |
| { |
| rtx memexp = XEXP (dst, 0); |
| int srcreg = REGNO (src); |
| int basereg = -1; |
| struct csky_address op0; |
| |
| decompose_csky_address (XEXP (dst, 0), &op0); |
| |
| if (GET_CODE (memexp) == REG) |
| basereg = REGNO (memexp); |
| else if (GET_CODE (memexp) == PLUS) |
| { |
| if (GET_CODE (XEXP (memexp, 0)) == REG) |
| basereg = REGNO (XEXP (memexp, 0)); |
| else if (GET_CODE (XEXP (memexp, 1)) == REG) |
| basereg = REGNO (XEXP (memexp, 1)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| gcc_unreachable (); |
| |
| /* When FPUV2. */ |
| if (CSKY_VREG_P (srcreg)) |
| { |
| if (op0.index) |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fstrd\t%1, %0"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fstr.64\t%1, %0"; |
| else |
| gcc_unreachable (); |
| } |
| else |
| { |
| if (CSKY_ISA_FEATURE(fpv2_df)) |
| return "fstd\t%1, %0"; |
| else if (CSKY_ISA_FEATURE(fpv3_df)) |
| return "fst.64\t%1, %0"; |
| else |
| gcc_unreachable (); |
| } |
| } |
| /* FIXME length attribute is wrong here. */ |
| if (srcreg == basereg) |
| /* Just load them in reverse order. */ |
| return "st.w\t%R1, %R0\n\tst.w\t%1, %0"; |
| else |
| return "st.w\t%1, %0\n\tst.w\t%R1, %R0"; |
| } |
| else |
| gcc_unreachable (); |
| } |
| |
| |
| const char * |
| csky_output_ck801_movedouble (rtx operands[], |
| machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| |
| if (REG_P (dst)) |
| { |
| if (REG_P (src)) |
| { |
| int dstreg = REGNO (dst); |
| int srcreg = REGNO (src); |
| |
| /* Ensure the second source not overwritten. */ |
| if (srcreg + 1 == dstreg) |
| return "mov\t%R0, %R1\n\tmov\t%0, %1"; |
| else |
| return "mov\t%0, %1\n\tmov\t%R0, %R1"; |
| } |
| else if (GET_CODE (src) == MEM) |
| { |
| rtx memexp = XEXP (src, 0); |
| int dstreg = REGNO (dst); |
| int basereg = -1; |
| struct csky_address op0; |
| |
| decompose_csky_address (XEXP (src, 0), &op0); |
| |
| if (GET_CODE (memexp) == LABEL_REF |
| || (GET_CODE (memexp) == CONST |
| && GET_CODE (XEXP (memexp, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (memexp, 0), 0)) == LABEL_REF)) |
| return "lrw\t%0, [%1]\n\tlrw\t%R0, [%R1]"; |
| else if (GET_CODE (memexp) == REG) |
| basereg = REGNO (memexp); |
| else if (GET_CODE (memexp) == PLUS) |
| { |
| if (GET_CODE (XEXP (memexp, 0)) == REG) |
| basereg = REGNO (XEXP (memexp, 0)); |
| else if (GET_CODE (XEXP (memexp, 1)) == REG) |
| basereg = REGNO (XEXP (memexp, 1)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| gcc_unreachable (); |
| |
| /* FIXME length attribute is wrong here. */ |
| if (dstreg == basereg) |
| /* Just load them in reverse order. */ |
| return "ld.w\t%R0, %R1\n\tld.w\t%0, %1"; |
| else |
| return "ld.w\t%0, %1\n\tld.w\t%R0, %R1"; |
| } |
| else if (GET_CODE (src) == CONST_INT || GET_CODE (src) == CONST_DOUBLE) |
| { |
| split_double (src, operands + 2, operands + 3); |
| |
| if (REGNO (dst) <= 7 |
| && CSKY_CONST_OK_FOR_N (INTVAL (operands[2]) + 1)) |
| output_asm_insn ("movi\t%0, %2", operands); |
| else |
| output_asm_insn ("lrw\t%0, %2", operands); |
| |
| |
| if (REGNO (dst) <= 6 |
| && CSKY_CONST_OK_FOR_N (INTVAL (operands[3]) + 1)) |
| output_asm_insn ("movi\t%R0, %3", operands); |
| else |
| output_asm_insn ("lrw\t%R0, %3", operands); |
| |
| return ""; |
| |
| |
| } |
| else |
| gcc_unreachable (); |
| } |
| else if (GET_CODE (dst) == MEM && GET_CODE (src) == REG) |
| { |
| rtx memexp = XEXP (dst, 0); |
| int srcreg = REGNO (src); |
| int basereg = -1; |
| struct csky_address op0; |
| |
| decompose_csky_address (XEXP (dst, 0), &op0); |
| |
| if (GET_CODE (memexp) == REG) |
| basereg = REGNO (memexp); |
| else if (GET_CODE (memexp) == PLUS) |
| { |
| if (GET_CODE (XEXP (memexp, 0)) == REG) |
| basereg = REGNO (XEXP (memexp, 0)); |
| else if (GET_CODE (XEXP (memexp, 1)) == REG) |
| basereg = REGNO (XEXP (memexp, 1)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| gcc_unreachable (); |
| |
| /* FIXME length attribute is wrong here. */ |
| if (srcreg == basereg) |
| /* Just load them in reverse order. */ |
| return "st.w\t%R1, %R0\n\tst.w\t%1, %0"; |
| else |
| return "st.w\t%1, %0\n\tst.w\t%R1, %R0"; |
| } |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Calculate the instruction's length for moving double-word data. */ |
| |
| int |
| csky_get_movedouble_length(rtx operands[]) |
| { |
| rtx dst = operands[0]; |
| rtx src = operands[1]; |
| |
| if (REG_P (dst)) |
| { |
| if (REG_P (src)) |
| { |
| int dstreg = REGNO (dst); |
| int srcreg = REGNO (src); |
| |
| if (CSKY_VREG_P (srcreg) && CSKY_VREG_P (dstreg)) |
| return 4; |
| else |
| return 8; |
| } |
| else if (GET_CODE (src) == MEM) |
| { |
| rtx memexp = XEXP (src, 0); |
| int dstreg = REGNO (dst); |
| struct csky_address op0; |
| decompose_csky_address (XEXP (src, 0), &op0); |
| |
| if (GET_CODE (memexp) == LABEL_REF) |
| return 8; |
| if (CSKY_VREG_P (dstreg)) |
| return 4; |
| return 8; |
| } |
| else if (GET_CODE (src) == CONST_INT || GET_CODE (src) == CONST_DOUBLE) |
| { |
| split_double (src, operands + 2, operands + 3); |
| if (CSKY_CONST_OK_FOR_N (INTVAL (operands[2]) + 1) |
| && CSKY_CONST_OK_FOR_N (INTVAL (operands[3]) + 1) |
| && REGNO (operands[0]) < 6) |
| return 4; |
| else |
| return 8; |
| } |
| } |
| else if (GET_CODE (dst) == MEM && GET_CODE (src) == REG) |
| { |
| rtx memexp = XEXP (dst, 0); |
| int srcreg = REGNO (src); |
| int offset = -1; |
| if (CSKY_VREG_P (srcreg)) |
| return 4; |
| |
| if (GET_CODE (memexp) == REG) |
| offset = 0; |
| else if (GET_CODE (memexp) == PLUS) |
| { |
| if (GET_CODE (XEXP (memexp, 0)) == REG) |
| offset = INTVAL (XEXP (memexp, 1)); |
| else if (GET_CODE (XEXP (memexp, 1)) == REG) |
| offset = INTVAL (XEXP (memexp, 0)); |
| else |
| gcc_unreachable (); |
| } |
| else |
| gcc_unreachable (); |
| |
| if (srcreg <= 6 && offset <= 1020) |
| return 4; |
| else if ((srcreg == 7 && offset <= 1024) || (srcreg <= 7 && offset == 1024)) |
| return 6; |
| else |
| return 8; |
| } |
| else |
| gcc_unreachable (); |
| |
| return 0; |
| } |
| |
| /* Output float point load/store instructions for fpuv3. */ |
| |
| const char * |
| fpuv3_output_move (rtx *operands) |
| { |
| rtx reg, mem, addr, ops[2]; |
| bool isload = REG_P (operands[0]); |
| |
| const char *templ = "f%s%s.%s\t%%0, %%1"; |
| char buff[50]; |
| machine_mode mode; |
| |
| reg = operands[isload ? 0 : 1]; |
| mem = operands[isload ? 1 : 0]; |
| |
| gcc_assert (REG_P (reg)); |
| gcc_assert (CSKY_VREG_P (REGNO (reg))); |
| gcc_assert (MEM_P (mem)); |
| |
| mode = GET_MODE (reg); |
| const char *type = mode == DFmode ? "64" : |
| mode == SFmode ? "32" : |
| mode == HFmode ? "16" : |
| NULL; |
| gcc_assert(type != NULL); |
| |
| addr = XEXP (mem, 0); |
| struct csky_address caddr; |
| decompose_csky_address (addr, &caddr); |
| |
| ops[0] = reg; |
| ops[1] = mem; |
| sprintf (buff, templ, |
| isload ? "ld" : "st", |
| caddr.index ? "r" : "", |
| type); |
| output_asm_insn (buff, ops); |
| |
| return ""; |
| } |
| |
| /* Check if a const_double can be used by a VFP fmovi instruction. */ |
| |
| int |
| fpuv3_const_double_rtx (rtx x) |
| { |
| REAL_VALUE_TYPE r, m; |
| r = *CONST_DOUBLE_REAL_VALUE (x); |
| |
| /* Fpuv3 doesn't support the following values. */ |
| if (REAL_VALUE_ISINF (r) || REAL_VALUE_ISNAN (r) || REAL_VALUE_MINUS_ZERO (r) |
| || r.cl == rvc_zero) |
| return 0; |
| |
| /* Extract sign, exponent and mantissa. */ |
| int exponent; |
| r = real_value_abs (&r); |
| exponent = REAL_EXP (&r); |
| |
| bool fail; |
| unsigned HOST_WIDE_INT mantissa, mant_hi; |
| unsigned HOST_WIDE_INT mask; |
| int point_pos = 2 * HOST_BITS_PER_WIDE_INT - 1; |
| real_ldexp (&m, &r, point_pos - exponent); |
| wide_int w = real_to_integer (&m, &fail, HOST_BITS_PER_WIDE_INT * 2); |
| mantissa = w.elt (0); |
| mant_hi = w.elt (1); |
| |
| exponent -= 1; |
| |
| if (!IN_RANGE (exponent, -4, 11)) |
| return 0; |
| |
| /* If there are bits set in the low part of the mantissa, these values are |
| not supported. */ |
| if (mantissa != 0) |
| return 0; |
| |
| /* Now, make the mantissa contain the most-significant bits, and the |
| point_pos indicates the number of these bits. */ |
| point_pos -= HOST_BITS_PER_WIDE_INT; |
| mantissa = mant_hi; |
| |
| /* We can only allow a mantissa of 9 significant digits, top of which is always 1. */ |
| mask = ((unsigned HOST_WIDE_INT)1 << (point_pos - 9)) - 1; |
| if ((mantissa & mask) != 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| |
| /* Split operands for an AND expression when OPERANDS[2] is a constant. |
| Note operands[0] is marked earlyclobber in this case and can be |
| overwritten. Return true if "DONE", false otherwise. */ |
| |
| bool |
| csky_split_and (rtx *operands) |
| { |
| HOST_WIDE_INT mask = INTVAL (operands[2]); |
| rtx not_value = GEN_INT (~mask); |
| int i; |
| |
| /* All zeros or all ones can be handled by a move instruction. */ |
| if (mask == 0) |
| { |
| emit_move_insn (operands[0], const0_rtx); |
| return true; |
| } |
| if (mask == -1) |
| { |
| emit_move_insn (operands[0], operands[1]); |
| return true; |
| } |
| |
| /* Check for constants that can be handled directly by the 32-bit andi |
| instruction. */ |
| if (CSKY_ISA_FEATURE (E2) && csky_arith_O_operand (operands[2], SImode)) |
| return false; |
| |
| /* Try to transform to andni instruction. */ |
| if (CSKY_ISA_FEATURE (E2) && csky_arith_O_operand (not_value, SImode)) |
| { |
| emit_insn (gen_cskyv2_andnsi3 (operands[0], not_value, operands[1])); |
| return true; |
| } |
| |
| /* If there are only one or two 0 bits in the constant, we can |
| replace the operation with bclri instructions on those bits. |
| Note CK801 has only the 16-bit bclri that operates on a single |
| register, so we must count a move if we are post-reload. */ |
| if (popcount_hwi (~mask & 0xffffffff) |
| <= (reload_completed && !CSKY_ISA_FEATURE (E2) ? 1 : 2)) |
| { |
| rtx input = operands[1]; |
| |
| if (!CSKY_ISA_FEATURE (E2)) |
| { |
| emit_move_insn (operands[0], input); |
| input = operands[0]; |
| } |
| |
| for (i = 0; i < 32; i++) |
| if ((mask & (1 << i)) == 0x0) |
| { |
| emit_insn (gen_bclri (operands[0], input, GEN_INT (i))); |
| input = operands[0]; |
| } |
| return true; |
| } |
| |
| /* If the constant mask is outside the [0, 4095] range for |
| constraint O, or if constraint O is not allowed (ck801), |
| maybe the constant is a contiguous bit range that we can |
| handle by bit extract (low bits) or shifts (high bits). */ |
| for (i = (CSKY_ISA_FEATURE (E2) ? 13 : 1); i < 32; i++) |
| { |
| if ((((HOST_WIDE_INT) 1) << i) - 1 == mask) |
| { |
| if (CSKY_ISA_FEATURE (2E3)) |
| emit_insn (gen_cskyv2_extzv (operands[0], operands[1], |
| GEN_INT (i), const0_rtx)); |
| else |
| { |
| rtx shift = GEN_INT (32 - i); |
| rtx reg = (reload_completed |
| ? operands[0] : gen_reg_rtx (SImode)); |
| |
| emit_insn (gen_ashlsi3 (reg, operands[1], shift)); |
| emit_insn (gen_lshrsi3 (operands[0], reg, shift)); |
| } |
| return true; |
| } |
| else if ((((HOST_WIDE_INT) 1) << i) - 1 == ~mask) |
| { |
| rtx shift = GEN_INT (i); |
| rtx reg = (reload_completed |
| ? operands[0] : gen_reg_rtx (SImode)); |
| |
| emit_insn (gen_lshrsi3 (reg, operands[1], shift)); |
| emit_insn (gen_ashlsi3 (operands[0], reg, shift)); |
| return true; |
| } |
| } |
| |
| /* If the constant is a negative number, it seems better to use |
| andn and copy the NOT_VALUE to a register instead of the |
| original value, since the NOT_VALUE is always smaller and thus |
| more likely to be representable as a small constant. |
| This transformation can only be done before reload because |
| it requires a temporary. Hopefully register allocation can get |
| rid of the extra move required for CK801. */ |
| if (!reload_completed && INTVAL (operands[2]) < 0) |
| { |
| rtx reg = copy_to_mode_reg (SImode, not_value); |
| |
| if (CSKY_ISA_FEATURE (E2)) |
| emit_insn (gen_cskyv2_andnsi3 (operands[0], reg, operands[1])); |
| else |
| { |
| emit_move_insn (operands[0], operands[1]); |
| emit_insn (gen_ck801_andnsi3 (operands[0], reg, operands[0])); |
| } |
| return true; |
| } |
| |
| /* If the above ways are all not working, move the constant |
| to a register. We can clobber operands[0] as it is |
| marked earlyclobber in the insn constraints, but then we have to |
| swap operands 1 and 2 to match the constraints on the 2-operand |
| 16-bit and instruction. */ |
| if (reload_completed) |
| { |
| emit_move_insn (operands[0], operands[2]); |
| operands[2] = operands[1]; |
| operands[1] = operands[0]; |
| } |
| else |
| operands[2] = copy_to_mode_reg (SImode, operands[2]); |
| return false; |
| } |
| |
| /* Split operands for an IOR expression when OPERANDS[2] is a constant. |
| Note operands[0] is marked earlyclobber in this case and can be |
| overwritten. Return true if "DONE", false otherwise. */ |
| |
| bool |
| csky_split_ior (rtx *operands) |
| { |
| HOST_WIDE_INT mask = INTVAL (operands[2]); |
| int i; |
| |
| /* All zeros or all ones can be handled by a move instruction. */ |
| if (mask == 0) |
| { |
| emit_move_insn (operands[0], operands[1]); |
| return true; |
| } |
| if (mask == -1) |
| { |
| emit_move_insn (operands[0], gen_int_mode (-1, SImode)); |
| return true; |
| } |
| |
| /* Check for constants that can be handled directly by the 32-bit ori |
| instruction. */ |
| if (CSKY_ISA_FEATURE (E2) && csky_literal_I_operand (operands[2], SImode)) |
| return false; |
| |
| /* If there are only one or two 1 bits in the value, we can replace |
| the operation with bseti instructions to set those bits. |
| Note CK801 has only the 16-bit bclri that operates on a single |
| register, so we must count a move if we are post-reload. */ |
| if (popcount_hwi (mask & 0xffffffff) |
| <= (reload_completed && !CSKY_ISA_FEATURE (E2) ? 1 : 2)) |
| { |
| rtx input = operands[1]; |
| |
| if (!CSKY_ISA_FEATURE (E2)) |
| { |
| emit_move_insn (operands[0], input); |
| input = operands[0]; |
| } |
| |
| for (i = 0; i < 32; i++) |
| if (mask & (1 << i)) |
| { |
| emit_insn (gen_bseti (operands[0], input, GEN_INT (i))); |
| input = operands[0]; |
| } |
| return true; |
| } |
| |
| /* If the above ways are all not working, move the constant |
| to a register. We can clobber operands[0] as it is |
| marked earlyclobber in the insn constraints, but then we have to |
| swap operands 1 and 2 to match the constraints on the 2-operand |
| 16-bit ior instruction. */ |
| if (reload_completed) |
| { |
| emit_move_insn (operands[0], operands[2]); |
| operands[2] = operands[1]; |
| operands[1] = operands[0]; |
| } |
| else |
| operands[2] = copy_to_mode_reg (SImode, operands[2]); |
| return false; |
| } |
| |
| |
| /* Split operands for an XOR expression when OPERANDS[2] is a constant. |
| Note operands[0] is marked earlyclobber in this case and can be |
| overwritten. Return true if "DONE", false otherwise. */ |
| |
| bool |
| csky_split_xor (rtx *operands) |
| { |
| HOST_WIDE_INT mask = INTVAL (operands[2]); |
| |
| /* All zeros can be turned into move instruction. */ |
| if (mask == 0) |
| { |
| emit_move_insn (operands[0], operands[1]); |
| return true; |
| } |
| |
| /* All ones can be turned into a bitwise not. */ |
| if (mask == -1) |
| { |
| if (CSKY_ISA_FEATURE (E2)) |
| emit_insn (gen_cskyv2_one_cmplsi2 (operands[0], operands[1])); |
| else |
| { |
| emit_move_insn (operands[0], operands[1]); |
| emit_insn (gen_ck801_one_cmplsi2 (operands[0], operands[0])); |
| } |
| return true; |
| } |
| |
| /* Check for constants that can be handled directly by the 32-bit xori |
| instruction. */ |
| if (CSKY_ISA_FEATURE (E2) && csky_arith_O_operand (operands[2], SImode)) |
| return false; |
| |
| /* If the above ways are all not working, move the constant |
| to a register. We can clobber operands[0] as it is |
| marked earlyclobber in the insn constraints, but then we have to |
| swap operands 1 and 2 to match the constraints on the 2-operand |
| 16-bit ior instruction. */ |
| if (reload_completed) |
| { |
| emit_move_insn (operands[0], operands[2]); |
| operands[2] = operands[1]; |
| operands[1] = operands[0]; |
| } |
| else |
| operands[2] = copy_to_mode_reg (SImode, operands[2]); |
| return false; |
| } |
| |
| |
| /* Return true if X is an address form involving a symbol or label ref. */ |
| |
| bool |
| csky_symbolic_address_p (rtx x) |
| { |
| switch (GET_CODE (x)) |
| { |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return 1; |
| case CONST: |
| x = XEXP (x, 0); |
| return ((GET_CODE (XEXP (x, 0)) == SYMBOL_REF |
| || GET_CODE (XEXP (x, 0)) == LABEL_REF) |
| && GET_CODE (XEXP (x, 1)) == CONST_INT); |
| default: |
| return 0; |
| } |
| } |
| |
| |
| /* Emit a comparison instruction. |
| Return true if an inverted comparison is generated. */ |
| |
| bool |
| csky_emit_compare (enum rtx_code code, rtx op0, rtx op1) |
| { |
| bool invert; |
| rtx cc_reg = gen_rtx_REG (CCmode, CSKY_CC_REGNUM); |
| |
| if (GET_MODE_CLASS(GET_MODE (op0)) == MODE_FLOAT) |
| return csky_emit_compare_float(code, op0, op1); |
| |
| if (GET_CODE (op1) == CONST_INT) |
| { |
| HOST_WIDE_INT val = INTVAL (op1); |
| |
| switch (code) |
| { |
| case GTU: |
| /* Unsigned (GTU 0) is the same as (NE 0); everything else is |
| converted below to LEU (reversed cmphs). */ |
| if (val == 0) |
| code = NE; |
| /* Check whether (GTU A imm) can become (GEU A imm + 1). */ |
| else if (TARGET_MINI_REGISTERS |
| ? CSKY_CONST_OK_FOR_J (val + 1) |
| : CSKY_CONST_OK_FOR_Uk (val + 1)) |
| { |
| op1 = GEN_INT (val + 1); |
| code = GEU; |
| } |
| break; |
| /* Check whether (LE A imm) can become (LT A imm + 1), |
| or (GT A imm) can become (GE A imm + 1). */ |
| case GT: |
| case LE: |
| if (TARGET_MINI_REGISTERS |
| ? CSKY_CONST_OK_FOR_J (val + 1) |
| : CSKY_CONST_OK_FOR_Uk (val + 1)) |
| { |
| op1 = GEN_INT (val + 1); |
| code = code == LE ? LT : GE; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if (CONSTANT_P (op1) && GET_CODE (op1) != CONST_INT) |
| op1 = force_reg (GET_MODE (op1), op1); |
| |
| /* cmpnei: 0-31 (K immediate) |
| ti: 1-32 (J immediate, 0 using btsti x,31). */ |
| invert = false; |
| switch (code) |
| { |
| /* Use inverted condition, cmpne. */ |
| case EQ: |
| code = NE; |
| invert = true; |
| /* Fall through. */ |
| /* Use normal condition, cmpne. */ |
| case NE: |
| if (GET_CODE (op1) == CONST_INT |
| && (TARGET_MINI_REGISTERS |
| ? !csky_literal_K_operand (op1, SImode) |
| : !csky_literal_I_operand (op1, SImode))) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| /* Use inverted condition, reversed cmplt. */ |
| case LE: |
| code = GT; |
| invert = true; |
| /* Fall through. */ |
| /* Use normal condition, reversed cmplt. */ |
| case GT: |
| if (GET_CODE (op1) == CONST_INT) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| /* Use inverted condition, cmplt. */ |
| case GE: |
| code = LT; |
| invert = true; |
| /* Fall through. */ |
| /* Use normal condition, cmplt. */ |
| case LT: |
| /* covered by btsti x,31. */ |
| if (GET_CODE (op1) == CONST_INT && INTVAL (op1) != 0 |
| && (TARGET_MINI_REGISTERS |
| ? !csky_literal_J_operand (op1, SImode) |
| : !csky_literal_Uk_operand (op1, SImode))) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| /* Use inverted condition, cmple. */ |
| case GTU: |
| /* We coped with unsigned > 0 above. */ |
| gcc_assert (GET_CODE (op1) != CONST_INT || INTVAL (op1) != 0); |
| code = LEU; |
| invert = true; |
| /* Fall through. */ |
| /* Use normal condition, reversed cmphs. */ |
| case LEU: |
| if (GET_CODE (op1) == CONST_INT && INTVAL (op1) != 0) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| /* Use inverted condition, cmphs. */ |
| case LTU: |
| code = GEU; |
| invert = true; |
| /* Fall through. */ |
| /* Use normal condition, cmphs. */ |
| case GEU: |
| if (GET_CODE (op1) == CONST_INT && INTVAL (op1) != 0 |
| && (TARGET_MINI_REGISTERS |
| ? !csky_literal_J_operand (op1, SImode) |
| : !csky_literal_Uk_operand (op1, SImode))) |
| op1 = force_reg (SImode, op1); |
| break; |
| |
| default: |
| break; |
| } |
| |
| emit_insn (gen_rtx_SET (cc_reg, |
| gen_rtx_fmt_ee (code, CCmode, op0, op1))); |
| return invert; |
| } |
| |
| /* Return true if push/pop can be used to save/restore all the registers |
| indicated by MASK. We currently don't attempt to handle situations where |
| some of the registers could be handled by push/pop and others saved and |
| restored individually. */ |
| |
| static bool |
| csky_can_use_pushpop (unsigned int mask) |
| { |
| int i; |
| int end_reg; |
| |
| if (!TARGET_PUSHPOP) |
| return false; |
| |
| if (mask == 0) |
| return false; |
| |
| /* Regs 0-3, 12-14, 18-27, 29-31 cannot be in the mask. */ |
| if (mask & 0xeffc700f) |
| return false; |
| |
| /* Regs in the range r4-r11 must be contiguous. */ |
| for (end_reg = 0, i = 11; i >= 4; i--) |
| { |
| if (!end_reg && (mask & (1 << i))) |
| end_reg = i; |
| if (end_reg && !(mask & (1 << i))) |
| return false; |
| } |
| |
| /* Likewise for regs in the range r16-r17. */ |
| for (end_reg = 0, i = 17; i >= 16; i--) |
| { |
| if (!end_reg && (mask & (1 << i))) |
| end_reg = i; |
| if (end_reg && !(mask & (1 << i))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| |
| /* Return true if store/load multiple instructions can be used to |
| save/restore at least some of the registers indicated by MASK. |
| Unlike the push/pop case, this does handle partial ranges. |
| Set *BR and *ER to the beginning and end (respectively) of the |
| register range that can be handled. */ |
| |
| static bool |
| csky_can_use_ldstm (int mask, int *br, int *er) |
| { |
| int regno; |
| int begin_reg = 0, end_reg = 0; |
| int count = 0; |
| |
| if (!TARGET_MULTIPLE_STLD) |
| return false; |
| |
| /* We'll only handle registers in the range 4-11, the contiguous range |
| of caller-saved registers. Higher-numbered registers are handled |
| individually in addition to this, but we'll give up on doing ldstm |
| entirely if we need to save/restore the low-numbered EH registers. */ |
| if (mask & 0xf) |
| return false; |
| |
| for (regno = 4; regno <= 11; regno++) |
| { |
| if (mask & 1 << regno) |
| { |
| if (!begin_reg) |
| begin_reg = regno; |
| end_reg = regno; |
| count++; |
| } |
| else if (begin_reg) |
| break; |
| } |
| |
| if (count >= CSKY_MIN_MULTIPLE_STLD && count <= CSKY_MAX_MULTIPLE_STLD) |
| { |
| if (br) |
| *br = begin_reg; |
| if (er) |
| *er = end_reg; |
| return true; |
| } |
| return false; |
| } |
| |
| |
| const char * |
| csky_output_return_instruction (void) |
| { |
| unsigned long func_type = get_csky_current_func_type (); |
| |
| if (CSKY_FUNCTION_IS_NAKED (func_type)) |
| return ""; |
| if (CSKY_FUNCTION_IS_INTERRUPT (func_type)) |
| return "ipop\n\tnir\n"; |
| else |
| return "rts\n"; |
| } |
| |
| |
| /* Adjust the stack pointer by OFFSET bytes. OFFSET is negative if this |
| is in the prologue, positive if in the epilogue. This may require |
| multiple instructions and/or use of CSKY_STACKADJUST_REGNUM as |
| a scratch register. Emit CFA notes as appropriate. */ |
| static void |
| expand_csky_stack_adjust (int offset) |
| { |
| rtx set; |
| rtx_insn *insn; |
| int size = (offset > 0 ? offset : -offset); |
| |
| if (offset == 0) |
| return; |
| |
| /* If OFFSET is too large for addi/subi, load it into |
| CSKY_STACKADJUST_REGNUM and use a register add/sub instead. |
| This case is not mentioned in the ABI documentation, but it is |
| supported by GDB prologue analysis provided that the instruction(s) |
| to initialize CSKY_STACKADJUST_REGNUM appear directly before |
| the sub. Depending on the value of OFFSET, this might be a |
| lrw instruction or the "tricks" used by csky_output_inline_const to |
| encode special-case integer constants. */ |
| if (size > CSKY_MAX_SP_ADJUST * 2) |
| { |
| rtx tmp, dwarf; |
| |
| /* We should have reserved the scratch register already in |
| csky_layout_stack_frame. */ |
| gcc_assert (cfun->machine->reg_size != 0 |
| && (cfun->machine->reg_mask |
| & (1 << CSKY_STACKADJUST_REGNUM))); |
| |
| /* Prevent the optimizer from reordering these instructions to |
| keep GDB happy. */ |
| if (!flag_sched_prolog) |
| emit_insn (gen_blockage ()); |
| |
| tmp = gen_rtx_REG (SImode, CSKY_STACKADJUST_REGNUM); |
| emit_move_insn (tmp, GEN_INT (size)); |
| |
| if (offset > 0) |
| set = gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, tmp); |
| else |
| set = gen_subsi3 (stack_pointer_rtx, stack_pointer_rtx, tmp); |
| insn = emit_insn (set); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| dwarf = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, stack_pointer_rtx, offset)); |
| add_reg_note (insn, REG_FRAME_RELATED_EXPR, dwarf); |
| |
| /* More make GDB happy. */ |
| if (!flag_sched_prolog) |
| emit_insn (gen_blockage ()); |
| } |
| |
| /* Use one or two addi or subi insns to adjust stack. */ |
| else |
| while (size) |
| { |
| int delta = (size > CSKY_MAX_SP_ADJUST |
| ? CSKY_MAX_SP_ADJUST : size); |
| |
| if (offset > 0) |
| set = gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (delta)); |
| else |
| set = gen_subsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| GEN_INT (delta)); |
| insn = emit_insn (set); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| size -= delta; |
| } |
| } |
| |
| |
| /* Generate and emit an insn that we will recognize as a push_multi. |
| Unfortunately, since this insn does not reflect very well the actual |
| semantics of the operation, we need to annotate the insn for the benefit |
| of DWARF2 frame unwind information. DWARF_REGS_MASK is a subset of |
| MASK for registers that should be annotated for DWARF2 frame unwind |
| information. */ |
| |
| static rtx |
| emit_csky_regs_push (unsigned long mask) |
| { |
| int num_regs = 0; |
| int i, j; |
| rtx par; |
| rtx dwarf; |
| rtx tmp; |
| int dwarf_par_index; |
| |
| for (i = 0; i < CSKY_NGPR_REGS; i++) |
| { |
| if (mask & (1 << i)) |
| num_regs++; |
| } |
| |
| /* The reg range for push is:r4-r11,r15-r17,r28. */ |
| gcc_assert (num_regs && num_regs <= 12); |
| |
| /* For the body of the insn we are going to generate an UNSPEC in |
| parallel with several USEs. This allows the insn to be recognized |
| by the push_multi pattern in the csky.md file. |
| |
| The body of the insn looks something like this: |
| |
| (parallel [ |
| (set (mem:BLK (pre_modify:SI (reg:SI sp) |
| (const_int:SI <num>))) |
| (unspec:BLK [(reg:SI r4)] UNSPEC_PUSHPOP_MULT)) |
| (use (reg:SI XX)) |
| (use (reg:SI YY)) |
| ... |
| ]) |
| |
| For the frame note however, we try to be more explicit and actually |
| show each register being stored into the stack frame, plus a (single) |
| decrement of the stack pointer. We do it this way in order to be |
| friendly to the stack unwinding code, which only wants to see a single |
| stack decrement per instruction. The RTL we generate for the note looks |
| something like this: |
| |
| (sequence [ |
| (set (reg:SI sp) (plus:SI (reg:SI sp) (const_int -20))) |
| (set (mem:SI (reg:SI sp)) (reg:SI r4)) |
| (set (mem:SI (plus:SI (reg:SI sp) (const_int 4))) (reg:SI XX)) |
| (set (mem:SI (plus:SI (reg:SI sp) (const_int 8))) (reg:SI YY)) |
| ... |
| ]) |
| |
| FIXME:: In an ideal world the PRE_MODIFY would not exist and |
| instead we'd have a parallel expression detailing all |
| the stores to the various memory addresses so that debug |
| information is more up-to-date. Remember however while writing |
| this to take care of the constraints with the push instruction. |
| |
| Note also that this has to be taken care of for the VFP registers. |
| |
| For more see PR43399. */ |
| |
| par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (num_regs)); |
| dwarf = gen_rtx_SEQUENCE (VOIDmode, rtvec_alloc (num_regs + 1)); |
| dwarf_par_index = 1; |
| |
| for (i = 0; i < CSKY_NGPR_REGS; i++) |
| if (mask & (1 << i)) |
| { |
| rtx reg = gen_rtx_REG (SImode, i); |
| rtx addr = plus_constant (Pmode, stack_pointer_rtx, -4 * num_regs); |
| tmp = gen_frame_mem (BLKmode, |
| gen_rtx_PRE_MODIFY (Pmode, |
| stack_pointer_rtx, addr)); |
| XVECEXP (par, 0, 0) |
| = gen_rtx_SET (tmp, |
| gen_rtx_UNSPEC (BLKmode, |
| gen_rtvec (1, reg), |
| UNSPEC_PUSHPOP_MULT)); |
| tmp = gen_rtx_SET (gen_frame_mem (SImode, stack_pointer_rtx), |
| reg); |
| RTX_FRAME_RELATED_P (tmp) = 1; |
| XVECEXP (dwarf, 0, dwarf_par_index++) = tmp; |
| |
| break; |
| } |
| |
| for (j = 1, i++; j < num_regs; i++) |
| if (mask & (1 << i)) |
| { |
| rtx reg = gen_rtx_REG (SImode, i); |
| rtx addr = plus_constant (Pmode, stack_pointer_rtx, 4 * j); |
| tmp = gen_rtx_SET (gen_frame_mem (SImode, addr), reg); |
| RTX_FRAME_RELATED_P (tmp) = 1; |
| XVECEXP (par, 0, j) = gen_rtx_USE (VOIDmode, reg); |
| XVECEXP (dwarf, 0, dwarf_par_index++) = tmp; |
| j++; |
| } |
| |
| par = emit_insn (par); |
| |
| tmp = gen_rtx_SET (stack_pointer_rtx, |
| plus_constant (Pmode, stack_pointer_rtx, -4 * num_regs)); |
| RTX_FRAME_RELATED_P (tmp) = 1; |
| XVECEXP (dwarf, 0, 0) = tmp; |
| |
| add_reg_note (par, REG_FRAME_RELATED_EXPR, dwarf); |
| RTX_FRAME_RELATED_P (par) = 1; |
| |
| return par; |
| } |
| |
| |
| /* Generate and emit an insn pattern that we will recognize as a pop_multi. |
| SAVED_REGS_MASK shows which registers need to be restored. |
| |
| Unfortunately, since this insn does not reflect very well the actual |
| semantics of the operation, we need to annotate the insn for the benefit |
| of DWARF2 frame unwind information. */ |
| |
| static void |
| emit_csky_regs_pop (unsigned long mask) |
| { |
| int num_regs = 0; |
| int i, j; |
| rtx par; |
| |
| for (i = 0; i < CSKY_NGPR_REGS; i++) |
| if (mask & (1 << i)) |
| num_regs++; |
| |
| /* The reg range for push is:r4-r11,r15-r17,r28. */ |
| gcc_assert (num_regs && num_regs <= 12); |
| |
| /* The first element is (return), |
| the second element is |
| (set (reg:SI 'first reg number') |
| (unspec:SI [(mem)] UNSPEC_PUSHPOP_MULT), |
| the rest elements is (use (reg:SI 'rest reg number')), |
| so the length should be number of register to be poped |
| plus one. */ |
| par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (num_regs + 1)); |
| |
| XVECEXP (par, 0, 0) = ret_rtx; |
| |
| for (i = 0; i < CSKY_NGPR_REGS; i++) |
| if (mask & (1 << i)) |
| { |
| rtx reg = gen_rtx_REG (SImode, i); |
| rtx addr = plus_constant (Pmode, stack_pointer_rtx, 4 * num_regs); |
| rtx tmp = gen_frame_mem (SImode, |
| gen_rtx_POST_MODIFY (Pmode, |
| stack_pointer_rtx, addr)); |
| XVECEXP (par, 0, 1) |
| = gen_rtx_SET (reg, |
| gen_rtx_UNSPEC (SImode, |
| gen_rtvec (1, tmp), |
| UNSPEC_PUSHPOP_MULT)); |
| break; |
| } |
| |
| for (j = 2, i++; j < (num_regs + 1); i++) |
| if (mask & (1 << i)) |
| { |
| rtx reg = gen_rtx_REG (SImode, i); |
| XVECEXP (par, 0, j) = gen_rtx_USE (VOIDmode, reg); |
| j++; |
| } |
| |
| par = emit_jump_insn (par); |
| } |
| |
| |
| /* Generate the function prologue. */ |
| |
| void |
| csky_expand_prologue (void) |
| { |
| rtx_insn *insn; |
| unsigned long func_type = get_csky_current_func_type (); |
| unsigned int reg_mask; |
| int reg_size; |
| |
| if (CSKY_FUNCTION_IS_NAKED (func_type)) |
| { |
| if (flag_stack_usage_info) |
| current_function_static_stack_size = 0; |
| return; |
| } |
| |
| csky_layout_stack_frame (); |
| reg_mask = cfun->machine->reg_mask; |
| reg_size = cfun->machine->reg_size; |
| |
| /* Adjust stack pointer past argument overflow area. */ |
| if (cfun->machine->arg_size != 0) |
| { |
| int offset = cfun->machine->arg_size; |
| expand_csky_stack_adjust (- offset); |
| |
| /* If we have a parameter passed partially in regs and partially |
| in memory, the registers will have been stored to memory already |
| in function.c. So we only need to copy varargs from registers |
| to stack. */ |
| if (cfun->machine->uses_anonymous_args) |
| { |
| int rn = CSKY_FIRST_PARM_REGNUM + CSKY_NPARM_REGS - 1; |
| for (offset -= 4; offset >= 0; offset -= 4, rn--) |
| { |
| rtx dst = gen_frame_mem (SImode, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| insn = emit_move_insn (dst, gen_rtx_REG (SImode, rn)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| } |
| |
| /* Push caller-saved registers to stack. */ |
| if (csky_can_use_pushpop (reg_mask)) |
| emit_csky_regs_push (reg_mask); |
| else if (reg_size) |
| { |
| int sreg = -1, ereg = -1; |
| bool stm_p = csky_can_use_ldstm (reg_mask, &sreg, &ereg); |
| int stm_regs = stm_p ? ereg - sreg + 1 : 0; |
| int stm_size = stm_regs * 4; |
| |
| /* First adjust the SP to the low end of the register save area. */ |
| expand_csky_stack_adjust (- reg_size); |
| |
| /* Emit individual register saves. Even if we are going to emit an |
| stm, we may need to save individual registers above that too. */ |
| if (reg_size > stm_size) |
| { |
| int offset = reg_size - 4; |
| int regno = 31; |
| for ( ; regno > ereg; regno--) |
| if (reg_mask & (1 << regno)) |
| { |
| rtx dst = gen_rtx_MEM (SImode, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| rtx insn = emit_insn (gen_movsi (dst, |
| gen_rtx_REG (SImode, regno))); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| if (offset == stm_size) |
| break; |
| offset -= 4; |
| } |
| } |
| |
| /* If possible, emit a stm to do a bulk store of sequential |
| registers to the stack. Note that it is an error in the ABI |
| documentation that it doesn't list stm as a valid prologue |
| instruction. */ |
| if (stm_p) |
| { |
| rtx par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (stm_regs)); |
| int regno, slot; |
| for (regno = sreg, slot = 0; regno <= ereg; regno++, slot++) |
| { |
| rtx reg = gen_rtx_REG (SImode, regno); |
| rtx addr = plus_constant (Pmode, stack_pointer_rtx, slot * 4); |
| rtx set = gen_rtx_SET (gen_frame_mem (SImode, addr), reg); |
| RTX_FRAME_RELATED_P (set) = 1; |
| XVECEXP (par, 0, slot) = set; |
| } |
| insn = emit_insn (par); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| } |
| |
| /* Initialize hard frame pointer, if necessary. It points at the base |
| of the register save area. */ |
| if (frame_pointer_needed) |
| { |
| insn = emit_insn (gen_movsi (hard_frame_pointer_rtx, stack_pointer_rtx)); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| |
| /* Reserve stack space for locals and outgoing args. */ |
| expand_csky_stack_adjust (- cfun->machine->reg_offset); |
| |
| /* Put the GOT address in reg_gb for PIC, using R13 as a scratch. |
| See section 4.7.1 in the ABI documentation, |
| "Function Prologue for PIC". */ |
| if (flag_pic && (reg_mask & (1 << PIC_OFFSET_TABLE_REGNUM))) |
| { |
| rtx l1 = gen_label_rtx (); |
| rtx grs_label = gen_rtx_LABEL_REF (SImode, l1); |
| rtx reg_gb = gen_rtx_REG (SImode, PIC_OFFSET_TABLE_REGNUM); |
| rtx reg_temp = gen_rtx_REG (SImode, 13); |
| |
| rtx tmp0_unspec = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, grs_label), |
| UNSPEC_PIC_SYMBOL_GOTPC_GRS); |
| rtx tmp1_unspec = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, grs_label), |
| UNSPEC_PIC_SYMBOL_GOTPC); |
| |
| emit_insn (gen_prologue_get_pc (tmp0_unspec)); |
| emit_move_insn (reg_temp, tmp1_unspec); |
| emit_insn (gen_addsi3 (reg_gb, reg_gb, reg_temp)); |
| } |
| |
| if (flag_stack_usage_info) |
| current_function_static_stack_size = cfun->machine->frame_size; |
| |
| if (!flag_sched_prolog) |
| emit_insn (gen_blockage ()); |
| } |
| |
| void |
| csky_expand_epilogue (void) |
| { |
| unsigned long func_type = get_csky_current_func_type (); |
| unsigned int reg_mask; |
| int reg_size; |
| int adjust; |
| rtx_insn *insn; |
| |
| if (!flag_sched_prolog) |
| emit_insn (gen_blockage ()); |
| |
| if (CSKY_FUNCTION_IS_NAKED (func_type)) |
| { |
| emit_jump_insn (gen_simple_return ()); |
| return; |
| } |
| |
| /* Get the frame information. */ |
| csky_layout_stack_frame (); |
| reg_mask = cfun->machine->reg_mask; |
| reg_size = cfun->machine->reg_size; |
| adjust = reg_size + cfun->machine->arg_size; |
| |
| /* Restore the SP to the base of the register save area. */ |
| if (frame_pointer_needed) |
| { |
| insn = emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| } |
| else |
| expand_csky_stack_adjust (cfun->machine->reg_offset); |
| |
| /* Restore the callee-saved registers. */ |
| if (csky_can_use_pushpop (reg_mask) |
| && cfun->machine->arg_size == 0 |
| && !CSKY_FUNCTION_IS_INTERRUPT (func_type) |
| && !crtl->calls_eh_return) |
| { |
| /* Pop includes an implicit return, so we are done. */ |
| emit_csky_regs_pop (reg_mask); |
| return; |
| } |
| else if (reg_size) |
| { |
| int sreg = -1, ereg = -1; |
| bool ldm_p = csky_can_use_ldstm (reg_mask, &sreg, &ereg); |
| int ldm_regs = ldm_p ? ereg - sreg + 1 : 0; |
| int ldm_size = ldm_regs * 4; |
| |
| /* Emit individual register loads. Even if we are going to emit an |
| ldm, we may need to load individual registers above that too. */ |
| if (reg_size > ldm_size) |
| { |
| int offset = reg_size - 4; |
| int regno = 31; |
| for ( ; regno > ereg; regno--) |
| if (reg_mask & (1 << regno)) |
| { |
| rtx src = gen_frame_mem (SImode, |
| plus_constant (Pmode, |
| stack_pointer_rtx, |
| offset)); |
| rtx reg = gen_rtx_REG (SImode, regno); |
| insn = emit_move_insn (reg, src); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| add_reg_note (insn, REG_CFA_RESTORE, reg); |
| if (offset == ldm_size) |
| break; |
| offset -= 4; |
| } |
| } |
| |
| /* If possible, emit a ldm to do a bulk load of sequential |
| registers from the stack. */ |
| if (ldm_p) |
| { |
| rtx par = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (ldm_regs)); |
| int regno, slot; |
| for (regno = sreg, slot = 0; regno <= ereg; regno++, slot++) |
| { |
| rtx reg = gen_rtx_REG (SImode, regno); |
| rtx addr = plus_constant (Pmode, stack_pointer_rtx, slot * 4); |
| rtx set = gen_rtx_SET (reg, gen_frame_mem (SImode, addr)); |
| XVECEXP (par, 0, slot) = set; |
| } |
| insn = emit_insn (par); |
| RTX_FRAME_RELATED_P (insn) = 1; |
| for (regno = sreg; regno <= ereg; regno++) |
| { |
| rtx reg = gen_rtx_REG (SImode, regno); |
| add_reg_note (insn, REG_CFA_RESTORE, reg); |
| } |
| } |
| } |
| |
| /* Emit the final stack pointer adjustment to deallocate the saved |
| registers and incoming argument area. */ |
| expand_csky_stack_adjust (adjust); |
| |
| /* Extra stack adjustment for exception handler return. */ |
| if (crtl->calls_eh_return) |
| emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, |
| EH_RETURN_STACKADJ_RTX)); |
| |
| /* Now we can return. */ |
| emit_jump_insn (gen_simple_return ()); |
| } |
| |
| |
| static void |
| csky_output_function_prologue (FILE *f) |
| { |
| unsigned long func_type = get_csky_current_func_type (); |
| |
| switch ((int) CSKY_FUNCTION_TYPE (func_type)) |
| { |
| default: |
| case CSKY_FT_NORMAL: |
| break; |
| case CSKY_FT_INTERRUPT: |
| { |
| asm_fprintf (f, "\t# Interrupt Service Routine.\n"); |
| asm_fprintf (f, "\tnie\n\tipush\n"); |
| break; |
| } |
| case CSKY_FT_FIQ: |
| asm_fprintf (f, "\t# Fast Interrupt Service Routine.\n"); |
| break; |
| case CSKY_FT_EXCEPTION: |
| asm_fprintf (f, "\t# CSKY Exception Handler.\n"); |
| break; |
| case CSKY_FT_NAKED: |
| asm_fprintf (f, "\t# Naked Function: prologue and epilogue \ |
| provided by programmer.\n"); |
| return; |
| } |
| |
| csky_layout_stack_frame (); |
| |
| /* Generate .stack_size function-name, size for callgraph; |
| the default stack size is 0. */ |
| if (TARGET_STACK_SIZE && cfun->machine->frame_size > 0) |
| { |
| gcc_assert (current_function_decl != NULL); |
| const char *func_name = |
| IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (current_function_decl)); |
| if (func_name[0] == '*') |
| asm_fprintf (f, "\t.stack_size %s, %d\n", |
| &func_name[1], cfun->machine->frame_size); |
| else |
| asm_fprintf (f, "\t.stack_size %s, %d\n", |
| func_name, cfun->machine->frame_size); |
| } |
| } |
| |
| |
| static void |
| csky_output_function_epilogue (FILE *file ATTRIBUTE_UNUSED) |
| { |
| |
| } |
| |
| |
| /* Helper for csky_eh_return splitter: store the call frame exception |
| handler address in lr. */ |
| void |
| csky_set_eh_return_address (rtx source, rtx scratch) |
| { |
| HOST_WIDE_INT delta = 0; |
| rtx basereg, addr; |
| unsigned int reg_mask; |
| |
| csky_layout_stack_frame (); |
| reg_mask = cfun->machine->reg_mask; |
| |
| if (reg_mask & (1 << CSKY_LR_REGNUM)) |
| { |
| /* Find LR in the stack frame. */ |
| int i = 0; |
| |
| if (frame_pointer_needed) |
| { |
| basereg = hard_frame_pointer_rtx; |
| delta = 0; |
| } |
| else |
| { |
| basereg = stack_pointer_rtx; |
| delta = cfun->machine->reg_offset; |
| } |
| |
| /* At this point, (basereg + delta) points at the low end of |
| the reg save area. Regs are saved sequentially from low |
| to high from this address. */ |
| for (i = 0; i < CSKY_LR_REGNUM; i++) |
| if (reg_mask & (1 << i)) |
| delta += 4; |
| |
| if ((CSKY_TARGET_ARCH (CK801) && delta >= CSKY_LD16_MAX_OFFSET (Pmode)) |
| || delta >= CSKY_LD32_MAX_OFFSET (Pmode)) |
| { |
| emit_insn (gen_movsi (scratch, GEN_INT (delta))); |
| emit_insn (gen_addsi3 (scratch, scratch, basereg)); |
| addr = scratch; |
| } |
| else |
| addr = plus_constant (Pmode, basereg, delta); |
| emit_move_insn (gen_frame_mem (Pmode, addr), source); |
| } |
| else |
| emit_move_insn (gen_rtx_REG (Pmode, CSKY_LR_REGNUM), source); |
| } |
| |
| /* Return TRUE if X references a SYMBOL_REF. */ |
| |
| bool |
| csky_symbol_mentioned_p (rtx x) |
| { |
| const char *fmt; |
| int i; |
| |
| if (GET_CODE (x) == SYMBOL_REF) |
| return true; |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (x)); |
| for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| int j; |
| |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| if (csky_symbol_mentioned_p (XVECEXP (x, i, j))) |
| return true; |
| } |
| else if (fmt[i] == 'e' && csky_symbol_mentioned_p (XEXP (x, i))) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Return TRUE if X references a LABEL_REF. */ |
| |
| bool |
| csky_label_mentioned_p (rtx x) |
| { |
| const char *fmt; |
| int i; |
| |
| if (GET_CODE (x) == LABEL_REF) |
| return true; |
| |
| fmt = GET_RTX_FORMAT (GET_CODE (x)); |
| for (i = GET_RTX_LENGTH (GET_CODE (x)) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'E') |
| { |
| int j; |
| |
| for (j = XVECLEN (x, i) - 1; j >= 0; j--) |
| if (csky_label_mentioned_p (XVECEXP (x, i, j))) |
| return true; |
| } |
| else if (fmt[i] == 'e' && csky_label_mentioned_p (XEXP (x, i))) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| static bool |
| tls_unspec_mentioned_p (rtx x) |
| { |
| switch (GET_CODE (x)) |
| { |
| case CONST: |
| return tls_unspec_mentioned_p (XEXP (x, 0)); |
| |
| case UNSPEC: |
| if (XINT (x, 1) == UNSPEC_TLS) |
| return true; |
| |
| /* Fall through. */ |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* Implement LEGITIMATE_PIC_OPERAND_P. */ |
| |
| bool |
| csky_legitimate_pic_operand_p (rtx x) |
| { |
| if (tls_unspec_mentioned_p (x)) |
| return true; |
| if (csky_symbol_mentioned_p (x) || csky_label_mentioned_p (x)) |
| return false; |
| return true; |
| } |
| |
| rtx |
| csky_legitimize_pic_address (rtx orig, rtx reg, bool gotrel_p) |
| { |
| rtx pic_reg = gen_rtx_REG (SImode, PIC_OFFSET_TABLE_REGNUM); |
| bool optimize_p = false; |
| |
| if (GET_CODE (orig) == SYMBOL_REF || GET_CODE (orig) == LABEL_REF) |
| { |
| rtx pic_ref, address, rtx_tmp; |
| rtx insn; |
| rtx pic_reg = gen_rtx_REG (SImode, PIC_OFFSET_TABLE_REGNUM); |
| int subregs = 0; |
| |
| if (reg == 0) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| subregs = 1; |
| } |
| |
| if (subregs) |
| address = gen_reg_rtx (Pmode); |
| else |
| address = reg; |
| |
| if (GET_CODE (orig) == SYMBOL_REF && !SYMBOL_REF_LOCAL_P (orig)) |
| { |
| /* When gotrel_p generate sym@GOT, otherwise generate sym@PLT. */ |
| rtx_tmp = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, orig), |
| (gotrel_p |
| ? UNSPEC_PIC_SYMBOL_GOT |
| : UNSPEC_PIC_SYMBOL_PLT)); |
| optimize_p = gotrel_p; |
| if (flag_pic != 1) |
| { |
| emit_move_insn (address, rtx_tmp); |
| rtx_tmp = gen_rtx_MULT (Pmode, address, GEN_INT (1)); |
| } |
| pic_ref = gen_const_mem (Pmode, |
| gen_rtx_PLUS (Pmode, pic_reg, rtx_tmp)); |
| } |
| else |
| { |
| /* bsr symbol */ |
| if (flag_pic == 1 && !gotrel_p) |
| { |
| pic_ref = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, orig), |
| UNSPEC_PIC_SYMBOL_BSR); |
| return pic_ref; |
| } |
| /* grs rx, symbol */ |
| else if (flag_pic == 1 && (GET_CODE (orig) == SYMBOL_REF) |
| && SYMBOL_REF_FUNCTION_P (orig)) |
| { |
| pic_ref = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, orig), |
| UNSPEC_PIC_SYMBOL_GRS); |
| return pic_ref; |
| } |
| /* lrw rx, symbol@GOTOFF; add rx, rx, gb */ |
| else |
| { |
| rtx_tmp = gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, orig), |
| UNSPEC_PIC_SYMBOL_GOTOFF); |
| emit_move_insn (address, rtx_tmp); |
| pic_ref = gen_rtx_PLUS (Pmode, address, pic_reg); |
| optimize_p = true; |
| } |
| } |
| |
| insn = emit_move_insn (reg, pic_ref); |
| /* Put a REG_EQUAL note on this insn, |
| so that it can be optimized by loop. */ |
| if (optimize_p) |
| set_unique_reg_note (insn, REG_EQUAL, orig); |
| |
| return reg; |
| } |
| else if (GET_CODE (orig) == CONST) |
| { |
| rtx base, offset; |
| |
| if (GET_CODE (XEXP (orig, 0)) == PLUS |
| && XEXP (XEXP (orig, 0), 1) == pic_reg) |
| return orig; |
| |
| if (reg == 0) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| } |
| |
| gcc_assert (GET_CODE (XEXP (orig, 0)) == PLUS); |
| |
| base = csky_legitimize_pic_address (XEXP (XEXP (orig, 0), 0), |
| reg, gotrel_p); |
| offset = csky_legitimize_pic_address (XEXP (XEXP (orig, 0), 1), |
| base == reg ? 0 : reg, gotrel_p); |
| |
| if (GET_CODE (offset) == CONST_INT) |
| return plus_constant (Pmode, base, INTVAL (offset)); |
| |
| return gen_rtx_PLUS (Pmode, base, offset); |
| } |
| |
| return orig; |
| } |
| |
| |
| /* Functions to output assembly code for a function call. */ |
| |
| char * |
| csky_output_call (rtx *operands, int index) |
| { |
| static char buffer[20]; |
| rtx addr = operands[index]; |
| |
| if (REG_P (addr)) |
| sprintf (buffer, "jsr\t%%%d", index); |
| else if (flag_pic && (GET_CODE (addr) == UNSPEC)) |
| sprintf (buffer, "bsr\t%%%d", index); |
| else |
| sprintf (buffer, "jbsr\t%%%d", index); |
| |
| return buffer; |
| } |
| |
| |
| /* Worker function for TARGET_ASM_TRAMPOLINE_TEMPLATE. |
| Output assembler code for a block containing the constant parts |
| of a trampoline, leaving space for the variable parts. |
| Note that STATIC_CHAIN_REGNUM is t1 (aka r12) on ck801 and |
| t1 (r13) otherwise. */ |
| |
| static void |
| csky_asm_trampoline_template (FILE *f) |
| { |
| if (CSKY_ISA_FEATURE (2E3)) |
| { |
| fprintf (f, "\tlrw\t%s, [.Lstatic_chain]\n", |
| reg_names[STATIC_CHAIN_REGNUM]); |
| fprintf (f, "\tjmpi\t[.Lfunc_address]\n"); |
| /* 2 32-bit insns = 8 bytes. */ |
| } |
| else if (CSKY_TARGET_ARCH (CK801)) |
| { |
| /* It's hard to provide general support for trampolines on this |
| core. We need a register other than the one holding the |
| static chain (r13) to hold the function pointer for the |
| indirect jump to it. But ck801 has such a limited register set |
| there is no other call-clobbered scratch register available -- in |
| particular, this core does not have r12, which we use for the |
| ck802 case below. If we use a callee-saved register like r4, |
| saving the old value on the stack screws up the stack frame |
| if there are overflow arguments pushed on the stack |
| by the caller. In theory we could test for that and handle |
| limited cases with parameters that all fit in r0-r3 with no |
| stack overflow, but punt for now. */ |
| sorry ("Nested function trampolines not supported on CK801."); |
| } |
| else |
| { |
| fprintf (f, "\tlrw\t%s, [.Lfunc_address]\n", |
| reg_names[CSKY_T1_REGNUM]); |
| fprintf (f, "\tlrw\t%s, [.Lstatic_chain]\n", |
| reg_names[STATIC_CHAIN_REGNUM]); |
| fprintf (f, "\tjmp\t%s\n", |
| reg_names[CSKY_T1_REGNUM]); |
| /* To align constant pool on a word boundary. */ |
| fprintf (f, "\t.align 2\n"); |
| /* 2 32-bit lrw insns + 16-bit jump + 16-bit pad = 12 bytes. */ |
| } |
| |
| fprintf (f, ".Lstatic_chain:\n"); |
| fprintf (f, "\t.long 0\n"); |
| fprintf (f, ".Lfunc_address:\n"); |
| fprintf (f, "\t.long 0\n"); |
| /* 2 words of constant pool = 8 bytes. */ |
| } |
| |
| /* Worker function for TARGET_TRAMPOLINE_INIT. */ |
| |
| static void |
| csky_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) |
| { |
| rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); |
| rtx mem, a_tramp; |
| int pool = TRAMPOLINE_SIZE - 8; |
| |
| emit_block_move (m_tramp, assemble_trampoline_template (), |
| GEN_INT (TRAMPOLINE_SIZE), BLOCK_OP_NORMAL); |
| |
| mem = adjust_address (m_tramp, SImode, pool); |
| emit_move_insn (mem, chain_value); |
| mem = adjust_address (m_tramp, SImode, pool + 4); |
| emit_move_insn (mem, fnaddr); |
| |
| a_tramp = XEXP (m_tramp, 0); |
| maybe_emit_call_builtin___clear_cache (a_tramp, |
| plus_constant (Pmode, |
| a_tramp, |
| TRAMPOLINE_SIZE)); |
| } |
| |
| |
| /* Emit a comparison insn for float values. |
| Return true if the comparison is inverted. */ |
| |
| bool |
| csky_emit_compare_float (enum rtx_code code, rtx op0, rtx op1) |
| { |
| rtx cc_reg = gen_rtx_REG (CCmode, CSKY_CC_REGNUM); |
| bool invert; |
| machine_mode mode = GET_MODE (op1); |
| |
| if (op1 != CONST0_RTX (mode)) |
| op1 = force_reg (mode, op1); |
| |
| invert = false; |
| |
| switch (code) |
| { |
| case EQ: |
| code = NE; |
| invert = true; |
| break; |
| case GT: |
| case LT: |
| case LE: |
| if (op1 == CONST0_RTX (mode) && (CSKY_ISA_FEATURE_GET(fpv2_sf) |
| || CSKY_ISA_FEATURE_GET(fpv2_df) |
| || CSKY_ISA_FEATURE_GET(fpv2_divd))) |
| op1 = force_reg (mode, op1); |
| break; |
| case ORDERED: |
| code = UNORDERED; |
| invert = true; |
| break; |
| |
| default: |
| break; |
| } |
| |
| emit_insn (gen_rtx_SET (cc_reg, gen_rtx_fmt_ee (code, CCmode, op0, op1))); |
| |
| return invert; |
| } |
| |
| /* Support for the Q or W memory constraint. Returns true if OP is a MEM |
| RTX with an address consisting of base + index or base + displacement. */ |
| |
| bool |
| csky_valid_mem_constraint_operand (rtx op, const char *constraint) |
| { |
| struct csky_address addr; |
| |
| if (GET_CODE (op) != MEM) |
| return false; |
| |
| if (!decompose_csky_address (XEXP (op, 0), &addr)) |
| return false; |
| |
| /* Verify base register. */ |
| if (!is_csky_address_register_rtx_p (addr.base, 0)) |
| return false; |
| |
| /* Verify index operand. */ |
| if (addr.index && (constraint[0] == 'Q' || constraint[0] == 'W')) |
| { |
| if (!is_csky_address_register_rtx_p (addr.index, 0)) |
| return false; |
| |
| if (addr.scale == 1 || addr.scale == 2 || addr.scale == 4 |
| || addr.scale == 8) |
| return true; |
| |
| return false; |
| } |
| /* Verify disp operand. */ |
| else if (addr.disp && constraint[0] == 'Q') |
| { |
| rtx disp = addr.disp; |
| |
| if (!CONST_INT_P (disp)) |
| return false; |
| |
| if (((unsigned) INTVAL (disp) % 4) == 0 |
| && (unsigned) INTVAL (disp) <= (unsigned) 1020) |
| return true; |
| |
| return false; |
| } |
| else if (constraint[0] == 'Q') |
| /* Single reg is valid for 'Q'. */ |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Returns the (interrupt) function type of the current |
| function, or CSKY_FT_UNKNOWN if the type cannot be determined. */ |
| |
| static unsigned long |
| csky_isr_value (tree argument) |
| { |
| const isr_attribute_entry *ptr; |
| const char *arg; |
| |
| /* No argument - default to IRQ. */ |
| if (argument == NULL_TREE) |
| return CSKY_FT_ISR; |
| |
| /* Get the value of the argument. */ |
| if (TREE_VALUE (argument) == NULL_TREE |
| || TREE_CODE (TREE_VALUE (argument)) != STRING_CST) |
| return CSKY_FT_UNKNOWN; |
| |
| arg = TREE_STRING_POINTER (TREE_VALUE (argument)); |
| |
| /* Check it against the list of known arguments. */ |
| for (ptr = isr_attribute_map; ptr->arg != NULL; ptr++) |
| if (strcmp (arg, ptr->arg) == 0) |
| return ptr->return_value; |
| |
| /* An unrecognized interrupt type. */ |
| return CSKY_FT_UNKNOWN; |
| } |
| |
| /* Handle an attribute requiring a FUNCTION_DECL; |
| arguments as in struct attribute_spec.handler. */ |
| |
| static tree |
| csky_handle_fndecl_attribute (tree *node, tree name, tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, bool *no_add_attrs) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (OPT_Wattributes, "%qE attribute only applies to functions", |
| name); |
| *no_add_attrs = true; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| /* Handle an "interrupt" or "isr" attribute; |
| arguments as in struct attribute_spec.handler. */ |
| |
| static tree |
| csky_handle_isr_attribute (tree *node, tree name, tree args, int flags, |
| bool *no_add_attrs) |
| { |
| |
| if (!TARGET_ISTACK) |
| { |
| warning (OPT_Wattributes, "%qE attribute ignored without %<-mistack%>", |
| name); |
| *no_add_attrs = true; |
| return NULL_TREE; |
| } |
| |
| if (DECL_P (*node)) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (OPT_Wattributes, "%qE attribute only applies to functions", |
| name); |
| *no_add_attrs = true; |
| } |
| } |
| else |
| { |
| if (TREE_CODE (*node) == FUNCTION_TYPE |
| || TREE_CODE (*node) == METHOD_TYPE) |
| { |
| if (csky_isr_value (args) == CSKY_FT_UNKNOWN) |
| { |
| warning (OPT_Wattributes, "%qE attribute ignored", name); |
| *no_add_attrs = true; |
| } |
| } |
| else if (TREE_CODE (*node) == POINTER_TYPE |
| && (TREE_CODE (TREE_TYPE (*node)) == FUNCTION_TYPE |
| || TREE_CODE (TREE_TYPE (*node)) == METHOD_TYPE) |
| && csky_isr_value (args) != CSKY_FT_UNKNOWN) |
| { |
| *node = build_variant_type_copy (*node); |
| TREE_TYPE (*node) = build_type_attribute_variant (TREE_TYPE (*node), |
| tree_cons (name, args, TYPE_ATTRIBUTES (TREE_TYPE (*node)))); |
| *no_add_attrs = true; |
| } |
| else if (flags & ((int)ATTR_FLAG_DECL_NEXT |
| | (int)ATTR_FLAG_FUNCTION_NEXT |
| | (int)ATTR_FLAG_ARRAY_NEXT)) |
| { |
| *no_add_attrs = true; |
| return tree_cons (name, args, NULL_TREE); |
| } |
| else |
| warning (OPT_Wattributes, "%qE attribute ignored", name); |
| } |
| return NULL_TREE; |
| } |
| |
| |
| /* Implement TARGET_REGISTER_MOVE_COST: compute extra cost of moving data |
| between one register class and another. */ |
| |
| int |
| csky_register_move_cost (machine_mode mode ATTRIBUTE_UNUSED, |
| reg_class_t from, reg_class_t to) |
| { |
| #define GR_REG_CLASS_P(CLASS) \ |
| ((CLASS) == GENERAL_REGS || (CLASS) == MINI_REGS || (CLASS) == SP_REGS \ |
| || (CLASS) == LOW_REGS) |
| |
| #define HILO_REG_CLASS_P(CLASS) \ |
| ((CLASS) == HILO_REGS) |
| |
| #define V_REG_CLASS_P(CLASS) \ |
| ((CLASS) == V_REGS) |
| |
| if (V_REG_CLASS_P (from) && V_REG_CLASS_P (to)) |
| return 2; |
| |
| if ((V_REG_CLASS_P (from) && GR_REG_CLASS_P (to)) |
| || (GR_REG_CLASS_P (from) && V_REG_CLASS_P (to))) |
| return 6; |
| |
| if ((HILO_REG_CLASS_P (from) && GR_REG_CLASS_P (to)) |
| || (GR_REG_CLASS_P (from) && HILO_REG_CLASS_P (to))) |
| return 16; |
| |
| if (HILO_REG_CLASS_P (from) && HILO_REG_CLASS_P (to)) |
| return 32; |
| |
| if ((HILO_REG_CLASS_P (from) && V_REG_CLASS_P (to)) |
| || (V_REG_CLASS_P (from) && HILO_REG_CLASS_P (to))) |
| return 64; |
| |
| return 2; |
| } |
| |
| |
| /* Implement TARGET_MEMORY_MOVE_COST: compute the cost of moving data |
| between registers and memory. */ |
| |
| int |
| csky_memory_move_cost (machine_mode mode, reg_class_t rclass, |
| bool in) |
| { |
| return (4 + memory_move_secondary_cost (mode, rclass, in)); |
| } |
| |
| |
| /* TARGET_RTX_COSTS helper for ck801/ck802. */ |
| |
| static bool |
| ck802_ck801_rtx_costs (rtx x, int code, int outer_code, int *total, |
| bool speed) |
| { |
| machine_mode mode = GET_MODE (x); |
| switch (code) |
| { |
| /* Accessing memory costs quite a lot for first word; */ |
| case MEM: |
| *total = COSTS_N_INSNS (1 + CSKY_NUM_REGS (mode)); |
| return false; |
| case DIV: |
| case UDIV: |
| case MOD: |
| case UMOD: |
| *total = 100; |
| return true; |
| |
| case ROTATE: |
| case ROTATERT: |
| case ASHIFT: |
| case LSHIFTRT: |
| case ASHIFTRT: |
| if (speed) |
| *total = 2; |
| else |
| *total = COSTS_N_INSNS (1); |
| return false; |
| |
| case MINUS: |
| case PLUS: |
| *total = COSTS_N_INSNS (CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case AND: |
| { |
| enum rtx_code subcode = GET_CODE (XEXP (x, 1)); |
| |
| /* If subcode is "not", we'll try to combine it into e.g. "andn" |
| instruction, so give AND itself zero cost. */ |
| if (subcode == NOT) |
| { |
| *total = 0; |
| return false; |
| } |
| } |
| /* Fall through. */ |
| case XOR: |
| case IOR: |
| *total = COSTS_N_INSNS (CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case MULT: |
| /* FIXME: is ixw supported on ck801/ck802? */ |
| /* We can use "ix.h/w" insn to replace multiply by 2 or 4. |
| "ix.h/w" is a 32-bit insn, so let its cost be a little less than |
| "mult" insn. */ |
| if (REG_P (XEXP (x, 0)) && CONST_INT_P (XEXP (x, 1))) |
| { |
| unsigned HOST_WIDE_INT m |
| = (unsigned HOST_WIDE_INT) (INTVAL (XEXP (x, 1))); |
| if ((m == 2 || m == 4) && outer_code == PLUS) |
| { |
| *total = 2; |
| return true; |
| } |
| else |
| { |
| /* Because mult is relatively slower than other operations, |
| we try to use other insns when optimizing for speed. |
| When optimizing for size, give it lower cost. */ |
| if (speed) |
| { |
| *total = COSTS_N_INSNS (10 * CSKY_NUM_REGS (mode)); |
| return true; |
| } |
| int cycle = 0; |
| while (m) |
| { |
| m >>= 2; |
| cycle++; |
| } |
| *total = COSTS_N_INSNS (1) + cycle; |
| return false; |
| } |
| } |
| if (!speed) |
| *total = COSTS_N_INSNS (1); |
| return false; |
| |
| case NEG: |
| /* Usually, we use subtract from 0 to substitute for neg, and |
| it costs 1 extra insn to move 0 to a register. */ |
| *total = COSTS_N_INSNS (2 * CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case NOT: |
| *total = COSTS_N_INSNS (CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case COMPARE: |
| *total = COSTS_N_INSNS (1); |
| return false; |
| |
| case SIGN_EXTEND: |
| case ZERO_EXTEND: |
| *total = COSTS_N_INSNS (CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case SIGN_EXTRACT: |
| case ZERO_EXTRACT: |
| if (REG_P (XEXP (x, 0)) |
| && CONST_INT_P (XEXP (x, 1)) |
| && CONST_INT_P (XEXP (x, 2)) |
| && INTVAL (XEXP (x, 1)) == 8 |
| && INTVAL (XEXP (x, 2)) % 8 == 0) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| *total = COSTS_N_INSNS (CSKY_NUM_REGS (mode)); |
| return false; |
| |
| case CONST_INT: |
| { |
| unsigned HOST_WIDE_INT t = (unsigned HOST_WIDE_INT) (INTVAL (x)); |
| |
| if (outer_code == COMPARE) |
| { |
| if (t < 0x10000) |
| *total = 0; |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| else if (outer_code == AND || outer_code == IOR || outer_code == XOR) |
| { |
| /* "andi,xori,ori" are 32-bit insns, so let it cost a |
| little more. */ |
| if (t < 0x1000) |
| { |
| /* Try replacing "andi" by "sextb/h", so let it cost more. */ |
| if (outer_code == AND && (t == 0xff || t == 0xffff)) |
| { |
| *total = 8; |
| return true; |
| } |
| *total = 2; |
| } |
| else if (t < 0x10000) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| else if (outer_code == PLUS || outer_code == MINUS) |
| { |
| /* "addi/subi rx,ry,imm", if imm<9, it is more often a |
| 16-bit insn. If imm>=9, use "movi" insn; it's probably |
| less than "addi/subi". */ |
| if (t < 9) |
| *total = 0; |
| else if (t < 0x1000) |
| *total = 2; |
| else if (t < 0x10000) |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| else if (outer_code == ROTATE || outer_code == ROTATERT |
| || outer_code == LSHIFTRT || outer_code == ASHIFTRT |
| || outer_code == ASHIFT) |
| { |
| if (t < 32) |
| *total = 0; |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| else |
| { |
| if (t < 0x10000) |
| if (outer_code == SET && t < 256) |
| *total = 0; |
| else |
| *total = COSTS_N_INSNS (1); |
| else |
| *total = COSTS_N_INSNS (2); |
| } |
| } |
| return true; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = COSTS_N_INSNS (3); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* TARGET_RTX_COSTS helper for ck803. */ |
| |
| static bool |
| ck803_rtx_costs (rtx x, int code, int outer_code ATTRIBUTE_UNUSED, |
| int *total, bool speed ATTRIBUTE_UNUSED) |
| { |
| switch (code) |
| { |
| case SET: |
| if (MEM_P (XEXP (x, 1))) |
| { |
| struct csky_address op1; |
| bool address_valid |
| = decompose_csky_address (XEXP (XEXP (x, 1), 0), &op1); |
| if (op1.index) |
| { |
| *total = COSTS_N_INSNS (3); |
| return true; |
| } |
| else if (address_valid) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| } |
| if (REG_P (XEXP (x, 0)) && (GET_CODE (XEXP (x, 1)) == PLUS)) |
| { |
| rtx sub_exp = XEXP (x, 1); |
| if (REG_P (XEXP (sub_exp, 0)) && REG_P (XEXP (sub_exp, 1))) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| } |
| return false; |
| case MULT: |
| if (REG_P (XEXP (x, 0)) && CONST_INT_P (XEXP (x, 1))) |
| { |
| HOST_WIDE_INT val = INTVAL (XEXP (x, 1)); |
| if (val % 2 == 0 && val < 0xffffffff && val > 0) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| } |
| return false; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = COSTS_N_INSNS (3); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /* TARGET_RTX_COSTS helper for ck807/ck810 arches. */ |
| |
| static bool |
| ck807_ck810_rtx_costs (rtx x, int code, |
| int outer_code ATTRIBUTE_UNUSED, |
| int *total, bool speed ATTRIBUTE_UNUSED) |
| { |
| switch (code) |
| { |
| case MULT: |
| if (REG_P (XEXP (x, 0)) && CONST_INT_P (XEXP (x, 1))) |
| { |
| HOST_WIDE_INT val = INTVAL (XEXP (x, 1)); |
| if (val % 2 == 0 && val < 0xffffffff && val > 0) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| } |
| return false; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = COSTS_N_INSNS (3); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /* TARGET_RTX_COSTS helper for ck860 arches. */ |
| |
| static bool |
| ck860_rtx_costs (rtx x, int code, machine_mode mode, |
| int outer_code ATTRIBUTE_UNUSED, |
| int *total, bool speed ATTRIBUTE_UNUSED) |
| { |
| switch (code) |
| { |
| case PLUS: |
| /* The costs of mula is 1 more than mult. */ |
| if (GET_CODE (XEXP (x, 0)) == MULT && REG_P (XEXP (x, 1)) && speed) |
| { |
| rtx mul_op0 = XEXP (XEXP (x, 0), 0); |
| rtx mul_op1 = XEXP (XEXP (x, 0), 1); |
| if (REG_P (mul_op0) && REG_P (mul_op1)) |
| { |
| *total = COSTS_N_INSNS (1); |
| *total += rtx_cost (XEXP (x, 0), mode, |
| (enum rtx_code) code, 0, speed); |
| return true; |
| } |
| } |
| return false; |
| case MULT: |
| if (REG_P (XEXP (x, 0)) && CONST_INT_P (XEXP (x, 1))) |
| { |
| HOST_WIDE_INT val = INTVAL (XEXP (x, 1)); |
| if (val % 2 == 0 && val < 0xffffffff && val > 0) |
| { |
| *total = COSTS_N_INSNS (1); |
| return true; |
| } |
| } |
| return false; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = COSTS_N_INSNS (3); |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| |
| /* Implement TARGET_RTX_COSTS, to compute a (partial) cost for rtx X. |
| Return true if the complete cost has been computed, and false if |
| subexpressions should be scanned. In either case, *TOTAL contains |
| the cost result. */ |
| |
| static bool |
| csky_rtx_costs (rtx x, machine_mode mode ATTRIBUTE_UNUSED, int outer_code, |
| int opno ATTRIBUTE_UNUSED, int *total, bool speed) |
| { |
| int code = GET_CODE (x); |
| |
| if (CSKY_TARGET_ARCH (CK802) || CSKY_TARGET_ARCH (CK801)) |
| return ck802_ck801_rtx_costs (x, code, outer_code, total, speed); |
| else if (CSKY_TARGET_ARCH (CK803)) |
| return ck803_rtx_costs (x, code, outer_code, total, speed); |
| else if (CSKY_TARGET_ARCH (CK807) || CSKY_TARGET_ARCH (CK810)) |
| return ck807_ck810_rtx_costs (x, code, outer_code, total, speed); |
| else if (CSKY_TARGET_ARCH (CK860)) |
| return ck860_rtx_costs (x, code, mode, outer_code, total, speed); |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Emit assembly code for CASESI. This is only used on CK801 and CK802 |
| when optimizing for size, and uses helper functions in libgcc instead |
| of doing the control transfer inline. */ |
| |
| const char * |
| csky_output_casesi (rtx *operands) |
| { |
| rtx diff_vec = PATTERN (NEXT_INSN (as_a <rtx_insn *> (operands[0]))); |
| |
| gcc_assert (GET_CODE (diff_vec) == ADDR_DIFF_VEC); |
| |
| switch (GET_MODE (diff_vec)) |
| { |
| case E_QImode: |
| return (ADDR_DIFF_VEC_FLAGS (diff_vec).offset_unsigned |
| ? "jbsr\t___gnu_csky_case_uqi" |
| : "jbsr\t___gnu_csky_case_sqi"); |
| case E_HImode: |
| return (ADDR_DIFF_VEC_FLAGS (diff_vec).offset_unsigned |
| ? "jbsr\t___gnu_csky_case_uhi" |
| : "jbsr\t___gnu_csky_case_shi"); |
| case E_SImode: |
| return "jbsr\t___gnu_csky_case_si"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Implement TARGET_SCHED_ISSUE_RATE. Lookup the issue rate in the |
| per-core tuning structs. */ |
| static int |
| csky_sched_issue_rate (void) |
| { |
| if (CSKY_TARGET_ARCH (CK810)) |
| return 2; |
| else |
| return 1; |
| } |
| |
| |
| /* This function implements the target macro TARGET_SCHED_ADJUST_COST. |
| It corrects the value of COST based on the relationship between |
| INSN and DEP through the dependence DEP_TYPE. It returns the new |
| value. */ |
| |
| static int |
| csky_sched_adjust_cost (rtx_insn *insn, |
| int dep_type, |
| rtx_insn *dep, |
| int cost, |
| unsigned int dw ATTRIBUTE_UNUSED) |
| { |
| if (dep_type == REG_DEP_ANTI || dep_type == REG_DEP_OUTPUT) |
| return 0; |
| /* The REG_DEP_TRUE situation. */ |
| else if (recog_memoized (insn) >= 0 && recog_memoized (dep) >= 0) |
| { |
| enum attr_type insn_type = get_attr_type (insn); |
| if (CSKY_TARGET_ARCH (CK803)) |
| { |
| /* The ld or st's base reg depends on the pre insn, |
| it will delay 1 cycle. */ |
| if (insn_type == TYPE_LOAD || insn_type == TYPE_STORE) |
| { |
| rtx pattern = PATTERN (insn); |
| |
| gcc_assert (GET_CODE (pattern) == SET); |
| rtx addr = (insn_type == TYPE_LOAD |
| ? SET_SRC (pattern) : SET_DEST (pattern)); |
| |
| enum rtx_code code = GET_CODE (addr); |
| if (code == ZERO_EXTEND || code == SIGN_EXTEND) |
| addr = XEXP (addr, 0); |
| gcc_assert (GET_CODE (addr) == MEM); |
| |
| rtx base = XEXP (addr, 0); |
| rtx reg = NULL_RTX; |
| if (REG_P (base)) |
| reg = base; |
| if (GET_CODE (base) == PLUS |
| && GET_CODE (XEXP (base, 0)) == REG) |
| reg = XEXP (base, 0); |
| if ((reg != NULL_RTX) && reg_set_p (reg, PATTERN (dep))) |
| return 2; |
| } |
| } |
| else if (CSKY_TARGET_ARCH (CK802)) |
| { |
| if ((insn_type == TYPE_CALL_JSR || insn_type == TYPE_BRANCH_JMP) |
| && get_attr_type (dep) != TYPE_LOAD) |
| return 1; |
| |
| if (insn_type == TYPE_LOAD || insn_type == TYPE_STORE) |
| { |
| rtx pattern = PATTERN (insn); |
| |
| gcc_assert (GET_CODE (pattern) == SET); |
| |
| rtx addr = (insn_type == TYPE_LOAD |
| ? SET_SRC (pattern) : SET_DEST (pattern)); |
| |
| enum rtx_code code = GET_CODE (addr); |
| if (code == ZERO_EXTEND || code == SIGN_EXTEND) |
| addr = XEXP (addr, 0); |
| gcc_assert (GET_CODE (addr) == MEM); |
| |
| rtx base = XEXP (addr, 0); |
| rtx reg = NULL_RTX; |
| if (REG_P (base)) |
| reg = base; |
| if (GET_CODE (base) == PLUS |
| && GET_CODE (XEXP (base, 0)) == REG) |
| reg = XEXP (base, 0); |
| if ((reg != NULL_RTX) && reg_set_p (reg, PATTERN (dep)) |
| && get_attr_type (dep) != TYPE_LOAD) |
| return 1; |
| |
| if (insn_type == TYPE_STORE |
| && reg_referenced_p (SET_SRC (pattern), PATTERN (dep))) |
| return 1; |
| } |
| } |
| } |
| return cost; |
| } |
| |
| static bool |
| csky_warn_func_return (tree decl) |
| { |
| /* Naked functions are implemented entirely in assembly, including the |
| return sequence, so suppress warnings about this. */ |
| return lookup_attribute ("naked", DECL_ATTRIBUTES (decl)) == NULL_TREE; |
| } |
| |
| |
| /* Implement TARGET_RETURN_IN_MEMORY to decide whether TYPE should be |
| returned in memory (true) or in a register (false). |
| FNTYPE is the type of the function making the call. */ |
| |
| static bool |
| csky_return_in_memory (const_tree type, |
| const_tree fntype ATTRIBUTE_UNUSED) |
| { |
| const HOST_WIDE_INT size = int_size_in_bytes (type); |
| return (size == -1 || size > 2 * UNITS_PER_WORD); |
| } |
| |
| |
| /* Implement TARGET_DWARF_REGISTER_SPAN. |
| Dwarf models VFP registers as 64-bit or 128-bit registers default. |
| GCC models tham as 32-bit registers, so we need to describe this to |
| the DWARF generation code. Other registers can use the default. */ |
| |
| static rtx |
| csky_dwarf_register_span (rtx rtl) |
| { |
| machine_mode mode; |
| unsigned regno; |
| rtx parts[16]; |
| int nregs; |
| int i; |
| |
| regno = REGNO (rtl); |
| if (!CSKY_VREG_P (regno)) |
| return NULL_RTX; |
| |
| if (CSKY_VREG_HI_P (regno)) |
| regno += 16; |
| |
| mode = GET_MODE (rtl); |
| if (GET_MODE_SIZE (mode) < 8) |
| return NULL_RTX; |
| |
| |
| if (TARGET_SINGLE_FPU) |
| { |
| nregs = GET_MODE_SIZE (mode) / 4; |
| for (i = 0; i < nregs; i += 2) |
| if (TARGET_BIG_ENDIAN) |
| { |
| parts[i] = gen_rtx_REG (SImode, regno + i + 1); |
| parts[i + 1] = gen_rtx_REG (SImode, regno + i); |
| } |
| else |
| { |
| parts[i] = gen_rtx_REG (SImode, regno + i); |
| parts[i + 1] = gen_rtx_REG (SImode, regno + i + 1); |
| } |
| } |
| else |
| { |
| /* FIXME: dwarf2 considers all general registers to be the same |
| as the CPU bit width. Transform the 64-bit FPU registers to |
| 32 bits here, and we will modify the unwind processing to |
| fit CSKY architecture later. */ |
| nregs = GET_MODE_SIZE (mode) / 4; |
| for (i = 0; i < nregs; i += 2) |
| if (TARGET_BIG_ENDIAN) |
| { |
| parts[i] = gen_rtx_REG (SImode, regno + i - 16); |
| parts[i + 1] = gen_rtx_REG (SImode, regno + i); |
| } |
| else |
| { |
| parts[i] = gen_rtx_REG (SImode, regno + i); |
| parts[i + 1] = gen_rtx_REG (SImode, regno + i - 16); |
| } |
| } |
| |
| return gen_rtx_PARALLEL (VOIDmode, gen_rtvec_v (nregs , parts)); |
| } |
| |
| /* Implement TARGET_INIT_LIBFUNCS. */ |
| |
| static void |
| csky_init_libfuncs (void) |
| { |
| if (TARGET_CSKY_LINUX) |
| init_sync_libfuncs (UNITS_PER_WORD); |
| if (!TARGET_LIBCCRT) |
| return; |
| |
| #define CSKY_GCC_SYM(sym) "__csky_ccrt_" # sym |
| |
| /* int */ |
| |
| /* Arithmetic functions */ |
| set_optab_libfunc (ashl_optab, DImode, CSKY_GCC_SYM (ashldi3)); |
| set_optab_libfunc (ashr_optab, DImode, CSKY_GCC_SYM (ashrdi3)); |
| set_optab_libfunc (sdiv_optab, SImode, CSKY_GCC_SYM (divsi3)); |
| set_optab_libfunc (sdiv_optab, DImode, CSKY_GCC_SYM (divdi3)); |
| set_optab_libfunc (lshr_optab, DImode, CSKY_GCC_SYM (lshrdi3)); |
| set_optab_libfunc (smod_optab, SImode, CSKY_GCC_SYM (modsi3)); |
| set_optab_libfunc (smod_optab, DImode, CSKY_GCC_SYM (moddi3)); |
| set_optab_libfunc (smul_optab, DImode, CSKY_GCC_SYM (muldi3)); |
| set_optab_libfunc (neg_optab, DImode, CSKY_GCC_SYM (negdi2)); |
| set_optab_libfunc (udiv_optab, SImode, CSKY_GCC_SYM (udivsi3)); |
| set_optab_libfunc (udiv_optab, DImode, CSKY_GCC_SYM (udivdi3)); |
| set_optab_libfunc (udivmod_optab, DImode, CSKY_GCC_SYM (udivmoddi4)); |
| set_optab_libfunc (umod_optab, SImode, CSKY_GCC_SYM (umodsi3)); |
| set_optab_libfunc (umod_optab, DImode, CSKY_GCC_SYM (umoddi3)); |
| |
| /* Comparison functions */ |
| set_optab_libfunc (cmp_optab, DImode, CSKY_GCC_SYM (cmpdi2)); |
| set_optab_libfunc (ucmp_optab, DImode, CSKY_GCC_SYM (ucmpdi2)); |
| |
| /* Trapping arithmetic functions */ |
| set_optab_libfunc (absv_optab, SImode, CSKY_GCC_SYM (absvsi2)); |
| set_optab_libfunc (absv_optab, DImode, CSKY_GCC_SYM (absvdi2)); |
| set_optab_libfunc (addv_optab, SImode, CSKY_GCC_SYM (addvsi3)); |
| set_optab_libfunc (addv_optab, DImode, CSKY_GCC_SYM (addvdi3)); |
| set_optab_libfunc (smulv_optab, SImode, CSKY_GCC_SYM (mulvsi3)); |
| set_optab_libfunc (smulv_optab, DImode, CSKY_GCC_SYM (mulvdi3)); |
| set_optab_libfunc (negv_optab, SImode, CSKY_GCC_SYM (negvsi2)); |
| set_optab_libfunc (negv_optab, DImode, CSKY_GCC_SYM (negvdi2)); |
| set_optab_libfunc (subv_optab, SImode, CSKY_GCC_SYM (subvsi3)); |
| set_optab_libfunc (subv_optab, DImode, CSKY_GCC_SYM (subvdi3)); |
| |
| /* Bit operations */ |
| set_optab_libfunc (clz_optab, SImode, CSKY_GCC_SYM (clzsi2)); |
| set_optab_libfunc (clz_optab, DImode, CSKY_GCC_SYM (clzdi2)); |
| set_optab_libfunc (ctz_optab, SImode, CSKY_GCC_SYM (ctzsi2)); |
| set_optab_libfunc (ctz_optab, DImode, CSKY_GCC_SYM (ctzdi2)); |
| set_optab_libfunc (ffs_optab, DImode, CSKY_GCC_SYM (ffsdi2)); |
| set_optab_libfunc (parity_optab, SImode, CSKY_GCC_SYM (paritysi2)); |
| set_optab_libfunc (parity_optab, DImode, CSKY_GCC_SYM (paritydi2)); |
| set_optab_libfunc (popcount_optab,SImode, CSKY_GCC_SYM (popcountsi2)); |
| set_optab_libfunc (popcount_optab,DImode, CSKY_GCC_SYM (popcountdi2)); |
| set_optab_libfunc (bswap_optab, SImode, CSKY_GCC_SYM (bswapsi2)); |
| set_optab_libfunc (bswap_optab, DImode, CSKY_GCC_SYM (bswapdi2)); |
| |
| /* float */ |
| |
| /* Arithmetic functions */ |
| set_optab_libfunc (add_optab, SFmode, CSKY_GCC_SYM (addsf3)); |
| set_optab_libfunc (add_optab, DFmode, CSKY_GCC_SYM (adddf3)); |
| set_optab_libfunc (sub_optab, SFmode, CSKY_GCC_SYM (subsf3)); |
| set_optab_libfunc (sub_optab, DFmode, CSKY_GCC_SYM (subdf3)); |
| set_optab_libfunc (smul_optab, SFmode, CSKY_GCC_SYM (mulsf3)); |
| set_optab_libfunc (smul_optab, DFmode, CSKY_GCC_SYM (muldf3)); |
| set_optab_libfunc (sdiv_optab, SFmode, CSKY_GCC_SYM (divsf3)); |
| set_optab_libfunc (sdiv_optab, DFmode, CSKY_GCC_SYM (divdf3)); |
| set_optab_libfunc (neg_optab, SFmode, CSKY_GCC_SYM (negsf2)); |
| set_optab_libfunc (neg_optab, DFmode, CSKY_GCC_SYM (negdf2)); |
| |
| /* Conversion functions */ |
| set_conv_libfunc (sext_optab, DFmode, SFmode, CSKY_GCC_SYM (extendsfdf2)); |
| set_conv_libfunc (trunc_optab, SFmode, DFmode, CSKY_GCC_SYM (truncdfsf2)); |
| set_conv_libfunc (sfix_optab, SImode, SFmode, CSKY_GCC_SYM (fixsfsi)); |
| set_conv_libfunc (sfix_optab, SImode, DFmode, CSKY_GCC_SYM (fixdfsi)); |
| set_conv_libfunc (sfix_optab, DImode, SFmode, CSKY_GCC_SYM (fixsfdi)); |
| set_conv_libfunc (sfix_optab, DImode, DFmode, CSKY_GCC_SYM (fixdfdi)); |
| set_conv_libfunc (ufix_optab, SImode, SFmode, CSKY_GCC_SYM (fixunssfsi)); |
| set_conv_libfunc (ufix_optab, SImode, DFmode, CSKY_GCC_SYM (fixunsdfsi)); |
| set_conv_libfunc (ufix_optab, DImode, SFmode, CSKY_GCC_SYM (fixunssfdi)); |
| set_conv_libfunc (ufix_optab, DImode, DFmode, CSKY_GCC_SYM (fixunsdfdi)); |
| set_conv_libfunc (sfloat_optab, SFmode, SImode, CSKY_GCC_SYM (floatsisf)); |
| set_conv_libfunc (sfloat_optab, DFmode, SImode, CSKY_GCC_SYM (floatsidf)); |
| set_conv_libfunc (sfloat_optab, SFmode, DImode, CSKY_GCC_SYM (floatdisf)); |
| set_conv_libfunc (sfloat_optab, DFmode, DImode, CSKY_GCC_SYM (floatdidf)); |
| set_conv_libfunc (ufloat_optab, SFmode, SImode, CSKY_GCC_SYM (floatunsisf)); |
| set_conv_libfunc (ufloat_optab, DFmode, SImode, CSKY_GCC_SYM (floatunsidf)); |
| set_conv_libfunc (ufloat_optab, SFmode, DImode, CSKY_GCC_SYM (floatundisf)); |
| set_conv_libfunc (ufloat_optab, DFmode, DImode, CSKY_GCC_SYM (floatundidf)); |
| |
| /* Comparison functions */ |
| set_optab_libfunc (cmp_optab, SFmode, CSKY_GCC_SYM (cmpsf2)); |
| set_optab_libfunc (cmp_optab, DFmode, CSKY_GCC_SYM (cmpdf2)); |
| set_optab_libfunc (unord_optab, SFmode, CSKY_GCC_SYM (unordsf2)); |
| set_optab_libfunc (unord_optab, DFmode, CSKY_GCC_SYM (unorddf2)); |
| set_optab_libfunc (eq_optab, SFmode, CSKY_GCC_SYM (eqsf2)); |
| set_optab_libfunc (eq_optab, DFmode, CSKY_GCC_SYM (eqdf2)); |
| set_optab_libfunc (ne_optab, SFmode, CSKY_GCC_SYM (nesf2)); |
| set_optab_libfunc (ne_optab, DFmode, CSKY_GCC_SYM (nedf2)); |
| set_optab_libfunc (ge_optab, SFmode, CSKY_GCC_SYM (gesf2)); |
| set_optab_libfunc (ge_optab, DFmode, CSKY_GCC_SYM (gedf2)); |
| set_optab_libfunc (lt_optab, SFmode, CSKY_GCC_SYM (ltsf2)); |
| set_optab_libfunc (lt_optab, DFmode, CSKY_GCC_SYM (ltdf2)); |
| set_optab_libfunc (le_optab, SFmode, CSKY_GCC_SYM (lesf2)); |
| set_optab_libfunc (le_optab, DFmode, CSKY_GCC_SYM (ledf2)); |
| set_optab_libfunc (gt_optab, SFmode, CSKY_GCC_SYM (gtsf2)); |
| set_optab_libfunc (gt_optab, DFmode, CSKY_GCC_SYM (gtdf2)); |
| } |
| |
| |
| /* Implement TARGET_ADDRESS_COST to estimate cost of the memory address X. |
| For C-SKY, (register) and (register + offset) have the same cost. |
| Other situations cost more. */ |
| |
| static int |
| csky_address_cost (rtx x, machine_mode mode ATTRIBUTE_UNUSED, |
| addr_space_t as ATTRIBUTE_UNUSED, |
| bool speed ATTRIBUTE_UNUSED) |
| { |
| enum rtx_code code = GET_CODE (x); |
| |
| if (code == REG) |
| return COSTS_N_INSNS (1); |
| if (code == PLUS |
| && REG_P (XEXP (x, 0)) |
| && CONST_INT_P (XEXP (x, 1))) |
| return COSTS_N_INSNS (1); |
| |
| return COSTS_N_INSNS (3); |
| } |
| |
| |
| /* Implement TARGET_FIXED_CONDITION_CODE_REGS. */ |
| |
| static bool |
| csky_fixed_condition_code_regs (unsigned int *p1, unsigned int *p2) |
| { |
| *p1 = CSKY_CC_REGNUM; |
| *p2 = INVALID_REGNUM; |
| return true; |
| } |
| |
| void |
| csky_init_cumulative_args (CUMULATIVE_ARGS *pcum, tree fntype, |
| rtx libname ATTRIBUTE_UNUSED, |
| tree fndecl ATTRIBUTE_UNUSED) |
| { |
| memset(pcum, 0, sizeof(*pcum)); |
| if (stdarg_p (fntype)) |
| pcum->is_stdarg = true; |
| } |
| |
| |
| /* Implement the TARGET_INIT_BUILTINS target macro. */ |
| |
| void |
| csky_init_builtins (void) |
| { |
| /* Inint fp16. */ |
| static tree csky_floatHF_type_node = make_node (REAL_TYPE); |
| TYPE_PRECISION (csky_floatHF_type_node) = GET_MODE_PRECISION (HFmode); |
| layout_type (csky_floatHF_type_node); |
| (*lang_hooks.types.register_builtin_type) (csky_floatHF_type_node, "__fp16"); |
| } |
| |
| |
| /* Implement TARGET_MANGLE_TYPE. */ |
| |
| static const char * |
| csky_mangle_type (const_tree type) |
| { |
| if (TYPE_NAME (type) && TREE_CODE (TYPE_NAME (type)) == TYPE_DECL |
| && DECL_NAME (TYPE_NAME (type)) |
| && !strcmp (IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (type))), "__fp16")) |
| return "__fp16"; |
| |
| /* Use the default mangling. */ |
| return NULL; |
| } |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| #include "gt-csky.h" |