[Mlir-commits] [mlir] [flang][acc] Prune the acc.loop zero-trip edge when the body is proven to run (PR #219287)

Susan Tan ス-ザン タン llvmlistbot at llvm.org
Thu Aug 27 13:47:03 PDT 2026


https://github.com/SusanTan created https://github.com/llvm/llvm-project/pull/219287

Structured acc.loop used to tell MLIR that control could either enter the body or skip straight past it, always both. The new helper tries to prove which of those actually happens: when the bounds are constants it can often show the body definitely runs, or definitely doesn't, and getSuccessorRegions then reports only the edge that's real. When it can't prove anything — non-constant bounds, or a loop whose iteration space lives inside its region — it reports both edges, exactly as before.

So the op's control flow graph goes from "always conservative" to "as precise as the bounds allow, conservative otherwise." Dataflow analyses running over that graph stop seeing a phantom path around loops that always execute, which is what unblocked the privatization cases.




>From d92ae99428a1947b959c01ffe78ba425ae8d305f Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 27 Aug 2026 12:14:36 -0700
Subject: [PATCH 1/2] impl

---
 .../mlir/Dialect/OpenACC/OpenACCOps.td        |   3 +-
 mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt    |   1 +
 mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp       | 102 +++++++++++++++++-
 .../OpenACC/region-branchop-interface.mlir    |  53 ++++++++-
 4 files changed, 153 insertions(+), 6 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index 375d2517da6c6..2baa501738eda 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -2635,7 +2635,8 @@ def OpenACC_LoopOp
           "loop", [AttrSizedOperandSegments, AutomaticAllocationScope,
                    RecursiveMemoryEffects,
                    DeclareOpInterfaceMethods<ComputeRegionOpInterface>,
-                   DeclareOpInterfaceMethods<LoopLikeOpInterface>,
+                   DeclareOpInterfaceMethods<LoopLikeOpInterface,
+                                             ["getStaticTripCount"]>,
                    DeclareOpInterfaceMethods<RegionBranchOpInterface,
                                              ["getSuccessorInputs"]>,
                    MemoryEffects<[MemWrite<OpenACC_ConstructResource>]>]> {
diff --git a/mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt b/mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt
index b04a30b442de0..409155ec18fe6 100644
--- a/mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt
@@ -15,6 +15,7 @@ add_mlir_dialect_library(MLIROpenACCDialect
 
   LINK_LIBS PUBLIC
   MLIRIR
+  MLIRDialectUtils
   MLIRGPUDialect
   MLIRLLVMDialect
   MLIRMemRefDialect
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index 360eac356cceb..01ca536e624d0 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -12,6 +12,7 @@
 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/Dialect/LLVMIR/LLVMTypes.h"
 #include "mlir/Dialect/MemRef/IR/MemRef.h"
+#include "mlir/Dialect/Utils/StaticValueUtils.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/BuiltinOps.h"
@@ -23,6 +24,7 @@
 #include "mlir/IR/SymbolTable.h"
 #include "mlir/Support/LLVM.h"
 #include "mlir/Transforms/DialectConversion.h"
+#include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/LogicalResult.h"
@@ -607,7 +609,26 @@ void LoopOp::getSuccessorRegions(RegionBranchPoint point,
     return;
   }
 
-  // Structured loops: model a loop-shaped region graph similar to scf.for.
+  // Structured loops: model a loop-shaped region graph similar to scf.for,
+  // including the trip-count refinements that drop edges the loop can never
+  // take.
+  if (std::optional<APInt> tripCount = getStaticTripCount()) {
+    if (point.isParent()) {
+      // A known-empty loop branches straight back to the parent. Otherwise
+      // the body is guaranteed to run, so nothing can branch past it.
+      if (tripCount->isZero())
+        regions.push_back(RegionSuccessor(getOperation()));
+      else
+        regions.push_back(RegionSuccessor(&getRegion()));
+      return;
+    }
+    if (tripCount->isOne()) {
+      // A single iteration has no backedge.
+      regions.push_back(RegionSuccessor(getOperation()));
+      return;
+    }
+  }
+
   regions.push_back(RegionSuccessor(&getRegion()));
   regions.push_back(RegionSuccessor(getOperation()));
 }
@@ -3904,6 +3925,85 @@ llvm::SmallVector<mlir::Region *> acc::LoopOp::getLoopRegions() {
   return {&getRegion()};
 }
 
+/// Trip count of a single `acc.loop` control dimension.
+///
+/// `constantTripCount` models the exclusive `iv < ub` form used by `scf.for`.
+/// An `acc.loop` dimension is exclusive as well, unless `inclusiveUpperbound`
+/// is set for it, in which case the equivalent exclusive bound is `ub + 1`.
+static std::optional<APInt> getStaticDimTripCount(Value lb, Value ub,
+                                                  Value step, bool inclusive) {
+  // `acc.loop` bounds are plain values, so there is no static `ub - lb` to
+  // recover once the bounds themselves are not constants.
+  auto noUbMinusLb = [](Value, Value, bool) -> std::optional<llvm::APSInt> {
+    return std::nullopt;
+  };
+
+  if (!inclusive)
+    return constantTripCount(lb, ub, step, /*isSigned=*/true, noUbMinusLb);
+
+  // `iv <= ub` with matching bounds is a single iteration. This has to be
+  // answered before the conversion below, which would otherwise report the
+  // empty range that `constantTripCount` derives from matching bounds.
+  if (lb == ub) {
+    std::optional<std::pair<APInt, bool>> stepCst = getConstantAPIntValue(step);
+    if (!stepCst || stepCst->first.isZero())
+      return std::nullopt;
+    return APInt(stepCst->first.getBitWidth(), 1);
+  }
+
+  // `ub + 1` is only representable for a known upper bound that does not
+  // overflow.
+  std::optional<std::pair<APInt, bool>> ubCst = getConstantAPIntValue(ub);
+  if (!ubCst || ubCst->first.isMaxSignedValue())
+    return std::nullopt;
+  OpFoldResult exclusiveUb = IntegerAttr::get(ub.getType(), ubCst->first + 1);
+  return constantTripCount(lb, exclusiveUb, step, /*isSigned=*/true,
+                           noUbMinusLb);
+}
+
+std::optional<APInt> LoopOp::getStaticTripCount() {
+  // An unstructured or container-like loop has no counted control of its own:
+  // the iteration space is described inside the region.
+  if (getUnstructured() || isContainerLike())
+    return std::nullopt;
+
+  ValueRange lbs = getLowerbound();
+  ValueRange ubs = getUpperbound();
+  ValueRange steps = getStep();
+  if (lbs.size() != ubs.size() || lbs.size() != steps.size())
+    return std::nullopt;
+
+  std::optional<ArrayRef<bool>> inclusive = getInclusiveUpperbound();
+
+  // Collapsed dimensions are iterated as a product, so a single empty
+  // dimension makes the whole loop empty even when the others are unknown.
+  APInt tripCount(64, 1);
+  bool anyUnknown = false;
+  for (unsigned i = 0, e = lbs.size(); i < e; ++i) {
+    bool dimInclusive = inclusive && i < inclusive->size() && (*inclusive)[i];
+    std::optional<APInt> dim =
+        getStaticDimTripCount(lbs[i], ubs[i], steps[i], dimInclusive);
+    if (!dim) {
+      anyUnknown = true;
+      continue;
+    }
+    if (dim->isZero())
+      return APInt(64, 0);
+    if (dim->getActiveBits() > 64) {
+      anyUnknown = true;
+      continue;
+    }
+    bool overflow = false;
+    tripCount = tripCount.umul_ov(dim->zextOrTrunc(64), overflow);
+    if (overflow)
+      anyUnknown = true;
+  }
+
+  if (anyUnknown)
+    return std::nullopt;
+  return tripCount;
+}
+
 /// loop-control ::= `control` `(` ssa-id-and-type-list `)` `=`
 /// `(` ssa-id-and-type-list `)` `to` `(` ssa-id-and-type-list `)` `step`
 /// `(` ssa-id-and-type-list `)`
diff --git a/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir b/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
index 708b48cbdfe5c..a003f7984fbee 100644
--- a/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
+++ b/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
@@ -119,10 +119,9 @@ func.func @last_mod_openacc_host_data(%arg0: memref<f32>, %mapped: memref<f32>)
 // CHECK-NEXT:   - loop_region
 // CHECK-LABEL: test_tag: acc_loop_after:
 // CHECK:  operand #0
-// CHECK-DAG:   - pre
-// CHECK-DAG:   - loop_region
-// the last writer is either the pre-loop store or
-// the store in the loop depending on the iteration count
+// CHECK-NEXT:   - loop_region
+// these bounds run the body at least once, so the store in the loop is the
+// only possible last writer
 // CHECK-LABEL: test_tag: acc_loop_post:
 // CHECK:  operand #0
 // CHECK-NEXT:   - post_loop
@@ -150,6 +149,52 @@ func.func @last_mod_openacc_loop(%arg0: memref<f32>) -> memref<f32> {
 
 // -----
 
+// structured acc.loop with an unknown upper bound: the body may or may not
+// run, so the edge that branches past it is kept.
+//
+// CHECK-LABEL: test_tag: acc_loop_dynamic_after:
+// CHECK:  operand #0
+// CHECK-DAG:   - pre
+// CHECK-DAG:   - loop_region
+func.func @last_mod_openacc_loop_dynamic(%arg0: memref<f32>, %n: i32) -> memref<f32> {
+  %zero = arith.constant 0.0 : f32
+  %one = arith.constant 1.0 : f32
+  memref.store %zero, %arg0[] {tag_name = "pre"} : memref<f32>
+  %c1_i32 = arith.constant 1 : i32
+  acc.loop control(%iv : i32) = (%c1_i32 : i32) to (%n : i32)
+      step (%c1_i32 : i32) {
+    memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+    acc.yield
+  } auto_
+  memref.load %arg0[] {tag = "acc_loop_dynamic_after"} : memref<f32>
+  return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop over a provably empty iteration space: the body is
+// never entered, so the store before the loop is the last writer.
+//
+// CHECK-LABEL: test_tag: acc_loop_empty_after:
+// CHECK:  operand #0
+// CHECK-NEXT:   - pre
+func.func @last_mod_openacc_loop_empty(%arg0: memref<f32>) -> memref<f32> {
+  %zero = arith.constant 0.0 : f32
+  %one = arith.constant 1.0 : f32
+  memref.store %zero, %arg0[] {tag_name = "pre"} : memref<f32>
+  %c1_i32 = arith.constant 1 : i32
+  %c10_i32 = arith.constant 10 : i32
+  acc.loop control(%iv : i32) = (%c10_i32 : i32) to (%c10_i32 : i32)
+      step (%c1_i32 : i32) {
+    memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+    acc.yield
+  } auto_
+  memref.load %arg0[] {tag = "acc_loop_empty_after"} : memref<f32>
+  return %arg0 : memref<f32>
+}
+
+// -----
+
 // Unstructured acc.loop: the RegionBranch is modeled with explicit CFG and early
 // exits, and the RegionBranch graph only exposes a single entry and single
 // exit edge (no region backedge).

>From 32d5123bbd3edc8f10cfa1fb65766a19aa67b295 Mon Sep 17 00:00:00 2001
From: Susan Tan <zujunt at nvidia.com>
Date: Thu, 27 Aug 2026 13:29:12 -0700
Subject: [PATCH 2/2] tweak

---
 .../mlir/Dialect/OpenACC/OpenACCOps.td        |   3 +-
 mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp       | 154 +++++++-----------
 .../OpenACC/region-branchop-interface.mlir    |  58 ++++++-
 3 files changed, 118 insertions(+), 97 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
index 2baa501738eda..375d2517da6c6 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCOps.td
@@ -2635,8 +2635,7 @@ def OpenACC_LoopOp
           "loop", [AttrSizedOperandSegments, AutomaticAllocationScope,
                    RecursiveMemoryEffects,
                    DeclareOpInterfaceMethods<ComputeRegionOpInterface>,
-                   DeclareOpInterfaceMethods<LoopLikeOpInterface,
-                                             ["getStaticTripCount"]>,
+                   DeclareOpInterfaceMethods<LoopLikeOpInterface>,
                    DeclareOpInterfaceMethods<RegionBranchOpInterface,
                                              ["getSuccessorInputs"]>,
                    MemoryEffects<[MemWrite<OpenACC_ConstructResource>]>]> {
diff --git a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
index 01ca536e624d0..f03bddca19a73 100644
--- a/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
+++ b/mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
@@ -24,7 +24,6 @@
 #include "mlir/IR/SymbolTable.h"
 #include "mlir/Support/LLVM.h"
 #include "mlir/Transforms/DialectConversion.h"
-#include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/LogicalResult.h"
@@ -595,6 +594,59 @@ ValueRange HostDataOp::getSuccessorInputs(RegionSuccessor successor) {
   return getSingleRegionSuccessorInputs(getOperation(), successor);
 }
 
+/// Whether the body of a structured `acc.loop` is proven to run.
+enum class BodyExecution {
+  /// The body runs at least once, so control cannot branch past it.
+  Always,
+  /// The body never runs, so control cannot enter it.
+  Never,
+  /// Neither could be proven, so both edges are possible.
+  Maybe
+};
+
+/// Prove whether the body of `loopOp` runs. A counted dimension whose entry
+/// test already fails at its lower bound runs exactly zero times, so a single
+/// comparison decides both `Always` and `Never`. Bounds that are not constant
+/// prove nothing.
+static BodyExecution getBodyExecution(LoopOp loopOp) {
+  // A container-like loop describes its iteration space inside the region.
+  if (loopOp.isContainerLike())
+    return BodyExecution::Maybe;
+
+  ValueRange lbs = loopOp.getLowerbound();
+  ValueRange ubs = loopOp.getUpperbound();
+  ValueRange steps = loopOp.getStep();
+  if (lbs.size() != ubs.size() || lbs.size() != steps.size())
+    return BodyExecution::Maybe;
+
+  std::optional<ArrayRef<bool>> inclusive = loopOp.getInclusiveUpperbound();
+
+  BodyExecution result = BodyExecution::Always;
+  for (unsigned i = 0, e = lbs.size(); i < e; ++i) {
+    std::optional<int64_t> lb = getConstantIntValue(lbs[i]);
+    std::optional<int64_t> ub = getConstantIntValue(ubs[i]);
+    std::optional<int64_t> step = getConstantIntValue(steps[i]);
+    // A zero step either never advances or never runs; the two are
+    // indistinguishable here.
+    if (!lb || !ub || !step || *step == 0) {
+      result = BodyExecution::Maybe;
+      continue;
+    }
+
+    // A descending dimension compares against its bound the other way round.
+    bool closed = inclusive && i < inclusive->size() && (*inclusive)[i];
+    bool runsOnce = *step > 0 ? (closed ? *lb <= *ub : *lb < *ub)
+                              : (closed ? *lb >= *ub : *lb > *ub);
+    // The dimensions are iterated as a nest, so one empty dimension empties
+    // the whole nest whatever the others do, while the body runs only if every
+    // dimension runs.
+    if (!runsOnce)
+      return BodyExecution::Never;
+  }
+
+  return result;
+}
+
 void LoopOp::getSuccessorRegions(RegionBranchPoint point,
                                  SmallVectorImpl<RegionSuccessor> &regions) {
   // Unstructured loops: the body may contain arbitrary CFG and early exits.
@@ -610,22 +662,17 @@ void LoopOp::getSuccessorRegions(RegionBranchPoint point,
   }
 
   // Structured loops: model a loop-shaped region graph similar to scf.for,
-  // including the trip-count refinements that drop edges the loop can never
-  // take.
-  if (std::optional<APInt> tripCount = getStaticTripCount()) {
-    if (point.isParent()) {
-      // A known-empty loop branches straight back to the parent. Otherwise
-      // the body is guaranteed to run, so nothing can branch past it.
-      if (tripCount->isZero())
-        regions.push_back(RegionSuccessor(getOperation()));
-      else
-        regions.push_back(RegionSuccessor(&getRegion()));
+  // minus the entry edge the loop is proven not to take.
+  if (point.isParent()) {
+    switch (getBodyExecution(*this)) {
+    case BodyExecution::Always:
+      regions.push_back(RegionSuccessor(&getRegion()));
       return;
-    }
-    if (tripCount->isOne()) {
-      // A single iteration has no backedge.
+    case BodyExecution::Never:
       regions.push_back(RegionSuccessor(getOperation()));
       return;
+    case BodyExecution::Maybe:
+      break;
     }
   }
 
@@ -3925,85 +3972,6 @@ llvm::SmallVector<mlir::Region *> acc::LoopOp::getLoopRegions() {
   return {&getRegion()};
 }
 
-/// Trip count of a single `acc.loop` control dimension.
-///
-/// `constantTripCount` models the exclusive `iv < ub` form used by `scf.for`.
-/// An `acc.loop` dimension is exclusive as well, unless `inclusiveUpperbound`
-/// is set for it, in which case the equivalent exclusive bound is `ub + 1`.
-static std::optional<APInt> getStaticDimTripCount(Value lb, Value ub,
-                                                  Value step, bool inclusive) {
-  // `acc.loop` bounds are plain values, so there is no static `ub - lb` to
-  // recover once the bounds themselves are not constants.
-  auto noUbMinusLb = [](Value, Value, bool) -> std::optional<llvm::APSInt> {
-    return std::nullopt;
-  };
-
-  if (!inclusive)
-    return constantTripCount(lb, ub, step, /*isSigned=*/true, noUbMinusLb);
-
-  // `iv <= ub` with matching bounds is a single iteration. This has to be
-  // answered before the conversion below, which would otherwise report the
-  // empty range that `constantTripCount` derives from matching bounds.
-  if (lb == ub) {
-    std::optional<std::pair<APInt, bool>> stepCst = getConstantAPIntValue(step);
-    if (!stepCst || stepCst->first.isZero())
-      return std::nullopt;
-    return APInt(stepCst->first.getBitWidth(), 1);
-  }
-
-  // `ub + 1` is only representable for a known upper bound that does not
-  // overflow.
-  std::optional<std::pair<APInt, bool>> ubCst = getConstantAPIntValue(ub);
-  if (!ubCst || ubCst->first.isMaxSignedValue())
-    return std::nullopt;
-  OpFoldResult exclusiveUb = IntegerAttr::get(ub.getType(), ubCst->first + 1);
-  return constantTripCount(lb, exclusiveUb, step, /*isSigned=*/true,
-                           noUbMinusLb);
-}
-
-std::optional<APInt> LoopOp::getStaticTripCount() {
-  // An unstructured or container-like loop has no counted control of its own:
-  // the iteration space is described inside the region.
-  if (getUnstructured() || isContainerLike())
-    return std::nullopt;
-
-  ValueRange lbs = getLowerbound();
-  ValueRange ubs = getUpperbound();
-  ValueRange steps = getStep();
-  if (lbs.size() != ubs.size() || lbs.size() != steps.size())
-    return std::nullopt;
-
-  std::optional<ArrayRef<bool>> inclusive = getInclusiveUpperbound();
-
-  // Collapsed dimensions are iterated as a product, so a single empty
-  // dimension makes the whole loop empty even when the others are unknown.
-  APInt tripCount(64, 1);
-  bool anyUnknown = false;
-  for (unsigned i = 0, e = lbs.size(); i < e; ++i) {
-    bool dimInclusive = inclusive && i < inclusive->size() && (*inclusive)[i];
-    std::optional<APInt> dim =
-        getStaticDimTripCount(lbs[i], ubs[i], steps[i], dimInclusive);
-    if (!dim) {
-      anyUnknown = true;
-      continue;
-    }
-    if (dim->isZero())
-      return APInt(64, 0);
-    if (dim->getActiveBits() > 64) {
-      anyUnknown = true;
-      continue;
-    }
-    bool overflow = false;
-    tripCount = tripCount.umul_ov(dim->zextOrTrunc(64), overflow);
-    if (overflow)
-      anyUnknown = true;
-  }
-
-  if (anyUnknown)
-    return std::nullopt;
-  return tripCount;
-}
-
 /// loop-control ::= `control` `(` ssa-id-and-type-list `)` `=`
 /// `(` ssa-id-and-type-list `)` `to` `(` ssa-id-and-type-list `)` `step`
 /// `(` ssa-id-and-type-list `)`
diff --git a/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir b/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
index a003f7984fbee..da10a76f3af46 100644
--- a/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
+++ b/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
@@ -172,8 +172,9 @@ func.func @last_mod_openacc_loop_dynamic(%arg0: memref<f32>, %n: i32) -> memref<
 
 // -----
 
-// structured acc.loop over a provably empty iteration space: the body is
-// never entered, so the store before the loop is the last writer.
+// structured acc.loop with matching bounds and an exclusive upper bound: the
+// entry test already fails at the lower bound, so the body never runs and the
+// store before the loop is the last writer.
 //
 // CHECK-LABEL: test_tag: acc_loop_empty_after:
 // CHECK:  operand #0
@@ -195,6 +196,59 @@ func.func @last_mod_openacc_loop_empty(%arg0: memref<f32>) -> memref<f32> {
 
 // -----
 
+// structured acc.loop counting down with an inclusive upper bound: `lb` is
+// above `ub`, which an ascending-only comparison would misread as an empty
+// iteration space. This loop runs 10 times, so the body is guaranteed to run
+// and the store inside it is the only possible last writer.
+//
+// CHECK-LABEL: test_tag: acc_loop_descending_after:
+// CHECK:  operand #0
+// CHECK-NEXT:   - loop_region
+func.func @last_mod_openacc_loop_descending(%arg0: memref<f32>) -> memref<f32> {
+  %zero = arith.constant 0.0 : f32
+  %one = arith.constant 1.0 : f32
+  memref.store %zero, %arg0[] {tag_name = "pre"} : memref<f32>
+  %c1_i32 = arith.constant 1 : i32
+  %cm1_i32 = arith.constant -1 : i32
+  %c10_i32 = arith.constant 10 : i32
+  acc.loop control(%iv : i32) = (%c10_i32 : i32) to (%c1_i32 : i32)
+      step (%cm1_i32 : i32) {
+    memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+    acc.yield
+  } auto_ inclusiveUpperbound(array<i1: true>)
+  memref.load %arg0[] {tag = "acc_loop_descending_after"} : memref<f32>
+  return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop stepping down away from an inclusive upper bound above
+// it: the body never runs, so the store before the loop is the last writer.
+// Comparing the bounds as if the step were ascending would instead prove the
+// body always runs, which is the opposite conclusion.
+//
+// CHECK-LABEL: test_tag: acc_loop_descending_empty_after:
+// CHECK:  operand #0
+// CHECK-NEXT:   - pre
+func.func @last_mod_openacc_loop_descending_empty(%arg0: memref<f32>)
+    -> memref<f32> {
+  %zero = arith.constant 0.0 : f32
+  %one = arith.constant 1.0 : f32
+  memref.store %zero, %arg0[] {tag_name = "pre"} : memref<f32>
+  %c1_i32 = arith.constant 1 : i32
+  %cm1_i32 = arith.constant -1 : i32
+  %c10_i32 = arith.constant 10 : i32
+  acc.loop control(%iv : i32) = (%c1_i32 : i32) to (%c10_i32 : i32)
+      step (%cm1_i32 : i32) {
+    memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+    acc.yield
+  } auto_ inclusiveUpperbound(array<i1: true>)
+  memref.load %arg0[] {tag = "acc_loop_descending_empty_after"} : memref<f32>
+  return %arg0 : memref<f32>
+}
+
+// -----
+
 // Unstructured acc.loop: the RegionBranch is modeled with explicit CFG and early
 // exits, and the RegionBranch graph only exposes a single entry and single
 // exit edge (no region backedge).



More information about the Mlir-commits mailing list