[Mlir-commits] [mlir] 61e9082 - [flang][acc] Prune the acc.loop zero-trip edge when the body is proven to run (#219287)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Fri Aug 28 10:06:02 PDT 2026
Author: Susan Tan (ス-ザン タン)
Date: 2026-08-28T13:05:56-04:00
New Revision: 61e90826d97f91a09ca2c97735b7b62efb4808ac
URL: https://github.com/llvm/llvm-project/commit/61e90826d97f91a09ca2c97735b7b62efb4808ac
DIFF: https://github.com/llvm/llvm-project/commit/61e90826d97f91a09ca2c97735b7b62efb4808ac.diff
LOG: [flang][acc] Prune the acc.loop zero-trip edge when the body is proven to run (#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.
Added:
Modified:
mlir/lib/Dialect/OpenACC/IR/CMakeLists.txt
mlir/lib/Dialect/OpenACC/IR/OpenACC.cpp
mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
Removed:
################################################################################
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..327fd5df49259 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"
@@ -593,6 +594,68 @@ ValueRange HostDataOp::getSuccessorInputs(RegionSuccessor successor) {
return getSingleRegionSuccessorInputs(getOperation(), successor);
}
+/// Whether the body of a structured `acc.loop` is proven to run. This decides
+/// which edges out of the parent are feasible; the edges out of the region are
+/// unaffected.
+enum class BodyExecution {
+ /// The body runs at least once, so the parent cannot bypass the region.
+ Always,
+ /// The body never runs, so the parent cannot enter the region.
+ Never,
+ /// Neither could be proven, so the parent may do either.
+ 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;
+
+ // The verifier guarantees one lower bound, upper bound and step per
+ // dimension.
+ ValueRange lbs = loopOp.getLowerbound();
+ ValueRange ubs = loopOp.getUpperbound();
+ ValueRange steps = loopOp.getStep();
+
+ 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]);
+ // An unknown bound cannot be tested, and a zero step either spins forever
+ // or never starts. Neither proves `Always`, but a later dimension may
+ // still prove the nest empty: `(0 to %n)` collapsed with `(0 to 0)`.
+ if (!lb || !ub || !step || *step == 0) {
+ result = BodyExecution::Maybe;
+ continue;
+ }
+
+ // The entry test at the lower bound. A descending dimension compares
+ // against its bound the other way round. The attribute is absent when
+ // every dimension is exclusive as in `scf.for`, and the verifier otherwise
+ // guarantees one entry per dimension.
+ std::optional<ArrayRef<bool>> inclusiveUbs =
+ loopOp.getInclusiveUpperbound();
+ bool inclusiveUb = inclusiveUbs && (*inclusiveUbs)[i];
+ assert(*step != 0 && "zero step should have been filtered out");
+ bool runsOnce = *step > 0 ? (inclusiveUb ? *lb <= *ub : *lb < *ub)
+ : (inclusiveUb ? *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;
+ }
+
+ // No dimension was empty, so the body runs unless some dimension was
+ // unknown.
+ return result;
+}
+
void LoopOp::getSuccessorRegions(RegionBranchPoint point,
SmallVectorImpl<RegionSuccessor> ®ions) {
// Unstructured loops: the body may contain arbitrary CFG and early exits.
@@ -607,7 +670,21 @@ 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,
+ // 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;
+ case BodyExecution::Never:
+ regions.push_back(RegionSuccessor(getOperation()));
+ return;
+ case BodyExecution::Maybe:
+ break;
+ }
+ }
+
regions.push_back(RegionSuccessor(&getRegion()));
regions.push_back(RegionSuccessor(getOperation()));
}
diff --git a/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir b/mlir/test/Dialect/OpenACC/region-branchop-interface.mlir
index 708b48cbdfe5c..8e6f2b7c7dedb 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,392 @@ 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 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
+// CHECK-NEXT: - pre
+// CHECK-NOT: - loop_region
+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>
+}
+
+// -----
+
+// 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
+// CHECK-NOT: - loop_region
+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>
+}
+
+// -----
+
+// structured acc.loop whose bounds match and whose upper bound is inclusive:
+// this runs exactly once. Same bounds as @last_mod_openacc_loop_empty, which
+// runs zero times because its upper bound is exclusive, so ignoring
+// `inclusiveUpperbound` here would reach the opposite conclusion.
+//
+// CHECK-LABEL: test_tag: acc_loop_inclusive_single_after:
+// CHECK: operand #0
+// CHECK-NEXT: - loop_region
+func.func @last_mod_openacc_loop_inclusive_single(%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_ inclusiveUpperbound(array<i1: true>)
+ memref.load %arg0[] {tag = "acc_loop_inclusive_single_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop ascending with an inclusive upper bound below its lower
+// bound: the body never runs.
+//
+// CHECK-LABEL: test_tag: acc_loop_inclusive_empty_after:
+// CHECK: operand #0
+// CHECK-NEXT: - pre
+// CHECK-NOT: - loop_region
+func.func @last_mod_openacc_loop_inclusive_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
+ %c11_i32 = arith.constant 11 : i32
+ acc.loop control(%iv : i32) = (%c11_i32 : i32) to (%c10_i32 : i32)
+ step (%c1_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_inclusive_empty_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop counting down with an exclusive upper bound: the body
+// runs 9 times. Together with @last_mod_openacc_loop_descending this covers
+// both upper-bound kinds for a descending step.
+//
+// CHECK-LABEL: test_tag: acc_loop_descending_exclusive_after:
+// CHECK: operand #0
+// CHECK-NEXT: - loop_region
+func.func @last_mod_openacc_loop_descending_exclusive(%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_
+ memref.load %arg0[] {tag = "acc_loop_descending_exclusive_after"}
+ : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop counting down with matching bounds and an exclusive
+// upper bound: the body never runs. The inclusive variant of these same bounds
+// would run once, so this is the descending mirror of
+// @last_mod_openacc_loop_inclusive_single.
+//
+// CHECK-LABEL: test_tag: acc_loop_descending_exclusive_empty_after:
+// CHECK: operand #0
+// CHECK-NEXT: - pre
+// CHECK-NOT: - loop_region
+func.func @last_mod_openacc_loop_descending_exclusive_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>
+ %cm1_i32 = arith.constant -1 : i32
+ %c10_i32 = arith.constant 10 : i32
+ acc.loop control(%iv : i32) = (%c10_i32 : i32) to (%c10_i32 : i32)
+ step (%cm1_i32 : i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_descending_exclusive_empty_after"}
+ : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop whose step overshoots the upper bound: the entry test
+// still holds at the lower bound, so the body runs once and no trip count is
+// needed to see it.
+//
+// CHECK-LABEL: test_tag: acc_loop_big_step_after:
+// CHECK: operand #0
+// CHECK-NEXT: - loop_region
+func.func @last_mod_openacc_loop_big_step(%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>
+ %c0_i32 = arith.constant 0 : i32
+ %c5_i32 = arith.constant 5 : i32
+ %c100_i32 = arith.constant 100 : i32
+ acc.loop control(%iv : i32) = (%c0_i32 : i32) to (%c5_i32 : i32)
+ step (%c100_i32 : i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_big_step_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// structured acc.loop with a zero step: it either never advances or never
+// starts, so neither edge can be ruled out.
+//
+// CHECK-LABEL: test_tag: acc_loop_zero_step_after:
+// CHECK: operand #0
+// CHECK-DAG: - pre
+// CHECK-DAG: - loop_region
+func.func @last_mod_openacc_loop_zero_step(%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>
+ %c0_i32 = arith.constant 0 : i32
+ %c1_i32 = arith.constant 1 : i32
+ %c10_i32 = arith.constant 10 : i32
+ acc.loop control(%iv : i32) = (%c1_i32 : i32) to (%c10_i32 : i32)
+ step (%c0_i32 : i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_zero_step_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// container-like acc.loop: it carries no bounds of its own, so its iteration
+// space cannot be inspected here even though the contained scf.for is proven
+// to run. Both edges out of the acc.loop are kept.
+//
+// CHECK-LABEL: test_tag: acc_loop_container_after:
+// CHECK: operand #0
+// CHECK-DAG: - pre
+// CHECK-DAG: - loop_region
+func.func @last_mod_openacc_loop_container(%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>
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ acc.loop {
+ scf.for %i = %c0 to %c10 step %c1 {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ }
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_container_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// collapsed acc.loop whose every dimension runs: the body is entered.
+//
+// CHECK-LABEL: test_tag: acc_loop_collapsed_after:
+// CHECK: operand #0
+// CHECK-NEXT: - loop_region
+func.func @last_mod_openacc_loop_collapsed(%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(%i : i32, %j : i32) = (%c1_i32, %c1_i32 : i32, i32)
+ to (%c10_i32, %c10_i32 : i32, i32) step (%c1_i32, %c1_i32 : i32, i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_collapsed_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// collapsed acc.loop with one empty dimension: the dimensions are iterated as
+// a nest, so the whole iteration space is empty even though the first
+// dimension would run on its own.
+//
+// CHECK-LABEL: test_tag: acc_loop_collapsed_empty_after:
+// CHECK: operand #0
+// CHECK-NEXT: - pre
+// CHECK-NOT: - loop_region
+func.func @last_mod_openacc_loop_collapsed_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(%i : i32, %j : i32) = (%c1_i32, %c10_i32 : i32, i32)
+ to (%c10_i32, %c10_i32 : i32, i32) step (%c1_i32, %c1_i32 : i32, i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_collapsed_empty_after"} : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// collapsed acc.loop pairing an unknown dimension with an empty one: an empty
+// dimension zeroes the nest whatever the others do, so the unknown dimension
+// does not stop the body from being proven unreachable.
+//
+// CHECK-LABEL: test_tag: acc_loop_collapsed_unknown_empty_after:
+// CHECK: operand #0
+// CHECK-NEXT: - pre
+// CHECK-NOT: - loop_region
+func.func @last_mod_openacc_loop_collapsed_unknown_empty(%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
+ %c10_i32 = arith.constant 10 : i32
+ acc.loop control(%i : i32, %j : i32) = (%c1_i32, %c10_i32 : i32, i32)
+ to (%n, %c10_i32 : i32, i32) step (%c1_i32, %c1_i32 : i32, i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_collapsed_unknown_empty_after"}
+ : memref<f32>
+ return %arg0 : memref<f32>
+}
+
+// -----
+
+// collapsed acc.loop pairing an unknown dimension with one that runs: the body
+// runs only if every dimension runs, so the unknown dimension keeps both edges.
+//
+// CHECK-LABEL: test_tag: acc_loop_collapsed_unknown_after:
+// CHECK: operand #0
+// CHECK-DAG: - pre
+// CHECK-DAG: - loop_region
+func.func @last_mod_openacc_loop_collapsed_unknown(%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
+ %c10_i32 = arith.constant 10 : i32
+ acc.loop control(%i : i32, %j : i32) = (%c1_i32, %c1_i32 : i32, i32)
+ to (%n, %c10_i32 : i32, i32) step (%c1_i32, %c1_i32 : i32, i32) {
+ memref.store %one, %arg0[] {tag_name = "loop_region"} : memref<f32>
+ acc.yield
+ } auto_
+ memref.load %arg0[] {tag = "acc_loop_collapsed_unknown_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