| /* Infrastructure for tracking user variable locations and values |
| throughout compilation. |
| Copyright (C) 2010-2024 Free Software Foundation, Inc. |
| Contributed by Alexandre Oliva <aoliva@redhat.com>. |
| |
| 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/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "rtl.h" |
| #include "df.h" |
| #include "valtrack.h" |
| #include "regs.h" |
| #include "memmodel.h" |
| #include "emit-rtl.h" |
| #include "rtl-iter.h" |
| |
| /* gen_lowpart_no_emit hook implementation for DEBUG_INSNs. In DEBUG_INSNs, |
| all lowpart SUBREGs are valid, despite what the machine requires for |
| instructions. */ |
| |
| static rtx |
| gen_lowpart_for_debug (machine_mode mode, rtx x) |
| { |
| rtx result = gen_lowpart_if_possible (mode, x); |
| if (result) |
| return result; |
| |
| if (GET_MODE (x) != VOIDmode) |
| return gen_rtx_raw_SUBREG (mode, x, |
| subreg_lowpart_offset (mode, GET_MODE (x))); |
| |
| return NULL_RTX; |
| } |
| |
| /* Replace auto-increment addressing modes with explicit operations to access |
| the same addresses without modifying the corresponding registers. */ |
| |
| static rtx |
| cleanup_auto_inc_dec (rtx src, machine_mode mem_mode ATTRIBUTE_UNUSED) |
| { |
| rtx x = src; |
| |
| const RTX_CODE code = GET_CODE (x); |
| int i; |
| const char *fmt; |
| |
| switch (code) |
| { |
| case REG: |
| CASE_CONST_ANY: |
| case SYMBOL_REF: |
| case CODE_LABEL: |
| case PC: |
| case SCRATCH: |
| /* SCRATCH must be shared because they represent distinct values. */ |
| return x; |
| case CLOBBER: |
| /* Share clobbers of hard registers, but do not share pseudo reg |
| clobbers or clobbers of hard registers that originated as pseudos. |
| This is needed to allow safe register renaming. */ |
| if (REG_P (XEXP (x, 0)) && REGNO (XEXP (x, 0)) < FIRST_PSEUDO_REGISTER |
| && ORIGINAL_REGNO (XEXP (x, 0)) == REGNO (XEXP (x, 0))) |
| return x; |
| break; |
| |
| case CONST: |
| if (shared_const_p (x)) |
| return x; |
| break; |
| |
| case MEM: |
| mem_mode = GET_MODE (x); |
| break; |
| |
| case PRE_INC: |
| case PRE_DEC: |
| { |
| gcc_assert (mem_mode != VOIDmode && mem_mode != BLKmode); |
| poly_int64 offset = GET_MODE_SIZE (mem_mode); |
| if (code == PRE_DEC) |
| offset = -offset; |
| return gen_rtx_PLUS (GET_MODE (x), |
| cleanup_auto_inc_dec (XEXP (x, 0), mem_mode), |
| gen_int_mode (offset, GET_MODE (x))); |
| } |
| |
| case POST_INC: |
| case POST_DEC: |
| case PRE_MODIFY: |
| case POST_MODIFY: |
| return cleanup_auto_inc_dec (code == PRE_MODIFY |
| ? XEXP (x, 1) : XEXP (x, 0), |
| mem_mode); |
| |
| default: |
| break; |
| } |
| |
| /* Copy the various flags, fields, and other information. We assume |
| that all fields need copying, and then clear the fields that should |
| not be copied. That is the sensible default behavior, and forces |
| us to explicitly document why we are *not* copying a flag. */ |
| x = shallow_copy_rtx (x); |
| |
| /* We do not copy FRAME_RELATED for INSNs. */ |
| if (INSN_P (x)) |
| RTX_FLAG (x, frame_related) = 0; |
| |
| fmt = GET_RTX_FORMAT (code); |
| for (i = GET_RTX_LENGTH (code) - 1; i >= 0; i--) |
| if (fmt[i] == 'e') |
| XEXP (x, i) = cleanup_auto_inc_dec (XEXP (x, i), mem_mode); |
| else if (fmt[i] == 'E' || fmt[i] == 'V') |
| { |
| int j; |
| XVEC (x, i) = rtvec_alloc (XVECLEN (x, i)); |
| for (j = 0; j < XVECLEN (x, i); j++) |
| XVECEXP (x, i, j) |
| = cleanup_auto_inc_dec (XVECEXP (src, i, j), mem_mode); |
| } |
| |
| return x; |
| } |
| |
| /* Auxiliary data structure for propagate_for_debug_stmt. */ |
| |
| struct rtx_subst_pair |
| { |
| rtx to; |
| bool adjusted; |
| rtx_insn *insn; |
| }; |
| |
| /* DATA points to an rtx_subst_pair. Return the value that should be |
| substituted. */ |
| |
| static rtx |
| propagate_for_debug_subst (rtx from, const_rtx old_rtx, void *data) |
| { |
| struct rtx_subst_pair *pair = (struct rtx_subst_pair *)data; |
| |
| if (!rtx_equal_p (from, old_rtx)) |
| return NULL_RTX; |
| if (!pair->adjusted) |
| { |
| pair->adjusted = true; |
| pair->to = cleanup_auto_inc_dec (pair->to, VOIDmode); |
| pair->to = make_compound_operation (pair->to, SET); |
| /* Avoid propagation from growing DEBUG_INSN expressions too much. */ |
| int cnt = 0; |
| subrtx_iterator::array_type array; |
| FOR_EACH_SUBRTX (iter, array, pair->to, ALL) |
| if (REG_P (*iter) && ++cnt > 1) |
| { |
| rtx dval = make_debug_expr_from_rtl (old_rtx); |
| rtx to = pair->to; |
| if (volatile_insn_p (to)) |
| to = gen_rtx_UNKNOWN_VAR_LOC (); |
| /* Emit a debug bind insn. */ |
| rtx bind |
| = gen_rtx_VAR_LOCATION (GET_MODE (old_rtx), |
| DEBUG_EXPR_TREE_DECL (dval), to, |
| VAR_INIT_STATUS_INITIALIZED); |
| rtx_insn *bind_insn = emit_debug_insn_before (bind, pair->insn); |
| df_insn_rescan (bind_insn); |
| pair->to = dval; |
| break; |
| } |
| return pair->to; |
| } |
| return copy_rtx (pair->to); |
| } |
| |
| /* Replace all the occurrences of DEST with SRC in DEBUG_INSNs between INSN |
| and LAST, not including INSN, but including LAST. Also stop at the end |
| of THIS_BASIC_BLOCK. */ |
| |
| void |
| propagate_for_debug (rtx_insn *insn, rtx_insn *last, rtx dest, rtx src, |
| basic_block this_basic_block) |
| { |
| rtx_insn *next, *end = NEXT_INSN (BB_END (this_basic_block)); |
| rtx loc; |
| rtx (*saved_rtl_hook_no_emit) (machine_mode, rtx); |
| |
| struct rtx_subst_pair p; |
| p.to = src; |
| p.adjusted = false; |
| p.insn = NEXT_INSN (insn); |
| |
| next = NEXT_INSN (insn); |
| last = NEXT_INSN (last); |
| saved_rtl_hook_no_emit = rtl_hooks.gen_lowpart_no_emit; |
| rtl_hooks.gen_lowpart_no_emit = gen_lowpart_for_debug; |
| while (next != last && next != end) |
| { |
| insn = next; |
| next = NEXT_INSN (insn); |
| if (DEBUG_BIND_INSN_P (insn)) |
| { |
| loc = simplify_replace_fn_rtx (INSN_VAR_LOCATION_LOC (insn), |
| dest, propagate_for_debug_subst, &p); |
| if (loc == INSN_VAR_LOCATION_LOC (insn)) |
| continue; |
| if (volatile_insn_p (loc)) |
| loc = gen_rtx_UNKNOWN_VAR_LOC (); |
| INSN_VAR_LOCATION_LOC (insn) = loc; |
| df_insn_rescan (insn); |
| } |
| } |
| rtl_hooks.gen_lowpart_no_emit = saved_rtl_hook_no_emit; |
| } |
| |
| /* Initialize DEBUG to an empty list, and clear USED, if given. */ |
| |
| void |
| dead_debug_global_init (struct dead_debug_global *debug, bitmap used) |
| { |
| debug->used = used; |
| debug->htab = NULL; |
| if (used) |
| bitmap_clear (used); |
| } |
| |
| /* Initialize DEBUG to an empty list, and clear USED, if given. Link |
| back to GLOBAL, if given, and bring in used bits from it. */ |
| |
| void |
| dead_debug_local_init (struct dead_debug_local *debug, bitmap used, |
| struct dead_debug_global *global) |
| { |
| if (!used && global && global->used) |
| used = BITMAP_ALLOC (NULL); |
| |
| debug->head = NULL; |
| debug->global = global; |
| debug->used = used; |
| debug->to_rescan = NULL; |
| |
| if (used) |
| { |
| if (global && global->used) |
| bitmap_copy (used, global->used); |
| else |
| bitmap_clear (used); |
| } |
| } |
| |
| /* Locate the entry for REG in GLOBAL->htab. */ |
| |
| static dead_debug_global_entry * |
| dead_debug_global_find (struct dead_debug_global *global, rtx reg) |
| { |
| dead_debug_global_entry temp_entry; |
| temp_entry.reg = reg; |
| |
| dead_debug_global_entry *entry = global->htab->find (&temp_entry); |
| gcc_checking_assert (entry && entry->reg == temp_entry.reg); |
| |
| return entry; |
| } |
| |
| /* Insert an entry mapping REG to DTEMP in GLOBAL->htab. */ |
| |
| static dead_debug_global_entry * |
| dead_debug_global_insert (struct dead_debug_global *global, rtx reg, rtx dtemp) |
| { |
| dead_debug_global_entry temp_entry; |
| temp_entry.reg = reg; |
| temp_entry.dtemp = dtemp; |
| |
| if (!global->htab) |
| global->htab = new hash_table<dead_debug_hash_descr> (31); |
| |
| dead_debug_global_entry **slot = global->htab->find_slot (&temp_entry, |
| INSERT); |
| gcc_checking_assert (!*slot); |
| *slot = XNEW (dead_debug_global_entry); |
| **slot = temp_entry; |
| return *slot; |
| } |
| |
| /* If UREGNO, referenced by USE, is a pseudo marked as used in GLOBAL, |
| replace it with a USE of the debug temp recorded for it, and |
| return TRUE. Otherwise, just return FALSE. |
| |
| If PTO_RESCAN is given, instead of rescanning modified INSNs right |
| away, add their UIDs to the bitmap, allocating one of *PTO_RESCAN |
| is NULL. */ |
| |
| static bool |
| dead_debug_global_replace_temp (struct dead_debug_global *global, |
| df_ref use, unsigned int uregno, |
| bitmap *pto_rescan) |
| { |
| if (!global || uregno < FIRST_PSEUDO_REGISTER |
| || !global->used |
| || !REG_P (*DF_REF_REAL_LOC (use)) |
| || REGNO (*DF_REF_REAL_LOC (use)) != uregno |
| || !bitmap_bit_p (global->used, uregno)) |
| return false; |
| |
| dead_debug_global_entry *entry |
| = dead_debug_global_find (global, *DF_REF_REAL_LOC (use)); |
| gcc_checking_assert (GET_CODE (entry->reg) == REG |
| && REGNO (entry->reg) == uregno); |
| |
| if (!entry->dtemp) |
| return true; |
| |
| *DF_REF_REAL_LOC (use) = entry->dtemp; |
| if (!pto_rescan) |
| df_insn_rescan (DF_REF_INSN (use)); |
| else |
| { |
| if (!*pto_rescan) |
| *pto_rescan = BITMAP_ALLOC (NULL); |
| bitmap_set_bit (*pto_rescan, INSN_UID (DF_REF_INSN (use))); |
| } |
| |
| return true; |
| } |
| |
| /* Reset all debug uses in HEAD, and clear DEBUG->to_rescan bits of |
| each reset insn. DEBUG is not otherwise modified. If HEAD is |
| DEBUG->head, DEBUG->head will be set to NULL at the end. |
| Otherwise, entries from DEBUG->head that pertain to reset insns |
| will be removed, and only then rescanned. */ |
| |
| static void |
| dead_debug_reset_uses (struct dead_debug_local *debug, |
| struct dead_debug_use *head) |
| { |
| bool got_head = (debug->head == head); |
| bitmap rescan; |
| struct dead_debug_use **tailp = &debug->head; |
| struct dead_debug_use *cur; |
| bitmap_iterator bi; |
| unsigned int uid; |
| |
| if (got_head) |
| rescan = NULL; |
| else |
| rescan = BITMAP_ALLOC (NULL); |
| |
| while (head) |
| { |
| struct dead_debug_use *next = head->next; |
| rtx_insn *insn; |
| |
| insn = DF_REF_INSN (head->use); |
| if (!next || DF_REF_INSN (next->use) != insn) |
| { |
| INSN_VAR_LOCATION_LOC (insn) = gen_rtx_UNKNOWN_VAR_LOC (); |
| if (got_head) |
| df_insn_rescan_debug_internal (insn); |
| else |
| bitmap_set_bit (rescan, INSN_UID (insn)); |
| if (debug->to_rescan) |
| bitmap_clear_bit (debug->to_rescan, INSN_UID (insn)); |
| } |
| XDELETE (head); |
| head = next; |
| } |
| |
| if (got_head) |
| { |
| debug->head = NULL; |
| return; |
| } |
| |
| while ((cur = *tailp)) |
| if (bitmap_bit_p (rescan, INSN_UID (DF_REF_INSN (cur->use)))) |
| { |
| *tailp = cur->next; |
| XDELETE (cur); |
| } |
| else |
| tailp = &cur->next; |
| |
| EXECUTE_IF_SET_IN_BITMAP (rescan, 0, uid, bi) |
| { |
| struct df_insn_info *insn_info = DF_INSN_UID_SAFE_GET (uid); |
| if (insn_info) |
| df_insn_rescan_debug_internal (insn_info->insn); |
| } |
| |
| BITMAP_FREE (rescan); |
| } |
| |
| /* Promote pending local uses of pseudos in DEBUG to global |
| substitutions. Uses of non-pseudos are left alone for |
| resetting. */ |
| |
| static void |
| dead_debug_promote_uses (struct dead_debug_local *debug) |
| { |
| for (struct dead_debug_use *head = debug->head, **headp = &debug->head; |
| head; head = *headp) |
| { |
| rtx reg = *DF_REF_REAL_LOC (head->use); |
| df_ref ref; |
| dead_debug_global_entry *entry; |
| |
| if (GET_CODE (reg) != REG |
| || REGNO (reg) < FIRST_PSEUDO_REGISTER) |
| { |
| headp = &head->next; |
| continue; |
| } |
| |
| if (!debug->global->used) |
| debug->global->used = BITMAP_ALLOC (NULL); |
| |
| bool added = bitmap_set_bit (debug->global->used, REGNO (reg)); |
| gcc_checking_assert (added); |
| |
| entry = dead_debug_global_insert (debug->global, reg, |
| make_debug_expr_from_rtl (reg)); |
| |
| gcc_checking_assert (entry->dtemp); |
| |
| /* Tentatively remove the USE from the list. */ |
| *headp = head->next; |
| |
| if (!debug->to_rescan) |
| debug->to_rescan = BITMAP_ALLOC (NULL); |
| |
| for (ref = DF_REG_USE_CHAIN (REGNO (reg)); ref; |
| ref = DF_REF_NEXT_REG (ref)) |
| if (DEBUG_INSN_P (DF_REF_INSN (ref))) |
| { |
| if (!dead_debug_global_replace_temp (debug->global, ref, |
| REGNO (reg), |
| &debug->to_rescan)) |
| { |
| rtx_insn *insn = DF_REF_INSN (ref); |
| INSN_VAR_LOCATION_LOC (insn) = gen_rtx_UNKNOWN_VAR_LOC (); |
| bitmap_set_bit (debug->to_rescan, INSN_UID (insn)); |
| } |
| } |
| |
| for (ref = DF_REG_DEF_CHAIN (REGNO (reg)); ref; |
| ref = DF_REF_NEXT_REG (ref)) |
| if (!dead_debug_insert_temp (debug, REGNO (reg), DF_REF_INSN (ref), |
| DEBUG_TEMP_BEFORE_WITH_VALUE)) |
| { |
| rtx bind; |
| bind = gen_rtx_VAR_LOCATION (GET_MODE (reg), |
| DEBUG_EXPR_TREE_DECL (entry->dtemp), |
| gen_rtx_UNKNOWN_VAR_LOC (), |
| VAR_INIT_STATUS_INITIALIZED); |
| rtx_insn *insn = emit_debug_insn_before (bind, DF_REF_INSN (ref)); |
| bitmap_set_bit (debug->to_rescan, INSN_UID (insn)); |
| } |
| |
| entry->dtemp = NULL; |
| XDELETE (head); |
| } |
| } |
| |
| /* Reset all debug insns with pending uses. Release the bitmap in it, |
| unless it is USED. USED must be the same bitmap passed to |
| dead_debug_local_init. */ |
| |
| void |
| dead_debug_local_finish (struct dead_debug_local *debug, bitmap used) |
| { |
| if (debug->global) |
| dead_debug_promote_uses (debug); |
| |
| if (debug->used != used) |
| BITMAP_FREE (debug->used); |
| |
| dead_debug_reset_uses (debug, debug->head); |
| |
| if (debug->to_rescan) |
| { |
| bitmap_iterator bi; |
| unsigned int uid; |
| |
| EXECUTE_IF_SET_IN_BITMAP (debug->to_rescan, 0, uid, bi) |
| { |
| struct df_insn_info *insn_info = DF_INSN_UID_SAFE_GET (uid); |
| if (insn_info) |
| df_insn_rescan (insn_info->insn); |
| } |
| BITMAP_FREE (debug->to_rescan); |
| } |
| } |
| |
| /* Release GLOBAL->used unless it is the same as USED. Release the |
| mapping hash table if it was initialized. */ |
| |
| void |
| dead_debug_global_finish (struct dead_debug_global *global, bitmap used) |
| { |
| if (global->used != used) |
| BITMAP_FREE (global->used); |
| |
| delete global->htab; |
| global->htab = NULL; |
| } |
| |
| /* Add USE to DEBUG, or substitute it right away if it's a pseudo in |
| the global substitution list. USE must be a dead reference to |
| UREGNO in a debug insn. Create a bitmap for DEBUG as needed. */ |
| |
| void |
| dead_debug_add (struct dead_debug_local *debug, df_ref use, unsigned int uregno) |
| { |
| if (dead_debug_global_replace_temp (debug->global, use, uregno, |
| &debug->to_rescan)) |
| return; |
| |
| struct dead_debug_use *newddu = XNEW (struct dead_debug_use); |
| |
| newddu->use = use; |
| newddu->next = debug->head; |
| debug->head = newddu; |
| |
| if (!debug->used) |
| debug->used = BITMAP_ALLOC (NULL); |
| |
| /* ??? If we dealt with split multi-registers below, we should set |
| all registers for the used mode in case of hardware |
| registers. */ |
| bitmap_set_bit (debug->used, uregno); |
| } |
| |
| /* Like lowpart_subreg, but if a subreg is not valid for machine, force |
| it anyway - for use in debug insns. */ |
| |
| static rtx |
| debug_lowpart_subreg (machine_mode outer_mode, rtx expr, |
| machine_mode inner_mode) |
| { |
| if (inner_mode == VOIDmode) |
| inner_mode = GET_MODE (expr); |
| poly_int64 offset = subreg_lowpart_offset (outer_mode, inner_mode); |
| rtx ret = simplify_gen_subreg (outer_mode, expr, inner_mode, offset); |
| if (ret) |
| return ret; |
| if (GET_MODE (expr) != VOIDmode) |
| return gen_rtx_raw_SUBREG (outer_mode, expr, offset); |
| return NULL_RTX; |
| } |
| |
| /* If UREGNO is referenced by any entry in DEBUG, emit a debug insn |
| before or after INSN (depending on WHERE), that binds a (possibly |
| global) debug temp to the widest-mode use of UREGNO, if WHERE is |
| *_WITH_REG, or the value stored in UREGNO by INSN otherwise, and |
| replace all uses of UREGNO in DEBUG with uses of the debug temp. |
| INSN must be where UREGNO dies, if WHERE is *_BEFORE_*, or where it |
| is set otherwise. Return the number of debug insns emitted. */ |
| |
| int |
| dead_debug_insert_temp (struct dead_debug_local *debug, unsigned int uregno, |
| rtx_insn *insn, enum debug_temp_where where) |
| { |
| struct dead_debug_use **tailp = &debug->head; |
| struct dead_debug_use *cur; |
| struct dead_debug_use *uses = NULL; |
| struct dead_debug_use **usesp = &uses; |
| rtx reg = NULL_RTX; |
| rtx breg; |
| rtx dval = NULL_RTX; |
| rtx bind; |
| bool global; |
| |
| if (!debug->used) |
| return 0; |
| |
| global = (debug->global && debug->global->used |
| && bitmap_bit_p (debug->global->used, uregno)); |
| |
| if (!global && !bitmap_clear_bit (debug->used, uregno)) |
| return 0; |
| |
| /* Move all uses of uregno from debug->head to uses, setting mode to |
| the widest referenced mode. */ |
| while ((cur = *tailp)) |
| { |
| if (DF_REF_REGNO (cur->use) == uregno) |
| { |
| /* If this loc has been changed e.g. to debug_expr already |
| as part of a multi-register use, just drop it. */ |
| if (!REG_P (*DF_REF_REAL_LOC (cur->use))) |
| { |
| *tailp = cur->next; |
| XDELETE (cur); |
| continue; |
| } |
| *usesp = cur; |
| usesp = &cur->next; |
| *tailp = cur->next; |
| cur->next = NULL; |
| /* "may" rather than "must" because we want (for example) |
| N V4SFs to win over plain V4SF even though N might be 1. */ |
| rtx candidate = *DF_REF_REAL_LOC (cur->use); |
| if (!reg |
| || maybe_lt (GET_MODE_BITSIZE (GET_MODE (reg)), |
| GET_MODE_BITSIZE (GET_MODE (candidate)))) |
| reg = candidate; |
| } |
| else |
| tailp = &(*tailp)->next; |
| } |
| |
| /* We may have dangling bits in debug->used for registers that were part |
| of a multi-register use, one component of which has been reset. */ |
| if (reg == NULL) |
| { |
| gcc_checking_assert (!uses); |
| if (!global) |
| return 0; |
| } |
| |
| if (global) |
| { |
| if (!reg) |
| reg = regno_reg_rtx[uregno]; |
| dead_debug_global_entry *entry |
| = dead_debug_global_find (debug->global, reg); |
| gcc_checking_assert (entry->reg == reg); |
| dval = entry->dtemp; |
| if (!dval) |
| return 0; |
| } |
| |
| gcc_checking_assert (uses || global); |
| |
| breg = reg; |
| /* Recover the expression INSN stores in REG. */ |
| if (where == DEBUG_TEMP_BEFORE_WITH_VALUE) |
| { |
| rtx set = single_set (insn); |
| rtx dest, src; |
| |
| if (set) |
| { |
| dest = SET_DEST (set); |
| src = SET_SRC (set); |
| /* Reset uses if the REG-setting insn is a CALL. Asm in |
| DEBUG_INSN is never useful, we can't emit debug info for |
| that. And for volatile_insn_p, it is actually harmful - |
| DEBUG_INSNs shouldn't have any side-effects. */ |
| if (GET_CODE (src) == CALL || GET_CODE (src) == ASM_OPERANDS |
| || volatile_insn_p (src)) |
| set = NULL_RTX; |
| } |
| |
| /* ??? Should we try to extract it from a PARALLEL? */ |
| if (!set) |
| breg = NULL; |
| /* Cool, it's the same REG, we can use SRC. */ |
| else if (dest == reg) |
| breg = cleanup_auto_inc_dec (src, VOIDmode); |
| else if (REG_P (dest)) |
| { |
| /* Hmm... Something's fishy, we should be setting REG here. */ |
| if (REGNO (dest) != REGNO (reg)) |
| breg = NULL; |
| /* If we're not overwriting all the hardware registers that |
| setting REG in its mode would, we won't know what to bind |
| the debug temp to. ??? We could bind the debug_expr to a |
| CONCAT or PARALLEL with the split multi-registers, and |
| replace them as we found the corresponding sets. */ |
| else if (REG_NREGS (reg) != REG_NREGS (dest)) |
| breg = NULL; |
| /* Ok, it's the same (hardware) REG, but with a different |
| mode, so SUBREG it. */ |
| else |
| breg = debug_lowpart_subreg (GET_MODE (reg), |
| cleanup_auto_inc_dec (src, VOIDmode), |
| GET_MODE (dest)); |
| } |
| else if (GET_CODE (dest) == SUBREG) |
| { |
| /* We should be setting REG here. Lose. */ |
| if (REGNO (SUBREG_REG (dest)) != REGNO (reg)) |
| breg = NULL; |
| /* Lose if we're setting something other than the lowpart of |
| REG. */ |
| else if (!subreg_lowpart_p (dest)) |
| breg = NULL; |
| /* If we're not overwriting all the hardware registers that |
| setting REG in its mode would, we won't know what to bind |
| the debug temp to. */ |
| else if (REGNO (reg) < FIRST_PSEUDO_REGISTER |
| && (REG_NREGS (reg) |
| != hard_regno_nregs (REGNO (reg), GET_MODE (dest)))) |
| breg = NULL; |
| /* Yay, we can use SRC, just adjust its mode. */ |
| else |
| breg = debug_lowpart_subreg (GET_MODE (reg), |
| cleanup_auto_inc_dec (src, VOIDmode), |
| GET_MODE (dest)); |
| } |
| /* Oh well, we're out of luck. */ |
| else |
| breg = NULL; |
| |
| /* We couldn't figure out the value stored in REG, so reset all |
| of its pending debug uses. */ |
| if (!breg) |
| { |
| dead_debug_reset_uses (debug, uses); |
| return 0; |
| } |
| } |
| |
| /* If there's a single (debug) use of an otherwise unused REG, and |
| the debug use is not part of a larger expression, then it |
| probably doesn't make sense to introduce a new debug temp. */ |
| if (where == DEBUG_TEMP_AFTER_WITH_REG && !uses->next) |
| { |
| rtx_insn *next = DF_REF_INSN (uses->use); |
| |
| if (DEBUG_INSN_P (next) && reg == INSN_VAR_LOCATION_LOC (next)) |
| { |
| XDELETE (uses); |
| return 0; |
| } |
| } |
| |
| if (!global) |
| /* Create DEBUG_EXPR (and DEBUG_EXPR_DECL). */ |
| dval = make_debug_expr_from_rtl (reg); |
| |
| /* Emit a debug bind insn before the insn in which reg dies. */ |
| bind = gen_rtx_VAR_LOCATION (GET_MODE (reg), |
| DEBUG_EXPR_TREE_DECL (dval), breg, |
| VAR_INIT_STATUS_INITIALIZED); |
| |
| if (where == DEBUG_TEMP_AFTER_WITH_REG |
| || where == DEBUG_TEMP_AFTER_WITH_REG_FORCE) |
| bind = emit_debug_insn_after (bind, insn); |
| else |
| bind = emit_debug_insn_before (bind, insn); |
| if (debug->to_rescan == NULL) |
| debug->to_rescan = BITMAP_ALLOC (NULL); |
| bitmap_set_bit (debug->to_rescan, INSN_UID (bind)); |
| |
| /* Adjust all uses. */ |
| while ((cur = uses)) |
| { |
| if (GET_MODE (*DF_REF_REAL_LOC (cur->use)) == GET_MODE (reg)) |
| *DF_REF_REAL_LOC (cur->use) = dval; |
| else |
| *DF_REF_REAL_LOC (cur->use) |
| = debug_lowpart_subreg (GET_MODE (*DF_REF_REAL_LOC (cur->use)), dval, |
| GET_MODE (dval)); |
| /* ??? Should we simplify subreg of subreg? */ |
| bitmap_set_bit (debug->to_rescan, INSN_UID (DF_REF_INSN (cur->use))); |
| uses = cur->next; |
| XDELETE (cur); |
| } |
| |
| return 1; |
| } |