Sometimes factor_out_conditional_operation can factor out an operation that causes a phi node to become the same element. Other times, we want to factor out a binary operator because it can improve code generation, an example is PR 110015 (openjpeg).
Note this includes a heuristic to decide if factoring out the operation is profitable or not. It can be expanded to include a better live range extend detector. Right now it has a simple one where if it is live on a dominating path, it is considered a live or if there are a small # of assign statements (defaults to 5), then it does not extend the live range too much. Bootstrapped and tested on x86_64-linux-gnu. PR tree-optimization/112418 gcc/ChangeLog: * tree-ssa-phiopt.cc (is_factor_profitable): New function. (factor_out_conditional_operation): Add merge argument. Remove arg0/arg1 arguments. Return bool instead of the new phi. Early return for virtual ops. Call is_factor_profitable to check if the factoring would be profitable. (pass_phiopt::execute): Call factor_out_conditional_operation on all phis instead of just singleton phi. * doc/invoke.texi (--param phiopt-factor-max-stmts-live=): Document. * params.opt (--param=phiopt-factor-max-stmts-live=): New opt. gcc/testsuite/ChangeLog: * gcc.dg/tree-ssa/factor_op_phi-1.c: New test. * gcc.dg/tree-ssa/factor_op_phi-2.c: New test. * gcc.dg/tree-ssa/factor_op_phi-3.c: New test. * gcc.dg/tree-ssa/factor_op_phi-4.c: New test. Signed-off-by: Andrew Pinski <quic_apin...@quicinc.com> --- gcc/doc/invoke.texi | 4 + gcc/params.opt | 4 + .../gcc.dg/tree-ssa/factor_op_phi-1.c | 27 +++ .../gcc.dg/tree-ssa/factor_op_phi-2.c | 29 +++ .../gcc.dg/tree-ssa/factor_op_phi-3.c | 33 +++ .../gcc.dg/tree-ssa/factor_op_phi-4.c | 29 +++ gcc/tree-ssa-phiopt.cc | 209 ++++++++++++------ 7 files changed, 272 insertions(+), 63 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-1.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-2.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-3.c create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-4.c diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 8437b2029ed..aebcc9082ff 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -15473,6 +15473,10 @@ In each case, the @var{value} is an integer. The following choices of @var{name} are recognized for all targets: @table @gcctabopt +@item phiopt-factor-max-stmts-live +When factoring statements out of if/then/else, this is the max # of statements +after the defining statement to be allow to extend the lifetime of a name + @item predictable-branch-outcome When branch is predicted to be taken with probability lower than this threshold (in percent), then it is considered well predictable. diff --git a/gcc/params.opt b/gcc/params.opt index a08e4c1042d..24f440bbe71 100644 --- a/gcc/params.opt +++ b/gcc/params.opt @@ -861,6 +861,10 @@ Enum(parloops_schedule_type) String(runtime) Value(PARLOOPS_SCHEDULE_RUNTIME) Common Joined UInteger Var(param_partial_inlining_entry_probability) Init(70) Optimization IntegerRange(0, 100) Param Maximum probability of the entry BB of split region (in percent relative to entry BB of the function) to make partial inlining happen. +-param=phiopt-factor-max-stmts-live= +Common Joined UInteger Var(param_phiopt_factor_max_stmts_live) Init(5) Optimization IntegerRange(0, 100) Param +Maximum number of statements allowed inbetween the statement and the end to considered not extending the liferange. + -param=predictable-branch-outcome= Common Joined UInteger Var(param_predictable_branch_outcome) Init(2) IntegerRange(0, 50) Param Optimization Maximal estimated outcome of branch considered predictable. diff --git a/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-1.c b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-1.c new file mode 100644 index 00000000000..6c0971ff801 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-1.c @@ -0,0 +1,27 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-phiopt1-details -fdump-tree-optimized" } */ + +/* PR tree-optimization/112418 */ + +int f(int a, int b, int d) +{ + int c; + if (a < 0) + { + a = -a; + c = d > 0 ? d : -d; + } + else + { + a = a; + c = d > 0 ? d : -d; + } + return a + c; +} + +/* ABS <d> should be able to pull out of the if statement early on in phiopt1. */ +/* { dg-final { scan-tree-dump "changed to factor operation out from " "phiopt1" } } */ +/* { dg-final { scan-tree-dump-not "if " "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "phiopt1" } } */ +/* { dg-final { scan-tree-dump-not "if " "optimized" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-2.c b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-2.c new file mode 100644 index 00000000000..93e2eb3a051 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-2.c @@ -0,0 +1,29 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-phiopt1-details -fdump-tree-optimized" } */ + +/* PR tree-optimization/112418 */ + +/* This is factor_op_phi-1.c but with statements swapped in the inner if. */ + +int f(int a, int b, int d) +{ + int c; + if (a < 0) + { + c = d > 0 ? d : -d; + a = -a; + } + else + { + c = d > 0 ? d : -d; + a = a; + } + return a + c; +} + +/* ABS <d> should be able to pull out of the if statement early on in phiopt1. */ +/* { dg-final { scan-tree-dump "changed to factor operation out from " "phiopt1" } } */ +/* { dg-final { scan-tree-dump-not "if " "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "phiopt1" } } */ +/* { dg-final { scan-tree-dump-not "if " "optimized" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-3.c b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-3.c new file mode 100644 index 00000000000..0aca877fa36 --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-3.c @@ -0,0 +1,33 @@ +/* { dg-do compile } */ +/* { dg-options "-O1 -fdump-tree-phiopt1-details -fdump-tree-optimized" } */ + +int h(void); +int h1(void); + +int f(int a, int b, int d) +{ + int c; + if (a < 0) + { + a = h(); + c = d > 0 ? d : -d; + a += h(); + } + else + { + a = h1(); + c = d > 0 ? d : -d; + a += h1(); + } + return a + c; +} + +/* ABS <d> should be not to pulled out of the if statement early on in phiopt1 as it lengthes d's + live range over a call. + Note pre can lift the */ +/* { dg-final { scan-tree-dump-not "changed to factor operation out from " "phiopt1" } } */ +/* { dg-final { scan-tree-dump "if " "phiopt1" } } */ +/* { dg-final { scan-tree-dump "if " "optimized" } } */ +/* There will be 2 ABS due to the need for it in the inner if. */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 2 "optimized" } } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-4.c b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-4.c new file mode 100644 index 00000000000..482c39b203b --- /dev/null +++ b/gcc/testsuite/gcc.dg/tree-ssa/factor_op_phi-4.c @@ -0,0 +1,29 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fdump-tree-phiopt1-details -fdump-tree-optimized" } */ + +/* PR tree-optimization/112418 */ + +/* This is factor_op_phi-1.c but with statements swapped in the inner if. */ + +int f(int a, int b, int d, int e) +{ + int c; + if (a < 0) + { + c = d > 0 ? d : -d; + a = -a; + } + else + { + c = e > 0 ? e : -e; + a = a; + } + return a + c; +} + +/* ABS <d> should be able to pull out of the if statement early on in phiopt1. */ +/* { dg-final { scan-tree-dump "changed to factor operation out from " "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "if " 1 "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 1 "phiopt1" } } */ +/* { dg-final { scan-tree-dump-times "if " 1 "optimized" } } */ +/* { dg-final { scan-tree-dump-times "ABS_EXPR " 1 "optimized" } } */ diff --git a/gcc/tree-ssa-phiopt.cc b/gcc/tree-ssa-phiopt.cc index f3ee3a80c0f..38cc8506d1f 100644 --- a/gcc/tree-ssa-phiopt.cc +++ b/gcc/tree-ssa-phiopt.cc @@ -213,14 +213,90 @@ replace_phi_edge_with_variable (basic_block cond_block, bb->index); } +/* Returns true if the ARG used from DEF_STMT is profitable to move + to a PHI node of the basic block MERGE where the new statement + will be located. */ +static bool +is_factor_profitable (gimple *def_stmt, basic_block merge, tree arg) +{ + /* The defining statement should be conditional. */ + if (dominated_by_p (CDI_DOMINATORS, merge, + gimple_bb (def_stmt))) + return false; + + /* If the arg is invariant, then there is + no extending of the live range. */ + if (is_gimple_min_invariant (arg)) + return true; + + /* Otherwise, the arg needs to be a ssa name. */ + if (TREE_CODE (arg) != SSA_NAME) + return false; + + /* We should not increase the live range of arg + across too many statements or calls. */ + gimple_stmt_iterator gsi = gsi_for_stmt (def_stmt); + gsi_next_nondebug (&gsi); + + /* Skip past nops and predicates. */ + while (!gsi_end_p (gsi) + && (gimple_code (gsi_stmt (gsi)) == GIMPLE_NOP + || gimple_code (gsi_stmt (gsi)) == GIMPLE_PREDICT)) + gsi_next_nondebug (&gsi); + + /* If the defining statement is at the end of the bb, then it is + always profitable to be to move. */ + if (gsi_end_p (gsi)) + return true; + + /* Check if the uses of arg is dominated by merge block, this is a quick and + rough estimate if arg is still alive at the merge bb. */ + /* FIXME: extend to a more complete live range detection. */ + use_operand_p use_p; + imm_use_iterator iter; + FOR_EACH_IMM_USE_FAST (use_p, iter, arg) + { + gimple *use_stmt = USE_STMT (use_p); + basic_block use_bb = gimple_bb (use_stmt); + if (dominated_by_p (CDI_DOMINATORS, merge, use_bb)) + return true; + } + + /* If there are a few (non-call/asm) statements between + the old defining statement and end of the bb, then + the live range of new arg does not increase enough. */ + int max_statements = param_phiopt_factor_max_stmts_live; + + while (!gsi_end_p (gsi)) + { + gimple *stmt = gsi_stmt (gsi); + auto gcode = gimple_code (stmt); + /* Skip over NOPs and predicts. */ + if (gcode == GIMPLE_NOP + || gcode == GIMPLE_PREDICT) + { + gsi_next_nondebug (&gsi); + continue; + } + /* Non-assigns will extend the live range too much. */ + if (gcode != GIMPLE_ASSIGN) + return false; + max_statements --; + if (max_statements == 0) + return false; + gsi_next_nondebug (&gsi); + } + return true; +} + /* PR66726: Factor operations out of COND_EXPR. If the arguments of the PHI stmt are Unary operator, factor out the operation and perform the operation to the result of PHI stmt. COND_STMT is the controlling predicate. - Return the newly-created PHI, if any. */ + Return true if the operation was factored out; false otherwise. */ -static gphi * -factor_out_conditional_operation (edge e0, edge e1, gphi *phi, - tree arg0, tree arg1, gimple *cond_stmt) +static bool +factor_out_conditional_operation (edge e0, edge e1, basic_block merge, + gphi *phi, gimple *cond_stmt) { gimple *arg0_def_stmt = NULL, *arg1_def_stmt = NULL; tree temp, result; @@ -229,12 +305,21 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, location_t locus = gimple_location (phi); gimple_match_op arg0_op, arg1_op; - /* Handle only PHI statements with two arguments. TODO: If all - other arguments to PHI are INTEGER_CST or if their defining - statement have the same unary operation, we can handle more - than two arguments too. */ - if (gimple_phi_num_args (phi) != 2) - return NULL; + /* We should only get here if the phi had two arguments. */ + gcc_assert (gimple_phi_num_args (phi) == 2); + + /* Virtual operands are never handled. */ + if (virtual_operand_p (gimple_phi_result (phi))) + return false; + + tree arg0 = gimple_phi_arg_def (phi, e0->dest_idx); + tree arg1 = gimple_phi_arg_def (phi, e1->dest_idx); + gcc_assert (arg0 != NULL_TREE && arg1 != NULL_TREE); + + /* Arugments that are the same don't have anything to be + done to them. */ + if (operand_equal_for_phi_arg_p (arg0, arg1)) + return false; /* First canonicalize to simplify tests. */ if (TREE_CODE (arg0) != SSA_NAME) @@ -246,21 +331,21 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, if (TREE_CODE (arg0) != SSA_NAME || (TREE_CODE (arg1) != SSA_NAME && TREE_CODE (arg1) != INTEGER_CST)) - return NULL; + return false; /* Check if arg0 is an SSA_NAME and the stmt which defines arg0 is an unary operation. */ arg0_def_stmt = SSA_NAME_DEF_STMT (arg0); if (!gimple_extract_op (arg0_def_stmt, &arg0_op)) - return NULL; + return false; /* Check to make sure none of the operands are in abnormal phis. */ if (arg0_op.operands_occurs_in_abnormal_phi ()) - return NULL; + return false; /* Currently just support one operand expressions. */ if (arg0_op.num_ops != 1) - return NULL; + return false; tree new_arg0 = arg0_op.ops[0]; tree new_arg1; @@ -268,42 +353,45 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, /* If arg0 have > 1 use, then this transformation actually increases the number of expressions evaluated at runtime. */ if (!has_single_use (arg0)) - return NULL; + return false; + if (!is_factor_profitable (arg0_def_stmt, merge, new_arg0)) + return false; if (TREE_CODE (arg1) == SSA_NAME) { /* Check if arg1 is an SSA_NAME. */ arg1_def_stmt = SSA_NAME_DEF_STMT (arg1); if (!gimple_extract_op (arg1_def_stmt, &arg1_op)) - return NULL; + return false; if (arg1_op.code != arg0_op.code) - return NULL; + return false; if (arg1_op.num_ops != arg0_op.num_ops) - return NULL; + return false; if (arg1_op.operands_occurs_in_abnormal_phi ()) - return NULL; + return false; /* If arg1 have > 1 use, then this transformation actually increases the number of expressions evaluated at runtime. */ if (!has_single_use (arg1)) - return NULL; + return false; - /* Either arg1_def_stmt or arg0_def_stmt should be conditional. */ - if (dominated_by_p (CDI_DOMINATORS, gimple_bb (phi), gimple_bb (arg0_def_stmt)) - && dominated_by_p (CDI_DOMINATORS, - gimple_bb (phi), gimple_bb (arg1_def_stmt))) - return NULL; new_arg1 = arg1_op.ops[0]; + + if (!is_factor_profitable (arg1_def_stmt, merge, new_arg1)) + return false; } else { + /* For constants only handle if the phi was the only one. */ + if (single_non_singleton_phi_for_edges (phi_nodes (merge), e0, e1) == NULL) + return false; /* TODO: handle more than just casts here. */ if (!gimple_assign_cast_p (arg0_def_stmt)) - return NULL; + return false; /* arg0_def_stmt should be conditional. */ if (dominated_by_p (CDI_DOMINATORS, gimple_bb (phi), gimple_bb (arg0_def_stmt))) - return NULL; + return false; /* Only handle if arg1 is a INTEGER_CST and one that fits into the new type or if it is the same precision. */ @@ -311,7 +399,7 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, || !(int_fits_type_p (arg1, TREE_TYPE (new_arg0)) || (TYPE_PRECISION (TREE_TYPE (new_arg0)) == TYPE_PRECISION (TREE_TYPE (arg1))))) - return NULL; + return false; /* For the INTEGER_CST case, we are just moving the conversion from one place to another, which can often @@ -356,26 +444,16 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, && !(INTEGRAL_TYPE_P (lhst) && TYPE_UNSIGNED (lhst) && TYPE_PRECISION (lhst) == 1)) - return NULL; + return false; if (lhs != gimple_assign_rhs1 (arg0_def_stmt)) - return NULL; + return false; gsi_prev_nondebug (&gsi); if (!gsi_end_p (gsi)) - return NULL; + return false; } else - return NULL; + return false; } - gsi = gsi_for_stmt (arg0_def_stmt); - gsi_next_nondebug (&gsi); - /* Skip past nops and predicates. */ - while (!gsi_end_p (gsi) - && (gimple_code (gsi_stmt (gsi)) == GIMPLE_NOP - || gimple_code (gsi_stmt (gsi)) == GIMPLE_PREDICT)) - gsi_next_nondebug (&gsi); - /* Reject if the statement was not at the end of the block. */ - if (!gsi_end_p (gsi)) - return NULL; } new_arg1 = fold_convert (TREE_TYPE (new_arg0), arg1); @@ -386,7 +464,7 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, /* If types of new_arg0 and new_arg1 are different bailout. */ if (!types_compatible_p (TREE_TYPE (new_arg0), TREE_TYPE (new_arg1))) - return NULL; + return false; /* Create a new PHI stmt. */ result = gimple_phi_result (phi); @@ -404,7 +482,7 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, if (!result) { release_ssa_name (temp); - return NULL; + return false; } gsi = gsi_after_labels (gimple_bb (phi)); @@ -444,7 +522,7 @@ factor_out_conditional_operation (edge e0, edge e1, gphi *phi, statistics_counter_event (cfun, "factored out operation", 1); - return newphi; + return true; } @@ -4327,6 +4405,29 @@ pass_phiopt::execute (function *) basic_block merge = diamond_p ? EDGE_SUCC (bb2, 0)->dest : bb2; gimple_seq phis = phi_nodes (merge); + if (gimple_seq_empty_p (phis)) + return; + + /* Factor out operations from the phi if possible. */ + if (single_pred_p (bb1) + && EDGE_COUNT (merge->preds) == 2) + { + for (gsi = gsi_start (phis); !gsi_end_p (gsi); ) + { + gphi *phi = as_a <gphi *> (gsi_stmt (gsi)); + + if (factor_out_conditional_operation (e1, e2, merge, phi, + cond_stmt)) + { + /* Start over if there was an operation that was factored out because the new phi might have another opportunity. */ + phis = phi_nodes (merge); + gsi = gsi_start (phis); + } + else + gsi_next (&gsi); + } + } + /* Value replacement can work with more than one PHI so try that first. */ if (!early_p && !diamond_p) @@ -4353,24 +4454,6 @@ pass_phiopt::execute (function *) node. */ gcc_assert (arg0 != NULL_TREE && arg1 != NULL_TREE); - if (single_pred_p (bb1) - && EDGE_COUNT (merge->preds) == 2) - { - gphi *newphi = phi; - while (newphi) - { - phi = newphi; - /* factor_out_conditional_operation may create a new PHI in - BB2 and eliminate an existing PHI in BB2. Recompute values - that may be affected by that change. */ - arg0 = gimple_phi_arg_def (phi, e1->dest_idx); - arg1 = gimple_phi_arg_def (phi, e2->dest_idx); - gcc_assert (arg0 != NULL_TREE && arg1 != NULL_TREE); - newphi = factor_out_conditional_operation (e1, e2, phi, - arg0, arg1, - cond_stmt); - } - } /* Do the replacement of conditional if it can be done. */ if (match_simplify_replacement (bb, bb1, bb2, e1, e2, phi, -- 2.43.0