Skip to content

[MLIR][SCF] Fix LoopPeelOp documentation (NFC) #113179

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 29, 2024
Merged

Conversation

nujaa
Copy link
Contributor

@nujaa nujaa commented Oct 21, 2024

There is a mistake in the SCF documentation explaining the order of results of PeelOp in the case where peel_front is true.
As an example, I added annotations to the peel_front unit test.

func.func @loop_peel_first_iter_op() {
  // CHECK: %[[C0:.+]] = arith.constant 0
  // CHECK: %[[C41:.+]] = arith.constant 41
  // CHECK: %[[C5:.+]] = arith.constant 5
  // CHECK: %[[C5_0:.+]] = arith.constant 5
  // CHECK: scf.for %{{.+}} = %[[C0]] to %[[C5_0]] step %[[C5]]
  // CHECK:   arith.addi
  // CHECK: scf.for %{{.+}} = %[[C5_0]] to %[[C41]] step %[[C5]]
  // CHECK:   arith.addi
  %0 = arith.constant 0 : index
  %1 = arith.constant 41 : index
  %2 = arith.constant 5 : index
  scf.for %i = %0 to %1 step %2 {
    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 {peel_front = true} : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.for">)
    transform.annotate %main_loop "main_loop" : !transform.op<"scf.for">
    transform.annotate %remainder "remainder" : !transform.op<"scf.for">
    transform.yield
  }
}

Gives :

  func.func @loop_peel_first_iter_op() {
    %c0 = arith.constant 0 : index
    %c41 = arith.constant 41 : index
    %c5 = arith.constant 5 : index
    %c5_0 = arith.constant 5 : index
    scf.for %arg0 = %c0 to %c5_0 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {remainder}  // The first iteration loop (second result) has been annotated remainder
    scf.for %arg0 = %c5_0 to %c41 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {main_loop} // The main loop (first result) has been annotated main_loop
    return
  }

@nujaa nujaa marked this pull request as ready for review October 22, 2024 14:41
@nujaa nujaa requested review from yzhang93 and removed request for ftynse and nicolasvasilache October 22, 2024 14:41
@llvmbot
Copy link
Member

llvmbot commented Oct 22, 2024

@llvm/pr-subscribers-mlir

Author: Hugo Trachino (nujaa)

Changes

There is a mistake in the SCF documentation explaining the order of results of PeelOp in the case where peel_front is true.
As an example, I added annotations to the peel_front unit test.

func.func @<!-- -->loop_peel_first_iter_op() {
  // CHECK: %[[C0:.+]] = arith.constant 0
  // CHECK: %[[C41:.+]] = arith.constant 41
  // CHECK: %[[C5:.+]] = arith.constant 5
  // CHECK: %[[C5_0:.+]] = arith.constant 5
  // CHECK: scf.for %{{.+}} = %[[C0]] to %[[C5_0]] step %[[C5]]
  // CHECK:   arith.addi
  // CHECK: scf.for %{{.+}} = %[[C5_0]] to %[[C41]] step %[[C5]]
  // CHECK:   arith.addi
  %0 = arith.constant 0 : index
  %1 = arith.constant 41 : index
  %2 = arith.constant 5 : index
  scf.for %i = %0 to %1 step %2 {
    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) -&gt; !transform.any_op
    %1 = transform.get_parent_op %0 {op_name = "scf.for"} : (!transform.any_op) -&gt; !transform.op&lt;"scf.for"&gt;
    %main_loop, %remainder = transform.loop.peel %1 {peel_front = true} : (!transform.op&lt;"scf.for"&gt;) -&gt; (!transform.op&lt;"scf.for"&gt;, !transform.op&lt;"scf.for"&gt;)
    transform.annotate %main_loop "main_loop" : !transform.op&lt;"scf.for"&gt;
    transform.annotate %remainder "remainder" : !transform.op&lt;"scf.for"&gt;
    transform.yield
  }
}

