[Mlir-commits] [mlir] [mlir][acc] Report independent loops not parallelized due to unstructured control flow (PR #230066)
Kareem Ergawy
llvmlistbot at llvm.org
Thu Oct 8 04:51:05 PDT 2026
https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/230066
>From 878a0ce2f85a80e1dc95b6152b1bb67daaa7748f Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Thu, 8 Oct 2026 04:22:22 -0700
Subject: [PATCH] [mlir][acc] Report independent loops not parallelized due to
unstructured control flow
An independent acc loop whose body has unstructured control flow (for
example a backward GOTO) cannot be represented as a structured loop, so it
is executed sequentially. This happened silently: no remark was emitted for
the loop, and the user only saw a serial kernel without a reason.
Emit a remark for such loops stating that they are not parallelized because
their control flow could not be represented as a structured loop. This lets
users identify loops that are asserted parallel but run sequentially.
Co-Authored-By: Claude
---
mlir/include/mlir/Dialect/OpenACC/OpenACC.h | 7 ++
.../OpenACC/Transforms/ACCComputeLowering.cpp | 13 +++
.../OpenACC/Transforms/ACCEmitRemarksLoop.cpp | 19 +++-
.../acc-compute-lowering-unstructured.mlir | 100 ++++++++++++++++++
.../OpenACC/acc-emit-remarks-loop.mlir | 22 ++++
5 files changed, 158 insertions(+), 3 deletions(-)
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
index 633be8159f2c6..a05d3d530358d 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACC.h
@@ -221,6 +221,13 @@ static constexpr StringLiteral getCollapseCountAttrName() {
return StringLiteral("acc.collapse_count");
}
+/// Name for a unit attribute attached to the region an unstructured
+/// independent loop was converted to, recording that the loop could not be
+/// represented as a structured loop and is therefore not parallelized.
+static constexpr StringLiteral getUnstructuredIndependentLoopAttrName() {
+ return StringLiteral("acc.unstructured_independent_loop");
+}
+
static constexpr StringLiteral getVarNameAttrName() {
return VarNameAttr::name;
}
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
index 84867cf58d5e6..f22d6f7b64afa 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCComputeLowering.cpp
@@ -346,10 +346,23 @@ class ACCLoopConversion : public OpRewritePattern<LoopOp> {
LogicalResult matchAndRewrite(LoopOp loopOp,
PatternRewriter &rewriter) const override {
if (loopOp.getUnstructured()) {
+ // An independent loop is asserted parallel but cannot be parallelized
+ // once it is lowered as a region of blocks. Record that, so the loss
+ // can be reported instead of silently running the loop sequentially.
+ bool lostIndependence =
+ loopOp.getDefaultOrDeviceTypeParallelism(deviceType) ==
+ LoopParMode::loop_independent &&
+ !isOpInSerialRegion(loopOp) &&
+ (isOpInComputeRegion(loopOp) ||
+ isSpecializedAccRoutine(
+ loopOp->getParentOfType<FunctionOpInterface>()));
auto executeRegion =
convertUnstructuredACCLoopToSCFExecuteRegion(loopOp, rewriter);
if (!executeRegion)
return failure();
+ if (lostIndependence)
+ executeRegion->setDiscardableAttr(
+ getUnstructuredIndependentLoopAttrName(), rewriter.getUnitAttr());
rewriter.replaceOp(loopOp, executeRegion);
return success();
}
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
index 77858609183d3..499e841195aa7 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCEmitRemarksLoop.cpp
@@ -144,6 +144,16 @@ static void emitLoopMappingRemark(acc::ComputeRegionOp computeRegion,
DEBUG_TYPE);
}
+static void
+emitUnstructuredIndependentLoopRemark(Operation *op,
+ acc::OpenACCSupport &accSupport) {
+ accSupport.emitRemark(op,
+ "Loop is not parallelized: unstructured control "
+ "flow prevents representing this independent loop "
+ "as a structured loop",
+ DEBUG_TYPE);
+}
+
class ACCEmitRemarksLoop
: public acc::impl::ACCEmitRemarksLoopBase<ACCEmitRemarksLoop> {
public:
@@ -160,9 +170,12 @@ class ACCEmitRemarksLoop
if (!shouldEmitLoopRemarks(computeRegion))
return;
- computeRegion.getRegion().walk([&](LoopLikeOpInterface loopOp) {
- emitLoopMappingRemark(computeRegion, loopOp, accSupport, policy,
- gpuDimSeparator);
+ computeRegion.getRegion().walk([&](Operation *op) {
+ if (auto loopOp = dyn_cast<LoopLikeOpInterface>(op))
+ emitLoopMappingRemark(computeRegion, loopOp, accSupport, policy,
+ gpuDimSeparator);
+ else if (op->hasAttr(acc::getUnstructuredIndependentLoopAttrName()))
+ emitUnstructuredIndependentLoopRemark(op, accSupport);
});
});
}
diff --git a/mlir/test/Dialect/OpenACC/acc-compute-lowering-unstructured.mlir b/mlir/test/Dialect/OpenACC/acc-compute-lowering-unstructured.mlir
index bcfc066302cc4..98253edf1ff08 100644
--- a/mlir/test/Dialect/OpenACC/acc-compute-lowering-unstructured.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-compute-lowering-unstructured.mlir
@@ -13,6 +13,7 @@ func.func @parallel_unstructured_loop(%buf: memref<10xi32>) {
// CHECK-NOT: acc.par_width
// CHECK: acc.compute_region
// CHECK: scf.execute_region
+ // CHECK: } {acc.unstructured_independent_loop}
acc.parallel dataOperands(%dev : memref<10xi32>) {
acc.loop {
^entry:
@@ -32,3 +33,102 @@ func.func @parallel_unstructured_loop(%buf: memref<10xi32>) {
acc.copyout accPtr(%dev : memref<10xi32>) to varPtr(%buf : memref<10xi32>)
return
}
+
+// An unstructured seq loop in parallel is not an independent loop that
+// lost its parallelism, so the converted region is not marked.
+// CHECK-LABEL: func.func @parallel_unstructured_seq_loop
+// CHECK: scf.execute_region
+// CHECK-NOT: acc.unstructured_independent_loop
+// CHECK: return
+func.func @parallel_unstructured_seq_loop(%buf: memref<10xi32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c1_i32 = arith.constant 1 : i32
+
+ %dev = acc.copyin varPtr(%buf : memref<10xi32>) -> memref<10xi32>
+ acc.parallel dataOperands(%dev : memref<10xi32>) {
+ acc.loop {
+ ^entry:
+ cf.br ^header(%c0 : index)
+ ^header(%iv: index):
+ %cond = arith.cmpi ult, %iv, %c10 : index
+ cf.cond_br %cond, ^body, ^exit
+ ^body:
+ memref.store %c1_i32, %dev[%iv] : memref<10xi32>
+ %iv_next = arith.addi %iv, %c1 : index
+ cf.br ^header(%iv_next : index)
+ ^exit:
+ acc.yield
+ } seq unstructured
+ acc.yield
+ }
+ acc.copyout accPtr(%dev : memref<10xi32>) to varPtr(%buf : memref<10xi32>)
+ return
+}
+
+// An unstructured independent loop in serial is not an independent loop that
+// lost its parallelism, so the converted region is not marked.
+// CHECK-LABEL: func.func @serial_unstructured_independent_loop
+// CHECK: scf.execute_region
+// CHECK-NOT: acc.unstructured_independent_loop
+// CHECK: return
+func.func @serial_unstructured_independent_loop(%buf: memref<10xi32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c1_i32 = arith.constant 1 : i32
+
+ %dev = acc.copyin varPtr(%buf : memref<10xi32>) -> memref<10xi32>
+ acc.serial dataOperands(%dev : memref<10xi32>) {
+ acc.loop {
+ ^entry:
+ cf.br ^header(%c0 : index)
+ ^header(%iv: index):
+ %cond = arith.cmpi ult, %iv, %c10 : index
+ cf.cond_br %cond, ^body, ^exit
+ ^body:
+ memref.store %c1_i32, %dev[%iv] : memref<10xi32>
+ %iv_next = arith.addi %iv, %c1 : index
+ cf.br ^header(%iv_next : index)
+ ^exit:
+ acc.yield
+ } independent unstructured
+ acc.yield
+ }
+ acc.copyout accPtr(%dev : memref<10xi32>) to varPtr(%buf : memref<10xi32>)
+ return
+}
+
+// An unstructured auto loop in kernels is not an independent loop that
+// lost its parallelism, so the converted region is not marked.
+// CHECK-LABEL: func.func @kernels_unstructured_auto_loop
+// CHECK: scf.execute_region
+// CHECK-NOT: acc.unstructured_independent_loop
+// CHECK: return
+func.func @kernels_unstructured_auto_loop(%buf: memref<10xi32>) {
+ %c0 = arith.constant 0 : index
+ %c1 = arith.constant 1 : index
+ %c10 = arith.constant 10 : index
+ %c1_i32 = arith.constant 1 : i32
+
+ %dev = acc.copyin varPtr(%buf : memref<10xi32>) -> memref<10xi32>
+ acc.kernels dataOperands(%dev : memref<10xi32>) {
+ acc.loop {
+ ^entry:
+ cf.br ^header(%c0 : index)
+ ^header(%iv: index):
+ %cond = arith.cmpi ult, %iv, %c10 : index
+ cf.cond_br %cond, ^body, ^exit
+ ^body:
+ memref.store %c1_i32, %dev[%iv] : memref<10xi32>
+ %iv_next = arith.addi %iv, %c1 : index
+ cf.br ^header(%iv_next : index)
+ ^exit:
+ acc.yield
+ } auto_ unstructured
+ acc.terminator
+ }
+ acc.copyout accPtr(%dev : memref<10xi32>) to varPtr(%buf : memref<10xi32>)
+ return
+}
diff --git a/mlir/test/Dialect/OpenACC/acc-emit-remarks-loop.mlir b/mlir/test/Dialect/OpenACC/acc-emit-remarks-loop.mlir
index 1b37317f19efc..e2a1eca986cdc 100644
--- a/mlir/test/Dialect/OpenACC/acc-emit-remarks-loop.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-emit-remarks-loop.mlir
@@ -174,3 +174,25 @@ func.func @acc_routine_gang_vector_loop() attributes {acc.specialized_routine =
}
return
}
+
+// -----
+
+// An independent loop that was lowered as an unstructured region is reported
+// as not parallelized instead of being skipped silently.
+// CHECK: remark: [Passed] openacc | Category:acc-emit-remarks-loop | Function=unstructured_independent_loop | Remark="Loop is not parallelized: unstructured control flow prevents representing this independent loop as a structured loop"
+func.func @unstructured_independent_loop() {
+ acc.kernel_environment {
+ acc.compute_region {
+ %flag = arith.constant true
+ scf.execute_region {
+ cf.cond_br %flag, ^bb1, ^bb2
+ ^bb1:
+ cf.br ^bb2
+ ^bb2:
+ scf.yield
+ } {acc.unstructured_independent_loop}
+ acc.yield
+ } <{origin = "acc.parallel"}>
+ }
+ return
+}
More information about the Mlir-commits
mailing list