57d1b68d65
gcc/ChangeLog: PR tree-optimization/99002 PR tree-optimization/99026 * gimple-if-to-switch.cc (if_chain::is_beneficial): Fix memory leak when adjacent cases are merged. * tree-switch-conversion.c (switch_decision_tree::analyze_switch_statement): Use release_clusters. (make_pass_lower_switch): Remove trailing whitespace. * tree-switch-conversion.h (release_clusters): New.
588 lines
16 KiB
C++
588 lines
16 KiB
C++
/* If-elseif-else to switch conversion pass
|
|
Copyright (C) 2020-2021 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/>. */
|
|
|
|
/* Algorithm of the pass runs in the following steps:
|
|
a) We walk basic blocks in DOMINATOR order so that we first reach
|
|
a first condition of a future switch.
|
|
b) We follow false edges of a if-else-chain and we record chain
|
|
of GIMPLE conditions. These blocks are only used for comparison
|
|
of a common SSA_NAME and we do not allow any side effect.
|
|
c) We remove all basic blocks (except first) of such chain and
|
|
GIMPLE switch replaces the condition in the first basic block.
|
|
d) We move all GIMPLE statements in the removed blocks into the
|
|
first one. */
|
|
|
|
#include "config.h"
|
|
#include "system.h"
|
|
#include "coretypes.h"
|
|
#include "backend.h"
|
|
#include "rtl.h"
|
|
#include "tree.h"
|
|
#include "gimple.h"
|
|
#include "tree-pass.h"
|
|
#include "ssa.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "fold-const.h"
|
|
#include "gimple-iterator.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-cfgcleanup.h"
|
|
#include "alias.h"
|
|
#include "tree-ssa-loop.h"
|
|
#include "diagnostic.h"
|
|
#include "cfghooks.h"
|
|
#include "tree-into-ssa.h"
|
|
#include "cfganal.h"
|
|
#include "dbgcnt.h"
|
|
#include "target.h"
|
|
#include "alloc-pool.h"
|
|
#include "tree-switch-conversion.h"
|
|
#include "tree-ssa-reassoc.h"
|
|
|
|
using namespace tree_switch_conversion;
|
|
|
|
struct condition_info
|
|
{
|
|
typedef auto_vec<std::pair<gphi *, tree>> mapping_vec;
|
|
|
|
condition_info (gcond *cond): m_cond (cond), m_bb (gimple_bb (cond)),
|
|
m_forwarder_bb (NULL), m_ranges (), m_true_edge (NULL), m_false_edge (NULL),
|
|
m_true_edge_phi_mapping (), m_false_edge_phi_mapping ()
|
|
{
|
|
m_ranges.create (0);
|
|
}
|
|
|
|
/* Recond PHI mapping for an original edge E and save these into
|
|
vector VEC. */
|
|
void record_phi_mapping (edge e, mapping_vec *vec);
|
|
|
|
gcond *m_cond;
|
|
basic_block m_bb;
|
|
basic_block m_forwarder_bb;
|
|
auto_vec<range_entry> m_ranges;
|
|
edge m_true_edge;
|
|
edge m_false_edge;
|
|
mapping_vec m_true_edge_phi_mapping;
|
|
mapping_vec m_false_edge_phi_mapping;
|
|
};
|
|
|
|
/* Recond PHI mapping for an original edge E and save these into vector VEC. */
|
|
|
|
void
|
|
condition_info::record_phi_mapping (edge e, mapping_vec *vec)
|
|
{
|
|
for (gphi_iterator gsi = gsi_start_phis (e->dest); !gsi_end_p (gsi);
|
|
gsi_next (&gsi))
|
|
{
|
|
gphi *phi = gsi.phi ();
|
|
tree arg = PHI_ARG_DEF_FROM_EDGE (phi, e);
|
|
vec->safe_push (std::make_pair (phi, arg));
|
|
}
|
|
}
|
|
|
|
/* Master structure for one if to switch conversion candidate. */
|
|
|
|
struct if_chain
|
|
{
|
|
/* Default constructor. */
|
|
if_chain (): m_entries ()
|
|
{
|
|
m_entries.create (2);
|
|
}
|
|
|
|
/* Default destructor. */
|
|
~if_chain ()
|
|
{
|
|
m_entries.release ();
|
|
}
|
|
|
|
/* Verify that all case ranges do not overlap. */
|
|
bool check_non_overlapping_cases ();
|
|
|
|
/* Return true when the switch can be expanded with a jump table or
|
|
a bit test (at least partially). */
|
|
bool is_beneficial ();
|
|
|
|
/* If chain entries. */
|
|
vec<condition_info *> m_entries;
|
|
};
|
|
|
|
/* Compare two case ranges by minimum value. */
|
|
|
|
static int
|
|
range_cmp (const void *a, const void *b)
|
|
{
|
|
const range_entry *re1 = *(const range_entry * const *) a;
|
|
const range_entry *re2 = *(const range_entry * const *) b;
|
|
|
|
return tree_int_cst_compare (re1->low, re2->low);
|
|
}
|
|
|
|
/* Verify that all case ranges do not overlap. */
|
|
|
|
bool
|
|
if_chain::check_non_overlapping_cases ()
|
|
{
|
|
auto_vec<range_entry *> all_ranges;
|
|
for (unsigned i = 0; i < m_entries.length (); i++)
|
|
for (unsigned j = 0; j < m_entries[i]->m_ranges.length (); j++)
|
|
all_ranges.safe_push (&m_entries[i]->m_ranges[j]);
|
|
|
|
all_ranges.qsort (range_cmp);
|
|
|
|
for (unsigned i = 0; i < all_ranges.length () - 1; i++)
|
|
{
|
|
range_entry *left = all_ranges[i];
|
|
range_entry *right = all_ranges[i + 1];
|
|
if (tree_int_cst_le (left->low, right->low)
|
|
&& tree_int_cst_le (right->low, left->high))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Compare clusters by minimum value. */
|
|
|
|
static int
|
|
cluster_cmp (const void *a, const void *b)
|
|
{
|
|
simple_cluster *sc1 = *(simple_cluster * const *) a;
|
|
simple_cluster *sc2 = *(simple_cluster * const *) b;
|
|
|
|
return tree_int_cst_compare (sc1->get_low (), sc2->get_high ());
|
|
}
|
|
|
|
/* Dump constructed CLUSTERS with prefix MESSAGE. */
|
|
|
|
static void
|
|
dump_clusters (vec<cluster *> *clusters, const char *message)
|
|
{
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, ";; %s: ", message);
|
|
for (unsigned i = 0; i < clusters->length (); i++)
|
|
(*clusters)[i]->dump (dump_file, dump_flags & TDF_DETAILS);
|
|
fprintf (dump_file, "\n");
|
|
}
|
|
}
|
|
|
|
/* Return true when the switch can be expanded with a jump table or
|
|
a bit test (at least partially). */
|
|
|
|
bool
|
|
if_chain::is_beneficial ()
|
|
{
|
|
profile_probability prob = profile_probability::uninitialized ();
|
|
|
|
auto_vec<cluster *> clusters;
|
|
clusters.create (m_entries.length ());
|
|
|
|
for (unsigned i = 0; i < m_entries.length (); i++)
|
|
{
|
|
condition_info *info = m_entries[i];
|
|
for (unsigned j = 0; j < info->m_ranges.length (); j++)
|
|
{
|
|
range_entry *range = &info->m_ranges[j];
|
|
basic_block bb = info->m_true_edge->dest;
|
|
bool has_forwarder = !info->m_true_edge_phi_mapping.is_empty ();
|
|
clusters.safe_push (new simple_cluster (range->low, range->high,
|
|
NULL_TREE, bb, prob,
|
|
has_forwarder));
|
|
}
|
|
}
|
|
|
|
/* Sort clusters and merge them. */
|
|
auto_vec<cluster *> filtered_clusters;
|
|
filtered_clusters.create (16);
|
|
clusters.qsort (cluster_cmp);
|
|
simple_cluster *left = static_cast<simple_cluster *> (clusters[0]);
|
|
filtered_clusters.safe_push (left);
|
|
|
|
for (unsigned i = 1; i < clusters.length (); i++)
|
|
{
|
|
simple_cluster *right = static_cast<simple_cluster *> (clusters[i]);
|
|
tree type = TREE_TYPE (left->get_low ());
|
|
if (!left->m_has_forward_bb
|
|
&& !right->m_has_forward_bb
|
|
&& left->m_case_bb == right->m_case_bb)
|
|
{
|
|
if (wi::eq_p (wi::to_wide (right->get_low ()) - wi::to_wide
|
|
(left->get_high ()), wi::one (TYPE_PRECISION (type))))
|
|
{
|
|
left->set_high (right->get_high ());
|
|
delete right;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
left = static_cast<simple_cluster *> (clusters[i]);
|
|
filtered_clusters.safe_push (left);
|
|
}
|
|
|
|
dump_clusters (&filtered_clusters, "Canonical GIMPLE case clusters");
|
|
|
|
vec<cluster *> output
|
|
= jump_table_cluster::find_jump_tables (filtered_clusters);
|
|
bool r = output.length () < filtered_clusters.length ();
|
|
if (r)
|
|
{
|
|
dump_clusters (&output, "JT can be built");
|
|
release_clusters (output);
|
|
return true;
|
|
}
|
|
else
|
|
output.release ();
|
|
|
|
output = bit_test_cluster::find_bit_tests (filtered_clusters);
|
|
r = output.length () < filtered_clusters.length ();
|
|
if (r)
|
|
dump_clusters (&output, "BT can be built");
|
|
|
|
release_clusters (output);
|
|
return r;
|
|
}
|
|
|
|
/* Build case label with MIN and MAX values of a given basic block DEST. */
|
|
|
|
static tree
|
|
build_case_label (tree index_type, tree min, tree max, basic_block dest)
|
|
{
|
|
if (min != NULL_TREE && index_type != TREE_TYPE (min))
|
|
min = fold_convert (index_type, min);
|
|
if (max != NULL_TREE && index_type != TREE_TYPE (max))
|
|
max = fold_convert (index_type, max);
|
|
|
|
tree label = gimple_block_label (dest);
|
|
return build_case_label (min, min == max ? NULL_TREE : max, label);
|
|
}
|
|
|
|
/* Compare two integer constants. */
|
|
|
|
static int
|
|
label_cmp (const void *a, const void *b)
|
|
{
|
|
const_tree l1 = *(const const_tree *) a;
|
|
const_tree l2 = *(const const_tree *) b;
|
|
|
|
return tree_int_cst_compare (CASE_LOW (l1), CASE_LOW (l2));
|
|
}
|
|
|
|
/* Convert a given if CHAIN into a switch GIMPLE statement. */
|
|
|
|
static void
|
|
convert_if_conditions_to_switch (if_chain *chain)
|
|
{
|
|
if (!dbg_cnt (if_to_switch))
|
|
return;
|
|
|
|
auto_vec<tree> labels;
|
|
unsigned entries = chain->m_entries.length ();
|
|
condition_info *first_cond = chain->m_entries[0];
|
|
condition_info *last_cond = chain->m_entries[entries - 1];
|
|
|
|
edge default_edge = last_cond->m_false_edge;
|
|
basic_block default_bb = default_edge->dest;
|
|
|
|
gimple_stmt_iterator gsi = gsi_for_stmt (first_cond->m_cond);
|
|
tree index_type = TREE_TYPE (first_cond->m_ranges[0].exp);
|
|
for (unsigned i = 0; i < entries; i++)
|
|
{
|
|
condition_info *info = chain->m_entries[i];
|
|
basic_block case_bb = info->m_true_edge->dest;
|
|
|
|
/* Create a forwarder block if needed. */
|
|
if (!info->m_true_edge_phi_mapping.is_empty ())
|
|
{
|
|
info->m_forwarder_bb = split_edge (info->m_true_edge);
|
|
case_bb = info->m_forwarder_bb;
|
|
}
|
|
|
|
for (unsigned j = 0; j < info->m_ranges.length (); j++)
|
|
labels.safe_push (build_case_label (index_type,
|
|
info->m_ranges[j].low,
|
|
info->m_ranges[j].high,
|
|
case_bb));
|
|
default_bb = info->m_false_edge->dest;
|
|
|
|
if (i == 0)
|
|
{
|
|
remove_edge (first_cond->m_true_edge);
|
|
remove_edge (first_cond->m_false_edge);
|
|
}
|
|
else
|
|
delete_basic_block (info->m_bb);
|
|
|
|
make_edge (first_cond->m_bb, case_bb, 0);
|
|
}
|
|
|
|
labels.qsort (label_cmp);
|
|
|
|
edge e = find_edge (first_cond->m_bb, default_bb);
|
|
if (e == NULL)
|
|
e = make_edge (first_cond->m_bb, default_bb, 0);
|
|
gswitch *s
|
|
= gimple_build_switch (first_cond->m_ranges[0].exp,
|
|
build_case_label (index_type, NULL_TREE,
|
|
NULL_TREE, default_bb),
|
|
labels);
|
|
|
|
gsi_remove (&gsi, true);
|
|
gsi_insert_before (&gsi, s, GSI_NEW_STMT);
|
|
|
|
if (dump_file)
|
|
{
|
|
fprintf (dump_file, "Expanded into a new gimple STMT: ");
|
|
print_gimple_stmt (dump_file, s, 0, TDF_SLIM);
|
|
putc ('\n', dump_file);
|
|
}
|
|
|
|
/* Fill up missing PHI node arguments. */
|
|
for (unsigned i = 0; i < chain->m_entries.length (); ++i)
|
|
{
|
|
condition_info *info = chain->m_entries[i];
|
|
for (unsigned j = 0; j < info->m_true_edge_phi_mapping.length (); ++j)
|
|
{
|
|
std::pair<gphi *, tree> item = info->m_true_edge_phi_mapping[j];
|
|
add_phi_arg (item.first, item.second,
|
|
single_succ_edge (info->m_forwarder_bb),
|
|
UNKNOWN_LOCATION);
|
|
}
|
|
}
|
|
|
|
/* Fill up missing PHI nodes for the default BB. */
|
|
for (unsigned j = 0; j < last_cond->m_false_edge_phi_mapping.length (); ++j)
|
|
{
|
|
std::pair<gphi *, tree> item = last_cond->m_false_edge_phi_mapping[j];
|
|
add_phi_arg (item.first, item.second, e, UNKNOWN_LOCATION);
|
|
}
|
|
}
|
|
|
|
/* Identify an index variable used in BB in a GIMPLE condition.
|
|
Save information about the condition into CONDITIONS_IN_BBS. */
|
|
|
|
static void
|
|
find_conditions (basic_block bb,
|
|
hash_map<basic_block, condition_info *> *conditions_in_bbs)
|
|
{
|
|
gimple_stmt_iterator gsi = gsi_last_nondebug_bb (bb);
|
|
if (gsi_end_p (gsi))
|
|
return;
|
|
|
|
gcond *cond = dyn_cast<gcond *> (gsi_stmt (gsi));
|
|
if (cond == NULL)
|
|
return;
|
|
|
|
if (!no_side_effect_bb (bb))
|
|
return;
|
|
|
|
tree lhs = gimple_cond_lhs (cond);
|
|
tree rhs = gimple_cond_rhs (cond);
|
|
tree_code code = gimple_cond_code (cond);
|
|
|
|
condition_info *info = new condition_info (cond);
|
|
|
|
gassign *def;
|
|
if (code == NE_EXPR
|
|
&& TREE_CODE (lhs) == SSA_NAME
|
|
&& (def = dyn_cast<gassign *> (SSA_NAME_DEF_STMT (lhs))) != NULL
|
|
&& integer_zerop (rhs))
|
|
{
|
|
enum tree_code rhs_code = gimple_assign_rhs_code (def);
|
|
if (rhs_code == BIT_IOR_EXPR)
|
|
{
|
|
info->m_ranges.safe_grow (2, true);
|
|
init_range_entry (&info->m_ranges[0], gimple_assign_rhs1 (def), NULL);
|
|
init_range_entry (&info->m_ranges[1], gimple_assign_rhs2 (def), NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
info->m_ranges.safe_grow (1, true);
|
|
init_range_entry (&info->m_ranges[0], NULL_TREE, cond);
|
|
}
|
|
|
|
/* All identified ranges must have equal expression and IN_P flag. */
|
|
if (!info->m_ranges.is_empty ())
|
|
{
|
|
edge true_edge, false_edge;
|
|
tree expr = info->m_ranges[0].exp;
|
|
bool in_p = info->m_ranges[0].in_p;
|
|
|
|
extract_true_false_edges_from_block (bb, &true_edge, &false_edge);
|
|
info->m_true_edge = in_p ? true_edge : false_edge;
|
|
info->m_false_edge = in_p ? false_edge : true_edge;
|
|
|
|
for (unsigned i = 0; i < info->m_ranges.length (); ++i)
|
|
if (info->m_ranges[i].exp == NULL_TREE
|
|
|| !INTEGRAL_TYPE_P (TREE_TYPE (info->m_ranges[i].exp))
|
|
|| info->m_ranges[i].low == NULL_TREE
|
|
|| info->m_ranges[i].high == NULL_TREE
|
|
|| (TYPE_PRECISION (TREE_TYPE (info->m_ranges[i].low))
|
|
!= TYPE_PRECISION (TREE_TYPE (info->m_ranges[i].high))))
|
|
goto exit;
|
|
|
|
for (unsigned i = 1; i < info->m_ranges.length (); ++i)
|
|
if (info->m_ranges[i].exp != expr
|
|
|| info->m_ranges[i].in_p != in_p)
|
|
goto exit;
|
|
|
|
info->record_phi_mapping (info->m_true_edge,
|
|
&info->m_true_edge_phi_mapping);
|
|
info->record_phi_mapping (info->m_false_edge,
|
|
&info->m_false_edge_phi_mapping);
|
|
conditions_in_bbs->put (bb, info);
|
|
return;
|
|
}
|
|
|
|
exit:
|
|
delete info;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_if_to_switch =
|
|
{
|
|
GIMPLE_PASS, /* type */
|
|
"iftoswitch", /* name */
|
|
OPTGROUP_NONE, /* optinfo_flags */
|
|
TV_TREE_IF_TO_SWITCH, /* tv_id */
|
|
( PROP_cfg | PROP_ssa ), /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* todo_flags_start */
|
|
TODO_update_ssa /* todo_flags_finish */
|
|
};
|
|
|
|
class pass_if_to_switch : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_if_to_switch (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_if_to_switch, ctxt)
|
|
{}
|
|
|
|
/* opt_pass methods: */
|
|
virtual bool gate (function *)
|
|
{
|
|
return (jump_table_cluster::is_enabled ()
|
|
|| bit_test_cluster::is_enabled ());
|
|
}
|
|
|
|
virtual unsigned int execute (function *);
|
|
|
|
}; // class pass_if_to_switch
|
|
|
|
unsigned int
|
|
pass_if_to_switch::execute (function *fun)
|
|
{
|
|
auto_vec<if_chain *> all_candidates;
|
|
hash_map<basic_block, condition_info *> conditions_in_bbs;
|
|
|
|
basic_block bb;
|
|
FOR_EACH_BB_FN (bb, fun)
|
|
find_conditions (bb, &conditions_in_bbs);
|
|
|
|
if (conditions_in_bbs.is_empty ())
|
|
return 0;
|
|
|
|
int *rpo = XNEWVEC (int, n_basic_blocks_for_fn (fun));
|
|
unsigned n = pre_and_rev_post_order_compute_fn (fun, NULL, rpo, false);
|
|
|
|
auto_bitmap seen_bbs;
|
|
for (int i = n - 1; i >= 0; --i)
|
|
{
|
|
basic_block bb = BASIC_BLOCK_FOR_FN (fun, rpo[i]);
|
|
if (bitmap_bit_p (seen_bbs, bb->index))
|
|
continue;
|
|
|
|
bitmap_set_bit (seen_bbs, bb->index);
|
|
condition_info **slot = conditions_in_bbs.get (bb);
|
|
if (slot)
|
|
{
|
|
condition_info *info = *slot;
|
|
if_chain *chain = new if_chain ();
|
|
chain->m_entries.safe_push (info);
|
|
/* Try to find a chain starting in this BB. */
|
|
while (true)
|
|
{
|
|
if (!single_pred_p (gimple_bb (info->m_cond)))
|
|
break;
|
|
edge e = single_pred_edge (gimple_bb (info->m_cond));
|
|
condition_info **info2 = conditions_in_bbs.get (e->src);
|
|
if (!info2 || info->m_ranges[0].exp != (*info2)->m_ranges[0].exp)
|
|
break;
|
|
|
|
/* It is important that the blocks are linked through FALSE_EDGE.
|
|
For an expression of index != VALUE, true and false edges
|
|
are flipped. */
|
|
if ((*info2)->m_false_edge != e)
|
|
break;
|
|
|
|
chain->m_entries.safe_push (*info2);
|
|
bitmap_set_bit (seen_bbs, e->src->index);
|
|
info = *info2;
|
|
}
|
|
|
|
chain->m_entries.reverse ();
|
|
if (chain->m_entries.length () >= 2
|
|
&& chain->check_non_overlapping_cases ()
|
|
&& chain->is_beneficial ())
|
|
{
|
|
gcond *cond = chain->m_entries[0]->m_cond;
|
|
if (dump_enabled_p ())
|
|
dump_printf_loc (MSG_OPTIMIZED_LOCATIONS, cond,
|
|
"Condition chain with %d BBs "
|
|
"transformed into a switch statement.\n",
|
|
chain->m_entries.length ());
|
|
all_candidates.safe_push (chain);
|
|
}
|
|
else
|
|
delete chain;
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < all_candidates.length (); i++)
|
|
{
|
|
convert_if_conditions_to_switch (all_candidates[i]);
|
|
delete all_candidates[i];
|
|
}
|
|
|
|
free (rpo);
|
|
|
|
for (hash_map<basic_block, condition_info *>::iterator it
|
|
= conditions_in_bbs.begin (); it != conditions_in_bbs.end (); ++it)
|
|
delete (*it).second;
|
|
|
|
if (!all_candidates.is_empty ())
|
|
{
|
|
free_dominance_info (CDI_DOMINATORS);
|
|
return TODO_cleanup_cfg;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
} // anon namespace
|
|
|
|
gimple_opt_pass *
|
|
make_pass_if_to_switch (gcc::context *ctxt)
|
|
{
|
|
return new pass_if_to_switch (ctxt);
|
|
}
|