/* Target Code for TI C6X Copyright (C) 2010-2022 Free Software Foundation, Inc. Contributed by Andrew Jenner Contributed by Bernd Schmidt 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 . */ #define IN_TARGET_CODE 1 #include "config.h" #include "system.h" #include "coretypes.h" #include "backend.h" #include "target.h" #include "rtl.h" #include "tree.h" #include "gimple-expr.h" #include "cfghooks.h" #include "df.h" #include "memmodel.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 "diagnostic-core.h" #include "stor-layout.h" #include "varasm.h" #include "calls.h" #include "output.h" #include "insn-attr.h" #include "explow.h" #include "expr.h" #include "cfgrtl.h" #include "sched-int.h" #include "tm-constrs.h" #include "langhooks.h" #include "sel-sched.h" #include "debug.h" #include "hw-doloop.h" #include "function-abi.h" #include "regrename.h" #include "dumpfile.h" #include "builtins.h" #include "flags.h" #include "opts.h" /* This file should be included last. */ #include "target-def.h" /* Table of supported architecture variants. */ typedef struct { const char *arch; enum c6x_cpu_type type; unsigned short features; } c6x_arch_table; /* A list of all ISAs, mapping each one to a representative device. Used for -march selection. */ static const c6x_arch_table all_isas[] = { #define C6X_ISA(NAME,DEVICE,FLAGS) \ { NAME, DEVICE, FLAGS }, #include "c6x-isas.def" #undef C6X_ISA { NULL, C6X_CPU_C62X, 0 } }; /* This is the parsed result of the "-march=" option, if given. */ enum c6x_cpu_type c6x_arch = C6X_DEFAULT_ARCH; /* A mask of insn types that are allowed by the architecture selected by the -march option. */ unsigned long c6x_insn_mask = C6X_DEFAULT_INSN_MASK; /* The instruction that is being output (as obtained from FINAL_PRESCAN_INSN). */ static rtx_insn *c6x_current_insn = NULL; /* A decl we build to access __c6xabi_DSBT_base. */ static GTY(()) tree dsbt_decl; /* Determines whether we run our final scheduling pass or not. We always avoid the normal second scheduling pass. */ static int c6x_flag_schedule_insns2; /* Determines whether we run variable tracking in machine dependent reorganization. */ static int c6x_flag_var_tracking; /* Determines whether we use modulo scheduling. */ static int c6x_flag_modulo_sched; /* Record the state of flag_pic before we set it to 1 for DSBT. */ int c6x_initial_flag_pic; typedef struct { /* We record the clock cycle for every insn during scheduling. */ int clock; /* After scheduling, we run assign_reservations to choose unit reservations for all insns. These are recorded here. */ int reservation; /* Records the new condition for insns which must be made conditional after scheduling. An entry of NULL_RTX means no such change is necessary. */ rtx new_cond; /* True for the first insn that was scheduled in an ebb. */ bool ebb_start; /* The scheduler state after the insn, transformed into a mask of UNIT_QID bits rather than storing the state. Meaningful only for the last insn in a cycle. */ unsigned int unit_mask; } c6x_sched_insn_info; /* Record a c6x_sched_insn_info structure for every insn in the function. */ static vec insn_info; #define INSN_INFO_LENGTH (insn_info).length () #define INSN_INFO_ENTRY(N) (insn_info[(N)]) static bool done_cfi_sections; #define RESERVATION_FLAG_D 1 #define RESERVATION_FLAG_L 2 #define RESERVATION_FLAG_S 4 #define RESERVATION_FLAG_M 8 #define RESERVATION_FLAG_DL (RESERVATION_FLAG_D | RESERVATION_FLAG_L) #define RESERVATION_FLAG_DS (RESERVATION_FLAG_D | RESERVATION_FLAG_S) #define RESERVATION_FLAG_LS (RESERVATION_FLAG_L | RESERVATION_FLAG_S) #define RESERVATION_FLAG_DLS (RESERVATION_FLAG_D | RESERVATION_FLAG_LS) /* The DFA names of the units. */ static const char *const c6x_unit_names[] = { "d1", "l1", "s1", "m1", "fps1", "fpl1", "adddps1", "adddpl1", "d2", "l2", "s2", "m2", "fps2", "fpl2", "adddps2", "adddpl2" }; /* The DFA unit number for each unit in c6x_unit_names[]. */ static int c6x_unit_codes[ARRAY_SIZE (c6x_unit_names)]; /* Unit query IDs. */ #define UNIT_QID_D1 0 #define UNIT_QID_L1 1 #define UNIT_QID_S1 2 #define UNIT_QID_M1 3 #define UNIT_QID_FPS1 4 #define UNIT_QID_FPL1 5 #define UNIT_QID_ADDDPS1 6 #define UNIT_QID_ADDDPL1 7 #define UNIT_QID_SIDE_OFFSET 8 #define RESERVATION_S1 2 #define RESERVATION_S2 10 /* An enum for the unit requirements we count in the UNIT_REQS table. */ enum unitreqs { UNIT_REQ_D, UNIT_REQ_L, UNIT_REQ_S, UNIT_REQ_M, UNIT_REQ_DL, UNIT_REQ_DS, UNIT_REQ_LS, UNIT_REQ_DLS, UNIT_REQ_T, UNIT_REQ_X, UNIT_REQ_MAX }; /* A table used to count unit requirements. Used when computing minimum iteration intervals. */ typedef int unit_req_table[2][UNIT_REQ_MAX]; static unit_req_table unit_reqs; /* Register map for debugging. */ unsigned const dbx_register_map[FIRST_PSEUDO_REGISTER] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* A0 - A15. */ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, /* A16 - A32. */ 50, 51, 52, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, /* B0 - B15. */ 29, 30, 31, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, /* B16 - B32. */ 66, 67, 68, -1, -1, -1 /* FP, ARGP, ILC. */ }; /* Allocate a new, cleared machine_function structure. */ static struct machine_function * c6x_init_machine_status (void) { return ggc_cleared_alloc (); } /* Implement TARGET_OPTION_OVERRIDE. */ static void c6x_option_override (void) { unsigned i; if (OPTION_SET_P (c6x_arch_option)) { c6x_arch = all_isas[c6x_arch_option].type; c6x_insn_mask &= ~C6X_INSNS_ALL_CPU_BITS; c6x_insn_mask |= all_isas[c6x_arch_option].features; } c6x_flag_schedule_insns2 = flag_schedule_insns_after_reload; flag_schedule_insns_after_reload = 0; c6x_flag_modulo_sched = flag_modulo_sched; flag_modulo_sched = 0; init_machine_status = c6x_init_machine_status; for (i = 0; i < ARRAY_SIZE (c6x_unit_names); i++) c6x_unit_codes[i] = get_cpu_unit_code (c6x_unit_names[i]); if (flag_pic && !TARGET_DSBT) { error ("%<-fpic%> and %<-fPIC%> not supported without %<-mdsbt%> " "on this target"); flag_pic = 0; } c6x_initial_flag_pic = flag_pic; if (TARGET_DSBT && !flag_pic) flag_pic = 1; } /* Implement the TARGET_CONDITIONAL_REGISTER_USAGE hook. */ static void c6x_conditional_register_usage (void) { int i; if (c6x_arch == C6X_CPU_C62X || c6x_arch == C6X_CPU_C67X) for (i = 16; i < 32; i++) { fixed_regs[i] = 1; fixed_regs[32 + i] = 1; } if (TARGET_INSNS_64) { SET_HARD_REG_BIT (reg_class_contents[(int)PREDICATE_A_REGS], REG_A0); SET_HARD_REG_BIT (reg_class_contents[(int)PREDICATE_REGS], REG_A0); CLEAR_HARD_REG_BIT (reg_class_contents[(int)NONPREDICATE_A_REGS], REG_A0); CLEAR_HARD_REG_BIT (reg_class_contents[(int)NONPREDICATE_REGS], REG_A0); } } static GTY(()) rtx eqdf_libfunc; static GTY(()) rtx nedf_libfunc; static GTY(()) rtx ledf_libfunc; static GTY(()) rtx ltdf_libfunc; static GTY(()) rtx gedf_libfunc; static GTY(()) rtx gtdf_libfunc; static GTY(()) rtx eqsf_libfunc; static GTY(()) rtx nesf_libfunc; static GTY(()) rtx lesf_libfunc; static GTY(()) rtx ltsf_libfunc; static GTY(()) rtx gesf_libfunc; static GTY(()) rtx gtsf_libfunc; static GTY(()) rtx strasgi_libfunc; static GTY(()) rtx strasgi64p_libfunc; /* Implement the TARGET_INIT_LIBFUNCS macro. We use this to rename library functions to match the C6x ABI. */ static void c6x_init_libfuncs (void) { /* Double-precision floating-point arithmetic. */ set_optab_libfunc (add_optab, DFmode, "__c6xabi_addd"); set_optab_libfunc (sdiv_optab, DFmode, "__c6xabi_divd"); set_optab_libfunc (smul_optab, DFmode, "__c6xabi_mpyd"); set_optab_libfunc (neg_optab, DFmode, "__c6xabi_negd"); set_optab_libfunc (sub_optab, DFmode, "__c6xabi_subd"); /* Single-precision floating-point arithmetic. */ set_optab_libfunc (add_optab, SFmode, "__c6xabi_addf"); set_optab_libfunc (sdiv_optab, SFmode, "__c6xabi_divf"); set_optab_libfunc (smul_optab, SFmode, "__c6xabi_mpyf"); set_optab_libfunc (neg_optab, SFmode, "__c6xabi_negf"); set_optab_libfunc (sub_optab, SFmode, "__c6xabi_subf"); /* Floating-point comparisons. */ eqsf_libfunc = init_one_libfunc ("__c6xabi_eqf"); nesf_libfunc = init_one_libfunc ("__c6xabi_neqf"); lesf_libfunc = init_one_libfunc ("__c6xabi_lef"); ltsf_libfunc = init_one_libfunc ("__c6xabi_ltf"); gesf_libfunc = init_one_libfunc ("__c6xabi_gef"); gtsf_libfunc = init_one_libfunc ("__c6xabi_gtf"); eqdf_libfunc = init_one_libfunc ("__c6xabi_eqd"); nedf_libfunc = init_one_libfunc ("__c6xabi_neqd"); ledf_libfunc = init_one_libfunc ("__c6xabi_led"); ltdf_libfunc = init_one_libfunc ("__c6xabi_ltd"); gedf_libfunc = init_one_libfunc ("__c6xabi_ged"); gtdf_libfunc = init_one_libfunc ("__c6xabi_gtd"); set_optab_libfunc (eq_optab, SFmode, NULL); set_optab_libfunc (ne_optab, SFmode, "__c6xabi_neqf"); set_optab_libfunc (gt_optab, SFmode, NULL); set_optab_libfunc (ge_optab, SFmode, NULL); set_optab_libfunc (lt_optab, SFmode, NULL); set_optab_libfunc (le_optab, SFmode, NULL); set_optab_libfunc (unord_optab, SFmode, "__c6xabi_unordf"); set_optab_libfunc (eq_optab, DFmode, NULL); set_optab_libfunc (ne_optab, DFmode, "__c6xabi_neqd"); set_optab_libfunc (gt_optab, DFmode, NULL); set_optab_libfunc (ge_optab, DFmode, NULL); set_optab_libfunc (lt_optab, DFmode, NULL); set_optab_libfunc (le_optab, DFmode, NULL); set_optab_libfunc (unord_optab, DFmode, "__c6xabi_unordd"); /* Floating-point to integer conversions. */ set_conv_libfunc (sfix_optab, SImode, DFmode, "__c6xabi_fixdi"); set_conv_libfunc (ufix_optab, SImode, DFmode, "__c6xabi_fixdu"); set_conv_libfunc (sfix_optab, DImode, DFmode, "__c6xabi_fixdlli"); set_conv_libfunc (ufix_optab, DImode, DFmode, "__c6xabi_fixdull"); set_conv_libfunc (sfix_optab, SImode, SFmode, "__c6xabi_fixfi"); set_conv_libfunc (ufix_optab, SImode, SFmode, "__c6xabi_fixfu"); set_conv_libfunc (sfix_optab, DImode, SFmode, "__c6xabi_fixflli"); set_conv_libfunc (ufix_optab, DImode, SFmode, "__c6xabi_fixfull"); /* Conversions between floating types. */ set_conv_libfunc (trunc_optab, SFmode, DFmode, "__c6xabi_cvtdf"); set_conv_libfunc (sext_optab, DFmode, SFmode, "__c6xabi_cvtfd"); /* Integer to floating-point conversions. */ set_conv_libfunc (sfloat_optab, DFmode, SImode, "__c6xabi_fltid"); set_conv_libfunc (ufloat_optab, DFmode, SImode, "__c6xabi_fltud"); set_conv_libfunc (sfloat_optab, DFmode, DImode, "__c6xabi_fltllid"); set_conv_libfunc (ufloat_optab, DFmode, DImode, "__c6xabi_fltulld"); set_conv_libfunc (sfloat_optab, SFmode, SImode, "__c6xabi_fltif"); set_conv_libfunc (ufloat_optab, SFmode, SImode, "__c6xabi_fltuf"); set_conv_libfunc (sfloat_optab, SFmode, DImode, "__c6xabi_fltllif"); set_conv_libfunc (ufloat_optab, SFmode, DImode, "__c6xabi_fltullf"); /* Long long. */ set_optab_libfunc (smul_optab, DImode, "__c6xabi_mpyll"); set_optab_libfunc (ashl_optab, DImode, "__c6xabi_llshl"); set_optab_libfunc (lshr_optab, DImode, "__c6xabi_llshru"); set_optab_libfunc (ashr_optab, DImode, "__c6xabi_llshr"); set_optab_libfunc (sdiv_optab, SImode, "__c6xabi_divi"); set_optab_libfunc (udiv_optab, SImode, "__c6xabi_divu"); set_optab_libfunc (smod_optab, SImode, "__c6xabi_remi"); set_optab_libfunc (umod_optab, SImode, "__c6xabi_remu"); set_optab_libfunc (sdivmod_optab, SImode, "__c6xabi_divremi"); set_optab_libfunc (udivmod_optab, SImode, "__c6xabi_divremu"); set_optab_libfunc (sdiv_optab, DImode, "__c6xabi_divlli"); set_optab_libfunc (udiv_optab, DImode, "__c6xabi_divull"); set_optab_libfunc (smod_optab, DImode, "__c6xabi_remlli"); set_optab_libfunc (umod_optab, DImode, "__c6xabi_remull"); set_optab_libfunc (udivmod_optab, DImode, "__c6xabi_divremull"); /* Block move. */ strasgi_libfunc = init_one_libfunc ("__c6xabi_strasgi"); strasgi64p_libfunc = init_one_libfunc ("__c6xabi_strasgi_64plus"); } /* Begin the assembly file. */ static void c6x_file_start (void) { /* Variable tracking should be run after all optimizations which change order of insns. It also needs a valid CFG. This can't be done in c6x_override_options, because flag_var_tracking is finalized after that. */ c6x_flag_var_tracking = flag_var_tracking; flag_var_tracking = 0; done_cfi_sections = false; default_file_start (); /* Arrays are aligned to 8-byte boundaries. */ asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_array_object_alignment, 0\n"); asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_array_object_align_expected, 0\n"); /* Stack alignment is 8 bytes. */ asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_stack_align_needed, 0\n"); asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_stack_align_preserved, 0\n"); #if 0 /* FIXME: Reenable when TI's tools are fixed. */ /* ??? Ideally we'd check flag_short_wchar somehow. */ asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_wchar_t, %d\n", 2); #endif /* We conform to version 1.0 of the ABI. */ asm_fprintf (asm_out_file, "\t.c6xabi_attribute Tag_ABI_conformance, \"1.0\"\n"); } /* The LTO frontend only enables exceptions when it sees a function that uses it. This changes the return value of dwarf2out_do_frame, so we have to check before every function. */ void c6x_output_file_unwind (FILE * f) { if (done_cfi_sections) return; /* Output a .cfi_sections directive. */ if (dwarf2out_do_frame ()) { if (flag_unwind_tables || flag_exceptions) { if (dwarf_debuginfo_p ()) asm_fprintf (f, "\t.cfi_sections .debug_frame, .c6xabi.exidx\n"); else asm_fprintf (f, "\t.cfi_sections .c6xabi.exidx\n"); } else asm_fprintf (f, "\t.cfi_sections .debug_frame\n"); done_cfi_sections = true; } } /* Output unwind directives at the end of a function. */ static void c6x_output_fn_unwind (FILE * f) { /* Return immediately if we are not generating unwinding tables. */ if (! (flag_unwind_tables || flag_exceptions)) return; /* If this function will never be unwound, then mark it as such. */ if (!(flag_unwind_tables || crtl->uses_eh_lsda) && (TREE_NOTHROW (current_function_decl) || crtl->all_throwers_are_sibcalls)) fputs("\t.cantunwind\n", f); fputs ("\t.endp\n", f); } /* Stack and Calling. */ int argument_registers[10] = { REG_A4, REG_B4, REG_A6, REG_B6, REG_A8, REG_B8, REG_A10, REG_B10, REG_A12, REG_B12 }; /* Implements the macro INIT_CUMULATIVE_ARGS defined in c6x.h. */ void c6x_init_cumulative_args (CUMULATIVE_ARGS *cum, const_tree fntype, rtx libname, int n_named_args ATTRIBUTE_UNUSED) { cum->count = 0; cum->nregs = 10; if (!libname && fntype) { /* We need to find out the number of named arguments. Unfortunately, for incoming arguments, N_NAMED_ARGS is set to -1. */ if (stdarg_p (fntype)) cum->nregs = type_num_arguments (fntype) - 1; if (cum->nregs > 10) cum->nregs = 10; } } /* Implement TARGET_FUNCTION_ARG. */ static rtx c6x_function_arg (cumulative_args_t cum_v, const function_arg_info &arg) { CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); if (cum->count >= cum->nregs) return NULL_RTX; if (tree type = arg.type) { HOST_WIDE_INT size = int_size_in_bytes (type); if (TARGET_BIG_ENDIAN && AGGREGATE_TYPE_P (type)) { if (size > 4) { rtx reg1 = gen_rtx_REG (SImode, argument_registers[cum->count] + 1); rtx reg2 = gen_rtx_REG (SImode, argument_registers[cum->count]); rtvec vec = gen_rtvec (2, gen_rtx_EXPR_LIST (VOIDmode, reg1, const0_rtx), gen_rtx_EXPR_LIST (VOIDmode, reg2, GEN_INT (4))); return gen_rtx_PARALLEL (arg.mode, vec); } } } return gen_rtx_REG (arg.mode, argument_registers[cum->count]); } static void c6x_function_arg_advance (cumulative_args_t cum_v, const function_arg_info &) { CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); cum->count++; } /* Return true if BLOCK_REG_PADDING (MODE, TYPE, FIRST) should return upward rather than downward. */ bool c6x_block_reg_pad_upward (machine_mode mode ATTRIBUTE_UNUSED, const_tree type, bool first) { HOST_WIDE_INT size; if (!TARGET_BIG_ENDIAN) return true; if (!first) return true; if (!type) return true; size = int_size_in_bytes (type); return size == 3; } /* Implement TARGET_FUNCTION_ARG_BOUNDARY. */ static unsigned int c6x_function_arg_boundary (machine_mode mode, const_tree type) { unsigned int boundary = type ? TYPE_ALIGN (type) : GET_MODE_BITSIZE (mode); if (boundary > BITS_PER_WORD) return 2 * BITS_PER_WORD; if (mode == BLKmode) { HOST_WIDE_INT size = int_size_in_bytes (type); if (size > 4) return 2 * BITS_PER_WORD; if (boundary < BITS_PER_WORD) { if (size >= 3) return BITS_PER_WORD; if (size >= 2) return 2 * BITS_PER_UNIT; } } return boundary; } /* Implement TARGET_FUNCTION_ARG_ROUND_BOUNDARY. */ static unsigned int c6x_function_arg_round_boundary (machine_mode mode, const_tree type) { return c6x_function_arg_boundary (mode, type); } /* TARGET_FUNCTION_VALUE implementation. Returns an RTX representing the place where function FUNC returns or receives a value of data type TYPE. */ static rtx c6x_function_value (const_tree type, const_tree func ATTRIBUTE_UNUSED, bool outgoing ATTRIBUTE_UNUSED) { /* Functions return values in register A4. When returning aggregates, we may have to adjust for endianness. */ if (TARGET_BIG_ENDIAN && type && AGGREGATE_TYPE_P (type)) { HOST_WIDE_INT size = int_size_in_bytes (type); if (size > 4) { rtx reg1 = gen_rtx_REG (SImode, REG_A4 + 1); rtx reg2 = gen_rtx_REG (SImode, REG_A4); rtvec vec = gen_rtvec (2, gen_rtx_EXPR_LIST (VOIDmode, reg1, const0_rtx), gen_rtx_EXPR_LIST (VOIDmode, reg2, GEN_INT (4))); return gen_rtx_PARALLEL (TYPE_MODE (type), vec); } } return gen_rtx_REG (TYPE_MODE (type), REG_A4); } /* Implement TARGET_LIBCALL_VALUE. */ static rtx c6x_libcall_value (machine_mode mode, const_rtx fun ATTRIBUTE_UNUSED) { return gen_rtx_REG (mode, REG_A4); } /* TARGET_STRUCT_VALUE_RTX implementation. */ static rtx c6x_struct_value_rtx (tree type ATTRIBUTE_UNUSED, int incoming ATTRIBUTE_UNUSED) { return gen_rtx_REG (Pmode, REG_A3); } /* Implement TARGET_FUNCTION_VALUE_REGNO_P. */ static bool c6x_function_value_regno_p (const unsigned int regno) { return regno == REG_A4; } /* Types larger than 64 bit, and variable sized types, are passed by reference. The callee must copy them; see TARGET_CALLEE_COPIES. */ static bool c6x_pass_by_reference (cumulative_args_t, const function_arg_info &arg) { int size = -1; if (arg.type) size = int_size_in_bytes (arg.type); else if (arg.mode != VOIDmode) size = GET_MODE_SIZE (arg.mode); return size > 2 * UNITS_PER_WORD || size == -1; } /* Decide whether a type should be returned in memory (true) or in a register (false). This is called by the macro TARGET_RETURN_IN_MEMORY. */ static bool c6x_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED) { int size = int_size_in_bytes (type); return size > 2 * UNITS_PER_WORD || size == -1; } /* Values which must be returned in the most-significant end of the return register. */ static bool c6x_return_in_msb (const_tree valtype) { HOST_WIDE_INT size = int_size_in_bytes (valtype); return TARGET_BIG_ENDIAN && AGGREGATE_TYPE_P (valtype) && size == 3; } /* Return the type to use as __builtin_va_list. */ static tree c6x_build_builtin_va_list (void) { return build_pointer_type (char_type_node); } static void c6x_asm_trampoline_template (FILE *f) { fprintf (f, "\t.long\t0x0000002b\n"); /* mvkl .s2 fnlow,B0 */ fprintf (f, "\t.long\t0x01000028\n"); /* || mvkl .s1 sclow,A2 */ fprintf (f, "\t.long\t0x0000006b\n"); /* mvkh .s2 fnhigh,B0 */ fprintf (f, "\t.long\t0x01000068\n"); /* || mvkh .s1 schigh,A2 */ fprintf (f, "\t.long\t0x00000362\n"); /* b .s2 B0 */ fprintf (f, "\t.long\t0x00008000\n"); /* nop 5 */ fprintf (f, "\t.long\t0x00000000\n"); /* nop */ fprintf (f, "\t.long\t0x00000000\n"); /* nop */ } /* Emit RTL insns to initialize the variable parts of a trampoline at TRAMP. FNADDR is an RTX for the address of the function's pure code. CXT is an RTX for the static chain value for the function. */ static void c6x_initialize_trampoline (rtx tramp, tree fndecl, rtx cxt) { rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); rtx t1 = copy_to_reg (fnaddr); rtx t2 = copy_to_reg (cxt); rtx mask = gen_reg_rtx (SImode); int i; emit_block_move (tramp, assemble_trampoline_template (), GEN_INT (TRAMPOLINE_SIZE), BLOCK_OP_NORMAL); emit_move_insn (mask, GEN_INT (0xffff << 7)); for (i = 0; i < 4; i++) { rtx mem = adjust_address (tramp, SImode, i * 4); rtx t = (i & 1) ? t2 : t1; rtx v1 = gen_reg_rtx (SImode); rtx v2 = gen_reg_rtx (SImode); emit_move_insn (v1, mem); if (i < 2) emit_insn (gen_ashlsi3 (v2, t, GEN_INT (7))); else emit_insn (gen_lshrsi3 (v2, t, GEN_INT (9))); emit_insn (gen_andsi3 (v2, v2, mask)); emit_insn (gen_iorsi3 (v2, v2, v1)); emit_move_insn (mem, v2); } #ifdef CLEAR_INSN_CACHE tramp = XEXP (tramp, 0); maybe_emit_call_builtin___clear_cache (tramp, plus_constant (Pmode, tramp, TRAMPOLINE_SIZE)); #endif } /* Determine whether c6x_output_mi_thunk can succeed. */ static bool c6x_can_output_mi_thunk (const_tree thunk ATTRIBUTE_UNUSED, HOST_WIDE_INT delta ATTRIBUTE_UNUSED, HOST_WIDE_INT vcall_offset ATTRIBUTE_UNUSED, const_tree function ATTRIBUTE_UNUSED) { return !TARGET_LONG_CALLS; } /* Output the assembler code for a thunk function. THUNK is the declaration for the thunk function itself, FUNCTION is the decl for the target function. DELTA is an immediate constant offset to be added to THIS. If VCALL_OFFSET is nonzero, the word at *(*this + vcall_offset) should be added to THIS. */ static void c6x_output_mi_thunk (FILE *file ATTRIBUTE_UNUSED, tree thunk ATTRIBUTE_UNUSED, HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, tree function) { const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk)); rtx xops[5]; /* The this parameter is passed as the first argument. */ rtx this_rtx = gen_rtx_REG (Pmode, REG_A4); assemble_start_function (thunk, fnname); c6x_current_insn = NULL; xops[4] = XEXP (DECL_RTL (function), 0); if (!vcall_offset) { output_asm_insn ("b .s2 \t%4", xops); if (!delta) output_asm_insn ("nop 5", xops); } /* Adjust the this parameter by a fixed constant. */ if (delta) { xops[0] = GEN_INT (delta); xops[1] = this_rtx; if (delta >= -16 && delta <= 15) { output_asm_insn ("add .s1 %0, %1, %1", xops); if (!vcall_offset) output_asm_insn ("nop 4", xops); } else if (delta >= 16 && delta < 32) { output_asm_insn ("add .d1 %0, %1, %1", xops); if (!vcall_offset) output_asm_insn ("nop 4", xops); } else if (delta >= -32768 && delta < 32768) { output_asm_insn ("mvk .s1 %0, A0", xops); output_asm_insn ("add .d1 %1, A0, %1", xops); if (!vcall_offset) output_asm_insn ("nop 3", xops); } else { output_asm_insn ("mvkl .s1 %0, A0", xops); output_asm_insn ("mvkh .s1 %0, A0", xops); output_asm_insn ("add .d1 %1, A0, %1", xops); if (!vcall_offset) output_asm_insn ("nop 3", xops); } } /* Adjust the this parameter by a value stored in the vtable. */ if (vcall_offset) { rtx a0tmp = gen_rtx_REG (Pmode, REG_A0); rtx a3tmp = gen_rtx_REG (Pmode, REG_A3); xops[1] = a3tmp; xops[2] = a0tmp; xops[3] = gen_rtx_MEM (Pmode, a0tmp); output_asm_insn ("mv .s1 a4, %2", xops); output_asm_insn ("ldw .d1t1 %3, %2", xops); /* Adjust the this parameter. */ xops[0] = gen_rtx_MEM (Pmode, plus_constant (Pmode, a0tmp, vcall_offset)); if (!memory_operand (xops[0], Pmode)) { rtx tmp2 = gen_rtx_REG (Pmode, REG_A1); xops[0] = GEN_INT (vcall_offset); xops[1] = tmp2; output_asm_insn ("mvkl .s1 %0, %1", xops); output_asm_insn ("mvkh .s1 %0, %1", xops); output_asm_insn ("nop 2", xops); output_asm_insn ("add .d1 %2, %1, %2", xops); xops[0] = gen_rtx_MEM (Pmode, a0tmp); } else output_asm_insn ("nop 4", xops); xops[2] = this_rtx; output_asm_insn ("ldw .d1t1 %0, %1", xops); output_asm_insn ("|| b .s2 \t%4", xops); output_asm_insn ("nop 4", xops); output_asm_insn ("add .d1 %2, %1, %2", xops); } assemble_end_function (thunk, fnname); } /* Return true if EXP goes in small data/bss. */ static bool c6x_in_small_data_p (const_tree exp) { /* We want to merge strings, so we never consider them small data. */ if (TREE_CODE (exp) == STRING_CST) return false; /* Functions are never small data. */ if (TREE_CODE (exp) == FUNCTION_DECL) return false; if (TREE_CODE (exp) == VAR_DECL && DECL_WEAK (exp)) return false; if (TREE_CODE (exp) == VAR_DECL && DECL_SECTION_NAME (exp)) { const char *section = DECL_SECTION_NAME (exp); if (strcmp (section, ".neardata") == 0 || startswith (section, ".neardata.") || startswith (section, ".gnu.linkonce.s.") || strcmp (section, ".bss") == 0 || startswith (section, ".bss.") || startswith (section, ".gnu.linkonce.sb.") || strcmp (section, ".rodata") == 0 || startswith (section, ".rodata.") || startswith (section, ".gnu.linkonce.s2.")) return true; } else return PLACE_IN_SDATA_P (exp); return false; } /* Return a section for X. The only special thing we do here is to honor small data. We don't have a tree type, so we can't use the PLACE_IN_SDATA_P macro we use everywhere else; we choose to place everything sized 8 bytes or smaller into small data. */ static section * c6x_select_rtx_section (machine_mode mode, rtx x, unsigned HOST_WIDE_INT align) { if (c6x_sdata_mode == C6X_SDATA_ALL || (c6x_sdata_mode != C6X_SDATA_NONE && GET_MODE_SIZE (mode) <= 8)) /* ??? Consider using mergeable sdata sections. */ return sdata_section; else return default_elf_select_rtx_section (mode, x, align); } static section * c6x_elf_select_section (tree decl, int reloc, unsigned HOST_WIDE_INT align) { const char *sname = NULL; unsigned int flags = SECTION_WRITE; if (c6x_in_small_data_p (decl)) { switch (categorize_decl_for_section (decl, reloc)) { case SECCAT_SRODATA: sname = ".rodata"; flags = 0; break; case SECCAT_SDATA: sname = ".neardata"; break; case SECCAT_SBSS: sname = ".bss"; flags |= SECTION_BSS; default: break; } } else { switch (categorize_decl_for_section (decl, reloc)) { case SECCAT_DATA: sname = ".fardata"; break; case SECCAT_DATA_REL: sname = ".fardata.rel"; break; case SECCAT_DATA_REL_LOCAL: sname = ".fardata.rel.local"; break; case SECCAT_DATA_REL_RO: sname = ".fardata.rel.ro"; break; case SECCAT_DATA_REL_RO_LOCAL: sname = ".fardata.rel.ro.local"; break; case SECCAT_BSS: sname = ".far"; flags |= SECTION_BSS; break; case SECCAT_RODATA: sname = ".const"; flags = 0; break; case SECCAT_SRODATA: case SECCAT_SDATA: case SECCAT_SBSS: gcc_unreachable (); default: break; } } if (sname) { /* We might get called with string constants, but get_named_section doesn't like them as they are not DECLs. Also, we need to set flags in that case. */ if (!DECL_P (decl)) return get_section (sname, flags, NULL); return get_named_section (decl, sname, reloc); } return default_elf_select_section (decl, reloc, align); } /* Build up a unique section name, expressed as a STRING_CST node, and assign it to DECL_SECTION_NAME (decl). RELOC indicates whether the initial value of EXP requires link-time relocations. */ static void ATTRIBUTE_UNUSED c6x_elf_unique_section (tree decl, int reloc) { const char *prefix = NULL; /* We only need to use .gnu.linkonce if we don't have COMDAT groups. */ bool one_only = DECL_COMDAT_GROUP (decl) && !HAVE_COMDAT_GROUP; if (c6x_in_small_data_p (decl)) { switch (categorize_decl_for_section (decl, reloc)) { case SECCAT_SDATA: prefix = one_only ? ".s" : ".neardata"; break; case SECCAT_SBSS: prefix = one_only ? ".sb" : ".bss"; break; case SECCAT_SRODATA: prefix = one_only ? ".s2" : ".rodata"; break; case SECCAT_RODATA_MERGE_STR: case SECCAT_RODATA_MERGE_STR_INIT: case SECCAT_RODATA_MERGE_CONST: case SECCAT_RODATA: case SECCAT_DATA: case SECCAT_DATA_REL: case SECCAT_DATA_REL_LOCAL: case SECCAT_DATA_REL_RO: case SECCAT_DATA_REL_RO_LOCAL: gcc_unreachable (); default: /* Everything else we place into default sections and hope for the best. */ break; } } else { switch (categorize_decl_for_section (decl, reloc)) { case SECCAT_DATA: case SECCAT_DATA_REL: case SECCAT_DATA_REL_LOCAL: case SECCAT_DATA_REL_RO: case SECCAT_DATA_REL_RO_LOCAL: prefix = one_only ? ".fd" : ".fardata"; break; case SECCAT_BSS: prefix = one_only ? ".fb" : ".far"; break; case SECCAT_RODATA: case SECCAT_RODATA_MERGE_STR: case SECCAT_RODATA_MERGE_STR_INIT: case SECCAT_RODATA_MERGE_CONST: prefix = one_only ? ".fr" : ".const"; break; case SECCAT_SRODATA: case SECCAT_SDATA: case SECCAT_SBSS: gcc_unreachable (); default: break; } } if (prefix) { const char *name, *linkonce; char *string; name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (decl)); name = targetm.strip_name_encoding (name); /* If we're using one_only, then there needs to be a .gnu.linkonce prefix to the section name. */ linkonce = one_only ? ".gnu.linkonce" : ""; string = ACONCAT ((linkonce, prefix, ".", name, NULL)); set_decl_section_name (decl, string); return; } default_unique_section (decl, reloc); } static unsigned int c6x_section_type_flags (tree decl, const char *name, int reloc) { unsigned int flags = 0; if (strcmp (name, ".far") == 0 || startswith (name, ".far.")) flags |= SECTION_BSS; flags |= default_section_type_flags (decl, name, reloc); /* The ".far" section will be declared with @nobits elsewhere. But when declared via this path it will not have the @nobits flag because of SECTION_NOTYPE. This causes linker warnings due to the mismatched attribute. Clearing SECTION_NOTYPE for the ".far" section is sufficient to fix this problem. */ if (strcmp (name, ".far") == 0) flags &= ~SECTION_NOTYPE; return flags; } /* Checks whether the given CALL_EXPR would use a caller saved register. This is used to decide whether sibling call optimization could be performed on the respective function call. */ static bool c6x_call_saved_register_used (tree call_expr) { CUMULATIVE_ARGS cum_v; cumulative_args_t cum; HARD_REG_SET call_saved_regset; tree parameter; rtx parm_rtx; int i; INIT_CUMULATIVE_ARGS (cum_v, NULL, NULL, 0, 0); cum = pack_cumulative_args (&cum_v); call_saved_regset = ~call_used_or_fixed_regs; for (i = 0; i < call_expr_nargs (call_expr); i++) { parameter = CALL_EXPR_ARG (call_expr, i); gcc_assert (parameter); /* For an undeclared variable passed as parameter we will get an ERROR_MARK node here. */ if (TREE_CODE (parameter) == ERROR_MARK) return true; function_arg_info arg (TREE_TYPE (parameter), /*named=*/true); apply_pass_by_reference_rules (&cum_v, arg); parm_rtx = c6x_function_arg (cum, arg); c6x_function_arg_advance (cum, arg); if (!parm_rtx) continue; if (REG_P (parm_rtx) && overlaps_hard_reg_set_p (call_saved_regset, GET_MODE (parm_rtx), REGNO (parm_rtx))) return true; if (GET_CODE (parm_rtx) == PARALLEL) { int n = XVECLEN (parm_rtx, 0); while (n-- > 0) { rtx x = XEXP (XVECEXP (parm_rtx, 0, n), 0); if (REG_P (x) && overlaps_hard_reg_set_p (call_saved_regset, GET_MODE (x), REGNO (x))) return true; } } } return false; } /* Decide whether we can make a sibling call to a function. DECL is the declaration of the function being targeted by the call and EXP is the CALL_EXPR representing the call. */ static bool c6x_function_ok_for_sibcall (tree decl, tree exp) { /* Registers A10, A12, B10 and B12 are available as arguments register but unfortunately caller saved. This makes functions needing these registers for arguments not suitable for sibcalls. */ if (c6x_call_saved_register_used (exp)) return false; if (!flag_pic) return true; if (TARGET_DSBT) { /* When compiling for DSBT, the calling function must be local, so that when we reload B14 in the sibcall epilogue, it will not change its value. */ if (!decl) /* Not enough information. */ return false; cgraph_node *this_func = cgraph_node::local_info_node (current_function_decl); return this_func->local; } return true; } /* Return true if DECL is known to be linked into section SECTION. */ static bool c6x_function_in_section_p (tree decl, section *section) { /* We can only be certain about functions defined in the same compilation unit. */ if (!TREE_STATIC (decl)) return false; /* Make sure that SYMBOL always binds to the definition in this compilation unit. */ if (!targetm.binds_local_p (decl)) return false; /* If DECL_SECTION_NAME is set, assume it is trustworthy. */ if (!DECL_SECTION_NAME (decl)) { /* Make sure that we will not create a unique section for DECL. */ if (flag_function_sections || DECL_COMDAT_GROUP (decl)) return false; } return function_section (decl) == section; } /* Return true if a call to OP, which is a SYMBOL_REF, must be expanded as a long call. */ bool c6x_long_call_p (rtx op) { tree decl; if (!TARGET_LONG_CALLS) return false; decl = SYMBOL_REF_DECL (op); /* Try to determine whether the symbol is in the same section as the current function. Be conservative, and only cater for cases in which the whole of the current function is placed in the same section. */ if (decl != NULL_TREE && !flag_reorder_blocks_and_partition && TREE_CODE (decl) == FUNCTION_DECL && c6x_function_in_section_p (decl, current_function_section ())) return false; return true; } /* Emit the sequence for a call. */ void c6x_expand_call (rtx retval, rtx address, bool sibcall) { rtx callee = XEXP (address, 0); rtx call_insn; if (!c6x_call_operand (callee, Pmode)) { callee = force_reg (Pmode, callee); address = change_address (address, Pmode, callee); } call_insn = gen_rtx_CALL (VOIDmode, address, const0_rtx); if (sibcall) { call_insn = emit_call_insn (call_insn); use_reg (&CALL_INSN_FUNCTION_USAGE (call_insn), gen_rtx_REG (Pmode, REG_B3)); } else { if (retval == NULL_RTX) call_insn = emit_call_insn (call_insn); else call_insn = emit_call_insn (gen_rtx_SET (retval, call_insn)); } if (flag_pic) use_reg (&CALL_INSN_FUNCTION_USAGE (call_insn), pic_offset_table_rtx); } /* Legitimize PIC addresses. If the address is already position-independent, we return ORIG. Newly generated position-independent addresses go into a reg. This is REG if nonzero, otherwise we allocate register(s) as necessary. PICREG is the register holding the pointer to the PIC offset table. */ static rtx legitimize_pic_address (rtx orig, rtx reg, rtx picreg) { rtx addr = orig; rtx new_rtx = orig; if (GET_CODE (addr) == SYMBOL_REF || GET_CODE (addr) == LABEL_REF) { int unspec = UNSPEC_LOAD_GOT; rtx tmp; if (reg == 0) { gcc_assert (can_create_pseudo_p ()); reg = gen_reg_rtx (Pmode); } if (flag_pic == 2) { if (can_create_pseudo_p ()) tmp = gen_reg_rtx (Pmode); else tmp = reg; emit_insn (gen_movsi_gotoff_high (tmp, addr)); emit_insn (gen_movsi_gotoff_lo_sum (tmp, tmp, addr)); emit_insn (gen_load_got_gotoff (reg, picreg, tmp)); } else { tmp = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, addr), unspec); new_rtx = gen_const_mem (Pmode, gen_rtx_PLUS (Pmode, picreg, tmp)); emit_move_insn (reg, new_rtx); } if (picreg == pic_offset_table_rtx) crtl->uses_pic_offset_table = 1; return reg; } else if (GET_CODE (addr) == CONST || GET_CODE (addr) == PLUS) { rtx base; if (GET_CODE (addr) == CONST) { addr = XEXP (addr, 0); gcc_assert (GET_CODE (addr) == PLUS); } if (XEXP (addr, 0) == picreg) return orig; if (reg == 0) { gcc_assert (can_create_pseudo_p ()); reg = gen_reg_rtx (Pmode); } base = legitimize_pic_address (XEXP (addr, 0), reg, picreg); addr = legitimize_pic_address (XEXP (addr, 1), base == reg ? NULL_RTX : reg, picreg); if (GET_CODE (addr) == CONST_INT) { gcc_assert (! reload_in_progress && ! reload_completed); addr = force_reg (Pmode, addr); } if (GET_CODE (addr) == PLUS && CONSTANT_P (XEXP (addr, 1))) { base = gen_rtx_PLUS (Pmode, base, XEXP (addr, 0)); addr = XEXP (addr, 1); } return gen_rtx_PLUS (Pmode, base, addr); } return new_rtx; } /* Expand a move operation in mode MODE. The operands are in OPERANDS. Returns true if no further code must be generated, false if the caller should generate an insn to move OPERANDS[1] to OPERANDS[0]. */ bool expand_move (rtx *operands, machine_mode mode) { rtx dest = operands[0]; rtx op = operands[1]; if ((reload_in_progress | reload_completed) == 0 && GET_CODE (dest) == MEM && GET_CODE (op) != REG) operands[1] = force_reg (mode, op); else if (mode == SImode && symbolic_operand (op, SImode)) { if (flag_pic) { if (sdata_symbolic_operand (op, SImode)) { emit_insn (gen_load_sdata_pic (dest, pic_offset_table_rtx, op)); crtl->uses_pic_offset_table = 1; return true; } else { rtx temp = (reload_completed || reload_in_progress ? dest : gen_reg_rtx (Pmode)); operands[1] = legitimize_pic_address (op, temp, pic_offset_table_rtx); } } else if (reload_completed && !sdata_symbolic_operand (op, SImode)) { emit_insn (gen_movsi_high (dest, op)); emit_insn (gen_movsi_lo_sum (dest, dest, op)); return true; } } return false; } /* This function is called when we're about to expand an integer compare operation which performs COMPARISON. It examines the second operand, and if it is an integer constant that cannot be used directly on the current machine in a comparison insn, it returns true. */ bool c6x_force_op_for_comparison_p (enum rtx_code code, rtx op) { if (!CONST_INT_P (op) || satisfies_constraint_Iu4 (op)) return false; if ((code == EQ || code == LT || code == GT) && !satisfies_constraint_Is5 (op)) return true; if ((code == GTU || code == LTU) && (!TARGET_INSNS_64 || !satisfies_constraint_Iu5 (op))) return true; return false; } /* Emit comparison instruction if necessary, returning the expression that holds the compare result in the proper mode. Return the comparison that should be used in the jump insn. */ rtx c6x_expand_compare (rtx comparison, machine_mode mode) { enum rtx_code code = GET_CODE (comparison); rtx op0 = XEXP (comparison, 0); rtx op1 = XEXP (comparison, 1); rtx cmp; enum rtx_code jump_code = code; machine_mode op_mode = GET_MODE (op0); if (op_mode == DImode && (code == NE || code == EQ) && op1 == const0_rtx) { rtx t = gen_reg_rtx (SImode); emit_insn (gen_iorsi3 (t, gen_lowpart (SImode, op0), gen_highpart (SImode, op0))); op_mode = SImode; cmp = t; } else if (op_mode == DImode) { rtx lo[2], high[2]; rtx cmp1, cmp2; if (code == NE || code == GEU || code == LEU || code == GE || code == LE) { code = reverse_condition (code); jump_code = EQ; } else jump_code = NE; split_di (&op0, 1, lo, high); split_di (&op1, 1, lo + 1, high + 1); if (c6x_force_op_for_comparison_p (code, high[1]) || c6x_force_op_for_comparison_p (EQ, high[1])) high[1] = force_reg (SImode, high[1]); cmp1 = gen_reg_rtx (SImode); cmp2 = gen_reg_rtx (SImode); emit_insn (gen_rtx_SET (cmp1, gen_rtx_fmt_ee (code, SImode, high[0], high[1]))); if (code == EQ) { if (c6x_force_op_for_comparison_p (code, lo[1])) lo[1] = force_reg (SImode, lo[1]); emit_insn (gen_rtx_SET (cmp2, gen_rtx_fmt_ee (code, SImode, lo[0], lo[1]))); emit_insn (gen_andsi3 (cmp1, cmp1, cmp2)); } else { emit_insn (gen_rtx_SET (cmp2, gen_rtx_EQ (SImode, high[0], high[1]))); if (code == GT) code = GTU; else if (code == LT) code = LTU; if (c6x_force_op_for_comparison_p (code, lo[1])) lo[1] = force_reg (SImode, lo[1]); emit_insn (gen_cmpsi_and (cmp2, gen_rtx_fmt_ee (code, SImode, lo[0], lo[1]), lo[0], lo[1], cmp2)); emit_insn (gen_iorsi3 (cmp1, cmp1, cmp2)); } cmp = cmp1; } else if (TARGET_FP && !flag_finite_math_only && (op_mode == DFmode || op_mode == SFmode) && code != EQ && code != NE && code != LT && code != GT && code != UNLE && code != UNGE) { enum rtx_code code1, code2, code3; rtx (*fn) (rtx, rtx, rtx, rtx, rtx); jump_code = NE; code3 = UNKNOWN; switch (code) { case UNLT: case UNGT: jump_code = EQ; /* fall through */ case LE: case GE: code1 = code == LE || code == UNGT ? LT : GT; code2 = EQ; break; case UNORDERED: jump_code = EQ; /* fall through */ case ORDERED: code3 = EQ; /* fall through */ case LTGT: code1 = LT; code2 = GT; break; case UNEQ: code1 = LT; code2 = GT; jump_code = EQ; break; default: gcc_unreachable (); } cmp = gen_reg_rtx (SImode); emit_insn (gen_rtx_SET (cmp, gen_rtx_fmt_ee (code1, SImode, op0, op1))); fn = op_mode == DFmode ? gen_cmpdf_ior : gen_cmpsf_ior; emit_insn (fn (cmp, gen_rtx_fmt_ee (code2, SImode, op0, op1), op0, op1, cmp)); if (code3 != UNKNOWN) emit_insn (fn (cmp, gen_rtx_fmt_ee (code3, SImode, op0, op1), op0, op1, cmp)); } else if (op_mode == SImode && (code == NE || code == EQ) && op1 == const0_rtx) cmp = op0; else { bool is_fp_libfunc; is_fp_libfunc = !TARGET_FP && (op_mode == DFmode || op_mode == SFmode); if ((code == NE || code == GEU || code == LEU || code == GE || code == LE) && !is_fp_libfunc) { code = reverse_condition (code); jump_code = EQ; } else if (code == UNGE) { code = LT; jump_code = EQ; } else if (code == UNLE) { code = GT; jump_code = EQ; } else jump_code = NE; if (is_fp_libfunc) { rtx_insn *insns; rtx libfunc; switch (code) { case EQ: libfunc = op_mode == DFmode ? eqdf_libfunc : eqsf_libfunc; break; case NE: libfunc = op_mode == DFmode ? nedf_libfunc : nesf_libfunc; break; case GT: libfunc = op_mode == DFmode ? gtdf_libfunc : gtsf_libfunc; break; case GE: libfunc = op_mode == DFmode ? gedf_libfunc : gesf_libfunc; break; case LT: libfunc = op_mode == DFmode ? ltdf_libfunc : ltsf_libfunc; break; case LE: libfunc = op_mode == DFmode ? ledf_libfunc : lesf_libfunc; break; default: gcc_unreachable (); } start_sequence (); cmp = emit_library_call_value (libfunc, 0, LCT_CONST, SImode, op0, op_mode, op1, op_mode); insns = get_insns (); end_sequence (); emit_libcall_block (insns, cmp, cmp, gen_rtx_fmt_ee (code, SImode, op0, op1)); } else { cmp = gen_reg_rtx (SImode); if (c6x_force_op_for_comparison_p (code, op1)) op1 = force_reg (SImode, op1); emit_insn (gen_rtx_SET (cmp, gen_rtx_fmt_ee (code, SImode, op0, op1))); } } return gen_rtx_fmt_ee (jump_code, mode, cmp, const0_rtx); } /* Return one word of double-word value OP. HIGH_P is true to select the high part, false to select the low part. When encountering auto-increment addressing, we make the assumption that the low part is going to be accessed first. */ rtx c6x_subword (rtx op, bool high_p) { unsigned int byte; machine_mode mode; mode = GET_MODE (op); if (mode == VOIDmode) mode = DImode; if (TARGET_BIG_ENDIAN ? !high_p : high_p) byte = UNITS_PER_WORD; else byte = 0; if (MEM_P (op)) { rtx addr = XEXP (op, 0); if (GET_CODE (addr) == PLUS || REG_P (addr)) return adjust_address (op, word_mode, byte); /* FIXME: should really support autoincrement addressing for multi-word modes. */ gcc_unreachable (); } return simplify_gen_subreg (word_mode, op, mode, byte); } /* Split one or more DImode RTL references into pairs of SImode references. The RTL can be REG, offsettable MEM, integer constant, or CONST_DOUBLE. "operands" is a pointer to an array of DImode RTL to split and "num" is its length. lo_half and hi_half are output arrays that parallel "operands". */ void split_di (rtx operands[], int num, rtx lo_half[], rtx hi_half[]) { while (num--) { rtx op = operands[num]; lo_half[num] = c6x_subword (op, false); hi_half[num] = c6x_subword (op, true); } } /* Return true if VAL is a mask valid for a clr instruction. */ bool c6x_valid_mask_p (HOST_WIDE_INT val) { int i; for (i = 0; i < 32; i++) if (!(val & ((unsigned HOST_WIDE_INT)1 << i))) break; for (; i < 32; i++) if (val & ((unsigned HOST_WIDE_INT)1 << i)) break; for (; i < 32; i++) if (!(val & ((unsigned HOST_WIDE_INT)1 << i))) return false; return true; } /* Expand a block move for a cpymemM pattern. */ bool c6x_expand_cpymem (rtx dst, rtx src, rtx count_exp, rtx align_exp, rtx expected_align_exp ATTRIBUTE_UNUSED, rtx expected_size_exp ATTRIBUTE_UNUSED) { unsigned HOST_WIDE_INT align = 1; unsigned HOST_WIDE_INT src_mem_align, dst_mem_align, min_mem_align; unsigned HOST_WIDE_INT count = 0, offset = 0; unsigned int biggest_move = TARGET_STDW ? 8 : 4; if (CONST_INT_P (align_exp)) align = INTVAL (align_exp); src_mem_align = MEM_ALIGN (src) / BITS_PER_UNIT; dst_mem_align = MEM_ALIGN (dst) / BITS_PER_UNIT; min_mem_align = MIN (src_mem_align, dst_mem_align); if (min_mem_align > align) align = min_mem_align / BITS_PER_UNIT; if (src_mem_align < align) src_mem_align = align; if (dst_mem_align < align) dst_mem_align = align; if (CONST_INT_P (count_exp)) count = INTVAL (count_exp); else return false; /* Make sure we don't need to care about overflow later on. */ if (count > ((unsigned HOST_WIDE_INT) 1 << 30)) return false; if (count >= 28 && (count & 3) == 0 && align >= 4) { tree dst_expr = MEM_EXPR (dst); tree src_expr = MEM_EXPR (src); rtx fn = TARGET_INSNS_64PLUS ? strasgi64p_libfunc : strasgi_libfunc; rtx srcreg = force_reg (Pmode, XEXP (src, 0)); rtx dstreg = force_reg (Pmode, XEXP (dst, 0)); if (src_expr) mark_addressable (src_expr); if (dst_expr) mark_addressable (dst_expr); emit_library_call (fn, LCT_NORMAL, VOIDmode, dstreg, Pmode, srcreg, Pmode, count_exp, SImode); return true; } if (biggest_move > align && !TARGET_INSNS_64) biggest_move = align; if (count / biggest_move > 7) return false; while (count > 0) { rtx reg, reg_lowpart; machine_mode srcmode, dstmode; unsigned HOST_WIDE_INT src_size, dst_size, src_left; int shift; rtx srcmem, dstmem; while (biggest_move > count) biggest_move /= 2; src_size = dst_size = biggest_move; if (src_size > src_mem_align && src_size == 2) src_size = 1; if (dst_size > dst_mem_align && dst_size == 2) dst_size = 1; if (dst_size > src_size) dst_size = src_size; srcmode = int_mode_for_size (src_size * BITS_PER_UNIT, 0).require (); dstmode = int_mode_for_size (dst_size * BITS_PER_UNIT, 0).require (); if (src_size >= 4) reg_lowpart = reg = gen_reg_rtx (srcmode); else { reg = gen_reg_rtx (SImode); reg_lowpart = gen_lowpart (srcmode, reg); } srcmem = adjust_address (copy_rtx (src), srcmode, offset); if (src_size > src_mem_align) { enum insn_code icode = (srcmode == SImode ? CODE_FOR_movmisalignsi : CODE_FOR_movmisaligndi); emit_insn (GEN_FCN (icode) (reg_lowpart, srcmem)); } else emit_move_insn (reg_lowpart, srcmem); src_left = src_size; shift = TARGET_BIG_ENDIAN ? (src_size - dst_size) * BITS_PER_UNIT : 0; while (src_left > 0) { rtx dstreg = reg_lowpart; if (src_size > dst_size) { rtx srcword = reg; int shift_amount = shift & (BITS_PER_WORD - 1); if (src_size > 4) srcword = operand_subword_force (srcword, src_left >= 4 ? 0 : 4, SImode); if (shift_amount > 0) { dstreg = gen_reg_rtx (SImode); emit_insn (gen_lshrsi3 (dstreg, srcword, GEN_INT (shift_amount))); } else dstreg = srcword; dstreg = gen_lowpart (dstmode, dstreg); } dstmem = adjust_address (copy_rtx (dst), dstmode, offset); if (dst_size > dst_mem_align) { enum insn_code icode = (dstmode == SImode ? CODE_FOR_movmisalignsi : CODE_FOR_movmisaligndi); emit_insn (GEN_FCN (icode) (dstmem, dstreg)); } else emit_move_insn (dstmem, dstreg); if (TARGET_BIG_ENDIAN) shift -= dst_size * BITS_PER_UNIT; else shift += dst_size * BITS_PER_UNIT; offset += dst_size; src_left -= dst_size; } count -= src_size; } return true; } /* Subroutine of print_address_operand, print a single address offset OFF for a memory access of mode MEM_MODE, choosing between normal form and scaled form depending on the type of the insn. Misaligned memory references must use the scaled form. */ static void print_address_offset (FILE *file, rtx off, machine_mode mem_mode) { rtx pat; if (c6x_current_insn != NULL_RTX) { pat = PATTERN (c6x_current_insn); if (GET_CODE (pat) == COND_EXEC) pat = COND_EXEC_CODE (pat); if (GET_CODE (pat) == PARALLEL) pat = XVECEXP (pat, 0, 0); if (GET_CODE (pat) == SET && GET_CODE (SET_SRC (pat)) == UNSPEC && XINT (SET_SRC (pat), 1) == UNSPEC_MISALIGNED_ACCESS) { gcc_assert (CONST_INT_P (off) && (INTVAL (off) & (GET_MODE_SIZE (mem_mode) - 1)) == 0); fprintf (file, "[" HOST_WIDE_INT_PRINT_DEC "]", INTVAL (off) / GET_MODE_SIZE (mem_mode)); return; } } fputs ("(", file); output_address (mem_mode, off); fputs (")", file); } static bool c6x_print_operand_punct_valid_p (unsigned char c) { return c == '$' || c == '.' || c == '|'; } static void c6x_print_operand (FILE *, rtx, int); /* Subroutine of c6x_print_operand; used to print a memory reference X to FILE. */ static void c6x_print_address_operand (FILE *file, rtx x, machine_mode mem_mode) { rtx off; switch (GET_CODE (x)) { case PRE_MODIFY: case POST_MODIFY: if (GET_CODE (x) == POST_MODIFY) output_address (mem_mode, XEXP (x, 0)); off = XEXP (XEXP (x, 1), 1); if (XEXP (x, 0) == stack_pointer_rtx) { if (GET_CODE (x) == PRE_MODIFY) gcc_assert (INTVAL (off) > 0); else gcc_assert (INTVAL (off) < 0); } if (CONST_INT_P (off) && INTVAL (off) < 0) { fprintf (file, "--"); off = GEN_INT (-INTVAL (off)); } else fprintf (file, "++"); if (GET_CODE (x) == PRE_MODIFY) output_address (mem_mode, XEXP (x, 0)); print_address_offset (file, off, mem_mode); break; case PLUS: off = XEXP (x, 1); if (CONST_INT_P (off) && INTVAL (off) < 0) { fprintf (file, "-"); off = GEN_INT (-INTVAL (off)); } else fprintf (file, "+"); output_address (mem_mode, XEXP (x, 0)); print_address_offset (file, off, mem_mode); break; case PRE_DEC: gcc_assert (XEXP (x, 0) != stack_pointer_rtx); fprintf (file, "--"); output_address (mem_mode, XEXP (x, 0)); fprintf (file, "[1]"); break; case PRE_INC: fprintf (file, "++"); output_address (mem_mode, XEXP (x, 0)); fprintf (file, "[1]"); break; case POST_INC: gcc_assert (XEXP (x, 0) != stack_pointer_rtx); output_address (mem_mode, XEXP (x, 0)); fprintf (file, "++[1]"); break; case POST_DEC: output_address (mem_mode, XEXP (x, 0)); fprintf (file, "--[1]"); break; case SYMBOL_REF: case CONST: case LABEL_REF: gcc_assert (sdata_symbolic_operand (x, Pmode)); fprintf (file, "+B14("); output_addr_const (file, x); fprintf (file, ")"); break; case UNSPEC: switch (XINT (x, 1)) { case UNSPEC_LOAD_GOT: fputs ("$GOT(", file); output_addr_const (file, XVECEXP (x, 0, 0)); fputs (")", file); break; case UNSPEC_LOAD_SDATA: output_addr_const (file, XVECEXP (x, 0, 0)); break; default: gcc_unreachable (); } break; default: gcc_assert (GET_CODE (x) != MEM); c6x_print_operand (file, x, 0); break; } } /* Return a single character, which is either 'l', 's', 'd' or 'm', which specifies the functional unit used by INSN. */ char c6x_get_unit_specifier (rtx_insn *insn) { enum attr_units units; if (insn_info.exists ()) { int unit = INSN_INFO_ENTRY (INSN_UID (insn)).reservation; return c6x_unit_names[unit][0]; } units = get_attr_units (insn); switch (units) { case UNITS_D: case UNITS_DL: case UNITS_DS: case UNITS_DLS: case UNITS_D_ADDR: return 'd'; case UNITS_L: case UNITS_LS: return 'l'; case UNITS_S: return 's'; case UNITS_M: return 'm'; default: gcc_unreachable (); } } /* Prints the unit specifier field. */ static void c6x_print_unit_specifier_field (FILE *file, rtx_insn *insn) { enum attr_units units = get_attr_units (insn); enum attr_cross cross = get_attr_cross (insn); enum attr_dest_regfile rf = get_attr_dest_regfile (insn); int half; char unitspec; if (units == UNITS_D_ADDR) { enum attr_addr_regfile arf = get_attr_addr_regfile (insn); int t_half; gcc_assert (arf != ADDR_REGFILE_UNKNOWN); half = arf == ADDR_REGFILE_A ? 1 : 2; t_half = rf == DEST_REGFILE_A ? 1 : 2; fprintf (file, ".d%dt%d", half, t_half); return; } if (insn_info.exists ()) { int unit = INSN_INFO_ENTRY (INSN_UID (insn)).reservation; fputs (".", file); fputs (c6x_unit_names[unit], file); if (cross == CROSS_Y) fputs ("x", file); return; } gcc_assert (rf != DEST_REGFILE_UNKNOWN); unitspec = c6x_get_unit_specifier (insn); half = rf == DEST_REGFILE_A ? 1 : 2; fprintf (file, ".%c%d%s", unitspec, half, cross == CROSS_Y ? "x" : ""); } /* Output assembly language output for the address ADDR to FILE. */ static void c6x_print_operand_address (FILE *file, machine_mode mode, rtx addr) { c6x_print_address_operand (file, addr, mode); } /* Print an operand, X, to FILE, with an optional modifier in CODE. Meaning of CODE: $ -- print the unit specifier field for the instruction. . -- print the predicate for the instruction or an emptry string for an unconditional one. | -- print "||" if the insn should be issued in parallel with the previous one. C -- print an opcode suffix for a reversed condition d -- H, W or D as a suffix for ADDA, based on the factor given by the operand D -- print either B, H, W or D as a suffix for ADDA, based on the size of the operand J -- print a predicate j -- like J, but use reverse predicate k -- treat a CONST_INT as a register number and print it as a register k -- like k, but print out a doubleword register n -- print an integer operand, negated p -- print the low part of a DImode register P -- print the high part of a DImode register r -- print the absolute value of an integer operand, shifted right by 1 R -- print the absolute value of an integer operand, shifted right by 2 f -- the first clear bit in an integer operand assumed to be a mask for a clr instruction F -- the last clear bit in such a mask s -- the first set bit in an integer operand assumed to be a mask for a set instruction S -- the last set bit in such a mask U -- print either 1 or 2, depending on the side of the machine used by the operand */ static void c6x_print_operand (FILE *file, rtx x, int code) { int i; HOST_WIDE_INT v; tree t; machine_mode mode; if (code == '|') { if (GET_MODE (c6x_current_insn) != TImode) fputs ("||", file); return; } if (code == '$') { c6x_print_unit_specifier_field (file, c6x_current_insn); return; } if (code == '.') { x = current_insn_predicate; if (x) { unsigned int regno = REGNO (XEXP (x, 0)); fputs ("[", file); if (GET_CODE (x) == EQ) fputs ("!", file); fputs (reg_names [regno], file); fputs ("]", file); } return; } mode = GET_MODE (x); switch (code) { case 'C': case 'c': { enum rtx_code c = GET_CODE (x); if (code == 'C') c = swap_condition (c); fputs (GET_RTX_NAME (c), file); } return; case 'J': case 'j': { unsigned int regno = REGNO (XEXP (x, 0)); if ((GET_CODE (x) == EQ) == (code == 'J')) fputs ("!", file); fputs (reg_names [regno], file); } return; case 'k': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); fprintf (file, "%s", reg_names[v]); return; case 'K': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); gcc_assert ((v & 1) == 0); fprintf (file, "%s:%s", reg_names[v + 1], reg_names[v]); return; case 's': case 'S': case 'f': case 'F': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); for (i = 0; i < 32; i++) { HOST_WIDE_INT tst = v & 1; if (((code == 'f' || code == 'F') && !tst) || ((code == 's' || code == 'S') && tst)) break; v >>= 1; } if (code == 'f' || code == 's') { fprintf (file, "%d", i); return; } for (;i < 32; i++) { HOST_WIDE_INT tst = v & 1; if ((code == 'F' && tst) || (code == 'S' && !tst)) break; v >>= 1; } fprintf (file, "%d", i - 1); return; case 'n': gcc_assert (GET_CODE (x) == CONST_INT); output_addr_const (file, GEN_INT (-INTVAL (x))); return; case 'r': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); if (v < 0) v = -v; output_addr_const (file, GEN_INT (v >> 1)); return; case 'R': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); if (v < 0) v = -v; output_addr_const (file, GEN_INT (v >> 2)); return; case 'd': gcc_assert (GET_CODE (x) == CONST_INT); v = INTVAL (x); fputs (v == 2 ? "h" : v == 4 ? "w" : "d", file); return; case 'p': case 'P': gcc_assert (GET_CODE (x) == REG); v = REGNO (x); if (code == 'P') v++; fputs (reg_names[v], file); return; case 'D': v = 0; if (GET_CODE (x) == CONST) { x = XEXP (x, 0); gcc_assert (GET_CODE (x) == PLUS); gcc_assert (GET_CODE (XEXP (x, 1)) == CONST_INT); v = INTVAL (XEXP (x, 1)); x = XEXP (x, 0); } gcc_assert (GET_CODE (x) == SYMBOL_REF); t = SYMBOL_REF_DECL (x); if (DECL_P (t)) v |= DECL_ALIGN_UNIT (t); else v |= TYPE_ALIGN_UNIT (TREE_TYPE (t)); if (v & 1) fputs ("b", file); else if (v & 2) fputs ("h", file); else fputs ("w", file); return; case 'U': if (MEM_P (x)) { x = XEXP (x, 0); if (GET_CODE (x) == PLUS || GET_RTX_CLASS (GET_CODE (x)) == RTX_AUTOINC) x = XEXP (x, 0); if (GET_CODE (x) == CONST || GET_CODE (x) == SYMBOL_REF) { gcc_assert (sdata_symbolic_operand (x, Pmode)); fputs ("2", file); return; } } gcc_assert (REG_P (x)); if (A_REGNO_P (REGNO (x))) fputs ("1", file); if (B_REGNO_P (REGNO (x))) fputs ("2", file); return; default: switch (GET_CODE (x)) { case REG: if (GET_MODE_SIZE (mode) == 8) fprintf (file, "%s:%s", reg_names[REGNO (x) + 1], reg_names[REGNO (x)]); else fprintf (file, "%s", reg_names[REGNO (x)]); break; case MEM: fputc ('*', file); gcc_assert (XEXP (x, 0) != stack_pointer_rtx); c6x_print_address_operand (file, XEXP (x, 0), GET_MODE (x)); break; case SYMBOL_REF: fputc ('(', file); output_addr_const (file, x); fputc (')', file); break; case CONST_INT: output_addr_const (file, x); break; case CONST_DOUBLE: output_operand_lossage ("invalid const_double operand"); break; default: output_addr_const (file, x); } } } /* Return TRUE if OP is a valid memory address with a base register of class C. If SMALL_OFFSET is true, we disallow memory references which would require a long offset with B14/B15. */ bool c6x_mem_operand (rtx op, enum reg_class c, bool small_offset) { machine_mode mode = GET_MODE (op); rtx base = XEXP (op, 0); switch (GET_CODE (base)) { case REG: break; case PLUS: if (small_offset && (XEXP (base, 0) == stack_pointer_rtx || XEXP (base, 0) == pic_offset_table_rtx)) { if (!c6x_legitimate_address_p_1 (mode, base, true, true)) return false; } /* fall through */ case PRE_INC: case PRE_DEC: case PRE_MODIFY: case POST_INC: case POST_DEC: case POST_MODIFY: base = XEXP (base, 0); break; case CONST: case LABEL_REF: case SYMBOL_REF: gcc_assert (sdata_symbolic_operand (base, Pmode)); return !small_offset && c == B_REGS; default: return false; } return TEST_HARD_REG_BIT (reg_class_contents[ (int) (c)], REGNO (base)); } /* Returns true if X is a valid address for use in a memory reference of mode MODE. If STRICT is true, we do not allow pseudo registers in the address. NO_LARGE_OFFSET is true if we are examining an address for use in a load or store misaligned instruction, or recursively examining an operand inside a PRE/POST_MODIFY. */ bool c6x_legitimate_address_p_1 (machine_mode mode, rtx x, bool strict, bool no_large_offset) { int size, size1; HOST_WIDE_INT off; enum rtx_code code = GET_CODE (x); switch (code) { case PRE_MODIFY: case POST_MODIFY: /* We can't split these into word-sized pieces yet. */ if (!TARGET_STDW && GET_MODE_SIZE (mode) > UNITS_PER_WORD) return false; if (GET_CODE (XEXP (x, 1)) != PLUS) return false; if (!c6x_legitimate_address_p_1 (mode, XEXP (x, 1), strict, true)) return false; if (!rtx_equal_p (XEXP (x, 0), XEXP (XEXP (x, 1), 0))) return false; /* fall through */ case PRE_INC: case PRE_DEC: case POST_INC: case POST_DEC: /* We can't split these into word-sized pieces yet. */ if (!TARGET_STDW && GET_MODE_SIZE (mode) > UNITS_PER_WORD) return false; x = XEXP (x, 0); if (!REG_P (x)) return false; /* fall through */ case REG: if (strict) return REGNO_OK_FOR_BASE_STRICT_P (REGNO (x)); else return REGNO_OK_FOR_BASE_NONSTRICT_P (REGNO (x)); case PLUS: if (!REG_P (XEXP (x, 0)) || !c6x_legitimate_address_p_1 (mode, XEXP (x, 0), strict, false)) return false; /* We cannot ensure currently that both registers end up in the same register file. */ if (REG_P (XEXP (x, 1))) return false; if (mode == BLKmode) size = 4; else if (mode == VOIDmode) /* ??? This can happen during ivopts. */ size = 1; else size = GET_MODE_SIZE (mode); if (flag_pic && GET_CODE (XEXP (x, 1)) == UNSPEC && XINT (XEXP (x, 1), 1) == UNSPEC_LOAD_SDATA && XEXP (x, 0) == pic_offset_table_rtx && sdata_symbolic_operand (XVECEXP (XEXP (x, 1), 0, 0), SImode)) return !no_large_offset && size <= 4; if (flag_pic == 1 && mode == Pmode && GET_CODE (XEXP (x, 1)) == UNSPEC && XINT (XEXP (x, 1), 1) == UNSPEC_LOAD_GOT && XEXP (x, 0) == pic_offset_table_rtx && (GET_CODE (XVECEXP (XEXP (x, 1), 0, 0)) == SYMBOL_REF || GET_CODE (XVECEXP (XEXP (x, 1), 0, 0)) == LABEL_REF)) return !no_large_offset; if (GET_CODE (XEXP (x, 1)) != CONST_INT) return false; off = INTVAL (XEXP (x, 1)); /* If the machine does not have doubleword load/stores, we'll use word size accesses. */ size1 = size; if (size == 2 * UNITS_PER_WORD && !TARGET_STDW) size = UNITS_PER_WORD; if (((HOST_WIDE_INT)size1 - 1) & off) return false; off /= size; if (off > -32 && off < (size1 == size ? 32 : 28)) return true; if (no_large_offset || code != PLUS || XEXP (x, 0) != stack_pointer_rtx || size1 > UNITS_PER_WORD) return false; return off >= 0 && off < 32768; case CONST: case SYMBOL_REF: case LABEL_REF: return (!no_large_offset /* With -fpic, we must wrap it in an unspec to show the B14 dependency. */ && !flag_pic && GET_MODE_SIZE (mode) <= UNITS_PER_WORD && sdata_symbolic_operand (x, Pmode)); default: return false; } } static bool c6x_legitimate_address_p (machine_mode mode, rtx x, bool strict) { return c6x_legitimate_address_p_1 (mode, x, strict, false); } static bool c6x_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x ATTRIBUTE_UNUSED) { return true; } /* Implements TARGET_PREFERRED_RENAME_CLASS. */ static reg_class_t c6x_preferred_rename_class (reg_class_t cl) { if (cl == A_REGS) return NONPREDICATE_A_REGS; if (cl == B_REGS) return NONPREDICATE_B_REGS; if (cl == ALL_REGS || cl == GENERAL_REGS) return NONPREDICATE_REGS; return NO_REGS; } /* Implements FINAL_PRESCAN_INSN. */ void c6x_final_prescan_insn (rtx_insn *insn, rtx *opvec ATTRIBUTE_UNUSED, int noperands ATTRIBUTE_UNUSED) { c6x_current_insn = insn; } /* A structure to describe the stack layout of a function. The layout is as follows: [saved frame pointer (or possibly padding0)] --> incoming stack pointer, new hard frame pointer [saved call-used regs] [optional padding1] --> soft frame pointer [frame] [outgoing arguments] [optional padding2] The structure members are laid out in this order. */ struct c6x_frame { int padding0; /* Number of registers to save. */ int nregs; int padding1; HOST_WIDE_INT frame; int outgoing_arguments_size; int padding2; HOST_WIDE_INT to_allocate; /* The offsets relative to the incoming stack pointer (which becomes HARD_FRAME_POINTER). */ HOST_WIDE_INT frame_pointer_offset; HOST_WIDE_INT b3_offset; /* True if we should call push_rts/pop_rts to save and restore registers. */ bool push_rts; }; /* Return true if we need to save and modify the PIC register in the prologue. */ static bool must_reload_pic_reg_p (void) { if (!TARGET_DSBT) return false; cgraph_node *local_info_node = cgraph_node::local_info_node (current_function_decl); if ((crtl->uses_pic_offset_table || !crtl->is_leaf) && !local_info_node->local) return true; return false; } /* Return 1 if we need to save REGNO. */ static int c6x_save_reg (unsigned int regno) { return ((df_regs_ever_live_p (regno) && !call_used_or_fixed_reg_p (regno)) || (regno == RETURN_ADDR_REGNO && (df_regs_ever_live_p (regno) || !crtl->is_leaf)) || (regno == PIC_OFFSET_TABLE_REGNUM && must_reload_pic_reg_p ())); } /* Examine the number of regs NREGS we've determined we must save. Return true if we should use __c6xabi_push_rts/__c6xabi_pop_rts for prologue and epilogue. */ static bool use_push_rts_p (int nregs) { if (TARGET_INSNS_64PLUS && optimize_function_for_size_p (cfun) && !cfun->machine->contains_sibcall && !cfun->returns_struct && !TARGET_LONG_CALLS && nregs >= 6 && !frame_pointer_needed) return true; return false; } /* Return number of saved general prupose registers. */ int c6x_nsaved_regs (void) { int nregs = 0; int regno; for (regno = 0; regno < FIRST_PSEUDO_REGISTER; regno++) if (c6x_save_reg (regno)) nregs++; return nregs; } /* The safe debug order mandated by the ABI. */ static unsigned reg_save_order[] = { REG_A10, REG_A11, REG_A12, REG_A13, REG_A14, REG_B3, REG_B10, REG_B11, REG_B12, REG_B13, REG_B14, REG_A15 }; #define N_SAVE_ORDER (sizeof reg_save_order / sizeof *reg_save_order) /* Compute the layout of the stack frame and store it in FRAME. */ static void c6x_compute_frame_layout (struct c6x_frame *frame) { HOST_WIDE_INT size = get_frame_size (); HOST_WIDE_INT offset; int nregs; /* We use the four bytes which are technically inside the caller's frame, usually to save the frame pointer. */ offset = -4; frame->padding0 = 0; nregs = c6x_nsaved_regs (); frame->push_rts = false; frame->b3_offset = 0; if (use_push_rts_p (nregs)) { frame->push_rts = true; frame->b3_offset = (TARGET_BIG_ENDIAN ? -12 : -13) * 4; nregs = 14; } else if (c6x_save_reg (REG_B3)) { int idx; for (idx = N_SAVE_ORDER - 1; reg_save_order[idx] != REG_B3; idx--) { if (c6x_save_reg (reg_save_order[idx])) frame->b3_offset -= 4; } } frame->nregs = nregs; if (size == 0 && nregs == 0) { frame->padding0 = 4; frame->padding1 = frame->padding2 = 0; frame->frame_pointer_offset = frame->to_allocate = 0; frame->outgoing_arguments_size = 0; return; } if (!frame->push_rts) offset += frame->nregs * 4; if (offset == 0 && size == 0 && crtl->outgoing_args_size == 0 && !crtl->is_leaf) /* Don't use the bottom of the caller's frame if we have no allocation of our own and call other functions. */ frame->padding0 = frame->padding1 = 4; else if (offset & 4) frame->padding1 = 4; else frame->padding1 = 0; offset += frame->padding0 + frame->padding1; frame->frame_pointer_offset = offset; offset += size; frame->outgoing_arguments_size = crtl->outgoing_args_size; offset += frame->outgoing_arguments_size; if ((offset & 4) == 0) frame->padding2 = 8; else frame->padding2 = 4; frame->to_allocate = offset + frame->padding2; } /* Return the offset between two registers, one to be eliminated, and the other its replacement, at the start of a routine. */ HOST_WIDE_INT c6x_initial_elimination_offset (int from, int to) { struct c6x_frame frame; c6x_compute_frame_layout (&frame); if (from == ARG_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM) return 0; else if (from == FRAME_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM) return -frame.frame_pointer_offset; else { gcc_assert (to == STACK_POINTER_REGNUM); if (from == ARG_POINTER_REGNUM) return frame.to_allocate + (frame.push_rts ? 56 : 0); gcc_assert (from == FRAME_POINTER_REGNUM); return frame.to_allocate - frame.frame_pointer_offset; } } /* Given FROM and TO register numbers, say whether this elimination is allowed. Frame pointer elimination is automatically handled. */ static bool c6x_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) { if (to == STACK_POINTER_REGNUM) return !frame_pointer_needed; return true; } /* Emit insns to increment the stack pointer by OFFSET. If FRAME_RELATED_P, set the RTX_FRAME_RELATED_P flag on the insns. Does nothing if the offset is zero. */ static void emit_add_sp_const (HOST_WIDE_INT offset, bool frame_related_p) { rtx to_add = GEN_INT (offset); rtx orig_to_add = to_add; rtx_insn *insn; if (offset == 0) return; if (offset < -32768 || offset > 32767) { rtx reg = gen_rtx_REG (SImode, REG_A0); rtx low = GEN_INT (trunc_int_for_mode (offset, HImode)); insn = emit_insn (gen_movsi_high (reg, low)); if (frame_related_p) RTX_FRAME_RELATED_P (insn) = 1; insn = emit_insn (gen_movsi_lo_sum (reg, reg, to_add)); if (frame_related_p) RTX_FRAME_RELATED_P (insn) = 1; to_add = reg; } insn = emit_insn (gen_addsi3 (stack_pointer_rtx, stack_pointer_rtx, to_add)); if (frame_related_p) { if (REG_P (to_add)) add_reg_note (insn, REG_FRAME_RELATED_EXPR, gen_rtx_SET (stack_pointer_rtx, gen_rtx_PLUS (Pmode, stack_pointer_rtx, orig_to_add))); RTX_FRAME_RELATED_P (insn) = 1; } } /* Prologue and epilogue. */ void c6x_expand_prologue (void) { struct c6x_frame frame; rtx_insn *insn; rtx mem; int nsaved = 0; HOST_WIDE_INT initial_offset, off, added_already; c6x_compute_frame_layout (&frame); if (flag_stack_usage_info) current_function_static_stack_size = frame.to_allocate; initial_offset = -frame.to_allocate; if (frame.push_rts) { emit_insn (gen_push_rts ()); nsaved = frame.nregs; } /* If the offsets would be too large for the memory references we will create to save registers, do the stack allocation in two parts. Ensure by subtracting 8 that we don't store to the word pointed to by the stack pointer. */ if (initial_offset < -32768) initial_offset = -frame.frame_pointer_offset - 8; if (frame.to_allocate > 0) gcc_assert (initial_offset != 0); off = -initial_offset + 4 - frame.padding0; mem = gen_frame_mem (Pmode, stack_pointer_rtx); added_already = 0; if (frame_pointer_needed) { rtx fp_reg = gen_rtx_REG (SImode, REG_A15); /* We go through some contortions here to both follow the ABI's recommendation that FP == incoming SP, and to avoid writing or reading the word pointed to by the stack pointer. */ rtx addr = gen_rtx_POST_MODIFY (Pmode, stack_pointer_rtx, gen_rtx_PLUS (Pmode, stack_pointer_rtx, GEN_INT (-8))); insn = emit_move_insn (gen_frame_mem (Pmode, addr), fp_reg); RTX_FRAME_RELATED_P (insn) = 1; nsaved++; insn = emit_insn (gen_addsi3 (hard_frame_pointer_rtx, stack_pointer_rtx, GEN_INT (8))); RTX_FRAME_RELATED_P (insn) = 1; off -= 4; added_already = -8; } emit_add_sp_const (initial_offset - added_already, true); if (nsaved < frame.nregs) { unsigned i; for (i = 0; i < N_SAVE_ORDER; i++) { int idx = N_SAVE_ORDER - i - 1; unsigned regno = reg_save_order[idx]; rtx reg; machine_mode save_mode = SImode; if (regno == REG_A15 && frame_pointer_needed) /* Already saved. */ continue; if (!c6x_save_reg (regno)) continue; if (TARGET_STDW && (off & 4) == 0 && off <= 256 && (regno & 1) == 1 && i + 1 < N_SAVE_ORDER && reg_save_order[idx - 1] == regno - 1 && c6x_save_reg (regno - 1)) { save_mode = DImode; regno--; i++; } reg = gen_rtx_REG (save_mode, regno); off -= GET_MODE_SIZE (save_mode); insn = emit_move_insn (adjust_address (mem, save_mode, off), reg); RTX_FRAME_RELATED_P (insn) = 1; nsaved += hard_regno_nregs (regno, save_mode); } } gcc_assert (nsaved == frame.nregs); emit_add_sp_const (-frame.to_allocate - initial_offset, true); if (must_reload_pic_reg_p ()) { if (dsbt_decl == NULL) { tree t; t = build_index_type (integer_one_node); t = build_array_type (integer_type_node, t); t = build_decl (BUILTINS_LOCATION, VAR_DECL, get_identifier ("__c6xabi_DSBT_BASE"), t); DECL_ARTIFICIAL (t) = 1; DECL_IGNORED_P (t) = 1; DECL_EXTERNAL (t) = 1; TREE_STATIC (t) = 1; TREE_PUBLIC (t) = 1; TREE_USED (t) = 1; dsbt_decl = t; } emit_insn (gen_setup_dsbt (pic_offset_table_rtx, XEXP (DECL_RTL (dsbt_decl), 0))); } } void c6x_expand_epilogue (bool sibcall) { unsigned i; struct c6x_frame frame; rtx mem; HOST_WIDE_INT off; int nsaved = 0; c6x_compute_frame_layout (&frame); mem = gen_frame_mem (Pmode, stack_pointer_rtx); /* Insert a dummy set/use of the stack pointer. This creates a scheduler barrier between the prologue saves and epilogue restores. */ emit_insn (gen_epilogue_barrier (stack_pointer_rtx, stack_pointer_rtx)); /* If the offsets would be too large for the memory references we will create to restore registers, do a preliminary stack adjustment here. */ off = frame.to_allocate - frame.frame_pointer_offset + frame.padding1; if (frame.push_rts) { nsaved = frame.nregs; } else { if (frame.to_allocate > 32768) { /* Don't add the entire offset so that we leave an unused word above the stack pointer. */ emit_add_sp_const ((off - 16) & ~7, false); off &= 7; off += 16; } for (i = 0; i < N_SAVE_ORDER; i++) { unsigned regno = reg_save_order[i]; rtx reg; machine_mode save_mode = SImode; if (!c6x_save_reg (regno)) continue; if (regno == REG_A15 && frame_pointer_needed) continue; if (TARGET_STDW && (off & 4) == 0 && off < 256 && (regno & 1) == 0 && i + 1 < N_SAVE_ORDER && reg_save_order[i + 1] == regno + 1 && c6x_save_reg (regno + 1)) { save_mode = DImode; i++; } reg = gen_rtx_REG (save_mode, regno); emit_move_insn (reg, adjust_address (mem, save_mode, off)); off += GET_MODE_SIZE (save_mode); nsaved += hard_regno_nregs (regno, save_mode); } } if (!frame_pointer_needed) emit_add_sp_const (off + frame.padding0 - 4, false); else { rtx fp_reg = gen_rtx_REG (SImode, REG_A15); rtx addr = gen_rtx_PRE_MODIFY (Pmode, stack_pointer_rtx, gen_rtx_PLUS (Pmode, stack_pointer_rtx, GEN_INT (8))); emit_insn (gen_addsi3 (stack_pointer_rtx, hard_frame_pointer_rtx, GEN_INT (-8))); emit_move_insn (fp_reg, gen_frame_mem (Pmode, addr)); nsaved++; } gcc_assert (nsaved == frame.nregs); if (!sibcall) { if (frame.push_rts) emit_jump_insn (gen_pop_rts ()); else emit_jump_insn (gen_return_internal (gen_rtx_REG (SImode, RETURN_ADDR_REGNO))); } } /* Return the value of the return address for the frame COUNT steps up from the current frame, after the prologue. We punt for everything but the current frame by returning const0_rtx. */ rtx c6x_return_addr_rtx (int count) { if (count != 0) return const0_rtx; return get_hard_reg_initial_val (Pmode, RETURN_ADDR_REGNO); } /* Return true iff TYPE is one of the shadow types. */ static bool shadow_type_p (enum attr_type type) { return (type == TYPE_SHADOW || type == TYPE_LOAD_SHADOW || type == TYPE_MULT_SHADOW); } /* Return true iff INSN is a shadow pattern. */ static bool shadow_p (rtx_insn *insn) { if (!NONDEBUG_INSN_P (insn) || recog_memoized (insn) < 0) return false; return shadow_type_p (get_attr_type (insn)); } /* Return true iff INSN is a shadow or blockage pattern. */ static bool shadow_or_blockage_p (rtx_insn *insn) { enum attr_type type; if (!NONDEBUG_INSN_P (insn) || recog_memoized (insn) < 0) return false; type = get_attr_type (insn); return shadow_type_p (type) || type == TYPE_BLOCKAGE; } /* Translate UNITS into a bitmask of units we can reserve for this insn. */ static int get_reservation_flags (enum attr_units units) { switch (units) { case UNITS_D: case UNITS_D_ADDR: return RESERVATION_FLAG_D; case UNITS_L: return RESERVATION_FLAG_L; case UNITS_S: return RESERVATION_FLAG_S; case UNITS_M: return RESERVATION_FLAG_M; case UNITS_LS: return RESERVATION_FLAG_LS; case UNITS_DL: return RESERVATION_FLAG_DL; case UNITS_DS: return RESERVATION_FLAG_DS; case UNITS_DLS: return RESERVATION_FLAG_DLS; default: return 0; } } /* Compute the side of the machine used by INSN, which reserves UNITS. This must match the reservations in the scheduling description. */ static int get_insn_side (rtx_insn *insn, enum attr_units units) { if (units == UNITS_D_ADDR) return (get_attr_addr_regfile (insn) == ADDR_REGFILE_A ? 0 : 1); else { enum attr_dest_regfile rf = get_attr_dest_regfile (insn); if (rf == DEST_REGFILE_ANY) return get_attr_type (insn) == TYPE_BRANCH ? 0 : 1; else return rf == DEST_REGFILE_A ? 0 : 1; } } /* After scheduling, walk the insns between HEAD and END and assign unit reservations. */ static void assign_reservations (rtx_insn *head, rtx_insn *end) { rtx_insn *insn; for (insn = head; insn != NEXT_INSN (end); insn = NEXT_INSN (insn)) { unsigned int sched_mask, reserved; rtx_insn *within, *last; int pass; int rsrv[2]; int rsrv_count[2][4]; int i; if (GET_MODE (insn) != TImode) continue; reserved = 0; last = NULL; /* Find the last insn in the packet. It has a state recorded for it, which we can use to determine the units we should be using. */ for (within = insn; (within != NEXT_INSN (end) && (within == insn || GET_MODE (within) != TImode)); within = NEXT_INSN (within)) { int icode; if (!NONDEBUG_INSN_P (within)) continue; icode = recog_memoized (within); if (icode < 0) continue; if (shadow_p (within)) continue; if (INSN_INFO_ENTRY (INSN_UID (within)).reservation != 0) reserved |= 1 << INSN_INFO_ENTRY (INSN_UID (within)).reservation; last = within; } if (last == NULL_RTX) continue; sched_mask = INSN_INFO_ENTRY (INSN_UID (last)).unit_mask; sched_mask &= ~reserved; memset (rsrv_count, 0, sizeof rsrv_count); rsrv[0] = rsrv[1] = ~0; for (i = 0; i < 8; i++) { int side = i / 4; int unit = i & 3; unsigned unit_bit = 1 << (unit + side * UNIT_QID_SIDE_OFFSET); /* Clear the bits which we expect to reserve in the following loop, leaving the ones set which aren't present in the scheduler's state and shouldn't be reserved. */ if (sched_mask & unit_bit) rsrv[i / 4] &= ~(1 << unit); } /* Walk through the insns that occur in the same cycle. We use multiple passes to assign units, assigning for insns with the most specific requirements first. */ for (pass = 0; pass < 4; pass++) for (within = insn; (within != NEXT_INSN (end) && (within == insn || GET_MODE (within) != TImode)); within = NEXT_INSN (within)) { int uid = INSN_UID (within); int this_rsrv, side; int icode; enum attr_units units; enum attr_type type; int j; if (!NONDEBUG_INSN_P (within)) continue; icode = recog_memoized (within); if (icode < 0) continue; if (INSN_INFO_ENTRY (uid).reservation != 0) continue; units = get_attr_units (within); type = get_attr_type (within); this_rsrv = get_reservation_flags (units); if (this_rsrv == 0) continue; side = get_insn_side (within, units); /* Certain floating point instructions are treated specially. If an insn can choose between units it can reserve, and its reservation spans more than one cycle, the reservation contains special markers in the first cycle to help us reconstruct what the automaton chose. */ if ((type == TYPE_ADDDP || type == TYPE_FP4) && units == UNITS_LS) { int test1_code = ((type == TYPE_FP4 ? UNIT_QID_FPL1 : UNIT_QID_ADDDPL1) + side * UNIT_QID_SIDE_OFFSET); int test2_code = ((type == TYPE_FP4 ? UNIT_QID_FPS1 : UNIT_QID_ADDDPS1) + side * UNIT_QID_SIDE_OFFSET); if ((sched_mask & (1 << test1_code)) != 0) { this_rsrv = RESERVATION_FLAG_L; sched_mask &= ~(1 << test1_code); } else if ((sched_mask & (1 << test2_code)) != 0) { this_rsrv = RESERVATION_FLAG_S; sched_mask &= ~(1 << test2_code); } } if ((this_rsrv & (this_rsrv - 1)) == 0) { int t = exact_log2 (this_rsrv) + side * UNIT_QID_SIDE_OFFSET; rsrv[side] |= this_rsrv; INSN_INFO_ENTRY (uid).reservation = t; continue; } if (pass == 1) { for (j = 0; j < 4; j++) if (this_rsrv & (1 << j)) rsrv_count[side][j]++; continue; } if ((pass == 2 && this_rsrv != RESERVATION_FLAG_DLS) || (pass == 3 && this_rsrv == RESERVATION_FLAG_DLS)) { int best = -1, best_cost = INT_MAX; for (j = 0; j < 4; j++) if ((this_rsrv & (1 << j)) && !(rsrv[side] & (1 << j)) && rsrv_count[side][j] < best_cost) { best_cost = rsrv_count[side][j]; best = j; } gcc_assert (best != -1); rsrv[side] |= 1 << best; for (j = 0; j < 4; j++) if ((this_rsrv & (1 << j)) && j != best) rsrv_count[side][j]--; INSN_INFO_ENTRY (uid).reservation = best + side * UNIT_QID_SIDE_OFFSET; } } } } /* Return a factor by which to weight unit imbalances for a reservation R. */ static int unit_req_factor (enum unitreqs r) { switch (r) { case UNIT_REQ_D: case UNIT_REQ_L: case UNIT_REQ_S: case UNIT_REQ_M: case UNIT_REQ_X: case UNIT_REQ_T: return 1; case UNIT_REQ_DL: case UNIT_REQ_LS: case UNIT_REQ_DS: return 2; case UNIT_REQ_DLS: return 3; default: gcc_unreachable (); } } /* Examine INSN, and store in REQ1/SIDE1 and REQ2/SIDE2 the unit requirements. Returns zero if INSN can't be handled, otherwise either one or two to show how many of the two pairs are in use. REQ1 is always used, it holds what is normally thought of as the instructions reservation, e.g. UNIT_REQ_DL. REQ2 is used to either describe a cross path, or for loads/stores, the T unit. */ static int get_unit_reqs (rtx_insn *insn, int *req1, int *side1, int *req2, int *side2) { enum attr_units units; enum attr_cross cross; int side, req; if (!NONDEBUG_INSN_P (insn) || recog_memoized (insn) < 0) return 0; units = get_attr_units (insn); if (units == UNITS_UNKNOWN) return 0; side = get_insn_side (insn, units); cross = get_attr_cross (insn); req = (units == UNITS_D ? UNIT_REQ_D : units == UNITS_D_ADDR ? UNIT_REQ_D : units == UNITS_DL ? UNIT_REQ_DL : units == UNITS_DS ? UNIT_REQ_DS : units == UNITS_L ? UNIT_REQ_L : units == UNITS_LS ? UNIT_REQ_LS : units == UNITS_S ? UNIT_REQ_S : units == UNITS_M ? UNIT_REQ_M : units == UNITS_DLS ? UNIT_REQ_DLS : -1); gcc_assert (req != -1); *req1 = req; *side1 = side; if (units == UNITS_D_ADDR) { *req2 = UNIT_REQ_T; *side2 = side ^ (cross == CROSS_Y ? 1 : 0); return 2; } else if (cross == CROSS_Y) { *req2 = UNIT_REQ_X; *side2 = side; return 2; } return 1; } /* Walk the insns between and including HEAD and TAIL, and mark the resource requirements in the unit_reqs table. */ static void count_unit_reqs (unit_req_table reqs, rtx_insn *head, rtx_insn *tail) { rtx_insn *insn; memset (reqs, 0, sizeof (unit_req_table)); for (insn = head; insn != NEXT_INSN (tail); insn = NEXT_INSN (insn)) { int side1, side2, req1, req2; switch (get_unit_reqs (insn, &req1, &side1, &req2, &side2)) { case 2: reqs[side2][req2]++; /* fall through */ case 1: reqs[side1][req1]++; break; } } } /* Update the table REQS by merging more specific unit reservations into more general ones, i.e. counting (for example) UNIT_REQ_D also in UNIT_REQ_DL, DS, and DLS. */ static void merge_unit_reqs (unit_req_table reqs) { int side; for (side = 0; side < 2; side++) { int d = reqs[side][UNIT_REQ_D]; int l = reqs[side][UNIT_REQ_L]; int s = reqs[side][UNIT_REQ_S]; int dl = reqs[side][UNIT_REQ_DL]; int ls = reqs[side][UNIT_REQ_LS]; int ds = reqs[side][UNIT_REQ_DS]; reqs[side][UNIT_REQ_DL] += d; reqs[side][UNIT_REQ_DL] += l; reqs[side][UNIT_REQ_DS] += d; reqs[side][UNIT_REQ_DS] += s; reqs[side][UNIT_REQ_LS] += l; reqs[side][UNIT_REQ_LS] += s; reqs[side][UNIT_REQ_DLS] += ds + dl + ls + d + l + s; } } /* Examine the table REQS and return a measure of unit imbalance by comparing the two sides of the machine. If, for example, D1 is used twice and D2 used not at all, the return value should be 1 in the absence of other imbalances. */ static int unit_req_imbalance (unit_req_table reqs) { int val = 0; int i; for (i = 0; i < UNIT_REQ_MAX; i++) { int factor = unit_req_factor ((enum unitreqs) i); int diff = abs (reqs[0][i] - reqs[1][i]); val += (diff + factor - 1) / factor / 2; } return val; } /* Return the resource-constrained minimum iteration interval given the data in the REQS table. This must have been processed with merge_unit_reqs already. */ static int res_mii (unit_req_table reqs) { int side, req; int worst = 1; for (side = 0; side < 2; side++) for (req = 0; req < UNIT_REQ_MAX; req++) { int factor = unit_req_factor ((enum unitreqs) req); worst = MAX ((reqs[side][UNIT_REQ_D] + factor - 1) / factor, worst); } return worst; } /* Examine INSN, and store in PMASK1 and PMASK2 bitmasks that represent the operands that are involved in the (up to) two reservations, as found by get_unit_reqs. Return true if we did this successfully, false if we couldn't identify what to do with INSN. */ static bool get_unit_operand_masks (rtx_insn *insn, unsigned int *pmask1, unsigned int *pmask2) { enum attr_op_pattern op_pat; if (recog_memoized (insn) < 0) return 0; if (GET_CODE (PATTERN (insn)) == COND_EXEC) return false; extract_insn (insn); op_pat = get_attr_op_pattern (insn); if (op_pat == OP_PATTERN_DT) { gcc_assert (recog_data.n_operands == 2); *pmask1 = 1 << 0; *pmask2 = 1 << 1; return true; } else if (op_pat == OP_PATTERN_TD) { gcc_assert (recog_data.n_operands == 2); *pmask1 = 1 << 1; *pmask2 = 1 << 0; return true; } else if (op_pat == OP_PATTERN_SXS) { gcc_assert (recog_data.n_operands == 3); *pmask1 = (1 << 0) | (1 << 2); *pmask2 = 1 << 1; return true; } else if (op_pat == OP_PATTERN_SX) { gcc_assert (recog_data.n_operands == 2); *pmask1 = 1 << 0; *pmask2 = 1 << 1; return true; } else if (op_pat == OP_PATTERN_SSX) { gcc_assert (recog_data.n_operands == 3); *pmask1 = (1 << 0) | (1 << 1); *pmask2 = 1 << 2; return true; } return false; } /* Try to replace a register in INSN, which has corresponding rename info from regrename_analyze in INFO. OP_MASK and ORIG_SIDE provide information about the operands that must be renamed and the side they are on. REQS is the table of unit reservations in the loop between HEAD and TAIL. We recompute this information locally after our transformation, and keep it only if we managed to improve the balance. */ static void try_rename_operands (rtx_insn *head, rtx_insn *tail, unit_req_table reqs, rtx insn, insn_rr_info *info, unsigned int op_mask, int orig_side) { enum reg_class super_class = orig_side == 0 ? B_REGS : A_REGS; HARD_REG_SET unavailable; du_head_p this_head; struct du_chain *chain; int i; unsigned tmp_mask; int best_reg, old_reg; vec involved_chains = vNULL; unit_req_table new_reqs; bool ok; for (i = 0, tmp_mask = op_mask; tmp_mask; i++) { du_head_p op_chain; if ((tmp_mask & (1 << i)) == 0) continue; if (info->op_info[i].n_chains != 1) goto out_fail; op_chain = regrename_chain_from_id (info->op_info[i].heads[0]->id); involved_chains.safe_push (op_chain); tmp_mask &= ~(1 << i); } if (involved_chains.length () > 1) goto out_fail; this_head = involved_chains[0]; if (this_head->cannot_rename) goto out_fail; for (chain = this_head->first; chain; chain = chain->next_use) { unsigned int mask1, mask2, mask_changed; int count, side1, side2, req1, req2; insn_rr_info *this_rr = &insn_rr[INSN_UID (chain->insn)]; count = get_unit_reqs (chain->insn, &req1, &side1, &req2, &side2); if (count == 0) goto out_fail; if (!get_unit_operand_masks (chain->insn, &mask1, &mask2)) goto out_fail; extract_insn (chain->insn); mask_changed = 0; for (i = 0; i < recog_data.n_operands; i++) { int j; int n_this_op = this_rr->op_info[i].n_chains; for (j = 0; j < n_this_op; j++) { du_head_p other = this_rr->op_info[i].heads[j]; if (regrename_chain_from_id (other->id) == this_head) break; } if (j == n_this_op) continue; if (n_this_op != 1) goto out_fail; mask_changed |= 1 << i; } gcc_assert (mask_changed != 0); if (mask_changed != mask1 && mask_changed != mask2) goto out_fail; } /* If we get here, we can do the renaming. */ unavailable = ~reg_class_contents[super_class]; old_reg = this_head->regno; best_reg = find_rename_reg (this_head, super_class, &unavailable, old_reg, true); ok = regrename_do_replace (this_head, best_reg); gcc_assert (ok); count_unit_reqs (new_reqs, head, PREV_INSN (tail)); merge_unit_reqs (new_reqs); if (dump_file) { fprintf (dump_file, "reshuffle for insn %d, op_mask %x, " "original side %d, new reg %d\n", INSN_UID (insn), op_mask, orig_side, best_reg); fprintf (dump_file, " imbalance %d -> %d\n", unit_req_imbalance (reqs), unit_req_imbalance (new_reqs)); } if (unit_req_imbalance (new_reqs) > unit_req_imbalance (reqs)) { ok = regrename_do_replace (this_head, old_reg); gcc_assert (ok); } else memcpy (reqs, new_reqs, sizeof (unit_req_table)); out_fail: involved_chains.release (); } /* Find insns in LOOP which would, if shifted to the other side of the machine, reduce an imbalance in the unit reservations. */ static void reshuffle_units (basic_block loop) { rtx_insn *head = BB_HEAD (loop); rtx_insn *tail = BB_END (loop); rtx_insn *insn; unit_req_table reqs; edge e; edge_iterator ei; bitmap_head bbs; count_unit_reqs (reqs, head, PREV_INSN (tail)); merge_unit_reqs (reqs); regrename_init (true); bitmap_initialize (&bbs, &bitmap_default_obstack); FOR_EACH_EDGE (e, ei, loop->preds) bitmap_set_bit (&bbs, e->src->index); bitmap_set_bit (&bbs, loop->index); regrename_analyze (&bbs); for (insn = head; insn != NEXT_INSN (tail); insn = NEXT_INSN (insn)) { enum attr_units units; int count, side1, side2, req1, req2; unsigned int mask1, mask2; insn_rr_info *info; if (!NONDEBUG_INSN_P (insn)) continue; count = get_unit_reqs (insn, &req1, &side1, &req2, &side2); if (count == 0) continue; if (!get_unit_operand_masks (insn, &mask1, &mask2)) continue; info = &insn_rr[INSN_UID (insn)]; if (info->op_info == NULL) continue; if (reqs[side1][req1] > 1 && reqs[side1][req1] > 2 * reqs[side1 ^ 1][req1]) { try_rename_operands (head, tail, reqs, insn, info, mask1, side1); } units = get_attr_units (insn); if (units == UNITS_D_ADDR) { gcc_assert (count == 2); if (reqs[side2][req2] > 1 && reqs[side2][req2] > 2 * reqs[side2 ^ 1][req2]) { try_rename_operands (head, tail, reqs, insn, info, mask2, side2); } } } regrename_finish (); } /* Backend scheduling state. */ typedef struct c6x_sched_context { /* The current scheduler clock, saved in the sched_reorder hook. */ int curr_sched_clock; /* Number of insns issued so far in this cycle. */ int issued_this_cycle; /* We record the time at which each jump occurs in JUMP_CYCLES. The theoretical maximum for number of jumps in flight is 12: 2 every cycle, with a latency of 6 cycles each. This is a circular buffer; JUMP_CYCLE_INDEX is the pointer to the start. Earlier jumps have a higher index. This array should be accessed through the jump_cycle function. */ int jump_cycles[12]; int jump_cycle_index; /* In parallel with jump_cycles, this array records the opposite of the condition used in each pending jump. This is used to predicate insns that are scheduled in the jump's delay slots. If this is NULL_RTX no such predication happens. */ rtx jump_cond[12]; /* Similar to the jump_cycles mechanism, but here we take into account all insns with delay slots, to avoid scheduling asms into the delay slots. */ int delays_finished_at; /* The following variable value is the last issued insn. */ rtx_insn *last_scheduled_insn; /* The last issued insn that isn't a shadow of another. */ rtx_insn *last_scheduled_iter0; /* The following variable value is DFA state before issuing the first insn in the current clock cycle. We do not use this member of the structure directly; we copy the data in and out of prev_cycle_state. */ state_t prev_cycle_state_ctx; int reg_n_accesses[FIRST_PSEUDO_REGISTER]; int reg_n_xaccesses[FIRST_PSEUDO_REGISTER]; int reg_set_in_cycle[FIRST_PSEUDO_REGISTER]; int tmp_reg_n_accesses[FIRST_PSEUDO_REGISTER]; int tmp_reg_n_xaccesses[FIRST_PSEUDO_REGISTER]; } *c6x_sched_context_t; /* The current scheduling state. */ static struct c6x_sched_context ss; /* The following variable value is DFA state before issuing the first insn in the current clock cycle. This is used in c6x_variable_issue for comparison with the state after issuing the last insn in a cycle. */ static state_t prev_cycle_state; /* Set when we discover while processing an insn that it would lead to too many accesses of the same register. */ static bool reg_access_stall; /* The highest insn uid after delayed insns were split, but before loop bodies were copied by the modulo scheduling code. */ static int sploop_max_uid_iter0; /* Look up the jump cycle with index N. For an out-of-bounds N, we return 0, so the caller does not specifically have to test for it. */ static int get_jump_cycle (int n) { if (n >= 12) return 0; n += ss.jump_cycle_index; if (n >= 12) n -= 12; return ss.jump_cycles[n]; } /* Look up the jump condition with index N. */ static rtx get_jump_cond (int n) { if (n >= 12) return NULL_RTX; n += ss.jump_cycle_index; if (n >= 12) n -= 12; return ss.jump_cond[n]; } /* Return the index of the first jump that occurs after CLOCK_VAR. If no jump has delay slots beyond CLOCK_VAR, return -1. */ static int first_jump_index (int clock_var) { int retval = -1; int n = 0; for (;;) { int t = get_jump_cycle (n); if (t <= clock_var) break; retval = n; n++; } return retval; } /* Add a new entry in our scheduling state for a jump that occurs in CYCLE and has the opposite condition of COND. */ static void record_jump (int cycle, rtx cond) { if (ss.jump_cycle_index == 0) ss.jump_cycle_index = 11; else ss.jump_cycle_index--; ss.jump_cycles[ss.jump_cycle_index] = cycle; ss.jump_cond[ss.jump_cycle_index] = cond; } /* Set the clock cycle of INSN to CYCLE. Also clears the insn's entry in new_conditions. */ static void insn_set_clock (rtx insn, int cycle) { unsigned uid = INSN_UID (insn); if (uid >= INSN_INFO_LENGTH) insn_info.safe_grow (uid * 5 / 4 + 10, true); INSN_INFO_ENTRY (uid).clock = cycle; INSN_INFO_ENTRY (uid).new_cond = NULL; INSN_INFO_ENTRY (uid).reservation = 0; INSN_INFO_ENTRY (uid).ebb_start = false; } /* Return the clock cycle we set for the insn with uid UID. */ static int insn_uid_get_clock (int uid) { return INSN_INFO_ENTRY (uid).clock; } /* Return the clock cycle we set for INSN. */ static int insn_get_clock (rtx insn) { return insn_uid_get_clock (INSN_UID (insn)); } /* Examine INSN, and if it is a conditional jump of any kind, return the opposite of the condition in which it branches. Otherwise, return NULL_RTX. */ static rtx condjump_opposite_condition (rtx insn) { rtx pat = PATTERN (insn); int icode = INSN_CODE (insn); rtx x = NULL; if (icode == CODE_FOR_br_true || icode == CODE_FOR_br_false) { x = XEXP (SET_SRC (pat), 0); if (icode == CODE_FOR_br_false) return x; } if (GET_CODE (pat) == COND_EXEC) { rtx t = COND_EXEC_CODE (pat); if ((GET_CODE (t) == PARALLEL && GET_CODE (XVECEXP (t, 0, 0)) == RETURN) || (GET_CODE (t) == UNSPEC && XINT (t, 1) == UNSPEC_REAL_JUMP) || (GET_CODE (t) == SET && SET_DEST (t) == pc_rtx)) x = COND_EXEC_TEST (pat); } if (x != NULL_RTX) { enum rtx_code code = GET_CODE (x); x = gen_rtx_fmt_ee (code == EQ ? NE : EQ, GET_MODE (x), XEXP (x, 0), XEXP (x, 1)); } return x; } /* Return true iff COND1 and COND2 are exactly opposite conditions one of them NE and the other EQ. */ static bool conditions_opposite_p (rtx cond1, rtx cond2) { return (rtx_equal_p (XEXP (cond1, 0), XEXP (cond2, 0)) && rtx_equal_p (XEXP (cond1, 1), XEXP (cond2, 1)) && GET_CODE (cond1) == reverse_condition (GET_CODE (cond2))); } /* Return true if we can add a predicate COND to INSN, or if INSN already has that predicate. If DOIT is true, also perform the modification. */ static bool predicate_insn (rtx_insn *insn, rtx cond, bool doit) { int icode; if (cond == NULL_RTX) { gcc_assert (!doit); return false; } if (get_attr_predicable (insn) == PREDICABLE_YES && GET_CODE (PATTERN (insn)) != COND_EXEC) { if (doit) { cond = copy_rtx (cond); rtx newpat = gen_rtx_COND_EXEC (VOIDmode, cond, PATTERN (insn)); PATTERN (insn) = newpat; INSN_CODE (insn) = -1; } return true; } if (GET_CODE (PATTERN (insn)) == COND_EXEC && rtx_equal_p (COND_EXEC_TEST (PATTERN (insn)), cond)) return true; icode = INSN_CODE (insn); if (icode == CODE_FOR_real_jump || icode == CODE_FOR_jump || icode == CODE_FOR_indirect_jump) { rtx pat = PATTERN (insn); rtx dest = (icode == CODE_FOR_real_jump ? XVECEXP (pat, 0, 0) : icode == CODE_FOR_jump ? XEXP (SET_SRC (pat), 0) : SET_SRC (pat)); if (doit) { rtx newpat; if (REG_P (dest)) newpat = gen_rtx_COND_EXEC (VOIDmode, cond, PATTERN (insn)); else newpat = gen_br_true (cond, XEXP (cond, 0), dest); PATTERN (insn) = newpat; INSN_CODE (insn) = -1; } return true; } if (INSN_CODE (insn) == CODE_FOR_br_true) { rtx br_cond = XEXP (SET_SRC (PATTERN (insn)), 0); return rtx_equal_p (br_cond, cond); } if (INSN_CODE (insn) == CODE_FOR_br_false) { rtx br_cond = XEXP (SET_SRC (PATTERN (insn)), 0); return conditions_opposite_p (br_cond, cond); } return false; } /* Initialize SC. Used by c6x_init_sched_context and c6x_sched_init. */ static void init_sched_state (c6x_sched_context_t sc) { sc->last_scheduled_insn = NULL; sc->last_scheduled_iter0 = NULL; sc->issued_this_cycle = 0; memset (sc->jump_cycles, 0, sizeof sc->jump_cycles); memset (sc->jump_cond, 0, sizeof sc->jump_cond); sc->jump_cycle_index = 0; sc->delays_finished_at = 0; sc->curr_sched_clock = 0; sc->prev_cycle_state_ctx = xmalloc (dfa_state_size); memset (sc->reg_n_accesses, 0, sizeof sc->reg_n_accesses); memset (sc->reg_n_xaccesses, 0, sizeof sc->reg_n_xaccesses); memset (sc->reg_set_in_cycle, 0, sizeof sc->reg_set_in_cycle); state_reset (sc->prev_cycle_state_ctx); } /* Allocate store for new scheduling context. */ static void * c6x_alloc_sched_context (void) { return xmalloc (sizeof (struct c6x_sched_context)); } /* If CLEAN_P is true then initializes _SC with clean data, and from the global context otherwise. */ static void c6x_init_sched_context (void *_sc, bool clean_p) { c6x_sched_context_t sc = (c6x_sched_context_t) _sc; if (clean_p) { init_sched_state (sc); } else { *sc = ss; sc->prev_cycle_state_ctx = xmalloc (dfa_state_size); memcpy (sc->prev_cycle_state_ctx, prev_cycle_state, dfa_state_size); } } /* Sets the global scheduling context to the one pointed to by _SC. */ static void c6x_set_sched_context (void *_sc) { c6x_sched_context_t sc = (c6x_sched_context_t) _sc; gcc_assert (sc != NULL); ss = *sc; memcpy (prev_cycle_state, sc->prev_cycle_state_ctx, dfa_state_size); } /* Clear data in _SC. */ static void c6x_clear_sched_context (void *_sc) { c6x_sched_context_t sc = (c6x_sched_context_t) _sc; gcc_assert (_sc != NULL); free (sc->prev_cycle_state_ctx); } /* Free _SC. */ static void c6x_free_sched_context (void *_sc) { free (_sc); } /* True if we are currently performing a preliminary scheduling pass before modulo scheduling; we can't allow the scheduler to modify instruction patterns using packetization assumptions, since there will be another scheduling pass later if modulo scheduling fails. */ static bool in_hwloop; /* Provide information about speculation capabilities, and set the DO_BACKTRACKING flag. */ static void c6x_set_sched_flags (spec_info_t spec_info) { unsigned int *flags = &(current_sched_info->flags); if (*flags & SCHED_EBB) { *flags |= DO_BACKTRACKING | DO_PREDICATION; } if (in_hwloop) *flags |= DONT_BREAK_DEPENDENCIES; spec_info->mask = 0; } /* Implement the TARGET_SCHED_ISSUE_RATE hook. */ static int c6x_issue_rate (void) { return 8; } /* Used together with the collapse_ndfa option, this ensures that we reach a deterministic automaton state before trying to advance a cycle. With collapse_ndfa, genautomata creates advance cycle arcs only for such deterministic states. */ static rtx c6x_sched_dfa_pre_cycle_insn (void) { return const0_rtx; } /* We're beginning a new block. Initialize data structures as necessary. */ static void c6x_sched_init (FILE *dump ATTRIBUTE_UNUSED, int sched_verbose ATTRIBUTE_UNUSED, int max_ready ATTRIBUTE_UNUSED) { if (prev_cycle_state == NULL) { prev_cycle_state = xmalloc (dfa_state_size); } init_sched_state (&ss); state_reset (prev_cycle_state); } /* We are about to being issuing INSN. Return nonzero if we cannot issue it on given cycle CLOCK and return zero if we should not sort the ready queue on the next clock start. For C6X, we use this function just to copy the previous DFA state for comparison purposes. */ static int c6x_dfa_new_cycle (FILE *dump ATTRIBUTE_UNUSED, int verbose ATTRIBUTE_UNUSED, rtx_insn *insn ATTRIBUTE_UNUSED, int last_clock ATTRIBUTE_UNUSED, int clock ATTRIBUTE_UNUSED, int *sort_p ATTRIBUTE_UNUSED) { if (clock != last_clock) memcpy (prev_cycle_state, curr_state, dfa_state_size); return 0; } static void c6x_mark_regno_read (int regno, bool cross) { int t = ++ss.tmp_reg_n_accesses[regno]; if (t > 4) reg_access_stall = true; if (cross) { int set_cycle = ss.reg_set_in_cycle[regno]; /* This must be done in this way rather than by tweaking things in adjust_cost, since the stall occurs even for insns with opposite predicates, and the scheduler may not even see a dependency. */ if (set_cycle > 0 && set_cycle == ss.curr_sched_clock) reg_access_stall = true; /* This doesn't quite do anything yet as we're only modeling one x unit. */ ++ss.tmp_reg_n_xaccesses[regno]; } } /* Note that REG is read in the insn being examined. If CROSS, it means the access is through a cross path. Update the temporary reg access arrays, and set REG_ACCESS_STALL if the insn can't be issued in the current cycle. */ static void c6x_mark_reg_read (rtx reg, bool cross) { unsigned regno = REGNO (reg); unsigned nregs = REG_NREGS (reg); while (nregs-- > 0) c6x_mark_regno_read (regno + nregs, cross); } /* Note that register REG is written in cycle CYCLES. */ static void c6x_mark_reg_written (rtx reg, int cycles) { unsigned regno = REGNO (reg); unsigned nregs = REG_NREGS (reg); while (nregs-- > 0) ss.reg_set_in_cycle[regno + nregs] = cycles; } /* Update the register state information for an instruction whose body is X. Return true if the instruction has to be delayed until the next cycle. */ static bool c6x_registers_update (rtx_insn *insn) { enum attr_cross cross; enum attr_dest_regfile destrf; int i, nops; rtx x; if (!reload_completed || recog_memoized (insn) < 0) return false; reg_access_stall = false; memcpy (ss.tmp_reg_n_accesses, ss.reg_n_accesses, sizeof ss.tmp_reg_n_accesses); memcpy (ss.tmp_reg_n_xaccesses, ss.reg_n_xaccesses, sizeof ss.tmp_reg_n_xaccesses); extract_insn (insn); cross = get_attr_cross (insn); destrf = get_attr_dest_regfile (insn); nops = recog_data.n_operands; x = PATTERN (insn); if (GET_CODE (x) == COND_EXEC) { c6x_mark_reg_read (XEXP (XEXP (x, 0), 0), false); nops -= 2; } for (i = 0; i < nops; i++) { rtx op = recog_data.operand[i]; if (recog_data.operand_type[i] == OP_OUT) continue; if (REG_P (op)) { bool this_cross = cross; if (destrf == DEST_REGFILE_A && A_REGNO_P (REGNO (op))) this_cross = false; if (destrf == DEST_REGFILE_B && B_REGNO_P (REGNO (op))) this_cross = false; c6x_mark_reg_read (op, this_cross); } else if (MEM_P (op)) { op = XEXP (op, 0); switch (GET_CODE (op)) { case POST_INC: case PRE_INC: case POST_DEC: case PRE_DEC: op = XEXP (op, 0); /* fall through */ case REG: c6x_mark_reg_read (op, false); break; case POST_MODIFY: case PRE_MODIFY: op = XEXP (op, 1); gcc_assert (GET_CODE (op) == PLUS); /* fall through */ case PLUS: c6x_mark_reg_read (XEXP (op, 0), false); if (REG_P (XEXP (op, 1))) c6x_mark_reg_read (XEXP (op, 1), false); break; case SYMBOL_REF: case LABEL_REF: case CONST: c6x_mark_regno_read (REG_B14, false); break; default: gcc_unreachable (); } } else if (!CONSTANT_P (op) && strlen (recog_data.constraints[i]) > 0) gcc_unreachable (); } return reg_access_stall; } /* Helper function for the TARGET_SCHED_REORDER and TARGET_SCHED_REORDER2 hooks. If scheduling an insn would be unsafe in the current cycle, move it down in the ready list and return the number of non-unsafe insns. */ static int c6x_sched_reorder_1 (rtx_insn **ready, int *pn_ready, int clock_var) { int n_ready = *pn_ready; rtx_insn **e_ready = ready + n_ready; rtx_insn **insnp; int first_jump; /* Keep track of conflicts due to a limit number of register accesses, and due to stalls incurred by too early accesses of registers using cross paths. */ for (insnp = ready; insnp < e_ready; insnp++) { rtx_insn *insn = *insnp; int icode = recog_memoized (insn); bool is_asm = (icode < 0 && (GET_CODE (PATTERN (insn)) == ASM_INPUT || asm_noperands (PATTERN (insn)) >= 0)); bool no_parallel = (is_asm || icode == CODE_FOR_sploop || (icode >= 0 && get_attr_type (insn) == TYPE_ATOMIC)); /* We delay asm insns until all delay slots are exhausted. We can't accurately tell how many cycles an asm takes, and the main scheduling code always assumes at least 1 cycle, which may be wrong. */ if ((no_parallel && (ss.issued_this_cycle > 0 || clock_var < ss.delays_finished_at)) || c6x_registers_update (insn) || (ss.issued_this_cycle > 0 && icode == CODE_FOR_sploop)) { memmove (ready + 1, ready, (insnp - ready) * sizeof (rtx)); *ready = insn; n_ready--; ready++; } else if (shadow_p (insn)) { memmove (ready + 1, ready, (insnp - ready) * sizeof (rtx)); *ready = insn; } } /* Ensure that no other jump is scheduled in jump delay slots, since it would put the machine into the wrong state. Also, we must avoid scheduling insns that have a latency longer than the remaining jump delay slots, as the code at the jump destination won't be prepared for it. However, we can relax this condition somewhat. The rest of the scheduler will automatically avoid scheduling an insn on which the jump shadow depends so late that its side effect happens after the jump. This means that if we see an insn with a longer latency here, it can safely be scheduled if we can ensure that it has a predicate opposite of the previous jump: the side effect will happen in what we think of as the same basic block. In c6x_variable_issue, we will record the necessary predicate in new_conditions, and after scheduling is finished, we will modify the insn. Special care must be taken whenever there is more than one jump in flight. */ first_jump = first_jump_index (clock_var); if (first_jump != -1) { int first_cycle = get_jump_cycle (first_jump); rtx first_cond = get_jump_cond (first_jump); int second_cycle = 0; if (first_jump > 0) second_cycle = get_jump_cycle (first_jump - 1); for (insnp = ready; insnp < e_ready; insnp++) { rtx_insn *insn = *insnp; int icode = recog_memoized (insn); bool is_asm = (icode < 0 && (GET_CODE (PATTERN (insn)) == ASM_INPUT || asm_noperands (PATTERN (insn)) >= 0)); int this_cycles, rsrv_cycles; enum attr_type type; gcc_assert (!is_asm); if (icode < 0) continue; this_cycles = get_attr_cycles (insn); rsrv_cycles = get_attr_reserve_cycles (insn); type = get_attr_type (insn); /* Treat branches specially; there is also a hazard if two jumps end at the same cycle. */ if (type == TYPE_BRANCH || type == TYPE_CALL) this_cycles++; if (clock_var + this_cycles <= first_cycle) continue; if ((first_jump > 0 && clock_var + this_cycles > second_cycle) || clock_var + rsrv_cycles > first_cycle || !predicate_insn (insn, first_cond, false)) { memmove (ready + 1, ready, (insnp - ready) * sizeof (rtx)); *ready = insn; n_ready--; ready++; } } } return n_ready; } /* Implement the TARGET_SCHED_REORDER hook. We save the current clock for later and clear the register access information for the new cycle. We also move asm statements out of the way if they would be scheduled in a delay slot. */ static int c6x_sched_reorder (FILE *dump ATTRIBUTE_UNUSED, int sched_verbose ATTRIBUTE_UNUSED, rtx_insn **ready ATTRIBUTE_UNUSED, int *pn_ready ATTRIBUTE_UNUSED, int clock_var) { ss.curr_sched_clock = clock_var; ss.issued_this_cycle = 0; memset (ss.reg_n_accesses, 0, sizeof ss.reg_n_accesses); memset (ss.reg_n_xaccesses, 0, sizeof ss.reg_n_xaccesses); if (ready == NULL) return 0; return c6x_sched_reorder_1 (ready, pn_ready, clock_var); } /* Implement the TARGET_SCHED_REORDER2 hook. We use this to record the clock cycle for every insn. */ static int c6x_sched_reorder2 (FILE *dump ATTRIBUTE_UNUSED, int sched_verbose ATTRIBUTE_UNUSED, rtx_insn **ready ATTRIBUTE_UNUSED, int *pn_ready ATTRIBUTE_UNUSED, int clock_var) { /* FIXME: the assembler rejects labels inside an execute packet. This can occur if prologue insns are scheduled in parallel with others, so we avoid this here. Also make sure that nothing is scheduled in parallel with a TYPE_ATOMIC insn or after a jump. */ if (RTX_FRAME_RELATED_P (ss.last_scheduled_insn) || JUMP_P (ss.last_scheduled_insn) || (recog_memoized (ss.last_scheduled_insn) >= 0 && get_attr_type (ss.last_scheduled_insn) == TYPE_ATOMIC)) { int n_ready = *pn_ready; rtx_insn **e_ready = ready + n_ready; rtx_insn **insnp; for (insnp = ready; insnp < e_ready; insnp++) { rtx_insn *insn = *insnp; if (!shadow_p (insn)) { memmove (ready + 1, ready, (insnp - ready) * sizeof (rtx)); *ready = insn; n_ready--; ready++; } } return n_ready; } return c6x_sched_reorder_1 (ready, pn_ready, clock_var); } /* Subroutine of maybe_clobber_cond, called through note_stores. */ static void clobber_cond_1 (rtx x, const_rtx pat ATTRIBUTE_UNUSED, void *data1) { rtx *cond = (rtx *)data1; if (*cond != NULL_RTX && reg_overlap_mentioned_p (x, *cond)) *cond = NULL_RTX; } /* Examine INSN, and if it destroys the conditions have recorded for any of the jumps in flight, clear that condition so that we don't predicate any more insns. CLOCK_VAR helps us limit the search to only those jumps which are still in flight. */ static void maybe_clobber_cond (rtx_insn *insn, int clock_var) { int n, idx; idx = ss.jump_cycle_index; for (n = 0; n < 12; n++, idx++) { rtx cond, link; int cycle; if (idx >= 12) idx -= 12; cycle = ss.jump_cycles[idx]; if (cycle <= clock_var) return; cond = ss.jump_cond[idx]; if (cond == NULL_RTX) continue; if (CALL_P (insn)) { ss.jump_cond[idx] = NULL_RTX; continue; } note_stores (insn, clobber_cond_1, ss.jump_cond + idx); for (link = REG_NOTES (insn); link; link = XEXP (link, 1)) if (REG_NOTE_KIND (link) == REG_INC) clobber_cond_1 (XEXP (link, 0), NULL_RTX, ss.jump_cond + idx); } } /* Implement the TARGET_SCHED_VARIABLE_ISSUE hook. We are about to issue INSN. Return the number of insns left on the ready queue that can be issued this cycle. We use this hook to record clock cycles and reservations for every insn. */ static int c6x_variable_issue (FILE *dump ATTRIBUTE_UNUSED, int sched_verbose ATTRIBUTE_UNUSED, rtx_insn *insn, int can_issue_more ATTRIBUTE_UNUSED) { ss.last_scheduled_insn = insn; if (INSN_UID (insn) < sploop_max_uid_iter0 && !JUMP_P (insn)) ss.last_scheduled_iter0 = insn; if (GET_CODE (PATTERN (insn)) != USE && GET_CODE (PATTERN (insn)) != CLOBBER) ss.issued_this_cycle++; if (insn_info.exists ()) { state_t st_after = alloca (dfa_state_size); int curr_clock = ss.curr_sched_clock; int uid = INSN_UID (insn); int icode = recog_memoized (insn); rtx first_cond; int first, first_cycle; unsigned int mask; int i; insn_set_clock (insn, curr_clock); INSN_INFO_ENTRY (uid).ebb_start = curr_clock == 0 && ss.issued_this_cycle == 1; first = first_jump_index (ss.curr_sched_clock); if (first == -1) { first_cycle = 0; first_cond = NULL_RTX; } else { first_cycle = get_jump_cycle (first); first_cond = get_jump_cond (first); } if (icode >= 0 && first_cycle > curr_clock && first_cond != NULL_RTX && (curr_clock + get_attr_cycles (insn) > first_cycle || get_attr_type (insn) == TYPE_BRANCH || get_attr_type (insn) == TYPE_CALL)) INSN_INFO_ENTRY (uid).new_cond = first_cond; memcpy (st_after, curr_state, dfa_state_size); state_transition (st_after, const0_rtx); mask = 0; for (i = 0; i < 2 * UNIT_QID_SIDE_OFFSET; i++) if (cpu_unit_reservation_p (st_after, c6x_unit_codes[i]) && !cpu_unit_reservation_p (prev_cycle_state, c6x_unit_codes[i])) mask |= 1 << i; INSN_INFO_ENTRY (uid).unit_mask = mask; maybe_clobber_cond (insn, curr_clock); if (icode >= 0) { int i, cycles; c6x_registers_update (insn); memcpy (ss.reg_n_accesses, ss.tmp_reg_n_accesses, sizeof ss.reg_n_accesses); memcpy (ss.reg_n_xaccesses, ss.tmp_reg_n_accesses, sizeof ss.reg_n_xaccesses); cycles = get_attr_cycles (insn); if (ss.delays_finished_at < ss.curr_sched_clock + cycles) ss.delays_finished_at = ss.curr_sched_clock + cycles; if (get_attr_type (insn) == TYPE_BRANCH || get_attr_type (insn) == TYPE_CALL) { rtx opposite = condjump_opposite_condition (insn); record_jump (ss.curr_sched_clock + cycles, opposite); } /* Mark the cycles in which the destination registers are written. This is used for calculating stalls when using cross units. */ extract_insn (insn); /* Cross-path stalls don't apply to results of load insns. */ if (get_attr_type (insn) == TYPE_LOAD || get_attr_type (insn) == TYPE_LOADN || get_attr_type (insn) == TYPE_LOAD_SHADOW) cycles--; for (i = 0; i < recog_data.n_operands; i++) { rtx op = recog_data.operand[i]; if (MEM_P (op)) { rtx addr = XEXP (op, 0); if (GET_RTX_CLASS (GET_CODE (addr)) == RTX_AUTOINC) c6x_mark_reg_written (XEXP (addr, 0), insn_uid_get_clock (uid) + 1); } if (recog_data.operand_type[i] != OP_IN && REG_P (op)) { c6x_mark_reg_written (op, insn_uid_get_clock (uid) + cycles); } } } } return can_issue_more; } /* Implement the TARGET_SCHED_ADJUST_COST hook. We need special handling for anti- and output dependencies. */ static int c6x_adjust_cost (rtx_insn *insn, int dep_type, rtx_insn *dep_insn, int cost, unsigned int) { enum attr_type insn_type = TYPE_UNKNOWN, dep_insn_type = TYPE_UNKNOWN; int dep_insn_code_number, insn_code_number; int shadow_bonus = 0; enum reg_note kind; dep_insn_code_number = recog_memoized (dep_insn); insn_code_number = recog_memoized (insn); if (dep_insn_code_number >= 0) dep_insn_type = get_attr_type (dep_insn); if (insn_code_number >= 0) insn_type = get_attr_type (insn); kind = (reg_note) dep_type; if (kind == 0) { /* If we have a dependency on a load, and it's not for the result of the load, it must be for an autoincrement. Reduce the cost in that case. */ if (dep_insn_type == TYPE_LOAD) { rtx set = PATTERN (dep_insn); if (GET_CODE (set) == COND_EXEC) set = COND_EXEC_CODE (set); if (GET_CODE (set) == UNSPEC) cost = 1; else { gcc_assert (GET_CODE (set) == SET); if (!reg_overlap_mentioned_p (SET_DEST (set), PATTERN (insn))) cost = 1; } } } /* A jump shadow needs to have its latency decreased by one. Conceptually, it occurs in between two cycles, but we schedule it at the end of the first cycle. */ if (shadow_type_p (insn_type)) shadow_bonus = 1; /* Anti and output dependencies usually have zero cost, but we want to insert a stall after a jump, and after certain floating point insns that take more than one cycle to read their inputs. In the future, we should try to find a better algorithm for scheduling jumps. */ if (kind != 0) { /* We can get anti-dependencies against shadow insns. Treat these like output dependencies, so that the insn is entirely finished before the branch takes place. */ if (kind == REG_DEP_ANTI && insn_type == TYPE_SHADOW) kind = REG_DEP_OUTPUT; switch (dep_insn_type) { case TYPE_CALLP: return 1; case TYPE_BRANCH: case TYPE_CALL: if (get_attr_has_shadow (dep_insn) == HAS_SHADOW_Y) /* This is a real_jump/real_call insn. These don't have outputs, and ensuring the validity of scheduling things in the delay slot is the job of c6x_sched_reorder_1. */ return 0; /* Unsplit calls can happen - e.g. for divide insns. */ return 6; case TYPE_LOAD: case TYPE_LOADN: case TYPE_INTDP: if (kind == REG_DEP_OUTPUT) return 5 - shadow_bonus; return 0; case TYPE_MPY4: case TYPE_FP4: if (kind == REG_DEP_OUTPUT) return 4 - shadow_bonus; return 0; case TYPE_MPY2: if (kind == REG_DEP_OUTPUT) return 2 - shadow_bonus; return 0; case TYPE_CMPDP: if (kind == REG_DEP_OUTPUT) return 2 - shadow_bonus; return 2; case TYPE_ADDDP: case TYPE_MPYSPDP: if (kind == REG_DEP_OUTPUT) return 7 - shadow_bonus; return 2; case TYPE_MPYSP2DP: if (kind == REG_DEP_OUTPUT) return 5 - shadow_bonus; return 2; case TYPE_MPYI: if (kind == REG_DEP_OUTPUT) return 9 - shadow_bonus; return 4; case TYPE_MPYID: case TYPE_MPYDP: if (kind == REG_DEP_OUTPUT) return 10 - shadow_bonus; return 4; default: if (insn_type == TYPE_SPKERNEL) return 0; if (kind == REG_DEP_OUTPUT) return 1 - shadow_bonus; return 0; } } return cost - shadow_bonus; } /* Create a SEQUENCE rtx to replace the instructions in SLOT, of which there are N_FILLED. REAL_FIRST identifies the slot if the insn that appears first in the original stream. */ static void gen_one_bundle (rtx_insn **slot, int n_filled, int real_first) { rtx seq; rtx_insn *bundle; rtx_insn *t; int i; seq = gen_rtx_SEQUENCE (VOIDmode, gen_rtvec_v (n_filled, slot)); bundle = make_insn_raw (seq); BLOCK_FOR_INSN (bundle) = BLOCK_FOR_INSN (slot[0]); INSN_LOCATION (bundle) = INSN_LOCATION (slot[0]); SET_PREV_INSN (bundle) = SET_PREV_INSN (slot[real_first]); t = NULL; for (i = 0; i < n_filled; i++) { rtx_insn *insn = slot[i]; remove_insn (insn); SET_PREV_INSN (insn) = t ? t : PREV_INSN (bundle); if (t != NULL_RTX) SET_NEXT_INSN (t) = insn; t = insn; if (i > 0) INSN_LOCATION (slot[i]) = INSN_LOCATION (bundle); } SET_NEXT_INSN (bundle) = NEXT_INSN (PREV_INSN (bundle)); SET_NEXT_INSN (t) = NEXT_INSN (bundle); SET_NEXT_INSN (PREV_INSN (bundle)) = bundle; SET_PREV_INSN (NEXT_INSN (bundle)) = bundle; } /* Move all parallel instructions into SEQUENCEs, so that no subsequent passes try to insert labels in the middle. */ static void c6x_gen_bundles (void) { basic_block bb; FOR_EACH_BB_FN (bb, cfun) { rtx_insn *insn, *next; /* The machine is eight insns wide. We can have up to six shadow insns, plus an extra slot for merging the jump shadow. */ rtx_insn *slot[15]; int n_filled = 0; int first_slot = 0; for (insn = BB_HEAD (bb);; insn = next) { int at_end; rtx delete_this = NULL_RTX; if (NONDEBUG_INSN_P (insn)) { /* Put calls at the start of the sequence. */ if (CALL_P (insn)) { first_slot++; if (n_filled) { memmove (&slot[1], &slot[0], n_filled * sizeof (slot[0])); } if (!shadow_p (insn)) { PUT_MODE (insn, TImode); if (n_filled) PUT_MODE (slot[1], VOIDmode); } n_filled++; slot[0] = insn; } else { slot[n_filled++] = insn; } } next = NEXT_INSN (insn); while (next && insn != BB_END (bb) && !(NONDEBUG_INSN_P (next) && GET_CODE (PATTERN (next)) != USE && GET_CODE (PATTERN (next)) != CLOBBER)) { insn = next; next = NEXT_INSN (insn); } at_end = insn == BB_END (bb); if (delete_this == NULL_RTX && (at_end || (GET_MODE (next) == TImode && !(shadow_p (next) && CALL_P (next))))) { if (n_filled >= 2) gen_one_bundle (slot, n_filled, first_slot); n_filled = 0; first_slot = 0; } if (at_end) break; } } } /* Emit a NOP instruction for CYCLES cycles after insn AFTER. Return it. */ static rtx_insn * emit_nop_after (int cycles, rtx_insn *after) { rtx_insn *insn; /* mpydp has 9 delay slots, and we may schedule a stall for a cross-path operation. We don't need the extra NOP since in this case, the hardware will automatically insert the required stall. */ if (cycles == 10) cycles--; gcc_assert (cycles < 10); insn = emit_insn_after (gen_nop_count (GEN_INT (cycles)), after); PUT_MODE (insn, TImode); return insn; } /* Determine whether INSN is a call that needs to have a return label placed. */ static bool returning_call_p (rtx_insn *insn) { if (CALL_P (insn)) return (!SIBLING_CALL_P (insn) && get_attr_type (insn) != TYPE_CALLP && get_attr_type (insn) != TYPE_SHADOW); if (recog_memoized (insn) < 0) return false; if (get_attr_type (insn) == TYPE_CALL) return true; return false; } /* Determine whether INSN's pattern can be converted to use callp. */ static bool can_use_callp (rtx_insn *insn) { int icode = recog_memoized (insn); if (!TARGET_INSNS_64PLUS || icode < 0 || GET_CODE (PATTERN (insn)) == COND_EXEC) return false; return ((icode == CODE_FOR_real_call || icode == CODE_FOR_call_internal || icode == CODE_FOR_call_value_internal) && get_attr_dest_regfile (insn) == DEST_REGFILE_ANY); } /* Convert the pattern of INSN, which must be a CALL_INSN, into a callp. */ static void convert_to_callp (rtx_insn *insn) { rtx lab; extract_insn (insn); if (GET_CODE (PATTERN (insn)) == SET) { rtx dest = recog_data.operand[0]; lab = recog_data.operand[1]; PATTERN (insn) = gen_callp_value (dest, lab); INSN_CODE (insn) = CODE_FOR_callp_value; } else { lab = recog_data.operand[0]; PATTERN (insn) = gen_callp (lab); INSN_CODE (insn) = CODE_FOR_callp; } } /* Scan forwards from INSN until we find the next insn that has mode TImode (indicating it starts a new cycle), and occurs in cycle CLOCK. Return it if we find such an insn, NULL_RTX otherwise. */ static rtx_insn * find_next_cycle_insn (rtx_insn *insn, int clock) { rtx_insn *t = insn; if (GET_MODE (t) == TImode) t = next_real_insn (t); while (t && GET_MODE (t) != TImode) t = next_real_insn (t); if (t && insn_get_clock (t) == clock) return t; return NULL; } /* If COND_INSN has a COND_EXEC condition, wrap the same condition around PAT. Return PAT either unchanged or modified in this way. */ static rtx duplicate_cond (rtx pat, rtx cond_insn) { rtx cond_pat = PATTERN (cond_insn); if (GET_CODE (cond_pat) == COND_EXEC) pat = gen_rtx_COND_EXEC (VOIDmode, copy_rtx (COND_EXEC_TEST (cond_pat)), pat); return pat; } /* Walk forward from INSN to find the last insn that issues in the same clock cycle. */ static rtx_insn * find_last_same_clock (rtx_insn *insn) { rtx_insn *retval = insn; rtx_insn *t = next_real_insn (insn); while (t && GET_MODE (t) != TImode) { if (!DEBUG_INSN_P (t) && recog_memoized (t) >= 0) retval = t; t = next_real_insn (t); } return retval; } /* For every call insn in the function, emit code to load the return address. For each call we create a return label and store it in CALL_LABELS. If are not scheduling, we emit the labels here, otherwise the caller will do it later. This function is called after final insn scheduling, but before creating the SEQUENCEs that represent execute packets. */ static void reorg_split_calls (rtx_code_label **call_labels) { unsigned int reservation_mask = 0; rtx_insn *insn = get_insns (); gcc_assert (NOTE_P (insn)); insn = next_real_insn (insn); while (insn) { int uid; rtx_insn *next = next_real_insn (insn); if (DEBUG_INSN_P (insn)) goto done; if (GET_MODE (insn) == TImode) reservation_mask = 0; uid = INSN_UID (insn); if (c6x_flag_schedule_insns2 && recog_memoized (insn) >= 0) reservation_mask |= 1 << INSN_INFO_ENTRY (uid).reservation; if (returning_call_p (insn)) { rtx_code_label *label = gen_label_rtx (); rtx labelref = gen_rtx_LABEL_REF (Pmode, label); rtx reg = gen_rtx_REG (SImode, RETURN_ADDR_REGNO); LABEL_NUSES (label) = 2; if (!c6x_flag_schedule_insns2) { if (can_use_callp (insn)) convert_to_callp (insn); else { rtx t; rtx_insn *slot[4]; emit_label_after (label, insn); /* Bundle the call and its delay slots into a single SEQUENCE. While these do not issue in parallel we need to group them into a single EH region. */ slot[0] = insn; PUT_MODE (insn, TImode); if (TARGET_INSNS_64) { t = gen_addkpc (reg, labelref, GEN_INT (4)); slot[1] = emit_insn_after (duplicate_cond (t, insn), insn); PUT_MODE (slot[1], TImode); gen_one_bundle (slot, 2, 0); } else { slot[3] = emit_insn_after (gen_nop_count (GEN_INT (3)), insn); PUT_MODE (slot[3], TImode); t = gen_movsi_lo_sum (reg, reg, labelref); slot[2] = emit_insn_after (duplicate_cond (t, insn), insn); PUT_MODE (slot[2], TImode); t = gen_movsi_high (reg, labelref); slot[1] = emit_insn_after (duplicate_cond (t, insn), insn); PUT_MODE (slot[1], TImode); gen_one_bundle (slot, 4, 0); } } } else { /* If we scheduled, we reserved the .S2 unit for one or two cycles after the call. Emit the insns in these slots, unless it's possible to create a CALLP insn. Note that this works because the dependencies ensure that no insn setting/using B3 is scheduled in the delay slots of a call. */ int this_clock = insn_get_clock (insn); rtx_insn *after1; call_labels[INSN_UID (insn)] = label; rtx_insn *last_same_clock = find_last_same_clock (insn); if (can_use_callp (insn)) { /* Find the first insn of the next execute packet. If it is the shadow insn corresponding to this call, we may use a CALLP insn. */ rtx_insn *shadow = next_nonnote_nondebug_insn (last_same_clock); if (CALL_P (shadow) && insn_get_clock (shadow) == this_clock + 5) { convert_to_callp (shadow); insn_set_clock (shadow, this_clock); INSN_INFO_ENTRY (INSN_UID (shadow)).reservation = RESERVATION_S2; INSN_INFO_ENTRY (INSN_UID (shadow)).unit_mask = INSN_INFO_ENTRY (INSN_UID (last_same_clock)).unit_mask; if (GET_MODE (insn) == TImode) { rtx_insn *new_cycle_first = NEXT_INSN (insn); while (!NONDEBUG_INSN_P (new_cycle_first) || GET_CODE (PATTERN (new_cycle_first)) == USE || GET_CODE (PATTERN (new_cycle_first)) == CLOBBER) new_cycle_first = NEXT_INSN (new_cycle_first); PUT_MODE (new_cycle_first, TImode); if (new_cycle_first != shadow) PUT_MODE (shadow, VOIDmode); INSN_INFO_ENTRY (INSN_UID (new_cycle_first)).ebb_start = INSN_INFO_ENTRY (INSN_UID (insn)).ebb_start; } else PUT_MODE (shadow, VOIDmode); delete_insn (insn); goto done; } } after1 = find_next_cycle_insn (last_same_clock, this_clock + 1); if (after1 == NULL_RTX) after1 = last_same_clock; else after1 = find_last_same_clock (after1); if (TARGET_INSNS_64) { rtx x1 = gen_addkpc (reg, labelref, const0_rtx); x1 = emit_insn_after (duplicate_cond (x1, insn), after1); insn_set_clock (x1, this_clock + 1); INSN_INFO_ENTRY (INSN_UID (x1)).reservation = RESERVATION_S2; if (after1 == last_same_clock) PUT_MODE (x1, TImode); else INSN_INFO_ENTRY (INSN_UID (x1)).unit_mask = INSN_INFO_ENTRY (INSN_UID (after1)).unit_mask; } else { rtx x1, x2; rtx_insn *after2 = find_next_cycle_insn (after1, this_clock + 2); if (after2 == NULL_RTX) after2 = after1; x2 = gen_movsi_lo_sum (reg, reg, labelref); x2 = emit_insn_after (duplicate_cond (x2, insn), after2); x1 = gen_movsi_high (reg, labelref); x1 = emit_insn_after (duplicate_cond (x1, insn), after1); insn_set_clock (x1, this_clock + 1); insn_set_clock (x2, this_clock + 2); INSN_INFO_ENTRY (INSN_UID (x1)).reservation = RESERVATION_S2; INSN_INFO_ENTRY (INSN_UID (x2)).reservation = RESERVATION_S2; if (after1 == last_same_clock) PUT_MODE (x1, TImode); else INSN_INFO_ENTRY (INSN_UID (x1)).unit_mask = INSN_INFO_ENTRY (INSN_UID (after1)).unit_mask; if (after1 == after2) PUT_MODE (x2, TImode); else INSN_INFO_ENTRY (INSN_UID (x2)).unit_mask = INSN_INFO_ENTRY (INSN_UID (after2)).unit_mask; } } } done: insn = next; } } /* Called as part of c6x_reorg. This function emits multi-cycle NOP insns as required for correctness. CALL_LABELS is the array that holds the return labels for call insns; we emit these here if scheduling was run earlier. */ static void reorg_emit_nops (rtx_code_label **call_labels) { bool first; rtx last_call; rtx_insn *prev; int prev_clock, earliest_bb_end; int prev_implicit_nops; rtx_insn *insn = get_insns (); /* We look at one insn (or bundle inside a sequence) in each iteration, storing its issue time in PREV_CLOCK for the next iteration. If there is a gap in clocks, we must insert a NOP. EARLIEST_BB_END tracks in which cycle all insns that have been issued in the current basic block will finish. We must not allow the next basic block to begin before this cycle. PREV_IMPLICIT_NOPS tells us whether we've seen an insn that implicitly contains a multi-cycle nop. The code is scheduled such that subsequent insns will show the cycle gap, but we needn't insert a real NOP instruction. */ insn = next_real_insn (insn); last_call = prev = NULL; prev_clock = -1; earliest_bb_end = 0; prev_implicit_nops = 0; first = true; while (insn) { int this_clock = -1; rtx_insn *next; int max_cycles = 0; next = next_real_insn (insn); if (DEBUG_INSN_P (insn) || GET_CODE (PATTERN (insn)) == USE || GET_CODE (PATTERN (insn)) == CLOBBER || shadow_or_blockage_p (insn) || JUMP_TABLE_DATA_P (insn)) goto next_insn; if (!c6x_flag_schedule_insns2) /* No scheduling; ensure that no parallel issue happens. */ PUT_MODE (insn, TImode); else { int cycles; this_clock = insn_get_clock (insn); if (this_clock != prev_clock) { PUT_MODE (insn, TImode); if (!first) { cycles = this_clock - prev_clock; cycles -= prev_implicit_nops; if (cycles > 1) { rtx nop = emit_nop_after (cycles - 1, prev); insn_set_clock (nop, prev_clock + prev_implicit_nops + 1); } } prev_clock = this_clock; if (last_call && insn_get_clock (last_call) + 6 <= this_clock) { emit_label_before (call_labels[INSN_UID (last_call)], insn); last_call = NULL_RTX; } prev_implicit_nops = 0; } } /* Examine how many cycles the current insn takes, and adjust LAST_CALL, EARLIEST_BB_END and PREV_IMPLICIT_NOPS. */ if (recog_memoized (insn) >= 0 /* If not scheduling, we've emitted NOPs after calls already. */ && (c6x_flag_schedule_insns2 || !returning_call_p (insn))) { max_cycles = get_attr_cycles (insn); if (get_attr_type (insn) == TYPE_CALLP) prev_implicit_nops = 5; } else max_cycles = 1; if (returning_call_p (insn)) last_call = insn; if (c6x_flag_schedule_insns2) { gcc_assert (this_clock >= 0); if (earliest_bb_end < this_clock + max_cycles) earliest_bb_end = this_clock + max_cycles; } else if (max_cycles > 1) emit_nop_after (max_cycles - 1, insn); prev = insn; first = false; next_insn: if (c6x_flag_schedule_insns2 && (next == NULL_RTX || (GET_MODE (next) == TImode && INSN_INFO_ENTRY (INSN_UID (next)).ebb_start)) && earliest_bb_end > 0) { int cycles = earliest_bb_end - prev_clock; if (cycles > 1) { prev = emit_nop_after (cycles - 1, prev); insn_set_clock (prev, prev_clock + prev_implicit_nops + 1); } earliest_bb_end = 0; prev_clock = -1; first = true; if (last_call) emit_label_after (call_labels[INSN_UID (last_call)], prev); last_call = NULL_RTX; } insn = next; } } /* If possible, split INSN, which we know is either a jump or a call, into a real insn and its shadow. */ static void split_delayed_branch (rtx_insn *insn) { int code = recog_memoized (insn); rtx_insn *i1; rtx newpat; rtx pat = PATTERN (insn); if (GET_CODE (pat) == COND_EXEC) pat = COND_EXEC_CODE (pat); if (CALL_P (insn)) { rtx src = pat, dest = NULL_RTX; rtx callee; if (GET_CODE (pat) == SET) { dest = SET_DEST (pat); src = SET_SRC (pat); } callee = XEXP (XEXP (src, 0), 0); if (SIBLING_CALL_P (insn)) { if (REG_P (callee)) newpat = gen_indirect_sibcall_shadow (); else newpat = gen_sibcall_shadow (callee); pat = gen_real_jump (callee); } else if (dest != NULL_RTX) { if (REG_P (callee)) newpat = gen_indirect_call_value_shadow (dest); else newpat = gen_call_value_shadow (dest, callee); pat = gen_real_call (callee); } else { if (REG_P (callee)) newpat = gen_indirect_call_shadow (); else newpat = gen_call_shadow (callee); pat = gen_real_call (callee); } pat = duplicate_cond (pat, insn); newpat = duplicate_cond (newpat, insn); } else { rtx src, op; if (GET_CODE (pat) == PARALLEL && GET_CODE (XVECEXP (pat, 0, 0)) == RETURN) { newpat = gen_return_shadow (); pat = gen_real_ret (XEXP (XVECEXP (pat, 0, 1), 0)); newpat = duplicate_cond (newpat, insn); } else switch (code) { case CODE_FOR_br_true: case CODE_FOR_br_false: src = SET_SRC (pat); op = XEXP (src, code == CODE_FOR_br_true ? 1 : 2); newpat = gen_condjump_shadow (op); pat = gen_real_jump (op); if (code == CODE_FOR_br_true) pat = gen_rtx_COND_EXEC (VOIDmode, XEXP (src, 0), pat); else pat = gen_rtx_COND_EXEC (VOIDmode, reversed_comparison (XEXP (src, 0), VOIDmode), pat); break; case CODE_FOR_jump: op = SET_SRC (pat); newpat = gen_jump_shadow (op); break; case CODE_FOR_indirect_jump: newpat = gen_indirect_jump_shadow (); break; case CODE_FOR_return_internal: newpat = gen_return_shadow (); pat = gen_real_ret (XEXP (XVECEXP (pat, 0, 1), 0)); break; default: return; } } i1 = emit_insn_before (pat, insn); PATTERN (insn) = newpat; INSN_CODE (insn) = -1; record_delay_slot_pair (i1, insn, 5, 0); } /* If INSN is a multi-cycle insn that should be handled properly in modulo-scheduling, split it into a real insn and a shadow. Return true if we made a change. It is valid for us to fail to split an insn; the caller has to deal with the possibility. Currently we handle loads and most mpy2 and mpy4 insns. */ static bool split_delayed_nonbranch (rtx_insn *insn) { int code = recog_memoized (insn); enum attr_type type; rtx_insn *i1; rtx newpat, src, dest; rtx pat = PATTERN (insn); rtvec rtv; int delay; if (GET_CODE (pat) == COND_EXEC) pat = COND_EXEC_CODE (pat); if (code < 0 || GET_CODE (pat) != SET) return false; src = SET_SRC (pat); dest = SET_DEST (pat); if (!REG_P (dest)) return false; type = get_attr_type (insn); if (code >= 0 && (type == TYPE_LOAD || type == TYPE_LOADN)) { if (!MEM_P (src) && (GET_CODE (src) != ZERO_EXTEND || !MEM_P (XEXP (src, 0)))) return false; if (GET_MODE_SIZE (GET_MODE (dest)) > 4 && (GET_MODE_SIZE (GET_MODE (dest)) != 8 || !TARGET_LDDW)) return false; rtv = gen_rtvec (2, GEN_INT (REGNO (SET_DEST (pat))), SET_SRC (pat)); newpat = gen_load_shadow (SET_DEST (pat)); pat = gen_rtx_UNSPEC (VOIDmode, rtv, UNSPEC_REAL_LOAD); delay = 4; } else if (code >= 0 && (type == TYPE_MPY2 || type == TYPE_MPY4)) { /* We don't handle floating point multiplies yet. */ if (GET_MODE (dest) == SFmode) return false; rtv = gen_rtvec (2, GEN_INT (REGNO (SET_DEST (pat))), SET_SRC (pat)); newpat = gen_mult_shadow (SET_DEST (pat)); pat = gen_rtx_UNSPEC (VOIDmode, rtv, UNSPEC_REAL_MULT); delay = type == TYPE_MPY2 ? 1 : 3; } else return false; pat = duplicate_cond (pat, insn); newpat = duplicate_cond (newpat, insn); i1 = emit_insn_before (pat, insn); PATTERN (insn) = newpat; INSN_CODE (insn) = -1; recog_memoized (insn); recog_memoized (i1); record_delay_slot_pair (i1, insn, delay, 0); return true; } /* Examine if INSN is the result of splitting a load into a real load and a shadow, and if so, undo the transformation. */ static void undo_split_delayed_nonbranch (rtx_insn *insn) { int icode = recog_memoized (insn); enum attr_type type; rtx prev_pat, insn_pat; rtx_insn *prev; if (icode < 0) return; type = get_attr_type (insn); if (type != TYPE_LOAD_SHADOW && type != TYPE_MULT_SHADOW) return; prev = PREV_INSN (insn); prev_pat = PATTERN (prev); insn_pat = PATTERN (insn); if (GET_CODE (prev_pat) == COND_EXEC) { prev_pat = COND_EXEC_CODE (prev_pat); insn_pat = COND_EXEC_CODE (insn_pat); } gcc_assert (GET_CODE (prev_pat) == UNSPEC && ((XINT (prev_pat, 1) == UNSPEC_REAL_LOAD && type == TYPE_LOAD_SHADOW) || (XINT (prev_pat, 1) == UNSPEC_REAL_MULT && type == TYPE_MULT_SHADOW))); insn_pat = gen_rtx_SET (SET_DEST (insn_pat), XVECEXP (prev_pat, 0, 1)); insn_pat = duplicate_cond (insn_pat, prev); PATTERN (insn) = insn_pat; INSN_CODE (insn) = -1; delete_insn (prev); } /* Split every insn (i.e. jumps and calls) which can have delay slots into two parts: the first one is scheduled normally and emits the instruction, while the second one is a shadow insn which shows the side effect taking place. The second one is placed in the right cycle by the scheduler, but not emitted as an assembly instruction. */ static void split_delayed_insns (void) { rtx_insn *insn; for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) { if (JUMP_P (insn) || CALL_P (insn)) split_delayed_branch (insn); } } /* For every insn that has an entry in the new_conditions vector, give it the appropriate predicate. */ static void conditionalize_after_sched (void) { basic_block bb; rtx_insn *insn; FOR_EACH_BB_FN (bb, cfun) FOR_BB_INSNS (bb, insn) { unsigned uid = INSN_UID (insn); rtx cond; if (!NONDEBUG_INSN_P (insn) || uid >= INSN_INFO_LENGTH) continue; cond = INSN_INFO_ENTRY (uid).new_cond; if (cond == NULL_RTX) continue; if (dump_file) fprintf (dump_file, "Conditionalizing insn %d\n", uid); predicate_insn (insn, cond, true); } } /* A callback for the hw-doloop pass. This function examines INSN; if it is a loop_end pattern we recognize, return the reg rtx for the loop counter. Otherwise, return NULL_RTX. */ static rtx hwloop_pattern_reg (rtx_insn *insn) { rtx pat, reg; if (!JUMP_P (insn) || recog_memoized (insn) != CODE_FOR_loop_end) return NULL_RTX; pat = PATTERN (insn); reg = SET_DEST (XVECEXP (pat, 0, 1)); if (!REG_P (reg)) return NULL_RTX; return reg; } /* Return the number of cycles taken by BB, as computed by scheduling, including the latencies of all insns with delay slots. IGNORE is an insn we should ignore in the calculation, usually the final branch. */ static int bb_earliest_end_cycle (basic_block bb, rtx ignore) { int earliest = 0; rtx_insn *insn; FOR_BB_INSNS (bb, insn) { int cycles, this_clock; if (LABEL_P (insn) || NOTE_P (insn) || DEBUG_INSN_P (insn) || GET_CODE (PATTERN (insn)) == USE || GET_CODE (PATTERN (insn)) == CLOBBER || insn == ignore) continue; this_clock = insn_get_clock (insn); cycles = get_attr_cycles (insn); if (earliest < this_clock + cycles) earliest = this_clock + cycles; } return earliest; } /* Examine the insns in BB and remove all which have a uid greater or equal to MAX_UID. */ static void filter_insns_above (basic_block bb, int max_uid) { rtx_insn *insn, *next; bool prev_ti = false; int prev_cycle = -1; FOR_BB_INSNS_SAFE (bb, insn, next) { int this_cycle; if (!NONDEBUG_INSN_P (insn)) continue; if (insn == BB_END (bb)) return; this_cycle = insn_get_clock (insn); if (prev_ti && this_cycle == prev_cycle) { gcc_assert (GET_MODE (insn) != TImode); PUT_MODE (insn, TImode); } prev_ti = false; if (INSN_UID (insn) >= max_uid) { if (GET_MODE (insn) == TImode) { prev_ti = true; prev_cycle = this_cycle; } delete_insn (insn); } } } /* Implement TARGET_ASM_EMIT_EXCEPT_PERSONALITY. */ static void c6x_asm_emit_except_personality (rtx personality) { fputs ("\t.personality\t", asm_out_file); output_addr_const (asm_out_file, personality); fputc ('\n', asm_out_file); } /* Use a special assembly directive rather than a regular setion for unwind table data. */ static void c6x_asm_init_sections (void) { exception_section = get_unnamed_section (0, output_section_asm_op, "\t.handlerdata"); } /* A callback for the hw-doloop pass. Called to optimize LOOP in a machine-specific fashion; returns true if successful and false if the hwloop_fail function should be called. */ static bool hwloop_optimize (hwloop_info loop) { basic_block entry_bb, bb; rtx_insn *seq, *insn, *prev, *entry_after, *end_packet; rtx_insn *head_insn, *tail_insn, *new_insns, *last_insn; int loop_earliest; int n_execute_packets; edge entry_edge; unsigned ix; int max_uid_before, delayed_splits; int i, sp_ii, min_ii, max_ii, max_parallel, n_insns, n_real_insns, stages; rtx_insn **orig_vec; rtx_insn **copies; rtx_insn ***insn_copies; if (!c6x_flag_modulo_sched || !c6x_flag_schedule_insns2 || !TARGET_INSNS_64PLUS) return false; if (loop->iter_reg_used || loop->depth > 1) return false; if (loop->has_call || loop->has_asm) return false; if (loop->head != loop->tail) return false; gcc_assert (loop->incoming_dest == loop->head); entry_edge = NULL; FOR_EACH_VEC_SAFE_ELT (loop->incoming, i, entry_edge) if (entry_edge->flags & EDGE_FALLTHRU) break; if (entry_edge == NULL) return false; reshuffle_units (loop->head); in_hwloop = true; schedule_ebbs_init (); schedule_ebb (BB_HEAD (loop->tail), loop->loop_end, true); schedule_ebbs_finish (); in_hwloop = false; bb = loop->head; loop_earliest = bb_earliest_end_cycle (bb, loop->loop_end) + 1; max_uid_before = get_max_uid (); /* Split all multi-cycle operations, such as loads. For normal scheduling, we only do this for branches, as the generated code would otherwise not be interrupt-safe. When using sploop, it is safe and beneficial to split them. If any multi-cycle operations remain after splitting (because we don't handle them yet), we cannot pipeline the loop. */ delayed_splits = 0; FOR_BB_INSNS (bb, insn) { if (NONDEBUG_INSN_P (insn)) { recog_memoized (insn); if (split_delayed_nonbranch (insn)) delayed_splits++; else if (INSN_CODE (insn) >= 0 && get_attr_cycles (insn) > 1) goto undo_splits; } } /* Count the number of insns as well as the number real insns, and save the original sequence of insns in case we must restore it later. */ n_insns = n_real_insns = 0; FOR_BB_INSNS (bb, insn) { n_insns++; if (NONDEBUG_INSN_P (insn) && insn != loop->loop_end) n_real_insns++; } orig_vec = XNEWVEC (rtx_insn *, n_insns); n_insns = 0; FOR_BB_INSNS (bb, insn) orig_vec[n_insns++] = insn; /* Count the unit reservations, and compute a minimum II from that table. */ count_unit_reqs (unit_reqs, loop->start_label, PREV_INSN (loop->loop_end)); merge_unit_reqs (unit_reqs); min_ii = res_mii (unit_reqs); max_ii = loop_earliest < 15 ? loop_earliest : 14; /* Make copies of the loop body, up to a maximum number of stages we want to handle. */ max_parallel = loop_earliest / min_ii + 1; copies = XCNEWVEC (rtx_insn *, (max_parallel + 1) * n_real_insns); insn_copies = XNEWVEC (rtx_insn **, max_parallel + 1); for (i = 0; i < max_parallel + 1; i++) insn_copies[i] = copies + i * n_real_insns; head_insn = next_nonnote_nondebug_insn (loop->start_label); tail_insn = prev_real_insn (BB_END (bb)); i = 0; FOR_BB_INSNS (bb, insn) if (NONDEBUG_INSN_P (insn) && insn != loop->loop_end) insn_copies[0][i++] = insn; sploop_max_uid_iter0 = get_max_uid (); /* Generate the copies of the loop body, and save them in the INSN_COPIES array. */ start_sequence (); for (i = 0; i < max_parallel; i++) { int j; rtx_insn *this_iter; copy_bb_data id; this_iter = duplicate_insn_chain (head_insn, tail_insn, NULL, &id); j = 0; while (this_iter) { rtx_insn *prev_stage_insn = insn_copies[i][j]; gcc_assert (INSN_CODE (this_iter) == INSN_CODE (prev_stage_insn)); if (INSN_CODE (this_iter) >= 0 && (get_attr_type (this_iter) == TYPE_LOAD_SHADOW || get_attr_type (this_iter) == TYPE_MULT_SHADOW)) { rtx_insn *prev = PREV_INSN (this_iter); record_delay_slot_pair (prev, this_iter, get_attr_cycles (prev) - 1, 0); } else record_delay_slot_pair (prev_stage_insn, this_iter, i, 1); insn_copies[i + 1][j] = this_iter; j++; this_iter = next_nonnote_nondebug_insn (this_iter); } } new_insns = get_insns (); last_insn = insn_copies[max_parallel][n_real_insns - 1]; end_sequence (); emit_insn_before (new_insns, BB_END (bb)); /* Try to schedule the loop using varying initiation intervals, starting with the smallest possible and incrementing it on failure. */ for (sp_ii = min_ii; sp_ii <= max_ii; sp_ii++) { basic_block tmp_bb; if (dump_file) fprintf (dump_file, "Trying to schedule for II %d\n", sp_ii); df_clear_flags (DF_LR_RUN_DCE); schedule_ebbs_init (); set_modulo_params (sp_ii, max_parallel, n_real_insns, sploop_max_uid_iter0); tmp_bb = schedule_ebb (BB_HEAD (bb), last_insn, true); schedule_ebbs_finish (); if (tmp_bb) { if (dump_file) fprintf (dump_file, "Found schedule with II %d\n", sp_ii); break; } } discard_delay_pairs_above (max_uid_before); if (sp_ii > max_ii) goto restore_loop; stages = insn_get_clock (ss.last_scheduled_iter0) / sp_ii + 1; if (stages == 1 && sp_ii > 5) goto restore_loop; /* At this point, we know we've been successful, unless we find later that there are too many execute packets for the loop buffer to hold. */ /* Assign reservations to the instructions in the loop. We must find the stage that contains the full loop kernel, and transfer the reservations of the instructions contained in it to the corresponding instructions from iteration 0, which are the only ones we'll keep. */ assign_reservations (BB_HEAD (bb), ss.last_scheduled_insn); SET_PREV_INSN (BB_END (bb)) = ss.last_scheduled_iter0; SET_NEXT_INSN (ss.last_scheduled_iter0) = BB_END (bb); filter_insns_above (bb, sploop_max_uid_iter0); for (i = 0; i < n_real_insns; i++) { rtx insn = insn_copies[0][i]; int uid = INSN_UID (insn); int stage = insn_uid_get_clock (uid) / sp_ii; if (stage + 1 < stages) { int copy_uid; stage = stages - stage - 1; copy_uid = INSN_UID (insn_copies[stage][i]); INSN_INFO_ENTRY (uid).reservation = INSN_INFO_ENTRY (copy_uid).reservation; } } if (stages == 1) stages++; /* Compute the number of execute packets the pipelined form of the loop will require. */ prev = NULL; n_execute_packets = 0; for (insn = loop->start_label; insn != loop->loop_end; insn = NEXT_INSN (insn)) { if (NONDEBUG_INSN_P (insn) && GET_MODE (insn) == TImode && !shadow_p (insn)) { n_execute_packets++; if (prev && insn_get_clock (prev) + 1 != insn_get_clock (insn)) /* We need an extra NOP instruction. */ n_execute_packets++; prev = insn; } } end_packet = ss.last_scheduled_iter0; while (!NONDEBUG_INSN_P (end_packet) || GET_MODE (end_packet) != TImode) end_packet = PREV_INSN (end_packet); /* The earliest cycle in which we can emit the SPKERNEL instruction. */ loop_earliest = (stages - 1) * sp_ii; if (loop_earliest > insn_get_clock (end_packet)) { n_execute_packets++; end_packet = loop->loop_end; } else loop_earliest = insn_get_clock (end_packet); if (n_execute_packets > 14) goto restore_loop; /* Generate the spkernel instruction, and place it at the appropriate spot. */ PUT_MODE (end_packet, VOIDmode); insn = emit_jump_insn_before ( gen_spkernel (GEN_INT (stages - 1), const0_rtx, JUMP_LABEL (loop->loop_end)), end_packet); JUMP_LABEL (insn) = JUMP_LABEL (loop->loop_end); insn_set_clock (insn, loop_earliest); PUT_MODE (insn, TImode); INSN_INFO_ENTRY (INSN_UID (insn)).ebb_start = false; delete_insn (loop->loop_end); /* Place the mvc and sploop instructions before the loop. */ entry_bb = entry_edge->src; start_sequence (); insn = emit_insn (gen_mvilc (loop->iter_reg)); if (loop->iter_reg_used_outside) insn = emit_move_insn (loop->iter_reg, const0_rtx); insn = emit_insn (gen_sploop (GEN_INT (sp_ii))); seq = get_insns (); if (!single_succ_p (entry_bb) || vec_safe_length (loop->incoming) > 1) { basic_block new_bb; edge e; edge_iterator ei; emit_insn_before (seq, BB_HEAD (loop->head)); seq = emit_label_before (gen_label_rtx (), seq); new_bb = create_basic_block (seq, insn, entry_bb); FOR_EACH_EDGE (e, ei, loop->incoming) { if (!(e->flags & EDGE_FALLTHRU)) redirect_edge_and_branch_force (e, new_bb); else redirect_edge_succ (e, new_bb); } make_edge (new_bb, loop->head, 0); } else { entry_after = BB_END (entry_bb); while (DEBUG_INSN_P (entry_after) || (NOTE_P (entry_after) && NOTE_KIND (entry_after) != NOTE_INSN_BASIC_BLOCK)) entry_after = PREV_INSN (entry_after); emit_insn_after (seq, entry_after); } end_sequence (); /* Make sure we don't try to schedule this loop again. */ for (ix = 0; loop->blocks.iterate (ix, &bb); ix++) bb->flags |= BB_DISABLE_SCHEDULE; return true; restore_loop: if (dump_file) fprintf (dump_file, "Unable to pipeline loop.\n"); for (i = 1; i < n_insns; i++) { SET_NEXT_INSN (orig_vec[i - 1]) = orig_vec[i]; SET_PREV_INSN (orig_vec[i]) = orig_vec[i - 1]; } SET_PREV_INSN (orig_vec[0]) = PREV_INSN (BB_HEAD (bb)); SET_NEXT_INSN (PREV_INSN (BB_HEAD (bb))) = orig_vec[0]; SET_NEXT_INSN (orig_vec[n_insns - 1]) = NEXT_INSN (BB_END (bb)); SET_PREV_INSN (NEXT_INSN (BB_END (bb))) = orig_vec[n_insns - 1]; BB_HEAD (bb) = orig_vec[0]; BB_END (bb) = orig_vec[n_insns - 1]; undo_splits: free_delay_pairs (); FOR_BB_INSNS (bb, insn) if (NONDEBUG_INSN_P (insn)) undo_split_delayed_nonbranch (insn); return false; } /* A callback for the hw-doloop pass. Called when a loop we have discovered turns out not to be optimizable; we have to split the doloop_end pattern into a subtract and a test. */ static void hwloop_fail (hwloop_info loop) { rtx insn, test, testreg; if (dump_file) fprintf (dump_file, "splitting doloop insn %d\n", INSN_UID (loop->loop_end)); insn = gen_addsi3 (loop->iter_reg, loop->iter_reg, constm1_rtx); /* See if we can emit the add at the head of the loop rather than at the end. */ if (loop->head == NULL || loop->iter_reg_used_outside || loop->iter_reg_used || TEST_HARD_REG_BIT (loop->regs_set_in_loop, REGNO (loop->iter_reg)) || loop->incoming_dest != loop->head || EDGE_COUNT (loop->head->preds) != 2) emit_insn_before (insn, loop->loop_end); else { rtx_insn *t = loop->start_label; while (!NOTE_P (t) || NOTE_KIND (t) != NOTE_INSN_BASIC_BLOCK) t = NEXT_INSN (t); emit_insn_after (insn, t); } testreg = SET_DEST (XVECEXP (PATTERN (loop->loop_end), 0, 2)); if (GET_CODE (testreg) == SCRATCH) testreg = loop->iter_reg; else emit_insn_before (gen_movsi (testreg, loop->iter_reg), loop->loop_end); test = gen_rtx_NE (VOIDmode, testreg, const0_rtx); insn = emit_jump_insn_before (gen_cbranchsi4 (test, testreg, const0_rtx, loop->start_label), loop->loop_end); JUMP_LABEL (insn) = loop->start_label; LABEL_NUSES (loop->start_label)++; delete_insn (loop->loop_end); } static struct hw_doloop_hooks c6x_doloop_hooks = { hwloop_pattern_reg, hwloop_optimize, hwloop_fail }; /* Run the hw-doloop pass to modulo-schedule hardware loops, or split the doloop_end patterns where such optimizations are impossible. */ static void c6x_hwloops (void) { if (optimize) reorg_loops (true, &c6x_doloop_hooks); } /* Implement the TARGET_MACHINE_DEPENDENT_REORG pass. We split call insns here into a sequence that loads the return register and performs the call, and emit the return label. If scheduling after reload is requested, it happens here. */ static void c6x_reorg (void) { basic_block bb; bool do_selsched = (c6x_flag_schedule_insns2 && flag_selective_scheduling2 && !maybe_skip_selective_scheduling ()); /* We are freeing block_for_insn in the toplev to keep compatibility with old MDEP_REORGS that are not CFG based. Recompute it now. */ compute_bb_for_insn (); df_clear_flags (DF_LR_RUN_DCE); df_note_add_problem (); /* If optimizing, we'll have split before scheduling. */ if (optimize == 0) split_all_insns (); df_analyze (); if (c6x_flag_schedule_insns2) { int sz = get_max_uid () * 3 / 2 + 1; insn_info.create (sz); } /* Make sure the real-jump insns we create are not deleted. When modulo- scheduling, situations where a reg is only stored in a loop can also cause dead code when doing the initial unrolling. */ sched_no_dce = true; c6x_hwloops (); if (c6x_flag_schedule_insns2) { split_delayed_insns (); timevar_push (TV_SCHED2); if (do_selsched) run_selective_scheduling (); else schedule_ebbs (); conditionalize_after_sched (); timevar_pop (TV_SCHED2); free_delay_pairs (); } sched_no_dce = false; rtx_code_label **call_labels = XCNEWVEC (rtx_code_label *, get_max_uid () + 1); reorg_split_calls (call_labels); if (c6x_flag_schedule_insns2) { FOR_EACH_BB_FN (bb, cfun) if ((bb->flags & BB_DISABLE_SCHEDULE) == 0) assign_reservations (BB_HEAD (bb), BB_END (bb)); } if (c6x_flag_var_tracking) { timevar_push (TV_VAR_TRACKING); variable_tracking_main (); timevar_pop (TV_VAR_TRACKING); } reorg_emit_nops (call_labels); /* Post-process the schedule to move parallel insns into SEQUENCEs. */ if (c6x_flag_schedule_insns2) { free_delay_pairs (); c6x_gen_bundles (); } df_finish_pass (false); } /* Called when a function has been assembled. It should perform all the tasks of ASM_DECLARE_FUNCTION_SIZE in elfos.h, plus target-specific tasks. We free the reservation (and other scheduling) information here now that all insns have been output. */ void c6x_function_end (FILE *file, const char *fname) { c6x_output_fn_unwind (file); insn_info.release (); if (!flag_inhibit_size_directive) ASM_OUTPUT_MEASURED_SIZE (file, fname); } /* Determine whether X is a shift with code CODE and an integer amount AMOUNT. */ static bool shift_p (rtx x, enum rtx_code code, int amount) { return (GET_CODE (x) == code && GET_CODE (XEXP (x, 1)) == CONST_INT && INTVAL (XEXP (x, 1)) == amount); } /* 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 c6x_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno, int *total, bool speed) { int cost2 = COSTS_N_INSNS (1); rtx op0, op1; int code = GET_CODE (x); switch (code) { case CONST_INT: if (outer_code == SET || outer_code == PLUS) *total = satisfies_constraint_IsB (x) ? 0 : cost2; else if (outer_code == AND || outer_code == IOR || outer_code == XOR || outer_code == MINUS) *total = satisfies_constraint_Is5 (x) ? 0 : cost2; else if (GET_RTX_CLASS (outer_code) == RTX_COMPARE || GET_RTX_CLASS (outer_code) == RTX_COMM_COMPARE) *total = satisfies_constraint_Iu4 (x) ? 0 : cost2; else if (outer_code == ASHIFT || outer_code == ASHIFTRT || outer_code == LSHIFTRT) *total = satisfies_constraint_Iu5 (x) ? 0 : cost2; else *total = cost2; return true; case CONST: case LABEL_REF: case SYMBOL_REF: case CONST_DOUBLE: *total = COSTS_N_INSNS (2); return true; case TRUNCATE: /* Recognize a mult_highpart operation. */ if ((mode == HImode || mode == SImode) && GET_CODE (XEXP (x, 0)) == LSHIFTRT && GET_MODE (XEXP (x, 0)) == GET_MODE_2XWIDER_MODE (mode).require () && GET_CODE (XEXP (XEXP (x, 0), 0)) == MULT && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT && INTVAL (XEXP (XEXP (x, 0), 1)) == GET_MODE_BITSIZE (mode)) { rtx mul = XEXP (XEXP (x, 0), 0); rtx op0 = XEXP (mul, 0); rtx op1 = XEXP (mul, 1); enum rtx_code code0 = GET_CODE (op0); enum rtx_code code1 = GET_CODE (op1); if ((code0 == code1 && (code0 == SIGN_EXTEND || code0 == ZERO_EXTEND)) || (mode == HImode && code0 == ZERO_EXTEND && code1 == SIGN_EXTEND)) { if (mode == HImode) *total = COSTS_N_INSNS (2); else *total = COSTS_N_INSNS (12); mode = GET_MODE (XEXP (op0, 0)); *total += rtx_cost (XEXP (op0, 0), mode, code0, 0, speed); *total += rtx_cost (XEXP (op1, 0), mode, code1, 0, speed); return true; } } return false; case ASHIFT: case ASHIFTRT: case LSHIFTRT: if (mode == DImode) *total = COSTS_N_INSNS (CONSTANT_P (XEXP (x, 1)) ? 4 : 15); else *total = COSTS_N_INSNS (1); return false; case PLUS: case MINUS: *total = COSTS_N_INSNS (1); op0 = code == PLUS ? XEXP (x, 0) : XEXP (x, 1); op1 = code == PLUS ? XEXP (x, 1) : XEXP (x, 0); if (GET_MODE_SIZE (mode) <= UNITS_PER_WORD && INTEGRAL_MODE_P (mode) && GET_CODE (op0) == MULT && GET_CODE (XEXP (op0, 1)) == CONST_INT && (INTVAL (XEXP (op0, 1)) == 2 || INTVAL (XEXP (op0, 1)) == 4 || (code == PLUS && INTVAL (XEXP (op0, 1)) == 8))) { *total += rtx_cost (XEXP (op0, 0), mode, ASHIFT, 0, speed); *total += rtx_cost (op1, mode, (enum rtx_code) code, 1, speed); return true; } return false; case MULT: op0 = XEXP (x, 0); op1 = XEXP (x, 1); if (mode == DFmode) { if (TARGET_FP) *total = COSTS_N_INSNS (speed ? 10 : 1); else *total = COSTS_N_INSNS (speed ? 200 : 4); } else if (mode == SFmode) { if (TARGET_FP) *total = COSTS_N_INSNS (speed ? 4 : 1); else *total = COSTS_N_INSNS (speed ? 100 : 4); } else if (mode == DImode) { if (TARGET_MPY32 && GET_CODE (op0) == GET_CODE (op1) && (GET_CODE (op0) == ZERO_EXTEND || GET_CODE (op0) == SIGN_EXTEND)) { *total = COSTS_N_INSNS (speed ? 2 : 1); op0 = XEXP (op0, 0); op1 = XEXP (op1, 0); } else /* Maybe improve this laster. */ *total = COSTS_N_INSNS (20); } else if (mode == SImode) { if (((GET_CODE (op0) == ZERO_EXTEND || GET_CODE (op0) == SIGN_EXTEND || shift_p (op0, LSHIFTRT, 16)) && (GET_CODE (op1) == SIGN_EXTEND || GET_CODE (op1) == ZERO_EXTEND || scst5_operand (op1, SImode) || shift_p (op1, ASHIFTRT, 16) || shift_p (op1, LSHIFTRT, 16))) || (shift_p (op0, ASHIFTRT, 16) && (GET_CODE (op1) == SIGN_EXTEND || shift_p (op1, ASHIFTRT, 16)))) { *total = COSTS_N_INSNS (speed ? 2 : 1); op0 = XEXP (op0, 0); if (scst5_operand (op1, SImode)) op1 = NULL_RTX; else op1 = XEXP (op1, 0); } else if (!speed) *total = COSTS_N_INSNS (1); else if (TARGET_MPY32) *total = COSTS_N_INSNS (4); else *total = COSTS_N_INSNS (6); } else if (mode == HImode) *total = COSTS_N_INSNS (speed ? 2 : 1); if (GET_CODE (op0) != REG && (GET_CODE (op0) != SUBREG || GET_CODE (SUBREG_REG (op0)) != REG)) *total += rtx_cost (op0, mode, MULT, 0, speed); if (op1 && GET_CODE (op1) != REG && (GET_CODE (op1) != SUBREG || GET_CODE (SUBREG_REG (op1)) != REG)) *total += rtx_cost (op1, mode, MULT, 1, speed); return true; case UDIV: case DIV: /* This is a bit random; assuming on average there'll be 16 leading zeros. FIXME: estimate better for constant dividends. */ *total = COSTS_N_INSNS (6 + 3 * 16); return false; case IF_THEN_ELSE: /* Recognize the cmp_and/ior patterns. */ op0 = XEXP (x, 0); if ((GET_CODE (op0) == EQ || GET_CODE (op0) == NE) && REG_P (XEXP (op0, 0)) && XEXP (op0, 1) == const0_rtx && rtx_equal_p (XEXP (x, 1), XEXP (op0, 0))) { *total = rtx_cost (XEXP (x, 1), VOIDmode, (enum rtx_code) outer_code, opno, speed); return false; } return false; default: return false; } } /* Implements target hook vector_mode_supported_p. */ static bool c6x_vector_mode_supported_p (machine_mode mode) { switch (mode) { case E_V2HImode: case E_V4QImode: case E_V2SImode: case E_V4HImode: case E_V8QImode: return true; default: return false; } } /* Implements TARGET_VECTORIZE_PREFERRED_SIMD_MODE. */ static machine_mode c6x_preferred_simd_mode (scalar_mode mode) { switch (mode) { case E_HImode: return V2HImode; case E_QImode: return V4QImode; default: return word_mode; } } /* Implement TARGET_SCALAR_MODE_SUPPORTED_P. */ static bool c6x_scalar_mode_supported_p (scalar_mode mode) { if (ALL_FIXED_POINT_MODE_P (mode) && GET_MODE_PRECISION (mode) <= 2 * BITS_PER_WORD) return true; return default_scalar_mode_supported_p (mode); } /* Output a reference from a function exception table to the type_info object X. Output these via a special assembly directive. */ static bool c6x_output_ttype (rtx x) { /* Use special relocations for symbol references. */ if (GET_CODE (x) != CONST_INT) fputs ("\t.ehtype\t", asm_out_file); else fputs ("\t.word\t", asm_out_file); output_addr_const (asm_out_file, x); fputc ('\n', asm_out_file); return TRUE; } /* Modify the return address of the current function. */ void c6x_set_return_address (rtx source, rtx scratch) { struct c6x_frame frame; rtx addr; HOST_WIDE_INT offset; c6x_compute_frame_layout (&frame); if (! c6x_save_reg (RETURN_ADDR_REGNO)) emit_move_insn (gen_rtx_REG (Pmode, RETURN_ADDR_REGNO), source); else { if (frame_pointer_needed) { addr = hard_frame_pointer_rtx; offset = frame.b3_offset; } else { addr = stack_pointer_rtx; offset = frame.to_allocate - frame.b3_offset; } /* TODO: Use base+offset loads where possible. */ if (offset) { HOST_WIDE_INT low = trunc_int_for_mode (offset, HImode); emit_insn (gen_movsi_high (scratch, GEN_INT (low))); if (low != offset) emit_insn (gen_movsi_lo_sum (scratch, scratch, GEN_INT(offset))); emit_insn (gen_addsi3 (scratch, addr, scratch)); addr = scratch; } emit_move_insn (gen_frame_mem (Pmode, addr), source); } } /* We save pairs of registers using a DImode store. Describe the component registers for DWARF generation code. */ static rtx c6x_dwarf_register_span (rtx rtl) { unsigned regno; unsigned real_regno; int nregs; int i; rtx p; regno = REGNO (rtl); nregs = REG_NREGS (rtl); if (nregs == 1) return NULL_RTX; p = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc(nregs)); for (i = 0; i < nregs; i++) { if (TARGET_BIG_ENDIAN) real_regno = regno + nregs - (i + 1); else real_regno = regno + i; XVECEXP (p, 0, i) = gen_rtx_REG (SImode, real_regno); } return p; } /* Codes for all the C6X builtins. */ enum c6x_builtins { C6X_BUILTIN_SADD, C6X_BUILTIN_SSUB, C6X_BUILTIN_ADD2, C6X_BUILTIN_SUB2, C6X_BUILTIN_ADD4, C6X_BUILTIN_SUB4, C6X_BUILTIN_SADD2, C6X_BUILTIN_SSUB2, C6X_BUILTIN_SADDU4, C6X_BUILTIN_SMPY, C6X_BUILTIN_SMPYH, C6X_BUILTIN_SMPYHL, C6X_BUILTIN_SMPYLH, C6X_BUILTIN_MPY2, C6X_BUILTIN_SMPY2, C6X_BUILTIN_CLRR, C6X_BUILTIN_EXTR, C6X_BUILTIN_EXTRU, C6X_BUILTIN_SSHL, C6X_BUILTIN_SUBC, C6X_BUILTIN_ABS, C6X_BUILTIN_ABS2, C6X_BUILTIN_AVG2, C6X_BUILTIN_AVGU4, C6X_BUILTIN_MAX }; static GTY(()) tree c6x_builtin_decls[C6X_BUILTIN_MAX]; /* Return the C6X builtin for CODE. */ static tree c6x_builtin_decl (unsigned code, bool initialize_p ATTRIBUTE_UNUSED) { if (code >= C6X_BUILTIN_MAX) return error_mark_node; return c6x_builtin_decls[code]; } #define def_builtin(NAME, TYPE, CODE) \ do { \ tree bdecl; \ bdecl = add_builtin_function ((NAME), (TYPE), (CODE), BUILT_IN_MD, \ NULL, NULL_TREE); \ c6x_builtin_decls[CODE] = bdecl; \ } while (0) /* Set up all builtin functions for this target. */ static void c6x_init_builtins (void) { tree V4QI_type_node = build_vector_type (unsigned_intQI_type_node, 4); tree V2HI_type_node = build_vector_type (intHI_type_node, 2); tree V2SI_type_node = build_vector_type (intSI_type_node, 2); tree int_ftype_int = build_function_type_list (integer_type_node, integer_type_node, NULL_TREE); tree int_ftype_int_int = build_function_type_list (integer_type_node, integer_type_node, integer_type_node, NULL_TREE); tree v2hi_ftype_v2hi = build_function_type_list (V2HI_type_node, V2HI_type_node, NULL_TREE); tree v4qi_ftype_v4qi_v4qi = build_function_type_list (V4QI_type_node, V4QI_type_node, V4QI_type_node, NULL_TREE); tree v2hi_ftype_v2hi_v2hi = build_function_type_list (V2HI_type_node, V2HI_type_node, V2HI_type_node, NULL_TREE); tree v2si_ftype_v2hi_v2hi = build_function_type_list (V2SI_type_node, V2HI_type_node, V2HI_type_node, NULL_TREE); def_builtin ("__builtin_c6x_sadd", int_ftype_int_int, C6X_BUILTIN_SADD); def_builtin ("__builtin_c6x_ssub", int_ftype_int_int, C6X_BUILTIN_SSUB); def_builtin ("__builtin_c6x_add2", v2hi_ftype_v2hi_v2hi, C6X_BUILTIN_ADD2); def_builtin ("__builtin_c6x_sub2", v2hi_ftype_v2hi_v2hi, C6X_BUILTIN_SUB2); def_builtin ("__builtin_c6x_add4", v4qi_ftype_v4qi_v4qi, C6X_BUILTIN_ADD4); def_builtin ("__builtin_c6x_sub4", v4qi_ftype_v4qi_v4qi, C6X_BUILTIN_SUB4); def_builtin ("__builtin_c6x_mpy2", v2si_ftype_v2hi_v2hi, C6X_BUILTIN_MPY2); def_builtin ("__builtin_c6x_sadd2", v2hi_ftype_v2hi_v2hi, C6X_BUILTIN_SADD2); def_builtin ("__builtin_c6x_ssub2", v2hi_ftype_v2hi_v2hi, C6X_BUILTIN_SSUB2); def_builtin ("__builtin_c6x_saddu4", v4qi_ftype_v4qi_v4qi, C6X_BUILTIN_SADDU4); def_builtin ("__builtin_c6x_smpy2", v2si_ftype_v2hi_v2hi, C6X_BUILTIN_SMPY2); def_builtin ("__builtin_c6x_smpy", int_ftype_int_int, C6X_BUILTIN_SMPY); def_builtin ("__builtin_c6x_smpyh", int_ftype_int_int, C6X_BUILTIN_SMPYH); def_builtin ("__builtin_c6x_smpyhl", int_ftype_int_int, C6X_BUILTIN_SMPYHL); def_builtin ("__builtin_c6x_smpylh", int_ftype_int_int, C6X_BUILTIN_SMPYLH); def_builtin ("__builtin_c6x_sshl", int_ftype_int_int, C6X_BUILTIN_SSHL); def_builtin ("__builtin_c6x_subc", int_ftype_int_int, C6X_BUILTIN_SUBC); def_builtin ("__builtin_c6x_avg2", v2hi_ftype_v2hi_v2hi, C6X_BUILTIN_AVG2); def_builtin ("__builtin_c6x_avgu4", v4qi_ftype_v4qi_v4qi, C6X_BUILTIN_AVGU4); def_builtin ("__builtin_c6x_clrr", int_ftype_int_int, C6X_BUILTIN_CLRR); def_builtin ("__builtin_c6x_extr", int_ftype_int_int, C6X_BUILTIN_EXTR); def_builtin ("__builtin_c6x_extru", int_ftype_int_int, C6X_BUILTIN_EXTRU); def_builtin ("__builtin_c6x_abs", int_ftype_int, C6X_BUILTIN_ABS); def_builtin ("__builtin_c6x_abs2", v2hi_ftype_v2hi, C6X_BUILTIN_ABS2); } struct builtin_description { const enum insn_code icode; const char *const name; const enum c6x_builtins code; }; static const struct builtin_description bdesc_2arg[] = { { CODE_FOR_saddsi3, "__builtin_c6x_sadd", C6X_BUILTIN_SADD }, { CODE_FOR_ssubsi3, "__builtin_c6x_ssub", C6X_BUILTIN_SSUB }, { CODE_FOR_addv2hi3, "__builtin_c6x_add2", C6X_BUILTIN_ADD2 }, { CODE_FOR_subv2hi3, "__builtin_c6x_sub2", C6X_BUILTIN_SUB2 }, { CODE_FOR_addv4qi3, "__builtin_c6x_add4", C6X_BUILTIN_ADD4 }, { CODE_FOR_subv4qi3, "__builtin_c6x_sub4", C6X_BUILTIN_SUB4 }, { CODE_FOR_ss_addv2hi3, "__builtin_c6x_sadd2", C6X_BUILTIN_SADD2 }, { CODE_FOR_ss_subv2hi3, "__builtin_c6x_ssub2", C6X_BUILTIN_SSUB2 }, { CODE_FOR_us_addv4qi3, "__builtin_c6x_saddu4", C6X_BUILTIN_SADDU4 }, { CODE_FOR_subcsi3, "__builtin_c6x_subc", C6X_BUILTIN_SUBC }, { CODE_FOR_ss_ashlsi3, "__builtin_c6x_sshl", C6X_BUILTIN_SSHL }, { CODE_FOR_avgv2hi3, "__builtin_c6x_avg2", C6X_BUILTIN_AVG2 }, { CODE_FOR_uavgv4qi3, "__builtin_c6x_avgu4", C6X_BUILTIN_AVGU4 }, { CODE_FOR_mulhqsq3, "__builtin_c6x_smpy", C6X_BUILTIN_SMPY }, { CODE_FOR_mulhqsq3_hh, "__builtin_c6x_smpyh", C6X_BUILTIN_SMPYH }, { CODE_FOR_mulhqsq3_lh, "__builtin_c6x_smpylh", C6X_BUILTIN_SMPYLH }, { CODE_FOR_mulhqsq3_hl, "__builtin_c6x_smpyhl", C6X_BUILTIN_SMPYHL }, { CODE_FOR_mulv2hqv2sq3, "__builtin_c6x_smpy2", C6X_BUILTIN_SMPY2 }, { CODE_FOR_clrr, "__builtin_c6x_clrr", C6X_BUILTIN_CLRR }, { CODE_FOR_extr, "__builtin_c6x_extr", C6X_BUILTIN_EXTR }, { CODE_FOR_extru, "__builtin_c6x_extru", C6X_BUILTIN_EXTRU } }; static const struct builtin_description bdesc_1arg[] = { { CODE_FOR_ssabssi2, "__builtin_c6x_abs", C6X_BUILTIN_ABS }, { CODE_FOR_ssabsv2hi2, "__builtin_c6x_abs2", C6X_BUILTIN_ABS2 } }; /* Errors in the source file can cause expand_expr to return const0_rtx where we expect a vector. To avoid crashing, use one of the vector clear instructions. */ static rtx safe_vector_operand (rtx x, machine_mode mode) { if (x != const0_rtx) return x; x = gen_reg_rtx (SImode); emit_insn (gen_movsi (x, CONST0_RTX (SImode))); return gen_lowpart (mode, x); } /* Subroutine of c6x_expand_builtin to take care of binop insns. MACFLAG is -1 if this is a normal binary op, or one of the MACFLAG_xxx constants. */ static rtx c6x_expand_binop_builtin (enum insn_code icode, tree exp, rtx target, bool match_op) { int offs = match_op ? 1 : 0; rtx pat; tree arg0 = CALL_EXPR_ARG (exp, 0); tree arg1 = CALL_EXPR_ARG (exp, 1); rtx op0 = expand_expr (arg0, NULL_RTX, VOIDmode, EXPAND_NORMAL); rtx op1 = expand_expr (arg1, NULL_RTX, VOIDmode, EXPAND_NORMAL); machine_mode op0mode = GET_MODE (op0); machine_mode op1mode = GET_MODE (op1); machine_mode tmode = insn_data[icode].operand[0].mode; machine_mode mode0 = insn_data[icode].operand[1 + offs].mode; machine_mode mode1 = insn_data[icode].operand[2 + offs].mode; rtx ret = target; if (VECTOR_MODE_P (mode0)) op0 = safe_vector_operand (op0, mode0); if (VECTOR_MODE_P (mode1)) op1 = safe_vector_operand (op1, mode1); if (! target || GET_MODE (target) != tmode || ! (*insn_data[icode].operand[0].predicate) (target, tmode)) { if (tmode == SQmode || tmode == V2SQmode) { ret = gen_reg_rtx (tmode == SQmode ? SImode : V2SImode); target = gen_lowpart (tmode, ret); } else target = gen_reg_rtx (tmode); } if ((op0mode == V2HImode || op0mode == SImode || op0mode == VOIDmode) && (mode0 == V2HQmode || mode0 == HQmode || mode0 == SQmode)) { op0mode = mode0; op0 = gen_lowpart (mode0, op0); } if ((op1mode == V2HImode || op1mode == SImode || op1mode == VOIDmode) && (mode1 == V2HQmode || mode1 == HQmode || mode1 == SQmode)) { op1mode = mode1; op1 = gen_lowpart (mode1, op1); } /* In case the insn wants input operands in modes different from the result, abort. */ gcc_assert ((op0mode == mode0 || op0mode == VOIDmode) && (op1mode == mode1 || op1mode == VOIDmode)); if (! (*insn_data[icode].operand[1 + offs].predicate) (op0, mode0)) op0 = copy_to_mode_reg (mode0, op0); if (! (*insn_data[icode].operand[2 + offs].predicate) (op1, mode1)) op1 = copy_to_mode_reg (mode1, op1); if (match_op) pat = GEN_FCN (icode) (target, target, op0, op1); else pat = GEN_FCN (icode) (target, op0, op1); if (! pat) return 0; emit_insn (pat); return ret; } /* Subroutine of c6x_expand_builtin to take care of unop insns. */ static rtx c6x_expand_unop_builtin (enum insn_code icode, tree exp, rtx target) { rtx pat; tree arg0 = CALL_EXPR_ARG (exp, 0); rtx op0 = expand_expr (arg0, NULL_RTX, VOIDmode, EXPAND_NORMAL); machine_mode op0mode = GET_MODE (op0); machine_mode tmode = insn_data[icode].operand[0].mode; machine_mode mode0 = insn_data[icode].operand[1].mode; if (! target || GET_MODE (target) != tmode || ! (*insn_data[icode].operand[0].predicate) (target, tmode)) target = gen_reg_rtx (tmode); if (VECTOR_MODE_P (mode0)) op0 = safe_vector_operand (op0, mode0); if (op0mode == SImode && mode0 == HImode) { op0mode = HImode; op0 = gen_lowpart (HImode, op0); } gcc_assert (op0mode == mode0 || op0mode == VOIDmode); if (! (*insn_data[icode].operand[1].predicate) (op0, mode0)) op0 = copy_to_mode_reg (mode0, op0); pat = GEN_FCN (icode) (target, op0); if (! pat) return 0; emit_insn (pat); return target; } /* Expand an expression EXP that calls a built-in function, with result going to TARGET if that's convenient (and in mode MODE if that's convenient). SUBTARGET may be used as the target for computing one of EXP's operands. IGNORE is nonzero if the value is to be ignored. */ static rtx c6x_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED, rtx subtarget ATTRIBUTE_UNUSED, machine_mode mode ATTRIBUTE_UNUSED, int ignore ATTRIBUTE_UNUSED) { size_t i; const struct builtin_description *d; tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0); unsigned int fcode = DECL_MD_FUNCTION_CODE (fndecl); for (i = 0, d = bdesc_2arg; i < ARRAY_SIZE (bdesc_2arg); i++, d++) if (d->code == fcode) return c6x_expand_binop_builtin (d->icode, exp, target, fcode == C6X_BUILTIN_CLRR); for (i = 0, d = bdesc_1arg; i < ARRAY_SIZE (bdesc_1arg); i++, d++) if (d->code == fcode) return c6x_expand_unop_builtin (d->icode, exp, target); gcc_unreachable (); } /* Target unwind frame info is generated from dwarf CFI directives, so always output dwarf2 unwind info. */ static enum unwind_info_type c6x_debug_unwind_info (void) { if (flag_unwind_tables || flag_exceptions) return UI_DWARF2; return default_debug_unwind_info (); } /* Implement TARGET_HARD_REGNO_MODE_OK. */ static bool c6x_hard_regno_mode_ok (unsigned int regno, machine_mode mode) { return GET_MODE_SIZE (mode) <= UNITS_PER_WORD || (regno & 1) == 0; } /* Implement TARGET_MODES_TIEABLE_P. */ static bool c6x_modes_tieable_p (machine_mode mode1, machine_mode mode2) { return (mode1 == mode2 || (GET_MODE_SIZE (mode1) <= UNITS_PER_WORD && GET_MODE_SIZE (mode2) <= UNITS_PER_WORD)); } /* Implement REGNO_REG_CLASS. */ enum reg_class c6x_regno_reg_class (int reg) { if (reg >= REG_A1 && reg <= REG_A2) return PREDICATE_A_REGS; if (reg == REG_A0 && TARGET_INSNS_64) return PREDICATE_A_REGS; if (reg >= REG_B0 && reg <= REG_B2) return PREDICATE_B_REGS; if (A_REGNO_P (reg)) return NONPREDICATE_A_REGS; if (call_used_or_fixed_reg_p (reg)) return CALL_USED_B_REGS; return B_REGS; } /* Target Structure. */ /* Initialize the GCC target structure. */ #undef TARGET_FUNCTION_ARG #define TARGET_FUNCTION_ARG c6x_function_arg #undef TARGET_FUNCTION_ARG_ADVANCE #define TARGET_FUNCTION_ARG_ADVANCE c6x_function_arg_advance #undef TARGET_FUNCTION_ARG_BOUNDARY #define TARGET_FUNCTION_ARG_BOUNDARY c6x_function_arg_boundary #undef TARGET_FUNCTION_ARG_ROUND_BOUNDARY #define TARGET_FUNCTION_ARG_ROUND_BOUNDARY \ c6x_function_arg_round_boundary #undef TARGET_FUNCTION_VALUE_REGNO_P #define TARGET_FUNCTION_VALUE_REGNO_P c6x_function_value_regno_p #undef TARGET_FUNCTION_VALUE #define TARGET_FUNCTION_VALUE c6x_function_value #undef TARGET_LIBCALL_VALUE #define TARGET_LIBCALL_VALUE c6x_libcall_value #undef TARGET_RETURN_IN_MEMORY #define TARGET_RETURN_IN_MEMORY c6x_return_in_memory #undef TARGET_RETURN_IN_MSB #define TARGET_RETURN_IN_MSB c6x_return_in_msb #undef TARGET_PASS_BY_REFERENCE #define TARGET_PASS_BY_REFERENCE c6x_pass_by_reference #undef TARGET_CALLEE_COPIES #define TARGET_CALLEE_COPIES hook_bool_CUMULATIVE_ARGS_arg_info_true #undef TARGET_STRUCT_VALUE_RTX #define TARGET_STRUCT_VALUE_RTX c6x_struct_value_rtx #undef TARGET_FUNCTION_OK_FOR_SIBCALL #define TARGET_FUNCTION_OK_FOR_SIBCALL c6x_function_ok_for_sibcall #undef TARGET_ASM_OUTPUT_MI_THUNK #define TARGET_ASM_OUTPUT_MI_THUNK c6x_output_mi_thunk #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK #define TARGET_ASM_CAN_OUTPUT_MI_THUNK c6x_can_output_mi_thunk #undef TARGET_BUILD_BUILTIN_VA_LIST #define TARGET_BUILD_BUILTIN_VA_LIST c6x_build_builtin_va_list #undef TARGET_ASM_TRAMPOLINE_TEMPLATE #define TARGET_ASM_TRAMPOLINE_TEMPLATE c6x_asm_trampoline_template #undef TARGET_TRAMPOLINE_INIT #define TARGET_TRAMPOLINE_INIT c6x_initialize_trampoline #undef TARGET_LEGITIMATE_CONSTANT_P #define TARGET_LEGITIMATE_CONSTANT_P c6x_legitimate_constant_p #undef TARGET_LEGITIMATE_ADDRESS_P #define TARGET_LEGITIMATE_ADDRESS_P c6x_legitimate_address_p #undef TARGET_LRA_P #define TARGET_LRA_P hook_bool_void_false #undef TARGET_IN_SMALL_DATA_P #define TARGET_IN_SMALL_DATA_P c6x_in_small_data_p #undef TARGET_ASM_SELECT_RTX_SECTION #define TARGET_ASM_SELECT_RTX_SECTION c6x_select_rtx_section #undef TARGET_ASM_SELECT_SECTION #define TARGET_ASM_SELECT_SECTION c6x_elf_select_section #undef TARGET_ASM_UNIQUE_SECTION #define TARGET_ASM_UNIQUE_SECTION c6x_elf_unique_section #undef TARGET_SECTION_TYPE_FLAGS #define TARGET_SECTION_TYPE_FLAGS c6x_section_type_flags #undef TARGET_HAVE_SRODATA_SECTION #define TARGET_HAVE_SRODATA_SECTION true #undef TARGET_ASM_MERGEABLE_RODATA_PREFIX #define TARGET_ASM_MERGEABLE_RODATA_PREFIX ".const" #undef TARGET_OPTION_OVERRIDE #define TARGET_OPTION_OVERRIDE c6x_option_override #undef TARGET_CONDITIONAL_REGISTER_USAGE #define TARGET_CONDITIONAL_REGISTER_USAGE c6x_conditional_register_usage #undef TARGET_INIT_LIBFUNCS #define TARGET_INIT_LIBFUNCS c6x_init_libfuncs #undef TARGET_LIBFUNC_GNU_PREFIX #define TARGET_LIBFUNC_GNU_PREFIX true #undef TARGET_SCALAR_MODE_SUPPORTED_P #define TARGET_SCALAR_MODE_SUPPORTED_P c6x_scalar_mode_supported_p #undef TARGET_VECTOR_MODE_SUPPORTED_P #define TARGET_VECTOR_MODE_SUPPORTED_P c6x_vector_mode_supported_p #undef TARGET_VECTORIZE_PREFERRED_SIMD_MODE #define TARGET_VECTORIZE_PREFERRED_SIMD_MODE c6x_preferred_simd_mode #undef TARGET_RTX_COSTS #define TARGET_RTX_COSTS c6x_rtx_costs #undef TARGET_SCHED_INIT #define TARGET_SCHED_INIT c6x_sched_init #undef TARGET_SCHED_SET_SCHED_FLAGS #define TARGET_SCHED_SET_SCHED_FLAGS c6x_set_sched_flags #undef TARGET_SCHED_ADJUST_COST #define TARGET_SCHED_ADJUST_COST c6x_adjust_cost #undef TARGET_SCHED_ISSUE_RATE #define TARGET_SCHED_ISSUE_RATE c6x_issue_rate #undef TARGET_SCHED_VARIABLE_ISSUE #define TARGET_SCHED_VARIABLE_ISSUE c6x_variable_issue #undef TARGET_SCHED_REORDER #define TARGET_SCHED_REORDER c6x_sched_reorder #undef TARGET_SCHED_REORDER2 #define TARGET_SCHED_REORDER2 c6x_sched_reorder2 #undef TARGET_SCHED_DFA_NEW_CYCLE #define TARGET_SCHED_DFA_NEW_CYCLE c6x_dfa_new_cycle #undef TARGET_SCHED_DFA_PRE_CYCLE_INSN #define TARGET_SCHED_DFA_PRE_CYCLE_INSN c6x_sched_dfa_pre_cycle_insn #undef TARGET_SCHED_EXPOSED_PIPELINE #define TARGET_SCHED_EXPOSED_PIPELINE true #undef TARGET_SCHED_ALLOC_SCHED_CONTEXT #define TARGET_SCHED_ALLOC_SCHED_CONTEXT c6x_alloc_sched_context #undef TARGET_SCHED_INIT_SCHED_CONTEXT #define TARGET_SCHED_INIT_SCHED_CONTEXT c6x_init_sched_context #undef TARGET_SCHED_SET_SCHED_CONTEXT #define TARGET_SCHED_SET_SCHED_CONTEXT c6x_set_sched_context #undef TARGET_SCHED_CLEAR_SCHED_CONTEXT #define TARGET_SCHED_CLEAR_SCHED_CONTEXT c6x_clear_sched_context #undef TARGET_SCHED_FREE_SCHED_CONTEXT #define TARGET_SCHED_FREE_SCHED_CONTEXT c6x_free_sched_context #undef TARGET_CAN_ELIMINATE #define TARGET_CAN_ELIMINATE c6x_can_eliminate #undef TARGET_PREFERRED_RENAME_CLASS #define TARGET_PREFERRED_RENAME_CLASS c6x_preferred_rename_class #undef TARGET_MACHINE_DEPENDENT_REORG #define TARGET_MACHINE_DEPENDENT_REORG c6x_reorg #undef TARGET_ASM_FILE_START #define TARGET_ASM_FILE_START c6x_file_start #undef TARGET_PRINT_OPERAND #define TARGET_PRINT_OPERAND c6x_print_operand #undef TARGET_PRINT_OPERAND_ADDRESS #define TARGET_PRINT_OPERAND_ADDRESS c6x_print_operand_address #undef TARGET_PRINT_OPERAND_PUNCT_VALID_P #define TARGET_PRINT_OPERAND_PUNCT_VALID_P c6x_print_operand_punct_valid_p /* C6x unwinding tables use a different format for the typeinfo tables. */ #undef TARGET_ASM_TTYPE #define TARGET_ASM_TTYPE c6x_output_ttype /* The C6x ABI follows the ARM EABI exception handling rules. */ #undef TARGET_ARM_EABI_UNWINDER #define TARGET_ARM_EABI_UNWINDER true #undef TARGET_ASM_EMIT_EXCEPT_PERSONALITY #define TARGET_ASM_EMIT_EXCEPT_PERSONALITY c6x_asm_emit_except_personality #undef TARGET_ASM_INIT_SECTIONS #define TARGET_ASM_INIT_SECTIONS c6x_asm_init_sections #undef TARGET_DEBUG_UNWIND_INFO #define TARGET_DEBUG_UNWIND_INFO c6x_debug_unwind_info #undef TARGET_DWARF_REGISTER_SPAN #define TARGET_DWARF_REGISTER_SPAN c6x_dwarf_register_span #undef TARGET_INIT_BUILTINS #define TARGET_INIT_BUILTINS c6x_init_builtins #undef TARGET_EXPAND_BUILTIN #define TARGET_EXPAND_BUILTIN c6x_expand_builtin #undef TARGET_BUILTIN_DECL #define TARGET_BUILTIN_DECL c6x_builtin_decl #undef TARGET_HARD_REGNO_MODE_OK #define TARGET_HARD_REGNO_MODE_OK c6x_hard_regno_mode_ok #undef TARGET_MODES_TIEABLE_P #define TARGET_MODES_TIEABLE_P c6x_modes_tieable_p struct gcc_target targetm = TARGET_INITIALIZER; #include "gt-c6x.h"