119efe141SMatthias Springer //===- BufferizableOpInterfaceImpl.cpp - Impl. of BufferizableOpInterface -===// 219efe141SMatthias Springer // 319efe141SMatthias Springer // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 419efe141SMatthias Springer // See https://llvm.org/LICENSE.txt for license information. 519efe141SMatthias Springer // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 619efe141SMatthias Springer // 719efe141SMatthias Springer //===----------------------------------------------------------------------===// 819efe141SMatthias Springer 98b68da2cSAlex Zinenko #include "mlir/Dialect/SCF/Transforms/BufferizableOpInterfaceImpl.h" 1019efe141SMatthias Springer 1119efe141SMatthias Springer #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h" 1219efe141SMatthias Springer #include "mlir/Dialect/Bufferization/IR/Bufferization.h" 131e1eeae8SMatthias Springer #include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h" 14eda6f907SRiver Riddle #include "mlir/Dialect/MemRef/IR/MemRef.h" 158b68da2cSAlex Zinenko #include "mlir/Dialect/SCF/IR/SCF.h" 1672de7588SNicolas Vasilache #include "mlir/Dialect/Tensor/IR/Tensor.h" 17a0f843fdSNicolas Vasilache #include "mlir/Dialect/Utils/StaticValueUtils.h" 1819efe141SMatthias Springer #include "mlir/IR/Dialect.h" 1919efe141SMatthias Springer #include "mlir/IR/Operation.h" 2019efe141SMatthias Springer #include "mlir/IR/PatternMatch.h" 2119efe141SMatthias Springer 2219efe141SMatthias Springer using namespace mlir; 2319efe141SMatthias Springer using namespace mlir::bufferization; 2419efe141SMatthias Springer using namespace mlir::scf; 2519efe141SMatthias Springer 2619efe141SMatthias Springer namespace mlir { 2719efe141SMatthias Springer namespace scf { 2819efe141SMatthias Springer namespace { 2919efe141SMatthias Springer 3019efe141SMatthias Springer // bufferization.to_memref is not allowed to change the rank. 3119efe141SMatthias Springer static void ensureToMemrefOpIsValid(Value tensor, Type memrefType) { 3219efe141SMatthias Springer #ifndef NDEBUG 3319efe141SMatthias Springer auto rankedTensorType = tensor.getType().dyn_cast<RankedTensorType>(); 3419efe141SMatthias Springer assert((!rankedTensorType || (memrefType.cast<MemRefType>().getRank() == 3519efe141SMatthias Springer rankedTensorType.getRank())) && 3619efe141SMatthias Springer "to_memref would be invalid: mismatching ranks"); 3719efe141SMatthias Springer #endif 3819efe141SMatthias Springer } 3919efe141SMatthias Springer 4019efe141SMatthias Springer /// Bufferization of scf.execute_region. Can be analyzed, but bufferization not 4119efe141SMatthias Springer /// fully implemented at the moment. 4219efe141SMatthias Springer struct ExecuteRegionOpInterface 4319efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<ExecuteRegionOpInterface, 4419efe141SMatthias Springer scf::ExecuteRegionOp> { 4519efe141SMatthias Springer SmallVector<OpOperand *> 4619efe141SMatthias Springer getAliasingOpOperand(Operation *op, OpResult opResult, 479597b16aSMatthias Springer const AnalysisState &state) const { 4819efe141SMatthias Springer // ExecuteRegionOps do not have tensor OpOperands. The yielded value can be 4919efe141SMatthias Springer // any SSA value that is in scope. To allow for use-def chain traversal 5019efe141SMatthias Springer // through ExecuteRegionOps in the analysis, the corresponding yield value 5119efe141SMatthias Springer // is considered to be aliasing with the result. 5219efe141SMatthias Springer auto executeRegionOp = cast<scf::ExecuteRegionOp>(op); 5319efe141SMatthias Springer size_t resultNum = std::distance(op->getOpResults().begin(), 5419efe141SMatthias Springer llvm::find(op->getOpResults(), opResult)); 5519efe141SMatthias Springer // TODO: Support multiple blocks. 5619efe141SMatthias Springer assert(executeRegionOp.getRegion().getBlocks().size() == 1 && 5719efe141SMatthias Springer "expected exactly 1 block"); 5819efe141SMatthias Springer auto yieldOp = dyn_cast<scf::YieldOp>( 5919efe141SMatthias Springer executeRegionOp.getRegion().front().getTerminator()); 6019efe141SMatthias Springer assert(yieldOp && "expected scf.yield terminator in scf.execute_region"); 6119efe141SMatthias Springer return {&yieldOp->getOpOperand(resultNum)}; 6219efe141SMatthias Springer } 6319efe141SMatthias Springer 6419efe141SMatthias Springer // TODO: For better bufferization results, this could return `true` only if 6519efe141SMatthias Springer // there is a memory write in the region. 6619efe141SMatthias Springer bool isMemoryWrite(Operation *op, OpResult opResult, 679597b16aSMatthias Springer const AnalysisState &state) const { 6819efe141SMatthias Springer // Similar to scf.if, results of this op are always considered memory writes 6919efe141SMatthias Springer // in the analysis. This is a useful pattern for all ops that have tensor 7019efe141SMatthias Springer // OpResults but no tensor OpOperands. By default, `isMemoryWrite` is 7119efe141SMatthias Springer // implemented in terms of `bufferizesToMemoryWrite`, which does not work on 7219efe141SMatthias Springer // ops without OpOperands. 7319efe141SMatthias Springer return true; 7419efe141SMatthias Springer } 7519efe141SMatthias Springer 7619efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 77b55d55ecSMatthias Springer const BufferizationOptions &options) const { 7819efe141SMatthias Springer auto executeRegionOp = cast<scf::ExecuteRegionOp>(op); 798e691e1fSMatthias Springer assert(executeRegionOp.getRegion().getBlocks().size() == 1 && 808e691e1fSMatthias Springer "only 1 block supported"); 818e691e1fSMatthias Springer auto yieldOp = 828e691e1fSMatthias Springer cast<scf::YieldOp>(executeRegionOp.getRegion().front().getTerminator()); 838e691e1fSMatthias Springer TypeRange newResultTypes(yieldOp.getResults()); 8419efe141SMatthias Springer 8519efe141SMatthias Springer // Create new op and move over region. 8619efe141SMatthias Springer auto newOp = 8719efe141SMatthias Springer rewriter.create<scf::ExecuteRegionOp>(op->getLoc(), newResultTypes); 8819efe141SMatthias Springer newOp.getRegion().takeBody(executeRegionOp.getRegion()); 8919efe141SMatthias Springer 9019efe141SMatthias Springer // Update all uses of the old op. 9119efe141SMatthias Springer rewriter.setInsertionPointAfter(newOp); 9219efe141SMatthias Springer SmallVector<Value> newResults; 93bb6119ebSMehdi Amini for (const auto &it : llvm::enumerate(executeRegionOp->getResultTypes())) { 9419efe141SMatthias Springer if (it.value().isa<TensorType>()) { 9519efe141SMatthias Springer newResults.push_back(rewriter.create<bufferization::ToTensorOp>( 9619efe141SMatthias Springer executeRegionOp.getLoc(), newOp->getResult(it.index()))); 9719efe141SMatthias Springer } else { 9819efe141SMatthias Springer newResults.push_back(newOp->getResult(it.index())); 9919efe141SMatthias Springer } 10019efe141SMatthias Springer } 10119efe141SMatthias Springer 10219efe141SMatthias Springer // Replace old op. 10319efe141SMatthias Springer rewriter.replaceOp(executeRegionOp, newResults); 10419efe141SMatthias Springer 10519efe141SMatthias Springer return success(); 10619efe141SMatthias Springer } 10719efe141SMatthias Springer 10819efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 1099597b16aSMatthias Springer const AnalysisState &state) const { 11019efe141SMatthias Springer return BufferRelation::Equivalent; 11119efe141SMatthias Springer } 11219efe141SMatthias Springer }; 11319efe141SMatthias Springer 11419efe141SMatthias Springer /// Bufferization of scf.if. Replace with a new scf.if that yields memrefs. 11519efe141SMatthias Springer struct IfOpInterface 11619efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<IfOpInterface, scf::IfOp> { 11719efe141SMatthias Springer SmallVector<OpOperand *> 11819efe141SMatthias Springer getAliasingOpOperand(Operation *op, OpResult opResult, 1199597b16aSMatthias Springer const AnalysisState &state) const { 12019efe141SMatthias Springer // IfOps do not have tensor OpOperands. The yielded value can be any SSA 12119efe141SMatthias Springer // value that is in scope. To allow for use-def chain traversal through 12219efe141SMatthias Springer // IfOps in the analysis, both corresponding yield values from the then/else 12319efe141SMatthias Springer // branches are considered to be aliasing with the result. 12419efe141SMatthias Springer auto ifOp = cast<scf::IfOp>(op); 12519efe141SMatthias Springer size_t resultNum = std::distance(op->getOpResults().begin(), 12619efe141SMatthias Springer llvm::find(op->getOpResults(), opResult)); 12719efe141SMatthias Springer return {&ifOp.thenYield()->getOpOperand(resultNum), 12819efe141SMatthias Springer &ifOp.elseYield()->getOpOperand(resultNum)}; 12919efe141SMatthias Springer } 13019efe141SMatthias Springer 13119efe141SMatthias Springer // TODO: For better bufferization results, this could return `true` only if 13219efe141SMatthias Springer // there is a memory write in one (or both) of the branches. Since this is not 13319efe141SMatthias Springer // allowed at the moment, we should never encounter scf.ifs that yield 13419efe141SMatthias Springer // unmodified tensors. Such scf.yield ops could just fold away. 13519efe141SMatthias Springer bool isMemoryWrite(Operation *op, OpResult opResult, 1369597b16aSMatthias Springer const AnalysisState &state) const { 13719efe141SMatthias Springer // IfOp results are always considered memory writes in the analysis. This 13819efe141SMatthias Springer // design decision simplifies the analysis considerably. E.g., consider the 13919efe141SMatthias Springer // following test case: 14019efe141SMatthias Springer // 14119efe141SMatthias Springer // %0 = "some_writing_op" : tensor<?xf32> 14219efe141SMatthias Springer // %r = scf.if %c -> (tensor<?xf32>) { 14319efe141SMatthias Springer // scf.yield %0 14419efe141SMatthias Springer // } else { 14519efe141SMatthias Springer // %1 = "another_writing_op"(%0) : tensor<?xf32> 14619efe141SMatthias Springer // } 14719efe141SMatthias Springer // "some_reading_op"(%r) 14819efe141SMatthias Springer // 14919efe141SMatthias Springer // "another_writing_op" in the above example should be able to bufferize 15019efe141SMatthias Springer // inplace in the absence of another read of %0. However, if the scf.if op 15119efe141SMatthias Springer // would not be considered a "write", the analysis would detect the 15219efe141SMatthias Springer // following conflict: 15319efe141SMatthias Springer // 15419efe141SMatthias Springer // * read = some_reading_op 15519efe141SMatthias Springer // * lastWrite = %0 (Note: The last write of %r would be a set: {%0, %1}.) 15619efe141SMatthias Springer // * conflictingWrite = %1 15719efe141SMatthias Springer // 15819efe141SMatthias Springer // For more details, check the "scf.IfOp" section of the design document. 15919efe141SMatthias Springer return true; 16019efe141SMatthias Springer } 16119efe141SMatthias Springer 16219efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 163b55d55ecSMatthias Springer const BufferizationOptions &options) const { 1648e691e1fSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 16519efe141SMatthias Springer auto ifOp = cast<scf::IfOp>(op); 1668e691e1fSMatthias Springer auto thenYieldOp = cast<scf::YieldOp>(ifOp.thenBlock()->getTerminator()); 1678e691e1fSMatthias Springer auto elseYieldOp = cast<scf::YieldOp>(ifOp.elseBlock()->getTerminator()); 16819efe141SMatthias Springer 1698e691e1fSMatthias Springer // Reconcile type mismatches between then/else branches by inserting memref 1708e691e1fSMatthias Springer // casts. 1718e691e1fSMatthias Springer SmallVector<Value> thenResults, elseResults; 1728e691e1fSMatthias Springer bool insertedCast = false; 1738e691e1fSMatthias Springer for (unsigned i = 0; i < thenYieldOp.getResults().size(); ++i) { 1748e691e1fSMatthias Springer Value thenValue = thenYieldOp.getResults()[i]; 1758e691e1fSMatthias Springer Value elseValue = elseYieldOp.getResults()[i]; 1768e691e1fSMatthias Springer if (thenValue.getType() == elseValue.getType()) { 1778e691e1fSMatthias Springer thenResults.push_back(thenValue); 1788e691e1fSMatthias Springer elseResults.push_back(elseValue); 1798e691e1fSMatthias Springer continue; 18019efe141SMatthias Springer } 1818e691e1fSMatthias Springer 1828e691e1fSMatthias Springer // Type mismatch between then/else yield value. Cast both to a memref type 1838e691e1fSMatthias Springer // with a fully dynamic layout map. 1848e691e1fSMatthias Springer auto thenMemrefType = thenValue.getType().cast<BaseMemRefType>(); 1858e691e1fSMatthias Springer auto elseMemrefType = elseValue.getType().cast<BaseMemRefType>(); 1868e691e1fSMatthias Springer if (thenMemrefType.getMemorySpaceAsInt() != 1878e691e1fSMatthias Springer elseMemrefType.getMemorySpaceAsInt()) 1888e691e1fSMatthias Springer return op->emitError("inconsistent memory space on then/else branches"); 1898e691e1fSMatthias Springer rewriter.setInsertionPoint(thenYieldOp); 1908e691e1fSMatthias Springer BaseMemRefType memrefType = getMemRefTypeWithFullyDynamicLayout( 1918e691e1fSMatthias Springer ifOp.getResultTypes()[i].cast<TensorType>(), 1928e691e1fSMatthias Springer thenMemrefType.getMemorySpaceAsInt()); 1938e691e1fSMatthias Springer thenResults.push_back(rewriter.create<memref::CastOp>( 1948e691e1fSMatthias Springer thenYieldOp.getLoc(), memrefType, thenValue)); 1958e691e1fSMatthias Springer rewriter.setInsertionPoint(elseYieldOp); 1968e691e1fSMatthias Springer elseResults.push_back(rewriter.create<memref::CastOp>( 1978e691e1fSMatthias Springer elseYieldOp.getLoc(), memrefType, elseValue)); 1988e691e1fSMatthias Springer insertedCast = true; 1998e691e1fSMatthias Springer } 2008e691e1fSMatthias Springer 2018e691e1fSMatthias Springer if (insertedCast) { 2028e691e1fSMatthias Springer rewriter.setInsertionPoint(thenYieldOp); 2038e691e1fSMatthias Springer rewriter.replaceOpWithNewOp<scf::YieldOp>(thenYieldOp, thenResults); 2048e691e1fSMatthias Springer rewriter.setInsertionPoint(elseYieldOp); 2058e691e1fSMatthias Springer rewriter.replaceOpWithNewOp<scf::YieldOp>(elseYieldOp, elseResults); 20619efe141SMatthias Springer } 20719efe141SMatthias Springer 20819efe141SMatthias Springer // Create new op. 2098e691e1fSMatthias Springer rewriter.setInsertionPoint(ifOp); 2108e691e1fSMatthias Springer ValueRange resultsValueRange(thenResults); 2118e691e1fSMatthias Springer TypeRange newTypes(resultsValueRange); 21219efe141SMatthias Springer auto newIfOp = 21319efe141SMatthias Springer rewriter.create<scf::IfOp>(ifOp.getLoc(), newTypes, ifOp.getCondition(), 21419efe141SMatthias Springer /*withElseRegion=*/true); 21519efe141SMatthias Springer 21619efe141SMatthias Springer // Move over then/else blocks. 21719efe141SMatthias Springer rewriter.mergeBlocks(ifOp.thenBlock(), newIfOp.thenBlock()); 21819efe141SMatthias Springer rewriter.mergeBlocks(ifOp.elseBlock(), newIfOp.elseBlock()); 21919efe141SMatthias Springer 22019efe141SMatthias Springer // Replace op results. 22119efe141SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newIfOp->getResults()); 22219efe141SMatthias Springer 22319efe141SMatthias Springer return success(); 22419efe141SMatthias Springer } 22519efe141SMatthias Springer 22619efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 2279597b16aSMatthias Springer const AnalysisState &state) const { 22819efe141SMatthias Springer // IfOp results are equivalent to their corresponding yield values if both 22919efe141SMatthias Springer // yield values are equivalent to each other. 23019efe141SMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 23119efe141SMatthias Springer SmallVector<OpOperand *> yieldValues = 23219efe141SMatthias Springer bufferizableOp.getAliasingOpOperand(opResult, state); 23319efe141SMatthias Springer assert(yieldValues.size() == 2 && "expected 2 yield values"); 23419efe141SMatthias Springer bool equivalentYields = state.areEquivalentBufferizedValues( 23519efe141SMatthias Springer yieldValues[0]->get(), yieldValues[1]->get()); 23619efe141SMatthias Springer return equivalentYields ? BufferRelation::Equivalent : BufferRelation::None; 23719efe141SMatthias Springer } 23819efe141SMatthias Springer }; 23919efe141SMatthias Springer 240417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the indices of all values 241417e1c7dSMatthias Springer /// that have a tensor type. 242417e1c7dSMatthias Springer static DenseSet<int64_t> getTensorIndices(ValueRange values) { 243417e1c7dSMatthias Springer DenseSet<int64_t> result; 244417e1c7dSMatthias Springer for (const auto &it : llvm::enumerate(values)) 245417e1c7dSMatthias Springer if (it.value().getType().isa<TensorType>()) 246417e1c7dSMatthias Springer result.insert(it.index()); 247417e1c7dSMatthias Springer return result; 248417e1c7dSMatthias Springer } 249417e1c7dSMatthias Springer 250417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the indices of all 251417e1c7dSMatthias Springer /// bbArg/yielded value pairs who's buffer relation is "Equivalent". 252a5d09c63SMatthias Springer DenseSet<int64_t> getEquivalentBuffers(Block::BlockArgListType bbArgs, 253417e1c7dSMatthias Springer ValueRange yieldedValues, 254417e1c7dSMatthias Springer const AnalysisState &state) { 255996834e6SMatthias Springer unsigned int minSize = std::min(bbArgs.size(), yieldedValues.size()); 256417e1c7dSMatthias Springer DenseSet<int64_t> result; 257996834e6SMatthias Springer for (unsigned int i = 0; i < minSize; ++i) { 258996834e6SMatthias Springer if (!bbArgs[i].getType().isa<TensorType>() || 259996834e6SMatthias Springer !yieldedValues[i].getType().isa<TensorType>()) 260417e1c7dSMatthias Springer continue; 261996834e6SMatthias Springer if (state.areEquivalentBufferizedValues(bbArgs[i], yieldedValues[i])) 262996834e6SMatthias Springer result.insert(i); 263417e1c7dSMatthias Springer } 264417e1c7dSMatthias Springer return result; 265417e1c7dSMatthias Springer } 266417e1c7dSMatthias Springer 267417e1c7dSMatthias Springer /// Helper function for loop bufferization. Cast the given buffer to the given 268417e1c7dSMatthias Springer /// memref type. 269417e1c7dSMatthias Springer static Value castBuffer(OpBuilder &b, Value buffer, Type type) { 270417e1c7dSMatthias Springer assert(type.isa<BaseMemRefType>() && "expected BaseMemRefType"); 271417e1c7dSMatthias Springer assert(buffer.getType().isa<BaseMemRefType>() && "expected BaseMemRefType"); 272417e1c7dSMatthias Springer // If the buffer already has the correct type, no cast is needed. 273417e1c7dSMatthias Springer if (buffer.getType() == type) 274417e1c7dSMatthias Springer return buffer; 275417e1c7dSMatthias Springer // TODO: In case `type` has a layout map that is not the fully dynamic 276417e1c7dSMatthias Springer // one, we may not be able to cast the buffer. In that case, the loop 277417e1c7dSMatthias Springer // iter_arg's layout map must be changed (see uses of `castBuffer`). 278417e1c7dSMatthias Springer assert(memref::CastOp::areCastCompatible(buffer.getType(), type) && 279417e1c7dSMatthias Springer "scf.while op bufferization: cast incompatible"); 280417e1c7dSMatthias Springer return b.create<memref::CastOp>(buffer.getLoc(), type, buffer).getResult(); 281417e1c7dSMatthias Springer } 282417e1c7dSMatthias Springer 283417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the bufferized values of the 284417e1c7dSMatthias Springer /// given OpOperands. If an operand is not a tensor, return the original value. 2855d50f51cSMatthias Springer static FailureOr<SmallVector<Value>> 2865d50f51cSMatthias Springer getBuffers(RewriterBase &rewriter, MutableArrayRef<OpOperand> operands, 287b55d55ecSMatthias Springer const BufferizationOptions &options) { 288417e1c7dSMatthias Springer SmallVector<Value> result; 289417e1c7dSMatthias Springer for (OpOperand &opOperand : operands) { 290417e1c7dSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) { 2915d50f51cSMatthias Springer FailureOr<Value> resultBuffer = 2925d50f51cSMatthias Springer getBuffer(rewriter, opOperand.get(), options); 2935d50f51cSMatthias Springer if (failed(resultBuffer)) 2945d50f51cSMatthias Springer return failure(); 2955d50f51cSMatthias Springer result.push_back(*resultBuffer); 296417e1c7dSMatthias Springer } else { 297417e1c7dSMatthias Springer result.push_back(opOperand.get()); 298417e1c7dSMatthias Springer } 299417e1c7dSMatthias Springer } 300417e1c7dSMatthias Springer return result; 301417e1c7dSMatthias Springer } 302417e1c7dSMatthias Springer 303417e1c7dSMatthias Springer /// Helper function for loop bufferization. Compute the buffer that should be 304b3ebe3beSMatthias Springer /// yielded from a loop block (loop body or loop condition). 3055d50f51cSMatthias Springer static FailureOr<Value> getYieldedBuffer(RewriterBase &rewriter, Value tensor, 306b55d55ecSMatthias Springer BaseMemRefType type, 307b55d55ecSMatthias Springer const BufferizationOptions &options) { 308417e1c7dSMatthias Springer assert(tensor.getType().isa<TensorType>() && "expected tensor"); 309417e1c7dSMatthias Springer ensureToMemrefOpIsValid(tensor, type); 3105d50f51cSMatthias Springer FailureOr<Value> yieldedVal = getBuffer(rewriter, tensor, options); 3115d50f51cSMatthias Springer if (failed(yieldedVal)) 3125d50f51cSMatthias Springer return failure(); 3135d50f51cSMatthias Springer return castBuffer(rewriter, *yieldedVal, type); 314417e1c7dSMatthias Springer } 315417e1c7dSMatthias Springer 316417e1c7dSMatthias Springer /// Helper function for loop bufferization. Given a range of values, apply 317417e1c7dSMatthias Springer /// `func` to those marked in `tensorIndices`. Otherwise, store the unmodified 318417e1c7dSMatthias Springer /// value in the result vector. 3195d50f51cSMatthias Springer static FailureOr<SmallVector<Value>> 320417e1c7dSMatthias Springer convertTensorValues(ValueRange values, const DenseSet<int64_t> &tensorIndices, 3215d50f51cSMatthias Springer llvm::function_ref<FailureOr<Value>(Value, int64_t)> func) { 322417e1c7dSMatthias Springer SmallVector<Value> result; 323417e1c7dSMatthias Springer for (const auto &it : llvm::enumerate(values)) { 324417e1c7dSMatthias Springer size_t idx = it.index(); 325417e1c7dSMatthias Springer Value val = it.value(); 3265d50f51cSMatthias Springer if (tensorIndices.contains(idx)) { 3275d50f51cSMatthias Springer FailureOr<Value> maybeVal = func(val, idx); 3285d50f51cSMatthias Springer if (failed(maybeVal)) 3295d50f51cSMatthias Springer return failure(); 3305d50f51cSMatthias Springer result.push_back(*maybeVal); 3315d50f51cSMatthias Springer } else { 3325d50f51cSMatthias Springer result.push_back(val); 3335d50f51cSMatthias Springer } 334417e1c7dSMatthias Springer } 335417e1c7dSMatthias Springer return result; 336417e1c7dSMatthias Springer } 337417e1c7dSMatthias Springer 338417e1c7dSMatthias Springer /// Helper function for loop bufferization. Given a list of pre-bufferization 339417e1c7dSMatthias Springer /// yielded values, compute the list of bufferized yielded values. 3405d50f51cSMatthias Springer FailureOr<SmallVector<Value>> 3415d50f51cSMatthias Springer getYieldedValues(RewriterBase &rewriter, ValueRange values, 342417e1c7dSMatthias Springer TypeRange bufferizedTypes, 343417e1c7dSMatthias Springer const DenseSet<int64_t> &tensorIndices, 344b55d55ecSMatthias Springer const BufferizationOptions &options) { 345417e1c7dSMatthias Springer return convertTensorValues( 346417e1c7dSMatthias Springer values, tensorIndices, [&](Value val, int64_t index) { 347417e1c7dSMatthias Springer return getYieldedBuffer(rewriter, val, 348417e1c7dSMatthias Springer bufferizedTypes[index].cast<BaseMemRefType>(), 349b55d55ecSMatthias Springer options); 350417e1c7dSMatthias Springer }); 351417e1c7dSMatthias Springer } 352417e1c7dSMatthias Springer 353a5d09c63SMatthias Springer /// Helper function for loop bufferization. Given a list of bbArgs of the new 354a5d09c63SMatthias Springer /// (bufferized) loop op, wrap the bufferized tensor args (now memrefs) into 355a5d09c63SMatthias Springer /// ToTensorOps, so that the block body can be moved over to the new op. 356a5d09c63SMatthias Springer SmallVector<Value> 357a5d09c63SMatthias Springer getBbArgReplacements(RewriterBase &rewriter, Block::BlockArgListType bbArgs, 358a5d09c63SMatthias Springer const DenseSet<int64_t> &tensorIndices) { 3595d50f51cSMatthias Springer SmallVector<Value> result; 3605d50f51cSMatthias Springer for (const auto &it : llvm::enumerate(bbArgs)) { 3615d50f51cSMatthias Springer size_t idx = it.index(); 3625d50f51cSMatthias Springer Value val = it.value(); 3635d50f51cSMatthias Springer if (tensorIndices.contains(idx)) { 3645d50f51cSMatthias Springer result.push_back( 3655d50f51cSMatthias Springer rewriter.create<bufferization::ToTensorOp>(val.getLoc(), val) 3665d50f51cSMatthias Springer .getResult()); 3675d50f51cSMatthias Springer } else { 3685d50f51cSMatthias Springer result.push_back(val); 3695d50f51cSMatthias Springer } 3705d50f51cSMatthias Springer } 3715d50f51cSMatthias Springer return result; 372a5d09c63SMatthias Springer } 373a5d09c63SMatthias Springer 37419efe141SMatthias Springer /// Bufferization of scf.for. Replace with a new scf.for that operates on 37519efe141SMatthias Springer /// memrefs. 37619efe141SMatthias Springer struct ForOpInterface 37719efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<ForOpInterface, 37819efe141SMatthias Springer scf::ForOp> { 37919efe141SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 3809597b16aSMatthias Springer const AnalysisState &state) const { 38119efe141SMatthias Springer // scf::ForOp alone doesn't bufferize to a memory read, one of the uses of 38219efe141SMatthias Springer // its matching bbArg may. 38319efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 38419efe141SMatthias Springer return state.isValueRead(forOp.getRegionIterArgForOpOperand(opOperand)); 38519efe141SMatthias Springer } 38619efe141SMatthias Springer 38719efe141SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 3889597b16aSMatthias Springer const AnalysisState &state) const { 3891e1eeae8SMatthias Springer // Tensor iter_args of scf::ForOps are always considered as a write. 39019efe141SMatthias Springer return true; 39119efe141SMatthias Springer } 39219efe141SMatthias Springer 3939597b16aSMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 3949597b16aSMatthias Springer const AnalysisState &state) const { 39519efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 396585a8a32SMatthias Springer return {forOp.getResultForOpOperand(opOperand)}; 39719efe141SMatthias Springer } 39819efe141SMatthias Springer 39919efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 4009597b16aSMatthias Springer const AnalysisState &state) const { 40119efe141SMatthias Springer // ForOp results are equivalent to their corresponding init_args if the 40219efe141SMatthias Springer // corresponding iter_args and yield values are equivalent. 40319efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 40419efe141SMatthias Springer OpOperand &forOperand = forOp.getOpOperandForResult(opResult); 40519efe141SMatthias Springer auto bbArg = forOp.getRegionIterArgForOpOperand(forOperand); 4061e1eeae8SMatthias Springer auto yieldOp = 4071e1eeae8SMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 40819efe141SMatthias Springer bool equivalentYield = state.areEquivalentBufferizedValues( 40919efe141SMatthias Springer bbArg, yieldOp->getOperand(opResult.getResultNumber())); 41019efe141SMatthias Springer return equivalentYield ? BufferRelation::Equivalent : BufferRelation::None; 41119efe141SMatthias Springer } 41219efe141SMatthias Springer 41319efe141SMatthias Springer bool isWritable(Operation *op, Value value, 4149597b16aSMatthias Springer const AnalysisState &state) const { 41519efe141SMatthias Springer // Interestingly, scf::ForOp's bbArg can **always** be viewed 41619efe141SMatthias Springer // inplace from the perspective of ops nested under: 41719efe141SMatthias Springer // 1. Either the matching iter operand is not bufferized inplace and an 41819efe141SMatthias Springer // alloc + optional copy makes the bbArg itself inplaceable. 41919efe141SMatthias Springer // 2. Or the matching iter operand is bufferized inplace and bbArg just 42019efe141SMatthias Springer // bufferizes to that too. 42119efe141SMatthias Springer return true; 42219efe141SMatthias Springer } 42319efe141SMatthias Springer 424d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 425d361ecbdSMatthias Springer const AnalysisState &state) const { 426d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 427d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 428d361ecbdSMatthias Springer return failure(); 429d361ecbdSMatthias Springer 430d361ecbdSMatthias Springer if (!state.getOptions().enforceAliasingInvariants) 431d361ecbdSMatthias Springer return success(); 432d361ecbdSMatthias Springer 433d361ecbdSMatthias Springer // According to the `getAliasing...` implementations, a bufferized OpResult 434d361ecbdSMatthias Springer // may alias only with the corresponding bufferized init_arg and with no 435d361ecbdSMatthias Springer // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg; 436d361ecbdSMatthias Springer // but not with any other OpOperand. If a corresponding OpResult/init_arg 437d361ecbdSMatthias Springer // pair bufferizes to equivalent buffers, this aliasing requirement is 438d361ecbdSMatthias Springer // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy. 439d361ecbdSMatthias Springer // (New buffer copies do not alias with any buffer.) 440d361ecbdSMatthias Springer auto forOp = cast<scf::ForOp>(op); 441d361ecbdSMatthias Springer auto yieldOp = 442d361ecbdSMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 443d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 444d361ecbdSMatthias Springer rewriter.setInsertionPoint(yieldOp); 445d361ecbdSMatthias Springer 446d361ecbdSMatthias Springer // Indices of all iter_args that have tensor type. These are the ones that 447d361ecbdSMatthias Springer // are bufferized. 448d361ecbdSMatthias Springer DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs()); 449d361ecbdSMatthias Springer // For every yielded value, is the value equivalent to its corresponding 450d361ecbdSMatthias Springer // bbArg? 451d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYields = getEquivalentBuffers( 452d361ecbdSMatthias Springer forOp.getRegionIterArgs(), yieldOp.getResults(), state); 453d361ecbdSMatthias Springer SmallVector<Value> yieldValues; 454d361ecbdSMatthias Springer for (int64_t idx = 0; 455d361ecbdSMatthias Springer idx < static_cast<int64_t>(yieldOp.getResults().size()); ++idx) { 456d361ecbdSMatthias Springer Value value = yieldOp.getResults()[idx]; 457d361ecbdSMatthias Springer if (!indices.contains(idx) || equivalentYields.contains(idx)) { 458d361ecbdSMatthias Springer yieldValues.push_back(value); 459d361ecbdSMatthias Springer continue; 460d361ecbdSMatthias Springer } 461*45b995cdSMatthias Springer FailureOr<Value> alloc = 462*45b995cdSMatthias Springer allocateTensorForShapedValue(rewriter, yieldOp.getLoc(), value, 463*45b995cdSMatthias Springer /*escape=*/true, state.getOptions()); 464*45b995cdSMatthias Springer if (failed(alloc)) 465*45b995cdSMatthias Springer return failure(); 466*45b995cdSMatthias Springer yieldValues.push_back(*alloc); 467d361ecbdSMatthias Springer } 468d361ecbdSMatthias Springer 469d361ecbdSMatthias Springer rewriter.updateRootInPlace( 470d361ecbdSMatthias Springer yieldOp, [&]() { yieldOp.getResultsMutable().assign(yieldValues); }); 471d361ecbdSMatthias Springer return success(); 472d361ecbdSMatthias Springer } 473d361ecbdSMatthias Springer 4745d50f51cSMatthias Springer FailureOr<BaseMemRefType> 4755d50f51cSMatthias Springer getBufferType(Operation *op, BlockArgument bbArg, 4763ff93f83SMatthias Springer const BufferizationOptions &options) const { 4773ff93f83SMatthias Springer auto forOp = cast<scf::ForOp>(op); 4783ff93f83SMatthias Springer return bufferization::getBufferType( 4793ff93f83SMatthias Springer forOp.getOpOperandForRegionIterArg(bbArg).get(), options); 4803ff93f83SMatthias Springer } 4813ff93f83SMatthias Springer 48219efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 483b55d55ecSMatthias Springer const BufferizationOptions &options) const { 48419efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 48519efe141SMatthias Springer Block *oldLoopBody = &forOp.getLoopBody().front(); 48619efe141SMatthias Springer 48719efe141SMatthias Springer // Indices of all iter_args that have tensor type. These are the ones that 48819efe141SMatthias Springer // are bufferized. 489417e1c7dSMatthias Springer DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs()); 49019efe141SMatthias Springer 491417e1c7dSMatthias Springer // The new memref init_args of the loop. 4925d50f51cSMatthias Springer FailureOr<SmallVector<Value>> maybeInitArgs = 493b55d55ecSMatthias Springer getBuffers(rewriter, forOp.getIterOpOperands(), options); 4945d50f51cSMatthias Springer if (failed(maybeInitArgs)) 4955d50f51cSMatthias Springer return failure(); 4965d50f51cSMatthias Springer SmallVector<Value> initArgs = *maybeInitArgs; 49719efe141SMatthias Springer 49819efe141SMatthias Springer // Construct a new scf.for op with memref instead of tensor values. 49919efe141SMatthias Springer auto newForOp = rewriter.create<scf::ForOp>( 50019efe141SMatthias Springer forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(), 50119efe141SMatthias Springer forOp.getStep(), initArgs); 502413fbb04SLei Zhang newForOp->setAttrs(forOp->getAttrs()); 503417e1c7dSMatthias Springer ValueRange initArgsRange(initArgs); 504417e1c7dSMatthias Springer TypeRange initArgsTypes(initArgsRange); 50519efe141SMatthias Springer Block *loopBody = &newForOp.getLoopBody().front(); 50619efe141SMatthias Springer 50719efe141SMatthias Springer // Set up new iter_args. The loop body uses tensors, so wrap the (memref) 50819efe141SMatthias Springer // iter_args of the new loop in ToTensorOps. 50919efe141SMatthias Springer rewriter.setInsertionPointToStart(loopBody); 510a5d09c63SMatthias Springer SmallVector<Value> iterArgs = 511a5d09c63SMatthias Springer getBbArgReplacements(rewriter, newForOp.getRegionIterArgs(), indices); 51219efe141SMatthias Springer iterArgs.insert(iterArgs.begin(), newForOp.getInductionVar()); 51319efe141SMatthias Springer 51419efe141SMatthias Springer // Move loop body to new loop. 51519efe141SMatthias Springer rewriter.mergeBlocks(oldLoopBody, loopBody, iterArgs); 51619efe141SMatthias Springer 51719efe141SMatthias Springer // Replace loop results. 51819efe141SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newForOp->getResults()); 51919efe141SMatthias Springer 52019efe141SMatthias Springer return success(); 52119efe141SMatthias Springer } 5224ec00fb3SMatthias Springer 5231e1eeae8SMatthias Springer /// Assert that yielded values of an scf.for op are equivalent to their 524f178c386SMatthias Springer /// corresponding bbArgs. In that case, the buffer relations of the 525f178c386SMatthias Springer /// corresponding OpResults are "Equivalent". 526f178c386SMatthias Springer /// 527f178c386SMatthias Springer /// If this is not the case, an allocs+copies are inserted and yielded from 528f178c386SMatthias Springer /// the loop. This could be a performance problem, so it must be explicitly 529f178c386SMatthias Springer /// activated with `alloc-return-allocs`. 5304ec00fb3SMatthias Springer LogicalResult verifyAnalysis(Operation *op, 5319597b16aSMatthias Springer const AnalysisState &state) const { 5321e1eeae8SMatthias Springer const auto &options = 5331e1eeae8SMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 5341e1eeae8SMatthias Springer if (options.allowReturnAllocs) 5351e1eeae8SMatthias Springer return success(); 5361e1eeae8SMatthias Springer 5374ec00fb3SMatthias Springer auto forOp = cast<scf::ForOp>(op); 5384ec00fb3SMatthias Springer auto yieldOp = 5394ec00fb3SMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 540f178c386SMatthias Springer for (OpResult opResult : op->getOpResults()) { 541f178c386SMatthias Springer if (!opResult.getType().isa<TensorType>()) 5424ec00fb3SMatthias Springer continue; 5434ec00fb3SMatthias Springer 5444ec00fb3SMatthias Springer // Note: This is overly strict. We should check for aliasing bufferized 5454ec00fb3SMatthias Springer // values. But we don't have a "must-alias" analysis yet. 546f178c386SMatthias Springer if (bufferRelation(op, opResult, state) != BufferRelation::Equivalent) 5474ec00fb3SMatthias Springer return yieldOp->emitError() 548f178c386SMatthias Springer << "Yield operand #" << opResult.getResultNumber() 549e3006825SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 5504ec00fb3SMatthias Springer } 551f178c386SMatthias Springer 5524ec00fb3SMatthias Springer return success(); 5534ec00fb3SMatthias Springer } 55419efe141SMatthias Springer }; 55519efe141SMatthias Springer 556a5d09c63SMatthias Springer /// Bufferization of scf.while. Replace with a new scf.while that operates on 557a5d09c63SMatthias Springer /// memrefs. 558a5d09c63SMatthias Springer struct WhileOpInterface 559a5d09c63SMatthias Springer : public BufferizableOpInterface::ExternalModel<WhileOpInterface, 560a5d09c63SMatthias Springer scf::WhileOp> { 561a5d09c63SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 562a5d09c63SMatthias Springer const AnalysisState &state) const { 563a5d09c63SMatthias Springer // Tensor iter_args of scf::WhileOps are always considered as a read. 564a5d09c63SMatthias Springer return true; 565a5d09c63SMatthias Springer } 566a5d09c63SMatthias Springer 567a5d09c63SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 568a5d09c63SMatthias Springer const AnalysisState &state) const { 569a5d09c63SMatthias Springer // Tensor iter_args of scf::WhileOps are always considered as a write. 570a5d09c63SMatthias Springer return true; 571a5d09c63SMatthias Springer } 572a5d09c63SMatthias Springer 573a5d09c63SMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 574a5d09c63SMatthias Springer const AnalysisState &state) const { 575a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 576996834e6SMatthias Springer unsigned int idx = opOperand.getOperandNumber(); 577996834e6SMatthias Springer 578996834e6SMatthias Springer // The OpResults and OpOperands may not match. They may not even have the 579996834e6SMatthias Springer // same type. The number of OpResults and OpOperands can also differ. 580996834e6SMatthias Springer if (idx >= op->getNumResults() || 581996834e6SMatthias Springer opOperand.get().getType() != op->getResult(idx).getType()) 582996834e6SMatthias Springer return {}; 583996834e6SMatthias Springer 584996834e6SMatthias Springer // The only aliasing OpResult may be the one at the same index. 585996834e6SMatthias Springer return {whileOp->getResult(idx)}; 586a5d09c63SMatthias Springer } 587a5d09c63SMatthias Springer 588a5d09c63SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 589a5d09c63SMatthias Springer const AnalysisState &state) const { 590a5d09c63SMatthias Springer // WhileOp results are equivalent to their corresponding init_args if the 591a5d09c63SMatthias Springer // corresponding iter_args and yield values are equivalent (for both the 592a5d09c63SMatthias Springer // "before" and the "after" block). 593a5d09c63SMatthias Springer unsigned int resultNumber = opResult.getResultNumber(); 594a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 595a5d09c63SMatthias Springer 596996834e6SMatthias Springer // The "before" region bbArgs and the OpResults may not match. 597996834e6SMatthias Springer if (resultNumber >= whileOp.getBeforeArguments().size()) 598996834e6SMatthias Springer return BufferRelation::None; 599996834e6SMatthias Springer if (opResult.getType() != 600996834e6SMatthias Springer whileOp.getBeforeArguments()[resultNumber].getType()) 601996834e6SMatthias Springer return BufferRelation::None; 602996834e6SMatthias Springer 603a5d09c63SMatthias Springer auto conditionOp = whileOp.getConditionOp(); 604a5d09c63SMatthias Springer BlockArgument conditionBbArg = whileOp.getBeforeArguments()[resultNumber]; 605a5d09c63SMatthias Springer Value conditionOperand = conditionOp.getArgs()[resultNumber]; 606a5d09c63SMatthias Springer bool equivCondition = 607a5d09c63SMatthias Springer state.areEquivalentBufferizedValues(conditionBbArg, conditionOperand); 608a5d09c63SMatthias Springer 609a5d09c63SMatthias Springer auto yieldOp = whileOp.getYieldOp(); 610a5d09c63SMatthias Springer BlockArgument bodyBbArg = whileOp.getAfterArguments()[resultNumber]; 611a5d09c63SMatthias Springer Value yieldOperand = yieldOp.getOperand(resultNumber); 612a5d09c63SMatthias Springer bool equivYield = 613a5d09c63SMatthias Springer state.areEquivalentBufferizedValues(bodyBbArg, yieldOperand); 614a5d09c63SMatthias Springer 615a5d09c63SMatthias Springer return equivCondition && equivYield ? BufferRelation::Equivalent 616a5d09c63SMatthias Springer : BufferRelation::None; 617a5d09c63SMatthias Springer } 618a5d09c63SMatthias Springer 619a5d09c63SMatthias Springer bool isWritable(Operation *op, Value value, 620a5d09c63SMatthias Springer const AnalysisState &state) const { 621a5d09c63SMatthias Springer // Interestingly, scf::WhileOp's bbArg can **always** be viewed 622a5d09c63SMatthias Springer // inplace from the perspective of ops nested under: 623a5d09c63SMatthias Springer // 1. Either the matching iter operand is not bufferized inplace and an 624a5d09c63SMatthias Springer // alloc + optional copy makes the bbArg itself inplaceable. 625a5d09c63SMatthias Springer // 2. Or the matching iter operand is bufferized inplace and bbArg just 626a5d09c63SMatthias Springer // bufferizes to that too. 627a5d09c63SMatthias Springer return true; 628a5d09c63SMatthias Springer } 629a5d09c63SMatthias Springer 630d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 631d361ecbdSMatthias Springer const AnalysisState &state) const { 632d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 633d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 634d361ecbdSMatthias Springer return failure(); 635d361ecbdSMatthias Springer 636d361ecbdSMatthias Springer if (!state.getOptions().enforceAliasingInvariants) 637d361ecbdSMatthias Springer return success(); 638d361ecbdSMatthias Springer 639d361ecbdSMatthias Springer // According to the `getAliasing...` implementations, a bufferized OpResult 640d361ecbdSMatthias Springer // may alias only with the corresponding bufferized init_arg and with no 641d361ecbdSMatthias Springer // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg; 642d361ecbdSMatthias Springer // but not with any other OpOperand. If a corresponding OpResult/init_arg 643d361ecbdSMatthias Springer // pair bufferizes to equivalent buffers, this aliasing requirement is 644d361ecbdSMatthias Springer // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy. 645d361ecbdSMatthias Springer // (New buffer copies do not alias with any buffer.) 646d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 647d361ecbdSMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 648d361ecbdSMatthias Springer auto conditionOp = whileOp.getConditionOp(); 649d361ecbdSMatthias Springer auto yieldOp = whileOp.getYieldOp(); 650d361ecbdSMatthias Springer 651d361ecbdSMatthias Springer // Indices of all bbArgs that have tensor type. These are the ones that 652d361ecbdSMatthias Springer // are bufferized. The "before" and "after" regions may have different args. 653d361ecbdSMatthias Springer DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits()); 654d361ecbdSMatthias Springer DenseSet<int64_t> indicesAfter = 655d361ecbdSMatthias Springer getTensorIndices(whileOp.getAfterArguments()); 656d361ecbdSMatthias Springer 657d361ecbdSMatthias Springer // For every yielded value, is the value equivalent to its corresponding 658d361ecbdSMatthias Springer // bbArg? 659d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYieldsBefore = getEquivalentBuffers( 660d361ecbdSMatthias Springer whileOp.getBeforeArguments(), conditionOp.getArgs(), state); 661d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYieldsAfter = getEquivalentBuffers( 662d361ecbdSMatthias Springer whileOp.getAfterArguments(), whileOp.getYieldOp().getResults(), state); 663d361ecbdSMatthias Springer 664d361ecbdSMatthias Springer // Update "before" region. 665d361ecbdSMatthias Springer rewriter.setInsertionPoint(conditionOp); 666d361ecbdSMatthias Springer SmallVector<Value> beforeYieldValues; 667d361ecbdSMatthias Springer for (int64_t idx = 0; 668d361ecbdSMatthias Springer idx < static_cast<int64_t>(conditionOp.getArgs().size()); ++idx) { 669d361ecbdSMatthias Springer Value value = conditionOp.getArgs()[idx]; 670d361ecbdSMatthias Springer if (!indicesBefore.contains(idx) || 671d361ecbdSMatthias Springer equivalentYieldsBefore.contains(idx)) { 672d361ecbdSMatthias Springer beforeYieldValues.push_back(value); 673d361ecbdSMatthias Springer continue; 674d361ecbdSMatthias Springer } 675*45b995cdSMatthias Springer FailureOr<Value> alloc = 676*45b995cdSMatthias Springer allocateTensorForShapedValue(rewriter, conditionOp.getLoc(), value, 677*45b995cdSMatthias Springer /*escape=*/true, state.getOptions()); 678*45b995cdSMatthias Springer if (failed(alloc)) 679*45b995cdSMatthias Springer return failure(); 680*45b995cdSMatthias Springer beforeYieldValues.push_back(*alloc); 681d361ecbdSMatthias Springer } 682d361ecbdSMatthias Springer rewriter.updateRootInPlace(conditionOp, [&]() { 683d361ecbdSMatthias Springer conditionOp.getArgsMutable().assign(beforeYieldValues); 684d361ecbdSMatthias Springer }); 685d361ecbdSMatthias Springer 686d361ecbdSMatthias Springer // Update "after" region. 687d361ecbdSMatthias Springer rewriter.setInsertionPoint(yieldOp); 688d361ecbdSMatthias Springer SmallVector<Value> afterYieldValues; 689d361ecbdSMatthias Springer for (int64_t idx = 0; 690d361ecbdSMatthias Springer idx < static_cast<int64_t>(yieldOp.getResults().size()); ++idx) { 691d361ecbdSMatthias Springer Value value = yieldOp.getResults()[idx]; 692d361ecbdSMatthias Springer if (!indicesAfter.contains(idx) || equivalentYieldsAfter.contains(idx)) { 693d361ecbdSMatthias Springer afterYieldValues.push_back(value); 694d361ecbdSMatthias Springer continue; 695d361ecbdSMatthias Springer } 696*45b995cdSMatthias Springer FailureOr<Value> alloc = 697*45b995cdSMatthias Springer allocateTensorForShapedValue(rewriter, yieldOp.getLoc(), value, 698*45b995cdSMatthias Springer /*escape=*/true, state.getOptions()); 699*45b995cdSMatthias Springer if (failed(alloc)) 700*45b995cdSMatthias Springer return failure(); 701*45b995cdSMatthias Springer afterYieldValues.push_back(*alloc); 702d361ecbdSMatthias Springer } 703d361ecbdSMatthias Springer rewriter.updateRootInPlace(yieldOp, [&]() { 704d361ecbdSMatthias Springer yieldOp.getResultsMutable().assign(afterYieldValues); 705d361ecbdSMatthias Springer }); 706d361ecbdSMatthias Springer 707d361ecbdSMatthias Springer return success(); 708d361ecbdSMatthias Springer } 709d361ecbdSMatthias Springer 710a5d09c63SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 711b55d55ecSMatthias Springer const BufferizationOptions &options) const { 712a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 713a5d09c63SMatthias Springer 714a5d09c63SMatthias Springer assert(whileOp.getBefore().getBlocks().size() == 1 && 715a5d09c63SMatthias Springer "regions with multiple blocks not supported"); 716a5d09c63SMatthias Springer Block *beforeBody = &whileOp.getBefore().front(); 717a5d09c63SMatthias Springer assert(whileOp.getAfter().getBlocks().size() == 1 && 718a5d09c63SMatthias Springer "regions with multiple blocks not supported"); 719a5d09c63SMatthias Springer Block *afterBody = &whileOp.getAfter().front(); 720a5d09c63SMatthias Springer 721996834e6SMatthias Springer // Indices of all bbArgs that have tensor type. These are the ones that 722996834e6SMatthias Springer // are bufferized. The "before" and "after" regions may have different args. 723996834e6SMatthias Springer DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits()); 724996834e6SMatthias Springer DenseSet<int64_t> indicesAfter = 725996834e6SMatthias Springer getTensorIndices(whileOp.getAfterArguments()); 726996834e6SMatthias Springer 727a5d09c63SMatthias Springer // The new memref init_args of the loop. 7285d50f51cSMatthias Springer FailureOr<SmallVector<Value>> maybeInitArgs = 729b55d55ecSMatthias Springer getBuffers(rewriter, whileOp->getOpOperands(), options); 7305d50f51cSMatthias Springer if (failed(maybeInitArgs)) 7315d50f51cSMatthias Springer return failure(); 7325d50f51cSMatthias Springer SmallVector<Value> initArgs = *maybeInitArgs; 733996834e6SMatthias Springer 734996834e6SMatthias Springer // The result types of a WhileOp are the same as the "after" bbArg types. 735996834e6SMatthias Springer SmallVector<Type> argsTypesAfter = llvm::to_vector( 736996834e6SMatthias Springer llvm::map_range(whileOp.getAfterArguments(), [&](BlockArgument bbArg) { 7375d50f51cSMatthias Springer // TODO: error handling 7385d50f51cSMatthias Springer return bufferization::getBufferType(bbArg, options)->cast<Type>(); 739996834e6SMatthias Springer })); 740a5d09c63SMatthias Springer 741a5d09c63SMatthias Springer // Construct a new scf.while op with memref instead of tensor values. 742996834e6SMatthias Springer ValueRange argsRangeBefore(initArgs); 743996834e6SMatthias Springer TypeRange argsTypesBefore(argsRangeBefore); 744996834e6SMatthias Springer auto newWhileOp = rewriter.create<scf::WhileOp>(whileOp.getLoc(), 745996834e6SMatthias Springer argsTypesAfter, initArgs); 746996834e6SMatthias Springer 747a5d09c63SMatthias Springer // Add before/after regions to the new op. 748996834e6SMatthias Springer SmallVector<Location> bbArgLocsBefore(initArgs.size(), whileOp.getLoc()); 749996834e6SMatthias Springer SmallVector<Location> bbArgLocsAfter(argsTypesAfter.size(), 750996834e6SMatthias Springer whileOp.getLoc()); 751a5d09c63SMatthias Springer Block *newBeforeBody = &newWhileOp.getBefore().emplaceBlock(); 752996834e6SMatthias Springer newWhileOp.getBefore().addArguments(argsTypesBefore, bbArgLocsBefore); 753a5d09c63SMatthias Springer Block *newAfterBody = &newWhileOp.getAfter().emplaceBlock(); 754996834e6SMatthias Springer newWhileOp.getAfter().addArguments(argsTypesAfter, bbArgLocsAfter); 755a5d09c63SMatthias Springer 756a5d09c63SMatthias Springer // Set up new iter_args and move the loop condition block to the new op. 757a5d09c63SMatthias Springer // The old block uses tensors, so wrap the (memref) bbArgs of the new block 758a5d09c63SMatthias Springer // in ToTensorOps. 759a5d09c63SMatthias Springer rewriter.setInsertionPointToStart(newBeforeBody); 760a5d09c63SMatthias Springer SmallVector<Value> newBeforeArgs = getBbArgReplacements( 761996834e6SMatthias Springer rewriter, newWhileOp.getBeforeArguments(), indicesBefore); 762a5d09c63SMatthias Springer rewriter.mergeBlocks(beforeBody, newBeforeBody, newBeforeArgs); 763a5d09c63SMatthias Springer 764a5d09c63SMatthias Springer // Update scf.condition of new loop. 765a5d09c63SMatthias Springer auto newConditionOp = newWhileOp.getConditionOp(); 766a5d09c63SMatthias Springer rewriter.setInsertionPoint(newConditionOp); 767996834e6SMatthias Springer // Only equivalent buffers or new buffer allocations may be yielded to the 768996834e6SMatthias Springer // "after" region. 769996834e6SMatthias Springer // TODO: This could be relaxed for better bufferization results. 7705d50f51cSMatthias Springer FailureOr<SmallVector<Value>> newConditionArgs = 771996834e6SMatthias Springer getYieldedValues(rewriter, newConditionOp.getArgs(), argsTypesAfter, 772b55d55ecSMatthias Springer indicesAfter, options); 7735d50f51cSMatthias Springer if (failed(newConditionArgs)) 7745d50f51cSMatthias Springer return failure(); 7755d50f51cSMatthias Springer newConditionOp.getArgsMutable().assign(*newConditionArgs); 776a5d09c63SMatthias Springer 777a5d09c63SMatthias Springer // Set up new iter_args and move the loop body block to the new op. 778a5d09c63SMatthias Springer // The old block uses tensors, so wrap the (memref) bbArgs of the new block 779a5d09c63SMatthias Springer // in ToTensorOps. 780a5d09c63SMatthias Springer rewriter.setInsertionPointToStart(newAfterBody); 781996834e6SMatthias Springer SmallVector<Value> newAfterArgs = getBbArgReplacements( 782996834e6SMatthias Springer rewriter, newWhileOp.getAfterArguments(), indicesAfter); 783a5d09c63SMatthias Springer rewriter.mergeBlocks(afterBody, newAfterBody, newAfterArgs); 784a5d09c63SMatthias Springer 785a5d09c63SMatthias Springer // Update scf.yield of the new loop. 786a5d09c63SMatthias Springer auto newYieldOp = newWhileOp.getYieldOp(); 787a5d09c63SMatthias Springer rewriter.setInsertionPoint(newYieldOp); 788996834e6SMatthias Springer // Only equivalent buffers or new buffer allocations may be yielded to the 789996834e6SMatthias Springer // "before" region. 790996834e6SMatthias Springer // TODO: This could be relaxed for better bufferization results. 7915d50f51cSMatthias Springer FailureOr<SmallVector<Value>> newYieldValues = 792996834e6SMatthias Springer getYieldedValues(rewriter, newYieldOp.getResults(), argsTypesBefore, 793b55d55ecSMatthias Springer indicesBefore, options); 7945d50f51cSMatthias Springer if (failed(newYieldValues)) 7955d50f51cSMatthias Springer return failure(); 7965d50f51cSMatthias Springer newYieldOp.getResultsMutable().assign(*newYieldValues); 797a5d09c63SMatthias Springer 798a5d09c63SMatthias Springer // Replace loop results. 799a5d09c63SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newWhileOp->getResults()); 800a5d09c63SMatthias Springer 801a5d09c63SMatthias Springer return success(); 802a5d09c63SMatthias Springer } 803a5d09c63SMatthias Springer 804a5d09c63SMatthias Springer /// Assert that yielded values of an scf.while op are equivalent to their 805a5d09c63SMatthias Springer /// corresponding bbArgs. In that case, the buffer relations of the 806a5d09c63SMatthias Springer /// corresponding OpResults are "Equivalent". 807a5d09c63SMatthias Springer /// 808a5d09c63SMatthias Springer /// If this is not the case, allocs+copies are inserted and yielded from 809a5d09c63SMatthias Springer /// the loop. This could be a performance problem, so it must be explicitly 810a5d09c63SMatthias Springer /// activated with `alloc-return-allocs`. 811a5d09c63SMatthias Springer /// 812a5d09c63SMatthias Springer /// Not: In contrast to scf::ForOp, scf::WhileOp has two regions and the 813a5d09c63SMatthias Springer /// equivalence condition must be checked for both. 814a5d09c63SMatthias Springer LogicalResult verifyAnalysis(Operation *op, 815a5d09c63SMatthias Springer const AnalysisState &state) const { 816a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 817a5d09c63SMatthias Springer const auto &options = 818a5d09c63SMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 819a5d09c63SMatthias Springer if (options.allowReturnAllocs) 820a5d09c63SMatthias Springer return success(); 821a5d09c63SMatthias Springer 822a5d09c63SMatthias Springer auto conditionOp = whileOp.getConditionOp(); 823a5d09c63SMatthias Springer for (const auto &it : llvm::enumerate(conditionOp.getArgs())) { 824a5d09c63SMatthias Springer if (!it.value().getType().isa<TensorType>()) 825a5d09c63SMatthias Springer continue; 826a5d09c63SMatthias Springer if (!state.areEquivalentBufferizedValues( 827a5d09c63SMatthias Springer it.value(), conditionOp->getBlock()->getArgument(it.index()))) 828a5d09c63SMatthias Springer return conditionOp->emitError() 829a5d09c63SMatthias Springer << "Condition arg #" << it.index() 830a5d09c63SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 831a5d09c63SMatthias Springer } 832a5d09c63SMatthias Springer 833a5d09c63SMatthias Springer auto yieldOp = whileOp.getYieldOp(); 834a5d09c63SMatthias Springer for (const auto &it : llvm::enumerate(yieldOp.getResults())) { 835a5d09c63SMatthias Springer if (!it.value().getType().isa<TensorType>()) 836a5d09c63SMatthias Springer continue; 837a5d09c63SMatthias Springer if (!state.areEquivalentBufferizedValues( 838a5d09c63SMatthias Springer it.value(), yieldOp->getBlock()->getArgument(it.index()))) 839a5d09c63SMatthias Springer return yieldOp->emitError() 840a5d09c63SMatthias Springer << "Yield operand #" << it.index() 841a5d09c63SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 842a5d09c63SMatthias Springer } 843a5d09c63SMatthias Springer 844a5d09c63SMatthias Springer return success(); 845a5d09c63SMatthias Springer } 846a5d09c63SMatthias Springer }; 847a5d09c63SMatthias Springer 84819efe141SMatthias Springer /// Bufferization of scf.yield. Bufferized as part of their enclosing ops, so 84919efe141SMatthias Springer /// this is for analysis only. 85019efe141SMatthias Springer struct YieldOpInterface 85119efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<YieldOpInterface, 85219efe141SMatthias Springer scf::YieldOp> { 85319efe141SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 8549597b16aSMatthias Springer const AnalysisState &state) const { 85519efe141SMatthias Springer return true; 85619efe141SMatthias Springer } 85719efe141SMatthias Springer 85819efe141SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 8599597b16aSMatthias Springer const AnalysisState &state) const { 86019efe141SMatthias Springer return false; 86119efe141SMatthias Springer } 86219efe141SMatthias Springer 8639597b16aSMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 8649597b16aSMatthias Springer const AnalysisState &state) const { 86519efe141SMatthias Springer if (isa<scf::IfOp>(op->getParentOp())) 866585a8a32SMatthias Springer return {op->getParentOp()->getResult(opOperand.getOperandNumber())}; 86719efe141SMatthias Springer if (isa<scf::ExecuteRegionOp>(op->getParentOp())) 868585a8a32SMatthias Springer return {op->getParentOp()->getResult(opOperand.getOperandNumber())}; 869585a8a32SMatthias Springer return {}; 87019efe141SMatthias Springer } 87119efe141SMatthias Springer 87219efe141SMatthias Springer bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand, 8739597b16aSMatthias Springer const AnalysisState &state) const { 87419efe141SMatthias Springer // Yield operands always bufferize inplace. Otherwise, an alloc + copy 87519efe141SMatthias Springer // may be generated inside the block. We should not return/yield allocations 87619efe141SMatthias Springer // when possible. 87719efe141SMatthias Springer return true; 87819efe141SMatthias Springer } 87919efe141SMatthias Springer 88019efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 881b55d55ecSMatthias Springer const BufferizationOptions &options) const { 88219efe141SMatthias Springer auto yieldOp = cast<scf::YieldOp>(op); 883a5d09c63SMatthias Springer if (!isa<scf::ExecuteRegionOp, scf::IfOp, scf::ForOp, scf::WhileOp>( 88419efe141SMatthias Springer yieldOp->getParentOp())) 88519efe141SMatthias Springer return yieldOp->emitError("unsupported scf::YieldOp parent"); 8868e691e1fSMatthias Springer 8873ff93f83SMatthias Springer // TODO: Bufferize scf.yield inside scf.while here. (Currently bufferized 8883ff93f83SMatthias Springer // together with scf.while.) 8893ff93f83SMatthias Springer if (isa<scf::WhileOp>(yieldOp->getParentOp())) 8908e691e1fSMatthias Springer return success(); 8918e691e1fSMatthias Springer 8928e691e1fSMatthias Springer SmallVector<Value> newResults; 8938e691e1fSMatthias Springer for (const auto &it : llvm::enumerate(yieldOp.getResults())) { 8948e691e1fSMatthias Springer Value value = it.value(); 8958e691e1fSMatthias Springer if (value.getType().isa<TensorType>()) { 8965d50f51cSMatthias Springer FailureOr<Value> maybeBuffer = getBuffer(rewriter, value, options); 8975d50f51cSMatthias Springer if (failed(maybeBuffer)) 8985d50f51cSMatthias Springer return failure(); 8995d50f51cSMatthias Springer Value buffer = *maybeBuffer; 9003ff93f83SMatthias Springer if (auto forOp = dyn_cast<scf::ForOp>(yieldOp->getParentOp())) { 9015d50f51cSMatthias Springer FailureOr<BaseMemRefType> resultType = 9023ff93f83SMatthias Springer cast<BufferizableOpInterface>(forOp.getOperation()) 9033ff93f83SMatthias Springer .getBufferType(forOp.getRegionIterArgs()[it.index()], 9043ff93f83SMatthias Springer options); 9055d50f51cSMatthias Springer if (failed(resultType)) 9065d50f51cSMatthias Springer return failure(); 9075d50f51cSMatthias Springer buffer = castBuffer(rewriter, buffer, *resultType); 9083ff93f83SMatthias Springer } 9098e691e1fSMatthias Springer newResults.push_back(buffer); 9108e691e1fSMatthias Springer } else { 9118e691e1fSMatthias Springer newResults.push_back(value); 9128e691e1fSMatthias Springer } 9138e691e1fSMatthias Springer } 9148e691e1fSMatthias Springer 9158e691e1fSMatthias Springer replaceOpWithNewBufferizedOp<scf::YieldOp>(rewriter, op, newResults); 91619efe141SMatthias Springer return success(); 91719efe141SMatthias Springer } 91819efe141SMatthias Springer }; 91919efe141SMatthias Springer 92072de7588SNicolas Vasilache using tensor::ExtractSliceOp; 92172de7588SNicolas Vasilache 92272de7588SNicolas Vasilache /// Return the destinations that an ForeachThreadOp is inserting into. One per 92372de7588SNicolas Vasilache /// ParallelInsertSliceOp. 92472de7588SNicolas Vasilache static SmallVector<OpOperand *> 92572de7588SNicolas Vasilache getInsertionDest(ForeachThreadOp foreachThreadOp) { 92672de7588SNicolas Vasilache PerformConcurrentlyOp terminator = foreachThreadOp.getTerminator(); 92772de7588SNicolas Vasilache SmallVector<OpOperand *> result; 92872de7588SNicolas Vasilache terminator.walk([&](ParallelInsertSliceOp insertOp) { 92972de7588SNicolas Vasilache result.push_back(&insertOp->getOpOperand(1) /*dest*/); 93072de7588SNicolas Vasilache }); 93172de7588SNicolas Vasilache return result; 93272de7588SNicolas Vasilache } 93372de7588SNicolas Vasilache 93472de7588SNicolas Vasilache /// Bufferization of ForeachThreadOp. This also bufferizes the terminator of the 93572de7588SNicolas Vasilache /// region. There are op interfaces for the terminators (PerformConcurrentlyOp 93672de7588SNicolas Vasilache /// and ParallelInsertSliceOp), but these are only used during analysis. Not 93772de7588SNicolas Vasilache /// for bufferization. 93872de7588SNicolas Vasilache struct ForeachThreadOpInterface 93972de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel<ForeachThreadOpInterface, 94072de7588SNicolas Vasilache ForeachThreadOp> { 94172de7588SNicolas Vasilache SmallVector<OpOperand *> 94272de7588SNicolas Vasilache getAliasingOpOperand(Operation *op, OpResult opResult, 94372de7588SNicolas Vasilache const AnalysisState &state) const { 94472de7588SNicolas Vasilache // Get OpOperand (dest) from corresponding ParallelInsertSliceOp. 94572de7588SNicolas Vasilache auto foreachThreadOp = cast<ForeachThreadOp>(op); 94672de7588SNicolas Vasilache return {getInsertionDest(foreachThreadOp)[opResult.getResultNumber()]}; 94772de7588SNicolas Vasilache } 94872de7588SNicolas Vasilache 94972de7588SNicolas Vasilache bool isMemoryWrite(Operation *op, OpResult opResult, 95072de7588SNicolas Vasilache const AnalysisState &state) const { 95172de7588SNicolas Vasilache // This op is a memory write. Stop lookup here to avoid finding false 95272de7588SNicolas Vasilache // conflicts involving this op and one of the ops in the region. This is 95372de7588SNicolas Vasilache // similar to how scf.if ops are analyzed. 95472de7588SNicolas Vasilache return true; 95572de7588SNicolas Vasilache } 95672de7588SNicolas Vasilache 95772de7588SNicolas Vasilache BufferRelation bufferRelation(Operation *op, OpResult opResult, 95872de7588SNicolas Vasilache const AnalysisState &state) const { 95972de7588SNicolas Vasilache return BufferRelation::Equivalent; 96072de7588SNicolas Vasilache } 96172de7588SNicolas Vasilache 962d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 963d361ecbdSMatthias Springer const AnalysisState &state) const { 964d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 965d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 966d361ecbdSMatthias Springer return failure(); 967d361ecbdSMatthias Springer 968d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 969d361ecbdSMatthias Springer auto foreachThreadOp = cast<ForeachThreadOp>(op); 970d361ecbdSMatthias Springer for (OpResult opResult : foreachThreadOp->getOpResults()) { 971d361ecbdSMatthias Springer SmallVector<OpOperand *> destOperands = 972d361ecbdSMatthias Springer state.getAliasingOpOperand(opResult); 973d361ecbdSMatthias Springer assert(destOperands.size() == 1 && 974d361ecbdSMatthias Springer "expected exactly one aliasing OpOperand"); 975d361ecbdSMatthias Springer assert(isa<ParallelInsertSliceOp>(destOperands.front()->getOwner()) && 976d361ecbdSMatthias Springer "expected ParallelInsertSliceOp"); 977d361ecbdSMatthias Springer 978d361ecbdSMatthias Springer // Nothing to do if there is no conflict. 979d361ecbdSMatthias Springer if (state.isInPlace(*destOperands.front())) 980d361ecbdSMatthias Springer continue; 981d361ecbdSMatthias Springer 9823474d10eSMatthias Springer // Insert tensor allocation. 983d361ecbdSMatthias Springer bool isYielded = state.isTensorYielded(opResult); 984*45b995cdSMatthias Springer FailureOr<Value> alloc = allocateTensorForShapedValue( 985*45b995cdSMatthias Springer rewriter, op->getLoc(), destOperands.front()->get(), 986*45b995cdSMatthias Springer /*escape=*/isYielded, state.getOptions()); 987*45b995cdSMatthias Springer if (failed(alloc)) 988*45b995cdSMatthias Springer return failure(); 989d361ecbdSMatthias Springer 990d361ecbdSMatthias Springer // Update terminator operand. 991d361ecbdSMatthias Springer rewriter.updateRootInPlace(destOperands.front()->getOwner(), 992*45b995cdSMatthias Springer [&]() { destOperands.front()->set(*alloc); }); 993d361ecbdSMatthias Springer } 994d361ecbdSMatthias Springer 995d361ecbdSMatthias Springer return success(); 996d361ecbdSMatthias Springer } 997d361ecbdSMatthias Springer 9987ebf70d8SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 999b55d55ecSMatthias Springer const BufferizationOptions &options) const { 100072de7588SNicolas Vasilache auto foreachThreadOp = cast<ForeachThreadOp>(op); 100172de7588SNicolas Vasilache 10027ebf70d8SMatthias Springer #ifndef NDEBUG 10037ebf70d8SMatthias Springer // ParallelInsertSliceOpInterface replaces all uses. 10047ebf70d8SMatthias Springer for (OpResult opResult : foreachThreadOp->getOpResults()) 10057ebf70d8SMatthias Springer assert(opResult.getUses().empty() && 10067ebf70d8SMatthias Springer "expected that all uses were already replaced"); 10077ebf70d8SMatthias Springer #endif // NDEBUG 100872de7588SNicolas Vasilache 100972de7588SNicolas Vasilache // Create new ForeachThreadOp without any results and drop the automatically 101072de7588SNicolas Vasilache // introduced terminator. 101172de7588SNicolas Vasilache TypeRange newResultTypes; 10127ebf70d8SMatthias Springer auto newForeachThreadOp = rewriter.create<ForeachThreadOp>( 10137ebf70d8SMatthias Springer foreachThreadOp.getLoc(), newResultTypes, 1014a0f843fdSNicolas Vasilache foreachThreadOp.getNumThreads(), 1015a0f843fdSNicolas Vasilache extractFromI64ArrayAttr(foreachThreadOp.getThreadDimMapping())); 101672de7588SNicolas Vasilache newForeachThreadOp.getBody()->getTerminator()->erase(); 101772de7588SNicolas Vasilache 101872de7588SNicolas Vasilache // Move over block contents of the old op. 10197ebf70d8SMatthias Springer rewriter.mergeBlocks(foreachThreadOp.getBody(), 10207ebf70d8SMatthias Springer newForeachThreadOp.getBody(), 102172de7588SNicolas Vasilache {newForeachThreadOp.getBody()->getArguments()}); 102272de7588SNicolas Vasilache 10237ebf70d8SMatthias Springer // Remove the old op. 10247ebf70d8SMatthias Springer rewriter.eraseOp(op); 102572de7588SNicolas Vasilache 102672de7588SNicolas Vasilache return success(); 102772de7588SNicolas Vasilache } 102872de7588SNicolas Vasilache }; 102972de7588SNicolas Vasilache 103072de7588SNicolas Vasilache /// Nothing to do for PerformConcurrentlyOp. 103172de7588SNicolas Vasilache struct PerformConcurrentlyOpInterface 103272de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel< 103372de7588SNicolas Vasilache PerformConcurrentlyOpInterface, PerformConcurrentlyOp> { 103472de7588SNicolas Vasilache LogicalResult bufferize(Operation *op, RewriterBase &b, 1035b55d55ecSMatthias Springer const BufferizationOptions &options) const { 1036b3ebe3beSMatthias Springer llvm_unreachable("op does not have any tensor OpOperands / OpResults"); 103772de7588SNicolas Vasilache return failure(); 103872de7588SNicolas Vasilache } 103972de7588SNicolas Vasilache }; 104072de7588SNicolas Vasilache 104172de7588SNicolas Vasilache /// Return true if the (ExtractSliceOp, ParallelInsertSliceOp) pair match (i.e. 104272de7588SNicolas Vasilache /// equivalent operand / result and same offset/sizes/strides specification). 104372de7588SNicolas Vasilache static bool areEquivalentExtractSliceOps(const AnalysisState &state, 104472de7588SNicolas Vasilache ExtractSliceOp st, 104572de7588SNicolas Vasilache ParallelInsertSliceOp sti) { 104672de7588SNicolas Vasilache if (!st || !sti) 104772de7588SNicolas Vasilache return false; 104872de7588SNicolas Vasilache if (st != sti && 10498df54a6aSJacques Pienaar !state.areEquivalentBufferizedValues(st.getSource(), sti.getDest())) 105072de7588SNicolas Vasilache return false; 105172de7588SNicolas Vasilache if (!sameOffsetsSizesAndStrides(st, sti, isEqualConstantIntOrValue)) 105272de7588SNicolas Vasilache return false; 105372de7588SNicolas Vasilache return true; 105472de7588SNicolas Vasilache } 105572de7588SNicolas Vasilache 105672de7588SNicolas Vasilache /// Return true if `value` is originating from an ExtractSliceOp that matches 105772de7588SNicolas Vasilache /// the given InsertSliceOp. 105872de7588SNicolas Vasilache static bool hasMatchingExtractSliceOp(const AnalysisState &state, Value value, 105972de7588SNicolas Vasilache ParallelInsertSliceOp insertOp) { 106072de7588SNicolas Vasilache auto condition = [&](Value val) { 106172de7588SNicolas Vasilache if (auto extractOp = val.getDefiningOp<ExtractSliceOp>()) 106272de7588SNicolas Vasilache if (areEquivalentExtractSliceOps(state, extractOp, insertOp)) 106372de7588SNicolas Vasilache return true; 106472de7588SNicolas Vasilache return false; 106572de7588SNicolas Vasilache }; 106672de7588SNicolas Vasilache 106772de7588SNicolas Vasilache return llvm::all_of(state.findValueInReverseUseDefChain(value, condition), 106872de7588SNicolas Vasilache condition); 106972de7588SNicolas Vasilache } 107072de7588SNicolas Vasilache 107172de7588SNicolas Vasilache /// Analysis of ParallelInsertSliceOp. 107272de7588SNicolas Vasilache struct ParallelInsertSliceOpInterface 107372de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel< 107472de7588SNicolas Vasilache ParallelInsertSliceOpInterface, ParallelInsertSliceOp> { 107572de7588SNicolas Vasilache SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 107672de7588SNicolas Vasilache const AnalysisState &state) const { 107772de7588SNicolas Vasilache if (&opOperand != &op->getOpOperand(1) /*dest*/) 107872de7588SNicolas Vasilache return {}; 107972de7588SNicolas Vasilache 108072de7588SNicolas Vasilache // ParallelInsertSliceOp itself has no results. Tensors are returned via 108172de7588SNicolas Vasilache // the parent op. 108272de7588SNicolas Vasilache auto foreachThreadOp = op->getParentOfType<ForeachThreadOp>(); 108372de7588SNicolas Vasilache assert(foreachThreadOp && 108472de7588SNicolas Vasilache "could not find valid owner of parallel_insert_slice"); 108572de7588SNicolas Vasilache 108672de7588SNicolas Vasilache // The i-th ParallelInsertSliceOp result is returned via the i-th OpResult 108772de7588SNicolas Vasilache // of the parent ForeachThreadOp. 108872de7588SNicolas Vasilache Block *block = op->getBlock(); 108972de7588SNicolas Vasilache unsigned int opIdx = 0; 109072de7588SNicolas Vasilache for (ParallelInsertSliceOp insertOp : 109172de7588SNicolas Vasilache block->getOps<ParallelInsertSliceOp>()) { 109272de7588SNicolas Vasilache if (insertOp.getOperation() == op) 109372de7588SNicolas Vasilache break; 109472de7588SNicolas Vasilache ++opIdx; 109572de7588SNicolas Vasilache } 109672de7588SNicolas Vasilache assert(opIdx < foreachThreadOp->getNumResults() && 109772de7588SNicolas Vasilache "could not find op inside terminator op"); 109872de7588SNicolas Vasilache 109972de7588SNicolas Vasilache return {foreachThreadOp->getResult(opIdx)}; 110072de7588SNicolas Vasilache } 110172de7588SNicolas Vasilache 110272de7588SNicolas Vasilache bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 110372de7588SNicolas Vasilache const AnalysisState &state) const { 110472de7588SNicolas Vasilache return true; 110572de7588SNicolas Vasilache } 110672de7588SNicolas Vasilache 110772de7588SNicolas Vasilache bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 110872de7588SNicolas Vasilache const AnalysisState &state) const { 110972de7588SNicolas Vasilache return &opOperand == &op->getOpOperand(1) /*dest*/; 111072de7588SNicolas Vasilache } 111172de7588SNicolas Vasilache 111272de7588SNicolas Vasilache BufferRelation bufferRelation(Operation *op, OpResult opResult, 111372de7588SNicolas Vasilache const AnalysisState &state) const { 111472de7588SNicolas Vasilache return BufferRelation::Equivalent; 111572de7588SNicolas Vasilache } 111672de7588SNicolas Vasilache 1117d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 1118d361ecbdSMatthias Springer const AnalysisState &state) const { 1119d361ecbdSMatthias Springer return success(); 1120d361ecbdSMatthias Springer } 1121d361ecbdSMatthias Springer 11227ebf70d8SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 1123b55d55ecSMatthias Springer const BufferizationOptions &options) const { 11247ebf70d8SMatthias Springer OpBuilder::InsertionGuard g(rewriter); 11257ebf70d8SMatthias Springer auto insertOp = cast<ParallelInsertSliceOp>(op); 11267ebf70d8SMatthias Springer auto performConcurrentlyOp = cast<PerformConcurrentlyOp>(op->getParentOp()); 11277ebf70d8SMatthias Springer auto foreachThreadOp = 11287ebf70d8SMatthias Springer cast<ForeachThreadOp>(performConcurrentlyOp->getParentOp()); 11297ebf70d8SMatthias Springer 11307ebf70d8SMatthias Springer // If the op bufferizes out-of-place, allocate the copy before the 11317ebf70d8SMatthias Springer // ForeachThreadOp. 11327ebf70d8SMatthias Springer rewriter.setInsertionPoint(foreachThreadOp); 11335d50f51cSMatthias Springer FailureOr<Value> destBuffer = 11345d50f51cSMatthias Springer getBuffer(rewriter, insertOp.getDest(), options); 11355d50f51cSMatthias Springer if (failed(destBuffer)) 11365d50f51cSMatthias Springer return failure(); 11377ebf70d8SMatthias Springer 11387ebf70d8SMatthias Springer // Bufferize the ParallelInsertSliceOp outside of the PerformConcurrentlyOp. 11397ebf70d8SMatthias Springer rewriter.setInsertionPoint(performConcurrentlyOp); 11405d50f51cSMatthias Springer FailureOr<Value> srcBuffer = 11415d50f51cSMatthias Springer getBuffer(rewriter, insertOp.getSource(), options); 11425d50f51cSMatthias Springer if (failed(srcBuffer)) 11435d50f51cSMatthias Springer return failure(); 11447ebf70d8SMatthias Springer Value subview = rewriter.create<memref::SubViewOp>( 11455d50f51cSMatthias Springer insertOp.getLoc(), *destBuffer, insertOp.getMixedOffsets(), 11467ebf70d8SMatthias Springer insertOp.getMixedSizes(), insertOp.getMixedStrides()); 11477ebf70d8SMatthias Springer // This memcpy will fold away if everything bufferizes in-place. 11485d50f51cSMatthias Springer if (failed(options.createMemCpy(rewriter, insertOp.getLoc(), *srcBuffer, 11497ebf70d8SMatthias Springer subview))) 11507ebf70d8SMatthias Springer return failure(); 11517ebf70d8SMatthias Springer rewriter.eraseOp(op); 11527ebf70d8SMatthias Springer 11537ebf70d8SMatthias Springer // Replace all uses of ForeachThreadOp (just the corresponding result). 11547ebf70d8SMatthias Springer rewriter.setInsertionPointAfter(foreachThreadOp); 11557ebf70d8SMatthias Springer Value toTensorOp = 11565d50f51cSMatthias Springer rewriter.create<ToTensorOp>(foreachThreadOp.getLoc(), *destBuffer); 11577ebf70d8SMatthias Springer unsigned resultNum = 0; 11587ebf70d8SMatthias Springer for (Operation &nextOp : performConcurrentlyOp.yieldingOps()) { 11597ebf70d8SMatthias Springer if (&nextOp == op) 11607ebf70d8SMatthias Springer break; 11617ebf70d8SMatthias Springer resultNum++; 11627ebf70d8SMatthias Springer } 11637ebf70d8SMatthias Springer assert(resultNum < foreachThreadOp->getNumResults() && 11647ebf70d8SMatthias Springer "ParallelInsertSliceOp not found in PerformConcurrentlyOp"); 11657ebf70d8SMatthias Springer SmallVector<OpOperand *> resultUses = llvm::to_vector( 11667ebf70d8SMatthias Springer llvm::map_range(foreachThreadOp->getResult(resultNum).getUses(), 11677ebf70d8SMatthias Springer [](OpOperand &use) { return &use; })); 11687ebf70d8SMatthias Springer for (OpOperand *use : resultUses) { 11697ebf70d8SMatthias Springer rewriter.updateRootInPlace(use->getOwner(), 11707ebf70d8SMatthias Springer [&]() { use->set(toTensorOp); }); 11717ebf70d8SMatthias Springer } 1172ba9d886dSMatthias Springer return success(); 117372de7588SNicolas Vasilache } 117472de7588SNicolas Vasilache 117572de7588SNicolas Vasilache // TODO: This is copied from TensorInterfaceImpl.cpp. Find a way to share 117672de7588SNicolas Vasilache // the code. 117772de7588SNicolas Vasilache bool isNotConflicting(Operation *op, OpOperand *uRead, 117872de7588SNicolas Vasilache OpOperand *uConflictingWrite, 117972de7588SNicolas Vasilache const AnalysisState &state) const { 118072de7588SNicolas Vasilache Operation *readingOp = uRead->getOwner(); 118172de7588SNicolas Vasilache Operation *conflictingWritingOp = uConflictingWrite->getOwner(); 118272de7588SNicolas Vasilache 118372de7588SNicolas Vasilache // Special rules for matching ExtractSliceOp/InsertSliceOp pairs. If 118472de7588SNicolas Vasilache // uRead is an InsertSliceOp... 118572de7588SNicolas Vasilache if (auto insertSliceOp = dyn_cast<ParallelInsertSliceOp>(readingOp)) { 118672de7588SNicolas Vasilache // As an example, consider the following IR. 118772de7588SNicolas Vasilache // 118872de7588SNicolas Vasilache // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] } 118972de7588SNicolas Vasilache // %1 = linalg.fill %cst, %0 {inplace= [true] } 119072de7588SNicolas Vasilache // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1] 119172de7588SNicolas Vasilache // {inplace= [true] } 119272de7588SNicolas Vasilache 119372de7588SNicolas Vasilache // TODO: Use insertSliceOp.getDestOpOperand etc. when available. 119472de7588SNicolas Vasilache if (uRead == &insertSliceOp->getOpOperand(1) /*dest*/ && 119572de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, uConflictingWrite->get(), 119672de7588SNicolas Vasilache insertSliceOp)) 119772de7588SNicolas Vasilache // Case 1: The main insight is that InsertSliceOp reads only part of 119872de7588SNicolas Vasilache // the destination tensor. The overwritten area is not read. If 119972de7588SNicolas Vasilache // uConflictingWrite writes into exactly the memory location that is 120072de7588SNicolas Vasilache // being read by uRead, this is not a conflict. 120172de7588SNicolas Vasilache // 120272de7588SNicolas Vasilache // In the above example: 120372de7588SNicolas Vasilache // uRead = OpOperand 1 (%t) of tensor.insert_slice 120472de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%0) of linalg.fill 120572de7588SNicolas Vasilache // 120672de7588SNicolas Vasilache // The read of %t does not conflict with the write of the FillOp 120772de7588SNicolas Vasilache // (same aliases!) because the area that the FillOp operates on is 120872de7588SNicolas Vasilache // exactly the one that is *not* read via %t. 120972de7588SNicolas Vasilache return true; 121072de7588SNicolas Vasilache 121172de7588SNicolas Vasilache if (uRead == &insertSliceOp->getOpOperand(0) /*source*/ && 121272de7588SNicolas Vasilache uConflictingWrite == &insertSliceOp->getOpOperand(1) /*dest*/ && 121372de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, uRead->get(), insertSliceOp)) 121472de7588SNicolas Vasilache // Case 2: The read of the source tensor and the write to the dest 121572de7588SNicolas Vasilache // tensor via an InsertSliceOp is not a conflict if the read is 121672de7588SNicolas Vasilache // reading exactly that part of an equivalent tensor that the 121772de7588SNicolas Vasilache // InsertSliceOp is writing. 121872de7588SNicolas Vasilache // 121972de7588SNicolas Vasilache // In the above example: 122072de7588SNicolas Vasilache // uRead = OpOperand 0 (%1) of tensor.insert_slice 122172de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice 122272de7588SNicolas Vasilache return true; 122372de7588SNicolas Vasilache } 122472de7588SNicolas Vasilache 122572de7588SNicolas Vasilache // If uConflictingWrite is an InsertSliceOp... 122672de7588SNicolas Vasilache if (auto insertSliceOp = 122772de7588SNicolas Vasilache dyn_cast<ParallelInsertSliceOp>(conflictingWritingOp)) 122872de7588SNicolas Vasilache // As an example, consider the following IR. 122972de7588SNicolas Vasilache // 123072de7588SNicolas Vasilache // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] } 123172de7588SNicolas Vasilache // %1 = linalg.fill %cst, %0 {inplace= [true] } 123272de7588SNicolas Vasilache // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1] 123372de7588SNicolas Vasilache // {inplace= [true] } 123472de7588SNicolas Vasilache // %3 = vector.transfer_read %1, %cst 123572de7588SNicolas Vasilache // 123672de7588SNicolas Vasilache // In the above example: 123772de7588SNicolas Vasilache // uRead = OpOperand 0 (%1) of vector.transfer_read 123872de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice 123972de7588SNicolas Vasilache // lastWrite = %1 124072de7588SNicolas Vasilache // 124172de7588SNicolas Vasilache // This is not a conflict because the InsertSliceOp overwrites the 124272de7588SNicolas Vasilache // memory segment of %1 with the exact same data. (Effectively, there 124372de7588SNicolas Vasilache // is no memory write here.) 124472de7588SNicolas Vasilache if (uConflictingWrite == &insertSliceOp->getOpOperand(1) /*dest*/ && 124572de7588SNicolas Vasilache state.areEquivalentBufferizedValues(uRead->get(), 124672de7588SNicolas Vasilache insertSliceOp.getSource()) && 124772de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, insertSliceOp.getSource(), 124872de7588SNicolas Vasilache insertSliceOp)) 124972de7588SNicolas Vasilache return true; 125072de7588SNicolas Vasilache 125172de7588SNicolas Vasilache return false; 125272de7588SNicolas Vasilache } 125372de7588SNicolas Vasilache }; 125472de7588SNicolas Vasilache 125519efe141SMatthias Springer } // namespace 125619efe141SMatthias Springer } // namespace scf 125719efe141SMatthias Springer } // namespace mlir 125819efe141SMatthias Springer 125919efe141SMatthias Springer void mlir::scf::registerBufferizableOpInterfaceExternalModels( 126019efe141SMatthias Springer DialectRegistry ®istry) { 126177eee579SRiver Riddle registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) { 126277eee579SRiver Riddle ExecuteRegionOp::attachInterface<ExecuteRegionOpInterface>(*ctx); 126377eee579SRiver Riddle ForOp::attachInterface<ForOpInterface>(*ctx); 126477eee579SRiver Riddle IfOp::attachInterface<IfOpInterface>(*ctx); 126572de7588SNicolas Vasilache ForeachThreadOp::attachInterface<ForeachThreadOpInterface>(*ctx); 126672de7588SNicolas Vasilache ParallelInsertSliceOp::attachInterface<ParallelInsertSliceOpInterface>( 126772de7588SNicolas Vasilache *ctx); 126872de7588SNicolas Vasilache PerformConcurrentlyOp::attachInterface<PerformConcurrentlyOpInterface>( 126972de7588SNicolas Vasilache *ctx); 1270a5d09c63SMatthias Springer WhileOp::attachInterface<WhileOpInterface>(*ctx); 127177eee579SRiver Riddle YieldOp::attachInterface<YieldOpInterface>(*ctx); 127277eee579SRiver Riddle }); 127319efe141SMatthias Springer } 1274