-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[MLIR][LLVM] Fix inlining of a single block ending with unreachable #122646
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
@llvm/pr-subscribers-mlir-core @llvm/pr-subscribers-mlir Author: William Moses (wsmoses) Changesalternate option to #122615 Full diff: https://github.com/llvm/llvm-project/pull/122646.diff 5 Files Affected:
diff --git a/mlir/include/mlir/Transforms/InliningUtils.h b/mlir/include/mlir/Transforms/InliningUtils.h
index 88fc033a6ab7be..24954325237adc 100644
--- a/mlir/include/mlir/Transforms/InliningUtils.h
+++ b/mlir/include/mlir/Transforms/InliningUtils.h
@@ -176,6 +176,15 @@ class DialectInlinerInterface
/// is invoked before inlined terminator operations have been processed.
virtual void processInlinedCallBlocks(
Operation *call, iterator_range<Region::iterator> inlinedBlocks) const {}
+
+ /// Process a set of blocks that have been inlined. This callback is invoked
+ /// *before* inlined terminator operations have been processed. Returns true
+ /// if the inliner can assume a fast path of not creating a new block, if
+ /// there is only one block.
+ virtual bool
+ processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) const {
+ return true;
+ }
};
/// This interface provides the hooks into the inlining interface.
@@ -186,11 +195,6 @@ class InlinerInterface
public:
using Base::Base;
- /// Process a set of blocks that have been inlined. This callback is invoked
- /// *before* inlined terminator operations have been processed.
- virtual void
- processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) {}
-
/// These hooks mirror the hooks for the DialectInlinerInterface, with default
/// implementations that call the hook on the handler for the dialect 'op' is
/// registered to.
@@ -211,6 +215,9 @@ class InlinerInterface
// Transformation Hooks
//===--------------------------------------------------------------------===//
+ virtual bool
+ processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks);
+
virtual void handleTerminator(Operation *op, Block *newDest) const;
virtual void handleTerminator(Operation *op, ValueRange valuesToRepl) const;
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
index b3bed5ab5f412f..1635809ce160c8 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
@@ -743,11 +743,19 @@ struct LLVMInlinerInterface : public DialectInlinerInterface {
op->erase();
}
+ bool processInlinedBlocks(
+ iterator_range<Region::iterator> inlinedBlocks) const final {
+ if (!inlinedBlocks.empty() &&
+ isa<LLVM::UnreachableOp>(inlinedBlocks.begin()->getTerminator()))
+ return false;
+ return true;
+ }
+
/// Handle the given inlined return by replacing the uses of the call with the
/// operands of the return. This overload is called when the inlined region
/// only contains one block.
void handleTerminator(Operation *op, ValueRange valuesToRepl) const final {
- // Return will be the only terminator present.
+ // Otherwise return will be the only terminator present.
auto returnOp = cast<LLVM::ReturnOp>(op);
// Replace the values directly with the return operands.
diff --git a/mlir/lib/Transforms/Utils/Inliner.cpp b/mlir/lib/Transforms/Utils/Inliner.cpp
index 8acfc96d2b611b..a153ab1182e9ae 100644
--- a/mlir/lib/Transforms/Utils/Inliner.cpp
+++ b/mlir/lib/Transforms/Utils/Inliner.cpp
@@ -382,7 +382,7 @@ struct InlinerInterfaceImpl : public InlinerInterface {
/// Process a set of blocks that have been inlined. This callback is invoked
/// *before* inlined terminator operations have been processed.
- void
+ bool
processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) final {
// Find the closest callgraph node from the first block.
CallGraphNode *node;
@@ -394,6 +394,7 @@ struct InlinerInterfaceImpl : public InlinerInterface {
collectCallOps(inlinedBlocks, node, cg, symbolTable, calls,
/*traverseNestedCGNodes=*/true);
+ return InlinerInterface::processInlinedBlocks(inlinedBlocks);
}
/// Mark the given callgraph node for deletion.
diff --git a/mlir/lib/Transforms/Utils/InliningUtils.cpp b/mlir/lib/Transforms/Utils/InliningUtils.cpp
index 0db097d14cd3c7..6c3e51e7770387 100644
--- a/mlir/lib/Transforms/Utils/InliningUtils.cpp
+++ b/mlir/lib/Transforms/Utils/InliningUtils.cpp
@@ -118,6 +118,21 @@ void InlinerInterface::handleTerminator(Operation *op,
handler->handleTerminator(op, valuesToRepl);
}
+/// Process a set of blocks that have been inlined. This callback is invoked
+/// *before* inlined terminator operations have been processed. Returns true
+/// if the inliner can assume a fast path of not creating a new block, if
+/// there is only one block.
+bool InlinerInterface::processInlinedBlocks(
+ iterator_range<Region::iterator> inlinedBlocks) {
+ if (inlinedBlocks.empty()) {
+ return true;
+ } else {
+ auto *handler = getInterfaceFor(inlinedBlocks.begin()->getParentOp());
+ assert(handler && "expected valid dialect handler");
+ return handler->processInlinedBlocks(inlinedBlocks);
+ }
+}
+
Value InlinerInterface::handleArgument(OpBuilder &builder, Operation *call,
Operation *callable, Value argument,
DictionaryAttr argumentAttrs) const {
@@ -292,10 +307,10 @@ inlineRegionImpl(InlinerInterface &interface, Region *src, Block *inlineBlock,
// Process the newly inlined blocks.
if (call)
interface.processInlinedCallBlocks(call, newBlocks);
- interface.processInlinedBlocks(newBlocks);
+ bool singleBlockFastPath = interface.processInlinedBlocks(newBlocks);
// Handle the case where only a single block was inlined.
- if (std::next(newBlocks.begin()) == newBlocks.end()) {
+ if (singleBlockFastPath && std::next(newBlocks.begin()) == newBlocks.end()) {
// Run the result attribute handler on the terminator operands.
Operation *firstBlockTerminator = firstNewBlock->getTerminator();
builder.setInsertionPoint(firstBlockTerminator);
diff --git a/mlir/test/Dialect/LLVMIR/inlining.mlir b/mlir/test/Dialect/LLVMIR/inlining.mlir
index edaac4da0b044c..eb249a47717534 100644
--- a/mlir/test/Dialect/LLVMIR/inlining.mlir
+++ b/mlir/test/Dialect/LLVMIR/inlining.mlir
@@ -676,3 +676,19 @@ llvm.func @caller(%x : i32) -> i32 {
%z = llvm.call @private_func(%x) : (i32) -> (i32)
llvm.return %z : i32
}
+
+// -----
+
+llvm.func @unreachable_func(%a : i32) -> i32 {
+ "llvm.intr.trap"() : () -> ()
+ llvm.unreachable
+}
+
+// CHECK-LABEL: func @caller
+llvm.func @caller(%x : i32) -> i32 {
+ // CHECK-NOT: llvm.call @unreachable_func
+ // CHECK: llvm.intr.trap
+ // CHECK: llvm.unreachable
+ %z = llvm.call @unreachable_func(%x) : (i32) -> (i32)
+ llvm.return %z : i32
+}
|
@llvm/pr-subscribers-mlir-llvm Author: William Moses (wsmoses) Changesalternate option to #122615 Full diff: https://github.com/llvm/llvm-project/pull/122646.diff 5 Files Affected:
diff --git a/mlir/include/mlir/Transforms/InliningUtils.h b/mlir/include/mlir/Transforms/InliningUtils.h
index 88fc033a6ab7be..24954325237adc 100644
--- a/mlir/include/mlir/Transforms/InliningUtils.h
+++ b/mlir/include/mlir/Transforms/InliningUtils.h
@@ -176,6 +176,15 @@ class DialectInlinerInterface
/// is invoked before inlined terminator operations have been processed.
virtual void processInlinedCallBlocks(
Operation *call, iterator_range<Region::iterator> inlinedBlocks) const {}
+
+ /// Process a set of blocks that have been inlined. This callback is invoked
+ /// *before* inlined terminator operations have been processed. Returns true
+ /// if the inliner can assume a fast path of not creating a new block, if
+ /// there is only one block.
+ virtual bool
+ processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) const {
+ return true;
+ }
};
/// This interface provides the hooks into the inlining interface.
@@ -186,11 +195,6 @@ class InlinerInterface
public:
using Base::Base;
- /// Process a set of blocks that have been inlined. This callback is invoked
- /// *before* inlined terminator operations have been processed.
- virtual void
- processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) {}
-
/// These hooks mirror the hooks for the DialectInlinerInterface, with default
/// implementations that call the hook on the handler for the dialect 'op' is
/// registered to.
@@ -211,6 +215,9 @@ class InlinerInterface
// Transformation Hooks
//===--------------------------------------------------------------------===//
+ virtual bool
+ processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks);
+
virtual void handleTerminator(Operation *op, Block *newDest) const;
virtual void handleTerminator(Operation *op, ValueRange valuesToRepl) const;
diff --git a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
index b3bed5ab5f412f..1635809ce160c8 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.cpp
@@ -743,11 +743,19 @@ struct LLVMInlinerInterface : public DialectInlinerInterface {
op->erase();
}
+ bool processInlinedBlocks(
+ iterator_range<Region::iterator> inlinedBlocks) const final {
+ if (!inlinedBlocks.empty() &&
+ isa<LLVM::UnreachableOp>(inlinedBlocks.begin()->getTerminator()))
+ return false;
+ return true;
+ }
+
/// Handle the given inlined return by replacing the uses of the call with the
/// operands of the return. This overload is called when the inlined region
/// only contains one block.
void handleTerminator(Operation *op, ValueRange valuesToRepl) const final {
- // Return will be the only terminator present.
+ // Otherwise return will be the only terminator present.
auto returnOp = cast<LLVM::ReturnOp>(op);
// Replace the values directly with the return operands.
diff --git a/mlir/lib/Transforms/Utils/Inliner.cpp b/mlir/lib/Transforms/Utils/Inliner.cpp
index 8acfc96d2b611b..a153ab1182e9ae 100644
--- a/mlir/lib/Transforms/Utils/Inliner.cpp
+++ b/mlir/lib/Transforms/Utils/Inliner.cpp
@@ -382,7 +382,7 @@ struct InlinerInterfaceImpl : public InlinerInterface {
/// Process a set of blocks that have been inlined. This callback is invoked
/// *before* inlined terminator operations have been processed.
- void
+ bool
processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) final {
// Find the closest callgraph node from the first block.
CallGraphNode *node;
@@ -394,6 +394,7 @@ struct InlinerInterfaceImpl : public InlinerInterface {
collectCallOps(inlinedBlocks, node, cg, symbolTable, calls,
/*traverseNestedCGNodes=*/true);
+ return InlinerInterface::processInlinedBlocks(inlinedBlocks);
}
/// Mark the given callgraph node for deletion.
diff --git a/mlir/lib/Transforms/Utils/InliningUtils.cpp b/mlir/lib/Transforms/Utils/InliningUtils.cpp
index 0db097d14cd3c7..6c3e51e7770387 100644
--- a/mlir/lib/Transforms/Utils/InliningUtils.cpp
+++ b/mlir/lib/Transforms/Utils/InliningUtils.cpp
@@ -118,6 +118,21 @@ void InlinerInterface::handleTerminator(Operation *op,
handler->handleTerminator(op, valuesToRepl);
}
+/// Process a set of blocks that have been inlined. This callback is invoked
+/// *before* inlined terminator operations have been processed. Returns true
+/// if the inliner can assume a fast path of not creating a new block, if
+/// there is only one block.
+bool InlinerInterface::processInlinedBlocks(
+ iterator_range<Region::iterator> inlinedBlocks) {
+ if (inlinedBlocks.empty()) {
+ return true;
+ } else {
+ auto *handler = getInterfaceFor(inlinedBlocks.begin()->getParentOp());
+ assert(handler && "expected valid dialect handler");
+ return handler->processInlinedBlocks(inlinedBlocks);
+ }
+}
+
Value InlinerInterface::handleArgument(OpBuilder &builder, Operation *call,
Operation *callable, Value argument,
DictionaryAttr argumentAttrs) const {
@@ -292,10 +307,10 @@ inlineRegionImpl(InlinerInterface &interface, Region *src, Block *inlineBlock,
// Process the newly inlined blocks.
if (call)
interface.processInlinedCallBlocks(call, newBlocks);
- interface.processInlinedBlocks(newBlocks);
+ bool singleBlockFastPath = interface.processInlinedBlocks(newBlocks);
// Handle the case where only a single block was inlined.
- if (std::next(newBlocks.begin()) == newBlocks.end()) {
+ if (singleBlockFastPath && std::next(newBlocks.begin()) == newBlocks.end()) {
// Run the result attribute handler on the terminator operands.
Operation *firstBlockTerminator = firstNewBlock->getTerminator();
builder.setInsertionPoint(firstBlockTerminator);
diff --git a/mlir/test/Dialect/LLVMIR/inlining.mlir b/mlir/test/Dialect/LLVMIR/inlining.mlir
index edaac4da0b044c..eb249a47717534 100644
--- a/mlir/test/Dialect/LLVMIR/inlining.mlir
+++ b/mlir/test/Dialect/LLVMIR/inlining.mlir
@@ -676,3 +676,19 @@ llvm.func @caller(%x : i32) -> i32 {
%z = llvm.call @private_func(%x) : (i32) -> (i32)
llvm.return %z : i32
}
+
+// -----
+
+llvm.func @unreachable_func(%a : i32) -> i32 {
+ "llvm.intr.trap"() : () -> ()
+ llvm.unreachable
+}
+
+// CHECK-LABEL: func @caller
+llvm.func @caller(%x : i32) -> i32 {
+ // CHECK-NOT: llvm.call @unreachable_func
+ // CHECK: llvm.intr.trap
+ // CHECK: llvm.unreachable
+ %z = llvm.call @unreachable_func(%x) : (i32) -> (i32)
+ llvm.return %z : i32
+}
|
I believe I prefer this solution over the other PR (especially with regards to the generated code). Should we add a separate function to the inlining interface called Personally, my preferred solution would be to remove the inlining "fast path" entirely and rely on region simplify to cleanup the control flow. I am not sure how easy it is to find consensus for such a change though. @ftynse do you have an opinion here? |
Removing the fast past is reasonable, but I feel that should be a follow up PR and/or RFC given the wider implication of the change. I'm happy to change this to a dedicated fn |
@gysit okay pushed a version with the dedicated function, if you can give it another look |
I'm onboard with the current proposition ( |
Just coming out of OOO, sorry for not participating on the other review. In general one thing you need to keep in mind is the fact that some dialects only have single blocks (i.e. no branching control flow), in those cases you can't have multiple blocks in any scenario. These kinds of handling should always be left to the dialect. I'm fine with this PR as-is, though for dialects with single blocks and unreachable something else will likely need to happen. |
alternate option to #122615