//===- TensorsToBuffers.cpp - Transformation from tensors to buffers ------===// // // 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 // //===----------------------------------------------------------------------===// // // This file implements the conversion from tensors to buffers on Linalg // operations. // //===----------------------------------------------------------------------===// #include "PassDetail.h" #include "mlir/Dialect/Linalg/IR/LinalgOps.h" #include "mlir/Dialect/Linalg/Passes.h" #include "mlir/IR/Function.h" #include "mlir/IR/Operation.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/BufferPlacement.h" using namespace mlir; namespace { /// A pattern to convert Generic Linalg operations which work on tensors to /// use buffers. A buffer is allocated using BufferAssignmentPlacer for /// each operation result. BufferPlacement pass should be later used to move /// Alloc operations to the correct positions and insert the missing Dealloc /// operations in the correct places. class GenericOpConverter : public BufferAssignmentOpConversionPattern { public: using BufferAssignmentOpConversionPattern< linalg::GenericOp>::BufferAssignmentOpConversionPattern; LogicalResult matchAndRewrite(linalg::GenericOp op, ArrayRef operands, ConversionPatternRewriter &rewriter) const final { linalg::GenericOpAdaptor adaptor(operands, op.getOperation()->getAttrDictionary()); // TODO: support ops with reduction. if (!op.init_tensors().empty()) return failure(); // All inputs need to be turned into buffers first. Until then, bail out. if (llvm::any_of(adaptor.inputs(), [](Value in) { return !in.getType().isa(); })) return failure(); Location loc = op.getLoc(); SmallVector outputBuffers, newOutputBuffers; outputBuffers.assign(adaptor.output_buffers().begin(), adaptor.output_buffers().end()); newOutputBuffers.reserve(op.getNumOutputs()); newOutputBuffers.append(adaptor.output_buffers().begin(), adaptor.output_buffers().end()); // Update all types to memref types. for (Type t : op.getResultTypes()) { auto type = t.cast(); if (!type.hasStaticShape()) return rewriter.notifyMatchFailure( op, "dynamic shapes not currently supported"); auto memrefType = MemRefType::get(type.getShape(), type.getElementType()); auto alloc = rewriter.create(loc, memrefType); newOutputBuffers.push_back(alloc); } // Generate a new linalg operation that works on buffers. auto linalgOp = rewriter.create( loc, /*resultTensorTypes=*/ArrayRef{}, /*inputs=*/adaptor.inputs(), /*outputBuffers=*/newOutputBuffers, /*initTensors=*/ValueRange{}, op.indexing_maps(), op.iterator_types(), op.docAttr(), op.library_callAttr(), op.symbol_sourceAttr()); // Create a new block in the region of the new Generic Op. Block &oldBlock = op.getRegion().front(); Region &newRegion = linalgOp.region(); Block *newBlock = rewriter.createBlock(&newRegion, newRegion.begin(), oldBlock.getArgumentTypes()); // Add the result arguments to the new block. for (Value v : newOutputBuffers) newBlock->addArgument(v.getType().cast().getElementType()); // Clone the body of the old block to the new block. BlockAndValueMapping mapping; for (unsigned i = 0; i < oldBlock.getNumArguments(); i++) mapping.map(oldBlock.getArgument(i), newBlock->getArgument(i)); OpBuilder::InsertionGuard guard(rewriter); rewriter.setInsertionPointToEnd(newBlock); for (auto &op : oldBlock.getOperations()) { Operation *clonedOp = rewriter.clone(op, mapping); mapping.map(op.getResults(), clonedOp->getResults()); } // Replace the results of the old op with the new output buffers. rewriter.replaceOp(op, newOutputBuffers); return success(); } }; /// Populate the given list with patterns to convert Linalg operations on /// tensors to buffers. static void populateConvertLinalgOnTensorsToBuffersPattern( MLIRContext *context, BufferAssignmentTypeConverter *converter, OwningRewritePatternList *patterns) { populateWithBufferAssignmentOpConversionPatterns< mlir::ReturnOp, mlir::ReturnOp, linalg::CopyOp>(context, converter, patterns); patterns->insert(context, converter); } /// Converts Linalg operations that work on tensor-type operands or results to /// work on buffers. struct ConvertLinalgOnTensorsToBuffers : public LinalgOnTensorsToBuffersBase { void runOnOperation() override { MLIRContext &context = getContext(); ConversionTarget target(context); BufferAssignmentTypeConverter converter; // Mark all Standard operations legal. target.addLegalDialect(); target.addLegalOp(); target.addLegalOp(); // Mark all Linalg operations illegal as long as they work on tensors. auto isLegalOperation = [&](Operation *op) { return converter.isLegal(op); }; target.addDynamicallyLegalDialect( Optional( isLegalOperation)); // Mark Standard Return operations illegal as long as one operand is tensor. target.addDynamicallyLegalOp([&](mlir::ReturnOp returnOp) { return converter.isLegal(returnOp.getOperandTypes()); }); // Mark the function operation illegal as long as an argument is tensor. target.addDynamicallyLegalOp([&](FuncOp funcOp) { return converter.isSignatureLegal(funcOp.getType()) && llvm::none_of(funcOp.getType().getResults(), [&](Type type) { return type.isa(); }) && converter.isLegal(&funcOp.getBody()); }); converter.setResultConversionKind( BufferAssignmentTypeConverter::AppendToArgumentsList); OwningRewritePatternList patterns; populateConvertLinalgOnTensorsToBuffersPattern(&context, &converter, &patterns); if (failed(applyFullConversion(this->getOperation(), target, patterns))) this->signalPassFailure(); } }; } // end anonymous namespace std::unique_ptr> mlir::createConvertLinalgOnTensorsToBuffersPass() { return std::make_unique(); }