Gives :

  func.func @<!-- -->loop_peel_first_iter_op() {
    %c0 = arith.constant 0 : index
    %c41 = arith.constant 41 : index
    %c5 = arith.constant 5 : index
    %c5_0 = arith.constant 5 : index
    scf.for %arg0 = %c0 to %c5_0 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {remainder}  // The first iteration loop (second result) has been annotated remainder
    scf.for %arg0 = %c5_0 to %c41 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {main_loop} // The main loop (first result) has been annotated main_loop
    return
  }

Full diff: https://github.com/llvm/llvm-project/pull/113179.diff

2 Files Affected:

  • (modified) mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td (+6-6)
  • (modified) mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp (+5-6)
diff --git a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
index 20880d94a83cac..d13c1ba9a8ed1e 100644
--- a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
+++ b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
@@ -146,7 +146,7 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
   let summary = "Peels the first or last iteration of the loop";
   let description = [{
      Rewrite the given loop with a main loop and a partial (first or last) loop.
-     When the `peelFront` option is set as true, the first iteration is peeled off.
+     When the `peelFront` option is set to true, the first iteration is peeled off.
      Otherwise, updates the given loop so that its step evenly divides its range and puts
      the remaining iteration into a separate loop or a conditional.
 
@@ -158,12 +158,12 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
      This operation ignores non-scf::ForOp ops and drops them in the return.
 
      When `peelFront` is true, this operation returns two scf::ForOp Ops, the
-     first scf::ForOp corresponds to the first iteration of the loop which can
-     be canonicalized away in the following optimization. The second loop Op
-     contains the remaining iteration, and the new lower bound is the original
-     lower bound plus the number of steps.
+     first scf::ForOp corresponds to the target loop, whose lower bound has
+     been updated to the original lower bound plus the step. The second result
+     is the first iteration of the loop which can be canonicalized away in the
+     following optimization.
 
-     When `peelFront` is not true, this operation returns two scf::ForOp Ops, with the first
+     When `peelFront` is false, this operation returns two scf::ForOp Ops, with the first
      scf::ForOp satisfying: "the loop trip count is divisible by the step".
      The second loop Op contains the remaining iteration. Note that even though the
      Payload IR modification may be performed in-place, this operation consumes
diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
index a30e349d49136c..c06a448c3108c3 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
@@ -206,12 +206,11 @@ LogicalResult mlir::scf::peelForLoopAndSimplifyBounds(RewriterBase &rewriter,
   return success();
 }
 
-/// When the `peelFront` option is set as true, the first iteration of the loop
-/// is peeled off. This function rewrites the original scf::ForOp as two
-/// scf::ForOp Ops, the first scf::ForOp corresponds to the first iteration of
-/// the loop which can be canonicalized away in the following optimization. The
-/// second loop Op contains the remaining iteration, and the new lower bound is
-/// the original lower bound plus the number of steps.
+/// Rewrites the original scf::ForOp as two scf::ForOp Ops, the first scf::ForOp
+/// corresponds to the first iteration of the loop which can be canonicalized
+/// away in the following optimization. The second loop Op contains the
+/// remaining iteration, and the new lower bound is the original lower bound
+/// plus the number of steps.
 LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
                                                    ForOp &firstIteration) {
   RewriterBase::InsertionGuard guard(b);

@llvmbot
Copy link
Member

llvmbot commented Oct 22, 2024

@llvm/pr-subscribers-mlir-scf

Author: Hugo Trachino (nujaa)

Changes

There is a mistake in the SCF documentation explaining the order of results of PeelOp in the case where peel_front is true.
As an example, I added annotations to the peel_front unit test.

func.func @<!-- -->loop_peel_first_iter_op() {
  // CHECK: %[[C0:.+]] = arith.constant 0
  // CHECK: %[[C41:.+]] = arith.constant 41
  // CHECK: %[[C5:.+]] = arith.constant 5
  // CHECK: %[[C5_0:.+]] = arith.constant 5
  // CHECK: scf.for %{{.+}} = %[[C0]] to %[[C5_0]] step %[[C5]]
  // CHECK:   arith.addi
  // CHECK: scf.for %{{.+}} = %[[C5_0]] to %[[C41]] step %[[C5]]
  // CHECK:   arith.addi
  %0 = arith.constant 0 : index
  %1 = arith.constant 41 : index
  %2 = arith.constant 5 : index
  scf.for %i = %0 to %1 step %2 {
    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) -&gt; !transform.any_op
    %1 = transform.get_parent_op %0 {op_name = "scf.for"} : (!transform.any_op) -&gt; !transform.op&lt;"scf.for"&gt;
    %main_loop, %remainder = transform.loop.peel %1 {peel_front = true} : (!transform.op&lt;"scf.for"&gt;) -&gt; (!transform.op&lt;"scf.for"&gt;, !transform.op&lt;"scf.for"&gt;)
    transform.annotate %main_loop "main_loop" : !transform.op&lt;"scf.for"&gt;
    transform.annotate %remainder "remainder" : !transform.op&lt;"scf.for"&gt;
    transform.yield
  }
}

Gives :

  func.func @<!-- -->loop_peel_first_iter_op() {
    %c0 = arith.constant 0 : index
    %c41 = arith.constant 41 : index
    %c5 = arith.constant 5 : index
    %c5_0 = arith.constant 5 : index
    scf.for %arg0 = %c0 to %c5_0 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {remainder}  // The first iteration loop (second result) has been annotated remainder
    scf.for %arg0 = %c5_0 to %c41 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {main_loop} // The main loop (first result) has been annotated main_loop
    return
  }

Full diff: https://github.com/llvm/llvm-project/pull/113179.diff

2 Files Affected:

  • (modified) mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td (+6-6)
  • (modified) mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp (+5-6)
diff --git a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
index 20880d94a83cac..d13c1ba9a8ed1e 100644
--- a/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
+++ b/mlir/include/mlir/Dialect/SCF/TransformOps/SCFTransformOps.td
@@ -146,7 +146,7 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
   let summary = "Peels the first or last iteration of the loop";
   let description = [{
      Rewrite the given loop with a main loop and a partial (first or last) loop.
-     When the `peelFront` option is set as true, the first iteration is peeled off.
+     When the `peelFront` option is set to true, the first iteration is peeled off.
      Otherwise, updates the given loop so that its step evenly divides its range and puts
      the remaining iteration into a separate loop or a conditional.
 
@@ -158,12 +158,12 @@ def LoopPeelOp : Op<Transform_Dialect, "loop.peel",
      This operation ignores non-scf::ForOp ops and drops them in the return.
 
      When `peelFront` is true, this operation returns two scf::ForOp Ops, the
-     first scf::ForOp corresponds to the first iteration of the loop which can
-     be canonicalized away in the following optimization. The second loop Op
-     contains the remaining iteration, and the new lower bound is the original
-     lower bound plus the number of steps.
+     first scf::ForOp corresponds to the target loop, whose lower bound has
+     been updated to the original lower bound plus the step. The second result
+     is the first iteration of the loop which can be canonicalized away in the
+     following optimization.
 
-     When `peelFront` is not true, this operation returns two scf::ForOp Ops, with the first
+     When `peelFront` is false, this operation returns two scf::ForOp Ops, with the first
      scf::ForOp satisfying: "the loop trip count is divisible by the step".
      The second loop Op contains the remaining iteration. Note that even though the
      Payload IR modification may be performed in-place, this operation consumes
diff --git a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
index a30e349d49136c..c06a448c3108c3 100644
--- a/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
+++ b/mlir/lib/Dialect/SCF/Transforms/LoopSpecialization.cpp
@@ -206,12 +206,11 @@ LogicalResult mlir::scf::peelForLoopAndSimplifyBounds(RewriterBase &rewriter,
   return success();
 }
 
-/// When the `peelFront` option is set as true, the first iteration of the loop
-/// is peeled off. This function rewrites the original scf::ForOp as two
-/// scf::ForOp Ops, the first scf::ForOp corresponds to the first iteration of
-/// the loop which can be canonicalized away in the following optimization. The
-/// second loop Op contains the remaining iteration, and the new lower bound is
-/// the original lower bound plus the number of steps.
+/// Rewrites the original scf::ForOp as two scf::ForOp Ops, the first scf::ForOp
+/// corresponds to the first iteration of the loop which can be canonicalized
+/// away in the following optimization. The second loop Op contains the
+/// remaining iteration, and the new lower bound is the original lower bound
+/// plus the number of steps.
 LogicalResult mlir::scf::peelForLoopFirstIteration(RewriterBase &b, ForOp forOp,
                                                    ForOp &firstIteration) {
   RewriterBase::InsertionGuard guard(b);

@nujaa nujaa requested review from ftynse and banach-space October 22, 2024 14:41
Comment on lines 161 to 164
first scf::ForOp corresponds to the first iteration of the loop which can
be canonicalized away in the following optimization. The second loop Op
contains the remaining iteration, and the new lower bound is the original
lower bound plus the number of steps.
first scf::ForOp corresponds to the target loop, whose lower bound has
been updated to the original lower bound plus the step. The second result
is the first iteration of the loop which can be canonicalized away in the
following optimization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, the original comment is almost correct ("remaining iteration" -> "remaining iterations" and "new lower bound is the original lower bound plus the number of steps" -> "new lower bound is the original lower bound plus the step"). What am I missing?

The newly added paragraph is not clear to me:

lower bound has been updated to the original lower bound plus the step

Are you sure? The lower bound doesn't change. In general, the lower bound of the "first" loop is equal to the lower bound of the original loop. That's one thing that peelFront won't change.

Copy link
Contributor Author

@nujaa nujaa Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I can understand my suggestion is unclear i'll try to come up with something better. What is originally wrong is the order of the results of the transformOp when peel_front is true.
The transform op returns 2 forops like :
%iteration1_to_n, %iteration_0= transform.loop.peel %1 {peel_front = true} : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.for">)
Whereas the original description suggested %iteration0, %iteration_1_to_n

New suggestion :
When peelFront is true, this operation returns two scf::ForOp Ops, the
first scf::ForOp executes all iterations of the target loop but the first one. The second result is another scf::ForOp corresponding to the first iteration of the loop which can
be canonicalized away in the following optimizations. Although not following the execution order, the results are kept in this order to keep the [mainloop, remainder] format of PeelOp general case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transform op returns 2 forops like :
%iteration1_to_n, %iteration_0= transform.loop.peel %1 {peel_front = true} : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.for">)
Whereas the original description suggested %iteration0, %iteration_1_to_n

Yeah, that's clearly not correct, thanks for the example!

When peelFront is true, this operation returns two scf::ForOp Ops

The op returns two loops regardless of peelFront. How about something like this (two avoid repeating that there are two results):

The op returns two loops, the peeled loop and the remainder loop.

When peelFront is true, the first scf::ForOp executes all iterations of the target loop but the first one (i.e. the remainder loop). The second scf::ForOp corresponds to the first iteration of the loop which can
be canonicalized away in the following optimizations (the peeled loop).

When peelFront is false, ...

So basically what you are saying, but a bit shorter and trying to clearly identify the peeled and the remainder loop. Hope I got my terminology right 😅

Also, apologies for the delay. I am travelling for LLVM Dev this week and trying to prioritise F2F interactions over GitHub. I really appreciate you improving this!

Comment on lines 159 to 171
The op returns two loops, the peeled loop whose tripcount is divisible by
the step and the remainder loop.

When `peelFront` is true, the first scf::ForOp (the remainder loop)
executes all iterations of the target loop but the first one. The second
scf::ForOp corresponds to the first iteration of the loop which can be
canonicalized away in the following optimizations (the peeled loop).

When `peelFront` is false, the first result (peeled loop) is "the loop
with the highest upper bound that is divisible by the step". The second
loopOp (remainder loop) contains the remaining iterations. Note that even
though the Payload IR modification may be performed in-place, this
operation consumes the operand handle and produces a new one.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive improvement, thanks! This is already so much clearer 🤩

I have a few small suggestions.

  • "first scf:ForOp" vs "first result" -> let's stick to one style
  • "Note that even though the Payload IR modification may be performed in-place ..." -> IIUC, this applies to both paragraphs (i.e. when peelFront is true and false?) So it should probably be a separate paragraph.
  • punctuation.
Suggested change
The op returns two loops, the peeled loop whose tripcount is divisible by
the step and the remainder loop.
When `peelFront` is true, the first scf::ForOp (the remainder loop)
executes all iterations of the target loop but the first one. The second
scf::ForOp corresponds to the first iteration of the loop which can be
canonicalized away in the following optimizations (the peeled loop).
When `peelFront` is false, the first result (peeled loop) is "the loop
with the highest upper bound that is divisible by the step". The second
loopOp (remainder loop) contains the remaining iterations. Note that even
though the Payload IR modification may be performed in-place, this
operation consumes the operand handle and produces a new one.
The op returns two loops: the peeled loop, which has trip count divisible by
the step, and the remainder loop.
When `peelFront` is true, the first result (remainder loop)
executes all but the first iteration of the target loop. The second
result (peeled loop) corresponds to the first iteration of the target loop, which can
later be canonicalized away in the following optimizations.
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.
Note that although the Payload IR modification may be done
in-place, this operation consumes the operand handle and produces
a new one.

/// Rewrites the original scf::ForOp as two scf::ForOp Ops, the first scf::ForOp
/// corresponds to the first iteration of the loop which can be canonicalized
/// away in the following optimization. The second loop Op contains the
/// remaining iteration, and the new lower bound is the original lower bound
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// remaining iteration, and the new lower bound is the original lower bound
/// remaining iterations, and the new lower bound is updated as the original lower bound

/// corresponds to the first iteration of the loop which can be canonicalized
/// away in the following optimization. The second loop Op contains the
/// remaining iteration, and the new lower bound is the original lower bound
/// plus the number of steps.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// plus the number of steps.
/// plus one (i.e. skips the first iteration).

Copy link
Contributor

@banach-space banach-space left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you for the improvements and for bearing with me 🙏🏻

@nujaa nujaa merged commit a9c417c into llvm:main Oct 29, 2024
4 of 5 checks passed
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this pull request Nov 4, 2024
As an example, I added annotations to the peel_front unit test.

```
func.func @loop_peel_first_iter_op() {
  // CHECK: %[[C0:.+]] = arith.constant 0
  // CHECK: %[[C41:.+]] = arith.constant 41
  // CHECK: %[[C5:.+]] = arith.constant 5
  // CHECK: %[[C5_0:.+]] = arith.constant 5
  // CHECK: scf.for %{{.+}} = %[[C0]] to %[[C5_0]] step %[[C5]]
  // CHECK:   arith.addi
  // CHECK: scf.for %{{.+}} = %[[C5_0]] to %[[C41]] step %[[C5]]
  // CHECK:   arith.addi
  %0 = arith.constant 0 : index
  %1 = arith.constant 41 : index
  %2 = arith.constant 5 : index
  scf.for %i = %0 to %1 step %2 {
    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 {peel_front = true} : (!transform.op<"scf.for">) -> (!transform.op<"scf.for">, !transform.op<"scf.for">)
    transform.annotate %main_loop "main_loop" : !transform.op<"scf.for">
    transform.annotate %remainder "remainder" : !transform.op<"scf.for">
    transform.yield
  }
}
```
Gives :
```
  func.func @loop_peel_first_iter_op() {
    %c0 = arith.constant 0 : index
    %c41 = arith.constant 41 : index
    %c5 = arith.constant 5 : index
    %c5_0 = arith.constant 5 : index
    scf.for %arg0 = %c0 to %c5_0 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {remainder}  // The first iteration loop (second result) has been annotated remainder
    scf.for %arg0 = %c5_0 to %c41 step %c5 {
      %0 = arith.addi %arg0, %arg0 : index
    } {main_loop} // The main loop (first result) has been annotated main_loop
    return
  }
```

---------

Co-authored-by: Andrzej Warzyński <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants