[llvm] [Transforms][Utils] Add LoopSplit for iteration-space loop splitting (PR #217232)

Caroline Newcombe via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 11:48:52 PDT 2026


================
@@ -0,0 +1,542 @@
+//===- LoopSplit.cpp - Split a loop's iteration space ---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Splits a counted loop's iteration space into a chain of per-partition
+// sub-loops. See LoopSplit.h for the high-level usage guidelines.
+//
+// Structure produced for partitions [S0,E0], [S1,E1], ... where E is the loop's
+// last iteration and each clamped end sel_i is min(E_i, E), or max descending:
+//
+//   guard0:                            ; every S_i and sel_i is computed here
+//     if (S0 <= sel0) goto preheader0 else goto guard1
+//   loop0: ...                         ; latch iterates while i < sel0
+//   exit0 -> guard1
+//   guard1:
+//     if (S1 <= sel1) goto preheader1 else goto guard2
+//   loop1: ...                         ; latch iterates while i < sel1
+//   exit1 -> guard2
+//     ...
+//   final.exit:
+//
+// Each guard holds the "S_i <= sel_i" check and skips an empty partition by
+// falling through to the next guard. All S_i/sel_i are materialized once in
+// guard0, and the end clamp keeps the "runs at least once" iteration in the
+// right partition.
+//
+// The latch keeps iterating while the value the next iteration would use is
+// still in the partition. That is written as the strict "i < sel_i" on the
+// induction PHI rather than "i + 1 <= sel_i" on the step value; the two agree
+// because isLegal() has established that the space does not wrap, and the
+// strict form never forms i + 1, so it remains a real test even when sel_i is
+// the last value of the type, where the inclusive one would be a tautology and
+// the partition would never exit.
+//
+// A descending (step -1) loop uses the same structure mirrored: partitions run
+// high-to-low and the clamp and predicates flip (>=/>).
+//
+// Usage guidelines:
+//  - Caller bounds must not wrap the induction type. The clamp absorbs a bound
+//    past the runtime trip count, and isLegal() reserves the one step past the
+//    induction start that an empty partition needs, but a bound reaching any
+//    further wraps in the bound arithmetic and cannot be repaired here.
+//  - Bounds must be loop-invariant: they are expanded in guard0, so a bound
+//    depending on a value defined inside the loop cannot be placed.
+//  - The partitions must tile the original iteration space exactly -- same
+//    iterations, same order -- so the split preserves program behaviour.
+//
+// The transform is structural: inside a partition it only seeds the induction
+// PHI with that partition's start and replaces the latch test. It never
+// rebuilds a value that flows between partitions, so no SSA reconstruction is
+// needed.
+//
+// Not yet supported, and rejected by isLegal(): loop-carried values, values
+// that escape the loop (exit values), non-unit and non-integer inductions,
----------------
cenewcombe wrote:

Thanks, that's good to hear. Here's a reproducer.

`minimal_ltguard.f90` is a small reproducer with a scalar reduction under a guard, no OpenMP involved:
```
subroutine minimal_ltguard(a, b, n, m, res)
  implicit none
  integer, intent(in)  :: n, m
  real(8), intent(in)  :: a(0:n-1), b(0:n-1)
  real(8), intent(out) :: res
  integer :: j
  real(8) :: acc

  acc = 0.0d0
  do j = 0, n-1
     if (j < m) then
        acc = acc + a(j)*b(j)
     end if
  end do
  res = acc
end subroutine minimal_ltguard
```

I've attached the Flang generated IR as min_lt.ll so you don't need a Fortran compiler. To regenerate it:
```
flang -S -emit-llvm -O3 -march=native minimal_ltguard.f90 -o min_lt.ll
```

Since the split point comes from the command line the guard predicate doesn't matter here, so any offset exercises the reconstruction:
```
opt -passes='loop-simplify,lcssa,loop-split' -loop-split-points=100 -debug-only=loop-split min_lt.ll -S
```

That reports `loop has exit values` on this PR. Running the same input through #205995 as it was before the revert splits it and threads the accumulator through the guard chain, with fmul contract going from one to two and the partition entry picking up `%.16 = phi double [ %.1, %._crit_edge.loopexit ], [ 0.000000e+00, %ls.guard0 ]`. I've attached that output as min_lt_lsu.ll in case it's useful as a reference for what the reconstruction looked like.

One thing that might be worth a test case: Flang emits a second loop carried phi that isn't the reduction, a downcounting trip counter that drives the latch test alongside the usual induction variable. You can see it as `%12` in the attached IR. So Fortran loops tend to carry two integer phis even when there's no reduction, and I wasn't sure whether that gets treated as an induction variable or as carried state in the reduced version.
[min_lt.ll.txt](https://github.com/user-attachments/files/31527562/min_lt.ll.txt)
[min_lt_lsu.ll.txt](https://github.com/user-attachments/files/31527583/min_lt_lsu.ll.txt)



https://github.com/llvm/llvm-project/pull/217232


More information about the llvm-commits mailing list