| /* Control flow functions for trees. |
| Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 |
| Free Software Foundation, Inc. |
| Contributed by Diego Novillo <dnovillo@redhat.com> |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, 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 COPYING. If not, write to |
| the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
| Boston, MA 02110-1301, USA. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "tree.h" |
| #include "rtl.h" |
| #include "tm_p.h" |
| #include "hard-reg-set.h" |
| #include "basic-block.h" |
| #include "output.h" |
| #include "flags.h" |
| #include "function.h" |
| #include "expr.h" |
| #include "ggc.h" |
| #include "langhooks.h" |
| #include "diagnostic.h" |
| #include "tree-flow.h" |
| #include "timevar.h" |
| #include "tree-dump.h" |
| #include "tree-pass.h" |
| #include "toplev.h" |
| #include "except.h" |
| #include "cfgloop.h" |
| #include "cfglayout.h" |
| #include "hashtab.h" |
| #include "tree-ssa-propagate.h" |
| |
| /* This file contains functions for building the Control Flow Graph (CFG) |
| for a function tree. */ |
| |
| /* Local declarations. */ |
| |
| /* Initial capacity for the basic block array. */ |
| static const int initial_cfg_capacity = 20; |
| |
| /* This hash table allows us to efficiently lookup all CASE_LABEL_EXPRs |
| which use a particular edge. The CASE_LABEL_EXPRs are chained together |
| via their TREE_CHAIN field, which we clear after we're done with the |
| hash table to prevent problems with duplication of SWITCH_EXPRs. |
| |
| Access to this list of CASE_LABEL_EXPRs allows us to efficiently |
| update the case vector in response to edge redirections. |
| |
| Right now this table is set up and torn down at key points in the |
| compilation process. It would be nice if we could make the table |
| more persistent. The key is getting notification of changes to |
| the CFG (particularly edge removal, creation and redirection). */ |
| |
| struct edge_to_cases_elt |
| { |
| /* The edge itself. Necessary for hashing and equality tests. */ |
| edge e; |
| |
| /* The case labels associated with this edge. We link these up via |
| their TREE_CHAIN field, then we wipe out the TREE_CHAIN fields |
| when we destroy the hash table. This prevents problems when copying |
| SWITCH_EXPRs. */ |
| tree case_labels; |
| }; |
| |
| static htab_t edge_to_cases; |
| |
| /* CFG statistics. */ |
| struct cfg_stats_d |
| { |
| long num_merged_labels; |
| }; |
| |
| static struct cfg_stats_d cfg_stats; |
| |
| /* Nonzero if we found a computed goto while building basic blocks. */ |
| static bool found_computed_goto; |
| |
| /* Basic blocks and flowgraphs. */ |
| static basic_block create_bb (void *, void *, basic_block); |
| static void make_blocks (tree); |
| static void factor_computed_gotos (void); |
| |
| /* Edges. */ |
| static void make_edges (void); |
| static void make_cond_expr_edges (basic_block); |
| static void make_switch_expr_edges (basic_block); |
| static void make_goto_expr_edges (basic_block); |
| static edge tree_redirect_edge_and_branch (edge, basic_block); |
| static edge tree_try_redirect_by_replacing_jump (edge, basic_block); |
| static unsigned int split_critical_edges (void); |
| |
| /* Various helpers. */ |
| static inline bool stmt_starts_bb_p (tree, tree); |
| static int tree_verify_flow_info (void); |
| static void tree_make_forwarder_block (edge); |
| static void tree_cfg2vcg (FILE *); |
| static inline void change_bb_for_stmt (tree t, basic_block bb); |
| |
| /* Flowgraph optimization and cleanup. */ |
| static void tree_merge_blocks (basic_block, basic_block); |
| static bool tree_can_merge_blocks_p (basic_block, basic_block); |
| static void remove_bb (basic_block); |
| 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); |
| |
| void |
| init_empty_tree_cfg (void) |
| { |
| /* Initialize the basic block array. */ |
| init_flow (); |
| profile_status = PROFILE_ABSENT; |
| n_basic_blocks = NUM_FIXED_BLOCKS; |
| last_basic_block = NUM_FIXED_BLOCKS; |
| basic_block_info = VEC_alloc (basic_block, gc, initial_cfg_capacity); |
| VEC_safe_grow (basic_block, gc, basic_block_info, initial_cfg_capacity); |
| memset (VEC_address (basic_block, basic_block_info), 0, |
| sizeof (basic_block) * initial_cfg_capacity); |
| |
| /* Build a mapping of labels to their associated blocks. */ |
| label_to_block_map = VEC_alloc (basic_block, gc, initial_cfg_capacity); |
| VEC_safe_grow (basic_block, gc, label_to_block_map, initial_cfg_capacity); |
| memset (VEC_address (basic_block, label_to_block_map), |
| 0, sizeof (basic_block) * initial_cfg_capacity); |
| |
| SET_BASIC_BLOCK (ENTRY_BLOCK, ENTRY_BLOCK_PTR); |
| SET_BASIC_BLOCK (EXIT_BLOCK, EXIT_BLOCK_PTR); |
| ENTRY_BLOCK_PTR->next_bb = EXIT_BLOCK_PTR; |
| EXIT_BLOCK_PTR->prev_bb = ENTRY_BLOCK_PTR; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| Create basic blocks |
| ---------------------------------------------------------------------------*/ |
| |
| /* Entry point to the CFG builder for trees. TP points to the list of |
| statements to be added to the flowgraph. */ |
| |
| static void |
| build_tree_cfg (tree *tp) |
| { |
| /* Register specific tree functions. */ |
| tree_register_cfg_hooks (); |
| |
| memset ((void *) &cfg_stats, 0, sizeof (cfg_stats)); |
| |
| init_empty_tree_cfg (); |
| |
| found_computed_goto = 0; |
| make_blocks (*tp); |
| |
| /* Computed gotos are hell to deal with, especially if there are |
| lots of them with a large number of destinations. So we factor |
| them to a common computed goto location before we build the |
| edge list. After we convert back to normal form, we will un-factor |
| the computed gotos since factoring introduces an unwanted jump. */ |
| if (found_computed_goto) |
| factor_computed_gotos (); |
| |
| /* Make sure there is always at least one block, even if it's empty. */ |
| if (n_basic_blocks == NUM_FIXED_BLOCKS) |
| create_empty_bb (ENTRY_BLOCK_PTR); |
| |
| /* Adjust the size of the array. */ |
| if (VEC_length (basic_block, basic_block_info) < (size_t) n_basic_blocks) |
| { |
| size_t old_size = VEC_length (basic_block, basic_block_info); |
| basic_block *p; |
| VEC_safe_grow (basic_block, gc, basic_block_info, n_basic_blocks); |
| p = VEC_address (basic_block, basic_block_info); |
| memset (&p[old_size], 0, |
| sizeof (basic_block) * (n_basic_blocks - old_size)); |
| } |
| |
| /* To speed up statement iterator walks, we first purge dead labels. */ |
| cleanup_dead_labels (); |
| |
| /* Group case nodes to reduce the number of edges. |
| We do this after cleaning up dead labels because otherwise we miss |
| a lot of obvious case merging opportunities. */ |
| group_case_labels (); |
| |
| /* Create the edges of the flowgraph. */ |
| make_edges (); |
| |
| /* Debugging dumps. */ |
| |
| /* Write the flowgraph to a VCG file. */ |
| { |
| int local_dump_flags; |
| FILE *vcg_file = dump_begin (TDI_vcg, &local_dump_flags); |
| if (vcg_file) |
| { |
| tree_cfg2vcg (vcg_file); |
| dump_end (TDI_vcg, vcg_file); |
| } |
| } |
| |
| #ifdef ENABLE_CHECKING |
| verify_stmts (); |
| #endif |
| |
| /* Dump a textual representation of the flowgraph. */ |
| if (dump_file) |
| dump_tree_cfg (dump_file, dump_flags); |
| } |
| |
| static unsigned int |
| execute_build_cfg (void) |
| { |
| build_tree_cfg (&DECL_SAVED_TREE (current_function_decl)); |
| return 0; |
| } |
| |
| struct tree_opt_pass pass_build_cfg = |
| { |
| "cfg", /* name */ |
| NULL, /* gate */ |
| execute_build_cfg, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| TV_TREE_CFG, /* tv_id */ |
| PROP_gimple_leh, /* properties_required */ |
| PROP_cfg, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_verify_stmts, /* todo_flags_finish */ |
| 0 /* letter */ |
| }; |
| |
| /* Search the CFG for any computed gotos. If found, factor them to a |
| common computed goto site. Also record the location of that site so |
| that we can un-factor the gotos after we have converted back to |
| normal form. */ |
| |
| static void |
| factor_computed_gotos (void) |
| { |
| basic_block bb; |
| tree factored_label_decl = NULL; |
| tree var = NULL; |
| tree factored_computed_goto_label = NULL; |
| tree factored_computed_goto = NULL; |
| |
| /* We know there are one or more computed gotos in this function. |
| Examine the last statement in each basic block to see if the block |
| ends with a computed goto. */ |
| |
| FOR_EACH_BB (bb) |
| { |
| block_stmt_iterator bsi = bsi_last (bb); |
| tree last; |
| |
| if (bsi_end_p (bsi)) |
| continue; |
| last = bsi_stmt (bsi); |
| |
| /* Ignore the computed goto we create when we factor the original |
| computed gotos. */ |
| if (last == factored_computed_goto) |
| continue; |
| |
| /* If the last statement is a computed goto, factor it. */ |
| if (computed_goto_p (last)) |
| { |
| tree assignment; |
| |
| /* The first time we find a computed goto we need to create |
| the factored goto block and the variable each original |
| computed goto will use for their goto destination. */ |
| if (! factored_computed_goto) |
| { |
| basic_block new_bb = create_empty_bb (bb); |
| block_stmt_iterator new_bsi = bsi_start (new_bb); |
| |
| /* Create the destination of the factored goto. Each original |
| computed goto will put its desired destination into this |
| variable and jump to the label we create immediately |
| below. */ |
| var = create_tmp_var (ptr_type_node, "gotovar"); |
| |
| /* Build a label for the new block which will contain the |
| factored computed goto. */ |
| factored_label_decl = create_artificial_label (); |
| factored_computed_goto_label |
| = build1 (LABEL_EXPR, void_type_node, factored_label_decl); |
| bsi_insert_after (&new_bsi, factored_computed_goto_label, |
| BSI_NEW_STMT); |
| |
| /* Build our new computed goto. */ |
| factored_computed_goto = build1 (GOTO_EXPR, void_type_node, var); |
| bsi_insert_after (&new_bsi, factored_computed_goto, |
| BSI_NEW_STMT); |
| } |
| |
| /* Copy the original computed goto's destination into VAR. */ |
| assignment = build2 (MODIFY_EXPR, ptr_type_node, |
| var, GOTO_DESTINATION (last)); |
| bsi_insert_before (&bsi, assignment, BSI_SAME_STMT); |
| |
| /* And re-vector the computed goto to the new destination. */ |
| GOTO_DESTINATION (last) = factored_label_decl; |
| } |
| } |
| } |
| |
| |
| /* Build a flowgraph for the statement_list STMT_LIST. */ |
| |
| static void |
| make_blocks (tree stmt_list) |
| { |
| tree_stmt_iterator i = tsi_start (stmt_list); |
| tree stmt = NULL; |
| bool start_new_block = true; |
| bool first_stmt_of_list = true; |
| basic_block bb = ENTRY_BLOCK_PTR; |
| |
| while (!tsi_end_p (i)) |
| { |
| tree prev_stmt; |
| |
| prev_stmt = stmt; |
| stmt = tsi_stmt (i); |
| |
| /* If the statement starts a new basic block or if we have determined |
| in a previous pass that we need to create a new block for STMT, do |
| so now. */ |
| if (start_new_block || stmt_starts_bb_p (stmt, prev_stmt)) |
| { |
| if (!first_stmt_of_list) |
| stmt_list = tsi_split_statement_list_before (&i); |
| bb = create_basic_block (stmt_list, NULL, bb); |
| start_new_block = false; |
| } |
| |
| /* Now add STMT to BB and create the subgraphs for special statement |
| codes. */ |
| set_bb_for_stmt (stmt, bb); |
| |
| if (computed_goto_p (stmt)) |
| found_computed_goto = true; |
| |
| /* If STMT is a basic block terminator, set START_NEW_BLOCK for the |
| next iteration. */ |
| if (stmt_ends_bb_p (stmt)) |
| start_new_block = true; |
| |
| tsi_next (&i); |
| first_stmt_of_list = false; |
| } |
| } |
| |
| |
| /* Create and return a new empty basic block after bb AFTER. */ |
| |
| static basic_block |
| create_bb (void *h, void *e, basic_block after) |
| { |
| basic_block bb; |
| |
| gcc_assert (!e); |
| |
| /* Create and initialize a new basic block. Since alloc_block uses |
| ggc_alloc_cleared to allocate a basic block, we do not have to |
| clear the newly allocated basic block here. */ |
| bb = alloc_block (); |
| |
| bb->index = last_basic_block; |
| bb->flags = BB_NEW; |
| bb->stmt_list = h ? (tree) h : alloc_stmt_list (); |
| |
| /* Add the new block to the linked list of blocks. */ |
| link_block (bb, after); |
| |
| /* Grow the basic block array if needed. */ |
| if ((size_t) last_basic_block == VEC_length (basic_block, basic_block_info)) |
| { |
| size_t old_size = VEC_length (basic_block, basic_block_info); |
| size_t new_size = last_basic_block + (last_basic_block + 3) / 4; |
| basic_block *p; |
| VEC_safe_grow (basic_block, gc, basic_block_info, new_size); |
| p = VEC_address (basic_block, basic_block_info); |
| memset (&p[old_size], 0, sizeof (basic_block) * (new_size - old_size)); |
| } |
| |
| /* Add the newly created block to the array. */ |
| SET_BASIC_BLOCK (last_basic_block, bb); |
| |
| n_basic_blocks++; |
| last_basic_block++; |
| |
| return bb; |
| } |
| |
| |
| /*--------------------------------------------------------------------------- |
| Edge creation |
| ---------------------------------------------------------------------------*/ |
| |
| /* Fold COND_EXPR_COND of each COND_EXPR. */ |
| |
| void |
| fold_cond_expr_cond (void) |
| { |
| basic_block bb; |
| |
| FOR_EACH_BB (bb) |
| { |
| tree stmt = last_stmt (bb); |
| |
| if (stmt |
| && TREE_CODE (stmt) == COND_EXPR) |
| { |
| tree cond; |
| bool zerop, onep; |
| |
| fold_defer_overflow_warnings (); |
| cond = fold (COND_EXPR_COND (stmt)); |
| zerop = integer_zerop (cond); |
| onep = integer_onep (cond); |
| fold_undefer_overflow_warnings (((zerop || onep) |
| && !TREE_NO_WARNING (stmt)), |
| stmt, |
| WARN_STRICT_OVERFLOW_CONDITIONAL); |
| if (zerop) |
| COND_EXPR_COND (stmt) = boolean_false_node; |
| else if (onep) |
| COND_EXPR_COND (stmt) = boolean_true_node; |
| } |
| } |
| } |
| |
| /* Join all the blocks in the flowgraph. */ |
| |
| static void |
| make_edges (void) |
| { |
| basic_block bb; |
| struct omp_region *cur_region = NULL; |
| |
| /* Create an edge from entry to the first block with executable |
| statements in it. */ |
| make_edge (ENTRY_BLOCK_PTR, BASIC_BLOCK (NUM_FIXED_BLOCKS), EDGE_FALLTHRU); |
| |
| /* Traverse the basic block array placing edges. */ |
| FOR_EACH_BB (bb) |
| { |
| tree last = last_stmt (bb); |
| bool fallthru; |
| |
| if (last) |
| { |
| enum tree_code code = TREE_CODE (last); |
| switch (code) |
| { |
| case GOTO_EXPR: |
| make_goto_expr_edges (bb); |
| fallthru = false; |
| break; |
| case RETURN_EXPR: |
| make_edge (bb, EXIT_BLOCK_PTR, 0); |
| fallthru = false; |
| break; |
| case COND_EXPR: |
| make_cond_expr_edges (bb); |
| fallthru = false; |
| break; |
| case SWITCH_EXPR: |
| make_switch_expr_edges (bb); |
| fallthru = false; |
| break; |
| case RESX_EXPR: |
| make_eh_edges (last); |
| fallthru = false; |
| break; |
| |
| case CALL_EXPR: |
| /* If this function receives a nonlocal goto, then we need to |
| make edges from this call site to all the nonlocal goto |
| handlers. */ |
| if (tree_can_make_abnormal_goto (last)) |
| make_abnormal_goto_edges (bb, true); |
| |
| /* If this statement has reachable exception handlers, then |
| create abnormal edges to them. */ |
| make_eh_edges (last); |
| |
| /* Some calls are known not to return. */ |
| fallthru = !(call_expr_flags (last) & ECF_NORETURN); |
| break; |
| |
| case MODIFY_EXPR: |
| if (is_ctrl_altering_stmt (last)) |
| { |
| /* A MODIFY_EXPR may have a CALL_EXPR on its RHS and the |
| CALL_EXPR may have an abnormal edge. Search the RHS for |
| this case and create any required edges. */ |
| if (tree_can_make_abnormal_goto (last)) |
| make_abnormal_goto_edges (bb, true); |
| |
| make_eh_edges (last); |
| } |
| fallthru = true; |
| break; |
| |
| case OMP_PARALLEL: |
| case OMP_FOR: |
| case OMP_SINGLE: |
| case OMP_MASTER: |
| case OMP_ORDERED: |
| case OMP_CRITICAL: |
| case OMP_SECTION: |
| cur_region = new_omp_region (bb, code, cur_region); |
| fallthru = true; |
| break; |
| |
| case OMP_SECTIONS: |
| cur_region = new_omp_region (bb, code, cur_region); |
| fallthru = false; |
| break; |
| |
| case OMP_RETURN: |
| /* In the case of an OMP_SECTION, the edge will go somewhere |
| other than the next block. This will be created later. */ |
| cur_region->exit = bb; |
| fallthru = cur_region->type != OMP_SECTION; |
| cur_region = cur_region->outer; |
| break; |
| |
| case OMP_CONTINUE: |
| cur_region->cont = bb; |
| switch (cur_region->type) |
| { |
| case OMP_FOR: |
| /* ??? Technically there should be a some sort of loopback |
| edge here, but it goes to a block that doesn't exist yet, |
| and without it, updating the ssa form would be a real |
| bear. Fortunately, we don't yet do ssa before expanding |
| these nodes. */ |
| break; |
| |
| case OMP_SECTIONS: |
| /* Wire up the edges into and out of the nested sections. */ |
| /* ??? Similarly wrt loopback. */ |
| { |
| struct omp_region *i; |
| for (i = cur_region->inner; i ; i = i->next) |
| { |
| gcc_assert (i->type == OMP_SECTION); |
| make_edge (cur_region->entry, i->entry, 0); |
| make_edge (i->exit, bb, EDGE_FALLTHRU); |
| } |
| } |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| fallthru = true; |
| break; |
| |
| default: |
| gcc_assert (!stmt_ends_bb_p (last)); |
| fallthru = true; |
| } |
| } |
| else |
| fallthru = true; |
| |
| if (fallthru) |
| make_edge (bb, bb->next_bb, EDGE_FALLTHRU); |
| } |
| |
| if (root_omp_region) |
| free_omp_regions (); |
| |
| /* Fold COND_EXPR_COND of each COND_EXPR. */ |
| fold_cond_expr_cond (); |
| |
| /* Clean up the graph and warn for unreachable code. */ |
| cleanup_tree_cfg (); |
| } |
| |
| |
| /* Create the edges for a COND_EXPR starting at block BB. |
| At this point, both clauses must contain only simple gotos. */ |
| |
| static void |
| make_cond_expr_edges (basic_block bb) |
| { |
| tree entry = last_stmt (bb); |
| basic_block then_bb, else_bb; |
| tree then_label, else_label; |
| edge e; |
| |
| gcc_assert (entry); |
| gcc_assert (TREE_CODE (entry) == COND_EXPR); |
| |
| /* Entry basic blocks for each component. */ |
| then_label = GOTO_DESTINATION (COND_EXPR_THEN (entry)); |
| else_label = GOTO_DESTINATION (COND_EXPR_ELSE (entry)); |
| then_bb = label_to_block (then_label); |
| else_bb = label_to_block (else_label); |
| |
| e = make_edge (bb, then_bb, EDGE_TRUE_VALUE); |
| #ifdef USE_MAPPED_LOCATION |
| e->goto_locus = EXPR_LOCATION (COND_EXPR_THEN (entry)); |
| #else |
| e->goto_locus = EXPR_LOCUS (COND_EXPR_THEN (entry)); |
| #endif |
| e = make_edge (bb, else_bb, EDGE_FALSE_VALUE); |
| if (e) |
| { |
| #ifdef USE_MAPPED_LOCATION |
| e->goto_locus = EXPR_LOCATION (COND_EXPR_ELSE (entry)); |
| #else |
| e->goto_locus = EXPR_LOCUS (COND_EXPR_ELSE (entry)); |
| #endif |
| } |
| } |
| |
| /* Hashing routine for EDGE_TO_CASES. */ |
| |
| static hashval_t |
| edge_to_cases_hash (const void *p) |
| { |
| edge e = ((struct edge_to_cases_elt *)p)->e; |
| |
| /* Hash on the edge itself (which is a pointer). */ |
| return htab_hash_pointer (e); |
| } |
| |
| /* Equality routine for EDGE_TO_CASES, edges are unique, so testing |
| for equality is just a pointer comparison. */ |
| |
| static int |
| edge_to_cases_eq (const void *p1, const void *p2) |
| { |
| edge e1 = ((struct edge_to_cases_elt *)p1)->e; |
| edge e2 = ((struct edge_to_cases_elt *)p2)->e; |
| |
| return e1 == e2; |
| } |
| |
| /* Called for each element in the hash table (P) as we delete the |
| edge to cases hash table. |
| |
| Clear all the TREE_CHAINs to prevent problems with copying of |
| SWITCH_EXPRs and structure sharing rules, then free the hash table |
| element. */ |
| |
| static void |
| edge_to_cases_cleanup (void *p) |
| { |
| struct edge_to_cases_elt *elt = (struct edge_to_cases_elt *) p; |
| tree t, next; |
| |
| for (t = elt->case_labels; t; t = next) |
| { |
| next = TREE_CHAIN (t); |
| TREE_CHAIN (t) = NULL; |
| } |
| free (p); |
| } |
| |
| /* Start recording information mapping edges to case labels. */ |
| |
| void |
| start_recording_case_labels (void) |
| { |
| gcc_assert (edge_to_cases == NULL); |
| |
| edge_to_cases = htab_create (37, |
| edge_to_cases_hash, |
| edge_to_cases_eq, |
| edge_to_cases_cleanup); |
| } |
| |
| /* Return nonzero if we are recording information for case labels. */ |
| |
| static bool |
| recording_case_labels_p (void) |
| { |
| return (edge_to_cases != NULL); |
| } |
| |
| /* Stop recording information mapping edges to case labels and |
| remove any information we have recorded. */ |
| void |
| end_recording_case_labels (void) |
| { |
| htab_delete (edge_to_cases); |
| edge_to_cases = NULL; |
| } |
| |
| /* Record that CASE_LABEL (a CASE_LABEL_EXPR) references edge E. */ |
| |
| static void |
| record_switch_edge (edge e, tree case_label) |
| { |
| struct edge_to_cases_elt *elt; |
| void **slot; |
| |
| /* Build a hash table element so we can see if E is already |
| in the table. */ |
| elt = XNEW (struct edge_to_cases_elt); |
| elt->e = e; |
| elt->case_labels = case_label; |
| |
| slot = htab_find_slot (edge_to_cases, elt, INSERT); |
| |
| if (*slot == NULL) |
| { |
| /* E was not in the hash table. Install E into the hash table. */ |
| *slot = (void *)elt; |
| } |
| else |
| { |
| /* E was already in the hash table. Free ELT as we do not need it |
| anymore. */ |
| free (elt); |
| |
| /* Get the entry stored in the hash table. */ |
| elt = (struct edge_to_cases_elt *) *slot; |
| |
| /* Add it to the chain of CASE_LABEL_EXPRs referencing E. */ |
| TREE_CHAIN (case_label) = elt->case_labels; |
| elt->case_labels = case_label; |
| } |
| } |
| |
| /* If we are inside a {start,end}_recording_cases block, then return |
| a chain of CASE_LABEL_EXPRs from T which reference E. |
| |
| Otherwise return NULL. */ |
| |
| static tree |
| get_cases_for_edge (edge e, tree t) |
| { |
| struct edge_to_cases_elt elt, *elt_p; |
| void **slot; |
| size_t i, n; |
| tree vec; |
| |
| /* If we are not recording cases, then we do not have CASE_LABEL_EXPR |
| chains available. Return NULL so the caller can detect this case. */ |
| if (!recording_case_labels_p ()) |
| return NULL; |
| |
| restart: |
| elt.e = e; |
| elt.case_labels = NULL; |
| slot = htab_find_slot (edge_to_cases, &elt, NO_INSERT); |
| |
| if (slot) |
| { |
| elt_p = (struct edge_to_cases_elt *)*slot; |
| return elt_p->case_labels; |
| } |
| |
| /* If we did not find E in the hash table, then this must be the first |
| time we have been queried for information about E & T. Add all the |
| elements from T to the hash table then perform the query again. */ |
| |
| vec = SWITCH_LABELS (t); |
| n = TREE_VEC_LENGTH (vec); |
| for (i = 0; i < n; i++) |
| { |
| tree lab = CASE_LABEL (TREE_VEC_ELT (vec, i)); |
| basic_block label_bb = label_to_block (lab); |
| record_switch_edge (find_edge (e->src, label_bb), TREE_VEC_ELT (vec, i)); |
| } |
| goto restart; |
| } |
| |
| /* Create the edges for a SWITCH_EXPR starting at block BB. |
| At this point, the switch body has been lowered and the |
| SWITCH_LABELS filled in, so this is in effect a multi-way branch. */ |
| |
| static void |
| make_switch_expr_edges (basic_block bb) |
| { |
| tree entry = last_stmt (bb); |
| size_t i, n; |
| tree vec; |
| |
| vec = SWITCH_LABELS (entry); |
| n = TREE_VEC_LENGTH (vec); |
| |
| for (i = 0; i < n; ++i) |
| { |
| tree lab = CASE_LABEL (TREE_VEC_ELT (vec, i)); |
| basic_block label_bb = label_to_block (lab); |
| make_edge (bb, label_bb, 0); |
| } |
| } |
| |
| |
| /* Return the basic block holding label DEST. */ |
| |
| basic_block |
| label_to_block_fn (struct function *ifun, tree dest) |
| { |
| int uid = LABEL_DECL_UID (dest); |
| |
| /* We would die hard when faced by an undefined label. Emit a label to |
| the very first basic block. This will hopefully make even the dataflow |
| and undefined variable warnings quite right. */ |
| if ((errorcount || sorrycount) && uid < 0) |
| { |
| block_stmt_iterator bsi = |
| bsi_start (BASIC_BLOCK (NUM_FIXED_BLOCKS)); |
| tree stmt; |
| |
| stmt = build1 (LABEL_EXPR, void_type_node, dest); |
| bsi_insert_before (&bsi, stmt, BSI_NEW_STMT); |
| uid = LABEL_DECL_UID (dest); |
| } |
| if (VEC_length (basic_block, ifun->cfg->x_label_to_block_map) |
| <= (unsigned int) uid) |
| return NULL; |
| return VEC_index (basic_block, ifun->cfg->x_label_to_block_map, uid); |
| } |
| |
| /* Create edges for an abnormal goto statement at block BB. If FOR_CALL |
| is true, the source statement is a CALL_EXPR instead of a GOTO_EXPR. */ |
| |
| void |
| make_abnormal_goto_edges (basic_block bb, bool for_call) |
| { |
| basic_block target_bb; |
| block_stmt_iterator bsi; |
| |
| FOR_EACH_BB (target_bb) |
| for (bsi = bsi_start (target_bb); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| tree target = bsi_stmt (bsi); |
| |
| if (TREE_CODE (target) != LABEL_EXPR) |
| break; |
| |
| target = LABEL_EXPR_LABEL (target); |
| |
| /* Make an edge to every label block that has been marked as a |
| potential target for a computed goto or a non-local goto. */ |
| if ((FORCED_LABEL (target) && !for_call) |
| || (DECL_NONLOCAL (target) && for_call)) |
| { |
| make_edge (bb, target_bb, EDGE_ABNORMAL); |
| break; |
| } |
| } |
| } |
| |
| /* Create edges for a goto statement at block BB. */ |
| |
| static void |
| make_goto_expr_edges (basic_block bb) |
| { |
| block_stmt_iterator last = bsi_last (bb); |
| tree goto_t = bsi_stmt (last); |
| |
| /* A simple GOTO creates normal edges. */ |
| if (simple_goto_p (goto_t)) |
| { |
| tree dest = GOTO_DESTINATION (goto_t); |
| edge e = make_edge (bb, label_to_block (dest), EDGE_FALLTHRU); |
| #ifdef USE_MAPPED_LOCATION |
| e->goto_locus = EXPR_LOCATION (goto_t); |
| #else |
| e->goto_locus = EXPR_LOCUS (goto_t); |
| #endif |
| bsi_remove (&last, true); |
| return; |
| } |
| |
| /* A computed GOTO creates abnormal edges. */ |
| make_abnormal_goto_edges (bb, false); |
| } |
| |
| |
| /*--------------------------------------------------------------------------- |
| Flowgraph analysis |
| ---------------------------------------------------------------------------*/ |
| |
| /* Cleanup useless labels in basic blocks. This is something we wish |
| to do early because it allows us to group case labels before creating |
| the edges for the CFG, and it speeds up block statement iterators in |
| all passes later on. |
| We only run this pass once, running it more than once is probably not |
| profitable. */ |
| |
| /* A map from basic block index to the leading label of that block. */ |
| static tree *label_for_bb; |
| |
| /* Callback for for_each_eh_region. Helper for cleanup_dead_labels. */ |
| static void |
| update_eh_label (struct eh_region *region) |
| { |
| tree old_label = get_eh_region_tree_label (region); |
| if (old_label) |
| { |
| tree new_label; |
| basic_block bb = label_to_block (old_label); |
| |
| /* ??? After optimizing, there may be EH regions with labels |
| that have already been removed from the function body, so |
| there is no basic block for them. */ |
| if (! bb) |
| return; |
| |
| new_label = label_for_bb[bb->index]; |
| set_eh_region_tree_label (region, new_label); |
| } |
| } |
| |
| /* Given LABEL return the first label in the same basic block. */ |
| static tree |
| main_block_label (tree label) |
| { |
| basic_block bb = label_to_block (label); |
| |
| /* label_to_block possibly inserted undefined label into the chain. */ |
| if (!label_for_bb[bb->index]) |
| label_for_bb[bb->index] = label; |
| return label_for_bb[bb->index]; |
| } |
| |
| /* Cleanup redundant labels. This is a three-step process: |
| 1) Find the leading label for each block. |
| 2) Redirect all references to labels to the leading labels. |
| 3) Cleanup all useless labels. */ |
| |
| void |
| cleanup_dead_labels (void) |
| { |
| basic_block bb; |
| label_for_bb = XCNEWVEC (tree, last_basic_block); |
| |
| /* Find a suitable label for each block. We use the first user-defined |
| label if there is one, or otherwise just the first label we see. */ |
| FOR_EACH_BB (bb) |
| { |
| block_stmt_iterator i; |
| |
| for (i = bsi_start (bb); !bsi_end_p (i); bsi_next (&i)) |
| { |
| tree label, stmt = bsi_stmt (i); |
| |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| break; |
| |
| label = LABEL_EXPR_LABEL (stmt); |
| |
| /* If we have not yet seen a label for the current block, |
| remember this one and see if there are more labels. */ |
| if (! label_for_bb[bb->index]) |
| { |
| label_for_bb[bb->index] = label; |
| continue; |
| } |
| |
| /* If we did see a label for the current block already, but it |
| is an artificially created label, replace it if the current |
| label is a user defined label. */ |
| if (! DECL_ARTIFICIAL (label) |
| && DECL_ARTIFICIAL (label_for_bb[bb->index])) |
| { |
| label_for_bb[bb->index] = label; |
| break; |
| } |
| } |
| } |
| |
| /* Now redirect all jumps/branches to the selected label. |
| First do so for each block ending in a control statement. */ |
| FOR_EACH_BB (bb) |
| { |
| tree stmt = last_stmt (bb); |
| if (!stmt) |
| continue; |
| |
| switch (TREE_CODE (stmt)) |
| { |
| case COND_EXPR: |
| { |
| tree true_branch, false_branch; |
| |
| true_branch = COND_EXPR_THEN (stmt); |
| false_branch = COND_EXPR_ELSE (stmt); |
| |
| GOTO_DESTINATION (true_branch) |
| = main_block_label (GOTO_DESTINATION (true_branch)); |
| GOTO_DESTINATION (false_branch) |
| = main_block_label (GOTO_DESTINATION (false_branch)); |
| |
| break; |
| } |
| |
| case SWITCH_EXPR: |
| { |
| size_t i; |
| tree vec = SWITCH_LABELS (stmt); |
| size_t n = TREE_VEC_LENGTH (vec); |
| |
| /* Replace all destination labels. */ |
| for (i = 0; i < n; ++i) |
| { |
| tree elt = TREE_VEC_ELT (vec, i); |
| tree label = main_block_label (CASE_LABEL (elt)); |
| CASE_LABEL (elt) = label; |
| } |
| break; |
| } |
| |
| /* We have to handle GOTO_EXPRs until they're removed, and we don't |
| remove them until after we've created the CFG edges. */ |
| case GOTO_EXPR: |
| if (! computed_goto_p (stmt)) |
| { |
| GOTO_DESTINATION (stmt) |
| = main_block_label (GOTO_DESTINATION (stmt)); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| for_each_eh_region (update_eh_label); |
| |
| /* APPLE LOCAL begin for-fsf-4_4 3274130 5295549 */ \ |
| /* Finally, purge dead labels. All user-defined labels, labels that |
| can be the target of non-local gotos, labels which have their |
| address taken and labels which have attributes or alignment are |
| preserved. */ |
| /* APPLE LOCAL end for-fsf-4_4 3274130 5295549 */ \ |
| FOR_EACH_BB (bb) |
| { |
| block_stmt_iterator i; |
| tree label_for_this_bb = label_for_bb[bb->index]; |
| |
| if (! label_for_this_bb) |
| continue; |
| |
| for (i = bsi_start (bb); !bsi_end_p (i); ) |
| { |
| tree label, stmt = bsi_stmt (i); |
| |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| break; |
| |
| label = LABEL_EXPR_LABEL (stmt); |
| |
| if (label == label_for_this_bb |
| || ! DECL_ARTIFICIAL (label) |
| /* APPLE LOCAL begin for-fsf-4_4 3274130 5295549 */ \ |
| || DECL_ATTRIBUTES (label) |
| || DECL_USER_ALIGN (label) |
| /* APPLE LOCAL end for-fsf-4_4 3274130 5295549 */ \ |
| || DECL_NONLOCAL (label) |
| || FORCED_LABEL (label)) |
| bsi_next (&i); |
| else |
| bsi_remove (&i, true); |
| } |
| } |
| |
| free (label_for_bb); |
| } |
| |
| /* Look for blocks ending in a multiway branch (a SWITCH_EXPR in GIMPLE), |
| and scan the sorted vector of cases. Combine the ones jumping to the |
| same label. |
| Eg. three separate entries 1: 2: 3: become one entry 1..3: */ |
| |
| void |
| group_case_labels (void) |
| { |
| basic_block bb; |
| |
| FOR_EACH_BB (bb) |
| { |
| tree stmt = last_stmt (bb); |
| if (stmt && TREE_CODE (stmt) == SWITCH_EXPR) |
| { |
| tree labels = SWITCH_LABELS (stmt); |
| int old_size = TREE_VEC_LENGTH (labels); |
| int i, j, new_size = old_size; |
| tree default_case = TREE_VEC_ELT (labels, old_size - 1); |
| tree default_label; |
| |
| /* The default label is always the last case in a switch |
| statement after gimplification. */ |
| default_label = CASE_LABEL (default_case); |
| |
| /* Look for possible opportunities to merge cases. |
| Ignore the last element of the label vector because it |
| must be the default case. */ |
| i = 0; |
| while (i < old_size - 1) |
| { |
| tree base_case, base_label, base_high; |
| base_case = TREE_VEC_ELT (labels, i); |
| |
| gcc_assert (base_case); |
| base_label = CASE_LABEL (base_case); |
| |
| /* Discard cases that have the same destination as the |
| default case. */ |
| if (base_label == default_label) |
| { |
| TREE_VEC_ELT (labels, i) = NULL_TREE; |
| i++; |
| new_size--; |
| continue; |
| } |
| |
| base_high = CASE_HIGH (base_case) ? |
| CASE_HIGH (base_case) : CASE_LOW (base_case); |
| i++; |
| /* Try to merge case labels. Break out when we reach the end |
| of the label vector or when we cannot merge the next case |
| label with the current one. */ |
| while (i < old_size - 1) |
| { |
| tree merge_case = TREE_VEC_ELT (labels, i); |
| tree merge_label = CASE_LABEL (merge_case); |
| tree t = int_const_binop (PLUS_EXPR, base_high, |
| integer_one_node, 1); |
| |
| /* Merge the cases if they jump to the same place, |
| and their ranges are consecutive. */ |
| if (merge_label == base_label |
| && tree_int_cst_equal (CASE_LOW (merge_case), t)) |
| { |
| base_high = CASE_HIGH (merge_case) ? |
| CASE_HIGH (merge_case) : CASE_LOW (merge_case); |
| CASE_HIGH (base_case) = base_high; |
| TREE_VEC_ELT (labels, i) = NULL_TREE; |
| new_size--; |
| i++; |
| } |
| else |
| break; |
| } |
| } |
| |
| /* Compress the case labels in the label vector, and adjust the |
| length of the vector. */ |
| for (i = 0, j = 0; i < new_size; i++) |
| { |
| while (! TREE_VEC_ELT (labels, j)) |
| j++; |
| TREE_VEC_ELT (labels, i) = TREE_VEC_ELT (labels, j++); |
| } |
| TREE_VEC_LENGTH (labels) = new_size; |
| } |
| } |
| } |
| |
| /* Checks whether we can merge block B into block A. */ |
| |
| static bool |
| tree_can_merge_blocks_p (basic_block a, basic_block b) |
| { |
| tree stmt; |
| block_stmt_iterator bsi; |
| tree phi; |
| |
| if (!single_succ_p (a)) |
| return false; |
| |
| if (single_succ_edge (a)->flags & EDGE_ABNORMAL) |
| return false; |
| |
| if (single_succ (a) != b) |
| return false; |
| |
| if (!single_pred_p (b)) |
| return false; |
| |
| if (b == EXIT_BLOCK_PTR) |
| return false; |
| |
| /* If A ends by a statement causing exceptions or something similar, we |
| cannot merge the blocks. */ |
| stmt = last_stmt (a); |
| if (stmt && stmt_ends_bb_p (stmt)) |
| return false; |
| |
| /* Do not allow a block with only a non-local label to be merged. */ |
| if (stmt && TREE_CODE (stmt) == LABEL_EXPR |
| && DECL_NONLOCAL (LABEL_EXPR_LABEL (stmt))) |
| return false; |
| |
| /* It must be possible to eliminate all phi nodes in B. If ssa form |
| is not up-to-date, we cannot eliminate any phis. */ |
| phi = phi_nodes (b); |
| if (phi) |
| { |
| if (need_ssa_update_p ()) |
| return false; |
| |
| for (; phi; phi = PHI_CHAIN (phi)) |
| if (!is_gimple_reg (PHI_RESULT (phi)) |
| && !may_propagate_copy (PHI_RESULT (phi), PHI_ARG_DEF (phi, 0))) |
| return false; |
| } |
| |
| /* Do not remove user labels. */ |
| for (bsi = bsi_start (b); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| stmt = bsi_stmt (bsi); |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| break; |
| if (!DECL_ARTIFICIAL (LABEL_EXPR_LABEL (stmt))) |
| return false; |
| } |
| |
| /* Protect the loop latches. */ |
| if (current_loops |
| && b->loop_father->latch == b) |
| return false; |
| |
| return true; |
| } |
| |
| /* Replaces all uses of NAME by VAL. */ |
| |
| void |
| replace_uses_by (tree name, tree val) |
| { |
| imm_use_iterator imm_iter; |
| use_operand_p use; |
| tree stmt; |
| edge e; |
| unsigned i; |
| |
| |
| FOR_EACH_IMM_USE_STMT (stmt, imm_iter, name) |
| { |
| FOR_EACH_IMM_USE_ON_STMT (use, imm_iter) |
| { |
| replace_exp (use, val); |
| |
| if (TREE_CODE (stmt) == PHI_NODE) |
| { |
| e = PHI_ARG_EDGE (stmt, PHI_ARG_INDEX_FROM_USE (use)); |
| if (e->flags & EDGE_ABNORMAL) |
| { |
| /* This can only occur for virtual operands, since |
| for the real ones SSA_NAME_OCCURS_IN_ABNORMAL_PHI (name)) |
| would prevent replacement. */ |
| gcc_assert (!is_gimple_reg (name)); |
| SSA_NAME_OCCURS_IN_ABNORMAL_PHI (val) = 1; |
| } |
| } |
| } |
| if (TREE_CODE (stmt) != PHI_NODE) |
| { |
| tree rhs; |
| |
| fold_stmt_inplace (stmt); |
| rhs = get_rhs (stmt); |
| if (TREE_CODE (rhs) == ADDR_EXPR) |
| recompute_tree_invariant_for_addr_expr (rhs); |
| |
| maybe_clean_or_replace_eh_stmt (stmt, stmt); |
| mark_new_vars_to_rename (stmt); |
| } |
| } |
| |
| gcc_assert (num_imm_uses (name) == 0); |
| |
| /* Also update the trees stored in loop structures. */ |
| if (current_loops) |
| { |
| struct loop *loop; |
| |
| for (i = 0; i < current_loops->num; i++) |
| { |
| loop = current_loops->parray[i]; |
| if (loop) |
| substitute_in_loop_info (loop, name, val); |
| } |
| } |
| } |
| |
| /* Merge block B into block A. */ |
| |
| static void |
| tree_merge_blocks (basic_block a, basic_block b) |
| { |
| block_stmt_iterator bsi; |
| tree_stmt_iterator last; |
| tree phi; |
| |
| if (dump_file) |
| fprintf (dump_file, "Merging blocks %d and %d\n", a->index, b->index); |
| |
| /* Remove all single-valued PHI nodes from block B of the form |
| V_i = PHI <V_j> by propagating V_j to all the uses of V_i. */ |
| bsi = bsi_last (a); |
| for (phi = phi_nodes (b); phi; phi = phi_nodes (b)) |
| { |
| tree def = PHI_RESULT (phi), use = PHI_ARG_DEF (phi, 0); |
| tree copy; |
| bool may_replace_uses = may_propagate_copy (def, use); |
| |
| /* In case we have loops to care about, do not propagate arguments of |
| loop closed ssa phi nodes. */ |
| if (current_loops |
| && is_gimple_reg (def) |
| && TREE_CODE (use) == SSA_NAME |
| && a->loop_father != b->loop_father) |
| may_replace_uses = false; |
| |
| if (!may_replace_uses) |
| { |
| gcc_assert (is_gimple_reg (def)); |
| |
| /* Note that just emitting the copies is fine -- there is no problem |
| with ordering of phi nodes. This is because A is the single |
| predecessor of B, therefore results of the phi nodes cannot |
| appear as arguments of the phi nodes. */ |
| copy = build2 (MODIFY_EXPR, void_type_node, def, use); |
| bsi_insert_after (&bsi, copy, BSI_NEW_STMT); |
| SET_PHI_RESULT (phi, NULL_TREE); |
| SSA_NAME_DEF_STMT (def) = copy; |
| } |
| else |
| replace_uses_by (def, use); |
| |
| remove_phi_node (phi, NULL); |
| } |
| |
| /* Ensure that B follows A. */ |
| move_block_after (b, a); |
| |
| gcc_assert (single_succ_edge (a)->flags & EDGE_FALLTHRU); |
| gcc_assert (!last_stmt (a) || !stmt_ends_bb_p (last_stmt (a))); |
| |
| /* Remove labels from B and set bb_for_stmt to A for other statements. */ |
| for (bsi = bsi_start (b); !bsi_end_p (bsi);) |
| { |
| if (TREE_CODE (bsi_stmt (bsi)) == LABEL_EXPR) |
| { |
| tree label = bsi_stmt (bsi); |
| |
| bsi_remove (&bsi, false); |
| /* 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 |
| { |
| change_bb_for_stmt (bsi_stmt (bsi), a); |
| bsi_next (&bsi); |
| } |
| } |
| |
| /* Merge the chains. */ |
| last = tsi_last (a->stmt_list); |
| tsi_link_after (&last, b->stmt_list, TSI_NEW_STMT); |
| b->stmt_list = NULL; |
| } |
| |
| |
| /* Return the one of two successors of BB that is not reachable by a |
| reached by a complex edge, if there is one. Else, return BB. We use |
| this in optimizations that use post-dominators for their heuristics, |
| to catch the cases in C++ where function calls are involved. */ |
| |
| basic_block |
| single_noncomplex_succ (basic_block bb) |
| { |
| edge e0, e1; |
| if (EDGE_COUNT (bb->succs) != 2) |
| return bb; |
| |
| e0 = EDGE_SUCC (bb, 0); |
| e1 = EDGE_SUCC (bb, 1); |
| if (e0->flags & EDGE_COMPLEX) |
| return e1->dest; |
| if (e1->flags & EDGE_COMPLEX) |
| return e0->dest; |
| |
| return bb; |
| } |
| |
| |
| /* Walk the function tree removing unnecessary statements. |
| |
| * Empty statement nodes are removed |
| |
| * Unnecessary TRY_FINALLY and TRY_CATCH blocks are removed |
| |
| * Unnecessary COND_EXPRs are removed |
| |
| * Some unnecessary BIND_EXPRs are removed |
| |
| Clearly more work could be done. The trick is doing the analysis |
| and removal fast enough to be a net improvement in compile times. |
| |
| Note that when we remove a control structure such as a COND_EXPR |
| BIND_EXPR, or TRY block, we will need to repeat this optimization pass |
| to ensure we eliminate all the useless code. */ |
| |
| struct rus_data |
| { |
| tree *last_goto; |
| bool repeat; |
| bool may_throw; |
| bool may_branch; |
| bool has_label; |
| }; |
| |
| static void remove_useless_stmts_1 (tree *, struct rus_data *); |
| |
| static bool |
| remove_useless_stmts_warn_notreached (tree stmt) |
| { |
| if (EXPR_HAS_LOCATION (stmt)) |
| { |
| location_t loc = EXPR_LOCATION (stmt); |
| if (LOCATION_LINE (loc) > 0) |
| { |
| warning (0, "%Hwill never be executed", &loc); |
| return true; |
| } |
| } |
| |
| switch (TREE_CODE (stmt)) |
| { |
| case STATEMENT_LIST: |
| { |
| tree_stmt_iterator i; |
| for (i = tsi_start (stmt); !tsi_end_p (i); tsi_next (&i)) |
| if (remove_useless_stmts_warn_notreached (tsi_stmt (i))) |
| return true; |
| } |
| break; |
| |
| case COND_EXPR: |
| if (remove_useless_stmts_warn_notreached (COND_EXPR_COND (stmt))) |
| return true; |
| if (remove_useless_stmts_warn_notreached (COND_EXPR_THEN (stmt))) |
| return true; |
| if (remove_useless_stmts_warn_notreached (COND_EXPR_ELSE (stmt))) |
| return true; |
| break; |
| |
| case TRY_FINALLY_EXPR: |
| case TRY_CATCH_EXPR: |
| if (remove_useless_stmts_warn_notreached (TREE_OPERAND (stmt, 0))) |
| return true; |
| if (remove_useless_stmts_warn_notreached (TREE_OPERAND (stmt, 1))) |
| return true; |
| break; |
| |
| case CATCH_EXPR: |
| return remove_useless_stmts_warn_notreached (CATCH_BODY (stmt)); |
| case EH_FILTER_EXPR: |
| return remove_useless_stmts_warn_notreached (EH_FILTER_FAILURE (stmt)); |
| case BIND_EXPR: |
| return remove_useless_stmts_warn_notreached (BIND_EXPR_BLOCK (stmt)); |
| |
| default: |
| /* Not a live container. */ |
| break; |
| } |
| |
| return false; |
| } |
| |
| static void |
| remove_useless_stmts_cond (tree *stmt_p, struct rus_data *data) |
| { |
| tree then_clause, else_clause, cond; |
| bool save_has_label, then_has_label, else_has_label; |
| |
| save_has_label = data->has_label; |
| data->has_label = false; |
| data->last_goto = NULL; |
| |
| remove_useless_stmts_1 (&COND_EXPR_THEN (*stmt_p), data); |
| |
| then_has_label = data->has_label; |
| data->has_label = false; |
| data->last_goto = NULL; |
| |
| remove_useless_stmts_1 (&COND_EXPR_ELSE (*stmt_p), data); |
| |
| else_has_label = data->has_label; |
| data->has_label = save_has_label | then_has_label | else_has_label; |
| |
| then_clause = COND_EXPR_THEN (*stmt_p); |
| else_clause = COND_EXPR_ELSE (*stmt_p); |
| cond = fold (COND_EXPR_COND (*stmt_p)); |
| |
| /* If neither arm does anything at all, we can remove the whole IF. */ |
| if (!TREE_SIDE_EFFECTS (then_clause) && !TREE_SIDE_EFFECTS (else_clause)) |
| { |
| *stmt_p = build_empty_stmt (); |
| data->repeat = true; |
| } |
| |
| /* If there are no reachable statements in an arm, then we can |
| zap the entire conditional. */ |
| else if (integer_nonzerop (cond) && !else_has_label) |
| { |
| if (warn_notreached) |
| remove_useless_stmts_warn_notreached (else_clause); |
| *stmt_p = then_clause; |
| data->repeat = true; |
| } |
| else if (integer_zerop (cond) && !then_has_label) |
| { |
| if (warn_notreached) |
| remove_useless_stmts_warn_notreached (then_clause); |
| *stmt_p = else_clause; |
| data->repeat = true; |
| } |
| |
| /* Check a couple of simple things on then/else with single stmts. */ |
| else |
| { |
| tree then_stmt = expr_only (then_clause); |
| tree else_stmt = expr_only (else_clause); |
| |
| /* Notice branches to a common destination. */ |
| if (then_stmt && else_stmt |
| && TREE_CODE (then_stmt) == GOTO_EXPR |
| && TREE_CODE (else_stmt) == GOTO_EXPR |
| && (GOTO_DESTINATION (then_stmt) == GOTO_DESTINATION (else_stmt))) |
| { |
| *stmt_p = then_stmt; |
| data->repeat = true; |
| } |
| |
| /* If the THEN/ELSE clause merely assigns a value to a variable or |
| parameter which is already known to contain that value, then |
| remove the useless THEN/ELSE clause. */ |
| else if (TREE_CODE (cond) == VAR_DECL || TREE_CODE (cond) == PARM_DECL) |
| { |
| if (else_stmt |
| && TREE_CODE (else_stmt) == MODIFY_EXPR |
| && TREE_OPERAND (else_stmt, 0) == cond |
| && integer_zerop (TREE_OPERAND (else_stmt, 1))) |
| COND_EXPR_ELSE (*stmt_p) = alloc_stmt_list (); |
| } |
| else if ((TREE_CODE (cond) == EQ_EXPR || TREE_CODE (cond) == NE_EXPR) |
| && (TREE_CODE (TREE_OPERAND (cond, 0)) == VAR_DECL |
| || TREE_CODE (TREE_OPERAND (cond, 0)) == PARM_DECL) |
| && TREE_CONSTANT (TREE_OPERAND (cond, 1))) |
| { |
| tree stmt = (TREE_CODE (cond) == EQ_EXPR |
| ? then_stmt : else_stmt); |
| tree *location = (TREE_CODE (cond) == EQ_EXPR |
| ? &COND_EXPR_THEN (*stmt_p) |
| : &COND_EXPR_ELSE (*stmt_p)); |
| |
| if (stmt |
| && TREE_CODE (stmt) == MODIFY_EXPR |
| && TREE_OPERAND (stmt, 0) == TREE_OPERAND (cond, 0) |
| && TREE_OPERAND (stmt, 1) == TREE_OPERAND (cond, 1)) |
| *location = alloc_stmt_list (); |
| } |
| } |
| |
| /* Protect GOTOs in the arm of COND_EXPRs from being removed. They |
| would be re-introduced during lowering. */ |
| data->last_goto = NULL; |
| } |
| |
| |
| static void |
| remove_useless_stmts_tf (tree *stmt_p, struct rus_data *data) |
| { |
| bool save_may_branch, save_may_throw; |
| bool this_may_branch, this_may_throw; |
| |
| /* Collect may_branch and may_throw information for the body only. */ |
| save_may_branch = data->may_branch; |
| save_may_throw = data->may_throw; |
| data->may_branch = false; |
| data->may_throw = false; |
| data->last_goto = NULL; |
| |
| remove_useless_stmts_1 (&TREE_OPERAND (*stmt_p, 0), data); |
| |
| this_may_branch = data->may_branch; |
| this_may_throw = data->may_throw; |
| data->may_branch |= save_may_branch; |
| data->may_throw |= save_may_throw; |
| data->last_goto = NULL; |
| |
| remove_useless_stmts_1 (&TREE_OPERAND (*stmt_p, 1), data); |
| |
| /* If the body is empty, then we can emit the FINALLY block without |
| the enclosing TRY_FINALLY_EXPR. */ |
| if (!TREE_SIDE_EFFECTS (TREE_OPERAND (*stmt_p, 0))) |
| { |
| *stmt_p = TREE_OPERAND (*stmt_p, 1); |
| data->repeat = true; |
| } |
| |
| /* If the handler is empty, then we can emit the TRY block without |
| the enclosing TRY_FINALLY_EXPR. */ |
| else if (!TREE_SIDE_EFFECTS (TREE_OPERAND (*stmt_p, 1))) |
| { |
| *stmt_p = TREE_OPERAND (*stmt_p, 0); |
| data->repeat = true; |
| } |
| |
| /* If the body neither throws, nor branches, then we can safely |
| string the TRY and FINALLY blocks together. */ |
| else if (!this_may_branch && !this_may_throw) |
| { |
| tree stmt = *stmt_p; |
| *stmt_p = TREE_OPERAND (stmt, 0); |
| append_to_statement_list (TREE_OPERAND (stmt, 1), stmt_p); |
| data->repeat = true; |
| } |
| } |
| |
| |
| static void |
| remove_useless_stmts_tc (tree *stmt_p, struct rus_data *data) |
| { |
| bool save_may_throw, this_may_throw; |
| tree_stmt_iterator i; |
| tree stmt; |
| |
| /* Collect may_throw information for the body only. */ |
| save_may_throw = data->may_throw; |
| data->may_throw = false; |
| data->last_goto = NULL; |
| |
| remove_useless_stmts_1 (&TREE_OPERAND (*stmt_p, 0), data); |
| |
| this_may_throw = data->may_throw; |
| data->may_throw = save_may_throw; |
| |
| /* If the body cannot throw, then we can drop the entire TRY_CATCH_EXPR. */ |
| if (!this_may_throw) |
| { |
| if (warn_notreached) |
| remove_useless_stmts_warn_notreached (TREE_OPERAND (*stmt_p, 1)); |
| *stmt_p = TREE_OPERAND (*stmt_p, 0); |
| data->repeat = true; |
| return; |
| } |
| |
| /* Process the catch clause specially. We may be able to tell that |
| no exceptions propagate past this point. */ |
| |
| this_may_throw = true; |
| i = tsi_start (TREE_OPERAND (*stmt_p, 1)); |
| stmt = tsi_stmt (i); |
| data->last_goto = NULL; |
| |
| switch (TREE_CODE (stmt)) |
| { |
| case CATCH_EXPR: |
| for (; !tsi_end_p (i); tsi_next (&i)) |
| { |
| stmt = tsi_stmt (i); |
| /* If we catch all exceptions, then the body does not |
| propagate exceptions past this point. */ |
| if (CATCH_TYPES (stmt) == NULL) |
| this_may_throw = false; |
| data->last_goto = NULL; |
| remove_useless_stmts_1 (&CATCH_BODY (stmt), data); |
| } |
| break; |
| |
| case EH_FILTER_EXPR: |
| if (EH_FILTER_MUST_NOT_THROW (stmt)) |
| this_may_throw = false; |
| else if (EH_FILTER_TYPES (stmt) == NULL) |
| this_may_throw = false; |
| remove_useless_stmts_1 (&EH_FILTER_FAILURE (stmt), data); |
| break; |
| |
| default: |
| /* Otherwise this is a cleanup. */ |
| remove_useless_stmts_1 (&TREE_OPERAND (*stmt_p, 1), data); |
| |
| /* If the cleanup is empty, then we can emit the TRY block without |
| the enclosing TRY_CATCH_EXPR. */ |
| if (!TREE_SIDE_EFFECTS (TREE_OPERAND (*stmt_p, 1))) |
| { |
| *stmt_p = TREE_OPERAND (*stmt_p, 0); |
| data->repeat = true; |
| } |
| break; |
| } |
| data->may_throw |= this_may_throw; |
| } |
| |
| |
| static void |
| remove_useless_stmts_bind (tree *stmt_p, struct rus_data *data) |
| { |
| tree block; |
| |
| /* First remove anything underneath the BIND_EXPR. */ |
| remove_useless_stmts_1 (&BIND_EXPR_BODY (*stmt_p), data); |
| |
| /* If the BIND_EXPR has no variables, then we can pull everything |
| up one level and remove the BIND_EXPR, unless this is the toplevel |
| BIND_EXPR for the current function or an inlined function. |
| |
| When this situation occurs we will want to apply this |
| optimization again. */ |
| block = BIND_EXPR_BLOCK (*stmt_p); |
| if (BIND_EXPR_VARS (*stmt_p) == NULL_TREE |
| && *stmt_p != DECL_SAVED_TREE (current_function_decl) |
| && (! block |
| || ! BLOCK_ABSTRACT_ORIGIN (block) |
| || (TREE_CODE (BLOCK_ABSTRACT_ORIGIN (block)) |
| != FUNCTION_DECL))) |
| { |
| *stmt_p = BIND_EXPR_BODY (*stmt_p); |
| data->repeat = true; |
| } |
| } |
| |
| |
| static void |
| remove_useless_stmts_goto (tree *stmt_p, struct rus_data *data) |
| { |
| tree dest = GOTO_DESTINATION (*stmt_p); |
| |
| data->may_branch = true; |
| data->last_goto = NULL; |
| |
| /* Record the last goto expr, so that we can delete it if unnecessary. */ |
| if (TREE_CODE (dest) == LABEL_DECL) |
| data->last_goto = stmt_p; |
| } |
| |
| |
| static void |
| remove_useless_stmts_label (tree *stmt_p, struct rus_data *data) |
| { |
| tree label = LABEL_EXPR_LABEL (*stmt_p); |
| |
| data->has_label = true; |
| |
| /* We do want to jump across non-local label receiver code. */ |
| if (DECL_NONLOCAL (label)) |
| data->last_goto = NULL; |
| |
| else if (data->last_goto && GOTO_DESTINATION (*data->last_goto) == label) |
| { |
| *data->last_goto = build_empty_stmt (); |
| data->repeat = true; |
| } |
| |
| /* ??? Add something here to delete unused labels. */ |
| } |
| |
| |
| /* If the function is "const" or "pure", then clear TREE_SIDE_EFFECTS on its |
| decl. This allows us to eliminate redundant or useless |
| calls to "const" functions. |
| |
| Gimplifier already does the same operation, but we may notice functions |
| being const and pure once their calls has been gimplified, so we need |
| to update the flag. */ |
| |
| static void |
| update_call_expr_flags (tree call) |
| { |
| tree decl = get_callee_fndecl (call); |
| if (!decl) |
| return; |
| if (call_expr_flags (call) & (ECF_CONST | ECF_PURE)) |
| TREE_SIDE_EFFECTS (call) = 0; |
| if (TREE_NOTHROW (decl)) |
| TREE_NOTHROW (call) = 1; |
| } |
| |
| |
| /* T is CALL_EXPR. Set current_function_calls_* flags. */ |
| |
| void |
| notice_special_calls (tree t) |
| { |
| int flags = call_expr_flags (t); |
| |
| if (flags & ECF_MAY_BE_ALLOCA) |
| current_function_calls_alloca = true; |
| if (flags & ECF_RETURNS_TWICE) |
| current_function_calls_setjmp = true; |
| } |
| |
| |
| /* Clear flags set by notice_special_calls. Used by dead code removal |
| to update the flags. */ |
| |
| void |
| clear_special_calls (void) |
| { |
| current_function_calls_alloca = false; |
| current_function_calls_setjmp = false; |
| } |
| |
| |
| static void |
| remove_useless_stmts_1 (tree *tp, struct rus_data *data) |
| { |
| tree t = *tp, op; |
| |
| switch (TREE_CODE (t)) |
| { |
| case COND_EXPR: |
| remove_useless_stmts_cond (tp, data); |
| break; |
| |
| case TRY_FINALLY_EXPR: |
| remove_useless_stmts_tf (tp, data); |
| break; |
| |
| case TRY_CATCH_EXPR: |
| remove_useless_stmts_tc (tp, data); |
| break; |
| |
| case BIND_EXPR: |
| remove_useless_stmts_bind (tp, data); |
| break; |
| |
| case GOTO_EXPR: |
| remove_useless_stmts_goto (tp, data); |
| break; |
| |
| case LABEL_EXPR: |
| remove_useless_stmts_label (tp, data); |
| break; |
| |
| case RETURN_EXPR: |
| fold_stmt (tp); |
| data->last_goto = NULL; |
| data->may_branch = true; |
| break; |
| |
| case CALL_EXPR: |
| fold_stmt (tp); |
| data->last_goto = NULL; |
| notice_special_calls (t); |
| update_call_expr_flags (t); |
| if (tree_could_throw_p (t)) |
| data->may_throw = true; |
| break; |
| |
| case MODIFY_EXPR: |
| data->last_goto = NULL; |
| fold_stmt (tp); |
| op = get_call_expr_in (t); |
| if (op) |
| { |
| update_call_expr_flags (op); |
| notice_special_calls (op); |
| } |
| if (tree_could_throw_p (t)) |
| data->may_throw = true; |
| break; |
| |
| case STATEMENT_LIST: |
| { |
| tree_stmt_iterator i = tsi_start (t); |
| while (!tsi_end_p (i)) |
| { |
| t = tsi_stmt (i); |
| if (IS_EMPTY_STMT (t)) |
| { |
| tsi_delink (&i); |
| continue; |
| } |
| |
| remove_useless_stmts_1 (tsi_stmt_ptr (i), data); |
| |
| t = tsi_stmt (i); |
| if (TREE_CODE (t) == STATEMENT_LIST) |
| { |
| tsi_link_before (&i, t, TSI_SAME_STMT); |
| tsi_delink (&i); |
| } |
| else |
| tsi_next (&i); |
| } |
| } |
| break; |
| case ASM_EXPR: |
| fold_stmt (tp); |
| data->last_goto = NULL; |
| break; |
| |
| default: |
| data->last_goto = NULL; |
| break; |
| } |
| } |
| |
| static unsigned int |
| remove_useless_stmts (void) |
| { |
| struct rus_data data; |
| |
| clear_special_calls (); |
| |
| do |
| { |
| memset (&data, 0, sizeof (data)); |
| remove_useless_stmts_1 (&DECL_SAVED_TREE (current_function_decl), &data); |
| } |
| while (data.repeat); |
| return 0; |
| } |
| |
| |
| struct tree_opt_pass pass_remove_useless_stmts = |
| { |
| "useless", /* name */ |
| NULL, /* gate */ |
| remove_useless_stmts, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| 0, /* tv_id */ |
| PROP_gimple_any, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_dump_func, /* todo_flags_finish */ |
| 0 /* letter */ |
| }; |
| |
| /* Remove PHI nodes associated with basic block BB and all edges out of BB. */ |
| |
| static void |
| remove_phi_nodes_and_edges_for_unreachable_block (basic_block bb) |
| { |
| tree phi; |
| |
| /* Since this block is no longer reachable, we can just delete all |
| of its PHI nodes. */ |
| phi = phi_nodes (bb); |
| while (phi) |
| { |
| tree next = PHI_CHAIN (phi); |
| remove_phi_node (phi, NULL_TREE); |
| phi = next; |
| } |
| |
| /* Remove edges to BB's successors. */ |
| while (EDGE_COUNT (bb->succs) > 0) |
| remove_edge (EDGE_SUCC (bb, 0)); |
| } |
| |
| |
| /* Remove statements of basic block BB. */ |
| |
| static void |
| remove_bb (basic_block bb) |
| { |
| block_stmt_iterator i; |
| #ifdef USE_MAPPED_LOCATION |
| source_location loc = UNKNOWN_LOCATION; |
| #else |
| source_locus loc = 0; |
| #endif |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "Removing basic block %d\n", bb->index); |
| if (dump_flags & TDF_DETAILS) |
| { |
| dump_bb (bb, dump_file, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| /* If we remove the header or the latch of a loop, mark the loop for |
| removal by setting its header and latch to NULL. */ |
| if (current_loops) |
| { |
| struct loop *loop = bb->loop_father; |
| |
| if (loop->latch == bb |
| || loop->header == bb) |
| { |
| loop->latch = NULL; |
| loop->header = NULL; |
| |
| /* Also clean up the information associated with the loop. Updating |
| it would waste time. More importantly, it may refer to ssa |
| names that were defined in other removed basic block -- these |
| ssa names are now removed and invalid. */ |
| free_numbers_of_iterations_estimates_loop (loop); |
| } |
| } |
| |
| /* Remove all the instructions in the block. */ |
| for (i = bsi_start (bb); !bsi_end_p (i);) |
| { |
| tree stmt = bsi_stmt (i); |
| if (TREE_CODE (stmt) == LABEL_EXPR |
| && (FORCED_LABEL (LABEL_EXPR_LABEL (stmt)) |
| || DECL_NONLOCAL (LABEL_EXPR_LABEL (stmt)))) |
| { |
| basic_block new_bb; |
| block_stmt_iterator new_bsi; |
| |
| /* A non-reachable non-local label may still be referenced. |
| But it no longer needs to carry the extra semantics of |
| non-locality. */ |
| if (DECL_NONLOCAL (LABEL_EXPR_LABEL (stmt))) |
| { |
| DECL_NONLOCAL (LABEL_EXPR_LABEL (stmt)) = 0; |
| FORCED_LABEL (LABEL_EXPR_LABEL (stmt)) = 1; |
| } |
| |
| new_bb = bb->prev_bb; |
| new_bsi = bsi_start (new_bb); |
| bsi_remove (&i, false); |
| bsi_insert_before (&new_bsi, stmt, BSI_NEW_STMT); |
| } |
| else |
| { |
| /* Release SSA definitions if we are in SSA. Note that we |
| may be called when not in SSA. For example, |
| final_cleanup calls this function via |
| cleanup_tree_cfg. */ |
| if (in_ssa_p) |
| release_defs (stmt); |
| |
| bsi_remove (&i, true); |
| } |
| |
| /* Don't warn for removed gotos. Gotos are often removed due to |
| jump threading, thus resulting in bogus warnings. Not great, |
| since this way we lose warnings for gotos in the original |
| program that are indeed unreachable. */ |
| if (TREE_CODE (stmt) != GOTO_EXPR && EXPR_HAS_LOCATION (stmt) && !loc) |
| { |
| #ifdef USE_MAPPED_LOCATION |
| if (EXPR_HAS_LOCATION (stmt)) |
| loc = EXPR_LOCATION (stmt); |
| #else |
| source_locus t; |
| t = EXPR_LOCUS (stmt); |
| if (t && LOCATION_LINE (*t) > 0) |
| loc = t; |
| #endif |
| } |
| } |
| |
| /* If requested, give a warning that the first statement in the |
| block is unreachable. We walk statements backwards in the |
| loop above, so the last statement we process is the first statement |
| in the block. */ |
| #ifdef USE_MAPPED_LOCATION |
| if (loc > BUILTINS_LOCATION) |
| warning (OPT_Wunreachable_code, "%Hwill never be executed", &loc); |
| #else |
| if (loc) |
| warning (OPT_Wunreachable_code, "%Hwill never be executed", loc); |
| #endif |
| |
| remove_phi_nodes_and_edges_for_unreachable_block (bb); |
| } |
| |
| |
| /* Given a basic block BB ending with COND_EXPR or SWITCH_EXPR, and a |
| predicate VAL, return the edge that will be taken out of the block. |
| If VAL does not match a unique edge, NULL is returned. */ |
| |
| edge |
| find_taken_edge (basic_block bb, tree val) |
| { |
| tree stmt; |
| |
| stmt = last_stmt (bb); |
| |
| gcc_assert (stmt); |
| gcc_assert (is_ctrl_stmt (stmt)); |
| gcc_assert (val); |
| |
| if (! is_gimple_min_invariant (val)) |
| return NULL; |
| |
| if (TREE_CODE (stmt) == COND_EXPR) |
| return find_taken_edge_cond_expr (bb, val); |
| |
| if (TREE_CODE (stmt) == SWITCH_EXPR) |
| return find_taken_edge_switch_expr (bb, val); |
| |
| if (computed_goto_p (stmt)) |
| { |
| /* Only optimize if the argument is a label, if the argument is |
| not a label then we can not construct a proper CFG. |
| |
| It may be the case that we only need to allow the LABEL_REF to |
| appear inside an ADDR_EXPR, but we also allow the LABEL_REF to |
| appear inside a LABEL_EXPR just to be safe. */ |
| if ((TREE_CODE (val) == ADDR_EXPR || TREE_CODE (val) == LABEL_EXPR) |
| && TREE_CODE (TREE_OPERAND (val, 0)) == LABEL_DECL) |
| return find_taken_edge_computed_goto (bb, TREE_OPERAND (val, 0)); |
| return NULL; |
| } |
| |
| 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 |
| block. Return NULL if either edge may be taken. */ |
| |
| static edge |
| find_taken_edge_cond_expr (basic_block bb, tree val) |
| { |
| edge true_edge, false_edge; |
| |
| extract_true_false_edges_from_block (bb, &true_edge, &false_edge); |
| |
| gcc_assert (TREE_CODE (val) == INTEGER_CST); |
| return (zero_p (val) ? false_edge : true_edge); |
| } |
| |
| /* Given an INTEGER_CST VAL and the entry block BB to a SWITCH_EXPR |
| statement, determine which edge will be taken out of the block. Return |
| NULL if any edge may be taken. */ |
| |
| static edge |
| find_taken_edge_switch_expr (basic_block bb, tree val) |
| { |
| tree switch_expr, taken_case; |
| basic_block dest_bb; |
| edge e; |
| |
| switch_expr = last_stmt (bb); |
| taken_case = find_case_label_for_value (switch_expr, val); |
| dest_bb = label_to_block (CASE_LABEL (taken_case)); |
| |
| e = find_edge (bb, dest_bb); |
| gcc_assert (e); |
| return e; |
| } |
| |
| |
| /* Return the CASE_LABEL_EXPR that SWITCH_EXPR will take for VAL. |
| We can make optimal use here of the fact that the case labels are |
| sorted: We can do a binary search for a case matching VAL. */ |
| |
| static tree |
| find_case_label_for_value (tree switch_expr, tree val) |
| { |
| tree vec = SWITCH_LABELS (switch_expr); |
| size_t low, high, n = TREE_VEC_LENGTH (vec); |
| tree default_case = TREE_VEC_ELT (vec, n - 1); |
| |
| for (low = -1, high = n - 1; high - low > 1; ) |
| { |
| size_t i = (high + low) / 2; |
| tree t = TREE_VEC_ELT (vec, i); |
| int cmp; |
| |
| /* Cache the result of comparing CASE_LOW and val. */ |
| cmp = tree_int_cst_compare (CASE_LOW (t), val); |
| |
| if (cmp > 0) |
| high = i; |
| else |
| low = i; |
| |
| if (CASE_HIGH (t) == NULL) |
| { |
| /* A singe-valued case label. */ |
| if (cmp == 0) |
| return t; |
| } |
| else |
| { |
| /* A case range. We can only handle integer ranges. */ |
| if (cmp <= 0 && tree_int_cst_compare (CASE_HIGH (t), val) >= 0) |
| return t; |
| } |
| } |
| |
| return default_case; |
| } |
| |
| |
| |
| |
| /*--------------------------------------------------------------------------- |
| Debugging functions |
| ---------------------------------------------------------------------------*/ |
| |
| /* Dump tree-specific information of block BB to file OUTF. */ |
| |
| void |
| tree_dump_bb (basic_block bb, FILE *outf, int indent) |
| { |
| dump_generic_bb (outf, bb, indent, TDF_VOPS); |
| } |
| |
| |
| /* Dump a basic block on stderr. */ |
| |
| void |
| debug_tree_bb (basic_block bb) |
| { |
| dump_bb (bb, stderr, 0); |
| } |
| |
| |
| /* Dump basic block with index N on stderr. */ |
| |
| basic_block |
| debug_tree_bb_n (int n) |
| { |
| debug_tree_bb (BASIC_BLOCK (n)); |
| return BASIC_BLOCK (n); |
| } |
| |
| |
| /* Dump the CFG on stderr. |
| |
| FLAGS are the same used by the tree dumping functions |
| (see TDF_* in tree-pass.h). */ |
| |
| void |
| debug_tree_cfg (int flags) |
| { |
| dump_tree_cfg (stderr, flags); |
| } |
| |
| |
| /* Dump the program showing basic block boundaries on the given FILE. |
| |
| FLAGS are the same used by the tree dumping functions (see TDF_* in |
| tree.h). */ |
| |
| void |
| dump_tree_cfg (FILE *file, int flags) |
| { |
| if (flags & TDF_DETAILS) |
| { |
| const char *funcname |
| = lang_hooks.decl_printable_name (current_function_decl, 2); |
| |
| fputc ('\n', file); |
| fprintf (file, ";; Function %s\n\n", funcname); |
| fprintf (file, ";; \n%d basic blocks, %d edges, last basic block %d.\n\n", |
| n_basic_blocks, n_edges, last_basic_block); |
| |
| brief_dump_cfg (file); |
| fprintf (file, "\n"); |
| } |
| |
| if (flags & TDF_STATS) |
| dump_cfg_stats (file); |
| |
| dump_function_to_file (current_function_decl, file, flags | TDF_BLOCKS); |
| } |
| |
| |
| /* Dump CFG statistics on FILE. */ |
| |
| void |
| dump_cfg_stats (FILE *file) |
| { |
| static long max_num_merged_labels = 0; |
| unsigned long size, total = 0; |
| long num_edges; |
| basic_block bb; |
| const char * const fmt_str = "%-30s%-13s%12s\n"; |
| const char * const fmt_str_1 = "%-30s%13d%11lu%c\n"; |
| const char * const fmt_str_2 = "%-30s%13ld%11lu%c\n"; |
| const char * const fmt_str_3 = "%-43s%11lu%c\n"; |
| const char *funcname |
| = lang_hooks.decl_printable_name (current_function_decl, 2); |
| |
| |
| fprintf (file, "\nCFG Statistics for %s\n\n", funcname); |
| |
| fprintf (file, "---------------------------------------------------------\n"); |
| fprintf (file, fmt_str, "", " Number of ", "Memory"); |
| fprintf (file, fmt_str, "", " instances ", "used "); |
| fprintf (file, "---------------------------------------------------------\n"); |
| |
| size = n_basic_blocks * sizeof (struct basic_block_def); |
| total += size; |
| fprintf (file, fmt_str_1, "Basic blocks", n_basic_blocks, |
| SCALE (size), LABEL (size)); |
| |
| num_edges = 0; |
| FOR_EACH_BB (bb) |
| num_edges += EDGE_COUNT (bb->succs); |
| size = num_edges * sizeof (struct edge_def); |
| total += size; |
| fprintf (file, fmt_str_2, "Edges", num_edges, SCALE (size), LABEL (size)); |
| |
| fprintf (file, "---------------------------------------------------------\n"); |
| fprintf (file, fmt_str_3, "Total memory used by CFG data", SCALE (total), |
| LABEL (total)); |
| fprintf (file, "---------------------------------------------------------\n"); |
| fprintf (file, "\n"); |
| |
| if (cfg_stats.num_merged_labels > max_num_merged_labels) |
| max_num_merged_labels = cfg_stats.num_merged_labels; |
| |
| fprintf (file, "Coalesced label blocks: %ld (Max so far: %ld)\n", |
| cfg_stats.num_merged_labels, max_num_merged_labels); |
| |
| fprintf (file, "\n"); |
| } |
| |
| |
| /* Dump CFG statistics on stderr. Keep extern so that it's always |
| linked in the final executable. */ |
| |
| void |
| debug_cfg_stats (void) |
| { |
| dump_cfg_stats (stderr); |
| } |
| |
| |
| /* Dump the flowgraph to a .vcg FILE. */ |
| |
| static void |
| tree_cfg2vcg (FILE *file) |
| { |
| edge e; |
| edge_iterator ei; |
| basic_block bb; |
| const char *funcname |
| = lang_hooks.decl_printable_name (current_function_decl, 2); |
| |
| /* Write the file header. */ |
| fprintf (file, "graph: { title: \"%s\"\n", funcname); |
| fprintf (file, "node: { title: \"ENTRY\" label: \"ENTRY\" }\n"); |
| fprintf (file, "node: { title: \"EXIT\" label: \"EXIT\" }\n"); |
| |
| /* Write blocks and edges. */ |
| FOR_EACH_EDGE (e, ei, ENTRY_BLOCK_PTR->succs) |
| { |
| fprintf (file, "edge: { sourcename: \"ENTRY\" targetname: \"%d\"", |
| e->dest->index); |
| |
| if (e->flags & EDGE_FAKE) |
| fprintf (file, " linestyle: dotted priority: 10"); |
| else |
| fprintf (file, " linestyle: solid priority: 100"); |
| |
| fprintf (file, " }\n"); |
| } |
| fputc ('\n', file); |
| |
| FOR_EACH_BB (bb) |
| { |
| enum tree_code head_code, end_code; |
| const char *head_name, *end_name; |
| int head_line = 0; |
| int end_line = 0; |
| tree first = first_stmt (bb); |
| tree last = last_stmt (bb); |
| |
| if (first) |
| { |
| head_code = TREE_CODE (first); |
| head_name = tree_code_name[head_code]; |
| head_line = get_lineno (first); |
| } |
| else |
| head_name = "no-statement"; |
| |
| if (last) |
| { |
| end_code = TREE_CODE (last); |
| end_name = tree_code_name[end_code]; |
| end_line = get_lineno (last); |
| } |
| else |
| end_name = "no-statement"; |
| |
| fprintf (file, "node: { title: \"%d\" label: \"#%d\\n%s (%d)\\n%s (%d)\"}\n", |
| bb->index, bb->index, head_name, head_line, end_name, |
| end_line); |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (e->dest == EXIT_BLOCK_PTR) |
| fprintf (file, "edge: { sourcename: \"%d\" targetname: \"EXIT\"", bb->index); |
| else |
| fprintf (file, "edge: { sourcename: \"%d\" targetname: \"%d\"", bb->index, e->dest->index); |
| |
| if (e->flags & EDGE_FAKE) |
| fprintf (file, " priority: 10 linestyle: dotted"); |
| else |
| fprintf (file, " priority: 100 linestyle: solid"); |
| |
| fprintf (file, " }\n"); |
| } |
| |
| if (bb->next_bb != EXIT_BLOCK_PTR) |
| fputc ('\n', file); |
| } |
| |
| fputs ("}\n\n", file); |
| } |
| |
| |
| |
| /*--------------------------------------------------------------------------- |
| Miscellaneous helpers |
| ---------------------------------------------------------------------------*/ |
| |
| /* Return true if T represents a stmt that always transfers control. */ |
| |
| bool |
| is_ctrl_stmt (tree t) |
| { |
| return (TREE_CODE (t) == COND_EXPR |
| || TREE_CODE (t) == SWITCH_EXPR |
| || TREE_CODE (t) == GOTO_EXPR |
| || TREE_CODE (t) == RETURN_EXPR |
| || TREE_CODE (t) == RESX_EXPR); |
| } |
| |
| |
| /* Return true if T is a statement that may alter the flow of control |
| (e.g., a call to a non-returning function). */ |
| |
| bool |
| is_ctrl_altering_stmt (tree t) |
| { |
| tree call; |
| |
| gcc_assert (t); |
| call = get_call_expr_in (t); |
| if (call) |
| { |
| /* A non-pure/const CALL_EXPR alters flow control if the current |
| function has nonlocal labels. */ |
| if (TREE_SIDE_EFFECTS (call) && current_function_has_nonlocal_label) |
| return true; |
| |
| /* A CALL_EXPR also alters control flow if it does not return. */ |
| if (call_expr_flags (call) & ECF_NORETURN) |
| return true; |
| } |
| |
| /* OpenMP directives alter control flow. */ |
| if (OMP_DIRECTIVE_P (t)) |
| return true; |
| |
| /* If a statement can throw, it alters control flow. */ |
| return tree_can_throw_internal (t); |
| } |
| |
| |
| /* Return true if T is a computed goto. */ |
| |
| bool |
| computed_goto_p (tree t) |
| { |
| return (TREE_CODE (t) == GOTO_EXPR |
| && TREE_CODE (GOTO_DESTINATION (t)) != LABEL_DECL); |
| } |
| |
| |
| /* Return true if T is a simple local goto. */ |
| |
| bool |
| simple_goto_p (tree t) |
| { |
| return (TREE_CODE (t) == GOTO_EXPR |
| && TREE_CODE (GOTO_DESTINATION (t)) == LABEL_DECL); |
| } |
| |
| |
| /* Return true if T can make an abnormal transfer of control flow. |
| Transfers of control flow associated with EH are excluded. */ |
| |
| bool |
| tree_can_make_abnormal_goto (tree t) |
| { |
| if (computed_goto_p (t)) |
| return true; |
| if (TREE_CODE (t) == MODIFY_EXPR) |
| t = TREE_OPERAND (t, 1); |
| if (TREE_CODE (t) == WITH_SIZE_EXPR) |
| t = TREE_OPERAND (t, 0); |
| if (TREE_CODE (t) == CALL_EXPR) |
| return TREE_SIDE_EFFECTS (t) && current_function_has_nonlocal_label; |
| return false; |
| } |
| |
| |
| /* Return true if T should start a new basic block. PREV_T is the |
| statement preceding T. It is used when T is a label or a case label. |
| Labels should only start a new basic block if their previous statement |
| wasn't a label. Otherwise, sequence of labels would generate |
| unnecessary basic blocks that only contain a single label. */ |
| |
| static inline bool |
| stmt_starts_bb_p (tree t, tree prev_t) |
| { |
| if (t == NULL_TREE) |
| return false; |
| |
| /* LABEL_EXPRs start a new basic block only if the preceding |
| statement wasn't a label of the same type. This prevents the |
| creation of consecutive blocks that have nothing but a single |
| label. */ |
| if (TREE_CODE (t) == LABEL_EXPR) |
| { |
| /* Nonlocal and computed GOTO targets always start a new block. */ |
| if (DECL_NONLOCAL (LABEL_EXPR_LABEL (t)) |
| || FORCED_LABEL (LABEL_EXPR_LABEL (t))) |
| return true; |
| |
| if (prev_t && TREE_CODE (prev_t) == LABEL_EXPR) |
| { |
| if (DECL_NONLOCAL (LABEL_EXPR_LABEL (prev_t))) |
| return true; |
| |
| cfg_stats.num_merged_labels++; |
| return false; |
| } |
| else |
| return true; |
| } |
| |
| return false; |
| } |
| |
| |
| /* Return true if T should end a basic block. */ |
| |
| bool |
| stmt_ends_bb_p (tree t) |
| { |
| return is_ctrl_stmt (t) || is_ctrl_altering_stmt (t); |
| } |
| |
| |
| /* Add gotos that used to be represented implicitly in the CFG. */ |
| |
| void |
| disband_implicit_edges (void) |
| { |
| basic_block bb; |
| block_stmt_iterator last; |
| edge e; |
| edge_iterator ei; |
| tree stmt, label; |
| |
| FOR_EACH_BB (bb) |
| { |
| last = bsi_last (bb); |
| stmt = last_stmt (bb); |
| |
| if (stmt && TREE_CODE (stmt) == COND_EXPR) |
| { |
| /* Remove superfluous gotos from COND_EXPR branches. Moved |
| from cfg_remove_useless_stmts here since it violates the |
| invariants for tree--cfg correspondence and thus fits better |
| here where we do it anyway. */ |
| e = find_edge (bb, bb->next_bb); |
| if (e) |
| { |
| if (e->flags & EDGE_TRUE_VALUE) |
| COND_EXPR_THEN (stmt) = build_empty_stmt (); |
| else if (e->flags & EDGE_FALSE_VALUE) |
| COND_EXPR_ELSE (stmt) = build_empty_stmt (); |
| else |
| gcc_unreachable (); |
| e->flags |= EDGE_FALLTHRU; |
| } |
| |
| continue; |
| } |
| |
| if (stmt && TREE_CODE (stmt) == RETURN_EXPR) |
| { |
| /* Remove the RETURN_EXPR if we may fall though to the exit |
| instead. */ |
| gcc_assert (single_succ_p (bb)); |
| gcc_assert (single_succ (bb) == EXIT_BLOCK_PTR); |
| |
| if (bb->next_bb == EXIT_BLOCK_PTR |
| && !TREE_OPERAND (stmt, 0)) |
| { |
| bsi_remove (&last, true); |
| single_succ_edge (bb)->flags |= EDGE_FALLTHRU; |
| } |
| continue; |
| } |
| |
| /* There can be no fallthru edge if the last statement is a control |
| one. */ |
| if (stmt && is_ctrl_stmt (stmt)) |
| continue; |
| |
| /* Find a fallthru edge and emit the goto if necessary. */ |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if (e->flags & EDGE_FALLTHRU) |
| break; |
| |
| if (!e || e->dest == bb->next_bb) |
| continue; |
| |
| gcc_assert (e->dest != EXIT_BLOCK_PTR); |
| label = tree_block_label (e->dest); |
| |
| stmt = build1 (GOTO_EXPR, void_type_node, label); |
| #ifdef USE_MAPPED_LOCATION |
| SET_EXPR_LOCATION (stmt, e->goto_locus); |
| #else |
| SET_EXPR_LOCUS (stmt, e->goto_locus); |
| #endif |
| bsi_insert_after (&last, stmt, BSI_NEW_STMT); |
| e->flags &= ~EDGE_FALLTHRU; |
| } |
| } |
| |
| /* Remove block annotations and other datastructures. */ |
| |
| void |
| delete_tree_cfg_annotations (void) |
| { |
| label_to_block_map = NULL; |
| } |
| |
| |
| /* Return the first statement in basic block BB. */ |
| |
| tree |
| first_stmt (basic_block bb) |
| { |
| block_stmt_iterator i = bsi_start (bb); |
| return !bsi_end_p (i) ? bsi_stmt (i) : NULL_TREE; |
| } |
| |
| |
| /* Return the last statement in basic block BB. */ |
| |
| tree |
| last_stmt (basic_block bb) |
| { |
| block_stmt_iterator b = bsi_last (bb); |
| return !bsi_end_p (b) ? bsi_stmt (b) : NULL_TREE; |
| } |
| |
| |
| /* Return a pointer to the last statement in block BB. */ |
| |
| tree * |
| last_stmt_ptr (basic_block bb) |
| { |
| block_stmt_iterator last = bsi_last (bb); |
| return !bsi_end_p (last) ? bsi_stmt_ptr (last) : NULL; |
| } |
| |
| |
| /* Return the last statement of an otherwise empty block. Return NULL |
| if the block is totally empty, or if it contains more than one |
| statement. */ |
| |
| tree |
| last_and_only_stmt (basic_block bb) |
| { |
| block_stmt_iterator i = bsi_last (bb); |
| tree last, prev; |
| |
| if (bsi_end_p (i)) |
| return NULL_TREE; |
| |
| last = bsi_stmt (i); |
| bsi_prev (&i); |
| if (bsi_end_p (i)) |
| return last; |
| |
| /* Empty statements should no longer appear in the instruction stream. |
| Everything that might have appeared before should be deleted by |
| remove_useless_stmts, and the optimizers should just bsi_remove |
| instead of smashing with build_empty_stmt. |
| |
| Thus the only thing that should appear here in a block containing |
| one executable statement is a label. */ |
| prev = bsi_stmt (i); |
| if (TREE_CODE (prev) == LABEL_EXPR) |
| return last; |
| else |
| return NULL_TREE; |
| } |
| |
| |
| /* Mark BB as the basic block holding statement T. */ |
| |
| void |
| set_bb_for_stmt (tree t, basic_block bb) |
| { |
| if (TREE_CODE (t) == PHI_NODE) |
| PHI_BB (t) = bb; |
| else if (TREE_CODE (t) == STATEMENT_LIST) |
| { |
| tree_stmt_iterator i; |
| for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i)) |
| set_bb_for_stmt (tsi_stmt (i), bb); |
| } |
| else |
| { |
| stmt_ann_t ann = get_stmt_ann (t); |
| ann->bb = bb; |
| |
| /* If the statement is a label, add the label to block-to-labels map |
| so that we can speed up edge creation for GOTO_EXPRs. */ |
| if (TREE_CODE (t) == LABEL_EXPR) |
| { |
| int uid; |
| |
| t = LABEL_EXPR_LABEL (t); |
| uid = LABEL_DECL_UID (t); |
| if (uid == -1) |
| { |
| unsigned old_len = VEC_length (basic_block, label_to_block_map); |
| LABEL_DECL_UID (t) = uid = cfun->last_label_uid++; |
| if (old_len <= (unsigned) uid) |
| { |
| basic_block *addr; |
| unsigned new_len = 3 * uid / 2; |
| |
| VEC_safe_grow (basic_block, gc, label_to_block_map, |
| new_len); |
| addr = VEC_address (basic_block, label_to_block_map); |
| memset (&addr[old_len], |
| 0, sizeof (basic_block) * (new_len - old_len)); |
| } |
| } |
| else |
| /* We're moving an existing label. Make sure that we've |
| removed it from the old block. */ |
| gcc_assert (!bb |
| || !VEC_index (basic_block, label_to_block_map, uid)); |
| VEC_replace (basic_block, label_to_block_map, uid, bb); |
| } |
| } |
| } |
| |
| /* Faster version of set_bb_for_stmt that assume that statement is being moved |
| from one basic block to another. |
| For BB splitting we can run into quadratic case, so performance is quite |
| important and knowing that the tables are big enough, change_bb_for_stmt |
| can inline as leaf function. */ |
| static inline void |
| change_bb_for_stmt (tree t, basic_block bb) |
| { |
| get_stmt_ann (t)->bb = bb; |
| if (TREE_CODE (t) == LABEL_EXPR) |
| VEC_replace (basic_block, label_to_block_map, |
| LABEL_DECL_UID (LABEL_EXPR_LABEL (t)), bb); |
| } |
| |
| /* Finds iterator for STMT. */ |
| |
| extern block_stmt_iterator |
| bsi_for_stmt (tree stmt) |
| { |
| block_stmt_iterator bsi; |
| |
| for (bsi = bsi_start (bb_for_stmt (stmt)); !bsi_end_p (bsi); bsi_next (&bsi)) |
| if (bsi_stmt (bsi) == stmt) |
| return bsi; |
| |
| gcc_unreachable (); |
| } |
| |
| /* Mark statement T as modified, and update it. */ |
| static inline void |
| update_modified_stmts (tree t) |
| { |
| if (TREE_CODE (t) == STATEMENT_LIST) |
| { |
| tree_stmt_iterator i; |
| tree stmt; |
| for (i = tsi_start (t); !tsi_end_p (i); tsi_next (&i)) |
| { |
| stmt = tsi_stmt (i); |
| update_stmt_if_modified (stmt); |
| } |
| } |
| else |
| update_stmt_if_modified (t); |
| } |
| |
| /* Insert statement (or statement list) T before the statement |
| pointed-to by iterator I. M specifies how to update iterator I |
| after insertion (see enum bsi_iterator_update). */ |
| |
| void |
| bsi_insert_before (block_stmt_iterator *i, tree t, enum bsi_iterator_update m) |
| { |
| set_bb_for_stmt (t, i->bb); |
| update_modified_stmts (t); |
| tsi_link_before (&i->tsi, t, m); |
| } |
| |
| |
| /* Insert statement (or statement list) T after the statement |
| pointed-to by iterator I. M specifies how to update iterator I |
| after insertion (see enum bsi_iterator_update). */ |
| |
| void |
| bsi_insert_after (block_stmt_iterator *i, tree t, enum bsi_iterator_update m) |
| { |
| set_bb_for_stmt (t, i->bb); |
| update_modified_stmts (t); |
| tsi_link_after (&i->tsi, t, m); |
| } |
| |
| |
| /* Remove the statement pointed to by iterator I. The iterator is updated |
| to the next statement. |
| |
| When REMOVE_EH_INFO is true we remove the statement pointed to by |
| iterator I from the EH tables. Otherwise we do not modify the EH |
| tables. |
| |
| Generally, REMOVE_EH_INFO should be true when the statement is going to |
| be removed from the IL and not reinserted elsewhere. */ |
| |
| void |
| bsi_remove (block_stmt_iterator *i, bool remove_eh_info) |
| { |
| tree t = bsi_stmt (*i); |
| set_bb_for_stmt (t, NULL); |
| delink_stmt_imm_use (t); |
| tsi_delink (&i->tsi); |
| mark_stmt_modified (t); |
| if (remove_eh_info) |
| remove_stmt_from_eh_region (t); |
| } |
| |
| |
| /* Move the statement at FROM so it comes right after the statement at TO. */ |
| |
| void |
| bsi_move_after (block_stmt_iterator *from, block_stmt_iterator *to) |
| { |
| tree stmt = bsi_stmt (*from); |
| bsi_remove (from, false); |
| bsi_insert_after (to, stmt, BSI_SAME_STMT); |
| } |
| |
| |
| /* Move the statement at FROM so it comes right before the statement at TO. */ |
| |
| void |
| bsi_move_before (block_stmt_iterator *from, block_stmt_iterator *to) |
| { |
| tree stmt = bsi_stmt (*from); |
| bsi_remove (from, false); |
| bsi_insert_before (to, stmt, BSI_SAME_STMT); |
| } |
| |
| |
| /* Move the statement at FROM to the end of basic block BB. */ |
| |
| void |
| bsi_move_to_bb_end (block_stmt_iterator *from, basic_block bb) |
| { |
| block_stmt_iterator last = bsi_last (bb); |
| |
| /* Have to check bsi_end_p because it could be an empty block. */ |
| if (!bsi_end_p (last) && is_ctrl_stmt (bsi_stmt (last))) |
| bsi_move_before (from, &last); |
| else |
| bsi_move_after (from, &last); |
| } |
| |
| |
| /* Replace the contents of the statement pointed to by iterator BSI |
| with STMT. If UPDATE_EH_INFO is true, the exception handling |
| information of the original statement is moved to the new statement. */ |
| |
| void |
| bsi_replace (const block_stmt_iterator *bsi, tree stmt, bool update_eh_info) |
| { |
| int eh_region; |
| tree orig_stmt = bsi_stmt (*bsi); |
| |
| SET_EXPR_LOCUS (stmt, EXPR_LOCUS (orig_stmt)); |
| set_bb_for_stmt (stmt, bsi->bb); |
| |
| /* Preserve EH region information from the original statement, if |
| requested by the caller. */ |
| if (update_eh_info) |
| { |
| eh_region = lookup_stmt_eh_region (orig_stmt); |
| if (eh_region >= 0) |
| { |
| remove_stmt_from_eh_region (orig_stmt); |
| add_stmt_to_eh_region (stmt, eh_region); |
| } |
| } |
| |
| delink_stmt_imm_use (orig_stmt); |
| *bsi_stmt_ptr (*bsi) = stmt; |
| mark_stmt_modified (stmt); |
| update_modified_stmts (stmt); |
| } |
| |
| |
| /* Insert the statement pointed-to by BSI into edge E. Every attempt |
| is made to place the statement in an existing basic block, but |
| sometimes that isn't possible. When it isn't possible, the edge is |
| split and the statement is added to the new block. |
| |
| In all cases, the returned *BSI points to the correct location. The |
| return value is true if insertion should be done after the location, |
| or false if it should be done before the location. If new basic block |
| has to be created, it is stored in *NEW_BB. */ |
| |
| static bool |
| tree_find_edge_insert_loc (edge e, block_stmt_iterator *bsi, |
| basic_block *new_bb) |
| { |
| basic_block dest, src; |
| tree tmp; |
| |
| dest = e->dest; |
| restart: |
| |
| /* If the destination has one predecessor which has no PHI nodes, |
| insert there. Except for the exit block. |
| |
| The requirement for no PHI nodes could be relaxed. Basically we |
| would have to examine the PHIs to prove that none of them used |
| the value set by the statement we want to insert on E. That |
| hardly seems worth the effort. */ |
| if (single_pred_p (dest) |
| && ! phi_nodes (dest) |
| && dest != EXIT_BLOCK_PTR) |
| { |
| *bsi = bsi_start (dest); |
| if (bsi_end_p (*bsi)) |
| return true; |
| |
| /* Make sure we insert after any leading labels. */ |
| tmp = bsi_stmt (*bsi); |
| while (TREE_CODE (tmp) == LABEL_EXPR) |
| { |
| bsi_next (bsi); |
| if (bsi_end_p (*bsi)) |
| break; |
| tmp = bsi_stmt (*bsi); |
| } |
| |
| if (bsi_end_p (*bsi)) |
| { |
| *bsi = bsi_last (dest); |
| return true; |
| } |
| else |
| return false; |
| } |
| |
| /* If the source has one successor, the edge is not abnormal and |
| the last statement does not end a basic block, insert there. |
| Except for the entry block. */ |
| src = e->src; |
| if ((e->flags & EDGE_ABNORMAL) == 0 |
| && single_succ_p (src) |
| && src != ENTRY_BLOCK_PTR) |
| { |
| *bsi = bsi_last (src); |
| if (bsi_end_p (*bsi)) |
| return true; |
| |
| tmp = bsi_stmt (*bsi); |
| if (!stmt_ends_bb_p (tmp)) |
| return true; |
| |
| /* Insert code just before returning the value. We may need to decompose |
| the return in the case it contains non-trivial operand. */ |
| if (TREE_CODE (tmp) == RETURN_EXPR) |
| { |
| tree op = TREE_OPERAND (tmp, 0); |
| if (op && !is_gimple_val (op)) |
| { |
| gcc_assert (TREE_CODE (op) == MODIFY_EXPR); |
| bsi_insert_before (bsi, op, BSI_NEW_STMT); |
| TREE_OPERAND (tmp, 0) = TREE_OPERAND (op, 0); |
| } |
| bsi_prev (bsi); |
| return true; |
| } |
| } |
| |
| /* Otherwise, create a new basic block, and split this edge. */ |
| dest = split_edge (e); |
| if (new_bb) |
| *new_bb = dest; |
| e = single_pred_edge (dest); |
| goto restart; |
| } |
| |
| |
| /* This routine will commit all pending edge insertions, creating any new |
| basic blocks which are necessary. */ |
| |
| void |
| bsi_commit_edge_inserts (void) |
| { |
| basic_block bb; |
| edge e; |
| edge_iterator ei; |
| |
| bsi_commit_one_edge_insert (single_succ_edge (ENTRY_BLOCK_PTR), NULL); |
| |
| FOR_EACH_BB (bb) |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| bsi_commit_one_edge_insert (e, NULL); |
| } |
| |
| |
| /* Commit insertions pending at edge E. If a new block is created, set NEW_BB |
| to this block, otherwise set it to NULL. */ |
| |
| void |
| bsi_commit_one_edge_insert (edge e, basic_block *new_bb) |
| { |
| if (new_bb) |
| *new_bb = NULL; |
| if (PENDING_STMT (e)) |
| { |
| block_stmt_iterator bsi; |
| tree stmt = PENDING_STMT (e); |
| |
| PENDING_STMT (e) = NULL_TREE; |
| |
| if (tree_find_edge_insert_loc (e, &bsi, new_bb)) |
| bsi_insert_after (&bsi, stmt, BSI_NEW_STMT); |
| else |
| bsi_insert_before (&bsi, stmt, BSI_NEW_STMT); |
| } |
| } |
| |
| |
| /* Add STMT to the pending list of edge E. No actual insertion is |
| made until a call to bsi_commit_edge_inserts () is made. */ |
| |
| void |
| bsi_insert_on_edge (edge e, tree stmt) |
| { |
| append_to_statement_list (stmt, &PENDING_STMT (e)); |
| } |
| |
| /* Similar to bsi_insert_on_edge+bsi_commit_edge_inserts. If a new |
| block has to be created, it is returned. */ |
| |
| basic_block |
| bsi_insert_on_edge_immediate (edge e, tree stmt) |
| { |
| block_stmt_iterator bsi; |
| basic_block new_bb = NULL; |
| |
| gcc_assert (!PENDING_STMT (e)); |
| |
| if (tree_find_edge_insert_loc (e, &bsi, &new_bb)) |
| bsi_insert_after (&bsi, stmt, BSI_NEW_STMT); |
| else |
| bsi_insert_before (&bsi, stmt, BSI_NEW_STMT); |
| |
| return new_bb; |
| } |
| |
| /*--------------------------------------------------------------------------- |
| Tree specific functions for CFG manipulation |
| ---------------------------------------------------------------------------*/ |
| |
| /* Reinstall those PHI arguments queued in OLD_EDGE to NEW_EDGE. */ |
| |
| static void |
| reinstall_phi_args (edge new_edge, edge old_edge) |
| { |
| tree var, phi; |
| |
| if (!PENDING_STMT (old_edge)) |
| return; |
| |
| for (var = PENDING_STMT (old_edge), phi = phi_nodes (new_edge->dest); |
| var && phi; |
| var = TREE_CHAIN (var), phi = PHI_CHAIN (phi)) |
| { |
| tree result = TREE_PURPOSE (var); |
| tree arg = TREE_VALUE (var); |
| |
| gcc_assert (result == PHI_RESULT (phi)); |
| |
| add_phi_arg (phi, arg, new_edge); |
| } |
| |
| PENDING_STMT (old_edge) = NULL; |
| } |
| |
| /* Returns the basic block after which the new basic block created |
| by splitting edge EDGE_IN should be placed. Tries to keep the new block |
| near its "logical" location. This is of most help to humans looking |
| at debugging dumps. */ |
| |
| static basic_block |
| split_edge_bb_loc (edge edge_in) |
| { |
| basic_block dest = edge_in->dest; |
| |
| if (dest->prev_bb && find_edge (dest->prev_bb, dest)) |
| return edge_in->src; |
| else |
| return dest->prev_bb; |
| } |
| |
| /* Split a (typically critical) edge EDGE_IN. Return the new block. |
| Abort on abnormal edges. */ |
| |
| static basic_block |
| tree_split_edge (edge edge_in) |
| { |
| basic_block new_bb, after_bb, dest; |
| edge new_edge, e; |
| |
| /* Abnormal edges cannot be split. */ |
| gcc_assert (!(edge_in->flags & EDGE_ABNORMAL)); |
| |
| dest = edge_in->dest; |
| |
| after_bb = split_edge_bb_loc (edge_in); |
| |
| new_bb = create_empty_bb (after_bb); |
| new_bb->frequency = EDGE_FREQUENCY (edge_in); |
| new_bb->count = edge_in->count; |
| new_edge = make_edge (new_bb, dest, EDGE_FALLTHRU); |
| new_edge->probability = REG_BR_PROB_BASE; |
| new_edge->count = edge_in->count; |
| |
| e = redirect_edge_and_branch (edge_in, new_bb); |
| gcc_assert (e); |
| reinstall_phi_args (new_edge, e); |
| |
| return new_bb; |
| } |
| |
| |
| /* Return true when BB has label LABEL in it. */ |
| |
| static bool |
| has_label_p (basic_block bb, tree label) |
| { |
| block_stmt_iterator bsi; |
| |
| for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| tree stmt = bsi_stmt (bsi); |
| |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| return false; |
| if (LABEL_EXPR_LABEL (stmt) == label) |
| return true; |
| } |
| return false; |
| } |
| |
| |
| /* Callback for walk_tree, check that all elements with address taken are |
| properly noticed as such. The DATA is an int* that is 1 if TP was seen |
| inside a PHI node. */ |
| |
| static tree |
| verify_expr (tree *tp, int *walk_subtrees, void *data ATTRIBUTE_UNUSED) |
| { |
| tree t = *tp, x; |
| bool in_phi = (data != NULL); |
| |
| if (TYPE_P (t)) |
| *walk_subtrees = 0; |
| |
| /* Check operand N for being valid GIMPLE and give error MSG if not. */ |
| #define CHECK_OP(N, MSG) \ |
| do { if (!is_gimple_val (TREE_OPERAND (t, N))) \ |
| { error (MSG); return TREE_OPERAND (t, N); }} while (0) |
| |
| switch (TREE_CODE (t)) |
| { |
| case SSA_NAME: |
| if (SSA_NAME_IN_FREE_LIST (t)) |
| { |
| error ("SSA name in freelist but still referenced"); |
| return *tp; |
| } |
| break; |
| |
| case ASSERT_EXPR: |
| x = fold (ASSERT_EXPR_COND (t)); |
| if (x == boolean_false_node) |
| { |
| error ("ASSERT_EXPR with an always-false condition"); |
| return *tp; |
| } |
| break; |
| |
| case MODIFY_EXPR: |
| x = TREE_OPERAND (t, 0); |
| if (TREE_CODE (x) == BIT_FIELD_REF |
| && is_gimple_reg (TREE_OPERAND (x, 0))) |
| { |
| error ("GIMPLE register modified with BIT_FIELD_REF"); |
| return t; |
| } |
| break; |
| |
| case ADDR_EXPR: |
| { |
| bool old_invariant; |
| bool old_constant; |
| bool old_side_effects; |
| bool new_invariant; |
| bool new_constant; |
| bool new_side_effects; |
| |
| /* ??? tree-ssa-alias.c may have overlooked dead PHI nodes, missing |
| dead PHIs that take the address of something. But if the PHI |
| result is dead, the fact that it takes the address of anything |
| is irrelevant. Because we can not tell from here if a PHI result |
| is dead, we just skip this check for PHIs altogether. This means |
| we may be missing "valid" checks, but what can you do? |
| This was PR19217. */ |
| if (in_phi) |
| break; |
| |
| old_invariant = TREE_INVARIANT (t); |
| old_constant = TREE_CONSTANT (t); |
| old_side_effects = TREE_SIDE_EFFECTS (t); |
| |
| recompute_tree_invariant_for_addr_expr (t); |
| new_invariant = TREE_INVARIANT (t); |
| new_side_effects = TREE_SIDE_EFFECTS (t); |
| new_constant = TREE_CONSTANT (t); |
| |
| if (old_invariant != new_invariant) |
| { |
| error ("invariant not recomputed when ADDR_EXPR changed"); |
| return t; |
| } |
| |
| if (old_constant != new_constant) |
| { |
| error ("constant not recomputed when ADDR_EXPR changed"); |
| return t; |
| } |
| if (old_side_effects != new_side_effects) |
| { |
| error ("side effects not recomputed when ADDR_EXPR changed"); |
| return t; |
| } |
| |
| /* Skip any references (they will be checked when we recurse down the |
| tree) and ensure that any variable used as a prefix is marked |
| addressable. */ |
| for (x = TREE_OPERAND (t, 0); |
| handled_component_p (x); |
| x = TREE_OPERAND (x, 0)) |
| ; |
| |
| if (TREE_CODE (x) != VAR_DECL && TREE_CODE (x) != PARM_DECL) |
| return NULL; |
| if (!TREE_ADDRESSABLE (x)) |
| { |
| error ("address taken, but ADDRESSABLE bit not set"); |
| return x; |
| } |
| break; |
| } |
| |
| case COND_EXPR: |
| x = COND_EXPR_COND (t); |
| if (TREE_CODE (TREE_TYPE (x)) != BOOLEAN_TYPE) |
| { |
| error ("non-boolean used in condition"); |
| return x; |
| } |
| if (!is_gimple_condexpr (x)) |
| { |
| error ("invalid conditional operand"); |
| return x; |
| } |
| break; |
| |
| case NOP_EXPR: |
| case CONVERT_EXPR: |
| case FIX_TRUNC_EXPR: |
| case FIX_CEIL_EXPR: |
| case FIX_FLOOR_EXPR: |
| case FIX_ROUND_EXPR: |
| case FLOAT_EXPR: |
| case NEGATE_EXPR: |
| case ABS_EXPR: |
| case BIT_NOT_EXPR: |
| case NON_LVALUE_EXPR: |
| case TRUTH_NOT_EXPR: |
| CHECK_OP (0, "invalid operand to unary operator"); |
| break; |
| |
| case REALPART_EXPR: |
| case IMAGPART_EXPR: |
| case COMPONENT_REF: |
| case ARRAY_REF: |
| case ARRAY_RANGE_REF: |
| case BIT_FIELD_REF: |
| case VIEW_CONVERT_EXPR: |
| /* We have a nest of references. Verify that each of the operands |
| that determine where to reference is either a constant or a variable, |
| verify that the base is valid, and then show we've already checked |
| the subtrees. */ |
| while (handled_component_p (t)) |
| { |
| if (TREE_CODE (t) == COMPONENT_REF && TREE_OPERAND (t, 2)) |
| CHECK_OP (2, "invalid COMPONENT_REF offset operator"); |
| else if (TREE_CODE (t) == ARRAY_REF |
| || TREE_CODE (t) == ARRAY_RANGE_REF) |
| { |
| CHECK_OP (1, "invalid array index"); |
| if (TREE_OPERAND (t, 2)) |
| CHECK_OP (2, "invalid array lower bound"); |
| if (TREE_OPERAND (t, 3)) |
| CHECK_OP (3, "invalid array stride"); |
| } |
| else if (TREE_CODE (t) == BIT_FIELD_REF) |
| { |
| CHECK_OP (1, "invalid operand to BIT_FIELD_REF"); |
| CHECK_OP (2, "invalid operand to BIT_FIELD_REF"); |
| } |
| |
| t = TREE_OPERAND (t, 0); |
| } |
| |
| if (!CONSTANT_CLASS_P (t) && !is_gimple_lvalue (t)) |
| { |
| error ("invalid reference prefix"); |
| return t; |
| } |
| *walk_subtrees = 0; |
| break; |
| |
| case LT_EXPR: |
| case LE_EXPR: |
| case GT_EXPR: |
| case GE_EXPR: |
| case EQ_EXPR: |
| case NE_EXPR: |
| case UNORDERED_EXPR: |
| case ORDERED_EXPR: |
| case UNLT_EXPR: |
| case UNLE_EXPR: |
| case UNGT_EXPR: |
| case UNGE_EXPR: |
| case UNEQ_EXPR: |
| case LTGT_EXPR: |
| case PLUS_EXPR: |
| case MINUS_EXPR: |
| case MULT_EXPR: |
| case TRUNC_DIV_EXPR: |
| case CEIL_DIV_EXPR: |
| case FLOOR_DIV_EXPR: |
| case ROUND_DIV_EXPR: |
| case TRUNC_MOD_EXPR: |
| case CEIL_MOD_EXPR: |
| case FLOOR_MOD_EXPR: |
| case ROUND_MOD_EXPR: |
| case RDIV_EXPR: |
| case EXACT_DIV_EXPR: |
| case MIN_EXPR: |
| case MAX_EXPR: |
| case LSHIFT_EXPR: |
| case RSHIFT_EXPR: |
| case LROTATE_EXPR: |
| case RROTATE_EXPR: |
| case BIT_IOR_EXPR: |
| case BIT_XOR_EXPR: |
| case BIT_AND_EXPR: |
| CHECK_OP (0, "invalid operand to binary operator"); |
| CHECK_OP (1, "invalid operand to binary operator"); |
| break; |
| |
| case CONSTRUCTOR: |
| if (TREE_CONSTANT (t) && TREE_CODE (TREE_TYPE (t)) == VECTOR_TYPE) |
| *walk_subtrees = 0; |
| break; |
| |
| default: |
| break; |
| } |
| return NULL; |
| |
| #undef CHECK_OP |
| } |
| |
| |
| /* Verify STMT, return true if STMT is not in GIMPLE form. |
| TODO: Implement type checking. */ |
| |
| static bool |
| verify_stmt (tree stmt, bool last_in_block) |
| { |
| tree addr; |
| |
| if (OMP_DIRECTIVE_P (stmt)) |
| { |
| /* OpenMP directives are validated by the FE and never operated |
| on by the optimizers. Furthermore, OMP_FOR may contain |
| non-gimple expressions when the main index variable has had |
| its address taken. This does not affect the loop itself |
| because the header of an OMP_FOR is merely used to determine |
| how to setup the parallel iteration. */ |
| return false; |
| } |
| |
| if (!is_gimple_stmt (stmt)) |
| { |
| error ("is not a valid GIMPLE statement"); |
| goto fail; |
| } |
| |
| addr = walk_tree (&stmt, verify_expr, NULL, NULL); |
| if (addr) |
| { |
| debug_generic_stmt (addr); |
| return true; |
| } |
| |
| /* If the statement is marked as part of an EH region, then it is |
| expected that the statement could throw. Verify that when we |
| have optimizations that simplify statements such that we prove |
| that they cannot throw, that we update other data structures |
| to match. */ |
| if (lookup_stmt_eh_region (stmt) >= 0) |
| { |
| if (!tree_could_throw_p (stmt)) |
| { |
| error ("statement marked for throw, but doesn%'t"); |
| goto fail; |
| } |
| if (!last_in_block && tree_can_throw_internal (stmt)) |
| { |
| error ("statement marked for throw in middle of block"); |
| goto fail; |
| } |
| } |
| |
| return false; |
| |
| fail: |
| debug_generic_stmt (stmt); |
| return true; |
| } |
| |
| |
| /* Return true when the T can be shared. */ |
| |
| static bool |
| tree_node_can_be_shared (tree t) |
| { |
| if (IS_TYPE_OR_DECL_P (t) |
| || is_gimple_min_invariant (t) |
| || TREE_CODE (t) == SSA_NAME |
| || t == error_mark_node |
| || TREE_CODE (t) == IDENTIFIER_NODE) |
| return true; |
| |
| if (TREE_CODE (t) == CASE_LABEL_EXPR) |
| return true; |
| |
| while (((TREE_CODE (t) == ARRAY_REF || TREE_CODE (t) == ARRAY_RANGE_REF) |
| && is_gimple_min_invariant (TREE_OPERAND (t, 1))) |
| || TREE_CODE (t) == COMPONENT_REF |
| || TREE_CODE (t) == REALPART_EXPR |
| || TREE_CODE (t) == IMAGPART_EXPR) |
| t = TREE_OPERAND (t, 0); |
| |
| if (DECL_P (t)) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Called via walk_trees. Verify tree sharing. */ |
| |
| static tree |
| verify_node_sharing (tree * tp, int *walk_subtrees, void *data) |
| { |
| htab_t htab = (htab_t) data; |
| void **slot; |
| |
| if (tree_node_can_be_shared (*tp)) |
| { |
| *walk_subtrees = false; |
| return NULL; |
| } |
| |
| slot = htab_find_slot (htab, *tp, INSERT); |
| if (*slot) |
| return (tree) *slot; |
| *slot = *tp; |
| |
| return NULL; |
| } |
| |
| |
| /* Verify the GIMPLE statement chain. */ |
| |
| void |
| verify_stmts (void) |
| { |
| basic_block bb; |
| block_stmt_iterator bsi; |
| bool err = false; |
| htab_t htab; |
| tree addr; |
| |
| timevar_push (TV_TREE_STMT_VERIFY); |
| htab = htab_create (37, htab_hash_pointer, htab_eq_pointer, NULL); |
| |
| FOR_EACH_BB (bb) |
| { |
| tree phi; |
| int i; |
| |
| for (phi = phi_nodes (bb); phi; phi = PHI_CHAIN (phi)) |
| { |
| int phi_num_args = PHI_NUM_ARGS (phi); |
| |
| if (bb_for_stmt (phi) != bb) |
| { |
| error ("bb_for_stmt (phi) is set to a wrong basic block"); |
| err |= true; |
| } |
| |
| for (i = 0; i < phi_num_args; i++) |
| { |
| tree t = PHI_ARG_DEF (phi, i); |
| tree addr; |
| |
| /* Addressable variables do have SSA_NAMEs but they |
| are not considered gimple values. */ |
| if (TREE_CODE (t) != SSA_NAME |
| && TREE_CODE (t) != FUNCTION_DECL |
| && !is_gimple_val (t)) |
| { |
| error ("PHI def is not a GIMPLE value"); |
| debug_generic_stmt (phi); |
| debug_generic_stmt (t); |
| err |= true; |
| } |
| |
| addr = walk_tree (&t, verify_expr, (void *) 1, NULL); |
| if (addr) |
| { |
| debug_generic_stmt (addr); |
| err |= true; |
| } |
| |
| addr = walk_tree (&t, verify_node_sharing, htab, NULL); |
| if (addr) |
| { |
| error ("incorrect sharing of tree nodes"); |
| debug_generic_stmt (phi); |
| debug_generic_stmt (addr); |
| err |= true; |
| } |
| } |
| } |
| |
| for (bsi = bsi_start (bb); !bsi_end_p (bsi); ) |
| { |
| tree stmt = bsi_stmt (bsi); |
| |
| if (bb_for_stmt (stmt) != bb) |
| { |
| error ("bb_for_stmt (stmt) is set to a wrong basic block"); |
| err |= true; |
| } |
| |
| bsi_next (&bsi); |
| err |= verify_stmt (stmt, bsi_end_p (bsi)); |
| addr = walk_tree (&stmt, verify_node_sharing, htab, NULL); |
| if (addr) |
| { |
| error ("incorrect sharing of tree nodes"); |
| debug_generic_stmt (stmt); |
| debug_generic_stmt (addr); |
| err |= true; |
| } |
| } |
| } |
| |
| if (err) |
| internal_error ("verify_stmts failed"); |
| |
| htab_delete (htab); |
| timevar_pop (TV_TREE_STMT_VERIFY); |
| } |
| |
| |
| /* Verifies that the flow information is OK. */ |
| |
| static int |
| tree_verify_flow_info (void) |
| { |
| int err = 0; |
| basic_block bb; |
| block_stmt_iterator bsi; |
| tree stmt; |
| edge e; |
| edge_iterator ei; |
| |
| if (ENTRY_BLOCK_PTR->stmt_list) |
| { |
| error ("ENTRY_BLOCK has a statement list associated with it"); |
| err = 1; |
| } |
| |
| if (EXIT_BLOCK_PTR->stmt_list) |
| { |
| error ("EXIT_BLOCK has a statement list associated with it"); |
| err = 1; |
| } |
| |
| FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR->preds) |
| if (e->flags & EDGE_FALLTHRU) |
| { |
| error ("fallthru to exit from bb %d", e->src->index); |
| err = 1; |
| } |
| |
| FOR_EACH_BB (bb) |
| { |
| bool found_ctrl_stmt = false; |
| |
| stmt = NULL_TREE; |
| |
| /* Skip labels on the start of basic block. */ |
| for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| tree prev_stmt = stmt; |
| |
| stmt = bsi_stmt (bsi); |
| |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| break; |
| |
| if (prev_stmt && DECL_NONLOCAL (LABEL_EXPR_LABEL (stmt))) |
| { |
| error ("nonlocal label "); |
| print_generic_expr (stderr, LABEL_EXPR_LABEL (stmt), 0); |
| fprintf (stderr, " is not first in a sequence of labels in bb %d", |
| bb->index); |
| err = 1; |
| } |
| |
| if (label_to_block (LABEL_EXPR_LABEL (stmt)) != bb) |
| { |
| error ("label "); |
| print_generic_expr (stderr, LABEL_EXPR_LABEL (stmt), 0); |
| fprintf (stderr, " to block does not match in bb %d", |
| bb->index); |
| err = 1; |
| } |
| |
| if (decl_function_context (LABEL_EXPR_LABEL (stmt)) |
| != current_function_decl) |
| { |
| error ("label "); |
| print_generic_expr (stderr, LABEL_EXPR_LABEL (stmt), 0); |
| fprintf (stderr, " has incorrect context in bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| |
| /* Verify that body of basic block BB is free of control flow. */ |
| for (; !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| tree stmt = bsi_stmt (bsi); |
| |
| if (found_ctrl_stmt) |
| { |
| error ("control flow in the middle of basic block %d", |
| bb->index); |
| err = 1; |
| } |
| |
| if (stmt_ends_bb_p (stmt)) |
| found_ctrl_stmt = true; |
| |
| if (TREE_CODE (stmt) == LABEL_EXPR) |
| { |
| error ("label "); |
| print_generic_expr (stderr, LABEL_EXPR_LABEL (stmt), 0); |
| fprintf (stderr, " in the middle of basic block %d", bb->index); |
| err = 1; |
| } |
| } |
| |
| bsi = bsi_last (bb); |
| if (bsi_end_p (bsi)) |
| continue; |
| |
| stmt = bsi_stmt (bsi); |
| |
| err |= verify_eh_edges (stmt); |
| |
| if (is_ctrl_stmt (stmt)) |
| { |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if (e->flags & EDGE_FALLTHRU) |
| { |
| error ("fallthru edge after a control statement in bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| |
| if (TREE_CODE (stmt) != COND_EXPR) |
| { |
| /* Verify that there are no edges with EDGE_TRUE/FALSE_FLAG set |
| after anything else but if statement. */ |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if (e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)) |
| { |
| error ("true/false edge after a non-COND_EXPR in bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| |
| switch (TREE_CODE (stmt)) |
| { |
| case COND_EXPR: |
| { |
| edge true_edge; |
| edge false_edge; |
| if (TREE_CODE (COND_EXPR_THEN (stmt)) != GOTO_EXPR |
| || TREE_CODE (COND_EXPR_ELSE (stmt)) != GOTO_EXPR) |
| { |
| error ("structured COND_EXPR at the end of bb %d", bb->index); |
| err = 1; |
| } |
| |
| extract_true_false_edges_from_block (bb, &true_edge, &false_edge); |
| |
| if (!true_edge || !false_edge |
| || !(true_edge->flags & EDGE_TRUE_VALUE) |
| || !(false_edge->flags & EDGE_FALSE_VALUE) |
| || (true_edge->flags & (EDGE_FALLTHRU | EDGE_ABNORMAL)) |
| || (false_edge->flags & (EDGE_FALLTHRU | EDGE_ABNORMAL)) |
| || EDGE_COUNT (bb->succs) >= 3) |
| { |
| error ("wrong outgoing edge flags at end of bb %d", |
| bb->index); |
| err = 1; |
| } |
| |
| if (!has_label_p (true_edge->dest, |
| GOTO_DESTINATION (COND_EXPR_THEN (stmt)))) |
| { |
| error ("%<then%> label does not match edge at end of bb %d", |
| bb->index); |
| err = 1; |
| } |
| |
| if (!has_label_p (false_edge->dest, |
| GOTO_DESTINATION (COND_EXPR_ELSE (stmt)))) |
| { |
| error ("%<else%> label does not match edge at end of bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| break; |
| |
| case GOTO_EXPR: |
| if (simple_goto_p (stmt)) |
| { |
| error ("explicit goto at end of bb %d", bb->index); |
| err = 1; |
| } |
| else |
| { |
| /* FIXME. We should double check that the labels in the |
| destination blocks have their address taken. */ |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if ((e->flags & (EDGE_FALLTHRU | EDGE_TRUE_VALUE |
| | EDGE_FALSE_VALUE)) |
| || !(e->flags & EDGE_ABNORMAL)) |
| { |
| error ("wrong outgoing edge flags at end of bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| break; |
| |
| case RETURN_EXPR: |
| if (!single_succ_p (bb) |
| || (single_succ_edge (bb)->flags |
| & (EDGE_FALLTHRU | EDGE_ABNORMAL |
| | EDGE_TRUE_VALUE | EDGE_FALSE_VALUE))) |
| { |
| error ("wrong outgoing edge flags at end of bb %d", bb->index); |
| err = 1; |
| } |
| if (single_succ (bb) != EXIT_BLOCK_PTR) |
| { |
| error ("return edge does not point to exit in bb %d", |
| bb->index); |
| err = 1; |
| } |
| break; |
| |
| case SWITCH_EXPR: |
| { |
| tree prev; |
| edge e; |
| size_t i, n; |
| tree vec; |
| |
| vec = SWITCH_LABELS (stmt); |
| n = TREE_VEC_LENGTH (vec); |
| |
| /* Mark all the destination basic blocks. */ |
| for (i = 0; i < n; ++i) |
| { |
| tree lab = CASE_LABEL (TREE_VEC_ELT (vec, i)); |
| basic_block label_bb = label_to_block (lab); |
| |
| gcc_assert (!label_bb->aux || label_bb->aux == (void *)1); |
| label_bb->aux = (void *)1; |
| } |
| |
| /* Verify that the case labels are sorted. */ |
| prev = TREE_VEC_ELT (vec, 0); |
| for (i = 1; i < n - 1; ++i) |
| { |
| tree c = TREE_VEC_ELT (vec, i); |
| if (! CASE_LOW (c)) |
| { |
| error ("found default case not at end of case vector"); |
| err = 1; |
| continue; |
| } |
| if (! tree_int_cst_lt (CASE_LOW (prev), CASE_LOW (c))) |
| { |
| error ("case labels not sorted: "); |
| print_generic_expr (stderr, prev, 0); |
| fprintf (stderr," is greater than "); |
| print_generic_expr (stderr, c, 0); |
| fprintf (stderr," but comes before it.\n"); |
| err = 1; |
| } |
| prev = c; |
| } |
| if (CASE_LOW (TREE_VEC_ELT (vec, n - 1))) |
| { |
| error ("no default case found at end of case vector"); |
| err = 1; |
| } |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| if (!e->dest->aux) |
| { |
| error ("extra outgoing edge %d->%d", |
| bb->index, e->dest->index); |
| err = 1; |
| } |
| e->dest->aux = (void *)2; |
| if ((e->flags & (EDGE_FALLTHRU | EDGE_ABNORMAL |
| | EDGE_TRUE_VALUE | EDGE_FALSE_VALUE))) |
| { |
| error ("wrong outgoing edge flags at end of bb %d", |
| bb->index); |
| err = 1; |
| } |
| } |
| |
| /* Check that we have all of them. */ |
| for (i = 0; i < n; ++i) |
| { |
| tree lab = CASE_LABEL (TREE_VEC_ELT (vec, i)); |
| basic_block label_bb = label_to_block (lab); |
| |
| if (label_bb->aux != (void *)2) |
| { |
| error ("missing edge %i->%i", |
| bb->index, label_bb->index); |
| err = 1; |
| } |
| } |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| e->dest->aux = (void *)0; |
| } |
| |
| default: ; |
| } |
| } |
| |
| if (dom_computed[CDI_DOMINATORS] >= DOM_NO_FAST_QUERY) |
| verify_dominators (CDI_DOMINATORS); |
| |
| return err; |
| } |
| |
| |
| /* Updates phi nodes after creating a forwarder block joined |
| by edge FALLTHRU. */ |
| |
| static void |
| tree_make_forwarder_block (edge fallthru) |
| { |
| edge e; |
| edge_iterator ei; |
| basic_block dummy, bb; |
| tree phi, new_phi, var; |
| |
| dummy = fallthru->src; |
| bb = fallthru->dest; |
| |
| if (single_pred_p (bb)) |
| return; |
| |
| /* If we redirected a branch we must create new phi nodes at the |
| start of BB. */ |
| for (phi = phi_nodes (dummy); phi; phi = PHI_CHAIN (phi)) |
| { |
| var = PHI_RESULT (phi); |
| new_phi = create_phi_node (var, bb); |
| SSA_NAME_DEF_STMT (var) = new_phi; |
| SET_PHI_RESULT (phi, make_ssa_name (SSA_NAME_VAR (var), phi)); |
| add_phi_arg (new_phi, PHI_RESULT (phi), fallthru); |
| } |
| |
| /* Ensure that the PHI node chain is in the same order. */ |
| set_phi_nodes (bb, phi_reverse (phi_nodes (bb))); |
| |
| /* Add the arguments we have stored on edges. */ |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| if (e == fallthru) |
| continue; |
| |
| flush_pending_stmts (e); |
| } |
| } |
| |
| |
| /* Return a non-special label in the head of basic block BLOCK. |
| Create one if it doesn't exist. */ |
| |
| tree |
| tree_block_label (basic_block bb) |
| { |
| block_stmt_iterator i, s = bsi_start (bb); |
| bool first = true; |
| tree label, stmt; |
| |
| for (i = s; !bsi_end_p (i); first = false, bsi_next (&i)) |
| { |
| stmt = bsi_stmt (i); |
| if (TREE_CODE (stmt) != LABEL_EXPR) |
| break; |
| label = LABEL_EXPR_LABEL (stmt); |
| if (!DECL_NONLOCAL (label)) |
| { |
| if (!first) |
| bsi_move_before (&i, &s); |
| return label; |
| } |
| } |
| |
| label = create_artificial_label (); |
| stmt = build1 (LABEL_EXPR, void_type_node, label); |
| bsi_insert_before (&s, stmt, BSI_NEW_STMT); |
| return label; |
| } |
| |
| |
| /* Attempt to perform edge redirection by replacing a possibly complex |
| jump instruction by a goto or by removing the jump completely. |
| This can apply only if all edges now point to the same block. The |
| parameters and return values are equivalent to |
| redirect_edge_and_branch. */ |
| |
| static edge |
| tree_try_redirect_by_replacing_jump (edge e, basic_block target) |
| { |
| basic_block src = e->src; |
| block_stmt_iterator b; |
| tree stmt; |
| |
| /* We can replace or remove a complex jump only when we have exactly |
| two edges. */ |
| if (EDGE_COUNT (src->succs) != 2 |
| /* Verify that all targets will be TARGET. Specifically, the |
| edge that is not E must also go to TARGET. */ |
| || EDGE_SUCC (src, EDGE_SUCC (src, 0) == e)->dest != target) |
| return NULL; |
| |
| b = bsi_last (src); |
| if (bsi_end_p (b)) |
| return NULL; |
| stmt = bsi_stmt (b); |
| |
| if (TREE_CODE (stmt) == COND_EXPR |
| || TREE_CODE (stmt) == SWITCH_EXPR) |
| { |
| bsi_remove (&b, true); |
| e = ssa_redirect_edge (e, target); |
| e->flags = EDGE_FALLTHRU; |
| return e; |
| } |
| |
| return NULL; |
| } |
| |
| |
| /* Redirect E to DEST. Return NULL on failure. Otherwise, return the |
| edge representing the redirected branch. */ |
| |
| static edge |
| tree_redirect_edge_and_branch (edge e, basic_block dest) |
| { |
| basic_block bb = e->src; |
| block_stmt_iterator bsi; |
| edge ret; |
| tree label, stmt; |
| |
| if (e->flags & EDGE_ABNORMAL) |
| return NULL; |
| |
| if (e->src != ENTRY_BLOCK_PTR |
| && (ret = tree_try_redirect_by_replacing_jump (e, dest))) |
| return ret; |
| |
| if (e->dest == dest) |
| return NULL; |
| |
| label = tree_block_label (dest); |
| |
| bsi = bsi_last (bb); |
| stmt = bsi_end_p (bsi) ? NULL : bsi_stmt (bsi); |
| |
| switch (stmt ? TREE_CODE (stmt) : ERROR_MARK) |
| { |
| case COND_EXPR: |
| stmt = (e->flags & EDGE_TRUE_VALUE |
| ? COND_EXPR_THEN (stmt) |
| : COND_EXPR_ELSE (stmt)); |
| GOTO_DESTINATION (stmt) = label; |
| break; |
| |
| case GOTO_EXPR: |
| /* No non-abnormal edges should lead from a non-simple goto, and |
| simple ones should be represented implicitly. */ |
| gcc_unreachable (); |
| |
| case SWITCH_EXPR: |
| { |
| tree cases = get_cases_for_edge (e, stmt); |
| |
| /* If we have a list of cases associated with E, then use it |
| as it's a lot faster than walking the entire case vector. */ |
| if (cases) |
| { |
| edge e2 = find_edge (e->src, dest); |
| tree last, first; |
| |
| first = cases; |
| while (cases) |
| { |
| last = cases; |
| CASE_LABEL (cases) = label; |
| cases = TREE_CHAIN (cases); |
| } |
| |
| /* If there was already an edge in the CFG, then we need |
| to move all the cases associated with E to E2. */ |
| if (e2) |
| { |
| tree cases2 = get_cases_for_edge (e2, stmt); |
| |
| TREE_CHAIN (last) = TREE_CHAIN (cases2); |
| TREE_CHAIN (cases2) = first; |
| } |
| } |
| else |
| { |
| tree vec = SWITCH_LABELS (stmt); |
| size_t i, n = TREE_VEC_LENGTH (vec); |
| |
| for (i = 0; i < n; i++) |
| { |
| tree elt = TREE_VEC_ELT (vec, i); |
| |
| if (label_to_block (CASE_LABEL (elt)) == e->dest) |
| CASE_LABEL (elt) = label; |
| } |
| } |
| |
| break; |
| } |
| |
| case RETURN_EXPR: |
| bsi_remove (&bsi, true); |
| e->flags |= EDGE_FALLTHRU; |
| break; |
| |
| default: |
| /* Otherwise it must be a fallthru edge, and we don't need to |
| do anything besides redirecting it. */ |
| gcc_assert (e->flags & EDGE_FALLTHRU); |
| break; |
| } |
| |
| /* Update/insert PHI nodes as necessary. */ |
| |
| /* Now update the edges in the CFG. */ |
| e = ssa_redirect_edge (e, dest); |
| |
| return e; |
| } |
| |
| |
| /* Simple wrapper, as we can always redirect fallthru edges. */ |
| |
| static basic_block |
| tree_redirect_edge_and_branch_force (edge e, basic_block dest) |
| { |
| e = tree_redirect_edge_and_branch (e, dest); |
| gcc_assert (e); |
| |
| return NULL; |
| } |
| |
| |
| /* Splits basic block BB after statement STMT (but at least after the |
| labels). If STMT is NULL, BB is split just after the labels. */ |
| |
| static basic_block |
| tree_split_block (basic_block bb, void *stmt) |
| { |
| block_stmt_iterator bsi; |
| tree_stmt_iterator tsi_tgt; |
| tree act; |
| basic_block new_bb; |
| edge e; |
| edge_iterator ei; |
| |
| new_bb = create_empty_bb (bb); |
| |
| /* Redirect the outgoing edges. */ |
| new_bb->succs = bb->succs; |
| bb->succs = NULL; |
| FOR_EACH_EDGE (e, ei, new_bb->succs) |
| e->src = new_bb; |
| |
| if (stmt && TREE_CODE ((tree) stmt) == LABEL_EXPR) |
| stmt = NULL; |
| |
| /* Move everything from BSI to the new basic block. */ |
| for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| act = bsi_stmt (bsi); |
| if (TREE_CODE (act) == LABEL_EXPR) |
| continue; |
| |
| if (!stmt) |
| break; |
| |
| if (stmt == act) |
| { |
| bsi_next (&bsi); |
| break; |
| } |
| } |
| |
| if (bsi_end_p (bsi)) |
| return new_bb; |
| |
| /* Split the statement list - avoid re-creating new containers as this |
| brings ugly quadratic memory consumption in the inliner. |
| (We are still quadratic since we need to update stmt BB pointers, |
| sadly.) */ |
| new_bb->stmt_list = tsi_split_statement_list_before (&bsi.tsi); |
| for (tsi_tgt = tsi_start (new_bb->stmt_list); |
| !tsi_end_p (tsi_tgt); tsi_next (&tsi_tgt)) |
| change_bb_for_stmt (tsi_stmt (tsi_tgt), new_bb); |
| |
| return new_bb; |
| } |
| |
| |
| /* Moves basic block BB after block AFTER. */ |
| |
| static bool |
| tree_move_block_after (basic_block bb, basic_block after) |
| { |
| if (bb->prev_bb == after) |
| return true; |
| |
| unlink_block (bb); |
| link_block (bb, after); |
| |
| return true; |
| } |
| |
| |
| /* Return true if basic_block can be duplicated. */ |
| |
| static bool |
| tree_can_duplicate_bb_p (basic_block bb ATTRIBUTE_UNUSED) |
| { |
| return true; |
| } |
| |
| |
| /* Create a duplicate of the basic block BB. NOTE: This does not |
| preserve SSA form. */ |
| |
| static basic_block |
| tree_duplicate_bb (basic_block bb) |
| { |
| basic_block new_bb; |
| block_stmt_iterator bsi, bsi_tgt; |
| tree phi; |
| |
| new_bb = create_empty_bb (EXIT_BLOCK_PTR->prev_bb); |
| |
| /* Copy the PHI nodes. We ignore PHI node arguments here because |
| the incoming edges have not been setup yet. */ |
| for (phi = phi_nodes (bb); phi; phi = PHI_CHAIN (phi)) |
| { |
| tree copy = create_phi_node (PHI_RESULT (phi), new_bb); |
| create_new_def_for (PHI_RESULT (copy), copy, PHI_RESULT_PTR (copy)); |
| } |
| |
| /* Keep the chain of PHI nodes in the same order so that they can be |
| updated by ssa_redirect_edge. */ |
| set_phi_nodes (new_bb, phi_reverse (phi_nodes (new_bb))); |
| |
| bsi_tgt = bsi_start (new_bb); |
| for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi)) |
| { |
| def_operand_p def_p; |
| ssa_op_iter op_iter; |
| tree stmt, copy; |
| int region; |
| |
| stmt = bsi_stmt (bsi); |
| if (TREE_CODE (stmt) == LABEL_EXPR) |
| continue; |
| |
| /* Create a new copy of STMT and duplicate STMT's virtual |
| operands. */ |
| copy = unshare_expr (stmt); |
| bsi_insert_after (&bsi_tgt, copy, BSI_NEW_STMT); |
| copy_virtual_operands (copy, stmt); |
| region = lookup_stmt_eh_region (stmt); |
| if (region >= 0) |
| add_stmt_to_eh_region (copy, region); |
| |
| /* Create new names for all the definitions created by COPY and |
| add replacement mappings for each new name. */ |
| FOR_EACH_SSA_DEF_OPERAND (def_p, copy, op_iter, SSA_OP_ALL_DEFS) |
| create_new_def_for (DEF_FROM_PTR (def_p), copy, def_p); |
| } |
| |
| return new_bb; |
| } |
| |
| |
| /* Basic block BB_COPY was created by code duplication. Add phi node |
| arguments for edges going out of BB_COPY. The blocks that were |
| duplicated have BB_DUPLICATED set. */ |
| |
| void |
| add_phi_args_after_copy_bb (basic_block bb_copy) |
| { |
| basic_block bb, dest; |
| edge e, e_copy; |
| edge_iterator ei; |
| tree phi, phi_copy, phi_next, def; |
| |
| bb = get_bb_original (bb_copy); |
| |
| FOR_EACH_EDGE (e_copy, ei, bb_copy->succs) |
| { |
| if (!phi_nodes (e_copy->dest)) |
| continue; |
| |
| if (e_copy->dest->flags & BB_DUPLICATED) |
| dest = get_bb_original (e_copy->dest); |
| else |
| dest = e_copy->dest; |
| |
| e = find_edge (bb, dest); |
| if (!e) |
| { |
| /* During loop unrolling the target of the latch edge is copied. |
| In this case we are not looking for edge to dest, but to |
| duplicated block whose original was dest. */ |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if ((e->dest->flags & BB_DUPLICATED) |
| && get_bb_original (e->dest) == dest) |
| break; |
| |
| gcc_assert (e != NULL); |
| } |
| |
| for (phi = phi_nodes (e->dest), phi_copy = phi_nodes (e_copy->dest); |
| phi; |
| phi = phi_next, phi_copy = PHI_CHAIN (phi_copy)) |
| { |
| phi_next = PHI_CHAIN (phi); |
| def = PHI_ARG_DEF_FROM_EDGE (phi, e); |
| add_phi_arg (phi_copy, def, e_copy); |
| } |
| } |
| } |
| |
| /* Blocks in REGION_COPY array of length N_REGION were created by |
| duplication of basic blocks. Add phi node arguments for edges |
| going from these blocks. */ |
| |
| void |
| add_phi_args_after_copy (basic_block *region_copy, unsigned n_region) |
| { |
| unsigned i; |
| |
| for (i = 0; i < n_region; i++) |
| region_copy[i]->flags |= BB_DUPLICATED; |
| |
| for (i = 0; i < n_region; i++) |
| add_phi_args_after_copy_bb (region_copy[i]); |
| |
| for (i = 0; i < n_region; i++) |
| region_copy[i]->flags &= ~BB_DUPLICATED; |
| } |
| |
| /* Duplicates a REGION (set of N_REGION basic blocks) with just a single |
| important exit edge EXIT. By important we mean that no SSA name defined |
| inside region is live over the other exit edges of the region. All entry |
| edges to the region must go to ENTRY->dest. The edge ENTRY is redirected |
| to the duplicate of the region. SSA form, dominance and loop information |
| is updated. The new basic blocks are stored to REGION_COPY in the same |
| order as they had in REGION, provided that REGION_COPY is not NULL. |
| The function returns false if it is unable to copy the region, |
| true otherwise. */ |
| |
| bool |
| tree_duplicate_sese_region (edge entry, edge exit, |
| basic_block *region, unsigned n_region, |
| basic_block *region_copy) |
| { |
| unsigned i, n_doms; |
| bool free_region_copy = false, copying_header = false; |
| struct loop *loop = entry->dest->loop_father; |
| edge exit_copy; |
| basic_block *doms; |
| edge redirected; |
| int total_freq = 0, entry_freq = 0; |
| gcov_type total_count = 0, entry_count = 0; |
| |
| if (!can_copy_bbs_p (region, n_region)) |
| return false; |
| |
| /* Some sanity checking. Note that we do not check for all possible |
| missuses of the functions. I.e. if you ask to copy something weird, |
| it will work, but the state of structures probably will not be |
| correct. */ |
| for (i = 0; i < n_region; i++) |
| { |
| /* We do not handle subloops, i.e. all the blocks must belong to the |
| same loop. */ |
| if (region[i]->loop_father != loop) |
| return false; |
| |
| if (region[i] != entry->dest |
| && region[i] == loop->header) |
| return false; |
| } |
| |
| loop->copy = loop; |
| |
| /* In case the function is used for loop header copying (which is the primary |
| use), ensure that EXIT and its copy will be new latch and entry edges. */ |
| if (loop->header == entry->dest) |
| { |
| copying_header = true; |
| loop->copy = loop->outer; |
| |
| if (!dominated_by_p (CDI_DOMINATORS, loop->latch, exit->src)) |
| return false; |
| |
| for (i = 0; i < n_region; i++) |
| if (region[i] != exit->src |
| && dominated_by_p (CDI_DOMINATORS, region[i], exit->src)) |
| return false; |
| } |
| |
| if (!region_copy) |
| { |
| region_copy = XNEWVEC (basic_block, n_region); |
| free_region_copy = true; |
| } |
| |
| gcc_assert (!need_ssa_update_p ()); |
| |
| /* Record blocks outside the region that are dominated by something |
| inside. */ |
| doms = XNEWVEC (basic_block, n_basic_blocks); |
| initialize_original_copy_tables (); |
| |
| n_doms = get_dominated_by_region (CDI_DOMINATORS, region, n_region, doms); |
| |
| if (entry->dest->count) |
| { |
| total_count = entry->dest->count; |
| entry_count = entry->count; |
| /* Fix up corner cases, to avoid division by zero or creation of negative |
| frequencies. */ |
| if (entry_count > total_count) |
| entry_count = total_count; |
| } |
| else |
| { |
| total_freq = entry->dest->frequency; |
| entry_freq = EDGE_FREQUENCY (entry); |
| /* Fix up corner cases, to avoid division by zero or creation of negative |
| frequencies. */ |
| if (total_freq == 0) |
| total_freq = 1; |
| else if (entry_freq > total_freq) |
| entry_freq = total_freq; |
| } |
| |
| copy_bbs (region, n_region, region_copy, &exit, 1, &exit_copy, loop, |
| split_edge_bb_loc (entry)); |
| if (total_count) |
| { |
| scale_bbs_frequencies_gcov_type (region, n_region, |
| total_count - entry_count, |
| total_count); |
| scale_bbs_frequencies_gcov_type (region_copy, n_region, entry_count, |
| total_count); |
| } |
| else |
| { |
| scale_bbs_frequencies_int (region, n_region, total_freq - entry_freq, |
| total_freq); |
| scale_bbs_frequencies_int (region_copy, n_region, entry_freq, total_freq); |
| } |
| |
| if (copying_header) |
| { |
| loop->header = exit->dest; |
| loop->latch = exit->src; |
| } |
| |
| /* Redirect the entry and add the phi node arguments. */ |
| redirected = redirect_edge_and_branch (entry, get_bb_copy (entry->dest)); |
| gcc_assert (redirected != NULL); |
| flush_pending_stmts (entry); |
| |
| /* Concerning updating of dominators: We must recount dominators |
| for entry block and its copy. Anything that is outside of the |
| region, but was dominated by something inside needs recounting as |
| well. */ |
| set_immediate_dominator (CDI_DOMINATORS, entry->dest, entry->src); |
| doms[n_doms++] = get_bb_original (entry->dest); |
| iterate_fix_dominators (CDI_DOMINATORS, doms, n_doms); |
| free (doms); |
| |
| /* Add the other PHI node arguments. */ |
| add_phi_args_after_copy (region_copy, n_region); |
| |
| /* Update the SSA web. */ |
| update_ssa (TODO_update_ssa); |
| |
| if (free_region_copy) |
| free (region_copy); |
| |
| free_original_copy_tables (); |
| return true; |
| } |
| |
| /* |
| DEF_VEC_P(basic_block); |
| DEF_VEC_ALLOC_P(basic_block,heap); |
| */ |
| |
| /* Add all the blocks dominated by ENTRY to the array BBS_P. Stop |
| adding blocks when the dominator traversal reaches EXIT. This |
| function silently assumes that ENTRY strictly dominates EXIT. */ |
| |
| static void |
| gather_blocks_in_sese_region (basic_block entry, basic_block exit, |
| VEC(basic_block,heap) **bbs_p) |
| { |
| basic_block son; |
| |
| for (son = first_dom_son (CDI_DOMINATORS, entry); |
| son; |
| son = next_dom_son (CDI_DOMINATORS, son)) |
| { |
| VEC_safe_push (basic_block, heap, *bbs_p, son); |
| if (son != exit) |
| gather_blocks_in_sese_region (son, exit, bbs_p); |
| } |
| } |
| |
| |
| struct move_stmt_d |
| { |
| tree block; |
| tree from_context; |
| tree to_context; |
| bitmap vars_to_remove; |
| htab_t new_label_map; |
| bool remap_decls_p; |
| }; |
| |
| /* Helper for move_block_to_fn. Set TREE_BLOCK in every expression |
| contained in *TP and change the DECL_CONTEXT of every local |
| variable referenced in *TP. */ |
| |
| static tree |
| move_stmt_r (tree *tp, int *walk_subtrees, void *data) |
| { |
| struct move_stmt_d *p = (struct move_stmt_d *) data; |
| tree t = *tp; |
| |
| if (p->block && IS_EXPR_CODE_CLASS (TREE_CODE_CLASS (TREE_CODE (t)))) |
| TREE_BLOCK (t) = p->block; |
| |
| if (OMP_DIRECTIVE_P (t) |
| && TREE_CODE (t) != OMP_RETURN |
| && TREE_CODE (t) != OMP_CONTINUE) |
| { |
| /* Do not remap variables inside OMP directives. Variables |
| referenced in clauses and directive header belong to the |
| parent function and should not be moved into the child |
| function. */ |
| bool save_remap_decls_p = p->remap_decls_p; |
| p->remap_decls_p = false; |
| *walk_subtrees = 0; |
| |
| walk_tree (&OMP_BODY (t), move_stmt_r, p, NULL); |
| |
| p->remap_decls_p = save_remap_decls_p; |
| } |
| else if (DECL_P (t) && DECL_CONTEXT (t) == p->from_context) |
| { |
| if (TREE_CODE (t) == LABEL_DECL) |
| { |
| if (p->new_label_map) |
| { |
| struct tree_map in, *out; |
| in.from = t; |
| out = htab_find_with_hash (p->new_label_map, &in, DECL_UID (t)); |
| if (out) |
| *tp = t = out->to; |
| } |
| |
| DECL_CONTEXT (t) = p->to_context; |
| } |
| else if (p->remap_decls_p) |
| { |
| DECL_CONTEXT (t) = p->to_context; |
| |
| if (TREE_CODE (t) == VAR_DECL) |
| { |
| struct function *f = DECL_STRUCT_FUNCTION (p->to_context); |
| f->unexpanded_var_list |
| = tree_cons (0, t, f->unexpanded_var_list); |
| |
| /* Mark T to be removed from the original function, |
| otherwise it will be given a DECL_RTL when the |
| original function is expanded. */ |
| bitmap_set_bit (p->vars_to_remove, DECL_UID (t)); |
| } |
| } |
| } |
| else if (TYPE_P (t)) |
| *walk_subtrees = 0; |
| |
| return NULL_TREE; |
| } |
| |
| |
| /* Move basic block BB from function CFUN to function DEST_FN. The |
| block is moved out of the original linked list and placed after |
| block AFTER in the new list. Also, the block is removed from the |
| original array of blocks and placed in DEST_FN's array of blocks. |
| If UPDATE_EDGE_COUNT_P is true, the edge counts on both CFGs is |
| updated to reflect the moved edges. |
| |
| On exit, local variables that need to be removed from |
| CFUN->UNEXPANDED_VAR_LIST will have been added to VARS_TO_REMOVE. */ |
| |
| static void |
| move_block_to_fn (struct function *dest_cfun, basic_block bb, |
| basic_block after, bool update_edge_count_p, |
| bitmap vars_to_remove, htab_t new_label_map, int eh_offset) |
| { |
| struct control_flow_graph *cfg; |
| edge_iterator ei; |
| edge e; |
| block_stmt_iterator si; |
| struct move_stmt_d d; |
| unsigned old_len, new_len; |
| basic_block *addr; |
| |
| /* Link BB to the new linked list. */ |
| move_block_after (bb, after); |
| |
| /* Update the edge count in the corresponding flowgraphs. */ |
| if (update_edge_count_p) |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| cfun->cfg->x_n_edges--; |
| dest_cfun->cfg->x_n_edges++; |
| } |
| |
| /* Remove BB from the original basic block array. */ |
| VEC_replace (basic_block, cfun->cfg->x_basic_block_info, bb->index, NULL); |
| cfun->cfg->x_n_basic_blocks--; |
| |
| /* Grow DEST_CFUN's basic block array if needed. */ |
| cfg = dest_cfun->cfg; |
| cfg->x_n_basic_blocks++; |
| if (bb->index > cfg->x_last_basic_block) |
| cfg->x_last_basic_block = bb->index; |
| |
| old_len = VEC_length (basic_block, cfg->x_basic_block_info); |
| if ((unsigned) cfg->x_last_basic_block >= old_len) |
| { |
| new_len = cfg->x_last_basic_block + (cfg->x_last_basic_block + 3) / 4; |
| VEC_safe_grow (basic_block, gc, cfg->x_basic_block_info, new_len); |
| addr = VEC_address (basic_block, cfg->x_basic_block_info); |
| memset (&addr[old_len], 0, sizeof (basic_block) * (new_len - old_len)); |
| } |
| |
| VEC_replace (basic_block, cfg->x_basic_block_info, |
| cfg->x_last_basic_block, bb); |
| |
| /* The statements in BB need to be associated with a new TREE_BLOCK. |
| Labels need to be associated with a new label-to-block map. */ |
| memset (&d, 0, sizeof (d)); |
| d.vars_to_remove = vars_to_remove; |
| |
| for (si = bsi_start (bb); !bsi_end_p (si); bsi_next (&si)) |
| { |
| tree stmt = bsi_stmt (si); |
| int region; |
| |
| d.from_context = cfun->decl; |
| d.to_context = dest_cfun->decl; |
| d.remap_decls_p = true; |
| d.new_label_map = new_label_map; |
| if (TREE_BLOCK (stmt)) |
| d.block = DECL_INITIAL (dest_cfun->decl); |
| |
| walk_tree (&stmt, move_stmt_r, &d, NULL); |
| |
| if (TREE_CODE (stmt) == LABEL_EXPR) |
| { |
| tree label = LABEL_EXPR_LABEL (stmt); |
| int uid = LABEL_DECL_UID (label); |
| |
| gcc_assert (uid > -1); |
| |
| old_len = VEC_length (basic_block, cfg->x_label_to_block_map); |
| if (old_len <= (unsigned) uid) |
| { |
| new_len = 3 * uid / 2; |
| VEC_safe_grow (basic_block, gc, cfg->x_label_to_block_map, |
| new_len); |
| addr = VEC_address (basic_block, cfg->x_label_to_block_map); |
| memset (&addr[old_len], 0, |
| sizeof (basic_block) * (new_len - old_len)); |
| } |
| |
| VEC_replace (basic_block, cfg->x_label_to_block_map, uid, bb); |
| VEC_replace (basic_block, cfun->cfg->x_label_to_block_map, uid, NULL); |
| |
| gcc_assert (DECL_CONTEXT (label) == dest_cfun->decl); |
| |
| if (uid >= dest_cfun->last_label_uid) |
| dest_cfun->last_label_uid = uid + 1; |
| } |
| else if (TREE_CODE (stmt) == RESX_EXPR && eh_offset != 0) |
| TREE_OPERAND (stmt, 0) = |
| build_int_cst (NULL_TREE, |
| TREE_INT_CST_LOW (TREE_OPERAND (stmt, 0)) |
| + eh_offset); |
| |
| region = lookup_stmt_eh_region (stmt); |
| if (region >= 0) |
| { |
| add_stmt_to_eh_region_fn (dest_cfun, stmt, region + eh_offset); |
| remove_stmt_from_eh_region (stmt); |
| } |
| } |
| } |
| |
| /* Examine the statements in BB (which is in SRC_CFUN); find and return |
| the outermost EH region. Use REGION as the incoming base EH region. */ |
| |
| static int |
| find_outermost_region_in_block (struct function *src_cfun, |
| basic_block bb, int region) |
| { |
| block_stmt_iterator si; |
| |
| for (si = bsi_start (bb); !bsi_end_p (si); bsi_next (&si)) |
| { |
| tree stmt = bsi_stmt (si); |
| int stmt_region; |
| |
| if (TREE_CODE (stmt) == RESX_EXPR) |
| stmt_region = TREE_INT_CST_LOW (TREE_OPERAND (stmt, 0)); |
| else |
| stmt_region = lookup_stmt_eh_region_fn (src_cfun, stmt); |
| if (stmt_region > 0) |
| { |
| if (region < 0) |
| region = stmt_region; |
| else if (stmt_region != region) |
| { |
| region = eh_region_outermost (src_cfun, stmt_region, region); |
| gcc_assert (region != -1); |
| } |
| } |
| } |
| |
| return region; |
| } |
| |
| static tree |
| new_label_mapper (tree decl, void *data) |
| { |
| htab_t hash = (htab_t) data; |
| struct tree_map *m; |
| void **slot; |
| |
| gcc_assert (TREE_CODE (decl) == LABEL_DECL); |
| |
| m = xmalloc (sizeof (struct tree_map)); |
| m->hash = DECL_UID (decl); |
| m->from = decl; |
| m->to = create_artificial_label (); |
| LABEL_DECL_UID (m->to) = LABEL_DECL_UID (decl); |
| |
| slot = htab_find_slot_with_hash (hash, m, m->hash, INSERT); |
| gcc_assert (*slot == NULL); |
| |
| *slot = m; |
| |
| return m->to; |
| } |
| |
| /* Move a single-entry, single-exit region delimited by ENTRY_BB and |
| EXIT_BB to function DEST_CFUN. The whole region is replaced by a |
| single basic block in the original CFG and the new basic block is |
| returned. DEST_CFUN must not have a CFG yet. |
| |
| Note that the region need not be a pure SESE region. Blocks inside |
| the region may contain calls to abort/exit. The only restriction |
| is that ENTRY_BB should be the only entry point and it must |
| dominate EXIT_BB. |
| |
| All local variables referenced in the region are assumed to be in |
| the corresponding BLOCK_VARS and unexpanded variable lists |
| associated with DEST_CFUN. */ |
| |
| basic_block |
| move_sese_region_to_fn (struct function *dest_cfun, basic_block entry_bb, |
| basic_block exit_bb) |
| { |
| VEC(basic_block,heap) *bbs; |
| basic_block after, bb, *entry_pred, *exit_succ; |
| struct function *saved_cfun; |
| int *entry_flag, *exit_flag, eh_offset; |
| unsigned i, num_entry_edges, num_exit_edges; |
| edge e; |
| edge_iterator ei; |
| bitmap vars_to_remove; |
| htab_t new_label_map; |
| |
| saved_cfun = cfun; |
| |
| /* Collect all the blocks in the region. Manually add ENTRY_BB |
| because it won't be added by dfs_enumerate_from. */ |
| calculate_dominance_info (CDI_DOMINATORS); |
| |
| /* If ENTRY does not strictly dominate EXIT, this cannot be an SESE |
| region. */ |
| gcc_assert (entry_bb != exit_bb |
| && (!exit_bb |
| || dominated_by_p (CDI_DOMINATORS, exit_bb, entry_bb))); |
| |
| bbs = NULL; |
| VEC_safe_push (basic_block, heap, bbs, entry_bb); |
| gather_blocks_in_sese_region (entry_bb, exit_bb, &bbs); |
| |
| /* Detach ENTRY_BB and EXIT_BB from CFUN->CFG. We need to remember |
| the predecessor edges to ENTRY_BB and the successor edges to |
| EXIT_BB so that we can re-attach them to the new basic block that |
| will replace the region. */ |
| num_entry_edges = EDGE_COUNT (entry_bb->preds); |
| entry_pred = (basic_block *) xcalloc (num_entry_edges, sizeof (basic_block)); |
| entry_flag = (int *) xcalloc (num_entry_edges, sizeof (int)); |
| i = 0; |
| for (ei = ei_start (entry_bb->preds); (e = ei_safe_edge (ei)) != NULL;) |
| { |
| entry_flag[i] = e->flags; |
| entry_pred[i++] = e->src; |
| remove_edge (e); |
| } |
| |
| if (exit_bb) |
| { |
| num_exit_edges = EDGE_COUNT (exit_bb->succs); |
| exit_succ = (basic_block *) xcalloc (num_exit_edges, |
| sizeof (basic_block)); |
| exit_flag = (int *) xcalloc (num_exit_edges, sizeof (int)); |
| i = 0; |
| for (ei = ei_start (exit_bb->succs); (e = ei_safe_edge (ei)) != NULL;) |
| { |
| exit_flag[i] = e->flags; |
| exit_succ[i++] = e->dest; |
| remove_edge (e); |
| } |
| } |
| else |
| { |
| num_exit_edges = 0; |
| exit_succ = NULL; |
| exit_flag = NULL; |
| } |
| |
| /* Switch context to the child function to initialize DEST_FN's CFG. */ |
| gcc_assert (dest_cfun->cfg == NULL); |
| cfun = dest_cfun; |
| |
| init_empty_tree_cfg (); |
| |
| /* Initialize EH information for the new function. */ |
| eh_offset = 0; |
| new_label_map = NULL; |
| if (saved_cfun->eh) |
| { |
| int region = -1; |
| |
| for (i = 0; VEC_iterate (basic_block, bbs, i, bb); i++) |
| region = find_outermost_region_in_block (saved_cfun, bb, region); |
| |
| init_eh_for_function (); |
| if (region != -1) |
| { |
| new_label_map = htab_create (17, tree_map_hash, tree_map_eq, free); |
| eh_offset = duplicate_eh_regions (saved_cfun, new_label_mapper, |
| new_label_map, region, 0); |
| } |
| } |
| |
| cfun = saved_cfun; |
| |
| /* Move blocks from BBS into DEST_CFUN. */ |
| gcc_assert (VEC_length (basic_block, bbs) >= 2); |
| after = dest_cfun->cfg->x_entry_block_ptr; |
| vars_to_remove = BITMAP_ALLOC (NULL); |
| for (i = 0; VEC_iterate (basic_block, bbs, i, bb); i++) |
| { |
| /* No need to update edge counts on the last block. It has |
| already been updated earlier when we detached the region from |
| the original CFG. */ |
| move_block_to_fn (dest_cfun, bb, after, bb != exit_bb, vars_to_remove, |
| new_label_map, eh_offset); |
| after = bb; |
| } |
| |
| if (new_label_map) |
| htab_delete (new_label_map); |
| |
| /* Remove the variables marked in VARS_TO_REMOVE from |
| CFUN->UNEXPANDED_VAR_LIST. Otherwise, they will be given a |
| DECL_RTL in the context of CFUN. */ |
| if (!bitmap_empty_p (vars_to_remove)) |
| { |
| tree *p; |
| |
| for (p = &cfun->unexpanded_var_list; *p; ) |
| { |
| tree var = TREE_VALUE (*p); |
| if (bitmap_bit_p (vars_to_remove, DECL_UID (var))) |
| { |
| *p = TREE_CHAIN (*p); |
| continue; |
| } |
| |
| p = &TREE_CHAIN (*p); |
| } |
| } |
| |
| BITMAP_FREE (vars_to_remove); |
| |
| /* Rewire the entry and exit blocks. The successor to the entry |
| block turns into the successor of DEST_FN's ENTRY_BLOCK_PTR in |
| the child function. Similarly, the predecessor of DEST_FN's |
| EXIT_BLOCK_PTR turns into the predecessor of EXIT_BLOCK_PTR. We |
| need to switch CFUN between DEST_CFUN and SAVED_CFUN so that the |
| various CFG manipulation function get to the right CFG. |
| |
| FIXME, this is silly. The CFG ought to become a parameter to |
| these helpers. */ |
| cfun = dest_cfun; |
| make_edge (ENTRY_BLOCK_PTR, entry_bb, EDGE_FALLTHRU); |
| if (exit_bb) |
| make_edge (exit_bb, EXIT_BLOCK_PTR, 0); |
| cfun = saved_cfun; |
| |
| /* Back in the original function, the SESE region has disappeared, |
| create a new basic block in its place. */ |
| bb = create_empty_bb (entry_pred[0]); |
| for (i = 0; i < num_entry_edges; i++) |
| make_edge (entry_pred[i], bb, entry_flag[i]); |
| |
| for (i = 0; i < num_exit_edges; i++) |
| make_edge (bb, exit_succ[i], exit_flag[i]); |
| |
| if (exit_bb) |
| { |
| free (exit_flag); |
| free (exit_succ); |
| } |
| free (entry_flag); |
| free (entry_pred); |
| free_dominance_info (CDI_DOMINATORS); |
| free_dominance_info (CDI_POST_DOMINATORS); |
| VEC_free (basic_block, heap, bbs); |
| |
| return bb; |
| } |
| |
| |
| /* Dump FUNCTION_DECL FN to file FILE using FLAGS (see TDF_* in tree.h) */ |
| |
| void |
| dump_function_to_file (tree fn, FILE *file, int flags) |
| { |
| tree arg, vars, var; |
| bool ignore_topmost_bind = false, any_var = false; |
| basic_block bb; |
| tree chain; |
| struct function *saved_cfun; |
| |
| fprintf (file, "%s (", lang_hooks.decl_printable_name (fn, 2)); |
| |
| arg = DECL_ARGUMENTS (fn); |
| while (arg) |
| { |
| print_generic_expr (file, arg, dump_flags); |
| if (TREE_CHAIN (arg)) |
| fprintf (file, ", "); |
| arg = TREE_CHAIN (arg); |
| } |
| fprintf (file, ")\n"); |
| |
| if (flags & TDF_DETAILS) |
| dump_eh_tree (file, DECL_STRUCT_FUNCTION (fn)); |
| if (flags & TDF_RAW) |
| { |
| dump_node (fn, TDF_SLIM | flags, file); |
| return; |
| } |
| |
| /* Switch CFUN to point to FN. */ |
| saved_cfun = cfun; |
| cfun = DECL_STRUCT_FUNCTION (fn); |
| |
| /* When GIMPLE is lowered, the variables are no longer available in |
| BIND_EXPRs, so display them separately. */ |
| if (cfun && cfun->decl == fn && cfun->unexpanded_var_list) |
| { |
| ignore_topmost_bind = true; |
| |
| fprintf (file, "{\n"); |
| for (vars = cfun->unexpanded_var_list; vars; vars = TREE_CHAIN (vars)) |
| { |
| var = TREE_VALUE (vars); |
| |
| print_generic_decl (file, var, flags); |
| fprintf (file, "\n"); |
| |
| any_var = true; |
| } |
| } |
| |
| if (cfun && cfun->decl == fn && cfun->cfg && basic_block_info) |
| { |
| /* Make a CFG based dump. */ |
| check_bb_profile (ENTRY_BLOCK_PTR, file); |
| if (!ignore_topmost_bind) |
| fprintf (file, "{\n"); |
| |
| if (any_var && n_basic_blocks) |
| fprintf (file, "\n"); |
| |
| FOR_EACH_BB (bb) |
| dump_generic_bb (file, bb, 2, flags); |
| |
| fprintf (file, "}\n"); |
| check_bb_profile (EXIT_BLOCK_PTR, file); |
| } |
| else |
| { |
| int indent; |
| |
| /* Make a tree based dump. */ |
| chain = DECL_SAVED_TREE (fn); |
| |
| if (chain && TREE_CODE (chain) == BIND_EXPR) |
| { |
| if (ignore_topmost_bind) |
| { |
| chain = BIND_EXPR_BODY (chain); |
| indent = 2; |
| } |
| else |
| indent = 0; |
| } |
| else |
| { |
| if (!ignore_topmost_bind) |
| fprintf (file, "{\n"); |
| indent = 2; |
| } |
| |
| if (any_var) |
| fprintf (file, "\n"); |
| |
| print_generic_stmt_indented (file, chain, flags, indent); |
| if (ignore_topmost_bind) |
| fprintf (file, "}\n"); |
| } |
| |
| fprintf (file, "\n\n"); |
| |
| /* Restore CFUN. */ |
| cfun = saved_cfun; |
| } |
| |
| |
| /* Dump FUNCTION_DECL FN to stderr using FLAGS (see TDF_* in tree.h) */ |
| |
| void |
| debug_function (tree fn, int flags) |
| { |
| dump_function_to_file (fn, stderr, flags); |
| } |
| |
| |
| /* Pretty print of the loops intermediate representation. */ |
| static void print_loop (FILE *, struct loop *, int); |
| static void print_pred_bbs (FILE *, basic_block bb); |
| static void print_succ_bbs (FILE *, basic_block bb); |
| |
| |
| /* Print on FILE the indexes for the predecessors of basic_block BB. */ |
| |
| static void |
| print_pred_bbs (FILE *file, basic_block bb) |
| { |
| edge e; |
| edge_iterator ei; |
| |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| fprintf (file, "bb_%d ", e->src->index); |
| } |
| |
| |
| /* Print on FILE the indexes for the successors of basic_block BB. */ |
| |
| static void |
| print_succ_bbs (FILE *file, basic_block bb) |
| { |
| edge e; |
| edge_iterator ei; |
| |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| fprintf (file, "bb_%d ", e->dest->index); |
| } |
| |
| |
| /* Pretty print LOOP on FILE, indented INDENT spaces. */ |
| |
| static void |
| print_loop (FILE *file, struct loop *loop, int indent) |
| { |
| char *s_indent; |
| basic_block bb; |
| |
| if (loop == NULL) |
| return; |
| |
| s_indent = (char *) alloca ((size_t) indent + 1); |
| memset ((void *) s_indent, ' ', (size_t) indent); |
| s_indent[indent] = '\0'; |
| |
| /* Print the loop's header. */ |
| fprintf (file, "%sloop_%d\n", s_indent, loop->num); |
| |
| /* Print the loop's body. */ |
| fprintf (file, "%s{\n", s_indent); |
| FOR_EACH_BB (bb) |
| if (bb->loop_father == loop) |
| { |
| /* Print the basic_block's header. */ |
| fprintf (file, "%s bb_%d (preds = {", s_indent, bb->index); |
| print_pred_bbs (file, bb); |
| fprintf (file, "}, succs = {"); |
| print_succ_bbs (file, bb); |
| fprintf (file, "})\n"); |
| |
| /* Print the basic_block's body. */ |
| fprintf (file, "%s {\n", s_indent); |
| tree_dump_bb (bb, file, indent + 4); |
| fprintf (file, "%s }\n", s_indent); |
| } |
| |
| print_loop (file, loop->inner, indent + 2); |
| fprintf (file, "%s}\n", s_indent); |
| print_loop (file, loop->next, indent); |
| } |
| |
| |
| /* Follow a CFG edge from the entry point of the program, and on entry |
| of a loop, pretty print the loop structure on FILE. */ |
| |
| void |
| print_loop_ir (FILE *file) |
| { |
| basic_block bb; |
| |
| bb = BASIC_BLOCK (NUM_FIXED_BLOCKS); |
| if (bb && bb->loop_father) |
| print_loop (file, bb->loop_father, 0); |
| } |
| |
| |
| /* Debugging loops structure at tree level. */ |
| |
| void |
| debug_loop_ir (void) |
| { |
| print_loop_ir (stderr); |
| } |
| |
| |
| /* Return true if BB ends with a call, possibly followed by some |
| instructions that must stay with the call. Return false, |
| otherwise. */ |
| |
| static bool |
| tree_block_ends_with_call_p (basic_block bb) |
| { |
| block_stmt_iterator bsi = bsi_last (bb); |
| /* APPLE LOCAL begin tree-profiling-branch --dbj */ |
| if (bsi_end_p (bsi)) |
| return false; |
| /* APPLE LOCAL end tree-profiling-branch --dbj */ |
| return get_call_expr_in (bsi_stmt (bsi)) != NULL; |
| } |
| |
| |
| /* Return true if BB ends with a conditional branch. Return false, |
| otherwise. */ |
| |
| static bool |
| tree_block_ends_with_condjump_p (basic_block bb) |
| { |
| tree stmt = last_stmt (bb); |
| return (stmt && TREE_CODE (stmt) == COND_EXPR); |
| } |
| |
| |
| /* Return true if we need to add fake edge to exit at statement T. |
| Helper function for tree_flow_call_edges_add. */ |
| |
| static bool |
| need_fake_edge_p (tree t) |
| { |
| tree call; |
| |
| /* NORETURN and LONGJMP calls already have an edge to exit. |
| CONST and PURE calls do not need one. |
| We don't currently check for CONST and PURE here, although |
| it would be a good idea, because those attributes are |
| figured out from the RTL in mark_constant_function, and |
| the counter incrementation code from -fprofile-arcs |
| leads to different results from -fbranch-probabilities. */ |
| call = get_call_expr_in (t); |
| if (call |
| && !(call_expr_flags (call) & ECF_NORETURN)) |
| return true; |
| |
| if (TREE_CODE (t) == ASM_EXPR |
| && (ASM_VOLATILE_P (t) || ASM_INPUT_P (t))) |
| return true; |
| |
| return false; |
| } |
| |
| |
| /* Add fake edges to the function exit for any non constant and non |
| noreturn calls, volatile inline assembly in the bitmap of blocks |
| specified by BLOCKS or to the whole CFG if BLOCKS is zero. Return |
| the number of blocks that were split. |
| |
| The goal is to expose cases in which entering a basic block does |
| not imply that all subsequent instructions must be executed. */ |
| |
| static int |
| tree_flow_call_edges_add (sbitmap blocks) |
| { |
| int i; |
| int blocks_split = 0; |
| int last_bb = last_basic_block; |
| bool check_last_block = false; |
| |
| if (n_basic_blocks == NUM_FIXED_BLOCKS) |
| return 0; |
| |
| if (! blocks) |
| check_last_block = true; |
| else |
| check_last_block = TEST_BIT (blocks, EXIT_BLOCK_PTR->prev_bb->index); |
| |
| /* In the last basic block, before epilogue generation, there will be |
| a fallthru edge to EXIT. Special care is required if the last insn |
| of the last basic block is a call because make_edge folds duplicate |
| edges, which would result in the fallthru edge also being marked |
| fake, which would result in the fallthru edge being removed by |
| remove_fake_edges, which would result in an invalid CFG. |
| |
| Moreover, we can't elide the outgoing fake edge, since the block |
| profiler needs to take this into account in order to solve the minimal |
| spanning tree in the case that the call doesn't return. |
| |
| Handle this by adding a dummy instruction in a new last basic block. */ |
| if (check_last_block) |
| { |
| basic_block bb = EXIT_BLOCK_PTR->prev_bb; |
| block_stmt_iterator bsi = bsi_last (bb); |
| tree t = NULL_TREE; |
| if (!bsi_end_p (bsi)) |
| t = bsi_stmt (bsi); |
| |
| if (t && need_fake_edge_p (t)) |
| { |
| edge e; |
| |
| e = find_edge (bb, EXIT_BLOCK_PTR); |
| if (e) |
| { |
| bsi_insert_on_edge (e, build_empty_stmt ()); |
| bsi_commit_edge_inserts (); |
| } |
| } |
| } |
| |
| /* Now add fake edges to the function exit for any non constant |
| calls since there is no way that we can determine if they will |
| return or not... */ |
| for (i = 0; i < last_bb; i++) |
| { |
| basic_block bb = BASIC_BLOCK (i); |
| block_stmt_iterator bsi; |
| tree stmt, last_stmt; |
| |
| if (!bb) |
| continue; |
| |
| if (blocks && !TEST_BIT (blocks, i)) |
| continue; |
| |
| bsi = bsi_last (bb); |
| if (!bsi_end_p (bsi)) |
| { |
| last_stmt = bsi_stmt (bsi); |
| do |
| { |
| stmt = bsi_stmt (bsi); |
| if (need_fake_edge_p (stmt)) |
| { |
| edge e; |
| /* The handling above of the final block before the |
| epilogue should be enough to verify that there is |
| no edge to the exit block in CFG already. |
| Calling make_edge in such case would cause us to |
| mark that edge as fake and remove it later. */ |
| #ifdef ENABLE_CHECKING |
| if (stmt == last_stmt) |
| { |
| e = find_edge (bb, EXIT_BLOCK_PTR); |
| gcc_assert (e == NULL); |
| } |
| #endif |
| |
| /* Note that the following may create a new basic block |
| and renumber the existing basic blocks. */ |
| if (stmt != last_stmt) |
| { |
| e = split_block (bb, stmt); |
| if (e) |
| blocks_split++; |
| } |
| make_edge (bb, EXIT_BLOCK_PTR, EDGE_FAKE); |
| } |
| bsi_prev (&bsi); |
| } |
| while (!bsi_end_p (bsi)); |
| } |
| } |
| |
| if (blocks_split) |
| verify_flow_info (); |
| |
| return blocks_split; |
| } |
| |
| /* Purge dead abnormal call edges from basic block BB. */ |
| |
| bool |
| tree_purge_dead_abnormal_call_edges (basic_block bb) |
| { |
| bool changed = tree_purge_dead_eh_edges (bb); |
| |
| if (current_function_has_nonlocal_label) |
| { |
| tree stmt = last_stmt (bb); |
| edge_iterator ei; |
| edge e; |
| |
| if (!(stmt && tree_can_make_abnormal_goto (stmt))) |
| for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); ) |
| { |
| if (e->flags & EDGE_ABNORMAL) |
| { |
| remove_edge (e); |
| changed = true; |
| } |
| else |
| ei_next (&ei); |
| } |
| |
| /* See tree_purge_dead_eh_edges below. */ |
| if (changed) |
| free_dominance_info (CDI_DOMINATORS); |
| } |
| |
| return changed; |
| } |
| |
| /* Purge dead EH edges from basic block BB. */ |
| |
| bool |
| tree_purge_dead_eh_edges (basic_block bb) |
| { |
| bool changed = false; |
| edge e; |
| edge_iterator ei; |
| tree stmt = last_stmt (bb); |
| |
| if (stmt && tree_can_throw_internal (stmt)) |
| return false; |
| |
| for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); ) |
| { |
| if (e->flags & EDGE_EH) |
| { |
| remove_edge (e); |
| changed = true; |
| } |
| else |
| ei_next (&ei); |
| } |
| |
| /* Removal of dead EH edges might change dominators of not |
| just immediate successors. E.g. when bb1 is changed so that |
| it no longer can throw and bb1->bb3 and bb1->bb4 are dead |
| eh edges purged by this function in: |
| 0 |
| / \ |
| v v |
| 1-->2 |
| / \ | |
| v v | |
| 3-->4 | |
| \ v |
| --->5 |
| | |
| - |
| idom(bb5) must be recomputed. For now just free the dominance |
| info. */ |
| if (changed) |
| free_dominance_info (CDI_DOMINATORS); |
| |
| return changed; |
| } |
| |
| bool |
| tree_purge_all_dead_eh_edges (bitmap blocks) |
| { |
| bool changed = false; |
| unsigned i; |
| bitmap_iterator bi; |
| |
| EXECUTE_IF_SET_IN_BITMAP (blocks, 0, i, bi) |
| { |
| changed |= tree_purge_dead_eh_edges (BASIC_BLOCK (i)); |
| } |
| |
| return changed; |
| } |
| |
| /* This function is called whenever a new edge is created or |
| redirected. */ |
| |
| static void |
| tree_execute_on_growing_pred (edge e) |
| { |
| basic_block bb = e->dest; |
| |
| if (phi_nodes (bb)) |
| reserve_phi_args_for_new_edge (bb); |
| } |
| |
| /* This function is called immediately before edge E is removed from |
| the edge vector E->dest->preds. */ |
| |
| static void |
| tree_execute_on_shrinking_pred (edge e) |
| { |
| if (phi_nodes (e->dest)) |
| remove_phi_args (e); |
| } |
| |
| /*--------------------------------------------------------------------------- |
| Helper functions for Loop versioning |
| ---------------------------------------------------------------------------*/ |
| |
| /* Adjust phi nodes for 'first' basic block. 'second' basic block is a copy |
| of 'first'. Both of them are dominated by 'new_head' basic block. When |
| 'new_head' was created by 'second's incoming edge it received phi arguments |
| on the edge by split_edge(). Later, additional edge 'e' was created to |
| connect 'new_head' and 'first'. Now this routine adds phi args on this |
| additional edge 'e' that new_head to second edge received as part of edge |
| splitting. |
| */ |
| |
| static void |
| tree_lv_adjust_loop_header_phi (basic_block first, basic_block second, |
| basic_block new_head, edge e) |
| { |
| tree phi1, phi2; |
| edge e2 = find_edge (new_head, second); |
| |
| /* Because NEW_HEAD has been created by splitting SECOND's incoming |
| edge, we should always have an edge from NEW_HEAD to SECOND. */ |
| gcc_assert (e2 != NULL); |
| |
| /* Browse all 'second' basic block phi nodes and add phi args to |
| edge 'e' for 'first' head. PHI args are always in correct order. */ |
| |
| for (phi2 = phi_nodes (second), phi1 = phi_nodes (first); |
| phi2 && phi1; |
| phi2 = PHI_CHAIN (phi2), phi1 = PHI_CHAIN (phi1)) |
| { |
| tree def = PHI_ARG_DEF (phi2, e2->dest_idx); |
| add_phi_arg (phi1, def, e); |
| } |
| } |
| |
| /* Adds a if else statement to COND_BB with condition COND_EXPR. |
| SECOND_HEAD is the destination of the THEN and FIRST_HEAD is |
| the destination of the ELSE part. */ |
| static void |
| tree_lv_add_condition_to_bb (basic_block first_head, basic_block second_head, |
| basic_block cond_bb, void *cond_e) |
| { |
| block_stmt_iterator bsi; |
| tree goto1 = NULL_TREE; |
| tree goto2 = NULL_TREE; |
| tree new_cond_expr = NULL_TREE; |
| tree cond_expr = (tree) cond_e; |
| edge e0; |
| |
| /* Build new conditional expr */ |
| goto1 = build1 (GOTO_EXPR, void_type_node, tree_block_label (first_head)); |
| goto2 = build1 (GOTO_EXPR, void_type_node, tree_block_label (second_head)); |
| new_cond_expr = build3 (COND_EXPR, void_type_node, cond_expr, goto1, goto2); |
| |
| /* Add new cond in cond_bb. */ |
| bsi = bsi_start (cond_bb); |
| bsi_insert_after (&bsi, new_cond_expr, BSI_NEW_STMT); |
| /* Adjust edges appropriately to connect new head with first head |
| as well as second head. */ |
| e0 = single_succ_edge (cond_bb); |
| e0->flags &= ~EDGE_FALLTHRU; |
| e0->flags |= EDGE_FALSE_VALUE; |
| } |
| |
| struct cfg_hooks tree_cfg_hooks = { |
| "tree", |
| tree_verify_flow_info, |
| tree_dump_bb, /* dump_bb */ |
| create_bb, /* create_basic_block */ |
| tree_redirect_edge_and_branch,/* redirect_edge_and_branch */ |
| tree_redirect_edge_and_branch_force,/* redirect_edge_and_branch_force */ |
| remove_bb, /* delete_basic_block */ |
| tree_split_block, /* split_block */ |
| tree_move_block_after, /* move_block_after */ |
| tree_can_merge_blocks_p, /* can_merge_blocks_p */ |
| tree_merge_blocks, /* merge_blocks */ |
| tree_predict_edge, /* predict_edge */ |
| tree_predicted_by_p, /* predicted_by_p */ |
| tree_can_duplicate_bb_p, /* can_duplicate_block_p */ |
| tree_duplicate_bb, /* duplicate_block */ |
| tree_split_edge, /* split_edge */ |
| tree_make_forwarder_block, /* make_forward_block */ |
| NULL, /* tidy_fallthru_edge */ |
| tree_block_ends_with_call_p, /* block_ends_with_call_p */ |
| tree_block_ends_with_condjump_p, /* block_ends_with_condjump_p */ |
| tree_flow_call_edges_add, /* flow_call_edges_add */ |
| tree_execute_on_growing_pred, /* execute_on_growing_pred */ |
| tree_execute_on_shrinking_pred, /* execute_on_shrinking_pred */ |
| tree_duplicate_loop_to_header_edge, /* duplicate loop for trees */ |
| tree_lv_add_condition_to_bb, /* lv_add_condition_to_bb */ |
| tree_lv_adjust_loop_header_phi, /* lv_adjust_loop_header_phi*/ |
| extract_true_false_edges_from_block, /* extract_cond_bb_edges */ |
| flush_pending_stmts /* flush_pending_stmts */ |
| }; |
| |
| |
| /* Split all critical edges. */ |
| |
| static unsigned int |
| split_critical_edges (void) |
| { |
| basic_block bb; |
| edge e; |
| edge_iterator ei; |
| |
| /* split_edge can redirect edges out of SWITCH_EXPRs, which can get |
| expensive. So we want to enable recording of edge to CASE_LABEL_EXPR |
| mappings around the calls to split_edge. */ |
| start_recording_case_labels (); |
| FOR_ALL_BB (bb) |
| { |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| if (EDGE_CRITICAL_P (e) && !(e->flags & EDGE_ABNORMAL)) |
| { |
| split_edge (e); |
| } |
| } |
| end_recording_case_labels (); |
| return 0; |
| } |
| |
| struct tree_opt_pass pass_split_crit_edges = |
| { |
| "crited", /* name */ |
| NULL, /* gate */ |
| split_critical_edges, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| TV_TREE_SPLIT_EDGES, /* tv_id */ |
| PROP_cfg, /* properties required */ |
| PROP_no_crit_edges, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| TODO_dump_func, /* todo_flags_finish */ |
| 0 /* letter */ |
| }; |
| |
| |
| /* Return EXP if it is a valid GIMPLE rvalue, else gimplify it into |
| a temporary, make sure and register it to be renamed if necessary, |
| and finally return the temporary. Put the statements to compute |
| EXP before the current statement in BSI. */ |
| |
| tree |
| gimplify_val (block_stmt_iterator *bsi, tree type, tree exp) |
| { |
| tree t, new_stmt, orig_stmt; |
| |
| if (is_gimple_val (exp)) |
| return exp; |
| |
| t = make_rename_temp (type, NULL); |
| new_stmt = build2 (MODIFY_EXPR, type, t, exp); |
| |
| orig_stmt = bsi_stmt (*bsi); |
| SET_EXPR_LOCUS (new_stmt, EXPR_LOCUS (orig_stmt)); |
| TREE_BLOCK (new_stmt) = TREE_BLOCK (orig_stmt); |
| |
| bsi_insert_before (bsi, new_stmt, BSI_SAME_STMT); |
| if (in_ssa_p) |
| mark_new_vars_to_rename (new_stmt); |
| |
| return t; |
| } |
| |
| /* Build a ternary operation and gimplify it. Emit code before BSI. |
| Return the gimple_val holding the result. */ |
| |
| tree |
| gimplify_build3 (block_stmt_iterator *bsi, enum tree_code code, |
| tree type, tree a, tree b, tree c) |
| { |
| tree ret; |
| |
| ret = fold_build3 (code, type, a, b, c); |
| STRIP_NOPS (ret); |
| |
| return gimplify_val (bsi, type, ret); |
| } |
| |
| /* Build a binary operation and gimplify it. Emit code before BSI. |
| Return the gimple_val holding the result. */ |
| |
| tree |
| gimplify_build2 (block_stmt_iterator *bsi, enum tree_code code, |
| tree type, tree a, tree b) |
| { |
| tree ret; |
| |
| ret = fold_build2 (code, type, a, b); |
| STRIP_NOPS (ret); |
| |
| return gimplify_val (bsi, type, ret); |
| } |
| |
| /* Build a unary operation and gimplify it. Emit code before BSI. |
| Return the gimple_val holding the result. */ |
| |
| tree |
| gimplify_build1 (block_stmt_iterator *bsi, enum tree_code code, tree type, |
| tree a) |
| { |
| tree ret; |
| |
| ret = fold_build1 (code, type, a); |
| STRIP_NOPS (ret); |
| |
| return gimplify_val (bsi, type, ret); |
| } |
| |
| |
| |
| /* Emit return warnings. */ |
| |
| static unsigned int |
| execute_warn_function_return (void) |
| { |
| #ifdef USE_MAPPED_LOCATION |
| source_location location; |
| #else |
| location_t *locus; |
| #endif |
| tree last; |
| edge e; |
| edge_iterator ei; |
| |
| /* If we have a path to EXIT, then we do return. */ |
| if (TREE_THIS_VOLATILE (cfun->decl) |
| && EDGE_COUNT (EXIT_BLOCK_PTR->preds) > 0) |
| { |
| #ifdef USE_MAPPED_LOCATION |
| location = UNKNOWN_LOCATION; |
| #else |
| locus = NULL; |
| #endif |
| FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR->preds) |
| { |
| last = last_stmt (e->src); |
| if (TREE_CODE (last) == RETURN_EXPR |
| #ifdef USE_MAPPED_LOCATION |
| && (location = EXPR_LOCATION (last)) != UNKNOWN_LOCATION) |
| #else |
| && (locus = EXPR_LOCUS (last)) != NULL) |
| #endif |
| break; |
| } |
| #ifdef USE_MAPPED_LOCATION |
| if (location == UNKNOWN_LOCATION) |
| location = cfun->function_end_locus; |
| warning (0, "%H%<noreturn%> function does return", &location); |
| #else |
| if (!locus) |
| locus = &cfun->function_end_locus; |
| warning (0, "%H%<noreturn%> function does return", locus); |
| #endif |
| } |
| |
| /* If we see "return;" in some basic block, then we do reach the end |
| without returning a value. */ |
| else if (warn_return_type |
| && !TREE_NO_WARNING (cfun->decl) |
| && EDGE_COUNT (EXIT_BLOCK_PTR->preds) > 0 |
| && !VOID_TYPE_P (TREE_TYPE (TREE_TYPE (cfun->decl)))) |
| { |
| FOR_EACH_EDGE (e, ei, EXIT_BLOCK_PTR->preds) |
| { |
| tree last = last_stmt (e->src); |
| if (TREE_CODE (last) == RETURN_EXPR |
| && TREE_OPERAND (last, 0) == NULL |
| && !TREE_NO_WARNING (last)) |
| { |
| #ifdef USE_MAPPED_LOCATION |
| location = EXPR_LOCATION (last); |
| if (location == UNKNOWN_LOCATION) |
| location = cfun->function_end_locus; |
| warning (0, "%Hcontrol reaches end of non-void function", &location); |
| #else |
| locus = EXPR_LOCUS (last); |
| if (!locus) |
| locus = &cfun->function_end_locus; |
| warning (0, "%Hcontrol reaches end of non-void function", locus); |
| #endif |
| TREE_NO_WARNING (cfun->decl) = 1; |
| break; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| |
| /* Given a basic block B which ends with a conditional and has |
| precisely two successors, determine which of the edges is taken if |
| the conditional is true and which is taken if the conditional is |
| false. Set TRUE_EDGE and FALSE_EDGE appropriately. */ |
| |
| void |
| extract_true_false_edges_from_block (basic_block b, |
| edge *true_edge, |
| edge *false_edge) |
| { |
| edge e = EDGE_SUCC (b, 0); |
| |
| if (e->flags & EDGE_TRUE_VALUE) |
| { |
| *true_edge = e; |
| *false_edge = EDGE_SUCC (b, 1); |
| } |
| else |
| { |
| *false_edge = e; |
| *true_edge = EDGE_SUCC (b, 1); |
| } |
| } |
| |
| struct tree_opt_pass pass_warn_function_return = |
| { |
| NULL, /* name */ |
| NULL, /* gate */ |
| execute_warn_function_return, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| 0, /* tv_id */ |
| PROP_cfg, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| 0 /* letter */ |
| }; |
| |
| /* Emit noreturn warnings. */ |
| |
| static unsigned int |
| execute_warn_function_noreturn (void) |
| { |
| if (warn_missing_noreturn |
| && !TREE_THIS_VOLATILE (cfun->decl) |
| && EDGE_COUNT (EXIT_BLOCK_PTR->preds) == 0 |
| && !lang_hooks.function.missing_noreturn_ok_p (cfun->decl)) |
| warning (OPT_Wmissing_noreturn, "%Jfunction might be possible candidate " |
| "for attribute %<noreturn%>", |
| cfun->decl); |
| return 0; |
| } |
| |
| struct tree_opt_pass pass_warn_function_noreturn = |
| { |
| NULL, /* name */ |
| NULL, /* gate */ |
| execute_warn_function_noreturn, /* execute */ |
| NULL, /* sub */ |
| NULL, /* next */ |
| 0, /* static_pass_number */ |
| 0, /* tv_id */ |
| PROP_cfg, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| 0 /* letter */ |
| }; |