phiopt already factors matching operations and loads out of diamond PHIs.
Extend this to direct calls: when both PHI arguments are single-use calls
to the same side-effect-free, non-throwing function and the calls differ
in exactly one argument. Create a PHI for the differing argument and emit
a single merged call in the merge block.
Direct-call cgraph edges for the two arm calls are removed and a new
edge is created for the merged call.
Assisted-by: GPT-5.5
gcc/ChangeLog
PR tree-optimization/125793
* tree-ssa-phiopt.cc: Include cgraph.h and attribs.h.
(last_nondebug_stmt_p): New helper.
(ssa_name_occurs_in_abnormal_phi_p): Likewise.
(call_operand_occurs_in_abnormal_phi_p): Likewise.
(factor_out_conditional_call): New function.
(factor_out_all): Call factor_out_conditional_call.
gcc/testsuite/ChangeLog
PR tree-optimization/125793
* gcc.dg/tree-ssa/pr125793.c: New test.
Signed-off-by: Naveen <[email protected]>
---
gcc/testsuite/gcc.dg/tree-ssa/pr125793.c | 55 +++++
gcc/tree-ssa-phiopt.cc | 245 +++++++++++++++++++++++
2 files changed, 300 insertions(+)
create mode 100644 gcc/testsuite/gcc.dg/tree-ssa/pr125793.c
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr125793.c
b/gcc/testsuite/gcc.dg/tree-ssa/pr125793.c
new file mode 100644
index 00000000000..6abdb4aecea
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/pr125793.c
@@ -0,0 +1,55 @@
+/* PR tree-optimization/125793 */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-phiopt1-details" } */
+
+__attribute__((const, nothrow)) long transform (long);
+__attribute__((const, nothrow)) long transform2 (long, long);
+__attribute__((nothrow)) long side_effect (long);
+
+long
+transform_max (long a, long b)
+{
+ return (a >= b) ? transform (a) : transform (b);
+}
+
+long
+transform2_max (long a, long b, long k)
+{
+ return (a >= b) ? transform2 (a, k) : transform2 (b, k);
+}
+
+long
+do_not_factor_side_effects (long a, long b, int c)
+{
+ return c ? side_effect (a) : side_effect (b);
+}
+
+long
+do_not_factor_two_diffs (long a, long b, long c, long d)
+{
+ return (a >= b) ? transform2 (a, c) : transform2 (b, d);
+}
+
+long
+do_not_factor_novops_side_effects (long a, long b, int c)
+{
+#ifdef __HAVE_SPECULATION_SAFE_VALUE
+ return (c
+ ? __builtin_speculation_safe_value (a)
+ : __builtin_speculation_safe_value (b));
+#else
+ return a + b + c;
+#endif
+}
+
+long
+do_not_factor_const_diff (long a, int c)
+{
+ return c ? transform2 (a, 1L) : transform2 (a, 2L);
+}
+
+/* { dg-final { scan-tree-dump-times "changed to factor call out from
COND_EXPR" 2 "phiopt1" } } */
+/* { dg-final { scan-tree-dump-times "transform \\(" 1 "phiopt1" } } */
+/* { dg-final { scan-tree-dump-times "transform2 \\(" 4 "phiopt1" } } */
+/* { dg-final { scan-tree-dump-times "side_effect \\(" 2 "phiopt1" } } */
+/* { dg-final { scan-tree-dump-times "__builtin_speculation_safe_value" 2
"phiopt1" { target { i?86-*-* x86_64-*-* } } } } */
diff --git a/gcc/tree-ssa-phiopt.cc b/gcc/tree-ssa-phiopt.cc
index 784221c83b3..23eebdb01c1 100644
--- a/gcc/tree-ssa-phiopt.cc
+++ b/gcc/tree-ssa-phiopt.cc
@@ -55,6 +55,8 @@ along with GCC; see the file COPYING3. If not see
#include "tree-ssa-loop-niter.h"
#include "gimple-predict.h"
#include "alias.h"
+#include "cgraph.h"
+#include "attribs.h"
/* Return the singleton PHI in the SEQ of PHIs for edges E0 and E1. */
@@ -639,6 +641,248 @@ factor_out_conditional_operation (edge e0, edge e1,
basic_block merge,
return true;
}
+/* Return true if STMT is the last non-debug statement in its basic block
+ and statements that do not affect the executable instruction stream. */
+
+static bool
+last_nondebug_stmt_p (gimple *stmt)
+{
+ gimple_stmt_iterator gsi = gsi_for_stmt (stmt);
+ gsi_next_nondebug (&gsi);
+ while (!gsi_end_p (gsi))
+ {
+ gimple *next = gsi_stmt (gsi);
+ if (gimple_code (next) != GIMPLE_NOP
+ && gimple_code (next) != GIMPLE_PREDICT
+ && gimple_code (next) != GIMPLE_LABEL)
+ return false;
+ gsi_next_nondebug (&gsi);
+ }
+ return true;
+}
+
+/* Return true if T is an SSA name that occurs in an abnormal PHI. */
+
+static bool
+ssa_name_occurs_in_abnormal_phi_p (tree t)
+{
+ return (t
+ && TREE_CODE (t) == SSA_NAME
+ && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (t));
+}
+
+/* Return true if any SSA operand of CALL that would be moved to the merge
+ block occurs in an abnormal PHI. */
+
+static bool
+call_operand_occurs_in_abnormal_phi_p (gcall *call)
+{
+ if (!gimple_call_internal_p (call)
+ && ssa_name_occurs_in_abnormal_phi_p (gimple_call_fn (call)))
+ return true;
+
+ if (ssa_name_occurs_in_abnormal_phi_p (gimple_call_chain (call)))
+ return true;
+
+ for (unsigned i = 0; i < gimple_call_num_args (call); ++i)
+ if (ssa_name_occurs_in_abnormal_phi_p (gimple_call_arg (call, i)))
+ return true;
+
+ return false;
+}
+
+/* If the arguments of PHI are calls to the same function and the calls
+ differ in exactly one argument, factor out the differing argument into
+ a new PHI and perform one call on the PHI result. */
+
+static bool
+factor_out_conditional_call (edge e0, edge e1, basic_block merge, gphi *phi)
+{
+ /* 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);
+ if (TREE_CODE (arg0) != SSA_NAME
+ || TREE_CODE (arg1) != SSA_NAME
+ || !has_single_use (arg0)
+ || !has_single_use (arg1))
+ return false;
+
+ gcall *call0 = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (arg0));
+ gcall *call1 = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (arg1));
+ if (!call0 || !call1)
+ return false;
+
+ /* The calls need to be in the two conditional blocks and be the final
+ statements in those blocks. */
+ if (gimple_bb (call0) != e0->src
+ || gimple_bb (call1) != e1->src
+ || !last_nondebug_stmt_p (call0)
+ || !last_nondebug_stmt_p (call1))
+ return false;
+
+ /* Only handle calls that are side-effect-free and whose movement does not
+ require virtual operand, EH or abnormal-control-flow rewiring. */
+ if (gimple_has_side_effects (call0)
+ || gimple_has_side_effects (call1)
+ || is_ctrl_altering_stmt (call0)
+ || is_ctrl_altering_stmt (call1)
+ || gimple_vuse (call0)
+ || gimple_vuse (call1)
+ || gimple_vdef (call0)
+ || gimple_vdef (call1)
+ || gimple_has_volatile_ops (call0)
+ || gimple_has_volatile_ops (call1)
+ || stmt_could_throw_p (cfun, call0)
+ || stmt_could_throw_p (cfun, call1)
+ || stmt_can_make_abnormal_goto (call0)
+ || stmt_can_make_abnormal_goto (call1))
+ return false;
+
+ if (gimple_call_internal_p (call0) != gimple_call_internal_p (call1)
+ || gimple_call_num_args (call0) != gimple_call_num_args (call1)
+ || gimple_call_chain (call0) != gimple_call_chain (call1)
+ || gimple_call_return_slot_opt_p (call0)
+ || gimple_call_return_slot_opt_p (call1)
+ || gimple_call_flags (call0) != gimple_call_flags (call1))
+ return false;
+
+ if (call0->subcode != call1->subcode)
+ return false;
+
+ if (call_operand_occurs_in_abnormal_phi_p (call0)
+ || call_operand_occurs_in_abnormal_phi_p (call1))
+ return false;
+
+ unsigned nargs = gimple_call_num_args (call0);
+ if (nargs == 0)
+ return false;
+
+ tree old_fndecl0 = NULL_TREE;
+ tree old_fndecl1 = NULL_TREE;
+ if (gimple_call_internal_p (call0))
+ {
+ if (gimple_call_internal_unique_p (call0)
+ || gimple_call_internal_unique_p (call1)
+ || (gimple_call_internal_fn (call0)
+ != gimple_call_internal_fn (call1)))
+ return false;
+ }
+ else
+ {
+ old_fndecl0 = gimple_call_fndecl (call0);
+ old_fndecl1 = gimple_call_fndecl (call1);
+ /* Keep this direct-call only so cgraph edges can be updated precisely.
*/
+ if (!old_fndecl0 || old_fndecl0 != old_fndecl1)
+ return false;
+ if (!operand_equal_p (gimple_call_fn (call0), gimple_call_fn (call1), 0))
+ return false;
+
+ tree fntype0 = gimple_call_fntype (call0);
+ tree fntype1 = gimple_call_fntype (call1);
+ if (!types_compatible_p (fntype0, fntype1)
+ || comp_type_attributes (fntype0, fntype1) != 1)
+ return false;
+ }
+
+ int opnum = -1;
+ tree new_arg0 = NULL_TREE;
+ tree new_arg1 = NULL_TREE;
+ for (unsigned i = 0; i < nargs; ++i)
+ {
+ tree carg0 = gimple_call_arg (call0, i);
+ tree carg1 = gimple_call_arg (call1, i);
+ if (operand_equal_for_phi_arg_p (carg0, carg1))
+ continue;
+ if (opnum != -1)
+ return false;
+ opnum = i;
+ new_arg0 = carg0;
+ new_arg1 = carg1;
+ }
+ if (opnum == -1)
+ return false;
+
+ /* Do not factor if the differing argument is an integer constant.
+ A PHI of two integer constants yields a non-constant SSA name which
+ may violate target immediate-operand constraints (e.g. AVX-512 builtins
+ that require an 8-bit integer constant argument). */
+ if (TREE_CODE (new_arg0) == INTEGER_CST
+ || TREE_CODE (new_arg1) == INTEGER_CST)
+ return false;
+
+ if (!types_compatible_p (TREE_TYPE (new_arg0), TREE_TYPE (new_arg1)))
+ return false;
+
+ if (!is_factor_profitable (call0, merge, &new_arg0, 1)
+ || !is_factor_profitable (call1, merge, &new_arg1, 1))
+ return false;
+
+ tree temp = make_ssa_name (TREE_TYPE (new_arg0));
+ gphi *newphi = create_phi_node (temp, merge);
+ add_phi_arg (newphi, new_arg0, e0, gimple_location (call0));
+ add_phi_arg (newphi, new_arg1, e1, gimple_location (call1));
+
+ auto_vec<tree, 8> args;
+ args.reserve_exact (nargs);
+ for (unsigned i = 0; i < nargs; ++i)
+ args.quick_push (i == (unsigned) opnum ? temp : gimple_call_arg (call0,
i));
+
+ gcall *new_call;
+ if (gimple_call_internal_p (call0))
+ new_call = gimple_build_call_internal_vec (gimple_call_internal_fn (call0),
+ args);
+ else
+ {
+ new_call = gimple_build_call_vec (gimple_call_fn (call0), args);
+ gimple_call_set_fntype (new_call, gimple_call_fntype (call0));
+ gimple_call_set_chain (new_call, gimple_call_chain (call0));
+ }
+ gimple_call_set_lhs (new_call, gimple_phi_result (phi));
+ gimple_call_copy_flags (new_call, call0);
+
+ location_t locus = gimple_location (phi);
+ if (locus == UNKNOWN_LOCATION)
+ locus = gimple_location (call0);
+ if (locus != UNKNOWN_LOCATION)
+ gimple_set_location (new_call, locus);
+
+ gimple_stmt_iterator gsi = gsi_after_labels (merge);
+ gsi_insert_before (&gsi, new_call, GSI_SAME_STMT);
+
+ if (!gimple_call_internal_p (new_call))
+ {
+ cgraph_update_edges_for_call_stmt (call0, old_fndecl0, NULL);
+ cgraph_update_edges_for_call_stmt (call1, old_fndecl1, NULL);
+ cgraph_update_edges_for_call_stmt (new_call, NULL_TREE, new_call);
+ }
+
+ gsi = gsi_for_stmt (phi);
+ remove_phi_node (&gsi, false);
+
+ gsi = gsi_for_stmt (call0);
+ gsi_remove (&gsi, true);
+ release_defs (call0);
+ gsi = gsi_for_stmt (call1);
+ gsi_remove (&gsi, true);
+ release_defs (call1);
+
+ if (dump_file && (dump_flags & TDF_DETAILS))
+ {
+ fprintf (dump_file, "PHI ");
+ print_generic_expr (dump_file, gimple_call_lhs (new_call));
+ fprintf (dump_file, " changed to factor call out from COND_EXPR.\n");
+ }
+
+ statistics_counter_event (cfun, "factored out call", 1);
+ return true;
+}
+
/* Return TRUE if SEQ/OP pair should be allowed during early phiopt.
Currently this is to allow MIN/MAX and ABS/NEGATE and constants. */
@@ -4431,6 +4675,7 @@ factor_out_all (edge e1, edge e2, basic_block merge,
if ((diamond_p
&& factor_out_conditional_load (e1, e2, merge, phi, early_p,
!fold_before_rtl_expansion_p ()))
+ || factor_out_conditional_call (e1, e2, merge, phi)
|| factor_out_conditional_operation (e1, e2, merge, phi,
cond_stmt, early_p))
{
--
2.34.1