[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