> -----Original Message-----
> From: Andrea Pinski <[email protected]>
> Sent: 14 November 2026 05:11
> To: Richard Ball <[email protected]>
> Cc: [email protected]; Richard Earnshaw
> <[email protected]>; Tamar Christina <[email protected]>;
> [email protected]; [email protected]; [email protected]
> Subject: Re: [PATCH] ifcombine: support aggregate ranged on loop bounds
>
> On Fri, Nov 13, 2026 at 6:44 AM <[email protected]> wrote:
> >
> > From: Richard Ball <[email protected]>
> >
> > The loop
> >
> > void add(int N, int *__restrict a, int *__restrict b, int *__restrict c) {
> > for (int i = 0; i < N; i++) {
> > c[i] = a[i] + b[i];
> > if (i > 1000) {
> > break;
> > }
> > }
> > }
> >
> > is an example of a loop that we have started vectorizing using early break
> > vectorization. However the given loop is not actually an early break loop
> > as
> > the condition on the exit introduces a MAX bound on N.
> >
> > This patch teaches ifcombine to identify such cases and rewrite the latch
> > condition from i < N to i < MIN (N, C) where C is a constant.
>
> First, this seems like it should be part of ivcanon pass rather than
> ifcombine. Is there a reason why you can't move it to there?
> Or maybe even loopinit ?
Because Richi rejected the ivcanon patch and said to move it to ifcombine.
> I can't tell if it reuses anything from ifcombine at all even so I
> question why it is even part of this pass in the first place. Why not
> its own or part of a loop one?
At this point I don't think this fits neatly in any of the existing passes.
So anywhere we move it it's going to "stick out".
I haven't looked at the patch yet, but will admit there's a lot more new
code than I was expecting.
Thanks,
Tamar
>
> Thanks,
> Andrea
>
> >
> > gcc/ChangeLog:
> >
> > PR tree-optimization/113134
> > * tree-ssa-ifcombine.cc
> > (struct ifcombine_exit_info): New struct.
> > (ifcombine_exit_niter): New function.
> > (ifcombine_reaches_header_via_latch_p): Likewise
> > (ifcombine_removable_exit_p): Likewise.
> > (ifcombine_collect_exit_bounds): Likewise.
> > (ifcombine_rewrite_loop_exits): Likewise.
> > (ifcombine_find_latch_exit): Likewise.
> > (ifcombine_aggregate_safe_p): Likewise.
> > (ifcombine_loop_exits): Likewise.
> > (ifcombine_loop_exits_in_function): Likewise.
> > (ifcombine_loop_exit_bounds): Likewise.
> > (pass_tree_ifcombine::execute): Add call for new functions.
> >
> > gcc/testsuite/ChangeLog:
> >
> > PR tree-optimization/113134
> > * gcc.dg/ifcombine-pr113134.c: New test.
> > ---
> > gcc/testsuite/gcc.dg/ifcombine-pr113134.c | 13 +
> > gcc/tree-ssa-ifcombine.cc | 300 +++++++++++++++++++++-
> > 2 files changed, 312 insertions(+), 1 deletion(-)
> > create mode 100644 gcc/testsuite/gcc.dg/ifcombine-pr113134.c
> >
> > diff --git a/gcc/testsuite/gcc.dg/ifcombine-pr113134.c
> b/gcc/testsuite/gcc.dg/ifcombine-pr113134.c
> > new file mode 100644
> > index 00000000000..7a8d7c1e98b
> > --- /dev/null
> > +++ b/gcc/testsuite/gcc.dg/ifcombine-pr113134.c
> > @@ -0,0 +1,13 @@
> > +/* { dg-do compile } */
> > +/* { dg-options "-O3 -fdump-tree-ifcombine-details -save-temps" } */
> > +
> > +void add(int N, int *__restrict a, int *__restrict b, int *__restrict c) {
> > + for (int i = 0; i < N; i++) {
> > + c[i] = a[i] + b[i];
> > + if (i > 1000) {
> > + break;
> > + }
> > + }
> > +}
> > +
> > +/* { dg-final { scan-tree-dump "ifcombine: aggregated loop" "ifcombine" } }
> */
> > \ No newline at end of file
>
> Watch out for missing newline at EOF.
>
> > diff --git a/gcc/tree-ssa-ifcombine.cc b/gcc/tree-ssa-ifcombine.cc
> > index d5e3d91e9b5..ad190427947 100644
> > --- a/gcc/tree-ssa-ifcombine.cc
> > +++ b/gcc/tree-ssa-ifcombine.cc
> > @@ -45,6 +45,11 @@ along with GCC; see the file COPYING3. If not see
> > #include "asan.h"
> > #include "bitmap.h"
> > #include "cfgloop.h"
> > +#include "tree-ssa-loop.h"
> > +#include "tree-ssa-loop-niter.h"
> > +#include "tree-ssa-loop-ivopts.h"
> > +#include "tree-scalar-evolution.h"
> > +#include "tree-chrec.h"
> >
> > #ifndef LOGICAL_OP_NON_SHORT_CIRCUIT
> > #define LOGICAL_OP_NON_SHORT_CIRCUIT \
> > @@ -523,6 +528,295 @@ ifcombine_rewrite_to_defined_overflow
> (gimple_stmt_iterator gsi)
> > rewrite_to_defined_unconditional (&gsi);
> > }
> >
> > +/* Information retained for each exit incorporated in the aggregate. */
> > +struct ifcombine_exit_info
> > +{
> > + edge exit;
> > + tree niter;
> > +};
> > +
> > +/* Return the exit iteration count, accounting for an immediate exit. */
> > +static tree
> > +ifcombine_exit_niter (class loop *loop, edge exit)
> > +{
> > + tree_niter_desc desc;
> > +
> > + if (!number_of_iterations_exit (loop, exit, &desc, false))
> > + return NULL_TREE;
> > +
> > + tree niter = desc.niter;
> > + if (!niter || chrec_contains_undetermined (niter))
> > + return NULL_TREE;
> > +
> > + if (integer_nonzerop (desc.may_be_zero))
> > + return build_zero_cst (TREE_TYPE (niter));
> > +
> > + if (!integer_zerop (desc.may_be_zero))
> > + niter = fold_build3 (COND_EXPR, TREE_TYPE (niter),
> > + desc.may_be_zero,
> > + build_zero_cst (TREE_TYPE (niter)), niter);
> > +
> > + return niter;
> > +}
> > +
> > +/* Require the non-exiting edge of E to reach the controlling header
> > + via the loop latch. */
> > +static bool
> > +ifcombine_reaches_header_via_latch_p (class loop *loop,
> > + edge e, edge controlling_exit)
> > +{
> > + if (EDGE_COUNT (e->src->succs) != 2)
> > + return false;
> > +
> > + edge nonexit = EDGE_SUCC (e->src, 0);
> > + if (nonexit == e)
> > + nonexit = EDGE_SUCC (e->src, 1);
> > +
> > + basic_block bb = nonexit->dest;
> > + return (bb == loop->latch
> > + && single_pred_p (bb)
> > + && single_pred_edge (bb) == nonexit
> > + && single_succ_p (bb)
> > + && single_succ (bb) == controlling_exit->src
> > + && controlling_exit->src == loop->header);
> > +}
> > +
> > +/* Check a candidate early exit's CFG shape and scalar exit PHIs.
> > + Virtual PHIs are not compared; */
> > +static bool
> > +ifcombine_removable_exit_p (class loop *loop, edge e,
> > + edge controlling_exit)
> > +{
> > + if (e == controlling_exit
> > + || !just_once_each_iteration_p (loop, e->src)
> > + || !dominated_by_p (CDI_DOMINATORS,
> > + e->src, controlling_exit->src)
> > + || e->dest != controlling_exit->dest
> > + || EDGE_COUNT (e->src->succs) != 2
> > + || !ifcombine_reaches_header_via_latch_p (loop, e,
> > + controlling_exit))
> > + return false;
> > +
> > + for (gphi_iterator gpi = gsi_start_phis (e->dest);
> > + !gsi_end_p (gpi); gsi_next (&gpi))
> > + {
> > + gphi *phi = gpi.phi ();
> > + if (virtual_operand_p (gimple_phi_result (phi)))
> > + continue;
> > +
> > + tree early_arg = PHI_ARG_DEF_FROM_EDGE (phi, e);
> > + tree control_arg = PHI_ARG_DEF_FROM_EDGE (phi, controlling_exit);
> > + if (!operand_equal_for_phi_arg_p (early_arg, control_arg))
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +/* Find eligible exits and form the minimum controlling bound. */
> > +static bool
> > +ifcombine_collect_exit_bounds (class loop *loop,
> > + edge controlling_exit,
> > + tree *aggregate_niter,
> > + auto_vec<ifcombine_exit_info> &candidates)
> > +{
> > + tree controlling_niter = ifcombine_exit_niter (loop, controlling_exit);
> > + if (!controlling_niter)
> > + return false;
> > +
> > + tree niter = controlling_niter;
> > + auto_vec<edge> exits = get_loop_exit_edges (loop);
> > +
> > + for (edge e : exits)
> > + {
> > + if (e == controlling_exit
> > + || !just_once_each_iteration_p (loop, e->src)
> > + || !dominated_by_p (CDI_DOMINATORS,
> > + e->src, controlling_exit->src))
> > + continue;
> > +
> > + tree exit_niter = ifcombine_exit_niter (loop, e);
> > + if (!exit_niter
> > + || TREE_TYPE (exit_niter) != TREE_TYPE (controlling_niter)
> > + || !ifcombine_removable_exit_p (loop, e, controlling_exit))
> > + continue;
> > +
> > + tree type = TREE_TYPE (controlling_niter);
> > + if (!TYPE_UNSIGNED (type)
> > + || TREE_CODE (exit_niter) != INTEGER_CST)
> > + continue;
> > +
> > + wide_int value = wi::to_wide (exit_niter);
> > + if (wi::lts_p (value, 0)
> > + || wi::geu_p (wi::to_wide (exit_niter),
> > + wi::to_wide (TYPE_MAX_VALUE (type))))
> > + continue;
> > +
> > + tree adjusted_niter = fold_convert (type, exit_niter);
> > + adjusted_niter = int_const_binop (PLUS_EXPR, adjusted_niter,
> > + build_int_cst (type, 1));
> > +
> > + ifcombine_exit_info info = { e, exit_niter };
> > + candidates.safe_push (info);
> > + niter = fold_build2 (MIN_EXPR, type, niter, adjusted_niter);
> > + }
> > +
> > + if (candidates.is_empty ())
> > + return false;
> > +
> > + *aggregate_niter = niter;
> > + return true;
> > +}
> > +
> > +/* Replace the controlling exit with a canonical IV test, then
> > + make the selected early exit conditions always take the non-exit
> > + path. */
> > +static bool
> > +ifcombine_rewrite_loop_exits (class loop *loop, edge controlling_exit,
> > + tree aggregate_niter,
> > + auto_vec<ifcombine_exit_info> &candidates)
> > +{
> > + create_canonical_iv (loop, controlling_exit, aggregate_niter);
> > +
> > + for (unsigned i = 0; i < candidates.length (); ++i)
> > + {
> > + edge e = candidates[i].exit;
> > + gcond *cond = as_a<gcond *> (*gsi_last_nondebug_bb (e->src));
> > +
> > + if (e->flags & EDGE_TRUE_VALUE)
> > + gimple_cond_make_false (cond);
> > + else
> > + gimple_cond_make_true (cond);
> > +
> > + update_stmt (cond);
> > + }
> > +
> > + if (dump_file && (dump_flags & TDF_DETAILS))
> > + fprintf (dump_file, "ifcombine: aggregated loop %d exits\n",
> > + loop->num);
> > +
> > + return true;
> > +}
> > +
> > +/* Locate a conditional loop-header exit. */
> > +static edge
> > +ifcombine_find_latch_exit (class loop *loop)
> > +{
> > + basic_block header = loop->header;
> > + if (EDGE_COUNT (header->succs) != 2)
> > + return NULL;
> > +
> > + gimple_stmt_iterator gsi = gsi_last_nondebug_bb (header);
> > + if (gsi_end_p (gsi)
> > + || gimple_code (gsi_stmt (gsi)) != GIMPLE_COND)
> > + return NULL;
> > +
> > + edge e0 = EDGE_SUCC (header, 0);
> > + edge e1 = EDGE_SUCC (header, 1);
> > +
> > + if (loop_exit_edge_p (loop, e0)
> > + && flow_bb_inside_loop_p (loop, e1->dest))
> > + return e0;
> > + if (loop_exit_edge_p (loop, e1)
> > + && flow_bb_inside_loop_p (loop, e0->dest))
> > + return e1;
> > +
> > + return NULL;
> > +}
> > +
> > +/* Check aggregate structural constraints. */
> > +static bool
> > +ifcombine_aggregate_safe_p (class loop *loop, edge controlling_exit,
> > + tree aggregate_niter,
> > + auto_vec<ifcombine_exit_info> &candidates)
> > +{
> > + if (!aggregate_niter
> > + || chrec_contains_undetermined (aggregate_niter)
> > + || candidates.is_empty ()
> > + || !loop_preheader_edge (loop)
> > + || controlling_exit->src != loop->header
> > + || !TYPE_UNSIGNED (TREE_TYPE (aggregate_niter)))
> > + return false;
> > +
> > + for (unsigned i = 0; i < candidates.length (); ++i)
> > + {
> > + edge e = candidates[i].exit;
> > + if (e == controlling_exit
> > + || e->dest != controlling_exit->dest
> > + || !ifcombine_reaches_header_via_latch_p (loop, e,
> > + controlling_exit))
> > + return false;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +/* Analyze and, if eligible, transform one loop. */
> > +static bool
> > +ifcombine_loop_exits (class loop *loop)
> > +{
> > + edge controlling_exit = ifcombine_find_latch_exit (loop);
> > + if (!controlling_exit)
> > + return false;
> > +
> > + tree aggregate_niter = NULL_TREE;
> > + auto_vec<ifcombine_exit_info> candidates;
> > +
> > + if (!ifcombine_collect_exit_bounds (loop, controlling_exit,
> > + &aggregate_niter, candidates)
> > + || !ifcombine_aggregate_safe_p (loop, controlling_exit,
> > + aggregate_niter, candidates))
> > + return false;
> > +
> > + return ifcombine_rewrite_loop_exits (loop, controlling_exit,
> > + aggregate_niter, candidates);
> > +}
> > +
> > +/* Process loops from the innermost out, invalidating SCEV after
> > + each successful rewrite. */
> > +static bool
> > +ifcombine_loop_exits_in_function (function *fun)
> > +{
> > + bool changed = false;
> > + for (auto loop : loops_list (fun, LI_FROM_INNERMOST))
> > + {
> > + if (loop->num == 0)
> > + continue;
> > +
> > + if (ifcombine_loop_exits (loop))
> > + {
> > + changed = true;
> > + scev_reset ();
> > + }
> > + }
> > +
> > + return changed;
> > +}
> > +
> > +/* Set up SCEV infrastructure if necessary, process loops, and
> > + restore the analysis state. */
> > +static bool
> > +ifcombine_loop_exit_bounds (function *fun)
> > +{
> > + bool in_loop_pipeline = scev_initialized_p ();
> > + if (!in_loop_pipeline)
> > + {
> > + loop_optimizer_init (LOOPS_NORMAL |
> LOOPS_HAVE_RECORDED_EXITS);
> > + scev_initialize ();
> > + }
> > +
> > + bool changed = false;
> > + if (number_of_loops (fun) > 1)
> > + changed = ifcombine_loop_exits_in_function (fun);
> > +
> > + if (!in_loop_pipeline)
> > + {
> > + scev_finalize ();
> > + loop_optimizer_finalize ();
> > + }
> > +
> > + return changed;
> > +}
> >
> > /* Replace the conditions in INNER_COND and OUTER_COND with COND
> and COND2.
> > COND and COND2 are computed for insertion at INNER_COND, with
> OUTER_COND
> > @@ -1556,7 +1850,11 @@ pass_tree_ifcombine::execute (function *fun)
> >
> > free (bbs);
> >
> > - return cfg_changed ? TODO_cleanup_cfg : 0;
> > + /* Combine loop exits with compatible iteration bounds. */
> > + bool loop_changed = ifcombine_loop_exit_bounds (fun);
> > +
> > + return (cfg_changed || loop_changed)
> > + ? TODO_cleanup_cfg : 0;
> > }
> >
> > } // anon namespace
> > --
> > 2.34.1
> >