17a1579acSMatthias Springer //===- OneShotAnalysis.cpp - One-Shot (Single Pass) Analysis --------------===// 27a1579acSMatthias Springer // 37a1579acSMatthias Springer // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 47a1579acSMatthias Springer // See https://llvm.org/LICENSE.txt for license information. 57a1579acSMatthias Springer // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 67a1579acSMatthias Springer // 77a1579acSMatthias Springer //===----------------------------------------------------------------------===// 87a1579acSMatthias Springer // 97a1579acSMatthias Springer // One-Shot Analysis analyzes function bodies. Function boundaries (FuncOp 107a1579acSMatthias Springer // bbArgs, CallOps, ReturnOps) are treated as "unknown" ops. 117a1579acSMatthias Springer // ModuleBufferization.cpp is an extension of One-Shot Analysis for simple 127a1579acSMatthias Springer // call graphs. 137a1579acSMatthias Springer // 147a1579acSMatthias Springer // One-Shot Bufferize consists of two phases. 157a1579acSMatthias Springer // 167a1579acSMatthias Springer // 1. Analyze ops to decide which OpResults can bufferize inplace, i.e., without 177a1579acSMatthias Springer // inserting buffer copies. The analysis queries op bufferization semantics 187a1579acSMatthias Springer // via `BufferizableOpInterface`. 197a1579acSMatthias Springer // 2. Bufferize ops by calling `BufferizableOpInterface::bufferize`. This 207a1579acSMatthias Springer // function does not generate buffer copies for OpResults that were decided 217a1579acSMatthias Springer // to bufferize inplace during the analysis phase. 227a1579acSMatthias Springer // 237a1579acSMatthias Springer // This file contains only the analysis. The actual bufferization is implemented 247a1579acSMatthias Springer // via `bufferizeOp` (Bufferize.h). For convenience, this file also contains a 257a1579acSMatthias Springer // helper function `runOneShotBufferize` that analyzes an op (and its nested 267a1579acSMatthias Springer // ops) and then bufferizes it. 277a1579acSMatthias Springer // 287a1579acSMatthias Springer // Inplace bufferization decisions are passed from the analysis to the 299597b16aSMatthias Springer // bufferization phase via `AnalysisState` and `BufferizationAliasInfo`. 307a1579acSMatthias Springer // They can be printed for debugging purposes with `testAnalysisOnly`. 317a1579acSMatthias Springer // 327a1579acSMatthias Springer // Ops that do not implement `BufferizableOpInterface` can be analyzed but are 337a1579acSMatthias Springer // treated conservatively. E.g., the analysis has to assume that their tensor 347a1579acSMatthias Springer // OpOperands bufferize to memory writes. While such ops can be analyzed, they 357a1579acSMatthias Springer // are not bufferized and remain in the IR. to_tensor and to_memref ops are 367a1579acSMatthias Springer // inserted at the bufferization boundary. 377a1579acSMatthias Springer // 387a1579acSMatthias Springer // This analysis caters to high-performance codegen where buffer reuse is deemed 397a1579acSMatthias Springer // critical: the analysis should fail if the bufferized form of the function 40855a11eeSMatthias Springer // needs to return a buffer, unless `allowReturnAllocs` is enabled. 417a1579acSMatthias Springer 427a1579acSMatthias Springer #include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h" 437a1579acSMatthias Springer 447a1579acSMatthias Springer #include <random> 457a1579acSMatthias Springer 467a1579acSMatthias Springer #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h" 477a1579acSMatthias Springer #include "mlir/Dialect/Bufferization/IR/Bufferization.h" 487a1579acSMatthias Springer #include "mlir/Dialect/Bufferization/Transforms/Bufferize.h" 49d6dab38aSMatthias Springer #include "mlir/Dialect/Func/IR/FuncOps.h" 507a1579acSMatthias Springer #include "mlir/Dialect/MemRef/IR/MemRef.h" 517a1579acSMatthias Springer #include "mlir/IR/AsmState.h" 527a1579acSMatthias Springer #include "mlir/IR/Dominance.h" 537a1579acSMatthias Springer #include "mlir/IR/Operation.h" 547a1579acSMatthias Springer #include "mlir/IR/TypeUtilities.h" 557a1579acSMatthias Springer #include "mlir/Interfaces/ControlFlowInterfaces.h" 567a1579acSMatthias Springer #include "llvm/ADT/DenseSet.h" 577a1579acSMatthias Springer #include "llvm/ADT/SetVector.h" 587a1579acSMatthias Springer 597a1579acSMatthias Springer using namespace mlir; 607a1579acSMatthias Springer using namespace mlir::bufferization; 617a1579acSMatthias Springer 627a1579acSMatthias Springer static bool isaTensor(Type t) { return t.isa<TensorType>(); } 637a1579acSMatthias Springer 647a1579acSMatthias Springer //===----------------------------------------------------------------------===// 657a1579acSMatthias Springer // Bufferization-specific attribute manipulation. 667a1579acSMatthias Springer // These are for testing and debugging only. Bufferization information is 677a1579acSMatthias Springer // stored in BufferizationAliasInfo. When run with `testAnalysisOnly`, the IR 687a1579acSMatthias Springer // is annotated with the results of the analysis (copied from 697a1579acSMatthias Springer // BufferizationAliasInfo), so that they can be checked in tests. 707a1579acSMatthias Springer //===----------------------------------------------------------------------===// 717a1579acSMatthias Springer 727a1579acSMatthias Springer /// Attribute marker to specify op results that can be bufferized inPlace. 737a1579acSMatthias Springer constexpr StringLiteral kInPlaceResultsAttrName = "__inplace_operands_attr__"; 747a1579acSMatthias Springer 757a1579acSMatthias Springer /// Mark whether OpOperand will be bufferized inplace. 767a1579acSMatthias Springer static void setInPlaceOpOperand(OpOperand &opOperand, bool inPlace) { 777a1579acSMatthias Springer Operation *op = opOperand.getOwner(); 787a1579acSMatthias Springer auto attr = 797a1579acSMatthias Springer op->getAttr(kInPlaceResultsAttrName).dyn_cast_or_null<ArrayAttr>(); 807a1579acSMatthias Springer SmallVector<StringRef> inPlaceVector; 817a1579acSMatthias Springer if (attr) { 827a1579acSMatthias Springer inPlaceVector = SmallVector<StringRef>( 837a1579acSMatthias Springer llvm::to_vector<4>(attr.getAsValueRange<StringAttr>())); 847a1579acSMatthias Springer } else { 857a1579acSMatthias Springer inPlaceVector = SmallVector<StringRef>(op->getNumOperands(), "none"); 867a1579acSMatthias Springer for (OpOperand &opOperand : op->getOpOperands()) 877a1579acSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) 887a1579acSMatthias Springer inPlaceVector[opOperand.getOperandNumber()] = "false"; 897a1579acSMatthias Springer } 907a1579acSMatthias Springer 917a1579acSMatthias Springer inPlaceVector[opOperand.getOperandNumber()] = inPlace ? "true" : "false"; 927a1579acSMatthias Springer op->setAttr(kInPlaceResultsAttrName, 937a1579acSMatthias Springer OpBuilder(op).getStrArrayAttr(inPlaceVector)); 947a1579acSMatthias Springer } 957a1579acSMatthias Springer 967a1579acSMatthias Springer //===----------------------------------------------------------------------===// 977a1579acSMatthias Springer // BufferizationAliasInfo 987a1579acSMatthias Springer //===----------------------------------------------------------------------===// 997a1579acSMatthias Springer 1007a1579acSMatthias Springer BufferizationAliasInfo::BufferizationAliasInfo(Operation *rootOp) { 1017a1579acSMatthias Springer rootOp->walk([&](Operation *op) { 1027a1579acSMatthias Springer for (Value v : op->getResults()) 1037a1579acSMatthias Springer if (v.getType().isa<TensorType>()) 1047a1579acSMatthias Springer createAliasInfoEntry(v); 1057a1579acSMatthias Springer for (Region &r : op->getRegions()) 1067a1579acSMatthias Springer for (Block &b : r.getBlocks()) 1077a1579acSMatthias Springer for (auto bbArg : b.getArguments()) 1087a1579acSMatthias Springer if (bbArg.getType().isa<TensorType>()) 1097a1579acSMatthias Springer createAliasInfoEntry(bbArg); 1107a1579acSMatthias Springer }); 1117a1579acSMatthias Springer } 1127a1579acSMatthias Springer 1137a1579acSMatthias Springer /// Add a new entry for `v` in the `aliasInfo` and `equivalentInfo`. In the 1147a1579acSMatthias Springer /// beginning the alias and equivalence sets only contain `v` itself. 1157a1579acSMatthias Springer void BufferizationAliasInfo::createAliasInfoEntry(Value v) { 1167a1579acSMatthias Springer aliasInfo.insert(v); 1177a1579acSMatthias Springer equivalentInfo.insert(v); 1187a1579acSMatthias Springer } 1197a1579acSMatthias Springer 1207a1579acSMatthias Springer /// Insert an info entry for `newValue` and merge its alias set with that of 1217a1579acSMatthias Springer /// `alias`. 1227a1579acSMatthias Springer void BufferizationAliasInfo::insertNewBufferAlias(Value newValue, Value alias) { 1237a1579acSMatthias Springer createAliasInfoEntry(newValue); 1247a1579acSMatthias Springer aliasInfo.unionSets(newValue, alias); 1257a1579acSMatthias Springer } 1267a1579acSMatthias Springer 1277a1579acSMatthias Springer /// Insert an info entry for `newValue` and merge its alias set with that of 1287a1579acSMatthias Springer /// `alias`. Additionally, merge their equivalence classes. 1297a1579acSMatthias Springer void BufferizationAliasInfo::insertNewBufferEquivalence(Value newValue, 1307a1579acSMatthias Springer Value alias) { 1317a1579acSMatthias Springer insertNewBufferAlias(newValue, alias); 1327a1579acSMatthias Springer equivalentInfo.unionSets(newValue, alias); 1337a1579acSMatthias Springer } 1347a1579acSMatthias Springer 1357a1579acSMatthias Springer /// Return `true` if a value was marked as in-place bufferized. 1367a1579acSMatthias Springer bool BufferizationAliasInfo::isInPlace(OpOperand &operand) const { 1377a1579acSMatthias Springer return inplaceBufferized.contains(&operand); 1387a1579acSMatthias Springer } 1397a1579acSMatthias Springer 1407a1579acSMatthias Springer /// Set the inPlace bufferization spec to true. 1417a1579acSMatthias Springer void BufferizationAliasInfo::bufferizeInPlace(OpOperand &operand, 1429597b16aSMatthias Springer AnalysisState &state) { 1437a1579acSMatthias Springer markInPlace(operand); 144585a8a32SMatthias Springer for (OpResult result : state.getAliasingOpResult(operand)) 1457a1579acSMatthias Springer aliasInfo.unionSets(result, operand.get()); 1467a1579acSMatthias Springer } 1477a1579acSMatthias Springer 1487a1579acSMatthias Springer /// Set the inPlace bufferization spec to false. 1497a1579acSMatthias Springer void BufferizationAliasInfo::bufferizeOutOfPlace(OpOperand &operand) { 1507a1579acSMatthias Springer assert(!inplaceBufferized.contains(&operand) && 1517a1579acSMatthias Springer "OpOperand was already decided to bufferize inplace"); 1527a1579acSMatthias Springer } 1537a1579acSMatthias Springer 1547a1579acSMatthias Springer /// Apply `fun` to all the members of the equivalence class of `v`. 1557a1579acSMatthias Springer void BufferizationAliasInfo::applyOnEquivalenceClass( 1567a1579acSMatthias Springer Value v, function_ref<void(Value)> fun) const { 1577a1579acSMatthias Springer auto leaderIt = equivalentInfo.findLeader(v); 1587a1579acSMatthias Springer for (auto mit = leaderIt, meit = equivalentInfo.member_end(); mit != meit; 1597a1579acSMatthias Springer ++mit) { 1607a1579acSMatthias Springer fun(*mit); 1617a1579acSMatthias Springer } 1627a1579acSMatthias Springer } 1637a1579acSMatthias Springer 1647a1579acSMatthias Springer /// Apply `fun` to all aliases of `v`. 1657a1579acSMatthias Springer void BufferizationAliasInfo::applyOnAliases( 1667a1579acSMatthias Springer Value v, function_ref<void(Value)> fun) const { 1677a1579acSMatthias Springer auto leaderIt = aliasInfo.findLeader(v); 1687a1579acSMatthias Springer for (auto mit = leaderIt, meit = aliasInfo.member_end(); mit != meit; ++mit) { 1697a1579acSMatthias Springer fun(*mit); 1707a1579acSMatthias Springer } 1717a1579acSMatthias Springer } 1727a1579acSMatthias Springer 1737a1579acSMatthias Springer BufferizationAliasInfo::EquivalenceClassRangeType 1747a1579acSMatthias Springer BufferizationAliasInfo::getAliases(Value v) const { 1757a1579acSMatthias Springer DenseSet<Value> res; 1767a1579acSMatthias Springer auto it = aliasInfo.findValue(aliasInfo.getLeaderValue(v)); 1777a1579acSMatthias Springer for (auto mit = aliasInfo.member_begin(it), meit = aliasInfo.member_end(); 1787a1579acSMatthias Springer mit != meit; ++mit) { 1797a1579acSMatthias Springer res.insert(static_cast<Value>(*mit)); 1807a1579acSMatthias Springer } 1817a1579acSMatthias Springer return BufferizationAliasInfo::EquivalenceClassRangeType( 1827a1579acSMatthias Springer aliasInfo.member_begin(it), aliasInfo.member_end()); 1837a1579acSMatthias Springer } 1847a1579acSMatthias Springer 1857a1579acSMatthias Springer //===----------------------------------------------------------------------===// 1869597b16aSMatthias Springer // OneShotAnalysisState 1877a1579acSMatthias Springer //===----------------------------------------------------------------------===// 1887a1579acSMatthias Springer 1899597b16aSMatthias Springer OneShotAnalysisState::OneShotAnalysisState( 1909597b16aSMatthias Springer Operation *op, const OneShotBufferizationOptions &options) 1919597b16aSMatthias Springer : AnalysisState(options), aliasInfo(op) { 1927a1579acSMatthias Springer // Set up alias sets for OpResults that must bufferize in-place. This should 1937a1579acSMatthias Springer // be done before making any other bufferization decisions. 1947a1579acSMatthias Springer op->walk([&](BufferizableOpInterface bufferizableOp) { 1957a1579acSMatthias Springer if (!options.isOpAllowed(bufferizableOp)) 1967a1579acSMatthias Springer return WalkResult::skip(); 1977a1579acSMatthias Springer for (OpOperand &opOperand : bufferizableOp->getOpOperands()) { 1987a1579acSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) 1997a1579acSMatthias Springer if (bufferizableOp.mustBufferizeInPlace(opOperand, *this)) { 200585a8a32SMatthias Springer for (OpResult opResult : 2017a1579acSMatthias Springer bufferizableOp.getAliasingOpResult(opOperand, *this)) 2027a1579acSMatthias Springer aliasInfo.unionAliasSets(opOperand.get(), opResult); 2037a1579acSMatthias Springer aliasInfo.markInPlace(opOperand); 2047a1579acSMatthias Springer } 2057a1579acSMatthias Springer } 2067a1579acSMatthias Springer return WalkResult::advance(); 2077a1579acSMatthias Springer }); 2087a1579acSMatthias Springer } 2097a1579acSMatthias Springer 2109597b16aSMatthias Springer bool OneShotAnalysisState::isInPlace(OpOperand &opOperand) const { 2117a1579acSMatthias Springer return aliasInfo.isInPlace(opOperand); 2127a1579acSMatthias Springer } 2137a1579acSMatthias Springer 2149597b16aSMatthias Springer bool OneShotAnalysisState::areEquivalentBufferizedValues(Value v1, 2157a1579acSMatthias Springer Value v2) const { 2167a1579acSMatthias Springer return aliasInfo.areEquivalentBufferizedValues(v1, v2); 2177a1579acSMatthias Springer } 2187a1579acSMatthias Springer 2199e24f0f4SMatthias Springer // Gather yielded tensors in `yieldedTensors` by querying all aliases. This is 2209e24f0f4SMatthias Springer // to ensure that such information is available during bufferization time. 2219e24f0f4SMatthias Springer // Alias information can no longer be queried through BufferizationAliasInfo 2229e24f0f4SMatthias Springer // once we have started modifying the IR. 2239e24f0f4SMatthias Springer void OneShotAnalysisState::gatherYieldedTensors(Operation *op) { 2249e24f0f4SMatthias Springer op->walk([&](Operation *returnOp) { 2259e24f0f4SMatthias Springer if (!isRegionReturnLike(returnOp) || !getOptions().isOpAllowed(returnOp)) 2269e24f0f4SMatthias Springer return WalkResult::advance(); 2279e24f0f4SMatthias Springer 2289e24f0f4SMatthias Springer for (OpOperand &returnValOperand : returnOp->getOpOperands()) { 2299e24f0f4SMatthias Springer Value returnVal = returnValOperand.get(); 2309e24f0f4SMatthias Springer // Skip non-tensor values. 2319e24f0f4SMatthias Springer if (!returnVal.getType().isa<TensorType>()) 2329e24f0f4SMatthias Springer continue; 2339e24f0f4SMatthias Springer 2349e24f0f4SMatthias Springer // Add all aliases of the returned value. But only the ones that are in 2359e24f0f4SMatthias Springer // the same block. 2369e24f0f4SMatthias Springer aliasInfo.applyOnAliases(returnVal, [&](Value v) { 2379e24f0f4SMatthias Springer if (auto bbArg = v.dyn_cast<BlockArgument>()) { 2389e24f0f4SMatthias Springer if (bbArg.getOwner()->getParentOp() == returnOp->getParentOp()) 2399e24f0f4SMatthias Springer yieldedTensors.insert(bbArg); 2409e24f0f4SMatthias Springer return; 2419e24f0f4SMatthias Springer } 2429e24f0f4SMatthias Springer Operation *definingOp = v.getDefiningOp(); 2439e24f0f4SMatthias Springer if (definingOp->getParentOp() == returnOp->getParentOp()) 2449e24f0f4SMatthias Springer yieldedTensors.insert(v); 2459e24f0f4SMatthias Springer }); 2469e24f0f4SMatthias Springer } 2479e24f0f4SMatthias Springer 2489e24f0f4SMatthias Springer return WalkResult::advance(); 2499e24f0f4SMatthias Springer }); 2509e24f0f4SMatthias Springer } 2519e24f0f4SMatthias Springer 252988748c0SMatthias Springer void OneShotAnalysisState::gatherUndefinedTensorUses(Operation *op) { 253988748c0SMatthias Springer op->walk([&](Operation *op) { 254988748c0SMatthias Springer // Skip unknown ops. 255988748c0SMatthias Springer auto bufferizableOp = getOptions().dynCastBufferizableOp(op); 256988748c0SMatthias Springer if (!bufferizableOp) 257988748c0SMatthias Springer return WalkResult::skip(); 258988748c0SMatthias Springer 259988748c0SMatthias Springer // Check all tensor OpResults. 260988748c0SMatthias Springer for (OpResult opResult : op->getOpResults()) { 261988748c0SMatthias Springer if (!opResult.getType().isa<TensorType>()) 262988748c0SMatthias Springer continue; 263988748c0SMatthias Springer 264988748c0SMatthias Springer // If there is no preceding memory write, the tensor contents are 265988748c0SMatthias Springer // undefined. 266988748c0SMatthias Springer // Note: If `findLastPrecedingWrite` reaches the end of the reverse SSA 267988748c0SMatthias Springer // use-def chain, it returns that value, regardless of whether it is a 268988748c0SMatthias Springer // memory write or not. 269988748c0SMatthias Springer SetVector<Value> lastWrites = findLastPrecedingWrite(opResult); 270988748c0SMatthias Springer bool isUndefined = llvm::none_of(lastWrites, [&](Value lastWrite) { 271988748c0SMatthias Springer if (auto bufferizableOp = getOptions().dynCastBufferizableOp(lastWrite)) 272988748c0SMatthias Springer return bufferizableOp.isMemoryWrite(lastWrite.cast<OpResult>(), 273988748c0SMatthias Springer *this); 274988748c0SMatthias Springer return true; 275988748c0SMatthias Springer }); 276988748c0SMatthias Springer if (isUndefined) 277988748c0SMatthias Springer for (OpOperand &use : opResult.getUses()) 278988748c0SMatthias Springer undefinedTensorUses.insert(&use); 279988748c0SMatthias Springer } 280988748c0SMatthias Springer 281988748c0SMatthias Springer return WalkResult::advance(); 282988748c0SMatthias Springer }); 283988748c0SMatthias Springer } 284988748c0SMatthias Springer 285988748c0SMatthias Springer bool OneShotAnalysisState::hasUndefinedContents(OpOperand *opOperand) const { 286988748c0SMatthias Springer return undefinedTensorUses.contains(opOperand); 287988748c0SMatthias Springer } 288988748c0SMatthias Springer 2899e24f0f4SMatthias Springer bool OneShotAnalysisState::isTensorYielded(Value tensor) const { 2909e24f0f4SMatthias Springer return yieldedTensors.contains(tensor); 2919e24f0f4SMatthias Springer } 2929e24f0f4SMatthias Springer 2937a1579acSMatthias Springer //===----------------------------------------------------------------------===// 2947a1579acSMatthias Springer // Bufferization-specific alias analysis. 2957a1579acSMatthias Springer //===----------------------------------------------------------------------===// 2967a1579acSMatthias Springer 2977a1579acSMatthias Springer /// Return true if opOperand has been decided to bufferize in-place. 2987a1579acSMatthias Springer static bool isInplaceMemoryWrite(OpOperand &opOperand, 2997a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 3009597b16aSMatthias Springer AnalysisState &state) { 3017a1579acSMatthias Springer // OpOperands that do not bufferize to a memory write do not write in-place. 3027a1579acSMatthias Springer if (!state.bufferizesToMemoryWrite(opOperand)) 3037a1579acSMatthias Springer return false; 3047a1579acSMatthias Springer // Check current bufferization decisions. 3057a1579acSMatthias Springer return aliasInfo.isInPlace(opOperand); 3067a1579acSMatthias Springer } 3077a1579acSMatthias Springer 3087a1579acSMatthias Springer /// Return true if, under current bufferization decisions, the buffer of `value` 3097a1579acSMatthias Springer /// is not writable. 3107a1579acSMatthias Springer static bool aliasesNonWritableBuffer(Value value, 3117a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 3129597b16aSMatthias Springer AnalysisState &state) { 3137a1579acSMatthias Springer bool foundNonWritableBuffer = false; 3147a1579acSMatthias Springer aliasInfo.applyOnAliases(value, [&](Value v) { 3157a1579acSMatthias Springer // Query BufferizableOpInterface to see if the value is writable. 3167a1579acSMatthias Springer // TODO: Out-of-place bufferized value could be considered writable. 3177a1579acSMatthias Springer if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp(v)) 3187a1579acSMatthias Springer if (bufferizableOp && bufferizableOp.isWritable(v, state)) 3197a1579acSMatthias Springer return; 3207a1579acSMatthias Springer 3217a1579acSMatthias Springer // Query BufferizableOpInterface to see if the BlockArgument is writable. 3227a1579acSMatthias Springer if (auto bbArg = v.dyn_cast<BlockArgument>()) 3237a1579acSMatthias Springer if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp( 3247a1579acSMatthias Springer bbArg.getOwner()->getParentOp())) 3257a1579acSMatthias Springer if (bufferizableOp.isWritable(bbArg, state)) 3267a1579acSMatthias Springer return; 3277a1579acSMatthias Springer 3287a1579acSMatthias Springer foundNonWritableBuffer = true; 3297a1579acSMatthias Springer }); 3307a1579acSMatthias Springer 3317a1579acSMatthias Springer return foundNonWritableBuffer; 3327a1579acSMatthias Springer } 3337a1579acSMatthias Springer 3347a1579acSMatthias Springer /// Return true if the buffer to which `operand` would bufferize is equivalent 3357a1579acSMatthias Springer /// to some buffer write. 3367a1579acSMatthias Springer static bool aliasesInPlaceWrite(Value value, 3377a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 3389597b16aSMatthias Springer AnalysisState &state) { 3397a1579acSMatthias Springer bool foundInplaceWrite = false; 3407a1579acSMatthias Springer aliasInfo.applyOnAliases(value, [&](Value v) { 3417a1579acSMatthias Springer for (auto &use : v.getUses()) { 3427a1579acSMatthias Springer if (isInplaceMemoryWrite(use, aliasInfo, state)) { 3437a1579acSMatthias Springer foundInplaceWrite = true; 3447a1579acSMatthias Springer return; 3457a1579acSMatthias Springer } 3467a1579acSMatthias Springer } 3477a1579acSMatthias Springer }); 3487a1579acSMatthias Springer return foundInplaceWrite; 3497a1579acSMatthias Springer } 3507a1579acSMatthias Springer 3517a1579acSMatthias Springer /// Return true if `a` happens before `b`, i.e., `a` or one of its ancestors 3527a1579acSMatthias Springer /// properly dominates `b` and `b` is not inside `a`. 3537a1579acSMatthias Springer static bool happensBefore(Operation *a, Operation *b, 3547a1579acSMatthias Springer const DominanceInfo &domInfo) { 3557a1579acSMatthias Springer do { 3567a1579acSMatthias Springer // TODO: Instead of isProperAncestor + properlyDominates, we should use 3577a1579acSMatthias Springer // properlyDominatesImpl(a, b, /*enclosingOpOk=*/false) 3587a1579acSMatthias Springer if (a->isProperAncestor(b)) 3597a1579acSMatthias Springer return false; 3607a1579acSMatthias Springer if (domInfo.properlyDominates(a, b)) 3617a1579acSMatthias Springer return true; 3627a1579acSMatthias Springer } while ((a = a->getParentOp())); 3637a1579acSMatthias Springer return false; 3647a1579acSMatthias Springer } 3657a1579acSMatthias Springer 3669235e597SMatthias Springer /// For each given value, find the closest enclosing repetitive region. If this 3679235e597SMatthias Springer /// is the same region for each value, return it. Otherwise return None. 3689235e597SMatthias Springer /// Note: If there is no enclosing repetitive region, return nullptr. 3699235e597SMatthias Springer static Optional<Region *> 3709235e597SMatthias Springer getCommonEnclosingRepetitiveRegion(ArrayRef<Value> values) { 3719235e597SMatthias Springer if (values.empty()) 3729235e597SMatthias Springer return None; 3739235e597SMatthias Springer Region *r = getEnclosingRepetitiveRegion(values.front()); 3749235e597SMatthias Springer for (Value value : values.drop_front()) 3759235e597SMatthias Springer if (getEnclosingRepetitiveRegion(value) != r) 3769235e597SMatthias Springer return None; 3779235e597SMatthias Springer return r; 3789235e597SMatthias Springer } 3799235e597SMatthias Springer 38037a14735SMatthias Springer /// Return `true` if the given tensor value is a memory write. Most values are 38137a14735SMatthias Springer /// tensor writes, but ops that define a tensor SSA value without specifying its 382*ffdbecccSMatthias Springer /// contents (e.g., alloc_tensor) are not. 38337a14735SMatthias Springer static bool isMemoryWrite(Value value, const AnalysisState &state) { 38437a14735SMatthias Springer auto opResult = value.dyn_cast<OpResult>(); 38537a14735SMatthias Springer if (!opResult) 38637a14735SMatthias Springer return true; 38737a14735SMatthias Springer auto bufferizableOp = state.getOptions().dynCastBufferizableOp(value); 38837a14735SMatthias Springer if (!bufferizableOp) 38937a14735SMatthias Springer return true; 39037a14735SMatthias Springer return bufferizableOp.isMemoryWrite(opResult, state); 39137a14735SMatthias Springer } 39237a14735SMatthias Springer 3937a1579acSMatthias Springer /// Annotate IR with details about the detected RaW conflict. 3947a1579acSMatthias Springer static void annotateConflict(OpOperand *uRead, OpOperand *uConflictingWrite, 3957a1579acSMatthias Springer Value lastWrite) { 3967a1579acSMatthias Springer static uint64_t counter = 0; 3977a1579acSMatthias Springer Operation *readingOp = uRead->getOwner(); 3987a1579acSMatthias Springer Operation *conflictingWritingOp = uConflictingWrite->getOwner(); 3997a1579acSMatthias Springer 4007a1579acSMatthias Springer OpBuilder b(conflictingWritingOp->getContext()); 4017a1579acSMatthias Springer std::string id = "C_" + std::to_string(counter++); 4027a1579acSMatthias Springer 4037a1579acSMatthias Springer std::string conflictingWriteAttr = 4047a1579acSMatthias Springer id + 4057a1579acSMatthias Springer "[CONFL-WRITE: " + std::to_string(uConflictingWrite->getOperandNumber()) + 4067a1579acSMatthias Springer "]"; 4077a1579acSMatthias Springer conflictingWritingOp->setAttr(conflictingWriteAttr, b.getUnitAttr()); 4087a1579acSMatthias Springer 4097a1579acSMatthias Springer std::string readAttr = 4107a1579acSMatthias Springer id + "[READ: " + std::to_string(uRead->getOperandNumber()) + "]"; 4117a1579acSMatthias Springer readingOp->setAttr(readAttr, b.getUnitAttr()); 4127a1579acSMatthias Springer 4137a1579acSMatthias Springer if (auto opResult = lastWrite.dyn_cast<OpResult>()) { 4147a1579acSMatthias Springer std::string lastWriteAttr = id + "[LAST-WRITE: result " + 4157a1579acSMatthias Springer std::to_string(opResult.getResultNumber()) + 4167a1579acSMatthias Springer "]"; 4177a1579acSMatthias Springer opResult.getDefiningOp()->setAttr(lastWriteAttr, b.getUnitAttr()); 4187a1579acSMatthias Springer } else { 4197a1579acSMatthias Springer auto bbArg = lastWrite.cast<BlockArgument>(); 4207a1579acSMatthias Springer std::string lastWriteAttr = 4217a1579acSMatthias Springer id + "[LAST-WRITE: bbArg " + std::to_string(bbArg.getArgNumber()) + "]"; 4227a1579acSMatthias Springer bbArg.getOwner()->getParentOp()->setAttr(lastWriteAttr, b.getUnitAttr()); 4237a1579acSMatthias Springer } 4247a1579acSMatthias Springer } 4257a1579acSMatthias Springer 4267a1579acSMatthias Springer /// Given sets of uses and writes, return true if there is a RaW conflict under 4277a1579acSMatthias Springer /// the assumption that all given reads/writes alias the same buffer and that 4287a1579acSMatthias Springer /// all given writes bufferize inplace. 4297a1579acSMatthias Springer /// 4307a1579acSMatthias Springer /// A conflict is: According to SSA use-def chains, a read R is supposed to read 4317a1579acSMatthias Springer /// the result of a write W1. But because of bufferization decisions, R actually 4327a1579acSMatthias Springer /// reads another write W2. 4337a1579acSMatthias Springer static bool hasReadAfterWriteInterference( 4347a1579acSMatthias Springer const DenseSet<OpOperand *> &usesRead, 4357a1579acSMatthias Springer const DenseSet<OpOperand *> &usesWrite, const DominanceInfo &domInfo, 4369597b16aSMatthias Springer AnalysisState &state, const BufferizationAliasInfo &aliasInfo) { 4377a1579acSMatthias Springer const BufferizationOptions &options = state.getOptions(); 4387a1579acSMatthias Springer 43937a14735SMatthias Springer // Gather all written aliases. Skip over aliases that are not actual writes. 4409235e597SMatthias Springer SmallVector<Value> writtenAliases; 4419235e597SMatthias Springer for (OpOperand *uWrite : usesWrite) 44237a14735SMatthias Springer if (isMemoryWrite(uWrite->get(), state)) 4439235e597SMatthias Springer writtenAliases.push_back(uWrite->get()); 4449235e597SMatthias Springer // Find the inner-most enclosing repetitive region of each alias. If this is 4459235e597SMatthias Springer // the same region for every alias, save it in `repetitiveRegionOfWrites`. 4469235e597SMatthias Springer Optional<Region *> repetitiveRegionOfWrites = 4479235e597SMatthias Springer getCommonEnclosingRepetitiveRegion(writtenAliases); 4489235e597SMatthias Springer 4497a1579acSMatthias Springer for (OpOperand *uRead : usesRead) { 4507a1579acSMatthias Springer Operation *readingOp = uRead->getOwner(); 4517a1579acSMatthias Springer 4527a1579acSMatthias Springer // Find most recent writes of uRead by following the SSA use-def chain. 4537a1579acSMatthias Springer // E.g.: 4547a1579acSMatthias Springer // 4557a1579acSMatthias Springer // %0 = "writing_op"(%t) : tensor<?x32> -> tensor<?xf32> 4567a1579acSMatthias Springer // %1 = "aliasing_op"(%0) : tensor<?x32> -> tensor<?xf32> 4577a1579acSMatthias Springer // %2 = "reading_op"(%1) : : tensor<?x32> -> not_a_tensor_type 4587a1579acSMatthias Springer // 4597a1579acSMatthias Springer // In the above example, if uRead is the OpOperand of reading_op, lastWrite 4607a1579acSMatthias Springer // is %0. Note that operations that create an alias but do not write (such 4617a1579acSMatthias Springer // as ExtractSliceOp) are skipped. 4627a1579acSMatthias Springer SetVector<Value> lastWrites = state.findLastPrecedingWrite(uRead->get()); 4637a1579acSMatthias Springer 4647a1579acSMatthias Springer // Look for conflicting memory writes. Potential conflicts are writes to an 4657a1579acSMatthias Springer // alias that have been decided to bufferize inplace. 4667a1579acSMatthias Springer for (OpOperand *uConflictingWrite : usesWrite) { 4677a1579acSMatthias Springer // Throughout this loop, check for multiple requirements that have to be 4687a1579acSMatthias Springer // met for uConflictingWrite to be an actual conflict. 4697a1579acSMatthias Springer Operation *conflictingWritingOp = uConflictingWrite->getOwner(); 4707a1579acSMatthias Springer 4719235e597SMatthias Springer // Check if conflictingWritingOp is in the same repetitive region as all 4729235e597SMatthias Springer // written aliases. If this is not the case, there is no meaningful 4739235e597SMatthias Springer // `happensBefore` relationship because conflictingWritingOp may be 4749235e597SMatthias Springer // executed multiple times. E.g.: 4759235e597SMatthias Springer // 4769235e597SMatthias Springer // %0 = ... : tensor<?xf32> 4779235e597SMatthias Springer // scf.for ... { 4789235e597SMatthias Springer // "reading_op"(%0) : tensor<?xf32> 4799235e597SMatthias Springer // %1 = "writing_op"(%0) : tensor<?xf32> -> tensor<?xf32> 4809235e597SMatthias Springer // ... 4819235e597SMatthias Springer // } 4829235e597SMatthias Springer // 4839235e597SMatthias Springer // In the above example, reading_op happens before writing_op according to 4849235e597SMatthias Springer // op dominance. However, both ops may happen multiple times; in 4859235e597SMatthias Springer // particular, the second execution of reading_op happens after the first 4869235e597SMatthias Springer // execution of writing_op. This is problematic if the tensor they operate 4879235e597SMatthias Springer // on (%0) is defined outside of the loop. 4889235e597SMatthias Springer // 4899235e597SMatthias Springer // Counter example: 4909235e597SMatthias Springer // 4919235e597SMatthias Springer // scf.for ... { 4929235e597SMatthias Springer // %0 = ... : tensor<?xf32> 4939235e597SMatthias Springer // "reading_op"(%0) : tensor<?xf32> 4949235e597SMatthias Springer // %1 = "writing_op"(%0) : tensor<?xf32> -> tensor<?xf32> 4959235e597SMatthias Springer // ... 4969235e597SMatthias Springer // } 4979235e597SMatthias Springer // 4989235e597SMatthias Springer // In this example, %0 is in the same repetitive region as 4999235e597SMatthias Springer // conflictingWritingOp, so op dominance can be used to compute the 5009235e597SMatthias Springer // `happensBefore` relationship. 5019235e597SMatthias Springer // 5029235e597SMatthias Springer // Note: iter_args of loops are not aliases of their respective block 5039235e597SMatthias Springer // arguments, so op domanice can be used when analyzing ops that operate 5049235e597SMatthias Springer // on them. 50537a14735SMatthias Springer // 50637a14735SMatthias Springer // Note: If `writtenAliases` is empty, there are no memory writes outside 50737a14735SMatthias Springer // of the repetitive region of conflictingWritingOp, which means that all 50837a14735SMatthias Springer // relevant aliases are inside the same repetitive region. 5099235e597SMatthias Springer bool canUseOpDominance = 51037a14735SMatthias Springer writtenAliases.empty() || 5119235e597SMatthias Springer repetitiveRegionOfWrites == 5129235e597SMatthias Springer getEnclosingRepetitiveRegion(conflictingWritingOp); 5139235e597SMatthias Springer 5147a1579acSMatthias Springer // No conflict if the readingOp dominates conflictingWritingOp, i.e., the 5157a1579acSMatthias Springer // write is not visible when reading. 5169235e597SMatthias Springer // 5179235e597SMatthias Springer // Note: If ops are executed multiple times (e.g., because they are inside 5189235e597SMatthias Springer // a loop), there may be no meaningful `happensBefore` relationship. 5199235e597SMatthias Springer if (canUseOpDominance && 5209235e597SMatthias Springer happensBefore(readingOp, conflictingWritingOp, domInfo)) 5217a1579acSMatthias Springer continue; 5227a1579acSMatthias Springer 5237a1579acSMatthias Springer // No conflict if the reading use equals the use of the conflicting write. 5249235e597SMatthias Springer // A use cannot conflict with itself. 5259235e597SMatthias Springer // 5269235e597SMatthias Springer // Note: Just being the same op is not enough. It has to be the same use. 5279235e597SMatthias Springer // Note: If the op is executed multiple times (e.g., because it is inside 5289235e597SMatthias Springer // a loop), it may be conflicting with itself. 5299235e597SMatthias Springer if (canUseOpDominance && uConflictingWrite == uRead) 5307a1579acSMatthias Springer continue; 5317a1579acSMatthias Springer 5327a1579acSMatthias Springer // No conflict if the op interface says so. 5337a1579acSMatthias Springer if (auto bufferizableOp = options.dynCastBufferizableOp(readingOp)) 5347a1579acSMatthias Springer if (bufferizableOp.isNotConflicting(uRead, uConflictingWrite, state)) 5357a1579acSMatthias Springer continue; 5367a1579acSMatthias Springer 5377a1579acSMatthias Springer if (conflictingWritingOp != readingOp) 5387a1579acSMatthias Springer if (auto bufferizableOp = 5397a1579acSMatthias Springer options.dynCastBufferizableOp(conflictingWritingOp)) 5407a1579acSMatthias Springer if (bufferizableOp.isNotConflicting(uRead, uConflictingWrite, state)) 5417a1579acSMatthias Springer continue; 5427a1579acSMatthias Springer 5437a1579acSMatthias Springer // Ops are not conflicting if they are in mutually exclusive regions. 5449235e597SMatthias Springer // 5459235e597SMatthias Springer // Note: If ops are executed multiple times (e.g., because they are inside 5469235e597SMatthias Springer // a loop), mutually exclusive regions may be executed multiple 5479235e597SMatthias Springer // times. 5489235e597SMatthias Springer if (canUseOpDominance && 5499235e597SMatthias Springer insideMutuallyExclusiveRegions(readingOp, conflictingWritingOp)) 5507a1579acSMatthias Springer continue; 5517a1579acSMatthias Springer 5527a1579acSMatthias Springer // Check all possible last writes. 5537a1579acSMatthias Springer for (Value lastWrite : lastWrites) { 5547a1579acSMatthias Springer // No conflict if the conflicting write happens before the last 5557a1579acSMatthias Springer // write. 5567a1579acSMatthias Springer if (Operation *writingOp = lastWrite.getDefiningOp()) { 5577a1579acSMatthias Springer if (happensBefore(conflictingWritingOp, writingOp, domInfo)) 5587a1579acSMatthias Springer // conflictingWritingOp happens before writingOp. No conflict. 5597a1579acSMatthias Springer continue; 5607a1579acSMatthias Springer // No conflict if conflictingWritingOp is contained in writingOp. 5617a1579acSMatthias Springer if (writingOp->isProperAncestor(conflictingWritingOp)) 5627a1579acSMatthias Springer continue; 5637a1579acSMatthias Springer } else { 5647a1579acSMatthias Springer auto bbArg = lastWrite.cast<BlockArgument>(); 5657a1579acSMatthias Springer Block *block = bbArg.getOwner(); 5667a1579acSMatthias Springer if (!block->findAncestorOpInBlock(*conflictingWritingOp)) 5677a1579acSMatthias Springer // conflictingWritingOp happens outside of the block. No 5687a1579acSMatthias Springer // conflict. 5697a1579acSMatthias Springer continue; 5707a1579acSMatthias Springer } 5717a1579acSMatthias Springer 5727a1579acSMatthias Springer // No conflict if the conflicting write and the last write are the same 5737a1579acSMatthias Springer // use. 574585a8a32SMatthias Springer SmallVector<OpResult> aliasingOpResult = 575585a8a32SMatthias Springer state.getAliasingOpResult(*uConflictingWrite); 576585a8a32SMatthias Springer if (aliasingOpResult.size() == 1 && aliasingOpResult[0] == lastWrite) 5777a1579acSMatthias Springer continue; 5787a1579acSMatthias Springer 5797a1579acSMatthias Springer // All requirements are met. Conflict found! 5807a1579acSMatthias Springer 5817a1579acSMatthias Springer if (options.printConflicts) 5827a1579acSMatthias Springer annotateConflict(uRead, uConflictingWrite, lastWrite); 5837a1579acSMatthias Springer 5847a1579acSMatthias Springer return true; 5857a1579acSMatthias Springer } 5867a1579acSMatthias Springer } 5877a1579acSMatthias Springer } 5887a1579acSMatthias Springer 5897a1579acSMatthias Springer return false; 5907a1579acSMatthias Springer } 5917a1579acSMatthias Springer 5927a1579acSMatthias Springer /// Return true if bufferizing `operand` inplace would create a conflict. A read 5937a1579acSMatthias Springer /// R and a write W of the same alias set is a conflict if inplace bufferization 5947a1579acSMatthias Springer /// of W changes the value read by R to a value different from the one that 5957a1579acSMatthias Springer /// would be expected by tracing back R's origin through SSA use-def chains. 5967a1579acSMatthias Springer /// A conflict can only be introduced by a new alias and/or an inplace 5977a1579acSMatthias Springer /// bufferization decision. 5987a1579acSMatthias Springer /// 5997a1579acSMatthias Springer /// Example: 6007a1579acSMatthias Springer /// %0 = tensor.extract_slice %t[...][...][1, 1] {inplace?} 6017a1579acSMatthias Springer /// %1 = vector.transfer_write %v1, %t {inplace} : vector<5xf32>, tensor<?xf32> 6027a1579acSMatthias Springer /// %e = tensor.extract_slice %1 6037a1579acSMatthias Springer /// %2 = vector.transfer_write %v2, %0 {inplace} : vector<6xf32>, tensor<?xf32> 6047a1579acSMatthias Springer /// %3 = vector.transfer_read %e, %cst : tensor<?xf32>, vector<7xf32> 6057a1579acSMatthias Springer /// 6067a1579acSMatthias Springer /// In the above example, the two TransferWriteOps have already been decided to 6077a1579acSMatthias Springer /// bufferize inplace. Bufferizing the ExtractSliceOp inplace would create a 6087a1579acSMatthias Springer /// conflict because: 6097a1579acSMatthias Springer /// * According to SSA use-def chains, we expect to read the result of %1. 6107a1579acSMatthias Springer /// * However, adding an alias {%0, %t} would mean that the second 6117a1579acSMatthias Springer /// TransferWriteOp overwrites the first one. Therefore, the TransferReadOp 6127a1579acSMatthias Springer /// would no longer be reading the result of %1. 6137a1579acSMatthias Springer /// 6147a1579acSMatthias Springer /// If `checkConsistencyOnly` is true, this function checks if there is a 6157a1579acSMatthias Springer /// read-after-write conflict without bufferizing `operand` inplace. This would 6167a1579acSMatthias Springer /// indicate a problem with the current inplace bufferization decisions. 6177a1579acSMatthias Springer /// 6187a1579acSMatthias Springer /// Note: If `checkConsistencyOnly`, this function may be called with a null 6197a1579acSMatthias Springer /// OpResult. In that case, only the consistency of bufferization decisions 6207a1579acSMatthias Springer /// involving aliases of the given OpOperand are checked. 6217a1579acSMatthias Springer static bool wouldCreateReadAfterWriteInterference( 6229597b16aSMatthias Springer OpOperand &operand, const DominanceInfo &domInfo, AnalysisState &state, 6237a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 6247a1579acSMatthias Springer bool checkConsistencyOnly = false) { 6257a1579acSMatthias Springer // Helper function to iterate on aliases of `root` and capture the reads. 6267a1579acSMatthias Springer auto getAliasingReads = [&](DenseSet<OpOperand *> &res, Value root) { 6277a1579acSMatthias Springer aliasInfo.applyOnAliases(root, [&](Value alias) { 6287a1579acSMatthias Springer for (auto &use : alias.getUses()) 6297a1579acSMatthias Springer // Read to a value that aliases root. 6307a1579acSMatthias Springer if (state.bufferizesToMemoryRead(use)) 6317a1579acSMatthias Springer res.insert(&use); 6327a1579acSMatthias Springer }); 6337a1579acSMatthias Springer }; 6347a1579acSMatthias Springer 6357a1579acSMatthias Springer // Helper function to iterate on aliases of `root` and capture the writes. 6367a1579acSMatthias Springer auto getAliasingInplaceWrites = [&](DenseSet<OpOperand *> &res, Value root) { 6377a1579acSMatthias Springer aliasInfo.applyOnAliases(root, [&](Value alias) { 6387a1579acSMatthias Springer for (auto &use : alias.getUses()) 6397a1579acSMatthias Springer // Inplace write to a value that aliases root. 6407a1579acSMatthias Springer if (isInplaceMemoryWrite(use, aliasInfo, state)) 6417a1579acSMatthias Springer res.insert(&use); 6427a1579acSMatthias Springer }); 6437a1579acSMatthias Springer }; 6447a1579acSMatthias Springer 6457a1579acSMatthias Springer // Collect reads and writes of all aliases of OpOperand and OpResult. 6467a1579acSMatthias Springer DenseSet<OpOperand *> usesRead, usesWrite; 6477a1579acSMatthias Springer getAliasingReads(usesRead, operand.get()); 6487a1579acSMatthias Springer getAliasingInplaceWrites(usesWrite, operand.get()); 649585a8a32SMatthias Springer for (OpResult result : state.getAliasingOpResult(operand)) { 6507a1579acSMatthias Springer getAliasingReads(usesRead, result); 6517a1579acSMatthias Springer getAliasingInplaceWrites(usesWrite, result); 6527a1579acSMatthias Springer } 6537a1579acSMatthias Springer if (!checkConsistencyOnly && state.bufferizesToMemoryWrite(operand)) 6547a1579acSMatthias Springer usesWrite.insert(&operand); 6557a1579acSMatthias Springer 6567a1579acSMatthias Springer return hasReadAfterWriteInterference(usesRead, usesWrite, domInfo, state, 6577a1579acSMatthias Springer aliasInfo); 6587a1579acSMatthias Springer } 6597a1579acSMatthias Springer 6607a1579acSMatthias Springer /// Return true if bufferizing `opOperand` inplace would create a write to a 6617a1579acSMatthias Springer /// non-writable buffer. 6627a1579acSMatthias Springer static bool 6637a1579acSMatthias Springer wouldCreateWriteToNonWritableBuffer(OpOperand &opOperand, 6647a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 6659597b16aSMatthias Springer AnalysisState &state) { 6667a1579acSMatthias Springer // Certain buffers are not writeable: 6677a1579acSMatthias Springer // 1. A function bbArg that is not inplaceable or 6687a1579acSMatthias Springer // 2. A constant op. 6697a1579acSMatthias Springer bool nonWritable = 6707a1579acSMatthias Springer aliasesNonWritableBuffer(opOperand.get(), aliasInfo, state); 6717a1579acSMatthias Springer if (!nonWritable) 6727a1579acSMatthias Springer return false; 6737a1579acSMatthias Springer 6747a1579acSMatthias Springer // This is a problem only if the buffer is written to via some alias. 6757a1579acSMatthias Springer bool hasWrite = aliasesInPlaceWrite(opOperand.get(), aliasInfo, state) || 6767a1579acSMatthias Springer state.bufferizesToMemoryWrite(opOperand); 6777a1579acSMatthias Springer 678585a8a32SMatthias Springer for (OpResult opResult : state.getAliasingOpResult(opOperand)) 6797a1579acSMatthias Springer hasWrite |= aliasesInPlaceWrite(opResult, aliasInfo, state); 6807a1579acSMatthias Springer 6817a1579acSMatthias Springer return hasWrite; 6827a1579acSMatthias Springer } 6837a1579acSMatthias Springer 6847a1579acSMatthias Springer //===----------------------------------------------------------------------===// 6857a1579acSMatthias Springer // Bufferization analyses. 6867a1579acSMatthias Springer //===----------------------------------------------------------------------===// 6877a1579acSMatthias Springer 6887a1579acSMatthias Springer /// Determine if `operand` can be bufferized in-place. 6897a1579acSMatthias Springer static LogicalResult bufferizableInPlaceAnalysisImpl( 6909597b16aSMatthias Springer OpOperand &operand, BufferizationAliasInfo &aliasInfo, AnalysisState &state, 6919597b16aSMatthias Springer const DominanceInfo &domInfo) { 6927a1579acSMatthias Springer bool foundInterference = 6937a1579acSMatthias Springer wouldCreateWriteToNonWritableBuffer(operand, aliasInfo, state) || 6947a1579acSMatthias Springer wouldCreateReadAfterWriteInterference(operand, domInfo, state, aliasInfo); 6957a1579acSMatthias Springer 6967a1579acSMatthias Springer if (foundInterference) 6977a1579acSMatthias Springer aliasInfo.bufferizeOutOfPlace(operand); 6987a1579acSMatthias Springer else 6997a1579acSMatthias Springer aliasInfo.bufferizeInPlace(operand, state); 7007a1579acSMatthias Springer 7017a1579acSMatthias Springer return success(); 7027a1579acSMatthias Springer } 7037a1579acSMatthias Springer 7047a1579acSMatthias Springer /// Analyze the `ops` to determine which OpOperands are inplaceable. Walk ops in 7057a1579acSMatthias Springer /// reverse and bufferize ops greedily. This is a good starter heuristic. 7067a1579acSMatthias Springer /// 7077a1579acSMatthias Springer /// Even if an op does not read or write, it may still create an alias when 7087a1579acSMatthias Springer /// bufferized in-place. An example of such ops is tensor.extract_slice. 7097a1579acSMatthias Springer /// 7107a1579acSMatthias Springer /// Rationale for bufferizing `%1 = tensor.extract_slice %0[...]` inplace: 7117a1579acSMatthias Springer /// 7127a1579acSMatthias Springer /// When bufferized out of place, an ExtractSliceOp lowers to alloc + copy. This 7137a1579acSMatthias Springer /// cannot change the flow of information for either the source or the 7147a1579acSMatthias Springer /// result buffers. 7157a1579acSMatthias Springer /// 7167a1579acSMatthias Springer /// When bufferized inplace, an ExtractSliceOp does not by itself create any 7177a1579acSMatthias Springer /// read or write from memory. Instead, it has the effect of merging the alias 7187a1579acSMatthias Springer /// sets of the source and the result buffers. 7197a1579acSMatthias Springer /// 7207a1579acSMatthias Springer /// An analysis is required to ensure inplace bufferization would not result in 7217a1579acSMatthias Springer /// RaW dependence violations. 7227a1579acSMatthias Springer static LogicalResult inPlaceAnalysis(SmallVector<Operation *> &ops, 7237a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo, 7249597b16aSMatthias Springer AnalysisState &state, 7257a1579acSMatthias Springer const DominanceInfo &domInfo, 7267a1579acSMatthias Springer unsigned analysisFuzzerSeed = 0) { 7277a1579acSMatthias Springer if (analysisFuzzerSeed) { 7287a1579acSMatthias Springer // This is a fuzzer. For testing purposes only. Randomize the order in which 7297a1579acSMatthias Springer // operations are analyzed. The bufferization quality is likely worse, but 7307a1579acSMatthias Springer // we want to make sure that no assertions are triggered anywhere. 7317a1579acSMatthias Springer std::mt19937 g(analysisFuzzerSeed); 7327a1579acSMatthias Springer llvm::shuffle(ops.begin(), ops.end(), g); 7337a1579acSMatthias Springer } 7347a1579acSMatthias Springer 7357a1579acSMatthias Springer // Walk ops in reverse for better interference analysis. 7367a1579acSMatthias Springer for (Operation *op : reverse(ops)) 7377a1579acSMatthias Springer for (OpOperand &opOperand : op->getOpOperands()) 7387a1579acSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) 7397a1579acSMatthias Springer if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp(op)) 7407a1579acSMatthias Springer if (failed(bufferizableInPlaceAnalysisImpl(opOperand, aliasInfo, 7417a1579acSMatthias Springer state, domInfo))) 7427a1579acSMatthias Springer return failure(); 7437a1579acSMatthias Springer 7447a1579acSMatthias Springer return success(); 7457a1579acSMatthias Springer } 7467a1579acSMatthias Springer 7477a1579acSMatthias Springer /// Return true if the given op has a tensor result or a tensor operand. 7487a1579acSMatthias Springer static bool hasTensorSemantics(Operation *op) { 7497a1579acSMatthias Springer bool hasTensorResult = any_of(op->getResultTypes(), isaTensor); 7507a1579acSMatthias Springer bool hasTensorOperand = any_of(op->getOperandTypes(), isaTensor); 7517a1579acSMatthias Springer return hasTensorResult || hasTensorOperand; 7527a1579acSMatthias Springer } 7537a1579acSMatthias Springer 7547a1579acSMatthias Springer /// Analyze all ops that are contained in `op`. 7557a1579acSMatthias Springer static LogicalResult inPlaceAnalysis(Operation *op, 7567a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo, 7579597b16aSMatthias Springer AnalysisState &state, 7587a1579acSMatthias Springer const DominanceInfo &domInfo, 7597a1579acSMatthias Springer unsigned analysisFuzzerSeed = 0) { 7607a1579acSMatthias Springer // Collect ops so we can build our own reverse traversal. 7617a1579acSMatthias Springer SmallVector<Operation *> ops; 7627a1579acSMatthias Springer op->walk([&](Operation *op) { 7637a1579acSMatthias Springer // No tensors => no buffers. 7647a1579acSMatthias Springer if (!hasTensorSemantics(op)) 7657a1579acSMatthias Springer return; 7667a1579acSMatthias Springer ops.push_back(op); 7677a1579acSMatthias Springer }); 7687a1579acSMatthias Springer 7697a1579acSMatthias Springer return inPlaceAnalysis(ops, aliasInfo, state, domInfo, analysisFuzzerSeed); 7707a1579acSMatthias Springer } 7717a1579acSMatthias Springer 7727a1579acSMatthias Springer /// Analyze equivalence of tied OpResult/OpOperand pairs of the given ops. 7737a1579acSMatthias Springer static void equivalenceAnalysis(SmallVector<Operation *> &ops, 7747a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo, 7759597b16aSMatthias Springer AnalysisState &state) { 7767a1579acSMatthias Springer for (Operation *op : ops) 7777a1579acSMatthias Springer if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp(op)) 7787a1579acSMatthias Springer for (OpResult opResult : op->getOpResults()) 7797a1579acSMatthias Springer if (opResult.getType().isa<TensorType>()) 7807a1579acSMatthias Springer for (OpOperand *opOperand : 7817a1579acSMatthias Springer bufferizableOp.getAliasingOpOperand(opResult, state)) 7827a1579acSMatthias Springer if (state.isInPlace(*opOperand)) 7837a1579acSMatthias Springer if (bufferizableOp.bufferRelation(opResult, state) == 7847a1579acSMatthias Springer BufferRelation::Equivalent) 7857a1579acSMatthias Springer aliasInfo.unionEquivalenceClasses(opResult, opOperand->get()); 7867a1579acSMatthias Springer } 7877a1579acSMatthias Springer 7887a1579acSMatthias Springer /// Analyze equivalence of tied OpResult/OpOperand pairs of all ops contained 7897a1579acSMatthias Springer /// in `op`. 7907a1579acSMatthias Springer static void equivalenceAnalysis(Operation *op, 7917a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo, 7929597b16aSMatthias Springer AnalysisState &state) { 7937a1579acSMatthias Springer // Traverse ops in PostOrder: Nested ops first, then enclosing ops. 7947a1579acSMatthias Springer SmallVector<Operation *> ops; 7957a1579acSMatthias Springer op->walk<WalkOrder::PostOrder>([&](Operation *op) { 7967a1579acSMatthias Springer // No tensors => no buffers. 7977a1579acSMatthias Springer if (none_of(op->getResultTypes(), isaTensor)) 7987a1579acSMatthias Springer return; 7997a1579acSMatthias Springer ops.push_back(op); 8007a1579acSMatthias Springer }); 8017a1579acSMatthias Springer 8027a1579acSMatthias Springer equivalenceAnalysis(ops, aliasInfo, state); 8037a1579acSMatthias Springer } 8047a1579acSMatthias Springer 8057a1579acSMatthias Springer /// Assert that the current bufferization decisions are consistent. 8067a1579acSMatthias Springer static LogicalResult 8077a1579acSMatthias Springer checkAliasInfoConsistency(Operation *op, const DominanceInfo &domInfo, 8089597b16aSMatthias Springer AnalysisState &state, 8097a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo) { 8107a1579acSMatthias Springer const BufferizationOptions &options = state.getOptions(); 8117a1579acSMatthias Springer Operation *inconsistentOp = nullptr; 8127a1579acSMatthias Springer WalkResult walkResult = op->walk([&](Operation *op) { 8137a1579acSMatthias Springer if (auto bufferizableOp = options.dynCastBufferizableOp(op)) 8147a1579acSMatthias Springer for (OpOperand &opOperand : op->getOpOperands()) 8157a1579acSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) { 8167a1579acSMatthias Springer if (wouldCreateReadAfterWriteInterference( 8177a1579acSMatthias Springer opOperand, domInfo, state, aliasInfo, 8187a1579acSMatthias Springer /*checkConsistencyOnly=*/true)) { 8197a1579acSMatthias Springer // This error can happen if certain "mustBufferizeInPlace" interface 8207a1579acSMatthias Springer // methods are implemented incorrectly, such that the IR already has 8217a1579acSMatthias Springer // a RaW conflict before making any bufferization decisions. 8227a1579acSMatthias Springer inconsistentOp = op; 8237a1579acSMatthias Springer return WalkResult::interrupt(); 8247a1579acSMatthias Springer } 8257a1579acSMatthias Springer } 8267a1579acSMatthias Springer return WalkResult::advance(); 8277a1579acSMatthias Springer }); 8287a1579acSMatthias Springer 8297a1579acSMatthias Springer if (walkResult.wasInterrupted()) 8307a1579acSMatthias Springer return inconsistentOp->emitError("input IR has RaW conflict"); 8317a1579acSMatthias Springer return success(); 8327a1579acSMatthias Springer } 8337a1579acSMatthias Springer 8347a1579acSMatthias Springer /// Annotate the IR with the result of the analysis. For testing/debugging only. 8357a1579acSMatthias Springer static void 8367a1579acSMatthias Springer annotateOpsWithBufferizationMarkers(Operation *op, 8377a1579acSMatthias Springer const BufferizationAliasInfo &aliasInfo, 8389597b16aSMatthias Springer AnalysisState &state) { 8397a1579acSMatthias Springer op->walk([&](Operation *op) { 8407a1579acSMatthias Springer if (auto bufferizableOp = state.getOptions().dynCastBufferizableOp(op)) 8417a1579acSMatthias Springer for (OpOperand &opOperand : op->getOpOperands()) 8427a1579acSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) 8437a1579acSMatthias Springer setInPlaceOpOperand(opOperand, aliasInfo.isInPlace(opOperand)); 8447a1579acSMatthias Springer }); 8457a1579acSMatthias Springer } 8467a1579acSMatthias Springer 8477a1579acSMatthias Springer /// Assert that IR is in destination-passing style. I.e., every value that is 8487a1579acSMatthias Springer /// returned or yielded from a block is: 8497a1579acSMatthias Springer /// * aliasing a bbArg of that block or a parent block, or 8507a1579acSMatthias Springer /// * aliasing an OpResult of a op in a parent block. 8517a1579acSMatthias Springer /// 8527a1579acSMatthias Springer /// Example: 8537a1579acSMatthias Springer /// ``` 8547a1579acSMatthias Springer /// %0 = "some_op" : tensor<?xf32> 8557a1579acSMatthias Springer /// %1 = scf.if %c -> (tensor<?xf32>) { 8567a1579acSMatthias Springer /// scf.yield %0 : tensor<?xf32> 8577a1579acSMatthias Springer /// } else { 858*ffdbecccSMatthias Springer /// %t = linalg.alloc_tensor : tensor<?xf32> 8597a1579acSMatthias Springer /// scf.yield %t : tensor<?xf32> 8607a1579acSMatthias Springer /// } 8617a1579acSMatthias Springer /// ``` 8627a1579acSMatthias Springer /// In the above example, the first scf.yield op satifies destination-passing 8637a1579acSMatthias Springer /// style because the yielded value %0 is defined in the parent block. The 8647a1579acSMatthias Springer /// second scf.yield op does not satisfy destination-passing style because the 8657a1579acSMatthias Springer /// yielded value %t is defined in the same block as the scf.yield op. 8667a1579acSMatthias Springer // TODO: The current implementation checks for equivalent values instead of 8677a1579acSMatthias Springer // aliasing values, which is stricter than needed. We can currently not check 8687a1579acSMatthias Springer // for aliasing values because the analysis is a maybe-alias analysis and we 8697a1579acSMatthias Springer // need a must-alias analysis here. 870cdb7675cSMatthias Springer static LogicalResult 8719597b16aSMatthias Springer assertDestinationPassingStyle(Operation *op, AnalysisState &state, 8727a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo, 873cdb7675cSMatthias Springer SmallVector<Operation *> &newOps) { 8747a1579acSMatthias Springer LogicalResult status = success(); 8757a1579acSMatthias Springer DominanceInfo domInfo(op); 8767a1579acSMatthias Springer op->walk([&](Operation *returnOp) { 8773b426868SMatthias Springer if (!isRegionReturnLike(returnOp) || 8783b426868SMatthias Springer !state.getOptions().isOpAllowed(returnOp)) 8797a1579acSMatthias Springer return WalkResult::advance(); 8807a1579acSMatthias Springer 8817a1579acSMatthias Springer for (OpOperand &returnValOperand : returnOp->getOpOperands()) { 8827a1579acSMatthias Springer Value returnVal = returnValOperand.get(); 8837a1579acSMatthias Springer // Skip non-tensor values. 8847a1579acSMatthias Springer if (!returnVal.getType().isa<TensorType>()) 8857a1579acSMatthias Springer continue; 8867a1579acSMatthias Springer 8877a1579acSMatthias Springer bool foundEquivValue = false; 8887a1579acSMatthias Springer aliasInfo.applyOnEquivalenceClass(returnVal, [&](Value equivVal) { 8897a1579acSMatthias Springer if (auto bbArg = equivVal.dyn_cast<BlockArgument>()) { 8907a1579acSMatthias Springer Operation *definingOp = bbArg.getOwner()->getParentOp(); 8917a1579acSMatthias Springer if (definingOp->isProperAncestor(returnOp)) 8927a1579acSMatthias Springer foundEquivValue = true; 8937a1579acSMatthias Springer return; 8947a1579acSMatthias Springer } 8957a1579acSMatthias Springer 8967a1579acSMatthias Springer Operation *definingOp = equivVal.getDefiningOp(); 8977a1579acSMatthias Springer if (definingOp->getBlock()->findAncestorOpInBlock( 8987a1579acSMatthias Springer *returnOp->getParentOp())) 8997a1579acSMatthias Springer // Skip ops that happen after `returnOp` and parent ops. 9007a1579acSMatthias Springer if (happensBefore(definingOp, returnOp, domInfo)) 9017a1579acSMatthias Springer foundEquivValue = true; 9027a1579acSMatthias Springer }); 9037a1579acSMatthias Springer 9047a1579acSMatthias Springer if (!foundEquivValue) 9057a1579acSMatthias Springer status = 9067a1579acSMatthias Springer returnOp->emitError() 9077a1579acSMatthias Springer << "operand #" << returnValOperand.getOperandNumber() 9087a1579acSMatthias Springer << " of ReturnLike op does not satisfy destination passing style"; 9097a1579acSMatthias Springer } 9107a1579acSMatthias Springer 9117a1579acSMatthias Springer return WalkResult::advance(); 9127a1579acSMatthias Springer }); 9137a1579acSMatthias Springer 9147a1579acSMatthias Springer return status; 9157a1579acSMatthias Springer } 9167a1579acSMatthias Springer 9177a1579acSMatthias Springer LogicalResult bufferization::analyzeOp(Operation *op, 9189597b16aSMatthias Springer OneShotAnalysisState &state) { 9197a1579acSMatthias Springer DominanceInfo domInfo(op); 9207a1579acSMatthias Springer BufferizationAliasInfo &aliasInfo = state.getAliasInfo(); 9217a1579acSMatthias Springer const auto &options = 9229597b16aSMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 9237a1579acSMatthias Springer 924d6dab38aSMatthias Springer // Catch incorrect API usage. 925d6dab38aSMatthias Springer assert((state.hasDialectState(func::FuncDialect::getDialectNamespace()) || 926d6dab38aSMatthias Springer !options.bufferizeFunctionBoundaries) && 927d6dab38aSMatthias Springer "must use ModuleBufferize to bufferize function boundaries"); 928d6dab38aSMatthias Springer 9297a1579acSMatthias Springer if (failed(checkAliasInfoConsistency(op, domInfo, state, aliasInfo))) 9307a1579acSMatthias Springer return failure(); 9317a1579acSMatthias Springer 9327a1579acSMatthias Springer // If the analysis fails, just return. 9337a1579acSMatthias Springer if (failed(inPlaceAnalysis(op, aliasInfo, state, domInfo, 9347a1579acSMatthias Springer options.analysisFuzzerSeed))) 9357a1579acSMatthias Springer return failure(); 9367a1579acSMatthias Springer equivalenceAnalysis(op, aliasInfo, state); 9377a1579acSMatthias Springer 938cdb7675cSMatthias Springer for (const PostAnalysisStepFn &fn : options.postAnalysisSteps) { 9397a1579acSMatthias Springer SmallVector<Operation *> newOps; 940cdb7675cSMatthias Springer if (failed(fn(op, state, aliasInfo, newOps))) 9417a1579acSMatthias Springer return failure(); 942cdb7675cSMatthias Springer // Analyze ops that were created by the PostAnalysisStepFn. 9437a1579acSMatthias Springer if (failed(inPlaceAnalysis(newOps, aliasInfo, state, domInfo))) 9447a1579acSMatthias Springer return failure(); 9457a1579acSMatthias Springer equivalenceAnalysis(newOps, aliasInfo, state); 9467a1579acSMatthias Springer } 9477a1579acSMatthias Springer 948d1d79920SMatthias Springer bool failedAnalysis = false; 949855a11eeSMatthias Springer if (!options.allowReturnAllocs) { 9507a1579acSMatthias Springer SmallVector<Operation *> newOps; 951d1d79920SMatthias Springer failedAnalysis |= 952d1d79920SMatthias Springer failed(assertDestinationPassingStyle(op, state, aliasInfo, newOps)); 9537a1579acSMatthias Springer } 9547a1579acSMatthias Springer 955988748c0SMatthias Springer // Gather some extra analysis data. 9569e24f0f4SMatthias Springer state.gatherYieldedTensors(op); 957988748c0SMatthias Springer state.gatherUndefinedTensorUses(op); 9589e24f0f4SMatthias Springer 9594ec00fb3SMatthias Springer // Analysis verification: After setting up alias/equivalence sets, each op 9604ec00fb3SMatthias Springer // can check for expected invariants/limitations and fail the analysis if 9614ec00fb3SMatthias Springer // necessary. 9624ec00fb3SMatthias Springer op->walk([&](Operation *op) { 9634ec00fb3SMatthias Springer if (BufferizableOpInterface bufferizableOp = 9644ec00fb3SMatthias Springer options.dynCastBufferizableOp(op)) 965d1d79920SMatthias Springer failedAnalysis |= failed(bufferizableOp.verifyAnalysis(state)); 9664ec00fb3SMatthias Springer }); 9674ec00fb3SMatthias Springer 9687a1579acSMatthias Springer // Annotate operations if we only want to report the analysis. 9697a1579acSMatthias Springer if (options.testAnalysisOnly) 9707a1579acSMatthias Springer annotateOpsWithBufferizationMarkers(op, aliasInfo, state); 9717a1579acSMatthias Springer 972d1d79920SMatthias Springer return success(!failedAnalysis); 9737a1579acSMatthias Springer } 9747a1579acSMatthias Springer 9759597b16aSMatthias Springer LogicalResult 9769597b16aSMatthias Springer bufferization::runOneShotBufferize(Operation *op, 9779597b16aSMatthias Springer const OneShotBufferizationOptions &options) { 9789597b16aSMatthias Springer OneShotAnalysisState state(op, options); 9797a1579acSMatthias Springer if (failed(analyzeOp(op, state))) 9807a1579acSMatthias Springer return failure(); 981d2dacde5SMatthias Springer if (options.testAnalysisOnly) 9827a1579acSMatthias Springer return success(); 9837a1579acSMatthias Springer return bufferizeOp(op, state); 9847a1579acSMatthias Springer } 985