summaryrefslogtreecommitdiff
path: root/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
diff options
context:
space:
mode:
authorMatthias Springer <me@m-sp.org>2024-11-05 10:18:35 +0900
committerGitHub <noreply@github.com>2024-11-05 10:18:35 +0900
commitc271ba7f7961f7c2ba911853113e67ed6237a77b (patch)
tree133e77e8f7fce6285986f8d80c053637b5879904 /mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
parent910a73f3aa56388028d87e319862580daaf03152 (diff)
[mlir][bufferization] Add support for recursive function calls (#114003)
This commit adds support for recursive function calls to One-Shot Bufferize. The analysis does not support recursive function calls. The function body itself can be analyzed, but we cannot make any assumptions about the aliasing relation between function result and function arguments. Similarly, when looking at a `call` op, we do not know whether the operands will bufferize to a memory read/write. In the absence of such information, we have to conservatively assume that they do. This commit is in preparation of removing the deprecated `func-bufferize` pass. That pass can bufferize recursive functions.
Diffstat (limited to 'mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp')
-rw-r--r--mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp77
1 files changed, 62 insertions, 15 deletions
diff --git a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
index 0a4072605c26..a492bcdd0f3e 100644
--- a/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
+++ b/mlir/lib/Dialect/Bufferization/Transforms/OneShotModuleBufferize.cpp
@@ -285,14 +285,17 @@ static bool hasTensorSignature(func::FuncOp funcOp) {
}
/// Store all functions of the `moduleOp` in `orderedFuncOps`, sorted by
-/// callee-caller order (i.e. callees without callers first).
+/// callee-caller order (i.e., callees without callers first). Store all
+/// remaining functions (i.e., the ones that call each other recursively) in
+/// `remainingFuncOps`.
+///
/// Store the map of FuncOp to all its callers in `callerMap`.
-/// Return `failure()` if a cycle of calls is detected or if we are unable to
-/// retrieve the called FuncOp from any func::CallOp.
-static LogicalResult
-getFuncOpsOrderedByCalls(ModuleOp moduleOp,
- SmallVectorImpl<func::FuncOp> &orderedFuncOps,
- FuncCallerMap &callerMap) {
+///
+/// Return `failure()` if we are unable to retrieve the called FuncOp from
+/// any func::CallOp.
+static LogicalResult getFuncOpsOrderedByCalls(
+ ModuleOp moduleOp, SmallVectorImpl<func::FuncOp> &orderedFuncOps,
+ SmallVectorImpl<func::FuncOp> &remainingFuncOps, FuncCallerMap &callerMap) {
// For each FuncOp, the set of functions called by it (i.e. the union of
// symbols of all nested func::CallOp).
DenseMap<func::FuncOp, DenseSet<func::FuncOp>> calledBy;
@@ -326,19 +329,25 @@ getFuncOpsOrderedByCalls(ModuleOp moduleOp,
});
if (res.wasInterrupted())
return failure();
+
// Iteratively remove function operations that do not call any of the
- // functions remaining in the callCounter map and add them to the worklist.
+ // functions remaining in the callCounter map and add them to ordered list.
while (!numberCallOpsContainedInFuncOp.empty()) {
auto it = llvm::find_if(numberCallOpsContainedInFuncOp,
[](auto entry) { return entry.getSecond() == 0; });
if (it == numberCallOpsContainedInFuncOp.end())
- return moduleOp.emitOpError(
- "expected callgraph to be free of circular dependencies.");
+ break;
orderedFuncOps.push_back(it->getFirst());
for (auto callee : calledBy[it->getFirst()])
numberCallOpsContainedInFuncOp[callee]--;
numberCallOpsContainedInFuncOp.erase(it);
}
+
+ // Put all other functions in the list of remaining functions. These are
+ // functions that call each other circularly.
+ for (auto it : numberCallOpsContainedInFuncOp)
+ remainingFuncOps.push_back(it.first);
+
return success();
}
@@ -378,16 +387,23 @@ mlir::bufferization::analyzeModuleOp(ModuleOp moduleOp,
"expected that function boundary bufferization is activated");
FuncAnalysisState &funcState = getOrCreateFuncAnalysisState(state);
- // A list of functions in the order in which they are analyzed + bufferized.
+ // A list of non-circular functions in the order in which they are analyzed
+ // and bufferized.
SmallVector<func::FuncOp> orderedFuncOps;
+ // A list of all other functions. I.e., functions that call each other
+ // recursively. For these, we analyze the function body but not the function
+ // boundary.
+ SmallVector<func::FuncOp> remainingFuncOps;
// A mapping of FuncOps to their callers.
FuncCallerMap callerMap;
- if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps, callerMap)))
+ if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps,
+ remainingFuncOps, callerMap)))
return failure();
- // Analyze ops.
+ // Analyze functions in order. Starting with functions that are not calling
+ // any other functions.
for (func::FuncOp funcOp : orderedFuncOps) {
if (!state.getOptions().isOpAllowed(funcOp))
continue;
@@ -411,6 +427,25 @@ mlir::bufferization::analyzeModuleOp(ModuleOp moduleOp,
funcState.analyzedFuncOps[funcOp] = FuncOpAnalysisState::Analyzed;
}
+ // Analyze all other functions. All function boundary analyses are skipped.
+ for (func::FuncOp funcOp : remainingFuncOps) {
+ if (!state.getOptions().isOpAllowed(funcOp))
+ continue;
+
+ // Gather equivalence info for CallOps.
+ equivalenceAnalysis(funcOp, state, funcState);
+
+ // Analyze funcOp.
+ if (failed(analyzeOp(funcOp, state, statistics)))
+ return failure();
+
+ // TODO: We currently skip all function argument analyses for functions
+ // that call each other circularly. These analyses do not support recursive
+ // calls yet. The `BufferizableOpInterface` implementations of `func`
+ // dialect ops return conservative results in the absence of analysis
+ // information.
+ }
+
return success();
}
@@ -429,14 +464,26 @@ LogicalResult mlir::bufferization::bufferizeModuleOp(
"expected that function boundary bufferization is activated");
IRRewriter rewriter(moduleOp.getContext());
- // A list of functions in the order in which they are analyzed + bufferized.
+ // A list of non-circular functions in the order in which they are analyzed
+ // and bufferized.
SmallVector<func::FuncOp> orderedFuncOps;
+ // A list of all other functions. I.e., functions that call each other
+ // recursively. For these, we analyze the function body but not the function
+ // boundary.
+ SmallVector<func::FuncOp> remainingFuncOps;
// A mapping of FuncOps to their callers.
FuncCallerMap callerMap;
- if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps, callerMap)))
+ // Try to bufferize functions in calling order. I.e., first bufferize
+ // functions that do not call other functions. This allows us to infer
+ // accurate buffer types for function return values. Functions that call
+ // each other recursively are bufferized in an unspecified order at the end.
+ // We may use unnecessarily "complex" (in terms of layout map) buffer types.
+ if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps,
+ remainingFuncOps, callerMap)))
return failure();
+ llvm::append_range(orderedFuncOps, remainingFuncOps);
// Bufferize functions.
for (func::FuncOp funcOp : orderedFuncOps) {