[flang-commits] [flang] [llvm] [mlir] [Flang][OpenMP] Add nsw flags to OMPIRBuilder loop IV arithmetic (PR #214165)
Kaviya Rajendiran via flang-commits
flang-commits at lists.llvm.org
Wed Aug 5 02:13:19 PDT 2026
https://github.com/kaviya2510 created https://github.com/llvm/llvm-project/pull/214165
- Extended the support of `-fno-wrapv` flag setting from frontend to the OMPIRBuilder via `omp.integer_wrap_around` module attribute.
- When this attribute `omp.integer_wrap_around` is false (-fno-wrapv), the OMPIRBuilder attaches `nsw` to all loop IV arithmetic.
- This enables SCEV to form proper `AddRec` expressions for the loop IV, allowing `IndVarSimplify pass` to widen it from i32 to i64 and eliminate the in-loop sext instruction, producing IR on par with Clang.
Fixes https://github.com/llvm/llvm-project/issues/213718
>From b0d300206055d9920d371a94acea85ac01b5bd40 Mon Sep 17 00:00:00 2001
From: Kaviya Rajendiran <kaviyara2000 at gmail.com>
Date: Wed, 5 Aug 2026 14:08:31 +0530
Subject: [PATCH] [Flang][OpenMP] Add nsw flags to OMPIRBuilder loop IV
arithmetic
---
flang/include/flang/Tools/CrossToolHelpers.h | 6 ++
flang/lib/Frontend/FrontendActions.cpp | 2 +
.../test/Integration/OpenMP/host-ir-flag.f90 | 2 +-
.../test/Lower/OpenMP/integer-wrap-around.f90 | 16 ++++
flang/tools/bbc/bbc.cpp | 2 +
.../llvm/Frontend/OpenMP/OMPIRBuilder.h | 8 ++
llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp | 12 ++-
.../mlir/Dialect/OpenMP/OpenMPAttrDefs.td | 11 +++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 10 +++
.../LLVMIR/openmp-integer-wrap-around.mlir | 77 +++++++++++++++++++
10 files changed, 141 insertions(+), 5 deletions(-)
create mode 100644 flang/test/Lower/OpenMP/integer-wrap-around.f90
create mode 100644 mlir/test/Target/LLVMIR/openmp-integer-wrap-around.mlir
diff --git a/flang/include/flang/Tools/CrossToolHelpers.h b/flang/include/flang/Tools/CrossToolHelpers.h
index 6569d34e0f255..fb8007637b114 100644
--- a/flang/include/flang/Tools/CrossToolHelpers.h
+++ b/flang/include/flang/Tools/CrossToolHelpers.h
@@ -206,4 +206,10 @@ struct MLIRToLLVMPassPipelineConfig : public FlangEPCallBacks {
Opts.NoGPULib);
}
+[[maybe_unused]] static void setOpenMPIntegerWrapAround(
+ mlir::ModuleOp module, bool value) {
+ module.getOperation()->setAttr("omp.integer_wrap_around",
+ mlir::omp::IntegerWrapAroundAttr::get(module.getContext(), value));
+}
+
#endif // FORTRAN_TOOLS_CROSS_TOOL_HELPERS_H
diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp
index 8955a8f61e513..e21590cc9fa7f 100644
--- a/flang/lib/Frontend/FrontendActions.cpp
+++ b/flang/lib/Frontend/FrontendActions.cpp
@@ -277,6 +277,8 @@ bool CodeGenAction::beginSourceFileAction() {
makeOffloadModuleOpts(ci.getInvocation().getLangOpts()));
mlir::omp::setOpenMPVersionAttribute(
lb.getModule(), ci.getInvocation().getLangOpts().OpenMPVersion);
+ if (!ci.getInvocation().getLoweringOpts().getIntegerWrapAround())
+ setOpenMPIntegerWrapAround(lb.getModule(), false);
}
if (ci.getInvocation().getLangOpts().FastRealMod) {
diff --git a/flang/test/Integration/OpenMP/host-ir-flag.f90 b/flang/test/Integration/OpenMP/host-ir-flag.f90
index 734b446fcccd4..db06b773d39f6 100644
--- a/flang/test/Integration/OpenMP/host-ir-flag.f90
+++ b/flang/test/Integration/OpenMP/host-ir-flag.f90
@@ -9,6 +9,6 @@
!RUN: %flang_fc1 -emit-llvm-bc -fopenmp -o %t.bc %s 2>&1
!RUN: %flang_fc1 -emit-mlir -fopenmp -fopenmp-is-target-device -fopenmp-host-ir-file-path %t.bc -o - %s 2>&1 | FileCheck %s
-!CHECK: module attributes {{{.*}}, omp.host_ir_filepath = "{{.*}}.bc", omp.is_gpu = false, omp.is_target_device = true{{.*}}}
+!CHECK: module attributes {{{.*}}, omp.host_ir_filepath = "{{.*}}.bc",{{.*}}omp.is_gpu = false, omp.is_target_device = true{{.*}}}
subroutine omp_subroutine()
end subroutine omp_subroutine
diff --git a/flang/test/Lower/OpenMP/integer-wrap-around.f90 b/flang/test/Lower/OpenMP/integer-wrap-around.f90
new file mode 100644
index 0000000000000..9e7c847096197
--- /dev/null
+++ b/flang/test/Lower/OpenMP/integer-wrap-around.f90
@@ -0,0 +1,16 @@
+! Tests that the omp.integer_wrap_around module attribute is set when -fno-wrapv (default) is active with OpenMP enabled and absent when -fwrapv is specified.
+
+! RUN: %flang_fc1 -emit-fir -fopenmp %s -o - | FileCheck %s --check-prefix=NOWRAPV
+! RUN: %flang_fc1 -emit-fir -fopenmp -fwrapv %s -o - | FileCheck %s --check-prefix=WRAPV
+
+! NOWRAPV: module attributes {{{.*}}omp.integer_wrap_around = #omp.integer_wrap_around<integer_wrap_around = false>{{.*}}}
+! WRAPV-NOT: omp.integer_wrap_around
+
+subroutine omp_loop(a, b, n)
+ integer :: n, i
+ real(8) :: a(n), b(n)
+ !$omp parallel do
+ do i = 1, n
+ a(i) = b(i)
+ end do
+end subroutine
diff --git a/flang/tools/bbc/bbc.cpp b/flang/tools/bbc/bbc.cpp
index e76808394becd..a549f5ca36708 100644
--- a/flang/tools/bbc/bbc.cpp
+++ b/flang/tools/bbc/bbc.cpp
@@ -540,6 +540,8 @@ static llvm::LogicalResult convertFortranSourceToMLIR(
mlir::omp::setOffloadModuleInterfaceAttributes(mlirModule,
offloadModuleOpts);
mlir::omp::setOpenMPVersionAttribute(mlirModule, setOpenMPVersion);
+ if (!integerWrapAround)
+ setOpenMPIntegerWrapAround(mlirModule, false);
}
burnside.lower(parseTree, semanticsContext);
std::error_code ec;
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index 1965f7b983805..ca91c89ce7567 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -132,6 +132,11 @@ class OpenMPIRBuilderConfig {
/// Separator used between all of the rest consecutive parts of s name.
std::optional<StringRef> Separator;
+ /// Flag for specifying whether the no-signed-wrap (nsw) flag should be added
+ /// to loop induction variable arithmetic. Set when the frontend guarantees
+ /// that signed integer overflow is undefined (with -fno-wrapv).
+ std::optional<bool> NoSignedWrap;
+
// Grid Value for the GPU target.
std::optional<omp::GV> GridValue;
@@ -176,6 +181,9 @@ class OpenMPIRBuilderConfig {
unsigned getDefaultTargetAS() const { return DefaultTargetAS; }
+ bool hasNoSignedWrap() const { return NoSignedWrap.value_or(false); }
+ void setNoSignedWrap(bool Value) { NoSignedWrap = Value; }
+
CallingConv::ID getRuntimeCC() const { return RuntimeCC; }
bool hasRequiresFlags() const { return RequiresFlags; }
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index c85cfe15d058c..7fce41bd438a7 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -5808,7 +5808,8 @@ CanonicalLoopInfo *OpenMPIRBuilder::createLoopSkeleton(
Builder.SetInsertPoint(Latch);
Value *Next = Builder.CreateAdd(IndVarPHI, ConstantInt::get(IndVarTy, 1),
- "omp_" + Name + ".next", /*HasNUW=*/true);
+ "omp_" + Name + ".next", /*HasNUW=*/true,
+ /*HasNSW=*/Config.hasNoSignedWrap());
Builder.CreateBr(Header);
IndVarPHI->addIncoming(Next, Latch);
@@ -6004,8 +6005,10 @@ Expected<CanonicalLoopInfo *> OpenMPIRBuilder::createCanonicalLoop(
auto BodyGen = [=](InsertPointTy CodeGenIP, Value *IV) {
Builder.restoreIP(CodeGenIP);
- Value *Span = Builder.CreateMul(IV, Step);
- Value *IndVar = Builder.CreateAdd(Span, Start);
+ Value *Span = Builder.CreateMul(IV, Step, "", /*HasNUW=*/false,
+ /*HasNSW=*/Config.hasNoSignedWrap());
+ Value *IndVar = Builder.CreateAdd(Span, Start, "", /*HasNUW=*/false,
+ /*HasNSW=*/Config.hasNoSignedWrap());
if (InScan)
ScanRedInfo->IV = IndVar;
return BodyGenCB(Builder.saveIP(), IndVar);
@@ -6160,7 +6163,8 @@ OpenMPIRBuilder::InsertPointOrErrorTy OpenMPIRBuilder::applyStaticWorkshareLoop(
Builder.SetInsertPoint(CLI->getBody(),
CLI->getBody()->getFirstInsertionPt());
Builder.SetCurrentDebugLocation(DL);
- return Builder.CreateAdd(OldIV, LowerBound);
+ return Builder.CreateAdd(OldIV, LowerBound, "", /*HasNUW=*/false,
+ /*HasNSW=*/Config.hasNoSignedWrap());
});
// In the "exit" block, call the "fini" function.
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
index f6bc8f11ccccb..8d8fcf9e381e8 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPAttrDefs.td
@@ -101,4 +101,15 @@ def VersionAttr : OpenMP_Attr<"Version", "version"> {
let assemblyFormat = "`<` struct(params) `>`";
}
+//===----------------------------------------------------------------------===//
+// IntegerWrapAroundAttr
+//===----------------------------------------------------------------------===//
+
+def IntegerWrapAroundAttr
+ : OpenMP_Attr<"IntegerWrapAround", "integer_wrap_around"> {
+ let parameters = (ins "bool":$integer_wrap_around);
+
+ let assemblyFormat = "`<` struct(params) `>`";
+}
+
#endif // OPENMP_ATTR_DEFS
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index e09bb720ced2d..fd644a48353fe 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -9523,6 +9523,16 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::amendOperation(
}
return failure();
})
+ .Case("omp.integer_wrap_around",
+ [&](Attribute attr) {
+ if (auto wrapAttr = dyn_cast<omp::IntegerWrapAroundAttr>(attr)) {
+ llvm::OpenMPIRBuilderConfig &config =
+ moduleTranslation.getOpenMPBuilder()->Config;
+ config.setNoSignedWrap(!wrapAttr.getIntegerWrapAround());
+ return success();
+ }
+ return failure();
+ })
.Default([](Attribute) {
// Fall through for omp attributes that do not require lowering.
return success();
diff --git a/mlir/test/Target/LLVMIR/openmp-integer-wrap-around.mlir b/mlir/test/Target/LLVMIR/openmp-integer-wrap-around.mlir
new file mode 100644
index 0000000000000..a2f42aa1f7bd9
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-integer-wrap-around.mlir
@@ -0,0 +1,77 @@
+// Tests how the omp.integer_wrap_around module attribute controls nsw flags on IV arithmetic in the generated LLVM IR:
+// - <omp.integer_wrap_around = absent> -> default behaviour, no nsw
+// - <omp.integer_wrap_around = true> -> wrap around allowed (-fwrapv), no nsw
+// - <omp.integer_wrap_around = false> -> wrap around disallowed, nsw emitted
+
+// RUN: mlir-translate -mlir-to-llvmir -split-input-file %s | FileCheck %s
+
+//-------------------------------------------------------------------------//
+// Default behaviour: without the attribute, IV increment should NOT have nsw.
+//-------------------------------------------------------------------------//
+
+// CHECK-LABEL: define void @wsloop_default_no_nsw
+// CHECK: omp_loop.header:
+// CHECK: %omp_loop.iv = phi i32 [ 0, %omp_loop.preheader ], [ %omp_loop.next, %omp_loop.inc ]
+// CHECK: omp_loop.body:
+// CHECK-NOT: add nsw
+// CHECK: omp_loop.inc:
+// CHECK: %omp_loop.next = add nuw i32 %omp_loop.iv, 1
+llvm.func @wsloop_default_no_nsw(%lb : i32, %ub : i32, %step : i32) {
+ omp.wsloop {
+ omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) {
+ omp.yield
+ }
+ }
+ llvm.return
+}
+
+// -----
+
+//-----------------------------------------------------------------------------------//
+// With omp.integer_wrap_around = true (-fwrapv), IV increment should NOT have nsw.
+//----------------------------------------------------------------------------------//
+
+// CHECK-LABEL: define void @wsloop_wrapv_no_nsw
+// CHECK: omp_loop.header:
+// CHECK: %omp_loop.iv = phi i32 [ 0, %omp_loop.preheader ], [ %omp_loop.next, %omp_loop.inc ]
+// CHECK: omp_loop.body:
+// CHECK-NOT: add nsw
+// CHECK: omp_loop.inc:
+// CHECK: %omp_loop.next = add nuw i32 %omp_loop.iv, 1
+module attributes {omp.integer_wrap_around = #omp.integer_wrap_around<integer_wrap_around = true>} {
+ llvm.func @wsloop_wrapv_no_nsw(%lb : i32, %ub : i32, %step : i32) {
+ omp.wsloop {
+ omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) {
+ omp.yield
+ }
+ }
+ llvm.return
+ }
+}
+
+// -----
+
+//-----------------------------------------------------------------------//
+// With omp.integer_wrap_around = false, IV increment should have nsw.
+//-----------------------------------------------------------------------//
+// CHECK-LABEL: define void @wsloop_nsw_iv
+// CHECK: omp_loop.header:
+// CHECK: %omp_loop.iv = phi i32 [ 0, %omp_loop.preheader ], [ %omp_loop.next, %omp_loop.inc ]
+
+// CHECK: omp_loop.body:
+// CHECK: %[[IV_ADD:[0-9]+]] = add nsw i32 %omp_loop.iv, %{{[0-9]+}}
+// CHECK: %[[MUL:[0-9]+]] = mul nsw i32 %[[IV_ADD]], %{{[0-9]+}}
+// CHECK: %{{[0-9]+}} = add nsw i32 %[[MUL]], %{{[0-9]+}}
+
+// CHECK: omp_loop.inc:
+// CHECK: %omp_loop.next = add nuw nsw i32 %omp_loop.iv, 1
+module attributes {omp.integer_wrap_around = #omp.integer_wrap_around<integer_wrap_around = false>} {
+ llvm.func @wsloop_nsw_iv(%lb : i32, %ub : i32, %step : i32) {
+ omp.wsloop {
+ omp.loop_nest (%iv) : i32 = (%lb) to (%ub) step (%step) {
+ omp.yield
+ }
+ }
+ llvm.return
+ }
+}
More information about the flang-commits
mailing list