Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
RemoveControlFlow.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
13#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
14#include "mlir/IR/Dominance.h"
15#include "mlir/IR/Matchers.h"
16#include "mlir/Pass/Pass.h"
17#include "llvm/ADT/PostOrderIterator.h"
18#include "llvm/Support/Debug.h"
19
20#define DEBUG_TYPE "llhd-remove-control-flow"
21
22namespace circt {
23namespace llhd {
24#define GEN_PASS_DEF_REMOVECONTROLFLOWPASS
25#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
26} // namespace llhd
27} // namespace circt
28
29using namespace mlir;
30using namespace circt;
31using namespace llhd;
32
33//===----------------------------------------------------------------------===//
34// Utilities
35//===----------------------------------------------------------------------===//
36
37namespace {
38/// A helper struct that tracks a boolean condition as either a constant false,
39/// constant true, or an SSA value.
40struct Condition {
41 Condition() {}
42 Condition(Value value) : pair(value, 0) {
43 if (value) {
44 if (matchPattern(value, m_One()))
45 *this = Condition(true);
46 if (matchPattern(value, m_Zero()))
47 *this = Condition(false);
48 }
49 }
50 Condition(bool konst) : pair(nullptr, konst ? 1 : 2) {}
51
52 explicit operator bool() const {
53 return pair.getPointer() != nullptr || pair.getInt() != 0;
54 }
55
56 bool isTrue() const { return !pair.getPointer() && pair.getInt() == 1; }
57 bool isFalse() const { return !pair.getPointer() && pair.getInt() == 2; }
58 Value getValue() const { return pair.getPointer(); }
59
60 /// Turn this condition into an SSA value, creating an `hw.constant` if the
61 /// condition is a constant.
62 Value materialize(OpBuilder &builder, Location loc) const {
63 if (isTrue())
64 return hw::ConstantOp::create(builder, loc, APInt(1, 1));
65 if (isFalse())
66 return hw::ConstantOp::create(builder, loc, APInt(1, 0));
67 return pair.getPointer();
68 }
69
70 Condition orWith(Condition other, OpBuilder &builder) const {
71 if (isTrue() || other.isTrue())
72 return true;
73 if (isFalse())
74 return other;
75 if (other.isFalse())
76 return *this;
77 return builder.createOrFold<comb::OrOp>(getValue().getLoc(), getValue(),
78 other.getValue());
79 }
80
81 Condition andWith(Condition other, OpBuilder &builder) const {
82 if (isFalse() || other.isFalse())
83 return false;
84 if (isTrue())
85 return other;
86 if (other.isTrue())
87 return *this;
88 return builder.createOrFold<comb::AndOp>(getValue().getLoc(), getValue(),
89 other.getValue());
90 }
91
92 Condition inverted(OpBuilder &builder) const {
93 if (isTrue())
94 return false;
95 if (isFalse())
96 return true;
97 return comb::createOrFoldNot(getValue().getLoc(), getValue(), builder);
98 }
99
100private:
101 llvm::PointerIntPair<Value, 2> pair;
102};
103} // namespace
104
105/// Compute the branch decisions that cause control to flow from the dominator
106/// to the target block.
107///
108/// TODO: This eagerly aggregates all control flow decisions. It may be more
109/// efficient to first determine which blocks lie in between dominator and
110/// target, and then only check that we are not taking decisions that cause
111/// control flow to *leave* that set of blocks.
113 OpBuilder &builder, Block *dominator, Block *target,
114 SmallDenseMap<std::pair<Block *, Block *>, Condition> &decisions) {
115 if (auto decision = decisions.lookup({dominator, target}))
116 return decision;
117
118 SmallPtrSet<Block *, 8> visitedBlocks;
119 visitedBlocks.insert(dominator); // stop at the dominator
120 if (auto &decision = decisions[{dominator, dominator}]; !decision)
121 decision = Condition(true);
122
123 // Traverse the blocks in inverse post order. This ensures that we are
124 // visiting all of a block's predecessors before we visit the block itself.
125 // This allows us to first compute the decision leading control flow to each
126 // of the predecessors, such that the current block can then just combine the
127 // predecessor decisions.
128 for (auto *block : llvm::inverse_post_order_ext(target, visitedBlocks)) {
129 auto merged = Condition(false);
130 for (auto *pred : block->getPredecessors()) {
131 auto predDecision = decisions.lookup({dominator, pred});
132 assert(predDecision);
133 if (pred->getTerminator()->getNumSuccessors() != 1) {
134 auto condBr = cast<cf::CondBranchOp>(pred->getTerminator());
135 if (condBr.getTrueDest() == condBr.getFalseDest()) {
136 merged = merged.orWith(predDecision, builder);
137 } else {
138 auto cond = Condition(condBr.getCondition());
139 if (condBr.getFalseDest() == block)
140 cond = cond.inverted(builder);
141 merged = merged.orWith(cond.andWith(predDecision, builder), builder);
142 }
143 } else {
144 merged = merged.orWith(predDecision, builder);
145 }
146 }
147 assert(merged);
148 decisions.insert({{dominator, block}, merged});
149 }
150
151 return decisions.lookup({dominator, target});
152}
153
154//===----------------------------------------------------------------------===//
155// Control Flow Removal
156//===----------------------------------------------------------------------===//
157
158namespace {
159/// The main helper struct implementing control flow removal for a region.
160struct CFRemover {
161 CFRemover(Region &region) : region(region) {}
162 void run();
163
164 /// The region within which we are removing control flow.
165 Region &region;
166 /// The blocks in the region, sorted such that a block's predecessors appear
167 /// in the list before the block itself.
168 SmallVector<Block *> sortedBlocks;
169 /// The dominance information for the region.
170 DominanceInfo domInfo;
171};
172} // namespace
173
174void CFRemover::run() {
175 LLVM_DEBUG(llvm::dbgs() << "Removing control flow in " << region.getLoc()
176 << "\n");
177
178 // Establish a topological order of the blocks in the region. Give up if we
179 // detect a control flow cycle. Also take note of all YieldOps, such that we
180 // can combine them into a single yield block later.
181 SmallVector<YieldOp, 2> yieldOps;
182 SmallPtrSet<Block *, 8> visitedBlocks, ipoSet;
183 for (auto &block : region) {
184 for (auto *ipoBlock : llvm::inverse_post_order_ext(&block, ipoSet)) {
185 if (!llvm::all_of(ipoBlock->getPredecessors(), [&](auto *pred) {
186 return visitedBlocks.contains(pred);
187 })) {
188 LLVM_DEBUG(llvm::dbgs() << "- Loop detected, giving up\n");
189 return;
190 }
191 visitedBlocks.insert(ipoBlock);
192 sortedBlocks.push_back(ipoBlock);
193 }
194
195 // Give up if there are any side-effecting ops in the region.
196 for (auto &op : block) {
197 if (!isMemoryEffectFree(&op)) {
198 LLVM_DEBUG(llvm::dbgs() << "- Has side effects, giving up\n");
199 return;
200 }
201 }
202
203 // Check that we know what to do with all terminators.
204 if (!isa<YieldOp, cf::BranchOp, cf::CondBranchOp>(block.getTerminator())) {
205 LLVM_DEBUG(llvm::dbgs()
206 << "- Has unsupported terminator "
207 << block.getTerminator()->getName() << ", giving up\n");
208 return;
209 }
210
211 // Keep track of yield ops.
212 if (auto yieldOp = dyn_cast<YieldOp>(block.getTerminator()))
213 yieldOps.push_back(yieldOp);
214 }
215
216 // If there are multiple yield ops, factor them out into a single yield block.
217 auto yieldOp = yieldOps[0];
218 if (yieldOps.size() > 1) {
219 LLVM_DEBUG(llvm::dbgs() << "- Creating single yield block\n");
220 OpBuilder builder(region.getContext());
221 SmallVector<Location> locs(yieldOps[0].getNumOperands(), region.getLoc());
222 auto *yieldBlock = builder.createBlock(&region, region.end(),
223 yieldOps[0].getOperandTypes(), locs);
224 sortedBlocks.push_back(yieldBlock);
225 yieldOp =
226 YieldOp::create(builder, region.getLoc(), yieldBlock->getArguments());
227 for (auto yieldOp : yieldOps) {
228 builder.setInsertionPoint(yieldOp);
229 cf::BranchOp::create(builder, yieldOp.getLoc(), yieldBlock,
230 yieldOp.getOperands());
231 yieldOp.erase();
232 }
233 }
234
235 // Compute the dominance info for this region.
236 domInfo = DominanceInfo(region.getParentOp());
237
238 // Move operations into the entry block, replacing block arguments with
239 // multiplexers as we go. The block order guarantees that we visit a block's
240 // predecessors before we visit the block itself.
241 SmallDenseMap<std::pair<Block *, Block *>, Condition> decisionCache;
242 auto *entryBlock = sortedBlocks.front();
243 for (auto *block : sortedBlocks) {
244 if (!domInfo.isReachableFromEntry(block))
245 continue;
246 LLVM_DEBUG({
247 llvm::dbgs() << "- Merging block ";
248 block->printAsOperand(llvm::dbgs());
249 llvm::dbgs() << "\n";
250 });
251
252 // Find the nearest common dominator block of all predecessors. Any block
253 // arguments reaching the current block will only depend on control flow
254 // decisions between this dominator block and the current block.
255 auto *domBlock = block;
256 for (auto *pred : block->getPredecessors())
257 if (domInfo.isReachableFromEntry(pred))
258 domBlock = domInfo.findNearestCommonDominator(domBlock, pred);
259 LLVM_DEBUG({
260 llvm::dbgs() << " - Common dominator: ";
261 domBlock->printAsOperand(llvm::dbgs());
262 llvm::dbgs() << "\n";
263 });
264
265 // Convert the block arguments into multiplexers.
266 OpBuilder builder(entryBlock->getTerminator());
267 SmallVector<Value> mergedArgs;
268 SmallPtrSet<Block *, 4> seenPreds;
269 for (auto *pred : block->getPredecessors()) {
270 // A block may be listed multiple times in the predecessors.
271 if (!seenPreds.insert(pred).second)
272 continue;
273
274 // Only consider values coming from reachable predecessors.
275 if (!domInfo.isReachableFromEntry(pred))
276 continue;
277
278 // Helper function to create a multiplexer between the current
279 // `mergedArgs` and a new set of `args`, where the new args are picked if
280 // `cond` is true and control flows from `domBlock` to `pred`. The
281 // condition may be null, in which case the mux will directly use the
282 // branch decisions that lead from `domBlock` to `pred`.
283 auto mergeArgs = [&](ValueRange args, Condition cond, bool invCond) {
284 if (mergedArgs.empty()) {
285 mergedArgs = args;
286 return;
287 }
289 builder, domBlock, pred, decisionCache);
290 if (cond) {
291 if (invCond)
292 cond = cond.inverted(builder);
293 decision = decision.andWith(cond, builder);
294 }
295 for (auto [mergedArg, arg] : llvm::zip(mergedArgs, args)) {
296 if (decision.isTrue())
297 mergedArg = arg;
298 else if (decision.isFalse())
299 continue;
300 else
301 mergedArg = builder.createOrFold<comb::MuxOp>(
302 arg.getLoc(), decision.materialize(builder, arg.getLoc()), arg,
303 mergedArg);
304 }
305 };
306
307 // Handle the different terminators that we support.
308 if (auto condBrOp = dyn_cast<cf::CondBranchOp>(pred->getTerminator())) {
309 if (condBrOp.getTrueDest() == condBrOp.getFalseDest()) {
310 // Both destinations lead to the current block. Insert a mux to
311 // collapse the destination operands and then treat this as an
312 // unconditional branch to the current block.
313 LLVM_DEBUG(llvm::dbgs() << " - Both from " << condBrOp << "\n");
314 SmallVector<Value> mergedOperands;
315 mergedOperands.reserve(block->getNumArguments());
316 for (auto [trueArg, falseArg] :
317 llvm::zip(condBrOp.getTrueDestOperands(),
318 condBrOp.getFalseDestOperands())) {
319 mergedOperands.push_back(builder.createOrFold<comb::MuxOp>(
320 trueArg.getLoc(), condBrOp.getCondition(), trueArg, falseArg));
321 }
322 mergeArgs(mergedOperands, Value{}, false);
323 } else if (condBrOp.getTrueDest() == block) {
324 // The branch leads to the current block if the condition is true.
325 LLVM_DEBUG(llvm::dbgs() << " - True from " << condBrOp << "\n");
326 mergeArgs(condBrOp.getTrueDestOperands(), condBrOp.getCondition(),
327 false);
328 } else {
329 // The branch leads to the current block if the condition is false.
330 LLVM_DEBUG(llvm::dbgs() << " - False from " << condBrOp << "\n");
331 mergeArgs(condBrOp.getFalseDestOperands(), condBrOp.getCondition(),
332 true);
333 }
334 } else {
335 auto brOp = cast<cf::BranchOp>(pred->getTerminator());
336 LLVM_DEBUG(llvm::dbgs() << " - From " << brOp << "\n");
337 mergeArgs(brOp.getDestOperands(), Value{}, false);
338 }
339 }
340 for (auto [blockArg, mergedArg] :
341 llvm::zip(block->getArguments(), mergedArgs))
342 blockArg.replaceAllUsesWith(mergedArg);
343
344 // Move all ops except for the terminator into the entry block.
345 if (block != entryBlock)
346 entryBlock->getOperations().splice(--entryBlock->end(),
347 block->getOperations(), block->begin(),
348 --block->end());
349 }
350
351 // Move the yield op into the entry block, replacing the original terminator.
352 if (yieldOp != entryBlock->getTerminator()) {
353 yieldOp->moveBefore(entryBlock->getTerminator());
354 entryBlock->getTerminator()->erase();
355 }
356
357 // Remove all blocks except for the entry block. We first clear all operations
358 // in the blocks such that the blocks have no more uses in branch ops. Then we
359 // remove the blocks themselves in a second pass.
360 for (auto *block : sortedBlocks)
361 if (block != entryBlock)
362 block->clear();
363 for (auto *block : sortedBlocks)
364 if (block != entryBlock)
365 block->erase();
366
367 return;
368}
369
370//===----------------------------------------------------------------------===//
371// Pass Infrastructure
372//===----------------------------------------------------------------------===//
373
374namespace {
375struct RemoveControlFlowPass
376 : public llhd::impl::RemoveControlFlowPassBase<RemoveControlFlowPass> {
377 void runOnOperation() override;
378};
379} // namespace
380
381void RemoveControlFlowPass::runOnOperation() {
382 for (auto op : getOperation().getOps<CombinationalOp>())
383 CFRemover(op.getBody()).run();
384}
assert(baseType &&"element must be base type")
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static Condition getBranchDecisionsFromDominatorToTarget(OpBuilder &builder, Block *dominator, Block *target, SmallDenseMap< std::pair< Block *, Block * >, Condition > &decisions)
Compute the branch decisions that cause control to flow from the dominator to the target block.
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121