CIRCT 22.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/Transforms/LLHDPasses.h.inc"
23} // namespace llhd
24} // namespace circt
25
26using namespace mlir;
27using namespace circt;
28using namespace llhd;
29using llvm::SmallDenseSet;
30using llvm::SmallSetVector;
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 if (auto op = dyn_cast<WaitOp>(block.getTerminator())) {
83 if (waitOp) {
84 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
85 << ": multiple wait ops\n");
86 return false;
87 }
88 waitOp = op;
89 }
90 }
91 if (!waitOp) {
92 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
93 << ": no wait op\n");
94 return false;
95 }
96 if (!waitOp.getDestOperands().empty()) {
97 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
98 << ": wait op has destination operands\n");
99 return false;
100 }
101 if (waitOp.getDelay()) {
102 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
103 << ": wait op has delay\n");
104 return false;
105 }
106
107 // Helper function to skip across empty blocks with only a single successor.
108 auto skipToMergePoint = [&](Block *block) -> std::pair<Block *, ValueRange> {
109 ValueRange operands;
110 while (auto branchOp = dyn_cast<cf::BranchOp>(block->getTerminator())) {
111 if (llvm::any_of(block->without_terminator(),
112 [](auto &op) { return !isMemoryEffectFree(&op); }))
113 break;
114 block = branchOp.getDest();
115 operands = branchOp.getDestOperands();
116 if (std::distance(block->pred_begin(), block->pred_end()) > 1)
117 break;
118 if (!operands.empty())
119 break;
120 }
121 return {block, operands};
122 };
123
124 // Ensure that the entry block and wait op converge on the same block.
125 auto &entry = processOp.getBody().front();
126 auto [entryMergeBlock, entryMergeArgs] = skipToMergePoint(&entry);
127 auto [waitMergeBlock, waitMergeArgs] = skipToMergePoint(waitOp.getDest());
128 if (entryMergeBlock != waitMergeBlock) {
129 LLVM_DEBUG(llvm::dbgs()
130 << "Skipping process " << processOp.getLoc()
131 << ": control from entry and wait does not converge\n");
132 return false;
133 }
134
135 // Helper function to check if two values are equivalent.
136 auto areValuesEquivalent = [](std::tuple<Value, Value> values) {
137 auto [a, b] = values;
138 if (a == b)
139 return true;
140 auto *opA = a.getDefiningOp();
141 auto *opB = b.getDefiningOp();
142 if (!opA || !opB)
143 return false;
144 return OperationEquivalence::isEquivalentTo(
145 opA, opB, OperationEquivalence::IgnoreLocations);
146 };
147
148 // Ensure that the entry block and wait op converge with equivalent block
149 // arguments.
150 if (!llvm::all_of(llvm::zip(entryMergeArgs, waitMergeArgs),
151 areValuesEquivalent)) {
152 LLVM_DEBUG(llvm::dbgs() << "Skipping process " << processOp.getLoc()
153 << ": control from entry and wait converges with "
154 "different block arguments\n");
155 return false;
156 }
157
158 // Ensure that no values are live across the wait op.
159 Liveness liveness(processOp);
160 for (auto value : liveness.getLiveOut(waitOp->getBlock())) {
161 if (value.getParentRegion()->isProperAncestor(&processOp.getBody()))
162 continue;
163 LLVM_DEBUG({
164 llvm::dbgs() << "Skipping process " << processOp.getLoc() << ": value ";
165 value.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
166 llvm::dbgs() << " live across wait\n";
167 });
168 return false;
169 }
170
171 return true;
172}
173
174/// Mark values the process observes that are defined outside the process.
175void Lowering::markObservedValues() {
176 SmallVector<Value> worklist;
177 auto markObserved = [&](Value value) {
178 if (observedValues.insert(value).second)
179 worklist.push_back(value);
180 };
181
182 for (auto value : waitOp.getObserved())
183 if (value.getParentRegion()->isProperAncestor(&processOp.getBody()))
184 markObserved(value);
185
186 while (!worklist.empty()) {
187 auto value = worklist.pop_back_val();
188 auto *op = value.getDefiningOp();
189 if (!op)
190 continue;
191
192 // Look through probe ops to mark the probe signal as well, just in case
193 // there may be multiple probes of the same signal.
194 if (auto probeOp = dyn_cast<ProbeOp>(op))
195 markObserved(probeOp.getSignal());
196
197 // Look through operations that simply reshape incoming values into an
198 // aggregate form from which any changes remain apparent.
200 hw::BitcastOp>(op))
201 for (auto operand : op->getOperands())
202 markObserved(operand);
203 }
204}
205
206/// Ensure that any value defined outside the process that is used inside the
207/// process is derived entirely from an observed value.
208bool Lowering::allOperandsObserved() {
209 // Collect all ancestor regions such that we can easily check if a value is
210 // defined outside the process.
211 SmallPtrSet<Region *, 4> properAncestors;
212 for (auto *region = processOp->getParentRegion(); region;
213 region = region->getParentRegion())
214 properAncestors.insert(region);
215
216 // Walk all operations under the process and check each operand.
217 auto walkResult = processOp.walk([&](Operation *op) {
218 for (auto operand : op->getOperands()) {
219 // We only care about values defined outside the process.
220 if (!properAncestors.count(operand.getParentRegion()))
221 continue;
222
223 // If the value is observed, all is well.
224 if (isObserved(operand))
225 continue;
226
227 // Otherwise complain and abort.
228 LLVM_DEBUG({
229 llvm::dbgs() << "Skipping process " << processOp.getLoc()
230 << ": unobserved value ";
231 operand.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
232 llvm::dbgs() << "\n";
233 });
234 return WalkResult::interrupt();
235 }
236 return WalkResult::advance();
237 });
238 return !walkResult.wasInterrupted();
239}
240
241/// Check if a value is observed by the wait op, or all its operands are only
242/// derived from observed values.
243bool Lowering::isObserved(Value value) {
244 // Check if the value is trivially observed.
245 if (observedValues.contains(value))
246 return true;
247
248 // Otherwise get the operation that defines it such that we can check if the
249 // value is derived from purely observed values. If it isn't define by an op,
250 // the value is unobserved.
251 auto *defOp = value.getDefiningOp();
252 if (!defOp)
253 return false;
254
255 // Otherwise visit all ops in the fan-in cone and ensure that they are
256 // observed. If any value is unobserved, immediately return false.
257 SmallDenseSet<Operation *> seenOps;
258 SmallVector<Operation *> worklist;
259 seenOps.insert(defOp);
260 worklist.push_back(defOp);
261 while (!worklist.empty()) {
262 auto *op = worklist.pop_back_val();
263
264 // Give up on ops with nested regions.
265 if (op->getNumRegions() != 0)
266 return false;
267
268 // Otherwise check that all operands are observed. If we haven't seen an
269 // operand before, and it is not a signal, add it to the worklist to be
270 // checked.
271 for (auto operand : op->getOperands()) {
272 if (observedValues.contains(operand))
273 continue;
274 if (isa<RefType>(operand.getType()))
275 return false;
276 auto *defOp = operand.getDefiningOp();
277 if (!defOp || !isMemoryEffectFree(defOp))
278 return false;
279 if (seenOps.insert(defOp).second)
280 worklist.push_back(defOp);
281 }
282 }
283
284 // If we arrive at this point, we weren't able to reach an unobserved value.
285 // Therefore we consider this value derived from only observed values.
286 observedValues.insert(value);
287 return true;
288}
289
290namespace {
291struct LowerProcessesPass
292 : public llhd::impl::LowerProcessesPassBase<LowerProcessesPass> {
293 void runOnOperation() override;
294};
295} // namespace
296
297void LowerProcessesPass::runOnOperation() {
298 SmallVector<ProcessOp> processOps(getOperation().getOps<ProcessOp>());
299 for (auto processOp : processOps)
300 Lowering(processOp).lower();
301}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.