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