• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //===- CopyRemoval.cpp - Removing the redundant copies --------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "mlir/Interfaces/CopyOpInterface.h"
10 #include "mlir/Interfaces/SideEffectInterfaces.h"
11 #include "mlir/Pass/Pass.h"
12 #include "mlir/Transforms/Passes.h"
13 
14 using namespace mlir;
15 using namespace MemoryEffects;
16 
17 namespace {
18 
19 //===----------------------------------------------------------------------===//
20 // CopyRemovalPass
21 //===----------------------------------------------------------------------===//
22 
23 /// This pass removes the redundant Copy operations. Additionally, it
24 /// removes the leftover definition and deallocation operations by erasing the
25 /// copy operation.
26 class CopyRemovalPass : public PassWrapper<CopyRemovalPass, OperationPass<>> {
27 public:
runOnOperation()28   void runOnOperation() override {
29     getOperation()->walk([&](CopyOpInterface copyOp) {
30       reuseCopySourceAsTarget(copyOp);
31       reuseCopyTargetAsSource(copyOp);
32     });
33     for (std::pair<Value, Value> &pair : replaceList)
34       pair.first.replaceAllUsesWith(pair.second);
35     for (Operation *op : eraseList)
36       op->erase();
37   }
38 
39 private:
40   /// List of operations that need to be removed.
41   llvm::SmallPtrSet<Operation *, 4> eraseList;
42 
43   /// List of values that need to be replaced with their counterparts.
44   llvm::SmallDenseSet<std::pair<Value, Value>, 4> replaceList;
45 
46   /// Returns the allocation operation for `value` in `block` if it exists.
47   /// nullptr otherwise.
getAllocationOpInBlock(Value value,Block * block)48   Operation *getAllocationOpInBlock(Value value, Block *block) {
49     assert(block && "Block cannot be null");
50     Operation *op = value.getDefiningOp();
51     if (op && op->getBlock() == block) {
52       auto effects = dyn_cast<MemoryEffectOpInterface>(op);
53       if (effects && effects.hasEffect<Allocate>())
54         return op;
55     }
56     return nullptr;
57   }
58 
59   /// Returns the deallocation operation for `value` in `block` if it exists.
60   /// nullptr otherwise.
getDeallocationOpInBlock(Value value,Block * block)61   Operation *getDeallocationOpInBlock(Value value, Block *block) {
62     assert(block && "Block cannot be null");
63     auto valueUsers = value.getUsers();
64     auto it = llvm::find_if(valueUsers, [&](Operation *op) {
65       auto effects = dyn_cast<MemoryEffectOpInterface>(op);
66       return effects && op->getBlock() == block && effects.hasEffect<Free>();
67     });
68     return (it == valueUsers.end() ? nullptr : *it);
69   }
70 
71   /// Returns true if an operation between start and end operations has memory
72   /// effect.
hasMemoryEffectOpBetween(Operation * start,Operation * end)73   bool hasMemoryEffectOpBetween(Operation *start, Operation *end) {
74     assert((start || end) && "Start and end operations cannot be null");
75     assert(start->getBlock() == end->getBlock() &&
76            "Start and end operations should be in the same block.");
77     Operation *op = start->getNextNode();
78     while (op->isBeforeInBlock(end)) {
79       if (isa<MemoryEffectOpInterface>(op))
80         return true;
81       op = op->getNextNode();
82     }
83     return false;
84   };
85 
86   /// Returns true if `val` value has at least a user between `start` and
87   /// `end` operations.
hasUsersBetween(Value val,Operation * start,Operation * end)88   bool hasUsersBetween(Value val, Operation *start, Operation *end) {
89     assert((start || end) && "Start and end operations cannot be null");
90     Block *block = start->getBlock();
91     assert(block == end->getBlock() &&
92            "Start and end operations should be in the same block.");
93     return llvm::any_of(val.getUsers(), [&](Operation *op) {
94       return op->getBlock() == block && start->isBeforeInBlock(op) &&
95              op->isBeforeInBlock(end);
96     });
97   };
98 
areOpsInTheSameBlock(ArrayRef<Operation * > operations)99   bool areOpsInTheSameBlock(ArrayRef<Operation *> operations) {
100     assert(!operations.empty() &&
101            "The operations list should contain at least a single operation");
102     Block *block = operations.front()->getBlock();
103     return llvm::none_of(
104         operations, [&](Operation *op) { return block != op->getBlock(); });
105   }
106 
107   /// Input:
108   /// func(){
109   ///   %from = alloc()
110   ///   write_to(%from)
111   ///   %to = alloc()
112   ///   copy(%from,%to)
113   ///   dealloc(%from)
114   ///   return %to
115   /// }
116   ///
117   /// Output:
118   /// func(){
119   ///   %from = alloc()
120   ///   write_to(%from)
121   ///   return %from
122   /// }
123   /// Constraints:
124   /// 1) %to, copy and dealloc must all be defined and lie in the same block.
125   /// 2) This transformation cannot be applied if there is a single user/alias
126   /// of `to` value between the defining operation of `to` and the copy
127   /// operation.
128   /// 3) This transformation cannot be applied if there is a single user/alias
129   /// of `from` value between the copy operation and the deallocation of `from`.
130   /// TODO: Alias analysis is not available at the moment. Currently, we check
131   /// if there are any operations with memory effects between copy and
132   /// deallocation operations.
reuseCopySourceAsTarget(CopyOpInterface copyOp)133   void reuseCopySourceAsTarget(CopyOpInterface copyOp) {
134     if (eraseList.count(copyOp))
135       return;
136 
137     Value from = copyOp.getSource();
138     Value to = copyOp.getTarget();
139 
140     Operation *copy = copyOp.getOperation();
141     Block *copyBlock = copy->getBlock();
142     Operation *fromDefiningOp = from.getDefiningOp();
143     Operation *fromFreeingOp = getDeallocationOpInBlock(from, copyBlock);
144     Operation *toDefiningOp = getAllocationOpInBlock(to, copyBlock);
145     if (!fromDefiningOp || !fromFreeingOp || !toDefiningOp ||
146         !areOpsInTheSameBlock({fromFreeingOp, toDefiningOp, copy}) ||
147         hasUsersBetween(to, toDefiningOp, copy) ||
148         hasUsersBetween(from, copy, fromFreeingOp) ||
149         hasMemoryEffectOpBetween(copy, fromFreeingOp))
150       return;
151 
152     replaceList.insert({to, from});
153     eraseList.insert(copy);
154     eraseList.insert(toDefiningOp);
155     eraseList.insert(fromFreeingOp);
156   }
157 
158   /// Input:
159   /// func(){
160   ///   %to = alloc()
161   ///   %from = alloc()
162   ///   write_to(%from)
163   ///   copy(%from,%to)
164   ///   dealloc(%from)
165   ///   return %to
166   /// }
167   ///
168   /// Output:
169   /// func(){
170   ///   %to = alloc()
171   ///   write_to(%to)
172   ///   return %to
173   /// }
174   /// Constraints:
175   /// 1) %from, copy and dealloc must all be defined and lie in the same block.
176   /// 2) This transformation cannot be applied if there is a single user/alias
177   /// of `to` value between the defining operation of `from` and the copy
178   /// operation.
179   /// 3) This transformation cannot be applied if there is a single user/alias
180   /// of `from` value between the copy operation and the deallocation of `from`.
181   /// TODO: Alias analysis is not available at the moment. Currently, we check
182   /// if there are any operations with memory effects between copy and
183   /// deallocation operations.
reuseCopyTargetAsSource(CopyOpInterface copyOp)184   void reuseCopyTargetAsSource(CopyOpInterface copyOp) {
185     if (eraseList.count(copyOp))
186       return;
187 
188     Value from = copyOp.getSource();
189     Value to = copyOp.getTarget();
190 
191     Operation *copy = copyOp.getOperation();
192     Block *copyBlock = copy->getBlock();
193     Operation *fromDefiningOp = getAllocationOpInBlock(from, copyBlock);
194     Operation *fromFreeingOp = getDeallocationOpInBlock(from, copyBlock);
195     if (!fromDefiningOp || !fromFreeingOp ||
196         !areOpsInTheSameBlock({fromFreeingOp, fromDefiningOp, copy}) ||
197         hasUsersBetween(to, fromDefiningOp, copy) ||
198         hasUsersBetween(from, copy, fromFreeingOp) ||
199         hasMemoryEffectOpBetween(copy, fromFreeingOp))
200       return;
201 
202     replaceList.insert({from, to});
203     eraseList.insert(copy);
204     eraseList.insert(fromDefiningOp);
205     eraseList.insert(fromFreeingOp);
206   }
207 };
208 
209 } // end anonymous namespace
210 
211 //===----------------------------------------------------------------------===//
212 // CopyRemovalPass construction
213 //===----------------------------------------------------------------------===//
214 
createCopyRemovalPass()215 std::unique_ptr<Pass> mlir::createCopyRemovalPass() {
216   return std::make_unique<CopyRemovalPass>();
217 }
218