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 919efe141SMatthias Springer #include "mlir/Dialect/SCF/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" 1519efe141SMatthias Springer #include "mlir/Dialect/SCF/SCF.h" 1672de7588SNicolas Vasilache #include "mlir/Dialect/Tensor/IR/Tensor.h" 1719efe141SMatthias Springer #include "mlir/IR/Dialect.h" 1819efe141SMatthias Springer #include "mlir/IR/Operation.h" 1919efe141SMatthias Springer #include "mlir/IR/PatternMatch.h" 2019efe141SMatthias Springer 2119efe141SMatthias Springer using namespace mlir; 2219efe141SMatthias Springer using namespace mlir::bufferization; 2319efe141SMatthias Springer using namespace mlir::scf; 2419efe141SMatthias Springer 2519efe141SMatthias Springer namespace mlir { 2619efe141SMatthias Springer namespace scf { 2719efe141SMatthias Springer namespace { 2819efe141SMatthias Springer 2919efe141SMatthias Springer // bufferization.to_memref is not allowed to change the rank. 3019efe141SMatthias Springer static void ensureToMemrefOpIsValid(Value tensor, Type memrefType) { 3119efe141SMatthias Springer #ifndef NDEBUG 3219efe141SMatthias Springer auto rankedTensorType = tensor.getType().dyn_cast<RankedTensorType>(); 3319efe141SMatthias Springer assert((!rankedTensorType || (memrefType.cast<MemRefType>().getRank() == 3419efe141SMatthias Springer rankedTensorType.getRank())) && 3519efe141SMatthias Springer "to_memref would be invalid: mismatching ranks"); 3619efe141SMatthias Springer #endif 3719efe141SMatthias Springer } 3819efe141SMatthias Springer 3919efe141SMatthias Springer /// Bufferization of scf.execute_region. Can be analyzed, but bufferization not 4019efe141SMatthias Springer /// fully implemented at the moment. 4119efe141SMatthias Springer struct ExecuteRegionOpInterface 4219efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<ExecuteRegionOpInterface, 4319efe141SMatthias Springer scf::ExecuteRegionOp> { 4419efe141SMatthias Springer SmallVector<OpOperand *> 4519efe141SMatthias Springer getAliasingOpOperand(Operation *op, OpResult opResult, 469597b16aSMatthias Springer const AnalysisState &state) const { 4719efe141SMatthias Springer // ExecuteRegionOps do not have tensor OpOperands. The yielded value can be 4819efe141SMatthias Springer // any SSA value that is in scope. To allow for use-def chain traversal 4919efe141SMatthias Springer // through ExecuteRegionOps in the analysis, the corresponding yield value 5019efe141SMatthias Springer // is considered to be aliasing with the result. 5119efe141SMatthias Springer auto executeRegionOp = cast<scf::ExecuteRegionOp>(op); 5219efe141SMatthias Springer size_t resultNum = std::distance(op->getOpResults().begin(), 5319efe141SMatthias Springer llvm::find(op->getOpResults(), opResult)); 5419efe141SMatthias Springer // TODO: Support multiple blocks. 5519efe141SMatthias Springer assert(executeRegionOp.getRegion().getBlocks().size() == 1 && 5619efe141SMatthias Springer "expected exactly 1 block"); 5719efe141SMatthias Springer auto yieldOp = dyn_cast<scf::YieldOp>( 5819efe141SMatthias Springer executeRegionOp.getRegion().front().getTerminator()); 5919efe141SMatthias Springer assert(yieldOp && "expected scf.yield terminator in scf.execute_region"); 6019efe141SMatthias Springer return {&yieldOp->getOpOperand(resultNum)}; 6119efe141SMatthias Springer } 6219efe141SMatthias Springer 6319efe141SMatthias Springer // TODO: For better bufferization results, this could return `true` only if 6419efe141SMatthias Springer // there is a memory write in the region. 6519efe141SMatthias Springer bool isMemoryWrite(Operation *op, OpResult opResult, 669597b16aSMatthias Springer const AnalysisState &state) const { 6719efe141SMatthias Springer // Similar to scf.if, results of this op are always considered memory writes 6819efe141SMatthias Springer // in the analysis. This is a useful pattern for all ops that have tensor 6919efe141SMatthias Springer // OpResults but no tensor OpOperands. By default, `isMemoryWrite` is 7019efe141SMatthias Springer // implemented in terms of `bufferizesToMemoryWrite`, which does not work on 7119efe141SMatthias Springer // ops without OpOperands. 7219efe141SMatthias Springer return true; 7319efe141SMatthias Springer } 7419efe141SMatthias Springer 7519efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 76b55d55ecSMatthias Springer const BufferizationOptions &options) const { 7719efe141SMatthias Springer auto executeRegionOp = cast<scf::ExecuteRegionOp>(op); 7819efe141SMatthias Springer 7919efe141SMatthias Springer // Compute new result types. 8019efe141SMatthias Springer SmallVector<Type> newResultTypes; 8119efe141SMatthias Springer for (Type type : executeRegionOp->getResultTypes()) { 8219efe141SMatthias Springer if (auto tensorType = type.dyn_cast<TensorType>()) { 8312e41d92SMatthias Springer // TODO: Infer the result type instead of computing it. 84b55d55ecSMatthias Springer newResultTypes.push_back(getMemRefType(tensorType, options)); 8519efe141SMatthias Springer } else { 8619efe141SMatthias Springer newResultTypes.push_back(type); 8719efe141SMatthias Springer } 8819efe141SMatthias Springer } 8919efe141SMatthias Springer 9019efe141SMatthias Springer // Create new op and move over region. 9119efe141SMatthias Springer auto newOp = 9219efe141SMatthias Springer rewriter.create<scf::ExecuteRegionOp>(op->getLoc(), newResultTypes); 9319efe141SMatthias Springer newOp.getRegion().takeBody(executeRegionOp.getRegion()); 9419efe141SMatthias Springer 9519efe141SMatthias Springer // Update terminator. 9619efe141SMatthias Springer assert(newOp.getRegion().getBlocks().size() == 1 && 9719efe141SMatthias Springer "only 1 block supported"); 9819efe141SMatthias Springer Block *newBlock = &newOp.getRegion().front(); 9919efe141SMatthias Springer auto yieldOp = cast<scf::YieldOp>(newBlock->getTerminator()); 10019efe141SMatthias Springer rewriter.setInsertionPoint(yieldOp); 10119efe141SMatthias Springer SmallVector<Value> newYieldValues; 102bb6119ebSMehdi Amini for (const auto &it : llvm::enumerate(yieldOp.getResults())) { 10319efe141SMatthias Springer Value val = it.value(); 10419efe141SMatthias Springer if (val.getType().isa<TensorType>()) { 10519efe141SMatthias Springer newYieldValues.push_back(rewriter.create<bufferization::ToMemrefOp>( 10619efe141SMatthias Springer yieldOp.getLoc(), newResultTypes[it.index()], val)); 10719efe141SMatthias Springer } else { 10819efe141SMatthias Springer newYieldValues.push_back(val); 10919efe141SMatthias Springer } 11019efe141SMatthias Springer } 11119efe141SMatthias Springer rewriter.replaceOpWithNewOp<scf::YieldOp>(yieldOp, newYieldValues); 11219efe141SMatthias Springer 11319efe141SMatthias Springer // Update all uses of the old op. 11419efe141SMatthias Springer rewriter.setInsertionPointAfter(newOp); 11519efe141SMatthias Springer SmallVector<Value> newResults; 116bb6119ebSMehdi Amini for (const auto &it : llvm::enumerate(executeRegionOp->getResultTypes())) { 11719efe141SMatthias Springer if (it.value().isa<TensorType>()) { 11819efe141SMatthias Springer newResults.push_back(rewriter.create<bufferization::ToTensorOp>( 11919efe141SMatthias Springer executeRegionOp.getLoc(), newOp->getResult(it.index()))); 12019efe141SMatthias Springer } else { 12119efe141SMatthias Springer newResults.push_back(newOp->getResult(it.index())); 12219efe141SMatthias Springer } 12319efe141SMatthias Springer } 12419efe141SMatthias Springer 12519efe141SMatthias Springer // Replace old op. 12619efe141SMatthias Springer rewriter.replaceOp(executeRegionOp, newResults); 12719efe141SMatthias Springer 12819efe141SMatthias Springer return success(); 12919efe141SMatthias Springer } 13019efe141SMatthias Springer 13119efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 1329597b16aSMatthias Springer const AnalysisState &state) const { 13319efe141SMatthias Springer return BufferRelation::Equivalent; 13419efe141SMatthias Springer } 13519efe141SMatthias Springer }; 13619efe141SMatthias Springer 13719efe141SMatthias Springer /// Bufferization of scf.if. Replace with a new scf.if that yields memrefs. 13819efe141SMatthias Springer struct IfOpInterface 13919efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<IfOpInterface, scf::IfOp> { 14019efe141SMatthias Springer SmallVector<OpOperand *> 14119efe141SMatthias Springer getAliasingOpOperand(Operation *op, OpResult opResult, 1429597b16aSMatthias Springer const AnalysisState &state) const { 14319efe141SMatthias Springer // IfOps do not have tensor OpOperands. The yielded value can be any SSA 14419efe141SMatthias Springer // value that is in scope. To allow for use-def chain traversal through 14519efe141SMatthias Springer // IfOps in the analysis, both corresponding yield values from the then/else 14619efe141SMatthias Springer // branches are considered to be aliasing with the result. 14719efe141SMatthias Springer auto ifOp = cast<scf::IfOp>(op); 14819efe141SMatthias Springer size_t resultNum = std::distance(op->getOpResults().begin(), 14919efe141SMatthias Springer llvm::find(op->getOpResults(), opResult)); 15019efe141SMatthias Springer return {&ifOp.thenYield()->getOpOperand(resultNum), 15119efe141SMatthias Springer &ifOp.elseYield()->getOpOperand(resultNum)}; 15219efe141SMatthias Springer } 15319efe141SMatthias Springer 15419efe141SMatthias Springer // TODO: For better bufferization results, this could return `true` only if 15519efe141SMatthias Springer // there is a memory write in one (or both) of the branches. Since this is not 15619efe141SMatthias Springer // allowed at the moment, we should never encounter scf.ifs that yield 15719efe141SMatthias Springer // unmodified tensors. Such scf.yield ops could just fold away. 15819efe141SMatthias Springer bool isMemoryWrite(Operation *op, OpResult opResult, 1599597b16aSMatthias Springer const AnalysisState &state) const { 16019efe141SMatthias Springer // IfOp results are always considered memory writes in the analysis. This 16119efe141SMatthias Springer // design decision simplifies the analysis considerably. E.g., consider the 16219efe141SMatthias Springer // following test case: 16319efe141SMatthias Springer // 16419efe141SMatthias Springer // %0 = "some_writing_op" : tensor<?xf32> 16519efe141SMatthias Springer // %r = scf.if %c -> (tensor<?xf32>) { 16619efe141SMatthias Springer // scf.yield %0 16719efe141SMatthias Springer // } else { 16819efe141SMatthias Springer // %1 = "another_writing_op"(%0) : tensor<?xf32> 16919efe141SMatthias Springer // } 17019efe141SMatthias Springer // "some_reading_op"(%r) 17119efe141SMatthias Springer // 17219efe141SMatthias Springer // "another_writing_op" in the above example should be able to bufferize 17319efe141SMatthias Springer // inplace in the absence of another read of %0. However, if the scf.if op 17419efe141SMatthias Springer // would not be considered a "write", the analysis would detect the 17519efe141SMatthias Springer // following conflict: 17619efe141SMatthias Springer // 17719efe141SMatthias Springer // * read = some_reading_op 17819efe141SMatthias Springer // * lastWrite = %0 (Note: The last write of %r would be a set: {%0, %1}.) 17919efe141SMatthias Springer // * conflictingWrite = %1 18019efe141SMatthias Springer // 18119efe141SMatthias Springer // For more details, check the "scf.IfOp" section of the design document. 18219efe141SMatthias Springer return true; 18319efe141SMatthias Springer } 18419efe141SMatthias Springer 18519efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 186b55d55ecSMatthias Springer const BufferizationOptions &options) const { 18719efe141SMatthias Springer auto ifOp = cast<scf::IfOp>(op); 18819efe141SMatthias Springer 18919efe141SMatthias Springer // Compute new types of the bufferized scf.if op. 19019efe141SMatthias Springer SmallVector<Type> newTypes; 19119efe141SMatthias Springer for (Type returnType : ifOp->getResultTypes()) { 19219efe141SMatthias Springer if (auto tensorType = returnType.dyn_cast<TensorType>()) { 19312e41d92SMatthias Springer // TODO: Infer the result type instead of computing it. 194b55d55ecSMatthias Springer newTypes.push_back(getMemRefType(tensorType, options)); 19519efe141SMatthias Springer } else { 19619efe141SMatthias Springer newTypes.push_back(returnType); 19719efe141SMatthias Springer } 19819efe141SMatthias Springer } 19919efe141SMatthias Springer 20019efe141SMatthias Springer // Create new op. 20119efe141SMatthias Springer auto newIfOp = 20219efe141SMatthias Springer rewriter.create<scf::IfOp>(ifOp.getLoc(), newTypes, ifOp.getCondition(), 20319efe141SMatthias Springer /*withElseRegion=*/true); 20419efe141SMatthias Springer 20519efe141SMatthias Springer // Remove terminators. 20619efe141SMatthias Springer if (!newIfOp.thenBlock()->empty()) { 20719efe141SMatthias Springer rewriter.eraseOp(newIfOp.thenBlock()->getTerminator()); 20819efe141SMatthias Springer rewriter.eraseOp(newIfOp.elseBlock()->getTerminator()); 20919efe141SMatthias Springer } 21019efe141SMatthias Springer 21119efe141SMatthias Springer // Move over then/else blocks. 21219efe141SMatthias Springer rewriter.mergeBlocks(ifOp.thenBlock(), newIfOp.thenBlock()); 21319efe141SMatthias Springer rewriter.mergeBlocks(ifOp.elseBlock(), newIfOp.elseBlock()); 21419efe141SMatthias Springer 21519efe141SMatthias Springer // Update scf.yield of new then-block. 21619efe141SMatthias Springer auto thenYieldOp = cast<scf::YieldOp>(newIfOp.thenBlock()->getTerminator()); 21719efe141SMatthias Springer rewriter.setInsertionPoint(thenYieldOp); 21819efe141SMatthias Springer SmallVector<Value> thenYieldValues; 21919efe141SMatthias Springer for (OpOperand &operand : thenYieldOp->getOpOperands()) { 22019efe141SMatthias Springer if (operand.get().getType().isa<TensorType>()) { 22119efe141SMatthias Springer ensureToMemrefOpIsValid(operand.get(), 22219efe141SMatthias Springer newTypes[operand.getOperandNumber()]); 22319efe141SMatthias Springer Value toMemrefOp = rewriter.create<bufferization::ToMemrefOp>( 22419efe141SMatthias Springer operand.get().getLoc(), newTypes[operand.getOperandNumber()], 22519efe141SMatthias Springer operand.get()); 22619efe141SMatthias Springer operand.set(toMemrefOp); 22719efe141SMatthias Springer } 22819efe141SMatthias Springer } 22919efe141SMatthias Springer 23019efe141SMatthias Springer // Update scf.yield of new else-block. 23119efe141SMatthias Springer auto elseYieldOp = cast<scf::YieldOp>(newIfOp.elseBlock()->getTerminator()); 23219efe141SMatthias Springer rewriter.setInsertionPoint(elseYieldOp); 23319efe141SMatthias Springer SmallVector<Value> elseYieldValues; 23419efe141SMatthias Springer for (OpOperand &operand : elseYieldOp->getOpOperands()) { 23519efe141SMatthias Springer if (operand.get().getType().isa<TensorType>()) { 23619efe141SMatthias Springer ensureToMemrefOpIsValid(operand.get(), 23719efe141SMatthias Springer newTypes[operand.getOperandNumber()]); 23819efe141SMatthias Springer Value toMemrefOp = rewriter.create<bufferization::ToMemrefOp>( 23919efe141SMatthias Springer operand.get().getLoc(), newTypes[operand.getOperandNumber()], 24019efe141SMatthias Springer operand.get()); 24119efe141SMatthias Springer operand.set(toMemrefOp); 24219efe141SMatthias Springer } 24319efe141SMatthias Springer } 24419efe141SMatthias Springer 24519efe141SMatthias Springer // Replace op results. 24619efe141SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newIfOp->getResults()); 24719efe141SMatthias Springer 24819efe141SMatthias Springer return success(); 24919efe141SMatthias Springer } 25019efe141SMatthias Springer 25119efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 2529597b16aSMatthias Springer const AnalysisState &state) const { 25319efe141SMatthias Springer // IfOp results are equivalent to their corresponding yield values if both 25419efe141SMatthias Springer // yield values are equivalent to each other. 25519efe141SMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 25619efe141SMatthias Springer SmallVector<OpOperand *> yieldValues = 25719efe141SMatthias Springer bufferizableOp.getAliasingOpOperand(opResult, state); 25819efe141SMatthias Springer assert(yieldValues.size() == 2 && "expected 2 yield values"); 25919efe141SMatthias Springer bool equivalentYields = state.areEquivalentBufferizedValues( 26019efe141SMatthias Springer yieldValues[0]->get(), yieldValues[1]->get()); 26119efe141SMatthias Springer return equivalentYields ? BufferRelation::Equivalent : BufferRelation::None; 26219efe141SMatthias Springer } 26319efe141SMatthias Springer }; 26419efe141SMatthias Springer 265417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the indices of all values 266417e1c7dSMatthias Springer /// that have a tensor type. 267417e1c7dSMatthias Springer static DenseSet<int64_t> getTensorIndices(ValueRange values) { 268417e1c7dSMatthias Springer DenseSet<int64_t> result; 269417e1c7dSMatthias Springer for (const auto &it : llvm::enumerate(values)) 270417e1c7dSMatthias Springer if (it.value().getType().isa<TensorType>()) 271417e1c7dSMatthias Springer result.insert(it.index()); 272417e1c7dSMatthias Springer return result; 273417e1c7dSMatthias Springer } 274417e1c7dSMatthias Springer 275417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the indices of all 276417e1c7dSMatthias Springer /// bbArg/yielded value pairs who's buffer relation is "Equivalent". 277a5d09c63SMatthias Springer DenseSet<int64_t> getEquivalentBuffers(Block::BlockArgListType bbArgs, 278417e1c7dSMatthias Springer ValueRange yieldedValues, 279417e1c7dSMatthias Springer const AnalysisState &state) { 280996834e6SMatthias Springer unsigned int minSize = std::min(bbArgs.size(), yieldedValues.size()); 281417e1c7dSMatthias Springer DenseSet<int64_t> result; 282996834e6SMatthias Springer for (unsigned int i = 0; i < minSize; ++i) { 283996834e6SMatthias Springer if (!bbArgs[i].getType().isa<TensorType>() || 284996834e6SMatthias Springer !yieldedValues[i].getType().isa<TensorType>()) 285417e1c7dSMatthias Springer continue; 286996834e6SMatthias Springer if (state.areEquivalentBufferizedValues(bbArgs[i], yieldedValues[i])) 287996834e6SMatthias Springer result.insert(i); 288417e1c7dSMatthias Springer } 289417e1c7dSMatthias Springer return result; 290417e1c7dSMatthias Springer } 291417e1c7dSMatthias Springer 292417e1c7dSMatthias Springer /// Helper function for loop bufferization. Cast the given buffer to the given 293417e1c7dSMatthias Springer /// memref type. 294417e1c7dSMatthias Springer static Value castBuffer(OpBuilder &b, Value buffer, Type type) { 295417e1c7dSMatthias Springer assert(type.isa<BaseMemRefType>() && "expected BaseMemRefType"); 296417e1c7dSMatthias Springer assert(buffer.getType().isa<BaseMemRefType>() && "expected BaseMemRefType"); 297417e1c7dSMatthias Springer // If the buffer already has the correct type, no cast is needed. 298417e1c7dSMatthias Springer if (buffer.getType() == type) 299417e1c7dSMatthias Springer return buffer; 300417e1c7dSMatthias Springer // TODO: In case `type` has a layout map that is not the fully dynamic 301417e1c7dSMatthias Springer // one, we may not be able to cast the buffer. In that case, the loop 302417e1c7dSMatthias Springer // iter_arg's layout map must be changed (see uses of `castBuffer`). 303417e1c7dSMatthias Springer assert(memref::CastOp::areCastCompatible(buffer.getType(), type) && 304417e1c7dSMatthias Springer "scf.while op bufferization: cast incompatible"); 305417e1c7dSMatthias Springer return b.create<memref::CastOp>(buffer.getLoc(), type, buffer).getResult(); 306417e1c7dSMatthias Springer } 307417e1c7dSMatthias Springer 308417e1c7dSMatthias Springer /// Helper function for loop bufferization. Return the bufferized values of the 309417e1c7dSMatthias Springer /// given OpOperands. If an operand is not a tensor, return the original value. 310417e1c7dSMatthias Springer static SmallVector<Value> getBuffers(RewriterBase &rewriter, 311417e1c7dSMatthias Springer MutableArrayRef<OpOperand> operands, 312b55d55ecSMatthias Springer const BufferizationOptions &options) { 313417e1c7dSMatthias Springer SmallVector<Value> result; 314417e1c7dSMatthias Springer for (OpOperand &opOperand : operands) { 315417e1c7dSMatthias Springer if (opOperand.get().getType().isa<TensorType>()) { 316b55d55ecSMatthias Springer Value resultBuffer = getBuffer(rewriter, opOperand.get(), options); 317b3ebe3beSMatthias Springer result.push_back(resultBuffer); 318417e1c7dSMatthias Springer } else { 319417e1c7dSMatthias Springer result.push_back(opOperand.get()); 320417e1c7dSMatthias Springer } 321417e1c7dSMatthias Springer } 322417e1c7dSMatthias Springer return result; 323417e1c7dSMatthias Springer } 324417e1c7dSMatthias Springer 325417e1c7dSMatthias Springer /// Helper function for loop bufferization. Compute the buffer that should be 326b3ebe3beSMatthias Springer /// yielded from a loop block (loop body or loop condition). 327417e1c7dSMatthias Springer static Value getYieldedBuffer(RewriterBase &rewriter, Value tensor, 328b55d55ecSMatthias Springer BaseMemRefType type, 329b55d55ecSMatthias Springer const BufferizationOptions &options) { 330417e1c7dSMatthias Springer assert(tensor.getType().isa<TensorType>() && "expected tensor"); 331417e1c7dSMatthias Springer ensureToMemrefOpIsValid(tensor, type); 332b55d55ecSMatthias Springer Value yieldedVal = getBuffer(rewriter, tensor, options); 333417e1c7dSMatthias Springer return castBuffer(rewriter, yieldedVal, type); 334417e1c7dSMatthias Springer } 335417e1c7dSMatthias Springer 336417e1c7dSMatthias Springer /// Helper function for loop bufferization. Given a range of values, apply 337417e1c7dSMatthias Springer /// `func` to those marked in `tensorIndices`. Otherwise, store the unmodified 338417e1c7dSMatthias Springer /// value in the result vector. 339417e1c7dSMatthias Springer static SmallVector<Value> 340417e1c7dSMatthias Springer convertTensorValues(ValueRange values, const DenseSet<int64_t> &tensorIndices, 341417e1c7dSMatthias Springer llvm::function_ref<Value(Value, int64_t)> func) { 342417e1c7dSMatthias Springer SmallVector<Value> result; 343417e1c7dSMatthias Springer for (const auto &it : llvm::enumerate(values)) { 344417e1c7dSMatthias Springer size_t idx = it.index(); 345417e1c7dSMatthias Springer Value val = it.value(); 346417e1c7dSMatthias Springer result.push_back(tensorIndices.contains(idx) ? func(val, idx) : val); 347417e1c7dSMatthias Springer } 348417e1c7dSMatthias Springer return result; 349417e1c7dSMatthias Springer } 350417e1c7dSMatthias Springer 351417e1c7dSMatthias Springer /// Helper function for loop bufferization. Given a list of pre-bufferization 352417e1c7dSMatthias Springer /// yielded values, compute the list of bufferized yielded values. 353417e1c7dSMatthias Springer SmallVector<Value> getYieldedValues(RewriterBase &rewriter, ValueRange values, 354417e1c7dSMatthias Springer TypeRange bufferizedTypes, 355417e1c7dSMatthias Springer const DenseSet<int64_t> &tensorIndices, 356b55d55ecSMatthias Springer const BufferizationOptions &options) { 357417e1c7dSMatthias Springer return convertTensorValues( 358417e1c7dSMatthias Springer values, tensorIndices, [&](Value val, int64_t index) { 359417e1c7dSMatthias Springer return getYieldedBuffer(rewriter, val, 360417e1c7dSMatthias Springer bufferizedTypes[index].cast<BaseMemRefType>(), 361b55d55ecSMatthias Springer options); 362417e1c7dSMatthias Springer }); 363417e1c7dSMatthias Springer } 364417e1c7dSMatthias Springer 365a5d09c63SMatthias Springer /// Helper function for loop bufferization. Given a list of bbArgs of the new 366a5d09c63SMatthias Springer /// (bufferized) loop op, wrap the bufferized tensor args (now memrefs) into 367a5d09c63SMatthias Springer /// ToTensorOps, so that the block body can be moved over to the new op. 368a5d09c63SMatthias Springer SmallVector<Value> 369a5d09c63SMatthias Springer getBbArgReplacements(RewriterBase &rewriter, Block::BlockArgListType bbArgs, 370a5d09c63SMatthias Springer const DenseSet<int64_t> &tensorIndices) { 371a5d09c63SMatthias Springer return convertTensorValues( 372a5d09c63SMatthias Springer bbArgs, tensorIndices, [&](Value val, int64_t index) { 373a5d09c63SMatthias Springer return rewriter.create<bufferization::ToTensorOp>(val.getLoc(), val); 374a5d09c63SMatthias Springer }); 375a5d09c63SMatthias Springer } 376a5d09c63SMatthias Springer 37719efe141SMatthias Springer /// Bufferization of scf.for. Replace with a new scf.for that operates on 37819efe141SMatthias Springer /// memrefs. 37919efe141SMatthias Springer struct ForOpInterface 38019efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<ForOpInterface, 38119efe141SMatthias Springer scf::ForOp> { 38219efe141SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 3839597b16aSMatthias Springer const AnalysisState &state) const { 38419efe141SMatthias Springer // scf::ForOp alone doesn't bufferize to a memory read, one of the uses of 38519efe141SMatthias Springer // its matching bbArg may. 38619efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 38719efe141SMatthias Springer return state.isValueRead(forOp.getRegionIterArgForOpOperand(opOperand)); 38819efe141SMatthias Springer } 38919efe141SMatthias Springer 39019efe141SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 3919597b16aSMatthias Springer const AnalysisState &state) const { 3921e1eeae8SMatthias Springer // Tensor iter_args of scf::ForOps are always considered as a write. 39319efe141SMatthias Springer return true; 39419efe141SMatthias Springer } 39519efe141SMatthias Springer 3969597b16aSMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 3979597b16aSMatthias Springer const AnalysisState &state) const { 39819efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 399585a8a32SMatthias Springer return {forOp.getResultForOpOperand(opOperand)}; 40019efe141SMatthias Springer } 40119efe141SMatthias Springer 40219efe141SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 4039597b16aSMatthias Springer const AnalysisState &state) const { 40419efe141SMatthias Springer // ForOp results are equivalent to their corresponding init_args if the 40519efe141SMatthias Springer // corresponding iter_args and yield values are equivalent. 40619efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 40719efe141SMatthias Springer OpOperand &forOperand = forOp.getOpOperandForResult(opResult); 40819efe141SMatthias Springer auto bbArg = forOp.getRegionIterArgForOpOperand(forOperand); 4091e1eeae8SMatthias Springer auto yieldOp = 4101e1eeae8SMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 41119efe141SMatthias Springer bool equivalentYield = state.areEquivalentBufferizedValues( 41219efe141SMatthias Springer bbArg, yieldOp->getOperand(opResult.getResultNumber())); 41319efe141SMatthias Springer return equivalentYield ? BufferRelation::Equivalent : BufferRelation::None; 41419efe141SMatthias Springer } 41519efe141SMatthias Springer 41619efe141SMatthias Springer bool isWritable(Operation *op, Value value, 4179597b16aSMatthias Springer const AnalysisState &state) const { 41819efe141SMatthias Springer // Interestingly, scf::ForOp's bbArg can **always** be viewed 41919efe141SMatthias Springer // inplace from the perspective of ops nested under: 42019efe141SMatthias Springer // 1. Either the matching iter operand is not bufferized inplace and an 42119efe141SMatthias Springer // alloc + optional copy makes the bbArg itself inplaceable. 42219efe141SMatthias Springer // 2. Or the matching iter operand is bufferized inplace and bbArg just 42319efe141SMatthias Springer // bufferizes to that too. 42419efe141SMatthias Springer return true; 42519efe141SMatthias Springer } 42619efe141SMatthias Springer 427d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 428d361ecbdSMatthias Springer const AnalysisState &state) const { 429d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 430d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 431d361ecbdSMatthias Springer return failure(); 432d361ecbdSMatthias Springer 433d361ecbdSMatthias Springer if (!state.getOptions().enforceAliasingInvariants) 434d361ecbdSMatthias Springer return success(); 435d361ecbdSMatthias Springer 436d361ecbdSMatthias Springer // According to the `getAliasing...` implementations, a bufferized OpResult 437d361ecbdSMatthias Springer // may alias only with the corresponding bufferized init_arg and with no 438d361ecbdSMatthias Springer // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg; 439d361ecbdSMatthias Springer // but not with any other OpOperand. If a corresponding OpResult/init_arg 440d361ecbdSMatthias Springer // pair bufferizes to equivalent buffers, this aliasing requirement is 441d361ecbdSMatthias Springer // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy. 442d361ecbdSMatthias Springer // (New buffer copies do not alias with any buffer.) 443d361ecbdSMatthias Springer auto forOp = cast<scf::ForOp>(op); 444d361ecbdSMatthias Springer auto yieldOp = 445d361ecbdSMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 446d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 447d361ecbdSMatthias Springer rewriter.setInsertionPoint(yieldOp); 448d361ecbdSMatthias Springer 449d361ecbdSMatthias Springer // Indices of all iter_args that have tensor type. These are the ones that 450d361ecbdSMatthias Springer // are bufferized. 451d361ecbdSMatthias Springer DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs()); 452d361ecbdSMatthias Springer // For every yielded value, is the value equivalent to its corresponding 453d361ecbdSMatthias Springer // bbArg? 454d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYields = getEquivalentBuffers( 455d361ecbdSMatthias Springer forOp.getRegionIterArgs(), yieldOp.getResults(), state); 456d361ecbdSMatthias Springer SmallVector<Value> yieldValues; 457d361ecbdSMatthias Springer for (int64_t idx = 0; 458d361ecbdSMatthias Springer idx < static_cast<int64_t>(yieldOp.getResults().size()); ++idx) { 459d361ecbdSMatthias Springer Value value = yieldOp.getResults()[idx]; 460d361ecbdSMatthias Springer if (!indices.contains(idx) || equivalentYields.contains(idx)) { 461d361ecbdSMatthias Springer yieldValues.push_back(value); 462d361ecbdSMatthias Springer continue; 463d361ecbdSMatthias Springer } 464d361ecbdSMatthias Springer Value alloc = rewriter.create<bufferization::AllocTensorOp>( 465d361ecbdSMatthias Springer yieldOp.getLoc(), value.getType().cast<RankedTensorType>(), 466d361ecbdSMatthias Springer /*dynamicSizes=*/ValueRange(), value, /*escape=*/true); 467d361ecbdSMatthias Springer yieldValues.push_back(alloc); 468d361ecbdSMatthias Springer } 469d361ecbdSMatthias Springer 470d361ecbdSMatthias Springer rewriter.updateRootInPlace( 471d361ecbdSMatthias Springer yieldOp, [&]() { yieldOp.getResultsMutable().assign(yieldValues); }); 472d361ecbdSMatthias Springer return success(); 473d361ecbdSMatthias Springer } 474d361ecbdSMatthias Springer 47519efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 476b55d55ecSMatthias Springer const BufferizationOptions &options) const { 47719efe141SMatthias Springer auto forOp = cast<scf::ForOp>(op); 47819efe141SMatthias Springer Block *oldLoopBody = &forOp.getLoopBody().front(); 47919efe141SMatthias Springer 48019efe141SMatthias Springer // Indices of all iter_args that have tensor type. These are the ones that 48119efe141SMatthias Springer // are bufferized. 482417e1c7dSMatthias Springer DenseSet<int64_t> indices = getTensorIndices(forOp.getInitArgs()); 48319efe141SMatthias Springer 484417e1c7dSMatthias Springer // The new memref init_args of the loop. 485417e1c7dSMatthias Springer SmallVector<Value> initArgs = 486b55d55ecSMatthias Springer getBuffers(rewriter, forOp.getIterOpOperands(), options); 48719efe141SMatthias Springer 48819efe141SMatthias Springer // Construct a new scf.for op with memref instead of tensor values. 48919efe141SMatthias Springer auto newForOp = rewriter.create<scf::ForOp>( 49019efe141SMatthias Springer forOp.getLoc(), forOp.getLowerBound(), forOp.getUpperBound(), 49119efe141SMatthias Springer forOp.getStep(), initArgs); 492413fbb04SLei Zhang newForOp->setAttrs(forOp->getAttrs()); 493417e1c7dSMatthias Springer ValueRange initArgsRange(initArgs); 494417e1c7dSMatthias Springer TypeRange initArgsTypes(initArgsRange); 49519efe141SMatthias Springer Block *loopBody = &newForOp.getLoopBody().front(); 49619efe141SMatthias Springer 49719efe141SMatthias Springer // Set up new iter_args. The loop body uses tensors, so wrap the (memref) 49819efe141SMatthias Springer // iter_args of the new loop in ToTensorOps. 49919efe141SMatthias Springer rewriter.setInsertionPointToStart(loopBody); 500a5d09c63SMatthias Springer SmallVector<Value> iterArgs = 501a5d09c63SMatthias Springer getBbArgReplacements(rewriter, newForOp.getRegionIterArgs(), indices); 50219efe141SMatthias Springer iterArgs.insert(iterArgs.begin(), newForOp.getInductionVar()); 50319efe141SMatthias Springer 50419efe141SMatthias Springer // Erase terminator if present. 50519efe141SMatthias Springer if (iterArgs.size() == 1) 50619efe141SMatthias Springer rewriter.eraseOp(loopBody->getTerminator()); 50719efe141SMatthias Springer 50819efe141SMatthias Springer // Move loop body to new loop. 50919efe141SMatthias Springer rewriter.mergeBlocks(oldLoopBody, loopBody, iterArgs); 51019efe141SMatthias Springer 51119efe141SMatthias Springer // Update scf.yield of new loop. 51219efe141SMatthias Springer auto yieldOp = cast<scf::YieldOp>(loopBody->getTerminator()); 51319efe141SMatthias Springer rewriter.setInsertionPoint(yieldOp); 514b3ebe3beSMatthias Springer SmallVector<Value> yieldValues = getYieldedValues( 515b55d55ecSMatthias Springer rewriter, yieldOp.getResults(), initArgsTypes, indices, options); 51619efe141SMatthias Springer yieldOp.getResultsMutable().assign(yieldValues); 51719efe141SMatthias Springer 51819efe141SMatthias Springer // Replace loop results. 51919efe141SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newForOp->getResults()); 52019efe141SMatthias Springer 52119efe141SMatthias Springer return success(); 52219efe141SMatthias Springer } 5234ec00fb3SMatthias Springer 5241e1eeae8SMatthias Springer /// Assert that yielded values of an scf.for op are equivalent to their 525f178c386SMatthias Springer /// corresponding bbArgs. In that case, the buffer relations of the 526f178c386SMatthias Springer /// corresponding OpResults are "Equivalent". 527f178c386SMatthias Springer /// 528f178c386SMatthias Springer /// If this is not the case, an allocs+copies are inserted and yielded from 529f178c386SMatthias Springer /// the loop. This could be a performance problem, so it must be explicitly 530f178c386SMatthias Springer /// activated with `alloc-return-allocs`. 5314ec00fb3SMatthias Springer LogicalResult verifyAnalysis(Operation *op, 5329597b16aSMatthias Springer const AnalysisState &state) const { 5331e1eeae8SMatthias Springer const auto &options = 5341e1eeae8SMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 5351e1eeae8SMatthias Springer if (options.allowReturnAllocs) 5361e1eeae8SMatthias Springer return success(); 5371e1eeae8SMatthias Springer 5384ec00fb3SMatthias Springer auto forOp = cast<scf::ForOp>(op); 5394ec00fb3SMatthias Springer auto yieldOp = 5404ec00fb3SMatthias Springer cast<scf::YieldOp>(forOp.getLoopBody().front().getTerminator()); 541f178c386SMatthias Springer for (OpResult opResult : op->getOpResults()) { 542f178c386SMatthias Springer if (!opResult.getType().isa<TensorType>()) 5434ec00fb3SMatthias Springer continue; 5444ec00fb3SMatthias Springer 5454ec00fb3SMatthias Springer // Note: This is overly strict. We should check for aliasing bufferized 5464ec00fb3SMatthias Springer // values. But we don't have a "must-alias" analysis yet. 547f178c386SMatthias Springer if (bufferRelation(op, opResult, state) != BufferRelation::Equivalent) 5484ec00fb3SMatthias Springer return yieldOp->emitError() 549f178c386SMatthias Springer << "Yield operand #" << opResult.getResultNumber() 550e3006825SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 5514ec00fb3SMatthias Springer } 552f178c386SMatthias Springer 5534ec00fb3SMatthias Springer return success(); 5544ec00fb3SMatthias Springer } 55519efe141SMatthias Springer }; 55619efe141SMatthias Springer 557a5d09c63SMatthias Springer /// Bufferization of scf.while. Replace with a new scf.while that operates on 558a5d09c63SMatthias Springer /// memrefs. 559a5d09c63SMatthias Springer struct WhileOpInterface 560a5d09c63SMatthias Springer : public BufferizableOpInterface::ExternalModel<WhileOpInterface, 561a5d09c63SMatthias Springer scf::WhileOp> { 562a5d09c63SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 563a5d09c63SMatthias Springer const AnalysisState &state) const { 564a5d09c63SMatthias Springer // Tensor iter_args of scf::WhileOps are always considered as a read. 565a5d09c63SMatthias Springer return true; 566a5d09c63SMatthias Springer } 567a5d09c63SMatthias Springer 568a5d09c63SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 569a5d09c63SMatthias Springer const AnalysisState &state) const { 570a5d09c63SMatthias Springer // Tensor iter_args of scf::WhileOps are always considered as a write. 571a5d09c63SMatthias Springer return true; 572a5d09c63SMatthias Springer } 573a5d09c63SMatthias Springer 574a5d09c63SMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 575a5d09c63SMatthias Springer const AnalysisState &state) const { 576a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 577996834e6SMatthias Springer unsigned int idx = opOperand.getOperandNumber(); 578996834e6SMatthias Springer 579996834e6SMatthias Springer // The OpResults and OpOperands may not match. They may not even have the 580996834e6SMatthias Springer // same type. The number of OpResults and OpOperands can also differ. 581996834e6SMatthias Springer if (idx >= op->getNumResults() || 582996834e6SMatthias Springer opOperand.get().getType() != op->getResult(idx).getType()) 583996834e6SMatthias Springer return {}; 584996834e6SMatthias Springer 585996834e6SMatthias Springer // The only aliasing OpResult may be the one at the same index. 586996834e6SMatthias Springer return {whileOp->getResult(idx)}; 587a5d09c63SMatthias Springer } 588a5d09c63SMatthias Springer 589a5d09c63SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 590a5d09c63SMatthias Springer const AnalysisState &state) const { 591a5d09c63SMatthias Springer // WhileOp results are equivalent to their corresponding init_args if the 592a5d09c63SMatthias Springer // corresponding iter_args and yield values are equivalent (for both the 593a5d09c63SMatthias Springer // "before" and the "after" block). 594a5d09c63SMatthias Springer unsigned int resultNumber = opResult.getResultNumber(); 595a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 596a5d09c63SMatthias Springer 597996834e6SMatthias Springer // The "before" region bbArgs and the OpResults may not match. 598996834e6SMatthias Springer if (resultNumber >= whileOp.getBeforeArguments().size()) 599996834e6SMatthias Springer return BufferRelation::None; 600996834e6SMatthias Springer if (opResult.getType() != 601996834e6SMatthias Springer whileOp.getBeforeArguments()[resultNumber].getType()) 602996834e6SMatthias Springer return BufferRelation::None; 603996834e6SMatthias Springer 604a5d09c63SMatthias Springer auto conditionOp = whileOp.getConditionOp(); 605a5d09c63SMatthias Springer BlockArgument conditionBbArg = whileOp.getBeforeArguments()[resultNumber]; 606a5d09c63SMatthias Springer Value conditionOperand = conditionOp.getArgs()[resultNumber]; 607a5d09c63SMatthias Springer bool equivCondition = 608a5d09c63SMatthias Springer state.areEquivalentBufferizedValues(conditionBbArg, conditionOperand); 609a5d09c63SMatthias Springer 610a5d09c63SMatthias Springer auto yieldOp = whileOp.getYieldOp(); 611a5d09c63SMatthias Springer BlockArgument bodyBbArg = whileOp.getAfterArguments()[resultNumber]; 612a5d09c63SMatthias Springer Value yieldOperand = yieldOp.getOperand(resultNumber); 613a5d09c63SMatthias Springer bool equivYield = 614a5d09c63SMatthias Springer state.areEquivalentBufferizedValues(bodyBbArg, yieldOperand); 615a5d09c63SMatthias Springer 616a5d09c63SMatthias Springer return equivCondition && equivYield ? BufferRelation::Equivalent 617a5d09c63SMatthias Springer : BufferRelation::None; 618a5d09c63SMatthias Springer } 619a5d09c63SMatthias Springer 620a5d09c63SMatthias Springer bool isWritable(Operation *op, Value value, 621a5d09c63SMatthias Springer const AnalysisState &state) const { 622a5d09c63SMatthias Springer // Interestingly, scf::WhileOp's bbArg can **always** be viewed 623a5d09c63SMatthias Springer // inplace from the perspective of ops nested under: 624a5d09c63SMatthias Springer // 1. Either the matching iter operand is not bufferized inplace and an 625a5d09c63SMatthias Springer // alloc + optional copy makes the bbArg itself inplaceable. 626a5d09c63SMatthias Springer // 2. Or the matching iter operand is bufferized inplace and bbArg just 627a5d09c63SMatthias Springer // bufferizes to that too. 628a5d09c63SMatthias Springer return true; 629a5d09c63SMatthias Springer } 630a5d09c63SMatthias Springer 631d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 632d361ecbdSMatthias Springer const AnalysisState &state) const { 633d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 634d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 635d361ecbdSMatthias Springer return failure(); 636d361ecbdSMatthias Springer 637d361ecbdSMatthias Springer if (!state.getOptions().enforceAliasingInvariants) 638d361ecbdSMatthias Springer return success(); 639d361ecbdSMatthias Springer 640d361ecbdSMatthias Springer // According to the `getAliasing...` implementations, a bufferized OpResult 641d361ecbdSMatthias Springer // may alias only with the corresponding bufferized init_arg and with no 642d361ecbdSMatthias Springer // other buffers. I.e., the i-th OpResult may alias with the i-th init_arg; 643d361ecbdSMatthias Springer // but not with any other OpOperand. If a corresponding OpResult/init_arg 644d361ecbdSMatthias Springer // pair bufferizes to equivalent buffers, this aliasing requirement is 645d361ecbdSMatthias Springer // satisfied. Otherwise, we cannot be sure and must yield a new buffer copy. 646d361ecbdSMatthias Springer // (New buffer copies do not alias with any buffer.) 647d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 648d361ecbdSMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 649d361ecbdSMatthias Springer auto conditionOp = whileOp.getConditionOp(); 650d361ecbdSMatthias Springer auto yieldOp = whileOp.getYieldOp(); 651d361ecbdSMatthias Springer 652d361ecbdSMatthias Springer // Indices of all bbArgs that have tensor type. These are the ones that 653d361ecbdSMatthias Springer // are bufferized. The "before" and "after" regions may have different args. 654d361ecbdSMatthias Springer DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits()); 655d361ecbdSMatthias Springer DenseSet<int64_t> indicesAfter = 656d361ecbdSMatthias Springer getTensorIndices(whileOp.getAfterArguments()); 657d361ecbdSMatthias Springer 658d361ecbdSMatthias Springer // For every yielded value, is the value equivalent to its corresponding 659d361ecbdSMatthias Springer // bbArg? 660d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYieldsBefore = getEquivalentBuffers( 661d361ecbdSMatthias Springer whileOp.getBeforeArguments(), conditionOp.getArgs(), state); 662d361ecbdSMatthias Springer DenseSet<int64_t> equivalentYieldsAfter = getEquivalentBuffers( 663d361ecbdSMatthias Springer whileOp.getAfterArguments(), whileOp.getYieldOp().getResults(), state); 664d361ecbdSMatthias Springer 665d361ecbdSMatthias Springer // Update "before" region. 666d361ecbdSMatthias Springer rewriter.setInsertionPoint(conditionOp); 667d361ecbdSMatthias Springer SmallVector<Value> beforeYieldValues; 668d361ecbdSMatthias Springer for (int64_t idx = 0; 669d361ecbdSMatthias Springer idx < static_cast<int64_t>(conditionOp.getArgs().size()); ++idx) { 670d361ecbdSMatthias Springer Value value = conditionOp.getArgs()[idx]; 671d361ecbdSMatthias Springer if (!indicesBefore.contains(idx) || 672d361ecbdSMatthias Springer equivalentYieldsBefore.contains(idx)) { 673d361ecbdSMatthias Springer beforeYieldValues.push_back(value); 674d361ecbdSMatthias Springer continue; 675d361ecbdSMatthias Springer } 676d361ecbdSMatthias Springer Value alloc = rewriter.create<bufferization::AllocTensorOp>( 677d361ecbdSMatthias Springer conditionOp.getLoc(), value.getType().cast<RankedTensorType>(), 678d361ecbdSMatthias Springer /*dynamicSizes=*/ValueRange(), value, /*escape=*/true); 679d361ecbdSMatthias Springer beforeYieldValues.push_back(alloc); 680d361ecbdSMatthias Springer } 681d361ecbdSMatthias Springer rewriter.updateRootInPlace(conditionOp, [&]() { 682d361ecbdSMatthias Springer conditionOp.getArgsMutable().assign(beforeYieldValues); 683d361ecbdSMatthias Springer }); 684d361ecbdSMatthias Springer 685d361ecbdSMatthias Springer // Update "after" region. 686d361ecbdSMatthias Springer rewriter.setInsertionPoint(yieldOp); 687d361ecbdSMatthias Springer SmallVector<Value> afterYieldValues; 688d361ecbdSMatthias Springer for (int64_t idx = 0; 689d361ecbdSMatthias Springer idx < static_cast<int64_t>(yieldOp.getResults().size()); ++idx) { 690d361ecbdSMatthias Springer Value value = yieldOp.getResults()[idx]; 691d361ecbdSMatthias Springer if (!indicesAfter.contains(idx) || equivalentYieldsAfter.contains(idx)) { 692d361ecbdSMatthias Springer afterYieldValues.push_back(value); 693d361ecbdSMatthias Springer continue; 694d361ecbdSMatthias Springer } 695d361ecbdSMatthias Springer Value alloc = rewriter.create<bufferization::AllocTensorOp>( 696d361ecbdSMatthias Springer yieldOp.getLoc(), value.getType().cast<RankedTensorType>(), 697d361ecbdSMatthias Springer /*dynamicSizes=*/ValueRange(), value, /*escape=*/true); 698d361ecbdSMatthias Springer afterYieldValues.push_back(alloc); 699d361ecbdSMatthias Springer } 700d361ecbdSMatthias Springer rewriter.updateRootInPlace(yieldOp, [&]() { 701d361ecbdSMatthias Springer yieldOp.getResultsMutable().assign(afterYieldValues); 702d361ecbdSMatthias Springer }); 703d361ecbdSMatthias Springer 704d361ecbdSMatthias Springer return success(); 705d361ecbdSMatthias Springer } 706d361ecbdSMatthias Springer 707a5d09c63SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 708b55d55ecSMatthias Springer const BufferizationOptions &options) const { 709a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 710a5d09c63SMatthias Springer 711a5d09c63SMatthias Springer assert(whileOp.getBefore().getBlocks().size() == 1 && 712a5d09c63SMatthias Springer "regions with multiple blocks not supported"); 713a5d09c63SMatthias Springer Block *beforeBody = &whileOp.getBefore().front(); 714a5d09c63SMatthias Springer assert(whileOp.getAfter().getBlocks().size() == 1 && 715a5d09c63SMatthias Springer "regions with multiple blocks not supported"); 716a5d09c63SMatthias Springer Block *afterBody = &whileOp.getAfter().front(); 717a5d09c63SMatthias Springer 718996834e6SMatthias Springer // Indices of all bbArgs that have tensor type. These are the ones that 719996834e6SMatthias Springer // are bufferized. The "before" and "after" regions may have different args. 720996834e6SMatthias Springer DenseSet<int64_t> indicesBefore = getTensorIndices(whileOp.getInits()); 721996834e6SMatthias Springer DenseSet<int64_t> indicesAfter = 722996834e6SMatthias Springer getTensorIndices(whileOp.getAfterArguments()); 723996834e6SMatthias Springer 724a5d09c63SMatthias Springer // The new memref init_args of the loop. 725a5d09c63SMatthias Springer SmallVector<Value> initArgs = 726b55d55ecSMatthias Springer getBuffers(rewriter, whileOp->getOpOperands(), options); 727996834e6SMatthias Springer 728996834e6SMatthias Springer // The result types of a WhileOp are the same as the "after" bbArg types. 729996834e6SMatthias Springer SmallVector<Type> argsTypesAfter = llvm::to_vector( 730996834e6SMatthias Springer llvm::map_range(whileOp.getAfterArguments(), [&](BlockArgument bbArg) { 731b55d55ecSMatthias Springer return getBufferType(bbArg, options).cast<Type>(); 732996834e6SMatthias Springer })); 733a5d09c63SMatthias Springer 734a5d09c63SMatthias Springer // Construct a new scf.while op with memref instead of tensor values. 735996834e6SMatthias Springer ValueRange argsRangeBefore(initArgs); 736996834e6SMatthias Springer TypeRange argsTypesBefore(argsRangeBefore); 737996834e6SMatthias Springer auto newWhileOp = rewriter.create<scf::WhileOp>(whileOp.getLoc(), 738996834e6SMatthias Springer argsTypesAfter, initArgs); 739996834e6SMatthias Springer 740a5d09c63SMatthias Springer // Add before/after regions to the new op. 741996834e6SMatthias Springer SmallVector<Location> bbArgLocsBefore(initArgs.size(), whileOp.getLoc()); 742996834e6SMatthias Springer SmallVector<Location> bbArgLocsAfter(argsTypesAfter.size(), 743996834e6SMatthias Springer whileOp.getLoc()); 744a5d09c63SMatthias Springer Block *newBeforeBody = &newWhileOp.getBefore().emplaceBlock(); 745996834e6SMatthias Springer newWhileOp.getBefore().addArguments(argsTypesBefore, bbArgLocsBefore); 746a5d09c63SMatthias Springer Block *newAfterBody = &newWhileOp.getAfter().emplaceBlock(); 747996834e6SMatthias Springer newWhileOp.getAfter().addArguments(argsTypesAfter, bbArgLocsAfter); 748a5d09c63SMatthias Springer 749a5d09c63SMatthias Springer // Set up new iter_args and move the loop condition block to the new op. 750a5d09c63SMatthias Springer // The old block uses tensors, so wrap the (memref) bbArgs of the new block 751a5d09c63SMatthias Springer // in ToTensorOps. 752a5d09c63SMatthias Springer rewriter.setInsertionPointToStart(newBeforeBody); 753a5d09c63SMatthias Springer SmallVector<Value> newBeforeArgs = getBbArgReplacements( 754996834e6SMatthias Springer rewriter, newWhileOp.getBeforeArguments(), indicesBefore); 755a5d09c63SMatthias Springer rewriter.mergeBlocks(beforeBody, newBeforeBody, newBeforeArgs); 756a5d09c63SMatthias Springer 757a5d09c63SMatthias Springer // Update scf.condition of new loop. 758a5d09c63SMatthias Springer auto newConditionOp = newWhileOp.getConditionOp(); 759a5d09c63SMatthias Springer rewriter.setInsertionPoint(newConditionOp); 760996834e6SMatthias Springer // Only equivalent buffers or new buffer allocations may be yielded to the 761996834e6SMatthias Springer // "after" region. 762996834e6SMatthias Springer // TODO: This could be relaxed for better bufferization results. 763a5d09c63SMatthias Springer SmallVector<Value> newConditionArgs = 764996834e6SMatthias Springer getYieldedValues(rewriter, newConditionOp.getArgs(), argsTypesAfter, 765b55d55ecSMatthias Springer indicesAfter, options); 766a5d09c63SMatthias Springer newConditionOp.getArgsMutable().assign(newConditionArgs); 767a5d09c63SMatthias Springer 768a5d09c63SMatthias Springer // Set up new iter_args and move the loop body block to the new op. 769a5d09c63SMatthias Springer // The old block uses tensors, so wrap the (memref) bbArgs of the new block 770a5d09c63SMatthias Springer // in ToTensorOps. 771a5d09c63SMatthias Springer rewriter.setInsertionPointToStart(newAfterBody); 772996834e6SMatthias Springer SmallVector<Value> newAfterArgs = getBbArgReplacements( 773996834e6SMatthias Springer rewriter, newWhileOp.getAfterArguments(), indicesAfter); 774a5d09c63SMatthias Springer rewriter.mergeBlocks(afterBody, newAfterBody, newAfterArgs); 775a5d09c63SMatthias Springer 776a5d09c63SMatthias Springer // Update scf.yield of the new loop. 777a5d09c63SMatthias Springer auto newYieldOp = newWhileOp.getYieldOp(); 778a5d09c63SMatthias Springer rewriter.setInsertionPoint(newYieldOp); 779996834e6SMatthias Springer // Only equivalent buffers or new buffer allocations may be yielded to the 780996834e6SMatthias Springer // "before" region. 781996834e6SMatthias Springer // TODO: This could be relaxed for better bufferization results. 782a5d09c63SMatthias Springer SmallVector<Value> newYieldValues = 783996834e6SMatthias Springer getYieldedValues(rewriter, newYieldOp.getResults(), argsTypesBefore, 784b55d55ecSMatthias Springer indicesBefore, options); 785a5d09c63SMatthias Springer newYieldOp.getResultsMutable().assign(newYieldValues); 786a5d09c63SMatthias Springer 787a5d09c63SMatthias Springer // Replace loop results. 788a5d09c63SMatthias Springer replaceOpWithBufferizedValues(rewriter, op, newWhileOp->getResults()); 789a5d09c63SMatthias Springer 790a5d09c63SMatthias Springer return success(); 791a5d09c63SMatthias Springer } 792a5d09c63SMatthias Springer 793a5d09c63SMatthias Springer /// Assert that yielded values of an scf.while op are equivalent to their 794a5d09c63SMatthias Springer /// corresponding bbArgs. In that case, the buffer relations of the 795a5d09c63SMatthias Springer /// corresponding OpResults are "Equivalent". 796a5d09c63SMatthias Springer /// 797a5d09c63SMatthias Springer /// If this is not the case, allocs+copies are inserted and yielded from 798a5d09c63SMatthias Springer /// the loop. This could be a performance problem, so it must be explicitly 799a5d09c63SMatthias Springer /// activated with `alloc-return-allocs`. 800a5d09c63SMatthias Springer /// 801a5d09c63SMatthias Springer /// Not: In contrast to scf::ForOp, scf::WhileOp has two regions and the 802a5d09c63SMatthias Springer /// equivalence condition must be checked for both. 803a5d09c63SMatthias Springer LogicalResult verifyAnalysis(Operation *op, 804a5d09c63SMatthias Springer const AnalysisState &state) const { 805a5d09c63SMatthias Springer auto whileOp = cast<scf::WhileOp>(op); 806a5d09c63SMatthias Springer const auto &options = 807a5d09c63SMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 808a5d09c63SMatthias Springer if (options.allowReturnAllocs) 809a5d09c63SMatthias Springer return success(); 810a5d09c63SMatthias Springer 811a5d09c63SMatthias Springer auto conditionOp = whileOp.getConditionOp(); 812a5d09c63SMatthias Springer for (const auto &it : llvm::enumerate(conditionOp.getArgs())) { 813a5d09c63SMatthias Springer if (!it.value().getType().isa<TensorType>()) 814a5d09c63SMatthias Springer continue; 815a5d09c63SMatthias Springer if (!state.areEquivalentBufferizedValues( 816a5d09c63SMatthias Springer it.value(), conditionOp->getBlock()->getArgument(it.index()))) 817a5d09c63SMatthias Springer return conditionOp->emitError() 818a5d09c63SMatthias Springer << "Condition arg #" << it.index() 819a5d09c63SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 820a5d09c63SMatthias Springer } 821a5d09c63SMatthias Springer 822a5d09c63SMatthias Springer auto yieldOp = whileOp.getYieldOp(); 823a5d09c63SMatthias Springer for (const auto &it : llvm::enumerate(yieldOp.getResults())) { 824a5d09c63SMatthias Springer if (!it.value().getType().isa<TensorType>()) 825a5d09c63SMatthias Springer continue; 826a5d09c63SMatthias Springer if (!state.areEquivalentBufferizedValues( 827a5d09c63SMatthias Springer it.value(), yieldOp->getBlock()->getArgument(it.index()))) 828a5d09c63SMatthias Springer return yieldOp->emitError() 829a5d09c63SMatthias Springer << "Yield operand #" << it.index() 830a5d09c63SMatthias Springer << " is not equivalent to the corresponding iter bbArg"; 831a5d09c63SMatthias Springer } 832a5d09c63SMatthias Springer 833a5d09c63SMatthias Springer return success(); 834a5d09c63SMatthias Springer } 835a5d09c63SMatthias Springer }; 836a5d09c63SMatthias Springer 83719efe141SMatthias Springer /// Bufferization of scf.yield. Bufferized as part of their enclosing ops, so 83819efe141SMatthias Springer /// this is for analysis only. 83919efe141SMatthias Springer struct YieldOpInterface 84019efe141SMatthias Springer : public BufferizableOpInterface::ExternalModel<YieldOpInterface, 84119efe141SMatthias Springer scf::YieldOp> { 84219efe141SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 8439597b16aSMatthias Springer const AnalysisState &state) const { 84419efe141SMatthias Springer return true; 84519efe141SMatthias Springer } 84619efe141SMatthias Springer 84719efe141SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 8489597b16aSMatthias Springer const AnalysisState &state) const { 84919efe141SMatthias Springer return false; 85019efe141SMatthias Springer } 85119efe141SMatthias Springer 8529597b16aSMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 8539597b16aSMatthias Springer const AnalysisState &state) const { 85419efe141SMatthias Springer if (isa<scf::IfOp>(op->getParentOp())) 855585a8a32SMatthias Springer return {op->getParentOp()->getResult(opOperand.getOperandNumber())}; 85619efe141SMatthias Springer if (isa<scf::ExecuteRegionOp>(op->getParentOp())) 857585a8a32SMatthias Springer return {op->getParentOp()->getResult(opOperand.getOperandNumber())}; 858585a8a32SMatthias Springer return {}; 85919efe141SMatthias Springer } 86019efe141SMatthias Springer 86119efe141SMatthias Springer bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand, 8629597b16aSMatthias Springer const AnalysisState &state) const { 86319efe141SMatthias Springer // Yield operands always bufferize inplace. Otherwise, an alloc + copy 86419efe141SMatthias Springer // may be generated inside the block. We should not return/yield allocations 86519efe141SMatthias Springer // when possible. 86619efe141SMatthias Springer return true; 86719efe141SMatthias Springer } 86819efe141SMatthias Springer 86919efe141SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 870b55d55ecSMatthias Springer const BufferizationOptions &options) const { 87119efe141SMatthias Springer auto yieldOp = cast<scf::YieldOp>(op); 872a5d09c63SMatthias Springer if (!isa<scf::ExecuteRegionOp, scf::IfOp, scf::ForOp, scf::WhileOp>( 87319efe141SMatthias Springer yieldOp->getParentOp())) 87419efe141SMatthias Springer return yieldOp->emitError("unsupported scf::YieldOp parent"); 87519efe141SMatthias Springer return success(); 87619efe141SMatthias Springer } 87719efe141SMatthias Springer }; 87819efe141SMatthias Springer 87972de7588SNicolas Vasilache using tensor::ExtractSliceOp; 88072de7588SNicolas Vasilache 88172de7588SNicolas Vasilache /// Return the destinations that an ForeachThreadOp is inserting into. One per 88272de7588SNicolas Vasilache /// ParallelInsertSliceOp. 88372de7588SNicolas Vasilache static SmallVector<OpOperand *> 88472de7588SNicolas Vasilache getInsertionDest(ForeachThreadOp foreachThreadOp) { 88572de7588SNicolas Vasilache PerformConcurrentlyOp terminator = foreachThreadOp.getTerminator(); 88672de7588SNicolas Vasilache SmallVector<OpOperand *> result; 88772de7588SNicolas Vasilache terminator.walk([&](ParallelInsertSliceOp insertOp) { 88872de7588SNicolas Vasilache result.push_back(&insertOp->getOpOperand(1) /*dest*/); 88972de7588SNicolas Vasilache }); 89072de7588SNicolas Vasilache return result; 89172de7588SNicolas Vasilache } 89272de7588SNicolas Vasilache 89372de7588SNicolas Vasilache /// Bufferization of ForeachThreadOp. This also bufferizes the terminator of the 89472de7588SNicolas Vasilache /// region. There are op interfaces for the terminators (PerformConcurrentlyOp 89572de7588SNicolas Vasilache /// and ParallelInsertSliceOp), but these are only used during analysis. Not 89672de7588SNicolas Vasilache /// for bufferization. 89772de7588SNicolas Vasilache struct ForeachThreadOpInterface 89872de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel<ForeachThreadOpInterface, 89972de7588SNicolas Vasilache ForeachThreadOp> { 90072de7588SNicolas Vasilache SmallVector<OpOperand *> 90172de7588SNicolas Vasilache getAliasingOpOperand(Operation *op, OpResult opResult, 90272de7588SNicolas Vasilache const AnalysisState &state) const { 90372de7588SNicolas Vasilache // Get OpOperand (dest) from corresponding ParallelInsertSliceOp. 90472de7588SNicolas Vasilache auto foreachThreadOp = cast<ForeachThreadOp>(op); 90572de7588SNicolas Vasilache return {getInsertionDest(foreachThreadOp)[opResult.getResultNumber()]}; 90672de7588SNicolas Vasilache } 90772de7588SNicolas Vasilache 90872de7588SNicolas Vasilache bool isMemoryWrite(Operation *op, OpResult opResult, 90972de7588SNicolas Vasilache const AnalysisState &state) const { 91072de7588SNicolas Vasilache // This op is a memory write. Stop lookup here to avoid finding false 91172de7588SNicolas Vasilache // conflicts involving this op and one of the ops in the region. This is 91272de7588SNicolas Vasilache // similar to how scf.if ops are analyzed. 91372de7588SNicolas Vasilache return true; 91472de7588SNicolas Vasilache } 91572de7588SNicolas Vasilache 91672de7588SNicolas Vasilache BufferRelation bufferRelation(Operation *op, OpResult opResult, 91772de7588SNicolas Vasilache const AnalysisState &state) const { 91872de7588SNicolas Vasilache return BufferRelation::Equivalent; 91972de7588SNicolas Vasilache } 92072de7588SNicolas Vasilache 921d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 922d361ecbdSMatthias Springer const AnalysisState &state) const { 923d361ecbdSMatthias Springer auto bufferizableOp = cast<BufferizableOpInterface>(op); 924d361ecbdSMatthias Springer if (failed(bufferizableOp.resolveTensorOpOperandConflicts(rewriter, state))) 925d361ecbdSMatthias Springer return failure(); 926d361ecbdSMatthias Springer 927d361ecbdSMatthias Springer OpBuilder::InsertionGuard g(rewriter); 928d361ecbdSMatthias Springer auto foreachThreadOp = cast<ForeachThreadOp>(op); 929d361ecbdSMatthias Springer for (OpResult opResult : foreachThreadOp->getOpResults()) { 930d361ecbdSMatthias Springer SmallVector<OpOperand *> destOperands = 931d361ecbdSMatthias Springer state.getAliasingOpOperand(opResult); 932d361ecbdSMatthias Springer assert(destOperands.size() == 1 && 933d361ecbdSMatthias Springer "expected exactly one aliasing OpOperand"); 934d361ecbdSMatthias Springer assert(isa<ParallelInsertSliceOp>(destOperands.front()->getOwner()) && 935d361ecbdSMatthias Springer "expected ParallelInsertSliceOp"); 936d361ecbdSMatthias Springer 937d361ecbdSMatthias Springer // Nothing to do if there is no conflict. 938d361ecbdSMatthias Springer if (state.isInPlace(*destOperands.front())) 939d361ecbdSMatthias Springer continue; 940d361ecbdSMatthias Springer 941d361ecbdSMatthias Springer // Create AllocTensorOp. 942d361ecbdSMatthias Springer bool isYielded = state.isTensorYielded(opResult); 943d361ecbdSMatthias Springer auto resultType = opResult.getType().cast<RankedTensorType>(); 944d361ecbdSMatthias Springer Value alloc = rewriter.create<bufferization::AllocTensorOp>( 945d361ecbdSMatthias Springer op->getLoc(), resultType, /*dynamicDims=*/ValueRange(), 946d361ecbdSMatthias Springer /*copy=*/destOperands.front()->get(), 947d361ecbdSMatthias Springer /*escape=*/isYielded); 948d361ecbdSMatthias Springer 949d361ecbdSMatthias Springer // Update terminator operand. 950d361ecbdSMatthias Springer rewriter.updateRootInPlace(destOperands.front()->getOwner(), 951d361ecbdSMatthias Springer [&]() { destOperands.front()->set(alloc); }); 952d361ecbdSMatthias Springer } 953d361ecbdSMatthias Springer 954d361ecbdSMatthias Springer return success(); 955d361ecbdSMatthias Springer } 956d361ecbdSMatthias Springer 95772de7588SNicolas Vasilache LogicalResult bufferize(Operation *op, RewriterBase &b, 958b55d55ecSMatthias Springer const BufferizationOptions &options) const { 95972de7588SNicolas Vasilache OpBuilder::InsertionGuard g(b); 96072de7588SNicolas Vasilache auto foreachThreadOp = cast<ForeachThreadOp>(op); 96172de7588SNicolas Vasilache 96272de7588SNicolas Vasilache // Gather new results of the ForeachThreadOp. 96372de7588SNicolas Vasilache SmallVector<Value> newResults; 96472de7588SNicolas Vasilache for (OpResult opResult : foreachThreadOp->getOpResults()) { 965b3ebe3beSMatthias Springer OpOperand *insertDest = 966b3ebe3beSMatthias Springer getInsertionDest(foreachThreadOp)[opResult.getResultNumber()]; 96772de7588SNicolas Vasilache // Insert copies right before the PerformConcurrentlyOp terminator. They 96872de7588SNicolas Vasilache // should not be inside terminator (which would be the default insertion 96972de7588SNicolas Vasilache // point). 970b55d55ecSMatthias Springer Value buffer = getBuffer(b, insertDest->get(), options); 97172de7588SNicolas Vasilache newResults.push_back(buffer); 97272de7588SNicolas Vasilache } 97372de7588SNicolas Vasilache 97472de7588SNicolas Vasilache // Create new ForeachThreadOp without any results and drop the automatically 97572de7588SNicolas Vasilache // introduced terminator. 97672de7588SNicolas Vasilache TypeRange newResultTypes; 97772de7588SNicolas Vasilache auto newForeachThreadOp = 97872de7588SNicolas Vasilache b.create<ForeachThreadOp>(foreachThreadOp.getLoc(), newResultTypes, 97972de7588SNicolas Vasilache foreachThreadOp.getNumThreads()); 98072de7588SNicolas Vasilache newForeachThreadOp.getBody()->getTerminator()->erase(); 98172de7588SNicolas Vasilache 98272de7588SNicolas Vasilache // Move over block contents of the old op. 98372de7588SNicolas Vasilache b.mergeBlocks(foreachThreadOp.getBody(), newForeachThreadOp.getBody(), 98472de7588SNicolas Vasilache {newForeachThreadOp.getBody()->getArguments()}); 98572de7588SNicolas Vasilache 98672de7588SNicolas Vasilache // Bufferize terminator. 98772de7588SNicolas Vasilache auto performConcurrentlyOp = cast<PerformConcurrentlyOp>( 98872de7588SNicolas Vasilache newForeachThreadOp.getBody()->getTerminator()); 98972de7588SNicolas Vasilache b.setInsertionPoint(performConcurrentlyOp); 99072de7588SNicolas Vasilache unsigned resultCounter = 0; 99172de7588SNicolas Vasilache WalkResult walkResult = 99272de7588SNicolas Vasilache performConcurrentlyOp.walk([&](ParallelInsertSliceOp insertOp) { 99372de7588SNicolas Vasilache Location loc = insertOp.getLoc(); 99472de7588SNicolas Vasilache Type srcType = getMemRefType( 995b55d55ecSMatthias Springer insertOp.getSource().getType().cast<RankedTensorType>(), options); 99672de7588SNicolas Vasilache // ParallelInsertSliceOp bufferizes to a copy. 99772de7588SNicolas Vasilache auto srcMemref = b.create<bufferization::ToMemrefOp>( 99872de7588SNicolas Vasilache loc, srcType, insertOp.getSource()); 99972de7588SNicolas Vasilache Value destMemref = newResults[resultCounter++]; 100072de7588SNicolas Vasilache Value subview = b.create<memref::SubViewOp>( 100172de7588SNicolas Vasilache loc, destMemref, insertOp.getMixedOffsets(), 100272de7588SNicolas Vasilache insertOp.getMixedSizes(), insertOp.getMixedStrides()); 100372de7588SNicolas Vasilache // This memcpy will fold away if everything bufferizes in-place. 1004b55d55ecSMatthias Springer if (failed(options.createMemCpy(b, insertOp.getLoc(), srcMemref, 1005b55d55ecSMatthias Springer subview))) 100672de7588SNicolas Vasilache return WalkResult::interrupt(); 100772de7588SNicolas Vasilache b.eraseOp(insertOp); 100872de7588SNicolas Vasilache return WalkResult::advance(); 100972de7588SNicolas Vasilache }); 101072de7588SNicolas Vasilache if (walkResult.wasInterrupted()) 101172de7588SNicolas Vasilache return failure(); 101272de7588SNicolas Vasilache 101372de7588SNicolas Vasilache // Replace the op. 101472de7588SNicolas Vasilache replaceOpWithBufferizedValues(b, op, newResults); 101572de7588SNicolas Vasilache 101672de7588SNicolas Vasilache return success(); 101772de7588SNicolas Vasilache } 101872de7588SNicolas Vasilache }; 101972de7588SNicolas Vasilache 102072de7588SNicolas Vasilache /// Nothing to do for PerformConcurrentlyOp. 102172de7588SNicolas Vasilache struct PerformConcurrentlyOpInterface 102272de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel< 102372de7588SNicolas Vasilache PerformConcurrentlyOpInterface, PerformConcurrentlyOp> { 102472de7588SNicolas Vasilache LogicalResult bufferize(Operation *op, RewriterBase &b, 1025b55d55ecSMatthias Springer const BufferizationOptions &options) const { 1026b3ebe3beSMatthias Springer llvm_unreachable("op does not have any tensor OpOperands / OpResults"); 102772de7588SNicolas Vasilache return failure(); 102872de7588SNicolas Vasilache } 102972de7588SNicolas Vasilache }; 103072de7588SNicolas Vasilache 103172de7588SNicolas Vasilache /// Return true if the (ExtractSliceOp, ParallelInsertSliceOp) pair match (i.e. 103272de7588SNicolas Vasilache /// equivalent operand / result and same offset/sizes/strides specification). 103372de7588SNicolas Vasilache static bool areEquivalentExtractSliceOps(const AnalysisState &state, 103472de7588SNicolas Vasilache ExtractSliceOp st, 103572de7588SNicolas Vasilache ParallelInsertSliceOp sti) { 103672de7588SNicolas Vasilache if (!st || !sti) 103772de7588SNicolas Vasilache return false; 103872de7588SNicolas Vasilache if (st != sti && 1039*8df54a6aSJacques Pienaar !state.areEquivalentBufferizedValues(st.getSource(), sti.getDest())) 104072de7588SNicolas Vasilache return false; 104172de7588SNicolas Vasilache if (!sameOffsetsSizesAndStrides(st, sti, isEqualConstantIntOrValue)) 104272de7588SNicolas Vasilache return false; 104372de7588SNicolas Vasilache return true; 104472de7588SNicolas Vasilache } 104572de7588SNicolas Vasilache 104672de7588SNicolas Vasilache /// Return true if `value` is originating from an ExtractSliceOp that matches 104772de7588SNicolas Vasilache /// the given InsertSliceOp. 104872de7588SNicolas Vasilache static bool hasMatchingExtractSliceOp(const AnalysisState &state, Value value, 104972de7588SNicolas Vasilache ParallelInsertSliceOp insertOp) { 105072de7588SNicolas Vasilache auto condition = [&](Value val) { 105172de7588SNicolas Vasilache if (auto extractOp = val.getDefiningOp<ExtractSliceOp>()) 105272de7588SNicolas Vasilache if (areEquivalentExtractSliceOps(state, extractOp, insertOp)) 105372de7588SNicolas Vasilache return true; 105472de7588SNicolas Vasilache return false; 105572de7588SNicolas Vasilache }; 105672de7588SNicolas Vasilache 105772de7588SNicolas Vasilache return llvm::all_of(state.findValueInReverseUseDefChain(value, condition), 105872de7588SNicolas Vasilache condition); 105972de7588SNicolas Vasilache } 106072de7588SNicolas Vasilache 106172de7588SNicolas Vasilache /// Analysis of ParallelInsertSliceOp. 106272de7588SNicolas Vasilache struct ParallelInsertSliceOpInterface 106372de7588SNicolas Vasilache : public BufferizableOpInterface::ExternalModel< 106472de7588SNicolas Vasilache ParallelInsertSliceOpInterface, ParallelInsertSliceOp> { 106572de7588SNicolas Vasilache SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 106672de7588SNicolas Vasilache const AnalysisState &state) const { 106772de7588SNicolas Vasilache if (&opOperand != &op->getOpOperand(1) /*dest*/) 106872de7588SNicolas Vasilache return {}; 106972de7588SNicolas Vasilache 107072de7588SNicolas Vasilache // ParallelInsertSliceOp itself has no results. Tensors are returned via 107172de7588SNicolas Vasilache // the parent op. 107272de7588SNicolas Vasilache auto foreachThreadOp = op->getParentOfType<ForeachThreadOp>(); 107372de7588SNicolas Vasilache assert(foreachThreadOp && 107472de7588SNicolas Vasilache "could not find valid owner of parallel_insert_slice"); 107572de7588SNicolas Vasilache 107672de7588SNicolas Vasilache // The i-th ParallelInsertSliceOp result is returned via the i-th OpResult 107772de7588SNicolas Vasilache // of the parent ForeachThreadOp. 107872de7588SNicolas Vasilache Block *block = op->getBlock(); 107972de7588SNicolas Vasilache unsigned int opIdx = 0; 108072de7588SNicolas Vasilache for (ParallelInsertSliceOp insertOp : 108172de7588SNicolas Vasilache block->getOps<ParallelInsertSliceOp>()) { 108272de7588SNicolas Vasilache if (insertOp.getOperation() == op) 108372de7588SNicolas Vasilache break; 108472de7588SNicolas Vasilache ++opIdx; 108572de7588SNicolas Vasilache } 108672de7588SNicolas Vasilache assert(opIdx < foreachThreadOp->getNumResults() && 108772de7588SNicolas Vasilache "could not find op inside terminator op"); 108872de7588SNicolas Vasilache 108972de7588SNicolas Vasilache return {foreachThreadOp->getResult(opIdx)}; 109072de7588SNicolas Vasilache } 109172de7588SNicolas Vasilache 109272de7588SNicolas Vasilache bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 109372de7588SNicolas Vasilache const AnalysisState &state) const { 109472de7588SNicolas Vasilache return true; 109572de7588SNicolas Vasilache } 109672de7588SNicolas Vasilache 109772de7588SNicolas Vasilache bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 109872de7588SNicolas Vasilache const AnalysisState &state) const { 109972de7588SNicolas Vasilache return &opOperand == &op->getOpOperand(1) /*dest*/; 110072de7588SNicolas Vasilache } 110172de7588SNicolas Vasilache 110272de7588SNicolas Vasilache BufferRelation bufferRelation(Operation *op, OpResult opResult, 110372de7588SNicolas Vasilache const AnalysisState &state) const { 110472de7588SNicolas Vasilache return BufferRelation::Equivalent; 110572de7588SNicolas Vasilache } 110672de7588SNicolas Vasilache 1107d361ecbdSMatthias Springer LogicalResult resolveConflicts(Operation *op, RewriterBase &rewriter, 1108d361ecbdSMatthias Springer const AnalysisState &state) const { 1109d361ecbdSMatthias Springer return success(); 1110d361ecbdSMatthias Springer } 1111d361ecbdSMatthias Springer 111272de7588SNicolas Vasilache LogicalResult bufferize(Operation *op, RewriterBase &b, 1113b55d55ecSMatthias Springer const BufferizationOptions &options) const { 111472de7588SNicolas Vasilache // Will be bufferized as part of ForeachThreadOp. 111572de7588SNicolas Vasilache return failure(); 111672de7588SNicolas Vasilache } 111772de7588SNicolas Vasilache 111872de7588SNicolas Vasilache // TODO: This is copied from TensorInterfaceImpl.cpp. Find a way to share 111972de7588SNicolas Vasilache // the code. 112072de7588SNicolas Vasilache bool isNotConflicting(Operation *op, OpOperand *uRead, 112172de7588SNicolas Vasilache OpOperand *uConflictingWrite, 112272de7588SNicolas Vasilache const AnalysisState &state) const { 112372de7588SNicolas Vasilache Operation *readingOp = uRead->getOwner(); 112472de7588SNicolas Vasilache Operation *conflictingWritingOp = uConflictingWrite->getOwner(); 112572de7588SNicolas Vasilache 112672de7588SNicolas Vasilache // Special rules for matching ExtractSliceOp/InsertSliceOp pairs. If 112772de7588SNicolas Vasilache // uRead is an InsertSliceOp... 112872de7588SNicolas Vasilache if (auto insertSliceOp = dyn_cast<ParallelInsertSliceOp>(readingOp)) { 112972de7588SNicolas Vasilache // As an example, consider the following IR. 113072de7588SNicolas Vasilache // 113172de7588SNicolas Vasilache // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] } 113272de7588SNicolas Vasilache // %1 = linalg.fill %cst, %0 {inplace= [true] } 113372de7588SNicolas Vasilache // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1] 113472de7588SNicolas Vasilache // {inplace= [true] } 113572de7588SNicolas Vasilache 113672de7588SNicolas Vasilache // TODO: Use insertSliceOp.getDestOpOperand etc. when available. 113772de7588SNicolas Vasilache if (uRead == &insertSliceOp->getOpOperand(1) /*dest*/ && 113872de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, uConflictingWrite->get(), 113972de7588SNicolas Vasilache insertSliceOp)) 114072de7588SNicolas Vasilache // Case 1: The main insight is that InsertSliceOp reads only part of 114172de7588SNicolas Vasilache // the destination tensor. The overwritten area is not read. If 114272de7588SNicolas Vasilache // uConflictingWrite writes into exactly the memory location that is 114372de7588SNicolas Vasilache // being read by uRead, this is not a conflict. 114472de7588SNicolas Vasilache // 114572de7588SNicolas Vasilache // In the above example: 114672de7588SNicolas Vasilache // uRead = OpOperand 1 (%t) of tensor.insert_slice 114772de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%0) of linalg.fill 114872de7588SNicolas Vasilache // 114972de7588SNicolas Vasilache // The read of %t does not conflict with the write of the FillOp 115072de7588SNicolas Vasilache // (same aliases!) because the area that the FillOp operates on is 115172de7588SNicolas Vasilache // exactly the one that is *not* read via %t. 115272de7588SNicolas Vasilache return true; 115372de7588SNicolas Vasilache 115472de7588SNicolas Vasilache if (uRead == &insertSliceOp->getOpOperand(0) /*source*/ && 115572de7588SNicolas Vasilache uConflictingWrite == &insertSliceOp->getOpOperand(1) /*dest*/ && 115672de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, uRead->get(), insertSliceOp)) 115772de7588SNicolas Vasilache // Case 2: The read of the source tensor and the write to the dest 115872de7588SNicolas Vasilache // tensor via an InsertSliceOp is not a conflict if the read is 115972de7588SNicolas Vasilache // reading exactly that part of an equivalent tensor that the 116072de7588SNicolas Vasilache // InsertSliceOp is writing. 116172de7588SNicolas Vasilache // 116272de7588SNicolas Vasilache // In the above example: 116372de7588SNicolas Vasilache // uRead = OpOperand 0 (%1) of tensor.insert_slice 116472de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice 116572de7588SNicolas Vasilache return true; 116672de7588SNicolas Vasilache } 116772de7588SNicolas Vasilache 116872de7588SNicolas Vasilache // If uConflictingWrite is an InsertSliceOp... 116972de7588SNicolas Vasilache if (auto insertSliceOp = 117072de7588SNicolas Vasilache dyn_cast<ParallelInsertSliceOp>(conflictingWritingOp)) 117172de7588SNicolas Vasilache // As an example, consider the following IR. 117272de7588SNicolas Vasilache // 117372de7588SNicolas Vasilache // %0 = tensor.extract_slice %t[%a, %b][%c, %d][1, 1] {inplace = [true] } 117472de7588SNicolas Vasilache // %1 = linalg.fill %cst, %0 {inplace= [true] } 117572de7588SNicolas Vasilache // %2 = tensor.insert_slice %1 into %t[%a, %b][%c, %d][1, 1] 117672de7588SNicolas Vasilache // {inplace= [true] } 117772de7588SNicolas Vasilache // %3 = vector.transfer_read %1, %cst 117872de7588SNicolas Vasilache // 117972de7588SNicolas Vasilache // In the above example: 118072de7588SNicolas Vasilache // uRead = OpOperand 0 (%1) of vector.transfer_read 118172de7588SNicolas Vasilache // uConflictingWrite = OpOperand 1 (%t) of tensor.insert_slice 118272de7588SNicolas Vasilache // lastWrite = %1 118372de7588SNicolas Vasilache // 118472de7588SNicolas Vasilache // This is not a conflict because the InsertSliceOp overwrites the 118572de7588SNicolas Vasilache // memory segment of %1 with the exact same data. (Effectively, there 118672de7588SNicolas Vasilache // is no memory write here.) 118772de7588SNicolas Vasilache if (uConflictingWrite == &insertSliceOp->getOpOperand(1) /*dest*/ && 118872de7588SNicolas Vasilache state.areEquivalentBufferizedValues(uRead->get(), 118972de7588SNicolas Vasilache insertSliceOp.getSource()) && 119072de7588SNicolas Vasilache hasMatchingExtractSliceOp(state, insertSliceOp.getSource(), 119172de7588SNicolas Vasilache insertSliceOp)) 119272de7588SNicolas Vasilache return true; 119372de7588SNicolas Vasilache 119472de7588SNicolas Vasilache return false; 119572de7588SNicolas Vasilache } 119672de7588SNicolas Vasilache }; 119772de7588SNicolas Vasilache 119819efe141SMatthias Springer } // namespace 119919efe141SMatthias Springer } // namespace scf 120019efe141SMatthias Springer } // namespace mlir 120119efe141SMatthias Springer 120219efe141SMatthias Springer void mlir::scf::registerBufferizableOpInterfaceExternalModels( 120319efe141SMatthias Springer DialectRegistry ®istry) { 120477eee579SRiver Riddle registry.addExtension(+[](MLIRContext *ctx, scf::SCFDialect *dialect) { 120577eee579SRiver Riddle ExecuteRegionOp::attachInterface<ExecuteRegionOpInterface>(*ctx); 120677eee579SRiver Riddle ForOp::attachInterface<ForOpInterface>(*ctx); 120777eee579SRiver Riddle IfOp::attachInterface<IfOpInterface>(*ctx); 120872de7588SNicolas Vasilache ForeachThreadOp::attachInterface<ForeachThreadOpInterface>(*ctx); 120972de7588SNicolas Vasilache ParallelInsertSliceOp::attachInterface<ParallelInsertSliceOpInterface>( 121072de7588SNicolas Vasilache *ctx); 121172de7588SNicolas Vasilache PerformConcurrentlyOp::attachInterface<PerformConcurrentlyOpInterface>( 121272de7588SNicolas Vasilache *ctx); 1213a5d09c63SMatthias Springer WhileOp::attachInterface<WhileOpInterface>(*ctx); 121477eee579SRiver Riddle YieldOp::attachInterface<YieldOpInterface>(*ctx); 121577eee579SRiver Riddle }); 121619efe141SMatthias Springer } 1217