| /* Expands front end tree to back end RTL for GCC. |
| Copyright (C) 1987, 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1996, 1997, |
| 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 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. */ |
| |
| /* This file handles the generation of rtl code from tree structure |
| at the level of the function as a whole. |
| It creates the rtl expressions for parameters and auto variables |
| and has full responsibility for allocating stack slots. |
| |
| `expand_function_start' is called at the beginning of a function, |
| before the function body is parsed, and `expand_function_end' is |
| called after parsing the body. |
| |
| Call `assign_stack_local' to allocate a stack slot for a local variable. |
| This is usually done during the RTL generation for the function body, |
| but it can also be done in the reload pass when a pseudo-register does |
| not get a hard register. |
| |
| Call `put_var_into_stack' when you learn, belatedly, that a variable |
| previously given a pseudo-register must in fact go in the stack. |
| This function changes the DECL_RTL to be a stack slot instead of a reg |
| then scans all the RTL instructions so far generated to correct them. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "flags.h" |
| #include "except.h" |
| #include "function.h" |
| #include "expr.h" |
| #include "optabs.h" |
| #include "libfuncs.h" |
| #include "regs.h" |
| #include "hard-reg-set.h" |
| #include "insn-config.h" |
| #include "recog.h" |
| #include "output.h" |
| #include "basic-block.h" |
| #include "toplev.h" |
| #include "hashtab.h" |
| #include "ggc.h" |
| #include "tm_p.h" |
| #include "integrate.h" |
| #include "langhooks.h" |
| #include "target.h" |
| |
| #ifndef TRAMPOLINE_ALIGNMENT |
| #define TRAMPOLINE_ALIGNMENT FUNCTION_BOUNDARY |
| #endif |
| |
| #ifndef LOCAL_ALIGNMENT |
| #define LOCAL_ALIGNMENT(TYPE, ALIGNMENT) ALIGNMENT |
| #endif |
| |
| #ifndef STACK_ALIGNMENT_NEEDED |
| #define STACK_ALIGNMENT_NEEDED 1 |
| #endif |
| |
| #define STACK_BYTES (STACK_BOUNDARY / BITS_PER_UNIT) |
| |
| /* Some systems use __main in a way incompatible with its use in gcc, in these |
| cases use the macros NAME__MAIN to give a quoted symbol and SYMBOL__MAIN to |
| give the same symbol without quotes for an alternative entry point. You |
| must define both, or neither. */ |
| #ifndef NAME__MAIN |
| #define NAME__MAIN "__main" |
| #endif |
| |
| /* Round a value to the lowest integer less than it that is a multiple of |
| the required alignment. Avoid using division in case the value is |
| negative. Assume the alignment is a power of two. */ |
| #define FLOOR_ROUND(VALUE,ALIGN) ((VALUE) & ~((ALIGN) - 1)) |
| |
| /* Similar, but round to the next highest integer that meets the |
| alignment. */ |
| #define CEIL_ROUND(VALUE,ALIGN) (((VALUE) + (ALIGN) - 1) & ~((ALIGN)- 1)) |
| |
| /* NEED_SEPARATE_AP means that we cannot derive ap from the value of fp |
| during rtl generation. If they are different register numbers, this is |
| always true. It may also be true if |
| FIRST_PARM_OFFSET - STARTING_FRAME_OFFSET is not a constant during rtl |
| generation. See fix_lexical_addr for details. */ |
| |
| #if ARG_POINTER_REGNUM != FRAME_POINTER_REGNUM |
| #define NEED_SEPARATE_AP |
| #endif |
| |
| /* Nonzero if function being compiled doesn't contain any calls |
| (ignoring the prologue and epilogue). This is set prior to |
| local register allocation and is valid for the remaining |
| compiler passes. */ |
| int current_function_is_leaf; |
| |
| /* Nonzero if function being compiled doesn't contain any instructions |
| that can throw an exception. This is set prior to final. */ |
| |
| int current_function_nothrow; |
| |
| /* Nonzero if function being compiled doesn't modify the stack pointer |
| (ignoring the prologue and epilogue). This is only valid after |
| life_analysis has run. */ |
| int current_function_sp_is_unchanging; |
| |
| /* Nonzero if the function being compiled is a leaf function which only |
| uses leaf registers. This is valid after reload (specifically after |
| sched2) and is useful only if the port defines LEAF_REGISTERS. */ |
| int current_function_uses_only_leaf_regs; |
| |
| /* Nonzero once virtual register instantiation has been done. |
| assign_stack_local uses frame_pointer_rtx when this is nonzero. |
| calls.c:emit_library_call_value_1 uses it to set up |
| post-instantiation libcalls. */ |
| int virtuals_instantiated; |
| |
| /* Nonzero if at least one trampoline has been created. */ |
| int trampolines_created; |
| |
| /* Assign unique numbers to labels generated for profiling, debugging, etc. */ |
| static GTY(()) int funcdef_no; |
| |
| /* These variables hold pointers to functions to create and destroy |
| target specific, per-function data structures. */ |
| struct machine_function * (*init_machine_status) (void); |
| |
| /* The FUNCTION_DECL for an inline function currently being expanded. */ |
| tree inline_function_decl; |
| |
| /* The currently compiled function. */ |
| struct function *cfun = 0; |
| |
| /* These arrays record the INSN_UIDs of the prologue and epilogue insns. */ |
| static GTY(()) varray_type prologue; |
| static GTY(()) varray_type epilogue; |
| |
| /* Array of INSN_UIDs to hold the INSN_UIDs for each sibcall epilogue |
| in this function. */ |
| static GTY(()) varray_type sibcall_epilogue; |
| |
| /* In order to evaluate some expressions, such as function calls returning |
| structures in memory, we need to temporarily allocate stack locations. |
| We record each allocated temporary in the following structure. |
| |
| Associated with each temporary slot is a nesting level. When we pop up |
| one level, all temporaries associated with the previous level are freed. |
| Normally, all temporaries are freed after the execution of the statement |
| in which they were created. However, if we are inside a ({...}) grouping, |
| the result may be in a temporary and hence must be preserved. If the |
| result could be in a temporary, we preserve it if we can determine which |
| one it is in. If we cannot determine which temporary may contain the |
| result, all temporaries are preserved. A temporary is preserved by |
| pretending it was allocated at the previous nesting level. |
| |
| Automatic variables are also assigned temporary slots, at the nesting |
| level where they are defined. They are marked a "kept" so that |
| free_temp_slots will not free them. */ |
| |
| struct temp_slot GTY(()) |
| { |
| /* Points to next temporary slot. */ |
| struct temp_slot *next; |
| /* The rtx to used to reference the slot. */ |
| rtx slot; |
| /* The rtx used to represent the address if not the address of the |
| slot above. May be an EXPR_LIST if multiple addresses exist. */ |
| rtx address; |
| /* The alignment (in bits) of the slot. */ |
| unsigned int align; |
| /* The size, in units, of the slot. */ |
| HOST_WIDE_INT size; |
| /* The type of the object in the slot, or zero if it doesn't correspond |
| to a type. We use this to determine whether a slot can be reused. |
| It can be reused if objects of the type of the new slot will always |
| conflict with objects of the type of the old slot. */ |
| tree type; |
| /* The value of `sequence_rtl_expr' when this temporary is allocated. */ |
| tree rtl_expr; |
| /* Nonzero if this temporary is currently in use. */ |
| char in_use; |
| /* Nonzero if this temporary has its address taken. */ |
| char addr_taken; |
| /* Nesting level at which this slot is being used. */ |
| int level; |
| /* Nonzero if this should survive a call to free_temp_slots. */ |
| int keep; |
| /* The offset of the slot from the frame_pointer, including extra space |
| for alignment. This info is for combine_temp_slots. */ |
| HOST_WIDE_INT base_offset; |
| /* The size of the slot, including extra space for alignment. This |
| info is for combine_temp_slots. */ |
| HOST_WIDE_INT full_size; |
| }; |
| |
| /* This structure is used to record MEMs or pseudos used to replace VAR, any |
| SUBREGs of VAR, and any MEMs containing VAR as an address. We need to |
| maintain this list in case two operands of an insn were required to match; |
| in that case we must ensure we use the same replacement. */ |
| |
| struct fixup_replacement GTY(()) |
| { |
| rtx old; |
| rtx new; |
| struct fixup_replacement *next; |
| }; |
| |
| struct insns_for_mem_entry |
| { |
| /* A MEM. */ |
| rtx key; |
| /* These are the INSNs which reference the MEM. */ |
| rtx insns; |
| }; |
| |
| /* Forward declarations. */ |
| |
| static rtx assign_stack_local_1 (enum machine_mode, HOST_WIDE_INT, int, |
| struct function *); |
| static struct temp_slot *find_temp_slot_from_address (rtx); |
| static void put_reg_into_stack (struct function *, rtx, tree, enum machine_mode, |
| enum machine_mode, int, unsigned int, int, htab_t); |
| static void schedule_fixup_var_refs (struct function *, rtx, tree, enum machine_mode, |
| htab_t); |
| static void fixup_var_refs (rtx, enum machine_mode, int, rtx, htab_t); |
| static struct fixup_replacement |
| *find_fixup_replacement (struct fixup_replacement **, rtx); |
| static void fixup_var_refs_insns (rtx, rtx, enum machine_mode, int, int, rtx); |
| static void fixup_var_refs_insns_with_hash (htab_t, rtx, enum machine_mode, int, rtx); |
| static void fixup_var_refs_insn (rtx, rtx, enum machine_mode, int, int, rtx); |
| static void fixup_var_refs_1 (rtx, enum machine_mode, rtx *, rtx, |
| struct fixup_replacement **, rtx); |
| static rtx fixup_memory_subreg (rtx, rtx, enum machine_mode, int); |
| static rtx walk_fixup_memory_subreg (rtx, rtx, enum machine_mode, int); |
| static rtx fixup_stack_1 (rtx, rtx); |
| static void optimize_bit_field (rtx, rtx, rtx *); |
| static void instantiate_decls (tree, int); |
| static void instantiate_decls_1 (tree, int); |
| static void instantiate_decl (rtx, HOST_WIDE_INT, int); |
| static rtx instantiate_new_reg (rtx, HOST_WIDE_INT *); |
| static int instantiate_virtual_regs_1 (rtx *, rtx, int); |
| static void delete_handlers (void); |
| static void pad_to_arg_alignment (struct args_size *, int, struct args_size *); |
| static void pad_below (struct args_size *, enum machine_mode, tree); |
| static rtx round_trampoline_addr (rtx); |
| static rtx adjust_trampoline_addr (rtx); |
| static tree *identify_blocks_1 (rtx, tree *, tree *, tree *); |
| static void reorder_blocks_0 (tree); |
| static void reorder_blocks_1 (rtx, tree, varray_type *); |
| static void reorder_fix_fragments (tree); |
| static tree blocks_nreverse (tree); |
| static int all_blocks (tree, tree *); |
| static tree *get_block_vector (tree, int *); |
| extern tree debug_find_var_in_block_tree (tree, tree); |
| /* We always define `record_insns' even if its not used so that we |
| can always export `prologue_epilogue_contains'. */ |
| static void record_insns (rtx, varray_type *) ATTRIBUTE_UNUSED; |
| static int contains (rtx, varray_type); |
| #ifdef HAVE_return |
| static void emit_return_into_block (basic_block, rtx); |
| #endif |
| static void put_addressof_into_stack (rtx, htab_t); |
| static bool purge_addressof_1 (rtx *, rtx, int, int, int, htab_t); |
| static void purge_single_hard_subreg_set (rtx); |
| #if defined(HAVE_epilogue) && defined(INCOMING_RETURN_ADDR_RTX) |
| static rtx keep_stack_depressed (rtx); |
| #endif |
| static int is_addressof (rtx *, void *); |
| static hashval_t insns_for_mem_hash (const void *); |
| static int insns_for_mem_comp (const void *, const void *); |
| static int insns_for_mem_walk (rtx *, void *); |
| static void compute_insns_for_mem (rtx, rtx, htab_t); |
| static void prepare_function_start (tree); |
| static void do_clobber_return_reg (rtx, void *); |
| static void do_use_return_reg (rtx, void *); |
| static void instantiate_virtual_regs_lossage (rtx); |
| static tree split_complex_args (tree); |
| static void set_insn_locators (rtx, int) ATTRIBUTE_UNUSED; |
| |
| /* Pointer to chain of `struct function' for containing functions. */ |
| struct function *outer_function_chain; |
| |
| /* List of insns that were postponed by purge_addressof_1. */ |
| static rtx postponed_insns; |
| |
| /* Given a function decl for a containing function, |
| return the `struct function' for it. */ |
| |
| struct function * |
| find_function_data (tree decl) |
| { |
| struct function *p; |
| |
| for (p = outer_function_chain; p; p = p->outer) |
| if (p->decl == decl) |
| return p; |
| |
| abort (); |
| } |
| |
| /* Save the current context for compilation of a nested function. |
| This is called from language-specific code. The caller should use |
| the enter_nested langhook to save any language-specific state, |
| since this function knows only about language-independent |
| variables. */ |
| |
| void |
| push_function_context_to (tree context) |
| { |
| struct function *p; |
| |
| if (context) |
| { |
| if (context == current_function_decl) |
| cfun->contains_functions = 1; |
| else |
| { |
| struct function *containing = find_function_data (context); |
| containing->contains_functions = 1; |
| } |
| } |
| |
| if (cfun == 0) |
| init_dummy_function_start (); |
| p = cfun; |
| |
| p->outer = outer_function_chain; |
| outer_function_chain = p; |
| p->fixup_var_refs_queue = 0; |
| |
| (*lang_hooks.function.enter_nested) (p); |
| |
| cfun = 0; |
| } |
| |
| void |
| push_function_context (void) |
| { |
| push_function_context_to (current_function_decl); |
| } |
| |
| /* Restore the last saved context, at the end of a nested function. |
| This function is called from language-specific code. */ |
| |
| void |
| pop_function_context_from (tree context ATTRIBUTE_UNUSED) |
| { |
| struct function *p = outer_function_chain; |
| struct var_refs_queue *queue; |
| |
| cfun = p; |
| outer_function_chain = p->outer; |
| |
| current_function_decl = p->decl; |
| reg_renumber = 0; |
| |
| restore_emit_status (p); |
| |
| (*lang_hooks.function.leave_nested) (p); |
| |
| /* Finish doing put_var_into_stack for any of our variables which became |
| addressable during the nested function. If only one entry has to be |
| fixed up, just do that one. Otherwise, first make a list of MEMs that |
| are not to be unshared. */ |
| if (p->fixup_var_refs_queue == 0) |
| ; |
| else if (p->fixup_var_refs_queue->next == 0) |
| fixup_var_refs (p->fixup_var_refs_queue->modified, |
| p->fixup_var_refs_queue->promoted_mode, |
| p->fixup_var_refs_queue->unsignedp, |
| p->fixup_var_refs_queue->modified, 0); |
| else |
| { |
| rtx list = 0; |
| |
| for (queue = p->fixup_var_refs_queue; queue; queue = queue->next) |
| list = gen_rtx_EXPR_LIST (VOIDmode, queue->modified, list); |
| |
| for (queue = p->fixup_var_refs_queue; queue; queue = queue->next) |
| fixup_var_refs (queue->modified, queue->promoted_mode, |
| queue->unsignedp, list, 0); |
| |
| } |
| |
| p->fixup_var_refs_queue = 0; |
| |
| /* Reset variables that have known state during rtx generation. */ |
| rtx_equal_function_value_matters = 1; |
| virtuals_instantiated = 0; |
| generating_concat_p = 1; |
| } |
| |
| void |
| pop_function_context (void) |
| { |
| pop_function_context_from (current_function_decl); |
| } |
| |
| /* Clear out all parts of the state in F that can safely be discarded |
| after the function has been parsed, but not compiled, to let |
| garbage collection reclaim the memory. */ |
| |
| void |
| free_after_parsing (struct function *f) |
| { |
| /* f->expr->forced_labels is used by code generation. */ |
| /* f->emit->regno_reg_rtx is used by code generation. */ |
| /* f->varasm is used by code generation. */ |
| /* f->eh->eh_return_stub_label is used by code generation. */ |
| |
| (*lang_hooks.function.final) (f); |
| f->stmt = NULL; |
| } |
| |
| /* Clear out all parts of the state in F that can safely be discarded |
| after the function has been compiled, to let garbage collection |
| reclaim the memory. */ |
| |
| void |
| free_after_compilation (struct function *f) |
| { |
| f->eh = NULL; |
| f->expr = NULL; |
| f->emit = NULL; |
| f->varasm = NULL; |
| f->machine = NULL; |
| |
| f->x_temp_slots = NULL; |
| f->arg_offset_rtx = NULL; |
| f->return_rtx = NULL; |
| f->internal_arg_pointer = NULL; |
| f->x_nonlocal_labels = NULL; |
| f->x_nonlocal_goto_handler_slots = NULL; |
| f->x_nonlocal_goto_handler_labels = NULL; |
| f->x_nonlocal_goto_stack_level = NULL; |
| f->x_cleanup_label = NULL; |
| f->x_return_label = NULL; |
| f->x_naked_return_label = NULL; |
| f->computed_goto_common_label = NULL; |
| f->computed_goto_common_reg = NULL; |
| f->x_save_expr_regs = NULL; |
| f->x_stack_slot_list = NULL; |
| f->x_rtl_expr_chain = NULL; |
| f->x_tail_recursion_label = NULL; |
| f->x_tail_recursion_reentry = NULL; |
| f->x_arg_pointer_save_area = NULL; |
| f->x_clobber_return_insn = NULL; |
| f->x_context_display = NULL; |
| f->x_trampoline_list = NULL; |
| f->x_parm_birth_insn = NULL; |
| f->x_last_parm_insn = NULL; |
| f->x_parm_reg_stack_loc = NULL; |
| f->fixup_var_refs_queue = NULL; |
| f->original_arg_vector = NULL; |
| f->original_decl_initial = NULL; |
| f->inl_last_parm_insn = NULL; |
| f->epilogue_delay_list = NULL; |
| } |
| |
| /* Allocate fixed slots in the stack frame of the current function. */ |
| |
| /* Return size needed for stack frame based on slots so far allocated in |
| function F. |
| This size counts from zero. It is not rounded to PREFERRED_STACK_BOUNDARY; |
| the caller may have to do that. */ |
| |
| HOST_WIDE_INT |
| get_func_frame_size (struct function *f) |
| { |
| #ifdef FRAME_GROWS_DOWNWARD |
| return -f->x_frame_offset; |
| #else |
| return f->x_frame_offset; |
| #endif |
| } |
| |
| /* Return size needed for stack frame based on slots so far allocated. |
| This size counts from zero. It is not rounded to PREFERRED_STACK_BOUNDARY; |
| the caller may have to do that. */ |
| HOST_WIDE_INT |
| get_frame_size (void) |
| { |
| return get_func_frame_size (cfun); |
| } |
| |
| /* Allocate a stack slot of SIZE bytes and return a MEM rtx for it |
| with machine mode MODE. |
| |
| ALIGN controls the amount of alignment for the address of the slot: |
| 0 means according to MODE, |
| -1 means use BIGGEST_ALIGNMENT and round size to multiple of that, |
| positive specifies alignment boundary in bits. |
| |
| We do not round to stack_boundary here. |
| |
| FUNCTION specifies the function to allocate in. */ |
| |
| static rtx |
| assign_stack_local_1 (enum machine_mode mode, HOST_WIDE_INT size, int align, |
| struct function *function) |
| { |
| rtx x, addr; |
| int bigend_correction = 0; |
| int alignment; |
| int frame_off, frame_alignment, frame_phase; |
| |
| if (align == 0) |
| { |
| tree type; |
| |
| if (mode == BLKmode) |
| alignment = BIGGEST_ALIGNMENT; |
| else |
| alignment = GET_MODE_ALIGNMENT (mode); |
| |
| /* Allow the target to (possibly) increase the alignment of this |
| stack slot. */ |
| type = (*lang_hooks.types.type_for_mode) (mode, 0); |
| if (type) |
| alignment = LOCAL_ALIGNMENT (type, alignment); |
| |
| alignment /= BITS_PER_UNIT; |
| } |
| else if (align == -1) |
| { |
| alignment = BIGGEST_ALIGNMENT / BITS_PER_UNIT; |
| size = CEIL_ROUND (size, alignment); |
| } |
| else |
| alignment = align / BITS_PER_UNIT; |
| |
| #ifdef FRAME_GROWS_DOWNWARD |
| function->x_frame_offset -= size; |
| #endif |
| |
| /* Ignore alignment we can't do with expected alignment of the boundary. */ |
| if (alignment * BITS_PER_UNIT > PREFERRED_STACK_BOUNDARY) |
| alignment = PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT; |
| |
| if (function->stack_alignment_needed < alignment * BITS_PER_UNIT) |
| function->stack_alignment_needed = alignment * BITS_PER_UNIT; |
| |
| /* Calculate how many bytes the start of local variables is off from |
| stack alignment. */ |
| frame_alignment = PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT; |
| frame_off = STARTING_FRAME_OFFSET % frame_alignment; |
| frame_phase = frame_off ? frame_alignment - frame_off : 0; |
| |
| /* Round the frame offset to the specified alignment. The default is |
| to always honor requests to align the stack but a port may choose to |
| do its own stack alignment by defining STACK_ALIGNMENT_NEEDED. */ |
| if (STACK_ALIGNMENT_NEEDED |
| || mode != BLKmode |
| || size != 0) |
| { |
| /* We must be careful here, since FRAME_OFFSET might be negative and |
| division with a negative dividend isn't as well defined as we might |
| like. So we instead assume that ALIGNMENT is a power of two and |
| use logical operations which are unambiguous. */ |
| #ifdef FRAME_GROWS_DOWNWARD |
| function->x_frame_offset |
| = (FLOOR_ROUND (function->x_frame_offset - frame_phase, alignment) |
| + frame_phase); |
| #else |
| function->x_frame_offset |
| = (CEIL_ROUND (function->x_frame_offset - frame_phase, alignment) |
| + frame_phase); |
| #endif |
| } |
| |
| /* On a big-endian machine, if we are allocating more space than we will use, |
| use the least significant bytes of those that are allocated. */ |
| if (BYTES_BIG_ENDIAN && mode != BLKmode) |
| bigend_correction = size - GET_MODE_SIZE (mode); |
| |
| /* If we have already instantiated virtual registers, return the actual |
| address relative to the frame pointer. */ |
| if (function == cfun && virtuals_instantiated) |
| addr = plus_constant (frame_pointer_rtx, |
| trunc_int_for_mode |
| (frame_offset + bigend_correction |
| + STARTING_FRAME_OFFSET, Pmode)); |
| else |
| addr = plus_constant (virtual_stack_vars_rtx, |
| trunc_int_for_mode |
| (function->x_frame_offset + bigend_correction, |
| Pmode)); |
| |
| #ifndef FRAME_GROWS_DOWNWARD |
| function->x_frame_offset += size; |
| #endif |
| |
| x = gen_rtx_MEM (mode, addr); |
| |
| function->x_stack_slot_list |
| = gen_rtx_EXPR_LIST (VOIDmode, x, function->x_stack_slot_list); |
| |
| return x; |
| } |
| |
| /* Wrapper around assign_stack_local_1; assign a local stack slot for the |
| current function. */ |
| |
| rtx |
| assign_stack_local (enum machine_mode mode, HOST_WIDE_INT size, int align) |
| { |
| return assign_stack_local_1 (mode, size, align, cfun); |
| } |
| |
| /* Allocate a temporary stack slot and record it for possible later |
| reuse. |
| |
| MODE is the machine mode to be given to the returned rtx. |
| |
| SIZE is the size in units of the space required. We do no rounding here |
| since assign_stack_local will do any required rounding. |
| |
| KEEP is 1 if this slot is to be retained after a call to |
| free_temp_slots. Automatic variables for a block are allocated |
| with this flag. KEEP is 2 if we allocate a longer term temporary, |
| whose lifetime is controlled by CLEANUP_POINT_EXPRs. KEEP is 3 |
| if we are to allocate something at an inner level to be treated as |
| a variable in the block (e.g., a SAVE_EXPR). |
| |
| TYPE is the type that will be used for the stack slot. */ |
| |
| rtx |
| assign_stack_temp_for_type (enum machine_mode mode, HOST_WIDE_INT size, int keep, |
| tree type) |
| { |
| unsigned int align; |
| struct temp_slot *p, *best_p = 0; |
| rtx slot; |
| |
| /* If SIZE is -1 it means that somebody tried to allocate a temporary |
| of a variable size. */ |
| if (size == -1) |
| abort (); |
| |
| if (mode == BLKmode) |
| align = BIGGEST_ALIGNMENT; |
| else |
| align = GET_MODE_ALIGNMENT (mode); |
| |
| if (! type) |
| type = (*lang_hooks.types.type_for_mode) (mode, 0); |
| |
| if (type) |
| align = LOCAL_ALIGNMENT (type, align); |
| |
| /* Try to find an available, already-allocated temporary of the proper |
| mode which meets the size and alignment requirements. Choose the |
| smallest one with the closest alignment. */ |
| for (p = temp_slots; p; p = p->next) |
| if (p->align >= align && p->size >= size && GET_MODE (p->slot) == mode |
| && ! p->in_use |
| && objects_must_conflict_p (p->type, type) |
| && (best_p == 0 || best_p->size > p->size |
| || (best_p->size == p->size && best_p->align > p->align))) |
| { |
| if (p->align == align && p->size == size) |
| { |
| best_p = 0; |
| break; |
| } |
| best_p = p; |
| } |
| |
| /* Make our best, if any, the one to use. */ |
| if (best_p) |
| { |
| /* If there are enough aligned bytes left over, make them into a new |
| temp_slot so that the extra bytes don't get wasted. Do this only |
| for BLKmode slots, so that we can be sure of the alignment. */ |
| if (GET_MODE (best_p->slot) == BLKmode) |
| { |
| int alignment = best_p->align / BITS_PER_UNIT; |
| HOST_WIDE_INT rounded_size = CEIL_ROUND (size, alignment); |
| |
| if (best_p->size - rounded_size >= alignment) |
| { |
| p = ggc_alloc (sizeof (struct temp_slot)); |
| p->in_use = p->addr_taken = 0; |
| p->size = best_p->size - rounded_size; |
| p->base_offset = best_p->base_offset + rounded_size; |
| p->full_size = best_p->full_size - rounded_size; |
| p->slot = gen_rtx_MEM (BLKmode, |
| plus_constant (XEXP (best_p->slot, 0), |
| rounded_size)); |
| p->align = best_p->align; |
| p->address = 0; |
| p->rtl_expr = 0; |
| p->type = best_p->type; |
| p->next = temp_slots; |
| temp_slots = p; |
| |
| stack_slot_list = gen_rtx_EXPR_LIST (VOIDmode, p->slot, |
| stack_slot_list); |
| |
| best_p->size = rounded_size; |
| best_p->full_size = rounded_size; |
| } |
| } |
| |
| p = best_p; |
| } |
| |
| /* If we still didn't find one, make a new temporary. */ |
| if (p == 0) |
| { |
| HOST_WIDE_INT frame_offset_old = frame_offset; |
| |
| p = ggc_alloc (sizeof (struct temp_slot)); |
| |
| /* We are passing an explicit alignment request to assign_stack_local. |
| One side effect of that is assign_stack_local will not round SIZE |
| to ensure the frame offset remains suitably aligned. |
| |
| So for requests which depended on the rounding of SIZE, we go ahead |
| and round it now. We also make sure ALIGNMENT is at least |
| BIGGEST_ALIGNMENT. */ |
| if (mode == BLKmode && align < BIGGEST_ALIGNMENT) |
| abort (); |
| p->slot = assign_stack_local (mode, |
| (mode == BLKmode |
| ? CEIL_ROUND (size, (int) align / BITS_PER_UNIT) |
| : size), |
| align); |
| |
| p->align = align; |
| |
| /* The following slot size computation is necessary because we don't |
| know the actual size of the temporary slot until assign_stack_local |
| has performed all the frame alignment and size rounding for the |
| requested temporary. Note that extra space added for alignment |
| can be either above or below this stack slot depending on which |
| way the frame grows. We include the extra space if and only if it |
| is above this slot. */ |
| #ifdef FRAME_GROWS_DOWNWARD |
| p->size = frame_offset_old - frame_offset; |
| #else |
| p->size = size; |
| #endif |
| |
| /* Now define the fields used by combine_temp_slots. */ |
| #ifdef FRAME_GROWS_DOWNWARD |
| p->base_offset = frame_offset; |
| p->full_size = frame_offset_old - frame_offset; |
| #else |
| p->base_offset = frame_offset_old; |
| p->full_size = frame_offset - frame_offset_old; |
| #endif |
| p->address = 0; |
| p->next = temp_slots; |
| temp_slots = p; |
| } |
| |
| p->in_use = 1; |
| p->addr_taken = 0; |
| p->rtl_expr = seq_rtl_expr; |
| p->type = type; |
| |
| if (keep == 2) |
| { |
| p->level = target_temp_slot_level; |
| p->keep = 1; |
| } |
| else if (keep == 3) |
| { |
| p->level = var_temp_slot_level; |
| p->keep = 0; |
| } |
| else |
| { |
| p->level = temp_slot_level; |
| p->keep = keep; |
| } |
| |
| |
| /* Create a new MEM rtx to avoid clobbering MEM flags of old slots. */ |
| slot = gen_rtx_MEM (mode, XEXP (p->slot, 0)); |
| stack_slot_list = gen_rtx_EXPR_LIST (VOIDmode, slot, stack_slot_list); |
| |
| /* If we know the alias set for the memory that will be used, use |
| it. If there's no TYPE, then we don't know anything about the |
| alias set for the memory. */ |
| set_mem_alias_set (slot, type ? get_alias_set (type) : 0); |
| set_mem_align (slot, align); |
| |
| /* If a type is specified, set the relevant flags. */ |
| if (type != 0) |
| { |
| RTX_UNCHANGING_P (slot) = (lang_hooks.honor_readonly |
| && TYPE_READONLY (type)); |
| MEM_VOLATILE_P (slot) = TYPE_VOLATILE (type); |
| MEM_SET_IN_STRUCT_P (slot, AGGREGATE_TYPE_P (type)); |
| } |
| |
| return slot; |
| } |
| |
| /* Allocate a temporary stack slot and record it for possible later |
| reuse. First three arguments are same as in preceding function. */ |
| |
| rtx |
| assign_stack_temp (enum machine_mode mode, HOST_WIDE_INT size, int keep) |
| { |
| return assign_stack_temp_for_type (mode, size, keep, NULL_TREE); |
| } |
| |
| /* Assign a temporary. |
| If TYPE_OR_DECL is a decl, then we are doing it on behalf of the decl |
| and so that should be used in error messages. In either case, we |
| allocate of the given type. |
| KEEP is as for assign_stack_temp. |
| MEMORY_REQUIRED is 1 if the result must be addressable stack memory; |
| it is 0 if a register is OK. |
| DONT_PROMOTE is 1 if we should not promote values in register |
| to wider modes. */ |
| |
| rtx |
| assign_temp (tree type_or_decl, int keep, int memory_required, |
| int dont_promote ATTRIBUTE_UNUSED) |
| { |
| tree type, decl; |
| enum machine_mode mode; |
| #ifndef PROMOTE_FOR_CALL_ONLY |
| int unsignedp; |
| #endif |
| |
| if (DECL_P (type_or_decl)) |
| decl = type_or_decl, type = TREE_TYPE (decl); |
| else |
| decl = NULL, type = type_or_decl; |
| |
| mode = TYPE_MODE (type); |
| #ifndef PROMOTE_FOR_CALL_ONLY |
| unsignedp = TREE_UNSIGNED (type); |
| #endif |
| |
| if (mode == BLKmode || memory_required) |
| { |
| HOST_WIDE_INT size = int_size_in_bytes (type); |
| rtx tmp; |
| |
| /* Zero sized arrays are GNU C extension. Set size to 1 to avoid |
| problems with allocating the stack space. */ |
| if (size == 0) |
| size = 1; |
| |
| /* Unfortunately, we don't yet know how to allocate variable-sized |
| temporaries. However, sometimes we have a fixed upper limit on |
| the size (which is stored in TYPE_ARRAY_MAX_SIZE) and can use that |
| instead. This is the case for Chill variable-sized strings. */ |
| if (size == -1 && TREE_CODE (type) == ARRAY_TYPE |
| && TYPE_ARRAY_MAX_SIZE (type) != NULL_TREE |
| && host_integerp (TYPE_ARRAY_MAX_SIZE (type), 1)) |
| size = tree_low_cst (TYPE_ARRAY_MAX_SIZE (type), 1); |
| |
| /* The size of the temporary may be too large to fit into an integer. */ |
| /* ??? Not sure this should happen except for user silliness, so limit |
| this to things that aren't compiler-generated temporaries. The |
| rest of the time we'll abort in assign_stack_temp_for_type. */ |
| if (decl && size == -1 |
| && TREE_CODE (TYPE_SIZE_UNIT (type)) == INTEGER_CST) |
| { |
| error ("%Jsize of variable '%D' is too large", decl, decl); |
| size = 1; |
| } |
| |
| tmp = assign_stack_temp_for_type (mode, size, keep, type); |
| return tmp; |
| } |
| |
| #ifndef PROMOTE_FOR_CALL_ONLY |
| if (! dont_promote) |
| mode = promote_mode (type, mode, &unsignedp, 0); |
| #endif |
| |
| return gen_reg_rtx (mode); |
| } |
| |
| /* Combine temporary stack slots which are adjacent on the stack. |
| |
| This allows for better use of already allocated stack space. This is only |
| done for BLKmode slots because we can be sure that we won't have alignment |
| problems in this case. */ |
| |
| void |
| combine_temp_slots (void) |
| { |
| struct temp_slot *p, *q; |
| struct temp_slot *prev_p, *prev_q; |
| int num_slots; |
| |
| /* We can't combine slots, because the information about which slot |
| is in which alias set will be lost. */ |
| if (flag_strict_aliasing) |
| return; |
| |
| /* If there are a lot of temp slots, don't do anything unless |
| high levels of optimization. */ |
| if (! flag_expensive_optimizations) |
| for (p = temp_slots, num_slots = 0; p; p = p->next, num_slots++) |
| if (num_slots > 100 || (num_slots > 10 && optimize == 0)) |
| return; |
| |
| for (p = temp_slots, prev_p = 0; p; p = prev_p ? prev_p->next : temp_slots) |
| { |
| int delete_p = 0; |
| |
| if (! p->in_use && GET_MODE (p->slot) == BLKmode) |
| for (q = p->next, prev_q = p; q; q = prev_q->next) |
| { |
| int delete_q = 0; |
| if (! q->in_use && GET_MODE (q->slot) == BLKmode) |
| { |
| if (p->base_offset + p->full_size == q->base_offset) |
| { |
| /* Q comes after P; combine Q into P. */ |
| p->size += q->size; |
| p->full_size += q->full_size; |
| delete_q = 1; |
| } |
| else if (q->base_offset + q->full_size == p->base_offset) |
| { |
| /* P comes after Q; combine P into Q. */ |
| q->size += p->size; |
| q->full_size += p->full_size; |
| delete_p = 1; |
| break; |
| } |
| } |
| /* Either delete Q or advance past it. */ |
| if (delete_q) |
| prev_q->next = q->next; |
| else |
| prev_q = q; |
| } |
| /* Either delete P or advance past it. */ |
| if (delete_p) |
| { |
| if (prev_p) |
| prev_p->next = p->next; |
| else |
| temp_slots = p->next; |
| } |
| else |
| prev_p = p; |
| } |
| } |
| |
| /* Find the temp slot corresponding to the object at address X. */ |
| |
| static struct temp_slot * |
| find_temp_slot_from_address (rtx x) |
| { |
| struct temp_slot *p; |
| rtx next; |
| |
| for (p = temp_slots; p; p = p->next) |
| { |
| if (! p->in_use) |
| continue; |
| |
| else if (XEXP (p->slot, 0) == x |
| || p->address == x |
| || (GET_CODE (x) == PLUS |
| && XEXP (x, 0) == virtual_stack_vars_rtx |
| && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && INTVAL (XEXP (x, 1)) >= p->base_offset |
| && INTVAL (XEXP (x, 1)) < p->base_offset + p->full_size)) |
| return p; |
| |
| else if (p->address != 0 && GET_CODE (p->address) == EXPR_LIST) |
| for (next = p->address; next; next = XEXP (next, 1)) |
| if (XEXP (next, 0) == x) |
| return p; |
| } |
| |
| /* If we have a sum involving a register, see if it points to a temp |
| slot. */ |
| if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 0)) == REG |
| && (p = find_temp_slot_from_address (XEXP (x, 0))) != 0) |
| return p; |
| else if (GET_CODE (x) == PLUS && GET_CODE (XEXP (x, 1)) == REG |
| && (p = find_temp_slot_from_address (XEXP (x, 1))) != 0) |
| return p; |
| |
| return 0; |
| } |
| |
| /* Indicate that NEW is an alternate way of referring to the temp slot |
| that previously was known by OLD. */ |
| |
| void |
| update_temp_slot_address (rtx old, rtx new) |
| { |
| struct temp_slot *p; |
| |
| if (rtx_equal_p (old, new)) |
| return; |
| |
| p = find_temp_slot_from_address (old); |
| |
| /* If we didn't find one, see if both OLD is a PLUS. If so, and NEW |
| is a register, see if one operand of the PLUS is a temporary |
| location. If so, NEW points into it. Otherwise, if both OLD and |
| NEW are a PLUS and if there is a register in common between them. |
| If so, try a recursive call on those values. */ |
| if (p == 0) |
| { |
| if (GET_CODE (old) != PLUS) |
| return; |
| |
| if (GET_CODE (new) == REG) |
| { |
| update_temp_slot_address (XEXP (old, 0), new); |
| update_temp_slot_address (XEXP (old, 1), new); |
| return; |
| } |
| else if (GET_CODE (new) != PLUS) |
| return; |
| |
| if (rtx_equal_p (XEXP (old, 0), XEXP (new, 0))) |
| update_temp_slot_address (XEXP (old, 1), XEXP (new, 1)); |
| else if (rtx_equal_p (XEXP (old, 1), XEXP (new, 0))) |
| update_temp_slot_address (XEXP (old, 0), XEXP (new, 1)); |
| else if (rtx_equal_p (XEXP (old, 0), XEXP (new, 1))) |
| update_temp_slot_address (XEXP (old, 1), XEXP (new, 0)); |
| else if (rtx_equal_p (XEXP (old, 1), XEXP (new, 1))) |
| update_temp_slot_address (XEXP (old, 0), XEXP (new, 0)); |
| |
| return; |
| } |
| |
| /* Otherwise add an alias for the temp's address. */ |
| else if (p->address == 0) |
| p->address = new; |
| else |
| { |
| if (GET_CODE (p->address) != EXPR_LIST) |
| p->address = gen_rtx_EXPR_LIST (VOIDmode, p->address, NULL_RTX); |
| |
| p->address = gen_rtx_EXPR_LIST (VOIDmode, new, p->address); |
| } |
| } |
| |
| /* If X could be a reference to a temporary slot, mark the fact that its |
| address was taken. */ |
| |
| void |
| mark_temp_addr_taken (rtx x) |
| { |
| struct temp_slot *p; |
| |
| if (x == 0) |
| return; |
| |
| /* If X is not in memory or is at a constant address, it cannot be in |
| a temporary slot. */ |
| if (GET_CODE (x) != MEM || CONSTANT_P (XEXP (x, 0))) |
| return; |
| |
| p = find_temp_slot_from_address (XEXP (x, 0)); |
| if (p != 0) |
| p->addr_taken = 1; |
| } |
| |
| /* If X could be a reference to a temporary slot, mark that slot as |
| belonging to the to one level higher than the current level. If X |
| matched one of our slots, just mark that one. Otherwise, we can't |
| easily predict which it is, so upgrade all of them. Kept slots |
| need not be touched. |
| |
| This is called when an ({...}) construct occurs and a statement |
| returns a value in memory. */ |
| |
| void |
| preserve_temp_slots (rtx x) |
| { |
| struct temp_slot *p = 0; |
| |
| /* If there is no result, we still might have some objects whose address |
| were taken, so we need to make sure they stay around. */ |
| if (x == 0) |
| { |
| for (p = temp_slots; p; p = p->next) |
| if (p->in_use && p->level == temp_slot_level && p->addr_taken) |
| p->level--; |
| |
| return; |
| } |
| |
| /* If X is a register that is being used as a pointer, see if we have |
| a temporary slot we know it points to. To be consistent with |
| the code below, we really should preserve all non-kept slots |
| if we can't find a match, but that seems to be much too costly. */ |
| if (GET_CODE (x) == REG && REG_POINTER (x)) |
| p = find_temp_slot_from_address (x); |
| |
| /* If X is not in memory or is at a constant address, it cannot be in |
| a temporary slot, but it can contain something whose address was |
| taken. */ |
| if (p == 0 && (GET_CODE (x) != MEM || CONSTANT_P (XEXP (x, 0)))) |
| { |
| for (p = temp_slots; p; p = p->next) |
| if (p->in_use && p->level == temp_slot_level && p->addr_taken) |
| p->level--; |
| |
| return; |
| } |
| |
| /* First see if we can find a match. */ |
| if (p == 0) |
| p = find_temp_slot_from_address (XEXP (x, 0)); |
| |
| if (p != 0) |
| { |
| /* Move everything at our level whose address was taken to our new |
| level in case we used its address. */ |
| struct temp_slot *q; |
| |
| if (p->level == temp_slot_level) |
| { |
| for (q = temp_slots; q; q = q->next) |
| if (q != p && q->addr_taken && q->level == p->level) |
| q->level--; |
| |
| p->level--; |
| p->addr_taken = 0; |
| } |
| return; |
| } |
| |
| /* Otherwise, preserve all non-kept slots at this level. */ |
| for (p = temp_slots; p; p = p->next) |
| if (p->in_use && p->level == temp_slot_level && ! p->keep) |
| p->level--; |
| } |
| |
| /* X is the result of an RTL_EXPR. If it is a temporary slot associated |
| with that RTL_EXPR, promote it into a temporary slot at the present |
| level so it will not be freed when we free slots made in the |
| RTL_EXPR. */ |
| |
| void |
| preserve_rtl_expr_result (rtx x) |
| { |
| struct temp_slot *p; |
| |
| /* If X is not in memory or is at a constant address, it cannot be in |
| a temporary slot. */ |
| if (x == 0 || GET_CODE (x) != MEM || CONSTANT_P (XEXP (x, 0))) |
| return; |
| |
| /* If we can find a match, move it to our level unless it is already at |
| an upper level. */ |
| p = find_temp_slot_from_address (XEXP (x, 0)); |
| if (p != 0) |
| { |
| p->level = MIN (p->level, temp_slot_level); |
| p->rtl_expr = 0; |
| } |
| |
| return; |
| } |
| |
| /* Free all temporaries used so far. This is normally called at the end |
| of generating code for a statement. Don't free any temporaries |
| currently in use for an RTL_EXPR that hasn't yet been emitted. |
| We could eventually do better than this since it can be reused while |
| generating the same RTL_EXPR, but this is complex and probably not |
| worthwhile. */ |
| |
| void |
| free_temp_slots (void) |
| { |
| struct temp_slot *p; |
| |
| for (p = temp_slots; p; p = p->next) |
| if (p->in_use && p->level == temp_slot_level && ! p->keep |
| && p->rtl_expr == 0) |
| p->in_use = 0; |
| |
| combine_temp_slots (); |
| } |
| |
| /* Free all temporary slots used in T, an RTL_EXPR node. */ |
| |
| void |
| free_temps_for_rtl_expr (tree t) |
| { |
| struct temp_slot *p; |
| |
| for (p = temp_slots; p; p = p->next) |
| if (p->rtl_expr == t) |
| { |
| /* If this slot is below the current TEMP_SLOT_LEVEL, then it |
| needs to be preserved. This can happen if a temporary in |
| the RTL_EXPR was addressed; preserve_temp_slots will move |
| the temporary into a higher level. */ |
| if (temp_slot_level <= p->level) |
| p->in_use = 0; |
| else |
| p->rtl_expr = NULL_TREE; |
| } |
| |
| combine_temp_slots (); |
| } |
| |
| /* Mark all temporaries ever allocated in this function as not suitable |
| for reuse until the current level is exited. */ |
| |
| void |
| mark_all_temps_used (void) |
| { |
| struct temp_slot *p; |
| |
| for (p = temp_slots; p; p = p->next) |
| { |
| p->in_use = p->keep = 1; |
| p->level = MIN (p->level, temp_slot_level); |
| } |
| } |
| |
| /* Push deeper into the nesting level for stack temporaries. */ |
| |
| void |
| push_temp_slots (void) |
| { |
| temp_slot_level++; |
| } |
| |
| /* Pop a temporary nesting level. All slots in use in the current level |
| are freed. */ |
| |
| void |
| pop_temp_slots (void) |
| { |
| struct temp_slot *p; |
| |
| for (p = temp_slots; p; p = p->next) |
| if (p->in_use && p->level == temp_slot_level && p->rtl_expr == 0) |
| p->in_use = 0; |
| |
| combine_temp_slots (); |
| |
| temp_slot_level--; |
| } |
| |
| /* Initialize temporary slots. */ |
| |
| void |
| init_temp_slots (void) |
| { |
| /* We have not allocated any temporaries yet. */ |
| temp_slots = 0; |
| temp_slot_level = 0; |
| var_temp_slot_level = 0; |
| target_temp_slot_level = 0; |
| } |
| |
| /* Retroactively move an auto variable from a register to a stack |
| slot. This is done when an address-reference to the variable is |
| seen. If RESCAN is true, all previously emitted instructions are |
| examined and modified to handle the fact that DECL is now |
| addressable. */ |
| |
| void |
| put_var_into_stack (tree decl, int rescan) |
| { |
| rtx reg; |
| enum machine_mode promoted_mode, decl_mode; |
| struct function *function = 0; |
| tree context; |
| int can_use_addressof; |
| int volatilep = TREE_CODE (decl) != SAVE_EXPR && TREE_THIS_VOLATILE (decl); |
| int usedp = (TREE_USED (decl) |
| || (TREE_CODE (decl) != SAVE_EXPR && DECL_INITIAL (decl) != 0)); |
| |
| context = decl_function_context (decl); |
| |
| /* Get the current rtl used for this object and its original mode. */ |
| reg = (TREE_CODE (decl) == SAVE_EXPR |
| ? SAVE_EXPR_RTL (decl) |
| : DECL_RTL_IF_SET (decl)); |
| |
| /* No need to do anything if decl has no rtx yet |
| since in that case caller is setting TREE_ADDRESSABLE |
| and a stack slot will be assigned when the rtl is made. */ |
| if (reg == 0) |
| return; |
| |
| /* Get the declared mode for this object. */ |
| decl_mode = (TREE_CODE (decl) == SAVE_EXPR ? TYPE_MODE (TREE_TYPE (decl)) |
| : DECL_MODE (decl)); |
| /* Get the mode it's actually stored in. */ |
| promoted_mode = GET_MODE (reg); |
| |
| /* If this variable comes from an outer function, find that |
| function's saved context. Don't use find_function_data here, |
| because it might not be in any active function. |
| FIXME: Is that really supposed to happen? |
| It does in ObjC at least. */ |
| if (context != current_function_decl && context != inline_function_decl) |
| for (function = outer_function_chain; function; function = function->outer) |
| if (function->decl == context) |
| break; |
| |
| /* If this is a variable-sized object or a structure passed by invisible |
| reference, with a pseudo to address it, put that pseudo into the stack |
| if the var is non-local. */ |
| if (TREE_CODE (decl) != SAVE_EXPR && DECL_NONLOCAL (decl) |
| && GET_CODE (reg) == MEM |
| && GET_CODE (XEXP (reg, 0)) == REG |
| && REGNO (XEXP (reg, 0)) > LAST_VIRTUAL_REGISTER) |
| { |
| reg = XEXP (reg, 0); |
| decl_mode = promoted_mode = GET_MODE (reg); |
| } |
| |
| /* If this variable lives in the current function and we don't need to put it |
| in the stack for the sake of setjmp or the non-locality, try to keep it in |
| a register until we know we actually need the address. */ |
| can_use_addressof |
| = (function == 0 |
| && ! (TREE_CODE (decl) != SAVE_EXPR && DECL_NONLOCAL (decl)) |
| && optimize > 0 |
| /* FIXME make it work for promoted modes too */ |
| && decl_mode == promoted_mode |
| #ifdef NON_SAVING_SETJMP |
| && ! (NON_SAVING_SETJMP && current_function_calls_setjmp) |
| #endif |
| ); |
| |
| /* If we can't use ADDRESSOF, make sure we see through one we already |
| generated. */ |
| if (! can_use_addressof && GET_CODE (reg) == MEM |
| && GET_CODE (XEXP (reg, 0)) == ADDRESSOF) |
| reg = XEXP (XEXP (reg, 0), 0); |
| |
| /* Now we should have a value that resides in one or more pseudo regs. */ |
| |
| if (GET_CODE (reg) == REG) |
| { |
| if (can_use_addressof) |
| gen_mem_addressof (reg, decl, rescan); |
| else |
| put_reg_into_stack (function, reg, TREE_TYPE (decl), promoted_mode, |
| decl_mode, volatilep, 0, usedp, 0); |
| } |
| else if (GET_CODE (reg) == CONCAT) |
| { |
| /* A CONCAT contains two pseudos; put them both in the stack. |
| We do it so they end up consecutive. |
| We fixup references to the parts only after we fixup references |
| to the whole CONCAT, lest we do double fixups for the latter |
| references. */ |
| enum machine_mode part_mode = GET_MODE (XEXP (reg, 0)); |
| tree part_type = (*lang_hooks.types.type_for_mode) (part_mode, 0); |
| rtx lopart = XEXP (reg, 0); |
| rtx hipart = XEXP (reg, 1); |
| #ifdef FRAME_GROWS_DOWNWARD |
| /* Since part 0 should have a lower address, do it second. */ |
| put_reg_into_stack (function, hipart, part_type, part_mode, |
| part_mode, volatilep, 0, 0, 0); |
| put_reg_into_stack (function, lopart, part_type, part_mode, |
| part_mode, volatilep, 0, 0, 0); |
| #else |
| put_reg_into_stack (function, lopart, part_type, part_mode, |
| part_mode, volatilep, 0, 0, 0); |
| put_reg_into_stack (function, hipart, part_type, part_mode, |
| part_mode, volatilep, 0, 0, 0); |
| #endif |
| |
| /* Change the CONCAT into a combined MEM for both parts. */ |
| PUT_CODE (reg, MEM); |
| MEM_ATTRS (reg) = 0; |
| |
| /* set_mem_attributes uses DECL_RTL to avoid re-generating of |
| already computed alias sets. Here we want to re-generate. */ |
| if (DECL_P (decl)) |
| SET_DECL_RTL (decl, NULL); |
| set_mem_attributes (reg, decl, 1); |
| if (DECL_P (decl)) |
| SET_DECL_RTL (decl, reg); |
| |
| /* The two parts are in memory order already. |
| Use the lower parts address as ours. */ |
| XEXP (reg, 0) = XEXP (XEXP (reg, 0), 0); |
| /* Prevent sharing of rtl that might lose. */ |
| if (GET_CODE (XEXP (reg, 0)) == PLUS) |
| XEXP (reg, 0) = copy_rtx (XEXP (reg, 0)); |
| if (usedp && rescan) |
| { |
| schedule_fixup_var_refs (function, reg, TREE_TYPE (decl), |
| promoted_mode, 0); |
| schedule_fixup_var_refs (function, lopart, part_type, part_mode, 0); |
| schedule_fixup_var_refs (function, hipart, part_type, part_mode, 0); |
| } |
| } |
| else |
| return; |
| } |
| |
| /* Subroutine of put_var_into_stack. This puts a single pseudo reg REG |
| into the stack frame of FUNCTION (0 means the current function). |
| DECL_MODE is the machine mode of the user-level data type. |
| PROMOTED_MODE is the machine mode of the register. |
| VOLATILE_P is nonzero if this is for a "volatile" decl. |
| USED_P is nonzero if this reg might have already been used in an insn. */ |
| |
| static void |
| put_reg_into_stack (struct function *function, rtx reg, tree type, |
| enum machine_mode promoted_mode, |
| enum machine_mode decl_mode, int volatile_p, |
| unsigned int original_regno, int used_p, htab_t ht) |
| { |
| struct function *func = function ? function : cfun; |
| rtx new = 0; |
| unsigned int regno = original_regno; |
| |
| if (regno == 0) |
| regno = REGNO (reg); |
| |
| if (regno < func->x_max_parm_reg) |
| { |
| if (!func->x_parm_reg_stack_loc) |
| abort (); |
| new = func->x_parm_reg_stack_loc[regno]; |
| } |
| |
| if (new == 0) |
| new = assign_stack_local_1 (decl_mode, GET_MODE_SIZE (decl_mode), 0, func); |
| |
| PUT_CODE (reg, MEM); |
| PUT_MODE (reg, decl_mode); |
| XEXP (reg, 0) = XEXP (new, 0); |
| MEM_ATTRS (reg) = 0; |
| /* `volatil' bit means one thing for MEMs, another entirely for REGs. */ |
| MEM_VOLATILE_P (reg) = volatile_p; |
| |
| /* If this is a memory ref that contains aggregate components, |
| mark it as such for cse and loop optimize. If we are reusing a |
| previously generated stack slot, then we need to copy the bit in |
| case it was set for other reasons. For instance, it is set for |
| __builtin_va_alist. */ |
| if (type) |
| { |
| MEM_SET_IN_STRUCT_P (reg, |
| AGGREGATE_TYPE_P (type) || MEM_IN_STRUCT_P (new)); |
| set_mem_alias_set (reg, get_alias_set (type)); |
| } |
| |
| if (used_p) |
| schedule_fixup_var_refs (function, reg, type, promoted_mode, ht); |
| } |
| |
| /* Make sure that all refs to the variable, previously made |
| when it was a register, are fixed up to be valid again. |
| See function above for meaning of arguments. */ |
| |
| static void |
| schedule_fixup_var_refs (struct function *function, rtx reg, tree type, |
| enum machine_mode promoted_mode, htab_t ht) |
| { |
| int unsigned_p = type ? TREE_UNSIGNED (type) : 0; |
| |
| if (function != 0) |
| { |
| struct var_refs_queue *temp; |
| |
| temp = ggc_alloc (sizeof (struct var_refs_queue)); |
| temp->modified = reg; |
| temp->promoted_mode = promoted_mode; |
| temp->unsignedp = unsigned_p; |
| temp->next = function->fixup_var_refs_queue; |
| function->fixup_var_refs_queue = temp; |
| } |
| else |
| /* Variable is local; fix it up now. */ |
| fixup_var_refs (reg, promoted_mode, unsigned_p, reg, ht); |
| } |
| |
| static void |
| fixup_var_refs (rtx var, enum machine_mode promoted_mode, int unsignedp, |
| rtx may_share, htab_t ht) |
| { |
| tree pending; |
| rtx first_insn = get_insns (); |
| struct sequence_stack *stack = seq_stack; |
| tree rtl_exps = rtl_expr_chain; |
| |
| /* If there's a hash table, it must record all uses of VAR. */ |
| if (ht) |
| { |
| if (stack != 0) |
| abort (); |
| fixup_var_refs_insns_with_hash (ht, var, promoted_mode, unsignedp, |
| may_share); |
| return; |
| } |
| |
| fixup_var_refs_insns (first_insn, var, promoted_mode, unsignedp, |
| stack == 0, may_share); |
| |
| /* Scan all pending sequences too. */ |
| for (; stack; stack = stack->next) |
| { |
| push_to_full_sequence (stack->first, stack->last); |
| fixup_var_refs_insns (stack->first, var, promoted_mode, unsignedp, |
| stack->next != 0, may_share); |
| /* Update remembered end of sequence |
| in case we added an insn at the end. */ |
| stack->last = get_last_insn (); |
| end_sequence (); |
| } |
| |
| /* Scan all waiting RTL_EXPRs too. */ |
| for (pending = rtl_exps; pending; pending = TREE_CHAIN (pending)) |
| { |
| rtx seq = RTL_EXPR_SEQUENCE (TREE_VALUE (pending)); |
| if (seq != const0_rtx && seq != 0) |
| { |
| push_to_sequence (seq); |
| fixup_var_refs_insns (seq, var, promoted_mode, unsignedp, 0, |
| may_share); |
| end_sequence (); |
| } |
| } |
| } |
| |
| /* REPLACEMENTS is a pointer to a list of the struct fixup_replacement and X is |
| some part of an insn. Return a struct fixup_replacement whose OLD |
| value is equal to X. Allocate a new structure if no such entry exists. */ |
| |
| static struct fixup_replacement * |
| find_fixup_replacement (struct fixup_replacement **replacements, rtx x) |
| { |
| struct fixup_replacement *p; |
| |
| /* See if we have already replaced this. */ |
| for (p = *replacements; p != 0 && ! rtx_equal_p (p->old, x); p = p->next) |
| ; |
| |
| if (p == 0) |
| { |
| p = xmalloc (sizeof (struct fixup_replacement)); |
| p->old = x; |
| p->new = 0; |
| p->next = *replacements; |
| *replacements = p; |
| } |
| |
| return p; |
| } |
| |
| /* Scan the insn-chain starting with INSN for refs to VAR and fix them |
| up. TOPLEVEL is nonzero if this chain is the main chain of insns |
| for the current function. MAY_SHARE is either a MEM that is not |
| to be unshared or a list of them. */ |
| |
| static void |
| fixup_var_refs_insns (rtx insn, rtx var, enum machine_mode promoted_mode, |
| int unsignedp, int toplevel, rtx may_share) |
| { |
| while (insn) |
| { |
| /* fixup_var_refs_insn might modify insn, so save its next |
| pointer now. */ |
| rtx next = NEXT_INSN (insn); |
| |
| /* CALL_PLACEHOLDERs are special; we have to switch into each of |
| the three sequences they (potentially) contain, and process |
| them recursively. The CALL_INSN itself is not interesting. */ |
| |
| if (GET_CODE (insn) == CALL_INSN |
| && GET_CODE (PATTERN (insn)) == CALL_PLACEHOLDER) |
| { |
| int i; |
| |
| /* Look at the Normal call, sibling call and tail recursion |
| sequences attached to the CALL_PLACEHOLDER. */ |
| for (i = 0; i < 3; i++) |
| { |
| rtx seq = XEXP (PATTERN (insn), i); |
| if (seq) |
| { |
| push_to_sequence (seq); |
| fixup_var_refs_insns (seq, var, promoted_mode, unsignedp, 0, |
| may_share); |
| XEXP (PATTERN (insn), i) = get_insns (); |
| end_sequence (); |
| } |
| } |
| } |
| |
| else if (INSN_P (insn)) |
| fixup_var_refs_insn (insn, var, promoted_mode, unsignedp, toplevel, |
| may_share); |
| |
| insn = next; |
| } |
| } |
| |
| /* Look up the insns which reference VAR in HT and fix them up. Other |
| arguments are the same as fixup_var_refs_insns. |
| |
| N.B. No need for special processing of CALL_PLACEHOLDERs here, |
| because the hash table will point straight to the interesting insn |
| (inside the CALL_PLACEHOLDER). */ |
| |
| static void |
| fixup_var_refs_insns_with_hash (htab_t ht, rtx var, enum machine_mode promoted_mode, |
| int unsignedp, rtx may_share) |
| { |
| struct insns_for_mem_entry tmp; |
| struct insns_for_mem_entry *ime; |
| rtx insn_list; |
| |
| tmp.key = var; |
| ime = htab_find (ht, &tmp); |
| for (insn_list = ime->insns; insn_list != 0; insn_list = XEXP (insn_list, 1)) |
| if (INSN_P (XEXP (insn_list, 0))) |
| fixup_var_refs_insn (XEXP (insn_list, 0), var, promoted_mode, |
| unsignedp, 1, may_share); |
| } |
| |
| |
| /* Per-insn processing by fixup_var_refs_insns(_with_hash). INSN is |
| the insn under examination, VAR is the variable to fix up |
| references to, PROMOTED_MODE and UNSIGNEDP describe VAR, and |
| TOPLEVEL is nonzero if this is the main insn chain for this |
| function. */ |
| |
| static void |
| fixup_var_refs_insn (rtx insn, rtx var, enum machine_mode promoted_mode, |
| int unsignedp, int toplevel, rtx no_share) |
| { |
| rtx call_dest = 0; |
| rtx set, prev, prev_set; |
| rtx note; |
| |
| /* Remember the notes in case we delete the insn. */ |
| note = REG_NOTES (insn); |
| |
| /* If this is a CLOBBER of VAR, delete it. |
| |
| If it has a REG_LIBCALL note, delete the REG_LIBCALL |
| and REG_RETVAL notes too. */ |
| if (GET_CODE (PATTERN (insn)) == CLOBBER |
| && (XEXP (PATTERN (insn), 0) == var |
| || (GET_CODE (XEXP (PATTERN (insn), 0)) == CONCAT |
| && (XEXP (XEXP (PATTERN (insn), 0), 0) == var |
| || XEXP (XEXP (PATTERN (insn), 0), 1) == var)))) |
| { |
| if ((note = find_reg_note (insn, REG_LIBCALL, NULL_RTX)) != 0) |
| /* The REG_LIBCALL note will go away since we are going to |
| turn INSN into a NOTE, so just delete the |
| corresponding REG_RETVAL note. */ |
| remove_note (XEXP (note, 0), |
| find_reg_note (XEXP (note, 0), REG_RETVAL, |
| NULL_RTX)); |
| |
| delete_insn (insn); |
| } |
| |
| /* The insn to load VAR from a home in the arglist |
| is now a no-op. When we see it, just delete it. |
| Similarly if this is storing VAR from a register from which |
| it was loaded in the previous insn. This will occur |
| when an ADDRESSOF was made for an arglist slot. */ |
| else if (toplevel |
| && (set = single_set (insn)) != 0 |
| && SET_DEST (set) == var |
| /* If this represents the result of an insn group, |
| don't delete the insn. */ |
| && find_reg_note (insn, REG_RETVAL, NULL_RTX) == 0 |
| && (rtx_equal_p (SET_SRC (set), var) |
| || (GET_CODE (SET_SRC (set)) == REG |
| && (prev = prev_nonnote_insn (insn)) != 0 |
| && (prev_set = single_set (prev)) != 0 |
| && SET_DEST (prev_set) == SET_SRC (set) |
| && rtx_equal_p (SET_SRC (prev_set), var)))) |
| { |
| delete_insn (insn); |
| } |
| else |
| { |
| struct fixup_replacement *replacements = 0; |
| rtx next_insn = NEXT_INSN (insn); |
| |
| if (SMALL_REGISTER_CLASSES) |
| { |
| /* If the insn that copies the results of a CALL_INSN |
| into a pseudo now references VAR, we have to use an |
| intermediate pseudo since we want the life of the |
| return value register to be only a single insn. |
| |
| If we don't use an intermediate pseudo, such things as |
| address computations to make the address of VAR valid |
| if it is not can be placed between the CALL_INSN and INSN. |
| |
| To make sure this doesn't happen, we record the destination |
| of the CALL_INSN and see if the next insn uses both that |
| and VAR. */ |
| |
| if (call_dest != 0 && GET_CODE (insn) == INSN |
| && reg_mentioned_p (var, PATTERN (insn)) |
| && reg_mentioned_p (call_dest, PATTERN (insn))) |
| { |
| rtx temp = gen_reg_rtx (GET_MODE (call_dest)); |
| |
| emit_insn_before (gen_move_insn (temp, call_dest), insn); |
| |
| PATTERN (insn) = replace_rtx (PATTERN (insn), |
| call_dest, temp); |
| } |
| |
| if (GET_CODE (insn) == CALL_INSN |
| && GET_CODE (PATTERN (insn)) == SET) |
| call_dest = SET_DEST (PATTERN (insn)); |
| else if (GET_CODE (insn) == CALL_INSN |
| && GET_CODE (PATTERN (insn)) == PARALLEL |
| && GET_CODE (XVECEXP (PATTERN (insn), 0, 0)) == SET) |
| call_dest = SET_DEST (XVECEXP (PATTERN (insn), 0, 0)); |
| else |
| call_dest = 0; |
| } |
| |
| /* See if we have to do anything to INSN now that VAR is in |
| memory. If it needs to be loaded into a pseudo, use a single |
| pseudo for the entire insn in case there is a MATCH_DUP |
| between two operands. We pass a pointer to the head of |
| a list of struct fixup_replacements. If fixup_var_refs_1 |
| needs to allocate pseudos or replacement MEMs (for SUBREGs), |
| it will record them in this list. |
| |
| If it allocated a pseudo for any replacement, we copy into |
| it here. */ |
| |
| fixup_var_refs_1 (var, promoted_mode, &PATTERN (insn), insn, |
| &replacements, no_share); |
| |
| /* If this is last_parm_insn, and any instructions were output |
| after it to fix it up, then we must set last_parm_insn to |
| the last such instruction emitted. */ |
| if (insn == last_parm_insn) |
| last_parm_insn = PREV_INSN (next_insn); |
| |
| while (replacements) |
| { |
| struct fixup_replacement *next; |
| |
| if (GET_CODE (replacements->new) == REG) |
| { |
| rtx insert_before; |
| rtx seq; |
| |
| /* OLD might be a (subreg (mem)). */ |
| if (GET_CODE (replacements->old) == SUBREG) |
| replacements->old |
| = fixup_memory_subreg (replacements->old, insn, |
| promoted_mode, 0); |
| else |
| replacements->old |
| = fixup_stack_1 (replacements->old, insn); |
| |
| insert_before = insn; |
| |
| /* If we are changing the mode, do a conversion. |
| This might be wasteful, but combine.c will |
| eliminate much of the waste. */ |
| |
| if (GET_MODE (replacements->new) |
| != GET_MODE (replacements->old)) |
| { |
| start_sequence (); |
| convert_move (replacements->new, |
| replacements->old, unsignedp); |
| seq = get_insns (); |
| end_sequence (); |
| } |
| else |
| seq = gen_move_insn (replacements->new, |
| replacements->old); |
| |
| emit_insn_before (seq, insert_before); |
| } |
| |
| next = replacements->next; |
| free (replacements); |
| replacements = next; |
| } |
| } |
| |
| /* Also fix up any invalid exprs in the REG_NOTES of this insn. |
| But don't touch other insns referred to by reg-notes; |
| we will get them elsewhere. */ |
| while (note) |
| { |
| if (GET_CODE (note) != INSN_LIST) |
| XEXP (note, 0) |
| = walk_fixup_memory_subreg (XEXP (note, 0), insn, |
| promoted_mode, 1); |
| note = XEXP (note, 1); |
| } |
| } |
| |
| /* VAR is a MEM that used to be a pseudo register with mode PROMOTED_MODE. |
| See if the rtx expression at *LOC in INSN needs to be changed. |
| |
| REPLACEMENTS is a pointer to a list head that starts out zero, but may |
| contain a list of original rtx's and replacements. If we find that we need |
| to modify this insn by replacing a memory reference with a pseudo or by |
| making a new MEM to implement a SUBREG, we consult that list to see if |
| we have already chosen a replacement. If none has already been allocated, |
| we allocate it and update the list. fixup_var_refs_insn will copy VAR |
| or the SUBREG, as appropriate, to the pseudo. */ |
| |
| static void |
| fixup_var_refs_1 (rtx var, enum machine_mode promoted_mode, rtx *loc, rtx insn, |
| struct fixup_replacement **replacements, rtx no_share) |
| { |
| int i; |
| rtx x = *loc; |
| RTX_CODE code = GET_CODE (x); |
| const char *fmt; |
| rtx tem, tem1; |
| struct fixup_replacement *replacement; |
| |
| switch (code) |
| { |
| case ADDRESSOF: |
| if (XEXP (x, 0) == var) |
| { |
| /* Prevent sharing of rtl that might lose. */ |
| rtx sub = copy_rtx (XEXP (var, 0)); |
| |
| if (! validate_change (insn, loc, sub, 0)) |
| { |
| rtx y = gen_reg_rtx (GET_MODE (sub)); |
| rtx seq, new_insn; |
| |
| /* We should be able to replace with a register or all is lost. |
| Note that we can't use validate_change to verify this, since |
| we're not caring for replacing all dups simultaneously. */ |
| if (! validate_replace_rtx (*loc, y, insn)) |
| abort (); |
| |
| /* Careful! First try to recognize a direct move of the |
| value, mimicking how things are done in gen_reload wrt |
| PLUS. Consider what happens when insn is a conditional |
| move instruction and addsi3 clobbers flags. */ |
| |
| start_sequence (); |
| new_insn = emit_insn (gen_rtx_SET (VOIDmode, y, sub)); |
| seq = get_insns (); |
| end_sequence (); |
| |
| if (recog_memoized (new_insn) < 0) |
| { |
| /* That failed. Fall back on force_operand and hope. */ |
| |
| start_sequence (); |
| sub = force_operand (sub, y); |
| if (sub != y) |
| emit_insn (gen_move_insn (y, sub)); |
| seq = get_insns (); |
| end_sequence (); |
| } |
| |
| #ifdef HAVE_cc0 |
| /* Don't separate setter from user. */ |
| if (PREV_INSN (insn) && sets_cc0_p (PREV_INSN (insn))) |
| insn = PREV_INSN (insn); |
| #endif |
| |
| emit_insn_before (seq, insn); |
| } |
| } |
| return; |
| |
| case MEM: |
| if (var == x) |
| { |
| /* If we already have a replacement, use it. Otherwise, |
| try to fix up this address in case it is invalid. */ |
| |
| replacement = find_fixup_replacement (replacements, var); |
| if (replacement->new) |
| { |
| *loc = replacement->new; |
| return; |
| } |
| |
| *loc = replacement->new = x = fixup_stack_1 (x, insn); |
| |
| /* Unless we are forcing memory to register or we changed the mode, |
| we can leave things the way they are if the insn is valid. */ |
| |
| INSN_CODE (insn) = -1; |
| if (! flag_force_mem && GET_MODE (x) == promoted_mode |
| && recog_memoized (insn) >= 0) |
| return; |
| |
| *loc = replacement->new = gen_reg_rtx (promoted_mode); |
| return; |
| } |
| |
| /* If X contains VAR, we need to unshare it here so that we update |
| each occurrence separately. But all identical MEMs in one insn |
| must be replaced with the same rtx because of the possibility of |
| MATCH_DUPs. */ |
| |
| if (reg_mentioned_p (var, x)) |
| { |
| replacement = find_fixup_replacement (replacements, x); |
| if (replacement->new == 0) |
| replacement->new = copy_most_rtx (x, no_share); |
| |
| *loc = x = replacement->new; |
| code = GET_CODE (x); |
| } |
| break; |
| |
| case REG: |
| case CC0: |
| case PC: |
| case CONST_INT: |
| case CONST: |
| case SYMBOL_REF: |
| case LABEL_REF: |
| case CONST_DOUBLE: |
| case CONST_VECTOR: |
| return; |
| |
| case SIGN_EXTRACT: |
| case ZERO_EXTRACT: |
| /* Note that in some cases those types of expressions are altered |
| by optimize_bit_field, and do not survive to get here. */ |
| if (XEXP (x, 0) == var |
| || (GET_CODE (XEXP (x, 0)) == SUBREG |
| && SUBREG_REG (XEXP (x, 0)) == var)) |
| { |
| /* Get TEM as a valid MEM in the mode presently in the insn. |
| |
| We don't worry about the possibility of MATCH_DUP here; it |
| is highly unlikely and would be tricky to handle. */ |
| |
| tem = XEXP (x, 0); |
| if (GET_CODE (tem) == SUBREG) |
| { |
| if (GET_MODE_BITSIZE (GET_MODE (tem)) |
| > GET_MODE_BITSIZE (GET_MODE (var))) |
| { |
| replacement = find_fixup_replacement (replacements, var); |
| if (replacement->new == 0) |
| replacement->new = gen_reg_rtx (GET_MODE (var)); |
| SUBREG_REG (tem) = replacement->new; |
| |
| /* The following code works only if we have a MEM, so we |
| need to handle the subreg here. We directly substitute |
| it assuming that a subreg must be OK here. We already |
| scheduled a replacement to copy the mem into the |
| subreg. */ |
| XEXP (x, 0) = tem; |
| return; |
| } |
| else |
| tem = fixup_memory_subreg (tem, insn, promoted_mode, 0); |
| } |
| else |
| tem = fixup_stack_1 (tem, insn); |
| |
| /* Unless we want to load from memory, get TEM into the proper mode |
| for an extract from memory. This can only be done if the |
| extract is at a constant position and length. */ |
| |
| if (! flag_force_mem && GET_CODE (XEXP (x, 1)) == CONST_INT |
| && GET_CODE (XEXP (x, 2)) == CONST_INT |
| && ! mode_dependent_address_p (XEXP (tem, 0)) |
| && ! MEM_VOLATILE_P (tem)) |
| { |
| enum machine_mode wanted_mode = VOIDmode; |
| enum machine_mode is_mode = GET_MODE (tem); |
| HOST_WIDE_INT pos = INTVAL (XEXP (x, 2)); |
| |
| if (GET_CODE (x) == ZERO_EXTRACT) |
| { |
| enum machine_mode new_mode |
| = mode_for_extraction (EP_extzv, 1); |
| if (new_mode != MAX_MACHINE_MODE) |
| wanted_mode = new_mode; |
| } |
| else if (GET_CODE (x) == SIGN_EXTRACT) |
| { |
| enum machine_mode new_mode |
| = mode_for_extraction (EP_extv, 1); |
| if (new_mode != MAX_MACHINE_MODE) |
| wanted_mode = new_mode; |
| } |
| |
| /* If we have a narrower mode, we can do something. */ |
| if (wanted_mode != VOIDmode |
| && GET_MODE_SIZE (wanted_mode) < GET_MODE_SIZE (is_mode)) |
| { |
| HOST_WIDE_INT offset = pos / BITS_PER_UNIT; |
| rtx old_pos = XEXP (x, 2); |
| rtx newmem; |
| |
| /* If the bytes and bits are counted differently, we |
| must adjust the offset. */ |
| if (BYTES_BIG_ENDIAN != BITS_BIG_ENDIAN) |
| offset = (GET_MODE_SIZE (is_mode) |
| - GET_MODE_SIZE (wanted_mode) - offset); |
| |
| pos %= GET_MODE_BITSIZE (wanted_mode); |
| |
| newmem = adjust_address_nv (tem, wanted_mode, offset); |
| |
| /* Make the change and see if the insn remains valid. */ |
| INSN_CODE (insn) = -1; |
| XEXP (x, 0) = newmem; |
| XEXP (x, 2) = GEN_INT (pos); |
| |
| if (recog_memoized (insn) >= 0) |
| return; |
| |
| /* Otherwise, restore old position. XEXP (x, 0) will be |
| restored later. */ |
| XEXP (x, 2) = old_pos; |
| } |
| } |
| |
| /* If we get here, the bitfield extract insn can't accept a memory |
| reference. Copy the input into a register. */ |
| |
| tem1 = gen_reg_rtx (GET_MODE (tem)); |
| emit_insn_before (gen_move_insn (tem1, tem), insn); |
| XEXP (x, 0) = tem1; |
| return; |
| } |
| break; |
| |
| case SUBREG: |
| if (SUBREG_REG (x) == var) |
| { |
| /* If this is a special SUBREG made because VAR was promoted |
| from a wider mode, replace it with VAR and call ourself |
| recursively, this time saying that the object previously |
| had its current mode (by virtue of the SUBREG). */ |
| |
| if (SUBREG_PROMOTED_VAR_P (x)) |
| { |
| *loc = var; |
| fixup_var_refs_1 (var, GET_MODE (var), loc, insn, replacements, |
| no_share); |
| return; |
| } |
| |
| /* If this SUBREG makes VAR wider, it has become a paradoxical |
| SUBREG with VAR in memory, but these aren't allowed at this |
| stage of the compilation. So load VAR into a pseudo and take |
| a SUBREG of that pseudo. */ |
| if (GET_MODE_SIZE (GET_MODE (x)) > GET_MODE_SIZE (GET_MODE (var))) |
| { |
| replacement = find_fixup_replacement (replacements, var); |
| if (replacement->new == 0) |
| replacement->new = gen_reg_rtx (promoted_mode); |
| SUBREG_REG (x) = replacement->new; |
| return; |
| } |
| |
| /* See if we have already found a replacement for this SUBREG. |
| If so, use it. Otherwise, make a MEM and see if the insn |
| is recognized. If not, or if we should force MEM into a register, |
| make a pseudo for this SUBREG. */ |
| replacement = find_fixup_replacement (replacements, x); |
| if (replacement->new) |
| { |
| enum machine_mode mode = GET_MODE (x); |
| *loc = replacement->new; |
| |
| /* Careful! We may have just replaced a SUBREG by a MEM, which |
| means that the insn may have become invalid again. We can't |
| in this case make a new replacement since we already have one |
| and we must deal with MATCH_DUPs. */ |
| if (GET_CODE (replacement->new) == MEM) |
| { |
| INSN_CODE (insn) = -1; |
| if (recog_memoized (insn) >= 0) |
| return; |
| |
| fixup_var_refs_1 (replacement->new, mode, &PATTERN (insn), |
| insn, replacements, no_share); |
| } |
| |
| return; |
| } |
| |
| replacement->new = *loc = fixup_memory_subreg (x, insn, |
| promoted_mode, 0); |
| |
| INSN_CODE (insn) = -1; |
| if (! flag_force_mem && recog_memoized (insn) >= 0) |
| return; |
| |
| *loc = replacement->new = gen_reg_rtx (GET_MODE (x)); |
| return; |
| } |
| break; |
| |
| case SET: |
| /* First do special simplification of bit-field references. */ |
| if (GET_CODE (SET_DEST (x)) == SIGN_EXTRACT |
| || GET_CODE (SET_DEST (x)) == ZERO_EXTRACT) |
| optimize_bit_field (x, insn, 0); |
| if (GET_CODE (SET_SRC (x)) == SIGN_EXTRACT |
| || GET_CODE (SET_SRC (x)) == ZERO_EXTRACT) |
| optimize_bit_field (x, insn, 0); |
| |
| /* For a paradoxical SUBREG inside a ZERO_EXTRACT, load the object |
| into a register and then store it back out. */ |
| if (GET_CODE (SET_DEST (x)) == ZERO_EXTRACT |
| && GET_CODE (XEXP (SET_DEST (x), 0)) == SUBREG |
| && SUBREG_REG (XEXP (SET_DEST (x), 0)) == var |
| && (GET_MODE_SIZE (GET_MODE (XEXP (SET_DEST (x), 0))) |
| > GET_MODE_SIZE (GET_MODE (var)))) |
| { |
| replacement = find_fixup_replacement (replacements, var); |
| if (replacement->new == 0) |
| replacement->new = gen_reg_rtx (GET_MODE (var)); |
| |
| SUBREG_REG (XEXP (SET_DEST (x), 0)) = replacement->new; |
| emit_insn_after (gen_move_insn (var, replacement->new), insn); |
| } |
| |
| /* If SET_DEST is now a paradoxical SUBREG, put the result of this |
| insn into a pseudo and store the low part of the pseudo into VAR. */ |
| if (GET_CODE (SET_DEST (x)) == SUBREG |
| && SUBREG_REG (SET_DEST (x)) == var |
| && (GET_MODE_SIZE (GET_MODE (SET_DEST (x))) |
| > GET_MODE_SIZE (GET_MODE (var)))) |
| { |
| SET_DEST (x) = tem = gen_reg_rtx (GET_MODE (SET_DEST (x))); |
| emit_insn_after (gen_move_insn (var, gen_lowpart (GET_MODE (var), |
| tem)), |
| insn); |
| break; |
| } |
| |
| { |
| rtx dest = SET_DEST (x); |
| rtx src = SET_SRC (x); |
| rtx outerdest = dest; |
| |
| while (GET_CODE (dest) == SUBREG || GET_CODE (dest) == STRICT_LOW_PART |
| || GET_CODE (dest) == SIGN_EXTRACT |
| || GET_CODE (dest) == ZERO_EXTRACT) |
| dest = XEXP (dest, 0); |
| |
| if (GET_CODE (src) == SUBREG) |
| src = SUBREG_REG (src); |
| |
| /* If VAR does not appear at the top level of the SET |
| just scan the lower levels of the tree. */ |
| |
| if (src != var && dest != var) |
| break; |
| |
| /* We will need to rerecognize this insn. */ |
| INSN_CODE (insn) = -1; |
| |
| if (GET_CODE (outerdest) == ZERO_EXTRACT && dest == var |
| && mode_for_extraction (EP_insv, -1) != MAX_MACHINE_MODE) |
| { |
| /* Since this case will return, ensure we fixup all the |
| operands here. */ |
| fixup_var_refs_1 (var, promoted_mode, &XEXP (outerdest, 1), |
| insn, replacements, no_share); |
| fixup_var_refs_1 (var, promoted_mode, &XEXP (outerdest, 2), |
| insn, replacements, no_share); |
| fixup_var_refs_1 (var, promoted_mode, &SET_SRC (x), |
| insn, replacements, no_share); |
| |
| tem = XEXP (outerdest, 0); |
| |
| /* Clean up (SUBREG:SI (MEM:mode ...) 0) |
| that may appear inside a ZERO_EXTRACT. |
| This was legitimate when the MEM was a REG. */ |
| if (GET_CODE (tem) == SUBREG |
| && SUBREG_REG (tem) == var) |
| tem = fixup_memory_subreg (tem, insn, promoted_mode, 0); |
| else |
| tem = fixup_stack_1 (tem, insn); |
| |
| if (GET_CODE (XEXP (outerdest, 1)) == CONST_INT |
| && GET_CODE (XEXP (outerdest, 2)) == CONST_INT |
| && ! mode_dependent_address_p (XEXP (tem, 0)) |
| && ! MEM_VOLATILE_P (tem)) |
| { |
| enum machine_mode wanted_mode; |
| enum machine_mode is_mode = GET_MODE (tem); |
| HOST_WIDE_INT pos = INTVAL (XEXP (outerdest, 2)); |
| |
| wanted_mode = mode_for_extraction (EP_insv, 0); |
| |
| /* If we have a narrower mode, we can do something. */ |
| if (GET_MODE_SIZE (wanted_mode) < GET_MODE_SIZE (is_mode)) |
| { |
| HOST_WIDE_INT offset = pos / BITS_PER_UNIT; |
| rtx old_pos = XEXP (outerdest, 2); |
| rtx newmem; |
| |
| if (BYTES_BIG_ENDIAN != BITS_BIG_ENDIAN) |
| offset = (GET_MODE_SIZE (is_mode) |
| - GET_MODE_SIZE (wanted_mode) - offset); |
| |
| pos %= GET_MODE_BITSIZE (wanted_mode); |
| |
| newmem = adjust_address_nv (tem, wanted_mode, offset); |
| |
| /* Make the change and see if the insn remains valid. */ |
| INSN_CODE (insn) = -1; |
| XEXP (outerdest, 0) = newmem; |
| XEXP (outerdest, 2) = GEN_INT (pos); |
| |
| if (recog_memoized (insn) >= 0) |
| return; |
| |
| /* Otherwise, restore old position. XEXP (x, 0) will be |
| restored later. */ |
| XEXP (outerdest, 2) = old_pos; |
| } |
| } |
| |
| /* If we get here, the bit-field store doesn't allow memory |
| or isn't located at a constant position. Load the value into |
| a register, do the store, and put it back into memory. */ |
| |
| tem1 = gen_reg_rtx (GET_MODE (tem)); |
| emit_insn_before (gen_move_insn (tem1, tem), insn); |
| emit_insn_after (gen_move_insn (tem, tem1), insn); |
| XEXP (outerdest, 0) = tem1; |
| return; |
| } |
| |
| /* STRICT_LOW_PART is a no-op on memory references |
| and it can cause combinations to be unrecognizable, |
| so eliminate it. */ |
| |
| if (dest == var && GET_CODE (SET_DEST (x)) == STRICT_LOW_PART) |
| SET_DEST (x) = XEXP (SET_DEST (x), 0); |
| |
| /* A valid insn to copy VAR into or out of a register |
| must be left alone, to avoid an infinite loop here. |
| If the reference to VAR is by a subreg, fix that up, |
| since SUBREG is not valid for a memref. |
| Also fix up the address of the stack slot. |
| |
| Note that we must not try to recognize the insn until |
| after we know that we have valid addresses and no |
| (subreg (mem ...) ...) constructs, since these interfere |
| with determining the validity of the insn. */ |
| |
| if ((SET_SRC (x) == var |
| || (GET_CODE (SET_SRC (x)) == SUBREG |
| && SUBREG_REG (SET_SRC (x)) == var)) |
| && (GET_CODE (SET_DEST (x)) == REG |
| || (GET_CODE (SET_DEST (x)) == SUBREG |
| && GET_CODE (SUBREG_REG (SET_DEST (x))) == REG)) |
| && GET_MODE (var) == promoted_mode |
| && x == single_set (insn)) |
| { |
| rtx pat, last; |
| |
| if (GET_CODE (SET_SRC (x)) == SUBREG |
| && (GET_MODE_SIZE (GET_MODE (SET_SRC (x))) |
| > GET_MODE_SIZE (GET_MODE (var)))) |
| { |
| /* This (subreg VAR) is now a paradoxical subreg. We need |
| to replace VAR instead of the subreg. */ |
| replacement = find_fixup_replacement (replacements, var); |
| if (replacement->new == NULL_RTX) |
| replacement->new = gen_reg_rtx (GET_MODE (var)); |
| SUBREG_REG (SET_SRC (x)) = replacement->new; |
| } |
| else |
| { |
| replacement = find_fixup_replacement (replacements, SET_SRC (x)); |
| if (replacement->new) |
| SET_SRC (x) = replacement->new; |
| else if (GET_CODE (SET_SRC (x)) == SUBREG) |
| SET_SRC (x) = replacement->new |
| = fixup_memory_subreg (SET_SRC (x), insn, promoted_mode, |
| 0); |
| else |
| SET_SRC (x) = replacement->new |
| = fixup_stack_1 (SET_SRC (x), insn); |
| } |
| |
| if (recog_memoized (insn) >= 0) |
| return; |
| |
| /* INSN is not valid, but we know that we want to |
| copy SET_SRC (x) to SET_DEST (x) in some way. So |
| we generate the move and see whether it requires more |
| than one insn. If it does, we emit those insns and |
| delete INSN. Otherwise, we can just replace the pattern |
| of INSN; we have already verified above that INSN has |
| no other function that to do X. */ |
| |
| pat = gen_move_insn (SET_DEST (x), SET_SRC (x)); |
| if (NEXT_INSN (pat) != NULL_RTX) |
| { |
| last = emit_insn_before (pat, insn); |
| |
| /* INSN might have REG_RETVAL or other important notes, so |
| we need to store the pattern of the last insn in the |
| sequence into INSN similarly to the normal case. LAST |
| should not have REG_NOTES, but we allow them if INSN has |
| no REG_NOTES. */ |
| if (REG_NOTES (last) && REG_NOTES (insn)) |
| abort (); |
| if (REG_NOTES (last)) |
| REG_NOTES (insn) = REG_NOTES (last); |
| PATTERN (insn) = PATTERN (last); |
| |
| delete_insn (last); |
| } |
| else |
| PATTERN (insn) = PATTERN (pat); |
| |
| return; |
| } |
| |
| if ((SET_DEST (x) == var |
| || (GET_CODE (SET_DEST (x)) == SUBREG |
| && SUBREG_REG (SET_DEST (x)) == var)) |
| && (GET_CODE (SET_SRC (x)) == REG |
| || (GET_CODE (SET_SRC (x)) == SUBREG |
| && GET_CODE (SUBREG_REG (SET_SRC (x))) == REG)) |
| && GET_MODE (var) == promoted_mode |
| && x == single_set (insn)) |
| { |
| rtx pat, last; |
| |
| if (GET_CODE (SET_DEST (x)) == SUBREG) |
| SET_DEST (x) = fixup_memory_subreg (SET_DEST (x), insn, |
| promoted_mode, 0); |
| else |
| SET_DEST (x) = fixup_stack_1 (SET_DEST (x), insn); |
| |
| if (recog_memoized (insn) >= 0) |
| return; |
| |
| pat = gen_move_insn (SET_DEST (x), SET_SRC (x)); |
| if (NEXT_INSN (pat) != NULL_RTX) |
| { |
| last = emit_insn_before (pat, insn); |
| |
| /* INSN might have REG_RETVAL or other important notes, so |
| we need to store the pattern of the last insn in the |
| sequence into INSN similarly to the normal case. LAST |
| should not have REG_NOTES, but we allow them if INSN has |
| no REG_NOTES. */ |
| if (REG_NOTES (last) && REG_NOTES (insn)) |
| abort (); |
| if (REG_NOTES (last)) |
| REG_NOTES (insn) = REG_NOTES (last); |
| PATTERN (insn) = PATTERN (last); |
| |
| delete_insn (last); |
| } |
| else |
| PATTERN (insn) = PATTERN (pat); |
| |
| return; |
| } |
| |
| /* Otherwise, storing into VAR must be handled specially |
| by storing into a temporary and copying that into VAR |
| with a new insn after this one. Note that this case |
| will be used when storing into a promoted scalar since |
| the insn will now have different modes on the input |
| and output and hence will be invalid (except for the case |
| of setting it to a constant, which does not need any |
| change if it is valid). We generate extra code in that case, |
| but combine.c will eliminate it. */ |
| |
| if (dest == var) |
| { |
| rtx temp; |
| rtx fixeddest = SET_DEST (x); |
| enum machine_mode temp_mode; |
| |
| /* STRICT_LOW_PART can be discarded, around a MEM. */ |
| if (GET_CODE (fixeddest) == STRICT_LOW_PART) |
| fixeddest = XEXP (fixeddest, 0); |
| /* Convert (SUBREG (MEM)) to a MEM in a changed mode. */ |
| if (GET_CODE (fixeddest) == SUBREG) |
| { |
| fixeddest = fixup_memory_subreg (fixeddest, insn, |
| promoted_mode, 0); |
| temp_mode = GET_MODE (fixeddest); |
| } |
| else |
| { |
| fixeddest = fixup_stack_1 (fixeddest, insn); |
| temp_mode = promoted_mode; |
| } |
| |
| temp = gen_reg_rtx (temp_mode); |
| |
| emit_insn_after (gen_move_insn (fixeddest, |
| gen_lowpart (GET_MODE (fixeddest), |
| temp)), |
| insn); |
| |
| SET_DEST (x) = temp; |
| } |
| } |
| |
| default: |
| break; |
| } |
| |
| /* Nothing special about this RTX; fix its operands. */ |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| fixup_var_refs_1 (var, promoted_mode, &XEXP (x, i), insn, replacements, |
| no_share); |
| else if (fmt[i] == 'E') |
| { |
| int j; |
| for (j = 0; j < XVECLEN (x, i); j++) |
| fixup_var_refs_1 (var, promoted_mode, &XVECEXP (x, i, j), |
| insn, replacements, no_share); |
| } |
| } |
| } |
| |
| /* Previously, X had the form (SUBREG:m1 (REG:PROMOTED_MODE ...)). |
| The REG was placed on the stack, so X now has the form (SUBREG:m1 |
| (MEM:m2 ...)). |
| |
| Return an rtx (MEM:m1 newaddr) which is equivalent. If any insns |
| must be emitted to compute NEWADDR, put them before INSN. |
| |
| UNCRITICAL nonzero means accept paradoxical subregs. |
| This is used for subregs found inside REG_NOTES. */ |
| |
| static rtx |
| fixup_memory_subreg (rtx x, rtx insn, enum machine_mode promoted_mode, int uncritical) |
| { |
| int offset; |
| rtx mem = SUBREG_REG (x); |
| rtx addr = XEXP (mem, 0); |
| enum machine_mode mode = GET_MODE (x); |
| rtx result, seq; |
| |
| /* Paradoxical SUBREGs are usually invalid during RTL generation. */ |
| if (GET_MODE_SIZE (mode) > GET_MODE_SIZE (GET_MODE (mem)) && ! uncritical) |
| abort (); |
| |
| offset = SUBREG_BYTE (x); |
| if (BYTES_BIG_ENDIAN) |
| /* If the PROMOTED_MODE is wider than the mode of the MEM, adjust |
| the offset so that it points to the right location within the |
| MEM. */ |
| offset -= (GET_MODE_SIZE (promoted_mode) - GET_MODE_SIZE (GET_MODE (mem))); |
| |
| if (!flag_force_addr |
| && memory_address_p (mode, plus_constant (addr, offset))) |
| /* Shortcut if no insns need be emitted. */ |
| return adjust_address (mem, mode, offset); |
| |
| start_sequence (); |
| result = adjust_address (mem, mode, offset); |
| seq = get_insns (); |
| end_sequence (); |
| |
| emit_insn_before (seq, insn); |
| return result; |
| } |
| |
| /* Do fixup_memory_subreg on all (SUBREG (MEM ...) ...) contained in X. |
| Replace subexpressions of X in place. |
| If X itself is a (SUBREG (MEM ...) ...), return the replacement expression. |
| Otherwise return X, with its contents possibly altered. |
| |
| INSN, PROMOTED_MODE and UNCRITICAL are as for |
| fixup_memory_subreg. */ |
| |
| static rtx |
| walk_fixup_memory_subreg (rtx x, rtx insn, enum machine_mode promoted_mode, |
| int uncritical) |
| { |
| enum rtx_code code; |
| const char *fmt; |
| int i; |
| |
| if (x == 0) |
| return 0; |
| |
| code = GET_CODE (x); |
| |
| if (code == SUBREG && GET_CODE (SUBREG_REG (x)) == MEM) |
| return fixup_memory_subreg (x, insn, promoted_mode, uncritical); |
| |
| /* Nothing special about this RTX; fix its operands. */ |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| XEXP (x, i) = walk_fixup_memory_subreg (XEXP (x, i), insn, |
| promoted_mode, uncritical); |
| else if (fmt[i] == 'E') |
| { |
| int j; |
| for (j = 0; j < XVECLEN (x, i); j++) |
| XVECEXP (x, i, j) |
| = walk_fixup_memory_subreg (XVECEXP (x, i, j), insn, |
| promoted_mode, uncritical); |
| } |
| } |
| return x; |
| } |
| |
| /* For each memory ref within X, if it refers to a stack slot |
| with an out of range displacement, put the address in a temp register |
| (emitting new insns before INSN to load these registers) |
| and alter the memory ref to use that register. |
| Replace each such MEM rtx with a copy, to avoid clobberage. */ |
| |
| static rtx |
| fixup_stack_1 (rtx x, rtx insn) |
| { |
| int i; |
| RTX_CODE code = GET_CODE (x); |
| const char *fmt; |
| |
| if (code == MEM) |
| { |
| rtx ad = XEXP (x, 0); |
| /* If we have address of a stack slot but it's not valid |
| (displacement is too large), compute the sum in a register. */ |
| if (GET_CODE (ad) == PLUS |
| && GET_CODE (XEXP (ad, 0)) == REG |
| && ((REGNO (XEXP (ad, 0)) >= FIRST_VIRTUAL_REGISTER |
| && REGNO (XEXP (ad, 0)) <= LAST_VIRTUAL_REGISTER) |
| || REGNO (XEXP (ad, 0)) == FRAME_POINTER_REGNUM |
| #if HARD_FRAME_POINTER_REGNUM != FRAME_POINTER_REGNUM |
| || REGNO (XEXP (ad, 0)) == HARD_FRAME_POINTER_REGNUM |
| #endif |
| || REGNO (XEXP (ad, 0)) == STACK_POINTER_REGNUM |
| || REGNO (XEXP (ad, 0)) == ARG_POINTER_REGNUM |
| || XEXP (ad, 0) == current_function_internal_arg_pointer) |
| && GET_CODE (XEXP (ad, 1)) == CONST_INT) |
| { |
| rtx temp, seq; |
| if (memory_address_p (GET_MODE (x), ad)) |
| return x; |
| |
| start_sequence (); |
| temp = copy_to_reg (ad); |
| seq = get_insns (); |
| end_sequence (); |
| emit_insn_before (seq, insn); |
| return replace_equiv_address (x, temp); |
| } |
| return x; |
| } |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| XEXP (x, i) = fixup_stack_1 (XEXP (x, i), insn); |
| else if (fmt[i] == 'E') |
| { |
| int j; |
| for (j = 0; j < XVECLEN (x, i); j++) |
| XVECEXP (x, i, j) = fixup_stack_1 (XVECEXP (x, i, j), insn); |
| } |
| } |
| return x; |
| } |
| |
| /* Optimization: a bit-field instruction whose field |
| happens to be a byte or halfword in memory |
| can be changed to a move instruction. |
| |
| We call here when INSN is an insn to examine or store into a bit-field. |
| BODY is the SET-rtx to be altered. |
| |
| EQUIV_MEM is the table `reg_equiv_mem' if that is available; else 0. |
| (Currently this is called only from function.c, and EQUIV_MEM |
| is always 0.) */ |
| |
| static void |
| optimize_bit_field (rtx body, rtx insn, rtx *equiv_mem) |
| { |
| rtx bitfield; |
| int destflag; |
| rtx seq = 0; |
| enum machine_mode mode; |
| |
| if (GET_CODE (SET_DEST (body)) == SIGN_EXTRACT |
| || GET_CODE (SET_DEST (body)) == ZERO_EXTRACT) |
| bitfield = SET_DEST (body), destflag = 1; |
| else |
| bitfield = SET_SRC (body), destflag = 0; |
| |
| /* First check that the field being stored has constant size and position |
| and is in fact a byte or halfword suitably aligned. */ |
| |
| if (GET_CODE (XEXP (bitfield, 1)) == CONST_INT |
| && GET_CODE (XEXP (bitfield, 2)) == CONST_INT |
| && ((mode = mode_for_size (INTVAL (XEXP (bitfield, 1)), MODE_INT, 1)) |
| != BLKmode) |
| && INTVAL (XEXP (bitfield, 2)) % INTVAL (XEXP (bitfield, 1)) == 0) |
| { |
| rtx memref = 0; |
| |
| /* Now check that the containing word is memory, not a register, |
| and that it is safe to change the machine mode. */ |
| |
| if (GET_CODE (XEXP (bitfield, 0)) == MEM) |
| memref = XEXP (bitfield, 0); |
| else if (GET_CODE (XEXP (bitfield, 0)) == REG |
| && equiv_mem != 0) |
| memref = equiv_mem[REGNO (XEXP (bitfield, 0))]; |
| else if (GET_CODE (XEXP (bitfield, 0)) == SUBREG |
| && GET_CODE (SUBREG_REG (XEXP (bitfield, 0))) == MEM) |
| memref = SUBREG_REG (XEXP (bitfield, 0)); |
| else if (GET_CODE (XEXP (bitfield, 0)) == SUBREG |
| && equiv_mem != 0 |
| && GET_CODE (SUBREG_REG (XEXP (bitfield, 0))) == REG) |
| memref = equiv_mem[REGNO (SUBREG_REG (XEXP (bitfield, 0)))]; |
| |
| if (memref |
| && ! mode_dependent_address_p (XEXP (memref, 0)) |
| && ! MEM_VOLATILE_P (memref)) |
| { |
| /* Now adjust the address, first for any subreg'ing |
| that we are now getting rid of, |
| and then for which byte of the word is wanted. */ |
| |
| HOST_WIDE_INT offset = INTVAL (XEXP (bitfield, 2)); |
| rtx insns; |
| |
| /* Adjust OFFSET to count bits from low-address byte. */ |
| if (BITS_BIG_ENDIAN != BYTES_BIG_ENDIAN) |
| offset = (GET_MODE_BITSIZE (GET_MODE (XEXP (bitfield, 0))) |
| - offset - INTVAL (XEXP (bitfield, 1))); |
| |
| /* Adjust OFFSET to count bytes from low-address byte. */ |
| offset /= BITS_PER_UNIT; |
| if (GET_CODE (XEXP (bitfield, 0)) == SUBREG) |
| { |
| offset += (SUBREG_BYTE (XEXP (bitfield, 0)) |
| / UNITS_PER_WORD) * UNITS_PER_WORD; |
| if (BYTES_BIG_ENDIAN) |
| offset -= (MIN (UNITS_PER_WORD, |
| GET_MODE_SIZE (GET_MODE (XEXP (bitfield, 0)))) |
| - MIN (UNITS_PER_WORD, |
| GET_MODE_SIZE (GET_MODE (memref)))); |
| } |
| |
| start_sequence (); |
| memref = adjust_address (memref, mode, offset); |
| insns = get_insns (); |
| end_sequence (); |
| emit_insn_before (insns, insn); |
| |
| /* Store this memory reference where |
| we found the bit field reference. */ |
| |
| if (destflag) |
| { |
| validate_change (insn, &SET_DEST (body), memref, 1); |
| if (! CONSTANT_ADDRESS_P (SET_SRC (body))) |
| { |
| rtx src = SET_SRC (body); |
| while (GET_CODE (src) == SUBREG |
| && SUBREG_BYTE (src) == 0) |
| src = SUBREG_REG (src); |
| if (GET_MODE (src) != GET_MODE (memref)) |
| src = gen_lowpart (GET_MODE (memref), SET_SRC (body)); |
| validate_change (insn, &SET_SRC (body), src, 1); |
| } |
| else if (GET_MODE (SET_SRC (body)) != VOIDmode |
| && GET_MODE (SET_SRC (body)) != GET_MODE (memref)) |
| /* This shouldn't happen because anything that didn't have |
| one of these modes should have got converted explicitly |
| and then referenced through a subreg. |
| This is so because the original bit-field was |
| handled by agg_mode and so its tree structure had |
| the same mode that memref now has. */ |
| abort (); |
| } |
| else |
| { |
| rtx dest = SET_DEST (body); |
| |
| while (GET_CODE (dest) == SUBREG |
| && SUBREG_BYTE (dest) == 0 |
| && (GET_MODE_CLASS (GET_MODE (dest)) |
| == GET_MODE_CLASS (GET_MODE (SUBREG_REG (dest)))) |
| && (GET_MODE_SIZE (GET_MODE (SUBREG_REG (dest))) |
| <= UNITS_PER_WORD)) |
| dest = SUBREG_REG (dest); |
| |
| validate_change (insn, &SET_DEST (body), dest, 1); |
| |
| if (GET_MODE (dest) == GET_MODE (memref)) |
| validate_change (insn, &SET_SRC (body), memref, 1); |
| else |
| { |
| /* Convert the mem ref to the destination mode. */ |
| rtx newreg = gen_reg_rtx (GET_MODE (dest)); |
| |
| start_sequence (); |
| convert_move (newreg, memref, |
| GET_CODE (SET_SRC (body)) == ZERO_EXTRACT); |
| seq = get_insns (); |
| end_sequence (); |
| |
| validate_change (insn, &SET_SRC (body), newreg, 1); |
| } |
| } |
| |
| /* See if we can convert this extraction or insertion into |
| a simple move insn. We might not be able to do so if this |
| was, for example, part of a PARALLEL. |
| |
| If we succeed, write out any needed conversions. If we fail, |
| it is hard to guess why we failed, so don't do anything |
| special; just let the optimization be suppressed. */ |
| |
| if (apply_change_group () && seq) |
| emit_insn_before (seq, insn); |
| } |
| } |
| } |
| |
| /* These routines are responsible for converting virtual register references |
| to the actual hard register references once RTL generation is complete. |
| |
| The following four variables are used for communication between the |
| routines. They contain the offsets of the virtual registers from their |
| respective hard registers. */ |
| |
| static int in_arg_offset; |
| static int var_offset; |
| static int dynamic_offset; |
| static int out_arg_offset; |
| static int cfa_offset; |
| |
| /* In most machines, the stack pointer register is equivalent to the bottom |
| of the stack. */ |
| |
| #ifndef STACK_POINTER_OFFSET |
| #define STACK_POINTER_OFFSET 0 |
| #endif |
| |
| /* If not defined, pick an appropriate default for the offset of dynamically |
| allocated memory depending on the value of ACCUMULATE_OUTGOING_ARGS, |
| REG_PARM_STACK_SPACE, and OUTGOING_REG_PARM_STACK_SPACE. */ |
| |
| #ifndef STACK_DYNAMIC_OFFSET |
| |
| /* The bottom of the stack points to the actual arguments. If |
| REG_PARM_STACK_SPACE is defined, this includes the space for the register |
| parameters. However, if OUTGOING_REG_PARM_STACK space is not defined, |
| stack space for register parameters is not pushed by the caller, but |
| rather part of the fixed stack areas and hence not included in |
| `current_function_outgoing_args_size'. Nevertheless, we must allow |
| for it when allocating stack dynamic objects. */ |
| |
| #if defined(REG_PARM_STACK_SPACE) && ! defined(OUTGOING_REG_PARM_STACK_SPACE) |
| #define STACK_DYNAMIC_OFFSET(FNDECL) \ |
| ((ACCUMULATE_OUTGOING_ARGS \ |
| ? (current_function_outgoing_args_size + REG_PARM_STACK_SPACE (FNDECL)) : 0)\ |
| + (STACK_POINTER_OFFSET)) \ |
| |
| #else |
| #define STACK_DYNAMIC_OFFSET(FNDECL) \ |
| ((ACCUMULATE_OUTGOING_ARGS ? current_function_outgoing_args_size : 0) \ |
| + (STACK_POINTER_OFFSET)) |
| #endif |
| #endif |
| |
| /* On most machines, the CFA coincides with the first incoming parm. */ |
| |
| #ifndef ARG_POINTER_CFA_OFFSET |
| #define ARG_POINTER_CFA_OFFSET(FNDECL) FIRST_PARM_OFFSET (FNDECL) |
| #endif |
| |
| /* Build up a (MEM (ADDRESSOF (REG))) rtx for a register REG that just |
| had its address taken. DECL is the decl or SAVE_EXPR for the |
| object stored in the register, for later use if we do need to force |
| REG into the stack. REG is overwritten by the MEM like in |
| put_reg_into_stack. RESCAN is true if previously emitted |
| instructions must be rescanned and modified now that the REG has |
| been transformed. */ |
| |
| rtx |
| gen_mem_addressof (rtx reg, tree decl, int rescan) |
| { |
| rtx r = gen_rtx_ADDRESSOF (Pmode, gen_reg_rtx (GET_MODE (reg)), |
| REGNO (reg), decl); |
| |
| /* Calculate this before we start messing with decl's RTL. */ |
| HOST_WIDE_INT set = decl ? get_alias_set (decl) : 0; |
| |
| /* If the original REG was a user-variable, then so is the REG whose |
| address is being taken. Likewise for unchanging. */ |
| REG_USERVAR_P (XEXP (r, 0)) = REG_USERVAR_P (reg); |
| RTX_UNCHANGING_P (XEXP (r, 0)) = RTX_UNCHANGING_P (reg); |
| |
| PUT_CODE (reg, MEM); |
| MEM_ATTRS (reg) = 0; |
| XEXP (reg, 0) = r; |
| |
| if (decl) |
| { |
| tree type = TREE_TYPE (decl); |
| enum machine_mode decl_mode |
| = (DECL_P (decl) ? DECL_MODE (decl) : TYPE_MODE (TREE_TYPE (decl))); |
| rtx decl_rtl = (TREE_CODE (decl) == SAVE_EXPR ? SAVE_EXPR_RTL (decl) |
| : DECL_RTL_IF_SET (decl)); |
| |
| PUT_MODE (reg, decl_mode); |
| |
| /* Clear DECL_RTL momentarily so functions below will work |
| properly, then set it again. */ |
| if (DECL_P (decl) && decl_rtl == reg) |
| SET_DECL_RTL (decl, 0); |
| |
| set_mem_attributes (reg, decl, 1); |
| set_mem_alias_set (reg, set); |
| |
| if (DECL_P (decl) && decl_rtl == reg) |
| SET_DECL_RTL (decl, reg); |
| |
| if (rescan |
| && (TREE_USED (decl) || (DECL_P (decl) && DECL_INITIAL (decl) != 0))) |
| fixup_var_refs (reg, GET_MODE (reg), TREE_UNSIGNED (type), reg, 0); |
| } |
| else if (rescan) |
| { |
| /* This can only happen during reload. Clear the same flag bits as |
| reload. */ |
| MEM_VOLATILE_P (reg) = 0; |
| RTX_UNCHANGING_P (reg) = 0; |
| MEM_IN_STRUCT_P (reg) = 0; |
| MEM_SCALAR_P (reg) = 0; |
| MEM_ATTRS (reg) = 0; |
| |
| fixup_var_refs (reg, GET_MODE (reg), 0, reg, 0); |
| } |
| |
| return reg; |
| } |
| |
| /* If DECL has an RTL that is an ADDRESSOF rtx, put it into the stack. */ |
| |
| void |
| flush_addressof (tree decl) |
| { |
| if ((TREE_CODE (decl) == PARM_DECL || TREE_CODE (decl) == VAR_DECL) |
| && DECL_RTL (decl) != 0 |
| && GET_CODE (DECL_RTL (decl)) == MEM |
| && GET_CODE (XEXP (DECL_RTL (decl), 0)) == ADDRESSOF |
| && GET_CODE (XEXP (XEXP (DECL_RTL (decl), 0), 0)) == REG) |
| put_addressof_into_stack (XEXP (DECL_RTL (decl), 0), 0); |
| } |
| |
| /* Force the register pointed to by R, an ADDRESSOF rtx, into the stack. */ |
| |
| static void |
| put_addressof_into_stack (rtx r, htab_t ht) |
| { |
| tree decl, type; |
| int volatile_p, used_p; |
| |
| rtx reg = XEXP (r, 0); |
| |
| if (GET_CODE (reg) != REG) |
| abort (); |
| |
| decl = ADDRESSOF_DECL (r); |
| if (decl) |
| { |
| type = TREE_TYPE (decl); |
| volatile_p = (TREE_CODE (decl) != SAVE_EXPR |
| && TREE_THIS_VOLATILE (decl)); |
| used_p = (TREE_USED (decl) |
| || (DECL_P (decl) && DECL_INITIAL (decl) != 0)); |
| } |
| else |
| { |
| type = NULL_TREE; |
| volatile_p = 0; |
| used_p = 1; |
| } |
| |
| put_reg_into_stack (0, reg, type, GET_MODE (reg), GET_MODE (reg), |
| volatile_p, ADDRESSOF_REGNO (r), used_p, ht); |
| } |
| |
| /* List of replacements made below in purge_addressof_1 when creating |
| bitfield insertions. */ |
| static rtx purge_bitfield_addressof_replacements; |
| |
| /* List of replacements made below in purge_addressof_1 for patterns |
| (MEM (ADDRESSOF (REG ...))). The key of the list entry is the |
| corresponding (ADDRESSOF (REG ...)) and value is a substitution for |
| the all pattern. List PURGE_BITFIELD_ADDRESSOF_REPLACEMENTS is not |
| enough in complex cases, e.g. when some field values can be |
| extracted by usage MEM with narrower mode. */ |
| static rtx purge_addressof_replacements; |
| |
| /* Helper function for purge_addressof. See if the rtx expression at *LOC |
| in INSN needs to be changed. If FORCE, always put any ADDRESSOFs into |
| the stack. If the function returns FALSE then the replacement could not |
| be made. If MAY_POSTPONE is true and we would not put the addressof |
| to stack, postpone processing of the insn. */ |
| |
| static bool |
| purge_addressof_1 (rtx *loc, rtx insn, int force, int store, int may_postpone, |
| htab_t ht) |
| { |
| rtx x; |
| RTX_CODE code; |
| int i, j; |
| const char *fmt; |
| bool result = true; |
| bool libcall = false; |
| |
| /* Re-start here to avoid recursion in common cases. */ |
| restart: |
| |
| x = *loc; |
| if (x == 0) |
| return true; |
| |
| /* Is this a libcall? */ |
| if (!insn) |
| libcall = REG_NOTE_KIND (*loc) == REG_RETVAL; |
| |
| code = GET_CODE (x); |
| |
| /* If we don't return in any of the cases below, we will recurse inside |
| the RTX, which will normally result in any ADDRESSOF being forced into |
| memory. */ |
| if (code == SET) |
| { |
| result = purge_addressof_1 (&SET_DEST (x), insn, force, 1, |
| may_postpone, ht); |
| result &= purge_addressof_1 (&SET_SRC (x), insn, force, 0, |
| may_postpone, ht); |
| return result; |
| } |
| else if (code == ADDRESSOF) |
| { |
| rtx sub, insns; |
| |
| if (GET_CODE (XEXP (x, 0)) != MEM) |
| put_addressof_into_stack (x, ht); |
| |
| /* We must create a copy of the rtx because it was created by |
| overwriting a REG rtx which is always shared. */ |
| sub = copy_rtx (XEXP (XEXP (x, 0), 0)); |
| if (validate_change (insn, loc, sub, 0) |
| || validate_replace_rtx (x, sub, insn)) |
| return true; |
| |
| start_sequence (); |
| |
| /* If SUB is a hard or virtual register, try it as a pseudo-register. |
| Otherwise, perhaps SUB is an expression, so generate code to compute |
| it. */ |
| if (GET_CODE (sub) == REG && REGNO (sub) <= LAST_VIRTUAL_REGISTER) |
| sub = copy_to_reg (sub); |
| else |
| sub = force_operand (sub, NULL_RTX); |
| |
| if (! validate_change (insn, loc, sub, 0) |
| && ! validate_replace_rtx (x, sub, insn)) |
| abort (); |
| |
| insns = get_insns (); |
| end_sequence (); |
| emit_insn_before (insns, insn); |
| return true; |
| } |
| |
| else if (code == MEM && GET_CODE (XEXP (x, 0)) == ADDRESSOF && ! force) |
| { |
| rtx sub = XEXP (XEXP (x, 0), 0); |
| |
| if (GET_CODE (sub) == MEM) |
| sub = adjust_address_nv (sub, GET_MODE (x), 0); |
| else if (GET_CODE (sub) == REG |
| && (MEM_VOLATILE_P (x) || GET_MODE (x) == BLKmode)) |
| ; |
| else if (GET_CODE (sub) == REG && GET_MODE (x) != GET_MODE (sub)) |
| { |
| int size_x, size_sub; |
| |
| if (may_postpone) |
| { |
| /* Postpone for now, so that we do not emit bitfield arithmetics |
| unless there is some benefit from it. */ |
| if (!postponed_insns || XEXP (postponed_insns, 0) != insn) |
| postponed_insns = alloc_INSN_LIST (insn, postponed_insns); |
| return true; |
| } |
| |
| if (!insn) |
| { |
| /* When processing REG_NOTES look at the list of |
| replacements done on the insn to find the register that X |
| was replaced by. */ |
| rtx tem; |
| |
| for (tem = purge_bitfield_addressof_replacements; |
| tem != NULL_RTX; |
| tem = XEXP (XEXP (tem, 1), 1)) |
| if (rtx_equal_p (x, XEXP (tem, 0))) |
| { |
| *loc = XEXP (XEXP (tem, 1), 0); |
| return true; |
| } |
| |
| /* See comment for purge_addressof_replacements. */ |
| for (tem = purge_addressof_replacements; |
| tem != NULL_RTX; |
| tem = XEXP (XEXP (tem, 1), 1)) |
| if (rtx_equal_p (XEXP (x, 0), XEXP (tem, 0))) |
| { |
| rtx z = XEXP (XEXP (tem, 1), 0); |
| |
| if (GET_MODE (x) == GET_MODE (z) |
| || (GET_CODE (XEXP (XEXP (tem, 1), 0)) != REG |
| && GET_CODE (XEXP (XEXP (tem, 1), 0)) != SUBREG)) |
| abort (); |
| |
| /* It can happen that the note may speak of things |
| in a wider (or just different) mode than the |
| code did. This is especially true of |
| REG_RETVAL. */ |
| |
| if (GET_CODE (z) == SUBREG && SUBREG_BYTE (z) == 0) |
| z = SUBREG_REG (z); |
| |
| if (GET_MODE_SIZE (GET_MODE (x)) > UNITS_PER_WORD |
| && (GET_MODE_SIZE (GET_MODE (x)) |
| > GET_MODE_SIZE (GET_MODE (z)))) |
| { |
| /* This can occur as a result in invalid |
| pointer casts, e.g. float f; ... |
| *(long long int *)&f. |
| ??? We could emit a warning here, but |
| without a line number that wouldn't be |
| very helpful. */ |
| z = gen_rtx_SUBREG (GET_MODE (x), z, 0); |
| } |
| else |
| z = gen_lowpart (GET_MODE (x), z); |
| |
| *loc = z; |
| return true; |
| } |
| |
| /* When we are processing the REG_NOTES of the last instruction |
| of a libcall, there will be typically no replacements |
| for that insn; the replacements happened before, piecemeal |
| fashion. OTOH we are not interested in the details of |
| this for the REG_EQUAL note, we want to know the big picture, |
| which can be succinctly described with a simple SUBREG. |
| Note that removing the REG_EQUAL note is not an option |
| on the last insn of a libcall, so we must do a replacement. */ |
| |
| /* In compile/990107-1.c:7 compiled at -O1 -m1 for sh-elf, |
| we got |
| (mem:DI (addressof:SI (reg/v:DF 160) 159 0x401c8510) |
| [0 S8 A32]), which can be expressed with a simple |
| same-size subreg */ |
| if ((GET_MODE_SIZE (GET_MODE (x)) |
| <= GET_MODE_SIZE (GET_MODE |