brintos

brintos / llvm-project-archived public Read only

0
0
Text · 24.4 KiB · c233e24 Raw
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 &region : 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 &region : 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 &region : 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