CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerProcesses.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
12#include "mlir/Analysis/Liveness.h"
13#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
14#include "mlir/Transforms/RegionUtils.h"
15#include "llvm/Support/Debug.h"
16
17#define DEBUG_TYPE "llhd-lower-processes"
18
19namespace circt {
20namespace llhd {
21#define GEN_PASS_DEF_LOWERPROCESSESPASS
22#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
23} // namespace llhd
24} // namespace circt
25
26using namespace mlir;
27using namespace circt;
28using namespace llhd;
29using llvm::SmallDenseSet;
31
32namespace {
33struct Lowering {
34 Lowering(ProcessOp processOp) : processOp(processOp) {}
35 void lower();
36 bool matchControlFlow();
37 void markObservedValues();
38 bool allOperandsObserved();
39 bool isObserved(Value value);
40
41 ProcessOp processOp;
42 WaitOp waitOp;
43 SmallDenseSet<Value> observedValues;
44};
45} // namespace
46
47void Lowering::lower() {
48 // Ensure that the process describes combinational logic.
49 if (!matchControlFlow())
50 return;
51 markObservedValues();
52 if (!allOperandsObserved())
53 return;
54 LLVM_DEBUG(llvm::dbgs() << "Lowering process " << processOp.getLoc() << "\n");
55
56 // Replace the process.
57 OpBuilder builder(processOp);
58 auto executeOp = CombinationalOp::create(builder, processOp.getLoc(),
59 processOp.getResultTypes());
60 executeOp.getRegion().takeBody(processOp.getBody());
61 processOp.replaceAllUsesWith(executeOp);
62 processOp.erase();
63 processOp = {};
64
65 // Replace the `llhd.wait` with an `llhd.yield`.
66 builder.setInsertionPoint(waitOp);
67 YieldOp::create(builder, waitOp.getLoc(), waitOp.getYieldOperands());
68 waitOp.erase();
69
70 // Simplify the execute op body region since disconnecting the control flow
71 // loop through the wait op has potentially created unreachable blocks.
72 IRRewriter rewriter(builder);
73 (void)simplifyRegions(rewriter, executeOp->getRegions());
74}
75
76/// Check that the process' entry block trivially joins a control flow loop
77/// immediately after the wait op.
78bool Lowering::matchControlFlow() {
79 // Ensure that there is only a single wait op in the process and that it has
80 // no destination operands.
81 for (auto &block : processOp.getBody()) {
82 // Processes that can terminate are not combinational. Lowering them to
83 // `llhd.combinational` would leave an illegal `llhd.halt` terminator in the
84 // new region.
85 if (isa<HaltOp>(block.getTerminator())) {
86 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
87 << ": contains halt terminator\n");
88 return false;
89 }
90 if (auto op = dyn_cast<WaitOp>(block.getTerminator())) {
91 if (waitOp) {
92 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
93 << ": multiple wait ops\n");
94 return false;
95 }
96 waitOp = op;
97 }
98 }
99 if (!waitOp) {
100 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
101 << ": no wait op\n");
102 return false;
103 }
104 if (!waitOp.getDestOperands().empty()) {
105 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
106 << ": wait op has destination operands\n");
107 return false;
108 }
109 if (waitOp.getDelay()) {
110 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
111 << ": wait op has delay\n");
112 return false;
113 }
114
115 // Helper function to skip across empty blocks with only a single successor.
116 auto skipToMergePoint = [&](Block *block) -> std::pair<Block *, ValueRange> {
117 ValueRange operands;
118 while (auto branchOp = dyn_cast<cf::BranchOp>(block->getTerminator())) {
119 if (llvm::any_of(block->without_terminator(),
120 [](auto &op) { return !isMemoryEffectFree(&op); }))
121 break;
122 block = branchOp.getDest();
123 operands = branchOp.getDestOperands();
124 if (std::distance(block->pred_begin(), block->pred_end()) > 1)
125 break;
126 if (!operands.empty())
127 break;
128 }
129 return {block, operands};
130 };
131
132 // Ensure that the entry block and wait op converge on the same block.
133 auto &entry = processOp.getBody().front();
134 auto [entryMergeBlock, entryMergeArgs] = skipToMergePoint(&entry);
135 auto [waitMergeBlock, waitMergeArgs] = skipToMergePoint(waitOp.getDest());
136 if (entryMergeBlock != waitMergeBlock) {
137 LLVM_DEBUG(llvm::dbgs()
138 << "Skipping process " << processOp.getLoc()
139 << ": control from entry and wait does not converge\n");
140 return false;
141 }
142
143 // Helper function to check if two values are equivalent.
144 auto areValuesEquivalent = [](std::tuple<Value, Value> values) {
145 auto [a, b] = values;
146 if (a == b)
147 return true;
148 auto *opA = a.getDefiningOp();
149 auto *opB = b.getDefiningOp();
150 if (!opA || !opB)
151 return false;
152 return OperationEquivalence::isEquivalentTo(
153 opA, opB, OperationEquivalence::IgnoreLocations);
154 };
155
156 // Ensure that the entry block and wait op converge with equivalent block
157 // arguments.
158 if (!llvm::all_of(llvm::zip(entryMergeArgs, waitMergeArgs),
159 areValuesEquivalent)) {
160 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
161 << ": control from entry and wait converges with "
162 "different block arguments\n");
163 return false;
164 }
165
166 // Ensure that no values are live across the wait op.
167 Liveness liveness(processOp);
168 for (auto value : liveness.getLiveOut(waitOp->getBlock())) {
169 if (value.getParentRegion()->isProperAncestor(&processOp.getBody()))
170 continue;
171 LLVM_DEBUG({
172 llvm::dbgs() << "Skipping process " << processOp.getLoc() << ": value ";
173 value.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
174 llvm::dbgs() << " live across wait\n";
175 });
176 return false;
177 }
178
179 return true;
180}
181
182/// Mark values the process observes that are defined outside the process.
183void Lowering::markObservedValues() {
184 SmallVector<Value> worklist;
185 auto markObserved = [&](Value value) {
186 if (observedValues.insert(value).second)
187 worklist.push_back(value);
188 };
189
190 for (auto value : waitOp.getObserved())
191 if (value.getParentRegion()->isProperAncestor(&processOp.getBody()))
192 markObserved(value);
193
194 while (!worklist.empty()) {
195 auto value = worklist.pop_back_val();
196 auto *op = value.getDefiningOp();
197 if (!op)
198 continue;
199
200 // Look through probe ops to mark the probe signal as well, just in case
201 // there may be multiple probes of the same signal.
202 if (auto probeOp = dyn_cast<ProbeOp>(op))
203 markObserved(probeOp.getSignal());
204
205 // Look through operations that simply reshape incoming values into an
206 // aggregate form from which any changes remain apparent.
208 hw::BitcastOp>(op))
209 for (auto operand : op->getOperands())
210 markObserved(operand);
211 }
212}
213
214/// Ensure that any value defined outside the process that is used inside the
215/// process is derived entirely from an observed value.
216bool Lowering::allOperandsObserved() {
217 // Collect all ancestor regions such that we can easily check if a value is
218 // defined outside the process.
219 SmallPtrSet<Region *, 4> properAncestors;
220 for (auto *region = processOp->getParentRegion(); region;
221 region = region->getParentRegion())
222 properAncestors.insert(region);
223
224 // Walk all operations under the process and check each operand.
225 auto walkResult = processOp.walk([&](Operation *op) {
226 for (auto operand : op->getOperands()) {
227 // We only care about values defined outside the process.
228 if (!properAncestors.count(operand.getParentRegion()))
229 continue;
230
231 // If the value is observed, all is well.
232 if (isObserved(operand))
233 continue;
234
235 // Otherwise complain and abort.
236 LLVM_DEBUG({
237 llvm::dbgs() << "Skipping process " << processOp.getLoc()
238 << ": unobserved value ";
239 operand.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
240 llvm::dbgs() << "\n";
241 });
242 return WalkResult::interrupt();
243 }
244 return WalkResult::advance();
245 });
246 return !walkResult.wasInterrupted();
247}
248
249/// Check if a value is observed by the wait op, or all its operands are only
250/// derived from observed values.
251bool Lowering::isObserved(Value value) {
252 // Check if the value is trivially observed.
253 if (observedValues.contains(value))
254 return true;
255
256 // Otherwise get the operation that defines it such that we can check if the
257 // value is derived from purely observed values. If it isn't define by an op,
258 // the value is unobserved.
259 auto *defOp = value.getDefiningOp();
260 if (!defOp)
261 return false;
262
263 // Otherwise visit all ops in the fan-in cone and ensure that they are
264 // observed. If any value is unobserved, immediately return false.
265 SmallDenseSet<Operation *> seenOps;
266 SmallVector<Operation *> worklist;
267 seenOps.insert(defOp);
268 worklist.push_back(defOp);
269 while (!worklist.empty()) {
270 auto *op = worklist.pop_back_val();
271
272 // Give up on ops with nested regions.
273 if (op->getNumRegions() != 0)
274 return false;
275
276 // Otherwise check that all operands are observed. If we haven't seen an
277 // operand before, and it is not a signal, add it to the worklist to be
278 // checked.
279 for (auto operand : op->getOperands()) {
280 if (observedValues.contains(operand))
281 continue;
282 if (isa<RefType>(operand.getType()))
283 return false;
284 auto *defOp = operand.getDefiningOp();
285 if (!defOp || !isMemoryEffectFree(defOp))
286 return false;
287 if (seenOps.insert(defOp).second)
288 worklist.push_back(defOp);
289 }
290 }
291
292 // If we arrive at this point, we weren't able to reach an unobserved value.
293 // Therefore we consider this value derived from only observed values.
294 observedValues.insert(value);
295 return true;
296}
297
298namespace {
299struct LowerProcessesPass
300 : public llhd::impl::LowerProcessesPassBase<LowerProcessesPass> {
301 void runOnOperation() override;
302};
303} // namespace
304
305void LowerProcessesPass::runOnOperation() {
306 SmallVector<ProcessOp> processOps(getOperation().getOps<ProcessOp>());
307 for (auto processOp : processOps)
308 Lowering(processOp).lower();
309}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.