[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