summaryrefslogtreecommitdiff
path: root/gcc/tree-cfg.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/tree-cfg.c')
-rw-r--r--gcc/tree-cfg.c79
1 files changed, 77 insertions, 2 deletions
diff --git a/gcc/tree-cfg.c b/gcc/tree-cfg.c
index 205b5c0e82e..7622bf77190 100644
--- a/gcc/tree-cfg.c
+++ b/gcc/tree-cfg.c
@@ -129,6 +129,7 @@ static bool tree_can_merge_blocks_p (basic_block, basic_block);
static void remove_bb (basic_block);
static bool cleanup_control_flow (void);
static bool cleanup_control_expr_graph (basic_block, block_stmt_iterator);
+static edge find_taken_edge_computed_goto (basic_block, tree);
static edge find_taken_edge_cond_expr (basic_block, tree);
static edge find_taken_edge_switch_expr (basic_block, tree);
static tree find_case_label_for_value (tree, tree);
@@ -1301,7 +1302,22 @@ tree_merge_blocks (basic_block a, basic_block b)
for (bsi = bsi_start (b); !bsi_end_p (bsi);)
{
if (TREE_CODE (bsi_stmt (bsi)) == LABEL_EXPR)
- bsi_remove (&bsi);
+ {
+ tree label = bsi_stmt (bsi);
+
+ bsi_remove (&bsi);
+ /* Now that we can thread computed gotos, we might have
+ a situation where we have a forced label in block B
+ However, the label at the start of block B might still be
+ used in other ways (think about the runtime checking for
+ Fortran assigned gotos). So we can not just delete the
+ label. Instead we move the label to the start of block A. */
+ if (FORCED_LABEL (LABEL_EXPR_LABEL (label)))
+ {
+ block_stmt_iterator dest_bsi = bsi_start (a);
+ bsi_insert_before (&dest_bsi, label, BSI_NEW_STMT);
+ }
+ }
else
{
set_bb_for_stmt (bsi_stmt (bsi), a);
@@ -2122,6 +2138,43 @@ cleanup_control_flow (void)
|| TREE_CODE (stmt) == SWITCH_EXPR)
retval |= cleanup_control_expr_graph (bb, bsi);
+ /* If we had a computed goto which has a compile-time determinable
+ destination, then we can eliminate the goto. */
+ if (TREE_CODE (stmt) == GOTO_EXPR
+ && TREE_CODE (GOTO_DESTINATION (stmt)) == ADDR_EXPR
+ && TREE_CODE (TREE_OPERAND (GOTO_DESTINATION (stmt), 0)) == LABEL_DECL)
+ {
+ edge e;
+ tree label;
+ edge_iterator ei;
+ basic_block target_block;
+
+ /* First look at all the outgoing edges. Delete any outgoing
+ edges which do not go to the right block. For the one
+ edge which goes to the right block, fix up its flags. */
+ label = TREE_OPERAND (GOTO_DESTINATION (stmt), 0);
+ target_block = label_to_block (label);
+ for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); )
+ {
+ if (e->dest != target_block)
+ remove_edge (e);
+ else
+ {
+ /* Turn off the EDGE_ABNORMAL flag. */
+ EDGE_SUCC (bb, 0)->flags &= ~EDGE_ABNORMAL;
+
+ /* And set EDGE_FALLTHRU. */
+ EDGE_SUCC (bb, 0)->flags |= EDGE_FALLTHRU;
+ ei_next (&ei);
+ }
+ }
+
+ /* Remove the GOTO_EXPR as it is not needed. The CFG has all the
+ relevant information we need. */
+ bsi_remove (&bsi);
+ retval = true;
+ }
+
/* Check for indirect calls that have been turned into
noreturn calls. */
if (noreturn_call_p (stmt) && remove_fallthru_edge (bb->succs))
@@ -2229,7 +2282,7 @@ find_taken_edge (basic_block bb, tree val)
gcc_assert (is_ctrl_stmt (stmt));
gcc_assert (val);
- if (TREE_CODE (val) != INTEGER_CST)
+ if (! is_gimple_min_invariant (val))
return NULL;
if (TREE_CODE (stmt) == COND_EXPR)
@@ -2238,9 +2291,31 @@ find_taken_edge (basic_block bb, tree val)
if (TREE_CODE (stmt) == SWITCH_EXPR)
return find_taken_edge_switch_expr (bb, val);
+ if (computed_goto_p (stmt))
+ return find_taken_edge_computed_goto (bb, TREE_OPERAND( val, 0));
+
gcc_unreachable ();
}
+/* Given a constant value VAL and the entry block BB to a GOTO_EXPR
+ statement, determine which of the outgoing edges will be taken out of the
+ block. Return NULL if either edge may be taken. */
+
+static edge
+find_taken_edge_computed_goto (basic_block bb, tree val)
+{
+ basic_block dest;
+ edge e = NULL;
+
+ dest = label_to_block (val);
+ if (dest)
+ {
+ e = find_edge (bb, dest);
+ gcc_assert (e != NULL);
+ }
+
+ return e;
+}
/* Given a constant value VAL and the entry block BB to a COND_EXPR
statement, determine which of the two edges will be taken out of the