1*e07a7fd5SMatthias Springer //===- BufferizableOpInterfaceImpl.cpp - Impl. of BufferizableOpInterface -===// 2*e07a7fd5SMatthias Springer // 3*e07a7fd5SMatthias Springer // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*e07a7fd5SMatthias Springer // See https://llvm.org/LICENSE.txt for license information. 5*e07a7fd5SMatthias Springer // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*e07a7fd5SMatthias Springer // 7*e07a7fd5SMatthias Springer //===----------------------------------------------------------------------===// 8*e07a7fd5SMatthias Springer 9*e07a7fd5SMatthias Springer #include "mlir/Dialect/Bufferization/Transforms/FuncBufferizableOpInterfaceImpl.h" 10*e07a7fd5SMatthias Springer #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h" 11*e07a7fd5SMatthias Springer #include "mlir/Dialect/Bufferization/IR/Bufferization.h" 12*e07a7fd5SMatthias Springer #include "mlir/Dialect/Bufferization/Transforms/OneShotAnalysis.h" 13*e07a7fd5SMatthias Springer #include "mlir/Dialect/Func/IR/FuncOps.h" 14*e07a7fd5SMatthias Springer #include "mlir/Dialect/MemRef/IR/MemRef.h" 15*e07a7fd5SMatthias Springer #include "mlir/IR/Dialect.h" 16*e07a7fd5SMatthias Springer #include "mlir/IR/Operation.h" 17*e07a7fd5SMatthias Springer 18*e07a7fd5SMatthias Springer namespace mlir { 19*e07a7fd5SMatthias Springer namespace bufferization { 20*e07a7fd5SMatthias Springer namespace func_ext { 21*e07a7fd5SMatthias Springer 22*e07a7fd5SMatthias Springer void FuncAnalysisState::startFunctionAnalysis(FuncOp funcOp) { 23*e07a7fd5SMatthias Springer analyzedFuncOps[funcOp] = FuncOpAnalysisState::InProgress; 24*e07a7fd5SMatthias Springer auto createdEquiv = equivalentFuncArgs.try_emplace(funcOp, IndexMapping()); 25*e07a7fd5SMatthias Springer auto createdAliasingOperands = 26*e07a7fd5SMatthias Springer aliasingFuncArgs.try_emplace(funcOp, IndexToIndexListMapping()); 27*e07a7fd5SMatthias Springer auto createdAliasingResults = 28*e07a7fd5SMatthias Springer aliasingReturnVals.try_emplace(funcOp, IndexToIndexListMapping()); 29*e07a7fd5SMatthias Springer auto createdRead = readBbArgs.try_emplace(funcOp, BbArgIndexSet()); 30*e07a7fd5SMatthias Springer auto createdWritten = writtenBbArgs.try_emplace(funcOp, BbArgIndexSet()); 31*e07a7fd5SMatthias Springer (void)createdEquiv; 32*e07a7fd5SMatthias Springer (void)createdAliasingOperands; 33*e07a7fd5SMatthias Springer (void)createdAliasingResults; 34*e07a7fd5SMatthias Springer (void)createdRead; 35*e07a7fd5SMatthias Springer (void)createdWritten; 36*e07a7fd5SMatthias Springer #ifndef NDEBUG 37*e07a7fd5SMatthias Springer assert(createdEquiv.second && "equivalence info exists already"); 38*e07a7fd5SMatthias Springer assert(createdAliasingOperands.second && "aliasing info exists already"); 39*e07a7fd5SMatthias Springer assert(createdAliasingResults.second && "aliasing info exists already"); 40*e07a7fd5SMatthias Springer assert(createdRead.second && "bbarg access info exists already"); 41*e07a7fd5SMatthias Springer assert(createdWritten.second && "bbarg access info exists already"); 42*e07a7fd5SMatthias Springer #endif // NDEBUG 43*e07a7fd5SMatthias Springer } 44*e07a7fd5SMatthias Springer 45*e07a7fd5SMatthias Springer /// Return the unique ReturnOp that terminates `funcOp`. 46*e07a7fd5SMatthias Springer /// Return nullptr if there is no such unique ReturnOp. 47*e07a7fd5SMatthias Springer static func::ReturnOp getAssumedUniqueReturnOp(FuncOp funcOp) { 48*e07a7fd5SMatthias Springer func::ReturnOp returnOp; 49*e07a7fd5SMatthias Springer for (Block &b : funcOp.getBody()) { 50*e07a7fd5SMatthias Springer if (auto candidateOp = dyn_cast<func::ReturnOp>(b.getTerminator())) { 51*e07a7fd5SMatthias Springer if (returnOp) 52*e07a7fd5SMatthias Springer return nullptr; 53*e07a7fd5SMatthias Springer returnOp = candidateOp; 54*e07a7fd5SMatthias Springer } 55*e07a7fd5SMatthias Springer } 56*e07a7fd5SMatthias Springer return returnOp; 57*e07a7fd5SMatthias Springer } 58*e07a7fd5SMatthias Springer 59*e07a7fd5SMatthias Springer /// Return the index-th bufferized function argument type. This assumes that the 60*e07a7fd5SMatthias Springer /// specified argument is a tensor. If the tensor is ranked, a layout map may be 61*e07a7fd5SMatthias Springer /// specified by the user. If no layout map is specified, a fully dynamic map is 62*e07a7fd5SMatthias Springer /// used. 63*e07a7fd5SMatthias Springer static BaseMemRefType 64*e07a7fd5SMatthias Springer getBufferizedFunctionArgType(FuncOp funcOp, int64_t index, 65*e07a7fd5SMatthias Springer const BufferizationOptions &options) { 66*e07a7fd5SMatthias Springer auto tensorType = 67*e07a7fd5SMatthias Springer funcOp.getFunctionType().getInput(index).dyn_cast<TensorType>(); 68*e07a7fd5SMatthias Springer assert(tensorType && "expected TensorType"); 69*e07a7fd5SMatthias Springer BaseMemRefType memrefType = getMemRefType(tensorType, options); 70*e07a7fd5SMatthias Springer 71*e07a7fd5SMatthias Springer auto layoutAttr = funcOp.getArgAttrOfType<AffineMapAttr>( 72*e07a7fd5SMatthias Springer index, BufferizationDialect::kBufferLayoutAttrName); 73*e07a7fd5SMatthias Springer if (!layoutAttr) 74*e07a7fd5SMatthias Springer return memrefType; 75*e07a7fd5SMatthias Springer 76*e07a7fd5SMatthias Springer auto rankedMemrefType = memrefType.dyn_cast<MemRefType>(); 77*e07a7fd5SMatthias Springer assert(rankedMemrefType && "buffer layout not supported on unranked tensors"); 78*e07a7fd5SMatthias Springer return MemRefType::get( 79*e07a7fd5SMatthias Springer rankedMemrefType.getShape(), rankedMemrefType.getElementType(), 80*e07a7fd5SMatthias Springer layoutAttr.getValue(), rankedMemrefType.getMemorySpaceAsInt()); 81*e07a7fd5SMatthias Springer } 82*e07a7fd5SMatthias Springer 83*e07a7fd5SMatthias Springer /// Return the FuncOp called by `callOp`. 84*e07a7fd5SMatthias Springer static FuncOp getCalledFunction(CallOpInterface callOp) { 85*e07a7fd5SMatthias Springer SymbolRefAttr sym = callOp.getCallableForCallee().dyn_cast<SymbolRefAttr>(); 86*e07a7fd5SMatthias Springer if (!sym) 87*e07a7fd5SMatthias Springer return nullptr; 88*e07a7fd5SMatthias Springer return dyn_cast_or_null<FuncOp>( 89*e07a7fd5SMatthias Springer SymbolTable::lookupNearestSymbolFrom(callOp, sym)); 90*e07a7fd5SMatthias Springer } 91*e07a7fd5SMatthias Springer 92*e07a7fd5SMatthias Springer /// Get FuncAnalysisState. 93*e07a7fd5SMatthias Springer static const FuncAnalysisState & 94*e07a7fd5SMatthias Springer getFuncAnalysisState(const AnalysisState &state) { 95*e07a7fd5SMatthias Springer Optional<const FuncAnalysisState *> maybeState = 96*e07a7fd5SMatthias Springer state.getDialectState<FuncAnalysisState>( 97*e07a7fd5SMatthias Springer func::FuncDialect::getDialectNamespace()); 98*e07a7fd5SMatthias Springer assert(maybeState.hasValue() && "FuncAnalysisState does not exist"); 99*e07a7fd5SMatthias Springer return **maybeState; 100*e07a7fd5SMatthias Springer } 101*e07a7fd5SMatthias Springer 102*e07a7fd5SMatthias Springer /// Return the state (phase) of analysis of the FuncOp. 103*e07a7fd5SMatthias Springer static FuncOpAnalysisState getFuncOpAnalysisState(const AnalysisState &state, 104*e07a7fd5SMatthias Springer FuncOp funcOp) { 105*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = getFuncAnalysisState(state); 106*e07a7fd5SMatthias Springer auto it = funcState.analyzedFuncOps.find(funcOp); 107*e07a7fd5SMatthias Springer if (it == funcState.analyzedFuncOps.end()) 108*e07a7fd5SMatthias Springer return FuncOpAnalysisState::NotAnalyzed; 109*e07a7fd5SMatthias Springer return it->second; 110*e07a7fd5SMatthias Springer } 111*e07a7fd5SMatthias Springer 112*e07a7fd5SMatthias Springer /// Return the index of the bbArg in the given FuncOp that is equivalent to the 113*e07a7fd5SMatthias Springer /// specified return value (if any). 114*e07a7fd5SMatthias Springer static Optional<int64_t> getEquivalentFuncArgIdx(FuncOp funcOp, 115*e07a7fd5SMatthias Springer const FuncAnalysisState &state, 116*e07a7fd5SMatthias Springer int64_t returnValIdx) { 117*e07a7fd5SMatthias Springer auto funcOpIt = state.equivalentFuncArgs.find(funcOp); 118*e07a7fd5SMatthias Springer if (funcOpIt == state.equivalentFuncArgs.end()) 119*e07a7fd5SMatthias Springer // No equivalence info stores for funcOp. 120*e07a7fd5SMatthias Springer return None; 121*e07a7fd5SMatthias Springer 122*e07a7fd5SMatthias Springer auto retValIt = funcOpIt->getSecond().find(returnValIdx); 123*e07a7fd5SMatthias Springer if (retValIt == funcOpIt->getSecond().end()) 124*e07a7fd5SMatthias Springer // Return value has no equivalent bbArg. 125*e07a7fd5SMatthias Springer return None; 126*e07a7fd5SMatthias Springer 127*e07a7fd5SMatthias Springer return retValIt->getSecond(); 128*e07a7fd5SMatthias Springer } 129*e07a7fd5SMatthias Springer 130*e07a7fd5SMatthias Springer struct CallOpInterface 131*e07a7fd5SMatthias Springer : public BufferizableOpInterface::ExternalModel<CallOpInterface, 132*e07a7fd5SMatthias Springer func::CallOp> { 133*e07a7fd5SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 134*e07a7fd5SMatthias Springer const AnalysisState &state) const { 135*e07a7fd5SMatthias Springer func::CallOp callOp = cast<func::CallOp>(op); 136*e07a7fd5SMatthias Springer FuncOp funcOp = getCalledFunction(callOp); 137*e07a7fd5SMatthias Springer assert(funcOp && "expected CallOp to a FuncOp"); 138*e07a7fd5SMatthias Springer 139*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = getFuncAnalysisState(state); 140*e07a7fd5SMatthias Springer if (getFuncOpAnalysisState(state, funcOp) != FuncOpAnalysisState::Analyzed) 141*e07a7fd5SMatthias Springer // FuncOp not analyzed yet. Assume that OpOperand is read. 142*e07a7fd5SMatthias Springer return true; 143*e07a7fd5SMatthias Springer 144*e07a7fd5SMatthias Springer return funcState.readBbArgs.lookup(funcOp).contains( 145*e07a7fd5SMatthias Springer opOperand.getOperandNumber()); 146*e07a7fd5SMatthias Springer } 147*e07a7fd5SMatthias Springer 148*e07a7fd5SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 149*e07a7fd5SMatthias Springer const AnalysisState &state) const { 150*e07a7fd5SMatthias Springer func::CallOp callOp = cast<func::CallOp>(op); 151*e07a7fd5SMatthias Springer FuncOp funcOp = getCalledFunction(callOp); 152*e07a7fd5SMatthias Springer assert(funcOp && "expected CallOp to a FuncOp"); 153*e07a7fd5SMatthias Springer 154*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = getFuncAnalysisState(state); 155*e07a7fd5SMatthias Springer if (getFuncOpAnalysisState(state, funcOp) != FuncOpAnalysisState::Analyzed) 156*e07a7fd5SMatthias Springer // FuncOp not analyzed yet. Assume that OpOperand is written. 157*e07a7fd5SMatthias Springer return true; 158*e07a7fd5SMatthias Springer 159*e07a7fd5SMatthias Springer return funcState.writtenBbArgs.lookup(funcOp).contains( 160*e07a7fd5SMatthias Springer opOperand.getOperandNumber()); 161*e07a7fd5SMatthias Springer } 162*e07a7fd5SMatthias Springer 163*e07a7fd5SMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 164*e07a7fd5SMatthias Springer const AnalysisState &state) const { 165*e07a7fd5SMatthias Springer func::CallOp callOp = cast<func::CallOp>(op); 166*e07a7fd5SMatthias Springer FuncOp funcOp = getCalledFunction(callOp); 167*e07a7fd5SMatthias Springer assert(funcOp && "expected CallOp to a FuncOp"); 168*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = getFuncAnalysisState(state); 169*e07a7fd5SMatthias Springer if (getFuncOpAnalysisState(state, funcOp) != 170*e07a7fd5SMatthias Springer FuncOpAnalysisState::Analyzed) { 171*e07a7fd5SMatthias Springer // FuncOp not analyzed yet. Any OpResult may be aliasing. 172*e07a7fd5SMatthias Springer SmallVector<OpResult> result; 173*e07a7fd5SMatthias Springer for (OpResult opResult : op->getOpResults()) 174*e07a7fd5SMatthias Springer if (opResult.getType().isa<TensorType>()) 175*e07a7fd5SMatthias Springer result.push_back(opResult); 176*e07a7fd5SMatthias Springer return result; 177*e07a7fd5SMatthias Springer } 178*e07a7fd5SMatthias Springer 179*e07a7fd5SMatthias Springer // Get aliasing results from state. 180*e07a7fd5SMatthias Springer auto aliasingReturnVals = 181*e07a7fd5SMatthias Springer funcState.aliasingReturnVals.lookup(funcOp).lookup( 182*e07a7fd5SMatthias Springer opOperand.getOperandNumber()); 183*e07a7fd5SMatthias Springer SmallVector<OpResult> result; 184*e07a7fd5SMatthias Springer for (int64_t resultIdx : aliasingReturnVals) 185*e07a7fd5SMatthias Springer result.push_back(callOp->getOpResult(resultIdx)); 186*e07a7fd5SMatthias Springer return result; 187*e07a7fd5SMatthias Springer } 188*e07a7fd5SMatthias Springer 189*e07a7fd5SMatthias Springer SmallVector<OpOperand *> 190*e07a7fd5SMatthias Springer getAliasingOpOperand(Operation *op, OpResult opResult, 191*e07a7fd5SMatthias Springer const AnalysisState &state) const { 192*e07a7fd5SMatthias Springer func::CallOp callOp = cast<func::CallOp>(op); 193*e07a7fd5SMatthias Springer FuncOp funcOp = getCalledFunction(callOp); 194*e07a7fd5SMatthias Springer assert(funcOp && "expected CallOp to a FuncOp"); 195*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = getFuncAnalysisState(state); 196*e07a7fd5SMatthias Springer if (getFuncOpAnalysisState(state, funcOp) != 197*e07a7fd5SMatthias Springer FuncOpAnalysisState::Analyzed) { 198*e07a7fd5SMatthias Springer // FuncOp not analyzed yet. Any OpOperand may be aliasing. 199*e07a7fd5SMatthias Springer SmallVector<OpOperand *> result; 200*e07a7fd5SMatthias Springer for (OpOperand &opOperand : op->getOpOperands()) 201*e07a7fd5SMatthias Springer if (opOperand.get().getType().isa<TensorType>()) 202*e07a7fd5SMatthias Springer result.push_back(&opOperand); 203*e07a7fd5SMatthias Springer return result; 204*e07a7fd5SMatthias Springer } 205*e07a7fd5SMatthias Springer 206*e07a7fd5SMatthias Springer // Get aliasing bbArgs from state. 207*e07a7fd5SMatthias Springer auto aliasingFuncArgs = funcState.aliasingFuncArgs.lookup(funcOp).lookup( 208*e07a7fd5SMatthias Springer opResult.getResultNumber()); 209*e07a7fd5SMatthias Springer SmallVector<OpOperand *> result; 210*e07a7fd5SMatthias Springer for (int64_t bbArgIdx : aliasingFuncArgs) 211*e07a7fd5SMatthias Springer result.push_back(&callOp->getOpOperand(bbArgIdx)); 212*e07a7fd5SMatthias Springer return result; 213*e07a7fd5SMatthias Springer } 214*e07a7fd5SMatthias Springer 215*e07a7fd5SMatthias Springer BufferRelation bufferRelation(Operation *op, OpResult opResult, 216*e07a7fd5SMatthias Springer const AnalysisState &state) const { 217*e07a7fd5SMatthias Springer return BufferRelation::Equivalent; 218*e07a7fd5SMatthias Springer } 219*e07a7fd5SMatthias Springer 220*e07a7fd5SMatthias Springer /// All function arguments are writable. It is the responsibility of the 221*e07a7fd5SMatthias Springer /// CallOp to insert buffer copies where necessary. 222*e07a7fd5SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 223*e07a7fd5SMatthias Springer BufferizationState &state) const { 224*e07a7fd5SMatthias Springer func::CallOp callOp = cast<func::CallOp>(op); 225*e07a7fd5SMatthias Springer unsigned numResults = callOp.getNumResults(); 226*e07a7fd5SMatthias Springer unsigned numOperands = callOp->getNumOperands(); 227*e07a7fd5SMatthias Springer FuncOp funcOp = getCalledFunction(callOp); 228*e07a7fd5SMatthias Springer assert(funcOp && "expected CallOp to a FuncOp"); 229*e07a7fd5SMatthias Springer FunctionType funcType = funcOp.getFunctionType(); 230*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = 231*e07a7fd5SMatthias Springer getFuncAnalysisState(state.getAnalysisState()); 232*e07a7fd5SMatthias Springer const OneShotBufferizationOptions &options = 233*e07a7fd5SMatthias Springer static_cast<const OneShotBufferizationOptions &>(state.getOptions()); 234*e07a7fd5SMatthias Springer 235*e07a7fd5SMatthias Springer // Result types of the bufferized CallOp. 236*e07a7fd5SMatthias Springer SmallVector<Type> resultTypes; 237*e07a7fd5SMatthias Springer // Replacement values for the existing CallOp. These are usually the results 238*e07a7fd5SMatthias Springer // of the bufferized CallOp, unless a tensor result folds onto an operand. 239*e07a7fd5SMatthias Springer SmallVector<Value> replacementValues(numResults, Value()); 240*e07a7fd5SMatthias Springer // For non-tensor results: A mapping from return val indices of the old 241*e07a7fd5SMatthias Springer // CallOp to return val indices of the bufferized CallOp. 242*e07a7fd5SMatthias Springer SmallVector<Optional<unsigned>> retValMapping(numResults, None); 243*e07a7fd5SMatthias Springer // Operands of the bufferized CallOp. 244*e07a7fd5SMatthias Springer SmallVector<Value> newOperands(numOperands, Value()); 245*e07a7fd5SMatthias Springer 246*e07a7fd5SMatthias Springer // Based on previously gathered equivalence information, we know if a 247*e07a7fd5SMatthias Springer // tensor result folds onto an operand. These are the only tensor value 248*e07a7fd5SMatthias Springer // results that are supported at the moment. 249*e07a7fd5SMatthias Springer // 250*e07a7fd5SMatthias Springer // For tensors return values that do not fold onto an operand, additional 251*e07a7fd5SMatthias Springer // work is needed (TODO) to either: 252*e07a7fd5SMatthias Springer // * hoist a result into an inplaceable operand or 253*e07a7fd5SMatthias Springer // * devise a better representation to truly return a buffer. 254*e07a7fd5SMatthias Springer // 255*e07a7fd5SMatthias Springer // Note: If a function has no body, no equivalence information is 256*e07a7fd5SMatthias Springer // available. Consequently, a tensor return value cannot be proven to fold 257*e07a7fd5SMatthias Springer // onto a FuncOp bbArg, so calls to such functions are not bufferizable at 258*e07a7fd5SMatthias Springer // the moment. 259*e07a7fd5SMatthias Springer 260*e07a7fd5SMatthias Springer // 1. Compute the result types of the new CallOp. Tensor results that are 261*e07a7fd5SMatthias Springer // equivalent to a FuncOp bbArg are no longer returned. 262*e07a7fd5SMatthias Springer for (const auto &it : llvm::enumerate(callOp.getResultTypes())) { 263*e07a7fd5SMatthias Springer unsigned returnValIdx = it.index(); 264*e07a7fd5SMatthias Springer Type returnType = it.value(); 265*e07a7fd5SMatthias Springer if (!returnType.isa<TensorType>()) { 266*e07a7fd5SMatthias Springer // Non-tensor values are returned. 267*e07a7fd5SMatthias Springer retValMapping[returnValIdx] = resultTypes.size(); 268*e07a7fd5SMatthias Springer resultTypes.push_back(returnType); 269*e07a7fd5SMatthias Springer continue; 270*e07a7fd5SMatthias Springer } 271*e07a7fd5SMatthias Springer 272*e07a7fd5SMatthias Springer if (Optional<int64_t> bbArgIdx = 273*e07a7fd5SMatthias Springer getEquivalentFuncArgIdx(funcOp, funcState, returnValIdx)) { 274*e07a7fd5SMatthias Springer // Return operands that are equivalent to some bbArg, are not 275*e07a7fd5SMatthias Springer // returned. 276*e07a7fd5SMatthias Springer FailureOr<Value> bufferOrFailure = 277*e07a7fd5SMatthias Springer state.getBuffer(rewriter, callOp->getOpOperand(*bbArgIdx)); 278*e07a7fd5SMatthias Springer if (failed(bufferOrFailure)) 279*e07a7fd5SMatthias Springer return failure(); 280*e07a7fd5SMatthias Springer replacementValues[returnValIdx] = *bufferOrFailure; 281*e07a7fd5SMatthias Springer newOperands[*bbArgIdx] = *bufferOrFailure; 282*e07a7fd5SMatthias Springer continue; 283*e07a7fd5SMatthias Springer } 284*e07a7fd5SMatthias Springer 285*e07a7fd5SMatthias Springer if (!options.allowReturnAllocs) 286*e07a7fd5SMatthias Springer return callOp->emitError( 287*e07a7fd5SMatthias Springer "call to FuncOp that returns non-equivalent tensors not supported"); 288*e07a7fd5SMatthias Springer 289*e07a7fd5SMatthias Springer // Returning a memref. This memref is not equivalent to any bbArg. It is 290*e07a7fd5SMatthias Springer // likely a newly allocated buffer. We may want to hoist such allocations 291*e07a7fd5SMatthias Springer // to the call site in the future. 292*e07a7fd5SMatthias Springer retValMapping[returnValIdx] = resultTypes.size(); 293*e07a7fd5SMatthias Springer resultTypes.push_back(funcType.getResult(resultTypes.size())); 294*e07a7fd5SMatthias Springer } 295*e07a7fd5SMatthias Springer 296*e07a7fd5SMatthias Springer // 2. Rewrite tensor operands as memrefs based on `bufferizedFuncType`. 297*e07a7fd5SMatthias Springer for (OpOperand &opOperand : callOp->getOpOperands()) { 298*e07a7fd5SMatthias Springer unsigned idx = opOperand.getOperandNumber(); 299*e07a7fd5SMatthias Springer Value tensorOperand = opOperand.get(); 300*e07a7fd5SMatthias Springer 301*e07a7fd5SMatthias Springer // Non-tensor operands are just copied. 302*e07a7fd5SMatthias Springer if (!tensorOperand.getType().isa<TensorType>()) { 303*e07a7fd5SMatthias Springer newOperands[idx] = tensorOperand; 304*e07a7fd5SMatthias Springer continue; 305*e07a7fd5SMatthias Springer } 306*e07a7fd5SMatthias Springer 307*e07a7fd5SMatthias Springer // Retrieve buffers for tensor operands. Tensor operand buffers, who's 308*e07a7fd5SMatthias Springer // corresponding FuncOp bbArgs are equivalent to a returned tensor, were 309*e07a7fd5SMatthias Springer // already stored in `newOperands` during Step 1. 310*e07a7fd5SMatthias Springer Value buffer = newOperands[idx]; 311*e07a7fd5SMatthias Springer if (!buffer) { 312*e07a7fd5SMatthias Springer FailureOr<Value> bufferOrFailure = state.getBuffer(rewriter, opOperand); 313*e07a7fd5SMatthias Springer if (failed(bufferOrFailure)) 314*e07a7fd5SMatthias Springer return failure(); 315*e07a7fd5SMatthias Springer buffer = *bufferOrFailure; 316*e07a7fd5SMatthias Springer } 317*e07a7fd5SMatthias Springer 318*e07a7fd5SMatthias Springer // Caller / callee type mismatch is handled with a CastOp. 319*e07a7fd5SMatthias Springer auto memRefType = funcType.getInput(idx); 320*e07a7fd5SMatthias Springer // Since we don't yet have a clear layout story, to_memref may 321*e07a7fd5SMatthias Springer // conservatively turn tensors into more dynamic memref than necessary. 322*e07a7fd5SMatthias Springer // If the memref type of the callee fails, introduce an extra memref.cast 323*e07a7fd5SMatthias Springer // that will either canonicalize away or fail compilation until we can do 324*e07a7fd5SMatthias Springer // something better. 325*e07a7fd5SMatthias Springer if (buffer.getType() != memRefType) { 326*e07a7fd5SMatthias Springer assert( 327*e07a7fd5SMatthias Springer memref::CastOp::areCastCompatible(buffer.getType(), memRefType) && 328*e07a7fd5SMatthias Springer "CallOp::bufferize: cast incompatible"); 329*e07a7fd5SMatthias Springer Value castBuffer = rewriter.create<memref::CastOp>(callOp.getLoc(), 330*e07a7fd5SMatthias Springer memRefType, buffer); 331*e07a7fd5SMatthias Springer buffer = castBuffer; 332*e07a7fd5SMatthias Springer } 333*e07a7fd5SMatthias Springer newOperands[idx] = buffer; 334*e07a7fd5SMatthias Springer } 335*e07a7fd5SMatthias Springer 336*e07a7fd5SMatthias Springer // 3. Create the new CallOp. 337*e07a7fd5SMatthias Springer Operation *newCallOp = rewriter.create<func::CallOp>( 338*e07a7fd5SMatthias Springer callOp.getLoc(), funcOp.getSymName(), resultTypes, newOperands); 339*e07a7fd5SMatthias Springer newCallOp->setAttrs(callOp->getAttrs()); 340*e07a7fd5SMatthias Springer // Get replacement values for non-tensor / non-equivalent results. 341*e07a7fd5SMatthias Springer for (unsigned i = 0; i < replacementValues.size(); ++i) { 342*e07a7fd5SMatthias Springer if (replacementValues[i]) 343*e07a7fd5SMatthias Springer continue; 344*e07a7fd5SMatthias Springer replacementValues[i] = newCallOp->getResult(*retValMapping[i]); 345*e07a7fd5SMatthias Springer } 346*e07a7fd5SMatthias Springer 347*e07a7fd5SMatthias Springer // 4. Replace the old op with the new op. 348*e07a7fd5SMatthias Springer replaceOpWithBufferizedValues(rewriter, callOp, replacementValues); 349*e07a7fd5SMatthias Springer 350*e07a7fd5SMatthias Springer return success(); 351*e07a7fd5SMatthias Springer } 352*e07a7fd5SMatthias Springer }; 353*e07a7fd5SMatthias Springer 354*e07a7fd5SMatthias Springer struct ReturnOpInterface 355*e07a7fd5SMatthias Springer : public BufferizableOpInterface::ExternalModel<ReturnOpInterface, 356*e07a7fd5SMatthias Springer func::ReturnOp> { 357*e07a7fd5SMatthias Springer bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, 358*e07a7fd5SMatthias Springer const AnalysisState &state) const { 359*e07a7fd5SMatthias Springer return true; 360*e07a7fd5SMatthias Springer } 361*e07a7fd5SMatthias Springer 362*e07a7fd5SMatthias Springer bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, 363*e07a7fd5SMatthias Springer const AnalysisState &state) const { 364*e07a7fd5SMatthias Springer return false; 365*e07a7fd5SMatthias Springer } 366*e07a7fd5SMatthias Springer 367*e07a7fd5SMatthias Springer SmallVector<OpResult> getAliasingOpResult(Operation *op, OpOperand &opOperand, 368*e07a7fd5SMatthias Springer const AnalysisState &state) const { 369*e07a7fd5SMatthias Springer return {}; 370*e07a7fd5SMatthias Springer } 371*e07a7fd5SMatthias Springer 372*e07a7fd5SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 373*e07a7fd5SMatthias Springer BufferizationState &state) const { 374*e07a7fd5SMatthias Springer #ifndef NDEBUG 375*e07a7fd5SMatthias Springer auto returnOp = cast<func::ReturnOp>(op); 376*e07a7fd5SMatthias Springer assert(isa<FuncOp>(returnOp->getParentOp()) && 377*e07a7fd5SMatthias Springer "only support FuncOp parent for ReturnOp"); 378*e07a7fd5SMatthias Springer #endif // NDEBUG 379*e07a7fd5SMatthias Springer 380*e07a7fd5SMatthias Springer // ReturnOps are bufferized as part of FuncOps. 381*e07a7fd5SMatthias Springer return failure(); 382*e07a7fd5SMatthias Springer } 383*e07a7fd5SMatthias Springer }; 384*e07a7fd5SMatthias Springer 385*e07a7fd5SMatthias Springer struct FuncOpInterface 386*e07a7fd5SMatthias Springer : public BufferizableOpInterface::ExternalModel<FuncOpInterface, FuncOp> { 387*e07a7fd5SMatthias Springer /// Rewrite function bbArgs and return values into buffer form (using the 388*e07a7fd5SMatthias Springer /// canonical memref layout for now). This function bufferizes the function 389*e07a7fd5SMatthias Springer /// signature and the ReturnOp. When the entire function body has been 390*e07a7fd5SMatthias Springer /// bufferized, function return types can be switched to more concise memref 391*e07a7fd5SMatthias Springer /// types as part of `foldMemRefCasts`. 392*e07a7fd5SMatthias Springer /// 393*e07a7fd5SMatthias Springer /// When a tensor function argument is known to be equivalent to a tensor 394*e07a7fd5SMatthias Springer /// result, it is dropped from the return values. 395*e07a7fd5SMatthias Springer /// 396*e07a7fd5SMatthias Springer /// All function bbArgs are writable unless they are explicitly marked as 397*e07a7fd5SMatthias Springer /// read-only. Callers must insert copies when needed. 398*e07a7fd5SMatthias Springer /// 399*e07a7fd5SMatthias Springer /// Note: Returning a memref is possible, but corresponding CallOp 400*e07a7fd5SMatthias Springer /// bufferizations fail unless `allowReturnAllocs`. 401*e07a7fd5SMatthias Springer LogicalResult bufferize(Operation *op, RewriterBase &rewriter, 402*e07a7fd5SMatthias Springer BufferizationState &state) const { 403*e07a7fd5SMatthias Springer auto funcOp = cast<FuncOp>(op); 404*e07a7fd5SMatthias Springer FunctionType funcType = funcOp.getFunctionType(); 405*e07a7fd5SMatthias Springer const FuncAnalysisState &funcState = 406*e07a7fd5SMatthias Springer getFuncAnalysisState(state.getAnalysisState()); 407*e07a7fd5SMatthias Springer const BufferizationOptions &options = state.getOptions(); 408*e07a7fd5SMatthias Springer 409*e07a7fd5SMatthias Springer // Construct the bufferized function type. 410*e07a7fd5SMatthias Springer SmallVector<Type> argTypes; 411*e07a7fd5SMatthias Springer for (const auto &it : llvm::enumerate(funcType.getInputs())) { 412*e07a7fd5SMatthias Springer Type argType = it.value(); 413*e07a7fd5SMatthias Springer if (auto tensorType = argType.dyn_cast<TensorType>()) { 414*e07a7fd5SMatthias Springer argTypes.push_back( 415*e07a7fd5SMatthias Springer getBufferizedFunctionArgType(funcOp, it.index(), options)); 416*e07a7fd5SMatthias Springer continue; 417*e07a7fd5SMatthias Springer } 418*e07a7fd5SMatthias Springer argTypes.push_back(argType); 419*e07a7fd5SMatthias Springer } 420*e07a7fd5SMatthias Springer 421*e07a7fd5SMatthias Springer // Bodiless functions are assumed opaque and we cannot know the 422*e07a7fd5SMatthias Springer // bufferization contract they want to enforce. As a consequence, only 423*e07a7fd5SMatthias Springer // support functions that don't return any tensors atm. 424*e07a7fd5SMatthias Springer if (funcOp.getBody().empty()) { 425*e07a7fd5SMatthias Springer SmallVector<Type> retTypes; 426*e07a7fd5SMatthias Springer for (Type resultType : funcType.getResults()) { 427*e07a7fd5SMatthias Springer if (resultType.isa<TensorType>()) 428*e07a7fd5SMatthias Springer return funcOp->emitError() << "cannot bufferize bodiless function " 429*e07a7fd5SMatthias Springer << "that returns a tensor"; 430*e07a7fd5SMatthias Springer retTypes.push_back(resultType); 431*e07a7fd5SMatthias Springer } 432*e07a7fd5SMatthias Springer funcOp.setType(FunctionType::get(op->getContext(), argTypes, retTypes)); 433*e07a7fd5SMatthias Springer return success(); 434*e07a7fd5SMatthias Springer } 435*e07a7fd5SMatthias Springer 436*e07a7fd5SMatthias Springer // TODO: Support functions with multiple returns. 437*e07a7fd5SMatthias Springer func::ReturnOp returnOp = getAssumedUniqueReturnOp(funcOp); 438*e07a7fd5SMatthias Springer assert(returnOp && "expected func with single return op"); 439*e07a7fd5SMatthias Springer 440*e07a7fd5SMatthias Springer // 1. Rewrite the bbArgs. Turn every tensor bbArg into a memref bbArg. 441*e07a7fd5SMatthias Springer Block &frontBlock = funcOp.getBody().front(); 442*e07a7fd5SMatthias Springer for (BlockArgument &bbArg : frontBlock.getArguments()) { 443*e07a7fd5SMatthias Springer auto tensorType = bbArg.getType().dyn_cast<TensorType>(); 444*e07a7fd5SMatthias Springer // Non-tensor types stay the same. 445*e07a7fd5SMatthias Springer if (!tensorType) 446*e07a7fd5SMatthias Springer continue; 447*e07a7fd5SMatthias Springer 448*e07a7fd5SMatthias Springer // Collect all uses of the bbArg. 449*e07a7fd5SMatthias Springer SmallVector<OpOperand *> bbArgUses; 450*e07a7fd5SMatthias Springer for (OpOperand &use : bbArg.getUses()) 451*e07a7fd5SMatthias Springer bbArgUses.push_back(&use); 452*e07a7fd5SMatthias Springer 453*e07a7fd5SMatthias Springer // Change the bbArg type to memref. 454*e07a7fd5SMatthias Springer Type memrefType = 455*e07a7fd5SMatthias Springer getBufferizedFunctionArgType(funcOp, bbArg.getArgNumber(), options); 456*e07a7fd5SMatthias Springer bbArg.setType(memrefType); 457*e07a7fd5SMatthias Springer 458*e07a7fd5SMatthias Springer // Replace all uses of the original tensor bbArg. 459*e07a7fd5SMatthias Springer rewriter.setInsertionPointToStart(&frontBlock); 460*e07a7fd5SMatthias Springer if (!bbArgUses.empty()) { 461*e07a7fd5SMatthias Springer // Insert to_tensor because the remaining function body has not been 462*e07a7fd5SMatthias Springer // bufferized yet. 463*e07a7fd5SMatthias Springer Value toTensorOp = 464*e07a7fd5SMatthias Springer rewriter.create<bufferization::ToTensorOp>(funcOp.getLoc(), bbArg); 465*e07a7fd5SMatthias Springer for (OpOperand *use : bbArgUses) 466*e07a7fd5SMatthias Springer use->set(toTensorOp); 467*e07a7fd5SMatthias Springer } 468*e07a7fd5SMatthias Springer } 469*e07a7fd5SMatthias Springer 470*e07a7fd5SMatthias Springer // 2. For each result, keep track of which inplace argument it reuses. 471*e07a7fd5SMatthias Springer SmallVector<Value> returnValues; 472*e07a7fd5SMatthias Springer for (OpOperand &returnOperand : returnOp->getOpOperands()) { 473*e07a7fd5SMatthias Springer Value returnVal = returnOperand.get(); 474*e07a7fd5SMatthias Springer 475*e07a7fd5SMatthias Springer // If not a tensor type just forward it. 476*e07a7fd5SMatthias Springer if (!returnVal.getType().isa<RankedTensorType>()) { 477*e07a7fd5SMatthias Springer returnValues.push_back(returnVal); 478*e07a7fd5SMatthias Springer continue; 479*e07a7fd5SMatthias Springer } 480*e07a7fd5SMatthias Springer 481*e07a7fd5SMatthias Springer // If return operand is equivalent to some bbArg, no need to return it. 482*e07a7fd5SMatthias Springer if (Optional<int64_t> equivBbArgIdx = getEquivalentFuncArgIdx( 483*e07a7fd5SMatthias Springer funcOp, funcState, returnOperand.getOperandNumber())) { 484*e07a7fd5SMatthias Springer rewriter.setInsertionPoint(returnOp); 485*e07a7fd5SMatthias Springer Location loc = returnOp.getLoc(); 486*e07a7fd5SMatthias Springer Value toMemrefOp = rewriter.create<bufferization::ToMemrefOp>( 487*e07a7fd5SMatthias Springer loc, getMemRefType(returnVal.getType().cast<TensorType>(), options), 488*e07a7fd5SMatthias Springer returnVal); 489*e07a7fd5SMatthias Springer BlockArgument equivBbArg = funcOp.getArgument(*equivBbArgIdx); 490*e07a7fd5SMatthias Springer // Note: This copy will fold away. It must be inserted here to ensure 491*e07a7fd5SMatthias Springer // that `returnVal` still has at least one use and does not fold away. 492*e07a7fd5SMatthias Springer if (failed( 493*e07a7fd5SMatthias Springer createMemCpy(rewriter, loc, toMemrefOp, equivBbArg, options))) 494*e07a7fd5SMatthias Springer return funcOp->emitError("could not generate copy for bbArg"); 495*e07a7fd5SMatthias Springer continue; 496*e07a7fd5SMatthias Springer } 497*e07a7fd5SMatthias Springer 498*e07a7fd5SMatthias Springer returnValues.push_back(*state.getBuffer(rewriter, returnOperand)); 499*e07a7fd5SMatthias Springer } 500*e07a7fd5SMatthias Springer 501*e07a7fd5SMatthias Springer // 3. Rewrite the terminator without the in-place bufferizable values. 502*e07a7fd5SMatthias Springer returnOp.operandsMutable().assign(returnValues); 503*e07a7fd5SMatthias Springer 504*e07a7fd5SMatthias Springer // 4. Rewrite the FuncOp type to buffer form. 505*e07a7fd5SMatthias Springer funcOp.setType(FunctionType::get(op->getContext(), argTypes, 506*e07a7fd5SMatthias Springer ValueRange(returnValues).getTypes())); 507*e07a7fd5SMatthias Springer 508*e07a7fd5SMatthias Springer return success(); 509*e07a7fd5SMatthias Springer } 510*e07a7fd5SMatthias Springer 511*e07a7fd5SMatthias Springer /// Return `true` if the given function argument is writable. 512*e07a7fd5SMatthias Springer bool isWritable(Operation *op, Value value, 513*e07a7fd5SMatthias Springer const AnalysisState &state) const { 514*e07a7fd5SMatthias Springer auto funcOp = cast<FuncOp>(op); 515*e07a7fd5SMatthias Springer BlockArgument bbArg = value.dyn_cast<BlockArgument>(); 516*e07a7fd5SMatthias Springer assert(bbArg && "expected BlockArgument"); 517*e07a7fd5SMatthias Springer 518*e07a7fd5SMatthias Springer // "bufferization.writable" overrides other writability decisions. This is 519*e07a7fd5SMatthias Springer // currently used for testing only. 520*e07a7fd5SMatthias Springer if (BoolAttr writable = funcOp.getArgAttrOfType<BoolAttr>( 521*e07a7fd5SMatthias Springer bbArg.getArgNumber(), BufferizationDialect::kWritableAttrName)) 522*e07a7fd5SMatthias Springer return writable.getValue(); 523*e07a7fd5SMatthias Springer 524*e07a7fd5SMatthias Springer // All function arguments are writable by default. 525*e07a7fd5SMatthias Springer return true; 526*e07a7fd5SMatthias Springer } 527*e07a7fd5SMatthias Springer 528*e07a7fd5SMatthias Springer bool isAllocationHoistingBarrier(Operation *op) const { return true; } 529*e07a7fd5SMatthias Springer }; 530*e07a7fd5SMatthias Springer 531*e07a7fd5SMatthias Springer } // namespace func_ext 532*e07a7fd5SMatthias Springer } // namespace bufferization 533*e07a7fd5SMatthias Springer } // namespace mlir 534*e07a7fd5SMatthias Springer 535*e07a7fd5SMatthias Springer void mlir::bufferization::func_ext:: 536*e07a7fd5SMatthias Springer registerBufferizableOpInterfaceExternalModels(DialectRegistry ®istry) { 537*e07a7fd5SMatthias Springer registry.addExtension(+[](MLIRContext *ctx, func::FuncDialect *dialect) { 538*e07a7fd5SMatthias Springer func::CallOp::attachInterface<func_ext::CallOpInterface>(*ctx); 539*e07a7fd5SMatthias Springer func::FuncOp::attachInterface<func_ext::FuncOpInterface>(*ctx); 540*e07a7fd5SMatthias Springer func::ReturnOp::attachInterface<func_ext::ReturnOpInterface>(*ctx); 541*e07a7fd5SMatthias Springer }); 542*e07a7fd5SMatthias Springer } 543