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