On Fri, Aug 21, 2026 at 12:20 AM Naveen
<[email protected]> wrote:
>
> 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.

We want to factor out the ones which have side effects, that is the
main point of the PR.

>
> 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;

This should only be for machine builtins and some other builtins e.g.
__builtin_crc* for some of its arguments.

Though, factor_out_conditional_operation should already handle all
internal and builtin functions that are const. Though maybe there is a
bug with respect to the __builtin_crc :).

> +
> +  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