-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[mlir][inliner] Add doClone and canHandleMultipleBlocks callbacks to Inliner #131226
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
Conversation
…the Inliner These callback functions enhance the flexibility to customize inliner behavior. . doClone: clones instructions and other information from the callee function into the caller function. . canHandleMultipleBlocks: checks if functions with multiple blocks can be inlined into a region with the SingleBlock trait. The default behavior of the inliner remains unchanged.
@llvm/pr-subscribers-mlir @llvm/pr-subscribers-mlir-core Author: None (junfengd-nv) ChangesThese callback functions enhance the flexibility to customize inliner behavior. . doClone: clones instructions and other information from the callee function into the caller function. . The default behavior of the inliner remains unchanged. Full diff: https://github.com/llvm/llvm-project/pull/131226.diff 4 Files Affected:
diff --git a/mlir/include/mlir/Transforms/Inliner.h b/mlir/include/mlir/Transforms/Inliner.h
index ec77319d6ac88..86fa6ce6a0c77 100644
--- a/mlir/include/mlir/Transforms/Inliner.h
+++ b/mlir/include/mlir/Transforms/Inliner.h
@@ -90,18 +90,40 @@ class Inliner {
/// this hook's interface might need to be extended in future.
using ProfitabilityCallbackTy = std::function<bool(const ResolvedCall &)>;
+ /// Type of the callback that determines if the inliner can inline a function
+ /// containing multiple blocks into a region that requires a single block. By
+ /// default, it is not allowed.
+ /// If this function return true, the static member function doClone()
+ /// should perform the actual transformation with its support.
+ using canHandleMultipleBlocksCbTy = std::function<bool()>;
+
+ using CloneCallbackTy =
+ std::function<void(OpBuilder &builder, Region *src, Block *inlineBlock,
+ Block *postInsertBlock, IRMapping &mapper,
+ bool shouldCloneInlinedRegion)>;
+
Inliner(Operation *op, CallGraph &cg, Pass &pass, AnalysisManager am,
RunPipelineHelperTy runPipelineHelper, const InlinerConfig &config,
- ProfitabilityCallbackTy isProfitableToInline)
+ ProfitabilityCallbackTy isProfitableToInline,
+ canHandleMultipleBlocksCbTy canHandleMultipleBlocks)
: op(op), cg(cg), pass(pass), am(am),
runPipelineHelper(std::move(runPipelineHelper)), config(config),
- isProfitableToInline(std::move(isProfitableToInline)) {}
+ isProfitableToInline(std::move(isProfitableToInline)),
+ canHandleMultipleBlocks(std::move(canHandleMultipleBlocks)) {}
Inliner(Inliner &) = delete;
void operator=(const Inliner &) = delete;
/// Perform inlining on a OpTrait::SymbolTable operation.
LogicalResult doInlining();
+ /// This function provides a callback mechanism to clone the instructions and
+ /// other information from the callee function into the caller function.
+ static CloneCallbackTy &doClone();
+
+ /// Set the clone callback function.
+ /// The provided function "func" will be invoked by Inliner::doClone().
+ void setCloneCallback(CloneCallbackTy func) { doClone() = func; }
+
private:
/// An OpTrait::SymbolTable operation to run the inlining on.
Operation *op;
@@ -119,10 +141,14 @@ class Inliner {
/// Returns true, if it is profitable to inline the callable operation
/// at the call site.
ProfitabilityCallbackTy isProfitableToInline;
+ /// Return true, if functions with multiple blocks can be inlined
+ /// into a region with the SingleBlock trait.
+ canHandleMultipleBlocksCbTy canHandleMultipleBlocks;
/// Forward declaration of the class providing the actual implementation.
class Impl;
};
+
} // namespace mlir
#endif // MLIR_TRANSFORMS_INLINER_H
diff --git a/mlir/lib/Transforms/InlinerPass.cpp b/mlir/lib/Transforms/InlinerPass.cpp
index 703e517d45374..fc831a5ebf4fb 100644
--- a/mlir/lib/Transforms/InlinerPass.cpp
+++ b/mlir/lib/Transforms/InlinerPass.cpp
@@ -142,9 +142,13 @@ void InlinerPass::runOnOperation() {
return isProfitableToInline(call, inliningThreshold);
};
+ // By default, prevent inlining a functon containing multiple blocks into a
+ // region that requires a single block.
+ auto canHandleMultipleBlocksCb = [=]() { return false; };
+
// Get an instance of the inliner.
Inliner inliner(op, cg, *this, getAnalysisManager(), runPipelineHelper,
- config, profitabilityCb);
+ config, profitabilityCb, canHandleMultipleBlocksCb);
// Run the inlining.
if (failed(inliner.doInlining()))
diff --git a/mlir/lib/Transforms/Utils/Inliner.cpp b/mlir/lib/Transforms/Utils/Inliner.cpp
index 756f5e379e7dd..f34fd089a8d98 100644
--- a/mlir/lib/Transforms/Utils/Inliner.cpp
+++ b/mlir/lib/Transforms/Utils/Inliner.cpp
@@ -344,6 +344,28 @@ static void collectCallOps(iterator_range<Region::iterator> blocks,
}
}
}
+//===----------------------------------------------------------------------===//
+// Inliner
+//===----------------------------------------------------------------------===//
+// Initialize doClone function with the default implementation
+Inliner::CloneCallbackTy &Inliner::doClone() {
+ static Inliner::CloneCallbackTy doWork =
+ [](OpBuilder &builder, Region *src, Block *inlineBlock,
+ Block *postInsertBlock, IRMapping &mapper,
+ bool shouldCloneInlinedRegion) {
+ // Check to see if the region is being cloned, or moved inline. In
+ // either case, move the new blocks after the 'insertBlock' to improve
+ // IR readability.
+ Region *insertRegion = inlineBlock->getParent();
+ if (shouldCloneInlinedRegion)
+ src->cloneInto(insertRegion, postInsertBlock->getIterator(), mapper);
+ else
+ insertRegion->getBlocks().splice(postInsertBlock->getIterator(),
+ src->getBlocks(), src->begin(),
+ src->end());
+ };
+ return doWork;
+}
//===----------------------------------------------------------------------===//
// InlinerInterfaceImpl
@@ -729,19 +751,22 @@ bool Inliner::Impl::shouldInline(ResolvedCall &resolvedCall) {
// Don't allow inlining if the callee has multiple blocks (unstructured
// control flow) but we cannot be sure that the caller region supports that.
- bool calleeHasMultipleBlocks =
- llvm::hasNItemsOrMore(*callableRegion, /*N=*/2);
- // If both parent ops have the same type, it is safe to inline. Otherwise,
- // decide based on whether the op has the SingleBlock trait or not.
- // Note: This check does currently not account for SizedRegion/MaxSizedRegion.
- auto callerRegionSupportsMultipleBlocks = [&]() {
- return callableRegion->getParentOp()->getName() ==
- resolvedCall.call->getParentOp()->getName() ||
- !resolvedCall.call->getParentOp()
- ->mightHaveTrait<OpTrait::SingleBlock>();
- };
- if (calleeHasMultipleBlocks && !callerRegionSupportsMultipleBlocks())
- return false;
+ if (!inliner.canHandleMultipleBlocks()) {
+ bool calleeHasMultipleBlocks =
+ llvm::hasNItemsOrMore(*callableRegion, /*N=*/2);
+ // If both parent ops have the same type, it is safe to inline. Otherwise,
+ // decide based on whether the op has the SingleBlock trait or not.
+ // Note: This check does currently not account for
+ // SizedRegion/MaxSizedRegion.
+ auto callerRegionSupportsMultipleBlocks = [&]() {
+ return callableRegion->getParentOp()->getName() ==
+ resolvedCall.call->getParentOp()->getName() ||
+ !resolvedCall.call->getParentOp()
+ ->mightHaveTrait<OpTrait::SingleBlock>();
+ };
+ if (calleeHasMultipleBlocks && !callerRegionSupportsMultipleBlocks())
+ return false;
+ }
if (!inliner.isProfitableToInline(resolvedCall))
return false;
diff --git a/mlir/lib/Transforms/Utils/InliningUtils.cpp b/mlir/lib/Transforms/Utils/InliningUtils.cpp
index 0cae63c58ca7b..4531dd17ee3b2 100644
--- a/mlir/lib/Transforms/Utils/InliningUtils.cpp
+++ b/mlir/lib/Transforms/Utils/InliningUtils.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "mlir/Transforms/InliningUtils.h"
+#include "mlir/Transforms/Inliner.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/IRMapping.h"
@@ -275,16 +276,10 @@ inlineRegionImpl(InlinerInterface &interface, Region *src, Block *inlineBlock,
if (call && callable)
handleArgumentImpl(interface, builder, call, callable, mapper);
- // Check to see if the region is being cloned, or moved inline. In either
- // case, move the new blocks after the 'insertBlock' to improve IR
- // readability.
+ // Clone the callee's source into the caller.
Block *postInsertBlock = inlineBlock->splitBlock(inlinePoint);
- if (shouldCloneInlinedRegion)
- src->cloneInto(insertRegion, postInsertBlock->getIterator(), mapper);
- else
- insertRegion->getBlocks().splice(postInsertBlock->getIterator(),
- src->getBlocks(), src->begin(),
- src->end());
+ Inliner::doClone()(builder, src, inlineBlock, postInsertBlock, mapper,
+ shouldCloneInlinedRegion);
// Get the range of newly inserted blocks.
auto newBlocks = llvm::make_range(std::next(inlineBlock->getIterator()),
|
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.
Makes sense to me. Letting people more familiar with this code reviewing/approving.
It would be great to be able to add a test showing the motivation of this interface by having a Clone version that can generate an scf.execute_region, but I understand it is not desired to make the InlinerPass dependent on the SCF dialect for the sake of this test.
I don't quite get the motivation for these and the description isn't clear on this: can you elaborate on why are these useful / desirable? |
Co-authored-by: jeanPerier <[email protected]>
Current inliner disables inlining when the caller is in a region with single block trait, while the callee function contains multiple blocks. the SingleBlock trait is used in operations such as do/while loop, for example fir.do_loop, fir.iterate_while and fir.if (refer to https://flang.llvm.org/docs/FIRLangRef.html). Typically, calls within loops are good candidates for inlining. However, functions with multiple blocks are also common. for example, any function with "if () then return" will result in multiple blocks in MLIR.
The body of the callee will be wrapped into a scf.execute_region operation and then inlined as follows:
With the two callback functions that I added, the derived inliner can return true with function "canHandleMultipleBlocks" to avoid stopping during the inline decision process. And then add their extra transformations in the function "doClone". |
Can you please:
|
Updated
The default behavior of MLIR inliner doesn't change, so I have not changed and added any tests. Do you want an example of demonstrating the creation of a customized inliner with wrapping in scf.execute_region? or just tests showing doClone/canHandleMultipleBlocks could be overridden? could you please pointing me an existing example if it exists? Thanks |
I think that would be a good test to have! Should be doable by implementing this in the test directory right? |
@joker-eph @jeanPerier I have added a test under the test dialect to demonstrate the concept of customizing the inliner to wrap the callee into an SCF execute region. The SCF dialect has been added as a dependency solely for that test pass, and not for the default inliner pass. Could you please review the changes again? Thank you very much. |
✅ With the latest revision this PR passed the C/C++ code formatter. |
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
Co-authored-by: Mehdi Amini <[email protected]>
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.
LGTM, thanks!
@@ -253,43 +254,51 @@ class InlinerInterface | |||
/// provided, will be used to update the inlined operations' location | |||
/// information. 'shouldCloneInlinedRegion' corresponds to whether the source | |||
/// region should be cloned into the 'inlinePoint' or spliced directly. |
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.
Actually, the documentation the all the API changes should be updated, can you do it in a follow-up PR please?
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.
Yes. I will.
LLVM Buildbot has detected a new failure on builder Full details are available at: https://lab.llvm.org/buildbot/#/builders/17/builds/7060 Here is the relevant piece of the build log for the reference
|
The major changes come from the following PRs: * llvm/llvm-project#131226 * llvm/llvm-project#134264 * llvm/llvm-project#134169 * llvm/llvm-project#134517 --------- Signed-off-by: MaheshRavishankar <[email protected]> Signed-off-by: Jakub Kuderski <[email protected]> Co-authored-by: MaheshRavishankar <[email protected]>
Current inliner disables inlining when the caller is in a region with single block trait, while the callee function contains multiple blocks. the SingleBlock trait is used in operations such as do/while loop, for example fir.do_loop, fir.iterate_while and fir.if (refer to https://flang.llvm.org/docs/FIRLangRef.html). Typically, calls within loops are good candidates for inlining. However, functions with multiple blocks are also common. for example, any function with "if () then return" will result in multiple blocks in MLIR.
My change gives the flexibility of a customized inliner to handle such cases.
doClone: clones instructions and other information from the callee function into the caller function. .
canHandleMultipleBlocks: checks if functions with multiple blocks can be inlined into a region with the SingleBlock trait.
With the two callback functions that I added, the derived inliner can return true with function "canHandleMultipleBlocks" to avoid stopping during the inline decision process. And then add their extra transformations in the function "doClone".
The default behavior of the inliner remains unchanged.