https://gcc.gnu.org/g:1a787fd3dfefc8827d3e4289ae7bbaf18934bcd2

commit r17-616-g1a787fd3dfefc8827d3e4289ae7bbaf18934bcd2
Author: liuhongt <[email protected]>
Date:   Sun Jan 4 18:52:23 2026 -0800

    Limit outer-loop unswitching by duplicated code size
    
    When unswitching predicates from the innermost loop, hoisting the
    unswitch out to an outer loop duplicates only the outer-loop bodies;
    the innermost loop is shared.  Estimate the cost as
    candidate_size - innermost_size and stop selecting an outer loop once
    that exceeds param_max_unswitch_insns.  innermost_size is hoisted out
    of the walk so estimate_loop_insns is called once per level.
    
    gcc/ChangeLog:
    
            * tree-ssa-loop-unswitch.cc (estimate_loop_insns): New function.
            (init_loop_unswitch_info): Do not select an outer loop for
            unswitching when the duplicated outer-body size exceeds
            param_max_unswitch_insns.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.dg/loop-unswitch-19.c: New test.

Diff:
---
 gcc/testsuite/gcc.dg/loop-unswitch-19.c | 43 ++++++++++++++++++++++++++++++
 gcc/tree-ssa-loop-unswitch.cc           | 46 +++++++++++++++++++++++++++++----
 2 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/gcc/testsuite/gcc.dg/loop-unswitch-19.c 
b/gcc/testsuite/gcc.dg/loop-unswitch-19.c
new file mode 100644
index 000000000000..4c84e226580f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/loop-unswitch-19.c
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O3 -fdump-tree-unswitch-optimized" } */
+
+extern volatile int sink;
+
+void
+foo (int flag, int n, int m)
+{
+  for (int i = 0; i < n; ++i)
+    {
+      /* Pad the outer loop to make it noticeably larger than the inner.  */
+      sink += i;
+      sink += 2;
+      sink += 3;
+      sink += 4;
+      sink += 5;
+      sink += 6;
+      sink += 7;
+      sink += 8;
+      sink += 9;
+      sink += 10;
+      sink += 11;
+      sink += 12;
+      sink += 13;
+      sink += 14;
+      sink += 15;
+      sink += 16;
+      sink += 17;
+      sink += 18;
+      sink += 19;
+      sink += 20;
+
+      for (int j = 0; j < m; ++j)
+        if (flag)
+          sink += j;
+    }
+}
+
+/* We should still unswitch, but stay on the inner loop because the
+   outer-only body (the part that would be duplicated by hoisting the
+   unswitching) exceeds param_max_unswitch_insns.  */
+/* { dg-final { scan-tree-dump "unswitching loop" "unswitch" } } */
+/* { dg-final { scan-tree-dump-not "unswitching outer loop" "unswitch" } } */
diff --git a/gcc/tree-ssa-loop-unswitch.cc b/gcc/tree-ssa-loop-unswitch.cc
index a18c9ccfffa4..a34c5385c7cc 100644
--- a/gcc/tree-ssa-loop-unswitch.cc
+++ b/gcc/tree-ssa-loop-unswitch.cc
@@ -249,6 +249,21 @@ set_predicates_for_bb (basic_block bb, 
vec<unswitch_predicate *> predicates)
   bb_predicates->safe_push (predicates);
 }
 
+/* Estimate number of instructions in LOOP using eni_size_weights.  */
+
+static unsigned
+estimate_loop_insns (class loop *loop)
+{
+  unsigned insns = 0;
+  basic_block *body = get_loop_body (loop);
+  for (unsigned i = 0; i < loop->num_nodes; i++)
+    for (gimple_stmt_iterator gsi = gsi_start_bb (body[i]);
+        !gsi_end_p (gsi); gsi_next (&gsi))
+      insns += estimate_num_insns (gsi_stmt (gsi), &eni_size_weights);
+  free (body);
+  return insns;
+}
+
 /* Initialize LOOP information reused during the unswitching pass.
    Return total number of instructions in the loop.  Adjusts LOOP to
    the outermost loop all candidates are invariant in.  */
@@ -261,13 +276,35 @@ init_loop_unswitch_info (class loop *&loop, 
unswitch_predicate *&hottest,
 
   basic_block *bbs = get_loop_body (loop);
 
-  /* Unswitch only nests with no sibling loops.  */
+  /* Unswitch only nests with no sibling loops.  Since predicates come from
+     the innermost loop, only the outer-loop bodies get duplicated by hoisting
+     the unswitching out; the innermost loop is shared in the cost estimate.  
*/
   class loop *outer_loop = loop;
   unsigned max_depth = param_max_unswitch_depth;
+  unsigned innermost_size = estimate_loop_insns (loop);
   while (loop_outer (outer_loop)->num != 0
-        && !loop_outer (outer_loop)->inner->next
-        && --max_depth != 0)
-    outer_loop = loop_outer (outer_loop);
+        && !loop_outer (outer_loop)->inner->next)
+    {
+      if (--max_depth == 0)
+       break;
+
+      class loop *candidate = loop_outer (outer_loop);
+      unsigned candidate_size = estimate_loop_insns (candidate);
+
+      if (candidate_size - innermost_size
+         > (unsigned) param_max_unswitch_insns)
+       {
+         if (dump_enabled_p ())
+           dump_printf_loc (MSG_NOTE, find_loop_location (loop),
+                            "Not unswitching outer loop, duplicated size %u "
+                            "exceeds max-unswitch-insns %u\n",
+                            candidate_size - innermost_size,
+                            (unsigned) param_max_unswitch_insns);
+         break;
+       }
+
+      outer_loop = candidate;
+    }
   hottest = NULL;
   hottest_bb = NULL;
   /* Find all unswitching candidates in the innermost loop.  */
@@ -1677,4 +1714,3 @@ make_pass_tree_unswitch (gcc::context *ctxt)
   return new pass_tree_unswitch (ctxt);
 }
 
-

Reply via email to