| /* Subroutines for insn-output.cc for Motorola 68000 family. |
| Copyright (C) 1987-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #define IN_TARGET_CODE 1 |
| |
| #include "config.h" |
| #define INCLUDE_STRING |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "cfghooks.h" |
| #include "tree.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "rtl.h" |
| #include "df.h" |
| #include "alias.h" |
| #include "fold-const.h" |
| #include "calls.h" |
| #include "stor-layout.h" |
| #include "varasm.h" |
| #include "regs.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "output.h" |
| #include "insn-attr.h" |
| #include "recog.h" |
| #include "diagnostic-core.h" |
| #include "flags.h" |
| #include "expmed.h" |
| #include "dojump.h" |
| #include "explow.h" |
| #include "memmodel.h" |
| #include "emit-rtl.h" |
| #include "stmt.h" |
| #include "expr.h" |
| #include "reload.h" |
| #include "tm_p.h" |
| #include "target.h" |
| #include "debug.h" |
| #include "cfgrtl.h" |
| #include "cfganal.h" |
| #include "lcm.h" |
| #include "cfgbuild.h" |
| #include "cfgcleanup.h" |
| /* ??? Need to add a dependency between m68k.o and sched-int.h. */ |
| #include "sched-int.h" |
| #include "insn-codes.h" |
| #include "opts.h" |
| #include "optabs.h" |
| #include "builtins.h" |
| #include "rtl-iter.h" |
| #include "toplev.h" |
| |
| /* This file should be included last. */ |
| #include "target-def.h" |
| |
| enum reg_class regno_reg_class[] = |
| { |
| DATA_REGS, DATA_REGS, DATA_REGS, DATA_REGS, |
| DATA_REGS, DATA_REGS, DATA_REGS, DATA_REGS, |
| ADDR_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, |
| ADDR_REGS, ADDR_REGS, ADDR_REGS, ADDR_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| FP_REGS, FP_REGS, FP_REGS, FP_REGS, |
| ADDR_REGS |
| }; |
| |
| |
| /* The minimum number of integer registers that we want to save with the |
| movem instruction. Using two movel instructions instead of a single |
| moveml is about 15% faster for the 68020 and 68030 at no expense in |
| code size. */ |
| #define MIN_MOVEM_REGS 3 |
| |
| /* The minimum number of floating point registers that we want to save |
| with the fmovem instruction. */ |
| #define MIN_FMOVEM_REGS 1 |
| |
| /* Structure describing stack frame layout. */ |
| struct m68k_frame |
| { |
| /* Stack pointer to frame pointer offset. */ |
| HOST_WIDE_INT offset; |
| |
| /* Offset of FPU registers. */ |
| HOST_WIDE_INT foffset; |
| |
| /* Frame size in bytes (rounded up). */ |
| HOST_WIDE_INT size; |
| |
| /* Data and address register. */ |
| int reg_no; |
| unsigned int reg_mask; |
| |
| /* FPU registers. */ |
| int fpu_no; |
| unsigned int fpu_mask; |
| |
| /* Offsets relative to ARG_POINTER. */ |
| HOST_WIDE_INT frame_pointer_offset; |
| HOST_WIDE_INT stack_pointer_offset; |
| |
| /* Function which the above information refers to. */ |
| int funcdef_no; |
| }; |
| |
| /* Current frame information calculated by m68k_compute_frame_layout(). */ |
| static struct m68k_frame current_frame; |
| |
| /* Structure describing an m68k address. |
| |
| If CODE is UNKNOWN, the address is BASE + INDEX * SCALE + OFFSET, |
| with null fields evaluating to 0. Here: |
| |
| - BASE satisfies m68k_legitimate_base_reg_p |
| - INDEX satisfies m68k_legitimate_index_reg_p |
| - OFFSET satisfies m68k_legitimate_constant_address_p |
| |
| INDEX is either HImode or SImode. The other fields are SImode. |
| |
| If CODE is PRE_DEC, the address is -(BASE). If CODE is POST_INC, |
| the address is (BASE)+. */ |
| struct m68k_address { |
| enum rtx_code code; |
| rtx base; |
| rtx index; |
| rtx offset; |
| int scale; |
| }; |
| |
| static int m68k_sched_adjust_cost (rtx_insn *, int, rtx_insn *, int, |
| unsigned int); |
| static int m68k_sched_issue_rate (void); |
| static int m68k_sched_variable_issue (FILE *, int, rtx_insn *, int); |
| static void m68k_sched_md_init_global (FILE *, int, int); |
| static void m68k_sched_md_finish_global (FILE *, int); |
| static void m68k_sched_md_init (FILE *, int, int); |
| static void m68k_sched_dfa_pre_advance_cycle (void); |
| static void m68k_sched_dfa_post_advance_cycle (void); |
| static int m68k_sched_first_cycle_multipass_dfa_lookahead (void); |
| |
| static bool m68k_can_eliminate (const int, const int); |
| static void m68k_conditional_register_usage (void); |
| static bool m68k_legitimate_address_p (machine_mode, rtx, bool); |
| static void m68k_option_override (void); |
| static void m68k_override_options_after_change (void); |
| static rtx find_addr_reg (rtx); |
| static const char *singlemove_string (rtx *); |
| static void m68k_output_mi_thunk (FILE *, tree, HOST_WIDE_INT, |
| HOST_WIDE_INT, tree); |
| static rtx m68k_struct_value_rtx (tree, int); |
| static tree m68k_handle_fndecl_attribute (tree *node, tree name, |
| tree args, int flags, |
| bool *no_add_attrs); |
| static void m68k_compute_frame_layout (void); |
| static bool m68k_save_reg (unsigned int regno, bool interrupt_handler); |
| static bool m68k_ok_for_sibcall_p (tree, tree); |
| static bool m68k_tls_symbol_p (rtx); |
| static rtx m68k_legitimize_address (rtx, rtx, machine_mode); |
| static bool m68k_rtx_costs (rtx, machine_mode, int, int, int *, bool); |
| #if M68K_HONOR_TARGET_STRICT_ALIGNMENT |
| static bool m68k_return_in_memory (const_tree, const_tree); |
| #endif |
| static void m68k_output_dwarf_dtprel (FILE *, int, rtx) ATTRIBUTE_UNUSED; |
| static void m68k_trampoline_init (rtx, tree, rtx); |
| static poly_int64 m68k_return_pops_args (tree, tree, poly_int64); |
| static rtx m68k_delegitimize_address (rtx); |
| static void m68k_function_arg_advance (cumulative_args_t, |
| const function_arg_info &); |
| static rtx m68k_function_arg (cumulative_args_t, const function_arg_info &); |
| static bool m68k_cannot_force_const_mem (machine_mode mode, rtx x); |
| static bool m68k_output_addr_const_extra (FILE *, rtx); |
| static void m68k_init_sync_libfuncs (void) ATTRIBUTE_UNUSED; |
| static enum flt_eval_method |
| m68k_excess_precision (enum excess_precision_type); |
| static unsigned int m68k_hard_regno_nregs (unsigned int, machine_mode); |
| static bool m68k_hard_regno_mode_ok (unsigned int, machine_mode); |
| static bool m68k_modes_tieable_p (machine_mode, machine_mode); |
| static machine_mode m68k_promote_function_mode (const_tree, machine_mode, |
| int *, const_tree, int); |
| static void m68k_asm_final_postscan_insn (FILE *, rtx_insn *insn, rtx [], int); |
| |
| /* Initialize the GCC target structure. */ |
| |
| #if INT_OP_GROUP == INT_OP_DOT_WORD |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\t.word\t" |
| #endif |
| |
| #if INT_OP_GROUP == INT_OP_NO_DOT |
| #undef TARGET_ASM_BYTE_OP |
| #define TARGET_ASM_BYTE_OP "\tbyte\t" |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\tshort\t" |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\tlong\t" |
| #endif |
| |
| #if INT_OP_GROUP == INT_OP_DC |
| #undef TARGET_ASM_BYTE_OP |
| #define TARGET_ASM_BYTE_OP "\tdc.b\t" |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\tdc.w\t" |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\tdc.l\t" |
| #endif |
| |
| #undef TARGET_ASM_UNALIGNED_HI_OP |
| #define TARGET_ASM_UNALIGNED_HI_OP TARGET_ASM_ALIGNED_HI_OP |
| #undef TARGET_ASM_UNALIGNED_SI_OP |
| #define TARGET_ASM_UNALIGNED_SI_OP TARGET_ASM_ALIGNED_SI_OP |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK m68k_output_mi_thunk |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK hook_bool_const_tree_hwi_hwi_const_tree_true |
| |
| #undef TARGET_ASM_FILE_START_APP_OFF |
| #define TARGET_ASM_FILE_START_APP_OFF true |
| |
| #undef TARGET_LEGITIMIZE_ADDRESS |
| #define TARGET_LEGITIMIZE_ADDRESS m68k_legitimize_address |
| |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST m68k_sched_adjust_cost |
| |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE m68k_sched_issue_rate |
| |
| #undef TARGET_SCHED_VARIABLE_ISSUE |
| #define TARGET_SCHED_VARIABLE_ISSUE m68k_sched_variable_issue |
| |
| #undef TARGET_SCHED_INIT_GLOBAL |
| #define TARGET_SCHED_INIT_GLOBAL m68k_sched_md_init_global |
| |
| #undef TARGET_SCHED_FINISH_GLOBAL |
| #define TARGET_SCHED_FINISH_GLOBAL m68k_sched_md_finish_global |
| |
| #undef TARGET_SCHED_INIT |
| #define TARGET_SCHED_INIT m68k_sched_md_init |
| |
| #undef TARGET_SCHED_DFA_PRE_ADVANCE_CYCLE |
| #define TARGET_SCHED_DFA_PRE_ADVANCE_CYCLE m68k_sched_dfa_pre_advance_cycle |
| |
| #undef TARGET_SCHED_DFA_POST_ADVANCE_CYCLE |
| #define TARGET_SCHED_DFA_POST_ADVANCE_CYCLE m68k_sched_dfa_post_advance_cycle |
| |
| #undef TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD |
| #define TARGET_SCHED_FIRST_CYCLE_MULTIPASS_DFA_LOOKAHEAD \ |
| m68k_sched_first_cycle_multipass_dfa_lookahead |
| |
| #undef TARGET_OPTION_OVERRIDE |
| #define TARGET_OPTION_OVERRIDE m68k_option_override |
| |
| #undef TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE |
| #define TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE m68k_override_options_after_change |
| |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS m68k_rtx_costs |
| |
| #undef TARGET_ATTRIBUTE_TABLE |
| #define TARGET_ATTRIBUTE_TABLE m68k_attribute_table |
| |
| #undef TARGET_PROMOTE_PROTOTYPES |
| #define TARGET_PROMOTE_PROTOTYPES hook_bool_const_tree_true |
| |
| #undef TARGET_STRUCT_VALUE_RTX |
| #define TARGET_STRUCT_VALUE_RTX m68k_struct_value_rtx |
| |
| #undef TARGET_CANNOT_FORCE_CONST_MEM |
| #define TARGET_CANNOT_FORCE_CONST_MEM m68k_cannot_force_const_mem |
| |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL m68k_ok_for_sibcall_p |
| |
| #if M68K_HONOR_TARGET_STRICT_ALIGNMENT |
| #undef TARGET_RETURN_IN_MEMORY |
| #define TARGET_RETURN_IN_MEMORY m68k_return_in_memory |
| #endif |
| |
| #ifdef HAVE_AS_TLS |
| #undef TARGET_HAVE_TLS |
| #define TARGET_HAVE_TLS (true) |
| |
| #undef TARGET_ASM_OUTPUT_DWARF_DTPREL |
| #define TARGET_ASM_OUTPUT_DWARF_DTPREL m68k_output_dwarf_dtprel |
| #endif |
| |
| #undef TARGET_LRA_P |
| #define TARGET_LRA_P hook_bool_void_false |
| |
| #undef TARGET_LEGITIMATE_ADDRESS_P |
| #define TARGET_LEGITIMATE_ADDRESS_P m68k_legitimate_address_p |
| |
| #undef TARGET_CAN_ELIMINATE |
| #define TARGET_CAN_ELIMINATE m68k_can_eliminate |
| |
| #undef TARGET_CONDITIONAL_REGISTER_USAGE |
| #define TARGET_CONDITIONAL_REGISTER_USAGE m68k_conditional_register_usage |
| |
| #undef TARGET_TRAMPOLINE_INIT |
| #define TARGET_TRAMPOLINE_INIT m68k_trampoline_init |
| |
| #undef TARGET_RETURN_POPS_ARGS |
| #define TARGET_RETURN_POPS_ARGS m68k_return_pops_args |
| |
| #undef TARGET_DELEGITIMIZE_ADDRESS |
| #define TARGET_DELEGITIMIZE_ADDRESS m68k_delegitimize_address |
| |
| #undef TARGET_FUNCTION_ARG |
| #define TARGET_FUNCTION_ARG m68k_function_arg |
| |
| #undef TARGET_FUNCTION_ARG_ADVANCE |
| #define TARGET_FUNCTION_ARG_ADVANCE m68k_function_arg_advance |
| |
| #undef TARGET_LEGITIMATE_CONSTANT_P |
| #define TARGET_LEGITIMATE_CONSTANT_P m68k_legitimate_constant_p |
| |
| #undef TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA |
| #define TARGET_ASM_OUTPUT_ADDR_CONST_EXTRA m68k_output_addr_const_extra |
| |
| #undef TARGET_C_EXCESS_PRECISION |
| #define TARGET_C_EXCESS_PRECISION m68k_excess_precision |
| |
| /* The value stored by TAS. */ |
| #undef TARGET_ATOMIC_TEST_AND_SET_TRUEVAL |
| #define TARGET_ATOMIC_TEST_AND_SET_TRUEVAL 128 |
| |
| #undef TARGET_HARD_REGNO_NREGS |
| #define TARGET_HARD_REGNO_NREGS m68k_hard_regno_nregs |
| #undef TARGET_HARD_REGNO_MODE_OK |
| #define TARGET_HARD_REGNO_MODE_OK m68k_hard_regno_mode_ok |
| |
| #undef TARGET_MODES_TIEABLE_P |
| #define TARGET_MODES_TIEABLE_P m68k_modes_tieable_p |
| |
| #undef TARGET_PROMOTE_FUNCTION_MODE |
| #define TARGET_PROMOTE_FUNCTION_MODE m68k_promote_function_mode |
| |
| #undef TARGET_HAVE_SPECULATION_SAFE_VALUE |
| #define TARGET_HAVE_SPECULATION_SAFE_VALUE speculation_safe_value_not_needed |
| |
| #undef TARGET_ASM_FINAL_POSTSCAN_INSN |
| #define TARGET_ASM_FINAL_POSTSCAN_INSN m68k_asm_final_postscan_insn |
| |
| static const struct attribute_spec m68k_attribute_table[] = |
| { |
| /* { name, min_len, max_len, decl_req, type_req, fn_type_req, |
| affects_type_identity, handler, exclude } */ |
| { "interrupt", 0, 0, true, false, false, false, |
| m68k_handle_fndecl_attribute, NULL }, |
| { "interrupt_handler", 0, 0, true, false, false, false, |
| m68k_handle_fndecl_attribute, NULL }, |
| { "interrupt_thread", 0, 0, true, false, false, false, |
| m68k_handle_fndecl_attribute, NULL }, |
| { NULL, 0, 0, false, false, false, false, NULL, NULL } |
| }; |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| /* Base flags for 68k ISAs. */ |
| #define FL_FOR_isa_00 FL_ISA_68000 |
| #define FL_FOR_isa_10 (FL_FOR_isa_00 | FL_ISA_68010) |
| /* FL_68881 controls the default setting of -m68881. gcc has traditionally |
| generated 68881 code for 68020 and 68030 targets unless explicitly told |
| not to. */ |
| #define FL_FOR_isa_20 (FL_FOR_isa_10 | FL_ISA_68020 \ |
| | FL_BITFIELD | FL_68881 | FL_CAS) |
| #define FL_FOR_isa_40 (FL_FOR_isa_20 | FL_ISA_68040) |
| #define FL_FOR_isa_cpu32 (FL_FOR_isa_10 | FL_ISA_68020) |
| |
| /* Base flags for ColdFire ISAs. */ |
| #define FL_FOR_isa_a (FL_COLDFIRE | FL_ISA_A) |
| #define FL_FOR_isa_aplus (FL_FOR_isa_a | FL_ISA_APLUS | FL_CF_USP) |
| /* Note ISA_B doesn't necessarily include USP (user stack pointer) support. */ |
| #define FL_FOR_isa_b (FL_FOR_isa_a | FL_ISA_B | FL_CF_HWDIV) |
| /* ISA_C is not upwardly compatible with ISA_B. */ |
| #define FL_FOR_isa_c (FL_FOR_isa_a | FL_ISA_C | FL_CF_USP) |
| |
| enum m68k_isa |
| { |
| /* Traditional 68000 instruction sets. */ |
| isa_00, |
| isa_10, |
| isa_20, |
| isa_40, |
| isa_cpu32, |
| /* ColdFire instruction set variants. */ |
| isa_a, |
| isa_aplus, |
| isa_b, |
| isa_c, |
| isa_max |
| }; |
| |
| /* Information about one of the -march, -mcpu or -mtune arguments. */ |
| struct m68k_target_selection |
| { |
| /* The argument being described. */ |
| const char *name; |
| |
| /* For -mcpu, this is the device selected by the option. |
| For -mtune and -march, it is a representative device |
| for the microarchitecture or ISA respectively. */ |
| enum target_device device; |
| |
| /* The M68K_DEVICE fields associated with DEVICE. See the comment |
| in m68k-devices.def for details. FAMILY is only valid for -mcpu. */ |
| const char *family; |
| enum uarch_type microarch; |
| enum m68k_isa isa; |
| unsigned long flags; |
| }; |
| |
| /* A list of all devices in m68k-devices.def. Used for -mcpu selection. */ |
| static const struct m68k_target_selection all_devices[] = |
| { |
| #define M68K_DEVICE(NAME,ENUM_VALUE,FAMILY,MULTILIB,MICROARCH,ISA,FLAGS) \ |
| { NAME, ENUM_VALUE, FAMILY, u##MICROARCH, ISA, FLAGS | FL_FOR_##ISA }, |
| #include "m68k-devices.def" |
| #undef M68K_DEVICE |
| { NULL, unk_device, NULL, unk_arch, isa_max, 0 } |
| }; |
| |
| /* A list of all ISAs, mapping each one to a representative device. |
| Used for -march selection. */ |
| static const struct m68k_target_selection all_isas[] = |
| { |
| #define M68K_ISA(NAME,DEVICE,MICROARCH,ISA,FLAGS) \ |
| { NAME, DEVICE, NULL, u##MICROARCH, ISA, FLAGS }, |
| #include "m68k-isas.def" |
| #undef M68K_ISA |
| { NULL, unk_device, NULL, unk_arch, isa_max, 0 } |
| }; |
| |
| /* A list of all microarchitectures, mapping each one to a representative |
| device. Used for -mtune selection. */ |
| static const struct m68k_target_selection all_microarchs[] = |
| { |
| #define M68K_MICROARCH(NAME,DEVICE,MICROARCH,ISA,FLAGS) \ |
| { NAME, DEVICE, NULL, u##MICROARCH, ISA, FLAGS }, |
| #include "m68k-microarchs.def" |
| #undef M68K_MICROARCH |
| { NULL, unk_device, NULL, unk_arch, isa_max, 0 } |
| }; |
| |
| /* The entries associated with the -mcpu, -march and -mtune settings, |
| or null for options that have not been used. */ |
| const struct m68k_target_selection *m68k_cpu_entry; |
| const struct m68k_target_selection *m68k_arch_entry; |
| const struct m68k_target_selection *m68k_tune_entry; |
| |
| /* Which CPU we are generating code for. */ |
| enum target_device m68k_cpu; |
| |
| /* Which microarchitecture to tune for. */ |
| enum uarch_type m68k_tune; |
| |
| /* Which FPU to use. */ |
| enum fpu_type m68k_fpu; |
| |
| /* The set of FL_* flags that apply to the target processor. */ |
| unsigned int m68k_cpu_flags; |
| |
| /* The set of FL_* flags that apply to the processor to be tuned for. */ |
| unsigned int m68k_tune_flags; |
| |
| /* Asm templates for calling or jumping to an arbitrary symbolic address, |
| or NULL if such calls or jumps are not supported. The address is held |
| in operand 0. */ |
| const char *m68k_symbolic_call; |
| const char *m68k_symbolic_jump; |
| |
| /* Enum variable that corresponds to m68k_symbolic_call values. */ |
| enum M68K_SYMBOLIC_CALL m68k_symbolic_call_var; |
| |
| |
| /* Implement TARGET_OPTION_OVERRIDE. */ |
| |
| static void |
| m68k_option_override (void) |
| { |
| const struct m68k_target_selection *entry; |
| unsigned long target_mask; |
| |
| if (OPTION_SET_P (m68k_arch_option)) |
| m68k_arch_entry = &all_isas[m68k_arch_option]; |
| |
| if (OPTION_SET_P (m68k_cpu_option)) |
| m68k_cpu_entry = &all_devices[(int) m68k_cpu_option]; |
| |
| if (OPTION_SET_P (m68k_tune_option)) |
| m68k_tune_entry = &all_microarchs[(int) m68k_tune_option]; |
| |
| /* User can choose: |
| |
| -mcpu= |
| -march= |
| -mtune= |
| |
| -march=ARCH should generate code that runs any processor |
| implementing architecture ARCH. -mcpu=CPU should override -march |
| and should generate code that runs on processor CPU, making free |
| use of any instructions that CPU understands. -mtune=UARCH applies |
| on top of -mcpu or -march and optimizes the code for UARCH. It does |
| not change the target architecture. */ |
| if (m68k_cpu_entry) |
| { |
| /* Complain if the -march setting is for a different microarchitecture, |
| or includes flags that the -mcpu setting doesn't. */ |
| if (m68k_arch_entry |
| && (m68k_arch_entry->microarch != m68k_cpu_entry->microarch |
| || (m68k_arch_entry->flags & ~m68k_cpu_entry->flags) != 0)) |
| warning (0, "%<-mcpu=%s%> conflicts with %<-march=%s%>", |
| m68k_cpu_entry->name, m68k_arch_entry->name); |
| |
| entry = m68k_cpu_entry; |
| } |
| else |
| entry = m68k_arch_entry; |
| |
| if (!entry) |
| entry = all_devices + TARGET_CPU_DEFAULT; |
| |
| m68k_cpu_flags = entry->flags; |
| |
| /* Use the architecture setting to derive default values for |
| certain flags. */ |
| target_mask = 0; |
| |
| /* ColdFire is lenient about alignment. */ |
| if (!TARGET_COLDFIRE) |
| target_mask |= MASK_STRICT_ALIGNMENT; |
| |
| if ((m68k_cpu_flags & FL_BITFIELD) != 0) |
| target_mask |= MASK_BITFIELD; |
| if ((m68k_cpu_flags & FL_CF_HWDIV) != 0) |
| target_mask |= MASK_CF_HWDIV; |
| if ((m68k_cpu_flags & (FL_68881 | FL_CF_FPU)) != 0) |
| target_mask |= MASK_HARD_FLOAT; |
| target_flags |= target_mask & ~target_flags_explicit; |
| |
| /* Set the directly-usable versions of the -mcpu and -mtune settings. */ |
| m68k_cpu = entry->device; |
| if (m68k_tune_entry) |
| { |
| m68k_tune = m68k_tune_entry->microarch; |
| m68k_tune_flags = m68k_tune_entry->flags; |
| } |
| #ifdef M68K_DEFAULT_TUNE |
| else if (!m68k_cpu_entry && !m68k_arch_entry) |
| { |
| enum target_device dev; |
| dev = all_microarchs[M68K_DEFAULT_TUNE].device; |
| m68k_tune_flags = all_devices[dev].flags; |
| } |
| #endif |
| else |
| { |
| m68k_tune = entry->microarch; |
| m68k_tune_flags = entry->flags; |
| } |
| |
| /* Set the type of FPU. */ |
| m68k_fpu = (!TARGET_HARD_FLOAT ? FPUTYPE_NONE |
| : (m68k_cpu_flags & FL_COLDFIRE) != 0 ? FPUTYPE_COLDFIRE |
| : FPUTYPE_68881); |
| |
| /* Sanity check to ensure that msep-data and mid-sahred-library are not |
| * both specified together. Doing so simply doesn't make sense. |
| */ |
| if (TARGET_SEP_DATA && TARGET_ID_SHARED_LIBRARY) |
| error ("cannot specify both %<-msep-data%> and %<-mid-shared-library%>"); |
| |
| /* If we're generating code for a separate A5 relative data segment, |
| * we've got to enable -fPIC as well. This might be relaxable to |
| * -fpic but it hasn't been tested properly. |
| */ |
| if (TARGET_SEP_DATA || TARGET_ID_SHARED_LIBRARY) |
| flag_pic = 2; |
| |
| /* -mpcrel -fPIC uses 32-bit pc-relative displacements. Raise an |
| error if the target does not support them. */ |
| if (TARGET_PCREL && !TARGET_68020 && flag_pic == 2) |
| error ("%<-mpcrel%> %<-fPIC%> is not currently supported on selected cpu"); |
| |
| /* ??? A historic way of turning on pic, or is this intended to |
| be an embedded thing that doesn't have the same name binding |
| significance that it does on hosted ELF systems? */ |
| if (TARGET_PCREL && flag_pic == 0) |
| flag_pic = 1; |
| |
| if (!flag_pic) |
| { |
| m68k_symbolic_call_var = M68K_SYMBOLIC_CALL_JSR; |
| |
| m68k_symbolic_jump = "jra %a0"; |
| } |
| else if (TARGET_ID_SHARED_LIBRARY) |
| /* All addresses must be loaded from the GOT. */ |
| ; |
| else if (TARGET_68020 || TARGET_ISAB || TARGET_ISAC) |
| { |
| if (TARGET_PCREL) |
| m68k_symbolic_call_var = M68K_SYMBOLIC_CALL_BSR_C; |
| else |
| m68k_symbolic_call_var = M68K_SYMBOLIC_CALL_BSR_P; |
| |
| if (TARGET_ISAC) |
| /* No unconditional long branch */; |
| else if (TARGET_PCREL) |
| m68k_symbolic_jump = "bra%.l %c0"; |
| else |
| m68k_symbolic_jump = "bra%.l %p0"; |
| /* Turn off function cse if we are doing PIC. We always want |
| function call to be done as `bsr foo@PLTPC'. */ |
| /* ??? It's traditional to do this for -mpcrel too, but it isn't |
| clear how intentional that is. */ |
| flag_no_function_cse = 1; |
| } |
| |
| switch (m68k_symbolic_call_var) |
| { |
| case M68K_SYMBOLIC_CALL_JSR: |
| m68k_symbolic_call = "jsr %a0"; |
| break; |
| |
| case M68K_SYMBOLIC_CALL_BSR_C: |
| m68k_symbolic_call = "bsr%.l %c0"; |
| break; |
| |
| case M68K_SYMBOLIC_CALL_BSR_P: |
| m68k_symbolic_call = "bsr%.l %p0"; |
| break; |
| |
| case M68K_SYMBOLIC_CALL_NONE: |
| gcc_assert (m68k_symbolic_call == NULL); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| #ifndef ASM_OUTPUT_ALIGN_WITH_NOP |
| parse_alignment_opts (); |
| int label_alignment = align_labels.levels[0].get_value (); |
| if (label_alignment > 2) |
| { |
| warning (0, "%<-falign-labels=%d%> is not supported", label_alignment); |
| str_align_labels = "1"; |
| } |
| |
| int loop_alignment = align_loops.levels[0].get_value (); |
| if (loop_alignment > 2) |
| { |
| warning (0, "%<-falign-loops=%d%> is not supported", loop_alignment); |
| str_align_loops = "1"; |
| } |
| #endif |
| |
| if ((opt_fstack_limit_symbol_arg != NULL || opt_fstack_limit_register_no >= 0) |
| && !TARGET_68020) |
| { |
| warning (0, "%<-fstack-limit-%> options are not supported on this cpu"); |
| opt_fstack_limit_symbol_arg = NULL; |
| opt_fstack_limit_register_no = -1; |
| } |
| |
| SUBTARGET_OVERRIDE_OPTIONS; |
| |
| /* Setup scheduling options. */ |
| if (TUNE_CFV1) |
| m68k_sched_cpu = CPU_CFV1; |
| else if (TUNE_CFV2) |
| m68k_sched_cpu = CPU_CFV2; |
| else if (TUNE_CFV3) |
| m68k_sched_cpu = CPU_CFV3; |
| else if (TUNE_CFV4) |
| m68k_sched_cpu = CPU_CFV4; |
| else |
| { |
| m68k_sched_cpu = CPU_UNKNOWN; |
| flag_schedule_insns = 0; |
| flag_schedule_insns_after_reload = 0; |
| flag_modulo_sched = 0; |
| flag_live_range_shrinkage = 0; |
| } |
| |
| if (m68k_sched_cpu != CPU_UNKNOWN) |
| { |
| if ((m68k_cpu_flags & (FL_CF_EMAC | FL_CF_EMAC_B)) != 0) |
| m68k_sched_mac = MAC_CF_EMAC; |
| else if ((m68k_cpu_flags & FL_CF_MAC) != 0) |
| m68k_sched_mac = MAC_CF_MAC; |
| else |
| m68k_sched_mac = MAC_NO; |
| } |
| } |
| |
| /* Implement TARGET_OVERRIDE_OPTIONS_AFTER_CHANGE. */ |
| |
| static void |
| m68k_override_options_after_change (void) |
| { |
| if (m68k_sched_cpu == CPU_UNKNOWN) |
| { |
| flag_schedule_insns = 0; |
| flag_schedule_insns_after_reload = 0; |
| flag_modulo_sched = 0; |
| flag_live_range_shrinkage = 0; |
| } |
| } |
| |
| /* Generate a macro of the form __mPREFIX_cpu_NAME, where PREFIX is the |
| given argument and NAME is the argument passed to -mcpu. Return NULL |
| if -mcpu was not passed. */ |
| |
| const char * |
| m68k_cpp_cpu_ident (const char *prefix) |
| { |
| if (!m68k_cpu_entry) |
| return NULL; |
| return concat ("__m", prefix, "_cpu_", m68k_cpu_entry->name, NULL); |
| } |
| |
| /* Generate a macro of the form __mPREFIX_family_NAME, where PREFIX is the |
| given argument and NAME is the name of the representative device for |
| the -mcpu argument's family. Return NULL if -mcpu was not passed. */ |
| |
| const char * |
| m68k_cpp_cpu_family (const char *prefix) |
| { |
| if (!m68k_cpu_entry) |
| return NULL; |
| return concat ("__m", prefix, "_family_", m68k_cpu_entry->family, NULL); |
| } |
| |
| /* Return m68k_fk_interrupt_handler if FUNC has an "interrupt" or |
| "interrupt_handler" attribute and interrupt_thread if FUNC has an |
| "interrupt_thread" attribute. Otherwise, return |
| m68k_fk_normal_function. */ |
| |
| enum m68k_function_kind |
| m68k_get_function_kind (tree func) |
| { |
| tree a; |
| |
| gcc_assert (TREE_CODE (func) == FUNCTION_DECL); |
| |
| a = lookup_attribute ("interrupt", DECL_ATTRIBUTES (func)); |
| if (a != NULL_TREE) |
| return m68k_fk_interrupt_handler; |
| |
| a = lookup_attribute ("interrupt_handler", DECL_ATTRIBUTES (func)); |
| if (a != NULL_TREE) |
| return m68k_fk_interrupt_handler; |
| |
| a = lookup_attribute ("interrupt_thread", DECL_ATTRIBUTES (func)); |
| if (a != NULL_TREE) |
| return m68k_fk_interrupt_thread; |
| |
| return m68k_fk_normal_function; |
| } |
| |
| /* Handle an attribute requiring a FUNCTION_DECL; arguments as in |
| struct attribute_spec.handler. */ |
| static tree |
| m68k_handle_fndecl_attribute (tree *node, tree name, |
| tree args ATTRIBUTE_UNUSED, |
| int flags ATTRIBUTE_UNUSED, |
| bool *no_add_attrs) |
| { |
| if (TREE_CODE (*node) != FUNCTION_DECL) |
| { |
| warning (OPT_Wattributes, "%qE attribute only applies to functions", |
| name); |
| *no_add_attrs = true; |
| } |
| |
| if (m68k_get_function_kind (*node) != m68k_fk_normal_function) |
| { |
| error ("multiple interrupt attributes not allowed"); |
| *no_add_attrs = true; |
| } |
| |
| if (!TARGET_FIDOA |
| && !strcmp (IDENTIFIER_POINTER (name), "interrupt_thread")) |
| { |
| error ("%<interrupt_thread%> is available only on fido"); |
| *no_add_attrs = true; |
| } |
| |
| return NULL_TREE; |
| } |
| |
| static void |
| m68k_compute_frame_layout (void) |
| { |
| int regno, saved; |
| unsigned int mask; |
| enum m68k_function_kind func_kind = |
| m68k_get_function_kind (current_function_decl); |
| bool interrupt_handler = func_kind == m68k_fk_interrupt_handler; |
| bool interrupt_thread = func_kind == m68k_fk_interrupt_thread; |
| |
| /* Only compute the frame once per function. |
| Don't cache information until reload has been completed. */ |
| if (current_frame.funcdef_no == current_function_funcdef_no |
| && reload_completed) |
| return; |
| |
| current_frame.size = (get_frame_size () + 3) & -4; |
| |
| mask = saved = 0; |
| |
| /* Interrupt thread does not need to save any register. */ |
| if (!interrupt_thread) |
| for (regno = 0; regno < 16; regno++) |
| if (m68k_save_reg (regno, interrupt_handler)) |
| { |
| mask |= 1 << (regno - D0_REG); |
| saved++; |
| } |
| current_frame.offset = saved * 4; |
| current_frame.reg_no = saved; |
| current_frame.reg_mask = mask; |
| |
| current_frame.foffset = 0; |
| mask = saved = 0; |
| if (TARGET_HARD_FLOAT) |
| { |
| /* Interrupt thread does not need to save any register. */ |
| if (!interrupt_thread) |
| for (regno = 16; regno < 24; regno++) |
| if (m68k_save_reg (regno, interrupt_handler)) |
| { |
| mask |= 1 << (regno - FP0_REG); |
| saved++; |
| } |
| current_frame.foffset = saved * TARGET_FP_REG_SIZE; |
| current_frame.offset += current_frame.foffset; |
| } |
| current_frame.fpu_no = saved; |
| current_frame.fpu_mask = mask; |
| |
| /* Remember what function this frame refers to. */ |
| current_frame.funcdef_no = current_function_funcdef_no; |
| } |
| |
| /* Worker function for TARGET_CAN_ELIMINATE. */ |
| |
| bool |
| m68k_can_eliminate (const int from ATTRIBUTE_UNUSED, const int to) |
| { |
| return (to == STACK_POINTER_REGNUM ? ! frame_pointer_needed : true); |
| } |
| |
| HOST_WIDE_INT |
| m68k_initial_elimination_offset (int from, int to) |
| { |
| int argptr_offset; |
| /* The arg pointer points 8 bytes before the start of the arguments, |
| as defined by FIRST_PARM_OFFSET. This makes it coincident with the |
| frame pointer in most frames. */ |
| argptr_offset = frame_pointer_needed ? 0 : UNITS_PER_WORD; |
| if (from == ARG_POINTER_REGNUM && to == FRAME_POINTER_REGNUM) |
| return argptr_offset; |
| |
| m68k_compute_frame_layout (); |
| |
| gcc_assert (to == STACK_POINTER_REGNUM); |
| switch (from) |
| { |
| case ARG_POINTER_REGNUM: |
| return current_frame.offset + current_frame.size - argptr_offset; |
| case FRAME_POINTER_REGNUM: |
| return current_frame.offset + current_frame.size; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Refer to the array `regs_ever_live' to determine which registers |
| to save; `regs_ever_live[I]' is nonzero if register number I |
| is ever used in the function. This function is responsible for |
| knowing which registers should not be saved even if used. |
| Return true if we need to save REGNO. */ |
| |
| static bool |
| m68k_save_reg (unsigned int regno, bool interrupt_handler) |
| { |
| if (flag_pic && regno == PIC_REG) |
| { |
| if (crtl->saves_all_registers) |
| return true; |
| if (crtl->uses_pic_offset_table) |
| return true; |
| /* Reload may introduce constant pool references into a function |
| that thitherto didn't need a PIC register. Note that the test |
| above will not catch that case because we will only set |
| crtl->uses_pic_offset_table when emitting |
| the address reloads. */ |
| if (crtl->uses_const_pool) |
| return true; |
| } |
| |
| if (crtl->calls_eh_return) |
| { |
| unsigned int i; |
| for (i = 0; ; i++) |
| { |
| unsigned int test = EH_RETURN_DATA_REGNO (i); |
| if (test == INVALID_REGNUM) |
| break; |
| if (test == regno) |
| return true; |
| } |
| } |
| |
| /* Fixed regs we never touch. */ |
| if (fixed_regs[regno]) |
| return false; |
| |
| /* The frame pointer (if it is such) is handled specially. */ |
| if (regno == FRAME_POINTER_REGNUM && frame_pointer_needed) |
| return false; |
| |
| /* Interrupt handlers must also save call_used_regs |
| if they are live or when calling nested functions. */ |
| if (interrupt_handler) |
| { |
| if (df_regs_ever_live_p (regno)) |
| return true; |
| |
| if (!crtl->is_leaf && call_used_or_fixed_reg_p (regno)) |
| return true; |
| } |
| |
| /* Never need to save registers that aren't touched. */ |
| if (!df_regs_ever_live_p (regno)) |
| return false; |
| |
| /* Otherwise save everything that isn't call-clobbered. */ |
| return !call_used_or_fixed_reg_p (regno); |
| } |
| |
| /* Emit RTL for a MOVEM or FMOVEM instruction. BASE + OFFSET represents |
| the lowest memory address. COUNT is the number of registers to be |
| moved, with register REGNO + I being moved if bit I of MASK is set. |
| STORE_P specifies the direction of the move and ADJUST_STACK_P says |
| whether or not this is pre-decrement (if STORE_P) or post-increment |
| (if !STORE_P) operation. */ |
| |
| static rtx_insn * |
| m68k_emit_movem (rtx base, HOST_WIDE_INT offset, |
| unsigned int count, unsigned int regno, |
| unsigned int mask, bool store_p, bool adjust_stack_p) |
| { |
| int i; |
| rtx body, addr, src, operands[2]; |
| machine_mode mode; |
| |
| body = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (adjust_stack_p + count)); |
| mode = reg_raw_mode[regno]; |
| i = 0; |
| |
| if (adjust_stack_p) |
| { |
| src = plus_constant (Pmode, base, |
| (count |
| * GET_MODE_SIZE (mode) |
| * (HOST_WIDE_INT) (store_p ? -1 : 1))); |
| XVECEXP (body, 0, i++) = gen_rtx_SET (base, src); |
| } |
| |
| for (; mask != 0; mask >>= 1, regno++) |
| if (mask & 1) |
| { |
| addr = plus_constant (Pmode, base, offset); |
| operands[!store_p] = gen_frame_mem (mode, addr); |
| operands[store_p] = gen_rtx_REG (mode, regno); |
| XVECEXP (body, 0, i++) |
| = gen_rtx_SET (operands[0], operands[1]); |
| offset += GET_MODE_SIZE (mode); |
| } |
| gcc_assert (i == XVECLEN (body, 0)); |
| |
| return emit_insn (body); |
| } |
| |
| /* Make INSN a frame-related instruction. */ |
| |
| static void |
| m68k_set_frame_related (rtx_insn *insn) |
| { |
| rtx body; |
| int i; |
| |
| RTX_FRAME_RELATED_P (insn) = 1; |
| body = PATTERN (insn); |
| if (GET_CODE (body) == PARALLEL) |
| for (i = 0; i < XVECLEN (body, 0); i++) |
| RTX_FRAME_RELATED_P (XVECEXP (body, 0, i)) = 1; |
| } |
| |
| /* Emit RTL for the "prologue" define_expand. */ |
| |
| void |
| m68k_expand_prologue (void) |
| { |
| HOST_WIDE_INT fsize_with_regs; |
| rtx limit, src, dest; |
| |
| m68k_compute_frame_layout (); |
| |
| if (flag_stack_usage_info) |
| current_function_static_stack_size |
| = current_frame.size + current_frame.offset; |
| |
| /* If the stack limit is a symbol, we can check it here, |
| before actually allocating the space. */ |
| if (crtl->limit_stack |
| && GET_CODE (stack_limit_rtx) == SYMBOL_REF) |
| { |
| limit = plus_constant (Pmode, stack_limit_rtx, current_frame.size + 4); |
| if (!m68k_legitimate_constant_p (Pmode, limit)) |
| { |
| emit_move_insn (gen_rtx_REG (Pmode, D0_REG), limit); |
| limit = gen_rtx_REG (Pmode, D0_REG); |
| } |
| emit_insn (gen_ctrapsi4 (gen_rtx_LTU (VOIDmode, |
| stack_pointer_rtx, limit), |
| stack_pointer_rtx, limit, |
| const1_rtx)); |
| } |
| |
| fsize_with_regs = current_frame.size; |
| if (TARGET_COLDFIRE) |
| { |
| /* ColdFire's move multiple instructions do not allow pre-decrement |
| addressing. Add the size of movem saves to the initial stack |
| allocation instead. */ |
| if (current_frame.reg_no >= MIN_MOVEM_REGS) |
| fsize_with_regs += current_frame.reg_no * GET_MODE_SIZE (SImode); |
| if (current_frame.fpu_no >= MIN_FMOVEM_REGS) |
| fsize_with_regs += current_frame.fpu_no * GET_MODE_SIZE (DFmode); |
| } |
| |
| if (frame_pointer_needed) |
| { |
| if (fsize_with_regs == 0 && TUNE_68040) |
| { |
| /* On the 68040, two separate moves are faster than link.w 0. */ |
| dest = gen_frame_mem (Pmode, |
| gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx)); |
| m68k_set_frame_related (emit_move_insn (dest, frame_pointer_rtx)); |
| m68k_set_frame_related (emit_move_insn (frame_pointer_rtx, |
| stack_pointer_rtx)); |
| } |
| else if (fsize_with_regs < 0x8000 || TARGET_68020) |
| m68k_set_frame_related |
| (emit_insn (gen_link (frame_pointer_rtx, |
| GEN_INT (-4 - fsize_with_regs)))); |
| else |
| { |
| m68k_set_frame_related |
| (emit_insn (gen_link (frame_pointer_rtx, GEN_INT (-4)))); |
| m68k_set_frame_related |
| (emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-fsize_with_regs)))); |
| } |
| |
| /* If the frame pointer is needed, emit a special barrier that |
| will prevent the scheduler from moving stores to the frame |
| before the stack adjustment. */ |
| emit_insn (gen_stack_tie (stack_pointer_rtx, frame_pointer_rtx)); |
| } |
| else if (fsize_with_regs != 0) |
| m68k_set_frame_related |
| (emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (-fsize_with_regs)))); |
| |
| if (current_frame.fpu_mask) |
| { |
| gcc_assert (current_frame.fpu_no >= MIN_FMOVEM_REGS); |
| if (TARGET_68881) |
| m68k_set_frame_related |
| (m68k_emit_movem (stack_pointer_rtx, |
| current_frame.fpu_no * -GET_MODE_SIZE (XFmode), |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, true, true)); |
| else |
| { |
| int offset; |
| |
| /* If we're using moveml to save the integer registers, |
| the stack pointer will point to the bottom of the moveml |
| save area. Find the stack offset of the first FP register. */ |
| if (current_frame.reg_no < MIN_MOVEM_REGS) |
| offset = 0; |
| else |
| offset = current_frame.reg_no * GET_MODE_SIZE (SImode); |
| m68k_set_frame_related |
| (m68k_emit_movem (stack_pointer_rtx, offset, |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, true, false)); |
| } |
| } |
| |
| /* If the stack limit is not a symbol, check it here. |
| This has the disadvantage that it may be too late... */ |
| if (crtl->limit_stack) |
| { |
| if (REG_P (stack_limit_rtx)) |
| emit_insn (gen_ctrapsi4 (gen_rtx_LTU (VOIDmode, stack_pointer_rtx, |
| stack_limit_rtx), |
| stack_pointer_rtx, stack_limit_rtx, |
| const1_rtx)); |
| |
| else if (GET_CODE (stack_limit_rtx) != SYMBOL_REF) |
| warning (0, "stack limit expression is not supported"); |
| } |
| |
| if (current_frame.reg_no < MIN_MOVEM_REGS) |
| { |
| /* Store each register separately in the same order moveml does. */ |
| int i; |
| |
| for (i = 16; i-- > 0; ) |
| if (current_frame.reg_mask & (1 << i)) |
| { |
| src = gen_rtx_REG (SImode, D0_REG + i); |
| dest = gen_frame_mem (SImode, |
| gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx)); |
| m68k_set_frame_related (emit_insn (gen_movsi (dest, src))); |
| } |
| } |
| else |
| { |
| if (TARGET_COLDFIRE) |
| /* The required register save space has already been allocated. |
| The first register should be stored at (%sp). */ |
| m68k_set_frame_related |
| (m68k_emit_movem (stack_pointer_rtx, 0, |
| current_frame.reg_no, D0_REG, |
| current_frame.reg_mask, true, false)); |
| else |
| m68k_set_frame_related |
| (m68k_emit_movem (stack_pointer_rtx, |
| current_frame.reg_no * -GET_MODE_SIZE (SImode), |
| current_frame.reg_no, D0_REG, |
| current_frame.reg_mask, true, true)); |
| } |
| |
| if (!TARGET_SEP_DATA |
| && crtl->uses_pic_offset_table) |
| emit_insn (gen_load_got (pic_offset_table_rtx)); |
| } |
| |
| /* Return true if a simple (return) instruction is sufficient for this |
| instruction (i.e. if no epilogue is needed). */ |
| |
| bool |
| m68k_use_return_insn (void) |
| { |
| if (!reload_completed || frame_pointer_needed || get_frame_size () != 0) |
| return false; |
| |
| m68k_compute_frame_layout (); |
| return current_frame.offset == 0; |
| } |
| |
| /* Emit RTL for the "epilogue" or "sibcall_epilogue" define_expand; |
| SIBCALL_P says which. |
| |
| The function epilogue should not depend on the current stack pointer! |
| It should use the frame pointer only, if there is a frame pointer. |
| This is mandatory because of alloca; we also take advantage of it to |
| omit stack adjustments before returning. */ |
| |
| void |
| m68k_expand_epilogue (bool sibcall_p) |
| { |
| HOST_WIDE_INT fsize, fsize_with_regs; |
| bool big, restore_from_sp; |
| |
| m68k_compute_frame_layout (); |
| |
| fsize = current_frame.size; |
| big = false; |
| restore_from_sp = false; |
| |
| /* FIXME : crtl->is_leaf below is too strong. |
| What we really need to know there is if there could be pending |
| stack adjustment needed at that point. */ |
| restore_from_sp = (!frame_pointer_needed |
| || (!cfun->calls_alloca && crtl->is_leaf)); |
| |
| /* fsize_with_regs is the size we need to adjust the sp when |
| popping the frame. */ |
| fsize_with_regs = fsize; |
| if (TARGET_COLDFIRE && restore_from_sp) |
| { |
| /* ColdFire's move multiple instructions do not allow post-increment |
| addressing. Add the size of movem loads to the final deallocation |
| instead. */ |
| if (current_frame.reg_no >= MIN_MOVEM_REGS) |
| fsize_with_regs += current_frame.reg_no * GET_MODE_SIZE (SImode); |
| if (current_frame.fpu_no >= MIN_FMOVEM_REGS) |
| fsize_with_regs += current_frame.fpu_no * GET_MODE_SIZE (DFmode); |
| } |
| |
| if (current_frame.offset + fsize >= 0x8000 |
| && !restore_from_sp |
| && (current_frame.reg_mask || current_frame.fpu_mask)) |
| { |
| if (TARGET_COLDFIRE |
| && (current_frame.reg_no >= MIN_MOVEM_REGS |
| || current_frame.fpu_no >= MIN_FMOVEM_REGS)) |
| { |
| /* ColdFire's move multiple instructions do not support the |
| (d8,Ax,Xi) addressing mode, so we're as well using a normal |
| stack-based restore. */ |
| emit_move_insn (gen_rtx_REG (Pmode, A1_REG), |
| GEN_INT (-(current_frame.offset + fsize))); |
| emit_insn (gen_blockage ()); |
| emit_insn (gen_addsi3 (stack_pointer_rtx, |
| gen_rtx_REG (Pmode, A1_REG), |
| frame_pointer_rtx)); |
| restore_from_sp = true; |
| } |
| else |
| { |
| emit_move_insn (gen_rtx_REG (Pmode, A1_REG), GEN_INT (-fsize)); |
| fsize = 0; |
| big = true; |
| } |
| } |
| |
| if (current_frame.reg_no < MIN_MOVEM_REGS) |
| { |
| /* Restore each register separately in the same order moveml does. */ |
| int i; |
| HOST_WIDE_INT offset; |
| |
| offset = current_frame.offset + fsize; |
| for (i = 0; i < 16; i++) |
| if (current_frame.reg_mask & (1 << i)) |
| { |
| rtx addr; |
| |
| if (big) |
| { |
| /* Generate the address -OFFSET(%fp,%a1.l). */ |
| addr = gen_rtx_REG (Pmode, A1_REG); |
| addr = gen_rtx_PLUS (Pmode, addr, frame_pointer_rtx); |
| addr = plus_constant (Pmode, addr, -offset); |
| } |
| else if (restore_from_sp) |
| addr = gen_rtx_POST_INC (Pmode, stack_pointer_rtx); |
| else |
| addr = plus_constant (Pmode, frame_pointer_rtx, -offset); |
| emit_move_insn (gen_rtx_REG (SImode, D0_REG + i), |
| gen_frame_mem (SImode, addr)); |
| offset -= GET_MODE_SIZE (SImode); |
| } |
| } |
| else if (current_frame.reg_mask) |
| { |
| if (big) |
| m68k_emit_movem (gen_rtx_PLUS (Pmode, |
| gen_rtx_REG (Pmode, A1_REG), |
| frame_pointer_rtx), |
| -(current_frame.offset + fsize), |
| current_frame.reg_no, D0_REG, |
| current_frame.reg_mask, false, false); |
| else if (restore_from_sp) |
| m68k_emit_movem (stack_pointer_rtx, 0, |
| current_frame.reg_no, D0_REG, |
| current_frame.reg_mask, false, |
| !TARGET_COLDFIRE); |
| else |
| m68k_emit_movem (frame_pointer_rtx, |
| -(current_frame.offset + fsize), |
| current_frame.reg_no, D0_REG, |
| current_frame.reg_mask, false, false); |
| } |
| |
| if (current_frame.fpu_no > 0) |
| { |
| if (big) |
| m68k_emit_movem (gen_rtx_PLUS (Pmode, |
| gen_rtx_REG (Pmode, A1_REG), |
| frame_pointer_rtx), |
| -(current_frame.foffset + fsize), |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, false, false); |
| else if (restore_from_sp) |
| { |
| if (TARGET_COLDFIRE) |
| { |
| int offset; |
| |
| /* If we used moveml to restore the integer registers, the |
| stack pointer will still point to the bottom of the moveml |
| save area. Find the stack offset of the first FP |
| register. */ |
| if (current_frame.reg_no < MIN_MOVEM_REGS) |
| offset = 0; |
| else |
| offset = current_frame.reg_no * GET_MODE_SIZE (SImode); |
| m68k_emit_movem (stack_pointer_rtx, offset, |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, false, false); |
| } |
| else |
| m68k_emit_movem (stack_pointer_rtx, 0, |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, false, true); |
| } |
| else |
| m68k_emit_movem (frame_pointer_rtx, |
| -(current_frame.foffset + fsize), |
| current_frame.fpu_no, FP0_REG, |
| current_frame.fpu_mask, false, false); |
| } |
| |
| emit_insn (gen_blockage ()); |
| if (frame_pointer_needed) |
| emit_insn (gen_unlink (frame_pointer_rtx)); |
| else if (fsize_with_regs) |
| emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| GEN_INT (fsize_with_regs))); |
| |
| if (crtl->calls_eh_return) |
| emit_insn (gen_addsi3 (stack_pointer_rtx, |
| stack_pointer_rtx, |
| EH_RETURN_STACKADJ_RTX)); |
| |
| if (!sibcall_p) |
| emit_jump_insn (ret_rtx); |
| } |
| |
| /* Return true if PARALLEL contains register REGNO. */ |
| static bool |
| m68k_reg_present_p (const_rtx parallel, unsigned int regno) |
| { |
| int i; |
| |
| if (REG_P (parallel) && REGNO (parallel) == regno) |
| return true; |
| |
| if (GET_CODE (parallel) != PARALLEL) |
| return false; |
| |
| for (i = 0; i < XVECLEN (parallel, 0); ++i) |
| { |
| const_rtx x; |
| |
| x = XEXP (XVECEXP (parallel, 0, i), 0); |
| if (REG_P (x) && REGNO (x) == regno) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Implement TARGET_FUNCTION_OK_FOR_SIBCALL_P. */ |
| |
| static bool |
| m68k_ok_for_sibcall_p (tree decl, tree exp) |
| { |
| enum m68k_function_kind kind; |
| |
| /* We cannot use sibcalls for nested functions because we use the |
| static chain register for indirect calls. */ |
| if (CALL_EXPR_STATIC_CHAIN (exp)) |
| return false; |
| |
| if (!VOID_TYPE_P (TREE_TYPE (DECL_RESULT (cfun->decl)))) |
| { |
| /* Check that the return value locations are the same. For |
| example that we aren't returning a value from the sibling in |
| a D0 register but then need to transfer it to a A0 register. */ |
| rtx cfun_value; |
| rtx call_value; |
| |
| cfun_value = FUNCTION_VALUE (TREE_TYPE (DECL_RESULT (cfun->decl)), |
| cfun->decl); |
| call_value = FUNCTION_VALUE (TREE_TYPE (exp), decl); |
| |
| /* Check that the values are equal or that the result the callee |
| function returns is superset of what the current function returns. */ |
| if (!(rtx_equal_p (cfun_value, call_value) |
| || (REG_P (cfun_value) |
| && m68k_reg_present_p (call_value, REGNO (cfun_value))))) |
| return false; |
| } |
| |
| kind = m68k_get_function_kind (current_function_decl); |
| if (kind == m68k_fk_normal_function) |
| /* We can always sibcall from a normal function, because it's |
| undefined if it is calling an interrupt function. */ |
| return true; |
| |
| /* Otherwise we can only sibcall if the function kind is known to be |
| the same. */ |
| if (decl && m68k_get_function_kind (decl) == kind) |
| return true; |
| |
| return false; |
| } |
| |
| /* On the m68k all args are always pushed. */ |
| |
| static rtx |
| m68k_function_arg (cumulative_args_t, const function_arg_info &) |
| { |
| return NULL_RTX; |
| } |
| |
| static void |
| m68k_function_arg_advance (cumulative_args_t cum_v, |
| const function_arg_info &arg) |
| { |
| CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v); |
| |
| *cum += (arg.promoted_size_in_bytes () + 3) & ~3; |
| } |
| |
| /* Convert X to a legitimate function call memory reference and return the |
| result. */ |
| |
| rtx |
| m68k_legitimize_call_address (rtx x) |
| { |
| gcc_assert (MEM_P (x)); |
| if (call_operand (XEXP (x, 0), VOIDmode)) |
| return x; |
| return replace_equiv_address (x, force_reg (Pmode, XEXP (x, 0))); |
| } |
| |
| /* Likewise for sibling calls. */ |
| |
| rtx |
| m68k_legitimize_sibcall_address (rtx x) |
| { |
| gcc_assert (MEM_P (x)); |
| if (sibcall_operand (XEXP (x, 0), VOIDmode)) |
| return x; |
| |
| emit_move_insn (gen_rtx_REG (Pmode, STATIC_CHAIN_REGNUM), XEXP (x, 0)); |
| return replace_equiv_address (x, gen_rtx_REG (Pmode, STATIC_CHAIN_REGNUM)); |
| } |
| |
| /* Convert X to a legitimate address and return it if successful. Otherwise |
| return X. |
| |
| For the 68000, we handle X+REG by loading X into a register R and |
| using R+REG. R will go in an address reg and indexing will be used. |
| However, if REG is a broken-out memory address or multiplication, |
| nothing needs to be done because REG can certainly go in an address reg. */ |
| |
| static rtx |
| m68k_legitimize_address (rtx x, rtx oldx, machine_mode mode) |
| { |
| if (m68k_tls_symbol_p (x)) |
| return m68k_legitimize_tls_address (x); |
| |
| if (GET_CODE (x) == PLUS) |
| { |
| int ch = (x) != (oldx); |
| int copied = 0; |
| |
| #define COPY_ONCE(Y) if (!copied) { Y = copy_rtx (Y); copied = ch = 1; } |
| |
| if (GET_CODE (XEXP (x, 0)) == MULT) |
| { |
| COPY_ONCE (x); |
| XEXP (x, 0) = force_operand (XEXP (x, 0), 0); |
| } |
| if (GET_CODE (XEXP (x, 1)) == MULT) |
| { |
| COPY_ONCE (x); |
| XEXP (x, 1) = force_operand (XEXP (x, 1), 0); |
| } |
| if (ch) |
| { |
| if (GET_CODE (XEXP (x, 1)) == REG |
| && GET_CODE (XEXP (x, 0)) == REG) |
| { |
| if (TARGET_COLDFIRE_FPU && GET_MODE_CLASS (mode) == MODE_FLOAT) |
| { |
| COPY_ONCE (x); |
| x = force_operand (x, 0); |
| } |
| return x; |
| } |
| if (memory_address_p (mode, x)) |
| return x; |
| } |
| if (GET_CODE (XEXP (x, 0)) == REG |
| || (GET_CODE (XEXP (x, 0)) == SIGN_EXTEND |
| && GET_CODE (XEXP (XEXP (x, 0), 0)) == REG |
| && GET_MODE (XEXP (XEXP (x, 0), 0)) == HImode)) |
| { |
| rtx temp = gen_reg_rtx (Pmode); |
| rtx val = force_operand (XEXP (x, 1), 0); |
| emit_move_insn (temp, val); |
| COPY_ONCE (x); |
| XEXP (x, 1) = temp; |
| if (TARGET_COLDFIRE_FPU && GET_MODE_CLASS (mode) == MODE_FLOAT |
| && GET_CODE (XEXP (x, 0)) == REG) |
| x = force_operand (x, 0); |
| } |
| else if (GET_CODE (XEXP (x, 1)) == REG |
| || (GET_CODE (XEXP (x, 1)) == SIGN_EXTEND |
| && GET_CODE (XEXP (XEXP (x, 1), 0)) == REG |
| && GET_MODE (XEXP (XEXP (x, 1), 0)) == HImode)) |
| { |
| rtx temp = gen_reg_rtx (Pmode); |
| rtx val = force_operand (XEXP (x, 0), 0); |
| emit_move_insn (temp, val); |
| COPY_ONCE (x); |
| XEXP (x, 0) = temp; |
| if (TARGET_COLDFIRE_FPU && GET_MODE_CLASS (mode) == MODE_FLOAT |
| && GET_CODE (XEXP (x, 1)) == REG) |
| x = force_operand (x, 0); |
| } |
| } |
| |
| return x; |
| } |
| |
| /* For eliding comparisons, we remember how the flags were set. |
| FLAGS_COMPARE_OP0 and FLAGS_COMPARE_OP1 are remembered for a direct |
| comparison, they take priority. FLAGS_OPERAND1 and FLAGS_OPERAND2 |
| are used in more cases, they are a fallback for comparisons against |
| zero after a move or arithmetic insn. |
| FLAGS_VALID is set to FLAGS_VALID_NO if we should not use any of |
| these values. */ |
| |
| static rtx flags_compare_op0, flags_compare_op1; |
| static rtx flags_operand1, flags_operand2; |
| static attr_flags_valid flags_valid = FLAGS_VALID_NO; |
| |
| /* Return a code other than UNKNOWN if we can elide a CODE comparison of |
| OP0 with OP1. */ |
| |
| rtx_code |
| m68k_find_flags_value (rtx op0, rtx op1, rtx_code code) |
| { |
| if (flags_compare_op0 != NULL_RTX) |
| { |
| if (rtx_equal_p (op0, flags_compare_op0) |
| && rtx_equal_p (op1, flags_compare_op1)) |
| return code; |
| if (rtx_equal_p (op0, flags_compare_op1) |
| && rtx_equal_p (op1, flags_compare_op0)) |
| return swap_condition (code); |
| return UNKNOWN; |
| } |
| |
| machine_mode mode = GET_MODE (op0); |
| if (op1 != CONST0_RTX (mode)) |
| return UNKNOWN; |
| /* Comparisons against 0 with these two should have been optimized out. */ |
| gcc_assert (code != LTU && code != GEU); |
| if (flags_valid == FLAGS_VALID_NOOV && (code == GT || code == LE)) |
| return UNKNOWN; |
| if (rtx_equal_p (flags_operand1, op0) || rtx_equal_p (flags_operand2, op0)) |
| return (FLOAT_MODE_P (mode) ? code |
| : code == GE ? PLUS : code == LT ? MINUS : code); |
| /* See if we are testing whether the high part of a DImode value is |
| positive or negative and we have the full value as a remembered |
| operand. */ |
| if (code != GE && code != LT) |
| return UNKNOWN; |
| if (mode == SImode |
| && flags_operand1 != NULL_RTX && GET_MODE (flags_operand1) == DImode |
| && REG_P (flags_operand1) && REG_P (op0) |
| && hard_regno_nregs (REGNO (flags_operand1), DImode) == 2 |
| && REGNO (flags_operand1) == REGNO (op0)) |
| return code == GE ? PLUS : MINUS; |
| if (mode == SImode |
| && flags_operand2 != NULL_RTX && GET_MODE (flags_operand2) == DImode |
| && REG_P (flags_operand2) && REG_P (op0) |
| && hard_regno_nregs (REGNO (flags_operand2), DImode) == 2 |
| && REGNO (flags_operand2) == REGNO (op0)) |
| return code == GE ? PLUS : MINUS; |
| return UNKNOWN; |
| } |
| |
| /* Called through CC_STATUS_INIT, which is invoked by final whenever a |
| label is encountered. */ |
| |
| void |
| m68k_init_cc () |
| { |
| flags_compare_op0 = flags_compare_op1 = NULL_RTX; |
| flags_operand1 = flags_operand2 = NULL_RTX; |
| flags_valid = FLAGS_VALID_NO; |
| } |
| |
| /* Update flags for a move operation with OPERANDS. Called for move |
| operations where attr_flags_valid returns "set". */ |
| |
| static void |
| handle_flags_for_move (rtx *operands) |
| { |
| flags_compare_op0 = flags_compare_op1 = NULL_RTX; |
| if (!ADDRESS_REG_P (operands[0])) |
| { |
| flags_valid = FLAGS_VALID_MOVE; |
| flags_operand1 = side_effects_p (operands[0]) ? NULL_RTX : operands[0]; |
| if (side_effects_p (operands[1]) |
| /* ??? For mem->mem moves, this can discard the source as a |
| valid compare operand. If you assume aligned moves, this |
| is unnecessary, but in theory, we could have an unaligned |
| move overwriting parts of its source. */ |
| || modified_in_p (operands[1], current_output_insn)) |
| flags_operand2 = NULL_RTX; |
| else |
| flags_operand2 = operands[1]; |
| return; |
| } |
| if (flags_operand1 != NULL_RTX |
| && modified_in_p (flags_operand1, current_output_insn)) |
| flags_operand1 = NULL_RTX; |
| if (flags_operand2 != NULL_RTX |
| && modified_in_p (flags_operand2, current_output_insn)) |
| flags_operand2 = NULL_RTX; |
| } |
| |
| /* Process INSN to remember flag operands if possible. */ |
| |
| static void |
| m68k_asm_final_postscan_insn (FILE *, rtx_insn *insn, rtx [], int) |
| { |
| enum attr_flags_valid v = get_attr_flags_valid (insn); |
| if (v == FLAGS_VALID_SET) |
| return; |
| /* Comparisons use FLAGS_VALID_SET, so we can be sure we need to clear these |
| now. */ |
| flags_compare_op0 = flags_compare_op1 = NULL_RTX; |
| |
| if (v == FLAGS_VALID_NO) |
| { |
| flags_operand1 = flags_operand2 = NULL_RTX; |
| return; |
| } |
| else if (v == FLAGS_VALID_UNCHANGED) |
| { |
| if (flags_operand1 != NULL_RTX && modified_in_p (flags_operand1, insn)) |
| flags_operand1 = NULL_RTX; |
| if (flags_operand2 != NULL_RTX && modified_in_p (flags_operand2, insn)) |
| flags_operand2 = NULL_RTX; |
| return; |
| } |
| |
| flags_valid = v; |
| rtx set = single_set (insn); |
| rtx dest = SET_DEST (set); |
| rtx src = SET_SRC (set); |
| if (side_effects_p (dest)) |
| dest = NULL_RTX; |
| |
| switch (v) |
| { |
| case FLAGS_VALID_YES: |
| case FLAGS_VALID_NOOV: |
| flags_operand1 = dest; |
| flags_operand2 = NULL_RTX; |
| break; |
| case FLAGS_VALID_MOVE: |
| /* fmoves to memory or data registers do not set the condition |
| codes. Normal moves _do_ set the condition codes, but not in |
| a way that is appropriate for comparison with 0, because -0.0 |
| would be treated as a negative nonzero number. Note that it |
| isn't appropriate to conditionalize this restriction on |
| HONOR_SIGNED_ZEROS because that macro merely indicates whether |
| we care about the difference between -0.0 and +0.0. */ |
| if (dest != NULL_RTX |
| && !FP_REG_P (dest) |
| && (FP_REG_P (src) |
| || GET_CODE (src) == FIX |
| || FLOAT_MODE_P (GET_MODE (dest)))) |
| flags_operand1 = flags_operand2 = NULL_RTX; |
| else |
| { |
| flags_operand1 = dest; |
| if (GET_MODE (src) != VOIDmode && !side_effects_p (src) |
| && !modified_in_p (src, insn)) |
| flags_operand2 = src; |
| else |
| flags_operand2 = NULL_RTX; |
| } |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| return; |
| } |
| |
| /* Output a dbCC; jCC sequence. Note we do not handle the |
| floating point version of this sequence (Fdbcc). |
| OPERANDS are as in the two peepholes. CODE is the code |
| returned by m68k_output_branch_<mode>. */ |
| |
| void |
| output_dbcc_and_branch (rtx *operands, rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| output_asm_insn ("dbeq %0,%l1\n\tjeq %l2", operands); |
| break; |
| |
| case NE: |
| output_asm_insn ("dbne %0,%l1\n\tjne %l2", operands); |
| break; |
| |
| case GT: |
| output_asm_insn ("dbgt %0,%l1\n\tjgt %l2", operands); |
| break; |
| |
| case GTU: |
| output_asm_insn ("dbhi %0,%l1\n\tjhi %l2", operands); |
| break; |
| |
| case LT: |
| output_asm_insn ("dblt %0,%l1\n\tjlt %l2", operands); |
| break; |
| |
| case LTU: |
| output_asm_insn ("dbcs %0,%l1\n\tjcs %l2", operands); |
| break; |
| |
| case GE: |
| output_asm_insn ("dbge %0,%l1\n\tjge %l2", operands); |
| break; |
| |
| case GEU: |
| output_asm_insn ("dbcc %0,%l1\n\tjcc %l2", operands); |
| break; |
| |
| case LE: |
| output_asm_insn ("dble %0,%l1\n\tjle %l2", operands); |
| break; |
| |
| case LEU: |
| output_asm_insn ("dbls %0,%l1\n\tjls %l2", operands); |
| break; |
| |
| case PLUS: |
| output_asm_insn ("dbpl %0,%l1\n\tjle %l2", operands); |
| break; |
| |
| case MINUS: |
| output_asm_insn ("dbmi %0,%l1\n\tjle %l2", operands); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| /* If the decrement is to be done in SImode, then we have |
| to compensate for the fact that dbcc decrements in HImode. */ |
| switch (GET_MODE (operands[0])) |
| { |
| case E_SImode: |
| output_asm_insn ("clr%.w %0\n\tsubq%.l #1,%0\n\tjpl %l1", operands); |
| break; |
| |
| case E_HImode: |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| const char * |
| output_scc_di (rtx op, rtx operand1, rtx operand2, rtx dest) |
| { |
| rtx loperands[7]; |
| enum rtx_code op_code = GET_CODE (op); |
| |
| /* This does not produce a useful cc. */ |
| CC_STATUS_INIT; |
| |
| /* The m68k cmp.l instruction requires operand1 to be a reg as used |
| below. Swap the operands and change the op if these requirements |
| are not fulfilled. */ |
| if (GET_CODE (operand2) == REG && GET_CODE (operand1) != REG) |
| { |
| rtx tmp = operand1; |
| |
| operand1 = operand2; |
| operand2 = tmp; |
| op_code = swap_condition (op_code); |
| } |
| loperands[0] = operand1; |
| if (GET_CODE (operand1) == REG) |
| loperands[1] = gen_rtx_REG (SImode, REGNO (operand1) + 1); |
| else |
| loperands[1] = adjust_address (operand1, SImode, 4); |
| if (operand2 != const0_rtx) |
| { |
| loperands[2] = operand2; |
| if (GET_CODE (operand2) == REG) |
| loperands[3] = gen_rtx_REG (SImode, REGNO (operand2) + 1); |
| else |
| loperands[3] = adjust_address (operand2, SImode, 4); |
| } |
| loperands[4] = gen_label_rtx (); |
| if (operand2 != const0_rtx) |
| output_asm_insn ("cmp%.l %2,%0\n\tjne %l4\n\tcmp%.l %3,%1", loperands); |
| else |
| { |
| if (TARGET_68020 || TARGET_COLDFIRE || ! ADDRESS_REG_P (loperands[0])) |
| output_asm_insn ("tst%.l %0", loperands); |
| else |
| output_asm_insn ("cmp%.w #0,%0", loperands); |
| |
| output_asm_insn ("jne %l4", loperands); |
| |
| if (TARGET_68020 || TARGET_COLDFIRE || ! ADDRESS_REG_P (loperands[1])) |
| output_asm_insn ("tst%.l %1", loperands); |
| else |
| output_asm_insn ("cmp%.w #0,%1", loperands); |
| } |
| |
| loperands[5] = dest; |
| |
| switch (op_code) |
| { |
| case EQ: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("seq %5", loperands); |
| break; |
| |
| case NE: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sne %5", loperands); |
| break; |
| |
| case GT: |
| loperands[6] = gen_label_rtx (); |
| output_asm_insn ("shi %5\n\tjra %l6", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sgt %5", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case GTU: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("shi %5", loperands); |
| break; |
| |
| case LT: |
| loperands[6] = gen_label_rtx (); |
| output_asm_insn ("scs %5\n\tjra %l6", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("slt %5", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case LTU: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("scs %5", loperands); |
| break; |
| |
| case GE: |
| loperands[6] = gen_label_rtx (); |
| output_asm_insn ("scc %5\n\tjra %l6", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sge %5", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case GEU: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("scc %5", loperands); |
| break; |
| |
| case LE: |
| loperands[6] = gen_label_rtx (); |
| output_asm_insn ("sls %5\n\tjra %l6", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sle %5", loperands); |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[6])); |
| break; |
| |
| case LEU: |
| (*targetm.asm_out.internal_label) (asm_out_file, "L", |
| CODE_LABEL_NUMBER (loperands[4])); |
| output_asm_insn ("sls %5", loperands); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| return ""; |
| } |
| |
| rtx_code |
| m68k_output_btst (rtx countop, rtx dataop, rtx_code code, int signpos) |
| { |
| rtx ops[2]; |
| ops[0] = countop; |
| ops[1] = dataop; |
| |
| if (GET_CODE (countop) == CONST_INT) |
| { |
| int count = INTVAL (countop); |
| /* If COUNT is bigger than size of storage unit in use, |
| advance to the containing unit of same size. */ |
| if (count > signpos) |
| { |
| int offset = (count & ~signpos) / 8; |
| count = count & signpos; |
| ops[1] = dataop = adjust_address (dataop, QImode, offset); |
| } |
| |
| if (code == EQ || code == NE) |
| { |
| if (count == 31) |
| { |
| output_asm_insn ("tst%.l %1", ops); |
| return code == EQ ? PLUS : MINUS; |
| } |
| if (count == 15) |
| { |
| output_asm_insn ("tst%.w %1", ops); |
| return code == EQ ? PLUS : MINUS; |
| } |
| if (count == 7) |
| { |
| output_asm_insn ("tst%.b %1", ops); |
| return code == EQ ? PLUS : MINUS; |
| } |
| } |
| /* Try to use `movew to ccr' followed by the appropriate branch insn. |
| On some m68k variants unfortunately that's slower than btst. |
| On 68000 and higher, that should also work for all HImode operands. */ |
| if (TUNE_CPU32 || TARGET_COLDFIRE || optimize_size) |
| { |
| if (count == 3 && DATA_REG_P (ops[1]) && (code == EQ || code == NE)) |
| { |
| output_asm_insn ("move%.w %1,%%ccr", ops); |
| return code == EQ ? PLUS : MINUS; |
| } |
| if (count == 2 && DATA_REG_P (ops[1]) && (code == EQ || code == NE)) |
| { |
| output_asm_insn ("move%.w %1,%%ccr", ops); |
| return code == EQ ? NE : EQ; |
| } |
| /* count == 1 followed by bvc/bvs and |
| count == 0 followed by bcc/bcs are also possible, but need |
| m68k-specific CC_Z_IN_NOT_V and CC_Z_IN_NOT_C flags. */ |
| } |
| } |
| output_asm_insn ("btst %0,%1", ops); |
| return code; |
| } |
| |
| /* Output a bftst instruction for a zero_extract with ZXOP0, ZXOP1 and ZXOP2 |
| operands. CODE is the code of the comparison, and we return the code to |
| be actually used in the jump. */ |
| |
| rtx_code |
| m68k_output_bftst (rtx zxop0, rtx zxop1, rtx zxop2, rtx_code code) |
| { |
| if (zxop1 == const1_rtx && GET_CODE (zxop2) == CONST_INT) |
| { |
| int width = GET_CODE (zxop0) == REG ? 31 : 7; |
| /* Pass 1000 as SIGNPOS argument so that btst will |
| not think we are testing the sign bit for an `and' |
| and assume that nonzero implies a negative result. */ |
| return m68k_output_btst (GEN_INT (width - INTVAL (zxop2)), zxop0, code, 1000); |
| } |
| rtx ops[3] = { zxop0, zxop1, zxop2 }; |
| output_asm_insn ("bftst %0{%b2:%b1}", ops); |
| return code; |
| } |
| |
| /* Return true if X is a legitimate base register. STRICT_P says |
| whether we need strict checking. */ |
| |
| bool |
| m68k_legitimate_base_reg_p (rtx x, bool strict_p) |
| { |
| /* Allow SUBREG everywhere we allow REG. This results in better code. */ |
| if (!strict_p && GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && (strict_p |
| ? REGNO_OK_FOR_BASE_P (REGNO (x)) |
| : REGNO_OK_FOR_BASE_NONSTRICT_P (REGNO (x)))); |
| } |
| |
| /* Return true if X is a legitimate index register. STRICT_P says |
| whether we need strict checking. */ |
| |
| bool |
| m68k_legitimate_index_reg_p (rtx x, bool strict_p) |
| { |
| if (!strict_p && GET_CODE (x) == SUBREG) |
| x = SUBREG_REG (x); |
| |
| return (REG_P (x) |
| && (strict_p |
| ? REGNO_OK_FOR_INDEX_P (REGNO (x)) |
| : REGNO_OK_FOR_INDEX_NONSTRICT_P (REGNO (x)))); |
| } |
| |
| /* Return true if X is a legitimate index expression for a (d8,An,Xn) or |
| (bd,An,Xn) addressing mode. Fill in the INDEX and SCALE fields of |
| ADDRESS if so. STRICT_P says whether we need strict checking. */ |
| |
| static bool |
| m68k_decompose_index (rtx x, bool strict_p, struct m68k_address *address) |
| { |
| int scale; |
| |
| /* Check for a scale factor. */ |
| scale = 1; |
| if ((TARGET_68020 || TARGET_COLDFIRE) |
| && GET_CODE (x) == MULT |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && (INTVAL (XEXP (x, 1)) == 2 |
| || INTVAL (XEXP (x, 1)) == 4 |
| || (INTVAL (XEXP (x, 1)) == 8 |
| && (TARGET_COLDFIRE_FPU || !TARGET_COLDFIRE)))) |
| { |
| scale = INTVAL (XEXP (x, 1)); |
| x = XEXP (x, 0); |
| } |
| |
| /* Check for a word extension. */ |
| if (!TARGET_COLDFIRE |
| && GET_CODE (x) == SIGN_EXTEND |
| && GET_MODE (XEXP (x, 0)) == HImode) |
| x = XEXP (x, 0); |
| |
| if (m68k_legitimate_index_reg_p (x, strict_p)) |
| { |
| address->scale = scale; |
| address->index = x; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Return true if X is an illegitimate symbolic constant. */ |
| |
| bool |
| m68k_illegitimate_symbolic_constant_p (rtx x) |
| { |
| rtx base, offset; |
| |
| if (M68K_OFFSETS_MUST_BE_WITHIN_SECTIONS_P) |
| { |
| split_const (x, &base, &offset); |
| if (GET_CODE (base) == SYMBOL_REF |
| && !offset_within_block_p (base, INTVAL (offset))) |
| return true; |
| } |
| return m68k_tls_reference_p (x, false); |
| } |
| |
| /* Implement TARGET_CANNOT_FORCE_CONST_MEM. */ |
| |
| static bool |
| m68k_cannot_force_const_mem (machine_mode mode ATTRIBUTE_UNUSED, rtx x) |
| { |
| return m68k_illegitimate_symbolic_constant_p (x); |
| } |
| |
| /* Return true if X is a legitimate constant address that can reach |
| bytes in the range [X, X + REACH). STRICT_P says whether we need |
| strict checking. */ |
| |
| static bool |
| m68k_legitimate_constant_address_p (rtx x, unsigned int reach, bool strict_p) |
| { |
| rtx base, offset; |
| |
| if (!CONSTANT_ADDRESS_P (x)) |
| return false; |
| |
| if (flag_pic |
| && !(strict_p && TARGET_PCREL) |
| && symbolic_operand (x, VOIDmode)) |
| return false; |
| |
| if (M68K_OFFSETS_MUST_BE_WITHIN_SECTIONS_P && reach > 1) |
| { |
| split_const (x, &base, &offset); |
| if (GET_CODE (base) == SYMBOL_REF |
| && !offset_within_block_p (base, INTVAL (offset) + reach - 1)) |
| return false; |
| } |
| |
| return !m68k_tls_reference_p (x, false); |
| } |
| |
| /* Return true if X is a LABEL_REF for a jump table. Assume that unplaced |
| labels will become jump tables. */ |
| |
| static bool |
| m68k_jump_table_ref_p (rtx x) |
| { |
| if (GET_CODE (x) != LABEL_REF) |
| return false; |
| |
| rtx_insn *insn = as_a <rtx_insn *> (XEXP (x, 0)); |
| if (!NEXT_INSN (insn) && !PREV_INSN (insn)) |
| return true; |
| |
| insn = next_nonnote_insn (insn); |
| return insn && JUMP_TABLE_DATA_P (insn); |
| } |
| |
| /* Return true if X is a legitimate address for values of mode MODE. |
| STRICT_P says whether strict checking is needed. If the address |
| is valid, describe its components in *ADDRESS. */ |
| |
| static bool |
| m68k_decompose_address (machine_mode mode, rtx x, |
| bool strict_p, struct m68k_address *address) |
| { |
| unsigned int reach; |
| |
| memset (address, 0, sizeof (*address)); |
| |
| if (mode == BLKmode) |
| reach = 1; |
| else |
| reach = GET_MODE_SIZE (mode); |
| |
| /* Check for (An) (mode 2). */ |
| if (m68k_legitimate_base_reg_p (x, strict_p)) |
| { |
| address->base = x; |
| return true; |
| } |
| |
| /* Check for -(An) and (An)+ (modes 3 and 4). */ |
| if ((GET_CODE (x) == PRE_DEC || GET_CODE (x) == POST_INC) |
| && m68k_legitimate_base_reg_p (XEXP (x, 0), strict_p)) |
| { |
| address->code = GET_CODE (x); |
| address->base = XEXP (x, 0); |
| return true; |
| } |
| |
| /* Check for (d16,An) (mode 5). */ |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && IN_RANGE (INTVAL (XEXP (x, 1)), -0x8000, 0x8000 - reach) |
| && m68k_legitimate_base_reg_p (XEXP (x, 0), strict_p)) |
| { |
| address->base = XEXP (x, 0); |
| address->offset = XEXP (x, 1); |
| return true; |
| } |
| |
| /* Check for GOT loads. These are (bd,An,Xn) addresses if |
| TARGET_68020 && flag_pic == 2, otherwise they are (d16,An) |
| addresses. */ |
| if (GET_CODE (x) == PLUS |
| && XEXP (x, 0) == pic_offset_table_rtx) |
| { |
| /* As we are processing a PLUS, do not unwrap RELOC32 symbols -- |
| they are invalid in this context. */ |
| if (m68k_unwrap_symbol (XEXP (x, 1), false) != XEXP (x, 1)) |
| { |
| address->base = XEXP (x, 0); |
| address->offset = XEXP (x, 1); |
| return true; |
| } |
| } |
| |
| /* The ColdFire FPU only accepts addressing modes 2-5. */ |
| if (TARGET_COLDFIRE_FPU && GET_MODE_CLASS (mode) == MODE_FLOAT) |
| return false; |
| |
| /* Check for (xxx).w and (xxx).l. Also, in the TARGET_PCREL case, |
| check for (d16,PC) or (bd,PC,Xn) with a suppressed index register. |
| All these modes are variations of mode 7. */ |
| if (m68k_legitimate_constant_address_p (x, reach, strict_p)) |
| { |
| address->offset = x; |
| return true; |
| } |
| |
| /* Check for (d8,PC,Xn), a mode 7 form. This case is needed for |
| tablejumps. |
| |
| ??? do_tablejump creates these addresses before placing the target |
| label, so we have to assume that unplaced labels are jump table |
| references. It seems unlikely that we would ever generate indexed |
| accesses to unplaced labels in other cases. */ |
| if (GET_CODE (x) == PLUS |
| && m68k_jump_table_ref_p (XEXP (x, 1)) |
| && m68k_decompose_index (XEXP (x, 0), strict_p, address)) |
| { |
| address->offset = XEXP (x, 1); |
| return true; |
| } |
| |
| /* Everything hereafter deals with (d8,An,Xn.SIZE*SCALE) or |
| (bd,An,Xn.SIZE*SCALE) addresses. */ |
| |
| if (TARGET_68020) |
| { |
| /* Check for a nonzero base displacement. */ |
| if (GET_CODE (x) == PLUS |
| && m68k_legitimate_constant_address_p (XEXP (x, 1), reach, strict_p)) |
| { |
| address->offset = XEXP (x, 1); |
| x = XEXP (x, 0); |
| } |
| |
| /* Check for a suppressed index register. */ |
| if (m68k_legitimate_base_reg_p (x, strict_p)) |
| { |
| address->base = x; |
| return true; |
| } |
| |
| /* Check for a suppressed base register. Do not allow this case |
| for non-symbolic offsets as it effectively gives gcc freedom |
| to treat data registers as base registers, which can generate |
| worse code. */ |
| if (address->offset |
| && symbolic_operand (address->offset, VOIDmode) |
| && m68k_decompose_index (x, strict_p, address)) |
| return true; |
| } |
| else |
| { |
| /* Check for a nonzero base displacement. */ |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && IN_RANGE (INTVAL (XEXP (x, 1)), -0x80, 0x80 - reach)) |
| { |
| address->offset = XEXP (x, 1); |
| x = XEXP (x, 0); |
| } |
| } |
| |
| /* We now expect the sum of a base and an index. */ |
| if (GET_CODE (x) == PLUS) |
| { |
| if (m68k_legitimate_base_reg_p (XEXP (x, 0), strict_p) |
| && m68k_decompose_index (XEXP (x, 1), strict_p, address)) |
| { |
| address->base = XEXP (x, 0); |
| return true; |
| } |
| |
| if (m68k_legitimate_base_reg_p (XEXP (x, 1), strict_p) |
| && m68k_decompose_index (XEXP (x, 0), strict_p, address)) |
| { |
| address->base = XEXP (x, 1); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Return true if X is a legitimate address for values of mode MODE. |
| STRICT_P says whether strict checking is needed. */ |
| |
| bool |
| m68k_legitimate_address_p (machine_mode mode, rtx x, bool strict_p) |
| { |
| struct m68k_address address; |
| |
| return m68k_decompose_address (mode, x, strict_p, &address); |
| } |
| |
| /* Return true if X is a memory, describing its address in ADDRESS if so. |
| Apply strict checking if called during or after reload. */ |
| |
| static bool |
| m68k_legitimate_mem_p (rtx x, struct m68k_address *address) |
| { |
| return (MEM_P (x) |
| && m68k_decompose_address (GET_MODE (x), XEXP (x, 0), |
| reload_in_progress || reload_completed, |
| address)); |
| } |
| |
| /* Implement TARGET_LEGITIMATE_CONSTANT_P. */ |
| |
| bool |
| m68k_legitimate_constant_p (machine_mode mode, rtx x) |
| { |
| return mode != XFmode && !m68k_illegitimate_symbolic_constant_p (x); |
| } |
| |
| /* Return true if X matches the 'Q' constraint. It must be a memory |
| with a base address and no constant offset or index. */ |
| |
| bool |
| m68k_matches_q_p (rtx x) |
| { |
| struct m68k_address address; |
| |
| return (m68k_legitimate_mem_p (x, &address) |
| && address.code == UNKNOWN |
| && address.base |
| && !address.offset |
| && !address.index); |
| } |
| |
| /* Return true if X matches the 'U' constraint. It must be a base address |
| with a constant offset and no index. */ |
| |
| bool |
| m68k_matches_u_p (rtx x) |
| { |
| struct m68k_address address; |
| |
| return (m68k_legitimate_mem_p (x, &address) |
| && address.code == UNKNOWN |
| && address.base |
| && address.offset |
| && !address.index); |
| } |
| |
| /* Return GOT pointer. */ |
| |
| static rtx |
| m68k_get_gp (void) |
| { |
| if (pic_offset_table_rtx == NULL_RTX) |
| pic_offset_table_rtx = gen_rtx_REG (Pmode, PIC_REG); |
| |
| crtl->uses_pic_offset_table = 1; |
| |
| return pic_offset_table_rtx; |
| } |
| |
| /* M68K relocations, used to distinguish GOT and TLS relocations in UNSPEC |
| wrappers. */ |
| enum m68k_reloc { RELOC_GOT, RELOC_TLSGD, RELOC_TLSLDM, RELOC_TLSLDO, |
| RELOC_TLSIE, RELOC_TLSLE }; |
| |
| #define TLS_RELOC_P(RELOC) ((RELOC) != RELOC_GOT) |
| |
| /* Wrap symbol X into unspec representing relocation RELOC. |
| BASE_REG - register that should be added to the result. |
| TEMP_REG - if non-null, temporary register. */ |
| |
| static rtx |
| m68k_wrap_symbol (rtx x, enum m68k_reloc reloc, rtx base_reg, rtx temp_reg) |
| { |
| bool use_x_p; |
| |
| use_x_p = (base_reg == pic_offset_table_rtx) ? TARGET_XGOT : TARGET_XTLS; |
| |
| if (TARGET_COLDFIRE && use_x_p) |
| /* When compiling with -mx{got, tls} switch the code will look like this: |
| |
| move.l <X>@<RELOC>,<TEMP_REG> |
| add.l <BASE_REG>,<TEMP_REG> */ |
| { |
| /* Wrap X in UNSPEC_??? to tip m68k_output_addr_const_extra |
| to put @RELOC after reference. */ |
| x = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, x, GEN_INT (reloc)), |
| UNSPEC_RELOC32); |
| x = gen_rtx_CONST (Pmode, x); |
| |
| if (temp_reg == NULL) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| temp_reg = gen_reg_rtx (Pmode); |
| } |
| |
| emit_move_insn (temp_reg, x); |
| emit_insn (gen_addsi3 (temp_reg, temp_reg, base_reg)); |
| x = temp_reg; |
| } |
| else |
| { |
| x = gen_rtx_UNSPEC (Pmode, gen_rtvec (2, x, GEN_INT (reloc)), |
| UNSPEC_RELOC16); |
| x = gen_rtx_CONST (Pmode, x); |
| |
| x = gen_rtx_PLUS (Pmode, base_reg, x); |
| } |
| |
| return x; |
| } |
| |
| /* Helper for m68k_unwrap_symbol. |
| Also, if unwrapping was successful (that is if (ORIG != <return value>)), |
| sets *RELOC_PTR to relocation type for the symbol. */ |
| |
| static rtx |
| m68k_unwrap_symbol_1 (rtx orig, bool unwrap_reloc32_p, |
| enum m68k_reloc *reloc_ptr) |
| { |
| if (GET_CODE (orig) == CONST) |
| { |
| rtx x; |
| enum m68k_reloc dummy; |
| |
| x = XEXP (orig, 0); |
| |
| if (reloc_ptr == NULL) |
| reloc_ptr = &dummy; |
| |
| /* Handle an addend. */ |
| if ((GET_CODE (x) == PLUS || GET_CODE (x) == MINUS) |
| && CONST_INT_P (XEXP (x, 1))) |
| x = XEXP (x, 0); |
| |
| if (GET_CODE (x) == UNSPEC) |
| { |
| switch (XINT (x, 1)) |
| { |
| case UNSPEC_RELOC16: |
| orig = XVECEXP (x, 0, 0); |
| *reloc_ptr = (enum m68k_reloc) INTVAL (XVECEXP (x, 0, 1)); |
| break; |
| |
| case UNSPEC_RELOC32: |
| if (unwrap_reloc32_p) |
| { |
| orig = XVECEXP (x, 0, 0); |
| *reloc_ptr = (enum m68k_reloc) INTVAL (XVECEXP (x, 0, 1)); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| } |
| |
| return orig; |
| } |
| |
| /* Unwrap symbol from UNSPEC_RELOC16 and, if unwrap_reloc32_p, |
| UNSPEC_RELOC32 wrappers. */ |
| |
| rtx |
| m68k_unwrap_symbol (rtx orig, bool unwrap_reloc32_p) |
| { |
| return m68k_unwrap_symbol_1 (orig, unwrap_reloc32_p, NULL); |
| } |
| |
| /* Adjust decorated address operand before outputing assembler for it. */ |
| |
| static void |
| m68k_adjust_decorated_operand (rtx op) |
| { |
| /* Combine and, possibly, other optimizations may do good job |
| converting |
| (const (unspec [(symbol)])) |
| into |
| (const (plus (unspec [(symbol)]) |
| (const_int N))). |
| The problem with this is emitting @TLS or @GOT decorations. |
| The decoration is emitted when processing (unspec), so the |
| result would be "#symbol@TLSLE+N" instead of "#symbol+N@TLSLE". |
| |
| It seems that the easiest solution to this is to convert such |
| operands to |
| (const (unspec [(plus (symbol) |
| (const_int N))])). |
| Note, that the top level of operand remains intact, so we don't have |
| to patch up anything outside of the operand. */ |
| |
| subrtx_var_iterator::array_type array; |
| FOR_EACH_SUBRTX_VAR (iter, array, op, ALL) |
| { |
| rtx x = *iter; |
| if (m68k_unwrap_symbol (x, true) != x) |
| { |
| rtx plus; |
| |
| gcc_assert (GET_CODE (x) == CONST); |
| plus = XEXP (x, 0); |
| |
| if (GET_CODE (plus) == PLUS || GET_CODE (plus) == MINUS) |
| { |
| rtx unspec; |
| rtx addend; |
| |
| unspec = XEXP (plus, 0); |
| gcc_assert (GET_CODE (unspec) == UNSPEC); |
| addend = XEXP (plus, 1); |
| gcc_assert (CONST_INT_P (addend)); |
| |
| /* We now have all the pieces, rearrange them. */ |
| |
| /* Move symbol to plus. */ |
| XEXP (plus, 0) = XVECEXP (unspec, 0, 0); |
| |
| /* Move plus inside unspec. */ |
| XVECEXP (unspec, 0, 0) = plus; |
| |
| /* Move unspec to top level of const. */ |
| XEXP (x, 0) = unspec; |
| } |
| iter.skip_subrtxes (); |
| } |
| } |
| } |
| |
| /* Move X to a register and add REG_EQUAL note pointing to ORIG. |
| If REG is non-null, use it; generate new pseudo otherwise. */ |
| |
| static rtx |
| m68k_move_to_reg (rtx x, rtx orig, rtx reg) |
| { |
| rtx_insn *insn; |
| |
| if (reg == NULL_RTX) |
| { |
| gcc_assert (can_create_pseudo_p ()); |
| reg = gen_reg_rtx (Pmode); |
| } |
| |
| insn = emit_move_insn (reg, x); |
| /* Put a REG_EQUAL note on this insn, so that it can be optimized |
| by loop. */ |
| set_unique_reg_note (insn, REG_EQUAL, orig); |
| |
| return reg; |
| } |
| |
| /* Does the same as m68k_wrap_symbol, but returns a memory reference to |
| GOT slot. */ |
| |
| static rtx |
| m68k_wrap_symbol_into_got_ref (rtx x, enum m68k_reloc reloc, rtx temp_reg) |
| { |
| x = m68k_wrap_symbol (x, reloc, m68k_get_gp (), temp_reg); |
| |
| x = gen_rtx_MEM (Pmode, x); |
| MEM_READONLY_P (x) = 1; |
| |
| return x; |
| } |
| |
| /* Legitimize PIC addresses. If the address is already |
| position-independent, we return ORIG. Newly generated |
| position-independent addresses go to REG. If we need more |
| than one register, we lose. |
| |
| An address is legitimized by making an indirect reference |
| through the Global Offset Table with the name of the symbol |
| used as an offset. |
| |
| The assembler and linker are responsible for placing the |
| address of the symbol in the GOT. The function prologue |
| is responsible for initializing a5 to the starting address |
| of the GOT. |
| |
| The assembler is also responsible for translating a symbol name |
| into a constant displacement from the start of the GOT. |
| |
| A quick example may make things a little clearer: |
| |
| When not generating PIC code to store the value 12345 into _foo |
| we would generate the following code: |
| |
| movel #12345, _foo |
| |
| When generating PIC two transformations are made. First, the compiler |
| loads the address of foo into a register. So the first transformation makes: |
| |
| lea _foo, a0 |
| movel #12345, a0@ |
| |
| The code in movsi will intercept the lea instruction and call this |
| routine which will transform the instructions into: |
| |
| movel a5@(_foo:w), a0 |
| movel #12345, a0@ |
| |
| |
| That (in a nutshell) is how *all* symbol and label references are |
| handled. */ |
| |
| rtx |
| legitimize_pic_address (rtx orig, machine_mode mode ATTRIBUTE_UNUSED, |
| rtx reg) |
| { |
| rtx pic_ref = orig; |
| |
| /* First handle a simple SYMBOL_REF or LABEL_REF */ |
| if (GET_CODE (orig) == SYMBOL_REF || GET_CODE (orig) == LABEL_REF) |
| { |
| gcc_assert (reg); |
| |
| pic_ref = m68k_wrap_symbol_into_got_ref (orig, RELOC_GOT, reg); |
| pic_ref = m68k_move_to_reg (pic_ref, orig, reg); |
| } |
| else if (GET_CODE (orig) == CONST) |
| { |
| rtx base; |
| |
| /* Make sure this has not already been legitimized. */ |
| if (m68k_unwrap_symbol (orig, true) != orig) |
| return orig; |
| |
| gcc_assert (reg); |
| |
| /* legitimize both operands of the PLUS */ |
| gcc_assert (GET_CODE (XEXP (orig, 0)) == PLUS); |
| |
| base = legitimize_pic_address (XEXP (XEXP (orig, 0), 0), Pmode, reg); |
| orig = legitimize_pic_address (XEXP (XEXP (orig, 0), 1), Pmode, |
| base == reg ? 0 : reg); |
| |
| if (GET_CODE (orig) == CONST_INT) |
| pic_ref = plus_constant (Pmode, base, INTVAL (orig)); |
| else |
| pic_ref = gen_rtx_PLUS (Pmode, base, orig); |
| } |
| |
| return pic_ref; |
| } |
| |
| /* The __tls_get_addr symbol. */ |
| static GTY(()) rtx m68k_tls_get_addr; |
| |
| /* Return SYMBOL_REF for __tls_get_addr. */ |
| |
| static rtx |
| m68k_get_tls_get_addr (void) |
| { |
| if (m68k_tls_get_addr == NULL_RTX) |
| m68k_tls_get_addr = init_one_libfunc ("__tls_get_addr"); |
| |
| return m68k_tls_get_addr; |
| } |
| |
| /* Return libcall result in A0 instead of usual D0. */ |
| static bool m68k_libcall_value_in_a0_p = false; |
| |
| /* Emit instruction sequence that calls __tls_get_addr. X is |
| the TLS symbol we are referencing and RELOC is the symbol type to use |
| (either TLSGD or TLSLDM). EQV is the REG_EQUAL note for the sequence |
| emitted. A pseudo register with result of __tls_get_addr call is |
| returned. */ |
| |
| static rtx |
| m68k_call_tls_get_addr (rtx x, rtx eqv, enum m68k_reloc reloc) |
| { |
| rtx a0; |
| rtx_insn *insns; |
| rtx dest; |
| |
| /* Emit the call sequence. */ |
| start_sequence (); |
| |
| /* FIXME: Unfortunately, emit_library_call_value does not |
| consider (plus (%a5) (const (unspec))) to be a good enough |
| operand for push, so it forces it into a register. The bad |
| thing about this is that combiner, due to copy propagation and other |
| optimizations, sometimes cannot later fix this. As a consequence, |
| additional register may be allocated resulting in a spill. |
| For reference, see args processing loops in |
| calls.cc:emit_library_call_value_1. |
| For testcase, see gcc.target/m68k/tls-{gd, ld}.c */ |
| x = m68k_wrap_symbol (x, reloc, m68k_get_gp (), NULL_RTX); |
| |
| /* __tls_get_addr() is not a libcall, but emitting a libcall_value |
| is the simpliest way of generating a call. The difference between |
| __tls_get_addr() and libcall is that the result is returned in D0 |
| instead of A0. To workaround this, we use m68k_libcall_value_in_a0_p |
| which temporarily switches returning the result to A0. */ |
| |
| m68k_libcall_value_in_a0_p = true; |
| a0 = emit_library_call_value (m68k_get_tls_get_addr (), NULL_RTX, LCT_PURE, |
| Pmode, x, Pmode); |
| m68k_libcall_value_in_a0_p = false; |
| |
| insns = get_insns (); |
| end_sequence (); |
| |
| gcc_assert (can_create_pseudo_p ()); |
| dest = gen_reg_rtx (Pmode); |
| emit_libcall_block (insns, dest, a0, eqv); |
| |
| return dest; |
| } |
| |
| /* The __tls_get_addr symbol. */ |
| static GTY(()) rtx m68k_read_tp; |
| |
| /* Return SYMBOL_REF for __m68k_read_tp. */ |
| |
| static rtx |
| m68k_get_m68k_read_tp (void) |
| { |
| if (m68k_read_tp == NULL_RTX) |
| m68k_read_tp = init_one_libfunc ("__m68k_read_tp"); |
| |
| return m68k_read_tp; |
| } |
| |
| /* Emit instruction sequence that calls __m68k_read_tp. |
| A pseudo register with result of __m68k_read_tp call is returned. */ |
| |
| static rtx |
| m68k_call_m68k_read_tp (void) |
| { |
| rtx a0; |
| rtx eqv; |
| rtx_insn *insns; |
| rtx dest; |
| |
| start_sequence (); |
| |
| /* __m68k_read_tp() is not a libcall, but emitting a libcall_value |
| is the simpliest way of generating a call. The difference between |
| __m68k_read_tp() and libcall is that the result is returned in D0 |
| instead of A0. To workaround this, we use m68k_libcall_value_in_a0_p |
| which temporarily switches returning the result to A0. */ |
| |
| /* Emit the call sequence. */ |
| m68k_libcall_value_in_a0_p = true; |
| a0 = emit_library_call_value (m68k_get_m68k_read_tp (), NULL_RTX, LCT_PURE, |
| Pmode); |
| m68k_libcall_value_in_a0_p = false; |
| insns = get_insns (); |
| end_sequence (); |
| |
| /* Attach a unique REG_EQUIV, to allow the RTL optimizers to |
| share the m68k_read_tp result with other IE/LE model accesses. */ |
| eqv = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const1_rtx), UNSPEC_RELOC32); |
| |
| gcc_assert (can_create_pseudo_p ()); |
| dest = gen_reg_rtx (Pmode); |
| emit_libcall_block (insns, dest, a0, eqv); |
| |
| return dest; |
| } |
| |
| /* Return a legitimized address for accessing TLS SYMBOL_REF X. |
| For explanations on instructions sequences see TLS/NPTL ABI for m68k and |
| ColdFire. */ |
| |
| rtx |
| m68k_legitimize_tls_address (rtx orig) |
| { |
| switch (SYMBOL_REF_TLS_MODEL (orig)) |
| { |
| case TLS_MODEL_GLOBAL_DYNAMIC: |
| orig = m68k_call_tls_get_addr (orig, orig, RELOC_TLSGD); |
| break; |
| |
| case TLS_MODEL_LOCAL_DYNAMIC: |
| { |
| rtx eqv; |
| rtx a0; |
| rtx x; |
| |
| /* Attach a unique REG_EQUIV, to allow the RTL optimizers to |
| share the LDM result with other LD model accesses. */ |
| eqv = gen_rtx_UNSPEC (Pmode, gen_rtvec (1, const0_rtx), |
| UNSPEC_RELOC32); |
| |
| a0 = m68k_call_tls_get_addr (orig, eqv, RELOC_TLSLDM); |
| |
| x = m68k_wrap_symbol (orig, RELOC_TLSLDO, a0, NULL_RTX); |
| |
| if (can_create_pseudo_p ()) |
| x = m68k_move_to_reg (x, orig, NULL_RTX); |
| |
| orig = x; |
| break; |
| } |
| |
| case TLS_MODEL_INITIAL_EXEC: |
| { |
| rtx a0; |
| rtx x; |
| |
| a0 = m68k_call_m68k_read_tp (); |
| |
| x = m68k_wrap_symbol_into_got_ref (orig, RELOC_TLSIE, NULL_RTX); |
| x = gen_rtx_PLUS (Pmode, x, a0); |
| |
| if (can_create_pseudo_p ()) |
| x = m68k_move_to_reg (x, orig, NULL_RTX); |
| |
| orig = x; |
| break; |
| } |
| |
| case TLS_MODEL_LOCAL_EXEC: |
| { |
| rtx a0; |
| rtx x; |
| |
| a0 = m68k_call_m68k_read_tp (); |
| |
| x = m68k_wrap_symbol (orig, RELOC_TLSLE, a0, NULL_RTX); |
| |
| if (can_create_pseudo_p ()) |
| x = m68k_move_to_reg (x, orig, NULL_RTX); |
| |
| orig = x; |
| break; |
| } |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| return orig; |
| } |
| |
| /* Return true if X is a TLS symbol. */ |
| |
| static bool |
| m68k_tls_symbol_p (rtx x) |
| { |
| if (!TARGET_HAVE_TLS) |
| return false; |
| |
| if (GET_CODE (x) != SYMBOL_REF) |
| return false; |
| |
| return SYMBOL_REF_TLS_MODEL (x) != 0; |
| } |
| |
| /* If !LEGITIMATE_P, return true if X is a TLS symbol reference, |
| though illegitimate one. |
| If LEGITIMATE_P, return true if X is a legitimate TLS symbol reference. */ |
| |
| bool |
| m68k_tls_reference_p (rtx x, bool legitimate_p) |
| { |
| if (!TARGET_HAVE_TLS) |
| return false; |
| |
| if (!legitimate_p) |
| { |
| subrtx_var_iterator::array_type array; |
| FOR_EACH_SUBRTX_VAR (iter, array, x, ALL) |
| { |
| rtx x = *iter; |
| |
| /* Note: this is not the same as m68k_tls_symbol_p. */ |
| if (GET_CODE (x) == SYMBOL_REF && SYMBOL_REF_TLS_MODEL (x) != 0) |
| return true; |
| |
| /* Don't recurse into legitimate TLS references. */ |
| if (m68k_tls_reference_p (x, true)) |
| iter.skip_subrtxes (); |
| } |
| return false; |
| } |
| else |
| { |
| enum m68k_reloc reloc = RELOC_GOT; |
| |
| return (m68k_unwrap_symbol_1 (x, true, &reloc) != x |
| && TLS_RELOC_P (reloc)); |
| } |
| } |
| |
| |
| |
| #define USE_MOVQ(i) ((unsigned) ((i) + 128) <= 255) |
| |
| /* Return the type of move that should be used for integer I. */ |
| |
| M68K_CONST_METHOD |
| m68k_const_method (HOST_WIDE_INT i) |
| { |
| unsigned u; |
| |
| if (USE_MOVQ (i)) |
| return MOVQ; |
| |
| /* The ColdFire doesn't have byte or word operations. */ |
| /* FIXME: This may not be useful for the m68060 either. */ |
| if (!TARGET_COLDFIRE) |
| { |
| /* if -256 < N < 256 but N is not in range for a moveq |
| N^ff will be, so use moveq #N^ff, dreg; not.b dreg. */ |
| if (USE_MOVQ (i ^ 0xff)) |
| return NOTB; |
| /* Likewise, try with not.w */ |
| if (USE_MOVQ (i ^ 0xffff)) |
| return NOTW; |
| /* This is the only value where neg.w is useful */ |
| if (i == -65408) |
| return NEGW; |
| } |
| |
| /* Try also with swap. */ |
| u = i; |
| if (USE_MOVQ ((u >> 16) | (u << 16))) |
| return SWAP; |
| |
| if (TARGET_ISAB) |
| { |
| /* Try using MVZ/MVS with an immediate value to load constants. */ |
| if (i >= 0 && i <= 65535) |
| return MVZ; |
| if (i >= -32768 && i <= 32767) |
| return MVS; |
| } |
| |
| /* Otherwise, use move.l */ |
| return MOVL; |
| } |
| |
| /* Return the cost of moving constant I into a data register. */ |
| |
| static int |
| const_int_cost (HOST_WIDE_INT i) |
| { |
| switch (m68k_const_method (i)) |
| { |
| case MOVQ: |
| /* Constants between -128 and 127 are cheap due to moveq. */ |
| return 0; |
| case MVZ: |
| case MVS: |
| case NOTB: |
| case NOTW: |
| case NEGW: |
| case SWAP: |
| /* Constants easily generated by moveq + not.b/not.w/neg.w/swap. */ |
| return 1; |
| case MOVL: |
| return 2; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| static bool |
| m68k_rtx_costs (rtx x, machine_mode mode, int outer_code, |
| int opno ATTRIBUTE_UNUSED, |
| int *total, bool speed ATTRIBUTE_UNUSED) |
| { |
| int code = GET_CODE (x); |
| |
| switch (code) |
| { |
| case CONST_INT: |
| /* Constant zero is super cheap due to clr instruction. */ |
| if (x == const0_rtx) |
| *total = 0; |
| else |
| *total = const_int_cost (INTVAL (x)); |
| return true; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = 3; |
| return true; |
| |
| case CONST_DOUBLE: |
| /* Make 0.0 cheaper than other floating constants to |
| encourage creating tstsf and tstdf insns. */ |
| if ((GET_RTX_CLASS (outer_code) == RTX_COMPARE |
| || GET_RTX_CLASS (outer_code) == RTX_COMM_COMPARE) |
| && (x == CONST0_RTX (SFmode) || x == CONST0_RTX (DFmode))) |
| *total = 4; |
| else |
| *total = 5; |
| return true; |
| |
| /* These are vaguely right for a 68020. */ |
| /* The costs for long multiply have been adjusted to work properly |
| in synth_mult on the 68020, relative to an average of the time |
| for add and the time for shift, taking away a little more because |
| sometimes move insns are needed. */ |
| /* div?.w is relatively cheaper on 68000 counted in COSTS_N_INSNS |
| terms. */ |
| #define MULL_COST \ |
| (TUNE_68060 ? 2 \ |
| : TUNE_68040 ? 5 \ |
| : (TUNE_CFV2 && TUNE_EMAC) ? 3 \ |
| : (TUNE_CFV2 && TUNE_MAC) ? 4 \ |
| : TUNE_CFV2 ? 8 \ |
| : TARGET_COLDFIRE ? 3 : 13) |
| |
| #define MULW_COST \ |
| (TUNE_68060 ? 2 \ |
| : TUNE_68040 ? 3 \ |
| : TUNE_68000_10 ? 5 \ |
| : (TUNE_CFV2 && TUNE_EMAC) ? 3 \ |
| : (TUNE_CFV2 && TUNE_MAC) ? 2 \ |
| : TUNE_CFV2 ? 8 \ |
| : TARGET_COLDFIRE ? 2 : 8) |
| |
| #define DIVW_COST \ |
| (TARGET_CF_HWDIV ? 11 \ |
| : TUNE_68000_10 || TARGET_COLDFIRE ? 12 : 27) |
| |
| case PLUS: |
| /* An lea costs about three times as much as a simple add. */ |
| if (mode == SImode |
| && GET_CODE (XEXP (x, 1)) == REG |
| && GET_CODE (XEXP (x, 0)) == MULT |
| && GET_CODE (XEXP (XEXP (x, 0), 0)) == REG |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT |
| && (INTVAL (XEXP (XEXP (x, 0), 1)) == 2 |
| || INTVAL (XEXP (XEXP (x, 0), 1)) == 4 |
| || INTVAL (XEXP (XEXP (x, 0), 1)) == 8)) |
| { |
| /* lea an@(dx:l:i),am */ |
| *total = COSTS_N_INSNS (TARGET_COLDFIRE ? 2 : 3); |
| return true; |
| } |
| return false; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| if (TUNE_68060) |
| { |
| *total = COSTS_N_INSNS(1); |
| return true; |
| } |
| if (TUNE_68000_10) |
| { |
| if (GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| if (INTVAL (XEXP (x, 1)) < 16) |
| *total = COSTS_N_INSNS (2) + INTVAL (XEXP (x, 1)) / 2; |
| else |
| /* We're using clrw + swap for these cases. */ |
| *total = COSTS_N_INSNS (4) + (INTVAL (XEXP (x, 1)) - 16) / 2; |
| } |
| else |
| *total = COSTS_N_INSNS (10); /* Worst case. */ |
| return true; |
| } |
| /* A shift by a big integer takes an extra instruction. */ |
| if (GET_CODE (XEXP (x, 1)) == CONST_INT |
| && (INTVAL (XEXP (x, 1)) == 16)) |
| { |
| *total = COSTS_N_INSNS (2); /* clrw;swap */ |
| return true; |
| } |
| if (GET_CODE (XEXP (x, 1)) == CONST_INT |
| && !(INTVAL (XEXP (x, 1)) > 0 |
| && INTVAL (XEXP (x, 1)) <= 8)) |
| { |
| *total = COSTS_N_INSNS (TARGET_COLDFIRE ? 1 : 3); /* lsr #i,dn */ |
| return true; |
| } |
| return false; |
| |
| case MULT: |
| if ((GET_CODE (XEXP (x, 0)) == ZERO_EXTEND |
| || GET_CODE (XEXP (x, 0)) == SIGN_EXTEND) |
| && mode == SImode) |
| *total = COSTS_N_INSNS (MULW_COST); |
| else if (mode == QImode || mode == HImode) |
| *total = COSTS_N_INSNS (MULW_COST); |
| else |
| *total = COSTS_N_INSNS (MULL_COST); |
| return true; |
| |
| case DIV: |
| case UDIV: |
| case MOD: |
| case UMOD: |
| if (mode == QImode || mode == HImode) |
| *total = COSTS_N_INSNS (DIVW_COST); /* div.w */ |
| else if (TARGET_CF_HWDIV) |
| *total = COSTS_N_INSNS (18); |
| else |
| *total = COSTS_N_INSNS (43); /* div.l */ |
| return true; |
| |
| case ZERO_EXTRACT: |
| if (GET_RTX_CLASS (outer_code) == RTX_COMPARE |
| || GET_RTX_CLASS (outer_code) == RTX_COMM_COMPARE) |
| *total = 0; |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Return an instruction to move CONST_INT OPERANDS[1] into data register |
| OPERANDS[0]. */ |
| |
| static const char * |
| output_move_const_into_data_reg (rtx *operands) |
| { |
| HOST_WIDE_INT i; |
| |
| i = INTVAL (operands[1]); |
| switch (m68k_const_method (i)) |
| { |
| case MVZ: |
| return "mvzw %1,%0"; |
| case MVS: |
| return "mvsw %1,%0"; |
| case MOVQ: |
| return "moveq %1,%0"; |
| case NOTB: |
| CC_STATUS_INIT; |
| operands[1] = GEN_INT (i ^ 0xff); |
| return "moveq %1,%0\n\tnot%.b %0"; |
| case NOTW: |
| CC_STATUS_INIT; |
| operands[1] = GEN_INT (i ^ 0xffff); |
| return "moveq %1,%0\n\tnot%.w %0"; |
| case NEGW: |
| CC_STATUS_INIT; |
| return "moveq #-128,%0\n\tneg%.w %0"; |
| case SWAP: |
| { |
| unsigned u = i; |
| |
| operands[1] = GEN_INT ((u << 16) | (u >> 16)); |
| return "moveq %1,%0\n\tswap %0"; |
| } |
| case MOVL: |
| return "move%.l %1,%0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return true if I can be handled by ISA B's mov3q instruction. */ |
| |
| bool |
| valid_mov3q_const (HOST_WIDE_INT i) |
| { |
| return TARGET_ISAB && (i == -1 || IN_RANGE (i, 1, 7)); |
| } |
| |
| /* Return an instruction to move CONST_INT OPERANDS[1] into OPERANDS[0]. |
| I is the value of OPERANDS[1]. */ |
| |
| static const char * |
| output_move_simode_const (rtx *operands) |
| { |
| rtx dest; |
| HOST_WIDE_INT src; |
| |
| dest = operands[0]; |
| src = INTVAL (operands[1]); |
| if (src == 0 |
| && (DATA_REG_P (dest) || MEM_P (dest)) |
| /* clr insns on 68000 read before writing. */ |
| && ((TARGET_68010 || TARGET_COLDFIRE) |
| || !(MEM_P (dest) && MEM_VOLATILE_P (dest)))) |
| return "clr%.l %0"; |
| else if (GET_MODE (dest) == SImode && valid_mov3q_const (src)) |
| return "mov3q%.l %1,%0"; |
| else if (src == 0 && ADDRESS_REG_P (dest)) |
| return "sub%.l %0,%0"; |
| else if (DATA_REG_P (dest)) |
| return output_move_const_into_data_reg (operands); |
| else if (ADDRESS_REG_P (dest) && IN_RANGE (src, -0x8000, 0x7fff)) |
| { |
| if (valid_mov3q_const (src)) |
| return "mov3q%.l %1,%0"; |
| return "move%.w %1,%0"; |
| } |
| else if (MEM_P (dest) |
| && GET_CODE (XEXP (dest, 0)) == PRE_DEC |
| && REGNO (XEXP (XEXP (dest, 0), 0)) == STACK_POINTER_REGNUM |
| && IN_RANGE (src, -0x8000, 0x7fff)) |
| { |
| if (valid_mov3q_const (src)) |
| return "mov3q%.l %1,%-"; |
| return "pea %a1"; |
| } |
| return "move%.l %1,%0"; |
| } |
| |
| const char * |
| output_move_simode (rtx *operands) |
| { |
| handle_flags_for_move (operands); |
| |
| if (GET_CODE (operands[1]) == CONST_INT) |
| return output_move_simode_const (operands); |
| else if ((GET_CODE (operands[1]) == SYMBOL_REF |
| || GET_CODE (operands[1]) == CONST) |
| && push_operand (operands[0], SImode)) |
| return "pea %a1"; |
| else if ((GET_CODE (operands[1]) == SYMBOL_REF |
| || GET_CODE (operands[1]) == CONST) |
| && ADDRESS_REG_P (operands[0])) |
| return "lea %a1,%0"; |
| return "move%.l %1,%0"; |
| } |
| |
| const char * |
| output_move_himode (rtx *operands) |
| { |
| if (GET_CODE (operands[1]) == CONST_INT) |
| { |
| if (operands[1] == const0_rtx |
| && (DATA_REG_P (operands[0]) |
| || GET_CODE (operands[0]) == MEM) |
| /* clr insns on 68000 read before writing. */ |
| && ((TARGET_68010 || TARGET_COLDFIRE) |
| || !(GET_CODE (operands[0]) == MEM |
| && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.w %0"; |
| else if (operands[1] == const0_rtx |
| && ADDRESS_REG_P (operands[0])) |
| return "sub%.l %0,%0"; |
| else if (DATA_REG_P (operands[0]) |
| && INTVAL (operands[1]) < 128 |
| && INTVAL (operands[1]) >= -128) |
| return "moveq %1,%0"; |
| else if (INTVAL (operands[1]) < 0x8000 |
| && INTVAL (operands[1]) >= -0x8000) |
| return "move%.w %1,%0"; |
| } |
| else if (CONSTANT_P (operands[1])) |
| gcc_unreachable (); |
| return "move%.w %1,%0"; |
| } |
| |
| const char * |
| output_move_qimode (rtx *operands) |
| { |
| handle_flags_for_move (operands); |
| |
| /* 68k family always modifies the stack pointer by at least 2, even for |
| byte pushes. The 5200 (ColdFire) does not do this. */ |
| |
| /* This case is generated by pushqi1 pattern now. */ |
| gcc_assert (!(GET_CODE (operands[0]) == MEM |
| && GET_CODE (XEXP (operands[0], 0)) == PRE_DEC |
| && XEXP (XEXP (operands[0], 0), 0) == stack_pointer_rtx |
| && ! ADDRESS_REG_P (operands[1]) |
| && ! TARGET_COLDFIRE)); |
| |
| /* clr and st insns on 68000 read before writing. */ |
| if (!ADDRESS_REG_P (operands[0]) |
| && ((TARGET_68010 || TARGET_COLDFIRE) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| { |
| if (operands[1] == const0_rtx) |
| return "clr%.b %0"; |
| if ((!TARGET_COLDFIRE || DATA_REG_P (operands[0])) |
| && GET_CODE (operands[1]) == CONST_INT |
| && (INTVAL (operands[1]) & 255) == 255) |
| { |
| CC_STATUS_INIT; |
| return "st %0"; |
| } |
| } |
| if (GET_CODE (operands[1]) == CONST_INT |
| && DATA_REG_P (operands[0]) |
| && INTVAL (operands[1]) < 128 |
| && INTVAL (operands[1]) >= -128) |
| return "moveq %1,%0"; |
| if (operands[1] == const0_rtx && ADDRESS_REG_P (operands[0])) |
| return "sub%.l %0,%0"; |
| if (GET_CODE (operands[1]) != CONST_INT && CONSTANT_P (operands[1])) |
| gcc_unreachable (); |
| /* 68k family (including the 5200 ColdFire) does not support byte moves to |
| from address registers. */ |
| if (ADDRESS_REG_P (operands[0]) || ADDRESS_REG_P (operands[1])) |
| { |
| if (ADDRESS_REG_P (operands[1])) |
| CC_STATUS_INIT; |
| return "move%.w %1,%0"; |
| } |
| return "move%.b %1,%0"; |
| } |
| |
| const char * |
| output_move_stricthi (rtx *operands) |
| { |
| if (operands[1] == const0_rtx |
| /* clr insns on 68000 read before writing. */ |
| && ((TARGET_68010 || TARGET_COLDFIRE) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.w %0"; |
| return "move%.w %1,%0"; |
| } |
| |
| const char * |
| output_move_strictqi (rtx *operands) |
| { |
| if (operands[1] == const0_rtx |
| /* clr insns on 68000 read before writing. */ |
| && ((TARGET_68010 || TARGET_COLDFIRE) |
| || !(GET_CODE (operands[0]) == MEM && MEM_VOLATILE_P (operands[0])))) |
| return "clr%.b %0"; |
| return "move%.b %1,%0"; |
| } |
| |
| /* Return the best assembler insn template |
| for moving operands[1] into operands[0] as a fullword. */ |
| |
| static const char * |
| singlemove_string (rtx *operands) |
| { |
| if (GET_CODE (operands[1]) == CONST_INT) |
| return output_move_simode_const (operands); |
| return "move%.l %1,%0"; |
| } |
| |
| |
| /* Output assembler or rtl code to perform a doubleword move insn |
| with operands OPERANDS. |
| Pointers to 3 helper functions should be specified: |
| HANDLE_REG_ADJUST to adjust a register by a small value, |
| HANDLE_COMPADR to compute an address and |
| HANDLE_MOVSI to move 4 bytes. */ |
| |
| static void |
| handle_move_double (rtx operands[2], |
| void (*handle_reg_adjust) (rtx, int), |
| void (*handle_compadr) (rtx [2]), |
| void (*handle_movsi) (rtx [2])) |
| { |
| enum |
| { |
| REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP |
| } optype0, optype1; |
| rtx latehalf[2]; |
| rtx middlehalf[2]; |
| rtx xops[2]; |
| rtx addreg0 = 0, addreg1 = 0; |
| int dest_overlapped_low = 0; |
| int size = GET_MODE_SIZE (GET_MODE (operands[0])); |
| |
| middlehalf[0] = 0; |
| middlehalf[1] = 0; |
| |
| /* First classify both operands. */ |
| |
| if (REG_P (operands[0])) |
| optype0 = REGOP; |
| else if (offsettable_memref_p (operands[0])) |
| optype0 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[0], 0)) == POST_INC) |
| optype0 = POPOP; |
| else if (GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) |
| optype0 = PUSHOP; |
| else if (GET_CODE (operands[0]) == MEM) |
| optype0 = MEMOP; |
| else |
| optype0 = RNDOP; |
| |
| if (REG_P (operands[1])) |
| optype1 = REGOP; |
| else if (CONSTANT_P (operands[1])) |
| optype1 = CNSTOP; |
| else if (offsettable_memref_p (operands[1])) |
| optype1 = OFFSOP; |
| else if (GET_CODE (XEXP (operands[1], 0)) == POST_INC) |
| optype1 = POPOP; |
| else if (GET_CODE (XEXP (operands[1], 0)) == PRE_DEC) |
| optype1 = PUSHOP; |
| else if (GET_CODE (operands[1]) == MEM) |
| optype1 = MEMOP; |
| else |
| optype1 = RNDOP; |
| |
| /* Check for the cases that the operand constraints are not supposed |
| to allow to happen. Generating code for these cases is |
| painful. */ |
| gcc_assert (optype0 != RNDOP && optype1 != RNDOP); |
| |
| /* If one operand is decrementing and one is incrementing |
| decrement the former register explicitly |
| and change that operand into ordinary indexing. */ |
| |
| if (optype0 == PUSHOP && optype1 == POPOP) |
| { |
| operands[0] = XEXP (XEXP (operands[0], 0), 0); |
| |
| handle_reg_adjust (operands[0], -size); |
| |
| if (GET_MODE (operands[1]) == XFmode) |
| operands[0] = gen_rtx_MEM (XFmode, operands[0]); |
| else if (GET_MODE (operands[0]) == DFmode) |
| operands[0] = gen_rtx_MEM (DFmode, operands[0]); |
| else |
| operands[0] = gen_rtx_MEM (DImode, operands[0]); |
| optype0 = OFFSOP; |
| } |
| if (optype0 == POPOP && optype1 == PUSHOP) |
| { |
| operands[1] = XEXP (XEXP (operands[1], 0), 0); |
| |
| handle_reg_adjust (operands[1], -size); |
| |
| if (GET_MODE (operands[1]) == XFmode) |
| operands[1] = gen_rtx_MEM (XFmode, operands[1]); |
| else if (GET_MODE (operands[1]) == DFmode) |
| operands[1] = gen_rtx_MEM (DFmode, operands[1]); |
| else |
| operands[1] = gen_rtx_MEM (DImode, operands[1]); |
| optype1 = OFFSOP; |
| } |
| |
| /* If an operand is an unoffsettable memory ref, find a register |
| we can increment temporarily to make it refer to the second word. */ |
| |
| if (optype0 == MEMOP) |
| addreg0 = find_addr_reg (XEXP (operands[0], 0)); |
| |
| if (optype1 == MEMOP) |
| addreg1 = find_addr_reg (XEXP (operands[1], 0)); |
| |
| /* Ok, we can do one word at a time. |
| Normally we do the low-numbered word first, |
| but if either operand is autodecrementing then we |
| do the high-numbered word first. |
| |
| In either case, set up in LATEHALF the operands to use |
| for the high-numbered word and in some cases alter the |
| operands in OPERANDS to be suitable for the low-numbered word. */ |
| |
| if (size == 12) |
| { |
| if (optype0 == REGOP) |
| { |
| latehalf[0] = gen_rtx_REG (SImode, REGNO (operands[0]) + 2); |
| middlehalf[0] = gen_rtx_REG (SImode, REGNO (operands[0]) + 1); |
| } |
| else if (optype0 == OFFSOP) |
| { |
| middlehalf[0] = adjust_address (operands[0], SImode, 4); |
| latehalf[0] = adjust_address (operands[0], SImode, size - 4); |
| } |
| else |
| { |
| middlehalf[0] = adjust_address (operands[0], SImode, 0); |
| latehalf[0] = adjust_address (operands[0], SImode, 0); |
| } |
| |
| if (optype1 == REGOP) |
| { |
| latehalf[1] = gen_rtx_REG (SImode, REGNO (operands[1]) + 2); |
| middlehalf[1] = gen_rtx_REG (SImode, REGNO (operands[1]) + 1); |
| } |
| else if (optype1 == OFFSOP) |
| { |
| middlehalf[1] = adjust_address (operands[1], SImode, 4); |
| latehalf[1] = adjust_address (operands[1], SImode, size - 4); |
| } |
| else if (optype1 == CNSTOP) |
| { |
| if (GET_CODE (operands[1]) == CONST_DOUBLE) |
| { |
| long l[3]; |
| |
| REAL_VALUE_TO_TARGET_LONG_DOUBLE |
| (*CONST_DOUBLE_REAL_VALUE (operands[1]), l); |
| operands[1] = GEN_INT (l[0]); |
| middlehalf[1] = GEN_INT (l[1]); |
| latehalf[1] = GEN_INT (l[2]); |
| } |
| else |
| { |
| /* No non-CONST_DOUBLE constant should ever appear |
| here. */ |
| gcc_assert (!CONSTANT_P (operands[1])); |
| } |
| } |
| else |
| { |
| middlehalf[1] = adjust_address (operands[1], SImode, 0); |
| latehalf[1] = adjust_address (operands[1], SImode, 0); |
| } |
| } |
| else |
| /* size is not 12: */ |
| { |
| if (optype0 == REGOP) |
| latehalf[0] = gen_rtx_REG (SImode, REGNO (operands[0]) + 1); |
| else if (optype0 == OFFSOP) |
| latehalf[0] = adjust_address (operands[0], SImode, size - 4); |
| else |
| latehalf[0] = adjust_address (operands[0], SImode, 0); |
| |
| if (optype1 == REGOP) |
| latehalf[1] = gen_rtx_REG (SImode, REGNO (operands[1]) + 1); |
| else if (optype1 == OFFSOP) |
| latehalf[1] = adjust_address (operands[1], SImode, size - 4); |
| else if (optype1 == CNSTOP) |
| split_double (operands[1], &operands[1], &latehalf[1]); |
| else |
| latehalf[1] = adjust_address (operands[1], SImode, 0); |
| } |
| |
| /* If insn is effectively movd N(REG),-(REG) then we will do the high |
| word first. We should use the adjusted operand 1 (which is N+4(REG)) |
| for the low word as well, to compensate for the first decrement of |
| REG. */ |
| if (optype0 == PUSHOP |
| && reg_overlap_mentioned_p (XEXP (XEXP (operands[0], 0), 0), operands[1])) |
| operands[1] = middlehalf[1] = latehalf[1]; |
| |
| /* For (set (reg:DI N) (mem:DI ... (reg:SI N) ...)), |
| if the upper part of reg N does not appear in the MEM, arrange to |
| emit the move late-half first. Otherwise, compute the MEM address |
| into the upper part of N and use that as a pointer to the memory |
| operand. */ |
| if (optype0 == REGOP |
| && (optype1 == OFFSOP || optype1 == MEMOP)) |
| { |
| rtx testlow = gen_rtx_REG (SImode, REGNO (operands[0])); |
| |
| if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0)) |
| && reg_overlap_mentioned_p (latehalf[0], XEXP (operands[1], 0))) |
| { |
| /* If both halves of dest are used in the src memory address, |
| compute the address into latehalf of dest. |
| Note that this can't happen if the dest is two data regs. */ |
| compadr: |
| xops[0] = latehalf[0]; |
| xops[1] = XEXP (operands[1], 0); |
| |
| handle_compadr (xops); |
| if (GET_MODE (operands[1]) == XFmode) |
| { |
| operands[1] = gen_rtx_MEM (XFmode, latehalf[0]); |
| middlehalf[1] = adjust_address (operands[1], DImode, size - 8); |
| latehalf[1] = adjust_address (operands[1], DImode, size - 4); |
| } |
| else |
| { |
| operands[1] = gen_rtx_MEM (DImode, latehalf[0]); |
| latehalf[1] = adjust_address (operands[1], DImode, size - 4); |
| } |
| } |
| else if (size == 12 |
| && reg_overlap_mentioned_p (middlehalf[0], |
| XEXP (operands[1], 0))) |
| { |
| /* Check for two regs used by both source and dest. |
| Note that this can't happen if the dest is all data regs. |
| It can happen if the dest is d6, d7, a0. |
| But in that case, latehalf is an addr reg, so |
| the code at compadr does ok. */ |
| |
| if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0)) |
| || reg_overlap_mentioned_p (latehalf[0], XEXP (operands[1], 0))) |
| goto compadr; |
| |
| /* JRV says this can't happen: */ |
| gcc_assert (!addreg0 && !addreg1); |
| |
| /* Only the middle reg conflicts; simply put it last. */ |
| handle_movsi (operands); |
| handle_movsi (latehalf); |
| handle_movsi (middlehalf); |
| |
| return; |
| } |
| else if (reg_overlap_mentioned_p (testlow, XEXP (operands[1], 0))) |
| /* If the low half of dest is mentioned in the source memory |
| address, the arrange to emit the move late half first. */ |
| dest_overlapped_low = 1; |
| } |
| |
| /* If one or both operands autodecrementing, |
| do the two words, high-numbered first. */ |
| |
| /* Likewise, the first move would clobber the source of the second one, |
| do them in the other order. This happens only for registers; |
| such overlap can't happen in memory unless the user explicitly |
| sets it up, and that is an undefined circumstance. */ |
| |
| if (optype0 == PUSHOP || optype1 == PUSHOP |
| || (optype0 == REGOP && optype1 == REGOP |
| && ((middlehalf[1] && REGNO (operands[0]) == REGNO (middlehalf[1])) |
| || REGNO (operands[0]) == REGNO (latehalf[1]))) |
| || dest_overlapped_low) |
| { |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| handle_reg_adjust (addreg0, size - 4); |
| if (addreg1) |
| handle_reg_adjust (addreg1, size - 4); |
| |
| /* Do that word. */ |
| handle_movsi (latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| handle_reg_adjust (addreg0, -4); |
| if (addreg1) |
| handle_reg_adjust (addreg1, -4); |
| |
| if (size == 12) |
| { |
| handle_movsi (middlehalf); |
| |
| if (addreg0) |
| handle_reg_adjust (addreg0, -4); |
| if (addreg1) |
| handle_reg_adjust (addreg1, -4); |
| } |
| |
| /* Do low-numbered word. */ |
| |
| handle_movsi (operands); |
| return; |
| } |
| |
| /* Normal case: do the two words, low-numbered first. */ |
| |
| handle_movsi (operands); |
| |
| /* Do the middle one of the three words for long double */ |
| if (size == 12) |
| { |
| if (addreg0) |
| handle_reg_adjust (addreg0, 4); |
| if (addreg1) |
| handle_reg_adjust (addreg1, 4); |
| |
| handle_movsi (middlehalf); |
| } |
| |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| handle_reg_adjust (addreg0, 4); |
| if (addreg1) |
| handle_reg_adjust (addreg1, 4); |
| |
| /* Do that word. */ |
| handle_movsi (latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| handle_reg_adjust (addreg0, -(size - 4)); |
| if (addreg1) |
| handle_reg_adjust (addreg1, -(size - 4)); |
| |
| return; |
| } |
| |
| /* Output assembler code to adjust REG by N. */ |
| static void |
| output_reg_adjust (rtx reg, int n) |
| { |
| const char *s; |
| |
| gcc_assert (GET_MODE (reg) == SImode && n >= -12 && n != 0 && n <= 12); |
| |
| switch (n) |
| { |
| case 12: |
| s = "add%.l #12,%0"; |
| break; |
| |
| case 8: |
| s = "addq%.l #8,%0"; |
| break; |
| |
| case 4: |
| s = "addq%.l #4,%0"; |
| break; |
| |
| case -12: |
| s = "sub%.l #12,%0"; |
| break; |
| |
| case -8: |
| s = "subq%.l #8,%0"; |
| break; |
| |
| case -4: |
| s = "subq%.l #4,%0"; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| s = NULL; |
| } |
| |
| output_asm_insn (s, ®); |
| } |
| |
| /* Emit rtl code to adjust REG by N. */ |
| static void |
| emit_reg_adjust (rtx reg1, int n) |
| { |
| rtx reg2; |
| |
| gcc_assert (GET_MODE (reg1) == SImode && n >= -12 && n != 0 && n <= 12); |
| |
| reg1 = copy_rtx (reg1); |
| reg2 = copy_rtx (reg1); |
| |
| if (n < 0) |
| emit_insn (gen_subsi3 (reg1, reg2, GEN_INT (-n))); |
| else if (n > 0) |
| emit_insn (gen_addsi3 (reg1, reg2, GEN_INT (n))); |
| else |
| gcc_unreachable (); |
| } |
| |
| /* Output assembler to load address OPERANDS[0] to register OPERANDS[1]. */ |
| static void |
| output_compadr (rtx operands[2]) |
| { |
| output_asm_insn ("lea %a1,%0", operands); |
| } |
| |
| /* Output the best assembler insn for moving operands[1] into operands[0] |
| as a fullword. */ |
| static void |
| output_movsi (rtx operands[2]) |
| { |
| output_asm_insn (singlemove_string (operands), operands); |
| } |
| |
| /* Copy OP and change its mode to MODE. */ |
| static rtx |
| copy_operand (rtx op, machine_mode mode) |
| { |
| /* ??? This looks really ugly. There must be a better way |
| to change a mode on the operand. */ |
| if (GET_MODE (op) != VOIDmode) |
| { |
| if (REG_P (op)) |
| op = gen_rtx_REG (mode, REGNO (op)); |
| else |
| { |
| op = copy_rtx (op); |
| PUT_MODE (op, mode); |
| } |
| } |
| |
| return op; |
| } |
| |
| /* Emit rtl code for moving operands[1] into operands[0] as a fullword. */ |
| static void |
| emit_movsi (rtx operands[2]) |
| { |
| operands[0] = copy_operand (operands[0], SImode); |
| operands[1] = copy_operand (operands[1], SImode); |
| |
| emit_insn (gen_movsi (operands[0], operands[1])); |
| } |
| |
| /* Output assembler code to perform a doubleword move insn |
| with operands OPERANDS. */ |
| const char * |
| output_move_double (rtx *operands) |
| { |
| handle_move_double (operands, |
| output_reg_adjust, output_compadr, output_movsi); |
| |
| return ""; |
| } |
| |
| /* Output rtl code to perform a doubleword move insn |
| with operands OPERANDS. */ |
| void |
| m68k_emit_move_double (rtx operands[2]) |
| { |
| handle_move_double (operands, emit_reg_adjust, emit_movsi, emit_movsi); |
| } |
| |
| /* Ensure mode of ORIG, a REG rtx, is MODE. Returns either ORIG or a |
| new rtx with the correct mode. */ |
| |
| static rtx |
| force_mode (machine_mode mode, rtx orig) |
| { |
| if (mode == GET_MODE (orig)) |
| return orig; |
| |
| if (REGNO (orig) >= FIRST_PSEUDO_REGISTER) |
| abort (); |
| |
| return gen_rtx_REG (mode, REGNO (orig)); |
| } |
| |
| static int |
| fp_reg_operand (rtx op, machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return reg_renumber && FP_REG_P (op); |
| } |
| |
| /* Emit insns to move operands[1] into operands[0]. |
| |
| Return 1 if we have written out everything that needs to be done to |
| do the move. Otherwise, return 0 and the caller will emit the move |
| normally. |
| |
| Note SCRATCH_REG may not be in the proper mode depending on how it |
| will be used. This routine is responsible for creating a new copy |
| of SCRATCH_REG in the proper mode. */ |
| |
| int |
| emit_move_sequence (rtx *operands, machine_mode mode, rtx scratch_reg) |
| { |
| rtx operand0 = operands[0]; |
| rtx operand1 = operands[1]; |
| rtx tem; |
| |
| if (scratch_reg |
| && reload_in_progress && GET_CODE (operand0) == REG |
| && REGNO (operand0) >= FIRST_PSEUDO_REGISTER) |
| operand0 = reg_equiv_mem (REGNO (operand0)); |
| else if (scratch_reg |
| && reload_in_progress && GET_CODE (operand0) == SUBREG |
| && GET_CODE (SUBREG_REG (operand0)) == REG |
| && REGNO (SUBREG_REG (operand0)) >= FIRST_PSEUDO_REGISTER) |
| { |
| /* We must not alter SUBREG_BYTE (operand0) since that would confuse |
| the code which tracks sets/uses for delete_output_reload. */ |
| rtx temp = gen_rtx_SUBREG (GET_MODE (operand0), |
| reg_equiv_mem (REGNO (SUBREG_REG (operand0))), |
| SUBREG_BYTE (operand0)); |
| operand0 = alter_subreg (&temp, true); |
| } |
| |
| if (scratch_reg |
| && reload_in_progress && GET_CODE (operand1) == REG |
| && REGNO (operand1) >= FIRST_PSEUDO_REGISTER) |
| operand1 = reg_equiv_mem (REGNO (operand1)); |
| else if (scratch_reg |
| && reload_in_progress && GET_CODE (operand1) == SUBREG |
| && GET_CODE (SUBREG_REG (operand1)) == REG |
| && REGNO (SUBREG_REG (operand1)) >= FIRST_PSEUDO_REGISTER) |
| { |
| /* We must not alter SUBREG_BYTE (operand0) since that would confuse |
| the code which tracks sets/uses for delete_output_reload. */ |
| rtx temp = gen_rtx_SUBREG (GET_MODE (operand1), |
| reg_equiv_mem (REGNO (SUBREG_REG (operand1))), |
| SUBREG_BYTE (operand1)); |
| operand1 = alter_subreg (&temp, true); |
| } |
| |
| if (scratch_reg && reload_in_progress && GET_CODE (operand0) == MEM |
| && ((tem = find_replacement (&XEXP (operand0, 0))) |
| != XEXP (operand0, 0))) |
| operand0 = gen_rtx_MEM (GET_MODE (operand0), tem); |
| if (scratch_reg && reload_in_progress && GET_CODE (operand1) == MEM |
| && ((tem = find_replacement (&XEXP (operand1, 0))) |
| != XEXP (operand1, 0))) |
| operand1 = gen_rtx_MEM (GET_MODE (operand1), tem); |
| |
| /* Handle secondary reloads for loads/stores of FP registers where |
| the address is symbolic by using the scratch register */ |
| if (fp_reg_operand (operand0, mode) |
| && ((GET_CODE (operand1) == MEM |
| && ! memory_address_p (DFmode, XEXP (operand1, 0))) |
| || ((GET_CODE (operand1) == SUBREG |
| && GET_CODE (XEXP (operand1, 0)) == MEM |
| && !memory_address_p (DFmode, XEXP (XEXP (operand1, 0), 0))))) |
| && scratch_reg) |
| { |
| if (GET_CODE (operand1) == SUBREG) |
| operand1 = XEXP (operand1, 0); |
| |
| /* SCRATCH_REG will hold an address. We want |
| it in SImode regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (SImode, scratch_reg); |
| |
| /* D might not fit in 14 bits either; for such cases load D into |
| scratch reg. */ |
| if (!memory_address_p (Pmode, XEXP (operand1, 0))) |
| { |
| emit_move_insn (scratch_reg, XEXP (XEXP (operand1, 0), 1)); |
| emit_move_insn (scratch_reg, gen_rtx_fmt_ee (GET_CODE (XEXP (operand1, 0)), |
| Pmode, |
| XEXP (XEXP (operand1, 0), 0), |
| scratch_reg)); |
| } |
| else |
| emit_move_insn (scratch_reg, XEXP (operand1, 0)); |
| emit_insn (gen_rtx_SET (operand0, gen_rtx_MEM (mode, scratch_reg))); |
| return 1; |
| } |
| else if (fp_reg_operand (operand1, mode) |
| && ((GET_CODE (operand0) == MEM |
| && ! memory_address_p (DFmode, XEXP (operand0, 0))) |
| || ((GET_CODE (operand0) == SUBREG) |
| && GET_CODE (XEXP (operand0, 0)) == MEM |
| && !memory_address_p (DFmode, XEXP (XEXP (operand0, 0), 0)))) |
| && scratch_reg) |
| { |
| if (GET_CODE (operand0) == SUBREG) |
| operand0 = XEXP (operand0, 0); |
| |
| /* SCRATCH_REG will hold an address and maybe the actual data. We want |
| it in SIMODE regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (SImode, scratch_reg); |
| |
| /* D might not fit in 14 bits either; for such cases load D into |
| scratch reg. */ |
| if (!memory_address_p (Pmode, XEXP (operand0, 0))) |
| { |
| emit_move_insn (scratch_reg, XEXP (XEXP (operand0, 0), 1)); |
| emit_move_insn (scratch_reg, gen_rtx_fmt_ee (GET_CODE (XEXP (operand0, |
| 0)), |
| Pmode, |
| XEXP (XEXP (operand0, 0), |
| 0), |
| scratch_reg)); |
| } |
| else |
| emit_move_insn (scratch_reg, XEXP (operand0, 0)); |
| emit_insn (gen_rtx_SET (gen_rtx_MEM (mode, scratch_reg), operand1)); |
| return 1; |
| } |
| /* Handle secondary reloads for loads of FP registers from constant |
| expressions by forcing the constant into memory. |
| |
| use scratch_reg to hold the address of the memory location. |
| |
| The proper fix is to change PREFERRED_RELOAD_CLASS to return |
| NO_REGS when presented with a const_int and an register class |
| containing only FP registers. Doing so unfortunately creates |
| more problems than it solves. Fix this for 2.5. */ |
| else if (fp_reg_operand (operand0, mode) |
| && CONSTANT_P (operand1) |
| && scratch_reg) |
| { |
| rtx xoperands[2]; |
| |
| /* SCRATCH_REG will hold an address and maybe the actual data. We want |
| it in SIMODE regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (SImode, scratch_reg); |
| |
| /* Force the constant into memory and put the address of the |
| memory location into scratch_reg. */ |
| xoperands[0] = scratch_reg; |
| xoperands[1] = XEXP (force_const_mem (mode, operand1), 0); |
| emit_insn (gen_rtx_SET (scratch_reg, xoperands[1])); |
| |
| /* Now load the destination register. */ |
| emit_insn (gen_rtx_SET (operand0, gen_rtx_MEM (mode, scratch_reg))); |
| return 1; |
| } |
| |
| /* Now have insn-emit do whatever it normally does. */ |
| return 0; |
| } |
| |
| /* 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]; |
| |
| /* simplify_subreg refuses to split volatile memory addresses, |
| but we still have to handle it. */ |
| if (GET_CODE (op) == MEM) |
| { |
| lo_half[num] = adjust_address (op, SImode, 4); |
| hi_half[num] = adjust_address (op, SImode, 0); |
| } |
| else |
| { |
| lo_half[num] = simplify_gen_subreg (SImode, op, |
| GET_MODE (op) == VOIDmode |
| ? DImode : GET_MODE (op), 4); |
| hi_half[num] = simplify_gen_subreg (SImode, op, |
| GET_MODE (op) == VOIDmode |
| ? DImode : GET_MODE (op), 0); |
| } |
| } |
| } |
| |
| /* Split X into a base and a constant offset, storing them in *BASE |
| and *OFFSET respectively. */ |
| |
| static void |
| m68k_split_offset (rtx x, rtx *base, HOST_WIDE_INT *offset) |
| { |
| *offset = 0; |
| if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == CONST_INT) |
| { |
| *offset += INTVAL (XEXP (x, 1)); |
| x = XEXP (x, 0); |
| } |
| *base = x; |
| } |
| |
| /* Return true if PATTERN is a PARALLEL suitable for a movem or fmovem |
| instruction. STORE_P says whether the move is a load or store. |
| |
| If the instruction uses post-increment or pre-decrement addressing, |
| AUTOMOD_BASE is the base register and AUTOMOD_OFFSET is the total |
| adjustment. This adjustment will be made by the first element of |
| PARALLEL, with the loads or stores starting at element 1. If the |
| instruction does not use post-increment or pre-decrement addressing, |
| AUTOMOD_BASE is null, AUTOMOD_OFFSET is 0, and the loads or stores |
| start at element 0. */ |
| |
| bool |
| m68k_movem_pattern_p (rtx pattern, rtx automod_base, |
| HOST_WIDE_INT automod_offset, bool store_p) |
| { |
| rtx base, mem_base, set, mem, reg, last_reg; |
| HOST_WIDE_INT offset, mem_offset; |
| int i, first, len; |
| enum reg_class rclass; |
| |
| len = XVECLEN (pattern, 0); |
| first = (automod_base != NULL); |
| |
| if (automod_base) |
| { |
| /* Stores must be pre-decrement and loads must be post-increment. */ |
| if (store_p != (automod_offset < 0)) |
| return false; |
| |
| /* Work out the base and offset for lowest memory location. */ |
| base = automod_base; |
| offset = (automod_offset < 0 ? automod_offset : 0); |
| } |
| else |
| { |
| /* Allow any valid base and offset in the first access. */ |
| base = NULL; |
| offset = 0; |
| } |
| |
| last_reg = NULL; |
| rclass = NO_REGS; |
| for (i = first; i < len; i++) |
| { |
| /* We need a plain SET. */ |
| set = XVECEXP (pattern, 0, i); |
| if (GET_CODE (set) != SET) |
| return false; |
| |
| /* Check that we have a memory location... */ |
| mem = XEXP (set, !store_p); |
| if (!MEM_P (mem) || !memory_operand (mem, VOIDmode)) |
| return false; |
| |
| /* ...with the right address. */ |
| if (base == NULL) |
| { |
| m68k_split_offset (XEXP (mem, 0), &base, &offset); |
| /* The ColdFire instruction only allows (An) and (d16,An) modes. |
| There are no mode restrictions for 680x0 besides the |
| automodification rules enforced above. */ |
| if (TARGET_COLDFIRE |
| && !m68k_legitimate_base_reg_p (base, reload_completed)) |
| return false; |
| } |
| else |
| { |
| m68k_split_offset (XEXP (mem, 0), &mem_base, &mem_offset); |
| if (!rtx_equal_p (base, mem_base) || offset != mem_offset) |
| return false; |
| } |
| |
| /* Check that we have a register of the required mode and class. */ |
| reg = XEXP (set, store_p); |
| if (!REG_P (reg) |
| || !HARD_REGISTER_P (reg) |
| || GET_MODE (reg) != reg_raw_mode[REGNO (reg)]) |
| return false; |
| |
| if (last_reg) |
| { |
| /* The register must belong to RCLASS and have a higher number |
| than the register in the previous SET. */ |
| if (!TEST_HARD_REG_BIT (reg_class_contents[rclass], REGNO (reg)) |
| || REGNO (last_reg) >= REGNO (reg)) |
| return false; |
| } |
| else |
| { |
| /* Work out which register class we need. */ |
| if (INT_REGNO_P (REGNO (reg))) |
| rclass = GENERAL_REGS; |
| else if (FP_REGNO_P (REGNO (reg))) |
| rclass = FP_REGS; |
| else |
| return false; |
| } |
| |
| last_reg = reg; |
| offset += GET_MODE_SIZE (GET_MODE (reg)); |
| } |
| |
| /* If we have an automodification, check whether the final offset is OK. */ |
| if (automod_base && offset != (automod_offset < 0 ? 0 : automod_offset)) |
| return false; |
| |
| /* Reject unprofitable cases. */ |
| if (len < first + (rclass == FP_REGS ? MIN_FMOVEM_REGS : MIN_MOVEM_REGS)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return the assembly code template for a movem or fmovem instruction |
| whose pattern is given by PATTERN. Store the template's operands |
| in OPERANDS. |
| |
| If the instruction uses post-increment or pre-decrement addressing, |
| AUTOMOD_OFFSET is the total adjustment, otherwise it is 0. STORE_P |
| is true if this is a store instruction. */ |
| |
| const char * |
| m68k_output_movem (rtx *operands, rtx pattern, |
| HOST_WIDE_INT automod_offset, bool store_p) |
| { |
| unsigned int mask; |
| int i, first; |
| |
| gcc_assert (GET_CODE (pattern) == PARALLEL); |
| mask = 0; |
| first = (automod_offset != 0); |
| for (i = first; i < XVECLEN (pattern, 0); i++) |
| { |
| /* When using movem with pre-decrement addressing, register X + D0_REG |
| is controlled by bit 15 - X. For all other addressing modes, |
| register X + D0_REG is controlled by bit X. Confusingly, the |
| register mask for fmovem is in the opposite order to that for |
| movem. */ |
| unsigned int regno; |
| |
| gcc_assert (MEM_P (XEXP (XVECEXP (pattern, 0, i), !store_p))); |
| gcc_assert (REG_P (XEXP (XVECEXP (pattern, 0, i), store_p))); |
| regno = REGNO (XEXP (XVECEXP (pattern, 0, i), store_p)); |
| if (automod_offset < 0) |
| { |
| if (FP_REGNO_P (regno)) |
| mask |= 1 << (regno - FP0_REG); |
| else |
| mask |= 1 << (15 - (regno - D0_REG)); |
| } |
| else |
| { |
| if (FP_REGNO_P (regno)) |
| mask |= 1 << (7 - (regno - FP0_REG)); |
| else |
| mask |= 1 << (regno - D0_REG); |
| } |
| } |
| CC_STATUS_INIT; |
| |
| if (automod_offset == 0) |
| operands[0] = XEXP (XEXP (XVECEXP (pattern, 0, first), !store_p), 0); |
| else if (automod_offset < 0) |
| operands[0] = gen_rtx_PRE_DEC (Pmode, SET_DEST (XVECEXP (pattern, 0, 0))); |
| else |
| operands[0] = gen_rtx_POST_INC (Pmode, SET_DEST (XVECEXP (pattern, 0, 0))); |
| operands[1] = GEN_INT (mask); |
| if (FP_REGNO_P (REGNO (XEXP (XVECEXP (pattern, 0, first), store_p)))) |
| { |
| if (store_p) |
| return "fmovem %1,%a0"; |
| else |
| return "fmovem %a0,%1"; |
| } |
| else |
| { |
| if (store_p) |
| return "movem%.l %1,%a0"; |
| else |
| return "movem%.l %a0,%1"; |
| } |
| } |
| |
| /* Return a REG that occurs in ADDR with coefficient 1. |
| ADDR can be effectively incremented by incrementing REG. */ |
| |
| static rtx |
| find_addr_reg (rtx addr) |
| { |
| while (GET_CODE (addr) == PLUS) |
| { |
| if (GET_CODE (XEXP (addr, 0)) == REG) |
| addr = XEXP (addr, 0); |
| else if (GET_CODE (XEXP (addr, 1)) == REG) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 0))) |
| addr = XEXP (addr, 1); |
| else if (CONSTANT_P (XEXP (addr, 1))) |
| addr = XEXP (addr, 0); |
| else |
| gcc_unreachable (); |
| } |
| gcc_assert (GET_CODE (addr) == REG); |
| return addr; |
| } |
| |
| /* Output assembler code to perform a 32-bit 3-operand add. */ |
| |
| const char * |
| output_addsi3 (rtx *operands) |
| { |
| if (! operands_match_p (operands[0], operands[1])) |
| { |
| if (!ADDRESS_REG_P (operands[1])) |
| { |
| rtx tmp = operands[1]; |
| |
| operands[1] = operands[2]; |
| operands[2] = tmp; |
| } |
| |
| /* These insns can result from reloads to access |
| stack slots over 64k from the frame pointer. */ |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (INTVAL (operands[2]) < -32768 || INTVAL (operands[2]) > 32767)) |
| return "move%.l %2,%0\n\tadd%.l %1,%0"; |
| if (GET_CODE (operands[2]) == REG) |
| return MOTOROLA ? "lea (%1,%2.l),%0" : "lea %1@(0,%2:l),%0"; |
| return MOTOROLA ? "lea (%c2,%1),%0" : "lea %1@(%c2),%0"; |
| } |
| if (GET_CODE (operands[2]) == CONST_INT) |
| { |
| if (INTVAL (operands[2]) > 0 |
| && INTVAL (operands[2]) <= 8) |
| return "addq%.l %2,%0"; |
| if (INTVAL (operands[2]) < 0 |
| && INTVAL (operands[2]) >= -8) |
| { |
| operands[2] = GEN_INT (- INTVAL (operands[2])); |
| return "subq%.l %2,%0"; |
| } |
| /* On the CPU32 it is faster to use two addql instructions to |
| add a small integer (8 < N <= 16) to a register. |
| Likewise for subql. */ |
| if (TUNE_CPU32 && REG_P (operands[0])) |
| { |
| if (INTVAL (operands[2]) > 8 |
| && INTVAL (operands[2]) <= 16) |
| { |
| operands[2] = GEN_INT (INTVAL (operands[2]) - 8); |
| return "addq%.l #8,%0\n\taddq%.l %2,%0"; |
| } |
| if (INTVAL (operands[2]) < -8 |
| && INTVAL (operands[2]) >= -16) |
| { |
| operands[2] = GEN_INT (- INTVAL (operands[2]) - 8); |
| return "subq%.l #8,%0\n\tsubq%.l %2,%0"; |
| } |
| } |
| if (ADDRESS_REG_P (operands[0]) |
| && INTVAL (operands[2]) >= -0x8000 |
| && INTVAL (operands[2]) < 0x8000) |
| { |
| if (TUNE_68040) |
| return "add%.w %2,%0"; |
| else |
| return MOTOROLA ? "lea (%c2,%0),%0" : "lea %0@(%c2),%0"; |
| } |
| } |
| return "add%.l %2,%0"; |
| } |
| |
| /* Emit a comparison between OP0 and OP1. Return true iff the comparison |
| was reversed. SC1 is an SImode scratch reg, and SC2 a DImode scratch reg, |
| as needed. CODE is the code of the comparison, we return it unchanged or |
| swapped, as necessary. */ |
| rtx_code |
| m68k_output_compare_di (rtx op0, rtx op1, rtx sc1, rtx sc2, rtx_insn *insn, |
| rtx_code code) |
| { |
| rtx ops[4]; |
| ops[0] = op0; |
| ops[1] = op1; |
| ops[2] = sc1; |
| ops[3] = sc2; |
| if (op1 == const0_rtx) |
| { |
| if (!REG_P (op0) || ADDRESS_REG_P (op0)) |
| { |
| rtx xoperands[2]; |
| |
| xoperands[0] = sc2; |
| xoperands[1] = op0; |
| output_move_double (xoperands); |
| output_asm_insn ("neg%.l %R0\n\tnegx%.l %0", xoperands); |
| return swap_condition (code); |
| } |
| if (find_reg_note (insn, REG_DEAD, op0)) |
| { |
| output_asm_insn ("neg%.l %R0\n\tnegx%.l %0", ops); |
| return swap_condition (code); |
| } |
| else |
| { |
| /* 'sub' clears %1, and also clears the X cc bit. |
| 'tst' sets the Z cc bit according to the low part of the DImode |
| operand. |
| 'subx %1' (i.e. subx #0) acts as a (non-existent) tstx on the high |
| part. */ |
| output_asm_insn ("sub%.l %2,%2\n\ttst%.l %R0\n\tsubx%.l %2,%0", ops); |
| return code; |
| } |
| } |
| |
| if (rtx_equal_p (sc2, op0)) |
| { |
| output_asm_insn ("sub%.l %R1,%R3\n\tsubx%.l %1,%3", ops); |
| return code; |
| } |
| else |
| { |
| output_asm_insn ("sub%.l %R0,%R3\n\tsubx%.l %0,%3", ops); |
| return swap_condition (code); |
| } |
| } |
| |
| static void |
| remember_compare_flags (rtx op0, rtx op1) |
| { |
| if (side_effects_p (op0) || side_effects_p (op1)) |
| CC_STATUS_INIT; |
| else |
| { |
| flags_compare_op0 = op0; |
| flags_compare_op1 = op1; |
| flags_operand1 = flags_operand2 = NULL_RTX; |
| flags_valid = FLAGS_VALID_SET; |
| } |
| } |
| |
| /* Emit a comparison between OP0 and OP1. CODE is the code of the |
| comparison. It is returned, potentially modified if necessary. */ |
| rtx_code |
| m68k_output_compare_si (rtx op0, rtx op1, rtx_code code) |
| { |
| rtx_code tmp = m68k_find_flags_value (op0, op1, code); |
| if (tmp != UNKNOWN) |
| return tmp; |
| |
| remember_compare_flags (op0, op1); |
| |
| rtx ops[2]; |
| ops[0] = op0; |
| ops[1] = op1; |
| if (op1 == const0_rtx && (TARGET_68020 || TARGET_COLDFIRE || !ADDRESS_REG_P (op0))) |
| output_asm_insn ("tst%.l %0", ops); |
| else if (GET_CODE (op0) == MEM && GET_CODE (op1) == MEM) |
| output_asm_insn ("cmpm%.l %1,%0", ops); |
| else if (REG_P (op1) |
| || (!REG_P (op0) && GET_CODE (op0) != MEM)) |
| { |
| output_asm_insn ("cmp%.l %d0,%d1", ops); |
| std::swap (flags_compare_op0, flags_compare_op1); |
| return swap_condition (code); |
| } |
| else if (!TARGET_COLDFIRE |
| && ADDRESS_REG_P (op0) |
| && GET_CODE (op1) == CONST_INT |
| && INTVAL (op1) < 0x8000 |
| && INTVAL (op1) >= -0x8000) |
| output_asm_insn ("cmp%.w %1,%0", ops); |
| else |
| output_asm_insn ("cmp%.l %d1,%d0", ops); |
| return code; |
| } |
| |
| /* Emit a comparison between OP0 and OP1. CODE is the code of the |
| comparison. It is returned, potentially modified if necessary. */ |
| rtx_code |
| m68k_output_compare_hi (rtx op0, rtx op1, rtx_code code) |
| { |
| rtx_code tmp = m68k_find_flags_value (op0, op1, code); |
| if (tmp != UNKNOWN) |
| return tmp; |
| |
| remember_compare_flags (op0, op1); |
| |
| rtx ops[2]; |
| ops[0] = op0; |
| ops[1] = op1; |
| if (op1 == const0_rtx) |
| output_asm_insn ("tst%.w %d0", ops); |
| else if (GET_CODE (op0) == MEM && GET_CODE (op1) == MEM) |
| output_asm_insn ("cmpm%.w %1,%0", ops); |
| else if ((REG_P (op1) && !ADDRESS_REG_P (op1)) |
| || (!REG_P (op0) && GET_CODE (op0) != MEM)) |
| { |
| output_asm_insn ("cmp%.w %d0,%d1", ops); |
| std::swap (flags_compare_op0, flags_compare_op1); |
| return swap_condition (code); |
| } |
| else |
| output_asm_insn ("cmp%.w %d1,%d0", ops); |
| return code; |
| } |
| |
| /* Emit a comparison between OP0 and OP1. CODE is the code of the |
| comparison. It is returned, potentially modified if necessary. */ |
| rtx_code |
| m68k_output_compare_qi (rtx op0, rtx op1, rtx_code code) |
| { |
| rtx_code tmp = m68k_find_flags_value (op0, op1, code); |
| if (tmp != UNKNOWN) |
| return tmp; |
| |
| remember_compare_flags (op0, op1); |
| |
| rtx ops[2]; |
| ops[0] = op0; |
| ops[1] = op1; |
| if (op1 == const0_rtx) |
| output_asm_insn ("tst%.b %d0", ops); |
| else if (GET_CODE (op0) == MEM && GET_CODE (op1) == MEM) |
| output_asm_insn ("cmpm%.b %1,%0", ops); |
| else if (REG_P (op1) || (!REG_P (op0) && GET_CODE (op0) != MEM)) |
| { |
| output_asm_insn ("cmp%.b %d0,%d1", ops); |
| std::swap (flags_compare_op0, flags_compare_op1); |
| return swap_condition (code); |
| } |
| else |
| output_asm_insn ("cmp%.b %d1,%d0", ops); |
| return code; |
| } |
| |
| /* Emit a comparison between OP0 and OP1. CODE is the code of the |
| comparison. It is returned, potentially modified if necessary. */ |
| rtx_code |
| m68k_output_compare_fp (rtx op0, rtx op1, rtx_code code) |
| { |
| rtx_code tmp = m68k_find_flags_value (op0, op1, code); |
| if (tmp != UNKNOWN) |
| return tmp; |
| |
| rtx ops[2]; |
| ops[0] = op0; |
| ops[1] = op1; |
| |
| remember_compare_flags (op0, op1); |
| |
| machine_mode mode = GET_MODE (op0); |
| std::string prec = mode == SFmode ? "s" : mode == DFmode ? "d" : "x"; |
| |
| if (op1 == CONST0_RTX (GET_MODE (op0))) |
| { |
| if (FP_REG_P (op0)) |
| { |
| if (TARGET_COLDFIRE_FPU) |
| output_asm_insn ("ftst%.d %0", ops); |
| else |
| output_asm_insn ("ftst%.x %0", ops); |
| } |
| else |
| output_asm_insn (("ftst%." + prec + " %0").c_str (), ops); |
| return code; |
| } |
| |
| switch (which_alternative) |
| { |
| case 0: |
| if (TARGET_COLDFIRE_FPU) |
| output_asm_insn ("fcmp%.d %1,%0", ops); |
| else |
| output_asm_insn ("fcmp%.x %1,%0", ops); |
| break; |
| case 1: |
| output_asm_insn (("fcmp%." + prec + " %f1,%0").c_str (), ops); |
| break; |
| case 2: |
| output_asm_insn (("fcmp%." + prec + " %0,%f1").c_str (), ops); |
| std::swap (flags_compare_op0, flags_compare_op1); |
| return swap_condition (code); |
| case 3: |
| /* This is the ftst case, handled earlier. */ |
| gcc_unreachable (); |
| } |
| return code; |
| } |
| |
| /* Return an output template for a branch with CODE. */ |
| const char * |
| m68k_output_branch_integer (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "jeq %l3"; |
| case NE: |
| return "jne %l3"; |
| case GT: |
| return "jgt %l3"; |
| case GTU: |
| return "jhi %l3"; |
| case LT: |
| return "jlt %l3"; |
| case LTU: |
| return "jcs %l3"; |
| case GE: |
| return "jge %l3"; |
| case GEU: |
| return "jcc %l3"; |
| case LE: |
| return "jle %l3"; |
| case LEU: |
| return "jls %l3"; |
| case PLUS: |
| return "jpl %l3"; |
| case MINUS: |
| return "jmi %l3"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return an output template for a reversed branch with CODE. */ |
| const char * |
| m68k_output_branch_integer_rev (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "jne %l3"; |
| case NE: |
| return "jeq %l3"; |
| case GT: |
| return "jle %l3"; |
| case GTU: |
| return "jls %l3"; |
| case LT: |
| return "jge %l3"; |
| case LTU: |
| return "jcc %l3"; |
| case GE: |
| return "jlt %l3"; |
| case GEU: |
| return "jcs %l3"; |
| case LE: |
| return "jgt %l3"; |
| case LEU: |
| return "jhi %l3"; |
| case PLUS: |
| return "jmi %l3"; |
| case MINUS: |
| return "jpl %l3"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return an output template for a scc instruction with CODE. */ |
| const char * |
| m68k_output_scc (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "seq %0"; |
| case NE: |
| return "sne %0"; |
| case GT: |
| return "sgt %0"; |
| case GTU: |
| return "shi %0"; |
| case LT: |
| return "slt %0"; |
| case LTU: |
| return "scs %0"; |
| case GE: |
| return "sge %0"; |
| case GEU: |
| return "scc %0"; |
| case LE: |
| return "sle %0"; |
| case LEU: |
| return "sls %0"; |
| case PLUS: |
| return "spl %0"; |
| case MINUS: |
| return "smi %0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return an output template for a floating point branch |
| instruction with CODE. */ |
| const char * |
| m68k_output_branch_float (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "fjeq %l3"; |
| case NE: |
| return "fjne %l3"; |
| case GT: |
| return "fjgt %l3"; |
| case LT: |
| return "fjlt %l3"; |
| case GE: |
| return "fjge %l3"; |
| case LE: |
| return "fjle %l3"; |
| case ORDERED: |
| return "fjor %l3"; |
| case UNORDERED: |
| return "fjun %l3"; |
| case UNEQ: |
| return "fjueq %l3"; |
| case UNGE: |
| return "fjuge %l3"; |
| case UNGT: |
| return "fjugt %l3"; |
| case UNLE: |
| return "fjule %l3"; |
| case UNLT: |
| return "fjult %l3"; |
| case LTGT: |
| return "fjogl %l3"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return an output template for a reversed floating point branch |
| instruction with CODE. */ |
| const char * |
| m68k_output_branch_float_rev (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "fjne %l3"; |
| case NE: |
| return "fjeq %l3"; |
| case GT: |
| return "fjngt %l3"; |
| case LT: |
| return "fjnlt %l3"; |
| case GE: |
| return "fjnge %l3"; |
| case LE: |
| return "fjnle %l3"; |
| case ORDERED: |
| return "fjun %l3"; |
| case UNORDERED: |
| return "fjor %l3"; |
| case UNEQ: |
| return "fjogl %l3"; |
| case UNGE: |
| return "fjolt %l3"; |
| case UNGT: |
| return "fjole %l3"; |
| case UNLE: |
| return "fjogt %l3"; |
| case UNLT: |
| return "fjoge %l3"; |
| case LTGT: |
| return "fjueq %l3"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return an output template for a floating point scc |
| instruction with CODE. */ |
| const char * |
| m68k_output_scc_float (rtx_code code) |
| { |
| switch (code) |
| { |
| case EQ: |
| return "fseq %0"; |
| case NE: |
| return "fsne %0"; |
| case GT: |
| return "fsgt %0"; |
| case GTU: |
| return "fshi %0"; |
| case LT: |
| return "fslt %0"; |
| case GE: |
| return "fsge %0"; |
| case LE: |
| return "fsle %0"; |
| case ORDERED: |
| return "fsor %0"; |
| case UNORDERED: |
| return "fsun %0"; |
| case UNEQ: |
| return "fsueq %0"; |
| case UNGE: |
| return "fsuge %0"; |
| case UNGT: |
| return "fsugt %0"; |
| case UNLE: |
| return "fsule %0"; |
| case UNLT: |
| return "fsult %0"; |
| case LTGT: |
| return "fsogl %0"; |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| const char * |
| output_move_const_double (rtx *operands) |
| { |
| int code = standard_68881_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fmovecr #0x%x,%%0", code & 0xff); |
| return buf; |
| } |
| return "fmove%.d %1,%0"; |
| } |
| |
| const char * |
| output_move_const_single (rtx *operands) |
| { |
| int code = standard_68881_constant_p (operands[1]); |
| |
| if (code != 0) |
| { |
| static char buf[40]; |
| |
| sprintf (buf, "fmovecr #0x%x,%%0", code & 0xff); |
| return buf; |
| } |
| return "fmove%.s %f1,%0"; |
| } |
| |
| /* Return nonzero if X, a CONST_DOUBLE, has a value that we can get |
| from the "fmovecr" instruction. |
| The value, anded with 0xff, gives the code to use in fmovecr |
| to get the desired constant. */ |
| |
| /* This code has been fixed for cross-compilation. */ |
| |
| static int inited_68881_table = 0; |
| |
| static const char *const strings_68881[7] = { |
| "0.0", |
| "1.0", |
| "10.0", |
| "100.0", |
| "10000.0", |
| "1e8", |
| "1e16" |
| }; |
| |
| static const int codes_68881[7] = { |
| 0x0f, |
| 0x32, |
| 0x33, |
| 0x34, |
| 0x35, |
| 0x36, |
| 0x37 |
| }; |
| |
| REAL_VALUE_TYPE values_68881[7]; |
| |
| /* Set up values_68881 array by converting the decimal values |
| strings_68881 to binary. */ |
| |
| void |
| init_68881_table (void) |
| { |
| int i; |
| REAL_VALUE_TYPE r; |
| machine_mode mode; |
| |
| mode = SFmode; |
| for (i = 0; i < 7; i++) |
| { |
| if (i == 6) |
| mode = DFmode; |
| r = REAL_VALUE_ATOF (strings_68881[i], mode); |
| values_68881[i] = r; |
| } |
| inited_68881_table = 1; |
| } |
| |
| int |
| standard_68881_constant_p (rtx x) |
| { |
| const REAL_VALUE_TYPE *r; |
| int i; |
| |
| /* fmovecr must be emulated on the 68040 and 68060, so it shouldn't be |
| used at all on those chips. */ |
| if (TUNE_68040_60) |
| return 0; |
| |
| if (! inited_68881_table) |
| init_68881_table (); |
| |
| r = CONST_DOUBLE_REAL_VALUE (x); |
| |
| /* Use real_identical instead of real_equal so that -0.0 is rejected. */ |
| for (i = 0; i < 6; i++) |
| { |
| if (real_identical (r, &values_68881[i])) |
| return (codes_68881[i]); |
| } |
| |
| if (GET_MODE (x) == SFmode) |
| return 0; |
| |
| if (real_equal (r, &values_68881[6])) |
| return (codes_68881[6]); |
| |
| /* larger powers of ten in the constants ram are not used |
| because they are not equal to a `double' C constant. */ |
| return 0; |
| } |
| |
| /* If X is a floating-point constant, return the logarithm of X base 2, |
| or 0 if X is not a power of 2. */ |
| |
| int |
| floating_exact_log2 (rtx x) |
| { |
| const REAL_VALUE_TYPE *r; |
| REAL_VALUE_TYPE r1; |
| int exp; |
| |
| r = CONST_DOUBLE_REAL_VALUE (x); |
| |
| if (real_less (r, &dconst1)) |
| return 0; |
| |
| exp = real_exponent (r); |
| real_2expN (&r1, exp, DFmode); |
| if (real_equal (&r1, r)) |
| return exp; |
| |
| return 0; |
| } |
| |
| /* A C compound statement to output to stdio stream STREAM the |
| assembler syntax for an instruction operand X. X is an RTL |
| expression. |
| |
| CODE is a value that can be used to specify one of several ways |
| of printing the operand. It is used when identical operands |
| must be printed differently depending on the context. CODE |
| comes from the `%' specification that was used to request |
| printing of the operand. If the specification was just `%DIGIT' |
| then CODE is 0; if the specification was `%LTR DIGIT' then CODE |
| is the ASCII code for LTR. |
| |
| If X is a register, this macro should print the register's name. |
| The names can be found in an array `reg_names' whose type is |
| `char *[]'. `reg_names' is initialized from `REGISTER_NAMES'. |
| |
| When the machine description has a specification `%PUNCT' (a `%' |
| followed by a punctuation character), this macro is called with |
| a null pointer for X and the punctuation character for CODE. |
| |
| The m68k specific codes are: |
| |
| '.' for dot needed in Motorola-style opcode names. |
| '-' for an operand pushing on the stack: |
| sp@-, -(sp) or -(%sp) depending on the style of syntax. |
| '+' for an operand pushing on the stack: |
| sp@+, (sp)+ or (%sp)+ depending on the style of syntax. |
| '@' for a reference to the top word on the stack: |
| sp@, (sp) or (%sp) depending on the style of syntax. |
| '#' for an immediate operand prefix (# in MIT and Motorola syntax |
| but & in SGS syntax). |
| '!' for the cc register (used in an `and to cc' insn). |
| '$' for the letter `s' in an op code, but only on the 68040. |
| '&' for the letter `d' in an op code, but only on the 68040. |
| '/' for register prefix needed by longlong.h. |
| '?' for m68k_library_id_string |
| |
| 'b' for byte insn (no effect, on the Sun; this is for the ISI). |
| 'd' to force memory addressing to be absolute, not relative. |
| 'f' for float insn (print a CONST_DOUBLE as a float rather than in hex) |
| 'x' for float insn (print a CONST_DOUBLE as a float rather than in hex), |
| or print pair of registers as rx:ry. |
| 'p' print an address with @PLTPC attached, but only if the operand |
| is not locally-bound. */ |
| |
| void |
| print_operand (FILE *file, rtx op, int letter) |
| { |
| if (op != NULL_RTX) |
| m68k_adjust_decorated_operand (op); |
| |
| if (letter == '.') |
| { |
| if (MOTOROLA) |
| fprintf (file, "."); |
| } |
| else if (letter == '#') |
| asm_fprintf (file, "%I"); |
| else if (letter == '-') |
| asm_fprintf (file, MOTOROLA ? "-(%Rsp)" : "%Rsp@-"); |
| else if (letter == '+') |
| asm_fprintf (file, MOTOROLA ? "(%Rsp)+" : "%Rsp@+"); |
| else if (letter == '@') |
| asm_fprintf (file, MOTOROLA ? "(%Rsp)" : "%Rsp@"); |
| else if (letter == '!') |
| asm_fprintf (file, "%Rfpcr"); |
| else if (letter == '$') |
| { |
| if (TARGET_68040) |
| fprintf (file, "s"); |
| } |
| else if (letter == '&') |
| { |
| if (TARGET_68040) |
| fprintf (file, "d"); |
| } |
| else if (letter == '/') |
| asm_fprintf (file, "%R"); |
| else if (letter == '?') |
| asm_fprintf (file, m68k_library_id_string); |
| else if (letter == 'p') |
| { |
| output_addr_const (file, op); |
| if (!(GET_CODE (op) == SYMBOL_REF && SYMBOL_REF_LOCAL_P (op))) |
| fprintf (file, "@PLTPC"); |
| } |
| else if (GET_CODE (op) == REG) |
| { |
| if (letter == 'R') |
| /* Print out the second register name of a register pair. |
| I.e., R (6) => 7. */ |
| fputs (M68K_REGNAME(REGNO (op) + 1), file); |
| else |
| fputs (M68K_REGNAME(REGNO (op)), file); |
| } |
| else if (GET_CODE (op) == MEM) |
| { |
| output_address (GET_MODE (op), XEXP (op, 0)); |
| if (letter == 'd' && ! TARGET_68020 |
| && CONSTANT_ADDRESS_P (XEXP (op, 0)) |
| && !(GET_CODE (XEXP (op, 0)) == CONST_INT |
| && INTVAL (XEXP (op, 0)) < 0x8000 |
| && INTVAL (XEXP (op, 0)) >= -0x8000)) |
| fprintf (file, MOTOROLA ? ".l" : ":l"); |
| } |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == SFmode) |
| { |
| long l; |
| REAL_VALUE_TO_TARGET_SINGLE (*CONST_DOUBLE_REAL_VALUE (op), l); |
| asm_fprintf (file, "%I0x%lx", l & 0xFFFFFFFF); |
| } |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == XFmode) |
| { |
| long l[3]; |
| REAL_VALUE_TO_TARGET_LONG_DOUBLE (*CONST_DOUBLE_REAL_VALUE (op), l); |
| asm_fprintf (file, "%I0x%lx%08lx%08lx", l[0] & 0xFFFFFFFF, |
| l[1] & 0xFFFFFFFF, l[2] & 0xFFFFFFFF); |
| } |
| else if (GET_CODE (op) == CONST_DOUBLE && GET_MODE (op) == DFmode) |
| { |
| long l[2]; |
| REAL_VALUE_TO_TARGET_DOUBLE (*CONST_DOUBLE_REAL_VALUE (op), l); |
| asm_fprintf (file, "%I0x%lx%08lx", l[0] & 0xFFFFFFFF, l[1] & 0xFFFFFFFF); |
| } |
| else |
| { |
| /* Use `print_operand_address' instead of `output_addr_const' |
| to ensure that we print relevant PIC stuff. */ |
| asm_fprintf (file, "%I"); |
| if (TARGET_PCREL |
| && (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST)) |
| print_operand_address (file, op); |
| else |
| output_addr_const (file, op); |
| } |
| } |
| |
| /* Return string for TLS relocation RELOC. */ |
| |
| static const char * |
| m68k_get_reloc_decoration (enum m68k_reloc reloc) |
| { |
| /* To my knowledge, !MOTOROLA assemblers don't support TLS. */ |
| gcc_assert (MOTOROLA || reloc == RELOC_GOT); |
| |
| switch (reloc) |
| { |
| case RELOC_GOT: |
| if (MOTOROLA) |
| { |
| if (flag_pic == 1 && TARGET_68020) |
| return "@GOT.w"; |
| else |
| return "@GOT"; |
| } |
| else |
| { |
| if (TARGET_68020) |
| { |
| switch (flag_pic) |
| { |
| case 1: |
| return ":w"; |
| case 2: |
| return ":l"; |
| default: |
| return ""; |
| } |
| } |
| } |
| gcc_unreachable (); |
| |
| case RELOC_TLSGD: |
| return "@TLSGD"; |
| |
| case RELOC_TLSLDM: |
| return "@TLSLDM"; |
| |
| case RELOC_TLSLDO: |
| return "@TLSLDO"; |
| |
| case RELOC_TLSIE: |
| return "@TLSIE"; |
| |
| case RELOC_TLSLE: |
| return "@TLSLE"; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* m68k implementation of TARGET_OUTPUT_ADDR_CONST_EXTRA. */ |
| |
| static bool |
| m68k_output_addr_const_extra (FILE *file, rtx x) |
| { |
| if (GET_CODE (x) == UNSPEC) |
| { |
| switch (XINT (x, 1)) |
| { |
| case UNSPEC_RELOC16: |
| case UNSPEC_RELOC32: |
| output_addr_const (file, XVECEXP (x, 0, 0)); |
| fputs (m68k_get_reloc_decoration |
| ((enum m68k_reloc) INTVAL (XVECEXP (x, 0, 1))), file); |
| return true; |
| |
| default: |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* M68K implementation of TARGET_ASM_OUTPUT_DWARF_DTPREL. */ |
| |
| static void |
| m68k_output_dwarf_dtprel (FILE *file, int size, rtx x) |
| { |
| gcc_assert (size == 4); |
| fputs ("\t.long\t", file); |
| output_addr_const (file, x); |
| fputs ("@TLSLDO+0x8000", file); |
| } |
| |
| /* In the name of slightly smaller debug output, and to cater to |
| general assembler lossage, recognize various UNSPEC sequences |
| and turn them back into a direct symbol reference. */ |
| |
| static rtx |
| m68k_delegitimize_address (rtx orig_x) |
| { |
| rtx x; |
| struct m68k_address addr; |
| rtx unspec; |
| |
| orig_x = delegitimize_mem_from_attrs (orig_x); |
| x = orig_x; |
| if (MEM_P (x)) |
| x = XEXP (x, 0); |
| |
| if (GET_CODE (x) != PLUS || GET_MODE (x) != Pmode) |
| return orig_x; |
| |
| if (!m68k_decompose_address (GET_MODE (x), x, false, &addr) |
| || addr.offset == NULL_RTX |
| || GET_CODE (addr.offset) != CONST) |
| return orig_x; |
| |
| unspec = XEXP (addr.offset, 0); |
| if (GET_CODE (unspec) == PLUS && CONST_INT_P (XEXP (unspec, 1))) |
| unspec = XEXP (unspec, 0); |
| if (GET_CODE (unspec) != UNSPEC |
| || (XINT (unspec, 1) != UNSPEC_RELOC16 |
| && XINT (unspec, 1) != UNSPEC_RELOC32)) |
| return orig_x; |
| x = XVECEXP (unspec, 0, 0); |
| gcc_assert (GET_CODE (x) == SYMBOL_REF || GET_CODE (x) == LABEL_REF); |
| if (unspec != XEXP (addr.offset, 0)) |
| x = gen_rtx_PLUS (Pmode, x, XEXP (XEXP (addr.offset, 0), 1)); |
| if (addr.index) |
| { |
| rtx idx = addr.index; |
| if (addr.scale != 1) |
| idx = gen_rtx_MULT (Pmode, idx, GEN_INT (addr.scale)); |
| x = gen_rtx_PLUS (Pmode, idx, x); |
| } |
| if (addr.base) |
| x = gen_rtx_PLUS (Pmode, addr.base, x); |
| if (MEM_P (orig_x)) |
| x = replace_equiv_address_nv (orig_x, x); |
| return x; |
| } |
| |
| |
| /* A C compound statement to output to stdio stream STREAM the |
| assembler syntax for an instruction operand that is a memory |
| reference whose address is ADDR. ADDR is an RTL expression. |
| |
| Note that this contains a kludge that knows that the only reason |
| we have an address (plus (label_ref...) (reg...)) when not generating |
| PIC code is in the insn before a tablejump, and we know that m68k.md |
| generates a label LInnn: on such an insn. |
| |
| It is possible for PIC to generate a (plus (label_ref...) (reg...)) |
| and we handle that just like we would a (plus (symbol_ref...) (reg...)). |
| |
| This routine is responsible for distinguishing between -fpic and -fPIC |
| style relocations in an address. When generating -fpic code the |
| offset is output in word mode (e.g. movel a5@(_foo:w), a0). When generating |
| -fPIC code the offset is output in long mode (e.g. movel a5@(_foo:l), a0) */ |
| |
| void |
| print_operand_address (FILE *file, rtx addr) |
| { |
| struct m68k_address address; |
| |
| m68k_adjust_decorated_operand (addr); |
| |
| if (!m68k_decompose_address (QImode, addr, true, &address)) |
| gcc_unreachable (); |
| |
| if (address.code == PRE_DEC) |
| fprintf (file, MOTOROLA ? "-(%s)" : "%s@-", |
| M68K_REGNAME (REGNO (address.base))); |
| else if (address.code == POST_INC) |
| fprintf (file, MOTOROLA ? "(%s)+" : "%s@+", |
| M68K_REGNAME (REGNO (address.base))); |
| else if (!address.base && !address.index) |
| { |
| /* A constant address. */ |
| gcc_assert (address.offset == addr); |
| if (GET_CODE (addr) == CONST_INT) |
| { |
| /* (xxx).w or (xxx).l. */ |
| if (IN_RANGE (INTVAL (addr), -0x8000, 0x7fff)) |
| fprintf (file, MOTOROLA ? "%d.w" : "%d:w", (int) INTVAL (addr)); |
| else |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (addr)); |
| } |
| else if (TARGET_PCREL) |
| { |
| /* (d16,PC) or (bd,PC,Xn) (with suppressed index register). */ |
| fputc ('(', file); |
| output_addr_const (file, addr); |
| asm_fprintf (file, flag_pic == 1 ? ":w,%Rpc)" : ":l,%Rpc)"); |
| } |
| else |
| { |
| /* (xxx).l. We need a special case for SYMBOL_REF if the symbol |
| name ends in `.<letter>', as the last 2 characters can be |
| mistaken as a size suffix. Put the name in parentheses. */ |
| if (GET_CODE (addr) == SYMBOL_REF |
| && strlen (XSTR (addr, 0)) > 2 |
| && XSTR (addr, 0)[strlen (XSTR (addr, 0)) - 2] == '.') |
| { |
| putc ('(', file); |
| output_addr_const (file, addr); |
| putc (')', file); |
| } |
| else |
| output_addr_const (file, addr); |
| } |
| } |
| else |
| { |
| int labelno; |
| |
| /* If ADDR is a (d8,pc,Xn) address, this is the number of the |
| label being accessed, otherwise it is -1. */ |
| labelno = (address.offset |
| && !address.base |
| && GET_CODE (address.offset) == LABEL_REF |
| ? CODE_LABEL_NUMBER (XEXP (address.offset, 0)) |
| : -1); |
| if (MOTOROLA) |
| { |
| /* Print the "offset(base" component. */ |
| if (labelno >= 0) |
| asm_fprintf (file, "%LL%d(%Rpc,", labelno); |
| else |
| { |
| if (address.offset) |
| output_addr_const (file, address.offset); |
| |
| putc ('(', file); |
| if (address.base) |
| fputs (M68K_REGNAME (REGNO (address.base)), file); |
| } |
| /* Print the ",index" component, if any. */ |
| if (address.index) |
| { |
| if (address.base) |
| putc (',', file); |
| fprintf (file, "%s.%c", |
| M68K_REGNAME (REGNO (address.index)), |
| GET_MODE (address.index) == HImode ? 'w' : 'l'); |
| if (address.scale != 1) |
| fprintf (file, "*%d", address.scale); |
| } |
| putc (')', file); |
| } |
| else /* !MOTOROLA */ |
| { |
| if (!address.offset && !address.index) |
| fprintf (file, "%s@", M68K_REGNAME (REGNO (address.base))); |
| else |
| { |
| /* Print the "base@(offset" component. */ |
| if (labelno >= 0) |
| asm_fprintf (file, "%Rpc@(%LL%d", labelno); |
| else |
| { |
| if (address.base) |
| fputs (M68K_REGNAME (REGNO (address.base)), file); |
| fprintf (file, "@("); |
| if (address.offset) |
| output_addr_const (file, address.offset); |
| } |
| /* Print the ",index" component, if any. */ |
| if (address.index) |
| { |
| fprintf (file, ",%s:%c", |
| M68K_REGNAME (REGNO (address.index)), |
| GET_MODE (address.index) == HImode ? 'w' : 'l'); |
| if (address.scale != 1) |
| fprintf (file, ":%d", address.scale); |
| } |
| putc (')', file); |
| } |
| } |
| } |
| } |
| |
| /* Check for cases where a clr insns can be omitted from code using |
| strict_low_part sets. For example, the second clrl here is not needed: |
| clrl d0; movw a0@+,d0; use d0; clrl d0; movw a0@+; use d0; ... |
| |
| MODE is the mode of this STRICT_LOW_PART set. FIRST_INSN is the clear |
| insn we are checking for redundancy. TARGET is the register set by the |
| clear insn. */ |
| |
| bool |
| strict_low_part_peephole_ok (machine_mode mode, rtx_insn *first_insn, |
| rtx target) |
| { |
| rtx_insn *p = first_insn; |
| |
| while ((p = PREV_INSN (p))) |
| { |
| if (NOTE_INSN_BASIC_BLOCK_P (p)) |
| return false; |
| |
| if (NOTE_P (p)) |
| continue; |
| |
| /* If it isn't an insn, then give up. */ |
| if (!INSN_P (p)) |
| return false; |
| |
| if (reg_set_p (target, p)) |
| { |
| rtx set = single_set (p); |
| rtx dest; |
| |
| /* If it isn't an easy to recognize insn, then give up. */ |
| if (! set) |
| return false; |
| |
| dest = SET_DEST (set); |
| |
| /* If this sets the entire target register to zero, then our |
| first_insn is redundant. */ |
| if (rtx_equal_p (dest, target) |
| && SET_SRC (set) == const0_rtx) |
| return true; |
| else if (GET_CODE (dest) == STRICT_LOW_PART |
| && GET_CODE (XEXP (dest, 0)) == REG |
| && REGNO (XEXP (dest, 0)) == REGNO (target) |
| && (GET_MODE_SIZE (GET_MODE (XEXP (dest, 0))) |
| <= GET_MODE_SIZE (mode))) |
| /* This is a strict low part set which modifies less than |
| we are using, so it is safe. */ |
| ; |
| else |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Operand predicates for implementing asymmetric pc-relative addressing |
| on m68k. The m68k supports pc-relative addressing (mode 7, register 2) |
| when used as a source operand, but not as a destination operand. |
| |
| We model this by restricting the meaning of the basic predicates |
| (general_operand, memory_operand, etc) to forbid the use of this |
| addressing mode, and then define the following predicates that permit |
| this addressing mode. These predicates can then be used for the |
| source operands of the appropriate instructions. |
| |
| n.b. While it is theoretically possible to change all machine patterns |
| to use this addressing more where permitted by the architecture, |
| it has only been implemented for "common" cases: SImode, HImode, and |
| QImode operands, and only for the principle operations that would |
| require this addressing mode: data movement and simple integer operations. |
| |
| In parallel with these new predicates, two new constraint letters |
| were defined: 'S' and 'T'. 'S' is the -mpcrel analog of 'm'. |
| 'T' replaces 's' in the non-pcrel case. It is a no-op in the pcrel case. |
| In the pcrel case 's' is only valid in combination with 'a' registers. |
| See addsi3, subsi3, cmpsi, and movsi patterns for a better understanding |
| of how these constraints are used. |
| |
| The use of these predicates is strictly optional, though patterns that |
| don't will cause an extra reload register to be allocated where one |
| was not necessary: |
| |
| lea (abc:w,%pc),%a0 ; need to reload address |
| moveq &1,%d1 ; since write to pc-relative space |
| movel %d1,%a0@ ; is not allowed |
| ... |
| lea (abc:w,%pc),%a1 ; no need to reload address here |
| movel %a1@,%d0 ; since "movel (abc:w,%pc),%d0" is ok |
| |
| For more info, consult tiemann@cygnus.com. |
| |
| |
| All of the ugliness with predicates and constraints is due to the |
| simple fact that the m68k does not allow a pc-relative addressing |
| mode as a destination. gcc does not distinguish between source and |
| destination addresses. Hence, if we claim that pc-relative address |
| modes are valid, e.g. TARGET_LEGITIMATE_ADDRESS_P accepts them, then we |
| end up with invalid code. To get around this problem, we left |
| pc-relative modes as invalid addresses, and then added special |
| predicates and constraints to accept them. |
| |
| A cleaner way to handle this is to modify gcc to distinguish |
| between source and destination addresses. We can then say that |
| pc-relative is a valid source address but not a valid destination |
| address, and hopefully avoid a lot of the predicate and constraint |
| hackery. Unfortunately, this would be a pretty big change. It would |
| be a useful change for a number of ports, but there aren't any current |
| plans to undertake this. |
| |
| ***************************************************************************/ |
| |
| |
| const char * |
| output_andsi3 (rtx *operands) |
| { |
| int logval; |
| CC_STATUS_INIT; |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (INTVAL (operands[2]) | 0xffff) == -1 |
| && (DATA_REG_P (operands[0]) |
| || offsettable_memref_p (operands[0])) |
| && !TARGET_COLDFIRE) |
| { |
| if (GET_CODE (operands[0]) != REG) |
| operands[0] = adjust_address (operands[0], HImode, 2); |
| operands[2] = GEN_INT (INTVAL (operands[2]) & 0xffff); |
| if (operands[2] == const0_rtx) |
| return "clr%.w %0"; |
| return "and%.w %2,%0"; |
| } |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (logval = exact_log2 (~ INTVAL (operands[2]) & 0xffffffff)) >= 0 |
| && (DATA_REG_P (operands[0]) |
| || offsettable_memref_p (operands[0]))) |
| { |
| if (DATA_REG_P (operands[0])) |
| operands[1] = GEN_INT (logval); |
| else |
| { |
| operands[0] = adjust_address (operands[0], SImode, 3 - (logval / 8)); |
| operands[1] = GEN_INT (logval % 8); |
| } |
| return "bclr %1,%0"; |
| } |
| /* Only a standard logical operation on the whole word sets the |
| condition codes in a way we can use. */ |
| if (!side_effects_p (operands[0])) |
| flags_operand1 = operands[0]; |
| flags_valid = FLAGS_VALID_YES; |
| return "and%.l %2,%0"; |
| } |
| |
| const char * |
| output_iorsi3 (rtx *operands) |
| { |
| int logval; |
| CC_STATUS_INIT; |
| if (GET_CODE (operands[2]) == CONST_INT |
| && INTVAL (operands[2]) >> 16 == 0 |
| && (DATA_REG_P (operands[0]) |
| || offsettable_memref_p (operands[0])) |
| && !TARGET_COLDFIRE) |
| { |
| if (GET_CODE (operands[0]) != REG) |
| operands[0] = adjust_address (operands[0], HImode, 2); |
| if (INTVAL (operands[2]) == 0xffff) |
| return "mov%.w %2,%0"; |
| return "or%.w %2,%0"; |
| } |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (logval = exact_log2 (INTVAL (operands[2]) & 0xffffffff)) >= 0 |
| && (DATA_REG_P (operands[0]) |
| || offsettable_memref_p (operands[0]))) |
| { |
| if (DATA_REG_P (operands[0])) |
| operands[1] = GEN_INT (logval); |
| else |
| { |
| operands[0] = adjust_address (operands[0], SImode, 3 - (logval / 8)); |
| operands[1] = GEN_INT (logval % 8); |
| } |
| return "bset %1,%0"; |
| } |
| /* Only a standard logical operation on the whole word sets the |
| condition codes in a way we can use. */ |
| if (!side_effects_p (operands[0])) |
| flags_operand1 = operands[0]; |
| flags_valid = FLAGS_VALID_YES; |
| return "or%.l %2,%0"; |
| } |
| |
| const char * |
| output_xorsi3 (rtx *operands) |
| { |
| int logval; |
| CC_STATUS_INIT; |
| if (GET_CODE (operands[2]) == CONST_INT |
| && INTVAL (operands[2]) >> 16 == 0 |
| && (offsettable_memref_p (operands[0]) || DATA_REG_P (operands[0])) |
| && !TARGET_COLDFIRE) |
| { |
| if (! DATA_REG_P (operands[0])) |
| operands[0] = adjust_address (operands[0], HImode, 2); |
| if (INTVAL (operands[2]) == 0xffff) |
| return "not%.w %0"; |
| return "eor%.w %2,%0"; |
| } |
| if (GET_CODE (operands[2]) == CONST_INT |
| && (logval = exact_log2 (INTVAL (operands[2]) & 0xffffffff)) >= 0 |
| && (DATA_REG_P (operands[0]) |
| || offsettable_memref_p (operands[0]))) |
| { |
| if (DATA_REG_P (operands[0])) |
| operands[1] = GEN_INT (logval); |
| else |
| { |
| operands[0] = adjust_address (operands[0], SImode, 3 - (logval / 8)); |
| operands[1] = GEN_INT (logval % 8); |
| } |
| return "bchg %1,%0"; |
| } |
| /* Only a standard logical operation on the whole word sets the |
| condition codes in a way we can use. */ |
| if (!side_effects_p (operands[0])) |
| flags_operand1 = operands[0]; |
| flags_valid = FLAGS_VALID_YES; |
| return "eor%.l %2,%0"; |
| } |
| |
| /* Return the instruction that should be used for a call to address X, |
| which is known to be in operand 0. */ |
| |
| const char * |
| output_call (rtx x) |
| { |
| if (symbolic_operand (x, VOIDmode)) |
| return m68k_symbolic_call; |
| else |
| return "jsr %a0"; |
| } |
| |
| /* Likewise sibling calls. */ |
| |
| const char * |
| output_sibcall (rtx x) |
| { |
| if (symbolic_operand (x, VOIDmode)) |
| return m68k_symbolic_jump; |
| else |
| return "jmp %a0"; |
| } |
| |
| static void |
| m68k_output_mi_thunk (FILE *file, tree thunk ATTRIBUTE_UNUSED, |
| HOST_WIDE_INT delta, HOST_WIDE_INT vcall_offset, |
| tree function) |
| { |
| const char *fnname = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (thunk)); |
| rtx this_slot, offset, addr, mem, tmp; |
| rtx_insn *insn; |
| |
| /* Avoid clobbering the struct value reg by using the |
| static chain reg as a temporary. */ |
| tmp = gen_rtx_REG (Pmode, STATIC_CHAIN_REGNUM); |
| |
| /* Pretend to be a post-reload pass while generating rtl. */ |
| reload_completed = 1; |
| |
| /* The "this" pointer is stored at 4(%sp). */ |
| this_slot = gen_rtx_MEM (Pmode, plus_constant (Pmode, |
| stack_pointer_rtx, 4)); |
| |
| /* Add DELTA to THIS. */ |
| if (delta != 0) |
| { |
| /* Make the offset a legitimate operand for memory addition. */ |
| offset = GEN_INT (delta); |
| if ((delta < -8 || delta > 8) |
| && (TARGET_COLDFIRE || USE_MOVQ (delta))) |
| { |
| emit_move_insn (gen_rtx_REG (Pmode, D0_REG), offset); |
| offset = gen_rtx_REG (Pmode, D0_REG); |
| } |
| emit_insn (gen_add3_insn (copy_rtx (this_slot), |
| copy_rtx (this_slot), offset)); |
| } |
| |
| /* If needed, add *(*THIS + VCALL_OFFSET) to THIS. */ |
| if (vcall_offset != 0) |
| { |
| /* Set the static chain register to *THIS. */ |
| emit_move_insn (tmp, this_slot); |
| emit_move_insn (tmp, gen_rtx_MEM (Pmode, tmp)); |
| |
| /* Set ADDR to a legitimate address for *THIS + VCALL_OFFSET. */ |
| addr = plus_constant (Pmode, tmp, vcall_offset); |
| if (!m68k_legitimate_address_p (Pmode, addr, true)) |
| { |
| emit_insn (gen_rtx_SET (tmp, addr)); |
| addr = tmp; |
| } |
| |
| /* Load the offset into %d0 and add it to THIS. */ |
| emit_move_insn (gen_rtx_REG (Pmode, D0_REG), |
| gen_rtx_MEM (Pmode, addr)); |
| emit_insn (gen_add3_insn (copy_rtx (this_slot), |
| copy_rtx (this_slot), |
| gen_rtx_REG (Pmode, D0_REG))); |
| } |
| |
| /* Jump to the target function. Use a sibcall if direct jumps are |
| allowed, otherwise load the address into a register first. */ |
| mem = DECL_RTL (function); |
| if (!sibcall_operand (XEXP (mem, 0), VOIDmode)) |
| { |
| gcc_assert (flag_pic); |
| |
| if (!TARGET_SEP_DATA) |
| { |
| /* Use the static chain register as a temporary (call-clobbered) |
| GOT pointer for this function. We can use the static chain |
| register because it isn't live on entry to the thunk. */ |
| SET_REGNO (pic_offset_table_rtx, STATIC_CHAIN_REGNUM); |
| emit_insn (gen_load_got (pic_offset_table_rtx)); |
| } |
| legitimize_pic_address (XEXP (mem, 0), Pmode, tmp); |
| mem = replace_equiv_address (mem, tmp); |
| } |
| insn = emit_call_insn (gen_sibcall (mem, const0_rtx)); |
| SIBLING_CALL_P (insn) = 1; |
| |
| /* Run just enough of rest_of_compilation. */ |
| insn = get_insns (); |
| split_all_insns_noflow (); |
| assemble_start_function (thunk, fnname); |
| final_start_function (insn, file, 1); |
| final (insn, file, 1); |
| final_end_function (); |
| assemble_end_function (thunk, fnname); |
| |
| /* Clean up the vars set above. */ |
| reload_completed = 0; |
| |
| /* Restore the original PIC register. */ |
| if (flag_pic) |
| SET_REGNO (pic_offset_table_rtx, PIC_REG); |
| } |
| |
| /* Worker function for TARGET_STRUCT_VALUE_RTX. */ |
| |
| static rtx |
| m68k_struct_value_rtx (tree fntype ATTRIBUTE_UNUSED, |
| int incoming ATTRIBUTE_UNUSED) |
| { |
| return gen_rtx_REG (Pmode, M68K_STRUCT_VALUE_REGNUM); |
| } |
| |
| /* Return nonzero if register old_reg can be renamed to register new_reg. */ |
| int |
| m68k_hard_regno_rename_ok (unsigned int old_reg ATTRIBUTE_UNUSED, |
| unsigned int new_reg) |
| { |
| |
| /* Interrupt functions can only use registers that have already been |
| saved by the prologue, even if they would normally be |
| call-clobbered. */ |
| |
| if ((m68k_get_function_kind (current_function_decl) |
| == m68k_fk_interrupt_handler) |
| && !df_regs_ever_live_p (new_reg)) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Implement TARGET_HARD_REGNO_NREGS. |
| |
| On the m68k, ordinary registers hold 32 bits worth; |
| for the 68881 registers, a single register is always enough for |
| anything that can be stored in them at all. */ |
| |
| static unsigned int |
| m68k_hard_regno_nregs (unsigned int regno, machine_mode mode) |
| { |
| if (regno >= 16) |
| return GET_MODE_NUNITS (mode); |
| return CEIL (GET_MODE_SIZE (mode), UNITS_PER_WORD); |
| } |
| |
| /* Implement TARGET_HARD_REGNO_MODE_OK. On the 68000, we let the cpu |
| registers can hold any mode, but restrict the 68881 registers to |
| floating-point modes. */ |
| |
| static bool |
| m68k_hard_regno_mode_ok (unsigned int regno, machine_mode mode) |
| { |
| if (DATA_REGNO_P (regno)) |
| { |
| /* Data Registers, can hold aggregate if fits in. */ |
| if (regno + GET_MODE_SIZE (mode) / 4 <= 8) |
| return true; |
| } |
| else if (ADDRESS_REGNO_P (regno)) |
| { |
| if (regno + GET_MODE_SIZE (mode) / 4 <= 16) |
| return true; |
| } |
| else if (FP_REGNO_P (regno)) |
| { |
| /* FPU registers, hold float or complex float of long double or |
| smaller. */ |
| if ((GET_MODE_CLASS (mode) == MODE_FLOAT |
| || GET_MODE_CLASS (mode) == MODE_COMPLEX_FLOAT) |
| && GET_MODE_UNIT_SIZE (mode) <= TARGET_FP_REG_SIZE) |
| return true; |
| } |
| return false; |
| } |
| |
| /* Implement TARGET_MODES_TIEABLE_P. */ |
| |
| static bool |
| m68k_modes_tieable_p (machine_mode mode1, machine_mode mode2) |
| { |
| return (!TARGET_HARD_FLOAT |
| || ((GET_MODE_CLASS (mode1) == MODE_FLOAT |
| || GET_MODE_CLASS (mode1) == MODE_COMPLEX_FLOAT) |
| == (GET_MODE_CLASS (mode2) == MODE_FLOAT |
| || GET_MODE_CLASS (mode2) == MODE_COMPLEX_FLOAT))); |
| } |
| |
| /* Implement SECONDARY_RELOAD_CLASS. */ |
| |
| enum reg_class |
| m68k_secondary_reload_class (enum reg_class rclass, |
| machine_mode mode, rtx x) |
| { |
| int regno; |
| |
| regno = true_regnum (x); |
| |
| /* If one operand of a movqi is an address register, the other |
| operand must be a general register or constant. Other types |
| of operand must be reloaded through a data register. */ |
| if (GET_MODE_SIZE (mode) == 1 |
| && reg_classes_intersect_p (rclass, ADDR_REGS) |
| && !(INT_REGNO_P (regno) || CONSTANT_P (x))) |
| return DATA_REGS; |
| |
| /* PC-relative addresses must be loaded into an address register first. */ |
| if (TARGET_PCREL |
| && !reg_class_subset_p (rclass, ADDR_REGS) |
| && symbolic_operand (x, VOIDmode)) |
| return ADDR_REGS; |
| |
| return NO_REGS; |
| } |
| |
| /* Implement PREFERRED_RELOAD_CLASS. */ |
| |
| enum reg_class |
| m68k_preferred_reload_class (rtx x, enum reg_class rclass) |
| { |
| enum reg_class secondary_class; |
| |
| /* If RCLASS might need a secondary reload, try restricting it to |
| a class that doesn't. */ |
| secondary_class = m68k_secondary_reload_class (rclass, GET_MODE (x), x); |
| if (secondary_class != NO_REGS |
| && reg_class_subset_p (secondary_class, rclass)) |
| return secondary_class; |
| |
| /* Prefer to use moveq for in-range constants. */ |
| if (GET_CODE (x) == CONST_INT |
| && reg_class_subset_p (DATA_REGS, rclass) |
| && IN_RANGE (INTVAL (x), -0x80, 0x7f)) |
| return DATA_REGS; |
| |
| /* ??? Do we really need this now? */ |
| if (GET_CODE (x) == CONST_DOUBLE |
| && GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) |
| { |
| if (TARGET_HARD_FLOAT && reg_class_subset_p (FP_REGS, rclass)) |
| return FP_REGS; |
| |
| return NO_REGS; |
| } |
| |
| return rclass; |
| } |
| |
| /* Return floating point values in a 68881 register. This makes 68881 code |
| a little bit faster. It also makes -msoft-float code incompatible with |
| hard-float code, so people have to be careful not to mix the two. |
| For ColdFire it was decided the ABI incompatibility is undesirable. |
| If there is need for a hard-float ABI it is probably worth doing it |
| properly and also passing function arguments in FP registers. */ |
| rtx |
| m68k_libcall_value (machine_mode mode) |
| { |
| switch (mode) { |
| case E_SFmode: |
| case E_DFmode: |
| case E_XFmode: |
| if (TARGET_68881) |
| return gen_rtx_REG (mode, FP0_REG); |
| break; |
| default: |
| break; |
| } |
| |
| return gen_rtx_REG (mode, m68k_libcall_value_in_a0_p ? A0_REG : D0_REG); |
| } |
| |
| /* Location in which function value is returned. |
| NOTE: Due to differences in ABIs, don't call this function directly, |
| use FUNCTION_VALUE instead. */ |
| rtx |
| m68k_function_value (const_tree valtype, const_tree func ATTRIBUTE_UNUSED) |
| { |
| machine_mode mode; |
| |
| mode = TYPE_MODE (valtype); |
| switch (mode) { |
| case E_SFmode: |
| case E_DFmode: |
| case E_XFmode: |
| if (TARGET_68881) |
| return gen_rtx_REG (mode, FP0_REG); |
| break; |
| default: |
| break; |
| } |
| |
| /* If the function returns a pointer, push that into %a0. */ |
| if (func && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (func)))) |
| /* For compatibility with the large body of existing code which |
| does not always properly declare external functions returning |
| pointer types, the m68k/SVR4 convention is to copy the value |
| returned for pointer functions from a0 to d0 in the function |
| epilogue, so that callers that have neglected to properly |
| declare the callee can still find the correct return value in |
| d0. */ |
| return gen_rtx_PARALLEL |
| (mode, |
| gen_rtvec (2, |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode, A0_REG), |
| const0_rtx), |
| gen_rtx_EXPR_LIST (VOIDmode, |
| gen_rtx_REG (mode, D0_REG), |
| const0_rtx))); |
| else if (POINTER_TYPE_P (valtype)) |
| return gen_rtx_REG (mode, A0_REG); |
| else |
| return gen_rtx_REG (mode, D0_REG); |
| } |
| |
| /* Worker function for TARGET_RETURN_IN_MEMORY. */ |
| #if M68K_HONOR_TARGET_STRICT_ALIGNMENT |
| static bool |
| m68k_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED) |
| { |
| machine_mode mode = TYPE_MODE (type); |
| |
| if (mode == BLKmode) |
| return true; |
| |
| /* If TYPE's known alignment is less than the alignment of MODE that |
| would contain the structure, then return in memory. We need to |
| do so to maintain the compatibility between code compiled with |
| -mstrict-align and that compiled with -mno-strict-align. */ |
| if (AGGREGATE_TYPE_P (type) |
| && TYPE_ALIGN (type) < GET_MODE_ALIGNMENT (mode)) |
| return true; |
| |
| return false; |
| } |
| #endif |
| |
| /* CPU to schedule the program for. */ |
| enum attr_cpu m68k_sched_cpu; |
| |
| /* MAC to schedule the program for. */ |
| enum attr_mac m68k_sched_mac; |
| |
| /* Operand type. */ |
| enum attr_op_type |
| { |
| /* No operand. */ |
| OP_TYPE_NONE, |
| |
| /* Integer register. */ |
| OP_TYPE_RN, |
| |
| /* FP register. */ |
| OP_TYPE_FPN, |
| |
| /* Implicit mem reference (e.g. stack). */ |
| OP_TYPE_MEM1, |
| |
| /* Memory without offset or indexing. EA modes 2, 3 and 4. */ |
| OP_TYPE_MEM234, |
| |
| /* Memory with offset but without indexing. EA mode 5. */ |
| OP_TYPE_MEM5, |
| |
| /* Memory with indexing. EA mode 6. */ |
| OP_TYPE_MEM6, |
| |
| /* Memory referenced by absolute address. EA mode 7. */ |
| OP_TYPE_MEM7, |
| |
| /* Immediate operand that doesn't require extension word. */ |
| OP_TYPE_IMM_Q, |
| |
| /* Immediate 16 bit operand. */ |
| OP_TYPE_IMM_W, |
| |
| /* Immediate 32 bit operand. */ |
| OP_TYPE_IMM_L |
| }; |
| |
| /* Return type of memory ADDR_RTX refers to. */ |
| static enum attr_op_type |
| sched_address_type (machine_mode mode, rtx addr_rtx) |
| { |
| struct m68k_address address; |
| |
| if (symbolic_operand (addr_rtx, VOIDmode)) |
| return OP_TYPE_MEM7; |
| |
| if (!m68k_decompose_address (mode, addr_rtx, |
| reload_completed, &address)) |
| { |
| gcc_assert (!reload_completed); |
| /* Reload will likely fix the address to be in the register. */ |
| return OP_TYPE_MEM234; |
| } |
| |
| if (address.scale != 0) |
| return OP_TYPE_MEM6; |
| |
| if (address.base != NULL_RTX) |
| { |
| if (address.offset == NULL_RTX) |
| return OP_TYPE_MEM234; |
| |
| return OP_TYPE_MEM5; |
| } |
| |
| gcc_assert (address.offset != NULL_RTX); |
| |
| return OP_TYPE_MEM7; |
| } |
| |
| /* Return X or Y (depending on OPX_P) operand of INSN. */ |
| static rtx |
| sched_get_operand (rtx_insn *insn, bool opx_p) |
| { |
| int i; |
| |
| if (recog_memoized (insn) < 0) |
| gcc_unreachable (); |
| |
| extract_constrain_insn_cached (insn); |
| |
| if (opx_p) |
| i = get_attr_opx (insn); |
| else |
| i = get_attr_opy (insn); |
| |
| if (i >= recog_data.n_operands) |
| return NULL; |
| |
| return recog_data.operand[i]; |
| } |
| |
| /* Return type of INSN's operand X (if OPX_P) or operand Y (if !OPX_P). |
| If ADDRESS_P is true, return type of memory location operand refers to. */ |
| static enum attr_op_type |
| sched_attr_op_type (rtx_insn *insn, bool opx_p, bool address_p) |
| { |
| rtx op; |
| |
| op = sched_get_operand (insn, opx_p); |
| |
| if (op == NULL) |
| { |
| gcc_assert (!reload_completed); |
| return OP_TYPE_RN; |
| } |
| |
| if (address_p) |
| return sched_address_type (QImode, op); |
| |
| if (memory_operand (op, VOIDmode)) |
| return sched_address_type (GET_MODE (op), XEXP (op, 0)); |
| |
| if (register_operand (op, VOIDmode)) |
| { |
| if ((!reload_completed && FLOAT_MODE_P (GET_MODE (op))) |
| || (reload_completed && FP_REG_P (op))) |
| return OP_TYPE_FPN; |
| |
| return OP_TYPE_RN; |
| } |
| |
| if (GET_CODE (op) == CONST_INT) |
| { |
| int ival; |
| |
| ival = INTVAL (op); |
| |
| /* Check for quick constants. */ |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_ALUQ_L: |
| if (IN_RANGE (ival, 1, 8) || IN_RANGE (ival, -8, -1)) |
| return OP_TYPE_IMM_Q; |
| |
| gcc_assert (!reload_completed); |
| break; |
| |
| case TYPE_MOVEQ_L: |
| if (USE_MOVQ (ival)) |
| return OP_TYPE_IMM_Q; |
| |
| gcc_assert (!reload_completed); |
| break; |
| |
| case TYPE_MOV3Q_L: |
| if (valid_mov3q_const (ival)) |
| return OP_TYPE_IMM_Q; |
| |
| gcc_assert (!reload_completed); |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (IN_RANGE (ival, -0x8000, 0x7fff)) |
| return OP_TYPE_IMM_W; |
| |
| return OP_TYPE_IMM_L; |
| } |
| |
| if (GET_CODE (op) == CONST_DOUBLE) |
| { |
| switch (GET_MODE (op)) |
| { |
| case E_SFmode: |
| return OP_TYPE_IMM_W; |
| |
| case E_VOIDmode: |
| case E_DFmode: |
| return OP_TYPE_IMM_L; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| if (GET_CODE (op) == CONST |
| || symbolic_operand (op, VOIDmode) |
| || LABEL_P (op)) |
| { |
| switch (GET_MODE (op)) |
| { |
| case E_QImode: |
| return OP_TYPE_IMM_Q; |
| |
| case E_HImode: |
| return OP_TYPE_IMM_W; |
| |
| case E_SImode: |
| return OP_TYPE_IMM_L; |
| |
| default: |
| if (symbolic_operand (m68k_unwrap_symbol (op, false), VOIDmode)) |
| /* Just a guess. */ |
| return OP_TYPE_IMM_W; |
| |
| return OP_TYPE_IMM_L; |
| } |
| } |
| |
| gcc_assert (!reload_completed); |
| |
| if (FLOAT_MODE_P (GET_MODE (op))) |
| return OP_TYPE_FPN; |
| |
| return OP_TYPE_RN; |
| } |
| |
| /* Implement opx_type attribute. |
| Return type of INSN's operand X. |
| If ADDRESS_P is true, return type of memory location operand refers to. */ |
| enum attr_opx_type |
| m68k_sched_attr_opx_type (rtx_insn *insn, int address_p) |
| { |
| switch (sched_attr_op_type (insn, true, address_p != 0)) |
| { |
| case OP_TYPE_RN: |
| return OPX_TYPE_RN; |
| |
| case OP_TYPE_FPN: |
| return OPX_TYPE_FPN; |
| |
| case OP_TYPE_MEM1: |
| return OPX_TYPE_MEM1; |
| |
| case OP_TYPE_MEM234: |
| return OPX_TYPE_MEM234; |
| |
| case OP_TYPE_MEM5: |
| return OPX_TYPE_MEM5; |
| |
| case OP_TYPE_MEM6: |
| return OPX_TYPE_MEM6; |
| |
| case OP_TYPE_MEM7: |
| return OPX_TYPE_MEM7; |
| |
| case OP_TYPE_IMM_Q: |
| return OPX_TYPE_IMM_Q; |
| |
| case OP_TYPE_IMM_W: |
| return OPX_TYPE_IMM_W; |
| |
| case OP_TYPE_IMM_L: |
| return OPX_TYPE_IMM_L; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Implement opy_type attribute. |
| Return type of INSN's operand Y. |
| If ADDRESS_P is true, return type of memory location operand refers to. */ |
| enum attr_opy_type |
| m68k_sched_attr_opy_type (rtx_insn *insn, int address_p) |
| { |
| switch (sched_attr_op_type (insn, false, address_p != 0)) |
| { |
| case OP_TYPE_RN: |
| return OPY_TYPE_RN; |
| |
| case OP_TYPE_FPN: |
| return OPY_TYPE_FPN; |
| |
| case OP_TYPE_MEM1: |
| return OPY_TYPE_MEM1; |
| |
| case OP_TYPE_MEM234: |
| return OPY_TYPE_MEM234; |
| |
| case OP_TYPE_MEM5: |
| return OPY_TYPE_MEM5; |
| |
| case OP_TYPE_MEM6: |
| return OPY_TYPE_MEM6; |
| |
| case OP_TYPE_MEM7: |
| return OPY_TYPE_MEM7; |
| |
| case OP_TYPE_IMM_Q: |
| return OPY_TYPE_IMM_Q; |
| |
| case OP_TYPE_IMM_W: |
| return OPY_TYPE_IMM_W; |
| |
| case OP_TYPE_IMM_L: |
| return OPY_TYPE_IMM_L; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return size of INSN as int. */ |
| static int |
| sched_get_attr_size_int (rtx_insn *insn) |
| { |
| int size; |
| |
| switch (get_attr_type (insn)) |
| { |
| case TYPE_IGNORE: |
| /* There should be no references to m68k_sched_attr_size for 'ignore' |
| instructions. */ |
| gcc_unreachable (); |
| return 0; |
| |
| case TYPE_MUL_L: |
| size = 2; |
| break; |
| |
| default: |
| size = 1; |
| break; |
| } |
| |
| switch (get_attr_opx_type (insn)) |
| { |
| case OPX_TYPE_NONE: |
| case OPX_TYPE_RN: |
| case OPX_TYPE_FPN: |
| case OPX_TYPE_MEM1: |
| case OPX_TYPE_MEM234: |
| case OPY_TYPE_IMM_Q: |
| break; |
| |
| case OPX_TYPE_MEM5: |
| case OPX_TYPE_MEM6: |
| /* Here we assume that most absolute references are short. */ |
| case OPX_TYPE_MEM7: |
| case OPY_TYPE_IMM_W: |
| ++size; |
| break; |
| |
| case OPY_TYPE_IMM_L: |
| size += 2; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| switch (get_attr_opy_type (insn)) |
| { |
| case OPY_TYPE_NONE: |
| case OPY_TYPE_RN: |
| case OPY_TYPE_FPN: |
| case OPY_TYPE_MEM1: |
| case OPY_TYPE_MEM234: |
| case OPY_TYPE_IMM_Q: |
| break; |
| |
| case OPY_TYPE_MEM5: |
| case OPY_TYPE_MEM6: |
| /* Here we assume that most absolute references are short. */ |
| case OPY_TYPE_MEM7: |
| case OPY_TYPE_IMM_W: |
| ++size; |
| break; |
| |
| case OPY_TYPE_IMM_L: |
| size += 2; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (size > 3) |
| { |
| gcc_assert (!reload_completed); |
| |
| size = 3; |
| } |
| |
| return size; |
| } |
| |
| /* Return size of INSN as attribute enum value. */ |
| enum attr_size |
| m68k_sched_attr_size (rtx_insn *insn) |
| { |
| switch (sched_get_attr_size_int (insn)) |
| { |
| case 1: |
| return SIZE_1; |
| |
| case 2: |
| return SIZE_2; |
| |
| case 3: |
| return SIZE_3; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Return operand X or Y (depending on OPX_P) of INSN, |
| if it is a MEM, or NULL overwise. */ |
| static enum attr_op_type |
| sched_get_opxy_mem_type (rtx_insn *insn, bool opx_p) |
| { |
| if (opx_p) |
| { |
| switch (get_attr_opx_type (insn)) |
| { |
| case OPX_TYPE_NONE: |
| case OPX_TYPE_RN: |
| case OPX_TYPE_FPN: |
| case OPX_TYPE_IMM_Q: |
| case OPX_TYPE_IMM_W: |
| case OPX_TYPE_IMM_L: |
| return OP_TYPE_RN; |
| |
| case OPX_TYPE_MEM1: |
| case OPX_TYPE_MEM234: |
| case OPX_TYPE_MEM5: |
| case OPX_TYPE_MEM7: |
| return OP_TYPE_MEM1; |
| |
| case OPX_TYPE_MEM6: |
| return OP_TYPE_MEM6; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| else |
| { |
| switch (get_attr_opy_type (insn)) |
| { |
| case OPY_TYPE_NONE: |
| case OPY_TYPE_RN: |
| case OPY_TYPE_FPN: |
| case OPY_TYPE_IMM_Q: |
| case OPY_TYPE_IMM_W: |
| case OPY_TYPE_IMM_L: |
| return OP_TYPE_RN; |
| |
| case OPY_TYPE_MEM1: |
| case OPY_TYPE_MEM234: |
| case OPY_TYPE_MEM5: |
| case OPY_TYPE_MEM7: |
| return OP_TYPE_MEM1; |
| |
| case OPY_TYPE_MEM6: |
| return OP_TYPE_MEM6; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| } |
| |
| /* Implement op_mem attribute. */ |
| enum attr_op_mem |
| m68k_sched_attr_op_mem (rtx_insn *insn) |
| { |
| enum attr_op_type opx; |
| enum attr_op_type opy; |
| |
| opx = sched_get_opxy_mem_type (insn, true); |
| opy = sched_get_opxy_mem_type (insn, false); |
| |
| if (opy == OP_TYPE_RN && opx == OP_TYPE_RN) |
| return OP_MEM_00; |
| |
| if (opy == OP_TYPE_RN && opx == OP_TYPE_MEM1) |
| { |
| switch (get_attr_opx_access (insn)) |
| { |
| case OPX_ACCESS_R: |
| return OP_MEM_10; |
| |
| case OPX_ACCESS_W: |
| return OP_MEM_01; |
| |
| case OPX_ACCESS_RW: |
| return OP_MEM_11; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| if (opy == OP_TYPE_RN && opx == OP_TYPE_MEM6) |
| { |
| switch (get_attr_opx_access (insn)) |
| { |
| case OPX_ACCESS_R: |
| return OP_MEM_I0; |
| |
| case OPX_ACCESS_W: |
| return OP_MEM_0I; |
| |
| case OPX_ACCESS_RW: |
| return OP_MEM_I1; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| if (opy == OP_TYPE_MEM1 && opx == OP_TYPE_RN) |
| return OP_MEM_10; |
| |
| if (opy == OP_TYPE_MEM1 && opx == OP_TYPE_MEM1) |
| { |
| switch (get_attr_opx_access (insn)) |
| { |
| case OPX_ACCESS_W: |
| return OP_MEM_11; |
| |
| default: |
| gcc_assert (!reload_completed); |
| return OP_MEM_11; |
| } |
| } |
| |
| if (opy == OP_TYPE_MEM1 && opx == OP_TYPE_MEM6) |
| { |
| switch (get_attr_opx_access (insn)) |
| { |
| case OPX_ACCESS_W: |
| return OP_MEM_1I; |
| |
| default: |
| gcc_assert (!reload_completed); |
| return OP_MEM_1I; |
| } |
| } |
| |
| if (opy == OP_TYPE_MEM6 && opx == OP_TYPE_RN) |
| return OP_MEM_I0; |
| |
| if (opy == OP_TYPE_MEM6 && opx == OP_TYPE_MEM1) |
| { |
| switch (get_attr_opx_access (insn)) |
| { |
| case OPX_ACCESS_W: |
| return OP_MEM_I1; |
| |
| default: |
| gcc_assert (!reload_completed); |
| return OP_MEM_I1; |
| } |
| } |
| |
| gcc_assert (opy == OP_TYPE_MEM6 && opx == OP_TYPE_MEM6); |
| gcc_assert (!reload_completed); |
| return OP_MEM_I1; |
| } |
| |
| /* Data for ColdFire V4 index bypass. |
| Producer modifies register that is used as index in consumer with |
| specified scale. */ |
| static struct |
| { |
| /* Producer instruction. */ |
| rtx pro; |
| |
| /* Consumer instruction. */ |
| rtx con; |
| |
| /* Scale of indexed memory access within consumer. |
| Or zero if bypass should not be effective at the moment. */ |
| int scale; |
| } sched_cfv4_bypass_data; |
| |
| /* An empty state that is used in m68k_sched_adjust_cost. */ |
| static state_t sched_adjust_cost_state; |
| |
| /* Implement adjust_cost scheduler hook. |
| Return adjusted COST of dependency LINK between DEF_INSN and INSN. */ |
| static int |
| m68k_sched_adjust_cost (rtx_insn *insn, int, rtx_insn *def_insn, int cost, |
| unsigned int) |
| { |
| int delay; |
| |
| if (recog_memoized (def_insn) < 0 |
| || recog_memoized (insn) < 0) |
| return cost; |
| |
| if (sched_cfv4_bypass_data.scale == 1) |
| /* Handle ColdFire V4 bypass for indexed address with 1x scale. */ |
| { |
| /* haifa-sched.cc: insn_cost () calls bypass_p () just before |
| targetm.sched.adjust_cost (). Hence, we can be relatively sure |
| that the data in sched_cfv4_bypass_data is up to date. */ |
| gcc_assert (sched_cfv4_bypass_data.pro == def_insn |
| && sched_cfv4_bypass_data.con == insn); |
| |
| if (cost < 3) |
| cost = 3; |
| |
| sched_cfv4_bypass_data.pro = NULL; |
| sched_cfv4_bypass_data.con = NULL; |
| sched_cfv4_bypass_data.scale = 0; |
| } |
| else |
| gcc_assert (sched_cfv4_bypass_data.pro == NULL |
| && sched_cfv4_bypass_data.con == NULL |
| && sched_cfv4_bypass_data.scale == 0); |
| |
| /* Don't try to issue INSN earlier than DFA permits. |
| This is especially useful for instructions that write to memory, |
| as their true dependence (default) latency is better to be set to 0 |
| to workaround alias analysis limitations. |
| This is, in fact, a machine independent tweak, so, probably, |
| it should be moved to haifa-sched.cc: insn_cost (). */ |
| delay = min_insn_conflict_delay (sched_adjust_cost_state, def_insn, insn); |
| if (delay > cost) |
| cost = delay; |
| |
| return cost; |
| } |
| |
| /* Return maximal number of insns that can be scheduled on a single cycle. */ |
| static int |
| m68k_sched_issue_rate (void) |
| { |
| switch (m68k_sched_cpu) |
| { |
| case CPU_CFV1: |
| case CPU_CFV2: |
| case CPU_CFV3: |
| return 1; |
| |
| case CPU_CFV4: |
| return 2; |
| |
| default: |
| gcc_unreachable (); |
| return 0; |
| } |
| } |
| |
| /* Maximal length of instruction for current CPU. |
| E.g. it is 3 for any ColdFire core. */ |
| static int max_insn_size; |
| |
| /* Data to model instruction buffer of CPU. */ |
| struct _sched_ib |
| { |
| /* True if instruction buffer model is modeled for current CPU. */ |
| bool enabled_p; |
| |
| /* Size of the instruction buffer in words. */ |
| int size; |
| |
| /* Number of filled words in the instruction buffer. */ |
| int filled; |
| |
| /* Additional information about instruction buffer for CPUs that have |
| a buffer of instruction records, rather then a plain buffer |
| of instruction words. */ |
| struct _sched_ib_records |
| { |
| /* Size of buffer in records. */ |
| int n_insns; |
| |
| /* Array to hold data on adjustments made to the size of the buffer. */ |
| int *adjust; |
| |
| /* Index of the above array. */ |
| int adjust_index; |
| } records; |
| |
| /* An insn that reserves (marks empty) one word in the instruction buffer. */ |
| rtx insn; |
| }; |
| |
| static struct _sched_ib sched_ib; |
| |
| /* ID of memory unit. */ |
| static int sched_mem_unit_code; |
| |
| /* Implementation of the targetm.sched.variable_issue () hook. |
| It is called after INSN was issued. It returns the number of insns |
| that can possibly get scheduled on the current cycle. |
| It is used here to determine the effect of INSN on the instruction |
| buffer. */ |
| static int |
| m68k_sched_variable_issue (FILE *sched_dump ATTRIBUTE_UNUSED, |
| int sched_verbose ATTRIBUTE_UNUSED, |
| rtx_insn *insn, int can_issue_more) |
| { |
| int insn_size; |
| |
| if (recog_memoized (insn) >= 0 && get_attr_type (insn) != TYPE_IGNORE) |
| { |
| switch (m68k_sched_cpu) |
| { |
| case CPU_CFV1: |
| case CPU_CFV2: |
| insn_size = sched_get_attr_size_int (insn); |
| break; |
| |
| case CPU_CFV3: |
| insn_size = sched_get_attr_size_int (insn); |
| |
| /* ColdFire V3 and V4 cores have instruction buffers that can |
| accumulate up to 8 instructions regardless of instructions' |
| sizes. So we should take care not to "prefetch" 24 one-word |
| or 12 two-words instructions. |
| To model this behavior we temporarily decrease size of the |
| buffer by (max_insn_size - insn_size) for next 7 instructions. */ |
| { |
| int adjust; |
| |
| adjust = max_insn_size - insn_size; |
| sched_ib.size -= adjust; |
| |
| if (sched_ib.filled > sched_ib.size) |
| sched_ib.filled = sched_ib.size; |
| |
| sched_ib.records.adjust[sched_ib.records.adjust_index] = adjust; |
| } |
| |
| ++sched_ib.records.adjust_index; |
| if (sched_ib.records.adjust_index == sched_ib.records.n_insns) |
| sched_ib.records.adjust_index = 0; |
| |
| /* Undo adjustment we did 7 instructions ago. */ |
| sched_ib.size |
| += sched_ib.records.adjust[sched_ib.records.adjust_index]; |
| |
| break; |
| |
| case CPU_CFV4: |
| gcc_assert (!sched_ib.enabled_p); |
| insn_size = 0; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (insn_size > sched_ib.filled) |
| /* Scheduling for register pressure does not always take DFA into |
| account. Workaround instruction buffer not being filled enough. */ |
| { |
| gcc_assert (sched_pressure == SCHED_PRESSURE_WEIGHTED); |
| insn_size = sched_ib.filled; |
| } |
| |
| --can_issue_more; |
| } |
| else if (GET_CODE (PATTERN (insn)) == ASM_INPUT |
| || asm_noperands (PATTERN (insn)) >= 0) |
| insn_size = sched_ib.filled; |
| else |
| insn_size = 0; |
| |
| sched_ib.filled -= insn_size; |
| |
| return can_issue_more; |
| } |
| |
| /* Return how many instructions should scheduler lookahead to choose the |
| best one. */ |
| static int |
| m68k_sched_first_cycle_multipass_dfa_lookahead (void) |
| { |
| return m68k_sched_issue_rate () - 1; |
| } |
| |
| /* Implementation of targetm.sched.init_global () hook. |
| It is invoked once per scheduling pass and is used here |
| to initialize scheduler constants. */ |
| static void |
| m68k_sched_md_init_global (FILE *sched_dump ATTRIBUTE_UNUSED, |
| int sched_verbose ATTRIBUTE_UNUSED, |
| int n_insns ATTRIBUTE_UNUSED) |
| { |
| /* Check that all instructions have DFA reservations and |
| that all instructions can be issued from a clean state. */ |
| if (flag_checking) |
| { |
| rtx_insn *insn; |
| state_t state; |
| |
| state = alloca (state_size ()); |
| |
| for (insn = get_insns (); insn != NULL; insn = NEXT_INSN (insn)) |
| { |
| if (INSN_P (insn) && recog_memoized (insn) >= 0) |
| { |
| gcc_assert (insn_has_dfa_reservation_p (insn)); |
| |
| state_reset (state); |
| if (state_transition (state, insn) >= 0) |
| gcc_unreachable (); |
| } |
| } |
| } |
| |
| /* Setup target cpu. */ |
| |
| /* ColdFire V4 has a set of features to keep its instruction buffer full |
| (e.g., a separate memory bus for instructions) and, hence, we do not model |
| buffer for this CPU. */ |
| sched_ib.enabled_p = (m68k_sched_cpu != CPU_CFV4); |
| |
| switch (m68k_sched_cpu) |
| { |
| case CPU_CFV4: |
| sched_ib.filled = 0; |
| |
| /* FALLTHRU */ |
| |
| case CPU_CFV1: |
| case CPU_CFV2: |
| max_insn_size = 3; |
| sched_ib.records.n_insns = 0; |
| sched_ib.records.adjust = NULL; |
| break; |
| |
| case CPU_CFV3: |
| max_insn_size = 3; |
| sched_ib.records.n_insns = 8; |
| sched_ib.records.adjust = XNEWVEC (int, sched_ib.records.n_insns); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| sched_mem_unit_code = get_cpu_unit_code ("cf_mem1"); |
| |
| sched_adjust_cost_state = xmalloc (state_size ()); |
| state_reset (sched_adjust_cost_state); |
| |
| start_sequence (); |
| emit_insn (gen_ib ()); |
| sched_ib.insn = get_insns (); |
| end_sequence (); |
| } |
| |
| /* Scheduling pass is now finished. Free/reset static variables. */ |
| static void |
| m68k_sched_md_finish_global (FILE *dump ATTRIBUTE_UNUSED, |
| int verbose ATTRIBUTE_UNUSED) |
| { |
| sched_ib.insn = NULL; |
| |
| free (sched_adjust_cost_state); |
| sched_adjust_cost_state = NULL; |
| |
| sched_mem_unit_code = 0; |
| |
| free (sched_ib.records.adjust); |
| sched_ib.records.adjust = NULL; |
| sched_ib.records.n_insns = 0; |
| max_insn_size = 0; |
| } |
| |
| /* Implementation of targetm.sched.init () hook. |
| It is invoked each time scheduler starts on the new block (basic block or |
| extended basic block). */ |
| static void |
| m68k_sched_md_init (FILE *sched_dump ATTRIBUTE_UNUSED, |
| int sched_verbose ATTRIBUTE_UNUSED, |
| int n_insns ATTRIBUTE_UNUSED) |
| { |
| switch (m68k_sched_cpu) |
| { |
| case CPU_CFV1: |
| case CPU_CFV2: |
| sched_ib.size = 6; |
| break; |
| |
| case CPU_CFV3: |
| sched_ib.size = sched_ib.records.n_insns * max_insn_size; |
| |
| memset (sched_ib.records.adjust, 0, |
| sched_ib.records.n_insns * sizeof (*sched_ib.records.adjust)); |
| sched_ib.records.adjust_index = 0; |
| break; |
| |
| case CPU_CFV4: |
| gcc_assert (!sched_ib.enabled_p); |
| sched_ib.size = 0; |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (sched_ib.enabled_p) |
| /* haifa-sched.cc: schedule_block () calls advance_cycle () just before |
| the first cycle. Workaround that. */ |
| sched_ib.filled = -2; |
| } |
| |
| /* Implementation of targetm.sched.dfa_pre_advance_cycle () hook. |
| It is invoked just before current cycle finishes and is used here |
| to track if instruction buffer got its two words this cycle. */ |
| static void |
| m68k_sched_dfa_pre_advance_cycle (void) |
| { |
| if (!sched_ib.enabled_p) |
| return; |
| |
| if (!cpu_unit_reservation_p (curr_state, sched_mem_unit_code)) |
| { |
| sched_ib.filled += 2; |
| |
| if (sched_ib.filled > sched_ib.size) |
| sched_ib.filled = sched_ib.size; |
| } |
| } |
| |
| /* Implementation of targetm.sched.dfa_post_advance_cycle () hook. |
| It is invoked just after new cycle begins and is used here |
| to setup number of filled words in the instruction buffer so that |
| instructions which won't have all their words prefetched would be |
| stalled for a cycle. */ |
| static void |
| m68k_sched_dfa_post_advance_cycle (void) |
| { |
| int i; |
| |
| if (!sched_ib.enabled_p) |
| return; |
| |
| /* Setup number of prefetched instruction words in the instruction |
| buffer. */ |
| i = max_insn_size - sched_ib.filled; |
| |
| while (--i >= 0) |
| { |
| if (state_transition (curr_state, sched_ib.insn) >= 0) |
| /* Pick up scheduler state. */ |
| ++sched_ib.filled; |
| } |
| } |
| |
| /* Return X or Y (depending on OPX_P) operand of INSN, |
| if it is an integer register, or NULL overwise. */ |
| static rtx |
| sched_get_reg_operand (rtx_insn *insn, bool opx_p) |
| { |
| rtx op = NULL; |
| |
| if (opx_p) |
| { |
| if (get_attr_opx_type (insn) == OPX_TYPE_RN) |
| { |
| op = sched_get_operand (insn, true); |
| gcc_assert (op != NULL); |
| |
| if (!reload_completed && !REG_P (op)) |
| return NULL; |
| } |
| } |
| else |
| { |
| if (get_attr_opy_type (insn) == OPY_TYPE_RN) |
| { |
| op = sched_get_operand (insn, false); |
| gcc_assert (op != NULL); |
| |
| if (!reload_completed && !REG_P (op)) |
| return NULL; |
| } |
| } |
| |
| return op; |
| } |
| |
| /* Return true, if X or Y (depending on OPX_P) operand of INSN |
| is a MEM. */ |
| static bool |
| sched_mem_operand_p (rtx_insn *insn, bool opx_p) |
| { |
| switch (sched_get_opxy_mem_type (insn, opx_p)) |
| { |
| case OP_TYPE_MEM1: |
| case OP_TYPE_MEM6: |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Return X or Y (depending on OPX_P) operand of INSN, |
| if it is a MEM, or NULL overwise. */ |
| static rtx |
| sched_get_mem_operand (rtx_insn *insn, bool must_read_p, bool must_write_p) |
| { |
| bool opx_p; |
| bool opy_p; |
| |
| opx_p = false; |
| opy_p = false; |
| |
| if (must_read_p) |
| { |
| opx_p = true; |
| opy_p = true; |
| } |
| |
| if (must_write_p) |
| { |
| opx_p = true; |
| opy_p = false; |
| } |
| |
| if (opy_p && sched_mem_operand_p (insn, false)) |
| return sched_get_operand (insn, false); |
| |
| if (opx_p && sched_mem_operand_p (insn, true)) |
| return sched_get_operand (insn, true); |
| |
| gcc_unreachable (); |
| return NULL; |
| } |
| |
| /* Return non-zero if PRO modifies register used as part of |
| address in CON. */ |
| int |
| m68k_sched_address_bypass_p (rtx_insn *pro, rtx_insn *con) |
| { |
| rtx pro_x; |
| rtx con_mem_read; |
| |
| pro_x = sched_get_reg_operand (pro, true); |
| if (pro_x == NULL) |
| return 0; |
| |
| con_mem_read = sched_get_mem_operand (con, true, false); |
| gcc_assert (con_mem_read != NULL); |
| |
| if (reg_mentioned_p (pro_x, con_mem_read)) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Helper function for m68k_sched_indexed_address_bypass_p. |
| if PRO modifies register used as index in CON, |
| return scale of indexed memory access in CON. Return zero overwise. */ |
| static int |
| sched_get_indexed_address_scale (rtx_insn *pro, rtx_insn *con) |
| { |
| rtx reg; |
| rtx mem; |
| struct m68k_address address; |
| |
| reg = sched_get_reg_operand (pro, true); |
| if (reg == NULL) |
| return 0; |
| |
| mem = sched_get_mem_operand (con, true, false); |
| gcc_assert (mem != NULL && MEM_P (mem)); |
| |
| if (!m68k_decompose_address (GET_MODE (mem), XEXP (mem, 0), reload_completed, |
| &address)) |
| gcc_unreachable (); |
| |
| if (REGNO (reg) == REGNO (address.index)) |
| { |
| gcc_assert (address.scale != 0); |
| return address.scale; |
| } |
| |
| return 0; |
| } |
| |
| /* Return non-zero if PRO modifies register used |
| as index with scale 2 or 4 in CON. */ |
| int |
| m68k_sched_indexed_address_bypass_p (rtx_insn *pro, rtx_insn *con) |
| { |
| gcc_assert (sched_cfv4_bypass_data.pro == NULL |
| && sched_cfv4_bypass_data.con == NULL |
| && sched_cfv4_bypass_data.scale == 0); |
| |
| switch (sched_get_indexed_address_scale (pro, con)) |
| { |
| case 1: |
| /* We can't have a variable latency bypass, so |
| remember to adjust the insn cost in adjust_cost hook. */ |
| sched_cfv4_bypass_data.pro = pro; |
| sched_cfv4_bypass_data.con = con; |
| sched_cfv4_bypass_data.scale = 1; |
| return 0; |
| |
| case 2: |
| case 4: |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* We generate a two-instructions program at M_TRAMP : |
| movea.l &CHAIN_VALUE,%a0 |
| jmp FNADDR |
| where %a0 can be modified by changing STATIC_CHAIN_REGNUM. */ |
| |
| static void |
| m68k_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value) |
| { |
| rtx fnaddr = XEXP (DECL_RTL (fndecl), 0); |
| rtx mem; |
| |
| gcc_assert (ADDRESS_REGNO_P (STATIC_CHAIN_REGNUM)); |
| |
| mem = adjust_address (m_tramp, HImode, 0); |
| emit_move_insn (mem, GEN_INT(0x207C + ((STATIC_CHAIN_REGNUM-8) << 9))); |
| mem = adjust_address (m_tramp, SImode, 2); |
| emit_move_insn (mem, chain_value); |
| |
| mem = adjust_address (m_tramp, HImode, 6); |
| emit_move_insn (mem, GEN_INT(0x4EF9)); |
| mem = adjust_address (m_tramp, SImode, 8); |
| emit_move_insn (mem, fnaddr); |
| |
| FINALIZE_TRAMPOLINE (XEXP (m_tramp, 0)); |
| } |
| |
| /* On the 68000, the RTS insn cannot pop anything. |
| On the 68010, the RTD insn may be used to pop them if the number |
| of args is fixed, but if the number is variable then the caller |
| must pop them all. RTD can't be used for library calls now |
| because the library is compiled with the Unix compiler. |
| Use of RTD is a selectable option, since it is incompatible with |
| standard Unix calling sequences. If the option is not selected, |
| the caller must always pop the args. */ |
| |
| static poly_int64 |
| m68k_return_pops_args (tree fundecl, tree funtype, poly_int64 size) |
| { |
| return ((TARGET_RTD |
| && (!fundecl |
| || TREE_CODE (fundecl) != IDENTIFIER_NODE) |
| && (!stdarg_p (funtype))) |
| ? (HOST_WIDE_INT) size : 0); |
| } |
| |
| /* Make sure everything's fine if we *don't* have a given processor. |
| This assumes that putting a register in fixed_regs will keep the |
| compiler's mitts completely off it. We don't bother to zero it out |
| of register classes. */ |
| |
| static void |
| m68k_conditional_register_usage (void) |
| { |
| int i; |
| HARD_REG_SET x; |
| if (!TARGET_HARD_FLOAT) |
| { |
| x = reg_class_contents[FP_REGS]; |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| if (TEST_HARD_REG_BIT (x, i)) |
| fixed_regs[i] = call_used_regs[i] = 1; |
| } |
| if (flag_pic) |
| fixed_regs[PIC_REG] = call_used_regs[PIC_REG] = 1; |
| } |
| |
| static void |
| m68k_init_sync_libfuncs (void) |
| { |
| init_sync_libfuncs (UNITS_PER_WORD); |
| } |
| |
| /* Implements EPILOGUE_USES. All registers are live on exit from an |
| interrupt routine. */ |
| bool |
| m68k_epilogue_uses (int regno ATTRIBUTE_UNUSED) |
| { |
| return (reload_completed |
| && (m68k_get_function_kind (current_function_decl) |
| == m68k_fk_interrupt_handler)); |
| } |
| |
| |
| /* Implement TARGET_C_EXCESS_PRECISION. |
| |
| Set the value of FLT_EVAL_METHOD in float.h. When using 68040 fp |
| instructions, we get proper intermediate rounding, otherwise we |
| get extended precision results. */ |
| |
| static enum flt_eval_method |
| m68k_excess_precision (enum excess_precision_type type) |
| { |
| switch (type) |
| { |
| case EXCESS_PRECISION_TYPE_FAST: |
| /* The fastest type to promote to will always be the native type, |
| whether that occurs with implicit excess precision or |
| otherwise. */ |
| return FLT_EVAL_METHOD_PROMOTE_TO_FLOAT; |
| case EXCESS_PRECISION_TYPE_STANDARD: |
| case EXCESS_PRECISION_TYPE_IMPLICIT: |
| /* Otherwise, the excess precision we want when we are |
| in a standards compliant mode, and the implicit precision we |
| provide can be identical. */ |
| if (TARGET_68040 || ! TARGET_68881) |
| return FLT_EVAL_METHOD_PROMOTE_TO_FLOAT; |
| |
| return FLT_EVAL_METHOD_PROMOTE_TO_LONG_DOUBLE; |
| case EXCESS_PRECISION_TYPE_FLOAT16: |
| error ("%<-fexcess-precision=16%> is not supported on this target"); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| return FLT_EVAL_METHOD_UNPREDICTABLE; |
| } |
| |
| /* Implement PUSH_ROUNDING. On the 680x0, sp@- in a byte insn really pushes |
| a word. On the ColdFire, sp@- in a byte insn pushes just a byte. */ |
| |
| poly_int64 |
| m68k_push_rounding (poly_int64 bytes) |
| { |
| if (TARGET_COLDFIRE) |
| return bytes; |
| return (bytes + 1) & ~1; |
| } |
| |
| /* Implement TARGET_PROMOTE_FUNCTION_MODE. */ |
| |
| static machine_mode |
| m68k_promote_function_mode (const_tree type, machine_mode mode, |
| int *punsignedp ATTRIBUTE_UNUSED, |
| const_tree fntype ATTRIBUTE_UNUSED, |
| int for_return) |
| { |
| /* Promote libcall arguments narrower than int to match the normal C |
| ABI (for which promotions are handled via |
| TARGET_PROMOTE_PROTOTYPES). */ |
| if (type == NULL_TREE && !for_return && (mode == QImode || mode == HImode)) |
| return SImode; |
| return mode; |
| } |
| |
| #include "gt-m68k.h" |