| /* Variable tracking routines for the GNU compiler. |
| Copyright (C) 2002, 2003, 2004, 2005, 2007, 2008 |
| Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| GCC is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This file contains the variable tracking pass. It computes where |
| variables are located (which registers or where in memory) at each position |
| in instruction stream and emits notes describing the locations. |
| Debug information (DWARF2 location lists) is finally generated from |
| these notes. |
| With this debug information, it is possible to show variables |
| even when debugging optimized code. |
| |
| How does the variable tracking pass work? |
| |
| First, it scans RTL code for uses, stores and clobbers (register/memory |
| references in instructions), for call insns and for stack adjustments |
| separately for each basic block and saves them to an array of micro |
| operations. |
| The micro operations of one instruction are ordered so that |
| pre-modifying stack adjustment < use < use with no var < call insn < |
| < set < clobber < post-modifying stack adjustment |
| |
| Then, a forward dataflow analysis is performed to find out how locations |
| of variables change through code and to propagate the variable locations |
| along control flow graph. |
| The IN set for basic block BB is computed as a union of OUT sets of BB's |
| predecessors, the OUT set for BB is copied from the IN set for BB and |
| is changed according to micro operations in BB. |
| |
| The IN and OUT sets for basic blocks consist of a current stack adjustment |
| (used for adjusting offset of variables addressed using stack pointer), |
| the table of structures describing the locations of parts of a variable |
| and for each physical register a linked list for each physical register. |
| The linked list is a list of variable parts stored in the register, |
| i.e. it is a list of triplets (reg, decl, offset) where decl is |
| REG_EXPR (reg) and offset is REG_OFFSET (reg). The linked list is used for |
| effective deleting appropriate variable parts when we set or clobber the |
| register. |
| |
| There may be more than one variable part in a register. The linked lists |
| should be pretty short so it is a good data structure here. |
| For example in the following code, register allocator may assign same |
| register to variables A and B, and both of them are stored in the same |
| register in CODE: |
| |
| if (cond) |
| set A; |
| else |
| set B; |
| CODE; |
| if (cond) |
| use A; |
| else |
| use B; |
| |
| Finally, the NOTE_INSN_VAR_LOCATION notes describing the variable locations |
| are emitted to appropriate positions in RTL code. Each such a note describes |
| the location of one variable at the point in instruction stream where the |
| note is. There is no need to emit a note for each variable before each |
| instruction, we only emit these notes where the location of variable changes |
| (this means that we also emit notes for changes between the OUT set of the |
| previous block and the IN set of the current block). |
| |
| The notes consist of two parts: |
| 1. the declaration (from REG_EXPR or MEM_EXPR) |
| 2. the location of a variable - it is either a simple register/memory |
| reference (for simple variables, for example int), |
| or a parallel of register/memory references (for a large variables |
| which consist of several parts, for example long long). |
| |
| */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "hard-reg-set.h" |
| #include "basic-block.h" |
| #include "flags.h" |
| #include "output.h" |
| #include "insn-config.h" |
| #include "reload.h" |
| #include "sbitmap.h" |
| #include "alloc-pool.h" |
| #include "fibheap.h" |
| #include "hashtab.h" |
| #include "regs.h" |
| #include "expr.h" |
| #include "timevar.h" |
| #include "tree-pass.h" |
| |
| /* Type of micro operation. */ |
| enum micro_operation_type |
| { |
| MO_USE, /* Use location (REG or MEM). */ |
| MO_USE_NO_VAR,/* Use location which is not associated with a variable |
| or the variable is not trackable. */ |
| MO_SET, /* Set location. */ |
| MO_COPY, /* Copy the same portion of a variable from one |
| location to another. */ |
| MO_CLOBBER, /* Clobber location. */ |
| MO_CALL, /* Call insn. */ |
| MO_ADJUST /* Adjust stack pointer. */ |
| }; |
| |
| /* Where shall the note be emitted? BEFORE or AFTER the instruction. */ |
| enum emit_note_where |
| { |
| EMIT_NOTE_BEFORE_INSN, |
| EMIT_NOTE_AFTER_INSN |
| }; |
| |
| /* Structure holding information about micro operation. */ |
| typedef struct micro_operation_def |
| { |
| /* Type of micro operation. */ |
| enum micro_operation_type type; |
| |
| union { |
| /* Location. For MO_SET and MO_COPY, this is the SET that performs |
| the assignment, if known, otherwise it is the target of the |
| assignment. */ |
| rtx loc; |
| |
| /* Stack adjustment. */ |
| HOST_WIDE_INT adjust; |
| } u; |
| |
| /* The instruction which the micro operation is in, for MO_USE, |
| MO_USE_NO_VAR, MO_CALL and MO_ADJUST, or the subsequent |
| instruction or note in the original flow (before any var-tracking |
| notes are inserted, to simplify emission of notes), for MO_SET |
| and MO_CLOBBER. */ |
| rtx insn; |
| } micro_operation; |
| |
| /* Structure for passing some other parameters to function |
| emit_note_insn_var_location. */ |
| typedef struct emit_note_data_def |
| { |
| /* The instruction which the note will be emitted before/after. */ |
| rtx insn; |
| |
| /* Where the note will be emitted (before/after insn)? */ |
| enum emit_note_where where; |
| } emit_note_data; |
| |
| /* Description of location of a part of a variable. The content of a physical |
| register is described by a chain of these structures. |
| The chains are pretty short (usually 1 or 2 elements) and thus |
| chain is the best data structure. */ |
| typedef struct attrs_def |
| { |
| /* Pointer to next member of the list. */ |
| struct attrs_def *next; |
| |
| /* The rtx of register. */ |
| rtx loc; |
| |
| /* The declaration corresponding to LOC. */ |
| tree decl; |
| |
| /* Offset from start of DECL. */ |
| HOST_WIDE_INT offset; |
| } *attrs; |
| |
| /* Structure holding the IN or OUT set for a basic block. */ |
| typedef struct dataflow_set_def |
| { |
| /* Adjustment of stack offset. */ |
| HOST_WIDE_INT stack_adjust; |
| |
| /* Attributes for registers (lists of attrs). */ |
| attrs regs[FIRST_PSEUDO_REGISTER]; |
| |
| /* Variable locations. */ |
| htab_t vars; |
| } dataflow_set; |
| |
| /* The structure (one for each basic block) containing the information |
| needed for variable tracking. */ |
| typedef struct variable_tracking_info_def |
| { |
| /* Number of micro operations stored in the MOS array. */ |
| int n_mos; |
| |
| /* The array of micro operations. */ |
| micro_operation *mos; |
| |
| /* The IN and OUT set for dataflow analysis. */ |
| dataflow_set in; |
| dataflow_set out; |
| |
| /* Has the block been visited in DFS? */ |
| bool visited; |
| } *variable_tracking_info; |
| |
| /* Structure for chaining the locations. */ |
| typedef struct location_chain_def |
| { |
| /* Next element in the chain. */ |
| struct location_chain_def *next; |
| |
| /* The location (REG or MEM). */ |
| rtx loc; |
| |
| /* The "value" stored in this location. */ |
| rtx set_src; |
| |
| /* Initialized? */ |
| enum var_init_status init; |
| } *location_chain; |
| |
| /* Structure describing one part of variable. */ |
| typedef struct variable_part_def |
| { |
| /* Chain of locations of the part. */ |
| location_chain loc_chain; |
| |
| /* Location which was last emitted to location list. */ |
| rtx cur_loc; |
| |
| /* The offset in the variable. */ |
| HOST_WIDE_INT offset; |
| } variable_part; |
| |
| /* Maximum number of location parts. */ |
| #define MAX_VAR_PARTS 16 |
| |
| /* Structure describing where the variable is located. */ |
| typedef struct variable_def |
| { |
| /* The declaration of the variable. */ |
| tree decl; |
| |
| /* Reference count. */ |
| int refcount; |
| |
| /* Number of variable parts. */ |
| int n_var_parts; |
| |
| /* The variable parts. */ |
| variable_part var_part[MAX_VAR_PARTS]; |
| } *variable; |
| typedef const struct variable_def *const_variable; |
| |
| /* Hash function for DECL for VARIABLE_HTAB. */ |
| #define VARIABLE_HASH_VAL(decl) (DECL_UID (decl)) |
| |
| /* Pointer to the BB's information specific to variable tracking pass. */ |
| #define VTI(BB) ((variable_tracking_info) (BB)->aux) |
| |
| /* Macro to access MEM_OFFSET as an HOST_WIDE_INT. Evaluates MEM twice. */ |
| #define INT_MEM_OFFSET(mem) (MEM_OFFSET (mem) ? INTVAL (MEM_OFFSET (mem)) : 0) |
| |
| /* Alloc pool for struct attrs_def. */ |
| static alloc_pool attrs_pool; |
| |
| /* Alloc pool for struct variable_def. */ |
| static alloc_pool var_pool; |
| |
| /* Alloc pool for struct location_chain_def. */ |
| static alloc_pool loc_chain_pool; |
| |
| /* Changed variables, notes will be emitted for them. */ |
| static htab_t changed_variables; |
| |
| /* Shall notes be emitted? */ |
| static bool emit_notes; |
| |
| /* Local function prototypes. */ |
| static void stack_adjust_offset_pre_post (rtx, HOST_WIDE_INT *, |
| HOST_WIDE_INT *); |
| static void insn_stack_adjust_offset_pre_post (rtx, HOST_WIDE_INT *, |
| HOST_WIDE_INT *); |
| static void bb_stack_adjust_offset (basic_block); |
| static bool vt_stack_adjustments (void); |
| static rtx adjust_stack_reference (rtx, HOST_WIDE_INT); |
| static hashval_t variable_htab_hash (const void *); |
| static int variable_htab_eq (const void *, const void *); |
| static void variable_htab_free (void *); |
| |
| static void init_attrs_list_set (attrs *); |
| static void attrs_list_clear (attrs *); |
| static attrs attrs_list_member (attrs, tree, HOST_WIDE_INT); |
| static void attrs_list_insert (attrs *, tree, HOST_WIDE_INT, rtx); |
| static void attrs_list_copy (attrs *, attrs); |
| static void attrs_list_union (attrs *, attrs); |
| |
| static void vars_clear (htab_t); |
| static variable unshare_variable (dataflow_set *set, variable var, |
| enum var_init_status); |
| static int vars_copy_1 (void **, void *); |
| static void vars_copy (htab_t, htab_t); |
| static tree var_debug_decl (tree); |
| static void var_reg_set (dataflow_set *, rtx, enum var_init_status, rtx); |
| static void var_reg_delete_and_set (dataflow_set *, rtx, bool, |
| enum var_init_status, rtx); |
| static void var_reg_delete (dataflow_set *, rtx, bool); |
| static void var_regno_delete (dataflow_set *, int); |
| static void var_mem_set (dataflow_set *, rtx, enum var_init_status, rtx); |
| static void var_mem_delete_and_set (dataflow_set *, rtx, bool, |
| enum var_init_status, rtx); |
| static void var_mem_delete (dataflow_set *, rtx, bool); |
| |
| static void dataflow_set_init (dataflow_set *, int); |
| static void dataflow_set_clear (dataflow_set *); |
| static void dataflow_set_copy (dataflow_set *, dataflow_set *); |
| static int variable_union_info_cmp_pos (const void *, const void *); |
| static int variable_union (void **, void *); |
| static void dataflow_set_union (dataflow_set *, dataflow_set *); |
| static bool variable_part_different_p (variable_part *, variable_part *); |
| static bool variable_different_p (variable, variable, bool); |
| static int dataflow_set_different_1 (void **, void *); |
| static int dataflow_set_different_2 (void **, void *); |
| static bool dataflow_set_different (dataflow_set *, dataflow_set *); |
| static void dataflow_set_destroy (dataflow_set *); |
| |
| static bool contains_symbol_ref (rtx); |
| static bool track_expr_p (tree); |
| static bool same_variable_part_p (rtx, tree, HOST_WIDE_INT); |
| static int count_uses (rtx *, void *); |
| static void count_uses_1 (rtx *, void *); |
| static void count_stores (rtx, const_rtx, void *); |
| static int add_uses (rtx *, void *); |
| static void add_uses_1 (rtx *, void *); |
| static void add_stores (rtx, const_rtx, void *); |
| static bool compute_bb_dataflow (basic_block); |
| static void vt_find_locations (void); |
| |
| static void dump_attrs_list (attrs); |
| static int dump_variable (void **, void *); |
| static void dump_vars (htab_t); |
| static void dump_dataflow_set (dataflow_set *); |
| static void dump_dataflow_sets (void); |
| |
| static void variable_was_changed (variable, htab_t); |
| static void set_variable_part (dataflow_set *, rtx, tree, HOST_WIDE_INT, |
| enum var_init_status, rtx); |
| static void clobber_variable_part (dataflow_set *, rtx, tree, HOST_WIDE_INT, |
| rtx); |
| static void delete_variable_part (dataflow_set *, rtx, tree, HOST_WIDE_INT); |
| static int emit_note_insn_var_location (void **, void *); |
| static void emit_notes_for_changes (rtx, enum emit_note_where); |
| static int emit_notes_for_differences_1 (void **, void *); |
| static int emit_notes_for_differences_2 (void **, void *); |
| static void emit_notes_for_differences (rtx, dataflow_set *, dataflow_set *); |
| static void emit_notes_in_bb (basic_block); |
| static void vt_emit_notes (void); |
| |
| static bool vt_get_decl_and_offset (rtx, tree *, HOST_WIDE_INT *); |
| static void vt_add_function_parameters (void); |
| static void vt_initialize (void); |
| static void vt_finalize (void); |
| |
| /* Given a SET, calculate the amount of stack adjustment it contains |
| PRE- and POST-modifying stack pointer. |
| This function is similar to stack_adjust_offset. */ |
| |
| static void |
| stack_adjust_offset_pre_post (rtx pattern, HOST_WIDE_INT *pre, |
| HOST_WIDE_INT *post) |
| { |
| rtx src = SET_SRC (pattern); |
| rtx dest = SET_DEST (pattern); |
| enum rtx_code code; |
| |
| if (dest == stack_pointer_rtx) |
| { |
| /* (set (reg sp) (plus (reg sp) (const_int))) */ |
| code = GET_CODE (src); |
| if (! (code == PLUS || code == MINUS) |
| || XEXP (src, 0) != stack_pointer_rtx |
| || GET_CODE (XEXP (src, 1)) != CONST_INT) |
| return; |
| |
| if (code == MINUS) |
| *post += INTVAL (XEXP (src, 1)); |
| else |
| *post -= INTVAL (XEXP (src, 1)); |
| } |
| else if (MEM_P (dest)) |
| { |
| /* (set (mem (pre_dec (reg sp))) (foo)) */ |
| src = XEXP (dest, 0); |
| code = GET_CODE (src); |
| |
| switch (code) |
| { |
| case PRE_MODIFY: |
| case POST_MODIFY: |
| if (XEXP (src, 0) == stack_pointer_rtx) |
| { |
| rtx val = XEXP (XEXP (src, 1), 1); |
| /* We handle only adjustments by constant amount. */ |
| gcc_assert (GET_CODE (XEXP (src, 1)) == PLUS && |
| GET_CODE (val) == CONST_INT); |
| |
| if (code == PRE_MODIFY) |
| *pre -= INTVAL (val); |
| else |
| *post -= INTVAL (val); |
| break; |
| } |
| return; |
| |
| case PRE_DEC: |
| if (XEXP (src, 0) == stack_pointer_rtx) |
| { |
| *pre += GET_MODE_SIZE (GET_MODE (dest)); |
| break; |
| } |
| return; |
| |
| case POST_DEC: |
| if (XEXP (src, 0) == stack_pointer_rtx) |
| { |
| *post += GET_MODE_SIZE (GET_MODE (dest)); |
| break; |
| } |
| return; |
| |
| case PRE_INC: |
| if (XEXP (src, 0) == stack_pointer_rtx) |
| { |
| *pre -= GET_MODE_SIZE (GET_MODE (dest)); |
| break; |
| } |
| return; |
| |
| case POST_INC: |
| if (XEXP (src, 0) == stack_pointer_rtx) |
| { |
| *post -= GET_MODE_SIZE (GET_MODE (dest)); |
| break; |
| } |
| return; |
| |
| default: |
| return; |
| } |
| } |
| } |
| |
| /* Given an INSN, calculate the amount of stack adjustment it contains |
| PRE- and POST-modifying stack pointer. */ |
| |
| static void |
| insn_stack_adjust_offset_pre_post (rtx insn, HOST_WIDE_INT *pre, |
| HOST_WIDE_INT *post) |
| { |
| rtx pattern; |
| |
| *pre = 0; |
| *post = 0; |
| |
| pattern = PATTERN (insn); |
| if (RTX_FRAME_RELATED_P (insn)) |
| { |
| rtx expr = find_reg_note (insn, REG_FRAME_RELATED_EXPR, NULL_RTX); |
| if (expr) |
| pattern = XEXP (expr, 0); |
| } |
| |
| if (GET_CODE (pattern) == SET) |
| stack_adjust_offset_pre_post (pattern, pre, post); |
| else if (GET_CODE (pattern) == PARALLEL |
| || GET_CODE (pattern) == SEQUENCE) |
| { |
| int i; |
| |
| /* There may be stack adjustments inside compound insns. Search |
| for them. */ |
| for ( i = XVECLEN (pattern, 0) - 1; i >= 0; i--) |
| if (GET_CODE (XVECEXP (pattern, 0, i)) == SET) |
| stack_adjust_offset_pre_post (XVECEXP (pattern, 0, i), pre, post); |
| } |
| } |
| |
| /* Compute stack adjustment in basic block BB. */ |
| |
| static void |
| bb_stack_adjust_offset (basic_block bb) |
| { |
| HOST_WIDE_INT offset; |
| int i; |
| |
| offset = VTI (bb)->in.stack_adjust; |
| for (i = 0; i < VTI (bb)->n_mos; i++) |
| { |
| if (VTI (bb)->mos[i].type == MO_ADJUST) |
| offset += VTI (bb)->mos[i].u.adjust; |
| else if (VTI (bb)->mos[i].type != MO_CALL) |
| { |
| if (MEM_P (VTI (bb)->mos[i].u.loc)) |
| { |
| VTI (bb)->mos[i].u.loc |
| = adjust_stack_reference (VTI (bb)->mos[i].u.loc, -offset); |
| } |
| } |
| } |
| VTI (bb)->out.stack_adjust = offset; |
| } |
| |
| /* Compute stack adjustments for all blocks by traversing DFS tree. |
| Return true when the adjustments on all incoming edges are consistent. |
| Heavily borrowed from pre_and_rev_post_order_compute. */ |
| |
| static bool |
| vt_stack_adjustments (void) |
| { |
| edge_iterator *stack; |
| int sp; |
| |
| /* Initialize entry block. */ |
| VTI (ENTRY_BLOCK_PTR)->visited = true; |
| VTI (ENTRY_BLOCK_PTR)->out.stack_adjust = INCOMING_FRAME_SP_OFFSET; |
| |
| /* Allocate stack for back-tracking up CFG. */ |
| stack = XNEWVEC (edge_iterator, n_basic_blocks + 1); |
| sp = 0; |
| |
| /* Push the first edge on to the stack. */ |
| stack[sp++] = ei_start (ENTRY_BLOCK_PTR->succs); |
| |
| while (sp) |
| { |
| edge_iterator ei; |
| basic_block src; |
| basic_block dest; |
| |
| /* Look at the edge on the top of the stack. */ |
| ei = stack[sp - 1]; |
| src = ei_edge (ei)->src; |
| dest = ei_edge (ei)->dest; |
| |
| /* Check if the edge destination has been visited yet. */ |
| if (!VTI (dest)->visited) |
| { |
| VTI (dest)->visited = true; |
| VTI (dest)->in.stack_adjust = VTI (src)->out.stack_adjust; |
| bb_stack_adjust_offset (dest); |
| |
| if (EDGE_COUNT (dest->succs) > 0) |
| /* Since the DEST node has been visited for the first |
| time, check its successors. */ |
| stack[sp++] = ei_start (dest->succs); |
| } |
| else |
| { |
| /* Check whether the adjustments on the edges are the same. */ |
| if (VTI (dest)->in.stack_adjust != VTI (src)->out.stack_adjust) |
| { |
| free (stack); |
| return false; |
| } |
| |
| if (! ei_one_before_end_p (ei)) |
| /* Go to the next edge. */ |
| ei_next (&stack[sp - 1]); |
| else |
| /* Return to previous level if there are no more edges. */ |
| sp--; |
| } |
| } |
| |
| free (stack); |
| return true; |
| } |
| |
| /* Adjust stack reference MEM by ADJUSTMENT bytes and make it relative |
| to the argument pointer. Return the new rtx. */ |
| |
| static rtx |
| adjust_stack_reference (rtx mem, HOST_WIDE_INT adjustment) |
| { |
| rtx addr, cfa, tmp; |
| |
| #ifdef FRAME_POINTER_CFA_OFFSET |
| adjustment -= FRAME_POINTER_CFA_OFFSET (current_function_decl); |
| cfa = plus_constant (frame_pointer_rtx, adjustment); |
| #else |
| adjustment -= ARG_POINTER_CFA_OFFSET (current_function_decl); |
| cfa = plus_constant (arg_pointer_rtx, adjustment); |
| #endif |
| |
| addr = replace_rtx (copy_rtx (XEXP (mem, 0)), stack_pointer_rtx, cfa); |
| tmp = simplify_rtx (addr); |
| if (tmp) |
| addr = tmp; |
| |
| return replace_equiv_address_nv (mem, addr); |
| } |
| |
| /* The hash function for variable_htab, computes the hash value |
| from the declaration of variable X. */ |
| |
| static hashval_t |
| variable_htab_hash (const void *x) |
| { |
| const_variable const v = (const_variable) x; |
| |
| return (VARIABLE_HASH_VAL (v->decl)); |
| } |
| |
| /* Compare the declaration of variable X with declaration Y. */ |
| |
| static int |
| variable_htab_eq (const void *x, const void *y) |
| { |
| const_variable const v = (const_variable) x; |
| const_tree const decl = (const_tree) y; |
| |
| return (VARIABLE_HASH_VAL (v->decl) == VARIABLE_HASH_VAL (decl)); |
| } |
| |
| /* Free the element of VARIABLE_HTAB (its type is struct variable_def). */ |
| |
| static void |
| variable_htab_free (void *elem) |
| { |
| int i; |
| variable var = (variable) elem; |
| location_chain node, next; |
| |
| gcc_assert (var->refcount > 0); |
| |
| var->refcount--; |
| if (var->refcount > 0) |
| return; |
| |
| for (i = 0; i < var->n_var_parts; i++) |
| { |
| for (node = var->var_part[i].loc_chain; node; node = next) |
| { |
| next = node->next; |
| pool_free (loc_chain_pool, node); |
| } |
| var->var_part[i].loc_chain = NULL; |
| } |
| pool_free (var_pool, var); |
| } |
| |
| /* Initialize the set (array) SET of attrs to empty lists. */ |
| |
| static void |
| init_attrs_list_set (attrs *set) |
| { |
| int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| set[i] = NULL; |
| } |
| |
| /* Make the list *LISTP empty. */ |
| |
| static void |
| attrs_list_clear (attrs *listp) |
| { |
| attrs list, next; |
| |
| for (list = *listp; list; list = next) |
| { |
| next = list->next; |
| pool_free (attrs_pool, list); |
| } |
| *listp = NULL; |
| } |
| |
| /* Return true if the pair of DECL and OFFSET is the member of the LIST. */ |
| |
| static attrs |
| attrs_list_member (attrs list, tree decl, HOST_WIDE_INT offset) |
| { |
| for (; list; list = list->next) |
| if (list->decl == decl && list->offset == offset) |
| return list; |
| return NULL; |
| } |
| |
| /* Insert the triplet DECL, OFFSET, LOC to the list *LISTP. */ |
| |
| static void |
| attrs_list_insert (attrs *listp, tree decl, HOST_WIDE_INT offset, rtx loc) |
| { |
| attrs list; |
| |
| list = (attrs) pool_alloc (attrs_pool); |
| list->loc = loc; |
| list->decl = decl; |
| list->offset = offset; |
| list->next = *listp; |
| *listp = list; |
| } |
| |
| /* Copy all nodes from SRC and create a list *DSTP of the copies. */ |
| |
| static void |
| attrs_list_copy (attrs *dstp, attrs src) |
| { |
| attrs n; |
| |
| attrs_list_clear (dstp); |
| for (; src; src = src->next) |
| { |
| n = (attrs) pool_alloc (attrs_pool); |
| n->loc = src->loc; |
| n->decl = src->decl; |
| n->offset = src->offset; |
| n->next = *dstp; |
| *dstp = n; |
| } |
| } |
| |
| /* Add all nodes from SRC which are not in *DSTP to *DSTP. */ |
| |
| static void |
| attrs_list_union (attrs *dstp, attrs src) |
| { |
| for (; src; src = src->next) |
| { |
| if (!attrs_list_member (*dstp, src->decl, src->offset)) |
| attrs_list_insert (dstp, src->decl, src->offset, src->loc); |
| } |
| } |
| |
| /* Delete all variables from hash table VARS. */ |
| |
| static void |
| vars_clear (htab_t vars) |
| { |
| htab_empty (vars); |
| } |
| |
| /* Return a copy of a variable VAR and insert it to dataflow set SET. */ |
| |
| static variable |
| unshare_variable (dataflow_set *set, variable var, |
| enum var_init_status initialized) |
| { |
| void **slot; |
| variable new_var; |
| int i; |
| |
| new_var = (variable) pool_alloc (var_pool); |
| new_var->decl = var->decl; |
| new_var->refcount = 1; |
| var->refcount--; |
| new_var->n_var_parts = var->n_var_parts; |
| |
| for (i = 0; i < var->n_var_parts; i++) |
| { |
| location_chain node; |
| location_chain *nextp; |
| |
| new_var->var_part[i].offset = var->var_part[i].offset; |
| nextp = &new_var->var_part[i].loc_chain; |
| for (node = var->var_part[i].loc_chain; node; node = node->next) |
| { |
| location_chain new_lc; |
| |
| new_lc = (location_chain) pool_alloc (loc_chain_pool); |
| new_lc->next = NULL; |
| if (node->init > initialized) |
| new_lc->init = node->init; |
| else |
| new_lc->init = initialized; |
| if (node->set_src && !(MEM_P (node->set_src))) |
| new_lc->set_src = node->set_src; |
| else |
| new_lc->set_src = NULL; |
| new_lc->loc = node->loc; |
| |
| *nextp = new_lc; |
| nextp = &new_lc->next; |
| } |
| |
| /* We are at the basic block boundary when copying variable description |
| so set the CUR_LOC to be the first element of the chain. */ |
| if (new_var->var_part[i].loc_chain) |
| new_var->var_part[i].cur_loc = new_var->var_part[i].loc_chain->loc; |
| else |
| new_var->var_part[i].cur_loc = NULL; |
| } |
| |
| slot = htab_find_slot_with_hash (set->vars, new_var->decl, |
| VARIABLE_HASH_VAL (new_var->decl), |
| INSERT); |
| *slot = new_var; |
| return new_var; |
| } |
| |
| /* Add a variable from *SLOT to hash table DATA and increase its reference |
| count. */ |
| |
| static int |
| vars_copy_1 (void **slot, void *data) |
| { |
| htab_t dst = (htab_t) data; |
| variable src, *dstp; |
| |
| src = *(variable *) slot; |
| src->refcount++; |
| |
| dstp = (variable *) htab_find_slot_with_hash (dst, src->decl, |
| VARIABLE_HASH_VAL (src->decl), |
| INSERT); |
| *dstp = src; |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Copy all variables from hash table SRC to hash table DST. */ |
| |
| static void |
| vars_copy (htab_t dst, htab_t src) |
| { |
| vars_clear (dst); |
| htab_traverse (src, vars_copy_1, dst); |
| } |
| |
| /* Map a decl to its main debug decl. */ |
| |
| static inline tree |
| var_debug_decl (tree decl) |
| { |
| if (decl && DECL_P (decl) |
| && DECL_DEBUG_EXPR_IS_FROM (decl) && DECL_DEBUG_EXPR (decl) |
| && DECL_P (DECL_DEBUG_EXPR (decl))) |
| decl = DECL_DEBUG_EXPR (decl); |
| |
| return decl; |
| } |
| |
| /* Set the register to contain REG_EXPR (LOC), REG_OFFSET (LOC). */ |
| |
| static void |
| var_reg_set (dataflow_set *set, rtx loc, enum var_init_status initialized, |
| rtx set_src) |
| { |
| tree decl = REG_EXPR (loc); |
| HOST_WIDE_INT offset = REG_OFFSET (loc); |
| attrs node; |
| |
| decl = var_debug_decl (decl); |
| |
| for (node = set->regs[REGNO (loc)]; node; node = node->next) |
| if (node->decl == decl && node->offset == offset) |
| break; |
| if (!node) |
| attrs_list_insert (&set->regs[REGNO (loc)], decl, offset, loc); |
| set_variable_part (set, loc, decl, offset, initialized, set_src); |
| } |
| |
| static int |
| get_init_value (dataflow_set *set, rtx loc, tree decl) |
| { |
| void **slot; |
| variable var; |
| int i; |
| int ret_val = VAR_INIT_STATUS_UNKNOWN; |
| |
| if (! flag_var_tracking_uninit) |
| return VAR_INIT_STATUS_INITIALIZED; |
| |
| slot = htab_find_slot_with_hash (set->vars, decl, VARIABLE_HASH_VAL (decl), |
| NO_INSERT); |
| if (slot) |
| { |
| var = * (variable *) slot; |
| for (i = 0; i < var->n_var_parts && ret_val == VAR_INIT_STATUS_UNKNOWN; i++) |
| { |
| location_chain nextp; |
| for (nextp = var->var_part[i].loc_chain; nextp; nextp = nextp->next) |
| if (rtx_equal_p (nextp->loc, loc)) |
| { |
| ret_val = nextp->init; |
| break; |
| } |
| } |
| } |
| |
| return ret_val; |
| } |
| |
| /* Delete current content of register LOC in dataflow set SET and set |
| the register to contain REG_EXPR (LOC), REG_OFFSET (LOC). If |
| MODIFY is true, any other live copies of the same variable part are |
| also deleted from the dataflow set, otherwise the variable part is |
| assumed to be copied from another location holding the same |
| part. */ |
| |
| static void |
| var_reg_delete_and_set (dataflow_set *set, rtx loc, bool modify, |
| enum var_init_status initialized, rtx set_src) |
| { |
| tree decl = REG_EXPR (loc); |
| HOST_WIDE_INT offset = REG_OFFSET (loc); |
| attrs node, next; |
| attrs *nextp; |
| |
| decl = var_debug_decl (decl); |
| |
| if (initialized == VAR_INIT_STATUS_UNKNOWN) |
| initialized = get_init_value (set, loc, decl); |
| |
| nextp = &set->regs[REGNO (loc)]; |
| for (node = *nextp; node; node = next) |
| { |
| next = node->next; |
| if (node->decl != decl || node->offset != offset) |
| { |
| delete_variable_part (set, node->loc, node->decl, node->offset); |
| pool_free (attrs_pool, node); |
| *nextp = next; |
| } |
| else |
| { |
| node->loc = loc; |
| nextp = &node->next; |
| } |
| } |
| if (modify) |
| clobber_variable_part (set, loc, decl, offset, set_src); |
| var_reg_set (set, loc, initialized, set_src); |
| } |
| |
| /* Delete current content of register LOC in dataflow set SET. If |
| CLOBBER is true, also delete any other live copies of the same |
| variable part. */ |
| |
| static void |
| var_reg_delete (dataflow_set *set, rtx loc, bool clobber) |
| { |
| attrs *reg = &set->regs[REGNO (loc)]; |
| attrs node, next; |
| |
| if (clobber) |
| { |
| tree decl = REG_EXPR (loc); |
| HOST_WIDE_INT offset = REG_OFFSET (loc); |
| |
| decl = var_debug_decl (decl); |
| |
| clobber_variable_part (set, NULL, decl, offset, NULL); |
| } |
| |
| for (node = *reg; node; node = next) |
| { |
| next = node->next; |
| delete_variable_part (set, node->loc, node->decl, node->offset); |
| pool_free (attrs_pool, node); |
| } |
| *reg = NULL; |
| } |
| |
| /* Delete content of register with number REGNO in dataflow set SET. */ |
| |
| static void |
| var_regno_delete (dataflow_set *set, int regno) |
| { |
| attrs *reg = &set->regs[regno]; |
| attrs node, next; |
| |
| for (node = *reg; node; node = next) |
| { |
| next = node->next; |
| delete_variable_part (set, node->loc, node->decl, node->offset); |
| pool_free (attrs_pool, node); |
| } |
| *reg = NULL; |
| } |
| |
| /* Set the location part of variable MEM_EXPR (LOC) in dataflow set |
| SET to LOC. |
| Adjust the address first if it is stack pointer based. */ |
| |
| static void |
| var_mem_set (dataflow_set *set, rtx loc, enum var_init_status initialized, |
| rtx set_src) |
| { |
| tree decl = MEM_EXPR (loc); |
| HOST_WIDE_INT offset = INT_MEM_OFFSET (loc); |
| |
| decl = var_debug_decl (decl); |
| |
| set_variable_part (set, loc, decl, offset, initialized, set_src); |
| } |
| |
| /* Delete and set the location part of variable MEM_EXPR (LOC) in |
| dataflow set SET to LOC. If MODIFY is true, any other live copies |
| of the same variable part are also deleted from the dataflow set, |
| otherwise the variable part is assumed to be copied from another |
| location holding the same part. |
| Adjust the address first if it is stack pointer based. */ |
| |
| static void |
| var_mem_delete_and_set (dataflow_set *set, rtx loc, bool modify, |
| enum var_init_status initialized, rtx set_src) |
| { |
| tree decl = MEM_EXPR (loc); |
| HOST_WIDE_INT offset = INT_MEM_OFFSET (loc); |
| |
| decl = var_debug_decl (decl); |
| |
| if (initialized == VAR_INIT_STATUS_UNKNOWN) |
| initialized = get_init_value (set, loc, decl); |
| |
| if (modify) |
| clobber_variable_part (set, NULL, decl, offset, set_src); |
| var_mem_set (set, loc, initialized, set_src); |
| } |
| |
| /* Delete the location part LOC from dataflow set SET. If CLOBBER is |
| true, also delete any other live copies of the same variable part. |
| Adjust the address first if it is stack pointer based. */ |
| |
| static void |
| var_mem_delete (dataflow_set *set, rtx loc, bool clobber) |
| { |
| tree decl = MEM_EXPR (loc); |
| HOST_WIDE_INT offset = INT_MEM_OFFSET (loc); |
| |
| decl = var_debug_decl (decl); |
| if (clobber) |
| clobber_variable_part (set, NULL, decl, offset, NULL); |
| delete_variable_part (set, loc, decl, offset); |
| } |
| |
| /* Initialize dataflow set SET to be empty. |
| VARS_SIZE is the initial size of hash table VARS. */ |
| |
| static void |
| dataflow_set_init (dataflow_set *set, int vars_size) |
| { |
| init_attrs_list_set (set->regs); |
| set->vars = htab_create (vars_size, variable_htab_hash, variable_htab_eq, |
| variable_htab_free); |
| set->stack_adjust = 0; |
| } |
| |
| /* Delete the contents of dataflow set SET. */ |
| |
| static void |
| dataflow_set_clear (dataflow_set *set) |
| { |
| int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| attrs_list_clear (&set->regs[i]); |
| |
| vars_clear (set->vars); |
| } |
| |
| /* Copy the contents of dataflow set SRC to DST. */ |
| |
| static void |
| dataflow_set_copy (dataflow_set *dst, dataflow_set *src) |
| { |
| int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| attrs_list_copy (&dst->regs[i], src->regs[i]); |
| |
| vars_copy (dst->vars, src->vars); |
| dst->stack_adjust = src->stack_adjust; |
| } |
| |
| /* Information for merging lists of locations for a given offset of variable. |
| */ |
| struct variable_union_info |
| { |
| /* Node of the location chain. */ |
| location_chain lc; |
| |
| /* The sum of positions in the input chains. */ |
| int pos; |
| |
| /* The position in the chains of SRC and DST dataflow sets. */ |
| int pos_src; |
| int pos_dst; |
| }; |
| |
| /* Compare function for qsort, order the structures by POS element. */ |
| |
| static int |
| variable_union_info_cmp_pos (const void *n1, const void *n2) |
| { |
| const struct variable_union_info *const i1 = |
| (const struct variable_union_info *) n1; |
| const struct variable_union_info *const i2 = |
| ( const struct variable_union_info *) n2; |
| |
| if (i1->pos != i2->pos) |
| return i1->pos - i2->pos; |
| |
| return (i1->pos_dst - i2->pos_dst); |
| } |
| |
| /* Compute union of location parts of variable *SLOT and the same variable |
| from hash table DATA. Compute "sorted" union of the location chains |
| for common offsets, i.e. the locations of a variable part are sorted by |
| a priority where the priority is the sum of the positions in the 2 chains |
| (if a location is only in one list the position in the second list is |
| defined to be larger than the length of the chains). |
| When we are updating the location parts the newest location is in the |
| beginning of the chain, so when we do the described "sorted" union |
| we keep the newest locations in the beginning. */ |
| |
| static int |
| variable_union (void **slot, void *data) |
| { |
| variable src, dst, *dstp; |
| dataflow_set *set = (dataflow_set *) data; |
| int i, j, k; |
| |
| src = *(variable *) slot; |
| dstp = (variable *) htab_find_slot_with_hash (set->vars, src->decl, |
| VARIABLE_HASH_VAL (src->decl), |
| INSERT); |
| if (!*dstp) |
| { |
| src->refcount++; |
| |
| /* If CUR_LOC of some variable part is not the first element of |
| the location chain we are going to change it so we have to make |
| a copy of the variable. */ |
| for (k = 0; k < src->n_var_parts; k++) |
| { |
| gcc_assert (!src->var_part[k].loc_chain |
| == !src->var_part[k].cur_loc); |
| if (src->var_part[k].loc_chain) |
| { |
| gcc_assert (src->var_part[k].cur_loc); |
| if (src->var_part[k].cur_loc != src->var_part[k].loc_chain->loc) |
| break; |
| } |
| } |
| if (k < src->n_var_parts) |
| { |
| enum var_init_status status = VAR_INIT_STATUS_UNKNOWN; |
| |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| |
| unshare_variable (set, src, status); |
| } |
| else |
| *dstp = src; |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| else |
| dst = *dstp; |
| |
| gcc_assert (src->n_var_parts); |
| |
| /* Count the number of location parts, result is K. */ |
| for (i = 0, j = 0, k = 0; |
| i < src->n_var_parts && j < dst->n_var_parts; k++) |
| { |
| if (src->var_part[i].offset == dst->var_part[j].offset) |
| { |
| i++; |
| j++; |
| } |
| else if (src->var_part[i].offset < dst->var_part[j].offset) |
| i++; |
| else |
| j++; |
| } |
| k += src->n_var_parts - i; |
| k += dst->n_var_parts - j; |
| |
| /* We track only variables whose size is <= MAX_VAR_PARTS bytes |
| thus there are at most MAX_VAR_PARTS different offsets. */ |
| gcc_assert (k <= MAX_VAR_PARTS); |
| |
| if (dst->refcount > 1 && dst->n_var_parts != k) |
| { |
| enum var_init_status status = VAR_INIT_STATUS_UNKNOWN; |
| |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| dst = unshare_variable (set, dst, status); |
| } |
| |
| i = src->n_var_parts - 1; |
| j = dst->n_var_parts - 1; |
| dst->n_var_parts = k; |
| |
| for (k--; k >= 0; k--) |
| { |
| location_chain node, node2; |
| |
| if (i >= 0 && j >= 0 |
| && src->var_part[i].offset == dst->var_part[j].offset) |
| { |
| /* Compute the "sorted" union of the chains, i.e. the locations which |
| are in both chains go first, they are sorted by the sum of |
| positions in the chains. */ |
| int dst_l, src_l; |
| int ii, jj, n; |
| struct variable_union_info *vui; |
| |
| /* If DST is shared compare the location chains. |
| If they are different we will modify the chain in DST with |
| high probability so make a copy of DST. */ |
| if (dst->refcount > 1) |
| { |
| for (node = src->var_part[i].loc_chain, |
| node2 = dst->var_part[j].loc_chain; node && node2; |
| node = node->next, node2 = node2->next) |
| { |
| if (!((REG_P (node2->loc) |
| && REG_P (node->loc) |
| && REGNO (node2->loc) == REGNO (node->loc)) |
| || rtx_equal_p (node2->loc, node->loc))) |
| { |
| if (node2->init < node->init) |
| node2->init = node->init; |
| break; |
| } |
| } |
| if (node || node2) |
| dst = unshare_variable (set, dst, VAR_INIT_STATUS_UNKNOWN); |
| } |
| |
| src_l = 0; |
| for (node = src->var_part[i].loc_chain; node; node = node->next) |
| src_l++; |
| dst_l = 0; |
| for (node = dst->var_part[j].loc_chain; node; node = node->next) |
| dst_l++; |
| vui = XCNEWVEC (struct variable_union_info, src_l + dst_l); |
| |
| /* Fill in the locations from DST. */ |
| for (node = dst->var_part[j].loc_chain, jj = 0; node; |
| node = node->next, jj++) |
| { |
| vui[jj].lc = node; |
| vui[jj].pos_dst = jj; |
| |
| /* Value larger than a sum of 2 valid positions. */ |
| vui[jj].pos_src = src_l + dst_l; |
| } |
| |
| /* Fill in the locations from SRC. */ |
| n = dst_l; |
| for (node = src->var_part[i].loc_chain, ii = 0; node; |
| node = node->next, ii++) |
| { |
| /* Find location from NODE. */ |
| for (jj = 0; jj < dst_l; jj++) |
| { |
| if ((REG_P (vui[jj].lc->loc) |
| && REG_P (node->loc) |
| && REGNO (vui[jj].lc->loc) == REGNO (node->loc)) |
| || rtx_equal_p (vui[jj].lc->loc, node->loc)) |
| { |
| vui[jj].pos_src = ii; |
| break; |
| } |
| } |
| if (jj >= dst_l) /* The location has not been found. */ |
| { |
| location_chain new_node; |
| |
| /* Copy the location from SRC. */ |
| new_node = (location_chain) pool_alloc (loc_chain_pool); |
| new_node->loc = node->loc; |
| new_node->init = node->init; |
| if (!node->set_src || MEM_P (node->set_src)) |
| new_node->set_src = NULL; |
| else |
| new_node->set_src = node->set_src; |
| vui[n].lc = new_node; |
| vui[n].pos_src = ii; |
| vui[n].pos_dst = src_l + dst_l; |
| n++; |
| } |
| } |
| |
| for (ii = 0; ii < src_l + dst_l; ii++) |
| vui[ii].pos = vui[ii].pos_src + vui[ii].pos_dst; |
| |
| qsort (vui, n, sizeof (struct variable_union_info), |
| variable_union_info_cmp_pos); |
| |
| /* Reconnect the nodes in sorted order. */ |
| for (ii = 1; ii < n; ii++) |
| vui[ii - 1].lc->next = vui[ii].lc; |
| vui[n - 1].lc->next = NULL; |
| |
| dst->var_part[k].loc_chain = vui[0].lc; |
| dst->var_part[k].offset = dst->var_part[j].offset; |
| |
| free (vui); |
| i--; |
| j--; |
| } |
| else if ((i >= 0 && j >= 0 |
| && src->var_part[i].offset < dst->var_part[j].offset) |
| || i < 0) |
| { |
| dst->var_part[k] = dst->var_part[j]; |
| j--; |
| } |
| else if ((i >= 0 && j >= 0 |
| && src->var_part[i].offset > dst->var_part[j].offset) |
| || j < 0) |
| { |
| location_chain *nextp; |
| |
| /* Copy the chain from SRC. */ |
| nextp = &dst->var_part[k].loc_chain; |
| for (node = src->var_part[i].loc_chain; node; node = node->next) |
| { |
| location_chain new_lc; |
| |
| new_lc = (location_chain) pool_alloc (loc_chain_pool); |
| new_lc->next = NULL; |
| new_lc->init = node->init; |
| if (!node->set_src || MEM_P (node->set_src)) |
| new_lc->set_src = NULL; |
| else |
| new_lc->set_src = node->set_src; |
| new_lc->loc = node->loc; |
| |
| *nextp = new_lc; |
| nextp = &new_lc->next; |
| } |
| |
| dst->var_part[k].offset = src->var_part[i].offset; |
| i--; |
| } |
| |
| /* We are at the basic block boundary when computing union |
| so set the CUR_LOC to be the first element of the chain. */ |
| if (dst->var_part[k].loc_chain) |
| dst->var_part[k].cur_loc = dst->var_part[k].loc_chain->loc; |
| else |
| dst->var_part[k].cur_loc = NULL; |
| } |
| |
| for (i = 0; i < src->n_var_parts && i < dst->n_var_parts; i++) |
| { |
| location_chain node, node2; |
| for (node = src->var_part[i].loc_chain; node; node = node->next) |
| for (node2 = dst->var_part[i].loc_chain; node2; node2 = node2->next) |
| if (rtx_equal_p (node->loc, node2->loc)) |
| { |
| if (node->init > node2->init) |
| node2->init = node->init; |
| } |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Compute union of dataflow sets SRC and DST and store it to DST. */ |
| |
| static void |
| dataflow_set_union (dataflow_set *dst, dataflow_set *src) |
| { |
| int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| attrs_list_union (&dst->regs[i], src->regs[i]); |
| |
| htab_traverse (src->vars, variable_union, dst); |
| } |
| |
| /* Flag whether two dataflow sets being compared contain different data. */ |
| static bool |
| dataflow_set_different_value; |
| |
| static bool |
| variable_part_different_p (variable_part *vp1, variable_part *vp2) |
| { |
| location_chain lc1, lc2; |
| |
| for (lc1 = vp1->loc_chain; lc1; lc1 = lc1->next) |
| { |
| for (lc2 = vp2->loc_chain; lc2; lc2 = lc2->next) |
| { |
| if (REG_P (lc1->loc) && REG_P (lc2->loc)) |
| { |
| if (REGNO (lc1->loc) == REGNO (lc2->loc)) |
| break; |
| } |
| if (rtx_equal_p (lc1->loc, lc2->loc)) |
| break; |
| } |
| if (!lc2) |
| return true; |
| } |
| return false; |
| } |
| |
| /* Return true if variables VAR1 and VAR2 are different. |
| If COMPARE_CURRENT_LOCATION is true compare also the cur_loc of each |
| variable part. */ |
| |
| static bool |
| variable_different_p (variable var1, variable var2, |
| bool compare_current_location) |
| { |
| int i; |
| |
| if (var1 == var2) |
| return false; |
| |
| if (var1->n_var_parts != var2->n_var_parts) |
| return true; |
| |
| for (i = 0; i < var1->n_var_parts; i++) |
| { |
| if (var1->var_part[i].offset != var2->var_part[i].offset) |
| return true; |
| if (compare_current_location) |
| { |
| if (!((REG_P (var1->var_part[i].cur_loc) |
| && REG_P (var2->var_part[i].cur_loc) |
| && (REGNO (var1->var_part[i].cur_loc) |
| == REGNO (var2->var_part[i].cur_loc))) |
| || rtx_equal_p (var1->var_part[i].cur_loc, |
| var2->var_part[i].cur_loc))) |
| return true; |
| } |
| if (variable_part_different_p (&var1->var_part[i], &var2->var_part[i])) |
| return true; |
| if (variable_part_different_p (&var2->var_part[i], &var1->var_part[i])) |
| return true; |
| } |
| return false; |
| } |
| |
| /* Compare variable *SLOT with the same variable in hash table DATA |
| and set DATAFLOW_SET_DIFFERENT_VALUE if they are different. */ |
| |
| static int |
| dataflow_set_different_1 (void **slot, void *data) |
| { |
| htab_t htab = (htab_t) data; |
| variable var1, var2; |
| |
| var1 = *(variable *) slot; |
| var2 = (variable) htab_find_with_hash (htab, var1->decl, |
| VARIABLE_HASH_VAL (var1->decl)); |
| if (!var2) |
| { |
| dataflow_set_different_value = true; |
| |
| /* Stop traversing the hash table. */ |
| return 0; |
| } |
| |
| if (variable_different_p (var1, var2, false)) |
| { |
| dataflow_set_different_value = true; |
| |
| /* Stop traversing the hash table. */ |
| return 0; |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Compare variable *SLOT with the same variable in hash table DATA |
| and set DATAFLOW_SET_DIFFERENT_VALUE if they are different. */ |
| |
| static int |
| dataflow_set_different_2 (void **slot, void *data) |
| { |
| htab_t htab = (htab_t) data; |
| variable var1, var2; |
| |
| var1 = *(variable *) slot; |
| var2 = (variable) htab_find_with_hash (htab, var1->decl, |
| VARIABLE_HASH_VAL (var1->decl)); |
| if (!var2) |
| { |
| dataflow_set_different_value = true; |
| |
| /* Stop traversing the hash table. */ |
| return 0; |
| } |
| |
| /* If both variables are defined they have been already checked for |
| equivalence. */ |
| gcc_assert (!variable_different_p (var1, var2, false)); |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Return true if dataflow sets OLD_SET and NEW_SET differ. */ |
| |
| static bool |
| dataflow_set_different (dataflow_set *old_set, dataflow_set *new_set) |
| { |
| dataflow_set_different_value = false; |
| |
| htab_traverse (old_set->vars, dataflow_set_different_1, new_set->vars); |
| if (!dataflow_set_different_value) |
| { |
| /* We have compared the variables which are in both hash tables |
| so now only check whether there are some variables in NEW_SET->VARS |
| which are not in OLD_SET->VARS. */ |
| htab_traverse (new_set->vars, dataflow_set_different_2, old_set->vars); |
| } |
| return dataflow_set_different_value; |
| } |
| |
| /* Free the contents of dataflow set SET. */ |
| |
| static void |
| dataflow_set_destroy (dataflow_set *set) |
| { |
| int i; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| attrs_list_clear (&set->regs[i]); |
| |
| htab_delete (set->vars); |
| set->vars = NULL; |
| } |
| |
| /* Return true if RTL X contains a SYMBOL_REF. */ |
| |
| static bool |
| contains_symbol_ref (rtx x) |
| { |
| const char *fmt; |
| RTX_CODE code; |
| int i; |
| |
| if (!x) |
| return false; |
| |
| code = GET_CODE (x); |
| if (code == SYMBOL_REF) |
| return true; |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| { |
| if (fmt[i] == 'e') |
| { |
| if (contains_symbol_ref (XEXP (x, i))) |
| return true; |
| } |
| else if (fmt[i] == 'E') |
| { |
| int j; |
| for (j = 0; j < XVECLEN (x, i); j++) |
| if (contains_symbol_ref (XVECEXP (x, i, j))) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Shall EXPR be tracked? */ |
| |
| static bool |
| track_expr_p (tree expr) |
| { |
| rtx decl_rtl; |
| tree realdecl; |
| |
| /* If EXPR is not a parameter or a variable do not track it. */ |
| if (TREE_CODE (expr) != VAR_DECL && TREE_CODE (expr) != PARM_DECL) |
| return 0; |
| |
| /* It also must have a name... */ |
| if (!DECL_NAME (expr)) |
| return 0; |
| |
| /* ... and a RTL assigned to it. */ |
| decl_rtl = DECL_RTL_IF_SET (expr); |
| if (!decl_rtl) |
| return 0; |
| |
| /* If this expression is really a debug alias of some other declaration, we |
| don't need to track this expression if the ultimate declaration is |
| ignored. */ |
| realdecl = expr; |
| if (DECL_DEBUG_EXPR_IS_FROM (realdecl) && DECL_DEBUG_EXPR (realdecl)) |
| { |
| realdecl = DECL_DEBUG_EXPR (realdecl); |
| /* ??? We don't yet know how to emit DW_OP_piece for variable |
| that has been SRA'ed. */ |
| if (!DECL_P (realdecl)) |
| return 0; |
| } |
| |
| /* Do not track EXPR if REALDECL it should be ignored for debugging |
| purposes. */ |
| if (DECL_IGNORED_P (realdecl)) |
| return 0; |
| |
| /* Do not track global variables until we are able to emit correct location |
| list for them. */ |
| if (TREE_STATIC (realdecl)) |
| return 0; |
| |
| /* When the EXPR is a DECL for alias of some variable (see example) |
| the TREE_STATIC flag is not used. Disable tracking all DECLs whose |
| DECL_RTL contains SYMBOL_REF. |
| |
| Example: |
| extern char **_dl_argv_internal __attribute__ ((alias ("_dl_argv"))); |
| char **_dl_argv; |
| */ |
| if (MEM_P (decl_rtl) |
| && contains_symbol_ref (XEXP (decl_rtl, 0))) |
| return 0; |
| |
| /* If RTX is a memory it should not be very large (because it would be |
| an array or struct). */ |
| if (MEM_P (decl_rtl)) |
| { |
| /* Do not track structures and arrays. */ |
| if (GET_MODE (decl_rtl) == BLKmode |
| || AGGREGATE_TYPE_P (TREE_TYPE (realdecl))) |
| return 0; |
| if (MEM_SIZE (decl_rtl) |
| && INTVAL (MEM_SIZE (decl_rtl)) > MAX_VAR_PARTS) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Determine whether a given LOC refers to the same variable part as |
| EXPR+OFFSET. */ |
| |
| static bool |
| same_variable_part_p (rtx loc, tree expr, HOST_WIDE_INT offset) |
| { |
| tree expr2; |
| HOST_WIDE_INT offset2; |
| |
| if (! DECL_P (expr)) |
| return false; |
| |
| if (REG_P (loc)) |
| { |
| expr2 = REG_EXPR (loc); |
| offset2 = REG_OFFSET (loc); |
| } |
| else if (MEM_P (loc)) |
| { |
| expr2 = MEM_EXPR (loc); |
| offset2 = INT_MEM_OFFSET (loc); |
| } |
| else |
| return false; |
| |
| if (! expr2 || ! DECL_P (expr2)) |
| return false; |
| |
| expr = var_debug_decl (expr); |
| expr2 = var_debug_decl (expr2); |
| |
| return (expr == expr2 && offset == offset2); |
| } |
| |
| /* LOC is a REG or MEM that we would like to track if possible. |
| If EXPR is null, we don't know what expression LOC refers to, |
| otherwise it refers to EXPR + OFFSET. STORE_REG_P is true if |
| LOC is an lvalue register. |
| |
| Return true if EXPR is nonnull and if LOC, or some lowpart of it, |
| is something we can track. When returning true, store the mode of |
| the lowpart we can track in *MODE_OUT (if nonnull) and its offset |
| from EXPR in *OFFSET_OUT (if nonnull). */ |
| |
| static bool |
| track_loc_p (rtx loc, tree expr, HOST_WIDE_INT offset, bool store_reg_p, |
| enum machine_mode *mode_out, HOST_WIDE_INT *offset_out) |
| { |
| enum machine_mode mode; |
| |
| if (expr == NULL || !track_expr_p (expr)) |
| return false; |
| |
| /* If REG was a paradoxical subreg, its REG_ATTRS will describe the |
| whole subreg, but only the old inner part is really relevant. */ |
| mode = GET_MODE (loc); |
| if (REG_P (loc) && !HARD_REGISTER_NUM_P (ORIGINAL_REGNO (loc))) |
| { |
| enum machine_mode pseudo_mode; |
| |
| pseudo_mode = PSEUDO_REGNO_MODE (ORIGINAL_REGNO (loc)); |
| if (GET_MODE_SIZE (mode) > GET_MODE_SIZE (pseudo_mode)) |
| { |
| offset += byte_lowpart_offset (pseudo_mode, mode); |
| mode = pseudo_mode; |
| } |
| } |
| |
| /* If LOC is a paradoxical lowpart of EXPR, refer to EXPR itself. |
| Do the same if we are storing to a register and EXPR occupies |
| the whole of register LOC; in that case, the whole of EXPR is |
| being changed. We exclude complex modes from the second case |
| because the real and imaginary parts are represented as separate |
| pseudo registers, even if the whole complex value fits into one |
| hard register. */ |
| if ((GET_MODE_SIZE (mode) > GET_MODE_SIZE (DECL_MODE (expr)) |
| || (store_reg_p |
| && !COMPLEX_MODE_P (DECL_MODE (expr)) |
| && hard_regno_nregs[REGNO (loc)][DECL_MODE (expr)] == 1)) |
| && offset + byte_lowpart_offset (DECL_MODE (expr), mode) == 0) |
| { |
| mode = DECL_MODE (expr); |
| offset = 0; |
| } |
| |
| if (offset < 0 || offset >= MAX_VAR_PARTS) |
| return false; |
| |
| if (mode_out) |
| *mode_out = mode; |
| if (offset_out) |
| *offset_out = offset; |
| return true; |
| } |
| |
| /* Return the MODE lowpart of LOC, or null if LOC is not something we |
| want to track. When returning nonnull, make sure that the attributes |
| on the returned value are updated. */ |
| |
| static rtx |
| var_lowpart (enum machine_mode mode, rtx loc) |
| { |
| unsigned int offset, reg_offset, regno; |
| |
| if (!REG_P (loc) && !MEM_P (loc)) |
| return NULL; |
| |
| if (GET_MODE (loc) == mode) |
| return loc; |
| |
| offset = byte_lowpart_offset (mode, GET_MODE (loc)); |
| |
| if (MEM_P (loc)) |
| return adjust_address_nv (loc, mode, offset); |
| |
| reg_offset = subreg_lowpart_offset (mode, GET_MODE (loc)); |
| regno = REGNO (loc) + subreg_regno_offset (REGNO (loc), GET_MODE (loc), |
| reg_offset, mode); |
| return gen_rtx_REG_offset (loc, mode, regno, offset); |
| } |
| |
| /* Count uses (register and memory references) LOC which will be tracked. |
| INSN is instruction which the LOC is part of. */ |
| |
| static int |
| count_uses (rtx *loc, void *insn) |
| { |
| basic_block bb = BLOCK_FOR_INSN ((rtx) insn); |
| |
| if (REG_P (*loc)) |
| { |
| gcc_assert (REGNO (*loc) < FIRST_PSEUDO_REGISTER); |
| VTI (bb)->n_mos++; |
| } |
| else if (MEM_P (*loc) |
| && track_loc_p (*loc, MEM_EXPR (*loc), INT_MEM_OFFSET (*loc), |
| false, NULL, NULL)) |
| { |
| VTI (bb)->n_mos++; |
| } |
| |
| return 0; |
| } |
| |
| /* Helper function for finding all uses of REG/MEM in X in insn INSN. */ |
| |
| static void |
| count_uses_1 (rtx *x, void *insn) |
| { |
| for_each_rtx (x, count_uses, insn); |
| } |
| |
| /* Count stores (register and memory references) LOC which will be tracked. |
| INSN is instruction which the LOC is part of. */ |
| |
| static void |
| count_stores (rtx loc, const_rtx expr ATTRIBUTE_UNUSED, void *insn) |
| { |
| count_uses (&loc, insn); |
| } |
| |
| /* Add uses (register and memory references) LOC which will be tracked |
| to VTI (bb)->mos. INSN is instruction which the LOC is part of. */ |
| |
| static int |
| add_uses (rtx *loc, void *insn) |
| { |
| enum machine_mode mode; |
| |
| if (REG_P (*loc)) |
| { |
| basic_block bb = BLOCK_FOR_INSN ((rtx) insn); |
| micro_operation *mo = VTI (bb)->mos + VTI (bb)->n_mos++; |
| |
| if (track_loc_p (*loc, REG_EXPR (*loc), REG_OFFSET (*loc), |
| false, &mode, NULL)) |
| { |
| mo->type = MO_USE; |
| mo->u.loc = var_lowpart (mode, *loc); |
| } |
| else |
| { |
| mo->type = MO_USE_NO_VAR; |
| mo->u.loc = *loc; |
| } |
| mo->insn = (rtx) insn; |
| } |
| else if (MEM_P (*loc) |
| && track_loc_p (*loc, MEM_EXPR (*loc), INT_MEM_OFFSET (*loc), |
| false, &mode, NULL)) |
| { |
| basic_block bb = BLOCK_FOR_INSN ((rtx) insn); |
| micro_operation *mo = VTI (bb)->mos + VTI (bb)->n_mos++; |
| |
| mo->type = MO_USE; |
| mo->u.loc = var_lowpart (mode, *loc); |
| mo->insn = (rtx) insn; |
| } |
| |
| return 0; |
| } |
| |
| /* Helper function for finding all uses of REG/MEM in X in insn INSN. */ |
| |
| static void |
| add_uses_1 (rtx *x, void *insn) |
| { |
| for_each_rtx (x, add_uses, insn); |
| } |
| |
| /* Add stores (register and memory references) LOC which will be tracked |
| to VTI (bb)->mos. EXPR is the RTL expression containing the store. |
| INSN is instruction which the LOC is part of. */ |
| |
| static void |
| add_stores (rtx loc, const_rtx expr, void *insn) |
| { |
| enum machine_mode mode; |
| |
| if (REG_P (loc)) |
| { |
| basic_block bb = BLOCK_FOR_INSN ((rtx) insn); |
| micro_operation *mo = VTI (bb)->mos + VTI (bb)->n_mos++; |
| |
| if (GET_CODE (expr) == CLOBBER |
| || !track_loc_p (loc, REG_EXPR (loc), REG_OFFSET (loc), |
| true, &mode, NULL)) |
| { |
| mo->type = MO_CLOBBER; |
| mo->u.loc = loc; |
| } |
| else |
| { |
| rtx src = NULL; |
| |
| if (GET_CODE (expr) == SET && SET_DEST (expr) == loc) |
| src = var_lowpart (mode, SET_SRC (expr)); |
| loc = var_lowpart (mode, loc); |
| |
| if (src == NULL) |
| { |
| mo->type = MO_SET; |
| mo->u.loc = loc; |
| } |
| else |
| { |
| if (SET_SRC (expr) != src) |
| expr = gen_rtx_SET (VOIDmode, loc, src); |
| if (same_variable_part_p (src, REG_EXPR (loc), REG_OFFSET (loc))) |
| mo->type = MO_COPY; |
| else |
| mo->type = MO_SET; |
| mo->u.loc = CONST_CAST_RTX (expr); |
| } |
| } |
| mo->insn = (rtx) insn; |
| } |
| else if (MEM_P (loc) |
| && track_loc_p (loc, MEM_EXPR (loc), INT_MEM_OFFSET (loc), |
| false, &mode, NULL)) |
| { |
| basic_block bb = BLOCK_FOR_INSN ((rtx) insn); |
| micro_operation *mo = VTI (bb)->mos + VTI (bb)->n_mos++; |
| |
| if (GET_CODE (expr) == CLOBBER) |
| { |
| mo->type = MO_CLOBBER; |
| mo->u.loc = var_lowpart (mode, loc); |
| } |
| else |
| { |
| rtx src = NULL; |
| |
| if (GET_CODE (expr) == SET && SET_DEST (expr) == loc) |
| src = var_lowpart (mode, SET_SRC (expr)); |
| loc = var_lowpart (mode, loc); |
| |
| if (src == NULL) |
| { |
| mo->type = MO_SET; |
| mo->u.loc = loc; |
| } |
| else |
| { |
| if (SET_SRC (expr) != src) |
| expr = gen_rtx_SET (VOIDmode, loc, src); |
| if (same_variable_part_p (SET_SRC (expr), |
| MEM_EXPR (loc), |
| INT_MEM_OFFSET (loc))) |
| mo->type = MO_COPY; |
| else |
| mo->type = MO_SET; |
| mo->u.loc = CONST_CAST_RTX (expr); |
| } |
| } |
| mo->insn = (rtx) insn; |
| } |
| } |
| |
| static enum var_init_status |
| find_src_status (dataflow_set *in, rtx src) |
| { |
| tree decl = NULL_TREE; |
| enum var_init_status status = VAR_INIT_STATUS_UNINITIALIZED; |
| |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| |
| if (src && REG_P (src)) |
| decl = var_debug_decl (REG_EXPR (src)); |
| else if (src && MEM_P (src)) |
| decl = var_debug_decl (MEM_EXPR (src)); |
| |
| if (src && decl) |
| status = get_init_value (in, src, decl); |
| |
| return status; |
| } |
| |
| /* SRC is the source of an assignment. Use SET to try to find what |
| was ultimately assigned to SRC. Return that value if known, |
| otherwise return SRC itself. */ |
| |
| static rtx |
| find_src_set_src (dataflow_set *set, rtx src) |
| { |
| tree decl = NULL_TREE; /* The variable being copied around. */ |
| rtx set_src = NULL_RTX; /* The value for "decl" stored in "src". */ |
| void **slot; |
| variable var; |
| location_chain nextp; |
| int i; |
| bool found; |
| |
| if (src && REG_P (src)) |
| decl = var_debug_decl (REG_EXPR (src)); |
| else if (src && MEM_P (src)) |
| decl = var_debug_decl (MEM_EXPR (src)); |
| |
| if (src && decl) |
| { |
| slot = htab_find_slot_with_hash (set->vars, decl, |
| VARIABLE_HASH_VAL (decl), NO_INSERT); |
| |
| if (slot) |
| { |
| var = *(variable *) slot; |
| found = false; |
| for (i = 0; i < var->n_var_parts && !found; i++) |
| for (nextp = var->var_part[i].loc_chain; nextp && !found; |
| nextp = nextp->next) |
| if (rtx_equal_p (nextp->loc, src)) |
| { |
| set_src = nextp->set_src; |
| found = true; |
| } |
| |
| } |
| } |
| |
| return set_src; |
| } |
| |
| /* Compute the changes of variable locations in the basic block BB. */ |
| |
| static bool |
| compute_bb_dataflow (basic_block bb) |
| { |
| int i, n, r; |
| bool changed; |
| dataflow_set old_out; |
| dataflow_set *in = &VTI (bb)->in; |
| dataflow_set *out = &VTI (bb)->out; |
| |
| dataflow_set_init (&old_out, htab_elements (VTI (bb)->out.vars) + 3); |
| dataflow_set_copy (&old_out, out); |
| dataflow_set_copy (out, in); |
| |
| n = VTI (bb)->n_mos; |
| for (i = 0; i < n; i++) |
| { |
| switch (VTI (bb)->mos[i].type) |
| { |
| case MO_CALL: |
| for (r = 0; r < FIRST_PSEUDO_REGISTER; r++) |
| if (TEST_HARD_REG_BIT (call_used_reg_set, r)) |
| var_regno_delete (out, r); |
| break; |
| |
| case MO_USE: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| enum var_init_status status = VAR_INIT_STATUS_UNINITIALIZED; |
| |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| |
| if (GET_CODE (loc) == REG) |
| var_reg_set (out, loc, status, NULL); |
| else if (GET_CODE (loc) == MEM) |
| var_mem_set (out, loc, status, NULL); |
| } |
| break; |
| |
| case MO_SET: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| rtx set_src = NULL; |
| |
| if (GET_CODE (loc) == SET) |
| { |
| set_src = SET_SRC (loc); |
| loc = SET_DEST (loc); |
| } |
| |
| if (REG_P (loc)) |
| var_reg_delete_and_set (out, loc, true, VAR_INIT_STATUS_INITIALIZED, |
| set_src); |
| else if (MEM_P (loc)) |
| var_mem_delete_and_set (out, loc, true, VAR_INIT_STATUS_INITIALIZED, |
| set_src); |
| } |
| break; |
| |
| case MO_COPY: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| enum var_init_status src_status; |
| rtx set_src = NULL; |
| |
| if (GET_CODE (loc) == SET) |
| { |
| set_src = SET_SRC (loc); |
| loc = SET_DEST (loc); |
| } |
| |
| if (! flag_var_tracking_uninit) |
| src_status = VAR_INIT_STATUS_INITIALIZED; |
| else |
| src_status = find_src_status (in, set_src); |
| |
| if (src_status == VAR_INIT_STATUS_UNKNOWN) |
| src_status = find_src_status (out, set_src); |
| |
| set_src = find_src_set_src (in, set_src); |
| |
| if (REG_P (loc)) |
| var_reg_delete_and_set (out, loc, false, src_status, set_src); |
| else if (MEM_P (loc)) |
| var_mem_delete_and_set (out, loc, false, src_status, set_src); |
| } |
| break; |
| |
| case MO_USE_NO_VAR: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| |
| if (REG_P (loc)) |
| var_reg_delete (out, loc, false); |
| else if (MEM_P (loc)) |
| var_mem_delete (out, loc, false); |
| } |
| break; |
| |
| case MO_CLOBBER: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| |
| if (REG_P (loc)) |
| var_reg_delete (out, loc, true); |
| else if (MEM_P (loc)) |
| var_mem_delete (out, loc, true); |
| } |
| break; |
| |
| case MO_ADJUST: |
| out->stack_adjust += VTI (bb)->mos[i].u.adjust; |
| break; |
| } |
| } |
| |
| changed = dataflow_set_different (&old_out, out); |
| dataflow_set_destroy (&old_out); |
| return changed; |
| } |
| |
| /* Find the locations of variables in the whole function. */ |
| |
| static void |
| vt_find_locations (void) |
| { |
| fibheap_t worklist, pending, fibheap_swap; |
| sbitmap visited, in_worklist, in_pending, sbitmap_swap; |
| basic_block bb; |
| edge e; |
| int *bb_order; |
| int *rc_order; |
| int i; |
| |
| /* Compute reverse completion order of depth first search of the CFG |
| so that the data-flow runs faster. */ |
| rc_order = XNEWVEC (int, n_basic_blocks - NUM_FIXED_BLOCKS); |
| bb_order = XNEWVEC (int, last_basic_block); |
| pre_and_rev_post_order_compute (NULL, rc_order, false); |
| for (i = 0; i < n_basic_blocks - NUM_FIXED_BLOCKS; i++) |
| bb_order[rc_order[i]] = i; |
| free (rc_order); |
| |
| worklist = fibheap_new (); |
| pending = fibheap_new (); |
| visited = sbitmap_alloc (last_basic_block); |
| in_worklist = sbitmap_alloc (last_basic_block); |
| in_pending = sbitmap_alloc (last_basic_block); |
| sbitmap_zero (in_worklist); |
| |
| FOR_EACH_BB (bb) |
| fibheap_insert (pending, bb_order[bb->index], bb); |
| sbitmap_ones (in_pending); |
| |
| while (!fibheap_empty (pending)) |
| { |
| fibheap_swap = pending; |
| pending = worklist; |
| worklist = fibheap_swap; |
| sbitmap_swap = in_pending; |
| in_pending = in_worklist; |
| in_worklist = sbitmap_swap; |
| |
| sbitmap_zero (visited); |
| |
| while (!fibheap_empty (worklist)) |
| { |
| bb = (basic_block) fibheap_extract_min (worklist); |
| RESET_BIT (in_worklist, bb->index); |
| if (!TEST_BIT (visited, bb->index)) |
| { |
| bool changed; |
| edge_iterator ei; |
| |
| SET_BIT (visited, bb->index); |
| |
| /* Calculate the IN set as union of predecessor OUT sets. */ |
| dataflow_set_clear (&VTI (bb)->in); |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| dataflow_set_union (&VTI (bb)->in, &VTI (e->src)->out); |
| } |
| |
| changed = compute_bb_dataflow (bb); |
| if (changed) |
| { |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (e->dest == EXIT_BLOCK_PTR) |
| continue; |
| |
| if (e->dest == bb) |
| continue; |
| |
| if (TEST_BIT (visited, e->dest->index)) |
| { |
| if (!TEST_BIT (in_pending, e->dest->index)) |
| { |
| /* Send E->DEST to next round. */ |
| SET_BIT (in_pending, e->dest->index); |
| fibheap_insert (pending, |
| bb_order[e->dest->index], |
| e->dest); |
| } |
| } |
| else if (!TEST_BIT (in_worklist, e->dest->index)) |
| { |
| /* Add E->DEST to current round. */ |
| SET_BIT (in_worklist, e->dest->index); |
| fibheap_insert (worklist, bb_order[e->dest->index], |
| e->dest); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| free (bb_order); |
| fibheap_delete (worklist); |
| fibheap_delete (pending); |
| sbitmap_free (visited); |
| sbitmap_free (in_worklist); |
| sbitmap_free (in_pending); |
| } |
| |
| /* Print the content of the LIST to dump file. */ |
| |
| static void |
| dump_attrs_list (attrs list) |
| { |
| for (; list; list = list->next) |
| { |
| print_mem_expr (dump_file, list->decl); |
| fprintf (dump_file, "+" HOST_WIDE_INT_PRINT_DEC, list->offset); |
| } |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* Print the information about variable *SLOT to dump file. */ |
| |
| static int |
| dump_variable (void **slot, void *data ATTRIBUTE_UNUSED) |
| { |
| variable var = *(variable *) slot; |
| int i; |
| location_chain node; |
| |
| fprintf (dump_file, " name: %s", |
| IDENTIFIER_POINTER (DECL_NAME (var->decl))); |
| if (dump_flags & TDF_UID) |
| fprintf (dump_file, " D.%u\n", DECL_UID (var->decl)); |
| else |
| fprintf (dump_file, "\n"); |
| |
| for (i = 0; i < var->n_var_parts; i++) |
| { |
| fprintf (dump_file, " offset %ld\n", |
| (long) var->var_part[i].offset); |
| for (node = var->var_part[i].loc_chain; node; node = node->next) |
| { |
| fprintf (dump_file, " "); |
| if (node->init == VAR_INIT_STATUS_UNINITIALIZED) |
| fprintf (dump_file, "[uninit]"); |
| print_rtl_single (dump_file, node->loc); |
| } |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Print the information about variables from hash table VARS to dump file. */ |
| |
| static void |
| dump_vars (htab_t vars) |
| { |
| if (htab_elements (vars) > 0) |
| { |
| fprintf (dump_file, "Variables:\n"); |
| htab_traverse (vars, dump_variable, NULL); |
| } |
| } |
| |
| /* Print the dataflow set SET to dump file. */ |
| |
| static void |
| dump_dataflow_set (dataflow_set *set) |
| { |
| int i; |
| |
| fprintf (dump_file, "Stack adjustment: " HOST_WIDE_INT_PRINT_DEC "\n", |
| set->stack_adjust); |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| { |
| if (set->regs[i]) |
| { |
| fprintf (dump_file, "Reg %d:", i); |
| dump_attrs_list (set->regs[i]); |
| } |
| } |
| dump_vars (set->vars); |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* Print the IN and OUT sets for each basic block to dump file. */ |
| |
| static void |
| dump_dataflow_sets (void) |
| { |
| basic_block bb; |
| |
| FOR_EACH_BB (bb) |
| { |
| fprintf (dump_file, "\nBasic block %d:\n", bb->index); |
| fprintf (dump_file, "IN:\n"); |
| dump_dataflow_set (&VTI (bb)->in); |
| fprintf (dump_file, "OUT:\n"); |
| dump_dataflow_set (&VTI (bb)->out); |
| } |
| } |
| |
| /* Add variable VAR to the hash table of changed variables and |
| if it has no locations delete it from hash table HTAB. */ |
| |
| static void |
| variable_was_changed (variable var, htab_t htab) |
| { |
| hashval_t hash = VARIABLE_HASH_VAL (var->decl); |
| |
| if (emit_notes) |
| { |
| variable *slot; |
| |
| slot = (variable *) htab_find_slot_with_hash (changed_variables, |
| var->decl, hash, INSERT); |
| |
| if (htab && var->n_var_parts == 0) |
| { |
| variable empty_var; |
| void **old; |
| |
| empty_var = (variable) pool_alloc (var_pool); |
| empty_var->decl = var->decl; |
| empty_var->refcount = 1; |
| empty_var->n_var_parts = 0; |
| *slot = empty_var; |
| |
| old = htab_find_slot_with_hash (htab, var->decl, hash, |
| NO_INSERT); |
| if (old) |
| htab_clear_slot (htab, old); |
| } |
| else |
| { |
| *slot = var; |
| } |
| } |
| else |
| { |
| gcc_assert (htab); |
| if (var->n_var_parts == 0) |
| { |
| void **slot = htab_find_slot_with_hash (htab, var->decl, hash, |
| NO_INSERT); |
| if (slot) |
| htab_clear_slot (htab, slot); |
| } |
| } |
| } |
| |
| /* Look for the index in VAR->var_part corresponding to OFFSET. |
| Return -1 if not found. If INSERTION_POINT is non-NULL, the |
| referenced int will be set to the index that the part has or should |
| have, if it should be inserted. */ |
| |
| static inline int |
| find_variable_location_part (variable var, HOST_WIDE_INT offset, |
| int *insertion_point) |
| { |
| int pos, low, high; |
| |
| /* Find the location part. */ |
| low = 0; |
| high = var->n_var_parts; |
| while (low != high) |
| { |
| pos = (low + high) / 2; |
| if (var->var_part[pos].offset < offset) |
| low = pos + 1; |
| else |
| high = pos; |
| } |
| pos = low; |
| |
| if (insertion_point) |
| *insertion_point = pos; |
| |
| if (pos < var->n_var_parts && var->var_part[pos].offset == offset) |
| return pos; |
| |
| return -1; |
| } |
| |
| /* Set the part of variable's location in the dataflow set SET. The variable |
| part is specified by variable's declaration DECL and offset OFFSET and the |
| part's location by LOC. */ |
| |
| static void |
| set_variable_part (dataflow_set *set, rtx loc, tree decl, HOST_WIDE_INT offset, |
| enum var_init_status initialized, rtx set_src) |
| { |
| int pos; |
| location_chain node, next; |
| location_chain *nextp; |
| variable var; |
| void **slot; |
| |
| slot = htab_find_slot_with_hash (set->vars, decl, |
| VARIABLE_HASH_VAL (decl), INSERT); |
| if (!*slot) |
| { |
| /* Create new variable information. */ |
| var = (variable) pool_alloc (var_pool); |
| var->decl = decl; |
| var->refcount = 1; |
| var->n_var_parts = 1; |
| var->var_part[0].offset = offset; |
| var->var_part[0].loc_chain = NULL; |
| var->var_part[0].cur_loc = NULL; |
| *slot = var; |
| pos = 0; |
| } |
| else |
| { |
| int inspos = 0; |
| |
| var = (variable) *slot; |
| |
| pos = find_variable_location_part (var, offset, &inspos); |
| |
| if (pos >= 0) |
| { |
| node = var->var_part[pos].loc_chain; |
| |
| if (node |
| && ((REG_P (node->loc) && REG_P (loc) |
| && REGNO (node->loc) == REGNO (loc)) |
| || rtx_equal_p (node->loc, loc))) |
| { |
| /* LOC is in the beginning of the chain so we have nothing |
| to do. */ |
| if (node->init < initialized) |
| node->init = initialized; |
| if (set_src != NULL) |
| node->set_src = set_src; |
| |
| *slot = var; |
| return; |
| } |
| else |
| { |
| /* We have to make a copy of a shared variable. */ |
| if (var->refcount > 1) |
| var = unshare_variable (set, var, initialized); |
| } |
| } |
| else |
| { |
| /* We have not found the location part, new one will be created. */ |
| |
| /* We have to make a copy of the shared variable. */ |
| if (var->refcount > 1) |
| var = unshare_variable (set, var, initialized); |
| |
| /* We track only variables whose size is <= MAX_VAR_PARTS bytes |
| thus there are at most MAX_VAR_PARTS different offsets. */ |
| gcc_assert (var->n_var_parts < MAX_VAR_PARTS); |
| |
| /* We have to move the elements of array starting at index |
| inspos to the next position. */ |
| for (pos = var->n_var_parts; pos > inspos; pos--) |
| var->var_part[pos] = var->var_part[pos - 1]; |
| |
| var->n_var_parts++; |
| var->var_part[pos].offset = offset; |
| var->var_part[pos].loc_chain = NULL; |
| var->var_part[pos].cur_loc = NULL; |
| } |
| } |
| |
| /* Delete the location from the list. */ |
| nextp = &var->var_part[pos].loc_chain; |
| for (node = var->var_part[pos].loc_chain; node; node = next) |
| { |
| next = node->next; |
| if ((REG_P (node->loc) && REG_P (loc) |
| && REGNO (node->loc) == REGNO (loc)) |
| || rtx_equal_p (node->loc, loc)) |
| { |
| /* Save these values, to assign to the new node, before |
| deleting this one. */ |
| if (node->init > initialized) |
| initialized = node->init; |
| if (node->set_src != NULL && set_src == NULL) |
| set_src = node->set_src; |
| pool_free (loc_chain_pool, node); |
| *nextp = next; |
| break; |
| } |
| else |
| nextp = &node->next; |
| } |
| |
| /* Add the location to the beginning. */ |
| node = (location_chain) pool_alloc (loc_chain_pool); |
| node->loc = loc; |
| node->init = initialized; |
| node->set_src = set_src; |
| node->next = var->var_part[pos].loc_chain; |
| var->var_part[pos].loc_chain = node; |
| |
| /* If no location was emitted do so. */ |
| if (var->var_part[pos].cur_loc == NULL) |
| { |
| var->var_part[pos].cur_loc = loc; |
| variable_was_changed (var, set->vars); |
| } |
| } |
| |
| /* Remove all recorded register locations for the given variable part |
| from dataflow set SET, except for those that are identical to loc. |
| The variable part is specified by variable's declaration DECL and |
| offset OFFSET. */ |
| |
| static void |
| clobber_variable_part (dataflow_set *set, rtx loc, tree decl, |
| HOST_WIDE_INT offset, rtx set_src) |
| { |
| void **slot; |
| |
| if (! decl || ! DECL_P (decl)) |
| return; |
| |
| slot = htab_find_slot_with_hash (set->vars, decl, VARIABLE_HASH_VAL (decl), |
| NO_INSERT); |
| if (slot) |
| { |
| variable var = (variable) *slot; |
| int pos = find_variable_location_part (var, offset, NULL); |
| |
| if (pos >= 0) |
| { |
| location_chain node, next; |
| |
| /* Remove the register locations from the dataflow set. */ |
| next = var->var_part[pos].loc_chain; |
| for (node = next; node; node = next) |
| { |
| next = node->next; |
| if (node->loc != loc |
| && (!flag_var_tracking_uninit |
| || !set_src |
| || MEM_P (set_src) |
| || !rtx_equal_p (set_src, node->set_src))) |
| { |
| if (REG_P (node->loc)) |
| { |
| attrs anode, anext; |
| attrs *anextp; |
| |
| /* Remove the variable part from the register's |
| list, but preserve any other variable parts |
| that might be regarded as live in that same |
| register. */ |
| anextp = &set->regs[REGNO (node->loc)]; |
| for (anode = *anextp; anode; anode = anext) |
| { |
| anext = anode->next; |
| if (anode->decl == decl |
| && anode->offset == offset) |
| { |
| pool_free (attrs_pool, anode); |
| *anextp = anext; |
| } |
| else |
| anextp = &anode->next; |
| } |
| } |
| |
| delete_variable_part (set, node->loc, decl, offset); |
| } |
| } |
| } |
| } |
| } |
| |
| /* Delete the part of variable's location from dataflow set SET. The variable |
| part is specified by variable's declaration DECL and offset OFFSET and the |
| part's location by LOC. */ |
| |
| static void |
| delete_variable_part (dataflow_set *set, rtx loc, tree decl, |
| HOST_WIDE_INT offset) |
| { |
| void **slot; |
| |
| slot = htab_find_slot_with_hash (set->vars, decl, VARIABLE_HASH_VAL (decl), |
| NO_INSERT); |
| if (slot) |
| { |
| variable var = (variable) *slot; |
| int pos = find_variable_location_part (var, offset, NULL); |
| |
| if (pos >= 0) |
| { |
| location_chain node, next; |
| location_chain *nextp; |
| bool changed; |
| |
| if (var->refcount > 1) |
| { |
| /* If the variable contains the location part we have to |
| make a copy of the variable. */ |
| for (node = var->var_part[pos].loc_chain; node; |
| node = node->next) |
| { |
| if ((REG_P (node->loc) && REG_P (loc) |
| && REGNO (node->loc) == REGNO (loc)) |
| || rtx_equal_p (node->loc, loc)) |
| { |
| enum var_init_status status = VAR_INIT_STATUS_UNKNOWN; |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| var = unshare_variable (set, var, status); |
| break; |
| } |
| } |
| } |
| |
| /* Delete the location part. */ |
| nextp = &var->var_part[pos].loc_chain; |
| for (node = *nextp; node; node = next) |
| { |
| next = node->next; |
| if ((REG_P (node->loc) && REG_P (loc) |
| && REGNO (node->loc) == REGNO (loc)) |
| || rtx_equal_p (node->loc, loc)) |
| { |
| pool_free (loc_chain_pool, node); |
| *nextp = next; |
| break; |
| } |
| else |
| nextp = &node->next; |
| } |
| |
| /* If we have deleted the location which was last emitted |
| we have to emit new location so add the variable to set |
| of changed variables. */ |
| if (var->var_part[pos].cur_loc |
| && ((REG_P (loc) |
| && REG_P (var->var_part[pos].cur_loc) |
| && REGNO (loc) == REGNO (var->var_part[pos].cur_loc)) |
| || rtx_equal_p (loc, var->var_part[pos].cur_loc))) |
| { |
| changed = true; |
| if (var->var_part[pos].loc_chain) |
| var->var_part[pos].cur_loc = var->var_part[pos].loc_chain->loc; |
| } |
| else |
| changed = false; |
| |
| if (var->var_part[pos].loc_chain == NULL) |
| { |
| var->n_var_parts--; |
| while (pos < var->n_var_parts) |
| { |
| var->var_part[pos] = var->var_part[pos + 1]; |
| pos++; |
| } |
| } |
| if (changed) |
| variable_was_changed (var, set->vars); |
| } |
| } |
| } |
| |
| /* Emit the NOTE_INSN_VAR_LOCATION for variable *VARP. DATA contains |
| additional parameters: WHERE specifies whether the note shall be emitted |
| before of after instruction INSN. */ |
| |
| static int |
| emit_note_insn_var_location (void **varp, void *data) |
| { |
| variable var = *(variable *) varp; |
| rtx insn = ((emit_note_data *)data)->insn; |
| enum emit_note_where where = ((emit_note_data *)data)->where; |
| rtx note; |
| int i, j, n_var_parts; |
| bool complete; |
| enum var_init_status initialized = VAR_INIT_STATUS_UNINITIALIZED; |
| HOST_WIDE_INT last_limit; |
| tree type_size_unit; |
| HOST_WIDE_INT offsets[MAX_VAR_PARTS]; |
| rtx loc[MAX_VAR_PARTS]; |
| |
| gcc_assert (var->decl); |
| |
| if (! flag_var_tracking_uninit) |
| initialized = VAR_INIT_STATUS_INITIALIZED; |
| |
| complete = true; |
| last_limit = 0; |
| n_var_parts = 0; |
| for (i = 0; i < var->n_var_parts; i++) |
| { |
| enum machine_mode mode, wider_mode; |
| |
| if (last_limit < var->var_part[i].offset) |
| { |
| complete = false; |
| break; |
| } |
| else if (last_limit > var->var_part[i].offset) |
| continue; |
| offsets[n_var_parts] = var->var_part[i].offset; |
| loc[n_var_parts] = var->var_part[i].loc_chain->loc; |
| mode = GET_MODE (loc[n_var_parts]); |
| initialized = var->var_part[i].loc_chain->init; |
| last_limit = offsets[n_var_parts] + GET_MODE_SIZE (mode); |
| |
| /* Attempt to merge adjacent registers or memory. */ |
| wider_mode = GET_MODE_WIDER_MODE (mode); |
| for (j = i + 1; j < var->n_var_parts; j++) |
| if (last_limit <= var->var_part[j].offset) |
| break; |
| if (j < var->n_var_parts |
| && wider_mode != VOIDmode |
| && GET_CODE (loc[n_var_parts]) |
| == GET_CODE (var->var_part[j].loc_chain->loc) |
| && mode == GET_MODE (var->var_part[j].loc_chain->loc) |
| && last_limit == var->var_part[j].offset) |
| { |
| rtx new_loc = NULL; |
| rtx loc2 = var->var_part[j].loc_chain->loc; |
| |
| if (REG_P (loc[n_var_parts]) |
| && hard_regno_nregs[REGNO (loc[n_var_parts])][mode] * 2 |
| == hard_regno_nregs[REGNO (loc[n_var_parts])][wider_mode] |
| && end_hard_regno (mode, REGNO (loc[n_var_parts])) |
| == REGNO (loc2)) |
| { |
| if (! WORDS_BIG_ENDIAN && ! BYTES_BIG_ENDIAN) |
| new_loc = simplify_subreg (wider_mode, loc[n_var_parts], |
| mode, 0); |
| else if (WORDS_BIG_ENDIAN && BYTES_BIG_ENDIAN) |
| new_loc = simplify_subreg (wider_mode, loc2, mode, 0); |
| if (new_loc) |
| { |
| if (!REG_P (new_loc) |
| || REGNO (new_loc) != REGNO (loc[n_var_parts])) |
| new_loc = NULL; |
| else |
| REG_ATTRS (new_loc) = REG_ATTRS (loc[n_var_parts]); |
| } |
| } |
| else if (MEM_P (loc[n_var_parts]) |
| && GET_CODE (XEXP (loc2, 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (loc2, 0), 0)) == REG |
| && GET_CODE (XEXP (XEXP (loc2, 0), 1)) == CONST_INT) |
| { |
| if ((GET_CODE (XEXP (loc[n_var_parts], 0)) == REG |
| && rtx_equal_p (XEXP (loc[n_var_parts], 0), |
| XEXP (XEXP (loc2, 0), 0)) |
| && INTVAL (XEXP (XEXP (loc2, 0), 1)) |
| == GET_MODE_SIZE (mode)) |
| || (GET_CODE (XEXP (loc[n_var_parts], 0)) == PLUS |
| && GET_CODE (XEXP (XEXP (loc[n_var_parts], 0), 1)) |
| == CONST_INT |
| && rtx_equal_p (XEXP (XEXP (loc[n_var_parts], 0), 0), |
| XEXP (XEXP (loc2, 0), 0)) |
| && INTVAL (XEXP (XEXP (loc[n_var_parts], 0), 1)) |
| + GET_MODE_SIZE (mode) |
| == INTVAL (XEXP (XEXP (loc2, 0), 1)))) |
| new_loc = adjust_address_nv (loc[n_var_parts], |
| wider_mode, 0); |
| } |
| |
| if (new_loc) |
| { |
| loc[n_var_parts] = new_loc; |
| mode = wider_mode; |
| last_limit = offsets[n_var_parts] + GET_MODE_SIZE (mode); |
| i = j; |
| } |
| } |
| ++n_var_parts; |
| } |
| type_size_unit = TYPE_SIZE_UNIT (TREE_TYPE (var->decl)); |
| if ((unsigned HOST_WIDE_INT) last_limit < TREE_INT_CST_LOW (type_size_unit)) |
| complete = false; |
| |
| if (where == EMIT_NOTE_AFTER_INSN) |
| note = emit_note_after (NOTE_INSN_VAR_LOCATION, insn); |
| else |
| note = emit_note_before (NOTE_INSN_VAR_LOCATION, insn); |
| |
| if (! flag_var_tracking_uninit) |
| initialized = VAR_INIT_STATUS_INITIALIZED; |
| |
| if (!complete) |
| { |
| NOTE_VAR_LOCATION (note) = gen_rtx_VAR_LOCATION (VOIDmode, var->decl, |
| NULL_RTX, (int) initialized); |
| } |
| else if (n_var_parts == 1) |
| { |
| rtx expr_list |
| = gen_rtx_EXPR_LIST (VOIDmode, loc[0], GEN_INT (offsets[0])); |
| |
| NOTE_VAR_LOCATION (note) = gen_rtx_VAR_LOCATION (VOIDmode, var->decl, |
| expr_list, |
| (int) initialized); |
| } |
| else if (n_var_parts) |
| { |
| rtx parallel; |
| |
| for (i = 0; i < n_var_parts; i++) |
| loc[i] |
| = gen_rtx_EXPR_LIST (VOIDmode, loc[i], GEN_INT (offsets[i])); |
| |
| parallel = gen_rtx_PARALLEL (VOIDmode, |
| gen_rtvec_v (n_var_parts, loc)); |
| NOTE_VAR_LOCATION (note) = gen_rtx_VAR_LOCATION (VOIDmode, var->decl, |
| parallel, |
| (int) initialized); |
| } |
| |
| htab_clear_slot (changed_variables, varp); |
| |
| /* When there are no location parts the variable has been already |
| removed from hash table and a new empty variable was created. |
| Free the empty variable. */ |
| if (var->n_var_parts == 0) |
| { |
| pool_free (var_pool, var); |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Emit NOTE_INSN_VAR_LOCATION note for each variable from a chain |
| CHANGED_VARIABLES and delete this chain. WHERE specifies whether the notes |
| shall be emitted before of after instruction INSN. */ |
| |
| static void |
| emit_notes_for_changes (rtx insn, enum emit_note_where where) |
| { |
| emit_note_data data; |
| |
| data.insn = insn; |
| data.where = where; |
| htab_traverse (changed_variables, emit_note_insn_var_location, &data); |
| } |
| |
| /* Add variable *SLOT to the chain CHANGED_VARIABLES if it differs from the |
| same variable in hash table DATA or is not there at all. */ |
| |
| static int |
| emit_notes_for_differences_1 (void **slot, void *data) |
| { |
| htab_t new_vars = (htab_t) data; |
| variable old_var, new_var; |
| |
| old_var = *(variable *) slot; |
| new_var = (variable) htab_find_with_hash (new_vars, old_var->decl, |
| VARIABLE_HASH_VAL (old_var->decl)); |
| |
| if (!new_var) |
| { |
| /* Variable has disappeared. */ |
| variable empty_var; |
| |
| empty_var = (variable) pool_alloc (var_pool); |
| empty_var->decl = old_var->decl; |
| empty_var->refcount = 1; |
| empty_var->n_var_parts = 0; |
| variable_was_changed (empty_var, NULL); |
| } |
| else if (variable_different_p (old_var, new_var, true)) |
| { |
| variable_was_changed (new_var, NULL); |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Add variable *SLOT to the chain CHANGED_VARIABLES if it is not in hash |
| table DATA. */ |
| |
| static int |
| emit_notes_for_differences_2 (void **slot, void *data) |
| { |
| htab_t old_vars = (htab_t) data; |
| variable old_var, new_var; |
| |
| new_var = *(variable *) slot; |
| old_var = (variable) htab_find_with_hash (old_vars, new_var->decl, |
| VARIABLE_HASH_VAL (new_var->decl)); |
| if (!old_var) |
| { |
| /* Variable has appeared. */ |
| variable_was_changed (new_var, NULL); |
| } |
| |
| /* Continue traversing the hash table. */ |
| return 1; |
| } |
| |
| /* Emit notes before INSN for differences between dataflow sets OLD_SET and |
| NEW_SET. */ |
| |
| static void |
| emit_notes_for_differences (rtx insn, dataflow_set *old_set, |
| dataflow_set *new_set) |
| { |
| htab_traverse (old_set->vars, emit_notes_for_differences_1, new_set->vars); |
| htab_traverse (new_set->vars, emit_notes_for_differences_2, old_set->vars); |
| emit_notes_for_changes (insn, EMIT_NOTE_BEFORE_INSN); |
| } |
| |
| /* Emit the notes for changes of location parts in the basic block BB. */ |
| |
| static void |
| emit_notes_in_bb (basic_block bb) |
| { |
| int i; |
| dataflow_set set; |
| |
| dataflow_set_init (&set, htab_elements (VTI (bb)->in.vars) + 3); |
| dataflow_set_copy (&set, &VTI (bb)->in); |
| |
| for (i = 0; i < VTI (bb)->n_mos; i++) |
| { |
| rtx insn = VTI (bb)->mos[i].insn; |
| |
| switch (VTI (bb)->mos[i].type) |
| { |
| case MO_CALL: |
| { |
| int r; |
| |
| for (r = 0; r < FIRST_PSEUDO_REGISTER; r++) |
| if (TEST_HARD_REG_BIT (call_used_reg_set, r)) |
| { |
| var_regno_delete (&set, r); |
| } |
| emit_notes_for_changes (insn, EMIT_NOTE_AFTER_INSN); |
| } |
| break; |
| |
| case MO_USE: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| |
| enum var_init_status status = VAR_INIT_STATUS_UNINITIALIZED; |
| if (! flag_var_tracking_uninit) |
| status = VAR_INIT_STATUS_INITIALIZED; |
| if (GET_CODE (loc) == REG) |
| var_reg_set (&set, loc, status, NULL); |
| else |
| var_mem_set (&set, loc, status, NULL); |
| |
| emit_notes_for_changes (insn, EMIT_NOTE_AFTER_INSN); |
| } |
| break; |
| |
| case MO_SET: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| rtx set_src = NULL; |
| |
| if (GET_CODE (loc) == SET) |
| { |
| set_src = SET_SRC (loc); |
| loc = SET_DEST (loc); |
| } |
| |
| if (REG_P (loc)) |
| var_reg_delete_and_set (&set, loc, true, VAR_INIT_STATUS_INITIALIZED, |
| set_src); |
| else |
| var_mem_delete_and_set (&set, loc, true, VAR_INIT_STATUS_INITIALIZED, |
| set_src); |
| |
| emit_notes_for_changes (NEXT_INSN (insn), EMIT_NOTE_BEFORE_INSN); |
| } |
| break; |
| |
| case MO_COPY: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| enum var_init_status src_status; |
| rtx set_src = NULL; |
| |
| if (GET_CODE (loc) == SET) |
| { |
| set_src = SET_SRC (loc); |
| loc = SET_DEST (loc); |
| } |
| |
| src_status = find_src_status (&set, set_src); |
| set_src = find_src_set_src (&set, set_src); |
| |
| if (REG_P (loc)) |
| var_reg_delete_and_set (&set, loc, false, src_status, set_src); |
| else |
| var_mem_delete_and_set (&set, loc, false, src_status, set_src); |
| |
| emit_notes_for_changes (NEXT_INSN (insn), EMIT_NOTE_BEFORE_INSN); |
| } |
| break; |
| |
| case MO_USE_NO_VAR: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| |
| if (REG_P (loc)) |
| var_reg_delete (&set, loc, false); |
| else |
| var_mem_delete (&set, loc, false); |
| |
| emit_notes_for_changes (insn, EMIT_NOTE_AFTER_INSN); |
| } |
| break; |
| |
| case MO_CLOBBER: |
| { |
| rtx loc = VTI (bb)->mos[i].u.loc; |
| |
| if (REG_P (loc)) |
| var_reg_delete (&set, loc, true); |
| else |
| var_mem_delete (&set, loc, true); |
| |
| emit_notes_for_changes (NEXT_INSN (insn), EMIT_NOTE_BEFORE_INSN); |
| } |
| break; |
| |
| case MO_ADJUST: |
| set.stack_adjust += VTI (bb)->mos[i].u.adjust; |
| break; |
| } |
| } |
| dataflow_set_destroy (&set); |
| } |
| |
| /* Emit notes for the whole function. */ |
| |
| static void |
| vt_emit_notes (void) |
| { |
| basic_block bb; |
| dataflow_set *last_out; |
| dataflow_set empty; |
| |
| gcc_assert (!htab_elements (changed_variables)); |
| |
| /* Enable emitting notes by functions (mainly by set_variable_part and |
| delete_variable_part). */ |
| emit_notes = true; |
| |
| dataflow_set_init (&empty, 7); |
| last_out = ∅ |
| |
| FOR_EACH_BB (bb) |
| { |
| /* Emit the notes for changes of variable locations between two |
| subsequent basic blocks. */ |
| emit_notes_for_differences (BB_HEAD (bb), last_out, &VTI (bb)->in); |
| |
| /* Emit the notes for the changes in the basic block itself. */ |
| emit_notes_in_bb (bb); |
| |
| last_out = &VTI (bb)->out; |
| } |
| dataflow_set_destroy (&empty); |
| emit_notes = false; |
| } |
| |
| /* If there is a declaration and offset associated with register/memory RTL |
| assign declaration to *DECLP and offset to *OFFSETP, and return true. */ |
| |
| static bool |
| vt_get_decl_and_offset (rtx rtl, tree *declp, HOST_WIDE_INT *offsetp) |
| { |
| if (REG_P (rtl)) |
| { |
| if (REG_ATTRS (rtl)) |
| { |
| *declp = REG_EXPR (rtl); |
| *offsetp = REG_OFFSET (rtl); |
| return true; |
| } |
| } |
| else if (MEM_P (rtl))<
|