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

Reply via email to