[Mlir-commits] [mlir] [mlir][scf] Add an option to emit the peeled partial iteration as scf.if (PR #236805)
Md Abdullah Shahneous Bari
llvmlistbot at llvm.org
Thu Oct 29 08:08:40 PDT 2026
https://github.com/mshahneo created https://github.com/llvm/llvm-project/pull/236805
Loop peeling splits an `scf.for` into a main loop and a partial iteration. The partial iteration runs at most once, but it is emitted as another `scf.for`. Later passes then have to prove again that it runs at most once. That proof depends on the exact form of the split bound, which has changed several times recently. After lowering to LLVM IR it often fails: in a small test, `opt -O2` keeps the peeled tail as a second loop, because the loop analysis cannot bound its trip count after loop rotation.
This patch adds an opt-in way to emit the partial iteration as an `scf.if`:
- `scf::convertPartialIterationToIf` rewrites the partial iteration returned by `peelForLoopAndSimplifyBounds` or `peelForLoopFirstIteration` into an `scf.if` with the condition `lb < ub` (`ult` for unsigned loops). The body is moved into the `then` block, with the induction variable replaced by the lower bound. The `else` block forwards the init args.
- `-scf-for-loop-peeling` gets a `partial-as-if` option.
- `transform.loop.peel` gets a `partial_as_if` attribute. With it, the second result is the `scf.if`.
Both default to false, so existing users see no change.
With `skip-partial`, the pass found the partial iteration by looking only at parent `scf.for` ops. It now looks at all parents, so loops inside an `scf.if` partial iteration are still skipped.
This also fixes the comment on `peelForLoop`, which still described the partial iteration as an `scf.if`.
Tests: a new `for-loop-peeling-partial-as-if.mlir` (back and front peeling, unsigned loops, `skip-partial` on and off) and two new cases in `SCF/transform-ops.mlir`.
Note: the partial iteration was first emitted as an `scf.if` (D105804). D109568 changed it to an `scf.for` for consistency with `linalg.tiled_loop` peeling, which has since been removed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
>From 0c8b523d05bcad301ca45b6e88472cd921c51de3 Mon Sep 17 00:00:00 2001
From: "Shahneous Bari, Md Abdullah" <md.abdullah.shahneous.bari at intel.com>
Date: Wed, 28 Oct 2026 23:23:39 +0000
Subject: [PATCH] [mlir][scf] Add an option to emit the peeled partial
iteration as scf.if
Loop peeling splits an `scf.for` into a main loop and a partial
iteration. The partial iteration runs at most once, but it is emitted
as another `scf.for`. Later passes then have to prove again that it
runs at most once. That proof depends on the exact form of the split
bound, which has changed several times recently. After lowering to
LLVM IR it often fails: in a small test, `opt -O2` keeps the peeled
tail as a second loop, because the loop analysis cannot bound its trip
count after loop rotation.
This patch adds an opt-in way to emit the partial iteration as an
`scf.if`:
- `scf::convertPartialIterationToIf` rewrites the partial iteration
returned by `peelForLoopAndSimplifyBounds` or
`peelForLoopFirstIteration` into an `scf.if` with the condition
`lb < ub` (`ult` for unsigned loops). The body is moved into the
`then` block, with the induction variable replaced by the lower
bound. The `else` block forwards the init args.
- `-scf-for-loop-peeling` gets a `partial-as-if` option.
- `transform.loop.peel` gets a `partial_as_if` attribute. With it, the
second result is the `scf.if`.
Both default to false, so existing users see no change.
With `skip-partial`, the pass found the partial iteration by looking
only at parent `scf.for` ops. It now looks at all parents, so loops
inside an `scf.if` partial iteration are still skipped.
This also fixes the comment on `peelForLoop`, which still described
the partial iteration as an `scf.if`.
Tests: a new `for-loop-peeling-partial-as-if.mlir` (back and front
peeling, unsigned loops, `skip-partial` on and off) and two new cases
in `SCF/transform-ops.mlir`.
Note: the partial iteration was first emitted as an `scf.if`
(D105804). D109568 changed it to an `scf.for` for consistency with
`linalg.tiled_loop` peeling, which has since been removed.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
.../SCF/TransformOps/SCFTransformOps.td | 18 ++-
.../mlir/Dialect/SCF/Transforms/Passes.td | 6 +-
.../mlir/Dialect/SCF/Transforms/Transforms.h | 11 ++
.../SCF/TransformOps/SCFTransformOps.cpp | 5 +-
.../SCF/Transforms/LoopSpecialization.cpp | 76 +++++++---
.../SCF/for-loop-peeling-partial-as-if.mlir | 131 ++++++++++++++++++
mlir/test/Dialect/SCF/transform-ops.mlir | 62 +++++++++
7 files changed, 286 insertions(+), 23 deletions(-)
create mode 100644 mlir/test/Dialect/SCF/for-loop-peeling-partial-as-if.mlir
diff --git a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
index 4f463bb02fb771..0b4496d58f8c69 100644
--- a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
+++ b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
@@ -194,8 +194,11 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
When `peelFront` is false, the first result (peeled loop) is the portion
of the target loop with the highest upper bound that is divisible by the
- step. The second result (remainder loop) contains the remaining iterations.
-
+ step. The second result (remainder loop) contains the remaining iterations.
+
+ The second result runs at most once. When `partialAsIf` is true, it is an
+ `scf.if` instead of an `scf.for`, in both modes.
+
Note that even though the Payload IR modification may be performed
in-place, this operation consumes the operand handle and produces a new one.
@@ -207,12 +210,17 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
let arguments =
(ins Transform_ScfForOp:$target,
DefaultValuedAttr<BoolAttr, "false">:$peel_front,
- DefaultValuedAttr<BoolAttr, "false">:$fail_if_already_divisible);
+ DefaultValuedAttr<BoolAttr, "false">:$fail_if_already_divisible,
+ DefaultValuedAttr<BoolAttr, "false">:$partial_as_if);
let results = (outs TransformHandleTypeInterface:$peeled_loop,
TransformHandleTypeInterface:$remainder_loop);
- let assemblyFormat =
- "$target oilist(`peel_front` `=` $peel_front | `fail_if_already_divisible` `=` $fail_if_already_divisible) attr-dict `:` functional-type(operands, results)";
+ let assemblyFormat = [{
+ $target oilist(`peel_front` `=` $peel_front
+ | `fail_if_already_divisible` `=` $fail_if_already_divisible
+ | `partial_as_if` `=` $partial_as_if)
+ attr-dict `:` functional-type(operands, results)
+ }];
let extraClassDeclaration = [{
::mlir::DiagnosedSilenceableFailure applyToOne(
diff --git a/mlir/include/mlir/Dialect/SCF/Transforms/Passes.td b/mlir/include/mlir/Dialect/SCF/Transforms/Passes.td
index 44e523fdfb8fe3..945e05be647f92 100644
--- a/mlir/include/mlir/Dialect/SCF/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/SCF/Transforms/Passes.td
@@ -30,7 +30,11 @@ def SCFForLoopPeeling : Pass<"scf-for-loop-peeling"> {
Option<"skipPartial", "skip-partial", "bool",
/*default=*/"true",
"Do not peel loops inside of the last, partial iteration of another "
- "already peeled loop.">
+ "already peeled loop.">,
+ Option<"partialAsIf", "partial-as-if", "bool",
+ /*default=*/"false",
+ "Emit the partial iteration, which runs at most once, as an scf.if "
+ "instead of an scf.for.">
];
let dependentDialects = ["affine::AffineDialect"];
}
diff --git a/mlir/include/mlir/Dialect/SCF/Transforms/Transforms.h b/mlir/include/mlir/Dialect/SCF/Transforms/Transforms.h
index 00e85723071519..edec096614eeed 100644
--- a/mlir/include/mlir/Dialect/SCF/Transforms/Transforms.h
+++ b/mlir/include/mlir/Dialect/SCF/Transforms/Transforms.h
@@ -106,6 +106,17 @@ LogicalResult peelForLoopAndSimplifyBounds(RewriterBase &rewriter, ForOp forOp,
LogicalResult peelForLoopFirstIteration(RewriterBase &rewriter, ForOp forOp,
scf::ForOp &partialIteration);
+/// Rewrite `partialIteration`, the partial iteration created by
+/// `peelForLoopAndSimplifyBounds` or `peelForLoopFirstIteration`, into an
+/// scf.if. The body runs if the lower bound is less than the upper bound, with
+/// the induction variable replaced by the lower bound. Otherwise the init args
+/// are forwarded. Returns the new scf.if.
+///
+/// Note: `partialIteration` must run at most once. Peeling guarantees this;
+/// it is not checked here.
+IfOp convertPartialIterationToIf(RewriterBase &rewriter,
+ ForOp partialIteration);
+
/// Tile a parallel loop of the form
/// scf.parallel (%i0, %i1) = (%arg0, %arg1) to (%arg2, %arg3)
/// step (%arg4, %arg5)
diff --git a/mlir/lib/Dialect/SCF/TransformOps/SCFTransformOps.cpp b/mlir/lib/Dialect/SCF/TransformOps/SCFTransformOps.cpp
index 1d2ec1ad0e0dc3..d441b7b3693ffe 100644
--- a/mlir/lib/Dialect/SCF/TransformOps/SCFTransformOps.cpp
+++ b/mlir/lib/Dialect/SCF/TransformOps/SCFTransformOps.cpp
@@ -282,7 +282,10 @@ transform::LoopPeelOp::applyToOne(transform::TransformRewriter &rewriter,
}
results.push_back(target);
- results.push_back(result);
+ if (getPartialAsIf())
+ results.push_back(scf::convertPartialIterationToIf(rewriter, result));
+ else
+ results.push_back(result);
return DiagnosedSilenceableFailure::success();
}
diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
index e232692ecc4e5a..a53a5c06733b4c 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
@@ -111,15 +111,16 @@ static void specializeForLoopForUnrolling(ForOp op) {
/// Rewrite a for loop with bounds/step that potentially do not divide evenly
/// into a for loop where the step divides the iteration space evenly, followed
-/// by an scf.if for the last (partial) iteration (if any).
+/// by another scf.for for the last (partial) iteration (if any).
///
/// This function rewrites the given scf.for loop in-place and creates a new
-/// scf.if operation for the last iteration. It replaces all uses of the
-/// unpeeled loop with the results of the newly generated scf.if.
+/// scf.for operation for the last iteration. It replaces all uses of the
+/// unpeeled loop with the results of the newly generated scf.for.
///
-/// The newly generated scf.if operation is returned via `ifOp`. The boundary
-/// at which the loop is split (new upper bound) is returned via `splitBound`.
-/// The return value indicates whether the loop was rewritten or not.
+/// The newly generated scf.for operation is returned via `partialIteration`.
+/// The boundary at which the loop is split (new upper bound) is returned via
+/// `splitBound`. The return value indicates whether the loop was rewritten or
+/// not.
///
/// Note: Loops with a step size of 0 cannot be peeled. Applying this function
/// to such a loop may result in IR with undefined behavior.
@@ -315,14 +316,48 @@ LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
return success();
}
+scf::IfOp mlir::scf::convertPartialIterationToIf(RewriterBase &rewriter,
+ ForOp partialIteration) {
+ RewriterBase::InsertionGuard guard(rewriter);
+ rewriter.setInsertionPoint(partialIteration);
+ Location loc = partialIteration.getLoc();
+
+ // The partial iteration runs at most once: once if lb < ub, else not at all.
+ auto pred = partialIteration.getUnsignedCmp() ? arith::CmpIPredicate::ult
+ : arith::CmpIPredicate::slt;
+ Value cond = arith::CmpIOp::create(rewriter, loc, pred,
+ partialIteration.getLowerBound(),
+ partialIteration.getUpperBound());
+ bool hasResults = partialIteration.getNumResults() > 0;
+ auto ifOp = IfOp::create(rewriter, loc, partialIteration.getResultTypes(),
+ cond, /*addThenBlock=*/true,
+ /*addElseBlock=*/hasResults);
+
+ // Move the body into the "then" block. Its scf.yield becomes the terminator.
+ SmallVector<Value> replacements = {partialIteration.getLowerBound()};
+ llvm::append_range(replacements, partialIteration.getInitArgs());
+ rewriter.inlineBlockBefore(partialIteration.getBody(), ifOp.thenBlock(),
+ ifOp.thenBlock()->end(), replacements);
+
+ // If the loop does not run, it returns its init args.
+ if (hasResults) {
+ rewriter.setInsertionPointToStart(ifOp.elseBlock());
+ YieldOp::create(rewriter, loc, partialIteration.getInitArgs());
+ }
+
+ rewriter.replaceOp(partialIteration, ifOp.getResults());
+ return ifOp;
+}
+
static constexpr char kPeeledLoopLabel[] = "__peeled_loop__";
static constexpr char kPartialIterationLabel[] = "__partial_iteration__";
namespace {
struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
- ForLoopPeelingPattern(MLIRContext *ctx, bool peelFront, bool skipPartial)
+ ForLoopPeelingPattern(MLIRContext *ctx, bool peelFront, bool skipPartial,
+ bool partialAsIf)
: OpRewritePattern<ForOp>(ctx), peelFront(peelFront),
- skipPartial(skipPartial) {}
+ skipPartial(skipPartial), partialAsIf(partialAsIf) {}
LogicalResult matchAndRewrite(ForOp forOp,
PatternRewriter &rewriter) const override {
@@ -341,9 +376,10 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
} else {
if (skipPartial) {
// No peeling of loops inside the partial iteration of another peeled
- // loop.
+ // loop. The partial iteration is an scf.for, or an scf.if if
+ // `partialAsIf` is set.
Operation *op = forOp.getOperation();
- while ((op = op->getParentOfType<scf::ForOp>())) {
+ while ((op = op->getParentOp())) {
if (op->hasDiscardableAttr(kPartialIterationLabel))
return failure();
}
@@ -354,12 +390,15 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
return failure();
}
+ Operation *partialOp = partialIteration;
+ if (partialAsIf)
+ partialOp = convertPartialIterationToIf(rewriter, partialIteration);
+
// Apply label, so that the same loop is not rewritten a second time.
- rewriter.modifyOpInPlace(partialIteration, [&]() {
- partialIteration->setDiscardableAttr(kPeeledLoopLabel,
- rewriter.getUnitAttr());
- partialIteration->setDiscardableAttr(kPartialIterationLabel,
- rewriter.getUnitAttr());
+ rewriter.modifyOpInPlace(partialOp, [&]() {
+ partialOp->setDiscardableAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
+ partialOp->setDiscardableAttr(kPartialIterationLabel,
+ rewriter.getUnitAttr());
});
rewriter.modifyOpInPlace(forOp, [&]() {
forOp->setDiscardableAttr(kPeeledLoopLabel, rewriter.getUnitAttr());
@@ -377,6 +416,10 @@ struct ForLoopPeelingPattern : public OpRewritePattern<ForOp> {
/// Note: Takes into account the entire chain of parent operations, not just
/// the direct parent.
bool skipPartial;
+
+ /// If set to true, the partial iteration is emitted as an scf.if instead of
+ /// an scf.for.
+ bool partialAsIf;
};
} // namespace
@@ -404,7 +447,8 @@ struct ForLoopPeeling : public impl::SCFForLoopPeelingBase<ForLoopPeeling> {
auto *parentOp = getOperation();
MLIRContext *ctx = parentOp->getContext();
RewritePatternSet patterns(ctx);
- patterns.add<ForLoopPeelingPattern>(ctx, peelFront, skipPartial);
+ patterns.add<ForLoopPeelingPattern>(ctx, peelFront, skipPartial,
+ partialAsIf);
(void)applyPatternsGreedily(parentOp, std::move(patterns));
// Drop the markers.
diff --git a/mlir/test/Dialect/SCF/for-loop-peeling-partial-as-if.mlir b/mlir/test/Dialect/SCF/for-loop-peeling-partial-as-if.mlir
new file mode 100644
index 00000000000000..20e382b56e3cc5
--- /dev/null
+++ b/mlir/test/Dialect/SCF/for-loop-peeling-partial-as-if.mlir
@@ -0,0 +1,131 @@
+// RUN: mlir-opt %s -scf-for-loop-peeling=partial-as-if=true -canonicalize -split-input-file | FileCheck %s
+// RUN: mlir-opt %s -scf-for-loop-peeling="partial-as-if=true peel-front=true" -canonicalize -split-input-file | FileCheck %s --check-prefix=FRONT
+// RUN: mlir-opt %s -scf-for-loop-peeling="partial-as-if=true skip-partial=false" -canonicalize -split-input-file | FileCheck %s --check-prefix=NO-SKIP
+
+// The partial iteration becomes an scf.if. Its body uses the lower bound as
+// the induction variable, and the else branch forwards the init args. The
+// affine.min in the partial iteration is still simplified.
+
+// CHECK-DAG: #[[$MAP:.*]] = affine_map<()[s0, s1] -> (s0 - s1)>
+// CHECK: func @iter_args(
+// CHECK-SAME: %[[LB:.*]]: index, %[[UB:.*]]: index, %[[INIT:.*]]: f32
+// CHECK-DAG: %[[C4:.*]] = arith.constant 4 : index
+// CHECK: %[[MAIN:.*]] = scf.for %[[IV:.*]] = %[[LB]] to %[[SPLIT:.*]] step %[[C4]] iter_args(%[[ACC:.*]] = %[[INIT]]) -> (f32) {
+// CHECK: %[[V:.*]] = func.call @use(%[[IV]], %[[C4]])
+// CHECK: %[[SUM:.*]] = arith.addf %[[ACC]], %[[V]]
+// CHECK: scf.yield %[[SUM]]
+// CHECK: }
+// CHECK: %[[COND:.*]] = arith.cmpi slt, %[[SPLIT]], %[[UB]] : index
+// CHECK: %[[RES:.*]] = scf.if %[[COND]] -> (f32) {
+// CHECK: %[[SIZE:.*]] = affine.apply #[[$MAP]]()[%[[UB]], %[[SPLIT]]]
+// CHECK: %[[V2:.*]] = func.call @use(%[[SPLIT]], %[[SIZE]])
+// CHECK: %[[SUM2:.*]] = arith.addf %[[MAIN]], %[[V2]]
+// CHECK: scf.yield %[[SUM2]]
+// CHECK: } else {
+// CHECK: scf.yield %[[MAIN]]
+// CHECK: }
+// CHECK: return %[[RES]]
+
+// With peel-front, the first iteration becomes the scf.if.
+
+// FRONT: func @iter_args(
+// FRONT-SAME: %[[LB:.*]]: index, %[[UB:.*]]: index, %[[INIT:.*]]: f32
+// FRONT: %[[SPLIT:.*]] = arith.maxsi %[[LB]], %{{.*}} : index
+// FRONT: %[[COND:.*]] = arith.cmpi slt, %[[LB]], %[[SPLIT]] : index
+// FRONT: %[[FIRST:.*]] = scf.if %[[COND]] -> (f32) {
+// FRONT: %[[V:.*]] = func.call @use(%[[LB]], %{{.*}})
+// FRONT: %[[SUM:.*]] = arith.addf %[[INIT]], %[[V]]
+// FRONT: scf.yield %[[SUM]]
+// FRONT: } else {
+// FRONT: scf.yield %[[INIT]]
+// FRONT: }
+// FRONT: %[[RES:.*]] = scf.for %{{.*}} = %[[SPLIT]] to %[[UB]] step %{{.*}} iter_args(%{{.*}} = %[[FIRST]]) -> (f32) {
+// FRONT: return %[[RES]]
+
+#map = affine_map<(d0)[s0, s1] -> (s0, s1 - d0)>
+func.func private @use(index, index) -> f32
+
+func.func @iter_args(%lb: index, %ub: index, %init: f32) -> f32 {
+ %c4 = arith.constant 4 : index
+ %r = scf.for %iv = %lb to %ub step %c4 iter_args(%acc = %init) -> f32 {
+ %size = affine.min #map(%iv)[%c4, %ub]
+ %v = func.call @use(%iv, %size) : (index, index) -> f32
+ %sum = arith.addf %acc, %v : f32
+ scf.yield %sum : f32
+ }
+ return %r : f32
+}
+
+// -----
+
+// An unsigned loop gets an unsigned compare. Without results, there is no
+// else branch.
+
+// CHECK: func @unsigned(
+// CHECK-SAME: %[[UB:.*]]: index
+// CHECK: scf.for unsigned %{{.*}} = %{{.*}} to %[[SPLIT:.*]] step
+// CHECK: %[[COND:.*]] = arith.cmpi ult, %[[SPLIT]], %[[UB]] : index
+// CHECK: scf.if %[[COND]] {
+// CHECK: func.call @use(%[[SPLIT]])
+// CHECK-NOT: else
+// CHECK: return
+
+// FRONT: func @unsigned(
+// FRONT-SAME: %[[UB:.*]]: index
+// FRONT-DAG: %[[C0:.*]] = arith.constant 0 : index
+// FRONT: %[[SPLIT:.*]] = arith.minui %[[UB]], %{{.*}} : index
+// FRONT: %[[COND:.*]] = arith.cmpi ugt, %[[SPLIT]], %[[C0]] : index
+// FRONT: scf.if %[[COND]] {
+// FRONT: func.call @use(%[[C0]])
+// FRONT: scf.for unsigned %{{.*}} = %[[SPLIT]] to %[[UB]] step
+
+func.func private @use(index)
+
+func.func @unsigned(%ub: index) {
+ %c0 = arith.constant 0 : index
+ %c4 = arith.constant 4 : index
+ scf.for unsigned %iv = %c0 to %ub step %c4 {
+ func.call @use(%iv) : (index) -> ()
+ }
+ return
+}
+
+// -----
+
+// With skip-partial (the default), the inner loop is not peeled inside the
+// scf.if of the outer partial iteration.
+
+// CHECK: func @nested(
+// CHECK: scf.for
+// CHECK: scf.for
+// CHECK: scf.if
+// CHECK: }
+// CHECK: scf.if
+// CHECK: scf.for
+// CHECK-NOT: scf.if
+// CHECK: return
+
+// NO-SKIP: func @nested(
+// NO-SKIP: scf.for
+// NO-SKIP: scf.for
+// NO-SKIP: scf.if
+// NO-SKIP: }
+// NO-SKIP: scf.if
+// NO-SKIP: scf.for
+// NO-SKIP: scf.if
+// NO-SKIP: }
+// NO-SKIP: return
+
+func.func private @use(index, index)
+
+func.func @nested(%ub0: index, %ub1: index) {
+ %c0 = arith.constant 0 : index
+ %c4 = arith.constant 4 : index
+ %c8 = arith.constant 8 : index
+ scf.for %i = %c0 to %ub0 step %c4 {
+ scf.for %j = %c0 to %ub1 step %c8 {
+ func.call @use(%i, %j) : (index, index) -> ()
+ }
+ }
+ return
+}
diff --git a/mlir/test/Dialect/SCF/transform-ops.mlir b/mlir/test/Dialect/SCF/transform-ops.mlir
index 96078c09c1eebe..7c53201a8f2e34 100644
--- a/mlir/test/Dialect/SCF/transform-ops.mlir
+++ b/mlir/test/Dialect/SCF/transform-ops.mlir
@@ -105,6 +105,68 @@ module attributes {transform.with_named_sequence} {
// -----
+// CHECK-LABEL: @loop_peel_partial_as_if_op
+// CHECK-SAME: %[[UB:.+]]: index
+func.func @loop_peel_partial_as_if_op(%ub: index) {
+ // CHECK: %[[C0:.+]] = arith.constant 0
+ // CHECK: %[[C5:.+]] = arith.constant 5
+ // CHECK: scf.for %{{.+}} = %[[C0]] to %[[SPLIT:.+]] step %[[C5]]
+ // CHECK: arith.addi
+ // CHECK: %[[COND:.+]] = arith.cmpi slt, %[[SPLIT]], %[[UB]]
+ // CHECK: scf.if %[[COND]]
+ // CHECK: arith.addi %[[SPLIT]], %[[SPLIT]]
+ %0 = arith.constant 0 : index
+ %1 = arith.constant 5 : index
+ // expected-remark @below {{main loop}}
+ // expected-remark @below {{remainder if}}
+ scf.for %i = %0 to %ub step %1 {
+ arith.addi %i, %i : index
+ }
+ return
+}
+
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["arith.addi"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1 = transform.get_parent_op %0 <op_name = "scf.for"> : (!transform.any_op) -> !transform.op<"scf.for">
+ %main_loop, %remainder = transform.loop.peel %1 partial_as_if = true : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.if">)
+ transform.debug.emit_remark_at %main_loop, "main loop" : !transform.op<"scf.for">
+ transform.debug.emit_remark_at %remainder, "remainder if" : !transform.op<"scf.if">
+ transform.yield
+ }
+}
+
+// -----
+
+// CHECK-LABEL: @loop_peel_first_iter_partial_as_if_op
+// CHECK-SAME: %[[UB:.+]]: index
+func.func @loop_peel_first_iter_partial_as_if_op(%ub: index) {
+ // CHECK: %[[C0:.+]] = arith.constant 0
+ // CHECK: %[[SPLIT:.+]] = arith.maxsi
+ // CHECK: %[[COND:.+]] = arith.cmpi slt, %[[C0]], %[[SPLIT]]
+ // CHECK: scf.if %[[COND]]
+ // CHECK: arith.addi %[[C0]], %[[C0]]
+ // CHECK: scf.for %{{.+}} = %[[SPLIT]] to %[[UB]]
+ // CHECK: arith.addi
+ %0 = arith.constant 0 : index
+ %1 = arith.constant 5 : index
+ scf.for %i = %0 to %ub step %1 {
+ arith.addi %i, %i : index
+ }
+ return
+}
+
+module attributes {transform.with_named_sequence} {
+ transform.named_sequence @__transform_main(%arg1: !transform.any_op {transform.readonly}) {
+ %0 = transform.structured.match ops{["arith.addi"]} in %arg1 : (!transform.any_op) -> !transform.any_op
+ %1 = transform.get_parent_op %0 <op_name = "scf.for"> : (!transform.any_op) -> !transform.op<"scf.for">
+ %main_loop, %first = transform.loop.peel %1 peel_front = true partial_as_if = true : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.if">)
+ transform.yield
+ }
+}
+
+// -----
+
func.func @loop_pipeline_op(%A: memref<?xf32>, %result: memref<?xf32>) {
%c0 = arith.constant 0 : index
%c1 = arith.constant 1 : index
More information about the Mlir-commits
mailing list