diff --git a/ChangeLog b/ChangeLog index a28e29cffb7..0b51053fa7e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ + +2008-05-17 Xinliang David Li + + * gcc/tree-ssa-dce.c : conditional dead call elimination + * gcc/opts.c : enable the optimization at >=O2 + * gcc/common.opt : new flag for control the optimization + * gcc/doc/invoke.texi : documentation change + * gcc/testsuite/gcc.dg/cdce1.c : new test case + * gcc/testsuite/gcc.dg/cdce2.c : new test case + *MAINTAINERS : Add myself (write after approval) + 2008-05-15 Janus Weil * MAINTAINERS (Write After Approval): Add myself. diff --git a/MAINTAINERS b/MAINTAINERS index 23f98faab5f..b1163975974 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -372,6 +372,7 @@ Manuel L Dave Love d.love@dl.ac.uk Martin v. Löwis loewis@informatik.hu-berlin.de H.J. Lu hjl.tools@gmail.com +Xinliang David Li davidxl@google.com William Maddox maddox@google.com Ziga Mahkovec ziga.mahkovec@klika.si Simon Martin simartin@users.sourceforge.net diff --git a/gcc/common.opt b/gcc/common.opt index 4a44cd68122..c7c6f49f40c 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1259,6 +1259,10 @@ fweb Common Report Var(flag_web) Init(2) Optimization Construct webs and split unrelated uses of single variable +ftree-builtin-dce +Common Report Var(flag_tree_builtin_dce) Init(0) Optimization +Enable conditional dead code elimination for builtin calls. + fwhole-program Common Report Var(flag_whole_program) Init(0) Optimization Perform whole program optimizations diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index e515a0f191b..0fdadd2aec1 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -353,7 +353,7 @@ Objective-C and Objective-C++ Dialects}. -fsignaling-nans -fsingle-precision-constant -fsplit-ivs-in-unroller @gol -fsplit-wide-types -fstack-protector -fstack-protector-all @gol -fstrict-aliasing -fstrict-overflow -fthread-jumps -ftracer -ftree-ccp @gol --ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce @gol +-ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-builtin-dce@gol -ftree-dominator-opts -ftree-dse -ftree-fre -ftree-loop-im @gol -ftree-loop-distribution @gol -ftree-loop-ivcanon -ftree-loop-linear -ftree-loop-optimize @gol @@ -5157,6 +5157,7 @@ compilation time. -ftree-ch @gol -ftree-copyrename @gol -ftree-dce @gol +-ftree-builtin-dce @gol -ftree-dominator-opts @gol -ftree-dse @gol -ftree-fre @gol @@ -5873,6 +5874,12 @@ enabled by default at @option{-O2} and higher. Perform dead code elimination (DCE) on trees. This flag is enabled by default at @option{-O} and higher. +@item -ftree-builtin-dce +@opindex ftree-builtin-dce +Perform conditional dead code elimination (DCE) on builtin calls that +may set errno but are otherwise side-effect free. This flag is enabled by +default at @option{-O} and higher. + @item -ftree-dominator-opts @opindex ftree-dominator-opts Perform a variety of simple scalar cleanups (constant/copy diff --git a/gcc/opts.c b/gcc/opts.c index 7add8d3d250..b03c5a7a627 100644 --- a/gcc/opts.c +++ b/gcc/opts.c @@ -886,6 +886,7 @@ decode_options (unsigned int argc, const char **argv) flag_reorder_functions = 1; flag_tree_store_ccp = 1; flag_tree_vrp = 1; + flag_tree_builtin_dce = 1; if (!optimize_size) { diff --git a/gcc/testsuite/gcc.dg/cdce1.c b/gcc/testsuite/gcc.dg/cdce1.c new file mode 100644 index 00000000000..8e29fc4979c --- /dev/null +++ b/gcc/testsuite/gcc.dg/cdce1.c @@ -0,0 +1,81 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -fdump-tree-dce1-details -lm" } */ +/* { dg-message "note: function call is shrink-wrapped into error conditions\." "Missing conditional dce" {target "*-*-*"} 15 } */ + +#include +#include +#include +#include +int total_err_count = 0; +double foo_opt(int x, double y) __attribute__((noinline)); +double foo_opt(int x, double y) +{ + double yy = 0; + errno = 0; + yy = pow(x,y); + return 0; +} + +double foo(int x, double y) __attribute__((noinline)); +double foo(int x, double y) +{ + double yy = 0; + errno = 0; + yy = pow(x,y); + return yy; +} + +int test(double (*fp)(int x, double y)) +{ + int i,x; + + x = 127; + for (i = 30; i < 300; i++) + { + fp(x,i); + if (errno) + total_err_count ++; + } + + x = -300; + for (i = 100; i < 300; i++) + { + fp(x,i); + if (errno) + total_err_count ++; + } + + x = 65577; + for (i = 60; i < 200; i++) + { + fp(x,i); + if (errno) + total_err_count ++; + } + + x = 65577*127; + for (i = 1; i < 100; i++) + { + fp(x,i); + if (errno) + total_err_count ++; + } + + return total_err_count; +} + +int main() +{ + int en1, en2; + total_err_count = 0; + en1 = test(foo_opt); + total_err_count = 0; + en2 = test(foo); + + printf("total number of errors = %d, %d\n", en1, en2); + if (en1 != en2) + abort(); + + return 0; + +} diff --git a/gcc/testsuite/gcc.dg/cdce2.c b/gcc/testsuite/gcc.dg/cdce2.c new file mode 100644 index 00000000000..8761872ad1f --- /dev/null +++ b/gcc/testsuite/gcc.dg/cdce2.c @@ -0,0 +1,56 @@ +/* { dg-do run } */ +/* { dg-options "-O2 -fdump-tree-dce1-details -lm" } */ +/* { dg-message "note: function call is shrink-wrapped into error conditions\." "Missing conditional dce" {target "*-*-*"} 15 } */ + +#include +#include +#include +#include +int total_err_count = 0; +double foo_opt(double y) __attribute__((noinline)); +double foo_opt(double y) +{ + double yy = 0; + errno = 0; + yy = log(y); + return 0; +} + +double foo(double y) __attribute__((noinline)); +double foo(double y) +{ + double yy = 0; + errno = 0; + yy = log(y); + return yy; +} + +int test(double (*fp)(double y) ) +{ + int i,x; + + for (i = -100; i < 100; i++) + { + fp(i); + if (errno) + total_err_count ++; + } + + return total_err_count; +} + +int main() +{ + int en1, en2; + double yy; + total_err_count = 0; + en1 = test(foo_opt); + total_err_count = 0; + en2 = test(foo); + + if (en1 != en2) + abort(); + + return 0; + +} diff --git a/gcc/tree-ssa-dce.c b/gcc/tree-ssa-dce.c index 95457eb7fe4..d1f1c06214d 100644 --- a/gcc/tree-ssa-dce.c +++ b/gcc/tree-ssa-dce.c @@ -48,7 +48,8 @@ along with GCC; see the file COPYING3. If not see #include "coretypes.h" #include "tm.h" #include "ggc.h" - +#include "diagnostic.h" +#include "toplev.h" /* These RTL headers are needed for basic-block.h. */ #include "rtl.h" #include "tm_p.h" @@ -75,7 +76,9 @@ static struct stmt_stats int removed_phis; } stats; -static VEC(tree,heap) *worklist; + +static VEC (tree, heap) *worklist; +static VEC (tree, heap) *cond_dead_built_in_calls; /* Vector indicating an SSA name has already been processed and marked as necessary. */ @@ -199,6 +202,189 @@ find_all_control_dependences (struct edge_list *el) #define NECESSARY(stmt) stmt->base.asm_written_flag +/* Conditional dead call elimination + + Some builtin functions can set errno on error conditions, but they + are otherwise pure. If the result of a call to such a function is + not used, the compiler can still not eliminate the call without + powerful interprocedural analysis to prove that the errno is not + checked. However, if the conditions under which the error occurs + are known, compiler can conditionally dead code eliminate the calls + by shrink-wrapping the semi-dead calls into the error condition: + + built_in_call(args) + ==> + if (error_cond(args)) + built_in_call(args) + + ( An actual simple exampl is : + log (x); // Mostly dead call + ==> + if (x < 0) + log(x); + With this change, call to log(x) is effectively eliminated, as + in majority of the cases, log won't be called with x out of + range. The branch is totally predicatible, so the branch cost + is low. Such optimization improves the performance of + an important application in a big search company by 20% ) + + Note that library functions are not supposed to clear errno to zero without + error. + + In this implementation, only 'pow' and 'log' are handled. ('sin' and 'cos' + seem to be wrongly handled by gcc -- they are treated as not touching errno + which is not correct.) + + The condition wrapping the builtin call is conservatively set to avoid too + aggressive (wrong) shrink wrapping. The optimization is called conditional + dead call elimination because the call is eliminated under the condition + that the input arguments would not lead to domain or range error (for + instance when x <= 0 for a log(x) call), however the chances that the error + condition is hit is very low (those builtin calls which are conditionally + dead are usually part of the C++ abstraction penalty exposed after + inlining). */ + + +static bool +check_pow (tree pow_call) +{ + tree base, expn; + enum tree_code bc, ec; + + gcc_assert (TREE_CODE (pow_call) == CALL_EXPR); + if (call_expr_nargs (pow_call) != 2) + return false; + + base = CALL_EXPR_ARG (pow_call, 0); + expn = CALL_EXPR_ARG (pow_call, 1); + + bc = TREE_CODE (base); + ec = TREE_CODE (expn); + + gcc_assert (TREE_CODE_CLASS (bc) != tcc_constant + || bc == REAL_CST); + gcc_assert (TREE_CODE_CLASS (ec) != tcc_constant + || ec == REAL_CST); + + /* Folding candidates are not interesting. */ + if (ec == REAL_CST && bc == REAL_CST) + return false; + + if (bc == REAL_CST) + { + /* Only handle a fixed range of constant. */ + REAL_VALUE_TYPE mv; + REAL_VALUE_TYPE bcv = TREE_REAL_CST (base); + if (REAL_VALUES_EQUAL (bcv, dconst1)) + return false; + if (REAL_VALUES_LESS (bcv, dconst1)) + return false; + real_from_integer (&mv, VOIDmode,256,0,1); + if (REAL_VALUES_LESS (mv, bcv)) + return false; + return true; + } + else if (bc == SSA_NAME) + { + tree def, nm, rhs, rhs0, var, typ; + int sz; + + def = SSA_NAME_DEF_STMT (base); + if (TREE_CODE (def) != GIMPLE_MODIFY_STMT) + return false; + + nm = GIMPLE_STMT_OPERAND (def,0); + gcc_assert (TREE_CODE (nm) == SSA_NAME); + if (nm != base) + return false; + + rhs = GIMPLE_STMT_OPERAND (def,1); + + if (TREE_CODE (rhs) != FLOAT_EXPR) + return false; + rhs0 = TREE_OPERAND (rhs,0); + + if (TREE_CODE (rhs0) != SSA_NAME) + return false; + + var = SSA_NAME_VAR (rhs0); + if (TREE_CODE (var) != VAR_DECL && + TREE_CODE (var) != PARM_DECL) + return false; + + typ = TREE_TYPE (var); + if (TREE_CODE (typ) != INTEGER_TYPE) + return false; + sz = int_size_in_bytes (typ); + if (sz == -1 || sz > INT_TYPE_SIZE) + return false; + + return true; + } + else + return false; +} + +static bool +check_log (tree log_call) +{ + tree arg_typ; + gcc_assert (TREE_CODE (log_call) == CALL_EXPR); + if (call_expr_nargs (log_call) != 1) + return false; + + arg_typ = TREE_TYPE (CALL_EXPR_ARG (log_call, 0)); + if (!is_gimple_reg_type (arg_typ)) + return false; + return true; +} + + +/* A helper function to determine if a builtin function + call is a candidate for conditional DCE. */ + +static bool +is_unnecessary_except_errno_call (tree call) +{ + tree fn; + bool is_unnecessary_except_errno = false; + enum built_in_function fnc; + + if (!flag_tree_builtin_dce) + return false; + + gcc_assert (call); + gcc_assert (!DECL_P (call)); + gcc_assert (TREE_CODE (call) == CALL_EXPR); + + fn = get_callee_fndecl (call); + if (!fn || !DECL_BUILT_IN (fn)) + return false; + + fnc = DECL_FUNCTION_CODE (fn); + switch (fnc) + { + CASE_FLT_FN (BUILT_IN_POW): + if (check_pow (call)) + is_unnecessary_except_errno = true; + break; + + CASE_FLT_FN (BUILT_IN_LOG): + if (check_log (call)) + is_unnecessary_except_errno = true; + break; + default : + is_unnecessary_except_errno = false; + break; + } + + if (!is_unnecessary_except_errno) + return false; + + return is_unnecessary_except_errno; +} + + /* If STMT is not already marked necessary, mark it, and add it to the worklist if ADD_TO_WORKLIST is true. */ static inline void @@ -244,6 +430,13 @@ mark_operand_necessary (tree op) if (NECESSARY (stmt) || IS_EMPTY_STMT (stmt)) return; + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, " Marked as necessary: "); + print_generic_stmt (dump_file, stmt, TDF_SLIM); + fprintf (dump_file, "\n"); + } + NECESSARY (stmt) = 1; VEC_safe_push (tree, heap, worklist, stmt); } @@ -294,15 +487,16 @@ mark_stmt_if_obviously_necessary (tree stmt, bool aggressive) /* Most, but not all function calls are required. Function calls that produce no result and have no side effects (i.e. const pure functions) are unnecessary. */ - if (TREE_SIDE_EFFECTS (stmt)) - mark_stmt_necessary (stmt, true); + if (TREE_SIDE_EFFECTS (stmt)) + mark_stmt_necessary (stmt, true); + return; case GIMPLE_MODIFY_STMT: op = get_call_expr_in (stmt); if (op && TREE_SIDE_EFFECTS (op)) { - mark_stmt_necessary (stmt, true); + mark_stmt_necessary (stmt, true); return; } @@ -523,6 +717,421 @@ propagate_necessity (struct edge_list *el) } } +/* Method to generate conditional statements for guarding condionally + dead calls to pow. One or more statements can be generated for + each logical condition. Statement groups of different conditions + are separated by a NULL tree. */ +static void +gen_conditions_for_pow (tree pow_call, enum built_in_function fnc, + VEC (tree, heap)* conds, unsigned * nconds) +{ + tree base, expn; + enum tree_code bc, ec; + gcc_assert (TREE_CODE (pow_call) == CALL_EXPR); + gcc_assert (call_expr_nargs (pow_call) == 2); + gcc_assert (fnc == BUILT_IN_POW); + + *nconds = 0; + + base = CALL_EXPR_ARG (pow_call, 0); + expn = CALL_EXPR_ARG (pow_call, 1); + + bc = TREE_CODE (base); + ec = TREE_CODE (expn); + + gcc_assert (TREE_CODE_CLASS (bc) != tcc_constant || + bc == REAL_CST); + gcc_assert (TREE_CODE_CLASS (ec) != tcc_constant || + ec == REAL_CST); + + if (bc == REAL_CST) + { + tree float_typ, max_exp_real_cst; + tree temp, tempn, tempc, tempcn, stmt1, stmt2, stmt3; + REAL_VALUE_TYPE mv; + + /* See candidate selection in check_pow. + Since the candidates have a given range + of base values, the guard code generated + for such calls: pow(const,y) are simple: + if ( y > max_y ) + pow(const, y); + max_y can be computed separately for each + const base, but in this implemetation, we + choose to compute it using the max base + in the allowed range. */ + + REAL_VALUE_TYPE bcv = TREE_REAL_CST (base); + gcc_assert (!REAL_VALUES_EQUAL (bcv, dconst1)); + gcc_assert (!REAL_VALUES_LESS (bcv, dconst1)); + real_from_integer (&mv, VOIDmode,256,0,1), + gcc_assert (!REAL_VALUES_LESS (mv, bcv)); + float_typ = TREE_TYPE (expn); + + max_exp_real_cst = build_real (float_typ, mv); + temp = create_tmp_var (float_typ, "DCE_COND"); + stmt1 = build_gimple_modify_stmt (temp, expn); + tempn = make_ssa_name (temp, stmt1); + GIMPLE_STMT_OPERAND (stmt1, 0) = tempn; + + tempc = create_tmp_var (boolean_type_node, "DCE_COND_TEST"); + stmt2 = build_gimple_modify_stmt (tempc, + build2 (GT_EXPR, + boolean_type_node, + tempn, max_exp_real_cst)); + tempcn = make_ssa_name (tempc, stmt2); + GIMPLE_STMT_OPERAND (stmt2, 0) = tempcn; + + stmt3 = build3 (COND_EXPR, void_type_node, + tempcn, NULL_TREE, NULL_TREE); + VEC_safe_push (tree, heap, conds, stmt1); + VEC_safe_push (tree, heap, conds, stmt2); + VEC_safe_push (tree, heap, conds, stmt3); + (*nconds)++; + + } + else if (bc == SSA_NAME) + { + tree def, nm, rhs, rhs0, var, int_typ, float_typ; + tree max_exp_cst, max_exp_real_cst; + tree temp1, temp1n, temp2, temp2n, temp2c, temp2cn; + tree cst0, stmt1, stmt2, stmt3; + int sz, max_exp; + + /* Generate error condition code for pow calls with + non constant base value. The candidates selected + have their base argument value converted from + integer (see check_pow) value (1,2,4 bytes), and + the max exp value is computed based on the size + of the integer type. The code below first does + sanity check and then does code generation. */ + + def = SSA_NAME_DEF_STMT (base); + gcc_assert (TREE_CODE (def) == GIMPLE_MODIFY_STMT); + + nm = GIMPLE_STMT_OPERAND (def,0); + gcc_assert (TREE_CODE (nm) == SSA_NAME); + gcc_assert (nm == base); + + rhs = GIMPLE_STMT_OPERAND (def,1); + + gcc_assert (TREE_CODE (rhs) == FLOAT_EXPR); + rhs0 = TREE_OPERAND (rhs,0); + gcc_assert (TREE_CODE (rhs0) == SSA_NAME); + + var = SSA_NAME_VAR (rhs0); + gcc_assert (TREE_CODE (var) == VAR_DECL + || TREE_CODE (var) == PARM_DECL); + + int_typ = TREE_TYPE (var); + gcc_assert (TREE_CODE (int_typ) == INTEGER_TYPE); + + sz = int_size_in_bytes (int_typ); + gcc_assert (sz > 0 && sz <= INT_TYPE_SIZE) ; + + + float_typ = TREE_TYPE (SSA_NAME_VAR (expn)); + if (sz == 1) + max_exp = 128; + else if (sz == 2) + max_exp = 64; + else + { + gcc_assert (sz == 4); + max_exp = 32; + } + max_exp_cst = build_int_cst (integer_type_node, max_exp); + max_exp_real_cst = build_real_from_int_cst (float_typ, max_exp_cst); + + /* For pow ((dobule)x,y), generate the following conditions: + cond 1: + temp1 = x; + if (temp1 <= 0) + + cond 2: + temp2 = y; + if (temp2 > max_exp_real_cst) */ + + temp2 = create_tmp_var (float_typ, "DCE_COND2"); + stmt1 = build_gimple_modify_stmt (temp2, expn); + temp2n = make_ssa_name (temp2, stmt1); + GIMPLE_STMT_OPERAND (stmt1,0) = temp2n; + + temp2c = create_tmp_var (boolean_type_node, "DCE_COND2_TEST"); + stmt2 = build_gimple_modify_stmt (temp2c, + build2 (GT_EXPR, + boolean_type_node, + temp2n, max_exp_real_cst)); + temp2cn = make_ssa_name (temp2c, stmt2); + GIMPLE_STMT_OPERAND (stmt2, 0) = temp2cn; + + stmt3 = build3 (COND_EXPR, void_type_node, + temp2cn, NULL_TREE, NULL_TREE); + VEC_safe_push (tree, heap, conds, stmt1); + VEC_safe_push (tree, heap, conds, stmt2); + VEC_safe_push (tree, heap, conds, stmt3); + (*nconds)++; + + /* now a seperator*/ + VEC_safe_push (tree, heap, conds, NULL); + + temp1 = create_tmp_var (int_typ, "DCE_COND1"); + cst0 = build_int_cst (int_typ, 0); + stmt1 = build_gimple_modify_stmt (temp1, rhs0); + temp1n = make_ssa_name (temp1, stmt1); + GIMPLE_STMT_OPERAND (stmt1,0) = temp1n; + stmt2 = build3 (COND_EXPR, void_type_node, + build2 (LE_EXPR, boolean_type_node, temp1n, cst0), + NULL_TREE, NULL_TREE); + + VEC_safe_push (tree, heap, conds, stmt1); + VEC_safe_push (tree, heap, conds, stmt2); + (*nconds)++; + + } + else + gcc_unreachable (); +} + +/* The method to generate error condition guard code for log(x) + calls. */ +static void +gen_conditions_for_log (tree call, enum built_in_function fnc, + VEC (tree, heap)* conds, unsigned * nconds) +{ + tree arg, cst0, temp, tempn, tempc, tempcn, stmt1, stmt2, stmt3; + gcc_assert (TREE_CODE (call) == CALL_EXPR); + gcc_assert (fnc == BUILT_IN_LOG || fnc == BUILT_IN_LOGF || fnc == BUILT_IN_LOGL); + + *nconds = 0; + + /* for log(x), + Generate condition + + temp = x + if (x <= 0) + */ + arg = CALL_EXPR_ARG (call, 0); + cst0 = build_real (TREE_TYPE (arg), dconst0); + temp = create_tmp_var (TREE_TYPE (arg), "DCE_COND"); + stmt1 = build_gimple_modify_stmt (temp, arg); + tempn = make_ssa_name (temp, stmt1); + GIMPLE_STMT_OPERAND (stmt1,0) = tempn; + + tempc = create_tmp_var (boolean_type_node, "DCE_COND_TEST"); + stmt2 = build_gimple_modify_stmt (tempc, + build2 (LE_EXPR, + boolean_type_node, + tempn, cst0)); + tempcn = make_ssa_name (tempc, stmt2); + GIMPLE_STMT_OPERAND (stmt2, 0) = tempcn; + + stmt3 = build3 (COND_EXPR, void_type_node, tempcn, + NULL_TREE, NULL_TREE); + + VEC_safe_push (tree, heap, conds, stmt1); + VEC_safe_push (tree, heap, conds, stmt2); + VEC_safe_push (tree, heap, conds, stmt3); + (*nconds)++; + +} + + +/* The method to generate shrink wrap conditions for partially + a dead builtin call whose return value is not used anywhere, + but has to be kept live due to potential error condition. + + BI_CALL: the builtin call + CONDS : the vector of statements for condition code + NCODES : the pointer to the number of logical conditions, + which may be different from the length of CONDS + vector. Statements belonging to different logical + condition are separated by NULL tree in the vector +*/ + +static void +gen_shrink_wrap_conditions (tree bi_call, VEC (tree, heap)* conds, unsigned int * nconds) +{ + tree call, fn; + enum built_in_function fnc; + gcc_assert (nconds && conds); + gcc_assert (VEC_length(tree, conds) == 0); + gcc_assert (TREE_CODE (bi_call) == GIMPLE_MODIFY_STMT || + TREE_CODE (bi_call) == CALL_EXPR); + + call = bi_call; + if (TREE_CODE (call) == GIMPLE_MODIFY_STMT) + call = get_call_expr_in (bi_call); + + fn = get_callee_fndecl (call); + gcc_assert (fn && DECL_BUILT_IN (fn)); + fnc = DECL_FUNCTION_CODE (fn); + *nconds = 0; + + switch (fnc) + { + /*CASE_FLT_FN(BUILT_IN_POW)*/ + case BUILT_IN_POW: + gen_conditions_for_pow (call, fnc, conds, nconds); + break; + /*CASE_FLT_FN(BUILT_IN_LOG):*/ + case BUILT_IN_LOG: + case BUILT_IN_LOGF: + case BUILT_IN_LOGL: + gen_conditions_for_log (call, fnc, conds, nconds); + break; + default : + gcc_assert (0); + break; + } + + gcc_assert (*nconds); + return; +} + + +#define ERR_PROB 0.01 + +/* The method to shrink wrap a partially dead builtin call + whose return value is not used anywhere, but has to be kept + live due to potential error condition. */ +static void +shrink_wrap_one_built_in_call (tree bi_call) +{ + block_stmt_iterator bi_call_bsi, join_tgt_bsi; + basic_block bi_call_bb, join_tgt_bb, guard_bb, guard_bb0; + edge join_tgt_in_edge_from_call, join_tgt_in_edge_fall_thru; + tree join_tgt_label_decl, join_tgt_label; + edge bi_call_in_edge0, guard_bb_in_edge; + VEC (tree, heap) *conds; + unsigned tn_cond_stmts, nconds; + unsigned ci; + tree cond_expr = NULL; + tree cond_expr_start; + tree bi_call_label_decl; + tree bi_call_label; + + conds = VEC_alloc (tree, heap, 10); + gen_shrink_wrap_conditions (bi_call, conds, &nconds); + + gcc_assert (nconds > 0); + /* Make sure no reallocation happens. */ + gcc_assert (VEC_length (tree, conds) <= 10); + gcc_assert (VEC_length (tree, conds) >= nconds); + bi_call_bb = bb_for_stmt (bi_call); + + /* Now find the join target bb -- split + bi_call_bb if needed */ + bi_call_bsi = bsi_for_stmt (bi_call); + + gcc_assert (!bsi_end_p (bi_call_bsi)); + join_tgt_in_edge_from_call = split_block (bi_call_bb, bi_call); + bi_call_bsi = bsi_for_stmt (bi_call); + + join_tgt_bb = join_tgt_in_edge_from_call->dest; + join_tgt_label_decl = create_artificial_label (); + join_tgt_label = build1 (LABEL_EXPR, void_type_node, join_tgt_label_decl); + join_tgt_bsi = bsi_start (join_tgt_bb); + bsi_insert_before (&join_tgt_bsi, join_tgt_label, BSI_SAME_STMT); + + /* Now it is time to insert the first condtional expression + into bi_call_bb and split this bb so that bi_call is + shrink-wrapped*/ + tn_cond_stmts = VEC_length (tree, conds); + cond_expr = NULL; + cond_expr_start = VEC_index (tree, conds,0); + for (ci = 0; ci < tn_cond_stmts; ci++) + { + tree c = VEC_index (tree, conds, ci); + gcc_assert ( c || ci != 0 ); + if (!c) break; + bsi_insert_before (&bi_call_bsi, c, BSI_SAME_STMT); + cond_expr = c; + } + nconds --; + ci ++; + gcc_assert (cond_expr && TREE_CODE (cond_expr) == COND_EXPR); + + /* now the label*/ + bi_call_label_decl = create_artificial_label (); + bi_call_label = build1 (LABEL_EXPR, void_type_node, bi_call_label_decl); + bsi_insert_before (&bi_call_bsi, bi_call_label, BSI_SAME_STMT); + + bi_call_in_edge0 = split_block (bi_call_bb, cond_expr); + bi_call_in_edge0->flags &= ~EDGE_FALLTHRU; + bi_call_in_edge0->flags |= EDGE_TRUE_VALUE; + guard_bb0 = bi_call_bb; + bi_call_bb = bi_call_in_edge0->dest; + join_tgt_in_edge_fall_thru = make_edge (guard_bb0, join_tgt_bb, EDGE_FALSE_VALUE); + + bi_call_in_edge0->probability = REG_BR_PROB_BASE*ERR_PROB; + join_tgt_in_edge_fall_thru->probability = + REG_BR_PROB_BASE - bi_call_in_edge0->probability; + + /* code generation for the rest of the conditions */ + guard_bb = guard_bb0; + for (; nconds > 0; ) + { + unsigned ci0; + edge bi_call_in_edge; + block_stmt_iterator guard_bsi = bsi_for_stmt (cond_expr_start); + ci0 = ci; + cond_expr_start = VEC_index (tree, conds, ci0); + for (; ci < tn_cond_stmts; ci++) + { + tree c = VEC_index (tree, conds, ci); + gcc_assert ( c || ci != ci0 ); + if (!c) + break; + bsi_insert_before (&guard_bsi, c, BSI_SAME_STMT); + cond_expr = c; + } + nconds --; + ci ++; + gcc_assert (cond_expr && TREE_CODE (cond_expr) == COND_EXPR); + guard_bb_in_edge = split_block (guard_bb, cond_expr); + guard_bb_in_edge->flags &= ~EDGE_FALLTHRU; + guard_bb_in_edge->flags |= EDGE_FALSE_VALUE; + + bi_call_in_edge = make_edge (guard_bb, bi_call_bb, EDGE_TRUE_VALUE); + + bi_call_in_edge->probability = REG_BR_PROB_BASE*ERR_PROB; + guard_bb_in_edge->probability = + REG_BR_PROB_BASE - bi_call_in_edge->probability; + + } + + VEC_free (tree, heap, conds); + if (dump_file && (dump_flags & TDF_DETAILS)) + { + location_t loc; + loc = EXPR_LOCATION (bi_call); + inform ( + "%Hfunction call is shrink-wrapped into error conditions.", + &loc); + } +} + +/* The top level method for conditional dead code shrink + wrapping transformation. */ + +static bool +shrink_wrap_conditional_dead_built_in_calls (void) +{ + unsigned i = 0; + unsigned n = VEC_length (tree, cond_dead_built_in_calls); + if (n == 0) return false; + + for (; i < n ; i++) + { + tree bi_call = VEC_index (tree, cond_dead_built_in_calls, i); + shrink_wrap_one_built_in_call (bi_call); + } + + cfg_altered = true; + + return true; +} /* Remove dead PHI nodes from block BB. */ @@ -711,6 +1320,18 @@ eliminate_unnecessary_stmts (void) print_generic_stmt (dump_file, t, TDF_SLIM); fprintf (dump_file, "\n"); } + + if (is_unnecessary_except_errno_call (call)) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fprintf (dump_file, "Found conditional dead call: "); + print_generic_stmt (dump_file, t, TDF_SLIM); + fprintf (dump_file, "\n"); + } + VEC_safe_push (tree, heap, cond_dead_built_in_calls, call); + } + push_stmt_changes (bsi_stmt_ptr (i)); TREE_BLOCK (call) = TREE_BLOCK (t); bsi_replace (&i, call, false); @@ -718,6 +1339,8 @@ eliminate_unnecessary_stmts (void) mark_symbols_for_renaming (call); pop_stmt_changes (bsi_stmt_ptr (i)); release_ssa_name (oldlhs); + + } notice_special_calls (call); } @@ -726,6 +1349,9 @@ eliminate_unnecessary_stmts (void) } } + something_changed |= + shrink_wrap_conditional_dead_built_in_calls (); + return something_changed; } @@ -776,7 +1402,9 @@ tree_dce_init (bool aggressive) sbitmap_zero (processed); worklist = VEC_alloc (tree, heap, 64); + cond_dead_built_in_calls = VEC_alloc (tree, heap,64); cfg_altered = false; + } /* Cleanup after this pass. */ @@ -799,7 +1427,9 @@ tree_dce_done (bool aggressive) sbitmap_free (processed); VEC_free (tree, heap, worklist); + VEC_free (tree, heap, cond_dead_built_in_calls); } + /* Main routine to eliminate dead code. @@ -841,7 +1471,7 @@ perform_tree_ssa_dce (bool aggressive) find_obviously_necessary_stmts (el); propagate_necessity (el); - + something_changed |= eliminate_unnecessary_stmts (); something_changed |= cfg_altered;