| //===- BufferizableOpInterfaceImpl.cpp - Impl. of BufferizableOpInterface -===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "mlir/Dialect/Bufferization/Transforms/BufferizableOpInterfaceImpl.h" |
| |
| #include "mlir/Dialect/Bufferization/IR/BufferizableOpInterface.h" |
| #include "mlir/Dialect/Bufferization/IR/Bufferization.h" |
| #include "mlir/Dialect/MemRef/IR/MemRef.h" |
| #include "mlir/IR/Dialect.h" |
| #include "mlir/IR/Operation.h" |
| |
| using namespace mlir; |
| using namespace mlir::bufferization; |
| |
| namespace mlir { |
| namespace bufferization { |
| namespace { |
| |
| struct AllocTensorOpInterface |
| : public BufferizableOpInterface::ExternalModel<AllocTensorOpInterface, |
| AllocTensorOp> { |
| bool bufferizesToAllocation(Operation *op, Value value) const { return true; } |
| |
| bool resultBufferizesToMemoryWrite(Operation *op, OpResult opResult, |
| const AnalysisState &state) const { |
| // AllocTensorOps do not write unless they have a `copy` value. |
| return static_cast<bool>(cast<AllocTensorOp>(op).getCopy()); |
| } |
| |
| bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| assert(&opOperand == &cast<AllocTensorOp>(op).getCopyMutable()[0] && |
| "expected copy operand"); |
| return true; |
| } |
| |
| bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| assert(&opOperand == &cast<AllocTensorOp>(op).getCopyMutable()[0] && |
| "expected copy operand"); |
| return false; |
| } |
| |
| AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| // This is a new allocation. It does not alias with any other buffer. |
| return {}; |
| } |
| |
| FailureOr<BufferLikeType> |
| getBufferType(Operation *op, Value value, const BufferizationOptions &options, |
| const BufferizationState &state, |
| SmallVector<Value> &invocationStack) const { |
| auto allocTensorOp = cast<AllocTensorOp>(op); |
| assert(value == allocTensorOp.getResult() && "invalid value"); |
| |
| // Compute memory space of this allocation. |
| Attribute memorySpace; |
| if (allocTensorOp.getMemorySpace().has_value()) { |
| memorySpace = *allocTensorOp.getMemorySpace(); |
| } else if (allocTensorOp.getCopy()) { |
| auto copyBufferType = |
| bufferization::detail::asMemRefType(bufferization::getBufferType( |
| allocTensorOp.getCopy(), options, state, invocationStack)); |
| if (failed(copyBufferType)) |
| return failure(); |
| memorySpace = copyBufferType->getMemorySpace(); |
| } else if (auto ms = options.defaultMemorySpaceFn( |
| cast<TensorLikeType>(allocTensorOp.getType()))) { |
| memorySpace = *ms; |
| } else { |
| return op->emitError("could not infer memory space"); |
| } |
| |
| return cast<BufferLikeType>(getMemRefTypeWithStaticIdentityLayout( |
| allocTensorOp.getType(), memorySpace)); |
| } |
| |
| LogicalResult bufferize(Operation *op, RewriterBase &rewriter, |
| const BufferizationOptions &options, |
| BufferizationState &state) const { |
| auto allocTensorOp = cast<AllocTensorOp>(op); |
| OpBuilder::InsertionGuard g(rewriter); |
| Location loc = allocTensorOp.getLoc(); |
| |
| // Nothing to do for dead AllocTensorOps. |
| if (op->getUses().empty()) { |
| rewriter.eraseOp(op); |
| return success(); |
| } |
| |
| // Get "copy" buffer. |
| Value copyBuffer; |
| if (allocTensorOp.getCopy()) { |
| FailureOr<Value> maybeCopyBuffer = bufferization::getBuffer( |
| rewriter, allocTensorOp.getCopy(), options, state); |
| if (failed(maybeCopyBuffer)) |
| return failure(); |
| copyBuffer = *maybeCopyBuffer; |
| } |
| |
| // Create memory allocation. |
| auto allocType = |
| bufferization::getBufferType(allocTensorOp.getResult(), options, state); |
| if (failed(allocType)) |
| return failure(); |
| SmallVector<Value> dynamicDims = allocTensorOp.getDynamicSizes(); |
| if (allocTensorOp.getCopy()) { |
| assert(dynamicDims.empty() && "expected either `copy` or `dynamicDims`"); |
| populateDynamicDimSizes(rewriter, loc, copyBuffer, dynamicDims); |
| } |
| FailureOr<Value> alloc = |
| options.allocationFn(rewriter, loc, llvm::cast<MemRefType>(*allocType), |
| dynamicDims, options.bufferAlignment); |
| if (failed(alloc)) |
| return failure(); |
| |
| // Create memory copy (if any). |
| if (allocTensorOp.getCopy()) { |
| if (failed(options.memCpyFn(rewriter, loc, copyBuffer, *alloc))) |
| return failure(); |
| } |
| |
| // Replace op. |
| replaceOpWithBufferizedValues(rewriter, op, *alloc); |
| |
| return success(); |
| } |
| }; |
| |
| struct DeallocTensorOpInterface |
| : public BufferizableOpInterface::ExternalModel<DeallocTensorOpInterface, |
| DeallocTensorOp> { |
| bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return false; |
| } |
| |
| bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return false; |
| } |
| |
| AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return {}; |
| } |
| |
| LogicalResult bufferize(Operation *op, RewriterBase &rewriter, |
| const BufferizationOptions &options, |
| BufferizationState &state) const { |
| auto deallocTensorOp = cast<DeallocTensorOp>(op); |
| FailureOr<Value> buffer = bufferization::getBuffer( |
| rewriter, deallocTensorOp.getTensor(), options, state); |
| if (failed(buffer)) |
| return failure(); |
| memref::DeallocOp::create(rewriter, deallocTensorOp.getLoc(), *buffer); |
| rewriter.eraseOp(op); |
| return success(); |
| } |
| }; |
| |
| struct MaterializeInDestinationOpInterface |
| : public BufferizableOpInterface::ExternalModel< |
| MaterializeInDestinationOpInterface, MaterializeInDestinationOp> { |
| bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return opOperand == cast<MaterializeInDestinationOp>(op).getSourceMutable(); |
| } |
| |
| bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| auto materializeOp = cast<MaterializeInDestinationOp>(op); |
| if (opOperand == materializeOp.getDestMutable()) { |
| assert(isa<TensorType>(materializeOp.getDest().getType()) && |
| "expected tensor type"); |
| return true; |
| } |
| return false; |
| } |
| |
| bool mustBufferizeInPlace(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| // The source is only read and not written, so it always bufferizes in-place |
| // by default. The destination is written and is forced to bufferize |
| // in-place (if it is a tensor). |
| return true; |
| } |
| |
| AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| auto materializeOp = cast<MaterializeInDestinationOp>(op); |
| if (opOperand == materializeOp.getDestMutable()) { |
| assert(isa<TensorType>(materializeOp.getDest().getType()) && |
| "expected tensor type"); |
| return {{op->getResult(0), BufferRelation::Equivalent}}; |
| } |
| return {}; |
| } |
| |
| LogicalResult bufferize(Operation *op, RewriterBase &rewriter, |
| const BufferizationOptions &options, |
| BufferizationState &state) const { |
| auto materializeOp = cast<MaterializeInDestinationOp>(op); |
| bool tensorDest = isa<TensorType>(materializeOp.getDest().getType()); |
| Value buffer; |
| if (tensorDest) { |
| FailureOr<Value> maybeBuffer = bufferization::getBuffer( |
| rewriter, materializeOp.getDest(), options, state); |
| if (failed(maybeBuffer)) |
| return failure(); |
| buffer = *maybeBuffer; |
| } else { |
| assert(isa<BaseMemRefType>(materializeOp.getDest().getType()) && |
| "expected memref type"); |
| buffer = materializeOp.getDest(); |
| } |
| auto srcBuffer = bufferization::getBuffer( |
| rewriter, materializeOp.getSource(), options, state); |
| if (failed(srcBuffer)) |
| return failure(); |
| if (failed(options.memCpyFn(rewriter, materializeOp.getLoc(), *srcBuffer, |
| buffer))) |
| return failure(); |
| replaceOpWithBufferizedValues( |
| rewriter, op, tensorDest ? ValueRange(buffer) : ValueRange()); |
| return success(); |
| } |
| |
| bool bufferizesToElementwiseAccess(Operation *op, const AnalysisState &state, |
| ArrayRef<OpOperand *> opOperands) const { |
| // As elements are copied from the "source" buffer to the "dest" buffer, |
| // already copied elements are not read a second time. |
| return true; |
| } |
| |
| bool isWritable(Operation *op, Value value, |
| const AnalysisState &state) const { |
| auto materializeOp = cast<MaterializeInDestinationOp>(op); |
| return isa<TensorType>(materializeOp.getDest().getType()) |
| ? true |
| : materializeOp.getWritable(); |
| } |
| }; |
| |
| // Note: ToBufferOp / ToTensorOp are temporary ops that are inserted at the |
| // bufferization boundary. When One-Shot bufferization is complete, there should |
| // be no such ops left over. If `allowUnknownOps` (or after running a partial |
| // bufferization pass), such ops may be part of the resulting IR, but such IR |
| // may no longer be analyzable by One-Shot analysis. |
| |
| struct ToTensorOpInterface |
| : public BufferizableOpInterface::ExternalModel<ToTensorOpInterface, |
| ToTensorOp> { |
| bool isWritable(Operation *op, Value value, |
| const AnalysisState &state) const { |
| return cast<ToTensorOp>(op).getWritable(); |
| } |
| |
| FailureOr<BufferLikeType> |
| getBufferType(Operation *op, Value value, const BufferizationOptions &options, |
| const BufferizationState &state, |
| SmallVector<Value> &invocationStack) const { |
| return cast<ToTensorOp>(op).getBuffer().getType(); |
| } |
| |
| LogicalResult bufferize(Operation *op, RewriterBase &rewriter, |
| const BufferizationOptions &options, |
| BufferizationState &state) const { |
| // to_tensor/to_buffer pairs fold away after bufferization. |
| return success(); |
| } |
| }; |
| |
| struct ToBufferOpInterface |
| : public BufferizableOpInterface::ExternalModel<ToBufferOpInterface, |
| ToBufferOp> { |
| bool bufferizesToMemoryRead(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| // It is unknown whether the resulting memref will be read or not. |
| return true; |
| } |
| |
| bool bufferizesToMemoryWrite(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return !cast<ToBufferOp>(op).getReadOnly(); |
| } |
| |
| AliasingValueList getAliasingValues(Operation *op, OpOperand &opOperand, |
| const AnalysisState &state) const { |
| return {}; |
| } |
| |
| LogicalResult bufferize(Operation *op, RewriterBase &rewriter, |
| const BufferizationOptions &options, |
| BufferizationState &state) const { |
| // Fold to_buffer(to_tensor(x)) to x. Insert a cast if necessary. |
| (void)foldToBufferToTensorPair(rewriter, cast<ToBufferOp>(op), options); |
| // Note: The return value of `bufferize` indicates whether there was an |
| // error or not. (And not whether the pattern matched or not.) |
| return success(); |
| } |
| }; |
| |
| } // namespace |
| } // namespace bufferization |
| } // namespace mlir |
| |
| void mlir::bufferization::registerBufferizableOpInterfaceExternalModels( |
| DialectRegistry ®istry) { |
| registry.addExtension(+[](MLIRContext *ctx, BufferizationDialect *dialect) { |
| AllocTensorOp::attachInterface<AllocTensorOpInterface>(*ctx); |
| DeallocTensorOp::attachInterface<DeallocTensorOpInterface>(*ctx); |
| MaterializeInDestinationOp::attachInterface< |
| MaterializeInDestinationOpInterface>(*ctx); |
| ToBufferOp::attachInterface<ToBufferOpInterface>(*ctx); |
| ToTensorOp::attachInterface<ToTensorOpInterface>(*ctx); |
| }); |
| } |