//===- CopyRemoval.cpp - Removing the redundant copies --------------------===// // // 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/Interfaces/CopyOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/Passes.h" using namespace mlir; using namespace MemoryEffects; namespace { //===----------------------------------------------------------------------===// // CopyRemovalPass //===----------------------------------------------------------------------===// /// This pass removes the redundant Copy operations. Additionally, it /// removes the leftover definition and deallocation operations by erasing the /// copy operation. class CopyRemovalPass : public PassWrapper> { public: void runOnOperation() override { getOperation()->walk([&](CopyOpInterface copyOp) { reuseCopySourceAsTarget(copyOp); reuseCopyTargetAsSource(copyOp); }); for (std::pair &pair : replaceList) pair.first.replaceAllUsesWith(pair.second); for (Operation *op : eraseList) op->erase(); } private: /// List of operations that need to be removed. llvm::SmallPtrSet eraseList; /// List of values that need to be replaced with their counterparts. llvm::SmallDenseSet, 4> replaceList; /// Returns the allocation operation for `value` in `block` if it exists. /// nullptr otherwise. Operation *getAllocationOpInBlock(Value value, Block *block) { assert(block && "Block cannot be null"); Operation *op = value.getDefiningOp(); if (op && op->getBlock() == block) { auto effects = dyn_cast(op); if (effects && effects.hasEffect()) return op; } return nullptr; } /// Returns the deallocation operation for `value` in `block` if it exists. /// nullptr otherwise. Operation *getDeallocationOpInBlock(Value value, Block *block) { assert(block && "Block cannot be null"); auto valueUsers = value.getUsers(); auto it = llvm::find_if(valueUsers, [&](Operation *op) { auto effects = dyn_cast(op); return effects && op->getBlock() == block && effects.hasEffect(); }); return (it == valueUsers.end() ? nullptr : *it); } /// Returns true if an operation between start and end operations has memory /// effect. bool hasMemoryEffectOpBetween(Operation *start, Operation *end) { assert((start || end) && "Start and end operations cannot be null"); assert(start->getBlock() == end->getBlock() && "Start and end operations should be in the same block."); Operation *op = start->getNextNode(); while (op->isBeforeInBlock(end)) { if (isa(op)) return true; op = op->getNextNode(); } return false; }; /// Returns true if `val` value has at least a user between `start` and /// `end` operations. bool hasUsersBetween(Value val, Operation *start, Operation *end) { assert((start || end) && "Start and end operations cannot be null"); Block *block = start->getBlock(); assert(block == end->getBlock() && "Start and end operations should be in the same block."); return llvm::any_of(val.getUsers(), [&](Operation *op) { return op->getBlock() == block && start->isBeforeInBlock(op) && op->isBeforeInBlock(end); }); }; bool areOpsInTheSameBlock(ArrayRef operations) { assert(!operations.empty() && "The operations list should contain at least a single operation"); Block *block = operations.front()->getBlock(); return llvm::none_of( operations, [&](Operation *op) { return block != op->getBlock(); }); } /// Input: /// func(){ /// %from = alloc() /// write_to(%from) /// %to = alloc() /// copy(%from,%to) /// dealloc(%from) /// return %to /// } /// /// Output: /// func(){ /// %from = alloc() /// write_to(%from) /// return %from /// } /// Constraints: /// 1) %to, copy and dealloc must all be defined and lie in the same block. /// 2) This transformation cannot be applied if there is a single user/alias /// of `to` value between the defining operation of `to` and the copy /// operation. /// 3) This transformation cannot be applied if there is a single user/alias /// of `from` value between the copy operation and the deallocation of `from`. /// TODO: Alias analysis is not available at the moment. Currently, we check /// if there are any operations with memory effects between copy and /// deallocation operations. void reuseCopySourceAsTarget(CopyOpInterface copyOp) { if (eraseList.count(copyOp)) return; Value from = copyOp.getSource(); Value to = copyOp.getTarget(); Operation *copy = copyOp.getOperation(); Block *copyBlock = copy->getBlock(); Operation *fromDefiningOp = from.getDefiningOp(); Operation *fromFreeingOp = getDeallocationOpInBlock(from, copyBlock); Operation *toDefiningOp = getAllocationOpInBlock(to, copyBlock); if (!fromDefiningOp || !fromFreeingOp || !toDefiningOp || !areOpsInTheSameBlock({fromFreeingOp, toDefiningOp, copy}) || hasUsersBetween(to, toDefiningOp, copy) || hasUsersBetween(from, copy, fromFreeingOp) || hasMemoryEffectOpBetween(copy, fromFreeingOp)) return; replaceList.insert({to, from}); eraseList.insert(copy); eraseList.insert(toDefiningOp); eraseList.insert(fromFreeingOp); } /// Input: /// func(){ /// %to = alloc() /// %from = alloc() /// write_to(%from) /// copy(%from,%to) /// dealloc(%from) /// return %to /// } /// /// Output: /// func(){ /// %to = alloc() /// write_to(%to) /// return %to /// } /// Constraints: /// 1) %from, copy and dealloc must all be defined and lie in the same block. /// 2) This transformation cannot be applied if there is a single user/alias /// of `to` value between the defining operation of `from` and the copy /// operation. /// 3) This transformation cannot be applied if there is a single user/alias /// of `from` value between the copy operation and the deallocation of `from`. /// TODO: Alias analysis is not available at the moment. Currently, we check /// if there are any operations with memory effects between copy and /// deallocation operations. void reuseCopyTargetAsSource(CopyOpInterface copyOp) { if (eraseList.count(copyOp)) return; Value from = copyOp.getSource(); Value to = copyOp.getTarget(); Operation *copy = copyOp.getOperation(); Block *copyBlock = copy->getBlock(); Operation *fromDefiningOp = getAllocationOpInBlock(from, copyBlock); Operation *fromFreeingOp = getDeallocationOpInBlock(from, copyBlock); if (!fromDefiningOp || !fromFreeingOp || !areOpsInTheSameBlock({fromFreeingOp, fromDefiningOp, copy}) || hasUsersBetween(to, fromDefiningOp, copy) || hasUsersBetween(from, copy, fromFreeingOp) || hasMemoryEffectOpBetween(copy, fromFreeingOp)) return; replaceList.insert({from, to}); eraseList.insert(copy); eraseList.insert(fromDefiningOp); eraseList.insert(fromFreeingOp); } }; } // end anonymous namespace //===----------------------------------------------------------------------===// // CopyRemovalPass construction //===----------------------------------------------------------------------===// std::unique_ptr mlir::createCopyRemovalPass() { return std::make_unique(); }