634 lines · cpp
1//===- OneShotModuleBufferize.cpp - Bufferization across Func. Boundaries2//----===//3//4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.5// See https://llvm.org/LICENSE.txt for license information.6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception7//8//===----------------------------------------------------------------------===//9//10// Module Bufferization is an extension of One-Shot Bufferize that11// bufferizes function boundaries. It provides `BufferizableOpInterface`12// implementations for FuncOp, CallOp and ReturnOp. Although it is named13// Module Bufferization, it may operate on any SymbolTable.14//15// Module Bufferization is run via `runOneShotModuleBufferize(SymbolTableOp,16// ...)`. This function analyzes the given op and determines the order of17// analysis and bufferization: Functions that are called are processed before18// their respective callers.19//20// After analyzing a FuncOp, additional information about its bbArgs is21// gathered and stored in `FuncAnalysisState`.22//23// * `aliasingFuncOpBBArgsAnalysis` determines the equivalent/aliasing bbArgs24// for25// each tensor return value (if any).26// * `funcOpBbArgReadWriteAnalysis` determines whether or not a tensor bbArg is27// read/written.28//29// Module Bufferization implements the following calling convention.30//31// * In the absence of conflicts within a FuncOp, the FuncOp's bbArgs may always32// be written to in-place.33// * If a tensor operand of a CallOp is read after the CallOp, the operand of34// the CallOp must bufferize out-of-place.35//36// Example: The tensor.insert op bufferizes in-place because it is allowed to37// modify the buffer of `%t1` directly. The CallOp in `caller` must bufferize38// out-of-place because `%t0` is modified by the callee but read by the39// tensor.extract op. The analysis of CallOps decides whether an OpOperand must40// bufferize out-of-place based on results of `funcOpBbArgReadWriteAnalysis`.41// ```42// func @callee(%t1 : tensor<?xf32>) -> tensor<?xf32> {43// %f = ... : f3244// %0 = tensor.insert %f into %t1[...] : tensor<?xf32>45// return %0 : tensor<?xf32>46// }47//48// func @caller() -> () {49// %t0 = ... : tensor<?xf32>50// %1 = call @callee(%t0) : (tensor<?xf32>) -> (tensor<?xf32>)51// %2 = tensor.extract %1[...] : tensor<?xf32>52// }53// ```54//55// Note: If a function is external, `funcOpBbArgReadWriteAnalysis` cannot56// analyze the function body. In such a case, the CallOp analysis conservatively57// assumes that each tensor OpOperand is both read and written.58//59// TODO: Add FuncOp attributes so that bbArgs of external FuncOps can be marked60// as "not reading" and/or "not writing".61 62#include "mlir/Dialect/Bufferization/Transforms/OneShotModuleBufferize.h"63 64#include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h"65#include "mlir/Dialect/Bufferization/IR/Bufferization.h"66#include "mlir/Dialect/Bufferization/Transforms/Bufferize.h"67#include "mlir/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.h"68#include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h"69#include "mlir/Dialect/Bufferization/Transforms/Transforms.h"70#include "mlir/Dialect/Func/IR/FuncOps.h"71#include "mlir/Dialect/MemRef/IR/MemRef.h"72#include "mlir/IR/BuiltinTypes.h"73#include "mlir/IR/Operation.h"74 75using namespace mlir;76using namespace mlir::bufferization;77using namespace mlir::bufferization::func_ext;78 79/// A mapping of FuncOps to their callers.80using FuncCallerMap = DenseMap<func::FuncOp, DenseSet<Operation *>>;81 82/// Get or create FuncAnalysisState.83static FuncAnalysisState &84getOrCreateFuncAnalysisState(OneShotAnalysisState &state) {85 auto *result = state.getExtension<FuncAnalysisState>();86 if (result)87 return *result;88 return state.addExtension<FuncAnalysisState>();89}90 91namespace {92 93/// Annotate IR with the results of the analysis. For testing purposes only.94static void annotateEquivalentReturnBbArg(OpOperand &returnVal,95 BlockArgument bbArg) {96 const char *kEquivalentArgsAttr = "__equivalent_func_args__";97 Operation *op = returnVal.getOwner();98 99 SmallVector<int64_t> equivBbArgs;100 if (op->hasAttr(kEquivalentArgsAttr)) {101 auto attr = cast<ArrayAttr>(op->getAttr(kEquivalentArgsAttr));102 equivBbArgs = llvm::to_vector<4>(llvm::map_range(attr, [](Attribute a) {103 return cast<IntegerAttr>(a).getValue().getSExtValue();104 }));105 } else {106 equivBbArgs.append(op->getNumOperands(), -1);107 }108 equivBbArgs[returnVal.getOperandNumber()] = bbArg.getArgNumber();109 110 OpBuilder b(op->getContext());111 op->setAttr(kEquivalentArgsAttr, b.getI64ArrayAttr(equivBbArgs));112}113 114/// Store function BlockArguments that are equivalent to/aliasing a returned115/// value in FuncAnalysisState.116static LogicalResult117aliasingFuncOpBBArgsAnalysis(FuncOp funcOp, OneShotAnalysisState &state,118 FuncAnalysisState &funcState) {119 if (funcOp.getBody().empty()) {120 // No function body available. Conservatively assume that every tensor121 // return value may alias with any tensor bbArg.122 FunctionType type = funcOp.getFunctionType();123 for (const auto &inputIt : llvm::enumerate(type.getInputs())) {124 if (!isa<TensorType>(inputIt.value()))125 continue;126 for (const auto &resultIt : llvm::enumerate(type.getResults())) {127 if (!isa<TensorType>(resultIt.value()))128 continue;129 int64_t returnIdx = resultIt.index();130 int64_t bbArgIdx = inputIt.index();131 funcState.aliasingReturnVals[funcOp][bbArgIdx].push_back(returnIdx);132 }133 }134 return success();135 }136 137 // Find all func.return ops.138 SmallVector<func::ReturnOp> returnOps = getReturnOps(funcOp);139 assert(!returnOps.empty() && "expected at least one ReturnOp");140 141 // Build alias sets. Merge all aliases from all func.return ops.142 for (BlockArgument bbArg : funcOp.getArguments()) {143 if (isa<RankedTensorType>(bbArg.getType())) {144 int64_t bbArgIdx = bbArg.getArgNumber();145 // Store aliases in a set, so that we don't add the same alias twice.146 SetVector<int64_t> aliases;147 for (func::ReturnOp returnOp : returnOps) {148 for (OpOperand &returnVal : returnOp->getOpOperands()) {149 if (isa<RankedTensorType>(returnVal.get().getType())) {150 int64_t returnIdx = returnVal.getOperandNumber();151 if (state.areAliasingBufferizedValues(returnVal.get(), bbArg))152 aliases.insert(returnIdx);153 }154 }155 }156 for (int64_t alias : aliases)157 funcState.aliasingReturnVals[funcOp][bbArgIdx].push_back(alias);158 }159 }160 161 // Build equivalence sets.162 // Helper function that finds an equivalent block argument index for the163 // given OpOperand. Return std::nullopt if no equivalent block argument could164 // be found.165 auto findEquivalentBlockArgIdx =166 [&](OpOperand &opOperand) -> std::optional<int64_t> {167 Value v = opOperand.get();168 if (!isa<TensorType>(v.getType()))169 return std::nullopt;170 for (BlockArgument bbArg : funcOp.getArguments()) {171 if (isa<RankedTensorType>(bbArg.getType())) {172 if (state.areEquivalentBufferizedValues(v, bbArg)) {173 if (state.getOptions().testAnalysisOnly)174 annotateEquivalentReturnBbArg(opOperand, bbArg);175 return bbArg.getArgNumber();176 }177 }178 }179 return std::nullopt;180 };181 182 int64_t numResults = returnOps.front()->getNumOperands();183 for (int64_t i = 0; i < numResults; ++i) {184 // Find the equivalent block argument index for the i-th operand of the185 // first func.return op.186 std::optional<int64_t> maybeEquiv =187 findEquivalentBlockArgIdx(returnOps.front()->getOpOperand(i));188 if (!maybeEquiv.has_value())189 continue;190 int64_t bbArgIdx = *maybeEquiv;191 bool allEquiv = true;192 193 // Check if all other func.return ops have the same equivalent block194 // argument for the i-th operand. In contrast to aliasing information,195 // which is just "merged", equivalence information must match across all196 // func.return ops.197 for (func::ReturnOp returnOp : ArrayRef(returnOps).drop_front()) {198 std::optional<int64_t> maybeEquiv =199 findEquivalentBlockArgIdx(returnOp->getOpOperand(i));200 if (maybeEquiv != bbArgIdx) {201 allEquiv = false;202 break;203 }204 }205 206 // All func.return ops have the same equivalent block argument for the i-th207 // operand.208 if (allEquiv)209 funcState.equivalentFuncArgs[funcOp][i] = bbArgIdx;210 }211 212 return success();213}214 215static void annotateFuncArgAccess(func::FuncOp funcOp, int64_t idx, bool isRead,216 bool isWritten) {217 OpBuilder b(funcOp.getContext());218 Attribute accessType;219 if (isRead && isWritten) {220 accessType = b.getStringAttr("read-write");221 } else if (isRead) {222 accessType = b.getStringAttr("read");223 } else if (isWritten) {224 accessType = b.getStringAttr("write");225 } else {226 accessType = b.getStringAttr("none");227 }228 funcOp.setArgAttr(idx, BufferizationDialect::kBufferAccessAttrName,229 accessType);230}231 232/// Determine which FuncOp bbArgs are read and which are written. When run on a233/// function with unknown ops, we conservatively assume that such ops bufferize234/// to a read + write.235static LogicalResult236funcOpBbArgReadWriteAnalysis(FuncOp funcOp, OneShotAnalysisState &state,237 FuncAnalysisState &funcState) {238 for (int64_t idx = 0, e = funcOp.getFunctionType().getNumInputs(); idx < e;239 ++idx) {240 // Skip non-tensor arguments.241 if (!isa<TensorType>(funcOp.getFunctionType().getInput(idx)))242 continue;243 bool isRead;244 bool isWritten;245 if (auto accessAttr = funcOp.getArgAttrOfType<StringAttr>(246 idx, BufferizationDialect::kBufferAccessAttrName)) {247 // Buffer access behavior is specified on the function. Skip the analysis.248 StringRef str = accessAttr.getValue();249 isRead = str == "read" || str == "read-write";250 isWritten = str == "write" || str == "read-write";251 } else if (funcOp.getBody().empty()) {252 // If the function has no body, conservatively assume that all args are253 // read + written.254 isRead = true;255 isWritten = true;256 } else {257 // Analyze the body of the function.258 BlockArgument bbArg = funcOp.getArgument(idx);259 isRead = state.isValueRead(bbArg);260 isWritten = state.isValueWritten(bbArg);261 }262 263 if (state.getOptions().testAnalysisOnly)264 annotateFuncArgAccess(funcOp, idx, isRead, isWritten);265 if (isRead)266 funcState.readBbArgs[funcOp].insert(idx);267 if (isWritten)268 funcState.writtenBbArgs[funcOp].insert(idx);269 }270 271 return success();272}273} // namespace274 275/// Remove bufferization attributes on FuncOp arguments.276static void removeBufferizationAttributes(BlockArgument bbArg) {277 auto funcOp = cast<func::FuncOp>(bbArg.getOwner()->getParentOp());278 funcOp.removeArgAttr(bbArg.getArgNumber(),279 BufferizationDialect::kBufferLayoutAttrName);280 funcOp.removeArgAttr(bbArg.getArgNumber(),281 BufferizationDialect::kWritableAttrName);282}283 284/// Return the func::FuncOp called by `callOp`.285static func::FuncOp286getCalledFunction(func::CallOp callOp,287 mlir::SymbolTableCollection &symbolTable) {288 return dyn_cast_or_null<func::FuncOp>(289 callOp.resolveCallableInTable(&symbolTable));290}291 292/// Return "true" if the given function signature has tensor semantics.293static bool hasTensorSignature(func::FuncOp funcOp) {294 return llvm::any_of(funcOp.getFunctionType().getInputs(),295 llvm::IsaPred<TensorType>) ||296 llvm::any_of(funcOp.getFunctionType().getResults(),297 llvm::IsaPred<TensorType>);298}299 300/// Store all functions of the `moduleOp` in `orderedFuncOps`, sorted by301/// callee-caller order (i.e., callees without callers first). Store all302/// remaining functions (i.e., the ones that call each other recursively) in303/// `remainingFuncOps`. Does not traverse nested symbol tables.304///305/// Store the map of FuncOp to all its callers in `callerMap`.306///307/// Return `failure()` if we are unable to retrieve the called FuncOp from308/// any func::CallOp.309static LogicalResult getFuncOpsOrderedByCalls(310 Operation *moduleOp, SmallVectorImpl<func::FuncOp> &orderedFuncOps,311 SmallVectorImpl<func::FuncOp> &remainingFuncOps, FuncCallerMap &callerMap,312 SymbolTableCollection &symbolTables) {313 // For each FuncOp, the set of functions called by it (i.e. the union of314 // symbols of all nested func::CallOp).315 DenseMap<func::FuncOp, DenseSet<func::FuncOp>> calledBy;316 // For each FuncOp, the number of func::CallOp it contains.317 DenseMap<func::FuncOp, unsigned> numberCallOpsContainedInFuncOp;318 for (mlir::Region ®ion : moduleOp->getRegions()) {319 for (mlir::Block &block : region.getBlocks()) {320 for (func::FuncOp funcOp : block.getOps<func::FuncOp>()) {321 // Collect function calls and populate the caller map.322 numberCallOpsContainedInFuncOp[funcOp] = 0;323 WalkResult res = funcOp.walk([&](func::CallOp callOp) -> WalkResult {324 func::FuncOp calledFunction = getCalledFunction(callOp, symbolTables);325 assert(calledFunction && "could not retrieved called func::FuncOp");326 // If the called function does not have any tensors in its signature,327 // then it is not necessary to bufferize the callee before the caller.328 if (!hasTensorSignature(calledFunction))329 return WalkResult::skip();330 331 callerMap[calledFunction].insert(callOp);332 if (calledBy[calledFunction].insert(funcOp).second) {333 numberCallOpsContainedInFuncOp[funcOp]++;334 }335 return WalkResult::advance();336 });337 if (res.wasInterrupted())338 return failure();339 }340 }341 }342 343 // Iteratively remove function operations that do not call any of the344 // functions remaining in the callCounter map and add them to ordered list.345 SmallVector<func::FuncOp> worklist;346 347 for (const auto &entry : numberCallOpsContainedInFuncOp) {348 if (entry.second == 0)349 worklist.push_back(entry.first);350 }351 352 while (!worklist.empty()) {353 func::FuncOp func = worklist.pop_back_val();354 orderedFuncOps.push_back(func);355 356 for (func::FuncOp caller : calledBy[func]) {357 auto &count = numberCallOpsContainedInFuncOp[caller];358 359 if (--count == 0)360 worklist.push_back(caller);361 }362 363 numberCallOpsContainedInFuncOp.erase(func);364 }365 366 // Put all other functions in the list of remaining functions. These are367 // functions that call each other circularly.368 for (auto it : numberCallOpsContainedInFuncOp)369 remainingFuncOps.push_back(it.first);370 371 return success();372}373 374/// Helper function that extracts the source from a memref.cast. If the given375/// value is not a memref.cast result, simply returns the given value.376static Value unpackCast(Value v) {377 auto castOp = v.getDefiningOp<memref::CastOp>();378 if (!castOp)379 return v;380 return castOp.getSource();381}382 383/// Helper function that returns the return types (skipping casts) of the given384/// func.return ops. This function returns as many types as the return ops have385/// operands. If the i-th operand is not the same for all func.return ops, then386/// the i-th returned type is an "empty" type.387static SmallVector<Type> getReturnTypes(SmallVector<func::ReturnOp> returnOps) {388 assert(!returnOps.empty() && "expected at least one ReturnOp");389 int numOperands = returnOps.front()->getNumOperands();390 391 // Helper function that unpacks memref.cast ops and returns the type.392 auto getSourceType = [&](Value v) { return unpackCast(v).getType(); };393 394 SmallVector<Type> result;395 for (int i = 0; i < numOperands; ++i) {396 // Get the type of the i-th operand of the first func.return ops.397 Type t = getSourceType(returnOps.front()->getOperand(i));398 399 // Check if all other func.return ops have a matching operand type.400 for (int j = 1; j < static_cast<int>(returnOps.size()); ++j)401 if (getSourceType(returnOps[j]->getOperand(i)) != t)402 t = Type();403 404 result.push_back(t);405 }406 407 return result;408}409 410/// Fold return values that are memref casts and update function return types.411///412/// During FuncOp bufferization, the exact type of the returned memrefs (if any)413/// is not known yet. Therefore, the bufferization uses memref types with the414/// most generic layout map as function return types. After bufferizing the415/// entire function body, a more concise memref type can potentially be used for416/// the return type of the function.417static void foldMemRefCasts(func::FuncOp funcOp) {418 // There is nothing to do for bodiless ops.419 if (funcOp.getBody().empty())420 return;421 422 // Compute the common result types of all return ops.423 SmallVector<func::ReturnOp> returnOps = getReturnOps(funcOp);424 SmallVector<Type> resultTypes = getReturnTypes(returnOps);425 426 // Remove direct casts.427 for (func::ReturnOp returnOp : returnOps) {428 for (OpOperand &operand : returnOp->getOpOperands()) {429 // Bail if no common result type was found.430 if (resultTypes[operand.getOperandNumber()]) {431 operand.set(unpackCast(operand.get()));432 }433 }434 }435 436 // Fill in the missing result types that were not the same among all437 // func.return ops.438 for (int i = 0; i < static_cast<int>(resultTypes.size()); ++i) {439 if (resultTypes[i])440 continue;441 resultTypes[i] = funcOp.getFunctionType().getResult(i);442 }443 444 // Update the function type.445 auto newFuncType = FunctionType::get(446 funcOp.getContext(), funcOp.getFunctionType().getInputs(), resultTypes);447 funcOp.setType(newFuncType);448}449 450LogicalResult451mlir::bufferization::analyzeModuleOp(Operation *moduleOp,452 OneShotAnalysisState &state,453 BufferizationStatistics *statistics) {454 assert(state.getOptions().bufferizeFunctionBoundaries &&455 "expected that function boundary bufferization is activated");456 FuncAnalysisState &funcState = getOrCreateFuncAnalysisState(state);457 458 // A list of non-circular functions in the order in which they are analyzed459 // and bufferized.460 SmallVector<func::FuncOp> orderedFuncOps;461 // A list of all other functions. I.e., functions that call each other462 // recursively. For these, we analyze the function body but not the function463 // boundary.464 SmallVector<func::FuncOp> remainingFuncOps;465 466 // A mapping of FuncOps to their callers.467 FuncCallerMap callerMap;468 469 if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps,470 remainingFuncOps, callerMap,471 funcState.symbolTables)))472 return failure();473 474 // Analyze functions in order. Starting with functions that are not calling475 // any other functions.476 for (func::FuncOp funcOp : orderedFuncOps) {477 if (!state.getOptions().isOpAllowed(funcOp))478 continue;479 480 // Now analyzing function.481 funcState.startFunctionAnalysis(funcOp);482 483 // Analyze funcOp.484 if (failed(analyzeOp(funcOp, state, statistics)))485 return failure();486 487 // Run some extra function analyses.488 if (failed(aliasingFuncOpBBArgsAnalysis(funcOp, state, funcState)) ||489 failed(funcOpBbArgReadWriteAnalysis(funcOp, state, funcState)))490 return failure();491 492 // Mark op as fully analyzed.493 funcState.analyzedFuncOps[funcOp] = FuncOpAnalysisState::Analyzed;494 }495 496 // Analyze all other functions. All function boundary analyses are skipped.497 for (func::FuncOp funcOp : remainingFuncOps) {498 if (!state.getOptions().isOpAllowed(funcOp))499 continue;500 501 // Analyze funcOp.502 if (failed(analyzeOp(funcOp, state, statistics)))503 return failure();504 505 // TODO: We currently skip all function argument analyses for functions506 // that call each other circularly. These analyses do not support recursive507 // calls yet. The `BufferizableOpInterface` implementations of `func`508 // dialect ops return conservative results in the absence of analysis509 // information.510 }511 512 return success();513}514 515void mlir::bufferization::removeBufferizationAttributesInModule(516 Operation *moduleOp) {517 for (mlir::Region ®ion : moduleOp->getRegions()) {518 for (mlir::Block &block : region.getBlocks()) {519 for (func::FuncOp funcOp : block.getOps<func::FuncOp>()) {520 for (BlockArgument bbArg : funcOp.getArguments())521 removeBufferizationAttributes(bbArg);522 }523 }524 }525}526 527LogicalResult mlir::bufferization::bufferizeModuleOp(528 Operation *moduleOp, const OneShotBufferizationOptions &options,529 BufferizationState &state, BufferizationStatistics *statistics) {530 assert(options.bufferizeFunctionBoundaries &&531 "expected that function boundary bufferization is activated");532 IRRewriter rewriter(moduleOp->getContext());533 534 // A list of non-circular functions in the order in which they are analyzed535 // and bufferized.536 SmallVector<func::FuncOp> orderedFuncOps;537 // A list of all other functions. I.e., functions that call each other538 // recursively. For these, we analyze the function body but not the function539 // boundary.540 SmallVector<func::FuncOp> remainingFuncOps;541 542 // A mapping of FuncOps to their callers.543 FuncCallerMap callerMap;544 545 // Try to bufferize functions in calling order. I.e., first bufferize546 // functions that do not call other functions. This allows us to infer547 // accurate buffer types for function return values. Functions that call548 // each other recursively are bufferized in an unspecified order at the end.549 // We may use unnecessarily "complex" (in terms of layout map) buffer types.550 if (failed(getFuncOpsOrderedByCalls(moduleOp, orderedFuncOps,551 remainingFuncOps, callerMap,552 state.getSymbolTables())))553 return failure();554 llvm::append_range(orderedFuncOps, remainingFuncOps);555 556 // Bufferize functions.557 for (func::FuncOp funcOp : orderedFuncOps) {558 // Note: It would be good to apply cleanups here but we cannot as aliasInfo559 // would be invalidated.560 561 if (llvm::is_contained(options.noAnalysisFuncFilter, funcOp.getSymName())) {562 // This function was not analyzed and RaW conflicts were not resolved.563 // Buffer copies must be inserted before every write.564 OneShotBufferizationOptions updatedOptions = options;565 updatedOptions.copyBeforeWrite = true;566 if (failed(bufferizeOp(funcOp, updatedOptions, state, statistics)))567 return failure();568 } else {569 if (failed(bufferizeOp(funcOp, options, state, statistics)))570 return failure();571 }572 573 // Change buffer return types to more precise layout maps.574 if (options.inferFunctionResultLayout)575 foldMemRefCasts(funcOp);576 }577 578 // Bufferize all other ops.579 for (mlir::Region ®ion : moduleOp->getRegions()) {580 for (mlir::Block &block : region.getBlocks()) {581 for (mlir::Operation &op :582 llvm::make_early_inc_range(block.getOperations())) {583 // Functions were already bufferized.584 if (isa<func::FuncOp>(&op) || op.hasTrait<OpTrait::SymbolTable>())585 continue;586 if (failed(bufferizeOp(&op, options, state, statistics)))587 return failure();588 }589 }590 }591 592 // Post-pass cleanup of function argument attributes.593 removeBufferizationAttributesInModule(moduleOp);594 595 return success();596}597 598LogicalResult mlir::bufferization::runOneShotModuleBufferize(599 Operation *moduleOp, const OneShotBufferizationOptions &options,600 BufferizationState &state, BufferizationStatistics *statistics) {601 assert(options.bufferizeFunctionBoundaries &&602 "expected that function boundary bufferization is activated");603 assert(!(options.copyBeforeWrite && options.testAnalysisOnly) &&604 "invalid combination of bufferization flags");605 if (!options.copyBeforeWrite) {606 if (options.noAnalysisFuncFilter.empty()) {607 if (failed(insertTensorCopies(moduleOp, options, state, statistics)))608 return failure();609 } else {610 // FuncOps whose names are specified in options.noAnalysisFuncFilter will611 // not be analyzed. Ops in these FuncOps will not be analyzed as well.612 OpFilter::Entry::FilterFn analysisFilterFn = [=](Operation *op) {613 auto func = dyn_cast<func::FuncOp>(op);614 if (!func)615 func = op->getParentOfType<func::FuncOp>();616 if (func)617 return llvm::is_contained(options.noAnalysisFuncFilter,618 func.getSymName());619 return false;620 };621 OneShotBufferizationOptions updatedOptions(options);622 updatedOptions.opFilter.denyOperation(analysisFilterFn);623 if (failed(624 insertTensorCopies(moduleOp, updatedOptions, state, statistics)))625 return failure();626 }627 }628 if (options.testAnalysisOnly)629 return success();630 if (failed(bufferizeModuleOp(moduleOp, options, state, statistics)))631 return failure();632 return success();633}634