CIRCT 21.0.0git
Loading...
Searching...
No Matches
HoistSignals.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
11#include "mlir/Analysis/Liveness.h"
12#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
13#include "llvm/Support/Debug.h"
14
15#define DEBUG_TYPE "llhd-hoist-signals"
16
17namespace circt {
18namespace llhd {
19#define GEN_PASS_DEF_HOISTSIGNALSPASS
20#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
21} // namespace llhd
22} // namespace circt
23
24using namespace mlir;
25using namespace circt;
26using namespace llhd;
27
28//===----------------------------------------------------------------------===//
29// Probe Hoisting
30//===----------------------------------------------------------------------===//
31
32namespace {
33/// The struct performing the hoisting in a single region.
34struct Hoister {
35 Hoister(Region &region) : region(region) {}
36 void hoist();
37
38 void findValuesLiveAcrossWait(Liveness &liveness);
39 void hoistProbes();
40
41 /// The region we are hoisting ops out of.
42 Region &region;
43
44 /// The set of values that are alive across wait ops themselves, or that have
45 /// transitive users that are live across wait ops.
46 DenseSet<Value> liveAcrossWait;
47
48 /// A lookup table of probes we have already hoisted, for deduplication.
49 DenseMap<Value, PrbOp> hoistedProbes;
50};
51} // namespace
52
53void Hoister::hoist() {
54 Liveness liveness(region.getParentOp());
55 findValuesLiveAcrossWait(liveness);
56 hoistProbes();
57}
58
59/// Find all values in the region that are alive across `llhd.wait` operations,
60/// or that have transitive uses that are alive across waits. We can only hoist
61/// probes that do not feed data flow graphs that are alive across such wait
62/// ops. Since control flow edges in `cf.br` and `cf.cond_br` ops are
63/// side-effect free, we have no guarantee that moving a probe out of a process
64/// could potentially cause other ops to become eligible for a move out of the
65/// process. Therefore, if such ops are moved outside of the process, they are
66/// effectively moved across the waits and thus sample their operands at
67/// different points in time. Only values that are explicitly carried across
68/// `llhd.wait`, where the LLHD dialect has control over the control flow
69/// semantics, may have probes in their fan-in cone hoisted out.
70void Hoister::findValuesLiveAcrossWait(Liveness &liveness) {
71 // First find all values that are live across `llhd.wait` operations. We are
72 // only interested in values defined in the current region.
73 SmallVector<Value> worklist;
74 for (auto &block : region)
75 if (isa<WaitOp>(block.getTerminator()))
76 for (auto value : liveness.getLiveOut(&block))
77 if (value.getParentRegion() == &region)
78 if (liveAcrossWait.insert(value).second)
79 worklist.push_back(value);
80
81 // Propagate liveness information along the use-def chain and across control
82 // flow. This will allow us to check `liveAcrossWait` to know if a value
83 // escapes across a wait along its use-def chain that isn't an explicit
84 // successor operand of the wait op.
85 while (!worklist.empty()) {
86 auto value = worklist.pop_back_val();
87 if (auto *defOp = value.getDefiningOp()) {
88 for (auto operand : defOp->getOperands())
89 if (operand.getParentRegion() == &region)
90 if (liveAcrossWait.insert(operand).second)
91 worklist.push_back(operand);
92 } else {
93 auto blockArg = cast<BlockArgument>(value);
94 for (auto &use : blockArg.getOwner()->getUses()) {
95 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
96 if (!branch)
97 continue;
98 auto operand = branch.getSuccessorOperands(
99 use.getOperandNumber())[blockArg.getArgNumber()];
100 if (operand.getParentRegion() == &region)
101 if (liveAcrossWait.insert(operand).second)
102 worklist.push_back(operand);
103 }
104 }
105 }
106
107 LLVM_DEBUG(llvm::dbgs() << liveAcrossWait.size()
108 << " values live across wait\n");
109}
110
111/// Hoist any probes at the beginning of resuming blocks out of the process if
112/// their values do not leak across wait ops. Resuming blocks are blocks where
113/// all predecessors are `llhd.wait` ops, and the entry block. Only waits
114/// without any side-effecting op in between themselves and the beginning of the
115/// block can be hoisted.
116void Hoister::hoistProbes() {
117 for (auto &block : region) {
118 // We can only hoist probes in blocks where all predecessors have wait
119 // terminators.
120 if (!llvm::all_of(block.getPredecessors(), [](auto *predecessor) {
121 return isa<WaitOp>(predecessor->getTerminator());
122 }))
123 continue;
124
125 for (auto &op : llvm::make_early_inc_range(block)) {
126 auto probeOp = dyn_cast<PrbOp>(op);
127
128 // We can only hoist probes that have no side-effecting ops between
129 // themselves and the beginning of a block. If we see a side-effecting op,
130 // give up on this block.
131 if (!probeOp) {
132 if (isMemoryEffectFree(&op))
133 continue;
134 else
135 break;
136 }
137
138 // Only hoist probes that don't leak across wait ops.
139 if (liveAcrossWait.contains(probeOp)) {
140 LLVM_DEBUG(llvm::dbgs()
141 << "- Skipping (live across wait) " << probeOp << "\n");
142 continue;
143 }
144
145 // We can only hoist probes of signals that are declared outside the
146 // process.
147 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(&region)) {
148 LLVM_DEBUG(llvm::dbgs()
149 << "- Skipping (local signal) " << probeOp << "\n");
150 continue;
151 }
152
153 // Move the probe out of the process, trying to reuse any previous probe
154 // that we've already hoisted.
155 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
156 if (hoistedOp) {
157 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << probeOp << "\n");
158 probeOp.replaceAllUsesWith(hoistedOp.getResult());
159 probeOp.erase();
160 } else {
161 LLVM_DEBUG(llvm::dbgs() << "- Hoisting " << probeOp << "\n");
162 probeOp->moveBefore(region.getParentOp());
163 hoistedOp = probeOp;
164 }
165 }
166 }
167}
168
169//===----------------------------------------------------------------------===//
170// Pass Infrastructure
171//===----------------------------------------------------------------------===//
172
173namespace {
174struct HoistSignalsPass
175 : public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
176 void runOnOperation() override;
177};
178} // namespace
179
180void HoistSignalsPass::runOnOperation() {
181 SmallVector<Region *> regions;
182 getOperation()->walk<WalkOrder::PreOrder>([&](Operation *op) {
183 if (isa<ProcessOp, FinalOp>(op)) {
184 auto &region = op->getRegion(0);
185 if (!region.empty())
186 regions.push_back(&region);
187 return WalkResult::skip();
188 }
189 return WalkResult::advance();
190 });
191 for (auto *region : regions)
192 Hoister(*region).hoist();
193}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.