[Mlir-commits] [mlir] [mlir][async] Clamp per-axis trip counts in async-parallel-for (PR #225370)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Tue Sep 22 04:20:02 PDT 2026
https://github.com/Shoryamishra61 created https://github.com/llvm/llvm-project/pull/225370
### Problem
`AsyncParallelFor` computes each dimension's trip count using signed ceiling division and directly multiplies those values to form the flattened iteration count.
For an empty dimension with a positive step, the raw result can be negative. If multiple dimensions are empty, multiplying an even number of negative trip counts can produce a positive flattened count, causing work to be dispatched for an `scf.parallel` operation whose semantic iteration space is empty.
### Fix
Normalize each per-dimension trip count to a non-negative iteration cardinality before computing the flattened trip count:
```cpp
Value rawTripCount = b.createOrFold<arith::CeilDivSIOp>(range, step);
tripCounts[i] = b.createOrFold<arith::MaxSIOp>(rawTripCount, c0);
```
This ensures any empty dimension contributes zero to the Cartesian-product iteration space while preserving positive trip counts for non-empty dimensions.
### Tests
Add regression coverage to `mlir/test/Dialect/Async/async-parallel-for-async-dispatch.mlir`:
- `@empty_2d_dynamic`: Multidimensional empty iteration domain with dynamic bounds ensuring each dimension clamps to zero and takes the no-op branch.
- `@empty_2d_static`: Constant bounds verifying compile-time folding of empty dimensions to a no-op branch.
Fixes #225254.
>From 19a89ee55353105c1f203f1b1241e00b677f4663 Mon Sep 17 00:00:00 2001
From: Shorya Mishra <shoryamishra181 at gmail.com>
Date: Tue, 22 Sep 2026 16:49:02 +0530
Subject: [PATCH] [mlir][async] Clamp per-axis trip counts in
async-parallel-for
AsyncParallelFor derives per-dimension trip counts using signed arithmetic.
An empty dimension with a positive step produces a negative raw trip count.
For a multidimensional loop with multiple empty dimensions, multiplying
negative counts can cancel out signs and produce a positive flattened
iteration count, causing an empty scf.parallel domain to execute work.
Normalize each per-dimension trip count to a non-negative cardinality
using arith::MaxSIOp before computing the product.
Fixes #225254
---
.../Async/Transforms/AsyncParallelFor.cpp | 10 +++--
.../async-parallel-for-async-dispatch.mlir | 40 +++++++++++++++++++
2 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp b/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
index 1db6f128f4c69..70c705b294d44 100644
--- a/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
+++ b/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
@@ -723,14 +723,19 @@ AsyncParallelForRewrite::matchAndRewrite(scf::ParallelOp op,
cloneConstantsIntoTheRegion(op.getRegion(), rewriter);
// Compute trip count for each loop induction variable:
- // tripCount = ceil_div(upperBound - lowerBound, step);
+ // tripCount = max(ceil_div(upperBound - lowerBound, step), 0);
+ // Each dimension is clamped at zero so that empty dimensions (where
+ // upperBound < lowerBound) contribute zero iterations rather than negative
+ // values which can cancel when multiplied together.
+ Value c0 = arith::ConstantIndexOp::create(b, 0);
SmallVector<Value> tripCounts(op.getNumLoops());
for (size_t i = 0; i < op.getNumLoops(); ++i) {
auto lb = op.getLowerBound()[i];
auto ub = op.getUpperBound()[i];
auto step = op.getStep()[i];
auto range = b.createOrFold<arith::SubIOp>(ub, lb);
- tripCounts[i] = b.createOrFold<arith::CeilDivSIOp>(range, step);
+ Value rawTripCount = b.createOrFold<arith::CeilDivSIOp>(range, step);
+ tripCounts[i] = b.createOrFold<arith::MaxSIOp>(rawTripCount, c0);
}
// Compute a product of trip counts to get the 1-dimensional iteration space
@@ -741,7 +746,6 @@ AsyncParallelForRewrite::matchAndRewrite(scf::ParallelOp op,
// Short circuit no-op parallel loops (zero iterations) that can arise from
// the memrefs with dynamic dimension(s) equal to zero.
- Value c0 = arith::ConstantIndexOp::create(b, 0);
Value isZeroIterations =
arith::CmpIOp::create(b, arith::CmpIPredicate::eq, tripCount, c0);
diff --git a/mlir/test/Dialect/Async/async-parallel-for-async-dispatch.mlir b/mlir/test/Dialect/Async/async-parallel-for-async-dispatch.mlir
index 3f412ecd0c8f9..8359e072d6dfe 100644
--- a/mlir/test/Dialect/Async/async-parallel-for-async-dispatch.mlir
+++ b/mlir/test/Dialect/Async/async-parallel-for-async-dispatch.mlir
@@ -84,3 +84,43 @@ func.func @loop_2d(%arg0: index, %arg1: index, %arg2: index, // lb, ub, step
// CHECK: memref.store
// CHECK-LABEL: func private @async_dispatch_fn
+
+// -----
+
+// CHECK-LABEL: @empty_2d_dynamic
+// CHECK-SAME: %[[LB:.*]]: index, %[[UB:.*]]: index, %[[STEP:.*]]: index
+func.func @empty_2d_dynamic(%lb: index, %ub: index, %step: index, %arg0: memref<?x?xf32>) {
+ // CHECK-DAG: %[[C0:.*]] = arith.constant 0 : index
+ // CHECK: %[[RANGE0:.*]] = arith.subi %[[UB]], %[[LB]]
+ // CHECK: %[[RAW0:.*]] = arith.ceildivsi %[[RANGE0]], %[[STEP]]
+ // CHECK: %[[TRIP0:.*]] = arith.maxsi %[[RAW0]], %[[C0]]
+ // CHECK: %[[RANGE1:.*]] = arith.subi %[[UB]], %[[LB]]
+ // CHECK: %[[RAW1:.*]] = arith.ceildivsi %[[RANGE1]], %[[STEP]]
+ // CHECK: %[[TRIP1:.*]] = arith.maxsi %[[RAW1]], %[[C0]]
+ // CHECK: %[[FLATTENED:.*]] = arith.muli %[[TRIP0]], %[[TRIP1]]
+ // CHECK: %[[IS_NOOP:.*]] = arith.cmpi eq, %[[FLATTENED]], %[[C0]] : index
+ // CHECK: scf.if %[[IS_NOOP]] {
+ // CHECK-NEXT: } else {
+ scf.parallel (%i, %j) = (%lb, %lb) to (%ub, %ub) step (%step, %step) {
+ %one = arith.constant 1.0 : f32
+ memref.store %one, %arg0[%i, %j] : memref<?x?xf32>
+ }
+ return
+}
+
+// -----
+
+// CHECK-LABEL: @empty_2d_static
+func.func @empty_2d_static(%arg0: memref<8x8xi32>) {
+ // CHECK: %[[TRUE:.*]] = arith.constant true
+ // CHECK: scf.if %[[TRUE]] {
+ // CHECK-NEXT: } else {
+ %c5 = arith.constant 5 : index
+ %c0 = arith.constant 0 : index
+ %c2 = arith.constant 2 : index
+ %one = arith.constant 1 : i32
+ scf.parallel (%i, %j) = (%c5,%c5) to (%c0,%c0) step (%c2,%c2) {
+ memref.store %one, %arg0[%i, %j] : memref<8x8xi32>
+ }
+ return
+}
More information about the Mlir-commits
mailing list