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 >
