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