[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
Wed Oct 14 08:55:23 PDT 2026


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

>From 0b3ea5e477e826228fe69bbf0c37d50ab381079e 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 1/2] [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 2844ee854a120d..662360725afea0 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 a13c69a5b2a50a..acd1356a6cef40 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 609ca499da3965..8db73e956bb61b 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 86994f7710576c..569a6aa289ca31 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 35eab740a28ddc..3d1035c99281c9 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 b9add141663b9c..085a2714779ed8 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 46ccebe3b030b3..4a02ee7c0ddbfb 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 bdaa12c13f475a..a624017b9df093 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 afa7fc7c58a547..ca5a40833b961e 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -204,6 +204,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 00000000000000..b1adffa8c0cfc2
--- /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 00000000000000..05e0cbf953bfa1
--- /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 88865083f245f7..9d199e028edd76 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);

>From 0b7d4616239bb9b55cb64de6dcb600e72f093706 Mon Sep 17 00:00:00 2001
From: Caroline Newcombe <caroline.newcombe at hpe.com>
Date: Tue, 13 Oct 2026 15:12:17 -0500
Subject: [PATCH 2/2] [flang] Plumb -loop-tripcount-versioning-max-unrolled-ops
 through the pipeline

---
 flang/include/flang/Optimizer/Passes/CommandLineOpts.h | 1 +
 flang/lib/Optimizer/Passes/CommandLineOpts.cpp         | 5 +++++
 flang/lib/Optimizer/Passes/Pipelines.cpp               | 3 ++-
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
index 3d1035c99281c9..dcb6dec3c30f88 100644
--- a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
+++ b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
@@ -56,6 +56,7 @@ 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<unsigned> loopTripCountVersioningMaxUnrolledOps;
 extern llvm::cl::opt<bool> useOldAliasTags;
 
 /// Experimental option to replace the stack-arrays and memory-allocation-opt
diff --git a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
index a624017b9df093..e819ac129ef0dc 100644
--- a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
+++ b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
@@ -66,6 +66,11 @@ cl::opt<unsigned> loopTripCountVersioningMaxSectionExtent(
     cl::desc("largest declared array extent to version an innermost loop on; "
              "0 disables"),
     cl::init(32), cl::Hidden);
+cl::opt<unsigned> loopTripCountVersioningMaxUnrolledOps(
+    "loop-tripcount-versioning-max-unrolled-ops",
+    cl::desc("do not version a loop if declared extent times body size "
+             "exceeds this; 0 removes the limit"),
+    cl::init(300), 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 ca5a40833b961e..80aa58abf56589 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -206,7 +206,8 @@ void createDefaultFIRPreCFGOptimizerPassPipeline(
 
   if (pc.LoopTripCountVersioning)
     pm.addPass(fir::createLoopTripCountVersioning(
-        {/*maxSectionExtent=*/loopTripCountVersioningMaxSectionExtent}));
+        {/*maxSectionExtent=*/loopTripCountVersioningMaxSectionExtent,
+         /*maxUnrolledOps=*/loopTripCountVersioningMaxUnrolledOps}));
 
   pm.addPass(mlir::createCSEPass());
 



More information about the llvm-branch-commits mailing list