-
Notifications
You must be signed in to change notification settings - Fork 10.7k
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
[mlir] Introduce wrapInZeroTripCheck in LoopLikeOpInterface #80331
Conversation
c07dce7
to
70f54b5
Compare
@llvm/pr-subscribers-mlir-scf @llvm/pr-subscribers-mlir Author: Jerry Wu (pzread) ChangesAdds a new method The purpose is to let loop ops (e.g. The implementation of Full diff: https://github.com/llvm/llvm-project/pull/80331.diff 1 Files Affected:
diff --git a/mlir/include/mlir/Interfaces/LoopLikeInterface.td b/mlir/include/mlir/Interfaces/LoopLikeInterface.td
index e2ac85a3f7725..77409cb3a8274 100644
--- a/mlir/include/mlir/Interfaces/LoopLikeInterface.td
+++ b/mlir/include/mlir/Interfaces/LoopLikeInterface.td
@@ -220,6 +220,28 @@ def LoopLikeOpInterface : OpInterface<"LoopLikeOpInterface"> {
/*defaultImplementation=*/[{
return ::mlir::failure();
}]
+ >,
+ InterfaceMethod<[{
+ Add a zero-trip-check around the loop to check if the loop body is ever
+ run and return the new loop inside the check. The loop body is moved
+ over to the new loop. Returns "failure" if the loop doesn't support
+ this transformation.
+
+ After the transformation, the ops inserted to the parent region of the
+ loop are guaranteed to be run only if the loop body runs at least one
+ iteration.
+
+ Note: Ops in the loop body might be rearranged because of loop rotating
+ to maintain the semantic. Terminators might be removed/added during this
+ transformation.
+ }],
+ /*retTy=*/"::mlir::FailureOr<::mlir::LoopLikeOpInterface>",
+ /*methodName=*/"replaceWithZeroTripCheck",
+ /*args=*/(ins "::mlir::RewriterBase &":$rewriter),
+ /*methodBody=*/"",
+ /*defaultImplementation=*/[{
+ return ::mlir::failure();
+ }]
>
];
|
|
||
Note: Ops in the loop body might be rearranged because of loop rotating | ||
to maintain the semantic. Terminators might be removed/added during this | ||
transformation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also mention that loop conditions might be checked redundantly so they are expected to not have side effects and that the utility won't check that but just apply the transformation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a few words to mention that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some tests for this?
Done. |
@@ -220,6 +220,31 @@ def LoopLikeOpInterface : OpInterface<"LoopLikeOpInterface"> { | |||
/*defaultImplementation=*/[{ | |||
return ::mlir::failure(); | |||
}] | |||
>, | |||
InterfaceMethod<[{ | |||
Add a zero-trip-check around the loop to check if the loop body is ever |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a "check" (it is not really clear what the "check" is; it could be an scf.if
, it could be a new basic block with a conditional branch, etc.), this method could also return just the condition. (Which evaluates to true
if the loop has at least one iteration.) That would keep the interface a bit simpler. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the tricky part of adding zero-trip-check is to do loop rotation when the loop condition has side-effects. So if we simply return the loop condition, callers will need to implement loop rotation by themselves for each loop op, which can be complicated.
If the kind of "check" op is a concern, maybe we can add a callback and let callers to create the check op in the callback by themselves?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to start with scf.if
and generalize later as needed?
I think it's worth clarifying that loop rotation will only be possible for scf.while
ops for now, that is, turning regular a "while-do" into a "do-while". That kind of rotation won't be possible at this level of abstraction for scf.for
, hence the comment about redundant first iteration check. I think the rotation responsibility belongs to the interface itself.
If we want to make it extra configurable, we could separate the zero-trip-check method from the rotation method...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes sense, I did not think of side effects.
The lambda sounds like a good idea, but I'm not sure what exactly it should look like. (Should it return a Block *
into which the loop is moved?) We can generalize later if needed.
I think it would also work for scf.for
. The check condition is lb < ub
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think whether the loop rotation is needed (or possible) is the implementation details of each type of loop. scf.while
needs it because the before block is in the loop and can contain ops with side-effects. scf.for
is simpler as the lb < ub
should have no side-effect and lightweight, which can be run twice (for scf.if
and the for loop)
|
||
EXPECT_TRUE(succeeded(result)); | ||
EXPECT_EQ(*result, testOp); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make it a pass: we're avoiding C++ unit-tests as much as possible.
I would implement the interface for scf.while and scf.for and have the pass just walk the IR, find all LoopLikeOpInterface
, and call replaceWithZeroTripCheck
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. PTAL. I prefer to keep this change focusing on the interface definition so I only add a scf.while
test with no transformation. The actual transformation and tests will be added in the follow-up change #80349
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kindly ping : )
@@ -220,6 +220,31 @@ def LoopLikeOpInterface : OpInterface<"LoopLikeOpInterface"> { | |||
/*defaultImplementation=*/[{ | |||
return ::mlir::failure(); | |||
}] | |||
>, | |||
InterfaceMethod<[{ | |||
Add a zero-trip-check around the loop to check if the loop body is ever |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to start with scf.if
and generalize later as needed?
I think it's worth clarifying that loop rotation will only be possible for scf.while
ops for now, that is, turning regular a "while-do" into a "do-while". That kind of rotation won't be possible at this level of abstraction for scf.for
, hence the comment about redundant first iteration check. I think the rotation responsibility belongs to the interface itself.
If we want to make it extra configurable, we could separate the zero-trip-check method from the rotation method...
This reverts commit d6703ebbeb5ddc358929672b44994a9d05683523.
e244f51
to
b3cf941
Compare
mlir/test/lib/Interfaces/LoopLikeInterface/TestLoopZeroTripCheck.cpp
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Late to the party here, but I think these transformations dont belong in the interface. Looking at the LoopLikeOpInterface
it is actually doing some very heavy weight transformations that not all loop ops that implement this interface can handle. I tried to do this too in an earlier PR, and it was rightly pointed out that those kind of transformations dont go into the interface definition.
Instead of adding oto the interface definition, maybe these are just transformation methods that are written on the interface. My read of interfaces are that they are a wrapper around the core operation to have a unified way to interpret operations. Adding transformations of this kind to an interface seem odd to me.
I am not pointing out something really off in this specific PR, but it is contributing to an aspect of the interface that I wanted to flag.
My initial thought is to create a common interface on loop ops so users can combine with utilities like llvm-project/mlir/lib/Transforms/Utils/LoopInvariantCodeMotionUtils.cpp Lines 106 to 115 in 7880b2c
I also have the same question when writing this as an interface. Do you have an example where you think this transformation should live? |
IMO, I can probably write this as a transformation helper like this one: llvm-project/mlir/lib/Dialect/SCF/Transforms/TileUsingInterface.cpp Lines 990 to 993 in 8b0f47b
|
As @MaheshRavishankar suggested, create #81050 to directly add a transformation for scf while loop. |
This is what I was thinking of. Thanks for addressing. |
Adds a new method
wrapInZeroTripCheck
toLoopLikeOpInterface
, to create zero-trip-check around the loopThe purpose is to let loop ops (e.g.
scf.while
,scf.for
) implement their own transformations to add zero-trip-check. The zero-trip-check creates a guard (e.g.scf.if
) around the loop and the condition will be true only if the loop body will run at least once. An example usage is to combine withmlir::moveLoopInvariantCode
to hoist resource-intense loop invariants into that guard region so they will only run once but also not run when the loop body won't run at all.The implementation for
scf.while
can be found in the follow-up change: #80349