Mercurial > hg > CbC > CbC_llvm
view mlir/lib/Transforms/BufferPlacement.cpp @ 201:a96fbbdf2d0f
...
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Fri, 04 Jun 2021 21:07:06 +0900 |
parents | 0572611fdcc8 |
children |
line wrap: on
line source
//===- BufferPlacement.cpp - the impl for buffer placement ---------------===// // // 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 logic for computing correct alloc and dealloc positions. // The main class is the BufferPlacementPass class that implements the // underlying algorithm. In order to put allocations and deallocations at safe // positions, it is significantly important to put them into the correct blocks. // However, the liveness analysis does not pay attention to aliases, which can // occur due to branches (and their associated block arguments) in general. For // this purpose, BufferPlacement firstly finds all possible aliases for a single // value (using the BufferPlacementAliasAnalysis class). Consider the following // example: // // ^bb0(%arg0): // cond_br %cond, ^bb1, ^bb2 // ^bb1: // br ^exit(%arg0) // ^bb2: // %new_value = ... // br ^exit(%new_value) // ^exit(%arg1): // return %arg1; // // Using liveness information on its own would cause us to place the allocs and // deallocs in the wrong block. This is due to the fact that %new_value will not // be liveOut of its block. Instead, we have to place the alloc for %new_value // in bb0 and its associated dealloc in exit. Using the class // BufferPlacementAliasAnalysis, we will find out that %new_value has a // potential alias %arg1. In order to find the dealloc position we have to find // all potential aliases, iterate over their uses and find the common // post-dominator block. In this block we can safely be sure that %new_value // will die and can use liveness information to determine the exact operation // after which we have to insert the dealloc. Finding the alloc position is // highly similar and non- obvious. Again, we have to consider all potential // aliases and find the common dominator block to place the alloc. // // TODO: // The current implementation does not support loops and the resulting code will // be invalid with respect to program semantics. The only thing that is // currently missing is a high-level loop analysis that allows us to move allocs // and deallocs outside of the loop blocks. Furthermore, it doesn't also accept // functions which return buffers already. // //===----------------------------------------------------------------------===// #include "mlir/Transforms/BufferPlacement.h" #include "mlir/IR/Function.h" #include "mlir/IR/Operation.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/Passes.h" using namespace mlir; namespace { //===----------------------------------------------------------------------===// // BufferPlacementAliasAnalysis //===----------------------------------------------------------------------===// /// A straight-forward alias analysis which ensures that all aliases of all /// values will be determined. This is a requirement for the BufferPlacement /// class since you need to determine safe positions to place alloc and /// deallocs. class BufferPlacementAliasAnalysis { public: using ValueSetT = SmallPtrSet<Value, 16>; public: /// Constructs a new alias analysis using the op provided. BufferPlacementAliasAnalysis(Operation *op) { build(op->getRegions()); } /// Finds all immediate and indirect aliases this value could potentially /// have. Note that the resulting set will also contain the value provided as /// it is an alias of itself. ValueSetT resolve(Value value) const { ValueSetT result; resolveRecursive(value, result); return result; } private: /// Recursively determines alias information for the given value. It stores /// all newly found potential aliases in the given result set. void resolveRecursive(Value value, ValueSetT &result) const { if (!result.insert(value).second) return; auto it = aliases.find(value); if (it == aliases.end()) return; for (Value alias : it->second) resolveRecursive(alias, result); } /// This function constructs a mapping from values to its immediate aliases. /// It iterates over all blocks, gets their predecessors, determines the /// values that will be passed to the corresponding block arguments and /// inserts them into the underlying map. void build(MutableArrayRef<Region> regions) { for (Region ®ion : regions) { for (Block &block : region) { // Iterate over all predecessor and get the mapped values to their // corresponding block arguments values. for (auto it = block.pred_begin(), e = block.pred_end(); it != e; ++it) { unsigned successorIndex = it.getSuccessorIndex(); // Get the terminator and the values that will be passed to our block. auto branchInterface = dyn_cast<BranchOpInterface>((*it)->getTerminator()); if (!branchInterface) continue; // Query the branch op interace to get the successor operands. auto successorOperands = branchInterface.getSuccessorOperands(successorIndex); if (successorOperands.hasValue()) { // Build the actual mapping of values to their immediate aliases. for (auto argPair : llvm::zip(block.getArguments(), successorOperands.getValue())) { aliases[std::get<1>(argPair)].insert(std::get<0>(argPair)); } } } } } } /// Maps values to all immediate aliases this value can have. llvm::DenseMap<Value, ValueSetT> aliases; }; //===----------------------------------------------------------------------===// // BufferPlacementPositions //===----------------------------------------------------------------------===// /// Stores correct alloc and dealloc positions to place dialect-specific alloc /// and dealloc operations. struct BufferPlacementPositions { public: BufferPlacementPositions() : allocPosition(nullptr), deallocPosition(nullptr) {} /// Creates a new positions tuple including alloc and dealloc positions. BufferPlacementPositions(Operation *allocPosition, Operation *deallocPosition) : allocPosition(allocPosition), deallocPosition(deallocPosition) {} /// Returns the alloc position before which the alloc operation has to be /// inserted. Operation *getAllocPosition() const { return allocPosition; } /// Returns the dealloc position after which the dealloc operation has to be /// inserted. Operation *getDeallocPosition() const { return deallocPosition; } private: Operation *allocPosition; Operation *deallocPosition; }; //===----------------------------------------------------------------------===// // BufferPlacementAnalysis //===----------------------------------------------------------------------===// // The main buffer placement analysis used to place allocs and deallocs. class BufferPlacementAnalysis { public: using DeallocSetT = SmallPtrSet<Operation *, 2>; public: BufferPlacementAnalysis(Operation *op) : operation(op), liveness(op), dominators(op), postDominators(op), aliases(op) {} /// Computes the actual positions to place allocs and deallocs for the given /// value. BufferPlacementPositions computeAllocAndDeallocPositions(OpResult result) const { if (result.use_empty()) return BufferPlacementPositions(result.getOwner(), result.getOwner()); // Get all possible aliases. auto possibleValues = aliases.resolve(result); return BufferPlacementPositions(getAllocPosition(result, possibleValues), getDeallocPosition(result, possibleValues)); } /// Finds all associated dealloc nodes for the alloc nodes using alias /// information. DeallocSetT findAssociatedDeallocs(OpResult allocResult) const { DeallocSetT result; auto possibleValues = aliases.resolve(allocResult); for (Value alias : possibleValues) for (Operation *op : alias.getUsers()) { // Check for an existing memory effect interface. auto effectInstance = dyn_cast<MemoryEffectOpInterface>(op); if (!effectInstance) continue; // Check whether the associated value will be freed using the current // operation. SmallVector<MemoryEffects::EffectInstance, 2> effects; effectInstance.getEffectsOnValue(alias, effects); if (llvm::any_of(effects, [=](MemoryEffects::EffectInstance &it) { return isa<MemoryEffects::Free>(it.getEffect()); })) result.insert(op); } return result; } /// Dumps the buffer placement information to the given stream. void print(raw_ostream &os) const { os << "// ---- Buffer Placement -----\n"; for (Region ®ion : operation->getRegions()) for (Block &block : region) for (Operation &operation : block) for (OpResult result : operation.getResults()) { BufferPlacementPositions positions = computeAllocAndDeallocPositions(result); os << "Positions for "; result.print(os); os << "\n Alloc: "; positions.getAllocPosition()->print(os); os << "\n Dealloc: "; positions.getDeallocPosition()->print(os); os << "\n"; } } private: /// Finds a correct placement block to store alloc/dealloc node according to /// the algorithm described at the top of the file. It supports dominator and /// post-dominator analyses via template arguments. template <typename DominatorT> Block * findPlacementBlock(OpResult result, const BufferPlacementAliasAnalysis::ValueSetT &aliases, const DominatorT &doms) const { // Start with the current block the value is defined in. Block *dom = result.getOwner()->getBlock(); // Iterate over all aliases and their uses to find a safe placement block // according to the given dominator information. for (Value alias : aliases) for (Operation *user : alias.getUsers()) { // Move upwards in the dominator tree to find an appropriate // dominator block that takes the current use into account. dom = doms.findNearestCommonDominator(dom, user->getBlock()); } return dom; } /// Finds a correct alloc position according to the algorithm described at /// the top of the file. Operation *getAllocPosition( OpResult result, const BufferPlacementAliasAnalysis::ValueSetT &aliases) const { // Determine the actual block to place the alloc and get liveness // information. Block *placementBlock = findPlacementBlock(result, aliases, dominators); const LivenessBlockInfo *livenessInfo = liveness.getLiveness(placementBlock); // We have to ensure that the alloc will be before the first use of all // aliases of the given value. We first assume that there are no uses in the // placementBlock and that we can safely place the alloc before the // terminator at the end of the block. Operation *startOperation = placementBlock->getTerminator(); // Iterate over all aliases and ensure that the startOperation will point to // the first operation of all potential aliases in the placementBlock. for (Value alias : aliases) { Operation *aliasStartOperation = livenessInfo->getStartOperation(alias); // Check whether the aliasStartOperation lies in the desired block and // whether it is before the current startOperation. If yes, this will be // the new startOperation. if (aliasStartOperation->getBlock() == placementBlock && aliasStartOperation->isBeforeInBlock(startOperation)) startOperation = aliasStartOperation; } // startOperation is the first operation before which we can safely store // the alloc taking all potential aliases into account. return startOperation; } /// Finds a correct dealloc position according to the algorithm described at /// the top of the file. Operation *getDeallocPosition( OpResult result, const BufferPlacementAliasAnalysis::ValueSetT &aliases) const { // Determine the actual block to place the dealloc and get liveness // information. Block *placementBlock = findPlacementBlock(result, aliases, postDominators); const LivenessBlockInfo *livenessInfo = liveness.getLiveness(placementBlock); // We have to ensure that the dealloc will be after the last use of all // aliases of the given value. We first assume that there are no uses in the // placementBlock and that we can safely place the dealloc at the beginning. Operation *endOperation = &placementBlock->front(); // Iterate over all aliases and ensure that the endOperation will point to // the last operation of all potential aliases in the placementBlock. for (Value alias : aliases) { Operation *aliasEndOperation = livenessInfo->getEndOperation(alias, endOperation); // Check whether the aliasEndOperation lies in the desired block and // whether it is behind the current endOperation. If yes, this will be the // new endOperation. if (aliasEndOperation->getBlock() == placementBlock && endOperation->isBeforeInBlock(aliasEndOperation)) endOperation = aliasEndOperation; } // endOperation is the last operation behind which we can safely store the // dealloc taking all potential aliases into account. return endOperation; } /// The operation this transformation was constructed from. Operation *operation; /// The underlying liveness analysis to compute fine grained information about /// alloc and dealloc positions. Liveness liveness; /// The dominator analysis to place allocs in the appropriate blocks. DominanceInfo dominators; /// The post dominator analysis to place deallocs in the appropriate blocks. PostDominanceInfo postDominators; /// The internal alias analysis to ensure that allocs and deallocs take all /// their potential aliases into account. BufferPlacementAliasAnalysis aliases; }; //===----------------------------------------------------------------------===// // BufferPlacementPass //===----------------------------------------------------------------------===// /// The actual buffer placement pass that moves alloc and dealloc nodes into /// the right positions. It uses the algorithm described at the top of the file. struct BufferPlacementPass : mlir::PassWrapper<BufferPlacementPass, FunctionPass> { void runOnFunction() override { // Get required analysis information first. auto &analysis = getAnalysis<BufferPlacementAnalysis>(); // Compute an initial placement of all nodes. llvm::SmallVector<std::pair<OpResult, BufferPlacementPositions>, 16> placements; getFunction().walk([&](MemoryEffectOpInterface op) { // Try to find a single allocation result. SmallVector<MemoryEffects::EffectInstance, 2> effects; op.getEffects(effects); SmallVector<MemoryEffects::EffectInstance, 2> allocateResultEffects; llvm::copy_if(effects, std::back_inserter(allocateResultEffects), [=](MemoryEffects::EffectInstance &it) { Value value = it.getValue(); return isa<MemoryEffects::Allocate>(it.getEffect()) && value && value.isa<OpResult>(); }); // If there is one result only, we will be able to move the allocation and // (possibly existing) deallocation ops. if (allocateResultEffects.size() == 1) { // Insert allocation result. auto allocResult = allocateResultEffects[0].getValue().cast<OpResult>(); placements.emplace_back( allocResult, analysis.computeAllocAndDeallocPositions(allocResult)); } }); // Move alloc (and dealloc - if any) nodes into the right places and insert // dealloc nodes if necessary. for (auto &entry : placements) { // Find already associated dealloc nodes. OpResult alloc = entry.first; auto deallocs = analysis.findAssociatedDeallocs(alloc); if (deallocs.size() > 1) { emitError(alloc.getLoc(), "not supported number of associated dealloc operations"); return; } // Move alloc node to the right place. BufferPlacementPositions &positions = entry.second; Operation *allocOperation = alloc.getOwner(); allocOperation->moveBefore(positions.getAllocPosition()); // If there is an existing dealloc, move it to the right place. Operation *nextOp = positions.getDeallocPosition()->getNextNode(); assert(nextOp && "Invalid Dealloc operation position"); if (deallocs.size()) { (*deallocs.begin())->moveBefore(nextOp); } else { // If there is no dealloc node, insert one in the right place. OpBuilder builder(nextOp); builder.create<DeallocOp>(allocOperation->getLoc(), alloc); } } }; }; } // end anonymous namespace //===----------------------------------------------------------------------===// // BufferAssignmentPlacer //===----------------------------------------------------------------------===// /// Creates a new assignment placer. BufferAssignmentPlacer::BufferAssignmentPlacer(Operation *op) : operation(op) {} /// Computes the actual position to place allocs for the given value. OpBuilder::InsertPoint BufferAssignmentPlacer::computeAllocPosition(OpResult result) { Operation *owner = result.getOwner(); return OpBuilder::InsertPoint(owner->getBlock(), Block::iterator(owner)); } //===----------------------------------------------------------------------===// // FunctionAndBlockSignatureConverter //===----------------------------------------------------------------------===// // Performs the actual signature rewriting step. LogicalResult FunctionAndBlockSignatureConverter::matchAndRewrite( FuncOp funcOp, ArrayRef<Value> operands, ConversionPatternRewriter &rewriter) const { if (!converter) { funcOp.emitError("The type converter has not been defined for " "FunctionAndBlockSignatureConverter"); return failure(); } auto funcType = funcOp.getType(); TypeRange resultTypes = funcType.getResults(); if (llvm::any_of(resultTypes, [](Type type) { return type.isa<MemRefType>(); })) return funcOp.emitError("BufferAssignmentPlacer doesn't currently support " "functions which return memref typed values"); // Convert function arguments using the provided TypeConverter. TypeConverter::SignatureConversion conversion(funcType.getNumInputs()); for (auto argType : llvm::enumerate(funcType.getInputs())) conversion.addInputs(argType.index(), converter->convertType(argType.value())); // Adding a function argument for each function result which is going to be a // memref type after type conversion. SmallVector<Type, 2> newResultTypes; newResultTypes.reserve(funcOp.getNumResults()); for (Type resType : resultTypes) { Type convertedType = converter->convertType(resType); // If the result type is memref after the type conversion, a new argument // should be appended to the function arguments list for this result. // Otherwise, it remains unchanged as a function result. if (convertedType.isa<MemRefType>()) conversion.addInputs(convertedType); else newResultTypes.push_back(convertedType); } // Update the signature of the function. rewriter.updateRootInPlace(funcOp, [&] { funcOp.setType(rewriter.getFunctionType(conversion.getConvertedTypes(), newResultTypes)); rewriter.applySignatureConversion(&funcOp.getBody(), conversion); }); return success(); } //===----------------------------------------------------------------------===// // BufferAssignmentTypeConverter //===----------------------------------------------------------------------===// /// Registers conversions into BufferAssignmentTypeConverter BufferAssignmentTypeConverter::BufferAssignmentTypeConverter() { // Keep all types unchanged. addConversion([](Type type) { return type; }); // A type conversion that converts ranked-tensor type to memref type. addConversion([](RankedTensorType type) { return (Type)MemRefType::get(type.getShape(), type.getElementType()); }); } //===----------------------------------------------------------------------===// // BufferPlacementPass construction //===----------------------------------------------------------------------===// std::unique_ptr<Pass> mlir::createBufferPlacementPass() { return std::make_unique<BufferPlacementPass>(); }