| /* Subroutines for insn-output.c for HPPA. |
| Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, |
| 2002, 2003, 2004 Free Software Foundation, Inc. |
| Contributed by Tim Moore (moore@cs.utah.edu), based on sparc.c |
| |
| 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 2, 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 COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "rtl.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "real.h" |
| #include "insn-config.h" |
| #include "conditions.h" |
| #include "insn-attr.h" |
| #include "flags.h" |
| #include "tree.h" |
| #include "output.h" |
| #include "except.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "reload.h" |
| #include "integrate.h" |
| #include "function.h" |
| #include "toplev.h" |
| #include "ggc.h" |
| #include "recog.h" |
| #include "predict.h" |
| #include "tm_p.h" |
| #include "target.h" |
| #include "target-def.h" |
| |
| static int hppa_use_dfa_pipeline_interface (void); |
| |
| #undef TARGET_SCHED_USE_DFA_PIPELINE_INTERFACE |
| #define TARGET_SCHED_USE_DFA_PIPELINE_INTERFACE hppa_use_dfa_pipeline_interface |
| |
| static int |
| hppa_use_dfa_pipeline_interface (void) |
| { |
| return 1; |
| } |
| |
| /* Return nonzero if there is a bypass for the output of |
| OUT_INSN and the fp store IN_INSN. */ |
| int |
| hppa_fpstore_bypass_p (rtx out_insn, rtx in_insn) |
| { |
| enum machine_mode store_mode; |
| enum machine_mode other_mode; |
| rtx set; |
| |
| if (recog_memoized (in_insn) < 0 |
| || get_attr_type (in_insn) != TYPE_FPSTORE |
| || recog_memoized (out_insn) < 0) |
| return 0; |
| |
| store_mode = GET_MODE (SET_SRC (PATTERN (in_insn))); |
| |
| set = single_set (out_insn); |
| if (!set) |
| return 0; |
| |
| other_mode = GET_MODE (SET_SRC (set)); |
| |
| return (GET_MODE_SIZE (store_mode) == GET_MODE_SIZE (other_mode)); |
| } |
| |
| |
| #ifndef DO_FRAME_NOTES |
| #ifdef INCOMING_RETURN_ADDR_RTX |
| #define DO_FRAME_NOTES 1 |
| #else |
| #define DO_FRAME_NOTES 0 |
| #endif |
| #endif |
| |
| static void copy_reg_pointer (rtx, rtx); |
| static int hppa_address_cost (rtx); |
| static bool hppa_rtx_costs (rtx, int, int, int *); |
| static inline rtx force_mode (enum machine_mode, rtx); |
| static void pa_reorg (void); |
| static void pa_combine_instructions (void); |
| static int pa_can_combine_p (rtx, rtx, rtx, int, rtx, rtx, rtx); |
| static int forward_branch_p (rtx); |
| static int shadd_constant_p (int); |
| static void compute_zdepwi_operands (unsigned HOST_WIDE_INT, unsigned *); |
| static int compute_movstr_length (rtx); |
| static int compute_clrstr_length (rtx); |
| static bool pa_assemble_integer (rtx, unsigned int, int); |
| static void remove_useless_addtr_insns (int); |
| static void store_reg (int, HOST_WIDE_INT, int); |
| static void store_reg_modify (int, int, HOST_WIDE_INT); |
| static void load_reg (int, HOST_WIDE_INT, int); |
| static void set_reg_plus_d (int, int, HOST_WIDE_INT, int); |
| static void pa_output_function_prologue (FILE *, HOST_WIDE_INT); |
| static void update_total_code_bytes (int); |
| static void pa_output_function_epilogue (FILE *, HOST_WIDE_INT); |
| static int pa_adjust_cost (rtx, rtx, rtx, int); |
| static int pa_adjust_priority (rtx, int); |
| static int pa_issue_rate (void); |
| static void pa_select_section (tree, int, unsigned HOST_WIDE_INT) |
| ATTRIBUTE_UNUSED; |
| static void pa_encode_section_info (tree, rtx, int); |
| static const char *pa_strip_name_encoding (const char *); |
| static bool pa_function_ok_for_sibcall (tree, tree); |
| static void pa_globalize_label (FILE *, const char *) |
| ATTRIBUTE_UNUSED; |
| static void pa_asm_output_mi_thunk (FILE *, tree, HOST_WIDE_INT, |
| HOST_WIDE_INT, tree); |
| #if !defined(USE_COLLECT2) |
| static void pa_asm_out_constructor (rtx, int); |
| static void pa_asm_out_destructor (rtx, int); |
| #endif |
| static void pa_init_builtins (void); |
| static void copy_fp_args (rtx) ATTRIBUTE_UNUSED; |
| static int length_fp_args (rtx) ATTRIBUTE_UNUSED; |
| static struct deferred_plabel *get_plabel (const char *) |
| ATTRIBUTE_UNUSED; |
| static inline void pa_file_start_level (void) ATTRIBUTE_UNUSED; |
| static inline void pa_file_start_space (int) ATTRIBUTE_UNUSED; |
| static inline void pa_file_start_file (int) ATTRIBUTE_UNUSED; |
| static inline void pa_file_start_mcount (const char*) ATTRIBUTE_UNUSED; |
| static void pa_elf_file_start (void) ATTRIBUTE_UNUSED; |
| static void pa_som_file_start (void) ATTRIBUTE_UNUSED; |
| static void pa_linux_file_start (void) ATTRIBUTE_UNUSED; |
| static void pa_hpux64_gas_file_start (void) ATTRIBUTE_UNUSED; |
| static void pa_hpux64_hpas_file_start (void) ATTRIBUTE_UNUSED; |
| static void output_deferred_plabels (void); |
| #ifdef HPUX_LONG_DOUBLE_LIBRARY |
| static void pa_hpux_init_libfuncs (void); |
| #endif |
| |
| /* Save the operands last given to a compare for use when we |
| generate a scc or bcc insn. */ |
| rtx hppa_compare_op0, hppa_compare_op1; |
| enum cmp_type hppa_branch_type; |
| |
| /* Which cpu we are scheduling for. */ |
| enum processor_type pa_cpu; |
| |
| /* String to hold which cpu we are scheduling for. */ |
| const char *pa_cpu_string; |
| |
| /* Which architecture we are generating code for. */ |
| enum architecture_type pa_arch; |
| |
| /* String to hold which architecture we are generating code for. */ |
| const char *pa_arch_string; |
| |
| /* Counts for the number of callee-saved general and floating point |
| registers which were saved by the current function's prologue. */ |
| static int gr_saved, fr_saved; |
| |
| static rtx find_addr_reg (rtx); |
| |
| /* Keep track of the number of bytes we have output in the CODE subspace |
| during this compilation so we'll know when to emit inline long-calls. */ |
| unsigned long total_code_bytes; |
| |
| /* The last address of the previous function plus the number of bytes in |
| associated thunks that have been output. This is used to determine if |
| a thunk can use an IA-relative branch to reach its target function. */ |
| static int last_address; |
| |
| /* Variables to handle plabels that we discover are necessary at assembly |
| output time. They are output after the current function. */ |
| struct deferred_plabel GTY(()) |
| { |
| rtx internal_label; |
| const char *name; |
| }; |
| static GTY((length ("n_deferred_plabels"))) struct deferred_plabel * |
| deferred_plabels; |
| static size_t n_deferred_plabels = 0; |
| |
| |
| /* Initialize the GCC target structure. */ |
| |
| #undef TARGET_ASM_ALIGNED_HI_OP |
| #define TARGET_ASM_ALIGNED_HI_OP "\t.half\t" |
| #undef TARGET_ASM_ALIGNED_SI_OP |
| #define TARGET_ASM_ALIGNED_SI_OP "\t.word\t" |
| #undef TARGET_ASM_ALIGNED_DI_OP |
| #define TARGET_ASM_ALIGNED_DI_OP "\t.dword\t" |
| #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_UNALIGNED_DI_OP |
| #define TARGET_ASM_UNALIGNED_DI_OP TARGET_ASM_ALIGNED_DI_OP |
| #undef TARGET_ASM_INTEGER |
| #define TARGET_ASM_INTEGER pa_assemble_integer |
| |
| #undef TARGET_ASM_FUNCTION_PROLOGUE |
| #define TARGET_ASM_FUNCTION_PROLOGUE pa_output_function_prologue |
| #undef TARGET_ASM_FUNCTION_EPILOGUE |
| #define TARGET_ASM_FUNCTION_EPILOGUE pa_output_function_epilogue |
| |
| #undef TARGET_SCHED_ADJUST_COST |
| #define TARGET_SCHED_ADJUST_COST pa_adjust_cost |
| #undef TARGET_SCHED_ADJUST_PRIORITY |
| #define TARGET_SCHED_ADJUST_PRIORITY pa_adjust_priority |
| #undef TARGET_SCHED_ISSUE_RATE |
| #define TARGET_SCHED_ISSUE_RATE pa_issue_rate |
| |
| #undef TARGET_ENCODE_SECTION_INFO |
| #define TARGET_ENCODE_SECTION_INFO pa_encode_section_info |
| #undef TARGET_STRIP_NAME_ENCODING |
| #define TARGET_STRIP_NAME_ENCODING pa_strip_name_encoding |
| |
| #undef TARGET_FUNCTION_OK_FOR_SIBCALL |
| #define TARGET_FUNCTION_OK_FOR_SIBCALL pa_function_ok_for_sibcall |
| |
| #undef TARGET_ASM_OUTPUT_MI_THUNK |
| #define TARGET_ASM_OUTPUT_MI_THUNK pa_asm_output_mi_thunk |
| #undef TARGET_ASM_CAN_OUTPUT_MI_THUNK |
| #define TARGET_ASM_CAN_OUTPUT_MI_THUNK default_can_output_mi_thunk_no_vcall |
| |
| #undef TARGET_ASM_FILE_END |
| #define TARGET_ASM_FILE_END output_deferred_plabels |
| |
| #if !defined(USE_COLLECT2) |
| #undef TARGET_ASM_CONSTRUCTOR |
| #define TARGET_ASM_CONSTRUCTOR pa_asm_out_constructor |
| #undef TARGET_ASM_DESTRUCTOR |
| #define TARGET_ASM_DESTRUCTOR pa_asm_out_destructor |
| #endif |
| |
| #undef TARGET_INIT_BUILTINS |
| #define TARGET_INIT_BUILTINS pa_init_builtins |
| |
| #undef TARGET_RTX_COSTS |
| #define TARGET_RTX_COSTS hppa_rtx_costs |
| #undef TARGET_ADDRESS_COST |
| #define TARGET_ADDRESS_COST hppa_address_cost |
| |
| #undef TARGET_MACHINE_DEPENDENT_REORG |
| #define TARGET_MACHINE_DEPENDENT_REORG pa_reorg |
| |
| #ifdef HPUX_LONG_DOUBLE_LIBRARY |
| #undef TARGET_INIT_LIBFUNCS |
| #define TARGET_INIT_LIBFUNCS pa_hpux_init_libfuncs |
| #endif |
| |
| struct gcc_target targetm = TARGET_INITIALIZER; |
| |
| void |
| override_options (void) |
| { |
| if (pa_cpu_string == NULL) |
| pa_cpu_string = TARGET_SCHED_DEFAULT; |
| |
| if (! strcmp (pa_cpu_string, "8000")) |
| { |
| pa_cpu_string = "8000"; |
| pa_cpu = PROCESSOR_8000; |
| } |
| else if (! strcmp (pa_cpu_string, "7100")) |
| { |
| pa_cpu_string = "7100"; |
| pa_cpu = PROCESSOR_7100; |
| } |
| else if (! strcmp (pa_cpu_string, "700")) |
| { |
| pa_cpu_string = "700"; |
| pa_cpu = PROCESSOR_700; |
| } |
| else if (! strcmp (pa_cpu_string, "7100LC")) |
| { |
| pa_cpu_string = "7100LC"; |
| pa_cpu = PROCESSOR_7100LC; |
| } |
| else if (! strcmp (pa_cpu_string, "7200")) |
| { |
| pa_cpu_string = "7200"; |
| pa_cpu = PROCESSOR_7200; |
| } |
| else if (! strcmp (pa_cpu_string, "7300")) |
| { |
| pa_cpu_string = "7300"; |
| pa_cpu = PROCESSOR_7300; |
| } |
| else |
| { |
| warning ("unknown -mschedule= option (%s).\nValid options are 700, 7100, 7100LC, 7200, 7300, and 8000\n", pa_cpu_string); |
| } |
| |
| /* Set the instruction set architecture. */ |
| if (pa_arch_string && ! strcmp (pa_arch_string, "1.0")) |
| { |
| pa_arch_string = "1.0"; |
| pa_arch = ARCHITECTURE_10; |
| target_flags &= ~(MASK_PA_11 | MASK_PA_20); |
| } |
| else if (pa_arch_string && ! strcmp (pa_arch_string, "1.1")) |
| { |
| pa_arch_string = "1.1"; |
| pa_arch = ARCHITECTURE_11; |
| target_flags &= ~MASK_PA_20; |
| target_flags |= MASK_PA_11; |
| } |
| else if (pa_arch_string && ! strcmp (pa_arch_string, "2.0")) |
| { |
| pa_arch_string = "2.0"; |
| pa_arch = ARCHITECTURE_20; |
| target_flags |= MASK_PA_11 | MASK_PA_20; |
| } |
| else if (pa_arch_string) |
| { |
| warning ("unknown -march= option (%s).\nValid options are 1.0, 1.1, and 2.0\n", pa_arch_string); |
| } |
| |
| /* Unconditional branches in the delay slot are not compatible with dwarf2 |
| call frame information. There is no benefit in using this optimization |
| on PA8000 and later processors. */ |
| if (pa_cpu >= PROCESSOR_8000 |
| || (! USING_SJLJ_EXCEPTIONS && flag_exceptions) |
| || flag_unwind_tables) |
| target_flags &= ~MASK_JUMP_IN_DELAY; |
| |
| if (flag_pic && TARGET_PORTABLE_RUNTIME) |
| { |
| warning ("PIC code generation is not supported in the portable runtime model\n"); |
| } |
| |
| if (flag_pic && TARGET_FAST_INDIRECT_CALLS) |
| { |
| warning ("PIC code generation is not compatible with fast indirect calls\n"); |
| } |
| |
| if (! TARGET_GAS && write_symbols != NO_DEBUG) |
| { |
| warning ("-g is only supported when using GAS on this processor,"); |
| warning ("-g option disabled"); |
| write_symbols = NO_DEBUG; |
| } |
| |
| /* We only support the "big PIC" model now. And we always generate PIC |
| code when in 64bit mode. */ |
| if (flag_pic == 1 || TARGET_64BIT) |
| flag_pic = 2; |
| |
| /* We can't guarantee that .dword is available for 32-bit targets. */ |
| if (UNITS_PER_WORD == 4) |
| targetm.asm_out.aligned_op.di = NULL; |
| |
| /* The unaligned ops are only available when using GAS. */ |
| if (!TARGET_GAS) |
| { |
| targetm.asm_out.unaligned_op.hi = NULL; |
| targetm.asm_out.unaligned_op.si = NULL; |
| targetm.asm_out.unaligned_op.di = NULL; |
| } |
| } |
| |
| static void |
| pa_init_builtins (void) |
| { |
| #ifdef DONT_HAVE_FPUTC_UNLOCKED |
| built_in_decls[(int) BUILT_IN_FPUTC_UNLOCKED] = NULL_TREE; |
| implicit_built_in_decls[(int) BUILT_IN_FPUTC_UNLOCKED] = NULL_TREE; |
| #endif |
| } |
| |
| /* If FROM is a probable pointer register, mark TO as a probable |
| pointer register with the same pointer alignment as FROM. */ |
| |
| static void |
| copy_reg_pointer (rtx to, rtx from) |
| { |
| if (REG_POINTER (from)) |
| mark_reg_pointer (to, REGNO_POINTER_ALIGN (REGNO (from))); |
| } |
| |
| /* Return nonzero only if OP is a register of mode MODE, |
| or CONST0_RTX. */ |
| int |
| reg_or_0_operand (rtx op, enum machine_mode mode) |
| { |
| return (op == CONST0_RTX (mode) || register_operand (op, mode)); |
| } |
| |
| /* Return nonzero if OP is suitable for use in a call to a named |
| function. |
| |
| For 2.5 try to eliminate either call_operand_address or |
| function_label_operand, they perform very similar functions. */ |
| int |
| call_operand_address (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_MODE (op) == word_mode |
| && CONSTANT_P (op) && ! TARGET_PORTABLE_RUNTIME); |
| } |
| |
| /* Return 1 if X contains a symbolic expression. We know these |
| expressions will have one of a few well defined forms, so |
| we need only check those forms. */ |
| int |
| symbolic_expression_p (rtx x) |
| { |
| |
| /* Strip off any HIGH. */ |
| if (GET_CODE (x) == HIGH) |
| x = XEXP (x, 0); |
| |
| return (symbolic_operand (x, VOIDmode)); |
| } |
| |
| int |
| symbolic_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| switch (GET_CODE (op)) |
| { |
| case SYMBOL_REF: |
| case LABEL_REF: |
| return 1; |
| case CONST: |
| op = XEXP (op, 0); |
| return ((GET_CODE (XEXP (op, 0)) == SYMBOL_REF |
| || GET_CODE (XEXP (op, 0)) == LABEL_REF) |
| && GET_CODE (XEXP (op, 1)) == CONST_INT); |
| default: |
| return 0; |
| } |
| } |
| |
| /* Return truth value of statement that OP is a symbolic memory |
| operand of mode MODE. */ |
| |
| int |
| symbolic_memory_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| if (GET_CODE (op) != MEM) |
| return 0; |
| op = XEXP (op, 0); |
| return (GET_CODE (op) == SYMBOL_REF || GET_CODE (op) == CONST |
| || GET_CODE (op) == HIGH || GET_CODE (op) == LABEL_REF); |
| } |
| |
| /* Return 1 if the operand is either a register, zero, or a memory operand |
| that is not symbolic. */ |
| |
| int |
| reg_or_0_or_nonsymb_mem_operand (rtx op, enum machine_mode mode) |
| { |
| if (register_operand (op, mode)) |
| return 1; |
| |
| if (op == CONST0_RTX (mode)) |
| return 1; |
| |
| if (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| if (GET_CODE (op) != MEM) |
| return 0; |
| |
| /* Until problems with management of the REG_POINTER flag are resolved, |
| we need to delay creating move insns with unscaled indexed addresses |
| until CSE is not expected. */ |
| if (!TARGET_NO_SPACE_REGS |
| && !cse_not_expected |
| && GET_CODE (XEXP (op, 0)) == PLUS |
| && REG_P (XEXP (XEXP (op, 0), 0)) |
| && REG_P (XEXP (XEXP (op, 0), 1))) |
| return 0; |
| |
| return (!symbolic_memory_operand (op, mode) |
| && memory_address_p (mode, XEXP (op, 0))); |
| } |
| |
| /* Return 1 if the operand is a register operand or a non-symbolic memory |
| operand after reload. This predicate is used for branch patterns that |
| internally handle register reloading. We need to accept non-symbolic |
| memory operands after reload to ensure that the pattern is still valid |
| if reload didn't find a hard register for the operand. */ |
| |
| int |
| reg_before_reload_operand (rtx op, enum machine_mode mode) |
| { |
| /* Don't accept a SUBREG since it will need a reload. */ |
| if (GET_CODE (op) == SUBREG) |
| return 0; |
| |
| if (register_operand (op, mode)) |
| return 1; |
| |
| if (reload_completed |
| && memory_operand (op, mode) |
| && !symbolic_memory_operand (op, mode)) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Accept any constant that can be moved in one instruction into a |
| general register. */ |
| int |
| cint_ok_for_move (HOST_WIDE_INT intval) |
| { |
| /* OK if ldo, ldil, or zdepi, can be used. */ |
| return (CONST_OK_FOR_LETTER_P (intval, 'J') |
| || CONST_OK_FOR_LETTER_P (intval, 'N') |
| || CONST_OK_FOR_LETTER_P (intval, 'K')); |
| } |
| |
| /* Return 1 iff OP is an indexed memory operand. */ |
| int |
| indexed_memory_operand (rtx op, enum machine_mode mode) |
| { |
| if (GET_MODE (op) != mode) |
| return 0; |
| |
| /* Before reload, a (SUBREG (MEM...)) forces reloading into a register. */ |
| if (reload_completed && GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| if (GET_CODE (op) != MEM || symbolic_memory_operand (op, mode)) |
| return 0; |
| |
| op = XEXP (op, 0); |
| |
| return (memory_address_p (mode, op) && IS_INDEX_ADDR_P (op)); |
| } |
| |
| /* Accept anything that can be used as a destination operand for a |
| move instruction. We don't accept indexed memory operands since |
| they are supported only for floating point stores. */ |
| int |
| move_dest_operand (rtx op, enum machine_mode mode) |
| { |
| if (register_operand (op, mode)) |
| return 1; |
| |
| if (GET_MODE (op) != mode) |
| return 0; |
| |
| if (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| if (GET_CODE (op) != MEM || symbolic_memory_operand (op, mode)) |
| return 0; |
| |
| op = XEXP (op, 0); |
| |
| return (memory_address_p (mode, op) |
| && !IS_INDEX_ADDR_P (op) |
| && !IS_LO_SUM_DLT_ADDR_P (op)); |
| } |
| |
| /* Accept anything that can be used as a source operand for a move |
| instruction. */ |
| int |
| move_src_operand (rtx op, enum machine_mode mode) |
| { |
| if (register_operand (op, mode)) |
| return 1; |
| |
| if (GET_CODE (op) == CONSTANT_P_RTX) |
| return 1; |
| |
| if (GET_CODE (op) == CONST_INT) |
| return cint_ok_for_move (INTVAL (op)); |
| |
| if (GET_MODE (op) != mode) |
| return 0; |
| |
| if (GET_CODE (op) == SUBREG) |
| op = SUBREG_REG (op); |
| |
| if (GET_CODE (op) != MEM) |
| return 0; |
| |
| /* Until problems with management of the REG_POINTER flag are resolved, |
| we need to delay creating move insns with unscaled indexed addresses |
| until CSE is not expected. */ |
| if (!TARGET_NO_SPACE_REGS |
| && !cse_not_expected |
| && GET_CODE (XEXP (op, 0)) == PLUS |
| && REG_P (XEXP (XEXP (op, 0), 0)) |
| && REG_P (XEXP (XEXP (op, 0), 1))) |
| return 0; |
| |
| return memory_address_p (mode, XEXP (op, 0)); |
| } |
| |
| /* Accept REG and any CONST_INT that can be moved in one instruction into a |
| general register. */ |
| int |
| reg_or_cint_move_operand (rtx op, enum machine_mode mode) |
| { |
| if (register_operand (op, mode)) |
| return 1; |
| |
| return (GET_CODE (op) == CONST_INT && cint_ok_for_move (INTVAL (op))); |
| } |
| |
| int |
| pic_label_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (!flag_pic) |
| return 0; |
| |
| switch (GET_CODE (op)) |
| { |
| case LABEL_REF: |
| return 1; |
| case CONST: |
| op = XEXP (op, 0); |
| return (GET_CODE (XEXP (op, 0)) == LABEL_REF |
| && GET_CODE (XEXP (op, 1)) == CONST_INT); |
| default: |
| return 0; |
| } |
| } |
| |
| int |
| fp_reg_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return reg_renumber && FP_REG_P (op); |
| } |
| |
| |
| |
| /* Return truth value of whether OP can be used as an operand in a |
| three operand arithmetic insn that accepts registers of mode MODE |
| or 14-bit signed integers. */ |
| int |
| arith_operand (rtx op, enum machine_mode mode) |
| { |
| return (register_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT && INT_14_BITS (op))); |
| } |
| |
| /* Return truth value of whether OP can be used as an operand in a |
| three operand arithmetic insn that accepts registers of mode MODE |
| or 11-bit signed integers. */ |
| int |
| arith11_operand (rtx op, enum machine_mode mode) |
| { |
| return (register_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT && INT_11_BITS (op))); |
| } |
| |
| /* Return truth value of whether OP can be used as an operand in a |
| adddi3 insn. */ |
| int |
| adddi3_operand (rtx op, enum machine_mode mode) |
| { |
| return (register_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT |
| && (TARGET_64BIT ? INT_14_BITS (op) : INT_11_BITS (op)))); |
| } |
| |
| /* A constant integer suitable for use in a PRE_MODIFY memory |
| reference. */ |
| int |
| pre_cint_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT |
| && INTVAL (op) >= -0x2000 && INTVAL (op) < 0x10); |
| } |
| |
| /* A constant integer suitable for use in a POST_MODIFY memory |
| reference. */ |
| int |
| post_cint_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT |
| && INTVAL (op) < 0x2000 && INTVAL (op) >= -0x10); |
| } |
| |
| int |
| arith_double_operand (rtx op, enum machine_mode mode) |
| { |
| return (register_operand (op, mode) |
| || (GET_CODE (op) == CONST_DOUBLE |
| && GET_MODE (op) == mode |
| && VAL_14_BITS_P (CONST_DOUBLE_LOW (op)) |
| && ((CONST_DOUBLE_HIGH (op) >= 0) |
| == ((CONST_DOUBLE_LOW (op) & 0x1000) == 0)))); |
| } |
| |
| /* Return truth value of whether OP is an integer which fits the |
| range constraining immediate operands in three-address insns, or |
| is an integer register. */ |
| |
| int |
| ireg_or_int5_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return ((GET_CODE (op) == CONST_INT && INT_5_BITS (op)) |
| || (GET_CODE (op) == REG && REGNO (op) > 0 && REGNO (op) < 32)); |
| } |
| |
| /* Return nonzero if OP is an integer register, else return zero. */ |
| int |
| ireg_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == REG && REGNO (op) > 0 && REGNO (op) < 32); |
| } |
| |
| /* Return truth value of whether OP is an integer which fits the |
| range constraining immediate operands in three-address insns. */ |
| |
| int |
| int5_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT && INT_5_BITS (op)); |
| } |
| |
| int |
| uint5_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT && INT_U5_BITS (op)); |
| } |
| |
| int |
| int11_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT && INT_11_BITS (op)); |
| } |
| |
| int |
| uint32_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| #if HOST_BITS_PER_WIDE_INT > 32 |
| /* All allowed constants will fit a CONST_INT. */ |
| return (GET_CODE (op) == CONST_INT |
| && (INTVAL (op) >= 0 && INTVAL (op) < (HOST_WIDE_INT) 1 << 32)); |
| #else |
| return (GET_CODE (op) == CONST_INT |
| || (GET_CODE (op) == CONST_DOUBLE |
| && CONST_DOUBLE_HIGH (op) == 0)); |
| #endif |
| } |
| |
| int |
| arith5_operand (rtx op, enum machine_mode mode) |
| { |
| return register_operand (op, mode) || int5_operand (op, mode); |
| } |
| |
| /* True iff zdepi can be used to generate this CONST_INT. |
| zdepi first sign extends a 5 bit signed number to a given field |
| length, then places this field anywhere in a zero. */ |
| int |
| zdepi_cint_p (unsigned HOST_WIDE_INT x) |
| { |
| unsigned HOST_WIDE_INT lsb_mask, t; |
| |
| /* This might not be obvious, but it's at least fast. |
| This function is critical; we don't have the time loops would take. */ |
| lsb_mask = x & -x; |
| t = ((x >> 4) + lsb_mask) & ~(lsb_mask - 1); |
| /* Return true iff t is a power of two. */ |
| return ((t & (t - 1)) == 0); |
| } |
| |
| /* True iff depi or extru can be used to compute (reg & mask). |
| Accept bit pattern like these: |
| 0....01....1 |
| 1....10....0 |
| 1..10..01..1 */ |
| int |
| and_mask_p (unsigned HOST_WIDE_INT mask) |
| { |
| mask = ~mask; |
| mask += mask & -mask; |
| return (mask & (mask - 1)) == 0; |
| } |
| |
| /* True iff depi or extru can be used to compute (reg & OP). */ |
| int |
| and_operand (rtx op, enum machine_mode mode) |
| { |
| return (register_operand (op, mode) |
| || (GET_CODE (op) == CONST_INT && and_mask_p (INTVAL (op)))); |
| } |
| |
| /* True iff depi can be used to compute (reg | MASK). */ |
| int |
| ior_mask_p (unsigned HOST_WIDE_INT mask) |
| { |
| mask += mask & -mask; |
| return (mask & (mask - 1)) == 0; |
| } |
| |
| /* True iff depi can be used to compute (reg | OP). */ |
| int |
| ior_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == CONST_INT && ior_mask_p (INTVAL (op))); |
| } |
| |
| int |
| lhs_lshift_operand (rtx op, enum machine_mode mode) |
| { |
| return register_operand (op, mode) || lhs_lshift_cint_operand (op, mode); |
| } |
| |
| /* True iff OP is a CONST_INT of the forms 0...0xxxx or 0...01...1xxxx. |
| Such values can be the left hand side x in (x << r), using the zvdepi |
| instruction. */ |
| int |
| lhs_lshift_cint_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| unsigned HOST_WIDE_INT x; |
| if (GET_CODE (op) != CONST_INT) |
| return 0; |
| x = INTVAL (op) >> 4; |
| return (x & (x + 1)) == 0; |
| } |
| |
| int |
| arith32_operand (rtx op, enum machine_mode mode) |
| { |
| return register_operand (op, mode) || GET_CODE (op) == CONST_INT; |
| } |
| |
| int |
| pc_or_label_operand (rtx op, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| return (GET_CODE (op) == PC || GET_CODE (op) == LABEL_REF); |
| } |
| |
| /* 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. */ |
| |
| rtx |
| legitimize_pic_address (rtx orig, enum machine_mode mode, rtx reg) |
| { |
| rtx pic_ref = orig; |
| |
| /* Labels need special handling. */ |
| if (pic_label_operand (orig, mode)) |
| { |
| /* We do not want to go through the movXX expanders here since that |
| would create recursion. |
| |
| Nor do we really want to call a generator for a named pattern |
| since that requires multiple patterns if we want to support |
| multiple word sizes. |
| |
| So instead we just emit the raw set, which avoids the movXX |
| expanders completely. */ |
| mark_reg_pointer (reg, BITS_PER_UNIT); |
| emit_insn (gen_rtx_SET (VOIDmode, reg, orig)); |
| current_function_uses_pic_offset_table = 1; |
| return reg; |
| } |
| if (GET_CODE (orig) == SYMBOL_REF) |
| { |
| rtx insn, tmp_reg; |
| |
| if (reg == 0) |
| abort (); |
| |
| /* Before reload, allocate a temporary register for the intermediate |
| result. This allows the sequence to be deleted when the final |
| result is unused and the insns are trivially dead. */ |
| tmp_reg = ((reload_in_progress || reload_completed) |
| ? reg : gen_reg_rtx (Pmode)); |
| |
| emit_move_insn (tmp_reg, |
| gen_rtx_PLUS (word_mode, pic_offset_table_rtx, |
| gen_rtx_HIGH (word_mode, orig))); |
| pic_ref |
| = gen_rtx_MEM (Pmode, |
| gen_rtx_LO_SUM (Pmode, tmp_reg, |
| gen_rtx_UNSPEC (Pmode, |
| gen_rtvec (1, orig), |
| 0))); |
| |
| current_function_uses_pic_offset_table = 1; |
| MEM_NOTRAP_P (pic_ref) = 1; |
| RTX_UNCHANGING_P (pic_ref) = 1; |
| mark_reg_pointer (reg, BITS_PER_UNIT); |
| insn = emit_move_insn (reg, pic_ref); |
| |
| /* Put a REG_EQUAL note on this insn, so that it can be optimized. */ |
| REG_NOTES (insn) = gen_rtx_EXPR_LIST (REG_EQUAL, orig, REG_NOTES (insn)); |
| |
| return reg; |
| } |
| else if (GET_CODE (orig) == CONST) |
| { |
| rtx base; |
| |
| if (GET_CODE (XEXP (orig, 0)) == PLUS |
| && XEXP (XEXP (orig, 0), 0) == pic_offset_table_rtx) |
| return orig; |
| |
| if (reg == 0) |
| abort (); |
| |
| if (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); |
| } |
| else |
| abort (); |
| |
| if (GET_CODE (orig) == CONST_INT) |
| { |
| if (INT_14_BITS (orig)) |
| return plus_constant (base, INTVAL (orig)); |
| orig = force_reg (Pmode, orig); |
| } |
| pic_ref = gen_rtx_PLUS (Pmode, base, orig); |
| /* Likewise, should we set special REG_NOTEs here? */ |
| } |
| |
| return pic_ref; |
| } |
| |
| /* Try machine-dependent ways of modifying an illegitimate address |
| to be legitimate. If we find one, return the new, valid address. |
| This macro is used in only one place: `memory_address' in explow.c. |
| |
| OLDX is the address as it was before break_out_memory_refs was called. |
| In some cases it is useful to look at this to decide what needs to be done. |
| |
| MODE and WIN are passed so that this macro can use |
| GO_IF_LEGITIMATE_ADDRESS. |
| |
| It is always safe for this macro to do nothing. It exists to recognize |
| opportunities to optimize the output. |
| |
| For the PA, transform: |
| |
| memory(X + <large int>) |
| |
| into: |
| |
| if (<large int> & mask) >= 16 |
| Y = (<large int> & ~mask) + mask + 1 Round up. |
| else |
| Y = (<large int> & ~mask) Round down. |
| Z = X + Y |
| memory (Z + (<large int> - Y)); |
| |
| This is for CSE to find several similar references, and only use one Z. |
| |
| X can either be a SYMBOL_REF or REG, but because combine can not |
| perform a 4->2 combination we do nothing for SYMBOL_REF + D where |
| D will not fit in 14 bits. |
| |
| MODE_FLOAT references allow displacements which fit in 5 bits, so use |
| 0x1f as the mask. |
| |
| MODE_INT references allow displacements which fit in 14 bits, so use |
| 0x3fff as the mask. |
| |
| This relies on the fact that most mode MODE_FLOAT references will use FP |
| registers and most mode MODE_INT references will use integer registers. |
| (In the rare case of an FP register used in an integer MODE, we depend |
| on secondary reloads to clean things up.) |
| |
| |
| It is also beneficial to handle (plus (mult (X) (Y)) (Z)) in a special |
| manner if Y is 2, 4, or 8. (allows more shadd insns and shifted indexed |
| addressing modes to be used). |
| |
| Put X and Z into registers. Then put the entire expression into |
| a register. */ |
| |
| rtx |
| hppa_legitimize_address (rtx x, rtx oldx ATTRIBUTE_UNUSED, |
| enum machine_mode mode) |
| { |
| rtx orig = x; |
| |
| /* We need to canonicalize the order of operands in unscaled indexed |
| addresses since the code that checks if an address is valid doesn't |
| always try both orders. */ |
| if (!TARGET_NO_SPACE_REGS |
| && GET_CODE (x) == PLUS |
| && GET_MODE (x) == Pmode |
| && REG_P (XEXP (x, 0)) |
| && REG_P (XEXP (x, 1)) |
| && REG_POINTER (XEXP (x, 0)) |
| && !REG_POINTER (XEXP (x, 1))) |
| return gen_rtx_PLUS (Pmode, XEXP (x, 1), XEXP (x, 0)); |
| |
| if (flag_pic) |
| return legitimize_pic_address (x, mode, gen_reg_rtx (Pmode)); |
| |
| /* Strip off CONST. */ |
| if (GET_CODE (x) == CONST) |
| x = XEXP (x, 0); |
| |
| /* Special case. Get the SYMBOL_REF into a register and use indexing. |
| That should always be safe. */ |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 0)) == REG |
| && GET_CODE (XEXP (x, 1)) == SYMBOL_REF) |
| { |
| rtx reg = force_reg (Pmode, XEXP (x, 1)); |
| return force_reg (Pmode, gen_rtx_PLUS (Pmode, reg, XEXP (x, 0))); |
| } |
| |
| /* Note we must reject symbols which represent function addresses |
| since the assembler/linker can't handle arithmetic on plabels. */ |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && ((GET_CODE (XEXP (x, 0)) == SYMBOL_REF |
| && !FUNCTION_NAME_P (XSTR (XEXP (x, 0), 0))) |
| || GET_CODE (XEXP (x, 0)) == REG)) |
| { |
| rtx int_part, ptr_reg; |
| int newoffset; |
| int offset = INTVAL (XEXP (x, 1)); |
| int mask; |
| |
| mask = (GET_MODE_CLASS (mode) == MODE_FLOAT |
| ? (TARGET_PA_20 ? 0x3fff : 0x1f) : 0x3fff); |
| |
| /* Choose which way to round the offset. Round up if we |
| are >= halfway to the next boundary. */ |
| if ((offset & mask) >= ((mask + 1) / 2)) |
| newoffset = (offset & ~ mask) + mask + 1; |
| else |
| newoffset = (offset & ~ mask); |
| |
| /* If the newoffset will not fit in 14 bits (ldo), then |
| handling this would take 4 or 5 instructions (2 to load |
| the SYMBOL_REF + 1 or 2 to load the newoffset + 1 to |
| add the new offset and the SYMBOL_REF.) Combine can |
| not handle 4->2 or 5->2 combinations, so do not create |
| them. */ |
| if (! VAL_14_BITS_P (newoffset) |
| && GET_CODE (XEXP (x, 0)) == SYMBOL_REF) |
| { |
| rtx const_part = plus_constant (XEXP (x, 0), newoffset); |
| rtx tmp_reg |
| = force_reg (Pmode, |
| gen_rtx_HIGH (Pmode, const_part)); |
| ptr_reg |
| = force_reg (Pmode, |
| gen_rtx_LO_SUM (Pmode, |
| tmp_reg, const_part)); |
| } |
| else |
| { |
| if (! VAL_14_BITS_P (newoffset)) |
| int_part = force_reg (Pmode, GEN_INT (newoffset)); |
| else |
| int_part = GEN_INT (newoffset); |
| |
| ptr_reg = force_reg (Pmode, |
| gen_rtx_PLUS (Pmode, |
| force_reg (Pmode, XEXP (x, 0)), |
| int_part)); |
| } |
| return plus_constant (ptr_reg, offset - newoffset); |
| } |
| |
| /* Handle (plus (mult (a) (shadd_constant)) (b)). */ |
| |
| if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 0)) == MULT |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT |
| && shadd_constant_p (INTVAL (XEXP (XEXP (x, 0), 1))) |
| && (GET_RTX_CLASS (GET_CODE (XEXP (x, 1))) == 'o' |
| || GET_CODE (XEXP (x, 1)) == SUBREG) |
| && GET_CODE (XEXP (x, 1)) != CONST) |
| { |
| int val = INTVAL (XEXP (XEXP (x, 0), 1)); |
| rtx reg1, reg2; |
| |
| reg1 = XEXP (x, 1); |
| if (GET_CODE (reg1) != REG) |
| reg1 = force_reg (Pmode, force_operand (reg1, 0)); |
| |
| reg2 = XEXP (XEXP (x, 0), 0); |
| if (GET_CODE (reg2) != REG) |
| reg2 = force_reg (Pmode, force_operand (reg2, 0)); |
| |
| return force_reg (Pmode, gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, |
| reg2, |
| GEN_INT (val)), |
| reg1)); |
| } |
| |
| /* Similarly for (plus (plus (mult (a) (shadd_constant)) (b)) (c)). |
| |
| Only do so for floating point modes since this is more speculative |
| and we lose if it's an integer store. */ |
| if (GET_CODE (x) == PLUS |
| && GET_CODE (XEXP (x, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (x, 0), 0)) == MULT |
| && GET_CODE (XEXP (XEXP (XEXP (x, 0), 0), 1)) == CONST_INT |
| && shadd_constant_p (INTVAL (XEXP (XEXP (XEXP (x, 0), 0), 1))) |
| && (mode == SFmode || mode == DFmode)) |
| { |
| |
| /* First, try and figure out what to use as a base register. */ |
| rtx reg1, reg2, base, idx, orig_base; |
| |
| reg1 = XEXP (XEXP (x, 0), 1); |
| reg2 = XEXP (x, 1); |
| base = NULL_RTX; |
| idx = NULL_RTX; |
| |
| /* Make sure they're both regs. If one was a SYMBOL_REF [+ const], |
| then emit_move_sequence will turn on REG_POINTER so we'll know |
| it's a base register below. */ |
| if (GET_CODE (reg1) != REG) |
| reg1 = force_reg (Pmode, force_operand (reg1, 0)); |
| |
| if (GET_CODE (reg2) != REG) |
| reg2 = force_reg (Pmode, force_operand (reg2, 0)); |
| |
| /* Figure out what the base and index are. */ |
| |
| if (GET_CODE (reg1) == REG |
| && REG_POINTER (reg1)) |
| { |
| base = reg1; |
| orig_base = XEXP (XEXP (x, 0), 1); |
| idx = gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, |
| XEXP (XEXP (XEXP (x, 0), 0), 0), |
| XEXP (XEXP (XEXP (x, 0), 0), 1)), |
| XEXP (x, 1)); |
| } |
| else if (GET_CODE (reg2) == REG |
| && REG_POINTER (reg2)) |
| { |
| base = reg2; |
| orig_base = XEXP (x, 1); |
| idx = XEXP (x, 0); |
| } |
| |
| if (base == 0) |
| return orig; |
| |
| /* If the index adds a large constant, try to scale the |
| constant so that it can be loaded with only one insn. */ |
| if (GET_CODE (XEXP (idx, 1)) == CONST_INT |
| && VAL_14_BITS_P (INTVAL (XEXP (idx, 1)) |
| / INTVAL (XEXP (XEXP (idx, 0), 1))) |
| && INTVAL (XEXP (idx, 1)) % INTVAL (XEXP (XEXP (idx, 0), 1)) == 0) |
| { |
| /* Divide the CONST_INT by the scale factor, then add it to A. */ |
| int val = INTVAL (XEXP (idx, 1)); |
| |
| val /= INTVAL (XEXP (XEXP (idx, 0), 1)); |
| reg1 = XEXP (XEXP (idx, 0), 0); |
| if (GET_CODE (reg1) != REG) |
| reg1 = force_reg (Pmode, force_operand (reg1, 0)); |
| |
| reg1 = force_reg (Pmode, gen_rtx_PLUS (Pmode, reg1, GEN_INT (val))); |
| |
| /* We can now generate a simple scaled indexed address. */ |
| return |
| force_reg |
| (Pmode, gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, reg1, |
| XEXP (XEXP (idx, 0), 1)), |
| base)); |
| } |
| |
| /* If B + C is still a valid base register, then add them. */ |
| if (GET_CODE (XEXP (idx, 1)) == CONST_INT |
| && INTVAL (XEXP (idx, 1)) <= 4096 |
| && INTVAL (XEXP (idx, 1)) >= -4096) |
| { |
| int val = INTVAL (XEXP (XEXP (idx, 0), 1)); |
| rtx reg1, reg2; |
| |
| reg1 = force_reg (Pmode, gen_rtx_PLUS (Pmode, base, XEXP (idx, 1))); |
| |
| reg2 = XEXP (XEXP (idx, 0), 0); |
| if (GET_CODE (reg2) != CONST_INT) |
| reg2 = force_reg (Pmode, force_operand (reg2, 0)); |
| |
| return force_reg (Pmode, gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, |
| reg2, |
| GEN_INT (val)), |
| reg1)); |
| } |
| |
| /* Get the index into a register, then add the base + index and |
| return a register holding the result. */ |
| |
| /* First get A into a register. */ |
| reg1 = XEXP (XEXP (idx, 0), 0); |
| if (GET_CODE (reg1) != REG) |
| reg1 = force_reg (Pmode, force_operand (reg1, 0)); |
| |
| /* And get B into a register. */ |
| reg2 = XEXP (idx, 1); |
| if (GET_CODE (reg2) != REG) |
| reg2 = force_reg (Pmode, force_operand (reg2, 0)); |
| |
| reg1 = force_reg (Pmode, |
| gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, reg1, |
| XEXP (XEXP (idx, 0), 1)), |
| reg2)); |
| |
| /* Add the result to our base register and return. */ |
| return force_reg (Pmode, gen_rtx_PLUS (Pmode, base, reg1)); |
| |
| } |
| |
| /* Uh-oh. We might have an address for x[n-100000]. This needs |
| special handling to avoid creating an indexed memory address |
| with x-100000 as the base. |
| |
| If the constant part is small enough, then it's still safe because |
| there is a guard page at the beginning and end of the data segment. |
| |
| Scaled references are common enough that we want to try and rearrange the |
| terms so that we can use indexing for these addresses too. Only |
| do the optimization for floatint point modes. */ |
| |
| if (GET_CODE (x) == PLUS |
| && symbolic_expression_p (XEXP (x, 1))) |
| { |
| /* Ugly. We modify things here so that the address offset specified |
| by the index expression is computed first, then added to x to form |
| the entire address. */ |
| |
| rtx regx1, regx2, regy1, regy2, y; |
| |
| /* Strip off any CONST. */ |
| y = XEXP (x, 1); |
| if (GET_CODE (y) == CONST) |
| y = XEXP (y, 0); |
| |
| if (GET_CODE (y) == PLUS || GET_CODE (y) == MINUS) |
| { |
| /* See if this looks like |
| (plus (mult (reg) (shadd_const)) |
| (const (plus (symbol_ref) (const_int)))) |
| |
| Where const_int is small. In that case the const |
| expression is a valid pointer for indexing. |
| |
| If const_int is big, but can be divided evenly by shadd_const |
| and added to (reg). This allows more scaled indexed addresses. */ |
| if (GET_CODE (XEXP (y, 0)) == SYMBOL_REF |
| && GET_CODE (XEXP (x, 0)) == MULT |
| && GET_CODE (XEXP (y, 1)) == CONST_INT |
| && INTVAL (XEXP (y, 1)) >= -4096 |
| && INTVAL (XEXP (y, 1)) <= 4095 |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT |
| && shadd_constant_p (INTVAL (XEXP (XEXP (x, 0), 1)))) |
| { |
| int val = INTVAL (XEXP (XEXP (x, 0), 1)); |
| rtx reg1, reg2; |
| |
| reg1 = XEXP (x, 1); |
| if (GET_CODE (reg1) != REG) |
| reg1 = force_reg (Pmode, force_operand (reg1, 0)); |
| |
| reg2 = XEXP (XEXP (x, 0), 0); |
| if (GET_CODE (reg2) != REG) |
| reg2 = force_reg (Pmode, force_operand (reg2, 0)); |
| |
| return force_reg (Pmode, |
| gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, |
| reg2, |
| GEN_INT (val)), |
| reg1)); |
| } |
| else if ((mode == DFmode || mode == SFmode) |
| && GET_CODE (XEXP (y, 0)) == SYMBOL_REF |
| && GET_CODE (XEXP (x, 0)) == MULT |
| && GET_CODE (XEXP (y, 1)) == CONST_INT |
| && INTVAL (XEXP (y, 1)) % INTVAL (XEXP (XEXP (x, 0), 1)) == 0 |
| && GET_CODE (XEXP (XEXP (x, 0), 1)) == CONST_INT |
| && shadd_constant_p (INTVAL (XEXP (XEXP (x, 0), 1)))) |
| { |
| regx1 |
| = force_reg (Pmode, GEN_INT (INTVAL (XEXP (y, 1)) |
| / INTVAL (XEXP (XEXP (x, 0), 1)))); |
| regx2 = XEXP (XEXP (x, 0), 0); |
| if (GET_CODE (regx2) != REG) |
| regx2 = force_reg (Pmode, force_operand (regx2, 0)); |
| regx2 = force_reg (Pmode, gen_rtx_fmt_ee (GET_CODE (y), Pmode, |
| regx2, regx1)); |
| return |
| force_reg (Pmode, |
| gen_rtx_PLUS (Pmode, |
| gen_rtx_MULT (Pmode, regx2, |
| XEXP (XEXP (x, 0), 1)), |
| force_reg (Pmode, XEXP (y, 0)))); |
| } |
| else if (GET_CODE (XEXP (y, 1)) == CONST_INT |
| && INTVAL (XEXP (y, 1)) >= -4096 |
| && INTVAL (XEXP (y, 1)) <= 4095) |
| { |
| /* This is safe because of the guard page at the |
| beginning and end of the data space. Just |
| return the original address. */ |
| return orig; |
| } |
| else |
| { |
| /* Doesn't look like one we can optimize. */ |
| regx1 = force_reg (Pmode, force_operand (XEXP (x, 0), 0)); |
| regy1 = force_reg (Pmode, force_operand (XEXP (y, 0), 0)); |
| regy2 = force_reg (Pmode, force_operand (XEXP (y, 1), 0)); |
| regx1 = force_reg (Pmode, |
| gen_rtx_fmt_ee (GET_CODE (y), Pmode, |
| regx1, regy2)); |
| return force_reg (Pmode, gen_rtx_PLUS (Pmode, regx1, regy1)); |
| } |
| } |
| } |
| |
| return orig; |
| } |
| |
| /* For the HPPA, REG and REG+CONST is cost 0 |
| and addresses involving symbolic constants are cost 2. |
| |
| PIC addresses are very expensive. |
| |
| It is no coincidence that this has the same structure |
| as GO_IF_LEGITIMATE_ADDRESS. */ |
| |
| static int |
| hppa_address_cost (rtx X) |
| { |
| switch (GET_CODE (X)) |
| { |
| case REG: |
| case PLUS: |
| case LO_SUM: |
| return 1; |
| case HIGH: |
| return 2; |
| default: |
| return 4; |
| } |
| } |
| |
| /* Compute a (partial) cost for rtx X. Return true if the complete |
| cost has been computed, and false if subexpressions should be |
| scanned. In either case, *TOTAL contains the cost result. */ |
| |
| static bool |
| hppa_rtx_costs (rtx x, int code, int outer_code, int *total) |
| { |
| switch (code) |
| { |
| case CONST_INT: |
| if (INTVAL (x) == 0) |
| *total = 0; |
| else if (INT_14_BITS (x)) |
| *total = 1; |
| else |
| *total = 2; |
| return true; |
| |
| case HIGH: |
| *total = 2; |
| return true; |
| |
| case CONST: |
| case LABEL_REF: |
| case SYMBOL_REF: |
| *total = 4; |
| return true; |
| |
| case CONST_DOUBLE: |
| if ((x == CONST0_RTX (DFmode) || x == CONST0_RTX (SFmode)) |
| && outer_code != SET) |
| *total = 0; |
| else |
| *total = 8; |
| return true; |
| |
| case MULT: |
| if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) |
| *total = COSTS_N_INSNS (3); |
| else if (TARGET_PA_11 && !TARGET_DISABLE_FPREGS && !TARGET_SOFT_FLOAT) |
| *total = COSTS_N_INSNS (8); |
| else |
| *total = COSTS_N_INSNS (20); |
| return true; |
| |
| case DIV: |
| if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) |
| { |
| *total = COSTS_N_INSNS (14); |
| return true; |
| } |
| /* FALLTHRU */ |
| |
| case UDIV: |
| case MOD: |
| case UMOD: |
| *total = COSTS_N_INSNS (60); |
| return true; |
| |
| case PLUS: /* this includes shNadd insns */ |
| case MINUS: |
| if (GET_MODE_CLASS (GET_MODE (x)) == MODE_FLOAT) |
| *total = COSTS_N_INSNS (3); |
| else |
| *total = COSTS_N_INSNS (1); |
| return true; |
| |
| case ASHIFT: |
| case ASHIFTRT: |
| case LSHIFTRT: |
| *total = COSTS_N_INSNS (1); |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /* Ensure mode of ORIG, a REG rtx, is MODE. Returns either ORIG or a |
| new rtx with the correct mode. */ |
| static inline rtx |
| force_mode (enum 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)); |
| } |
| |
| /* 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, enum machine_mode mode, rtx scratch_reg) |
| { |
| register rtx operand0 = operands[0]; |
| register rtx operand1 = operands[1]; |
| register rtx tem; |
| |
| /* We can only handle indexed addresses in the destination operand |
| of floating point stores. Thus, we need to break out indexed |
| addresses from the destination operand. */ |
| if (GET_CODE (operand0) == MEM && IS_INDEX_ADDR_P (XEXP (operand0, 0))) |
| { |
| /* This is only safe up to the beginning of life analysis. */ |
| if (no_new_pseudos) |
| abort (); |
| |
| tem = copy_to_mode_reg (Pmode, XEXP (operand0, 0)); |
| operand0 = replace_equiv_address (operand0, tem); |
| } |
| |
| /* On targets with non-equivalent space registers, break out unscaled |
| indexed addresses from the source operand before the final CSE. |
| We have to do this because the REG_POINTER flag is not correctly |
| carried through various optimization passes and CSE may substitute |
| a pseudo without the pointer set for one with the pointer set. As |
| a result, we loose various opportunites to create insns with |
| unscaled indexed addresses. */ |
| if (!TARGET_NO_SPACE_REGS |
| && !cse_not_expected |
| && GET_CODE (operand1) == MEM |
| && GET_CODE (XEXP (operand1, 0)) == PLUS |
| && REG_P (XEXP (XEXP (operand1, 0), 0)) |
| && REG_P (XEXP (XEXP (operand1, 0), 1))) |
| operand1 |
| = replace_equiv_address (operand1, |
| copy_to_mode_reg (Pmode, XEXP (operand1, 0))); |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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 from |
| REG+D addresses where D does not fit in 5 or 14 bits, including |
| (subreg (mem (addr))) cases. */ |
| if (scratch_reg |
| && fp_reg_operand (operand0, mode) |
| && ((GET_CODE (operand1) == MEM |
| && !memory_address_p ((GET_MODE_SIZE (mode) == 4 ? SFmode : DFmode), |
| XEXP (operand1, 0))) |
| || ((GET_CODE (operand1) == SUBREG |
| && GET_CODE (XEXP (operand1, 0)) == MEM |
| && !memory_address_p ((GET_MODE_SIZE (mode) == 4 |
| ? SFmode : DFmode), |
| XEXP (XEXP (operand1, 0), 0)))))) |
| { |
| if (GET_CODE (operand1) == SUBREG) |
| operand1 = XEXP (operand1, 0); |
| |
| /* SCRATCH_REG will hold an address and maybe the actual data. We want |
| it in WORD_MODE regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (word_mode, 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 (VOIDmode, operand0, |
| gen_rtx_MEM (mode, scratch_reg))); |
| return 1; |
| } |
| else if (scratch_reg |
| && fp_reg_operand (operand1, mode) |
| && ((GET_CODE (operand0) == MEM |
| && !memory_address_p ((GET_MODE_SIZE (mode) == 4 |
| ? SFmode : DFmode), |
| XEXP (operand0, 0))) |
| || ((GET_CODE (operand0) == SUBREG) |
| && GET_CODE (XEXP (operand0, 0)) == MEM |
| && !memory_address_p ((GET_MODE_SIZE (mode) == 4 |
| ? SFmode : DFmode), |
| XEXP (XEXP (operand0, 0), 0))))) |
| { |
| if (GET_CODE (operand0) == SUBREG) |
| operand0 = XEXP (operand0, 0); |
| |
| /* SCRATCH_REG will hold an address and maybe the actual data. We want |
| it in WORD_MODE regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (word_mode, 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 (VOIDmode, 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 a register class |
| containing only FP registers. Doing so unfortunately creates |
| more problems than it solves. Fix this for 2.5. */ |
| else if (scratch_reg |
| && CONSTANT_P (operand1) |
| && fp_reg_operand (operand0, mode)) |
| { |
| rtx xoperands[2]; |
| |
| /* SCRATCH_REG will hold an address and maybe the actual data. We want |
| it in WORD_MODE regardless of what mode it was originally given |
| to us. */ |
| scratch_reg = force_mode (word_mode, 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_move_sequence (xoperands, Pmode, 0); |
| |
| /* Now load the destination register. */ |
| emit_insn (gen_rtx_SET (mode, operand0, |
| gen_rtx_MEM (mode, scratch_reg))); |
| return 1; |
| } |
| /* Handle secondary reloads for SAR. These occur when trying to load |
| the SAR from memory, FP register, or with a constant. */ |
| else if (scratch_reg |
| && GET_CODE (operand0) == REG |
| && REGNO (operand0) < FIRST_PSEUDO_REGISTER |
| && REGNO_REG_CLASS (REGNO (operand0)) == SHIFT_REGS |
| && (GET_CODE (operand1) == MEM |
| || GET_CODE (operand1) == CONST_INT |
| || (GET_CODE (operand1) == REG |
| && FP_REG_CLASS_P (REGNO_REG_CLASS (REGNO (operand1)))))) |
| { |
| /* D might not fit in 14 bits either; for such cases load D into |
| scratch reg. */ |
| if (GET_CODE (operand1) == MEM |
| && !memory_address_p (Pmode, XEXP (operand1, 0))) |
| { |
| /* We are reloading the address into the scratch register, so we |
| want to make sure the scratch register is a full register. */ |
| scratch_reg = force_mode (word_mode, scratch_reg); |
| |
| 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)); |
| |
| /* Now we are going to load the scratch register from memory, |
| we want to load it in the same width as the original MEM, |
| which must be the same as the width of the ultimate destination, |
| OPERAND0. */ |
| scratch_reg = force_mode (GET_MODE (operand0), scratch_reg); |
| |
| emit_move_insn (scratch_reg, gen_rtx_MEM (GET_MODE (operand0), |
| scratch_reg)); |
| } |
| else |
| { |
| /* We want to load the scratch register using the same mode as |
| the ultimate destination. */ |
| scratch_reg = force_mode (GET_MODE (operand0), scratch_reg); |
| |
| emit_move_insn (scratch_reg, operand1); |
| } |
| |
| /* And emit the insn to set the ultimate destination. We know that |
| the scratch register has the same mode as the destination at this |
| point. */ |
| emit_move_insn (operand0, scratch_reg); |
| return 1; |
| } |
| /* Handle the most common case: storing into a register. */ |
| else if (register_operand (operand0, mode)) |
| { |
| if (register_operand (operand1, mode) |
| || (GET_CODE (operand1) == CONST_INT |
| && cint_ok_for_move (INTVAL (operand1))) |
| || (operand1 == CONST0_RTX (mode)) |
| || (GET_CODE (operand1) == HIGH |
| && !symbolic_operand (XEXP (operand1, 0), VOIDmode)) |
| /* Only `general_operands' can come here, so MEM is ok. */ |
| || GET_CODE (operand1) == MEM) |
| { |
| /* Various sets are created during RTL generation which don't |
| have the REG_POINTER flag correctly set. After the CSE pass, |
| instruction recognition can fail if we don't consistently |
| set this flag when performing register copies. This should |
| also improve the opportunities for creating insns that use |
| unscaled indexing. */ |
| if (REG_P (operand0) && REG_P (operand1)) |
| { |
| if (REG_POINTER (operand1) |
| && !REG_POINTER (operand0) |
| && !HARD_REGISTER_P (operand0)) |
| copy_reg_pointer (operand0, operand1); |
| else if (REG_POINTER (operand0) |
| && !REG_POINTER (operand1) |
| && !HARD_REGISTER_P (operand1)) |
| copy_reg_pointer (operand1, operand0); |
| } |
| |
| /* When MEMs are broken out, the REG_POINTER flag doesn't |
| get set. In some cases, we can set the REG_POINTER flag |
| from the declaration for the MEM. */ |
| if (REG_P (operand0) |
| && GET_CODE (operand1) == MEM |
| && !REG_POINTER (operand0)) |
| { |
| tree decl = MEM_EXPR (operand1); |
| |
| /* Set the register pointer flag and register alignment |
| if the declaration for this memory reference is a |
| pointer type. Fortran indirect argument references |
| are ignored. */ |
| if (decl |
| && !(flag_argument_noalias > 1 |
| && TREE_CODE (decl) == INDIRECT_REF |
| && TREE_CODE (TREE_OPERAND (decl, 0)) == PARM_DECL)) |
| { |
| tree type; |
| |
| /* If this is a COMPONENT_REF, use the FIELD_DECL from |
| tree operand 1. */ |
| if (TREE_CODE (decl) == COMPONENT_REF) |
| decl = TREE_OPERAND (decl, 1); |
| |
| type = TREE_TYPE (decl); |
| if (TREE_CODE (type) == ARRAY_TYPE) |
| type = get_inner_array_type (type); |
| |
| if (POINTER_TYPE_P (type)) |
| { |
| int align; |
| |
| type = TREE_TYPE (type); |
| /* Using TYPE_ALIGN_OK is rather conservative as |
| only the ada frontend actually sets it. */ |
| align = (TYPE_ALIGN_OK (type) ? TYPE_ALIGN (type) |
| : BITS_PER_UNIT); |
| mark_reg_pointer (operand0, align); |
| } |
| } |
| } |
| |
| emit_insn (gen_rtx_SET (VOIDmode, operand0, operand1)); |
| return 1; |
| } |
| } |
| else if (GET_CODE (operand0) == MEM) |
| { |
| if (mode == DFmode && operand1 == CONST0_RTX (mode) |
| && !(reload_in_progress || reload_completed)) |
| { |
| rtx temp = gen_reg_rtx (DFmode); |
| |
| emit_insn (gen_rtx_SET (VOIDmode, temp, operand1)); |
| emit_insn (gen_rtx_SET (VOIDmode, operand0, temp)); |
| return 1; |
| } |
| if (register_operand (operand1, mode) || operand1 == CONST0_RTX (mode)) |
| { |
| /* Run this case quickly. */ |
| emit_insn (gen_rtx_SET (VOIDmode, operand0, operand1)); |
| return 1; |
| } |
| if (! (reload_in_progress || reload_completed)) |
| { |
| operands[0] = validize_mem (operand0); |
| operands[1] = operand1 = force_reg (mode, operand1); |
| } |
| } |
| |
| /* Simplify the source if we need to. |
| Note we do have to handle function labels here, even though we do |
| not consider them legitimate constants. Loop optimizations can |
| call the emit_move_xxx with one as a source. */ |
| if ((GET_CODE (operand1) != HIGH && immediate_operand (operand1, mode)) |
| || function_label_operand (operand1, mode) |
| || (GET_CODE (operand1) == HIGH |
| && symbolic_operand (XEXP (operand1, 0), mode))) |
| { |
| int ishighonly = 0; |
| |
| if (GET_CODE (operand1) == HIGH) |
| { |
| ishighonly = 1; |
| operand1 = XEXP (operand1, 0); |
| } |
| if (symbolic_operand (operand1, mode)) |
| { |
| /* Argh. The assembler and linker can't handle arithmetic |
| involving plabels. |
| |
| So we force the plabel into memory, load operand0 from |
| the memory location, then add in the constant part. */ |
| if ((GET_CODE (operand1) == CONST |
| && GET_CODE (XEXP (operand1, 0)) == PLUS |
| && function_label_operand (XEXP (XEXP (operand1, 0), 0), Pmode)) |
| || function_label_operand (operand1, mode)) |
| { |
| rtx temp, const_part; |
| |
| /* Figure out what (if any) scratch register to use. */ |
| if (reload_in_progress || reload_completed) |
| { |
| scratch_reg = scratch_reg ? scratch_reg : operand0; |
| /* SCRATCH_REG will hold an address and maybe the actual |
| data. We want it in WORD_MODE regardless of what mode it |
| was originally given to us. */ |
| scratch_reg = force_mode (word_mode, scratch_reg); |
| } |
| else if (flag_pic) |
| scratch_reg = gen_reg_rtx (Pmode); |
| |
| if (GET_CODE (operand1) == CONST) |
| { |
| /* Save away the constant part of the expression. */ |
| const_part = XEXP (XEXP (operand1, 0), 1); |
| if (GET_CODE (const_part) != CONST_INT) |
| abort (); |
| |
| /* Force the function label into memory. */ |
| temp = force_const_mem (mode, XEXP (XEXP (operand1, 0), 0)); |
| } |
| else |
| { |
| /* No constant part. */ |
| const_part = NULL_RTX; |
| |
| /* Force the function label into memory. */ |
| temp = force_const_mem (mode, operand1); |
| } |
| |
| |
| /* Get the address of the memory location. PIC-ify it if |
| necessary. */ |
| temp = XEXP (temp, 0); |
| if (flag_pic) |
| temp = legitimize_pic_address (temp, mode, scratch_reg); |
| |
| /* Put the address of the memory location into our destination |
| register. */ |
| operands[1] = temp; |
| emit_move_sequence (operands, mode, scratch_reg); |
| |
| /* Now load from the memory location into our destination |
| register. */ |
| operands[1] = gen_rtx_MEM (Pmode, operands[0]); |
| emit_move_sequence (operands, mode, scratch_reg); |
| |
| /* And add back in the constant part. */ |
| if (const_part != NULL_RTX) |
| expand_inc (operand0, const_part); |
| |
| return 1; |
| } |
| |
| if (flag_pic) |
| { |
| rtx temp; |
| |
| if (reload_in_progress || reload_completed) |
| { |
| temp = scratch_reg ? scratch_reg : operand0; |
| /* TEMP will hold an address and maybe the actual |
| data. We want it in WORD_MODE regardless of what mode it |
| was originally given to us. */ |
| temp = force_mode (word_mode, temp); |
| } |
| else |
| temp = gen_reg_rtx (Pmode); |
| |
| /* (const (plus (symbol) (const_int))) must be forced to |
| memory during/after reload if the const_int will not fit |
| in 14 bits. */ |
| if (GET_CODE (operand1) == CONST |
| && GET_CODE (XEXP (operand1, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (operand1, 0), 1)) == CONST_INT |
| && !INT_14_BITS (XEXP (XEXP (operand1, 0), 1)) |
| && (reload_completed || reload_in_progress) |
| && flag_pic) |
| { |
| operands[1] = force_const_mem (mode, operand1); |
| operands[1] = legitimize_pic_address (XEXP (operands[1], 0), |
| mode, temp); |
| operands[1] = gen_rtx_MEM (mode, operands[1]); |
| emit_move_sequence (operands, mode, temp); |
| } |
| else |
| { |
| operands[1] = legitimize_pic_address (operand1, mode, temp); |
| if (REG_P (operand0) && REG_P (operands[1])) |
| copy_reg_pointer (operand0, operands[1]); |
| emit_insn (gen_rtx_SET (VOIDmode, operand0, operands[1])); |
| } |
| } |
| /* On the HPPA, references to data space are supposed to use dp, |
| register 27, but showing it in the RTL inhibits various cse |
| and loop optimizations. */ |
| else |
| { |
| rtx temp, set; |
| |
| if (reload_in_progress || reload_completed) |
| { |
| temp = scratch_reg ? scratch_reg : operand0; |
| /* TEMP will hold an address and maybe the actual |
| data. We want it in WORD_MODE regardless of what mode it |
| was originally given to us. */ |
| temp = force_mode (word_mode, temp); |
| } |
| else |
| temp = gen_reg_rtx (mode); |
| |
| /* Loading a SYMBOL_REF into a register makes that register |
| safe to be used as the base in an indexed address. |
| |
| Don't mark hard registers though. That loses. */ |
| if (GET_CODE (operand0) == REG |
| && REGNO (operand0) >= FIRST_PSEUDO_REGISTER) |
| mark_reg_pointer (operand0, BITS_PER_UNIT); |
| if (REGNO (temp) >= FIRST_PSEUDO_REGISTER) |
| mark_reg_pointer (temp, BITS_PER_UNIT); |
| |
| if (ishighonly) |
| set = gen_rtx_SET (mode, operand0, temp); |
| else |
| set = gen_rtx_SET (VOIDmode, |
| operand0, |
| gen_rtx_LO_SUM (mode, temp, operand1)); |
| |
| emit_insn (gen_rtx_SET (VOIDmode, |
| temp, |
| gen_rtx_HIGH (mode, operand1))); |
| emit_insn (set); |
| |
| } |
| return 1; |
| } |
| else if (GET_CODE (operand1) != CONST_INT |
| || !cint_ok_for_move (INTVAL (operand1))) |
| { |
| rtx insn, temp; |
| rtx op1 = operand1; |
| HOST_WIDE_INT value = 0; |
| HOST_WIDE_INT insv = 0; |
| int insert = 0; |
| |
| if (GET_CODE (operand1) == CONST_INT) |
| value = INTVAL (operand1); |
| |
| if (TARGET_64BIT |
| && GET_CODE (operand1) == CONST_INT |
| && HOST_BITS_PER_WIDE_INT > 32 |
| && GET_MODE_BITSIZE (GET_MODE (operand0)) > 32) |
| { |
| HOST_WIDE_INT nval; |
| |
| /* Extract the low order 32 bits of the value and sign extend. |
| If the new value is the same as the original value, we can |
| can use the original value as-is. If the new value is |
| different, we use it and insert the most-significant 32-bits |
| of the original value into the final result. */ |
| nval = ((value & (((HOST_WIDE_INT) 2 << 31) - 1)) |
| ^ ((HOST_WIDE_INT) 1 << 31)) - ((HOST_WIDE_INT) 1 << 31); |
| if (value != nval) |
| { |
| #if HOST_BITS_PER_WIDE_INT > 32 |
| insv = value >= 0 ? value >> 32 : ~(~value >> 32); |
| #endif |
| insert = 1; |
| value = nval; |
| operand1 = GEN_INT (nval); |
| } |
| } |
| |
| if (reload_in_progress || reload_completed) |
| temp = scratch_reg ? scratch_reg : operand0; |
| else |
| temp = gen_reg_rtx (mode); |
| |
| /* We don't directly split DImode constants on 32-bit targets |
| because PLUS uses an 11-bit immediate and the insn sequence |
| generated is not as efficient as the one using HIGH/LO_SUM. */ |
| if (GET_CODE (operand1) == CONST_INT |
| && GET_MODE_BITSIZE (mode) <= HOST_BITS_PER_WIDE_INT |
| && !insert) |
| { |
| /* Directly break constant into high and low parts. This |
| provides better optimization opportunities because various |
| passes recognize constants split with PLUS but not LO_SUM. |
| We use a 14-bit signed low part except when the addition |
| of 0x4000 to the high part might change the sign of the |
| high part. */ |
| HOST_WIDE_INT low = value & 0x3fff; |
| HOST_WIDE_INT high = value & ~ 0x3fff; |
| |
| if (low >= 0x2000) |
| { |
| if (high == 0x7fffc000 || (mode == HImode && high == 0x4000)) |
| high += 0x2000; |
| else |
| high += 0x4000; |
| } |
| |
| low = value - high; |
| |
| emit_insn (gen_rtx_SET (VOIDmode, temp, GEN_INT (high))); |
| operands[1] = gen_rtx_PLUS (mode, temp, GEN_INT (low)); |
| } |
| else |
| { |
| emit_insn (gen_rtx_SET (VOIDmode, temp, |
| gen_rtx_HIGH (mode, operand1))); |
| operands[1] = gen_rtx_LO_SUM (mode, temp, operand1); |
| } |
| |
| insn = emit_move_insn (operands[0], operands[1]); |
| |
| /* Now insert the most significant 32 bits of the value |
| into the register. When we don't have a second register |
| available, it could take up to nine instructions to load |
| a 64-bit integer constant. Prior to reload, we force |
| constants that would take more than three instructions |
| to load to the constant pool. During and after reload, |
| we have to handle all possible values. */ |
| if (insert) |
| { |
| /* Use a HIGH/LO_SUM/INSV sequence if we have a second |
| register and the value to be inserted is outside the |
| range that can be loaded with three depdi instructions. */ |
| if (temp != operand0 && (insv >= 16384 || insv < -16384)) |
| { |
| operand1 = GEN_INT (insv); |
| |
| emit_insn (gen_rtx_SET (VOIDmode, temp, |
| gen_rtx_HIGH (mode, operand1))); |
| emit_move_insn (temp, gen_rtx_LO_SUM (mode, temp, operand1)); |
| emit_insn (gen_insv (operand0, GEN_INT (32), |
| const0_rtx, temp)); |
| } |
| else |
| { |
| int len = 5, pos = 27; |
| |
| /* Insert the bits using the depdi instruction. */ |
| while (pos >= 0) |
| { |
| HOST_WIDE_INT v5 = ((insv & 31) ^ 16) - 16; |
| HOST_WIDE_INT sign = v5 < 0; |
| |
| /* Left extend the insertion. */ |
| insv = (insv >= 0 ? insv >> len : ~(~insv >> len)); |
| while (pos > 0 && (insv & 1) == sign) |
| { |
| insv = (insv >= 0 ? insv >> 1 : ~(~insv >> 1)); |
| len += 1; |
| pos -= 1; |
| } |
| |
| emit_insn (gen_insv (operand0, GEN_INT (len), |
| GEN_INT (pos), GEN_INT (v5))); |
| |
| len = pos > 0 && pos < 5 ? pos : 5; |
| pos -= len; |
| } |
| } |
| } |
| |
| REG_NOTES (insn) |
| = gen_rtx_EXPR_LIST (REG_EQUAL, op1, REG_NOTES (insn)); |
| |
| return 1; |
| } |
| } |
| /* Now have insn-emit do whatever it normally does. */ |
| return 0; |
| } |
| |
| /* Examine EXP and return nonzero if it contains an ADDR_EXPR (meaning |
| it will need a link/runtime reloc). */ |
| |
| int |
| reloc_needed (tree exp) |
| { |
| int reloc = 0; |
| |
| switch (TREE_CODE (exp)) |
| { |
| case ADDR_EXPR: |
| return 1; |
| |
| case PLUS_EXPR: |
| case MINUS_EXPR: |
| reloc = reloc_needed (TREE_OPERAND (exp, 0)); |
| reloc |= reloc_needed (TREE_OPERAND (exp, 1)); |
| break; |
| |
| case NOP_EXPR: |
| case CONVERT_EXPR: |
| case NON_LVALUE_EXPR: |
| reloc = reloc_needed (TREE_OPERAND (exp, 0)); |
| break; |
| |
| case CONSTRUCTOR: |
| { |
| register tree link; |
| for (link = CONSTRUCTOR_ELTS (exp); link; link = TREE_CHAIN (link)) |
| if (TREE_VALUE (link) != 0) |
| reloc |= reloc_needed (TREE_VALUE (link)); |
| } |
| break; |
| |
| case ERROR_MARK: |
| break; |
| |
| default: |
| break; |
| } |
| return reloc; |
| } |
| |
| /* Does operand (which is a symbolic_operand) live in text space? |
| If so, SYMBOL_REF_FLAG, which is set by pa_encode_section_info, |
| will be true. */ |
| |
| int |
| read_only_operand (rtx operand, enum machine_mode mode ATTRIBUTE_UNUSED) |
| { |
| if (GET_CODE (operand) == CONST) |
| operand = XEXP (XEXP (operand, 0), 0); |
| if (flag_pic) |
| { |
| if (GET_CODE (operand) == SYMBOL_REF) |
| return SYMBOL_REF_FLAG (operand) && !CONSTANT_POOL_ADDRESS_P (operand); |
| } |
| else |
| { |
| if (GET_CODE (operand) == SYMBOL_REF) |
| return SYMBOL_REF_FLAG (operand) || CONSTANT_POOL_ADDRESS_P (operand); |
| } |
| return 1; |
| } |
| |
| |
| /* Return the best assembler insn template |
| for moving operands[1] into operands[0] as a fullword. */ |
| const char * |
| singlemove_string (rtx *operands) |
| { |
| HOST_WIDE_INT intval; |
| |
| if (GET_CODE (operands[0]) == MEM) |
| return "stw %r1,%0"; |
| if (GET_CODE (operands[1]) == MEM) |
| return "ldw %1,%0"; |
| if (GET_CODE (operands[1]) == CONST_DOUBLE) |
| { |
| long i; |
| REAL_VALUE_TYPE d; |
| |
| if (GET_MODE (operands[1]) != SFmode) |
| abort (); |
| |
| /* Translate the CONST_DOUBLE to a CONST_INT with the same target |
| bit pattern. */ |
| REAL_VALUE_FROM_CONST_DOUBLE (d, operands[1]); |
| REAL_VALUE_TO_TARGET_SINGLE (d, i); |
| |
| operands[1] = GEN_INT (i); |
| /* Fall through to CONST_INT case. */ |
| } |
| if (GET_CODE (operands[1]) == CONST_INT) |
| { |
| intval = INTVAL (operands[1]); |
| |
| if (VAL_14_BITS_P (intval)) |
| return "ldi %1,%0"; |
| else if ((intval & 0x7ff) == 0) |
| return "ldil L'%1,%0"; |
| else if (zdepi_cint_p (intval)) |
| return "{zdepi %Z1,%0|depwi,z %Z1,%0}"; |
| else |
| return "ldil L'%1,%0\n\tldo R'%1(%0),%0"; |
| } |
| return "copy %1,%0"; |
| } |
| |
| |
| /* Compute position (in OP[1]) and width (in OP[2]) |
| useful for copying IMM to a register using the zdepi |
| instructions. Store the immediate value to insert in OP[0]. */ |
| static void |
| compute_zdepwi_operands (unsigned HOST_WIDE_INT imm, unsigned *op) |
| { |
| int lsb, len; |
| |
| /* Find the least significant set bit in IMM. */ |
| for (lsb = 0; lsb < 32; lsb++) |
| { |
| if ((imm & 1) != 0) |
| break; |
| imm >>= 1; |
| } |
| |
| /* Choose variants based on *sign* of the 5-bit field. */ |
| if ((imm & 0x10) == 0) |
| len = (lsb <= 28) ? 4 : 32 - lsb; |
| else |
| { |
| /* Find the width of the bitstring in IMM. */ |
| for (len = 5; len < 32; len++) |
| { |
| if ((imm & (1 << len)) == 0) |
| break; |
| } |
| |
| /* Sign extend IMM as a 5-bit value. */ |
| imm = (imm & 0xf) - 0x10; |
| } |
| |
| op[0] = imm; |
| op[1] = 31 - lsb; |
| op[2] = len; |
| } |
| |
| /* Compute position (in OP[1]) and width (in OP[2]) |
| useful for copying IMM to a register using the depdi,z |
| instructions. Store the immediate value to insert in OP[0]. */ |
| void |
| compute_zdepdi_operands (unsigned HOST_WIDE_INT imm, unsigned *op) |
| { |
| HOST_WIDE_INT lsb, len; |
| |
| /* Find the least significant set bit in IMM. */ |
| for (lsb = 0; lsb < HOST_BITS_PER_WIDE_INT; lsb++) |
| { |
| if ((imm & 1) != 0) |
| break; |
| imm >>= 1; |
| } |
| |
| /* Choose variants based on *sign* of the 5-bit field. */ |
| if ((imm & 0x10) == 0) |
| len = ((lsb <= HOST_BITS_PER_WIDE_INT - 4) |
| ? 4 : HOST_BITS_PER_WIDE_INT - lsb); |
| else |
| { |
| /* Find the width of the bitstring in IMM. */ |
| for (len = 5; len < HOST_BITS_PER_WIDE_INT; len++) |
| { |
| if ((imm & ((unsigned HOST_WIDE_INT) 1 << len)) == 0) |
| break; |
| } |
| |
| /* Sign extend IMM as a 5-bit value. */ |
| imm = (imm & 0xf) - 0x10; |
| } |
| |
| op[0] = imm; |
| op[1] = 63 - lsb; |
| op[2] = len; |
| } |
| |
| /* Output assembler code to perform a doubleword move insn |
| with operands OPERANDS. */ |
| |
| const char * |
| output_move_double (rtx *operands) |
| { |
| enum { REGOP, OFFSOP, MEMOP, CNSTOP, RNDOP } optype0, optype1; |
| rtx latehalf[2]; |
| rtx addreg0 = 0, addreg1 = 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 (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 (operands[1]) == MEM) |
| optype1 = MEMOP; |
| else |
| optype1 = RNDOP; |
| |
| /* Check for the cases that the operand constraints are not |
| supposed to allow to happen. Abort if we get one, |
| because generating code for these cases is painful. */ |
| |
| if (optype0 != REGOP && optype1 != REGOP) |
| abort (); |
| |
| /* Handle auto decrementing and incrementing loads and stores |
| specifically, since the structure of the function doesn't work |
| for them without major modification. Do it better when we learn |
| this port about the general inc/dec addressing of PA. |
| (This was written by tege. Chide him if it doesn't work.) */ |
| |
| if (optype0 == MEMOP) |
| { |
| /* We have to output the address syntax ourselves, since print_operand |
| doesn't deal with the addresses we want to use. Fix this later. */ |
| |
| rtx addr = XEXP (operands[0], 0); |
| if (GET_CODE (addr) == POST_INC || GET_CODE (addr) == POST_DEC) |
| { |
| rtx high_reg = gen_rtx_SUBREG (SImode, operands[1], 0); |
| |
| operands[0] = XEXP (addr, 0); |
| if (GET_CODE (operands[1]) != REG || GET_CODE (operands[0]) != REG) |
| abort (); |
| |
| if (!reg_overlap_mentioned_p (high_reg, addr)) |
| { |
| /* No overlap between high target register and address |
| register. (We do this in a non-obvious way to |
| save a register file writeback) */ |
| if (GET_CODE (addr) == POST_INC) |
| return "{stws|stw},ma %1,8(%0)\n\tstw %R1,-4(%0)"; |
| return "{stws|stw},ma %1,-8(%0)\n\tstw %R1,12(%0)"; |
| } |
| else |
| abort (); |
| } |
| else if (GET_CODE (addr) == PRE_INC || GET_CODE (addr) == PRE_DEC) |
| { |
| rtx high_reg = gen_rtx_SUBREG (SImode, operands[1], 0); |
| |
| operands[0] = XEXP (addr, 0); |
| if (GET_CODE (operands[1]) != REG || GET_CODE (operands[0]) != REG) |
| abort (); |
| |
| if (!reg_overlap_mentioned_p (high_reg, addr)) |
| { |
| /* No overlap between high target register and address |
| register. (We do this in a non-obvious way to |
| save a register file writeback) */ |
| if (GET_CODE (addr) == PRE_INC) |
| return "{stws|stw},mb %1,8(%0)\n\tstw %R1,4(%0)"; |
| return "{stws|stw},mb %1,-8(%0)\n\tstw %R1,4(%0)"; |
| } |
| else |
| abort (); |
| } |
| } |
| if (optype1 == MEMOP) |
| { |
| /* We have to output the address syntax ourselves, since print_operand |
| doesn't deal with the addresses we want to use. Fix this later. */ |
| |
| rtx addr = XEXP (operands[1], 0); |
| if (GET_CODE (addr) == POST_INC || GET_CODE (addr) == POST_DEC) |
| { |
| rtx high_reg = gen_rtx_SUBREG (SImode, operands[0], 0); |
| |
| operands[1] = XEXP (addr, 0); |
| if (GET_CODE (operands[0]) != REG || GET_CODE (operands[1]) != REG) |
| abort (); |
| |
| if (!reg_overlap_mentioned_p (high_reg, addr)) |
| { |
| /* No overlap between high target register and address |
| register. (We do this in a non-obvious way to |
| save a register file writeback) */ |
| if (GET_CODE (addr) == POST_INC) |
| return "{ldws|ldw},ma 8(%1),%0\n\tldw -4(%1),%R0"; |
| return "{ldws|ldw},ma -8(%1),%0\n\tldw 12(%1),%R0"; |
| } |
| else |
| { |
| /* This is an undefined situation. We should load into the |
| address register *and* update that register. Probably |
| we don't need to handle this at all. */ |
| if (GET_CODE (addr) == POST_INC) |
| return "ldw 4(%1),%R0\n\t{ldws|ldw},ma 8(%1),%0"; |
| return "ldw 4(%1),%R0\n\t{ldws|ldw},ma -8(%1),%0"; |
| } |
| } |
| else if (GET_CODE (addr) == PRE_INC || GET_CODE (addr) == PRE_DEC) |
| { |
| rtx high_reg = gen_rtx_SUBREG (SImode, operands[0], 0); |
| |
| operands[1] = XEXP (addr, 0); |
| if (GET_CODE (operands[0]) != REG || GET_CODE (operands[1]) != REG) |
| abort (); |
| |
| if (!reg_overlap_mentioned_p (high_reg, addr)) |
| { |
| /* No overlap between high target register and address |
| register. (We do this in a non-obvious way to |
| save a register file writeback) */ |
| if (GET_CODE (addr) == PRE_INC) |
| return "{ldws|ldw},mb 8(%1),%0\n\tldw 4(%1),%R0"; |
| return "{ldws|ldw},mb -8(%1),%0\n\tldw 4(%1),%R0"; |
| } |
| else |
| { |
| /* This is an undefined situation. We should load into the |
| address register *and* update that register. Probably |
| we don't need to handle this at all. */ |
| if (GET_CODE (addr) == PRE_INC) |
| return "ldw 12(%1),%R0\n\t{ldws|ldw},mb 8(%1),%0"; |
| return "ldw -4(%1),%R0\n\t{ldws|ldw},mb -8(%1),%0"; |
| } |
| } |
| else if (GET_CODE (addr) == PLUS |
| && GET_CODE (XEXP (addr, 0)) == MULT) |
| { |
| rtx high_reg = gen_rtx_SUBREG (SImode, operands[0], 0); |
| |
| if (!reg_overlap_mentioned_p (high_reg, addr)) |
| { |
| rtx xoperands[3]; |
| |
| xoperands[0] = high_reg; |
| xoperands[1] = XEXP (addr, 1); |
| xoperands[2] = XEXP (XEXP (addr, 0), 0); |
| xoperands[3] = XEXP (XEXP (addr, 0), 1); |
| output_asm_insn ("{sh%O3addl %2,%1,%0|shladd,l %2,%O3,%1,%0}", |
| xoperands); |
| return "ldw 4(%0),%R0\n\tldw 0(%0),%0"; |
| } |
| else |
| { |
| rtx xoperands[3]; |
| |
| xoperands[0] = high_reg; |
| xoperands[1] = XEXP (addr, 1); |
| xoperands[2] = XEXP (XEXP (addr, 0), 0); |
| xoperands[3] = XEXP (XEXP (addr, 0), 1); |
| output_asm_insn ("{sh%O3addl %2,%1,%R0|shladd,l %2,%O3,%1,%R0}", |
| xoperands); |
| return "ldw 0(%R0),%0\n\tldw 4(%R0),%R0"; |
| } |
| } |
| } |
| |
| /* 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. |
| |
| 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 (optype0 == REGOP) |
| latehalf[0] = gen_rtx_REG (SImode, REGNO (operands[0]) + 1); |
| else if (optype0 == OFFSOP) |
| latehalf[0] = adjust_address (operands[0], SImode, 4); |
| else |
| latehalf[0] = operands[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, 4); |
| else if (optype1 == CNSTOP) |
| split_double (operands[1], &operands[1], &latehalf[1]); |
| else |
| latehalf[1] = operands[1]; |
| |
| /* If the first move would clobber the source of the second one, |
| do them in the other order. |
| |
| This can happen in two cases: |
| |
| mem -> register where the first half of the destination register |
| is the same register used in the memory's address. Reload |
| can create such insns. |
| |
| mem in this case will be either register indirect or register |
| indirect plus a valid offset. |
| |
| register -> register move where REGNO(dst) == REGNO(src + 1) |
| someone (Tim/Tege?) claimed this can happen for parameter loads. |
| |
| Handle mem -> register case first. */ |
| if (optype0 == REGOP |
| && (optype1 == MEMOP || optype1 == OFFSOP) |
| && refers_to_regno_p (REGNO (operands[0]), REGNO (operands[0]) + 1, |
| operands[1], 0)) |
| { |
| /* Do the late half first. */ |
| if (addreg1) |
| output_asm_insn ("ldo 4(%0),%0", &addreg1); |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Then clobber. */ |
| if (addreg1) |
| output_asm_insn ("ldo -4(%0),%0", &addreg1); |
| return singlemove_string (operands); |
| } |
| |
| /* Now handle register -> register case. */ |
| if (optype0 == REGOP && optype1 == REGOP |
| && REGNO (operands[0]) == REGNO (operands[1]) + 1) |
| { |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| return singlemove_string (operands); |
| } |
| |
| /* Normal case: do the two words, low-numbered first. */ |
| |
| output_asm_insn (singlemove_string (operands), operands); |
| |
| /* Make any unoffsettable addresses point at high-numbered word. */ |
| if (addreg0) |
| output_asm_insn ("ldo 4(%0),%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("ldo 4(%0),%0", &addreg1); |
| |
| /* Do that word. */ |
| output_asm_insn (singlemove_string (latehalf), latehalf); |
| |
| /* Undo the adds we just did. */ |
| if (addreg0) |
| output_asm_insn ("ldo -4(%0),%0", &addreg0); |
| if (addreg1) |
| output_asm_insn ("ldo -4(%0),%0", &addreg1); |
| |
| return ""; |
| } |
| |
| const char * |
| output_fp_move_double (rtx *operands) |
| { |
| if (FP_REG_P (operands[0])) |
| { |
| if (FP_REG_P (operands[1]) |
| || operands[1] == CONST0_RTX (GET_MODE (operands[0]))) |
| output_asm_insn ("fcpy,dbl %f1,%0", operands); |
| else |
| output_asm_insn ("fldd%F1 %1,%0", operands); |
| } |
| else if (FP_REG_P (operands[1])) |
| { |
| output_asm_insn ("fstd%F0 %1,%0", operands); |
| } |
| else if (operands[1] == CONST0_RTX (GET_MODE (operands[0]))) |
| { |
| if (GET_CODE (operands[0]) == REG) |
| { |
| rtx xoperands[2]; |
| xoperands[1] = gen_rtx_REG (SImode, REGNO (operands[0]) + 1); |
| xoperands[0] = operands[0]; |
| output_asm_insn ("copy %%r0,%0\n\tcopy %%r0,%1", xoperands); |
| } |
| /* This is a pain. You have to be prepared to deal with an |
| arbitrary address here including pre/post increment/decrement. |
| |
| so avoid this in the MD. */ |
| else |
| abort (); |
| } |
| else abort (); |
| return ""; |
| } |
| |
| /* 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 |
| abort (); |
| } |
| if (GET_CODE (addr) == REG) |
| return addr; |
| abort (); |
| } |
| |
| /* Emit code to perform a block move. |
| |
| OPERANDS[0] is the destination pointer as a REG, clobbered. |
| OPERANDS[1] is the source pointer as a REG, clobbered. |
| OPERANDS[2] is a register for temporary storage. |
| OPERANDS[3] is a register for temporary storage. |
| OPERANDS[4] is the size as a CONST_INT |
| OPERANDS[5] is the alignment safe to use, as a CONST_INT. |
| OPERANDS[6] is another temporary register. */ |
| |
| const char * |
| output_block_move (rtx *operands, int size_is_constant ATTRIBUTE_UNUSED) |
| { |
| int align = INTVAL (operands[5]); |
| unsigned long n_bytes = INTVAL (operands[4]); |
| |
| /* We can't move more than a word at a time because the PA |
| has no longer integer move insns. (Could use fp mem ops?) */ |
| if (align > (TARGET_64BIT ? 8 : 4)) |
| align = (TARGET_64BIT ? 8 : 4); |
| |
| /* Note that we know each loop below will execute at least twice |
| (else we would have open-coded the copy). */ |
| switch (align) |
| { |
| case 8: |
| /* Pre-adjust the loop counter. */ |
| operands[4] = GEN_INT (n_bytes - 16); |
| output_asm_insn ("ldi %4,%2", operands); |
| |
| /* Copying loop. */ |
| output_asm_insn ("ldd,ma 8(%1),%3", operands); |
| output_asm_insn ("ldd,ma 8(%1),%6", operands); |
| output_asm_insn ("std,ma %3,8(%0)", operands); |
| output_asm_insn ("addib,>= -16,%2,.-12", operands); |
| output_asm_insn ("std,ma %6,8(%0)", operands); |
| |
| /* Handle the residual. There could be up to 7 bytes of |
| residual to copy! */ |
| if (n_bytes % 16 != 0) |
| { |
| operands[4] = GEN_INT (n_bytes % 8); |
| if (n_bytes % 16 >= 8) |
| output_asm_insn ("ldd,ma 8(%1),%3", operands); |
| if (n_bytes % 8 != 0) |
| output_asm_insn ("ldd 0(%1),%6", operands); |
| if (n_bytes % 16 >= 8) |
| output_asm_insn ("std,ma %3,8(%0)", operands); |
| if (n_bytes % 8 != 0) |
| output_asm_insn ("stdby,e %6,%4(%0)", operands); |
| } |
| return ""; |
| |
| case 4: |
| /* Pre-adjust the loop counter. */ |
| operands[4] = GEN_INT (n_bytes - 8); |
| output_asm_insn ("ldi %4,%2", operands); |
| |
| /* Copying loop. */ |
| output_asm_insn ("{ldws|ldw},ma 4(%1),%3", operands); |
| output_asm_insn ("{ldws|ldw},ma 4(%1),%6", operands); |
| output_asm_insn ("{stws|stw},ma %3,4(%0)", operands); |
| output_asm_insn ("addib,>= -8,%2,.-12", operands); |
| output_asm_insn ("{stws|stw},ma %6,4(%0)", operands); |
| |
| /* Handle the residual. There could be up to 7 bytes of |
| residual to copy! */ |
| if (n_bytes % 8 != 0) |
| { |
| operands[4] = GEN_INT (n_bytes % 4); |
| if (n_bytes % 8 >= 4) |
| output_asm_insn ("{ldws|ldw},ma 4(%1),%3", operands); |
| if (n_bytes % 4 != 0) |
| output_asm_insn ("ldw 0(%1),%6", operands); |
| if (n_bytes % 8 >= 4) |
| output_asm_insn ("{stws|stw},ma %3,4(%0)", operands); |
| if (n_bytes % 4 != 0) |
| output_asm_insn ("{stbys|stby},e %6,%4(%0)", operands); |
| } |
| return ""; |
| |
| case 2: |
| /* Pre-adjust the loop counter. */ |
| operands[4] = GEN_INT (n_bytes - 4); |
| output_asm_insn ("ldi %4,%2", operands); |
| |
| /* Copying loop. */ |
| output_asm_insn ("{ldhs|ldh},ma 2(%1),%3", operands); |
| output_asm_insn ("{ldhs|ldh},ma 2(%1),%6", operands); |
| output_asm_insn ("{sths|sth},ma %3,2(%0)", operands); |
| output_asm_insn ("addib,>= -4,%2,.-12", operands); |
| output_asm_insn ("{sths|sth},ma %6,2(%0)", operands); |
| |
| /* Handle the residual. */ |
| if (n_bytes % 4 != 0) |
| { |
| if (n_bytes % 4 >= 2) |
| output_asm_insn ("{ldhs|ldh},ma 2(%1),%3", operands); |
| if (n_bytes % 2 != 0) |
| output_asm_insn ("ldb 0(%1),%6", operands); |
| if (n_bytes % 4 >= 2) |
| output_asm_insn ("{sths|sth},ma %3,2(%0)", operands); |
| if (n_bytes % 2 != 0) |
| output_asm_insn ("stb %6,0(%0)", operands); |
| } |
| return ""; |
| |
| case 1: |
| /* Pre-adjust the loop counter. */ |
| operands[4] = GEN_INT (n_bytes - 2); |
| output_asm_insn ("ldi %4,%2", operands); |
| |
| /* Copying loop. */ |
| output_asm_insn ("{ldbs|ldb},ma 1(%1),%3", operands); |
| output_asm_insn ("{ldbs|ldb},ma 1(%1),%6", operands); |
| output_asm_insn ("{stbs|stb},ma %3,1(%0)", operands); |
| output_asm_insn ("addib,>= -2,%2,.-12", operands); |
| output_asm_insn ("{stbs|stb},ma %6,1(%0)", operands); |
| |
| /* Handle the residual. */ |
| if (n_bytes % 2 != 0) |
| { |
| output_asm_insn ("ldb 0(%1),%3", operands); |
| output_asm_insn ("stb %3,0(%0)", operands); |
| } |
| return ""; |
| |
| default: |
| abort (); |
| } |
| } |
| |
| /* Count the number of insns necessary to handle this block move. |
| |
| Basic structure is the same as emit_block_move, except that we |
| count insns rather than emit them. */ |
| |
| static int |
| compute_movstr_length (rtx insn) |
| { |
| rtx pat = PATTERN (insn); |
| unsigned int align = INTVAL (XEXP (XVECEXP (pat, 0, 7), 0)); |
| unsigned long n_bytes = INTVAL (XEXP (XVECEXP (pat, 0, 6), 0)); |
| unsigned int n_insns = 0; |
| |
| /* We can't move more than four bytes at a time because the PA |
| has no longer integer move insns. (Could use fp mem ops?) */ |
| if (align > (TARGET_64BIT ? 8 : 4)) |
| align = (TARGET_64BIT ? 8 : 4); |
| |
| /* The basic copying loop. */ |
| n_insns = 6; |
| |
| /* Residuals. */ |
| if (n_bytes % (2 * align) != 0) |
| { |
| if ((n_bytes % (2 * align)) >= align) |
| n_insns += 2; |
| |
| if ((n_bytes % align) != 0) |
| n_insns += 2; |
| } |
| |
| /* Lengths are expressed in bytes now; each insn is 4 bytes. */ |
| return n_insns * 4; |
| } |
| |
| /* Emit code to perform a block clear. |
| |
| OPERANDS[0] is the destination pointer as a REG, clobbered. |
| OPERANDS[1] is a register for temporary storage. |
| OPERANDS[2] is the size as a CONST_INT |
| OPERANDS[3] is the alignment safe to use, as a CONST_INT. */ |
| |
| const char * |
| output_block_clear (rtx *operands, int size_is_constant ATTRIBUTE_UNUSED) |
| { |
| int align = INTVAL (operands[3]); |
| unsigned long n_bytes = INTVAL (operands[2]); |
| |
| /* We can't clear more than a word at a time because the PA |
| has no longer integer move insns. */ |
| if (align > (TARGET_64BIT ? 8 : 4)) |
| align = (TARGET_64BIT ? 8 : 4); |
| |
| /* Note that we know each loop below will execute at least twice |
| (else we would have open-coded the copy). */ |
| switch (align) |
| { |
| case 8: |
| /* Pre-adjust the loop counter. */ |
| operands[2] = GEN_INT (n_bytes - 16); |
| output_asm_insn ("ldi %2,%1", operands); |
| |
| /* Loop. */ |
| output_asm_insn ("std,ma %%r0,8(%0)", operands); |
| output_asm_insn ("addib,>= -16,%1,.-4", operands); |
| output_asm_insn ("std,ma %%r0,8(%0)", operands); |
| |
| /* Handle the residual. There could be up to 7 bytes of |
| residual to copy! */ |
| if (n_bytes % 16 != 0) |
| { |
| operands[2] = GEN_INT (n_bytes % 8); |
| if (n_bytes % 16 >= 8) |
| output_asm_insn ("std,ma %%r0,8(%0)", operands); |
| if (n_bytes % 8 != 0) |
| output_asm_insn ("stdby,e %%r0,%2(%0)", operands); |
| } |
| return ""; |
| |
| case 4: |
| /* Pre-adjust the loop counter. */ |
| operands[2] = GEN_INT (n_bytes - 8); |
| output_asm_insn ("ldi %2,%1", operands); |
| |
| /* Loop. */ |
| output_asm_insn ("{stws|stw},ma %%r0,4(%0)", operands); |
| output_asm_insn ("addib,>= -8,%1,.-4", operands); |
| output_asm_insn ("{stws|stw},ma %%r0,4(%0)", operands); |
| |
| /* Handle the residual. There could be up to 7 bytes of |
| residual to copy! */ |
| if (n_bytes % 8 != 0) |
| { |
| operands[2] = GEN_INT (n_bytes % 4); |
| if (n_bytes % 8 >= 4) |
| output_asm_insn ("{stws|stw},ma %%r0,4(%0)", operands); |
| if (n_bytes % 4 != 0) |
| output_asm_insn ("{stbys|stby},e %%r0,%2(%0)", operands); |
| } |
| return ""; |
| |
| case 2: |
| /* Pre-adjust the loop counter. */ |
| operands[2] = GEN_INT (n_bytes - 4); |
| output_asm_insn ("ldi %2,%1", operands); |
| |
| /* Loop. */ |
| output_asm_insn ("{sths|sth},ma %%r0,2(%0)", operands); |
| output_asm_insn ("addib,>= -4,%1,.-4", operands); |
| output_asm_insn ("{sths|sth},ma %%r0,2(%0)", operands); |
| |
| /* Handle the residual. */ |
| if (n_bytes % 4 != 0) |
| { |
| if (n_bytes % 4 >= 2) |
| output_asm_insn ("{sths|sth},ma %%r0,2(%0)", operands); |
| if (n_bytes % 2 != 0) |
| output_asm_insn ("stb %%r0,0(%0)", operands); |
| } |
| return ""; |
| |
| case 1: |
| /* Pre-adjust the loop counter. */ |
| operands[2] = GEN_INT (n_bytes - 2); |
| output_asm_insn ("ldi %2,%1", operands); |
| |
| /* Loop. */ |
| output_asm_insn ("{stbs|stb},ma %%r0,1(%0)", operands); |
| output_asm_insn ("addib,>= -2,%1,.-4", operands); |
| output_asm_insn ("{stbs|stb},ma %%r0,1(%0)", operands); |
| |
| /* Handle the residual. */ |
| if (n_bytes % 2 != 0) |
| output_asm_insn ("stb %%r0,0(%0)", operands); |
| |
| return ""; |
| |
| default: |
| abort (); |
| } |
| } |
| |
| /* Count the number of insns necessary to handle this block move. |
| |
| Basic structure is the same as emit_block_move, except that we |
| count insns rather than emit them. */ |
| |
| static int |
| compute_clrstr_length (rtx insn) |
| { |
| rtx pat = PATTERN (insn); |
| unsigned int align = INTVAL (XEXP (XVECEXP (pat, 0, 4), 0)); |
| unsigned long n_bytes = INTVAL (XEXP (XVECEXP (pat, 0, 3), 0)); |
| unsigned int n_insns = 0; |
| |
| /* We can't clear more than a word at a time because the PA |
| has no longer integer move insns. */ |
| if (align > (TARGET_64BIT ? 8 : 4)) |
| align = (TARGET_64BIT ? 8 : 4); |
| |
| /* The basic loop. */ |
| n_insns = 4; |
| |
| /* Residuals. */ |
| if (n_bytes % (2 * align) != 0) |
| { |
| if ((n_bytes % (2 * align)) >= align) |
| n_insns++; |
| |
| if ((n_bytes % align) != 0) |
| n_insns++; |
| } |
| |
| /* Lengths are expressed in bytes now; each insn is 4 bytes. */ |
| return n_insns * 4; |
| } |
| |
| |
| const char * |
| output_and (rtx *operands) |
| { |
| if (GET_CODE (operands[2]) == CONST_INT && INTVAL (operands[2]) != 0) |
| { |
| unsigned HOST_WIDE_INT mask = INTVAL (operands[2]); |
| int ls0, ls1, ms0, p, len; |
| |
| for (ls0 = 0; ls0 < 32; ls0++) |
| if ((mask & (1 << ls0)) == 0) |
| break; |
| |
| for (ls1 = ls0; ls1 < 32; ls1++) |
| if ((mask & (1 << ls1)) != 0) |
| break; |
| |
| for (ms0 = ls1; ms0 < 32; ms0++) |
| if ((mask & (1 << ms0)) == 0) |
| break; |
| |
| if (ms0 != 32) |
| abort (); |
| |
| if (ls1 == 32) |
| { |
| len = ls0; |
| |
| if (len == 0) |
| abort (); |
| |
| operands[2] = GEN_INT (len); |
| return "{extru|extrw,u} %1,31,%2,%0"; |
| } |
| else |
| { |
| /* We could use this `depi' for the case above as well, but `depi' |
| requires one more register file access than an `extru'. */ |
| |
| p = 31 - ls0; |
| len = ls1 - ls0; |
| |
| operands[2] = GEN_INT (p); |
| operands[3] = GEN_INT (len); |
| return "{depi|depwi} 0,%2,%3,%0"; |
| } |
| } |
| else |
| return "and %1,%2,%0"; |
| } |
| |
| /* Return a string to perform a bitwise-and of operands[1] with operands[2] |
| storing the result in operands[0]. */ |
| const char * |
| output_64bit_and (rtx *operands) |
| { |
| if (GET_CODE (operands[2]) == CONST_INT && INTVAL (operands[2]) != 0) |
| { |
| unsigned HOST_WIDE_INT mask = INTVAL (operands[2]); |
| int ls0, ls1, ms0, p, len; |
| |
| for (ls0 = 0; ls0 < HOST_BITS_PER_WIDE_INT; ls0++) |
| if ((mask & ((unsigned HOST_WIDE_INT) 1 << ls0)) == 0) |
| break; |
| |
| for (ls1 = ls0; ls1 < HOST_BITS_PER_WIDE_INT; ls1++) |
| if ((mask & ((unsigned HOST_WIDE_INT) 1 << ls1)) != 0) |
| break; |
| |
| for (ms0 = ls1; ms0 < HOST_BITS_PER_WIDE_INT; ms0++) |
| if ((mask & ((unsigned HOST_WIDE_INT) 1 << ms0)) == 0) |
| break; |
| |
| if (ms0 != HOST_BITS_PER_WIDE_INT) |
| abort (); |
| |
| if (ls1 == HOST_BITS_PER_WIDE_INT) |
| { |
| len = ls0; |
| |
| if (len == 0) |
| abort (); |
| |
| operands[2] = GEN_INT (len); |
| return "extrd,u %1,63,%2,%0"; |
| } |
| else |
| { |
| /* We could use this `depi' for the case above as well, but `depi' |
| requires one more register file access than an `extru'. */ |
| |
|