[Mlir-commits] [mlir] [mlir][scf] Use a signed compare for the parallel loop tiling in-bound check (PR #226130)

Samarth Narang llvmlistbot at llvm.org
Sun Sep 27 14:59:50 PDT 2026


https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/226130

>From cc566cf7fc9daa9e9ea511c3a85b7d302adb4179 Mon Sep 17 00:00:00 2001
From: Samarth Narang <sn2910 at nyu.edu>
Date: Thu, 24 Sep 2026 11:19:52 +0000
Subject: [PATCH 1/2] [mlir][scf] Use a signed compare for the parallel loop
 tiling in-bound check

With no-min-max-bounds the tiled loop guards the tail iterations with
`inner_iv * inner_step + outer_iv < outer_upper_bound`, but emitted the compare
as `ult`. scf.parallel bounds are signed index values and a negative lower
bound is legal, so every negative index was treated as out of bounds and
skipped: tiling `scf.parallel (%i) = (-3) to (4) step (1)` by 3 executed 4 of
the 7 iterations. Use `slt`.

Fixes #223233
---
 .../SCF/Transforms/ParallelLoopTiling.cpp     |  2 +-
 .../parallel-loop-tiling-inbound-check.mlir   | 24 +++++++++++++++++--
 2 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
index 081f5fb3dc8f2..9638f0a3c75c5 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
@@ -150,7 +150,7 @@ mlir::scf::tileParallelLoop(ParallelOp op, ArrayRef<int64_t> tileSizes,
           b, op.getLoc(),
           arith::MulIOp::create(b, op.getLoc(), innerIV, innerStep), outerIV);
       Value dimInbound = arith::CmpIOp::create(
-          b, op.getLoc(), arith::CmpIPredicate::ult, index, outerUpperBound);
+          b, op.getLoc(), arith::CmpIPredicate::slt, index, outerUpperBound);
       inbound = arith::AndIOp::create(b, op.getLoc(), inbound, dimInbound);
     }
     auto ifInbound = IfOp::create(b, op.getLoc(),
diff --git a/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir b/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
index 7491550c1dc7c..79907b03996aa 100644
--- a/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
+++ b/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
@@ -27,11 +27,11 @@ func.func @parallel_loop(%arg0 : index, %arg1 : index, %arg2 : index,
 // CHECK:               %true = arith.constant true
 // CHECK:               [[V11:%.*]] = arith.muli [[V7]], [[ARG5]] : index
 // CHECK:               [[V12:%.*]] = arith.addi [[V11]], [[V3]] : index
-// CHECK:               [[V13:%.*]] = arith.cmpi ult, [[V12]], [[ARG3]] : index
+// CHECK:               [[V13:%.*]] = arith.cmpi slt, [[V12]], [[ARG3]] : index
 // CHECK:               [[V14:%.*]] = arith.andi %true, [[V13]] : i1
 // CHECK:               [[V15:%.*]] = arith.muli [[V8]], [[ARG6]] : index
 // CHECK:               [[V16:%.*]] = arith.addi [[V15]], [[V4]] : index
-// CHECK:               [[V17:%.*]] = arith.cmpi ult, [[V16]], [[ARG4]] : index
+// CHECK:               [[V17:%.*]] = arith.cmpi slt, [[V16]], [[ARG4]] : index
 // CHECK:               [[V18:%.*]] = arith.andi [[V14]], [[V17]] : i1
 // CHECK:               scf.if [[V18]] {
 // CHECK:                 [[V19:%.*]] = memref.load [[ARG8]]{{\[}}[[V9]], [[V10]]] : memref<?x?xf32>
@@ -147,3 +147,23 @@ func.func @tile_nested_in_non_ploop() {
 // CHECK:           }
 // CHECK:         }
 // CHECK:       }
+
+// -----
+
+func.func @parallel_loop_negative_lower_bound(%arg0 : index, %arg1 : index,
+                                              %result: memref<?x?xf32>) {
+  %c1 = arith.constant 1 : index
+  %cm3 = arith.constant -3 : index
+  %cst = arith.constant 0.0 : f32
+  scf.parallel (%i0, %i1) = (%cm3, %cm3) to (%arg0, %arg1) step (%c1, %c1) {
+    memref.store %cst, %result[%i0, %i1] : memref<?x?xf32>
+  }
+  return
+}
+
+// The bounds are signed, so a negative index must not be treated as out of
+// bounds by an unsigned compare.
+// CHECK-LABEL:   func @parallel_loop_negative_lower_bound(
+// CHECK-NOT:       arith.cmpi ult
+// CHECK:           arith.cmpi slt
+// CHECK-NOT:       arith.cmpi ult

>From d5f7146122f9fed215bcc2517f1ca63fac2a6513 Mon Sep 17 00:00:00 2001
From: Samarth Narang <sn2910 at nyu.edu>
Date: Sun, 27 Sep 2026 01:54:32 +0000
Subject: [PATCH 2/2] [mlir][scf] Add unsignedCmp to scf.parallel and honour it
 in the tiling guard

Mirror scf.for: bounds and steps of scf.parallel are signed by default and
unsigned when the `unsignedCmp` unit attribute is present, spelled `unsigned`
in the custom form. Document the per-dimension trip count and no-overflow
rule as scf.for does.

Propagate the attribute where a loop is rebuilt from another: parallel loop
tiling (which now picks `ult` or `slt` for its in-bound guard accordingly),
parallel loop fusion (which also refuses to fuse loops of different
signedness), parallel-to-nested-fors and SCF-to-CF lowering. Lowerings whose
bound arithmetic assumes signed values decline unsigned loops instead of
miscompiling them: SCF-to-GPU, SCF-to-OpenMP, async-parallel-for and the
parallel-loop-collapsing test pass.
---
 mlir/include/mlir/Dialect/SCF/IR/SCFOps.td    | 28 +++++++++++----
 .../SCFToControlFlow/SCFToControlFlow.cpp     |  4 ++-
 mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp     |  3 ++
 .../Conversion/SCFToOpenMP/SCFToOpenMP.cpp    |  3 ++
 .../Async/Transforms/AsyncParallelFor.cpp     |  4 +++
 mlir/lib/Dialect/SCF/IR/SCF.cpp               | 19 +++++++---
 .../Transforms/ParallelForToNestedFors.cpp    |  2 ++
 .../SCF/Transforms/ParallelLoopCollapsing.cpp |  4 +++
 .../SCF/Transforms/ParallelLoopFusion.cpp     | 15 ++++----
 .../SCF/Transforms/ParallelLoopTiling.cpp     |  9 +++--
 .../SCFToControlFlow/convert-to-cfg.mlir      | 16 +++++++++
 .../Conversion/SCFToGPU/parallel_loop.mlir    | 13 +++++++
 .../SCFToOpenMP/scf-to-openmp-unsigned.mlir   | 11 ++++++
 .../async-parallel-for-async-dispatch.mlir    | 16 +++++++++
 mlir/test/Dialect/SCF/ops.mlir                | 10 ++++++
 .../Dialect/SCF/parallel-loop-fusion.mlir     | 36 +++++++++++++++++++
 .../parallel-loop-tiling-inbound-check.mlir   | 22 ++++++++++++
 .../Dialect/SCF/parallel-to-nested-fors.mlir  | 14 ++++++++
 .../parallel-loop-collapsing-unsigned.mlir    | 11 ++++++
 19 files changed, 221 insertions(+), 19 deletions(-)
 create mode 100644 mlir/test/Conversion/SCFToOpenMP/scf-to-openmp-unsigned.mlir
 create mode 100644 mlir/test/Transforms/parallel-loop-collapsing-unsigned.mlir

diff --git a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
index 31330be70ee2d..6704e656a9ec8 100644
--- a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
+++ b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
@@ -835,9 +835,22 @@ def ParallelOp : SCF_Op<"parallel",
     region. These SSA values always have type index, which is the size of the
     machine word. The steps are values of type index, required to be positive.
     The lower and upper bounds specify a half-open range: the range includes
-    the lower bound but does not include the upper bound. The initial values
-    have the same types as results of `scf.parallel`. If there are no results,
-    the keyword `init` can be omitted.
+    the lower bound but does not include the upper bound. Lower bounds, upper
+    bounds and steps are interpreted as signed integers by default, or as
+    unsigned integers if the `unsignedCmp` unit attribute is present. The
+    initial values have the same types as results of `scf.parallel`. If there
+    are no results, the keyword `init` can be omitted.
+
+    More precisely, the iteration space of each dimension is governed by the
+    following two rules, where arithmetic is performed with arbitrary
+    precision:
+
+    1. The trip count `n` is `max(0, ceil((UB - LB) / Step))` and the induction
+       variable takes the values `LB + j*Step` for `j = 0, ..., n - 1`.
+    2. No-overflow condition: `LB + n*Step` must be representable in the type of
+       the induction variable. Otherwise the behavior is undefined. Leaving this
+       case undefined lets the loop be lowered to a plain increment-and-compare
+       on a single induction register without having to account for wraparound.
 
     Semantically we require that the iteration space can be iterated in any
     order, and the loop body can be executed in parallel. If there are data
@@ -883,7 +896,8 @@ def ParallelOp : SCF_Op<"parallel",
   let arguments = (ins Variadic<Index>:$lowerBound,
                        Variadic<Index>:$upperBound,
                        Variadic<Index>:$step,
-                       Variadic<AnyType>:$initVals);
+                       Variadic<AnyType>:$initVals,
+                       UnitAttr:$unsignedCmp);
   let results = (outs Variadic<AnyType>:$results);
   let regions = (region SizedRegion<1>:$region);
 
@@ -892,11 +906,13 @@ def ParallelOp : SCF_Op<"parallel",
     OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds,
       "ValueRange":$steps, "ValueRange":$initVals,
       CArg<"function_ref<void (OpBuilder &, Location, ValueRange, ValueRange)>",
-           "nullptr">:$bodyBuilderFn)>,
+           "nullptr">:$bodyBuilderFn,
+      CArg<"bool", "false">:$unsignedCmp)>,
     OpBuilder<(ins "ValueRange":$lowerBounds, "ValueRange":$upperBounds,
       "ValueRange":$steps,
       CArg<"function_ref<void (OpBuilder &, Location, ValueRange)>",
-           "nullptr">:$bodyBuilderFn)>,
+           "nullptr">:$bodyBuilderFn,
+      CArg<"bool", "false">:$unsignedCmp)>,
   ];
 
   let extraClassDeclaration = [{
diff --git a/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp b/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
index 75cb4a8502f5a..16894f10b5ff1 100644
--- a/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
+++ b/mlir/lib/Conversion/SCFToControlFlow/SCFToControlFlow.cpp
@@ -515,7 +515,9 @@ ParallelLowering::matchAndRewrite(ParallelOp parallelOp,
   for (auto [iv, lower, upper, step] :
        llvm::zip(parallelOp.getInductionVars(), parallelOp.getLowerBound(),
                  parallelOp.getUpperBound(), parallelOp.getStep())) {
-    ForOp forOp = ForOp::create(rewriter, loc, lower, upper, step, iterArgs);
+    ForOp forOp =
+        ForOp::create(rewriter, loc, lower, upper, step, iterArgs,
+                      /*bodyBuilder=*/nullptr, parallelOp.getUnsignedCmp());
     innermostForOp = forOp;
     ivs.push_back(forOp.getInductionVar());
     auto iterRange = forOp.getRegionIterArgs();
diff --git a/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp b/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
index 944e535c08a18..38e49fc0b71d7 100644
--- a/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
+++ b/mlir/lib/Conversion/SCFToGPU/SCFToGPU.cpp
@@ -613,6 +613,9 @@ static LogicalResult processParallelLoop(
 LogicalResult
 ParallelToGpuLaunchLowering::matchAndRewrite(ParallelOp parallelOp,
                                              PatternRewriter &rewriter) const {
+  if (parallelOp.getUnsignedCmp())
+    return rewriter.notifyMatchFailure(
+        parallelOp, "unsigned loop bounds are not supported");
   // Mark the operation as visited for recursive legality check.
   parallelOp->setDiscardableAttr(kVisitedAttrName, rewriter.getUnitAttr());
 
diff --git a/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp b/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp
index 815c83aeb713a..e762fac3b8c5c 100644
--- a/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp
+++ b/mlir/lib/Conversion/SCFToOpenMP/SCFToOpenMP.cpp
@@ -413,6 +413,9 @@ struct ParallelOpLowering : public OpRewritePattern<scf::ParallelOp> {
 
   LogicalResult matchAndRewrite(scf::ParallelOp parallelOp,
                                 PatternRewriter &rewriter) const override {
+    if (parallelOp.getUnsignedCmp())
+      return rewriter.notifyMatchFailure(
+          parallelOp, "unsigned loop bounds are not supported");
     // Bail out early if any reduction init value has a type that is not
     // compatible with LLVM (e.g. index), since we cannot allocate a reduction
     // variable for such types.
diff --git a/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp b/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
index 1db6f128f4c69..d792dad9ad15d 100644
--- a/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
+++ b/mlir/lib/Dialect/Async/Transforms/AsyncParallelFor.cpp
@@ -710,6 +710,10 @@ AsyncParallelForRewrite::matchAndRewrite(scf::ParallelOp op,
   // We do not currently support rewrite for parallel op with reductions.
   if (op.getNumReductions() != 0)
     return failure();
+  // The trip count computation below assumes signed bounds.
+  if (op.getUnsignedCmp())
+    return rewriter.notifyMatchFailure(
+        op, "unsigned loop bounds are not supported");
 
   ImplicitLocOpBuilder b(op.getLoc(), rewriter);
 
diff --git a/mlir/lib/Dialect/SCF/IR/SCF.cpp b/mlir/lib/Dialect/SCF/IR/SCF.cpp
index f0e4b5586bd5d..ea2fecd63c2ec 100644
--- a/mlir/lib/Dialect/SCF/IR/SCF.cpp
+++ b/mlir/lib/Dialect/SCF/IR/SCF.cpp
@@ -2753,11 +2753,15 @@ void ParallelOp::build(
     OpBuilder &builder, OperationState &result, ValueRange lowerBounds,
     ValueRange upperBounds, ValueRange steps, ValueRange initVals,
     function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)>
-        bodyBuilderFn) {
+        bodyBuilderFn,
+    bool unsignedCmp) {
   result.addOperands(lowerBounds);
   result.addOperands(upperBounds);
   result.addOperands(steps);
   result.addOperands(initVals);
+  if (unsignedCmp)
+    result.addAttribute(getUnsignedCmpAttrName(result.name),
+                        builder.getUnitAttr());
   result.addAttribute(
       ParallelOp::getOperandSegmentSizeAttr(),
       builder.getDenseI32ArrayAttr({static_cast<int32_t>(lowerBounds.size()),
@@ -2787,7 +2791,8 @@ void ParallelOp::build(
 void ParallelOp::build(
     OpBuilder &builder, OperationState &result, ValueRange lowerBounds,
     ValueRange upperBounds, ValueRange steps,
-    function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn) {
+    function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuilderFn,
+    bool unsignedCmp) {
   // Only pass a non-null wrapper if bodyBuilderFn is non-null itself. Make sure
   // we don't capture a reference to a temporary by constructing the lambda at
   // function level.
@@ -2800,8 +2805,8 @@ void ParallelOp::build(
   if (bodyBuilderFn)
     wrapper = wrappedBuilderFn;
 
-  build(builder, result, lowerBounds, upperBounds, steps, ValueRange(),
-        wrapper);
+  build(builder, result, lowerBounds, upperBounds, steps, ValueRange(), wrapper,
+        unsignedCmp);
 }
 
 LogicalResult ParallelOp::verify() {
@@ -2869,6 +2874,10 @@ LogicalResult ParallelOp::verify() {
 
 ParseResult ParallelOp::parse(OpAsmParser &parser, OperationState &result) {
   auto &builder = parser.getBuilder();
+  if (succeeded(parser.parseOptionalKeyword("unsigned")))
+    result.addAttribute(getUnsignedCmpAttrName(result.name),
+                        builder.getUnitAttr());
+
   // Parse an opening `(` followed by induction variables followed by `)`
   SmallVector<OpAsmParser::Argument, 4> ivs;
   if (parser.parseArgumentList(ivs, OpAsmParser::Delimiter::Paren))
@@ -2935,6 +2944,8 @@ ParseResult ParallelOp::parse(OpAsmParser &parser, OperationState &result) {
 }
 
 void ParallelOp::print(OpAsmPrinter &p) {
+  if (getUnsignedCmp())
+    p << " unsigned";
   p << " (" << getBody()->getArguments() << ") = (" << getLowerBound()
     << ") to (" << getUpperBound() << ") step (" << getStep() << ")";
   if (!getInitVals().empty())
diff --git a/mlir/lib/Dialect/SCF/Transforms/ParallelForToNestedFors.cpp b/mlir/lib/Dialect/SCF/Transforms/ParallelForToNestedFors.cpp
index c469a991fff64..b3bf4d1925483 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ParallelForToNestedFors.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ParallelForToNestedFors.cpp
@@ -46,6 +46,8 @@ mlir::scf::parallelForToNestedFors(RewriterBase &rewriter,
 
   scf::LoopNest loopNest =
       scf::buildLoopNest(rewriter, loc, lowerBounds, upperBounds, steps);
+  for (scf::ForOp forOp : loopNest.loops)
+    forOp.setUnsignedCmp(parallelOp.getUnsignedCmp());
 
   SmallVector<Value> newInductionVars = llvm::map_to_vector(
       loopNest.loops, [](scf::ForOp forOp) { return forOp.getInductionVar(); });
diff --git a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopCollapsing.cpp b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopCollapsing.cpp
index 0f00806870d8a..1ad5ba3f127cd 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopCollapsing.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopCollapsing.cpp
@@ -98,6 +98,10 @@ struct TestSCFParallelLoopCollapsing
             << flattenedCombinedLoops.size() << " iter args.";
         return;
       }
+      if (op.getUnsignedCmp()) {
+        op.emitOpError("with unsigned bounds is not supported");
+        return;
+      }
       collapseParallelLoops(rewriter, op, combinedLoops);
     });
   }
diff --git a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopFusion.cpp b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopFusion.cpp
index 397920565c5ba..17352933acc29 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopFusion.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopFusion.cpp
@@ -63,7 +63,8 @@ static bool hasNestedParallelOp(ParallelOp ploop) {
 /// Verify equal iteration spaces.
 static bool equalIterationSpaces(ParallelOp firstPloop,
                                  ParallelOp secondPloop) {
-  if (firstPloop.getNumLoops() != secondPloop.getNumLoops())
+  if (firstPloop.getNumLoops() != secondPloop.getNumLoops() ||
+      firstPloop.getUnsignedCmp() != secondPloop.getUnsignedCmp())
     return false;
 
   // Two bounds match if they are the same value, or if both are constants
@@ -779,8 +780,9 @@ interchangeLoops(OpBuilder &builder, ParallelOp &loop,
       applyPermutation(SmallVector<Value>(loop.getUpperBound()), indices);
   SmallVector<Value> newStep =
       applyPermutation(SmallVector<Value>(loop.getStep()), indices);
-  auto newOp = ParallelOp::create(builder, loop.getLoc(), newLB, newUB, newStep,
-                                  loop.getInitVals(), nullptr);
+  auto newOp =
+      ParallelOp::create(builder, loop.getLoc(), newLB, newUB, newStep,
+                         loop.getInitVals(), nullptr, loop.getUnsignedCmp());
   auto ivs = loop.getInductionVars();
   SmallVector<Value> newIvs = applyPermutation(
       newOp.getInductionVars(), invertPermutationVector(indices));
@@ -964,9 +966,10 @@ static void applyLoopFusion(ParallelOp &firstPloop, ParallelOp &secondPloop,
 
   IRRewriter b(builder);
   b.setInsertionPoint(secondPloop);
-  auto newSecondPloop = ParallelOp::create(
-      b, secondPloop.getLoc(), secondPloop.getLowerBound(),
-      secondPloop.getUpperBound(), secondPloop.getStep(), newInitVars);
+  auto newSecondPloop =
+      ParallelOp::create(b, secondPloop.getLoc(), secondPloop.getLowerBound(),
+                         secondPloop.getUpperBound(), secondPloop.getStep(),
+                         newInitVars, nullptr, secondPloop.getUnsignedCmp());
 
   Block *newBlock = newSecondPloop.getBody();
   auto term1 = cast<ReduceOp>(block1->getTerminator());
diff --git a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
index 9638f0a3c75c5..36fe6b108ba5f 100644
--- a/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/ParallelLoopTiling.cpp
@@ -80,6 +80,7 @@ mlir::scf::tileParallelLoop(ParallelOp op, ArrayRef<int64_t> tileSizes,
   }
   auto outerLoop = ParallelOp::create(b, op.getLoc(), op.getLowerBound(),
                                       op.getUpperBound(), newSteps);
+  outerLoop.setUnsignedCmp(op.getUnsignedCmp());
   b.setInsertionPointToStart(outerLoop.getBody());
 
   // Compute min(size, dim - offset) to avoid out-of-bounds accesses.
@@ -135,12 +136,16 @@ mlir::scf::tileParallelLoop(ParallelOp op, ArrayRef<int64_t> tileSizes,
   auto innerLoop = ParallelOp::create(
       b, op.getLoc(), SmallVector<Value, 2>(newBounds.size(), zero), newBounds,
       op.getStep());
+  innerLoop.setUnsignedCmp(op.getUnsignedCmp());
 
   if (noMinMaxBounds && needInboundCheck) {
     b.setInsertionPointToStart(innerLoop.getBody());
     // Insert in-bound check
     Value inbound =
         arith::ConstantIntOp::create(b, op.getLoc(), b.getIntegerType(1), 1);
+    arith::CmpIPredicate predicate = op.getUnsignedCmp()
+                                         ? arith::CmpIPredicate::ult
+                                         : arith::CmpIPredicate::slt;
     for (auto [outerUpperBound, outerIV, innerIV, innerStep] :
          llvm::zip(outerLoop.getUpperBound(), outerLoop.getInductionVars(),
                    innerLoop.getInductionVars(), innerLoop.getStep())) {
@@ -149,8 +154,8 @@ mlir::scf::tileParallelLoop(ParallelOp op, ArrayRef<int64_t> tileSizes,
       Value index = arith::AddIOp::create(
           b, op.getLoc(),
           arith::MulIOp::create(b, op.getLoc(), innerIV, innerStep), outerIV);
-      Value dimInbound = arith::CmpIOp::create(
-          b, op.getLoc(), arith::CmpIPredicate::slt, index, outerUpperBound);
+      Value dimInbound = arith::CmpIOp::create(b, op.getLoc(), predicate, index,
+                                               outerUpperBound);
       inbound = arith::AndIOp::create(b, op.getLoc(), inbound, dimInbound);
     }
     auto ifInbound = IfOp::create(b, op.getLoc(),
diff --git a/mlir/test/Conversion/SCFToControlFlow/convert-to-cfg.mlir b/mlir/test/Conversion/SCFToControlFlow/convert-to-cfg.mlir
index be89bfe9780ba..47e085509dd91 100644
--- a/mlir/test/Conversion/SCFToControlFlow/convert-to-cfg.mlir
+++ b/mlir/test/Conversion/SCFToControlFlow/convert-to-cfg.mlir
@@ -278,6 +278,22 @@ func.func @parallel_loop(%arg0 : index, %arg1 : index, %arg2 : index,
   return
 }
 
+// CHECK-LABEL: func @unsigned_parallel_loop(
+// CHECK:           arith.cmpi ult
+// CHECK:           cf.cond_br
+// CHECK:           arith.cmpi ult
+// CHECK:           cf.cond_br
+func.func @unsigned_parallel_loop(%arg0 : index, %arg1 : index, %arg2 : index,
+                                  %arg3 : index, %arg4 : index) {
+  %step = arith.constant 1 : index
+  scf.parallel unsigned (%i0, %i1) = (%arg0, %arg1) to (%arg2, %arg3)
+                                                   step (%arg4, %step) {
+    %c1 = arith.constant 1 : index
+    scf.reduce
+  }
+  return
+}
+
 // CHECK-LABEL: @for_yield
 // CHECK-SAME: (%[[LB:.*]]: index, %[[UB:.*]]: index, %[[STEP:.*]]: index)
 // CHECK:        %[[INIT0:.*]] = arith.constant 0
diff --git a/mlir/test/Conversion/SCFToGPU/parallel_loop.mlir b/mlir/test/Conversion/SCFToGPU/parallel_loop.mlir
index 2f192df1dad2e..8eccf0b66a60d 100644
--- a/mlir/test/Conversion/SCFToGPU/parallel_loop.mlir
+++ b/mlir/test/Conversion/SCFToGPU/parallel_loop.mlir
@@ -721,3 +721,16 @@ func.func @scf2gpu_index_creation_1d() {
 //       CHECK:   gpu.launch
 //       CHECK:     %[[IDX:.*]] = affine.apply
 //       CHECK:     arith.addi %[[IDX]],
+
+// -----
+
+// Unsigned bounds are not supported: the mapping is refused and the loop
+// stays illegal.
+func.func @parallel_loop_unsigned(%arg0 : index, %arg1 : index, %arg2 : index,
+                                  %buf : memref<?xf32>) {
+  // expected-error @+1 {{failed to legalize operation 'scf.parallel'}}
+  scf.parallel unsigned (%i0) = (%arg0) to (%arg1) step (%arg2) {
+    %val = memref.load %buf[%i0] : memref<?xf32>
+  } { mapping = [#gpu.loop_dim_map<processor = block_x, map = (d0) -> (d0), bound = (d0) -> (d0)>] }
+  return
+}
diff --git a/mlir/test/Conversion/SCFToOpenMP/scf-to-openmp-unsigned.mlir b/mlir/test/Conversion/SCFToOpenMP/scf-to-openmp-unsigned.mlir
new file mode 100644
index 0000000000000..208012ab3405a
--- /dev/null
+++ b/mlir/test/Conversion/SCFToOpenMP/scf-to-openmp-unsigned.mlir
@@ -0,0 +1,11 @@
+// RUN: mlir-opt -convert-scf-to-openmp='num-threads=4' -verify-diagnostics %s
+
+// The OpenMP loop bounds are signed; an unsigned scf.parallel is left
+// unconverted, which the pass reports.
+func.func @parallel_unsigned(%arg0: index, %arg1: index, %arg2: index) {
+  // expected-error @+1 {{unconverted operation found}}
+  scf.parallel unsigned (%i) = (%arg0) to (%arg1) step (%arg2) {
+    "test.payload"(%i) : (index) -> ()
+  }
+  return
+}
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..7c33dfd7a7caf 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,19 @@ func.func @loop_2d(%arg0: index, %arg1: index, %arg2: index, // lb, ub, step
 // CHECK:           memref.store
 
 // CHECK-LABEL: func private @async_dispatch_fn
+
+// -----
+
+// Unsigned bounds are not supported by the trip-count computation; the loop
+// is left untouched.
+// CHECK-LABEL: @unsigned_loop_1d(
+// CHECK-NOT:     async.execute
+// CHECK:         scf.parallel unsigned (
+// CHECK-NOT:     async.execute
+func.func @unsigned_loop_1d(%arg0: index, %arg1: index, %arg2: index, %arg3: memref<?xf32>) {
+  scf.parallel unsigned (%i) = (%arg0) to (%arg1) step (%arg2) {
+    %one = arith.constant 1.0 : f32
+    memref.store %one, %arg3[%i] : memref<?xf32>
+  }
+  return
+}
diff --git a/mlir/test/Dialect/SCF/ops.mlir b/mlir/test/Dialect/SCF/ops.mlir
index 912dcc04bca7e..3fee8c4926d0f 100644
--- a/mlir/test/Dialect/SCF/ops.mlir
+++ b/mlir/test/Dialect/SCF/ops.mlir
@@ -441,3 +441,13 @@ func.func @switch(%arg0: index) -> i32 {
 
   return %0 : i32
 }
+
+func.func @std_parallel_loop_unsigned(%arg0 : index, %arg1 : index,
+                                      %arg2 : index) {
+  scf.parallel unsigned (%i0) = (%arg0) to (%arg1) step (%arg2) {
+    scf.reduce
+  }
+  return
+}
+// CHECK-LABEL: func @std_parallel_loop_unsigned
+//  CHECK-NEXT:   scf.parallel unsigned (%{{.*}}) = (%{{.*}}) to (%{{.*}}) step (%{{.*}}) {
diff --git a/mlir/test/Dialect/SCF/parallel-loop-fusion.mlir b/mlir/test/Dialect/SCF/parallel-loop-fusion.mlir
index 9472e9ebb9d22..dd5874d104f72 100644
--- a/mlir/test/Dialect/SCF/parallel-loop-fusion.mlir
+++ b/mlir/test/Dialect/SCF/parallel-loop-fusion.mlir
@@ -1761,3 +1761,39 @@ func.func @do_not_fuse_distinct_dynamic_bounds(%A: memref<16xf32>,
 // CHECK-LABEL: func @do_not_fuse_distinct_dynamic_bounds
 // CHECK:        scf.parallel
 // CHECK:        scf.parallel
+
+// -----
+
+func.func @fuse_unsigned_loops() {
+  %c2 = arith.constant 2 : index
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  scf.parallel unsigned (%i, %j) = (%c0, %c0) to (%c2, %c2) step (%c1, %c1) {
+    scf.reduce
+  }
+  scf.parallel unsigned (%i, %j) = (%c0, %c0) to (%c2, %c2) step (%c1, %c1) {
+    scf.reduce
+  }
+  return
+}
+// CHECK-LABEL: func @fuse_unsigned_loops
+// CHECK:        scf.parallel unsigned (
+// CHECK-NOT:    scf.parallel
+
+// -----
+
+func.func @do_not_fuse_mixed_signedness() {
+  %c2 = arith.constant 2 : index
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  scf.parallel unsigned (%i, %j) = (%c0, %c0) to (%c2, %c2) step (%c1, %c1) {
+    scf.reduce
+  }
+  scf.parallel (%i, %j) = (%c0, %c0) to (%c2, %c2) step (%c1, %c1) {
+    scf.reduce
+  }
+  return
+}
+// CHECK-LABEL: func @do_not_fuse_mixed_signedness
+// CHECK:        scf.parallel unsigned (
+// CHECK:        scf.parallel (
diff --git a/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir b/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
index 79907b03996aa..591dfb6919495 100644
--- a/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
+++ b/mlir/test/Dialect/SCF/parallel-loop-tiling-inbound-check.mlir
@@ -167,3 +167,25 @@ func.func @parallel_loop_negative_lower_bound(%arg0 : index, %arg1 : index,
 // CHECK-NOT:       arith.cmpi ult
 // CHECK:           arith.cmpi slt
 // CHECK-NOT:       arith.cmpi ult
+
+// -----
+
+func.func @parallel_loop_unsigned(%arg0 : index, %arg1 : index,
+                                  %result: memref<?x?xf32>) {
+  %c0 = arith.constant 0 : index
+  %c1 = arith.constant 1 : index
+  %cst = arith.constant 0.0 : f32
+  scf.parallel unsigned (%i0, %i1) = (%c0, %c0) to (%arg0, %arg1) step (%c1, %c1) {
+    memref.store %cst, %result[%i0, %i1] : memref<?x?xf32>
+  }
+  return
+}
+
+// Unsigned bounds keep the unsigned compare and the attribute is propagated to
+// both generated loops.
+// CHECK-LABEL:   func @parallel_loop_unsigned(
+// CHECK:           scf.parallel unsigned (
+// CHECK:             scf.parallel unsigned (
+// CHECK-NOT:         arith.cmpi slt
+// CHECK:             arith.cmpi ult
+// CHECK-NOT:         arith.cmpi slt
diff --git a/mlir/test/Dialect/SCF/parallel-to-nested-fors.mlir b/mlir/test/Dialect/SCF/parallel-to-nested-fors.mlir
index 47a8da8c244b2..d18a6582c66a6 100644
--- a/mlir/test/Dialect/SCF/parallel-to-nested-fors.mlir
+++ b/mlir/test/Dialect/SCF/parallel-to-nested-fors.mlir
@@ -78,3 +78,17 @@ func.func @two_iters_with_reduce(%lb1: index, %lb2: index, %ub1: index, %ub2: in
   }
   return %0 : i32
 }
+
+// -----
+
+func.func private @callee(%i: index, %j: index)
+
+func.func @unsigned(%lb1: index, %lb2: index, %ub1: index, %ub2: index, %step1: index, %step2: index) {
+  scf.parallel unsigned (%i, %j) = (%lb1, %lb2) to (%ub1, %ub2) step (%step1, %step2) {
+    func.call @callee(%i, %j) : (index, index) -> ()
+  }
+  // CHECK:           scf.for unsigned %[[VAL_0:.*]] = %[[ARG0:.*]] to %[[ARG2:.*]] step %[[ARG4:.*]] {
+  // CHECK:             scf.for unsigned %[[VAL_1:.*]] = %[[ARG1:.*]] to %[[ARG3:.*]] step %[[ARG5:.*]] {
+  // CHECK:               func.call @callee(%[[VAL_0]], %[[VAL_1]]) : (index, index) -> ()
+  return
+}
diff --git a/mlir/test/Transforms/parallel-loop-collapsing-unsigned.mlir b/mlir/test/Transforms/parallel-loop-collapsing-unsigned.mlir
new file mode 100644
index 0000000000000..c465fcb914145
--- /dev/null
+++ b/mlir/test/Transforms/parallel-loop-collapsing-unsigned.mlir
@@ -0,0 +1,11 @@
+// RUN: mlir-opt -allow-unregistered-dialect %s -pass-pipeline='builtin.module(func.func(test-scf-parallel-loop-collapsing{collapsed-indices-0=0,1}))' -verify-diagnostics
+
+// The normalization performed by the collapsing assumes signed bounds.
+func.func @collapse_unsigned(%lb0: index, %ub0: index, %lb1: index, %ub1: index) {
+  %c1 = arith.constant 1 : index
+  // expected-error @+1 {{'scf.parallel' op with unsigned bounds is not supported}}
+  scf.parallel unsigned (%i0, %i1) = (%lb0, %lb1) to (%ub0, %ub1) step (%c1, %c1) {
+    %result = "magic.op"(%i0, %i1) : (index, index) -> index
+  }
+  return
+}



More information about the Mlir-commits mailing list