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.

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

Reply via email to