80d6f89e78
The following patch avoids keeping the inherited MEM_ATTRS when set_mem_attributes_minus_bitpos is called with a variable ARRAY_REF. The inherited ones may not reflect the correct offset and neither does the updated alias-set match the inherited MEM_EXPR. This all ends up confusing path-based alias-analysis, causing wrong-code. The fix is to stop not adopting a MEM_EXPR for certain kinds of expressions and instead handle everything we can. There's still the constant kind trees case which I'm too lazy to look into right now. I did refrain from adding SSA_NAME there and instead avoided calling set_mem_attributes_minus_bitpos when debug expression expansion ended up expanding a SSA definition RHS which should already have taken care of setting the appropriate MEM_ATTRS. 2020-06-04 Richard Biener <rguenther@suse.de> PR middle-end/95493 * cfgexpand.c (expand_debug_expr): Avoid calling set_mem_attributes_minus_bitpos when we were expanding an SSA name. * emit-rtl.c (set_mem_attributes_minus_bitpos): Remove ARRAY_REF special-casing, add CONSTRUCTOR to the set of special-cases we do not want MEM_EXPRs for. Assert we end up with reasonable MEM_EXPRs. * g++.dg/torture/pr95493.C: New testcase.
6804 lines
197 KiB
C
6804 lines
197 KiB
C
/* A pass for lowering trees to RTL.
|
||
Copyright (C) 2004-2020 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/>. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "backend.h"
|
||
#include "target.h"
|
||
#include "rtl.h"
|
||
#include "tree.h"
|
||
#include "gimple.h"
|
||
#include "cfghooks.h"
|
||
#include "tree-pass.h"
|
||
#include "memmodel.h"
|
||
#include "tm_p.h"
|
||
#include "ssa.h"
|
||
#include "optabs.h"
|
||
#include "regs.h" /* For reg_renumber. */
|
||
#include "emit-rtl.h"
|
||
#include "recog.h"
|
||
#include "cgraph.h"
|
||
#include "diagnostic.h"
|
||
#include "fold-const.h"
|
||
#include "varasm.h"
|
||
#include "stor-layout.h"
|
||
#include "stmt.h"
|
||
#include "print-tree.h"
|
||
#include "cfgrtl.h"
|
||
#include "cfganal.h"
|
||
#include "cfgbuild.h"
|
||
#include "cfgcleanup.h"
|
||
#include "dojump.h"
|
||
#include "explow.h"
|
||
#include "calls.h"
|
||
#include "expr.h"
|
||
#include "internal-fn.h"
|
||
#include "tree-eh.h"
|
||
#include "gimple-iterator.h"
|
||
#include "gimple-expr.h"
|
||
#include "gimple-walk.h"
|
||
#include "tree-cfg.h"
|
||
#include "tree-dfa.h"
|
||
#include "tree-ssa.h"
|
||
#include "except.h"
|
||
#include "gimple-pretty-print.h"
|
||
#include "toplev.h"
|
||
#include "debug.h"
|
||
#include "tree-inline.h"
|
||
#include "value-prof.h"
|
||
#include "tree-ssa-live.h"
|
||
#include "tree-outof-ssa.h"
|
||
#include "cfgloop.h"
|
||
#include "insn-attr.h" /* For INSN_SCHEDULING. */
|
||
#include "stringpool.h"
|
||
#include "attribs.h"
|
||
#include "asan.h"
|
||
#include "tree-ssa-address.h"
|
||
#include "output.h"
|
||
#include "builtins.h"
|
||
|
||
/* Some systems use __main in a way incompatible with its use in gcc, in these
|
||
cases use the macros NAME__MAIN to give a quoted symbol and SYMBOL__MAIN to
|
||
give the same symbol without quotes for an alternative entry point. You
|
||
must define both, or neither. */
|
||
#ifndef NAME__MAIN
|
||
#define NAME__MAIN "__main"
|
||
#endif
|
||
|
||
/* This variable holds information helping the rewriting of SSA trees
|
||
into RTL. */
|
||
struct ssaexpand SA;
|
||
|
||
/* This variable holds the currently expanded gimple statement for purposes
|
||
of comminucating the profile info to the builtin expanders. */
|
||
gimple *currently_expanding_gimple_stmt;
|
||
|
||
static rtx expand_debug_expr (tree);
|
||
|
||
static bool defer_stack_allocation (tree, bool);
|
||
|
||
static void record_alignment_for_reg_var (unsigned int);
|
||
|
||
/* Return an expression tree corresponding to the RHS of GIMPLE
|
||
statement STMT. */
|
||
|
||
tree
|
||
gimple_assign_rhs_to_tree (gimple *stmt)
|
||
{
|
||
tree t;
|
||
switch (get_gimple_rhs_class (gimple_expr_code (stmt)))
|
||
{
|
||
case GIMPLE_TERNARY_RHS:
|
||
t = build3 (gimple_assign_rhs_code (stmt),
|
||
TREE_TYPE (gimple_assign_lhs (stmt)),
|
||
gimple_assign_rhs1 (stmt), gimple_assign_rhs2 (stmt),
|
||
gimple_assign_rhs3 (stmt));
|
||
break;
|
||
case GIMPLE_BINARY_RHS:
|
||
t = build2 (gimple_assign_rhs_code (stmt),
|
||
TREE_TYPE (gimple_assign_lhs (stmt)),
|
||
gimple_assign_rhs1 (stmt), gimple_assign_rhs2 (stmt));
|
||
break;
|
||
case GIMPLE_UNARY_RHS:
|
||
t = build1 (gimple_assign_rhs_code (stmt),
|
||
TREE_TYPE (gimple_assign_lhs (stmt)),
|
||
gimple_assign_rhs1 (stmt));
|
||
break;
|
||
case GIMPLE_SINGLE_RHS:
|
||
{
|
||
t = gimple_assign_rhs1 (stmt);
|
||
/* Avoid modifying this tree in place below. */
|
||
if ((gimple_has_location (stmt) && CAN_HAVE_LOCATION_P (t)
|
||
&& gimple_location (stmt) != EXPR_LOCATION (t))
|
||
|| (gimple_block (stmt) && currently_expanding_to_rtl
|
||
&& EXPR_P (t)))
|
||
t = copy_node (t);
|
||
break;
|
||
}
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
|
||
if (gimple_has_location (stmt) && CAN_HAVE_LOCATION_P (t))
|
||
SET_EXPR_LOCATION (t, gimple_location (stmt));
|
||
|
||
return t;
|
||
}
|
||
|
||
|
||
#ifndef STACK_ALIGNMENT_NEEDED
|
||
#define STACK_ALIGNMENT_NEEDED 1
|
||
#endif
|
||
|
||
#define SSAVAR(x) (TREE_CODE (x) == SSA_NAME ? SSA_NAME_VAR (x) : x)
|
||
|
||
/* Choose either CUR or NEXT as the leader DECL for a partition.
|
||
Prefer ignored decls, to simplify debug dumps and reduce ambiguity
|
||
out of the same user variable being in multiple partitions (this is
|
||
less likely for compiler-introduced temps). */
|
||
|
||
static tree
|
||
leader_merge (tree cur, tree next)
|
||
{
|
||
if (cur == NULL || cur == next)
|
||
return next;
|
||
|
||
if (DECL_P (cur) && DECL_IGNORED_P (cur))
|
||
return cur;
|
||
|
||
if (DECL_P (next) && DECL_IGNORED_P (next))
|
||
return next;
|
||
|
||
return cur;
|
||
}
|
||
|
||
/* Associate declaration T with storage space X. If T is no
|
||
SSA name this is exactly SET_DECL_RTL, otherwise make the
|
||
partition of T associated with X. */
|
||
static inline void
|
||
set_rtl (tree t, rtx x)
|
||
{
|
||
gcc_checking_assert (!x
|
||
|| !(TREE_CODE (t) == SSA_NAME || is_gimple_reg (t))
|
||
|| (use_register_for_decl (t)
|
||
? (REG_P (x)
|
||
|| (GET_CODE (x) == CONCAT
|
||
&& (REG_P (XEXP (x, 0))
|
||
|| SUBREG_P (XEXP (x, 0)))
|
||
&& (REG_P (XEXP (x, 1))
|
||
|| SUBREG_P (XEXP (x, 1))))
|
||
/* We need to accept PARALLELs for RESUT_DECLs
|
||
because of vector types with BLKmode returned
|
||
in multiple registers, but they are supposed
|
||
to be uncoalesced. */
|
||
|| (GET_CODE (x) == PARALLEL
|
||
&& SSAVAR (t)
|
||
&& TREE_CODE (SSAVAR (t)) == RESULT_DECL
|
||
&& (GET_MODE (x) == BLKmode
|
||
|| !flag_tree_coalesce_vars)))
|
||
: (MEM_P (x) || x == pc_rtx
|
||
|| (GET_CODE (x) == CONCAT
|
||
&& MEM_P (XEXP (x, 0))
|
||
&& MEM_P (XEXP (x, 1))))));
|
||
/* Check that the RTL for SSA_NAMEs and gimple-reg PARM_DECLs and
|
||
RESULT_DECLs has the expected mode. For memory, we accept
|
||
unpromoted modes, since that's what we're likely to get. For
|
||
PARM_DECLs and RESULT_DECLs, we'll have been called by
|
||
set_parm_rtl, which will give us the default def, so we don't
|
||
have to compute it ourselves. For RESULT_DECLs, we accept mode
|
||
mismatches too, as long as we have BLKmode or are not coalescing
|
||
across variables, so that we don't reject BLKmode PARALLELs or
|
||
unpromoted REGs. */
|
||
gcc_checking_assert (!x || x == pc_rtx || TREE_CODE (t) != SSA_NAME
|
||
|| (SSAVAR (t)
|
||
&& TREE_CODE (SSAVAR (t)) == RESULT_DECL
|
||
&& (promote_ssa_mode (t, NULL) == BLKmode
|
||
|| !flag_tree_coalesce_vars))
|
||
|| !use_register_for_decl (t)
|
||
|| GET_MODE (x) == promote_ssa_mode (t, NULL));
|
||
|
||
if (x)
|
||
{
|
||
bool skip = false;
|
||
tree cur = NULL_TREE;
|
||
rtx xm = x;
|
||
|
||
retry:
|
||
if (MEM_P (xm))
|
||
cur = MEM_EXPR (xm);
|
||
else if (REG_P (xm))
|
||
cur = REG_EXPR (xm);
|
||
else if (SUBREG_P (xm))
|
||
{
|
||
gcc_assert (subreg_lowpart_p (xm));
|
||
xm = SUBREG_REG (xm);
|
||
goto retry;
|
||
}
|
||
else if (GET_CODE (xm) == CONCAT)
|
||
{
|
||
xm = XEXP (xm, 0);
|
||
goto retry;
|
||
}
|
||
else if (GET_CODE (xm) == PARALLEL)
|
||
{
|
||
xm = XVECEXP (xm, 0, 0);
|
||
gcc_assert (GET_CODE (xm) == EXPR_LIST);
|
||
xm = XEXP (xm, 0);
|
||
goto retry;
|
||
}
|
||
else if (xm == pc_rtx)
|
||
skip = true;
|
||
else
|
||
gcc_unreachable ();
|
||
|
||
tree next = skip ? cur : leader_merge (cur, SSAVAR (t) ? SSAVAR (t) : t);
|
||
|
||
if (cur != next)
|
||
{
|
||
if (MEM_P (x))
|
||
set_mem_attributes (x,
|
||
next && TREE_CODE (next) == SSA_NAME
|
||
? TREE_TYPE (next)
|
||
: next, true);
|
||
else
|
||
set_reg_attrs_for_decl_rtl (next, x);
|
||
}
|
||
}
|
||
|
||
if (TREE_CODE (t) == SSA_NAME)
|
||
{
|
||
int part = var_to_partition (SA.map, t);
|
||
if (part != NO_PARTITION)
|
||
{
|
||
if (SA.partition_to_pseudo[part])
|
||
gcc_assert (SA.partition_to_pseudo[part] == x);
|
||
else if (x != pc_rtx)
|
||
SA.partition_to_pseudo[part] = x;
|
||
}
|
||
/* For the benefit of debug information at -O0 (where
|
||
vartracking doesn't run) record the place also in the base
|
||
DECL. For PARMs and RESULTs, do so only when setting the
|
||
default def. */
|
||
if (x && x != pc_rtx && SSA_NAME_VAR (t)
|
||
&& (VAR_P (SSA_NAME_VAR (t))
|
||
|| SSA_NAME_IS_DEFAULT_DEF (t)))
|
||
{
|
||
tree var = SSA_NAME_VAR (t);
|
||
/* If we don't yet have something recorded, just record it now. */
|
||
if (!DECL_RTL_SET_P (var))
|
||
SET_DECL_RTL (var, x);
|
||
/* If we have it set already to "multiple places" don't
|
||
change this. */
|
||
else if (DECL_RTL (var) == pc_rtx)
|
||
;
|
||
/* If we have something recorded and it's not the same place
|
||
as we want to record now, we have multiple partitions for the
|
||
same base variable, with different places. We can't just
|
||
randomly chose one, hence we have to say that we don't know.
|
||
This only happens with optimization, and there var-tracking
|
||
will figure out the right thing. */
|
||
else if (DECL_RTL (var) != x)
|
||
SET_DECL_RTL (var, pc_rtx);
|
||
}
|
||
}
|
||
else
|
||
SET_DECL_RTL (t, x);
|
||
}
|
||
|
||
/* This structure holds data relevant to one variable that will be
|
||
placed in a stack slot. */
|
||
class stack_var
|
||
{
|
||
public:
|
||
/* The Variable. */
|
||
tree decl;
|
||
|
||
/* Initially, the size of the variable. Later, the size of the partition,
|
||
if this variable becomes it's partition's representative. */
|
||
poly_uint64 size;
|
||
|
||
/* The *byte* alignment required for this variable. Or as, with the
|
||
size, the alignment for this partition. */
|
||
unsigned int alignb;
|
||
|
||
/* The partition representative. */
|
||
size_t representative;
|
||
|
||
/* The next stack variable in the partition, or EOC. */
|
||
size_t next;
|
||
|
||
/* The numbers of conflicting stack variables. */
|
||
bitmap conflicts;
|
||
};
|
||
|
||
#define EOC ((size_t)-1)
|
||
|
||
/* We have an array of such objects while deciding allocation. */
|
||
static class stack_var *stack_vars;
|
||
static size_t stack_vars_alloc;
|
||
static size_t stack_vars_num;
|
||
static hash_map<tree, size_t> *decl_to_stack_part;
|
||
|
||
/* Conflict bitmaps go on this obstack. This allows us to destroy
|
||
all of them in one big sweep. */
|
||
static bitmap_obstack stack_var_bitmap_obstack;
|
||
|
||
/* An array of indices such that stack_vars[stack_vars_sorted[i]].size
|
||
is non-decreasing. */
|
||
static size_t *stack_vars_sorted;
|
||
|
||
/* The phase of the stack frame. This is the known misalignment of
|
||
virtual_stack_vars_rtx from PREFERRED_STACK_BOUNDARY. That is,
|
||
(frame_offset+frame_phase) % PREFERRED_STACK_BOUNDARY == 0. */
|
||
static int frame_phase;
|
||
|
||
/* Used during expand_used_vars to remember if we saw any decls for
|
||
which we'd like to enable stack smashing protection. */
|
||
static bool has_protected_decls;
|
||
|
||
/* Used during expand_used_vars. Remember if we say a character buffer
|
||
smaller than our cutoff threshold. Used for -Wstack-protector. */
|
||
static bool has_short_buffer;
|
||
|
||
/* Compute the byte alignment to use for DECL. Ignore alignment
|
||
we can't do with expected alignment of the stack boundary. */
|
||
|
||
static unsigned int
|
||
align_local_variable (tree decl, bool really_expand)
|
||
{
|
||
unsigned int align;
|
||
|
||
if (TREE_CODE (decl) == SSA_NAME)
|
||
align = TYPE_ALIGN (TREE_TYPE (decl));
|
||
else
|
||
{
|
||
align = LOCAL_DECL_ALIGNMENT (decl);
|
||
/* Don't change DECL_ALIGN when called from estimated_stack_frame_size.
|
||
That is done before IPA and could bump alignment based on host
|
||
backend even for offloaded code which wants different
|
||
LOCAL_DECL_ALIGNMENT. */
|
||
if (really_expand)
|
||
SET_DECL_ALIGN (decl, align);
|
||
}
|
||
return align / BITS_PER_UNIT;
|
||
}
|
||
|
||
/* Align given offset BASE with ALIGN. Truncate up if ALIGN_UP is true,
|
||
down otherwise. Return truncated BASE value. */
|
||
|
||
static inline unsigned HOST_WIDE_INT
|
||
align_base (HOST_WIDE_INT base, unsigned HOST_WIDE_INT align, bool align_up)
|
||
{
|
||
return align_up ? (base + align - 1) & -align : base & -align;
|
||
}
|
||
|
||
/* Allocate SIZE bytes at byte alignment ALIGN from the stack frame.
|
||
Return the frame offset. */
|
||
|
||
static poly_int64
|
||
alloc_stack_frame_space (poly_int64 size, unsigned HOST_WIDE_INT align)
|
||
{
|
||
poly_int64 offset, new_frame_offset;
|
||
|
||
if (FRAME_GROWS_DOWNWARD)
|
||
{
|
||
new_frame_offset
|
||
= aligned_lower_bound (frame_offset - frame_phase - size,
|
||
align) + frame_phase;
|
||
offset = new_frame_offset;
|
||
}
|
||
else
|
||
{
|
||
new_frame_offset
|
||
= aligned_upper_bound (frame_offset - frame_phase,
|
||
align) + frame_phase;
|
||
offset = new_frame_offset;
|
||
new_frame_offset += size;
|
||
}
|
||
frame_offset = new_frame_offset;
|
||
|
||
if (frame_offset_overflow (frame_offset, cfun->decl))
|
||
frame_offset = offset = 0;
|
||
|
||
return offset;
|
||
}
|
||
|
||
/* Accumulate DECL into STACK_VARS. */
|
||
|
||
static void
|
||
add_stack_var (tree decl, bool really_expand)
|
||
{
|
||
class stack_var *v;
|
||
|
||
if (stack_vars_num >= stack_vars_alloc)
|
||
{
|
||
if (stack_vars_alloc)
|
||
stack_vars_alloc = stack_vars_alloc * 3 / 2;
|
||
else
|
||
stack_vars_alloc = 32;
|
||
stack_vars
|
||
= XRESIZEVEC (class stack_var, stack_vars, stack_vars_alloc);
|
||
}
|
||
if (!decl_to_stack_part)
|
||
decl_to_stack_part = new hash_map<tree, size_t>;
|
||
|
||
v = &stack_vars[stack_vars_num];
|
||
decl_to_stack_part->put (decl, stack_vars_num);
|
||
|
||
v->decl = decl;
|
||
tree size = TREE_CODE (decl) == SSA_NAME
|
||
? TYPE_SIZE_UNIT (TREE_TYPE (decl))
|
||
: DECL_SIZE_UNIT (decl);
|
||
v->size = tree_to_poly_uint64 (size);
|
||
/* Ensure that all variables have size, so that &a != &b for any two
|
||
variables that are simultaneously live. */
|
||
if (known_eq (v->size, 0U))
|
||
v->size = 1;
|
||
v->alignb = align_local_variable (decl, really_expand);
|
||
/* An alignment of zero can mightily confuse us later. */
|
||
gcc_assert (v->alignb != 0);
|
||
|
||
/* All variables are initially in their own partition. */
|
||
v->representative = stack_vars_num;
|
||
v->next = EOC;
|
||
|
||
/* All variables initially conflict with no other. */
|
||
v->conflicts = NULL;
|
||
|
||
/* Ensure that this decl doesn't get put onto the list twice. */
|
||
set_rtl (decl, pc_rtx);
|
||
|
||
stack_vars_num++;
|
||
}
|
||
|
||
/* Make the decls associated with luid's X and Y conflict. */
|
||
|
||
static void
|
||
add_stack_var_conflict (size_t x, size_t y)
|
||
{
|
||
class stack_var *a = &stack_vars[x];
|
||
class stack_var *b = &stack_vars[y];
|
||
if (x == y)
|
||
return;
|
||
if (!a->conflicts)
|
||
a->conflicts = BITMAP_ALLOC (&stack_var_bitmap_obstack);
|
||
if (!b->conflicts)
|
||
b->conflicts = BITMAP_ALLOC (&stack_var_bitmap_obstack);
|
||
bitmap_set_bit (a->conflicts, y);
|
||
bitmap_set_bit (b->conflicts, x);
|
||
}
|
||
|
||
/* Check whether the decls associated with luid's X and Y conflict. */
|
||
|
||
static bool
|
||
stack_var_conflict_p (size_t x, size_t y)
|
||
{
|
||
class stack_var *a = &stack_vars[x];
|
||
class stack_var *b = &stack_vars[y];
|
||
if (x == y)
|
||
return false;
|
||
/* Partitions containing an SSA name result from gimple registers
|
||
with things like unsupported modes. They are top-level and
|
||
hence conflict with everything else. */
|
||
if (TREE_CODE (a->decl) == SSA_NAME || TREE_CODE (b->decl) == SSA_NAME)
|
||
return true;
|
||
|
||
if (!a->conflicts || !b->conflicts)
|
||
return false;
|
||
return bitmap_bit_p (a->conflicts, y);
|
||
}
|
||
|
||
/* Callback for walk_stmt_ops. If OP is a decl touched by add_stack_var
|
||
enter its partition number into bitmap DATA. */
|
||
|
||
static bool
|
||
visit_op (gimple *, tree op, tree, void *data)
|
||
{
|
||
bitmap active = (bitmap)data;
|
||
op = get_base_address (op);
|
||
if (op
|
||
&& DECL_P (op)
|
||
&& DECL_RTL_IF_SET (op) == pc_rtx)
|
||
{
|
||
size_t *v = decl_to_stack_part->get (op);
|
||
if (v)
|
||
bitmap_set_bit (active, *v);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Callback for walk_stmt_ops. If OP is a decl touched by add_stack_var
|
||
record conflicts between it and all currently active other partitions
|
||
from bitmap DATA. */
|
||
|
||
static bool
|
||
visit_conflict (gimple *, tree op, tree, void *data)
|
||
{
|
||
bitmap active = (bitmap)data;
|
||
op = get_base_address (op);
|
||
if (op
|
||
&& DECL_P (op)
|
||
&& DECL_RTL_IF_SET (op) == pc_rtx)
|
||
{
|
||
size_t *v = decl_to_stack_part->get (op);
|
||
if (v && bitmap_set_bit (active, *v))
|
||
{
|
||
size_t num = *v;
|
||
bitmap_iterator bi;
|
||
unsigned i;
|
||
gcc_assert (num < stack_vars_num);
|
||
EXECUTE_IF_SET_IN_BITMAP (active, 0, i, bi)
|
||
add_stack_var_conflict (num, i);
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Helper routine for add_scope_conflicts, calculating the active partitions
|
||
at the end of BB, leaving the result in WORK. We're called to generate
|
||
conflicts when FOR_CONFLICT is true, otherwise we're just tracking
|
||
liveness. */
|
||
|
||
static void
|
||
add_scope_conflicts_1 (basic_block bb, bitmap work, bool for_conflict)
|
||
{
|
||
edge e;
|
||
edge_iterator ei;
|
||
gimple_stmt_iterator gsi;
|
||
walk_stmt_load_store_addr_fn visit;
|
||
|
||
bitmap_clear (work);
|
||
FOR_EACH_EDGE (e, ei, bb->preds)
|
||
bitmap_ior_into (work, (bitmap)e->src->aux);
|
||
|
||
visit = visit_op;
|
||
|
||
for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
gimple *stmt = gsi_stmt (gsi);
|
||
walk_stmt_load_store_addr_ops (stmt, work, NULL, NULL, visit);
|
||
}
|
||
for (gsi = gsi_after_labels (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
gimple *stmt = gsi_stmt (gsi);
|
||
|
||
if (gimple_clobber_p (stmt))
|
||
{
|
||
tree lhs = gimple_assign_lhs (stmt);
|
||
size_t *v;
|
||
/* Nested function lowering might introduce LHSs
|
||
that are COMPONENT_REFs. */
|
||
if (!VAR_P (lhs))
|
||
continue;
|
||
if (DECL_RTL_IF_SET (lhs) == pc_rtx
|
||
&& (v = decl_to_stack_part->get (lhs)))
|
||
bitmap_clear_bit (work, *v);
|
||
}
|
||
else if (!is_gimple_debug (stmt))
|
||
{
|
||
if (for_conflict
|
||
&& visit == visit_op)
|
||
{
|
||
/* If this is the first real instruction in this BB we need
|
||
to add conflicts for everything live at this point now.
|
||
Unlike classical liveness for named objects we can't
|
||
rely on seeing a def/use of the names we're interested in.
|
||
There might merely be indirect loads/stores. We'd not add any
|
||
conflicts for such partitions. */
|
||
bitmap_iterator bi;
|
||
unsigned i;
|
||
EXECUTE_IF_SET_IN_BITMAP (work, 0, i, bi)
|
||
{
|
||
class stack_var *a = &stack_vars[i];
|
||
if (!a->conflicts)
|
||
a->conflicts = BITMAP_ALLOC (&stack_var_bitmap_obstack);
|
||
bitmap_ior_into (a->conflicts, work);
|
||
}
|
||
visit = visit_conflict;
|
||
}
|
||
walk_stmt_load_store_addr_ops (stmt, work, visit, visit, visit);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Generate stack partition conflicts between all partitions that are
|
||
simultaneously live. */
|
||
|
||
static void
|
||
add_scope_conflicts (void)
|
||
{
|
||
basic_block bb;
|
||
bool changed;
|
||
bitmap work = BITMAP_ALLOC (NULL);
|
||
int *rpo;
|
||
int n_bbs;
|
||
|
||
/* We approximate the live range of a stack variable by taking the first
|
||
mention of its name as starting point(s), and by the end-of-scope
|
||
death clobber added by gimplify as ending point(s) of the range.
|
||
This overapproximates in the case we for instance moved an address-taken
|
||
operation upward, without also moving a dereference to it upwards.
|
||
But it's conservatively correct as a variable never can hold values
|
||
before its name is mentioned at least once.
|
||
|
||
We then do a mostly classical bitmap liveness algorithm. */
|
||
|
||
FOR_ALL_BB_FN (bb, cfun)
|
||
bb->aux = BITMAP_ALLOC (&stack_var_bitmap_obstack);
|
||
|
||
rpo = XNEWVEC (int, last_basic_block_for_fn (cfun));
|
||
n_bbs = pre_and_rev_post_order_compute (NULL, rpo, false);
|
||
|
||
changed = true;
|
||
while (changed)
|
||
{
|
||
int i;
|
||
changed = false;
|
||
for (i = 0; i < n_bbs; i++)
|
||
{
|
||
bitmap active;
|
||
bb = BASIC_BLOCK_FOR_FN (cfun, rpo[i]);
|
||
active = (bitmap)bb->aux;
|
||
add_scope_conflicts_1 (bb, work, false);
|
||
if (bitmap_ior_into (active, work))
|
||
changed = true;
|
||
}
|
||
}
|
||
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
add_scope_conflicts_1 (bb, work, true);
|
||
|
||
free (rpo);
|
||
BITMAP_FREE (work);
|
||
FOR_ALL_BB_FN (bb, cfun)
|
||
BITMAP_FREE (bb->aux);
|
||
}
|
||
|
||
/* A subroutine of partition_stack_vars. A comparison function for qsort,
|
||
sorting an array of indices by the properties of the object. */
|
||
|
||
static int
|
||
stack_var_cmp (const void *a, const void *b)
|
||
{
|
||
size_t ia = *(const size_t *)a;
|
||
size_t ib = *(const size_t *)b;
|
||
unsigned int aligna = stack_vars[ia].alignb;
|
||
unsigned int alignb = stack_vars[ib].alignb;
|
||
poly_int64 sizea = stack_vars[ia].size;
|
||
poly_int64 sizeb = stack_vars[ib].size;
|
||
tree decla = stack_vars[ia].decl;
|
||
tree declb = stack_vars[ib].decl;
|
||
bool largea, largeb;
|
||
unsigned int uida, uidb;
|
||
|
||
/* Primary compare on "large" alignment. Large comes first. */
|
||
largea = (aligna * BITS_PER_UNIT > MAX_SUPPORTED_STACK_ALIGNMENT);
|
||
largeb = (alignb * BITS_PER_UNIT > MAX_SUPPORTED_STACK_ALIGNMENT);
|
||
if (largea != largeb)
|
||
return (int)largeb - (int)largea;
|
||
|
||
/* Secondary compare on size, decreasing */
|
||
int diff = compare_sizes_for_sort (sizeb, sizea);
|
||
if (diff != 0)
|
||
return diff;
|
||
|
||
/* Tertiary compare on true alignment, decreasing. */
|
||
if (aligna < alignb)
|
||
return -1;
|
||
if (aligna > alignb)
|
||
return 1;
|
||
|
||
/* Final compare on ID for sort stability, increasing.
|
||
Two SSA names are compared by their version, SSA names come before
|
||
non-SSA names, and two normal decls are compared by their DECL_UID. */
|
||
if (TREE_CODE (decla) == SSA_NAME)
|
||
{
|
||
if (TREE_CODE (declb) == SSA_NAME)
|
||
uida = SSA_NAME_VERSION (decla), uidb = SSA_NAME_VERSION (declb);
|
||
else
|
||
return -1;
|
||
}
|
||
else if (TREE_CODE (declb) == SSA_NAME)
|
||
return 1;
|
||
else
|
||
uida = DECL_UID (decla), uidb = DECL_UID (declb);
|
||
if (uida < uidb)
|
||
return 1;
|
||
if (uida > uidb)
|
||
return -1;
|
||
return 0;
|
||
}
|
||
|
||
struct part_traits : unbounded_int_hashmap_traits <size_t, bitmap> {};
|
||
typedef hash_map<size_t, bitmap, part_traits> part_hashmap;
|
||
|
||
/* If the points-to solution *PI points to variables that are in a partition
|
||
together with other variables add all partition members to the pointed-to
|
||
variables bitmap. */
|
||
|
||
static void
|
||
add_partitioned_vars_to_ptset (struct pt_solution *pt,
|
||
part_hashmap *decls_to_partitions,
|
||
hash_set<bitmap> *visited, bitmap temp)
|
||
{
|
||
bitmap_iterator bi;
|
||
unsigned i;
|
||
bitmap *part;
|
||
|
||
if (pt->anything
|
||
|| pt->vars == NULL
|
||
/* The pointed-to vars bitmap is shared, it is enough to
|
||
visit it once. */
|
||
|| visited->add (pt->vars))
|
||
return;
|
||
|
||
bitmap_clear (temp);
|
||
|
||
/* By using a temporary bitmap to store all members of the partitions
|
||
we have to add we make sure to visit each of the partitions only
|
||
once. */
|
||
EXECUTE_IF_SET_IN_BITMAP (pt->vars, 0, i, bi)
|
||
if ((!temp
|
||
|| !bitmap_bit_p (temp, i))
|
||
&& (part = decls_to_partitions->get (i)))
|
||
bitmap_ior_into (temp, *part);
|
||
if (!bitmap_empty_p (temp))
|
||
bitmap_ior_into (pt->vars, temp);
|
||
}
|
||
|
||
/* Update points-to sets based on partition info, so we can use them on RTL.
|
||
The bitmaps representing stack partitions will be saved until expand,
|
||
where partitioned decls used as bases in memory expressions will be
|
||
rewritten. */
|
||
|
||
static void
|
||
update_alias_info_with_stack_vars (void)
|
||
{
|
||
part_hashmap *decls_to_partitions = NULL;
|
||
size_t i, j;
|
||
tree var = NULL_TREE;
|
||
|
||
for (i = 0; i < stack_vars_num; i++)
|
||
{
|
||
bitmap part = NULL;
|
||
tree name;
|
||
struct ptr_info_def *pi;
|
||
|
||
/* Not interested in partitions with single variable. */
|
||
if (stack_vars[i].representative != i
|
||
|| stack_vars[i].next == EOC)
|
||
continue;
|
||
|
||
if (!decls_to_partitions)
|
||
{
|
||
decls_to_partitions = new part_hashmap;
|
||
cfun->gimple_df->decls_to_pointers = new hash_map<tree, tree>;
|
||
}
|
||
|
||
/* Create an SSA_NAME that points to the partition for use
|
||
as base during alias-oracle queries on RTL for bases that
|
||
have been partitioned. */
|
||
if (var == NULL_TREE)
|
||
var = create_tmp_var (ptr_type_node);
|
||
name = make_ssa_name (var);
|
||
|
||
/* Create bitmaps representing partitions. They will be used for
|
||
points-to sets later, so use GGC alloc. */
|
||
part = BITMAP_GGC_ALLOC ();
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
{
|
||
tree decl = stack_vars[j].decl;
|
||
unsigned int uid = DECL_PT_UID (decl);
|
||
bitmap_set_bit (part, uid);
|
||
decls_to_partitions->put (uid, part);
|
||
cfun->gimple_df->decls_to_pointers->put (decl, name);
|
||
if (TREE_ADDRESSABLE (decl))
|
||
TREE_ADDRESSABLE (name) = 1;
|
||
}
|
||
|
||
/* Make the SSA name point to all partition members. */
|
||
pi = get_ptr_info (name);
|
||
pt_solution_set (&pi->pt, part, false);
|
||
}
|
||
|
||
/* Make all points-to sets that contain one member of a partition
|
||
contain all members of the partition. */
|
||
if (decls_to_partitions)
|
||
{
|
||
unsigned i;
|
||
tree name;
|
||
hash_set<bitmap> visited;
|
||
bitmap temp = BITMAP_ALLOC (&stack_var_bitmap_obstack);
|
||
|
||
FOR_EACH_SSA_NAME (i, name, cfun)
|
||
{
|
||
struct ptr_info_def *pi;
|
||
|
||
if (POINTER_TYPE_P (TREE_TYPE (name))
|
||
&& ((pi = SSA_NAME_PTR_INFO (name)) != NULL))
|
||
add_partitioned_vars_to_ptset (&pi->pt, decls_to_partitions,
|
||
&visited, temp);
|
||
}
|
||
|
||
add_partitioned_vars_to_ptset (&cfun->gimple_df->escaped,
|
||
decls_to_partitions, &visited, temp);
|
||
|
||
delete decls_to_partitions;
|
||
BITMAP_FREE (temp);
|
||
}
|
||
}
|
||
|
||
/* A subroutine of partition_stack_vars. The UNION portion of a UNION/FIND
|
||
partitioning algorithm. Partitions A and B are known to be non-conflicting.
|
||
Merge them into a single partition A. */
|
||
|
||
static void
|
||
union_stack_vars (size_t a, size_t b)
|
||
{
|
||
class stack_var *vb = &stack_vars[b];
|
||
bitmap_iterator bi;
|
||
unsigned u;
|
||
|
||
gcc_assert (stack_vars[b].next == EOC);
|
||
/* Add B to A's partition. */
|
||
stack_vars[b].next = stack_vars[a].next;
|
||
stack_vars[b].representative = a;
|
||
stack_vars[a].next = b;
|
||
|
||
/* Make sure A is big enough to hold B. */
|
||
stack_vars[a].size = upper_bound (stack_vars[a].size, stack_vars[b].size);
|
||
|
||
/* Update the required alignment of partition A to account for B. */
|
||
if (stack_vars[a].alignb < stack_vars[b].alignb)
|
||
stack_vars[a].alignb = stack_vars[b].alignb;
|
||
|
||
/* Update the interference graph and merge the conflicts. */
|
||
if (vb->conflicts)
|
||
{
|
||
EXECUTE_IF_SET_IN_BITMAP (vb->conflicts, 0, u, bi)
|
||
add_stack_var_conflict (a, stack_vars[u].representative);
|
||
BITMAP_FREE (vb->conflicts);
|
||
}
|
||
}
|
||
|
||
/* A subroutine of expand_used_vars. Binpack the variables into
|
||
partitions constrained by the interference graph. The overall
|
||
algorithm used is as follows:
|
||
|
||
Sort the objects by size in descending order.
|
||
For each object A {
|
||
S = size(A)
|
||
O = 0
|
||
loop {
|
||
Look for the largest non-conflicting object B with size <= S.
|
||
UNION (A, B)
|
||
}
|
||
}
|
||
*/
|
||
|
||
static void
|
||
partition_stack_vars (void)
|
||
{
|
||
size_t si, sj, n = stack_vars_num;
|
||
|
||
stack_vars_sorted = XNEWVEC (size_t, stack_vars_num);
|
||
for (si = 0; si < n; ++si)
|
||
stack_vars_sorted[si] = si;
|
||
|
||
if (n == 1)
|
||
return;
|
||
|
||
qsort (stack_vars_sorted, n, sizeof (size_t), stack_var_cmp);
|
||
|
||
for (si = 0; si < n; ++si)
|
||
{
|
||
size_t i = stack_vars_sorted[si];
|
||
unsigned int ialign = stack_vars[i].alignb;
|
||
poly_int64 isize = stack_vars[i].size;
|
||
|
||
/* Ignore objects that aren't partition representatives. If we
|
||
see a var that is not a partition representative, it must
|
||
have been merged earlier. */
|
||
if (stack_vars[i].representative != i)
|
||
continue;
|
||
|
||
for (sj = si + 1; sj < n; ++sj)
|
||
{
|
||
size_t j = stack_vars_sorted[sj];
|
||
unsigned int jalign = stack_vars[j].alignb;
|
||
poly_int64 jsize = stack_vars[j].size;
|
||
|
||
/* Ignore objects that aren't partition representatives. */
|
||
if (stack_vars[j].representative != j)
|
||
continue;
|
||
|
||
/* Do not mix objects of "small" (supported) alignment
|
||
and "large" (unsupported) alignment. */
|
||
if ((ialign * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
!= (jalign * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT))
|
||
break;
|
||
|
||
/* For Address Sanitizer do not mix objects with different
|
||
sizes, as the shorter vars wouldn't be adequately protected.
|
||
Don't do that for "large" (unsupported) alignment objects,
|
||
those aren't protected anyway. */
|
||
if (asan_sanitize_stack_p ()
|
||
&& maybe_ne (isize, jsize)
|
||
&& ialign * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
break;
|
||
|
||
/* Ignore conflicting objects. */
|
||
if (stack_var_conflict_p (i, j))
|
||
continue;
|
||
|
||
/* UNION the objects, placing J at OFFSET. */
|
||
union_stack_vars (i, j);
|
||
}
|
||
}
|
||
|
||
update_alias_info_with_stack_vars ();
|
||
}
|
||
|
||
/* A debugging aid for expand_used_vars. Dump the generated partitions. */
|
||
|
||
static void
|
||
dump_stack_var_partition (void)
|
||
{
|
||
size_t si, i, j, n = stack_vars_num;
|
||
|
||
for (si = 0; si < n; ++si)
|
||
{
|
||
i = stack_vars_sorted[si];
|
||
|
||
/* Skip variables that aren't partition representatives, for now. */
|
||
if (stack_vars[i].representative != i)
|
||
continue;
|
||
|
||
fprintf (dump_file, "Partition %lu: size ", (unsigned long) i);
|
||
print_dec (stack_vars[i].size, dump_file);
|
||
fprintf (dump_file, " align %u\n", stack_vars[i].alignb);
|
||
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
{
|
||
fputc ('\t', dump_file);
|
||
print_generic_expr (dump_file, stack_vars[j].decl, dump_flags);
|
||
}
|
||
fputc ('\n', dump_file);
|
||
}
|
||
}
|
||
|
||
/* Assign rtl to DECL at BASE + OFFSET. */
|
||
|
||
static void
|
||
expand_one_stack_var_at (tree decl, rtx base, unsigned base_align,
|
||
poly_int64 offset)
|
||
{
|
||
unsigned align;
|
||
rtx x;
|
||
|
||
/* If this fails, we've overflowed the stack frame. Error nicely? */
|
||
gcc_assert (known_eq (offset, trunc_int_for_mode (offset, Pmode)));
|
||
|
||
x = plus_constant (Pmode, base, offset);
|
||
x = gen_rtx_MEM (TREE_CODE (decl) == SSA_NAME
|
||
? TYPE_MODE (TREE_TYPE (decl))
|
||
: DECL_MODE (SSAVAR (decl)), x);
|
||
|
||
if (TREE_CODE (decl) != SSA_NAME)
|
||
{
|
||
/* Set alignment we actually gave this decl if it isn't an SSA name.
|
||
If it is we generate stack slots only accidentally so it isn't as
|
||
important, we'll simply use the alignment that is already set. */
|
||
if (base == virtual_stack_vars_rtx)
|
||
offset -= frame_phase;
|
||
align = known_alignment (offset);
|
||
align *= BITS_PER_UNIT;
|
||
if (align == 0 || align > base_align)
|
||
align = base_align;
|
||
|
||
/* One would think that we could assert that we're not decreasing
|
||
alignment here, but (at least) the i386 port does exactly this
|
||
via the MINIMUM_ALIGNMENT hook. */
|
||
|
||
SET_DECL_ALIGN (decl, align);
|
||
DECL_USER_ALIGN (decl) = 0;
|
||
}
|
||
|
||
set_rtl (decl, x);
|
||
}
|
||
|
||
class stack_vars_data
|
||
{
|
||
public:
|
||
/* Vector of offset pairs, always end of some padding followed
|
||
by start of the padding that needs Address Sanitizer protection.
|
||
The vector is in reversed, highest offset pairs come first. */
|
||
auto_vec<HOST_WIDE_INT> asan_vec;
|
||
|
||
/* Vector of partition representative decls in between the paddings. */
|
||
auto_vec<tree> asan_decl_vec;
|
||
|
||
/* Base pseudo register for Address Sanitizer protected automatic vars. */
|
||
rtx asan_base;
|
||
|
||
/* Alignment needed for the Address Sanitizer protected automatic vars. */
|
||
unsigned int asan_alignb;
|
||
};
|
||
|
||
/* A subroutine of expand_used_vars. Give each partition representative
|
||
a unique location within the stack frame. Update each partition member
|
||
with that location. */
|
||
|
||
static void
|
||
expand_stack_vars (bool (*pred) (size_t), class stack_vars_data *data)
|
||
{
|
||
size_t si, i, j, n = stack_vars_num;
|
||
poly_uint64 large_size = 0, large_alloc = 0;
|
||
rtx large_base = NULL;
|
||
unsigned large_align = 0;
|
||
bool large_allocation_done = false;
|
||
tree decl;
|
||
|
||
/* Determine if there are any variables requiring "large" alignment.
|
||
Since these are dynamically allocated, we only process these if
|
||
no predicate involved. */
|
||
large_align = stack_vars[stack_vars_sorted[0]].alignb * BITS_PER_UNIT;
|
||
if (pred == NULL && large_align > MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
{
|
||
/* Find the total size of these variables. */
|
||
for (si = 0; si < n; ++si)
|
||
{
|
||
unsigned alignb;
|
||
|
||
i = stack_vars_sorted[si];
|
||
alignb = stack_vars[i].alignb;
|
||
|
||
/* All "large" alignment decls come before all "small" alignment
|
||
decls, but "large" alignment decls are not sorted based on
|
||
their alignment. Increase large_align to track the largest
|
||
required alignment. */
|
||
if ((alignb * BITS_PER_UNIT) > large_align)
|
||
large_align = alignb * BITS_PER_UNIT;
|
||
|
||
/* Stop when we get to the first decl with "small" alignment. */
|
||
if (alignb * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
break;
|
||
|
||
/* Skip variables that aren't partition representatives. */
|
||
if (stack_vars[i].representative != i)
|
||
continue;
|
||
|
||
/* Skip variables that have already had rtl assigned. See also
|
||
add_stack_var where we perpetrate this pc_rtx hack. */
|
||
decl = stack_vars[i].decl;
|
||
if (TREE_CODE (decl) == SSA_NAME
|
||
? SA.partition_to_pseudo[var_to_partition (SA.map, decl)] != NULL_RTX
|
||
: DECL_RTL (decl) != pc_rtx)
|
||
continue;
|
||
|
||
large_size = aligned_upper_bound (large_size, alignb);
|
||
large_size += stack_vars[i].size;
|
||
}
|
||
}
|
||
|
||
for (si = 0; si < n; ++si)
|
||
{
|
||
rtx base;
|
||
unsigned base_align, alignb;
|
||
poly_int64 offset;
|
||
|
||
i = stack_vars_sorted[si];
|
||
|
||
/* Skip variables that aren't partition representatives, for now. */
|
||
if (stack_vars[i].representative != i)
|
||
continue;
|
||
|
||
/* Skip variables that have already had rtl assigned. See also
|
||
add_stack_var where we perpetrate this pc_rtx hack. */
|
||
decl = stack_vars[i].decl;
|
||
if (TREE_CODE (decl) == SSA_NAME
|
||
? SA.partition_to_pseudo[var_to_partition (SA.map, decl)] != NULL_RTX
|
||
: DECL_RTL (decl) != pc_rtx)
|
||
continue;
|
||
|
||
/* Check the predicate to see whether this variable should be
|
||
allocated in this pass. */
|
||
if (pred && !pred (i))
|
||
continue;
|
||
|
||
alignb = stack_vars[i].alignb;
|
||
if (alignb * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
{
|
||
base = virtual_stack_vars_rtx;
|
||
/* ASAN description strings don't yet have a syntax for expressing
|
||
polynomial offsets. */
|
||
HOST_WIDE_INT prev_offset;
|
||
if (asan_sanitize_stack_p ()
|
||
&& pred
|
||
&& frame_offset.is_constant (&prev_offset)
|
||
&& stack_vars[i].size.is_constant ())
|
||
{
|
||
if (data->asan_vec.is_empty ())
|
||
{
|
||
alloc_stack_frame_space (0, ASAN_RED_ZONE_SIZE);
|
||
prev_offset = frame_offset.to_constant ();
|
||
}
|
||
prev_offset = align_base (prev_offset,
|
||
ASAN_MIN_RED_ZONE_SIZE,
|
||
!FRAME_GROWS_DOWNWARD);
|
||
tree repr_decl = NULL_TREE;
|
||
unsigned HOST_WIDE_INT size
|
||
= asan_var_and_redzone_size (stack_vars[i].size.to_constant ());
|
||
if (data->asan_vec.is_empty ())
|
||
size = MAX (size, ASAN_RED_ZONE_SIZE);
|
||
|
||
unsigned HOST_WIDE_INT alignment = MAX (alignb,
|
||
ASAN_MIN_RED_ZONE_SIZE);
|
||
offset = alloc_stack_frame_space (size, alignment);
|
||
|
||
data->asan_vec.safe_push (prev_offset);
|
||
/* Allocating a constant amount of space from a constant
|
||
starting offset must give a constant result. */
|
||
data->asan_vec.safe_push ((offset + stack_vars[i].size)
|
||
.to_constant ());
|
||
/* Find best representative of the partition.
|
||
Prefer those with DECL_NAME, even better
|
||
satisfying asan_protect_stack_decl predicate. */
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
if (asan_protect_stack_decl (stack_vars[j].decl)
|
||
&& DECL_NAME (stack_vars[j].decl))
|
||
{
|
||
repr_decl = stack_vars[j].decl;
|
||
break;
|
||
}
|
||
else if (repr_decl == NULL_TREE
|
||
&& DECL_P (stack_vars[j].decl)
|
||
&& DECL_NAME (stack_vars[j].decl))
|
||
repr_decl = stack_vars[j].decl;
|
||
if (repr_decl == NULL_TREE)
|
||
repr_decl = stack_vars[i].decl;
|
||
data->asan_decl_vec.safe_push (repr_decl);
|
||
|
||
/* Make sure a representative is unpoison if another
|
||
variable in the partition is handled by
|
||
use-after-scope sanitization. */
|
||
if (asan_handled_variables != NULL
|
||
&& !asan_handled_variables->contains (repr_decl))
|
||
{
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
if (asan_handled_variables->contains (stack_vars[j].decl))
|
||
break;
|
||
if (j != EOC)
|
||
asan_handled_variables->add (repr_decl);
|
||
}
|
||
|
||
data->asan_alignb = MAX (data->asan_alignb, alignb);
|
||
if (data->asan_base == NULL)
|
||
data->asan_base = gen_reg_rtx (Pmode);
|
||
base = data->asan_base;
|
||
|
||
if (!STRICT_ALIGNMENT)
|
||
base_align = crtl->max_used_stack_slot_alignment;
|
||
else
|
||
base_align = MAX (crtl->max_used_stack_slot_alignment,
|
||
GET_MODE_ALIGNMENT (SImode)
|
||
<< ASAN_SHADOW_SHIFT);
|
||
}
|
||
else
|
||
{
|
||
offset = alloc_stack_frame_space (stack_vars[i].size, alignb);
|
||
base_align = crtl->max_used_stack_slot_alignment;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Large alignment is only processed in the last pass. */
|
||
if (pred)
|
||
continue;
|
||
|
||
/* If there were any variables requiring "large" alignment, allocate
|
||
space. */
|
||
if (maybe_ne (large_size, 0U) && ! large_allocation_done)
|
||
{
|
||
poly_int64 loffset;
|
||
rtx large_allocsize;
|
||
|
||
large_allocsize = gen_int_mode (large_size, Pmode);
|
||
get_dynamic_stack_size (&large_allocsize, 0, large_align, NULL);
|
||
loffset = alloc_stack_frame_space
|
||
(rtx_to_poly_int64 (large_allocsize),
|
||
PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT);
|
||
large_base = get_dynamic_stack_base (loffset, large_align);
|
||
large_allocation_done = true;
|
||
}
|
||
gcc_assert (large_base != NULL);
|
||
|
||
large_alloc = aligned_upper_bound (large_alloc, alignb);
|
||
offset = large_alloc;
|
||
large_alloc += stack_vars[i].size;
|
||
|
||
base = large_base;
|
||
base_align = large_align;
|
||
}
|
||
|
||
/* Create rtl for each variable based on their location within the
|
||
partition. */
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
{
|
||
expand_one_stack_var_at (stack_vars[j].decl,
|
||
base, base_align,
|
||
offset);
|
||
}
|
||
}
|
||
|
||
gcc_assert (known_eq (large_alloc, large_size));
|
||
}
|
||
|
||
/* Take into account all sizes of partitions and reset DECL_RTLs. */
|
||
static poly_uint64
|
||
account_stack_vars (void)
|
||
{
|
||
size_t si, j, i, n = stack_vars_num;
|
||
poly_uint64 size = 0;
|
||
|
||
for (si = 0; si < n; ++si)
|
||
{
|
||
i = stack_vars_sorted[si];
|
||
|
||
/* Skip variables that aren't partition representatives, for now. */
|
||
if (stack_vars[i].representative != i)
|
||
continue;
|
||
|
||
size += stack_vars[i].size;
|
||
for (j = i; j != EOC; j = stack_vars[j].next)
|
||
set_rtl (stack_vars[j].decl, NULL);
|
||
}
|
||
return size;
|
||
}
|
||
|
||
/* Record the RTL assignment X for the default def of PARM. */
|
||
|
||
extern void
|
||
set_parm_rtl (tree parm, rtx x)
|
||
{
|
||
gcc_assert (TREE_CODE (parm) == PARM_DECL
|
||
|| TREE_CODE (parm) == RESULT_DECL);
|
||
|
||
if (x && !MEM_P (x))
|
||
{
|
||
unsigned int align = MINIMUM_ALIGNMENT (TREE_TYPE (parm),
|
||
TYPE_MODE (TREE_TYPE (parm)),
|
||
TYPE_ALIGN (TREE_TYPE (parm)));
|
||
|
||
/* If the variable alignment is very large we'll dynamicaly
|
||
allocate it, which means that in-frame portion is just a
|
||
pointer. ??? We've got a pseudo for sure here, do we
|
||
actually dynamically allocate its spilling area if needed?
|
||
??? Isn't it a problem when Pmode alignment also exceeds
|
||
MAX_SUPPORTED_STACK_ALIGNMENT, as can happen on cris and lm32? */
|
||
if (align > MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
align = GET_MODE_ALIGNMENT (Pmode);
|
||
|
||
record_alignment_for_reg_var (align);
|
||
}
|
||
|
||
tree ssa = ssa_default_def (cfun, parm);
|
||
if (!ssa)
|
||
return set_rtl (parm, x);
|
||
|
||
int part = var_to_partition (SA.map, ssa);
|
||
gcc_assert (part != NO_PARTITION);
|
||
|
||
bool changed = bitmap_bit_p (SA.partitions_for_parm_default_defs, part);
|
||
gcc_assert (changed);
|
||
|
||
set_rtl (ssa, x);
|
||
gcc_assert (DECL_RTL (parm) == x);
|
||
}
|
||
|
||
/* A subroutine of expand_one_var. Called to immediately assign rtl
|
||
to a variable to be allocated in the stack frame. */
|
||
|
||
static void
|
||
expand_one_stack_var_1 (tree var)
|
||
{
|
||
poly_uint64 size;
|
||
poly_int64 offset;
|
||
unsigned byte_align;
|
||
|
||
if (TREE_CODE (var) == SSA_NAME)
|
||
{
|
||
tree type = TREE_TYPE (var);
|
||
size = tree_to_poly_uint64 (TYPE_SIZE_UNIT (type));
|
||
byte_align = TYPE_ALIGN_UNIT (type);
|
||
}
|
||
else
|
||
{
|
||
size = tree_to_poly_uint64 (DECL_SIZE_UNIT (var));
|
||
byte_align = align_local_variable (var, true);
|
||
}
|
||
|
||
/* We handle highly aligned variables in expand_stack_vars. */
|
||
gcc_assert (byte_align * BITS_PER_UNIT <= MAX_SUPPORTED_STACK_ALIGNMENT);
|
||
|
||
offset = alloc_stack_frame_space (size, byte_align);
|
||
|
||
expand_one_stack_var_at (var, virtual_stack_vars_rtx,
|
||
crtl->max_used_stack_slot_alignment, offset);
|
||
}
|
||
|
||
/* Wrapper for expand_one_stack_var_1 that checks SSA_NAMEs are
|
||
already assigned some MEM. */
|
||
|
||
static void
|
||
expand_one_stack_var (tree var)
|
||
{
|
||
if (TREE_CODE (var) == SSA_NAME)
|
||
{
|
||
int part = var_to_partition (SA.map, var);
|
||
if (part != NO_PARTITION)
|
||
{
|
||
rtx x = SA.partition_to_pseudo[part];
|
||
gcc_assert (x);
|
||
gcc_assert (MEM_P (x));
|
||
return;
|
||
}
|
||
}
|
||
|
||
return expand_one_stack_var_1 (var);
|
||
}
|
||
|
||
/* A subroutine of expand_one_var. Called to assign rtl to a VAR_DECL
|
||
that will reside in a hard register. */
|
||
|
||
static void
|
||
expand_one_hard_reg_var (tree var)
|
||
{
|
||
rest_of_decl_compilation (var, 0, 0);
|
||
}
|
||
|
||
/* Record the alignment requirements of some variable assigned to a
|
||
pseudo. */
|
||
|
||
static void
|
||
record_alignment_for_reg_var (unsigned int align)
|
||
{
|
||
if (SUPPORTS_STACK_ALIGNMENT
|
||
&& crtl->stack_alignment_estimated < align)
|
||
{
|
||
/* stack_alignment_estimated shouldn't change after stack
|
||
realign decision made */
|
||
gcc_assert (!crtl->stack_realign_processed);
|
||
crtl->stack_alignment_estimated = align;
|
||
}
|
||
|
||
/* stack_alignment_needed > PREFERRED_STACK_BOUNDARY is permitted.
|
||
So here we only make sure stack_alignment_needed >= align. */
|
||
if (crtl->stack_alignment_needed < align)
|
||
crtl->stack_alignment_needed = align;
|
||
if (crtl->max_used_stack_slot_alignment < align)
|
||
crtl->max_used_stack_slot_alignment = align;
|
||
}
|
||
|
||
/* Create RTL for an SSA partition. */
|
||
|
||
static void
|
||
expand_one_ssa_partition (tree var)
|
||
{
|
||
int part = var_to_partition (SA.map, var);
|
||
gcc_assert (part != NO_PARTITION);
|
||
|
||
if (SA.partition_to_pseudo[part])
|
||
return;
|
||
|
||
unsigned int align = MINIMUM_ALIGNMENT (TREE_TYPE (var),
|
||
TYPE_MODE (TREE_TYPE (var)),
|
||
TYPE_ALIGN (TREE_TYPE (var)));
|
||
|
||
/* If the variable alignment is very large we'll dynamicaly allocate
|
||
it, which means that in-frame portion is just a pointer. */
|
||
if (align > MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
align = GET_MODE_ALIGNMENT (Pmode);
|
||
|
||
record_alignment_for_reg_var (align);
|
||
|
||
if (!use_register_for_decl (var))
|
||
{
|
||
if (defer_stack_allocation (var, true))
|
||
add_stack_var (var, true);
|
||
else
|
||
expand_one_stack_var_1 (var);
|
||
return;
|
||
}
|
||
|
||
machine_mode reg_mode = promote_ssa_mode (var, NULL);
|
||
rtx x = gen_reg_rtx (reg_mode);
|
||
|
||
set_rtl (var, x);
|
||
|
||
/* For a promoted variable, X will not be used directly but wrapped in a
|
||
SUBREG with SUBREG_PROMOTED_VAR_P set, which means that the RTL land
|
||
will assume that its upper bits can be inferred from its lower bits.
|
||
Therefore, if X isn't initialized on every path from the entry, then
|
||
we must do it manually in order to fulfill the above assumption. */
|
||
if (reg_mode != TYPE_MODE (TREE_TYPE (var))
|
||
&& bitmap_bit_p (SA.partitions_for_undefined_values, part))
|
||
emit_move_insn (x, CONST0_RTX (reg_mode));
|
||
}
|
||
|
||
/* Record the association between the RTL generated for partition PART
|
||
and the underlying variable of the SSA_NAME VAR. */
|
||
|
||
static void
|
||
adjust_one_expanded_partition_var (tree var)
|
||
{
|
||
if (!var)
|
||
return;
|
||
|
||
tree decl = SSA_NAME_VAR (var);
|
||
|
||
int part = var_to_partition (SA.map, var);
|
||
if (part == NO_PARTITION)
|
||
return;
|
||
|
||
rtx x = SA.partition_to_pseudo[part];
|
||
|
||
gcc_assert (x);
|
||
|
||
set_rtl (var, x);
|
||
|
||
if (!REG_P (x))
|
||
return;
|
||
|
||
/* Note if the object is a user variable. */
|
||
if (decl && !DECL_ARTIFICIAL (decl))
|
||
mark_user_reg (x);
|
||
|
||
if (POINTER_TYPE_P (decl ? TREE_TYPE (decl) : TREE_TYPE (var)))
|
||
mark_reg_pointer (x, get_pointer_alignment (var));
|
||
}
|
||
|
||
/* A subroutine of expand_one_var. Called to assign rtl to a VAR_DECL
|
||
that will reside in a pseudo register. */
|
||
|
||
static void
|
||
expand_one_register_var (tree var)
|
||
{
|
||
if (TREE_CODE (var) == SSA_NAME)
|
||
{
|
||
int part = var_to_partition (SA.map, var);
|
||
if (part != NO_PARTITION)
|
||
{
|
||
rtx x = SA.partition_to_pseudo[part];
|
||
gcc_assert (x);
|
||
gcc_assert (REG_P (x));
|
||
return;
|
||
}
|
||
gcc_unreachable ();
|
||
}
|
||
|
||
tree decl = var;
|
||
tree type = TREE_TYPE (decl);
|
||
machine_mode reg_mode = promote_decl_mode (decl, NULL);
|
||
rtx x = gen_reg_rtx (reg_mode);
|
||
|
||
set_rtl (var, x);
|
||
|
||
/* Note if the object is a user variable. */
|
||
if (!DECL_ARTIFICIAL (decl))
|
||
mark_user_reg (x);
|
||
|
||
if (POINTER_TYPE_P (type))
|
||
mark_reg_pointer (x, get_pointer_alignment (var));
|
||
}
|
||
|
||
/* A subroutine of expand_one_var. Called to assign rtl to a VAR_DECL that
|
||
has some associated error, e.g. its type is error-mark. We just need
|
||
to pick something that won't crash the rest of the compiler. */
|
||
|
||
static void
|
||
expand_one_error_var (tree var)
|
||
{
|
||
machine_mode mode = DECL_MODE (var);
|
||
rtx x;
|
||
|
||
if (mode == BLKmode)
|
||
x = gen_rtx_MEM (BLKmode, const0_rtx);
|
||
else if (mode == VOIDmode)
|
||
x = const0_rtx;
|
||
else
|
||
x = gen_reg_rtx (mode);
|
||
|
||
SET_DECL_RTL (var, x);
|
||
}
|
||
|
||
/* A subroutine of expand_one_var. VAR is a variable that will be
|
||
allocated to the local stack frame. Return true if we wish to
|
||
add VAR to STACK_VARS so that it will be coalesced with other
|
||
variables. Return false to allocate VAR immediately.
|
||
|
||
This function is used to reduce the number of variables considered
|
||
for coalescing, which reduces the size of the quadratic problem. */
|
||
|
||
static bool
|
||
defer_stack_allocation (tree var, bool toplevel)
|
||
{
|
||
tree size_unit = TREE_CODE (var) == SSA_NAME
|
||
? TYPE_SIZE_UNIT (TREE_TYPE (var))
|
||
: DECL_SIZE_UNIT (var);
|
||
poly_uint64 size;
|
||
|
||
/* Whether the variable is small enough for immediate allocation not to be
|
||
a problem with regard to the frame size. */
|
||
bool smallish
|
||
= (poly_int_tree_p (size_unit, &size)
|
||
&& (estimated_poly_value (size)
|
||
< param_min_size_for_stack_sharing));
|
||
|
||
/* If stack protection is enabled, *all* stack variables must be deferred,
|
||
so that we can re-order the strings to the top of the frame.
|
||
Similarly for Address Sanitizer. */
|
||
if (flag_stack_protect || asan_sanitize_stack_p ())
|
||
return true;
|
||
|
||
unsigned int align = TREE_CODE (var) == SSA_NAME
|
||
? TYPE_ALIGN (TREE_TYPE (var))
|
||
: DECL_ALIGN (var);
|
||
|
||
/* We handle "large" alignment via dynamic allocation. We want to handle
|
||
this extra complication in only one place, so defer them. */
|
||
if (align > MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
return true;
|
||
|
||
bool ignored = TREE_CODE (var) == SSA_NAME
|
||
? !SSAVAR (var) || DECL_IGNORED_P (SSA_NAME_VAR (var))
|
||
: DECL_IGNORED_P (var);
|
||
|
||
/* When optimization is enabled, DECL_IGNORED_P variables originally scoped
|
||
might be detached from their block and appear at toplevel when we reach
|
||
here. We want to coalesce them with variables from other blocks when
|
||
the immediate contribution to the frame size would be noticeable. */
|
||
if (toplevel && optimize > 0 && ignored && !smallish)
|
||
return true;
|
||
|
||
/* Variables declared in the outermost scope automatically conflict
|
||
with every other variable. The only reason to want to defer them
|
||
at all is that, after sorting, we can more efficiently pack
|
||
small variables in the stack frame. Continue to defer at -O2. */
|
||
if (toplevel && optimize < 2)
|
||
return false;
|
||
|
||
/* Without optimization, *most* variables are allocated from the
|
||
stack, which makes the quadratic problem large exactly when we
|
||
want compilation to proceed as quickly as possible. On the
|
||
other hand, we don't want the function's stack frame size to
|
||
get completely out of hand. So we avoid adding scalars and
|
||
"small" aggregates to the list at all. */
|
||
if (optimize == 0 && smallish)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
/* A subroutine of expand_used_vars. Expand one variable according to
|
||
its flavor. Variables to be placed on the stack are not actually
|
||
expanded yet, merely recorded.
|
||
When REALLY_EXPAND is false, only add stack values to be allocated.
|
||
Return stack usage this variable is supposed to take.
|
||
*/
|
||
|
||
static poly_uint64
|
||
expand_one_var (tree var, bool toplevel, bool really_expand)
|
||
{
|
||
unsigned int align = BITS_PER_UNIT;
|
||
tree origvar = var;
|
||
|
||
var = SSAVAR (var);
|
||
|
||
if (TREE_TYPE (var) != error_mark_node && VAR_P (var))
|
||
{
|
||
if (is_global_var (var))
|
||
return 0;
|
||
|
||
/* Because we don't know if VAR will be in register or on stack,
|
||
we conservatively assume it will be on stack even if VAR is
|
||
eventually put into register after RA pass. For non-automatic
|
||
variables, which won't be on stack, we collect alignment of
|
||
type and ignore user specified alignment. Similarly for
|
||
SSA_NAMEs for which use_register_for_decl returns true. */
|
||
if (TREE_STATIC (var)
|
||
|| DECL_EXTERNAL (var)
|
||
|| (TREE_CODE (origvar) == SSA_NAME && use_register_for_decl (var)))
|
||
align = MINIMUM_ALIGNMENT (TREE_TYPE (var),
|
||
TYPE_MODE (TREE_TYPE (var)),
|
||
TYPE_ALIGN (TREE_TYPE (var)));
|
||
else if (DECL_HAS_VALUE_EXPR_P (var)
|
||
|| (DECL_RTL_SET_P (var) && MEM_P (DECL_RTL (var))))
|
||
/* Don't consider debug only variables with DECL_HAS_VALUE_EXPR_P set
|
||
or variables which were assigned a stack slot already by
|
||
expand_one_stack_var_at - in the latter case DECL_ALIGN has been
|
||
changed from the offset chosen to it. */
|
||
align = crtl->stack_alignment_estimated;
|
||
else
|
||
align = MINIMUM_ALIGNMENT (var, DECL_MODE (var), DECL_ALIGN (var));
|
||
|
||
/* If the variable alignment is very large we'll dynamicaly allocate
|
||
it, which means that in-frame portion is just a pointer. */
|
||
if (align > MAX_SUPPORTED_STACK_ALIGNMENT)
|
||
align = GET_MODE_ALIGNMENT (Pmode);
|
||
}
|
||
|
||
record_alignment_for_reg_var (align);
|
||
|
||
poly_uint64 size;
|
||
if (TREE_CODE (origvar) == SSA_NAME)
|
||
{
|
||
gcc_assert (!VAR_P (var)
|
||
|| (!DECL_EXTERNAL (var)
|
||
&& !DECL_HAS_VALUE_EXPR_P (var)
|
||
&& !TREE_STATIC (var)
|
||
&& TREE_TYPE (var) != error_mark_node
|
||
&& !DECL_HARD_REGISTER (var)
|
||
&& really_expand));
|
||
}
|
||
if (!VAR_P (var) && TREE_CODE (origvar) != SSA_NAME)
|
||
;
|
||
else if (DECL_EXTERNAL (var))
|
||
;
|
||
else if (DECL_HAS_VALUE_EXPR_P (var))
|
||
;
|
||
else if (TREE_STATIC (var))
|
||
;
|
||
else if (TREE_CODE (origvar) != SSA_NAME && DECL_RTL_SET_P (var))
|
||
;
|
||
else if (TREE_TYPE (var) == error_mark_node)
|
||
{
|
||
if (really_expand)
|
||
expand_one_error_var (var);
|
||
}
|
||
else if (VAR_P (var) && DECL_HARD_REGISTER (var))
|
||
{
|
||
if (really_expand)
|
||
{
|
||
expand_one_hard_reg_var (var);
|
||
if (!DECL_HARD_REGISTER (var))
|
||
/* Invalid register specification. */
|
||
expand_one_error_var (var);
|
||
}
|
||
}
|
||
else if (use_register_for_decl (var))
|
||
{
|
||
if (really_expand)
|
||
expand_one_register_var (origvar);
|
||
}
|
||
else if (!poly_int_tree_p (DECL_SIZE_UNIT (var), &size)
|
||
|| !valid_constant_size_p (DECL_SIZE_UNIT (var)))
|
||
{
|
||
/* Reject variables which cover more than half of the address-space. */
|
||
if (really_expand)
|
||
{
|
||
if (DECL_NONLOCAL_FRAME (var))
|
||
error_at (DECL_SOURCE_LOCATION (current_function_decl),
|
||
"total size of local objects is too large");
|
||
else
|
||
error_at (DECL_SOURCE_LOCATION (var),
|
||
"size of variable %q+D is too large", var);
|
||
expand_one_error_var (var);
|
||
}
|
||
}
|
||
else if (defer_stack_allocation (var, toplevel))
|
||
add_stack_var (origvar, really_expand);
|
||
else
|
||
{
|
||
if (really_expand)
|
||
{
|
||
if (lookup_attribute ("naked",
|
||
DECL_ATTRIBUTES (current_function_decl)))
|
||
error ("cannot allocate stack for variable %q+D, naked function",
|
||
var);
|
||
|
||
expand_one_stack_var (origvar);
|
||
}
|
||
return size;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* A subroutine of expand_used_vars. Walk down through the BLOCK tree
|
||
expanding variables. Those variables that can be put into registers
|
||
are allocated pseudos; those that can't are put on the stack.
|
||
|
||
TOPLEVEL is true if this is the outermost BLOCK. */
|
||
|
||
static void
|
||
expand_used_vars_for_block (tree block, bool toplevel)
|
||
{
|
||
tree t;
|
||
|
||
/* Expand all variables at this level. */
|
||
for (t = BLOCK_VARS (block); t ; t = DECL_CHAIN (t))
|
||
if (TREE_USED (t)
|
||
&& ((!VAR_P (t) && TREE_CODE (t) != RESULT_DECL)
|
||
|| !DECL_NONSHAREABLE (t)))
|
||
expand_one_var (t, toplevel, true);
|
||
|
||
/* Expand all variables at containing levels. */
|
||
for (t = BLOCK_SUBBLOCKS (block); t ; t = BLOCK_CHAIN (t))
|
||
expand_used_vars_for_block (t, false);
|
||
}
|
||
|
||
/* A subroutine of expand_used_vars. Walk down through the BLOCK tree
|
||
and clear TREE_USED on all local variables. */
|
||
|
||
static void
|
||
clear_tree_used (tree block)
|
||
{
|
||
tree t;
|
||
|
||
for (t = BLOCK_VARS (block); t ; t = DECL_CHAIN (t))
|
||
/* if (!TREE_STATIC (t) && !DECL_EXTERNAL (t)) */
|
||
if ((!VAR_P (t) && TREE_CODE (t) != RESULT_DECL)
|
||
|| !DECL_NONSHAREABLE (t))
|
||
TREE_USED (t) = 0;
|
||
|
||
for (t = BLOCK_SUBBLOCKS (block); t ; t = BLOCK_CHAIN (t))
|
||
clear_tree_used (t);
|
||
}
|
||
|
||
enum {
|
||
SPCT_FLAG_DEFAULT = 1,
|
||
SPCT_FLAG_ALL = 2,
|
||
SPCT_FLAG_STRONG = 3,
|
||
SPCT_FLAG_EXPLICIT = 4
|
||
};
|
||
|
||
/* Examine TYPE and determine a bit mask of the following features. */
|
||
|
||
#define SPCT_HAS_LARGE_CHAR_ARRAY 1
|
||
#define SPCT_HAS_SMALL_CHAR_ARRAY 2
|
||
#define SPCT_HAS_ARRAY 4
|
||
#define SPCT_HAS_AGGREGATE 8
|
||
|
||
static unsigned int
|
||
stack_protect_classify_type (tree type)
|
||
{
|
||
unsigned int ret = 0;
|
||
tree t;
|
||
|
||
switch (TREE_CODE (type))
|
||
{
|
||
case ARRAY_TYPE:
|
||
t = TYPE_MAIN_VARIANT (TREE_TYPE (type));
|
||
if (t == char_type_node
|
||
|| t == signed_char_type_node
|
||
|| t == unsigned_char_type_node)
|
||
{
|
||
unsigned HOST_WIDE_INT max = param_ssp_buffer_size;
|
||
unsigned HOST_WIDE_INT len;
|
||
|
||
if (!TYPE_SIZE_UNIT (type)
|
||
|| !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type)))
|
||
len = max;
|
||
else
|
||
len = tree_to_uhwi (TYPE_SIZE_UNIT (type));
|
||
|
||
if (len < max)
|
||
ret = SPCT_HAS_SMALL_CHAR_ARRAY | SPCT_HAS_ARRAY;
|
||
else
|
||
ret = SPCT_HAS_LARGE_CHAR_ARRAY | SPCT_HAS_ARRAY;
|
||
}
|
||
else
|
||
ret = SPCT_HAS_ARRAY;
|
||
break;
|
||
|
||
case UNION_TYPE:
|
||
case QUAL_UNION_TYPE:
|
||
case RECORD_TYPE:
|
||
ret = SPCT_HAS_AGGREGATE;
|
||
for (t = TYPE_FIELDS (type); t ; t = TREE_CHAIN (t))
|
||
if (TREE_CODE (t) == FIELD_DECL)
|
||
ret |= stack_protect_classify_type (TREE_TYPE (t));
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/* Return nonzero if DECL should be segregated into the "vulnerable" upper
|
||
part of the local stack frame. Remember if we ever return nonzero for
|
||
any variable in this function. The return value is the phase number in
|
||
which the variable should be allocated. */
|
||
|
||
static int
|
||
stack_protect_decl_phase (tree decl)
|
||
{
|
||
unsigned int bits = stack_protect_classify_type (TREE_TYPE (decl));
|
||
int ret = 0;
|
||
|
||
if (bits & SPCT_HAS_SMALL_CHAR_ARRAY)
|
||
has_short_buffer = true;
|
||
|
||
if (flag_stack_protect == SPCT_FLAG_ALL
|
||
|| flag_stack_protect == SPCT_FLAG_STRONG
|
||
|| (flag_stack_protect == SPCT_FLAG_EXPLICIT
|
||
&& lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl))))
|
||
{
|
||
if ((bits & (SPCT_HAS_SMALL_CHAR_ARRAY | SPCT_HAS_LARGE_CHAR_ARRAY))
|
||
&& !(bits & SPCT_HAS_AGGREGATE))
|
||
ret = 1;
|
||
else if (bits & SPCT_HAS_ARRAY)
|
||
ret = 2;
|
||
}
|
||
else
|
||
ret = (bits & SPCT_HAS_LARGE_CHAR_ARRAY) != 0;
|
||
|
||
if (ret)
|
||
has_protected_decls = true;
|
||
|
||
return ret;
|
||
}
|
||
|
||
/* Two helper routines that check for phase 1 and phase 2. These are used
|
||
as callbacks for expand_stack_vars. */
|
||
|
||
static bool
|
||
stack_protect_decl_phase_1 (size_t i)
|
||
{
|
||
return stack_protect_decl_phase (stack_vars[i].decl) == 1;
|
||
}
|
||
|
||
static bool
|
||
stack_protect_decl_phase_2 (size_t i)
|
||
{
|
||
return stack_protect_decl_phase (stack_vars[i].decl) == 2;
|
||
}
|
||
|
||
/* And helper function that checks for asan phase (with stack protector
|
||
it is phase 3). This is used as callback for expand_stack_vars.
|
||
Returns true if any of the vars in the partition need to be protected. */
|
||
|
||
static bool
|
||
asan_decl_phase_3 (size_t i)
|
||
{
|
||
while (i != EOC)
|
||
{
|
||
if (asan_protect_stack_decl (stack_vars[i].decl))
|
||
return true;
|
||
i = stack_vars[i].next;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Ensure that variables in different stack protection phases conflict
|
||
so that they are not merged and share the same stack slot.
|
||
Return true if there are any address taken variables. */
|
||
|
||
static bool
|
||
add_stack_protection_conflicts (void)
|
||
{
|
||
size_t i, j, n = stack_vars_num;
|
||
unsigned char *phase;
|
||
bool ret = false;
|
||
|
||
phase = XNEWVEC (unsigned char, n);
|
||
for (i = 0; i < n; ++i)
|
||
{
|
||
phase[i] = stack_protect_decl_phase (stack_vars[i].decl);
|
||
if (TREE_ADDRESSABLE (stack_vars[i].decl))
|
||
ret = true;
|
||
}
|
||
|
||
for (i = 0; i < n; ++i)
|
||
{
|
||
unsigned char ph_i = phase[i];
|
||
for (j = i + 1; j < n; ++j)
|
||
if (ph_i != phase[j])
|
||
add_stack_var_conflict (i, j);
|
||
}
|
||
|
||
XDELETEVEC (phase);
|
||
return ret;
|
||
}
|
||
|
||
/* Create a decl for the guard at the top of the stack frame. */
|
||
|
||
static void
|
||
create_stack_guard (void)
|
||
{
|
||
tree guard = build_decl (DECL_SOURCE_LOCATION (current_function_decl),
|
||
VAR_DECL, NULL, ptr_type_node);
|
||
TREE_THIS_VOLATILE (guard) = 1;
|
||
TREE_USED (guard) = 1;
|
||
expand_one_stack_var (guard);
|
||
crtl->stack_protect_guard = guard;
|
||
}
|
||
|
||
/* Prepare for expanding variables. */
|
||
static void
|
||
init_vars_expansion (void)
|
||
{
|
||
/* Conflict bitmaps, and a few related temporary bitmaps, go here. */
|
||
bitmap_obstack_initialize (&stack_var_bitmap_obstack);
|
||
|
||
/* A map from decl to stack partition. */
|
||
decl_to_stack_part = new hash_map<tree, size_t>;
|
||
|
||
/* Initialize local stack smashing state. */
|
||
has_protected_decls = false;
|
||
has_short_buffer = false;
|
||
}
|
||
|
||
/* Free up stack variable graph data. */
|
||
static void
|
||
fini_vars_expansion (void)
|
||
{
|
||
bitmap_obstack_release (&stack_var_bitmap_obstack);
|
||
if (stack_vars)
|
||
XDELETEVEC (stack_vars);
|
||
if (stack_vars_sorted)
|
||
XDELETEVEC (stack_vars_sorted);
|
||
stack_vars = NULL;
|
||
stack_vars_sorted = NULL;
|
||
stack_vars_alloc = stack_vars_num = 0;
|
||
delete decl_to_stack_part;
|
||
decl_to_stack_part = NULL;
|
||
}
|
||
|
||
/* Make a fair guess for the size of the stack frame of the function
|
||
in NODE. This doesn't have to be exact, the result is only used in
|
||
the inline heuristics. So we don't want to run the full stack var
|
||
packing algorithm (which is quadratic in the number of stack vars).
|
||
Instead, we calculate the total size of all stack vars. This turns
|
||
out to be a pretty fair estimate -- packing of stack vars doesn't
|
||
happen very often. */
|
||
|
||
HOST_WIDE_INT
|
||
estimated_stack_frame_size (struct cgraph_node *node)
|
||
{
|
||
poly_int64 size = 0;
|
||
size_t i;
|
||
tree var;
|
||
struct function *fn = DECL_STRUCT_FUNCTION (node->decl);
|
||
|
||
push_cfun (fn);
|
||
|
||
init_vars_expansion ();
|
||
|
||
FOR_EACH_LOCAL_DECL (fn, i, var)
|
||
if (auto_var_in_fn_p (var, fn->decl))
|
||
size += expand_one_var (var, true, false);
|
||
|
||
if (stack_vars_num > 0)
|
||
{
|
||
/* Fake sorting the stack vars for account_stack_vars (). */
|
||
stack_vars_sorted = XNEWVEC (size_t, stack_vars_num);
|
||
for (i = 0; i < stack_vars_num; ++i)
|
||
stack_vars_sorted[i] = i;
|
||
size += account_stack_vars ();
|
||
}
|
||
|
||
fini_vars_expansion ();
|
||
pop_cfun ();
|
||
return estimated_poly_value (size);
|
||
}
|
||
|
||
/* Check if the current function has calls that use a return slot. */
|
||
|
||
static bool
|
||
stack_protect_return_slot_p ()
|
||
{
|
||
basic_block bb;
|
||
|
||
FOR_ALL_BB_FN (bb, cfun)
|
||
for (gimple_stmt_iterator gsi = gsi_start_bb (bb);
|
||
!gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
gimple *stmt = gsi_stmt (gsi);
|
||
/* This assumes that calls to internal-only functions never
|
||
use a return slot. */
|
||
if (is_gimple_call (stmt)
|
||
&& !gimple_call_internal_p (stmt)
|
||
&& aggregate_value_p (TREE_TYPE (gimple_call_fntype (stmt)),
|
||
gimple_call_fndecl (stmt)))
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/* Expand all variables used in the function. */
|
||
|
||
static rtx_insn *
|
||
expand_used_vars (void)
|
||
{
|
||
tree var, outer_block = DECL_INITIAL (current_function_decl);
|
||
auto_vec<tree> maybe_local_decls;
|
||
rtx_insn *var_end_seq = NULL;
|
||
unsigned i;
|
||
unsigned len;
|
||
bool gen_stack_protect_signal = false;
|
||
|
||
/* Compute the phase of the stack frame for this function. */
|
||
{
|
||
int align = PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT;
|
||
int off = targetm.starting_frame_offset () % align;
|
||
frame_phase = off ? align - off : 0;
|
||
}
|
||
|
||
/* Set TREE_USED on all variables in the local_decls. */
|
||
FOR_EACH_LOCAL_DECL (cfun, i, var)
|
||
TREE_USED (var) = 1;
|
||
/* Clear TREE_USED on all variables associated with a block scope. */
|
||
clear_tree_used (DECL_INITIAL (current_function_decl));
|
||
|
||
init_vars_expansion ();
|
||
|
||
if (targetm.use_pseudo_pic_reg ())
|
||
pic_offset_table_rtx = gen_reg_rtx (Pmode);
|
||
|
||
for (i = 0; i < SA.map->num_partitions; i++)
|
||
{
|
||
if (bitmap_bit_p (SA.partitions_for_parm_default_defs, i))
|
||
continue;
|
||
|
||
tree var = partition_to_var (SA.map, i);
|
||
|
||
gcc_assert (!virtual_operand_p (var));
|
||
|
||
expand_one_ssa_partition (var);
|
||
}
|
||
|
||
if (flag_stack_protect == SPCT_FLAG_STRONG)
|
||
gen_stack_protect_signal = stack_protect_return_slot_p ();
|
||
|
||
/* At this point all variables on the local_decls with TREE_USED
|
||
set are not associated with any block scope. Lay them out. */
|
||
|
||
len = vec_safe_length (cfun->local_decls);
|
||
FOR_EACH_LOCAL_DECL (cfun, i, var)
|
||
{
|
||
bool expand_now = false;
|
||
|
||
/* Expanded above already. */
|
||
if (is_gimple_reg (var))
|
||
{
|
||
TREE_USED (var) = 0;
|
||
goto next;
|
||
}
|
||
/* We didn't set a block for static or extern because it's hard
|
||
to tell the difference between a global variable (re)declared
|
||
in a local scope, and one that's really declared there to
|
||
begin with. And it doesn't really matter much, since we're
|
||
not giving them stack space. Expand them now. */
|
||
else if (TREE_STATIC (var) || DECL_EXTERNAL (var))
|
||
expand_now = true;
|
||
|
||
/* Expand variables not associated with any block now. Those created by
|
||
the optimizers could be live anywhere in the function. Those that
|
||
could possibly have been scoped originally and detached from their
|
||
block will have their allocation deferred so we coalesce them with
|
||
others when optimization is enabled. */
|
||
else if (TREE_USED (var))
|
||
expand_now = true;
|
||
|
||
/* Finally, mark all variables on the list as used. We'll use
|
||
this in a moment when we expand those associated with scopes. */
|
||
TREE_USED (var) = 1;
|
||
|
||
if (expand_now)
|
||
expand_one_var (var, true, true);
|
||
|
||
next:
|
||
if (DECL_ARTIFICIAL (var) && !DECL_IGNORED_P (var))
|
||
{
|
||
rtx rtl = DECL_RTL_IF_SET (var);
|
||
|
||
/* Keep artificial non-ignored vars in cfun->local_decls
|
||
chain until instantiate_decls. */
|
||
if (rtl && (MEM_P (rtl) || GET_CODE (rtl) == CONCAT))
|
||
add_local_decl (cfun, var);
|
||
else if (rtl == NULL_RTX)
|
||
/* If rtl isn't set yet, which can happen e.g. with
|
||
-fstack-protector, retry before returning from this
|
||
function. */
|
||
maybe_local_decls.safe_push (var);
|
||
}
|
||
}
|
||
|
||
/* We duplicated some of the decls in CFUN->LOCAL_DECLS.
|
||
|
||
+-----------------+-----------------+
|
||
| ...processed... | ...duplicates...|
|
||
+-----------------+-----------------+
|
||
^
|
||
+-- LEN points here.
|
||
|
||
We just want the duplicates, as those are the artificial
|
||
non-ignored vars that we want to keep until instantiate_decls.
|
||
Move them down and truncate the array. */
|
||
if (!vec_safe_is_empty (cfun->local_decls))
|
||
cfun->local_decls->block_remove (0, len);
|
||
|
||
/* At this point, all variables within the block tree with TREE_USED
|
||
set are actually used by the optimized function. Lay them out. */
|
||
expand_used_vars_for_block (outer_block, true);
|
||
|
||
if (stack_vars_num > 0)
|
||
{
|
||
bool has_addressable_vars = false;
|
||
|
||
add_scope_conflicts ();
|
||
|
||
/* If stack protection is enabled, we don't share space between
|
||
vulnerable data and non-vulnerable data. */
|
||
if (flag_stack_protect != 0
|
||
&& (flag_stack_protect != SPCT_FLAG_EXPLICIT
|
||
|| (flag_stack_protect == SPCT_FLAG_EXPLICIT
|
||
&& lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl)))))
|
||
has_addressable_vars = add_stack_protection_conflicts ();
|
||
|
||
if (flag_stack_protect == SPCT_FLAG_STRONG && has_addressable_vars)
|
||
gen_stack_protect_signal = true;
|
||
|
||
/* Now that we have collected all stack variables, and have computed a
|
||
minimal interference graph, attempt to save some stack space. */
|
||
partition_stack_vars ();
|
||
if (dump_file)
|
||
dump_stack_var_partition ();
|
||
}
|
||
|
||
switch (flag_stack_protect)
|
||
{
|
||
case SPCT_FLAG_ALL:
|
||
create_stack_guard ();
|
||
break;
|
||
|
||
case SPCT_FLAG_STRONG:
|
||
if (gen_stack_protect_signal
|
||
|| cfun->calls_alloca
|
||
|| has_protected_decls
|
||
|| lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl)))
|
||
create_stack_guard ();
|
||
break;
|
||
|
||
case SPCT_FLAG_DEFAULT:
|
||
if (cfun->calls_alloca
|
||
|| has_protected_decls
|
||
|| lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl)))
|
||
create_stack_guard ();
|
||
break;
|
||
|
||
case SPCT_FLAG_EXPLICIT:
|
||
if (lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl)))
|
||
create_stack_guard ();
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Assign rtl to each variable based on these partitions. */
|
||
if (stack_vars_num > 0)
|
||
{
|
||
class stack_vars_data data;
|
||
|
||
data.asan_base = NULL_RTX;
|
||
data.asan_alignb = 0;
|
||
|
||
/* Reorder decls to be protected by iterating over the variables
|
||
array multiple times, and allocating out of each phase in turn. */
|
||
/* ??? We could probably integrate this into the qsort we did
|
||
earlier, such that we naturally see these variables first,
|
||
and thus naturally allocate things in the right order. */
|
||
if (has_protected_decls)
|
||
{
|
||
/* Phase 1 contains only character arrays. */
|
||
expand_stack_vars (stack_protect_decl_phase_1, &data);
|
||
|
||
/* Phase 2 contains other kinds of arrays. */
|
||
if (flag_stack_protect == SPCT_FLAG_ALL
|
||
|| flag_stack_protect == SPCT_FLAG_STRONG
|
||
|| (flag_stack_protect == SPCT_FLAG_EXPLICIT
|
||
&& lookup_attribute ("stack_protect",
|
||
DECL_ATTRIBUTES (current_function_decl))))
|
||
expand_stack_vars (stack_protect_decl_phase_2, &data);
|
||
}
|
||
|
||
if (asan_sanitize_stack_p ())
|
||
/* Phase 3, any partitions that need asan protection
|
||
in addition to phase 1 and 2. */
|
||
expand_stack_vars (asan_decl_phase_3, &data);
|
||
|
||
/* ASAN description strings don't yet have a syntax for expressing
|
||
polynomial offsets. */
|
||
HOST_WIDE_INT prev_offset;
|
||
if (!data.asan_vec.is_empty ()
|
||
&& frame_offset.is_constant (&prev_offset))
|
||
{
|
||
HOST_WIDE_INT offset, sz, redzonesz;
|
||
redzonesz = ASAN_RED_ZONE_SIZE;
|
||
sz = data.asan_vec[0] - prev_offset;
|
||
if (data.asan_alignb > ASAN_RED_ZONE_SIZE
|
||
&& data.asan_alignb <= 4096
|
||
&& sz + ASAN_RED_ZONE_SIZE >= (int) data.asan_alignb)
|
||
redzonesz = ((sz + ASAN_RED_ZONE_SIZE + data.asan_alignb - 1)
|
||
& ~(data.asan_alignb - HOST_WIDE_INT_1)) - sz;
|
||
/* Allocating a constant amount of space from a constant
|
||
starting offset must give a constant result. */
|
||
offset = (alloc_stack_frame_space (redzonesz, ASAN_RED_ZONE_SIZE)
|
||
.to_constant ());
|
||
data.asan_vec.safe_push (prev_offset);
|
||
data.asan_vec.safe_push (offset);
|
||
/* Leave space for alignment if STRICT_ALIGNMENT. */
|
||
if (STRICT_ALIGNMENT)
|
||
alloc_stack_frame_space ((GET_MODE_ALIGNMENT (SImode)
|
||
<< ASAN_SHADOW_SHIFT)
|
||
/ BITS_PER_UNIT, 1);
|
||
|
||
var_end_seq
|
||
= asan_emit_stack_protection (virtual_stack_vars_rtx,
|
||
data.asan_base,
|
||
data.asan_alignb,
|
||
data.asan_vec.address (),
|
||
data.asan_decl_vec.address (),
|
||
data.asan_vec.length ());
|
||
}
|
||
|
||
expand_stack_vars (NULL, &data);
|
||
}
|
||
|
||
if (asan_sanitize_allocas_p () && cfun->calls_alloca)
|
||
var_end_seq = asan_emit_allocas_unpoison (virtual_stack_dynamic_rtx,
|
||
virtual_stack_vars_rtx,
|
||
var_end_seq);
|
||
|
||
fini_vars_expansion ();
|
||
|
||
/* If there were any artificial non-ignored vars without rtl
|
||
found earlier, see if deferred stack allocation hasn't assigned
|
||
rtl to them. */
|
||
FOR_EACH_VEC_ELT_REVERSE (maybe_local_decls, i, var)
|
||
{
|
||
rtx rtl = DECL_RTL_IF_SET (var);
|
||
|
||
/* Keep artificial non-ignored vars in cfun->local_decls
|
||
chain until instantiate_decls. */
|
||
if (rtl && (MEM_P (rtl) || GET_CODE (rtl) == CONCAT))
|
||
add_local_decl (cfun, var);
|
||
}
|
||
|
||
/* If the target requires that FRAME_OFFSET be aligned, do it. */
|
||
if (STACK_ALIGNMENT_NEEDED)
|
||
{
|
||
HOST_WIDE_INT align = PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT;
|
||
if (FRAME_GROWS_DOWNWARD)
|
||
frame_offset = aligned_lower_bound (frame_offset, align);
|
||
else
|
||
frame_offset = aligned_upper_bound (frame_offset, align);
|
||
}
|
||
|
||
return var_end_seq;
|
||
}
|
||
|
||
|
||
/* If we need to produce a detailed dump, print the tree representation
|
||
for STMT to the dump file. SINCE is the last RTX after which the RTL
|
||
generated for STMT should have been appended. */
|
||
|
||
static void
|
||
maybe_dump_rtl_for_gimple_stmt (gimple *stmt, rtx_insn *since)
|
||
{
|
||
if (dump_file && (dump_flags & TDF_DETAILS))
|
||
{
|
||
fprintf (dump_file, "\n;; ");
|
||
print_gimple_stmt (dump_file, stmt, 0,
|
||
TDF_SLIM | (dump_flags & TDF_LINENO));
|
||
fprintf (dump_file, "\n");
|
||
|
||
print_rtl (dump_file, since ? NEXT_INSN (since) : since);
|
||
}
|
||
}
|
||
|
||
/* Maps the blocks that do not contain tree labels to rtx labels. */
|
||
|
||
static hash_map<basic_block, rtx_code_label *> *lab_rtx_for_bb;
|
||
|
||
/* Returns the label_rtx expression for a label starting basic block BB. */
|
||
|
||
static rtx_code_label *
|
||
label_rtx_for_bb (basic_block bb ATTRIBUTE_UNUSED)
|
||
{
|
||
gimple_stmt_iterator gsi;
|
||
tree lab;
|
||
|
||
if (bb->flags & BB_RTL)
|
||
return block_label (bb);
|
||
|
||
rtx_code_label **elt = lab_rtx_for_bb->get (bb);
|
||
if (elt)
|
||
return *elt;
|
||
|
||
/* Find the tree label if it is present. */
|
||
|
||
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
glabel *lab_stmt;
|
||
|
||
lab_stmt = dyn_cast <glabel *> (gsi_stmt (gsi));
|
||
if (!lab_stmt)
|
||
break;
|
||
|
||
lab = gimple_label_label (lab_stmt);
|
||
if (DECL_NONLOCAL (lab))
|
||
break;
|
||
|
||
return jump_target_rtx (lab);
|
||
}
|
||
|
||
rtx_code_label *l = gen_label_rtx ();
|
||
lab_rtx_for_bb->put (bb, l);
|
||
return l;
|
||
}
|
||
|
||
|
||
/* A subroutine of expand_gimple_cond. Given E, a fallthrough edge
|
||
of a basic block where we just expanded the conditional at the end,
|
||
possibly clean up the CFG and instruction sequence. LAST is the
|
||
last instruction before the just emitted jump sequence. */
|
||
|
||
static void
|
||
maybe_cleanup_end_of_block (edge e, rtx_insn *last)
|
||
{
|
||
/* Special case: when jumpif decides that the condition is
|
||
trivial it emits an unconditional jump (and the necessary
|
||
barrier). But we still have two edges, the fallthru one is
|
||
wrong. purge_dead_edges would clean this up later. Unfortunately
|
||
we have to insert insns (and split edges) before
|
||
find_many_sub_basic_blocks and hence before purge_dead_edges.
|
||
But splitting edges might create new blocks which depend on the
|
||
fact that if there are two edges there's no barrier. So the
|
||
barrier would get lost and verify_flow_info would ICE. Instead
|
||
of auditing all edge splitters to care for the barrier (which
|
||
normally isn't there in a cleaned CFG), fix it here. */
|
||
if (BARRIER_P (get_last_insn ()))
|
||
{
|
||
rtx_insn *insn;
|
||
remove_edge (e);
|
||
/* Now, we have a single successor block, if we have insns to
|
||
insert on the remaining edge we potentially will insert
|
||
it at the end of this block (if the dest block isn't feasible)
|
||
in order to avoid splitting the edge. This insertion will take
|
||
place in front of the last jump. But we might have emitted
|
||
multiple jumps (conditional and one unconditional) to the
|
||
same destination. Inserting in front of the last one then
|
||
is a problem. See PR 40021. We fix this by deleting all
|
||
jumps except the last unconditional one. */
|
||
insn = PREV_INSN (get_last_insn ());
|
||
/* Make sure we have an unconditional jump. Otherwise we're
|
||
confused. */
|
||
gcc_assert (JUMP_P (insn) && !any_condjump_p (insn));
|
||
for (insn = PREV_INSN (insn); insn != last;)
|
||
{
|
||
insn = PREV_INSN (insn);
|
||
if (JUMP_P (NEXT_INSN (insn)))
|
||
{
|
||
if (!any_condjump_p (NEXT_INSN (insn)))
|
||
{
|
||
gcc_assert (BARRIER_P (NEXT_INSN (NEXT_INSN (insn))));
|
||
delete_insn (NEXT_INSN (NEXT_INSN (insn)));
|
||
}
|
||
delete_insn (NEXT_INSN (insn));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* A subroutine of expand_gimple_basic_block. Expand one GIMPLE_COND.
|
||
Returns a new basic block if we've terminated the current basic
|
||
block and created a new one. */
|
||
|
||
static basic_block
|
||
expand_gimple_cond (basic_block bb, gcond *stmt)
|
||
{
|
||
basic_block new_bb, dest;
|
||
edge true_edge;
|
||
edge false_edge;
|
||
rtx_insn *last2, *last;
|
||
enum tree_code code;
|
||
tree op0, op1;
|
||
|
||
code = gimple_cond_code (stmt);
|
||
op0 = gimple_cond_lhs (stmt);
|
||
op1 = gimple_cond_rhs (stmt);
|
||
/* We're sometimes presented with such code:
|
||
D.123_1 = x < y;
|
||
if (D.123_1 != 0)
|
||
...
|
||
This would expand to two comparisons which then later might
|
||
be cleaned up by combine. But some pattern matchers like if-conversion
|
||
work better when there's only one compare, so make up for this
|
||
here as special exception if TER would have made the same change. */
|
||
if (SA.values
|
||
&& TREE_CODE (op0) == SSA_NAME
|
||
&& TREE_CODE (TREE_TYPE (op0)) == BOOLEAN_TYPE
|
||
&& TREE_CODE (op1) == INTEGER_CST
|
||
&& ((gimple_cond_code (stmt) == NE_EXPR
|
||
&& integer_zerop (op1))
|
||
|| (gimple_cond_code (stmt) == EQ_EXPR
|
||
&& integer_onep (op1)))
|
||
&& bitmap_bit_p (SA.values, SSA_NAME_VERSION (op0)))
|
||
{
|
||
gimple *second = SSA_NAME_DEF_STMT (op0);
|
||
if (gimple_code (second) == GIMPLE_ASSIGN)
|
||
{
|
||
enum tree_code code2 = gimple_assign_rhs_code (second);
|
||
if (TREE_CODE_CLASS (code2) == tcc_comparison)
|
||
{
|
||
code = code2;
|
||
op0 = gimple_assign_rhs1 (second);
|
||
op1 = gimple_assign_rhs2 (second);
|
||
}
|
||
/* If jumps are cheap and the target does not support conditional
|
||
compare, turn some more codes into jumpy sequences. */
|
||
else if (BRANCH_COST (optimize_insn_for_speed_p (), false) < 4
|
||
&& targetm.gen_ccmp_first == NULL)
|
||
{
|
||
if ((code2 == BIT_AND_EXPR
|
||
&& TYPE_PRECISION (TREE_TYPE (op0)) == 1
|
||
&& TREE_CODE (gimple_assign_rhs2 (second)) != INTEGER_CST)
|
||
|| code2 == TRUTH_AND_EXPR)
|
||
{
|
||
code = TRUTH_ANDIF_EXPR;
|
||
op0 = gimple_assign_rhs1 (second);
|
||
op1 = gimple_assign_rhs2 (second);
|
||
}
|
||
else if (code2 == BIT_IOR_EXPR || code2 == TRUTH_OR_EXPR)
|
||
{
|
||
code = TRUTH_ORIF_EXPR;
|
||
op0 = gimple_assign_rhs1 (second);
|
||
op1 = gimple_assign_rhs2 (second);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Optimize (x % C1) == C2 or (x % C1) != C2 if it is beneficial
|
||
into (x - C2) * C3 < C4. */
|
||
if ((code == EQ_EXPR || code == NE_EXPR)
|
||
&& TREE_CODE (op0) == SSA_NAME
|
||
&& TREE_CODE (op1) == INTEGER_CST)
|
||
code = maybe_optimize_mod_cmp (code, &op0, &op1);
|
||
|
||
last2 = last = get_last_insn ();
|
||
|
||
extract_true_false_edges_from_block (bb, &true_edge, &false_edge);
|
||
set_curr_insn_location (gimple_location (stmt));
|
||
|
||
/* These flags have no purpose in RTL land. */
|
||
true_edge->flags &= ~EDGE_TRUE_VALUE;
|
||
false_edge->flags &= ~EDGE_FALSE_VALUE;
|
||
|
||
/* We can either have a pure conditional jump with one fallthru edge or
|
||
two-way jump that needs to be decomposed into two basic blocks. */
|
||
if (false_edge->dest == bb->next_bb)
|
||
{
|
||
jumpif_1 (code, op0, op1, label_rtx_for_bb (true_edge->dest),
|
||
true_edge->probability);
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last);
|
||
if (true_edge->goto_locus != UNKNOWN_LOCATION)
|
||
set_curr_insn_location (true_edge->goto_locus);
|
||
false_edge->flags |= EDGE_FALLTHRU;
|
||
maybe_cleanup_end_of_block (false_edge, last);
|
||
return NULL;
|
||
}
|
||
if (true_edge->dest == bb->next_bb)
|
||
{
|
||
jumpifnot_1 (code, op0, op1, label_rtx_for_bb (false_edge->dest),
|
||
false_edge->probability);
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last);
|
||
if (false_edge->goto_locus != UNKNOWN_LOCATION)
|
||
set_curr_insn_location (false_edge->goto_locus);
|
||
true_edge->flags |= EDGE_FALLTHRU;
|
||
maybe_cleanup_end_of_block (true_edge, last);
|
||
return NULL;
|
||
}
|
||
|
||
jumpif_1 (code, op0, op1, label_rtx_for_bb (true_edge->dest),
|
||
true_edge->probability);
|
||
last = get_last_insn ();
|
||
if (false_edge->goto_locus != UNKNOWN_LOCATION)
|
||
set_curr_insn_location (false_edge->goto_locus);
|
||
emit_jump (label_rtx_for_bb (false_edge->dest));
|
||
|
||
BB_END (bb) = last;
|
||
if (BARRIER_P (BB_END (bb)))
|
||
BB_END (bb) = PREV_INSN (BB_END (bb));
|
||
update_bb_for_insn (bb);
|
||
|
||
new_bb = create_basic_block (NEXT_INSN (last), get_last_insn (), bb);
|
||
dest = false_edge->dest;
|
||
redirect_edge_succ (false_edge, new_bb);
|
||
false_edge->flags |= EDGE_FALLTHRU;
|
||
new_bb->count = false_edge->count ();
|
||
loop_p loop = find_common_loop (bb->loop_father, dest->loop_father);
|
||
add_bb_to_loop (new_bb, loop);
|
||
if (loop->latch == bb
|
||
&& loop->header == dest)
|
||
loop->latch = new_bb;
|
||
make_single_succ_edge (new_bb, dest, 0);
|
||
if (BARRIER_P (BB_END (new_bb)))
|
||
BB_END (new_bb) = PREV_INSN (BB_END (new_bb));
|
||
update_bb_for_insn (new_bb);
|
||
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last2);
|
||
|
||
if (true_edge->goto_locus != UNKNOWN_LOCATION)
|
||
{
|
||
set_curr_insn_location (true_edge->goto_locus);
|
||
true_edge->goto_locus = curr_insn_location ();
|
||
}
|
||
|
||
return new_bb;
|
||
}
|
||
|
||
/* Mark all calls that can have a transaction restart. */
|
||
|
||
static void
|
||
mark_transaction_restart_calls (gimple *stmt)
|
||
{
|
||
struct tm_restart_node dummy;
|
||
tm_restart_node **slot;
|
||
|
||
if (!cfun->gimple_df->tm_restart)
|
||
return;
|
||
|
||
dummy.stmt = stmt;
|
||
slot = cfun->gimple_df->tm_restart->find_slot (&dummy, NO_INSERT);
|
||
if (slot)
|
||
{
|
||
struct tm_restart_node *n = *slot;
|
||
tree list = n->label_or_list;
|
||
rtx_insn *insn;
|
||
|
||
for (insn = next_real_insn (get_last_insn ());
|
||
!CALL_P (insn);
|
||
insn = next_real_insn (insn))
|
||
continue;
|
||
|
||
if (TREE_CODE (list) == LABEL_DECL)
|
||
add_reg_note (insn, REG_TM, label_rtx (list));
|
||
else
|
||
for (; list ; list = TREE_CHAIN (list))
|
||
add_reg_note (insn, REG_TM, label_rtx (TREE_VALUE (list)));
|
||
}
|
||
}
|
||
|
||
/* A subroutine of expand_gimple_stmt_1, expanding one GIMPLE_CALL
|
||
statement STMT. */
|
||
|
||
static void
|
||
expand_call_stmt (gcall *stmt)
|
||
{
|
||
tree exp, decl, lhs;
|
||
bool builtin_p;
|
||
size_t i;
|
||
|
||
if (gimple_call_internal_p (stmt))
|
||
{
|
||
expand_internal_call (stmt);
|
||
return;
|
||
}
|
||
|
||
/* If this is a call to a built-in function and it has no effect other
|
||
than setting the lhs, try to implement it using an internal function
|
||
instead. */
|
||
decl = gimple_call_fndecl (stmt);
|
||
if (gimple_call_lhs (stmt)
|
||
&& !gimple_has_side_effects (stmt)
|
||
&& (optimize || (decl && called_as_built_in (decl))))
|
||
{
|
||
internal_fn ifn = replacement_internal_fn (stmt);
|
||
if (ifn != IFN_LAST)
|
||
{
|
||
expand_internal_call (ifn, stmt);
|
||
return;
|
||
}
|
||
}
|
||
|
||
exp = build_vl_exp (CALL_EXPR, gimple_call_num_args (stmt) + 3);
|
||
|
||
CALL_EXPR_FN (exp) = gimple_call_fn (stmt);
|
||
builtin_p = decl && fndecl_built_in_p (decl);
|
||
|
||
/* If this is not a builtin function, the function type through which the
|
||
call is made may be different from the type of the function. */
|
||
if (!builtin_p)
|
||
CALL_EXPR_FN (exp)
|
||
= fold_convert (build_pointer_type (gimple_call_fntype (stmt)),
|
||
CALL_EXPR_FN (exp));
|
||
|
||
TREE_TYPE (exp) = gimple_call_return_type (stmt);
|
||
CALL_EXPR_STATIC_CHAIN (exp) = gimple_call_chain (stmt);
|
||
|
||
for (i = 0; i < gimple_call_num_args (stmt); i++)
|
||
{
|
||
tree arg = gimple_call_arg (stmt, i);
|
||
gimple *def;
|
||
/* TER addresses into arguments of builtin functions so we have a
|
||
chance to infer more correct alignment information. See PR39954. */
|
||
if (builtin_p
|
||
&& TREE_CODE (arg) == SSA_NAME
|
||
&& (def = get_gimple_for_ssa_name (arg))
|
||
&& gimple_assign_rhs_code (def) == ADDR_EXPR)
|
||
arg = gimple_assign_rhs1 (def);
|
||
CALL_EXPR_ARG (exp, i) = arg;
|
||
}
|
||
|
||
if (gimple_has_side_effects (stmt))
|
||
TREE_SIDE_EFFECTS (exp) = 1;
|
||
|
||
if (gimple_call_nothrow_p (stmt))
|
||
TREE_NOTHROW (exp) = 1;
|
||
|
||
if (gimple_no_warning_p (stmt))
|
||
TREE_NO_WARNING (exp) = 1;
|
||
|
||
CALL_EXPR_TAILCALL (exp) = gimple_call_tail_p (stmt);
|
||
CALL_EXPR_MUST_TAIL_CALL (exp) = gimple_call_must_tail_p (stmt);
|
||
CALL_EXPR_RETURN_SLOT_OPT (exp) = gimple_call_return_slot_opt_p (stmt);
|
||
if (decl
|
||
&& fndecl_built_in_p (decl, BUILT_IN_NORMAL)
|
||
&& ALLOCA_FUNCTION_CODE_P (DECL_FUNCTION_CODE (decl)))
|
||
CALL_ALLOCA_FOR_VAR_P (exp) = gimple_call_alloca_for_var_p (stmt);
|
||
else
|
||
CALL_FROM_THUNK_P (exp) = gimple_call_from_thunk_p (stmt);
|
||
CALL_EXPR_VA_ARG_PACK (exp) = gimple_call_va_arg_pack_p (stmt);
|
||
CALL_EXPR_BY_DESCRIPTOR (exp) = gimple_call_by_descriptor_p (stmt);
|
||
SET_EXPR_LOCATION (exp, gimple_location (stmt));
|
||
|
||
/* Ensure RTL is created for debug args. */
|
||
if (decl && DECL_HAS_DEBUG_ARGS_P (decl))
|
||
{
|
||
vec<tree, va_gc> **debug_args = decl_debug_args_lookup (decl);
|
||
unsigned int ix;
|
||
tree dtemp;
|
||
|
||
if (debug_args)
|
||
for (ix = 1; (*debug_args)->iterate (ix, &dtemp); ix += 2)
|
||
{
|
||
gcc_assert (TREE_CODE (dtemp) == DEBUG_EXPR_DECL);
|
||
expand_debug_expr (dtemp);
|
||
}
|
||
}
|
||
|
||
rtx_insn *before_call = get_last_insn ();
|
||
lhs = gimple_call_lhs (stmt);
|
||
if (lhs)
|
||
expand_assignment (lhs, exp, false);
|
||
else
|
||
expand_expr (exp, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
|
||
/* If the gimple call is an indirect call and has 'nocf_check'
|
||
attribute find a generated CALL insn to mark it as no
|
||
control-flow verification is needed. */
|
||
if (gimple_call_nocf_check_p (stmt)
|
||
&& !gimple_call_fndecl (stmt))
|
||
{
|
||
rtx_insn *last = get_last_insn ();
|
||
while (!CALL_P (last)
|
||
&& last != before_call)
|
||
last = PREV_INSN (last);
|
||
|
||
if (last != before_call)
|
||
add_reg_note (last, REG_CALL_NOCF_CHECK, const0_rtx);
|
||
}
|
||
|
||
mark_transaction_restart_calls (stmt);
|
||
}
|
||
|
||
|
||
/* Generate RTL for an asm statement (explicit assembler code).
|
||
STRING is a STRING_CST node containing the assembler code text,
|
||
or an ADDR_EXPR containing a STRING_CST. VOL nonzero means the
|
||
insn is volatile; don't optimize it. */
|
||
|
||
static void
|
||
expand_asm_loc (tree string, int vol, location_t locus)
|
||
{
|
||
rtx body;
|
||
|
||
body = gen_rtx_ASM_INPUT_loc (VOIDmode,
|
||
ggc_strdup (TREE_STRING_POINTER (string)),
|
||
locus);
|
||
|
||
MEM_VOLATILE_P (body) = vol;
|
||
|
||
/* Non-empty basic ASM implicitly clobbers memory. */
|
||
if (TREE_STRING_LENGTH (string) != 0)
|
||
{
|
||
rtx asm_op, clob;
|
||
unsigned i, nclobbers;
|
||
auto_vec<rtx> input_rvec, output_rvec;
|
||
auto_vec<const char *> constraints;
|
||
auto_vec<rtx> clobber_rvec;
|
||
HARD_REG_SET clobbered_regs;
|
||
CLEAR_HARD_REG_SET (clobbered_regs);
|
||
|
||
clob = gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode));
|
||
clobber_rvec.safe_push (clob);
|
||
|
||
if (targetm.md_asm_adjust)
|
||
targetm.md_asm_adjust (output_rvec, input_rvec,
|
||
constraints, clobber_rvec,
|
||
clobbered_regs);
|
||
|
||
asm_op = body;
|
||
nclobbers = clobber_rvec.length ();
|
||
body = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (1 + nclobbers));
|
||
|
||
XVECEXP (body, 0, 0) = asm_op;
|
||
for (i = 0; i < nclobbers; i++)
|
||
XVECEXP (body, 0, i + 1) = gen_rtx_CLOBBER (VOIDmode, clobber_rvec[i]);
|
||
}
|
||
|
||
emit_insn (body);
|
||
}
|
||
|
||
/* Return the number of times character C occurs in string S. */
|
||
static int
|
||
n_occurrences (int c, const char *s)
|
||
{
|
||
int n = 0;
|
||
while (*s)
|
||
n += (*s++ == c);
|
||
return n;
|
||
}
|
||
|
||
/* A subroutine of expand_asm_operands. Check that all operands have
|
||
the same number of alternatives. Return true if so. */
|
||
|
||
static bool
|
||
check_operand_nalternatives (const vec<const char *> &constraints)
|
||
{
|
||
unsigned len = constraints.length();
|
||
if (len > 0)
|
||
{
|
||
int nalternatives = n_occurrences (',', constraints[0]);
|
||
|
||
if (nalternatives + 1 > MAX_RECOG_ALTERNATIVES)
|
||
{
|
||
error ("too many alternatives in %<asm%>");
|
||
return false;
|
||
}
|
||
|
||
for (unsigned i = 1; i < len; ++i)
|
||
if (n_occurrences (',', constraints[i]) != nalternatives)
|
||
{
|
||
error ("operand constraints for %<asm%> differ "
|
||
"in number of alternatives");
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/* Check for overlap between registers marked in CLOBBERED_REGS and
|
||
anything inappropriate in T. Emit error and return the register
|
||
variable definition for error, NULL_TREE for ok. */
|
||
|
||
static bool
|
||
tree_conflicts_with_clobbers_p (tree t, HARD_REG_SET *clobbered_regs)
|
||
{
|
||
/* Conflicts between asm-declared register variables and the clobber
|
||
list are not allowed. */
|
||
tree overlap = tree_overlaps_hard_reg_set (t, clobbered_regs);
|
||
|
||
if (overlap)
|
||
{
|
||
error ("%<asm%> specifier for variable %qE conflicts with "
|
||
"%<asm%> clobber list",
|
||
DECL_NAME (overlap));
|
||
|
||
/* Reset registerness to stop multiple errors emitted for a single
|
||
variable. */
|
||
DECL_REGISTER (overlap) = 0;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* Check that the given REGNO spanning NREGS is a valid
|
||
asm clobber operand. Some HW registers cannot be
|
||
saved/restored, hence they should not be clobbered by
|
||
asm statements. */
|
||
static bool
|
||
asm_clobber_reg_is_valid (int regno, int nregs, const char *regname)
|
||
{
|
||
bool is_valid = true;
|
||
HARD_REG_SET regset;
|
||
|
||
CLEAR_HARD_REG_SET (regset);
|
||
|
||
add_range_to_hard_reg_set (®set, regno, nregs);
|
||
|
||
/* Clobbering the PIC register is an error. */
|
||
if (PIC_OFFSET_TABLE_REGNUM != INVALID_REGNUM
|
||
&& overlaps_hard_reg_set_p (regset, Pmode, PIC_OFFSET_TABLE_REGNUM))
|
||
{
|
||
/* ??? Diagnose during gimplification? */
|
||
error ("PIC register clobbered by %qs in %<asm%>", regname);
|
||
is_valid = false;
|
||
}
|
||
else if (!in_hard_reg_set_p
|
||
(accessible_reg_set, reg_raw_mode[regno], regno))
|
||
{
|
||
/* ??? Diagnose during gimplification? */
|
||
error ("the register %qs cannot be clobbered in %<asm%>"
|
||
" for the current target", regname);
|
||
is_valid = false;
|
||
}
|
||
|
||
/* Clobbering the stack pointer register is deprecated. GCC expects
|
||
the value of the stack pointer after an asm statement to be the same
|
||
as it was before, so no asm can validly clobber the stack pointer in
|
||
the usual sense. Adding the stack pointer to the clobber list has
|
||
traditionally had some undocumented and somewhat obscure side-effects. */
|
||
if (overlaps_hard_reg_set_p (regset, Pmode, STACK_POINTER_REGNUM)
|
||
&& warning (OPT_Wdeprecated, "listing the stack pointer register"
|
||
" %qs in a clobber list is deprecated", regname))
|
||
inform (input_location, "the value of the stack pointer after an %<asm%>"
|
||
" statement must be the same as it was before the statement");
|
||
|
||
return is_valid;
|
||
}
|
||
|
||
/* Generate RTL for an asm statement with arguments.
|
||
STRING is the instruction template.
|
||
OUTPUTS is a list of output arguments (lvalues); INPUTS a list of inputs.
|
||
Each output or input has an expression in the TREE_VALUE and
|
||
a tree list in TREE_PURPOSE which in turn contains a constraint
|
||
name in TREE_VALUE (or NULL_TREE) and a constraint string
|
||
in TREE_PURPOSE.
|
||
CLOBBERS is a list of STRING_CST nodes each naming a hard register
|
||
that is clobbered by this insn.
|
||
|
||
LABELS is a list of labels, and if LABELS is non-NULL, FALLTHRU_BB
|
||
should be the fallthru basic block of the asm goto.
|
||
|
||
Not all kinds of lvalue that may appear in OUTPUTS can be stored directly.
|
||
Some elements of OUTPUTS may be replaced with trees representing temporary
|
||
values. The caller should copy those temporary values to the originally
|
||
specified lvalues.
|
||
|
||
VOL nonzero means the insn is volatile; don't optimize it. */
|
||
|
||
static void
|
||
expand_asm_stmt (gasm *stmt)
|
||
{
|
||
class save_input_location
|
||
{
|
||
location_t old;
|
||
|
||
public:
|
||
explicit save_input_location(location_t where)
|
||
{
|
||
old = input_location;
|
||
input_location = where;
|
||
}
|
||
|
||
~save_input_location()
|
||
{
|
||
input_location = old;
|
||
}
|
||
};
|
||
|
||
location_t locus = gimple_location (stmt);
|
||
|
||
if (gimple_asm_input_p (stmt))
|
||
{
|
||
const char *s = gimple_asm_string (stmt);
|
||
tree string = build_string (strlen (s), s);
|
||
expand_asm_loc (string, gimple_asm_volatile_p (stmt), locus);
|
||
return;
|
||
}
|
||
|
||
/* There are some legacy diagnostics in here, and also avoids a
|
||
sixth parameger to targetm.md_asm_adjust. */
|
||
save_input_location s_i_l(locus);
|
||
|
||
unsigned noutputs = gimple_asm_noutputs (stmt);
|
||
unsigned ninputs = gimple_asm_ninputs (stmt);
|
||
unsigned nlabels = gimple_asm_nlabels (stmt);
|
||
unsigned i;
|
||
|
||
/* ??? Diagnose during gimplification? */
|
||
if (ninputs + noutputs + nlabels > MAX_RECOG_OPERANDS)
|
||
{
|
||
error ("more than %d operands in %<asm%>", MAX_RECOG_OPERANDS);
|
||
return;
|
||
}
|
||
|
||
auto_vec<tree, MAX_RECOG_OPERANDS> output_tvec;
|
||
auto_vec<tree, MAX_RECOG_OPERANDS> input_tvec;
|
||
auto_vec<const char *, MAX_RECOG_OPERANDS> constraints;
|
||
|
||
/* Copy the gimple vectors into new vectors that we can manipulate. */
|
||
|
||
output_tvec.safe_grow (noutputs);
|
||
input_tvec.safe_grow (ninputs);
|
||
constraints.safe_grow (noutputs + ninputs);
|
||
|
||
for (i = 0; i < noutputs; ++i)
|
||
{
|
||
tree t = gimple_asm_output_op (stmt, i);
|
||
output_tvec[i] = TREE_VALUE (t);
|
||
constraints[i] = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
|
||
}
|
||
for (i = 0; i < ninputs; i++)
|
||
{
|
||
tree t = gimple_asm_input_op (stmt, i);
|
||
input_tvec[i] = TREE_VALUE (t);
|
||
constraints[i + noutputs]
|
||
= TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
|
||
}
|
||
|
||
/* ??? Diagnose during gimplification? */
|
||
if (! check_operand_nalternatives (constraints))
|
||
return;
|
||
|
||
/* Count the number of meaningful clobbered registers, ignoring what
|
||
we would ignore later. */
|
||
auto_vec<rtx> clobber_rvec;
|
||
HARD_REG_SET clobbered_regs;
|
||
CLEAR_HARD_REG_SET (clobbered_regs);
|
||
|
||
if (unsigned n = gimple_asm_nclobbers (stmt))
|
||
{
|
||
clobber_rvec.reserve (n);
|
||
for (i = 0; i < n; i++)
|
||
{
|
||
tree t = gimple_asm_clobber_op (stmt, i);
|
||
const char *regname = TREE_STRING_POINTER (TREE_VALUE (t));
|
||
int nregs, j;
|
||
|
||
j = decode_reg_name_and_count (regname, &nregs);
|
||
if (j < 0)
|
||
{
|
||
if (j == -2)
|
||
{
|
||
/* ??? Diagnose during gimplification? */
|
||
error ("unknown register name %qs in %<asm%>", regname);
|
||
}
|
||
else if (j == -4)
|
||
{
|
||
rtx x = gen_rtx_MEM (BLKmode, gen_rtx_SCRATCH (VOIDmode));
|
||
clobber_rvec.safe_push (x);
|
||
}
|
||
else
|
||
{
|
||
/* Otherwise we should have -1 == empty string
|
||
or -3 == cc, which is not a register. */
|
||
gcc_assert (j == -1 || j == -3);
|
||
}
|
||
}
|
||
else
|
||
for (int reg = j; reg < j + nregs; reg++)
|
||
{
|
||
if (!asm_clobber_reg_is_valid (reg, nregs, regname))
|
||
return;
|
||
|
||
SET_HARD_REG_BIT (clobbered_regs, reg);
|
||
rtx x = gen_rtx_REG (reg_raw_mode[reg], reg);
|
||
clobber_rvec.safe_push (x);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* First pass over inputs and outputs checks validity and sets
|
||
mark_addressable if needed. */
|
||
/* ??? Diagnose during gimplification? */
|
||
|
||
for (i = 0; i < noutputs; ++i)
|
||
{
|
||
tree val = output_tvec[i];
|
||
tree type = TREE_TYPE (val);
|
||
const char *constraint;
|
||
bool is_inout;
|
||
bool allows_reg;
|
||
bool allows_mem;
|
||
|
||
/* Try to parse the output constraint. If that fails, there's
|
||
no point in going further. */
|
||
constraint = constraints[i];
|
||
if (!parse_output_constraint (&constraint, i, ninputs, noutputs,
|
||
&allows_mem, &allows_reg, &is_inout))
|
||
return;
|
||
|
||
/* If the output is a hard register, verify it doesn't conflict with
|
||
any other operand's possible hard register use. */
|
||
if (DECL_P (val)
|
||
&& REG_P (DECL_RTL (val))
|
||
&& HARD_REGISTER_P (DECL_RTL (val)))
|
||
{
|
||
unsigned j, output_hregno = REGNO (DECL_RTL (val));
|
||
bool early_clobber_p = strchr (constraints[i], '&') != NULL;
|
||
unsigned long match;
|
||
|
||
/* Verify the other outputs do not use the same hard register. */
|
||
for (j = i + 1; j < noutputs; ++j)
|
||
if (DECL_P (output_tvec[j])
|
||
&& REG_P (DECL_RTL (output_tvec[j]))
|
||
&& HARD_REGISTER_P (DECL_RTL (output_tvec[j]))
|
||
&& output_hregno == REGNO (DECL_RTL (output_tvec[j])))
|
||
error ("invalid hard register usage between output operands");
|
||
|
||
/* Verify matching constraint operands use the same hard register
|
||
and that the non-matching constraint operands do not use the same
|
||
hard register if the output is an early clobber operand. */
|
||
for (j = 0; j < ninputs; ++j)
|
||
if (DECL_P (input_tvec[j])
|
||
&& REG_P (DECL_RTL (input_tvec[j]))
|
||
&& HARD_REGISTER_P (DECL_RTL (input_tvec[j])))
|
||
{
|
||
unsigned input_hregno = REGNO (DECL_RTL (input_tvec[j]));
|
||
switch (*constraints[j + noutputs])
|
||
{
|
||
case '0': case '1': case '2': case '3': case '4':
|
||
case '5': case '6': case '7': case '8': case '9':
|
||
match = strtoul (constraints[j + noutputs], NULL, 10);
|
||
break;
|
||
default:
|
||
match = ULONG_MAX;
|
||
break;
|
||
}
|
||
if (i == match
|
||
&& output_hregno != input_hregno)
|
||
error ("invalid hard register usage between output operand "
|
||
"and matching constraint operand");
|
||
else if (early_clobber_p
|
||
&& i != match
|
||
&& output_hregno == input_hregno)
|
||
error ("invalid hard register usage between earlyclobber "
|
||
"operand and input operand");
|
||
}
|
||
}
|
||
|
||
if (! allows_reg
|
||
&& (allows_mem
|
||
|| is_inout
|
||
|| (DECL_P (val)
|
||
&& REG_P (DECL_RTL (val))
|
||
&& GET_MODE (DECL_RTL (val)) != TYPE_MODE (type))))
|
||
mark_addressable (val);
|
||
}
|
||
|
||
for (i = 0; i < ninputs; ++i)
|
||
{
|
||
bool allows_reg, allows_mem;
|
||
const char *constraint;
|
||
|
||
constraint = constraints[i + noutputs];
|
||
if (! parse_input_constraint (&constraint, i, ninputs, noutputs, 0,
|
||
constraints.address (),
|
||
&allows_mem, &allows_reg))
|
||
return;
|
||
|
||
if (! allows_reg && allows_mem)
|
||
mark_addressable (input_tvec[i]);
|
||
}
|
||
|
||
/* Second pass evaluates arguments. */
|
||
|
||
/* Make sure stack is consistent for asm goto. */
|
||
if (nlabels > 0)
|
||
do_pending_stack_adjust ();
|
||
int old_generating_concat_p = generating_concat_p;
|
||
|
||
/* Vector of RTX's of evaluated output operands. */
|
||
auto_vec<rtx, MAX_RECOG_OPERANDS> output_rvec;
|
||
auto_vec<int, MAX_RECOG_OPERANDS> inout_opnum;
|
||
rtx_insn *after_rtl_seq = NULL, *after_rtl_end = NULL;
|
||
|
||
output_rvec.safe_grow (noutputs);
|
||
|
||
for (i = 0; i < noutputs; ++i)
|
||
{
|
||
tree val = output_tvec[i];
|
||
tree type = TREE_TYPE (val);
|
||
bool is_inout, allows_reg, allows_mem, ok;
|
||
rtx op;
|
||
|
||
ok = parse_output_constraint (&constraints[i], i, ninputs,
|
||
noutputs, &allows_mem, &allows_reg,
|
||
&is_inout);
|
||
gcc_assert (ok);
|
||
|
||
/* If an output operand is not a decl or indirect ref and our constraint
|
||
allows a register, make a temporary to act as an intermediate.
|
||
Make the asm insn write into that, then we will copy it to
|
||
the real output operand. Likewise for promoted variables. */
|
||
|
||
generating_concat_p = 0;
|
||
|
||
if ((TREE_CODE (val) == INDIRECT_REF && allows_mem)
|
||
|| (DECL_P (val)
|
||
&& (allows_mem || REG_P (DECL_RTL (val)))
|
||
&& ! (REG_P (DECL_RTL (val))
|
||
&& GET_MODE (DECL_RTL (val)) != TYPE_MODE (type)))
|
||
|| ! allows_reg
|
||
|| is_inout
|
||
|| TREE_ADDRESSABLE (type))
|
||
{
|
||
op = expand_expr (val, NULL_RTX, VOIDmode,
|
||
!allows_reg ? EXPAND_MEMORY : EXPAND_WRITE);
|
||
if (MEM_P (op))
|
||
op = validize_mem (op);
|
||
|
||
if (! allows_reg && !MEM_P (op))
|
||
error ("output number %d not directly addressable", i);
|
||
if ((! allows_mem && MEM_P (op) && GET_MODE (op) != BLKmode)
|
||
|| GET_CODE (op) == CONCAT)
|
||
{
|
||
rtx old_op = op;
|
||
op = gen_reg_rtx (GET_MODE (op));
|
||
|
||
generating_concat_p = old_generating_concat_p;
|
||
|
||
if (is_inout)
|
||
emit_move_insn (op, old_op);
|
||
|
||
push_to_sequence2 (after_rtl_seq, after_rtl_end);
|
||
emit_move_insn (old_op, op);
|
||
after_rtl_seq = get_insns ();
|
||
after_rtl_end = get_last_insn ();
|
||
end_sequence ();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
op = assign_temp (type, 0, 1);
|
||
op = validize_mem (op);
|
||
if (!MEM_P (op) && TREE_CODE (val) == SSA_NAME)
|
||
set_reg_attrs_for_decl_rtl (SSA_NAME_VAR (val), op);
|
||
|
||
generating_concat_p = old_generating_concat_p;
|
||
|
||
push_to_sequence2 (after_rtl_seq, after_rtl_end);
|
||
expand_assignment (val, make_tree (type, op), false);
|
||
after_rtl_seq = get_insns ();
|
||
after_rtl_end = get_last_insn ();
|
||
end_sequence ();
|
||
}
|
||
output_rvec[i] = op;
|
||
|
||
if (is_inout)
|
||
inout_opnum.safe_push (i);
|
||
}
|
||
|
||
auto_vec<rtx, MAX_RECOG_OPERANDS> input_rvec;
|
||
auto_vec<machine_mode, MAX_RECOG_OPERANDS> input_mode;
|
||
|
||
input_rvec.safe_grow (ninputs);
|
||
input_mode.safe_grow (ninputs);
|
||
|
||
generating_concat_p = 0;
|
||
|
||
for (i = 0; i < ninputs; ++i)
|
||
{
|
||
tree val = input_tvec[i];
|
||
tree type = TREE_TYPE (val);
|
||
bool allows_reg, allows_mem, ok;
|
||
const char *constraint;
|
||
rtx op;
|
||
|
||
constraint = constraints[i + noutputs];
|
||
ok = parse_input_constraint (&constraint, i, ninputs, noutputs, 0,
|
||
constraints.address (),
|
||
&allows_mem, &allows_reg);
|
||
gcc_assert (ok);
|
||
|
||
/* EXPAND_INITIALIZER will not generate code for valid initializer
|
||
constants, but will still generate code for other types of operand.
|
||
This is the behavior we want for constant constraints. */
|
||
op = expand_expr (val, NULL_RTX, VOIDmode,
|
||
allows_reg ? EXPAND_NORMAL
|
||
: allows_mem ? EXPAND_MEMORY
|
||
: EXPAND_INITIALIZER);
|
||
|
||
/* Never pass a CONCAT to an ASM. */
|
||
if (GET_CODE (op) == CONCAT)
|
||
op = force_reg (GET_MODE (op), op);
|
||
else if (MEM_P (op))
|
||
op = validize_mem (op);
|
||
|
||
if (asm_operand_ok (op, constraint, NULL) <= 0)
|
||
{
|
||
if (allows_reg && TYPE_MODE (type) != BLKmode)
|
||
op = force_reg (TYPE_MODE (type), op);
|
||
else if (!allows_mem)
|
||
warning (0, "%<asm%> operand %d probably does not match "
|
||
"constraints",
|
||
i + noutputs);
|
||
else if (MEM_P (op))
|
||
{
|
||
/* We won't recognize either volatile memory or memory
|
||
with a queued address as available a memory_operand
|
||
at this point. Ignore it: clearly this *is* a memory. */
|
||
}
|
||
else
|
||
gcc_unreachable ();
|
||
}
|
||
input_rvec[i] = op;
|
||
input_mode[i] = TYPE_MODE (type);
|
||
}
|
||
|
||
/* For in-out operands, copy output rtx to input rtx. */
|
||
unsigned ninout = inout_opnum.length();
|
||
for (i = 0; i < ninout; i++)
|
||
{
|
||
int j = inout_opnum[i];
|
||
rtx o = output_rvec[j];
|
||
|
||
input_rvec.safe_push (o);
|
||
input_mode.safe_push (GET_MODE (o));
|
||
|
||
char buffer[16];
|
||
sprintf (buffer, "%d", j);
|
||
constraints.safe_push (ggc_strdup (buffer));
|
||
}
|
||
ninputs += ninout;
|
||
|
||
/* Sometimes we wish to automatically clobber registers across an asm.
|
||
Case in point is when the i386 backend moved from cc0 to a hard reg --
|
||
maintaining source-level compatibility means automatically clobbering
|
||
the flags register. */
|
||
rtx_insn *after_md_seq = NULL;
|
||
if (targetm.md_asm_adjust)
|
||
after_md_seq = targetm.md_asm_adjust (output_rvec, input_rvec,
|
||
constraints, clobber_rvec,
|
||
clobbered_regs);
|
||
|
||
/* Do not allow the hook to change the output and input count,
|
||
lest it mess up the operand numbering. */
|
||
gcc_assert (output_rvec.length() == noutputs);
|
||
gcc_assert (input_rvec.length() == ninputs);
|
||
gcc_assert (constraints.length() == noutputs + ninputs);
|
||
|
||
/* But it certainly can adjust the clobbers. */
|
||
unsigned nclobbers = clobber_rvec.length ();
|
||
|
||
/* Third pass checks for easy conflicts. */
|
||
/* ??? Why are we doing this on trees instead of rtx. */
|
||
|
||
bool clobber_conflict_found = 0;
|
||
for (i = 0; i < noutputs; ++i)
|
||
if (tree_conflicts_with_clobbers_p (output_tvec[i], &clobbered_regs))
|
||
clobber_conflict_found = 1;
|
||
for (i = 0; i < ninputs - ninout; ++i)
|
||
if (tree_conflicts_with_clobbers_p (input_tvec[i], &clobbered_regs))
|
||
clobber_conflict_found = 1;
|
||
|
||
/* Make vectors for the expression-rtx, constraint strings,
|
||
and named operands. */
|
||
|
||
rtvec argvec = rtvec_alloc (ninputs);
|
||
rtvec constraintvec = rtvec_alloc (ninputs);
|
||
rtvec labelvec = rtvec_alloc (nlabels);
|
||
|
||
rtx body = gen_rtx_ASM_OPERANDS ((noutputs == 0 ? VOIDmode
|
||
: GET_MODE (output_rvec[0])),
|
||
ggc_strdup (gimple_asm_string (stmt)),
|
||
"", 0, argvec, constraintvec,
|
||
labelvec, locus);
|
||
MEM_VOLATILE_P (body) = gimple_asm_volatile_p (stmt);
|
||
|
||
for (i = 0; i < ninputs; ++i)
|
||
{
|
||
ASM_OPERANDS_INPUT (body, i) = input_rvec[i];
|
||
ASM_OPERANDS_INPUT_CONSTRAINT_EXP (body, i)
|
||
= gen_rtx_ASM_INPUT_loc (input_mode[i],
|
||
constraints[i + noutputs],
|
||
locus);
|
||
}
|
||
|
||
/* Copy labels to the vector. */
|
||
rtx_code_label *fallthru_label = NULL;
|
||
if (nlabels > 0)
|
||
{
|
||
basic_block fallthru_bb = NULL;
|
||
edge fallthru = find_fallthru_edge (gimple_bb (stmt)->succs);
|
||
if (fallthru)
|
||
fallthru_bb = fallthru->dest;
|
||
|
||
for (i = 0; i < nlabels; ++i)
|
||
{
|
||
tree label = TREE_VALUE (gimple_asm_label_op (stmt, i));
|
||
rtx_insn *r;
|
||
/* If asm goto has any labels in the fallthru basic block, use
|
||
a label that we emit immediately after the asm goto. Expansion
|
||
may insert further instructions into the same basic block after
|
||
asm goto and if we don't do this, insertion of instructions on
|
||
the fallthru edge might misbehave. See PR58670. */
|
||
if (fallthru_bb && label_to_block (cfun, label) == fallthru_bb)
|
||
{
|
||
if (fallthru_label == NULL_RTX)
|
||
fallthru_label = gen_label_rtx ();
|
||
r = fallthru_label;
|
||
}
|
||
else
|
||
r = label_rtx (label);
|
||
ASM_OPERANDS_LABEL (body, i) = gen_rtx_LABEL_REF (Pmode, r);
|
||
}
|
||
}
|
||
|
||
/* Now, for each output, construct an rtx
|
||
(set OUTPUT (asm_operands INSN OUTPUTCONSTRAINT OUTPUTNUMBER
|
||
ARGVEC CONSTRAINTS OPNAMES))
|
||
If there is more than one, put them inside a PARALLEL. */
|
||
|
||
if (nlabels > 0 && nclobbers == 0)
|
||
{
|
||
gcc_assert (noutputs == 0);
|
||
emit_jump_insn (body);
|
||
}
|
||
else if (noutputs == 0 && nclobbers == 0)
|
||
{
|
||
/* No output operands: put in a raw ASM_OPERANDS rtx. */
|
||
emit_insn (body);
|
||
}
|
||
else if (noutputs == 1 && nclobbers == 0)
|
||
{
|
||
ASM_OPERANDS_OUTPUT_CONSTRAINT (body) = constraints[0];
|
||
emit_insn (gen_rtx_SET (output_rvec[0], body));
|
||
}
|
||
else
|
||
{
|
||
rtx obody = body;
|
||
int num = noutputs;
|
||
|
||
if (num == 0)
|
||
num = 1;
|
||
|
||
body = gen_rtx_PARALLEL (VOIDmode, rtvec_alloc (num + nclobbers));
|
||
|
||
/* For each output operand, store a SET. */
|
||
for (i = 0; i < noutputs; ++i)
|
||
{
|
||
rtx src, o = output_rvec[i];
|
||
if (i == 0)
|
||
{
|
||
ASM_OPERANDS_OUTPUT_CONSTRAINT (obody) = constraints[0];
|
||
src = obody;
|
||
}
|
||
else
|
||
{
|
||
src = gen_rtx_ASM_OPERANDS (GET_MODE (o),
|
||
ASM_OPERANDS_TEMPLATE (obody),
|
||
constraints[i], i, argvec,
|
||
constraintvec, labelvec, locus);
|
||
MEM_VOLATILE_P (src) = gimple_asm_volatile_p (stmt);
|
||
}
|
||
XVECEXP (body, 0, i) = gen_rtx_SET (o, src);
|
||
}
|
||
|
||
/* If there are no outputs (but there are some clobbers)
|
||
store the bare ASM_OPERANDS into the PARALLEL. */
|
||
if (i == 0)
|
||
XVECEXP (body, 0, i++) = obody;
|
||
|
||
/* Store (clobber REG) for each clobbered register specified. */
|
||
for (unsigned j = 0; j < nclobbers; ++j)
|
||
{
|
||
rtx clobbered_reg = clobber_rvec[j];
|
||
|
||
/* Do sanity check for overlap between clobbers and respectively
|
||
input and outputs that hasn't been handled. Such overlap
|
||
should have been detected and reported above. */
|
||
if (!clobber_conflict_found && REG_P (clobbered_reg))
|
||
{
|
||
/* We test the old body (obody) contents to avoid
|
||
tripping over the under-construction body. */
|
||
for (unsigned k = 0; k < noutputs; ++k)
|
||
if (reg_overlap_mentioned_p (clobbered_reg, output_rvec[k]))
|
||
internal_error ("%<asm%> clobber conflict with "
|
||
"output operand");
|
||
|
||
for (unsigned k = 0; k < ninputs - ninout; ++k)
|
||
if (reg_overlap_mentioned_p (clobbered_reg, input_rvec[k]))
|
||
internal_error ("%<asm%> clobber conflict with "
|
||
"input operand");
|
||
}
|
||
|
||
XVECEXP (body, 0, i++) = gen_rtx_CLOBBER (VOIDmode, clobbered_reg);
|
||
}
|
||
|
||
if (nlabels > 0)
|
||
emit_jump_insn (body);
|
||
else
|
||
emit_insn (body);
|
||
}
|
||
|
||
generating_concat_p = old_generating_concat_p;
|
||
|
||
if (fallthru_label)
|
||
emit_label (fallthru_label);
|
||
|
||
if (after_md_seq)
|
||
emit_insn (after_md_seq);
|
||
if (after_rtl_seq)
|
||
emit_insn (after_rtl_seq);
|
||
|
||
free_temp_slots ();
|
||
crtl->has_asm_statement = 1;
|
||
}
|
||
|
||
/* Emit code to jump to the address
|
||
specified by the pointer expression EXP. */
|
||
|
||
static void
|
||
expand_computed_goto (tree exp)
|
||
{
|
||
rtx x = expand_normal (exp);
|
||
|
||
do_pending_stack_adjust ();
|
||
emit_indirect_jump (x);
|
||
}
|
||
|
||
/* Generate RTL code for a `goto' statement with target label LABEL.
|
||
LABEL should be a LABEL_DECL tree node that was or will later be
|
||
defined with `expand_label'. */
|
||
|
||
static void
|
||
expand_goto (tree label)
|
||
{
|
||
if (flag_checking)
|
||
{
|
||
/* Check for a nonlocal goto to a containing function. Should have
|
||
gotten translated to __builtin_nonlocal_goto. */
|
||
tree context = decl_function_context (label);
|
||
gcc_assert (!context || context == current_function_decl);
|
||
}
|
||
|
||
emit_jump (jump_target_rtx (label));
|
||
}
|
||
|
||
/* Output a return with no value. */
|
||
|
||
static void
|
||
expand_null_return_1 (void)
|
||
{
|
||
clear_pending_stack_adjust ();
|
||
do_pending_stack_adjust ();
|
||
emit_jump (return_label);
|
||
}
|
||
|
||
/* Generate RTL to return from the current function, with no value.
|
||
(That is, we do not do anything about returning any value.) */
|
||
|
||
void
|
||
expand_null_return (void)
|
||
{
|
||
/* If this function was declared to return a value, but we
|
||
didn't, clobber the return registers so that they are not
|
||
propagated live to the rest of the function. */
|
||
clobber_return_register ();
|
||
|
||
expand_null_return_1 ();
|
||
}
|
||
|
||
/* Generate RTL to return from the current function, with value VAL. */
|
||
|
||
static void
|
||
expand_value_return (rtx val)
|
||
{
|
||
/* Copy the value to the return location unless it's already there. */
|
||
|
||
tree decl = DECL_RESULT (current_function_decl);
|
||
rtx return_reg = DECL_RTL (decl);
|
||
if (return_reg != val)
|
||
{
|
||
tree funtype = TREE_TYPE (current_function_decl);
|
||
tree type = TREE_TYPE (decl);
|
||
int unsignedp = TYPE_UNSIGNED (type);
|
||
machine_mode old_mode = DECL_MODE (decl);
|
||
machine_mode mode;
|
||
if (DECL_BY_REFERENCE (decl))
|
||
mode = promote_function_mode (type, old_mode, &unsignedp, funtype, 2);
|
||
else
|
||
mode = promote_function_mode (type, old_mode, &unsignedp, funtype, 1);
|
||
|
||
if (mode != old_mode)
|
||
val = convert_modes (mode, old_mode, val, unsignedp);
|
||
|
||
if (GET_CODE (return_reg) == PARALLEL)
|
||
emit_group_load (return_reg, val, type, int_size_in_bytes (type));
|
||
else
|
||
emit_move_insn (return_reg, val);
|
||
}
|
||
|
||
expand_null_return_1 ();
|
||
}
|
||
|
||
/* Generate RTL to evaluate the expression RETVAL and return it
|
||
from the current function. */
|
||
|
||
static void
|
||
expand_return (tree retval)
|
||
{
|
||
rtx result_rtl;
|
||
rtx val = 0;
|
||
tree retval_rhs;
|
||
|
||
/* If function wants no value, give it none. */
|
||
if (TREE_CODE (TREE_TYPE (TREE_TYPE (current_function_decl))) == VOID_TYPE)
|
||
{
|
||
expand_normal (retval);
|
||
expand_null_return ();
|
||
return;
|
||
}
|
||
|
||
if (retval == error_mark_node)
|
||
{
|
||
/* Treat this like a return of no value from a function that
|
||
returns a value. */
|
||
expand_null_return ();
|
||
return;
|
||
}
|
||
else if ((TREE_CODE (retval) == MODIFY_EXPR
|
||
|| TREE_CODE (retval) == INIT_EXPR)
|
||
&& TREE_CODE (TREE_OPERAND (retval, 0)) == RESULT_DECL)
|
||
retval_rhs = TREE_OPERAND (retval, 1);
|
||
else
|
||
retval_rhs = retval;
|
||
|
||
result_rtl = DECL_RTL (DECL_RESULT (current_function_decl));
|
||
|
||
/* If we are returning the RESULT_DECL, then the value has already
|
||
been stored into it, so we don't have to do anything special. */
|
||
if (TREE_CODE (retval_rhs) == RESULT_DECL)
|
||
expand_value_return (result_rtl);
|
||
|
||
/* If the result is an aggregate that is being returned in one (or more)
|
||
registers, load the registers here. */
|
||
|
||
else if (retval_rhs != 0
|
||
&& TYPE_MODE (TREE_TYPE (retval_rhs)) == BLKmode
|
||
&& REG_P (result_rtl))
|
||
{
|
||
val = copy_blkmode_to_reg (GET_MODE (result_rtl), retval_rhs);
|
||
if (val)
|
||
{
|
||
/* Use the mode of the result value on the return register. */
|
||
PUT_MODE (result_rtl, GET_MODE (val));
|
||
expand_value_return (val);
|
||
}
|
||
else
|
||
expand_null_return ();
|
||
}
|
||
else if (retval_rhs != 0
|
||
&& !VOID_TYPE_P (TREE_TYPE (retval_rhs))
|
||
&& (REG_P (result_rtl)
|
||
|| (GET_CODE (result_rtl) == PARALLEL)))
|
||
{
|
||
/* Compute the return value into a temporary (usually a pseudo reg). */
|
||
val
|
||
= assign_temp (TREE_TYPE (DECL_RESULT (current_function_decl)), 0, 1);
|
||
val = expand_expr (retval_rhs, val, GET_MODE (val), EXPAND_NORMAL);
|
||
val = force_not_mem (val);
|
||
expand_value_return (val);
|
||
}
|
||
else
|
||
{
|
||
/* No hard reg used; calculate value into hard return reg. */
|
||
expand_expr (retval, const0_rtx, VOIDmode, EXPAND_NORMAL);
|
||
expand_value_return (result_rtl);
|
||
}
|
||
}
|
||
|
||
/* Expand a clobber of LHS. If LHS is stored it in a multi-part
|
||
register, tell the rtl optimizers that its value is no longer
|
||
needed. */
|
||
|
||
static void
|
||
expand_clobber (tree lhs)
|
||
{
|
||
if (DECL_P (lhs))
|
||
{
|
||
rtx decl_rtl = DECL_RTL_IF_SET (lhs);
|
||
if (decl_rtl && REG_P (decl_rtl))
|
||
{
|
||
machine_mode decl_mode = GET_MODE (decl_rtl);
|
||
if (maybe_gt (GET_MODE_SIZE (decl_mode),
|
||
REGMODE_NATURAL_SIZE (decl_mode)))
|
||
emit_clobber (decl_rtl);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* A subroutine of expand_gimple_stmt, expanding one gimple statement
|
||
STMT that doesn't require special handling for outgoing edges. That
|
||
is no tailcalls and no GIMPLE_COND. */
|
||
|
||
static void
|
||
expand_gimple_stmt_1 (gimple *stmt)
|
||
{
|
||
tree op0;
|
||
|
||
set_curr_insn_location (gimple_location (stmt));
|
||
|
||
switch (gimple_code (stmt))
|
||
{
|
||
case GIMPLE_GOTO:
|
||
op0 = gimple_goto_dest (stmt);
|
||
if (TREE_CODE (op0) == LABEL_DECL)
|
||
expand_goto (op0);
|
||
else
|
||
expand_computed_goto (op0);
|
||
break;
|
||
case GIMPLE_LABEL:
|
||
expand_label (gimple_label_label (as_a <glabel *> (stmt)));
|
||
break;
|
||
case GIMPLE_NOP:
|
||
case GIMPLE_PREDICT:
|
||
break;
|
||
case GIMPLE_SWITCH:
|
||
{
|
||
gswitch *swtch = as_a <gswitch *> (stmt);
|
||
if (gimple_switch_num_labels (swtch) == 1)
|
||
expand_goto (CASE_LABEL (gimple_switch_default_label (swtch)));
|
||
else
|
||
expand_case (swtch);
|
||
}
|
||
break;
|
||
case GIMPLE_ASM:
|
||
expand_asm_stmt (as_a <gasm *> (stmt));
|
||
break;
|
||
case GIMPLE_CALL:
|
||
expand_call_stmt (as_a <gcall *> (stmt));
|
||
break;
|
||
|
||
case GIMPLE_RETURN:
|
||
{
|
||
op0 = gimple_return_retval (as_a <greturn *> (stmt));
|
||
|
||
/* If a return doesn't have a location, it very likely represents
|
||
multiple user returns so we cannot let it inherit the location
|
||
of the last statement of the previous basic block in RTL. */
|
||
if (!gimple_has_location (stmt))
|
||
set_curr_insn_location (cfun->function_end_locus);
|
||
|
||
if (op0 && op0 != error_mark_node)
|
||
{
|
||
tree result = DECL_RESULT (current_function_decl);
|
||
|
||
/* If we are not returning the current function's RESULT_DECL,
|
||
build an assignment to it. */
|
||
if (op0 != result)
|
||
{
|
||
/* I believe that a function's RESULT_DECL is unique. */
|
||
gcc_assert (TREE_CODE (op0) != RESULT_DECL);
|
||
|
||
/* ??? We'd like to use simply expand_assignment here,
|
||
but this fails if the value is of BLKmode but the return
|
||
decl is a register. expand_return has special handling
|
||
for this combination, which eventually should move
|
||
to common code. See comments there. Until then, let's
|
||
build a modify expression :-/ */
|
||
op0 = build2 (MODIFY_EXPR, TREE_TYPE (result),
|
||
result, op0);
|
||
}
|
||
}
|
||
|
||
if (!op0)
|
||
expand_null_return ();
|
||
else
|
||
expand_return (op0);
|
||
}
|
||
break;
|
||
|
||
case GIMPLE_ASSIGN:
|
||
{
|
||
gassign *assign_stmt = as_a <gassign *> (stmt);
|
||
tree lhs = gimple_assign_lhs (assign_stmt);
|
||
|
||
/* Tree expand used to fiddle with |= and &= of two bitfield
|
||
COMPONENT_REFs here. This can't happen with gimple, the LHS
|
||
of binary assigns must be a gimple reg. */
|
||
|
||
if (TREE_CODE (lhs) != SSA_NAME
|
||
|| get_gimple_rhs_class (gimple_expr_code (stmt))
|
||
== GIMPLE_SINGLE_RHS)
|
||
{
|
||
tree rhs = gimple_assign_rhs1 (assign_stmt);
|
||
gcc_assert (get_gimple_rhs_class (gimple_expr_code (stmt))
|
||
== GIMPLE_SINGLE_RHS);
|
||
if (gimple_has_location (stmt) && CAN_HAVE_LOCATION_P (rhs)
|
||
/* Do not put locations on possibly shared trees. */
|
||
&& !is_gimple_min_invariant (rhs))
|
||
SET_EXPR_LOCATION (rhs, gimple_location (stmt));
|
||
if (TREE_CLOBBER_P (rhs))
|
||
/* This is a clobber to mark the going out of scope for
|
||
this LHS. */
|
||
expand_clobber (lhs);
|
||
else
|
||
expand_assignment (lhs, rhs,
|
||
gimple_assign_nontemporal_move_p (
|
||
assign_stmt));
|
||
}
|
||
else
|
||
{
|
||
rtx target, temp;
|
||
bool nontemporal = gimple_assign_nontemporal_move_p (assign_stmt);
|
||
struct separate_ops ops;
|
||
bool promoted = false;
|
||
|
||
target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
|
||
if (GET_CODE (target) == SUBREG && SUBREG_PROMOTED_VAR_P (target))
|
||
promoted = true;
|
||
|
||
ops.code = gimple_assign_rhs_code (assign_stmt);
|
||
ops.type = TREE_TYPE (lhs);
|
||
switch (get_gimple_rhs_class (ops.code))
|
||
{
|
||
case GIMPLE_TERNARY_RHS:
|
||
ops.op2 = gimple_assign_rhs3 (assign_stmt);
|
||
/* Fallthru */
|
||
case GIMPLE_BINARY_RHS:
|
||
ops.op1 = gimple_assign_rhs2 (assign_stmt);
|
||
/* Fallthru */
|
||
case GIMPLE_UNARY_RHS:
|
||
ops.op0 = gimple_assign_rhs1 (assign_stmt);
|
||
break;
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
ops.location = gimple_location (stmt);
|
||
|
||
/* If we want to use a nontemporal store, force the value to
|
||
register first. If we store into a promoted register,
|
||
don't directly expand to target. */
|
||
temp = nontemporal || promoted ? NULL_RTX : target;
|
||
temp = expand_expr_real_2 (&ops, temp, GET_MODE (target),
|
||
EXPAND_NORMAL);
|
||
|
||
if (temp == target)
|
||
;
|
||
else if (promoted)
|
||
{
|
||
int unsignedp = SUBREG_PROMOTED_SIGN (target);
|
||
/* If TEMP is a VOIDmode constant, use convert_modes to make
|
||
sure that we properly convert it. */
|
||
if (CONSTANT_P (temp) && GET_MODE (temp) == VOIDmode)
|
||
{
|
||
temp = convert_modes (GET_MODE (target),
|
||
TYPE_MODE (ops.type),
|
||
temp, unsignedp);
|
||
temp = convert_modes (GET_MODE (SUBREG_REG (target)),
|
||
GET_MODE (target), temp, unsignedp);
|
||
}
|
||
|
||
convert_move (SUBREG_REG (target), temp, unsignedp);
|
||
}
|
||
else if (nontemporal && emit_storent_insn (target, temp))
|
||
;
|
||
else
|
||
{
|
||
temp = force_operand (temp, target);
|
||
if (temp != target)
|
||
emit_move_insn (target, temp);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
default:
|
||
gcc_unreachable ();
|
||
}
|
||
}
|
||
|
||
/* Expand one gimple statement STMT and return the last RTL instruction
|
||
before any of the newly generated ones.
|
||
|
||
In addition to generating the necessary RTL instructions this also
|
||
sets REG_EH_REGION notes if necessary and sets the current source
|
||
location for diagnostics. */
|
||
|
||
static rtx_insn *
|
||
expand_gimple_stmt (gimple *stmt)
|
||
{
|
||
location_t saved_location = input_location;
|
||
rtx_insn *last = get_last_insn ();
|
||
int lp_nr;
|
||
|
||
gcc_assert (cfun);
|
||
|
||
/* We need to save and restore the current source location so that errors
|
||
discovered during expansion are emitted with the right location. But
|
||
it would be better if the diagnostic routines used the source location
|
||
embedded in the tree nodes rather than globals. */
|
||
if (gimple_has_location (stmt))
|
||
input_location = gimple_location (stmt);
|
||
|
||
expand_gimple_stmt_1 (stmt);
|
||
|
||
/* Free any temporaries used to evaluate this statement. */
|
||
free_temp_slots ();
|
||
|
||
input_location = saved_location;
|
||
|
||
/* Mark all insns that may trap. */
|
||
lp_nr = lookup_stmt_eh_lp (stmt);
|
||
if (lp_nr)
|
||
{
|
||
rtx_insn *insn;
|
||
for (insn = next_real_insn (last); insn;
|
||
insn = next_real_insn (insn))
|
||
{
|
||
if (! find_reg_note (insn, REG_EH_REGION, NULL_RTX)
|
||
/* If we want exceptions for non-call insns, any
|
||
may_trap_p instruction may throw. */
|
||
&& GET_CODE (PATTERN (insn)) != CLOBBER
|
||
&& GET_CODE (PATTERN (insn)) != USE
|
||
&& insn_could_throw_p (insn))
|
||
make_reg_eh_region_note (insn, 0, lp_nr);
|
||
}
|
||
}
|
||
|
||
return last;
|
||
}
|
||
|
||
/* A subroutine of expand_gimple_basic_block. Expand one GIMPLE_CALL
|
||
that has CALL_EXPR_TAILCALL set. Returns non-null if we actually
|
||
generated a tail call (something that might be denied by the ABI
|
||
rules governing the call; see calls.c).
|
||
|
||
Sets CAN_FALLTHRU if we generated a *conditional* tail call, and
|
||
can still reach the rest of BB. The case here is __builtin_sqrt,
|
||
where the NaN result goes through the external function (with a
|
||
tailcall) and the normal result happens via a sqrt instruction. */
|
||
|
||
static basic_block
|
||
expand_gimple_tailcall (basic_block bb, gcall *stmt, bool *can_fallthru)
|
||
{
|
||
rtx_insn *last2, *last;
|
||
edge e;
|
||
edge_iterator ei;
|
||
profile_probability probability;
|
||
|
||
last2 = last = expand_gimple_stmt (stmt);
|
||
|
||
for (last = NEXT_INSN (last); last; last = NEXT_INSN (last))
|
||
if (CALL_P (last) && SIBLING_CALL_P (last))
|
||
goto found;
|
||
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last2);
|
||
|
||
*can_fallthru = true;
|
||
return NULL;
|
||
|
||
found:
|
||
/* ??? Wouldn't it be better to just reset any pending stack adjust?
|
||
Any instructions emitted here are about to be deleted. */
|
||
do_pending_stack_adjust ();
|
||
|
||
/* Remove any non-eh, non-abnormal edges that don't go to exit. */
|
||
/* ??? I.e. the fallthrough edge. HOWEVER! If there were to be
|
||
EH or abnormal edges, we shouldn't have created a tail call in
|
||
the first place. So it seems to me we should just be removing
|
||
all edges here, or redirecting the existing fallthru edge to
|
||
the exit block. */
|
||
|
||
probability = profile_probability::never ();
|
||
|
||
for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); )
|
||
{
|
||
if (!(e->flags & (EDGE_ABNORMAL | EDGE_EH)))
|
||
{
|
||
if (e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
|
||
e->dest->count -= e->count ();
|
||
probability += e->probability;
|
||
remove_edge (e);
|
||
}
|
||
else
|
||
ei_next (&ei);
|
||
}
|
||
|
||
/* This is somewhat ugly: the call_expr expander often emits instructions
|
||
after the sibcall (to perform the function return). These confuse the
|
||
find_many_sub_basic_blocks code, so we need to get rid of these. */
|
||
last = NEXT_INSN (last);
|
||
gcc_assert (BARRIER_P (last));
|
||
|
||
*can_fallthru = false;
|
||
while (NEXT_INSN (last))
|
||
{
|
||
/* For instance an sqrt builtin expander expands if with
|
||
sibcall in the then and label for `else`. */
|
||
if (LABEL_P (NEXT_INSN (last)))
|
||
{
|
||
*can_fallthru = true;
|
||
break;
|
||
}
|
||
delete_insn (NEXT_INSN (last));
|
||
}
|
||
|
||
e = make_edge (bb, EXIT_BLOCK_PTR_FOR_FN (cfun), EDGE_ABNORMAL
|
||
| EDGE_SIBCALL);
|
||
e->probability = probability;
|
||
BB_END (bb) = last;
|
||
update_bb_for_insn (bb);
|
||
|
||
if (NEXT_INSN (last))
|
||
{
|
||
bb = create_basic_block (NEXT_INSN (last), get_last_insn (), bb);
|
||
|
||
last = BB_END (bb);
|
||
if (BARRIER_P (last))
|
||
BB_END (bb) = PREV_INSN (last);
|
||
}
|
||
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last2);
|
||
|
||
return bb;
|
||
}
|
||
|
||
/* Return the difference between the floor and the truncated result of
|
||
a signed division by OP1 with remainder MOD. */
|
||
static rtx
|
||
floor_sdiv_adjust (machine_mode mode, rtx mod, rtx op1)
|
||
{
|
||
/* (mod != 0 ? (op1 / mod < 0 ? -1 : 0) : 0) */
|
||
return gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_NE (BImode, mod, const0_rtx),
|
||
gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_LT (BImode,
|
||
gen_rtx_DIV (mode, op1, mod),
|
||
const0_rtx),
|
||
constm1_rtx, const0_rtx),
|
||
const0_rtx);
|
||
}
|
||
|
||
/* Return the difference between the ceil and the truncated result of
|
||
a signed division by OP1 with remainder MOD. */
|
||
static rtx
|
||
ceil_sdiv_adjust (machine_mode mode, rtx mod, rtx op1)
|
||
{
|
||
/* (mod != 0 ? (op1 / mod > 0 ? 1 : 0) : 0) */
|
||
return gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_NE (BImode, mod, const0_rtx),
|
||
gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_GT (BImode,
|
||
gen_rtx_DIV (mode, op1, mod),
|
||
const0_rtx),
|
||
const1_rtx, const0_rtx),
|
||
const0_rtx);
|
||
}
|
||
|
||
/* Return the difference between the ceil and the truncated result of
|
||
an unsigned division by OP1 with remainder MOD. */
|
||
static rtx
|
||
ceil_udiv_adjust (machine_mode mode, rtx mod, rtx op1 ATTRIBUTE_UNUSED)
|
||
{
|
||
/* (mod != 0 ? 1 : 0) */
|
||
return gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_NE (BImode, mod, const0_rtx),
|
||
const1_rtx, const0_rtx);
|
||
}
|
||
|
||
/* Return the difference between the rounded and the truncated result
|
||
of a signed division by OP1 with remainder MOD. Halfway cases are
|
||
rounded away from zero, rather than to the nearest even number. */
|
||
static rtx
|
||
round_sdiv_adjust (machine_mode mode, rtx mod, rtx op1)
|
||
{
|
||
/* (abs (mod) >= abs (op1) - abs (mod)
|
||
? (op1 / mod > 0 ? 1 : -1)
|
||
: 0) */
|
||
return gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_GE (BImode, gen_rtx_ABS (mode, mod),
|
||
gen_rtx_MINUS (mode,
|
||
gen_rtx_ABS (mode, op1),
|
||
gen_rtx_ABS (mode, mod))),
|
||
gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_GT (BImode,
|
||
gen_rtx_DIV (mode, op1, mod),
|
||
const0_rtx),
|
||
const1_rtx, constm1_rtx),
|
||
const0_rtx);
|
||
}
|
||
|
||
/* Return the difference between the rounded and the truncated result
|
||
of a unsigned division by OP1 with remainder MOD. Halfway cases
|
||
are rounded away from zero, rather than to the nearest even
|
||
number. */
|
||
static rtx
|
||
round_udiv_adjust (machine_mode mode, rtx mod, rtx op1)
|
||
{
|
||
/* (mod >= op1 - mod ? 1 : 0) */
|
||
return gen_rtx_IF_THEN_ELSE
|
||
(mode, gen_rtx_GE (BImode, mod,
|
||
gen_rtx_MINUS (mode, op1, mod)),
|
||
const1_rtx, const0_rtx);
|
||
}
|
||
|
||
/* Convert X to MODE, that must be Pmode or ptr_mode, without emitting
|
||
any rtl. */
|
||
|
||
static rtx
|
||
convert_debug_memory_address (scalar_int_mode mode, rtx x,
|
||
addr_space_t as)
|
||
{
|
||
#ifndef POINTERS_EXTEND_UNSIGNED
|
||
gcc_assert (mode == Pmode
|
||
|| mode == targetm.addr_space.address_mode (as));
|
||
gcc_assert (GET_MODE (x) == mode || GET_MODE (x) == VOIDmode);
|
||
#else
|
||
rtx temp;
|
||
|
||
gcc_assert (targetm.addr_space.valid_pointer_mode (mode, as));
|
||
|
||
if (GET_MODE (x) == mode || GET_MODE (x) == VOIDmode)
|
||
return x;
|
||
|
||
/* X must have some form of address mode already. */
|
||
scalar_int_mode xmode = as_a <scalar_int_mode> (GET_MODE (x));
|
||
if (GET_MODE_PRECISION (mode) < GET_MODE_PRECISION (xmode))
|
||
x = lowpart_subreg (mode, x, xmode);
|
||
else if (POINTERS_EXTEND_UNSIGNED > 0)
|
||
x = gen_rtx_ZERO_EXTEND (mode, x);
|
||
else if (!POINTERS_EXTEND_UNSIGNED)
|
||
x = gen_rtx_SIGN_EXTEND (mode, x);
|
||
else
|
||
{
|
||
switch (GET_CODE (x))
|
||
{
|
||
case SUBREG:
|
||
if ((SUBREG_PROMOTED_VAR_P (x)
|
||
|| (REG_P (SUBREG_REG (x)) && REG_POINTER (SUBREG_REG (x)))
|
||
|| (GET_CODE (SUBREG_REG (x)) == PLUS
|
||
&& REG_P (XEXP (SUBREG_REG (x), 0))
|
||
&& REG_POINTER (XEXP (SUBREG_REG (x), 0))
|
||
&& CONST_INT_P (XEXP (SUBREG_REG (x), 1))))
|
||
&& GET_MODE (SUBREG_REG (x)) == mode)
|
||
return SUBREG_REG (x);
|
||
break;
|
||
case LABEL_REF:
|
||
temp = gen_rtx_LABEL_REF (mode, label_ref_label (x));
|
||
LABEL_REF_NONLOCAL_P (temp) = LABEL_REF_NONLOCAL_P (x);
|
||
return temp;
|
||
case SYMBOL_REF:
|
||
temp = shallow_copy_rtx (x);
|
||
PUT_MODE (temp, mode);
|
||
return temp;
|
||
case CONST:
|
||
temp = convert_debug_memory_address (mode, XEXP (x, 0), as);
|
||
if (temp)
|
||
temp = gen_rtx_CONST (mode, temp);
|
||
return temp;
|
||
case PLUS:
|
||
case MINUS:
|
||
if (CONST_INT_P (XEXP (x, 1)))
|
||
{
|
||
temp = convert_debug_memory_address (mode, XEXP (x, 0), as);
|
||
if (temp)
|
||
return gen_rtx_fmt_ee (GET_CODE (x), mode, temp, XEXP (x, 1));
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
/* Don't know how to express ptr_extend as operation in debug info. */
|
||
return NULL;
|
||
}
|
||
#endif /* POINTERS_EXTEND_UNSIGNED */
|
||
|
||
return x;
|
||
}
|
||
|
||
/* Map from SSA_NAMEs to corresponding DEBUG_EXPR_DECLs created
|
||
by avoid_deep_ter_for_debug. */
|
||
|
||
static hash_map<tree, tree> *deep_ter_debug_map;
|
||
|
||
/* Split too deep TER chains for debug stmts using debug temporaries. */
|
||
|
||
static void
|
||
avoid_deep_ter_for_debug (gimple *stmt, int depth)
|
||
{
|
||
use_operand_p use_p;
|
||
ssa_op_iter iter;
|
||
FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_USE)
|
||
{
|
||
tree use = USE_FROM_PTR (use_p);
|
||
if (TREE_CODE (use) != SSA_NAME || SSA_NAME_IS_DEFAULT_DEF (use))
|
||
continue;
|
||
gimple *g = get_gimple_for_ssa_name (use);
|
||
if (g == NULL)
|
||
continue;
|
||
if (depth > 6 && !stmt_ends_bb_p (g))
|
||
{
|
||
if (deep_ter_debug_map == NULL)
|
||
deep_ter_debug_map = new hash_map<tree, tree>;
|
||
|
||
tree &vexpr = deep_ter_debug_map->get_or_insert (use);
|
||
if (vexpr != NULL)
|
||
continue;
|
||
vexpr = make_node (DEBUG_EXPR_DECL);
|
||
gimple *def_temp = gimple_build_debug_bind (vexpr, use, g);
|
||
DECL_ARTIFICIAL (vexpr) = 1;
|
||
TREE_TYPE (vexpr) = TREE_TYPE (use);
|
||
SET_DECL_MODE (vexpr, TYPE_MODE (TREE_TYPE (use)));
|
||
gimple_stmt_iterator gsi = gsi_for_stmt (g);
|
||
gsi_insert_after (&gsi, def_temp, GSI_NEW_STMT);
|
||
avoid_deep_ter_for_debug (def_temp, 0);
|
||
}
|
||
else
|
||
avoid_deep_ter_for_debug (g, depth + 1);
|
||
}
|
||
}
|
||
|
||
/* Return an RTX equivalent to the value of the parameter DECL. */
|
||
|
||
static rtx
|
||
expand_debug_parm_decl (tree decl)
|
||
{
|
||
rtx incoming = DECL_INCOMING_RTL (decl);
|
||
|
||
if (incoming
|
||
&& GET_MODE (incoming) != BLKmode
|
||
&& ((REG_P (incoming) && HARD_REGISTER_P (incoming))
|
||
|| (MEM_P (incoming)
|
||
&& REG_P (XEXP (incoming, 0))
|
||
&& HARD_REGISTER_P (XEXP (incoming, 0)))))
|
||
{
|
||
rtx rtl = gen_rtx_ENTRY_VALUE (GET_MODE (incoming));
|
||
|
||
#ifdef HAVE_window_save
|
||
/* DECL_INCOMING_RTL uses the INCOMING_REGNO of parameter registers.
|
||
If the target machine has an explicit window save instruction, the
|
||
actual entry value is the corresponding OUTGOING_REGNO instead. */
|
||
if (REG_P (incoming)
|
||
&& OUTGOING_REGNO (REGNO (incoming)) != REGNO (incoming))
|
||
incoming
|
||
= gen_rtx_REG_offset (incoming, GET_MODE (incoming),
|
||
OUTGOING_REGNO (REGNO (incoming)), 0);
|
||
else if (MEM_P (incoming))
|
||
{
|
||
rtx reg = XEXP (incoming, 0);
|
||
if (OUTGOING_REGNO (REGNO (reg)) != REGNO (reg))
|
||
{
|
||
reg = gen_raw_REG (GET_MODE (reg), OUTGOING_REGNO (REGNO (reg)));
|
||
incoming = replace_equiv_address_nv (incoming, reg);
|
||
}
|
||
else
|
||
incoming = copy_rtx (incoming);
|
||
}
|
||
#endif
|
||
|
||
ENTRY_VALUE_EXP (rtl) = incoming;
|
||
return rtl;
|
||
}
|
||
|
||
if (incoming
|
||
&& GET_MODE (incoming) != BLKmode
|
||
&& !TREE_ADDRESSABLE (decl)
|
||
&& MEM_P (incoming)
|
||
&& (XEXP (incoming, 0) == virtual_incoming_args_rtx
|
||
|| (GET_CODE (XEXP (incoming, 0)) == PLUS
|
||
&& XEXP (XEXP (incoming, 0), 0) == virtual_incoming_args_rtx
|
||
&& CONST_INT_P (XEXP (XEXP (incoming, 0), 1)))))
|
||
return copy_rtx (incoming);
|
||
|
||
return NULL_RTX;
|
||
}
|
||
|
||
/* Return an RTX equivalent to the value of the tree expression EXP. */
|
||
|
||
static rtx
|
||
expand_debug_expr (tree exp)
|
||
{
|
||
rtx op0 = NULL_RTX, op1 = NULL_RTX, op2 = NULL_RTX;
|
||
machine_mode mode = TYPE_MODE (TREE_TYPE (exp));
|
||
machine_mode inner_mode = VOIDmode;
|
||
int unsignedp = TYPE_UNSIGNED (TREE_TYPE (exp));
|
||
addr_space_t as;
|
||
scalar_int_mode op0_mode, op1_mode, addr_mode;
|
||
|
||
switch (TREE_CODE_CLASS (TREE_CODE (exp)))
|
||
{
|
||
case tcc_expression:
|
||
switch (TREE_CODE (exp))
|
||
{
|
||
case COND_EXPR:
|
||
case DOT_PROD_EXPR:
|
||
case SAD_EXPR:
|
||
case WIDEN_MULT_PLUS_EXPR:
|
||
case WIDEN_MULT_MINUS_EXPR:
|
||
goto ternary;
|
||
|
||
case TRUTH_ANDIF_EXPR:
|
||
case TRUTH_ORIF_EXPR:
|
||
case TRUTH_AND_EXPR:
|
||
case TRUTH_OR_EXPR:
|
||
case TRUTH_XOR_EXPR:
|
||
goto binary;
|
||
|
||
case TRUTH_NOT_EXPR:
|
||
goto unary;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
|
||
ternary:
|
||
op2 = expand_debug_expr (TREE_OPERAND (exp, 2));
|
||
if (!op2)
|
||
return NULL_RTX;
|
||
/* Fall through. */
|
||
|
||
binary:
|
||
case tcc_binary:
|
||
if (mode == BLKmode)
|
||
return NULL_RTX;
|
||
op1 = expand_debug_expr (TREE_OPERAND (exp, 1));
|
||
if (!op1)
|
||
return NULL_RTX;
|
||
switch (TREE_CODE (exp))
|
||
{
|
||
case LSHIFT_EXPR:
|
||
case RSHIFT_EXPR:
|
||
case LROTATE_EXPR:
|
||
case RROTATE_EXPR:
|
||
case WIDEN_LSHIFT_EXPR:
|
||
/* Ensure second operand isn't wider than the first one. */
|
||
inner_mode = TYPE_MODE (TREE_TYPE (TREE_OPERAND (exp, 1)));
|
||
if (is_a <scalar_int_mode> (inner_mode, &op1_mode)
|
||
&& (GET_MODE_UNIT_PRECISION (mode)
|
||
< GET_MODE_PRECISION (op1_mode)))
|
||
op1 = lowpart_subreg (GET_MODE_INNER (mode), op1, op1_mode);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
/* Fall through. */
|
||
|
||
unary:
|
||
case tcc_unary:
|
||
if (mode == BLKmode)
|
||
return NULL_RTX;
|
||
inner_mode = TYPE_MODE (TREE_TYPE (TREE_OPERAND (exp, 0)));
|
||
op0 = expand_debug_expr (TREE_OPERAND (exp, 0));
|
||
if (!op0)
|
||
return NULL_RTX;
|
||
break;
|
||
|
||
case tcc_comparison:
|
||
unsignedp = TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp, 0)));
|
||
goto binary;
|
||
|
||
case tcc_type:
|
||
case tcc_statement:
|
||
gcc_unreachable ();
|
||
|
||
case tcc_constant:
|
||
case tcc_exceptional:
|
||
case tcc_declaration:
|
||
case tcc_reference:
|
||
case tcc_vl_exp:
|
||
break;
|
||
}
|
||
|
||
switch (TREE_CODE (exp))
|
||
{
|
||
case STRING_CST:
|
||
if (!lookup_constant_def (exp))
|
||
{
|
||
if (strlen (TREE_STRING_POINTER (exp)) + 1
|
||
!= (size_t) TREE_STRING_LENGTH (exp))
|
||
return NULL_RTX;
|
||
op0 = gen_rtx_CONST_STRING (Pmode, TREE_STRING_POINTER (exp));
|
||
op0 = gen_rtx_MEM (BLKmode, op0);
|
||
set_mem_attributes (op0, exp, 0);
|
||
return op0;
|
||
}
|
||
/* Fall through. */
|
||
|
||
case INTEGER_CST:
|
||
case REAL_CST:
|
||
case FIXED_CST:
|
||
op0 = expand_expr (exp, NULL_RTX, mode, EXPAND_INITIALIZER);
|
||
return op0;
|
||
|
||
case POLY_INT_CST:
|
||
return immed_wide_int_const (poly_int_cst_value (exp), mode);
|
||
|
||
case COMPLEX_CST:
|
||
gcc_assert (COMPLEX_MODE_P (mode));
|
||
op0 = expand_debug_expr (TREE_REALPART (exp));
|
||
op1 = expand_debug_expr (TREE_IMAGPART (exp));
|
||
return gen_rtx_CONCAT (mode, op0, op1);
|
||
|
||
case DEBUG_EXPR_DECL:
|
||
op0 = DECL_RTL_IF_SET (exp);
|
||
|
||
if (op0)
|
||
return op0;
|
||
|
||
op0 = gen_rtx_DEBUG_EXPR (mode);
|
||
DEBUG_EXPR_TREE_DECL (op0) = exp;
|
||
SET_DECL_RTL (exp, op0);
|
||
|
||
return op0;
|
||
|
||
case VAR_DECL:
|
||
case PARM_DECL:
|
||
case FUNCTION_DECL:
|
||
case LABEL_DECL:
|
||
case CONST_DECL:
|
||
case RESULT_DECL:
|
||
op0 = DECL_RTL_IF_SET (exp);
|
||
|
||
/* This decl was probably optimized away. */
|
||
if (!op0
|
||
/* At least label RTXen are sometimes replaced by
|
||
NOTE_INSN_DELETED_LABEL. Any notes here are not
|
||
handled by copy_rtx. */
|
||
|| NOTE_P (op0))
|
||
{
|
||
if (!VAR_P (exp)
|
||
|| DECL_EXTERNAL (exp)
|
||
|| !TREE_STATIC (exp)
|
||
|| !DECL_NAME (exp)
|
||
|| DECL_HARD_REGISTER (exp)
|
||
|| DECL_IN_CONSTANT_POOL (exp)
|
||
|| mode == VOIDmode)
|
||
return NULL;
|
||
|
||
op0 = make_decl_rtl_for_debug (exp);
|
||
if (!MEM_P (op0)
|
||
|| GET_CODE (XEXP (op0, 0)) != SYMBOL_REF
|
||
|| SYMBOL_REF_DECL (XEXP (op0, 0)) != exp)
|
||
return NULL;
|
||
}
|
||
else
|
||
op0 = copy_rtx (op0);
|
||
|
||
if (GET_MODE (op0) == BLKmode
|
||
/* If op0 is not BLKmode, but mode is, adjust_mode
|
||
below would ICE. While it is likely a FE bug,
|
||
try to be robust here. See PR43166. */
|
||
|| mode == BLKmode
|
||
|| (mode == VOIDmode && GET_MODE (op0) != VOIDmode))
|
||
{
|
||
gcc_assert (MEM_P (op0));
|
||
op0 = adjust_address_nv (op0, mode, 0);
|
||
return op0;
|
||
}
|
||
|
||
/* Fall through. */
|
||
|
||
adjust_mode:
|
||
case PAREN_EXPR:
|
||
CASE_CONVERT:
|
||
{
|
||
inner_mode = GET_MODE (op0);
|
||
|
||
if (mode == inner_mode)
|
||
return op0;
|
||
|
||
if (inner_mode == VOIDmode)
|
||
{
|
||
if (TREE_CODE (exp) == SSA_NAME)
|
||
inner_mode = TYPE_MODE (TREE_TYPE (exp));
|
||
else
|
||
inner_mode = TYPE_MODE (TREE_TYPE (TREE_OPERAND (exp, 0)));
|
||
if (mode == inner_mode)
|
||
return op0;
|
||
}
|
||
|
||
if (FLOAT_MODE_P (mode) && FLOAT_MODE_P (inner_mode))
|
||
{
|
||
if (GET_MODE_UNIT_BITSIZE (mode)
|
||
== GET_MODE_UNIT_BITSIZE (inner_mode))
|
||
op0 = simplify_gen_subreg (mode, op0, inner_mode, 0);
|
||
else if (GET_MODE_UNIT_BITSIZE (mode)
|
||
< GET_MODE_UNIT_BITSIZE (inner_mode))
|
||
op0 = simplify_gen_unary (FLOAT_TRUNCATE, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (FLOAT_EXTEND, mode, op0, inner_mode);
|
||
}
|
||
else if (FLOAT_MODE_P (mode))
|
||
{
|
||
gcc_assert (TREE_CODE (exp) != SSA_NAME);
|
||
if (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp, 0))))
|
||
op0 = simplify_gen_unary (UNSIGNED_FLOAT, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (FLOAT, mode, op0, inner_mode);
|
||
}
|
||
else if (FLOAT_MODE_P (inner_mode))
|
||
{
|
||
if (unsignedp)
|
||
op0 = simplify_gen_unary (UNSIGNED_FIX, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (FIX, mode, op0, inner_mode);
|
||
}
|
||
else if (GET_MODE_UNIT_PRECISION (mode)
|
||
== GET_MODE_UNIT_PRECISION (inner_mode))
|
||
op0 = lowpart_subreg (mode, op0, inner_mode);
|
||
else if (GET_MODE_UNIT_PRECISION (mode)
|
||
< GET_MODE_UNIT_PRECISION (inner_mode))
|
||
op0 = simplify_gen_unary (TRUNCATE, mode, op0, inner_mode);
|
||
else if (UNARY_CLASS_P (exp)
|
||
? TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp, 0)))
|
||
: unsignedp)
|
||
op0 = simplify_gen_unary (ZERO_EXTEND, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (SIGN_EXTEND, mode, op0, inner_mode);
|
||
|
||
return op0;
|
||
}
|
||
|
||
case MEM_REF:
|
||
if (!is_gimple_mem_ref_addr (TREE_OPERAND (exp, 0)))
|
||
{
|
||
tree newexp = fold_binary (MEM_REF, TREE_TYPE (exp),
|
||
TREE_OPERAND (exp, 0),
|
||
TREE_OPERAND (exp, 1));
|
||
if (newexp)
|
||
return expand_debug_expr (newexp);
|
||
}
|
||
/* FALLTHROUGH */
|
||
case INDIRECT_REF:
|
||
inner_mode = TYPE_MODE (TREE_TYPE (TREE_OPERAND (exp, 0)));
|
||
op0 = expand_debug_expr (TREE_OPERAND (exp, 0));
|
||
if (!op0)
|
||
return NULL;
|
||
|
||
if (TREE_CODE (exp) == MEM_REF)
|
||
{
|
||
if (GET_CODE (op0) == DEBUG_IMPLICIT_PTR
|
||
|| (GET_CODE (op0) == PLUS
|
||
&& GET_CODE (XEXP (op0, 0)) == DEBUG_IMPLICIT_PTR))
|
||
/* (mem (debug_implicit_ptr)) might confuse aliasing.
|
||
Instead just use get_inner_reference. */
|
||
goto component_ref;
|
||
|
||
op1 = expand_debug_expr (TREE_OPERAND (exp, 1));
|
||
poly_int64 offset;
|
||
if (!op1 || !poly_int_rtx_p (op1, &offset))
|
||
return NULL;
|
||
|
||
op0 = plus_constant (inner_mode, op0, offset);
|
||
}
|
||
|
||
as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (exp, 0))));
|
||
|
||
op0 = convert_debug_memory_address (targetm.addr_space.address_mode (as),
|
||
op0, as);
|
||
if (op0 == NULL_RTX)
|
||
return NULL;
|
||
|
||
op0 = gen_rtx_MEM (mode, op0);
|
||
set_mem_attributes (op0, exp, 0);
|
||
if (TREE_CODE (exp) == MEM_REF
|
||
&& !is_gimple_mem_ref_addr (TREE_OPERAND (exp, 0)))
|
||
set_mem_expr (op0, NULL_TREE);
|
||
set_mem_addr_space (op0, as);
|
||
|
||
return op0;
|
||
|
||
case TARGET_MEM_REF:
|
||
if (TREE_CODE (TMR_BASE (exp)) == ADDR_EXPR
|
||
&& !DECL_RTL_SET_P (TREE_OPERAND (TMR_BASE (exp), 0)))
|
||
return NULL;
|
||
|
||
op0 = expand_debug_expr
|
||
(tree_mem_ref_addr (build_pointer_type (TREE_TYPE (exp)), exp));
|
||
if (!op0)
|
||
return NULL;
|
||
|
||
as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (exp, 0))));
|
||
op0 = convert_debug_memory_address (targetm.addr_space.address_mode (as),
|
||
op0, as);
|
||
if (op0 == NULL_RTX)
|
||
return NULL;
|
||
|
||
op0 = gen_rtx_MEM (mode, op0);
|
||
|
||
set_mem_attributes (op0, exp, 0);
|
||
set_mem_addr_space (op0, as);
|
||
|
||
return op0;
|
||
|
||
component_ref:
|
||
case ARRAY_REF:
|
||
case ARRAY_RANGE_REF:
|
||
case COMPONENT_REF:
|
||
case BIT_FIELD_REF:
|
||
case REALPART_EXPR:
|
||
case IMAGPART_EXPR:
|
||
case VIEW_CONVERT_EXPR:
|
||
{
|
||
machine_mode mode1;
|
||
poly_int64 bitsize, bitpos;
|
||
tree offset;
|
||
int reversep, volatilep = 0;
|
||
tree tem
|
||
= get_inner_reference (exp, &bitsize, &bitpos, &offset, &mode1,
|
||
&unsignedp, &reversep, &volatilep);
|
||
rtx orig_op0;
|
||
|
||
if (known_eq (bitsize, 0))
|
||
return NULL;
|
||
|
||
orig_op0 = op0 = expand_debug_expr (tem);
|
||
|
||
if (!op0)
|
||
return NULL;
|
||
|
||
if (offset)
|
||
{
|
||
machine_mode addrmode, offmode;
|
||
|
||
if (!MEM_P (op0))
|
||
return NULL;
|
||
|
||
op0 = XEXP (op0, 0);
|
||
addrmode = GET_MODE (op0);
|
||
if (addrmode == VOIDmode)
|
||
addrmode = Pmode;
|
||
|
||
op1 = expand_debug_expr (offset);
|
||
if (!op1)
|
||
return NULL;
|
||
|
||
offmode = GET_MODE (op1);
|
||
if (offmode == VOIDmode)
|
||
offmode = TYPE_MODE (TREE_TYPE (offset));
|
||
|
||
if (addrmode != offmode)
|
||
op1 = lowpart_subreg (addrmode, op1, offmode);
|
||
|
||
/* Don't use offset_address here, we don't need a
|
||
recognizable address, and we don't want to generate
|
||
code. */
|
||
op0 = gen_rtx_MEM (mode, simplify_gen_binary (PLUS, addrmode,
|
||
op0, op1));
|
||
}
|
||
|
||
if (MEM_P (op0))
|
||
{
|
||
if (mode1 == VOIDmode)
|
||
{
|
||
if (maybe_gt (bitsize, MAX_BITSIZE_MODE_ANY_INT))
|
||
return NULL;
|
||
/* Bitfield. */
|
||
mode1 = smallest_int_mode_for_size (bitsize);
|
||
}
|
||
poly_int64 bytepos = bits_to_bytes_round_down (bitpos);
|
||
if (maybe_ne (bytepos, 0))
|
||
{
|
||
op0 = adjust_address_nv (op0, mode1, bytepos);
|
||
bitpos = num_trailing_bits (bitpos);
|
||
}
|
||
else if (known_eq (bitpos, 0)
|
||
&& known_eq (bitsize, GET_MODE_BITSIZE (mode)))
|
||
op0 = adjust_address_nv (op0, mode, 0);
|
||
else if (GET_MODE (op0) != mode1)
|
||
op0 = adjust_address_nv (op0, mode1, 0);
|
||
else
|
||
op0 = copy_rtx (op0);
|
||
if (op0 == orig_op0)
|
||
op0 = shallow_copy_rtx (op0);
|
||
if (TREE_CODE (tem) != SSA_NAME)
|
||
set_mem_attributes (op0, exp, 0);
|
||
}
|
||
|
||
if (known_eq (bitpos, 0) && mode == GET_MODE (op0))
|
||
return op0;
|
||
|
||
if (maybe_lt (bitpos, 0))
|
||
return NULL;
|
||
|
||
if (GET_MODE (op0) == BLKmode || mode == BLKmode)
|
||
return NULL;
|
||
|
||
poly_int64 bytepos;
|
||
if (multiple_p (bitpos, BITS_PER_UNIT, &bytepos)
|
||
&& known_eq (bitsize, GET_MODE_BITSIZE (mode1)))
|
||
{
|
||
machine_mode opmode = GET_MODE (op0);
|
||
|
||
if (opmode == VOIDmode)
|
||
opmode = TYPE_MODE (TREE_TYPE (tem));
|
||
|
||
/* This condition may hold if we're expanding the address
|
||
right past the end of an array that turned out not to
|
||
be addressable (i.e., the address was only computed in
|
||
debug stmts). The gen_subreg below would rightfully
|
||
crash, and the address doesn't really exist, so just
|
||
drop it. */
|
||
if (known_ge (bitpos, GET_MODE_BITSIZE (opmode)))
|
||
return NULL;
|
||
|
||
if (multiple_p (bitpos, GET_MODE_BITSIZE (mode)))
|
||
return simplify_gen_subreg (mode, op0, opmode, bytepos);
|
||
}
|
||
|
||
return simplify_gen_ternary (SCALAR_INT_MODE_P (GET_MODE (op0))
|
||
&& TYPE_UNSIGNED (TREE_TYPE (exp))
|
||
? SIGN_EXTRACT
|
||
: ZERO_EXTRACT, mode,
|
||
GET_MODE (op0) != VOIDmode
|
||
? GET_MODE (op0)
|
||
: TYPE_MODE (TREE_TYPE (tem)),
|
||
op0, gen_int_mode (bitsize, word_mode),
|
||
gen_int_mode (bitpos, word_mode));
|
||
}
|
||
|
||
case ABS_EXPR:
|
||
case ABSU_EXPR:
|
||
return simplify_gen_unary (ABS, mode, op0, mode);
|
||
|
||
case NEGATE_EXPR:
|
||
return simplify_gen_unary (NEG, mode, op0, mode);
|
||
|
||
case BIT_NOT_EXPR:
|
||
return simplify_gen_unary (NOT, mode, op0, mode);
|
||
|
||
case FLOAT_EXPR:
|
||
return simplify_gen_unary (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp,
|
||
0)))
|
||
? UNSIGNED_FLOAT : FLOAT, mode, op0,
|
||
inner_mode);
|
||
|
||
case FIX_TRUNC_EXPR:
|
||
return simplify_gen_unary (unsignedp ? UNSIGNED_FIX : FIX, mode, op0,
|
||
inner_mode);
|
||
|
||
case POINTER_PLUS_EXPR:
|
||
/* For the rare target where pointers are not the same size as
|
||
size_t, we need to check for mis-matched modes and correct
|
||
the addend. */
|
||
if (op0 && op1
|
||
&& is_a <scalar_int_mode> (GET_MODE (op0), &op0_mode)
|
||
&& is_a <scalar_int_mode> (GET_MODE (op1), &op1_mode)
|
||
&& op0_mode != op1_mode)
|
||
{
|
||
if (GET_MODE_BITSIZE (op0_mode) < GET_MODE_BITSIZE (op1_mode)
|
||
/* If OP0 is a partial mode, then we must truncate, even
|
||
if it has the same bitsize as OP1 as GCC's
|
||
representation of partial modes is opaque. */
|
||
|| (GET_MODE_CLASS (op0_mode) == MODE_PARTIAL_INT
|
||
&& (GET_MODE_BITSIZE (op0_mode)
|
||
== GET_MODE_BITSIZE (op1_mode))))
|
||
op1 = simplify_gen_unary (TRUNCATE, op0_mode, op1, op1_mode);
|
||
else
|
||
/* We always sign-extend, regardless of the signedness of
|
||
the operand, because the operand is always unsigned
|
||
here even if the original C expression is signed. */
|
||
op1 = simplify_gen_unary (SIGN_EXTEND, op0_mode, op1, op1_mode);
|
||
}
|
||
/* Fall through. */
|
||
case PLUS_EXPR:
|
||
return simplify_gen_binary (PLUS, mode, op0, op1);
|
||
|
||
case MINUS_EXPR:
|
||
case POINTER_DIFF_EXPR:
|
||
return simplify_gen_binary (MINUS, mode, op0, op1);
|
||
|
||
case MULT_EXPR:
|
||
return simplify_gen_binary (MULT, mode, op0, op1);
|
||
|
||
case RDIV_EXPR:
|
||
case TRUNC_DIV_EXPR:
|
||
case EXACT_DIV_EXPR:
|
||
if (unsignedp)
|
||
return simplify_gen_binary (UDIV, mode, op0, op1);
|
||
else
|
||
return simplify_gen_binary (DIV, mode, op0, op1);
|
||
|
||
case TRUNC_MOD_EXPR:
|
||
return simplify_gen_binary (unsignedp ? UMOD : MOD, mode, op0, op1);
|
||
|
||
case FLOOR_DIV_EXPR:
|
||
if (unsignedp)
|
||
return simplify_gen_binary (UDIV, mode, op0, op1);
|
||
else
|
||
{
|
||
rtx div = simplify_gen_binary (DIV, mode, op0, op1);
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = floor_sdiv_adjust (mode, mod, op1);
|
||
return simplify_gen_binary (PLUS, mode, div, adj);
|
||
}
|
||
|
||
case FLOOR_MOD_EXPR:
|
||
if (unsignedp)
|
||
return simplify_gen_binary (UMOD, mode, op0, op1);
|
||
else
|
||
{
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = floor_sdiv_adjust (mode, mod, op1);
|
||
adj = simplify_gen_unary (NEG, mode,
|
||
simplify_gen_binary (MULT, mode, adj, op1),
|
||
mode);
|
||
return simplify_gen_binary (PLUS, mode, mod, adj);
|
||
}
|
||
|
||
case CEIL_DIV_EXPR:
|
||
if (unsignedp)
|
||
{
|
||
rtx div = simplify_gen_binary (UDIV, mode, op0, op1);
|
||
rtx mod = simplify_gen_binary (UMOD, mode, op0, op1);
|
||
rtx adj = ceil_udiv_adjust (mode, mod, op1);
|
||
return simplify_gen_binary (PLUS, mode, div, adj);
|
||
}
|
||
else
|
||
{
|
||
rtx div = simplify_gen_binary (DIV, mode, op0, op1);
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = ceil_sdiv_adjust (mode, mod, op1);
|
||
return simplify_gen_binary (PLUS, mode, div, adj);
|
||
}
|
||
|
||
case CEIL_MOD_EXPR:
|
||
if (unsignedp)
|
||
{
|
||
rtx mod = simplify_gen_binary (UMOD, mode, op0, op1);
|
||
rtx adj = ceil_udiv_adjust (mode, mod, op1);
|
||
adj = simplify_gen_unary (NEG, mode,
|
||
simplify_gen_binary (MULT, mode, adj, op1),
|
||
mode);
|
||
return simplify_gen_binary (PLUS, mode, mod, adj);
|
||
}
|
||
else
|
||
{
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = ceil_sdiv_adjust (mode, mod, op1);
|
||
adj = simplify_gen_unary (NEG, mode,
|
||
simplify_gen_binary (MULT, mode, adj, op1),
|
||
mode);
|
||
return simplify_gen_binary (PLUS, mode, mod, adj);
|
||
}
|
||
|
||
case ROUND_DIV_EXPR:
|
||
if (unsignedp)
|
||
{
|
||
rtx div = simplify_gen_binary (UDIV, mode, op0, op1);
|
||
rtx mod = simplify_gen_binary (UMOD, mode, op0, op1);
|
||
rtx adj = round_udiv_adjust (mode, mod, op1);
|
||
return simplify_gen_binary (PLUS, mode, div, adj);
|
||
}
|
||
else
|
||
{
|
||
rtx div = simplify_gen_binary (DIV, mode, op0, op1);
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = round_sdiv_adjust (mode, mod, op1);
|
||
return simplify_gen_binary (PLUS, mode, div, adj);
|
||
}
|
||
|
||
case ROUND_MOD_EXPR:
|
||
if (unsignedp)
|
||
{
|
||
rtx mod = simplify_gen_binary (UMOD, mode, op0, op1);
|
||
rtx adj = round_udiv_adjust (mode, mod, op1);
|
||
adj = simplify_gen_unary (NEG, mode,
|
||
simplify_gen_binary (MULT, mode, adj, op1),
|
||
mode);
|
||
return simplify_gen_binary (PLUS, mode, mod, adj);
|
||
}
|
||
else
|
||
{
|
||
rtx mod = simplify_gen_binary (MOD, mode, op0, op1);
|
||
rtx adj = round_sdiv_adjust (mode, mod, op1);
|
||
adj = simplify_gen_unary (NEG, mode,
|
||
simplify_gen_binary (MULT, mode, adj, op1),
|
||
mode);
|
||
return simplify_gen_binary (PLUS, mode, mod, adj);
|
||
}
|
||
|
||
case LSHIFT_EXPR:
|
||
return simplify_gen_binary (ASHIFT, mode, op0, op1);
|
||
|
||
case RSHIFT_EXPR:
|
||
if (unsignedp)
|
||
return simplify_gen_binary (LSHIFTRT, mode, op0, op1);
|
||
else
|
||
return simplify_gen_binary (ASHIFTRT, mode, op0, op1);
|
||
|
||
case LROTATE_EXPR:
|
||
return simplify_gen_binary (ROTATE, mode, op0, op1);
|
||
|
||
case RROTATE_EXPR:
|
||
return simplify_gen_binary (ROTATERT, mode, op0, op1);
|
||
|
||
case MIN_EXPR:
|
||
return simplify_gen_binary (unsignedp ? UMIN : SMIN, mode, op0, op1);
|
||
|
||
case MAX_EXPR:
|
||
return simplify_gen_binary (unsignedp ? UMAX : SMAX, mode, op0, op1);
|
||
|
||
case BIT_AND_EXPR:
|
||
case TRUTH_AND_EXPR:
|
||
return simplify_gen_binary (AND, mode, op0, op1);
|
||
|
||
case BIT_IOR_EXPR:
|
||
case TRUTH_OR_EXPR:
|
||
return simplify_gen_binary (IOR, mode, op0, op1);
|
||
|
||
case BIT_XOR_EXPR:
|
||
case TRUTH_XOR_EXPR:
|
||
return simplify_gen_binary (XOR, mode, op0, op1);
|
||
|
||
case TRUTH_ANDIF_EXPR:
|
||
return gen_rtx_IF_THEN_ELSE (mode, op0, op1, const0_rtx);
|
||
|
||
case TRUTH_ORIF_EXPR:
|
||
return gen_rtx_IF_THEN_ELSE (mode, op0, const_true_rtx, op1);
|
||
|
||
case TRUTH_NOT_EXPR:
|
||
return simplify_gen_relational (EQ, mode, inner_mode, op0, const0_rtx);
|
||
|
||
case LT_EXPR:
|
||
return simplify_gen_relational (unsignedp ? LTU : LT, mode, inner_mode,
|
||
op0, op1);
|
||
|
||
case LE_EXPR:
|
||
return simplify_gen_relational (unsignedp ? LEU : LE, mode, inner_mode,
|
||
op0, op1);
|
||
|
||
case GT_EXPR:
|
||
return simplify_gen_relational (unsignedp ? GTU : GT, mode, inner_mode,
|
||
op0, op1);
|
||
|
||
case GE_EXPR:
|
||
return simplify_gen_relational (unsignedp ? GEU : GE, mode, inner_mode,
|
||
op0, op1);
|
||
|
||
case EQ_EXPR:
|
||
return simplify_gen_relational (EQ, mode, inner_mode, op0, op1);
|
||
|
||
case NE_EXPR:
|
||
return simplify_gen_relational (NE, mode, inner_mode, op0, op1);
|
||
|
||
case UNORDERED_EXPR:
|
||
return simplify_gen_relational (UNORDERED, mode, inner_mode, op0, op1);
|
||
|
||
case ORDERED_EXPR:
|
||
return simplify_gen_relational (ORDERED, mode, inner_mode, op0, op1);
|
||
|
||
case UNLT_EXPR:
|
||
return simplify_gen_relational (UNLT, mode, inner_mode, op0, op1);
|
||
|
||
case UNLE_EXPR:
|
||
return simplify_gen_relational (UNLE, mode, inner_mode, op0, op1);
|
||
|
||
case UNGT_EXPR:
|
||
return simplify_gen_relational (UNGT, mode, inner_mode, op0, op1);
|
||
|
||
case UNGE_EXPR:
|
||
return simplify_gen_relational (UNGE, mode, inner_mode, op0, op1);
|
||
|
||
case UNEQ_EXPR:
|
||
return simplify_gen_relational (UNEQ, mode, inner_mode, op0, op1);
|
||
|
||
case LTGT_EXPR:
|
||
return simplify_gen_relational (LTGT, mode, inner_mode, op0, op1);
|
||
|
||
case COND_EXPR:
|
||
return gen_rtx_IF_THEN_ELSE (mode, op0, op1, op2);
|
||
|
||
case COMPLEX_EXPR:
|
||
gcc_assert (COMPLEX_MODE_P (mode));
|
||
if (GET_MODE (op0) == VOIDmode)
|
||
op0 = gen_rtx_CONST (GET_MODE_INNER (mode), op0);
|
||
if (GET_MODE (op1) == VOIDmode)
|
||
op1 = gen_rtx_CONST (GET_MODE_INNER (mode), op1);
|
||
return gen_rtx_CONCAT (mode, op0, op1);
|
||
|
||
case CONJ_EXPR:
|
||
if (GET_CODE (op0) == CONCAT)
|
||
return gen_rtx_CONCAT (mode, XEXP (op0, 0),
|
||
simplify_gen_unary (NEG, GET_MODE_INNER (mode),
|
||
XEXP (op0, 1),
|
||
GET_MODE_INNER (mode)));
|
||
else
|
||
{
|
||
scalar_mode imode = GET_MODE_INNER (mode);
|
||
rtx re, im;
|
||
|
||
if (MEM_P (op0))
|
||
{
|
||
re = adjust_address_nv (op0, imode, 0);
|
||
im = adjust_address_nv (op0, imode, GET_MODE_SIZE (imode));
|
||
}
|
||
else
|
||
{
|
||
scalar_int_mode ifmode;
|
||
scalar_int_mode ihmode;
|
||
rtx halfsize;
|
||
if (!int_mode_for_mode (mode).exists (&ifmode)
|
||
|| !int_mode_for_mode (imode).exists (&ihmode))
|
||
return NULL;
|
||
halfsize = GEN_INT (GET_MODE_BITSIZE (ihmode));
|
||
re = op0;
|
||
if (mode != ifmode)
|
||
re = gen_rtx_SUBREG (ifmode, re, 0);
|
||
re = gen_rtx_ZERO_EXTRACT (ihmode, re, halfsize, const0_rtx);
|
||
if (imode != ihmode)
|
||
re = gen_rtx_SUBREG (imode, re, 0);
|
||
im = copy_rtx (op0);
|
||
if (mode != ifmode)
|
||
im = gen_rtx_SUBREG (ifmode, im, 0);
|
||
im = gen_rtx_ZERO_EXTRACT (ihmode, im, halfsize, halfsize);
|
||
if (imode != ihmode)
|
||
im = gen_rtx_SUBREG (imode, im, 0);
|
||
}
|
||
im = gen_rtx_NEG (imode, im);
|
||
return gen_rtx_CONCAT (mode, re, im);
|
||
}
|
||
|
||
case ADDR_EXPR:
|
||
op0 = expand_debug_expr (TREE_OPERAND (exp, 0));
|
||
if (!op0 || !MEM_P (op0))
|
||
{
|
||
if ((TREE_CODE (TREE_OPERAND (exp, 0)) == VAR_DECL
|
||
|| TREE_CODE (TREE_OPERAND (exp, 0)) == PARM_DECL
|
||
|| TREE_CODE (TREE_OPERAND (exp, 0)) == RESULT_DECL)
|
||
&& (!TREE_ADDRESSABLE (TREE_OPERAND (exp, 0))
|
||
|| target_for_debug_bind (TREE_OPERAND (exp, 0))))
|
||
return gen_rtx_DEBUG_IMPLICIT_PTR (mode, TREE_OPERAND (exp, 0));
|
||
|
||
if (handled_component_p (TREE_OPERAND (exp, 0)))
|
||
{
|
||
poly_int64 bitoffset, bitsize, maxsize, byteoffset;
|
||
bool reverse;
|
||
tree decl
|
||
= get_ref_base_and_extent (TREE_OPERAND (exp, 0), &bitoffset,
|
||
&bitsize, &maxsize, &reverse);
|
||
if ((VAR_P (decl)
|
||
|| TREE_CODE (decl) == PARM_DECL
|
||
|| TREE_CODE (decl) == RESULT_DECL)
|
||
&& (!TREE_ADDRESSABLE (decl)
|
||
|| target_for_debug_bind (decl))
|
||
&& multiple_p (bitoffset, BITS_PER_UNIT, &byteoffset)
|
||
&& known_gt (bitsize, 0)
|
||
&& known_eq (bitsize, maxsize))
|
||
{
|
||
rtx base = gen_rtx_DEBUG_IMPLICIT_PTR (mode, decl);
|
||
return plus_constant (mode, base, byteoffset);
|
||
}
|
||
}
|
||
|
||
if (TREE_CODE (TREE_OPERAND (exp, 0)) == MEM_REF
|
||
&& TREE_CODE (TREE_OPERAND (TREE_OPERAND (exp, 0), 0))
|
||
== ADDR_EXPR)
|
||
{
|
||
op0 = expand_debug_expr (TREE_OPERAND (TREE_OPERAND (exp, 0),
|
||
0));
|
||
if (op0 != NULL
|
||
&& (GET_CODE (op0) == DEBUG_IMPLICIT_PTR
|
||
|| (GET_CODE (op0) == PLUS
|
||
&& GET_CODE (XEXP (op0, 0)) == DEBUG_IMPLICIT_PTR
|
||
&& CONST_INT_P (XEXP (op0, 1)))))
|
||
{
|
||
op1 = expand_debug_expr (TREE_OPERAND (TREE_OPERAND (exp, 0),
|
||
1));
|
||
poly_int64 offset;
|
||
if (!op1 || !poly_int_rtx_p (op1, &offset))
|
||
return NULL;
|
||
|
||
return plus_constant (mode, op0, offset);
|
||
}
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (exp)));
|
||
addr_mode = SCALAR_INT_TYPE_MODE (TREE_TYPE (exp));
|
||
op0 = convert_debug_memory_address (addr_mode, XEXP (op0, 0), as);
|
||
|
||
return op0;
|
||
|
||
case VECTOR_CST:
|
||
{
|
||
unsigned HOST_WIDE_INT i, nelts;
|
||
|
||
if (!VECTOR_CST_NELTS (exp).is_constant (&nelts))
|
||
return NULL;
|
||
|
||
op0 = gen_rtx_CONCATN (mode, rtvec_alloc (nelts));
|
||
|
||
for (i = 0; i < nelts; ++i)
|
||
{
|
||
op1 = expand_debug_expr (VECTOR_CST_ELT (exp, i));
|
||
if (!op1)
|
||
return NULL;
|
||
XVECEXP (op0, 0, i) = op1;
|
||
}
|
||
|
||
return op0;
|
||
}
|
||
|
||
case CONSTRUCTOR:
|
||
if (TREE_CLOBBER_P (exp))
|
||
return NULL;
|
||
else if (TREE_CODE (TREE_TYPE (exp)) == VECTOR_TYPE)
|
||
{
|
||
unsigned i;
|
||
unsigned HOST_WIDE_INT nelts;
|
||
tree val;
|
||
|
||
if (!TYPE_VECTOR_SUBPARTS (TREE_TYPE (exp)).is_constant (&nelts))
|
||
goto flag_unsupported;
|
||
|
||
op0 = gen_rtx_CONCATN (mode, rtvec_alloc (nelts));
|
||
|
||
FOR_EACH_CONSTRUCTOR_VALUE (CONSTRUCTOR_ELTS (exp), i, val)
|
||
{
|
||
op1 = expand_debug_expr (val);
|
||
if (!op1)
|
||
return NULL;
|
||
XVECEXP (op0, 0, i) = op1;
|
||
}
|
||
|
||
if (i < nelts)
|
||
{
|
||
op1 = expand_debug_expr
|
||
(build_zero_cst (TREE_TYPE (TREE_TYPE (exp))));
|
||
|
||
if (!op1)
|
||
return NULL;
|
||
|
||
for (; i < nelts; i++)
|
||
XVECEXP (op0, 0, i) = op1;
|
||
}
|
||
|
||
return op0;
|
||
}
|
||
else
|
||
goto flag_unsupported;
|
||
|
||
case CALL_EXPR:
|
||
/* ??? Maybe handle some builtins? */
|
||
return NULL;
|
||
|
||
case SSA_NAME:
|
||
{
|
||
gimple *g = get_gimple_for_ssa_name (exp);
|
||
if (g)
|
||
{
|
||
tree t = NULL_TREE;
|
||
if (deep_ter_debug_map)
|
||
{
|
||
tree *slot = deep_ter_debug_map->get (exp);
|
||
if (slot)
|
||
t = *slot;
|
||
}
|
||
if (t == NULL_TREE)
|
||
t = gimple_assign_rhs_to_tree (g);
|
||
op0 = expand_debug_expr (t);
|
||
if (!op0)
|
||
return NULL;
|
||
}
|
||
else
|
||
{
|
||
/* If this is a reference to an incoming value of
|
||
parameter that is never used in the code or where the
|
||
incoming value is never used in the code, use
|
||
PARM_DECL's DECL_RTL if set. */
|
||
if (SSA_NAME_IS_DEFAULT_DEF (exp)
|
||
&& SSA_NAME_VAR (exp)
|
||
&& TREE_CODE (SSA_NAME_VAR (exp)) == PARM_DECL
|
||
&& has_zero_uses (exp))
|
||
{
|
||
op0 = expand_debug_parm_decl (SSA_NAME_VAR (exp));
|
||
if (op0)
|
||
goto adjust_mode;
|
||
op0 = expand_debug_expr (SSA_NAME_VAR (exp));
|
||
if (op0)
|
||
goto adjust_mode;
|
||
}
|
||
|
||
int part = var_to_partition (SA.map, exp);
|
||
|
||
if (part == NO_PARTITION)
|
||
return NULL;
|
||
|
||
gcc_assert (part >= 0 && (unsigned)part < SA.map->num_partitions);
|
||
|
||
op0 = copy_rtx (SA.partition_to_pseudo[part]);
|
||
}
|
||
goto adjust_mode;
|
||
}
|
||
|
||
case ERROR_MARK:
|
||
return NULL;
|
||
|
||
/* Vector stuff. For most of the codes we don't have rtl codes. */
|
||
case REALIGN_LOAD_EXPR:
|
||
case VEC_COND_EXPR:
|
||
case VEC_PACK_FIX_TRUNC_EXPR:
|
||
case VEC_PACK_FLOAT_EXPR:
|
||
case VEC_PACK_SAT_EXPR:
|
||
case VEC_PACK_TRUNC_EXPR:
|
||
case VEC_UNPACK_FIX_TRUNC_HI_EXPR:
|
||
case VEC_UNPACK_FIX_TRUNC_LO_EXPR:
|
||
case VEC_UNPACK_FLOAT_HI_EXPR:
|
||
case VEC_UNPACK_FLOAT_LO_EXPR:
|
||
case VEC_UNPACK_HI_EXPR:
|
||
case VEC_UNPACK_LO_EXPR:
|
||
case VEC_WIDEN_MULT_HI_EXPR:
|
||
case VEC_WIDEN_MULT_LO_EXPR:
|
||
case VEC_WIDEN_MULT_EVEN_EXPR:
|
||
case VEC_WIDEN_MULT_ODD_EXPR:
|
||
case VEC_WIDEN_LSHIFT_HI_EXPR:
|
||
case VEC_WIDEN_LSHIFT_LO_EXPR:
|
||
case VEC_PERM_EXPR:
|
||
case VEC_DUPLICATE_EXPR:
|
||
case VEC_SERIES_EXPR:
|
||
case SAD_EXPR:
|
||
return NULL;
|
||
|
||
/* Misc codes. */
|
||
case ADDR_SPACE_CONVERT_EXPR:
|
||
case FIXED_CONVERT_EXPR:
|
||
case OBJ_TYPE_REF:
|
||
case WITH_SIZE_EXPR:
|
||
case BIT_INSERT_EXPR:
|
||
return NULL;
|
||
|
||
case DOT_PROD_EXPR:
|
||
if (SCALAR_INT_MODE_P (GET_MODE (op0))
|
||
&& SCALAR_INT_MODE_P (mode))
|
||
{
|
||
op0
|
||
= simplify_gen_unary (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp,
|
||
0)))
|
||
? ZERO_EXTEND : SIGN_EXTEND, mode, op0,
|
||
inner_mode);
|
||
op1
|
||
= simplify_gen_unary (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp,
|
||
1)))
|
||
? ZERO_EXTEND : SIGN_EXTEND, mode, op1,
|
||
inner_mode);
|
||
op0 = simplify_gen_binary (MULT, mode, op0, op1);
|
||
return simplify_gen_binary (PLUS, mode, op0, op2);
|
||
}
|
||
return NULL;
|
||
|
||
case WIDEN_MULT_EXPR:
|
||
case WIDEN_MULT_PLUS_EXPR:
|
||
case WIDEN_MULT_MINUS_EXPR:
|
||
if (SCALAR_INT_MODE_P (GET_MODE (op0))
|
||
&& SCALAR_INT_MODE_P (mode))
|
||
{
|
||
inner_mode = GET_MODE (op0);
|
||
if (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp, 0))))
|
||
op0 = simplify_gen_unary (ZERO_EXTEND, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (SIGN_EXTEND, mode, op0, inner_mode);
|
||
if (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp, 1))))
|
||
op1 = simplify_gen_unary (ZERO_EXTEND, mode, op1, inner_mode);
|
||
else
|
||
op1 = simplify_gen_unary (SIGN_EXTEND, mode, op1, inner_mode);
|
||
op0 = simplify_gen_binary (MULT, mode, op0, op1);
|
||
if (TREE_CODE (exp) == WIDEN_MULT_EXPR)
|
||
return op0;
|
||
else if (TREE_CODE (exp) == WIDEN_MULT_PLUS_EXPR)
|
||
return simplify_gen_binary (PLUS, mode, op0, op2);
|
||
else
|
||
return simplify_gen_binary (MINUS, mode, op2, op0);
|
||
}
|
||
return NULL;
|
||
|
||
case MULT_HIGHPART_EXPR:
|
||
/* ??? Similar to the above. */
|
||
return NULL;
|
||
|
||
case WIDEN_SUM_EXPR:
|
||
case WIDEN_LSHIFT_EXPR:
|
||
if (SCALAR_INT_MODE_P (GET_MODE (op0))
|
||
&& SCALAR_INT_MODE_P (mode))
|
||
{
|
||
op0
|
||
= simplify_gen_unary (TYPE_UNSIGNED (TREE_TYPE (TREE_OPERAND (exp,
|
||
0)))
|
||
? ZERO_EXTEND : SIGN_EXTEND, mode, op0,
|
||
inner_mode);
|
||
return simplify_gen_binary (TREE_CODE (exp) == WIDEN_LSHIFT_EXPR
|
||
? ASHIFT : PLUS, mode, op0, op1);
|
||
}
|
||
return NULL;
|
||
|
||
default:
|
||
flag_unsupported:
|
||
if (flag_checking)
|
||
{
|
||
debug_tree (exp);
|
||
gcc_unreachable ();
|
||
}
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
/* Return an RTX equivalent to the source bind value of the tree expression
|
||
EXP. */
|
||
|
||
static rtx
|
||
expand_debug_source_expr (tree exp)
|
||
{
|
||
rtx op0 = NULL_RTX;
|
||
machine_mode mode = VOIDmode, inner_mode;
|
||
|
||
switch (TREE_CODE (exp))
|
||
{
|
||
case VAR_DECL:
|
||
if (DECL_ABSTRACT_ORIGIN (exp))
|
||
return expand_debug_source_expr (DECL_ABSTRACT_ORIGIN (exp));
|
||
break;
|
||
case PARM_DECL:
|
||
{
|
||
mode = DECL_MODE (exp);
|
||
op0 = expand_debug_parm_decl (exp);
|
||
if (op0)
|
||
break;
|
||
/* See if this isn't an argument that has been completely
|
||
optimized out. */
|
||
if (!DECL_RTL_SET_P (exp)
|
||
&& !DECL_INCOMING_RTL (exp)
|
||
&& DECL_ABSTRACT_ORIGIN (current_function_decl))
|
||
{
|
||
tree aexp = DECL_ORIGIN (exp);
|
||
if (DECL_CONTEXT (aexp)
|
||
== DECL_ABSTRACT_ORIGIN (current_function_decl))
|
||
{
|
||
vec<tree, va_gc> **debug_args;
|
||
unsigned int ix;
|
||
tree ddecl;
|
||
debug_args = decl_debug_args_lookup (current_function_decl);
|
||
if (debug_args != NULL)
|
||
{
|
||
for (ix = 0; vec_safe_iterate (*debug_args, ix, &ddecl);
|
||
ix += 2)
|
||
if (ddecl == aexp)
|
||
return gen_rtx_DEBUG_PARAMETER_REF (mode, aexp);
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
if (op0 == NULL_RTX)
|
||
return NULL_RTX;
|
||
|
||
inner_mode = GET_MODE (op0);
|
||
if (mode == inner_mode)
|
||
return op0;
|
||
|
||
if (FLOAT_MODE_P (mode) && FLOAT_MODE_P (inner_mode))
|
||
{
|
||
if (GET_MODE_UNIT_BITSIZE (mode)
|
||
== GET_MODE_UNIT_BITSIZE (inner_mode))
|
||
op0 = simplify_gen_subreg (mode, op0, inner_mode, 0);
|
||
else if (GET_MODE_UNIT_BITSIZE (mode)
|
||
< GET_MODE_UNIT_BITSIZE (inner_mode))
|
||
op0 = simplify_gen_unary (FLOAT_TRUNCATE, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (FLOAT_EXTEND, mode, op0, inner_mode);
|
||
}
|
||
else if (FLOAT_MODE_P (mode))
|
||
gcc_unreachable ();
|
||
else if (FLOAT_MODE_P (inner_mode))
|
||
{
|
||
if (TYPE_UNSIGNED (TREE_TYPE (exp)))
|
||
op0 = simplify_gen_unary (UNSIGNED_FIX, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (FIX, mode, op0, inner_mode);
|
||
}
|
||
else if (GET_MODE_UNIT_PRECISION (mode)
|
||
== GET_MODE_UNIT_PRECISION (inner_mode))
|
||
op0 = lowpart_subreg (mode, op0, inner_mode);
|
||
else if (GET_MODE_UNIT_PRECISION (mode)
|
||
< GET_MODE_UNIT_PRECISION (inner_mode))
|
||
op0 = simplify_gen_unary (TRUNCATE, mode, op0, inner_mode);
|
||
else if (TYPE_UNSIGNED (TREE_TYPE (exp)))
|
||
op0 = simplify_gen_unary (ZERO_EXTEND, mode, op0, inner_mode);
|
||
else
|
||
op0 = simplify_gen_unary (SIGN_EXTEND, mode, op0, inner_mode);
|
||
|
||
return op0;
|
||
}
|
||
|
||
/* Ensure INSN_VAR_LOCATION_LOC (insn) doesn't have unbound complexity.
|
||
Allow 4 levels of rtl nesting for most rtl codes, and if we see anything
|
||
deeper than that, create DEBUG_EXPRs and emit DEBUG_INSNs before INSN. */
|
||
|
||
static void
|
||
avoid_complex_debug_insns (rtx_insn *insn, rtx *exp_p, int depth)
|
||
{
|
||
rtx exp = *exp_p;
|
||
|
||
if (exp == NULL_RTX)
|
||
return;
|
||
|
||
if ((OBJECT_P (exp) && !MEM_P (exp)) || GET_CODE (exp) == CLOBBER)
|
||
return;
|
||
|
||
if (depth == 4)
|
||
{
|
||
/* Create DEBUG_EXPR (and DEBUG_EXPR_DECL). */
|
||
rtx dval = make_debug_expr_from_rtl (exp);
|
||
|
||
/* Emit a debug bind insn before INSN. */
|
||
rtx bind = gen_rtx_VAR_LOCATION (GET_MODE (exp),
|
||
DEBUG_EXPR_TREE_DECL (dval), exp,
|
||
VAR_INIT_STATUS_INITIALIZED);
|
||
|
||
emit_debug_insn_before (bind, insn);
|
||
*exp_p = dval;
|
||
return;
|
||
}
|
||
|
||
const char *format_ptr = GET_RTX_FORMAT (GET_CODE (exp));
|
||
int i, j;
|
||
for (i = 0; i < GET_RTX_LENGTH (GET_CODE (exp)); i++)
|
||
switch (*format_ptr++)
|
||
{
|
||
case 'e':
|
||
avoid_complex_debug_insns (insn, &XEXP (exp, i), depth + 1);
|
||
break;
|
||
|
||
case 'E':
|
||
case 'V':
|
||
for (j = 0; j < XVECLEN (exp, i); j++)
|
||
avoid_complex_debug_insns (insn, &XVECEXP (exp, i, j), depth + 1);
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Expand the _LOCs in debug insns. We run this after expanding all
|
||
regular insns, so that any variables referenced in the function
|
||
will have their DECL_RTLs set. */
|
||
|
||
static void
|
||
expand_debug_locations (void)
|
||
{
|
||
rtx_insn *insn;
|
||
rtx_insn *last = get_last_insn ();
|
||
int save_strict_alias = flag_strict_aliasing;
|
||
|
||
/* New alias sets while setting up memory attributes cause
|
||
-fcompare-debug failures, even though it doesn't bring about any
|
||
codegen changes. */
|
||
flag_strict_aliasing = 0;
|
||
|
||
for (insn = get_insns (); insn; insn = NEXT_INSN (insn))
|
||
if (DEBUG_BIND_INSN_P (insn))
|
||
{
|
||
tree value = (tree)INSN_VAR_LOCATION_LOC (insn);
|
||
rtx val;
|
||
rtx_insn *prev_insn, *insn2;
|
||
machine_mode mode;
|
||
|
||
if (value == NULL_TREE)
|
||
val = NULL_RTX;
|
||
else
|
||
{
|
||
if (INSN_VAR_LOCATION_STATUS (insn)
|
||
== VAR_INIT_STATUS_UNINITIALIZED)
|
||
val = expand_debug_source_expr (value);
|
||
/* The avoid_deep_ter_for_debug function inserts
|
||
debug bind stmts after SSA_NAME definition, with the
|
||
SSA_NAME as the whole bind location. Disable temporarily
|
||
expansion of that SSA_NAME into the DEBUG_EXPR_DECL
|
||
being defined in this DEBUG_INSN. */
|
||
else if (deep_ter_debug_map && TREE_CODE (value) == SSA_NAME)
|
||
{
|
||
tree *slot = deep_ter_debug_map->get (value);
|
||
if (slot)
|
||
{
|
||
if (*slot == INSN_VAR_LOCATION_DECL (insn))
|
||
*slot = NULL_TREE;
|
||
else
|
||
slot = NULL;
|
||
}
|
||
val = expand_debug_expr (value);
|
||
if (slot)
|
||
*slot = INSN_VAR_LOCATION_DECL (insn);
|
||
}
|
||
else
|
||
val = expand_debug_expr (value);
|
||
gcc_assert (last == get_last_insn ());
|
||
}
|
||
|
||
if (!val)
|
||
val = gen_rtx_UNKNOWN_VAR_LOC ();
|
||
else
|
||
{
|
||
mode = GET_MODE (INSN_VAR_LOCATION (insn));
|
||
|
||
gcc_assert (mode == GET_MODE (val)
|
||
|| (GET_MODE (val) == VOIDmode
|
||
&& (CONST_SCALAR_INT_P (val)
|
||
|| GET_CODE (val) == CONST_FIXED
|
||
|| GET_CODE (val) == LABEL_REF)));
|
||
}
|
||
|
||
INSN_VAR_LOCATION_LOC (insn) = val;
|
||
prev_insn = PREV_INSN (insn);
|
||
for (insn2 = insn; insn2 != prev_insn; insn2 = PREV_INSN (insn2))
|
||
avoid_complex_debug_insns (insn2, &INSN_VAR_LOCATION_LOC (insn2), 0);
|
||
}
|
||
|
||
flag_strict_aliasing = save_strict_alias;
|
||
}
|
||
|
||
/* Performs swapping operands of commutative operations to expand
|
||
the expensive one first. */
|
||
|
||
static void
|
||
reorder_operands (basic_block bb)
|
||
{
|
||
unsigned int *lattice; /* Hold cost of each statement. */
|
||
unsigned int i = 0, n = 0;
|
||
gimple_stmt_iterator gsi;
|
||
gimple_seq stmts;
|
||
gimple *stmt;
|
||
bool swap;
|
||
tree op0, op1;
|
||
ssa_op_iter iter;
|
||
use_operand_p use_p;
|
||
gimple *def0, *def1;
|
||
|
||
/* Compute cost of each statement using estimate_num_insns. */
|
||
stmts = bb_seq (bb);
|
||
for (gsi = gsi_start (stmts); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
stmt = gsi_stmt (gsi);
|
||
if (!is_gimple_debug (stmt))
|
||
gimple_set_uid (stmt, n++);
|
||
}
|
||
lattice = XNEWVEC (unsigned int, n);
|
||
for (gsi = gsi_start (stmts); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
unsigned cost;
|
||
stmt = gsi_stmt (gsi);
|
||
if (is_gimple_debug (stmt))
|
||
continue;
|
||
cost = estimate_num_insns (stmt, &eni_size_weights);
|
||
lattice[i] = cost;
|
||
FOR_EACH_SSA_USE_OPERAND (use_p, stmt, iter, SSA_OP_USE)
|
||
{
|
||
tree use = USE_FROM_PTR (use_p);
|
||
gimple *def_stmt;
|
||
if (TREE_CODE (use) != SSA_NAME)
|
||
continue;
|
||
def_stmt = get_gimple_for_ssa_name (use);
|
||
if (!def_stmt)
|
||
continue;
|
||
lattice[i] += lattice[gimple_uid (def_stmt)];
|
||
}
|
||
i++;
|
||
if (!is_gimple_assign (stmt)
|
||
|| !commutative_tree_code (gimple_assign_rhs_code (stmt)))
|
||
continue;
|
||
op0 = gimple_op (stmt, 1);
|
||
op1 = gimple_op (stmt, 2);
|
||
if (TREE_CODE (op0) != SSA_NAME
|
||
|| TREE_CODE (op1) != SSA_NAME)
|
||
continue;
|
||
/* Swap operands if the second one is more expensive. */
|
||
def0 = get_gimple_for_ssa_name (op0);
|
||
def1 = get_gimple_for_ssa_name (op1);
|
||
if (!def1)
|
||
continue;
|
||
swap = false;
|
||
if (!def0 || lattice[gimple_uid (def1)] > lattice[gimple_uid (def0)])
|
||
swap = true;
|
||
if (swap)
|
||
{
|
||
if (dump_file && (dump_flags & TDF_DETAILS))
|
||
{
|
||
fprintf (dump_file, "Swap operands in stmt:\n");
|
||
print_gimple_stmt (dump_file, stmt, 0, TDF_SLIM);
|
||
fprintf (dump_file, "Cost left opnd=%d, right opnd=%d\n",
|
||
def0 ? lattice[gimple_uid (def0)] : 0,
|
||
lattice[gimple_uid (def1)]);
|
||
}
|
||
swap_ssa_operands (stmt, gimple_assign_rhs1_ptr (stmt),
|
||
gimple_assign_rhs2_ptr (stmt));
|
||
}
|
||
}
|
||
XDELETE (lattice);
|
||
}
|
||
|
||
/* Expand basic block BB from GIMPLE trees to RTL. */
|
||
|
||
static basic_block
|
||
expand_gimple_basic_block (basic_block bb, bool disable_tail_calls)
|
||
{
|
||
gimple_stmt_iterator gsi;
|
||
gimple_seq stmts;
|
||
gimple *stmt = NULL;
|
||
rtx_note *note = NULL;
|
||
rtx_insn *last;
|
||
edge e;
|
||
edge_iterator ei;
|
||
|
||
if (dump_file)
|
||
fprintf (dump_file, "\n;; Generating RTL for gimple basic block %d\n",
|
||
bb->index);
|
||
|
||
/* Note that since we are now transitioning from GIMPLE to RTL, we
|
||
cannot use the gsi_*_bb() routines because they expect the basic
|
||
block to be in GIMPLE, instead of RTL. Therefore, we need to
|
||
access the BB sequence directly. */
|
||
if (optimize)
|
||
reorder_operands (bb);
|
||
stmts = bb_seq (bb);
|
||
bb->il.gimple.seq = NULL;
|
||
bb->il.gimple.phi_nodes = NULL;
|
||
rtl_profile_for_bb (bb);
|
||
init_rtl_bb_info (bb);
|
||
bb->flags |= BB_RTL;
|
||
|
||
/* Remove the RETURN_EXPR if we may fall though to the exit
|
||
instead. */
|
||
gsi = gsi_last (stmts);
|
||
if (!gsi_end_p (gsi)
|
||
&& gimple_code (gsi_stmt (gsi)) == GIMPLE_RETURN)
|
||
{
|
||
greturn *ret_stmt = as_a <greturn *> (gsi_stmt (gsi));
|
||
|
||
gcc_assert (single_succ_p (bb));
|
||
gcc_assert (single_succ (bb) == EXIT_BLOCK_PTR_FOR_FN (cfun));
|
||
|
||
if (bb->next_bb == EXIT_BLOCK_PTR_FOR_FN (cfun)
|
||
&& !gimple_return_retval (ret_stmt))
|
||
{
|
||
gsi_remove (&gsi, false);
|
||
single_succ_edge (bb)->flags |= EDGE_FALLTHRU;
|
||
}
|
||
}
|
||
|
||
gsi = gsi_start (stmts);
|
||
if (!gsi_end_p (gsi))
|
||
{
|
||
stmt = gsi_stmt (gsi);
|
||
if (gimple_code (stmt) != GIMPLE_LABEL)
|
||
stmt = NULL;
|
||
}
|
||
|
||
rtx_code_label **elt = lab_rtx_for_bb->get (bb);
|
||
|
||
if (stmt || elt)
|
||
{
|
||
gcc_checking_assert (!note);
|
||
last = get_last_insn ();
|
||
|
||
if (stmt)
|
||
{
|
||
expand_gimple_stmt (stmt);
|
||
gsi_next (&gsi);
|
||
}
|
||
|
||
if (elt)
|
||
emit_label (*elt);
|
||
|
||
BB_HEAD (bb) = NEXT_INSN (last);
|
||
if (NOTE_P (BB_HEAD (bb)))
|
||
BB_HEAD (bb) = NEXT_INSN (BB_HEAD (bb));
|
||
gcc_assert (LABEL_P (BB_HEAD (bb)));
|
||
note = emit_note_after (NOTE_INSN_BASIC_BLOCK, BB_HEAD (bb));
|
||
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last);
|
||
}
|
||
else
|
||
BB_HEAD (bb) = note = emit_note (NOTE_INSN_BASIC_BLOCK);
|
||
|
||
if (note)
|
||
NOTE_BASIC_BLOCK (note) = bb;
|
||
|
||
for (; !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
basic_block new_bb;
|
||
|
||
stmt = gsi_stmt (gsi);
|
||
|
||
/* If this statement is a non-debug one, and we generate debug
|
||
insns, then this one might be the last real use of a TERed
|
||
SSA_NAME, but where there are still some debug uses further
|
||
down. Expanding the current SSA name in such further debug
|
||
uses by their RHS might lead to wrong debug info, as coalescing
|
||
might make the operands of such RHS be placed into the same
|
||
pseudo as something else. Like so:
|
||
a_1 = a_0 + 1; // Assume a_1 is TERed and a_0 is dead
|
||
use(a_1);
|
||
a_2 = ...
|
||
#DEBUG ... => a_1
|
||
As a_0 and a_2 don't overlap in lifetime, assume they are coalesced.
|
||
If we now would expand a_1 by it's RHS (a_0 + 1) in the debug use,
|
||
the write to a_2 would actually have clobbered the place which
|
||
formerly held a_0.
|
||
|
||
So, instead of that, we recognize the situation, and generate
|
||
debug temporaries at the last real use of TERed SSA names:
|
||
a_1 = a_0 + 1;
|
||
#DEBUG #D1 => a_1
|
||
use(a_1);
|
||
a_2 = ...
|
||
#DEBUG ... => #D1
|
||
*/
|
||
if (MAY_HAVE_DEBUG_BIND_INSNS
|
||
&& SA.values
|
||
&& !is_gimple_debug (stmt))
|
||
{
|
||
ssa_op_iter iter;
|
||
tree op;
|
||
gimple *def;
|
||
|
||
location_t sloc = curr_insn_location ();
|
||
|
||
/* Look for SSA names that have their last use here (TERed
|
||
names always have only one real use). */
|
||
FOR_EACH_SSA_TREE_OPERAND (op, stmt, iter, SSA_OP_USE)
|
||
if ((def = get_gimple_for_ssa_name (op)))
|
||
{
|
||
imm_use_iterator imm_iter;
|
||
use_operand_p use_p;
|
||
bool have_debug_uses = false;
|
||
|
||
FOR_EACH_IMM_USE_FAST (use_p, imm_iter, op)
|
||
{
|
||
if (gimple_debug_bind_p (USE_STMT (use_p)))
|
||
{
|
||
have_debug_uses = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (have_debug_uses)
|
||
{
|
||
/* OP is a TERed SSA name, with DEF its defining
|
||
statement, and where OP is used in further debug
|
||
instructions. Generate a debug temporary, and
|
||
replace all uses of OP in debug insns with that
|
||
temporary. */
|
||
gimple *debugstmt;
|
||
tree value = gimple_assign_rhs_to_tree (def);
|
||
tree vexpr = make_node (DEBUG_EXPR_DECL);
|
||
rtx val;
|
||
machine_mode mode;
|
||
|
||
set_curr_insn_location (gimple_location (def));
|
||
|
||
DECL_ARTIFICIAL (vexpr) = 1;
|
||
TREE_TYPE (vexpr) = TREE_TYPE (value);
|
||
if (DECL_P (value))
|
||
mode = DECL_MODE (value);
|
||
else
|
||
mode = TYPE_MODE (TREE_TYPE (value));
|
||
SET_DECL_MODE (vexpr, mode);
|
||
|
||
val = gen_rtx_VAR_LOCATION
|
||
(mode, vexpr, (rtx)value, VAR_INIT_STATUS_INITIALIZED);
|
||
|
||
emit_debug_insn (val);
|
||
|
||
FOR_EACH_IMM_USE_STMT (debugstmt, imm_iter, op)
|
||
{
|
||
if (!gimple_debug_bind_p (debugstmt))
|
||
continue;
|
||
|
||
FOR_EACH_IMM_USE_ON_STMT (use_p, imm_iter)
|
||
SET_USE (use_p, vexpr);
|
||
|
||
update_stmt (debugstmt);
|
||
}
|
||
}
|
||
}
|
||
set_curr_insn_location (sloc);
|
||
}
|
||
|
||
currently_expanding_gimple_stmt = stmt;
|
||
|
||
/* Expand this statement, then evaluate the resulting RTL and
|
||
fixup the CFG accordingly. */
|
||
if (gimple_code (stmt) == GIMPLE_COND)
|
||
{
|
||
new_bb = expand_gimple_cond (bb, as_a <gcond *> (stmt));
|
||
if (new_bb)
|
||
return new_bb;
|
||
}
|
||
else if (is_gimple_debug (stmt))
|
||
{
|
||
location_t sloc = curr_insn_location ();
|
||
gimple_stmt_iterator nsi = gsi;
|
||
|
||
for (;;)
|
||
{
|
||
tree var;
|
||
tree value = NULL_TREE;
|
||
rtx val = NULL_RTX;
|
||
machine_mode mode;
|
||
|
||
if (!gimple_debug_nonbind_marker_p (stmt))
|
||
{
|
||
if (gimple_debug_bind_p (stmt))
|
||
{
|
||
var = gimple_debug_bind_get_var (stmt);
|
||
|
||
if (TREE_CODE (var) != DEBUG_EXPR_DECL
|
||
&& TREE_CODE (var) != LABEL_DECL
|
||
&& !target_for_debug_bind (var))
|
||
goto delink_debug_stmt;
|
||
|
||
if (DECL_P (var))
|
||
mode = DECL_MODE (var);
|
||
else
|
||
mode = TYPE_MODE (TREE_TYPE (var));
|
||
|
||
if (gimple_debug_bind_has_value_p (stmt))
|
||
value = gimple_debug_bind_get_value (stmt);
|
||
|
||
val = gen_rtx_VAR_LOCATION
|
||
(mode, var, (rtx)value, VAR_INIT_STATUS_INITIALIZED);
|
||
}
|
||
else if (gimple_debug_source_bind_p (stmt))
|
||
{
|
||
var = gimple_debug_source_bind_get_var (stmt);
|
||
|
||
value = gimple_debug_source_bind_get_value (stmt);
|
||
|
||
mode = DECL_MODE (var);
|
||
|
||
val = gen_rtx_VAR_LOCATION (mode, var, (rtx)value,
|
||
VAR_INIT_STATUS_UNINITIALIZED);
|
||
}
|
||
else
|
||
gcc_unreachable ();
|
||
}
|
||
/* If this function was first compiled with markers
|
||
enabled, but they're now disable (e.g. LTO), drop
|
||
them on the floor. */
|
||
else if (gimple_debug_nonbind_marker_p (stmt)
|
||
&& !MAY_HAVE_DEBUG_MARKER_INSNS)
|
||
goto delink_debug_stmt;
|
||
else if (gimple_debug_begin_stmt_p (stmt))
|
||
val = GEN_RTX_DEBUG_MARKER_BEGIN_STMT_PAT ();
|
||
else if (gimple_debug_inline_entry_p (stmt))
|
||
{
|
||
tree block = gimple_block (stmt);
|
||
|
||
if (block)
|
||
val = GEN_RTX_DEBUG_MARKER_INLINE_ENTRY_PAT ();
|
||
else
|
||
goto delink_debug_stmt;
|
||
}
|
||
else
|
||
gcc_unreachable ();
|
||
|
||
last = get_last_insn ();
|
||
|
||
set_curr_insn_location (gimple_location (stmt));
|
||
|
||
emit_debug_insn (val);
|
||
|
||
if (dump_file && (dump_flags & TDF_DETAILS))
|
||
{
|
||
/* We can't dump the insn with a TREE where an RTX
|
||
is expected. */
|
||
if (GET_CODE (val) == VAR_LOCATION)
|
||
{
|
||
gcc_checking_assert (PAT_VAR_LOCATION_LOC (val) == (rtx)value);
|
||
PAT_VAR_LOCATION_LOC (val) = const0_rtx;
|
||
}
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last);
|
||
if (GET_CODE (val) == VAR_LOCATION)
|
||
PAT_VAR_LOCATION_LOC (val) = (rtx)value;
|
||
}
|
||
|
||
delink_debug_stmt:
|
||
/* In order not to generate too many debug temporaries,
|
||
we delink all uses of debug statements we already expanded.
|
||
Therefore debug statements between definition and real
|
||
use of TERed SSA names will continue to use the SSA name,
|
||
and not be replaced with debug temps. */
|
||
delink_stmt_imm_use (stmt);
|
||
|
||
gsi = nsi;
|
||
gsi_next (&nsi);
|
||
if (gsi_end_p (nsi))
|
||
break;
|
||
stmt = gsi_stmt (nsi);
|
||
if (!is_gimple_debug (stmt))
|
||
break;
|
||
}
|
||
|
||
set_curr_insn_location (sloc);
|
||
}
|
||
else
|
||
{
|
||
gcall *call_stmt = dyn_cast <gcall *> (stmt);
|
||
if (call_stmt
|
||
&& gimple_call_tail_p (call_stmt)
|
||
&& disable_tail_calls)
|
||
gimple_call_set_tail (call_stmt, false);
|
||
|
||
if (call_stmt && gimple_call_tail_p (call_stmt))
|
||
{
|
||
bool can_fallthru;
|
||
new_bb = expand_gimple_tailcall (bb, call_stmt, &can_fallthru);
|
||
if (new_bb)
|
||
{
|
||
if (can_fallthru)
|
||
bb = new_bb;
|
||
else
|
||
return new_bb;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
def_operand_p def_p;
|
||
def_p = SINGLE_SSA_DEF_OPERAND (stmt, SSA_OP_DEF);
|
||
|
||
if (def_p != NULL)
|
||
{
|
||
/* Ignore this stmt if it is in the list of
|
||
replaceable expressions. */
|
||
if (SA.values
|
||
&& bitmap_bit_p (SA.values,
|
||
SSA_NAME_VERSION (DEF_FROM_PTR (def_p))))
|
||
continue;
|
||
}
|
||
last = expand_gimple_stmt (stmt);
|
||
maybe_dump_rtl_for_gimple_stmt (stmt, last);
|
||
}
|
||
}
|
||
}
|
||
|
||
currently_expanding_gimple_stmt = NULL;
|
||
|
||
/* Expand implicit goto and convert goto_locus. */
|
||
FOR_EACH_EDGE (e, ei, bb->succs)
|
||
{
|
||
if (e->goto_locus != UNKNOWN_LOCATION)
|
||
set_curr_insn_location (e->goto_locus);
|
||
if ((e->flags & EDGE_FALLTHRU) && e->dest != bb->next_bb)
|
||
{
|
||
emit_jump (label_rtx_for_bb (e->dest));
|
||
e->flags &= ~EDGE_FALLTHRU;
|
||
}
|
||
}
|
||
|
||
/* Expanded RTL can create a jump in the last instruction of block.
|
||
This later might be assumed to be a jump to successor and break edge insertion.
|
||
We need to insert dummy move to prevent this. PR41440. */
|
||
if (single_succ_p (bb)
|
||
&& (single_succ_edge (bb)->flags & EDGE_FALLTHRU)
|
||
&& (last = get_last_insn ())
|
||
&& (JUMP_P (last)
|
||
|| (DEBUG_INSN_P (last)
|
||
&& JUMP_P (prev_nondebug_insn (last)))))
|
||
{
|
||
rtx dummy = gen_reg_rtx (SImode);
|
||
emit_insn_after_noloc (gen_move_insn (dummy, dummy), last, NULL);
|
||
}
|
||
|
||
do_pending_stack_adjust ();
|
||
|
||
/* Find the block tail. The last insn in the block is the insn
|
||
before a barrier and/or table jump insn. */
|
||
last = get_last_insn ();
|
||
if (BARRIER_P (last))
|
||
last = PREV_INSN (last);
|
||
if (JUMP_TABLE_DATA_P (last))
|
||
last = PREV_INSN (PREV_INSN (last));
|
||
if (BARRIER_P (last))
|
||
last = PREV_INSN (last);
|
||
BB_END (bb) = last;
|
||
|
||
update_bb_for_insn (bb);
|
||
|
||
return bb;
|
||
}
|
||
|
||
|
||
/* Create a basic block for initialization code. */
|
||
|
||
static basic_block
|
||
construct_init_block (void)
|
||
{
|
||
basic_block init_block, first_block;
|
||
edge e = NULL;
|
||
int flags;
|
||
|
||
/* Multiple entry points not supported yet. */
|
||
gcc_assert (EDGE_COUNT (ENTRY_BLOCK_PTR_FOR_FN (cfun)->succs) == 1);
|
||
init_rtl_bb_info (ENTRY_BLOCK_PTR_FOR_FN (cfun));
|
||
init_rtl_bb_info (EXIT_BLOCK_PTR_FOR_FN (cfun));
|
||
ENTRY_BLOCK_PTR_FOR_FN (cfun)->flags |= BB_RTL;
|
||
EXIT_BLOCK_PTR_FOR_FN (cfun)->flags |= BB_RTL;
|
||
|
||
e = EDGE_SUCC (ENTRY_BLOCK_PTR_FOR_FN (cfun), 0);
|
||
|
||
/* When entry edge points to first basic block, we don't need jump,
|
||
otherwise we have to jump into proper target. */
|
||
if (e && e->dest != ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb)
|
||
{
|
||
tree label = gimple_block_label (e->dest);
|
||
|
||
emit_jump (jump_target_rtx (label));
|
||
flags = 0;
|
||
}
|
||
else
|
||
flags = EDGE_FALLTHRU;
|
||
|
||
init_block = create_basic_block (NEXT_INSN (get_insns ()),
|
||
get_last_insn (),
|
||
ENTRY_BLOCK_PTR_FOR_FN (cfun));
|
||
init_block->count = ENTRY_BLOCK_PTR_FOR_FN (cfun)->count;
|
||
add_bb_to_loop (init_block, ENTRY_BLOCK_PTR_FOR_FN (cfun)->loop_father);
|
||
if (e)
|
||
{
|
||
first_block = e->dest;
|
||
redirect_edge_succ (e, init_block);
|
||
make_single_succ_edge (init_block, first_block, flags);
|
||
}
|
||
else
|
||
make_single_succ_edge (init_block, EXIT_BLOCK_PTR_FOR_FN (cfun),
|
||
EDGE_FALLTHRU);
|
||
|
||
update_bb_for_insn (init_block);
|
||
return init_block;
|
||
}
|
||
|
||
/* For each lexical block, set BLOCK_NUMBER to the depth at which it is
|
||
found in the block tree. */
|
||
|
||
static void
|
||
set_block_levels (tree block, int level)
|
||
{
|
||
while (block)
|
||
{
|
||
BLOCK_NUMBER (block) = level;
|
||
set_block_levels (BLOCK_SUBBLOCKS (block), level + 1);
|
||
block = BLOCK_CHAIN (block);
|
||
}
|
||
}
|
||
|
||
/* Create a block containing landing pads and similar stuff. */
|
||
|
||
static void
|
||
construct_exit_block (void)
|
||
{
|
||
rtx_insn *head = get_last_insn ();
|
||
rtx_insn *end;
|
||
basic_block exit_block;
|
||
edge e, e2;
|
||
unsigned ix;
|
||
edge_iterator ei;
|
||
basic_block prev_bb = EXIT_BLOCK_PTR_FOR_FN (cfun)->prev_bb;
|
||
rtx_insn *orig_end = BB_END (prev_bb);
|
||
|
||
rtl_profile_for_bb (EXIT_BLOCK_PTR_FOR_FN (cfun));
|
||
|
||
/* Make sure the locus is set to the end of the function, so that
|
||
epilogue line numbers and warnings are set properly. */
|
||
if (LOCATION_LOCUS (cfun->function_end_locus) != UNKNOWN_LOCATION)
|
||
input_location = cfun->function_end_locus;
|
||
|
||
/* Generate rtl for function exit. */
|
||
expand_function_end ();
|
||
|
||
end = get_last_insn ();
|
||
if (head == end)
|
||
return;
|
||
/* While emitting the function end we could move end of the last basic
|
||
block. */
|
||
BB_END (prev_bb) = orig_end;
|
||
while (NEXT_INSN (head) && NOTE_P (NEXT_INSN (head)))
|
||
head = NEXT_INSN (head);
|
||
/* But make sure exit_block starts with RETURN_LABEL, otherwise the
|
||
bb count counting will be confused. Any instructions before that
|
||
label are emitted for the case where PREV_BB falls through into the
|
||
exit block, so append those instructions to prev_bb in that case. */
|
||
if (NEXT_INSN (head) != return_label)
|
||
{
|
||
while (NEXT_INSN (head) != return_label)
|
||
{
|
||
if (!NOTE_P (NEXT_INSN (head)))
|
||
BB_END (prev_bb) = NEXT_INSN (head);
|
||
head = NEXT_INSN (head);
|
||
}
|
||
}
|
||
exit_block = create_basic_block (NEXT_INSN (head), end, prev_bb);
|
||
exit_block->count = EXIT_BLOCK_PTR_FOR_FN (cfun)->count;
|
||
add_bb_to_loop (exit_block, EXIT_BLOCK_PTR_FOR_FN (cfun)->loop_father);
|
||
|
||
ix = 0;
|
||
while (ix < EDGE_COUNT (EXIT_BLOCK_PTR_FOR_FN (cfun)->preds))
|
||
{
|
||
e = EDGE_PRED (EXIT_BLOCK_PTR_FOR_FN (cfun), ix);
|
||
if (!(e->flags & EDGE_ABNORMAL))
|
||
redirect_edge_succ (e, exit_block);
|
||
else
|
||
ix++;
|
||
}
|
||
|
||
e = make_single_succ_edge (exit_block, EXIT_BLOCK_PTR_FOR_FN (cfun),
|
||
EDGE_FALLTHRU);
|
||
FOR_EACH_EDGE (e2, ei, EXIT_BLOCK_PTR_FOR_FN (cfun)->preds)
|
||
if (e2 != e)
|
||
{
|
||
exit_block->count -= e2->count ();
|
||
}
|
||
update_bb_for_insn (exit_block);
|
||
}
|
||
|
||
/* Helper function for discover_nonconstant_array_refs.
|
||
Look for ARRAY_REF nodes with non-constant indexes and mark them
|
||
addressable. */
|
||
|
||
static tree
|
||
discover_nonconstant_array_refs_r (tree * tp, int *walk_subtrees,
|
||
void *data ATTRIBUTE_UNUSED)
|
||
{
|
||
tree t = *tp;
|
||
|
||
if (IS_TYPE_OR_DECL_P (t))
|
||
*walk_subtrees = 0;
|
||
else if (TREE_CODE (t) == ARRAY_REF || TREE_CODE (t) == ARRAY_RANGE_REF)
|
||
{
|
||
while (((TREE_CODE (t) == ARRAY_REF || TREE_CODE (t) == ARRAY_RANGE_REF)
|
||
&& is_gimple_min_invariant (TREE_OPERAND (t, 1))
|
||
&& (!TREE_OPERAND (t, 2)
|
||
|| is_gimple_min_invariant (TREE_OPERAND (t, 2))))
|
||
|| (TREE_CODE (t) == COMPONENT_REF
|
||
&& (!TREE_OPERAND (t,2)
|
||
|| is_gimple_min_invariant (TREE_OPERAND (t, 2))))
|
||
|| TREE_CODE (t) == BIT_FIELD_REF
|
||
|| TREE_CODE (t) == REALPART_EXPR
|
||
|| TREE_CODE (t) == IMAGPART_EXPR
|
||
|| TREE_CODE (t) == VIEW_CONVERT_EXPR
|
||
|| CONVERT_EXPR_P (t))
|
||
t = TREE_OPERAND (t, 0);
|
||
|
||
if (TREE_CODE (t) == ARRAY_REF || TREE_CODE (t) == ARRAY_RANGE_REF)
|
||
{
|
||
t = get_base_address (t);
|
||
if (t && DECL_P (t)
|
||
&& DECL_MODE (t) != BLKmode)
|
||
TREE_ADDRESSABLE (t) = 1;
|
||
}
|
||
|
||
*walk_subtrees = 0;
|
||
}
|
||
/* References of size POLY_INT_CST to a fixed-size object must go
|
||
through memory. It's more efficient to force that here than
|
||
to create temporary slots on the fly. */
|
||
else if ((TREE_CODE (t) == MEM_REF || TREE_CODE (t) == TARGET_MEM_REF)
|
||
&& TYPE_SIZE (TREE_TYPE (t))
|
||
&& POLY_INT_CST_P (TYPE_SIZE (TREE_TYPE (t))))
|
||
{
|
||
tree base = get_base_address (t);
|
||
if (base
|
||
&& DECL_P (base)
|
||
&& DECL_MODE (base) != BLKmode
|
||
&& GET_MODE_SIZE (DECL_MODE (base)).is_constant ())
|
||
TREE_ADDRESSABLE (base) = 1;
|
||
*walk_subtrees = 0;
|
||
}
|
||
|
||
return NULL_TREE;
|
||
}
|
||
|
||
/* If there's a chance to get a pseudo for t then if it would be of float mode
|
||
and the actual access is via an integer mode (lowered memcpy or similar
|
||
access) then avoid the register expansion if the mode likely is not storage
|
||
suitable for raw bits processing (like XFmode on i?86). */
|
||
|
||
static void
|
||
avoid_type_punning_on_regs (tree t)
|
||
{
|
||
machine_mode access_mode = TYPE_MODE (TREE_TYPE (t));
|
||
if (access_mode != BLKmode
|
||
&& !SCALAR_INT_MODE_P (access_mode))
|
||
return;
|
||
tree base = get_base_address (t);
|
||
if (DECL_P (base)
|
||
&& !TREE_ADDRESSABLE (base)
|
||
&& FLOAT_MODE_P (DECL_MODE (base))
|
||
&& maybe_lt (GET_MODE_PRECISION (DECL_MODE (base)),
|
||
GET_MODE_BITSIZE (GET_MODE_INNER (DECL_MODE (base))))
|
||
/* Double check in the expensive way we really would get a pseudo. */
|
||
&& use_register_for_decl (base))
|
||
TREE_ADDRESSABLE (base) = 1;
|
||
}
|
||
|
||
/* RTL expansion is not able to compile array references with variable
|
||
offsets for arrays stored in single register. Discover such
|
||
expressions and mark variables as addressable to avoid this
|
||
scenario. */
|
||
|
||
static void
|
||
discover_nonconstant_array_refs (void)
|
||
{
|
||
basic_block bb;
|
||
gimple_stmt_iterator gsi;
|
||
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
{
|
||
gimple *stmt = gsi_stmt (gsi);
|
||
if (!is_gimple_debug (stmt))
|
||
{
|
||
walk_gimple_op (stmt, discover_nonconstant_array_refs_r, NULL);
|
||
gcall *call = dyn_cast <gcall *> (stmt);
|
||
if (call && gimple_call_internal_p (call))
|
||
switch (gimple_call_internal_fn (call))
|
||
{
|
||
case IFN_LOAD_LANES:
|
||
/* The source must be a MEM. */
|
||
mark_addressable (gimple_call_arg (call, 0));
|
||
break;
|
||
case IFN_STORE_LANES:
|
||
/* The destination must be a MEM. */
|
||
mark_addressable (gimple_call_lhs (call));
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
if (gimple_vdef (stmt))
|
||
{
|
||
tree t = gimple_get_lhs (stmt);
|
||
if (t && REFERENCE_CLASS_P (t))
|
||
avoid_type_punning_on_regs (t);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* This function sets crtl->args.internal_arg_pointer to a virtual
|
||
register if DRAP is needed. Local register allocator will replace
|
||
virtual_incoming_args_rtx with the virtual register. */
|
||
|
||
static void
|
||
expand_stack_alignment (void)
|
||
{
|
||
rtx drap_rtx;
|
||
unsigned int preferred_stack_boundary;
|
||
|
||
if (! SUPPORTS_STACK_ALIGNMENT)
|
||
return;
|
||
|
||
if (cfun->calls_alloca
|
||
|| cfun->has_nonlocal_label
|
||
|| crtl->has_nonlocal_goto)
|
||
crtl->need_drap = true;
|
||
|
||
/* Call update_stack_boundary here again to update incoming stack
|
||
boundary. It may set incoming stack alignment to a different
|
||
value after RTL expansion. TARGET_FUNCTION_OK_FOR_SIBCALL may
|
||
use the minimum incoming stack alignment to check if it is OK
|
||
to perform sibcall optimization since sibcall optimization will
|
||
only align the outgoing stack to incoming stack boundary. */
|
||
if (targetm.calls.update_stack_boundary)
|
||
targetm.calls.update_stack_boundary ();
|
||
|
||
/* The incoming stack frame has to be aligned at least at
|
||
parm_stack_boundary. */
|
||
gcc_assert (crtl->parm_stack_boundary <= INCOMING_STACK_BOUNDARY);
|
||
|
||
/* Update crtl->stack_alignment_estimated and use it later to align
|
||
stack. We check PREFERRED_STACK_BOUNDARY if there may be non-call
|
||
exceptions since callgraph doesn't collect incoming stack alignment
|
||
in this case. */
|
||
if (cfun->can_throw_non_call_exceptions
|
||
&& PREFERRED_STACK_BOUNDARY > crtl->preferred_stack_boundary)
|
||
preferred_stack_boundary = PREFERRED_STACK_BOUNDARY;
|
||
else
|
||
preferred_stack_boundary = crtl->preferred_stack_boundary;
|
||
if (preferred_stack_boundary > crtl->stack_alignment_estimated)
|
||
crtl->stack_alignment_estimated = preferred_stack_boundary;
|
||
if (preferred_stack_boundary > crtl->stack_alignment_needed)
|
||
crtl->stack_alignment_needed = preferred_stack_boundary;
|
||
|
||
gcc_assert (crtl->stack_alignment_needed
|
||
<= crtl->stack_alignment_estimated);
|
||
|
||
crtl->stack_realign_needed
|
||
= INCOMING_STACK_BOUNDARY < crtl->stack_alignment_estimated;
|
||
crtl->stack_realign_tried = crtl->stack_realign_needed;
|
||
|
||
crtl->stack_realign_processed = true;
|
||
|
||
/* Target has to redefine TARGET_GET_DRAP_RTX to support stack
|
||
alignment. */
|
||
gcc_assert (targetm.calls.get_drap_rtx != NULL);
|
||
drap_rtx = targetm.calls.get_drap_rtx ();
|
||
|
||
/* stack_realign_drap and drap_rtx must match. */
|
||
gcc_assert ((stack_realign_drap != 0) == (drap_rtx != NULL));
|
||
|
||
/* Do nothing if NULL is returned, which means DRAP is not needed. */
|
||
if (drap_rtx != NULL)
|
||
{
|
||
crtl->args.internal_arg_pointer = drap_rtx;
|
||
|
||
/* Call fixup_tail_calls to clean up REG_EQUIV note if DRAP is
|
||
needed. */
|
||
fixup_tail_calls ();
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
expand_main_function (void)
|
||
{
|
||
#if (defined(INVOKE__main) \
|
||
|| (!defined(HAS_INIT_SECTION) \
|
||
&& !defined(INIT_SECTION_ASM_OP) \
|
||
&& !defined(INIT_ARRAY_SECTION_ASM_OP)))
|
||
emit_library_call (init_one_libfunc (NAME__MAIN), LCT_NORMAL, VOIDmode);
|
||
#endif
|
||
}
|
||
|
||
|
||
/* Expand code to initialize the stack_protect_guard. This is invoked at
|
||
the beginning of a function to be protected. */
|
||
|
||
static void
|
||
stack_protect_prologue (void)
|
||
{
|
||
tree guard_decl = targetm.stack_protect_guard ();
|
||
rtx x, y;
|
||
|
||
crtl->stack_protect_guard_decl = guard_decl;
|
||
x = expand_normal (crtl->stack_protect_guard);
|
||
|
||
if (targetm.have_stack_protect_combined_set () && guard_decl)
|
||
{
|
||
gcc_assert (DECL_P (guard_decl));
|
||
y = DECL_RTL (guard_decl);
|
||
|
||
/* Allow the target to compute address of Y and copy it to X without
|
||
leaking Y into a register. This combined address + copy pattern
|
||
allows the target to prevent spilling of any intermediate results by
|
||
splitting it after register allocator. */
|
||
if (rtx_insn *insn = targetm.gen_stack_protect_combined_set (x, y))
|
||
{
|
||
emit_insn (insn);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (guard_decl)
|
||
y = expand_normal (guard_decl);
|
||
else
|
||
y = const0_rtx;
|
||
|
||
/* Allow the target to copy from Y to X without leaking Y into a
|
||
register. */
|
||
if (targetm.have_stack_protect_set ())
|
||
if (rtx_insn *insn = targetm.gen_stack_protect_set (x, y))
|
||
{
|
||
emit_insn (insn);
|
||
return;
|
||
}
|
||
|
||
/* Otherwise do a straight move. */
|
||
emit_move_insn (x, y);
|
||
}
|
||
|
||
/* Translate the intermediate representation contained in the CFG
|
||
from GIMPLE trees to RTL.
|
||
|
||
We do conversion per basic block and preserve/update the tree CFG.
|
||
This implies we have to do some magic as the CFG can simultaneously
|
||
consist of basic blocks containing RTL and GIMPLE trees. This can
|
||
confuse the CFG hooks, so be careful to not manipulate CFG during
|
||
the expansion. */
|
||
|
||
namespace {
|
||
|
||
const pass_data pass_data_expand =
|
||
{
|
||
RTL_PASS, /* type */
|
||
"expand", /* name */
|
||
OPTGROUP_NONE, /* optinfo_flags */
|
||
TV_EXPAND, /* tv_id */
|
||
( PROP_ssa | PROP_gimple_leh | PROP_cfg
|
||
| PROP_gimple_lcx
|
||
| PROP_gimple_lvec
|
||
| PROP_gimple_lva), /* properties_required */
|
||
PROP_rtl, /* properties_provided */
|
||
( PROP_ssa | PROP_trees ), /* properties_destroyed */
|
||
0, /* todo_flags_start */
|
||
0, /* todo_flags_finish */
|
||
};
|
||
|
||
class pass_expand : public rtl_opt_pass
|
||
{
|
||
public:
|
||
pass_expand (gcc::context *ctxt)
|
||
: rtl_opt_pass (pass_data_expand, ctxt)
|
||
{}
|
||
|
||
/* opt_pass methods: */
|
||
virtual unsigned int execute (function *);
|
||
|
||
}; // class pass_expand
|
||
|
||
unsigned int
|
||
pass_expand::execute (function *fun)
|
||
{
|
||
basic_block bb, init_block;
|
||
edge_iterator ei;
|
||
edge e;
|
||
rtx_insn *var_seq, *var_ret_seq;
|
||
unsigned i;
|
||
|
||
timevar_push (TV_OUT_OF_SSA);
|
||
rewrite_out_of_ssa (&SA);
|
||
timevar_pop (TV_OUT_OF_SSA);
|
||
SA.partition_to_pseudo = XCNEWVEC (rtx, SA.map->num_partitions);
|
||
|
||
if (MAY_HAVE_DEBUG_BIND_STMTS && flag_tree_ter)
|
||
{
|
||
gimple_stmt_iterator gsi;
|
||
FOR_EACH_BB_FN (bb, cfun)
|
||
for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
|
||
if (gimple_debug_bind_p (gsi_stmt (gsi)))
|
||
avoid_deep_ter_for_debug (gsi_stmt (gsi), 0);
|
||
}
|
||
|
||
/* Mark arrays indexed with non-constant indices with TREE_ADDRESSABLE. */
|
||
discover_nonconstant_array_refs ();
|
||
|
||
/* Make sure all values used by the optimization passes have sane
|
||
defaults. */
|
||
reg_renumber = 0;
|
||
|
||
/* Some backends want to know that we are expanding to RTL. */
|
||
currently_expanding_to_rtl = 1;
|
||
/* Dominators are not kept up-to-date as we may create new basic-blocks. */
|
||
free_dominance_info (CDI_DOMINATORS);
|
||
|
||
rtl_profile_for_bb (ENTRY_BLOCK_PTR_FOR_FN (fun));
|
||
|
||
insn_locations_init ();
|
||
if (!DECL_IS_BUILTIN (current_function_decl))
|
||
{
|
||
/* Eventually, all FEs should explicitly set function_start_locus. */
|
||
if (LOCATION_LOCUS (fun->function_start_locus) == UNKNOWN_LOCATION)
|
||
set_curr_insn_location
|
||
(DECL_SOURCE_LOCATION (current_function_decl));
|
||
else
|
||
set_curr_insn_location (fun->function_start_locus);
|
||
}
|
||
else
|
||
set_curr_insn_location (UNKNOWN_LOCATION);
|
||
prologue_location = curr_insn_location ();
|
||
|
||
#ifdef INSN_SCHEDULING
|
||
init_sched_attrs ();
|
||
#endif
|
||
|
||
/* Make sure first insn is a note even if we don't want linenums.
|
||
This makes sure the first insn will never be deleted.
|
||
Also, final expects a note to appear there. */
|
||
emit_note (NOTE_INSN_DELETED);
|
||
|
||
targetm.expand_to_rtl_hook ();
|
||
crtl->init_stack_alignment ();
|
||
fun->cfg->max_jumptable_ents = 0;
|
||
|
||
/* Resovle the function section. Some targets, like ARM EABI rely on knowledge
|
||
of the function section at exapnsion time to predict distance of calls. */
|
||
resolve_unique_section (current_function_decl, 0, flag_function_sections);
|
||
|
||
/* Expand the variables recorded during gimple lowering. */
|
||
timevar_push (TV_VAR_EXPAND);
|
||
start_sequence ();
|
||
|
||
var_ret_seq = expand_used_vars ();
|
||
|
||
var_seq = get_insns ();
|
||
end_sequence ();
|
||
timevar_pop (TV_VAR_EXPAND);
|
||
|
||
/* Honor stack protection warnings. */
|
||
if (warn_stack_protect)
|
||
{
|
||
if (fun->calls_alloca)
|
||
warning (OPT_Wstack_protector,
|
||
"stack protector not protecting local variables: "
|
||
"variable length buffer");
|
||
if (has_short_buffer && !crtl->stack_protect_guard)
|
||
warning (OPT_Wstack_protector,
|
||
"stack protector not protecting function: "
|
||
"all local arrays are less than %d bytes long",
|
||
(int) param_ssp_buffer_size);
|
||
}
|
||
|
||
/* Set up parameters and prepare for return, for the function. */
|
||
expand_function_start (current_function_decl);
|
||
|
||
/* If we emitted any instructions for setting up the variables,
|
||
emit them before the FUNCTION_START note. */
|
||
if (var_seq)
|
||
{
|
||
emit_insn_before (var_seq, parm_birth_insn);
|
||
|
||
/* In expand_function_end we'll insert the alloca save/restore
|
||
before parm_birth_insn. We've just insertted an alloca call.
|
||
Adjust the pointer to match. */
|
||
parm_birth_insn = var_seq;
|
||
}
|
||
|
||
/* Now propagate the RTL assignment of each partition to the
|
||
underlying var of each SSA_NAME. */
|
||
tree name;
|
||
|
||
FOR_EACH_SSA_NAME (i, name, cfun)
|
||
{
|
||
/* We might have generated new SSA names in
|
||
update_alias_info_with_stack_vars. They will have a NULL
|
||
defining statements, and won't be part of the partitioning,
|
||
so ignore those. */
|
||
if (!SSA_NAME_DEF_STMT (name))
|
||
continue;
|
||
|
||
adjust_one_expanded_partition_var (name);
|
||
}
|
||
|
||
/* Clean up RTL of variables that straddle across multiple
|
||
partitions, and check that the rtl of any PARM_DECLs that are not
|
||
cleaned up is that of their default defs. */
|
||
FOR_EACH_SSA_NAME (i, name, cfun)
|
||
{
|
||
int part;
|
||
|
||
/* We might have generated new SSA names in
|
||
update_alias_info_with_stack_vars. They will have a NULL
|
||
defining statements, and won't be part of the partitioning,
|
||
so ignore those. */
|
||
if (!SSA_NAME_DEF_STMT (name))
|
||
continue;
|
||
part = var_to_partition (SA.map, name);
|
||
if (part == NO_PARTITION)
|
||
continue;
|
||
|
||
/* If this decl was marked as living in multiple places, reset
|
||
this now to NULL. */
|
||
tree var = SSA_NAME_VAR (name);
|
||
if (var && DECL_RTL_IF_SET (var) == pc_rtx)
|
||
SET_DECL_RTL (var, NULL);
|
||
/* Check that the pseudos chosen by assign_parms are those of
|
||
the corresponding default defs. */
|
||
else if (SSA_NAME_IS_DEFAULT_DEF (name)
|
||
&& (TREE_CODE (var) == PARM_DECL
|
||
|| TREE_CODE (var) == RESULT_DECL))
|
||
{
|
||
rtx in = DECL_RTL_IF_SET (var);
|
||
gcc_assert (in);
|
||
rtx out = SA.partition_to_pseudo[part];
|
||
gcc_assert (in == out);
|
||
|
||
/* Now reset VAR's RTL to IN, so that the _EXPR attrs match
|
||
those expected by debug backends for each parm and for
|
||
the result. This is particularly important for stabs,
|
||
whose register elimination from parm's DECL_RTL may cause
|
||
-fcompare-debug differences as SET_DECL_RTL changes reg's
|
||
attrs. So, make sure the RTL already has the parm as the
|
||
EXPR, so that it won't change. */
|
||
SET_DECL_RTL (var, NULL_RTX);
|
||
if (MEM_P (in))
|
||
set_mem_attributes (in, var, true);
|
||
SET_DECL_RTL (var, in);
|
||
}
|
||
}
|
||
|
||
/* If this function is `main', emit a call to `__main'
|
||
to run global initializers, etc. */
|
||
if (DECL_NAME (current_function_decl)
|
||
&& MAIN_NAME_P (DECL_NAME (current_function_decl))
|
||
&& DECL_FILE_SCOPE_P (current_function_decl))
|
||
expand_main_function ();
|
||
|
||
/* Initialize the stack_protect_guard field. This must happen after the
|
||
call to __main (if any) so that the external decl is initialized. */
|
||
if (crtl->stack_protect_guard && targetm.stack_protect_runtime_enabled_p ())
|
||
stack_protect_prologue ();
|
||
|
||
expand_phi_nodes (&SA);
|
||
|
||
/* Release any stale SSA redirection data. */
|
||
redirect_edge_var_map_empty ();
|
||
|
||
/* Register rtl specific functions for cfg. */
|
||
rtl_register_cfg_hooks ();
|
||
|
||
init_block = construct_init_block ();
|
||
|
||
/* Clear EDGE_EXECUTABLE on the entry edge(s). It is cleaned from the
|
||
remaining edges later. */
|
||
FOR_EACH_EDGE (e, ei, ENTRY_BLOCK_PTR_FOR_FN (fun)->succs)
|
||
e->flags &= ~EDGE_EXECUTABLE;
|
||
|
||
/* If the function has too many markers, drop them while expanding. */
|
||
if (cfun->debug_marker_count
|
||
>= param_max_debug_marker_count)
|
||
cfun->debug_nonbind_markers = false;
|
||
|
||
lab_rtx_for_bb = new hash_map<basic_block, rtx_code_label *>;
|
||
FOR_BB_BETWEEN (bb, init_block->next_bb, EXIT_BLOCK_PTR_FOR_FN (fun),
|
||
next_bb)
|
||
bb = expand_gimple_basic_block (bb, var_ret_seq != NULL_RTX);
|
||
|
||
if (MAY_HAVE_DEBUG_BIND_INSNS)
|
||
expand_debug_locations ();
|
||
|
||
if (deep_ter_debug_map)
|
||
{
|
||
delete deep_ter_debug_map;
|
||
deep_ter_debug_map = NULL;
|
||
}
|
||
|
||
/* Free stuff we no longer need after GIMPLE optimizations. */
|
||
free_dominance_info (CDI_DOMINATORS);
|
||
free_dominance_info (CDI_POST_DOMINATORS);
|
||
delete_tree_cfg_annotations (fun);
|
||
|
||
timevar_push (TV_OUT_OF_SSA);
|
||
finish_out_of_ssa (&SA);
|
||
timevar_pop (TV_OUT_OF_SSA);
|
||
|
||
timevar_push (TV_POST_EXPAND);
|
||
/* We are no longer in SSA form. */
|
||
fun->gimple_df->in_ssa_p = false;
|
||
loops_state_clear (LOOP_CLOSED_SSA);
|
||
|
||
/* Expansion is used by optimization passes too, set maybe_hot_insn_p
|
||
conservatively to true until they are all profile aware. */
|
||
delete lab_rtx_for_bb;
|
||
free_histograms (fun);
|
||
|
||
construct_exit_block ();
|
||
insn_locations_finalize ();
|
||
|
||
if (var_ret_seq)
|
||
{
|
||
rtx_insn *after = return_label;
|
||
rtx_insn *next = NEXT_INSN (after);
|
||
if (next && NOTE_INSN_BASIC_BLOCK_P (next))
|
||
after = next;
|
||
emit_insn_after (var_ret_seq, after);
|
||
}
|
||
|
||
/* Zap the tree EH table. */
|
||
set_eh_throw_stmt_table (fun, NULL);
|
||
|
||
/* We need JUMP_LABEL be set in order to redirect jumps, and hence
|
||
split edges which edge insertions might do. */
|
||
rebuild_jump_labels (get_insns ());
|
||
|
||
/* If we have a single successor to the entry block, put the pending insns
|
||
after parm birth, but before NOTE_INSNS_FUNCTION_BEG. */
|
||
if (single_succ_p (ENTRY_BLOCK_PTR_FOR_FN (fun)))
|
||
{
|
||
edge e = single_succ_edge (ENTRY_BLOCK_PTR_FOR_FN (fun));
|
||
if (e->insns.r)
|
||
{
|
||
rtx_insn *insns = e->insns.r;
|
||
e->insns.r = NULL;
|
||
rebuild_jump_labels_chain (insns);
|
||
if (NOTE_P (parm_birth_insn)
|
||
&& NOTE_KIND (parm_birth_insn) == NOTE_INSN_FUNCTION_BEG)
|
||
emit_insn_before_noloc (insns, parm_birth_insn, e->dest);
|
||
else
|
||
emit_insn_after_noloc (insns, parm_birth_insn, e->dest);
|
||
}
|
||
}
|
||
|
||
/* Otherwise, as well as for other edges, take the usual way. */
|
||
commit_edge_insertions ();
|
||
|
||
/* We're done expanding trees to RTL. */
|
||
currently_expanding_to_rtl = 0;
|
||
|
||
flush_mark_addressable_queue ();
|
||
|
||
FOR_BB_BETWEEN (bb, ENTRY_BLOCK_PTR_FOR_FN (fun)->next_bb,
|
||
EXIT_BLOCK_PTR_FOR_FN (fun), next_bb)
|
||
{
|
||
edge e;
|
||
edge_iterator ei;
|
||
for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); )
|
||
{
|
||
/* Clear EDGE_EXECUTABLE. This flag is never used in the backend. */
|
||
e->flags &= ~EDGE_EXECUTABLE;
|
||
|
||
/* At the moment not all abnormal edges match the RTL
|
||
representation. It is safe to remove them here as
|
||
find_many_sub_basic_blocks will rediscover them.
|
||
In the future we should get this fixed properly. */
|
||
if ((e->flags & EDGE_ABNORMAL)
|
||
&& !(e->flags & EDGE_SIBCALL))
|
||
remove_edge (e);
|
||
else
|
||
ei_next (&ei);
|
||
}
|
||
}
|
||
|
||
auto_sbitmap blocks (last_basic_block_for_fn (fun));
|
||
bitmap_ones (blocks);
|
||
find_many_sub_basic_blocks (blocks);
|
||
purge_all_dead_edges ();
|
||
|
||
/* After initial rtl generation, call back to finish generating
|
||
exception support code. We need to do this before cleaning up
|
||
the CFG as the code does not expect dead landing pads. */
|
||
if (fun->eh->region_tree != NULL)
|
||
finish_eh_generation ();
|
||
|
||
/* Call expand_stack_alignment after finishing all
|
||
updates to crtl->preferred_stack_boundary. */
|
||
expand_stack_alignment ();
|
||
|
||
/* Fixup REG_EQUIV notes in the prologue if there are tailcalls in this
|
||
function. */
|
||
if (crtl->tail_call_emit)
|
||
fixup_tail_calls ();
|
||
|
||
unsigned HOST_WIDE_INT patch_area_size = function_entry_patch_area_size;
|
||
unsigned HOST_WIDE_INT patch_area_entry = function_entry_patch_area_start;
|
||
|
||
tree patchable_function_entry_attr
|
||
= lookup_attribute ("patchable_function_entry",
|
||
DECL_ATTRIBUTES (cfun->decl));
|
||
if (patchable_function_entry_attr)
|
||
{
|
||
tree pp_val = TREE_VALUE (patchable_function_entry_attr);
|
||
tree patchable_function_entry_value1 = TREE_VALUE (pp_val);
|
||
|
||
patch_area_size = tree_to_uhwi (patchable_function_entry_value1);
|
||
patch_area_entry = 0;
|
||
if (TREE_CHAIN (pp_val) != NULL_TREE)
|
||
{
|
||
tree patchable_function_entry_value2
|
||
= TREE_VALUE (TREE_CHAIN (pp_val));
|
||
patch_area_entry = tree_to_uhwi (patchable_function_entry_value2);
|
||
}
|
||
}
|
||
|
||
if (patch_area_entry > patch_area_size)
|
||
{
|
||
if (patch_area_size > 0)
|
||
warning (OPT_Wattributes,
|
||
"patchable function entry %wu exceeds size %wu",
|
||
patch_area_entry, patch_area_size);
|
||
patch_area_entry = 0;
|
||
}
|
||
|
||
crtl->patch_area_size = patch_area_size;
|
||
crtl->patch_area_entry = patch_area_entry;
|
||
|
||
/* BB subdivision may have created basic blocks that are only reachable
|
||
from unlikely bbs but not marked as such in the profile. */
|
||
if (optimize)
|
||
propagate_unlikely_bbs_forward ();
|
||
|
||
/* Remove unreachable blocks, otherwise we cannot compute dominators
|
||
which are needed for loop state verification. As a side-effect
|
||
this also compacts blocks.
|
||
??? We cannot remove trivially dead insns here as for example
|
||
the DRAP reg on i?86 is not magically live at this point.
|
||
gcc.c-torture/execute/ipa-sra-2.c execution, -Os -m32 fails otherwise. */
|
||
cleanup_cfg (CLEANUP_NO_INSN_DEL);
|
||
|
||
checking_verify_flow_info ();
|
||
|
||
/* Initialize pseudos allocated for hard registers. */
|
||
emit_initial_value_sets ();
|
||
|
||
/* And finally unshare all RTL. */
|
||
unshare_all_rtl ();
|
||
|
||
/* There's no need to defer outputting this function any more; we
|
||
know we want to output it. */
|
||
DECL_DEFER_OUTPUT (current_function_decl) = 0;
|
||
|
||
/* Now that we're done expanding trees to RTL, we shouldn't have any
|
||
more CONCATs anywhere. */
|
||
generating_concat_p = 0;
|
||
|
||
if (dump_file)
|
||
{
|
||
fprintf (dump_file,
|
||
"\n\n;;\n;; Full RTL generated for this function:\n;;\n");
|
||
/* And the pass manager will dump RTL for us. */
|
||
}
|
||
|
||
/* If we're emitting a nested function, make sure its parent gets
|
||
emitted as well. Doing otherwise confuses debug info. */
|
||
{
|
||
tree parent;
|
||
for (parent = DECL_CONTEXT (current_function_decl);
|
||
parent != NULL_TREE;
|
||
parent = get_containing_scope (parent))
|
||
if (TREE_CODE (parent) == FUNCTION_DECL)
|
||
TREE_SYMBOL_REFERENCED (DECL_ASSEMBLER_NAME (parent)) = 1;
|
||
}
|
||
|
||
TREE_ASM_WRITTEN (current_function_decl) = 1;
|
||
|
||
/* After expanding, the return labels are no longer needed. */
|
||
return_label = NULL;
|
||
naked_return_label = NULL;
|
||
|
||
/* After expanding, the tm_restart map is no longer needed. */
|
||
if (fun->gimple_df->tm_restart)
|
||
fun->gimple_df->tm_restart = NULL;
|
||
|
||
/* Tag the blocks with a depth number so that change_scope can find
|
||
the common parent easily. */
|
||
set_block_levels (DECL_INITIAL (fun->decl), 0);
|
||
default_rtl_profile ();
|
||
|
||
/* For -dx discard loops now, otherwise IL verify in clean_state will
|
||
ICE. */
|
||
if (rtl_dump_and_exit)
|
||
{
|
||
cfun->curr_properties &= ~PROP_loops;
|
||
loop_optimizer_finalize ();
|
||
}
|
||
|
||
timevar_pop (TV_POST_EXPAND);
|
||
|
||
return 0;
|
||
}
|
||
|
||
} // anon namespace
|
||
|
||
rtl_opt_pass *
|
||
make_pass_expand (gcc::context *ctxt)
|
||
{
|
||
return new pass_expand (ctxt);
|
||
}
|