[llvm-branch-commits] [clang] [flang] [flang] Add -f[no-]version-loops-for-trip-count and enable it at -O3 (2/2) (PR #231207)

Caroline Newcombe via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Oct 12 08:49:51 PDT 2026


https://github.com/cenewcombe created https://github.com/llvm/llvm-project/pull/231207

Stacked on #231205; only the last commit is new.

Wire `LoopTripCountVersioning` into the default pipeline behind a new flag, mirroring `-fversion-loops-for-stride`:

- `-f[no-]version-loops-for-trip-count` in the `flang` driver and `-fc1`, on by default at `-O3` and above.
- `CompilerInvocation` records the flag in `CodeGenOptions`, which feeds `MLIRToLLVMPassPipelineConfig::LoopTripCountVersioning`; `Pipelines.cpp` adds the pass when that is set. `tco` sets it directly at `-O3`.
- The pass runs right after `LoopVersioning`, before CSE, so the guard's `arith.cmpi` is shared when several loops version on the same bound.
- `-mmlir -loop-tripcount-versioning-max-section-extent=N` (hidden) sets the pass option for experiments; 0 disables.
- Release note under New Compiler Flags.

Tests:
- `flang/test/Driver/version-loops-trip-count.f90`
- `flang/test/Integration/loop-tripcount-versioning.f90`: real lowering
  output through the `-O3` pipeline, checked with

Measurements from benchmark codes will be included in comments in the associated RFC (https://discourse.llvm.org/t/rfc-flang-version-array-section-loops-when-the-runtime-extent-matches-the-declared-extent/91956).

Fixes #227442.

Assisted-by: Copilot

>From 05c1b33ea628580097a361a144afcae37261d59b Mon Sep 17 00:00:00 2001
From: Caroline Newcombe <caroline.newcombe at hpe.com>
Date: Thu, 8 Oct 2026 14:02:41 -0500
Subject: [PATCH] [flang] Add -f[no-]version-loops-for-trip-count and enable it
 at -O3

---
 clang/include/clang/Options/FlangOptions.td   |  4 +++
 clang/lib/Driver/ToolChains/Flang.cpp         | 25 +++++++------
 flang/docs/ReleaseNotes.md                    |  5 +++
 .../include/flang/Frontend/CodeGenOptions.def |  1 +
 .../flang/Optimizer/Passes/CommandLineOpts.h  |  1 +
 flang/include/flang/Tools/CrossToolHelpers.h  |  2 ++
 flang/lib/Frontend/CompilerInvocation.cpp     |  4 +++
 .../lib/Optimizer/Passes/CommandLineOpts.cpp  |  5 +++
 flang/lib/Optimizer/Passes/Pipelines.cpp      |  4 +++
 .../test/Driver/version-loops-trip-count.f90  | 34 ++++++++++++++++++
 .../Integration/loop-tripcount-versioning.f90 | 36 +++++++++++++++++++
 flang/tools/tco/tco.cpp                       |  1 +
 12 files changed, 111 insertions(+), 11 deletions(-)
 create mode 100644 flang/test/Driver/version-loops-trip-count.f90
 create mode 100644 flang/test/Integration/loop-tripcount-versioning.f90

diff --git a/clang/include/clang/Options/FlangOptions.td b/clang/include/clang/Options/FlangOptions.td
index 2844ee854a120..662360725afea 100644
--- a/clang/include/clang/Options/FlangOptions.td
+++ b/clang/include/clang/Options/FlangOptions.td
@@ -278,6 +278,10 @@ defm loop_versioning : BoolOptionWithoutMarshalling<"f", "version-loops-for-stri
   PosFlag<SetTrue, [], [ClangOption], "Create unit-strided versions of loops">,
    NegFlag<SetFalse, [], [ClangOption], "Do not create unit-strided loops (default)">>;
 
+defm loop_tripcount_versioning : BoolOptionWithoutMarshalling<"f", "version-loops-for-trip-count",
+  PosFlag<SetTrue, [], [], "Create constant-trip-count versions of array section loops">,
+   NegFlag<SetFalse, [], [], "Do not create constant-trip-count loops (default)">>;
+
 defm stack_repack_arrays
     : BoolOptionWithoutMarshalling<
           "f", "stack-repack-arrays",
diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp
index a13c69a5b2a50..acd1356a6cef4 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -175,13 +175,12 @@ void Flang::addPreprocessingOptions(const ArgList &Args,
 
 /// @C shouldLoopVersion
 ///
-/// Check if Loop Versioning should be enabled.
+/// Check if a loop versioning pass should be enabled.
 /// We look for the last of one of the following:
-///   -Ofast, -O4, -O<number> and -f[no-]version-loops-for-stride.
-/// Loop versioning is disabled if the last option is
-///  -fno-version-loops-for-stride.
+///   -Ofast, -O4, -O<number> and the pass's -f[no-]version-loops-for-* flag.
+/// Loop versioning is disabled if the last option is the -fno- flag.
 /// Loop versioning is enabled if the last option is one of:
-///  -floop-versioning
+///  the -f flag
 ///  -Ofast
 ///  -O4
 ///  -O3
@@ -190,17 +189,17 @@ void Flang::addPreprocessingOptions(const ArgList &Args,
 /// The gfortran compiler automatically enables the option for -O3 or -Ofast.
 ///
 /// @return true if loop-versioning should be enabled, otherwise false.
-static bool shouldLoopVersion(const ArgList &Args) {
+static bool shouldLoopVersion(const ArgList &Args, options::ID PosFlag,
+                              options::ID NegFlag) {
   const Arg *LoopVersioningArg = Args.getLastArg(
-      options::OPT_Ofast, options::OPT_O, options::OPT_O4,
-      options::OPT_floop_versioning, options::OPT_fno_loop_versioning);
+      options::OPT_Ofast, options::OPT_O, options::OPT_O4, PosFlag, NegFlag);
   if (!LoopVersioningArg)
     return false;
 
-  if (LoopVersioningArg->getOption().matches(options::OPT_fno_loop_versioning))
+  if (LoopVersioningArg->getOption().matches(NegFlag))
     return false;
 
-  if (LoopVersioningArg->getOption().matches(options::OPT_floop_versioning))
+  if (LoopVersioningArg->getOption().matches(PosFlag))
     return true;
 
   if (LoopVersioningArg->getOption().matches(options::OPT_Ofast) ||
@@ -367,8 +366,12 @@ void Flang::addCodegenOptions(const ArgList &Args,
   handleVectorizeLoopsArgs(Args, CmdArgs);
   handleVectorizeSLPArgs(Args, CmdArgs);
 
-  if (shouldLoopVersion(Args))
+  if (shouldLoopVersion(Args, options::OPT_floop_versioning,
+                        options::OPT_fno_loop_versioning))
     CmdArgs.push_back("-fversion-loops-for-stride");
+  if (shouldLoopVersion(Args, options::OPT_floop_tripcount_versioning,
+                        options::OPT_fno_loop_tripcount_versioning))
+    CmdArgs.push_back("-fversion-loops-for-trip-count");
 
   for (const auto &arg :
        Args.getAllArgValues(options::OPT_frepack_arrays_contiguity_EQ))
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 609ca499da396..8db73e956bb61 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -158,6 +158,11 @@ page](https://llvm.org/releases/).
   Both modes fill every storage byte including struct padding.
   PowerPC vector locals (`vector(real(4))` etc.) are not initialized.
 
+- Added `-f[no-]version-loops-for-trip-count`, enabled by default at `-O3` and
+  above. It versions innermost array-section loops whose runtime bound may
+  equal the array's declared leading extent, so that the common case has a
+  constant trip count.
+
 ## Windows Support
 
 ## Fortran Language Changes in Flang
diff --git a/flang/include/flang/Frontend/CodeGenOptions.def b/flang/include/flang/Frontend/CodeGenOptions.def
index 86994f7710576..569a6aa289ca3 100644
--- a/flang/include/flang/Frontend/CodeGenOptions.def
+++ b/flang/include/flang/Frontend/CodeGenOptions.def
@@ -55,6 +55,7 @@ CODEGENOPT(VectorizeSLP, 1, 0) ///< Enable SLP vectorization.
 CODEGENOPT(InterchangeLoops, 1, 0) ///< Enable loop interchange.
 CODEGENOPT(FuseLoops, 1, 0) ///< Enable loop fusion.
 CODEGENOPT(LoopVersioning, 1, 0) ///< Enable loop versioning.
+CODEGENOPT(LoopTripCountVersioning, 1, 0) ///< Enable loop trip-count versioning.
 CODEGENOPT(SplitSumExpressionTree, 1, 0) ///< Split REAL addition expression trees.
 CODEGENOPT(UnrollLoops, 1, 0) ///< Enable loop unrolling
 CODEGENOPT(AliasAnalysis, 1, 0) ///< Enable alias analysis pass
diff --git a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
index 35eab740a28dd..3d1035c99281c 100644
--- a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
+++ b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
@@ -55,6 +55,7 @@ extern llvm::cl::opt<bool> disableCfgConversion;
 extern llvm::cl::opt<bool> disableFirAliasTags;
 extern llvm::cl::opt<bool> disableFirLICM;
 extern llvm::cl::opt<bool> disableFirMao;
+extern llvm::cl::opt<unsigned> loopTripCountVersioningMaxSectionExtent;
 extern llvm::cl::opt<bool> useOldAliasTags;
 
 /// Experimental option to replace the stack-arrays and memory-allocation-opt
diff --git a/flang/include/flang/Tools/CrossToolHelpers.h b/flang/include/flang/Tools/CrossToolHelpers.h
index b9add141663b9..085a2714779ed 100644
--- a/flang/include/flang/Tools/CrossToolHelpers.h
+++ b/flang/include/flang/Tools/CrossToolHelpers.h
@@ -123,6 +123,7 @@ struct MLIRToLLVMPassPipelineConfig : public FlangEPCallBacks {
     EnableSafeTrampoline = opts.EnableSafeTrampoline;
     Underscoring = opts.Underscoring;
     LoopVersioning = opts.LoopVersioning;
+    LoopTripCountVersioning = opts.LoopTripCountVersioning;
     DebugInfo = opts.getDebugInfo();
     AliasAnalysis = opts.AliasAnalysis;
     FramePointerKind = opts.getFramePointer();
@@ -155,6 +156,7 @@ struct MLIRToLLVMPassPipelineConfig : public FlangEPCallBacks {
   bool EnableSafeTrampoline{false}; ///< Use runtime trampoline pool (W^X).
   bool Underscoring = true; ///< add underscores to function names.
   bool LoopVersioning = false; ///< Run the version loop pass.
+  bool LoopTripCountVersioning = false; ///< Run the trip-count versioning pass.
   bool AliasAnalysis = false; ///< Add TBAA tags to generated LLVMIR.
   llvm::codegenoptions::DebugInfoKind DebugInfo =
       llvm::codegenoptions::NoDebugInfo; ///< Debug info generation.
diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index 46ccebe3b030b..4a02ee7c0ddbf 100644
--- a/flang/lib/Frontend/CompilerInvocation.cpp
+++ b/flang/lib/Frontend/CompilerInvocation.cpp
@@ -343,6 +343,10 @@ static void parseCodeGenArgs(Fortran::frontend::CodeGenOptions &opts,
                    clang::options::OPT_fno_loop_versioning, false))
     opts.LoopVersioning = 1;
 
+  if (args.hasFlag(clang::options::OPT_floop_tripcount_versioning,
+                   clang::options::OPT_fno_loop_tripcount_versioning, false))
+    opts.LoopTripCountVersioning = 1;
+
   opts.UnrollLoops = args.hasFlag(clang::options::OPT_funroll_loops,
                                   clang::options::OPT_fno_unroll_loops,
                                   (opts.OptimizationLevel > 1));
diff --git a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
index bdaa12c13f475..a624017b9df09 100644
--- a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
+++ b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
@@ -61,6 +61,11 @@ cl::opt<bool> useOldAliasTags(
              "the FIR alias tags pass"),
     cl::init(false), cl::Hidden);
 DisableOption(FirLICM, "fir-licm", "FIR loop invariant code motion");
+cl::opt<unsigned> loopTripCountVersioningMaxSectionExtent(
+    "loop-tripcount-versioning-max-section-extent",
+    cl::desc("largest declared array extent to version an innermost loop on; "
+             "0 disables"),
+    cl::init(32), cl::Hidden);
 EnableOption(AllocationPlacement, "allocation-placement",
              "unified array allocation placement (experimental; replaces "
              "stack-arrays and memory-allocation-opt)");
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index d965400e028d2..7a614de94b16d 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -203,6 +203,10 @@ void createDefaultFIRPreCFGOptimizerPassPipeline(
   if (pc.LoopVersioning)
     pm.addPass(fir::createLoopVersioning());
 
+  if (pc.LoopTripCountVersioning)
+    pm.addPass(fir::createLoopTripCountVersioning(
+        {/*maxSectionExtent=*/loopTripCountVersioningMaxSectionExtent}));
+
   pm.addPass(mlir::createCSEPass());
 
   // Unconditional and ahead of the array allocation placement below: under
diff --git a/flang/test/Driver/version-loops-trip-count.f90 b/flang/test/Driver/version-loops-trip-count.f90
new file mode 100644
index 0000000000000..b1adffa8c0cfc
--- /dev/null
+++ b/flang/test/Driver/version-loops-trip-count.f90
@@ -0,0 +1,34 @@
+! Test that flang forwards -f{no-,}version-loops-for-trip-count to flang -fc1.
+! The flag follows the same -O level rule as -fversion-loops-for-stride (on at
+! -O3 and above, explicit flag wins), which version-loops.f90 covers in full;
+! this checks the new flag and that the two flags are independent.
+
+! RUN: %flang -### %s -o %t 2>&1 -O3 \
+! RUN:   | FileCheck %s --check-prefix=CHECK-O3
+
+! RUN: %flang -### %s -o %t 2>&1 -O3 -fno-version-loops-for-trip-count \
+! RUN:   | FileCheck %s --check-prefix=CHECK-O3-no
+
+! RUN: %flang -### %s -o %t 2>&1 -O2 -fversion-loops-for-trip-count \
+! RUN:   | FileCheck %s --check-prefix=CHECK-O2-with
+
+! RUN: %flang -### %s -o %t 2>&1 -O3 -fno-version-loops-for-stride \
+! RUN:   | FileCheck %s --check-prefix=CHECK-O3-no-stride
+
+! CHECK-O3: "{{.*}}flang{{.*}}" "-fc1"
+! CHECK-O3-SAME: "-fversion-loops-for-trip-count"
+! CHECK-O3-SAME: "-O3"
+
+! CHECK-O3-no: "{{.*}}flang{{.*}}" "-fc1"
+! CHECK-O3-no-NOT: "-fversion-loops-for-trip-count"
+! CHECK-O3-no-SAME: "-O3"
+
+! CHECK-O2-with: "{{.*}}flang{{.*}}" "-fc1"
+! CHECK-O2-with-SAME: "-fversion-loops-for-trip-count"
+! CHECK-O2-with-SAME: "-O2"
+
+! Disabling stride versioning leaves trip-count versioning on.
+! CHECK-O3-no-stride: "{{.*}}flang{{.*}}" "-fc1"
+! CHECK-O3-no-stride-NOT: "-fversion-loops-for-stride"
+! CHECK-O3-no-stride-SAME: "-fversion-loops-for-trip-count"
+! CHECK-O3-no-stride-SAME: "-O3"
diff --git a/flang/test/Integration/loop-tripcount-versioning.f90 b/flang/test/Integration/loop-tripcount-versioning.f90
new file mode 100644
index 0000000000000..05e0cbf953bfa
--- /dev/null
+++ b/flang/test/Integration/loop-tripcount-versioning.f90
@@ -0,0 +1,36 @@
+! Check that loop trip-count versioning fires on real lowering output and is
+! controlled by -f[no-]version-loops-for-trip-count. The pass matches the
+! canonical form of array-section loops (arith.maxsi bound, shared slice and
+! loop bound), so this test guards against lowering or canonicalization
+! changes that would silently stop it from matching.
+!
+! RUN: %flang_fc1 -emit-llvm -O3 -fversion-loops-for-trip-count \
+! RUN:   -mmlir --mlir-disable-threading \
+! RUN:   -mmlir --mlir-print-ir-after=loop-tripcount-versioning \
+! RUN:   %s -o /dev/null 2>&1 | FileCheck %s
+! RUN: %flang_fc1 -emit-llvm -O3 \
+! RUN:   -mmlir --mlir-disable-threading \
+! RUN:   -mmlir --mlir-print-ir-after=loop-tripcount-versioning \
+! RUN:   %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=OFF --allow-empty
+
+! The section bound is a runtime value; the declared leading extent is 4.
+subroutine zero(tau, ncol)
+  real(8), intent(out) :: tau(4, 26)
+  integer, intent(in) :: ncol
+  tau(1:ncol, :) = 0.0d0
+end subroutine
+
+! CHECK-LABEL: IR Dump After LoopTripCountVersioning
+! CHECK-LABEL: func.func @_QPzero(
+! CHECK:         %[[UB:.*]] = arith.maxsi %{{.*}}, %{{.*}} : index
+! CHECK:         fir.do_loop
+! CHECK:           %[[C4:.*]] = arith.constant 4 : index
+! CHECK:           %[[CMP:.*]] = arith.cmpi eq, %[[UB]], %[[C4]] : index
+! CHECK:           fir.if %[[CMP]] {
+! CHECK:             fir.do_loop %{{.*}} = %{{.*}} to %[[C4]] step
+! CHECK:           } else {
+! CHECK:             fir.do_loop %{{.*}} = %{{.*}} to %[[UB]] step
+! CHECK:           }
+
+! Without the flag the pass is not in the pipeline, so there is no IR dump.
+! OFF-NOT: IR Dump After LoopTripCountVersioning
diff --git a/flang/tools/tco/tco.cpp b/flang/tools/tco/tco.cpp
index 88865083f245f..9d199e028edd7 100644
--- a/flang/tools/tco/tco.cpp
+++ b/flang/tools/tco/tco.cpp
@@ -208,6 +208,7 @@ compileFIR(const mlir::PassPipelineCLParser &passPipeline) {
     config.EnableOpenMP = true;  // assume the input contains OpenMP
     config.AliasAnalysis = enableAliasAnalysis && !testGeneratorMode;
     config.LoopVersioning = OptLevel > 2;
+    config.LoopTripCountVersioning = OptLevel > 2;
     if (codeGenLLVM) {
       // Run only CodeGen passes.
       fir::createDefaultFIRCodeGenPassPipeline(pm, config);



More information about the llvm-branch-commits mailing list