+2019-05-08 Jakub Jelinek <jakub@redhat.com>
+
+ PR c++/59813
+ PR tree-optimization/89060
+ * tree-ssa-live.h (live_vars_map): New typedef.
+ (compute_live_vars, live_vars_at_stmt, destroy_live_vars): Declare.
+ * tree-ssa-live.c: Include gimple-walk.h and cfganal.h.
+ (struct compute_live_vars_data): New type.
+ (compute_live_vars_visit, compute_live_vars_1, compute_live_vars,
+ live_vars_at_stmt, destroy_live_vars): New functions.
+ * tree-tailcall.c: Include tree-ssa-live.h.
+ (live_vars, live_vars_vec): New global variables.
+ (find_tail_calls): Perform variable life analysis before punting.
+ (tree_optimize_tail_calls_1): Clean up live_vars and live_vars_vec.
+ * tree-inline.h (struct copy_body_data): Add eh_landing_pad_dest
+ member.
+ * tree-inline.c (add_clobbers_to_eh_landing_pad): Remove BB argument.
+ Perform variable life analysis to select variables that really need
+ clobbers added.
+ (copy_edges_for_bb): Don't call add_clobbers_to_eh_landing_pad here,
+ instead set id->eh_landing_pad_dest and assert it is the same.
+ (copy_cfg_body): Call it here if id->eh_landing_pad_dest is non-NULL.
+
2019-05-08 Mihail Ionescu <mihail.ionescu@arm.com>
Richard Earnshaw <rearnsha@arm.com>
+2019-05-08 Jakub Jelinek <jakub@redhat.com>
+
+ PR c++/59813
+ PR tree-optimization/89060
+ * gcc.dg/tree-ssa/pr89060.c: New test.
+
2019-05-08 Mihail Ionescu <mihail.ionescu@arm.com>
Richard Earnshaw <rearnsha@arm.com>
--- /dev/null
+/* PR tree-optimization/89060 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-tailc" } */
+/* { dg-final { scan-tree-dump-not "baz \\\(1\\\); \\\[tail call\\\]" "tailc" } } */
+/* { dg-final { scan-tree-dump "baz \\\(2\\\); \\\[tail call\\\]" "tailc" } } */
+/* { dg-final { scan-tree-dump "baz \\\(3\\\); \\\[tail call\\\]" "tailc" } } */
+/* { dg-final { scan-tree-dump "baz \\\(4\\\); \\\[tail call\\\]" "tailc" } } */
+/* { dg-final { scan-tree-dump-not "baz \\\(5\\\); \\\[tail call\\\]" "tailc" } } */
+
+void qux (char *, int n);
+int baz (int);
+
+int
+foo (int n)
+{
+ char buf[64];
+ qux (buf, n);
+ return baz (1);
+}
+
+int
+bar (int n)
+{
+ {
+ char buf[64];
+ qux (buf, n);
+ }
+ return baz (2);
+}
+
+int
+quux (int n)
+{
+ if (n < 10)
+ {
+ {
+ char buf[64];
+ qux (buf, n);
+ }
+ return baz (3);
+ }
+ if (n > 20)
+ {
+ {
+ char buf2[64];
+ qux (buf2, n + 1);
+ }
+ return baz (4);
+ }
+ char buf3[64];
+ qux (buf3, n + 2);
+ return baz (5);
+}
#include "attribs.h"
#include "sreal.h"
#include "tree-cfgcleanup.h"
+#include "tree-ssa-live.h"
/* I'm not real happy about this, but we need to handle gimple and
non-gimple trees. */
}
/* Insert clobbers for automatic variables of inlined ID->src_fn
- function at the start of basic block BB. */
+ function at the start of basic block ID->eh_landing_pad_dest. */
static void
-add_clobbers_to_eh_landing_pad (basic_block bb, copy_body_data *id)
+add_clobbers_to_eh_landing_pad (copy_body_data *id)
{
tree var;
+ basic_block bb = id->eh_landing_pad_dest;
+ live_vars_map *vars = NULL;
+ unsigned int cnt = 0;
unsigned int i;
FOR_EACH_VEC_SAFE_ELT (id->src_cfun->local_decls, i, var)
if (VAR_P (var)
&& !is_gimple_reg (new_var)
&& auto_var_in_fn_p (new_var, id->dst_fn))
{
+ if (vars == NULL)
+ vars = new live_vars_map;
+ vars->put (DECL_UID (var), cnt++);
+ }
+ }
+ if (vars == NULL)
+ return;
+
+ vec<bitmap_head> live = compute_live_vars (id->src_cfun, vars);
+ FOR_EACH_VEC_SAFE_ELT (id->src_cfun->local_decls, i, var)
+ if (VAR_P (var))
+ {
+ edge e;
+ edge_iterator ei;
+ bool needed = false;
+ unsigned int *v = vars->get (DECL_UID (var));
+ if (v == NULL)
+ continue;
+ FOR_EACH_EDGE (e, ei, bb->preds)
+ if ((e->flags & EDGE_EH) != 0
+ && e->src->index >= id->add_clobbers_to_eh_landing_pads)
+ {
+ basic_block src_bb = (basic_block) e->src->aux;
+
+ if (bitmap_bit_p (&live[src_bb->index], *v))
+ {
+ needed = true;
+ break;
+ }
+ }
+ if (needed)
+ {
+ tree new_var = *id->decl_map->get (var);
gimple_stmt_iterator gsi = gsi_after_labels (bb);
tree clobber = build_clobber (TREE_TYPE (new_var));
gimple *clobber_stmt = gimple_build_assign (new_var, clobber);
gsi_insert_before (&gsi, clobber_stmt, GSI_NEW_STMT);
}
}
+ destroy_live_vars (live);
+ delete vars;
}
/* Copy edges from BB into its copy constructed earlier, scale profile
e->probability = profile_probability::never ();
if (e->dest->index < id->add_clobbers_to_eh_landing_pads)
{
- add_clobbers_to_eh_landing_pad (e->dest, id);
- id->add_clobbers_to_eh_landing_pads = 0;
+ if (id->eh_landing_pad_dest == NULL)
+ id->eh_landing_pad_dest = e->dest;
+ else
+ gcc_assert (id->eh_landing_pad_dest == e->dest);
}
}
}
need_debug_cleanup |= copy_edges_for_bb (bb, num, den, exit_block_map,
abnormal_goto_dest, id);
+ if (id->eh_landing_pad_dest)
+ {
+ add_clobbers_to_eh_landing_pad (id);
+ id->eh_landing_pad_dest = NULL;
+ }
+
if (new_entry)
{
edge e = make_edge (entry_block_map, (basic_block)new_entry->aux,
when inlining a call within an OpenMP SIMD-on-SIMT loop. */
vec<tree> *dst_simt_vars;
+ /* Basic block to which clobbers for local variables from the inline
+ function that need to live in memory should be added. */
+ basic_block eh_landing_pad_dest;
+
/* If clobbers for local variables from the inline function
that need to live in memory should be added to EH landing pads
outside of the inlined function, this should be the number
#include "stringpool.h"
#include "attribs.h"
#include "optinfo.h"
+#include "gimple-walk.h"
+#include "cfganal.h"
static void verify_live_on_entry (tree_live_info_p);
return live;
}
+\f
+/* Data structure for compute_live_vars* functions. */
+
+struct compute_live_vars_data {
+ /* Vector of bitmaps for live vars indices at the end of basic blocks,
+ indexed by bb->index. ACTIVE[ENTRY_BLOCK] must be empty bitmap,
+ ACTIVE[EXIT_BLOCK] is used for STOP_AFTER. */
+ vec<bitmap_head> active;
+ /* Work bitmap of currently live variables. */
+ bitmap work;
+ /* Set of interesting variables. Variables with uids not in this
+ hash_map are not tracked. */
+ live_vars_map *vars;
+};
+
+/* Callback for walk_stmt_load_store_addr_ops. If OP is a VAR_DECL with
+ uid set in DATA->vars, enter its corresponding index into bitmap
+ DATA->work. */
+static bool
+compute_live_vars_visit (gimple *, tree op, tree, void *pdata)
+{
+ compute_live_vars_data *data = (compute_live_vars_data *) pdata;
+ op = get_base_address (op);
+ if (op && VAR_P (op))
+ if (unsigned int *v = data->vars->get (DECL_UID (op)))
+ bitmap_set_bit (data->work, *v);
+ return false;
+}
+
+/* Helper routine for compute_live_vars, calculating the sets of live
+ variables at the end of BB, leaving the result in DATA->work.
+ If STOP_AFTER is non-NULL, stop processing after that stmt. */
+
+static void
+compute_live_vars_1 (basic_block bb, compute_live_vars_data *data,
+ gimple *stop_after)
+{
+ edge e;
+ edge_iterator ei;
+ gimple_stmt_iterator gsi;
+ walk_stmt_load_store_addr_fn visit = compute_live_vars_visit;
+
+ bitmap_clear (data->work);
+ FOR_EACH_EDGE (e, ei, bb->preds)
+ bitmap_ior_into (data->work, &data->active[e->src->index]);
+
+ for (gsi = gsi_start_phis (bb); !gsi_end_p (gsi); gsi_next (&gsi))
+ walk_stmt_load_store_addr_ops (gsi_stmt (gsi), data, 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);
+ if (VAR_P (lhs))
+ if (unsigned int *v = data->vars->get (DECL_UID (lhs)))
+ bitmap_clear_bit (data->work, *v);
+ }
+ else if (!is_gimple_debug (stmt))
+ walk_stmt_load_store_addr_ops (stmt, data, visit, visit, visit);
+ if (stmt == stop_after)
+ break;
+ }
+}
+
+/* For function FN and live_vars_map (hash map from DECL_UIDs to a dense set of
+ indexes of automatic variables VARS, compute which of those variables are
+ (might be) live at the end of each basic block. */
+
+vec<bitmap_head>
+compute_live_vars (struct function *fn, live_vars_map *vars)
+{
+ vec<bitmap_head> active;
+ /* 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. */
+
+ active.create (last_basic_block_for_fn (fn));
+ active.quick_grow (last_basic_block_for_fn (fn));
+ for (int i = 0; i < last_basic_block_for_fn (fn); i++)
+ bitmap_initialize (&active[i], &bitmap_default_obstack);
+
+ bitmap work = BITMAP_ALLOC (NULL);
+
+ int *rpo = XNEWVEC (int, last_basic_block_for_fn (fn));
+ int n_bbs = pre_and_rev_post_order_compute_fn (fn, NULL, rpo, false);
+
+ bool changed = true;
+ compute_live_vars_data data = { active, work, vars };
+ while (changed)
+ {
+ int i;
+ changed = false;
+ for (i = 0; i < n_bbs; i++)
+ {
+ basic_block bb = BASIC_BLOCK_FOR_FN (fn, rpo[i]);
+ compute_live_vars_1 (bb, &data, NULL);
+ if (bitmap_ior_into (&active[bb->index], work))
+ changed = true;
+ }
+ }
+
+ free (rpo);
+ BITMAP_FREE (work);
+
+ return active;
+}
+
+/* For ACTIVE computed by compute_live_vars, compute a bitmap of variables
+ live after the STOP_AFTER statement and return that bitmap. */
+
+bitmap
+live_vars_at_stmt (vec<bitmap_head> &active, live_vars_map *vars,
+ gimple *stop_after)
+{
+ bitmap work = BITMAP_ALLOC (NULL);
+ compute_live_vars_data data = { active, work, vars };
+ basic_block bb = gimple_bb (stop_after);
+ compute_live_vars_1 (bb, &data, stop_after);
+ return work;
+}
+
+/* Destroy what compute_live_vars has returned when it is no longer needed. */
+
+void
+destroy_live_vars (vec<bitmap_head> &active)
+{
+ unsigned len = active.length ();
+ for (unsigned i = 0; i < len; i++)
+ bitmap_clear (&active[i]);
+
+ active.release ();
+}
+\f
/* Output partition map MAP to file F. */
void
extern void debug (tree_live_info_d *ptr);
extern void dump_live_info (FILE *, tree_live_info_p, int);
+typedef hash_map<int_hash <unsigned int, -1U>, unsigned int> live_vars_map;
+extern vec<bitmap_head> compute_live_vars (struct function *, live_vars_map *);
+extern bitmap live_vars_at_stmt (vec<bitmap_head> &, live_vars_map *,
+ gimple *);
+extern void destroy_live_vars (vec<bitmap_head> &);
/* Return TRUE if P is marked as a global in LIVE. */
#include "cfgloop.h"
#include "common/common-target.h"
#include "ipa-utils.h"
+#include "tree-ssa-live.h"
/* The file implements the tail recursion elimination. It is also used to
analyze the tail calls in general, passing the results to the rtl level
return var;
}
+/* Argument for compute_live_vars/live_vars_at_stmt and what compute_live_vars
+ returns. Computed lazily, but just once for the function. */
+static live_vars_map *live_vars;
+static vec<bitmap_head> live_vars_vec;
+
/* Finds tailcalls falling into basic block BB. The list of found tailcalls is
added to the start of RET. */
tail_recursion = true;
}
+ /* Compute live vars if not computed yet. */
+ if (live_vars == NULL)
+ {
+ unsigned int cnt = 0;
+ FOR_EACH_LOCAL_DECL (cfun, idx, var)
+ if (VAR_P (var)
+ && auto_var_in_fn_p (var, cfun->decl)
+ && may_be_aliased (var))
+ {
+ if (live_vars == NULL)
+ live_vars = new live_vars_map;
+ live_vars->put (DECL_UID (var), cnt++);
+ }
+ if (live_vars)
+ live_vars_vec = compute_live_vars (cfun, live_vars);
+ }
+
+ /* Determine a bitmap of variables which are still in scope after the
+ call. */
+ bitmap local_live_vars = NULL;
+ if (live_vars)
+ local_live_vars = live_vars_at_stmt (live_vars_vec, live_vars, call);
+
/* Make sure the tail invocation of this function does not indirectly
refer to local variables. (Passing variables directly by value
is OK.) */
&& may_be_aliased (var)
&& (ref_maybe_used_by_stmt_p (call, var)
|| call_may_clobber_ref_p (call, var)))
- return;
+ {
+ if (!VAR_P (var))
+ {
+ if (local_live_vars)
+ BITMAP_FREE (local_live_vars);
+ return;
+ }
+ else
+ {
+ unsigned int *v = live_vars->get (DECL_UID (var));
+ if (bitmap_bit_p (local_live_vars, *v))
+ {
+ BITMAP_FREE (local_live_vars);
+ return;
+ }
+ }
+ }
}
+ if (local_live_vars)
+ BITMAP_FREE (local_live_vars);
+
/* Now check the statements after the call. None of them has virtual
operands, so they may only depend on the call through its return
value. The return value should also be dependent on each of them,
find_tail_calls (e->src, &tailcalls);
}
+ if (live_vars)
+ {
+ destroy_live_vars (live_vars_vec);
+ delete live_vars;
+ live_vars = NULL;
+ }
+
/* Construct the phi nodes and accumulators if necessary. */
a_acc = m_acc = NULL_TREE;
for (act = tailcalls; act; act = act->next)