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 "mlir/Dialect/SCF/IR/SCF.h"
14#include "mlir/IR/Matchers.h"
15#include "llvm/Support/Debug.h"
16
17#define DEBUG_TYPE "llhd-hoist-signals"
18
19namespace circt {
20namespace llhd {
21#define GEN_PASS_DEF_HOISTSIGNALSPASS
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::PointerUnion;
30using llvm::SmallSetVector;
31
32//===----------------------------------------------------------------------===//
33// Probe Hoisting
34//===----------------------------------------------------------------------===//
35
36namespace {
37/// The struct performing the hoisting of probes in a single region.
38struct ProbeHoister {
39 ProbeHoister(Region &region) : region(region) {}
40 void hoist();
41
42 void findValuesLiveAcrossWait(Liveness &liveness);
43 void hoistProbes();
44
45 /// The region we are hoisting ops out of.
46 Region &region;
47
48 /// The set of values that are alive across wait ops themselves, or that have
49 /// transitive users that are live across wait ops.
50 DenseSet<Value> liveAcrossWait;
51
52 /// A lookup table of probes we have already hoisted, for deduplication.
53 DenseMap<Value, PrbOp> hoistedProbes;
54};
55} // namespace
56
57void ProbeHoister::hoist() {
58 Liveness liveness(region.getParentOp());
59 findValuesLiveAcrossWait(liveness);
60 hoistProbes();
61}
62
63/// Find all values in the region that are alive across `llhd.wait` operations,
64/// or that have transitive uses that are alive across waits. We can only hoist
65/// probes that do not feed data flow graphs that are alive across such wait
66/// ops. Since control flow edges in `cf.br` and `cf.cond_br` ops are
67/// side-effect free, we have no guarantee that moving a probe out of a process
68/// could potentially cause other ops to become eligible for a move out of the
69/// process. Therefore, if such ops are moved outside of the process, they are
70/// effectively moved across the waits and thus sample their operands at
71/// different points in time. Only values that are explicitly carried across
72/// `llhd.wait`, where the LLHD dialect has control over the control flow
73/// semantics, may have probes in their fan-in cone hoisted out.
74void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
75 // First find all values that are live across `llhd.wait` operations. We are
76 // only interested in values defined in the current region.
77 SmallVector<Value> worklist;
78 for (auto &block : region)
79 if (isa<WaitOp>(block.getTerminator()))
80 for (auto value : liveness.getLiveOut(&block))
81 if (value.getParentRegion() == &region)
82 if (liveAcrossWait.insert(value).second)
83 worklist.push_back(value);
84
85 // Propagate liveness information along the use-def chain and across control
86 // flow. This will allow us to check `liveAcrossWait` to know if a value
87 // escapes across a wait along its use-def chain that isn't an explicit
88 // successor operand of the wait op.
89 while (!worklist.empty()) {
90 auto value = worklist.pop_back_val();
91 if (auto *defOp = value.getDefiningOp()) {
92 for (auto operand : defOp->getOperands())
93 if (operand.getParentRegion() == &region)
94 if (liveAcrossWait.insert(operand).second)
95 worklist.push_back(operand);
96 } else {
97 auto blockArg = cast<BlockArgument>(value);
98 for (auto &use : blockArg.getOwner()->getUses()) {
99 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
100 if (!branch)
101 continue;
102 auto operand = branch.getSuccessorOperands(
103 use.getOperandNumber())[blockArg.getArgNumber()];
104 if (operand.getParentRegion() == &region)
105 if (liveAcrossWait.insert(operand).second)
106 worklist.push_back(operand);
107 }
108 }
109 }
110
111 LLVM_DEBUG(llvm::dbgs() << "Found " << liveAcrossWait.size()
112 << " values live across wait\n");
113}
114
115/// Hoist any probes at the beginning of resuming blocks out of the process if
116/// their values do not leak across wait ops. Resuming blocks are blocks where
117/// all predecessors are `llhd.wait` ops, and the entry block. Only waits
118/// without any side-effecting op in between themselves and the beginning of the
119/// block can be hoisted.
120void ProbeHoister::hoistProbes() {
121 auto findExistingProbe = [&](Value signal) {
122 for (auto *user : signal.getUsers())
123 if (auto probeOp = dyn_cast<PrbOp>(user))
124 if (probeOp->getParentRegion()->isProperAncestor(&region))
125 return probeOp;
126 return PrbOp{};
127 };
128
129 for (auto &block : region) {
130 // We can only hoist probes in blocks where all predecessors have wait
131 // terminators.
132 if (!llvm::all_of(block.getPredecessors(), [](auto *predecessor) {
133 return isa<WaitOp>(predecessor->getTerminator());
134 }))
135 continue;
136
137 for (auto &op : llvm::make_early_inc_range(block)) {
138 auto probeOp = dyn_cast<PrbOp>(op);
139
140 // We can only hoist probes that have no side-effecting ops between
141 // themselves and the beginning of a block. If we see a side-effecting op,
142 // give up on this block.
143 if (!probeOp) {
144 if (isMemoryEffectFree(&op))
145 continue;
146 else
147 break;
148 }
149
150 // Only hoist probes that don't leak across wait ops.
151 if (liveAcrossWait.contains(probeOp)) {
152 LLVM_DEBUG(llvm::dbgs()
153 << "- Skipping (live across wait) " << probeOp << "\n");
154 continue;
155 }
156
157 // We can only hoist probes of signals that are declared outside the
158 // process.
159 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(&region)) {
160 LLVM_DEBUG(llvm::dbgs()
161 << "- Skipping (local signal) " << probeOp << "\n");
162 continue;
163 }
164
165 // Move the probe out of the process, trying to reuse any previous probe
166 // that we've already hoisted.
167 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
168 if (hoistedOp) {
169 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << probeOp << "\n");
170 probeOp.replaceAllUsesWith(hoistedOp.getResult());
171 probeOp.erase();
172 } else {
173 LLVM_DEBUG(llvm::dbgs() << "- Hoisting " << probeOp << "\n");
174 if (auto existingOp = findExistingProbe(probeOp.getSignal())) {
175 probeOp.replaceAllUsesWith(existingOp.getResult());
176 probeOp.erase();
177 hoistedOp = existingOp;
178 } else {
179 if (auto *defOp = probeOp.getSignal().getDefiningOp())
180 probeOp->moveAfter(defOp);
181 else
182 probeOp->moveBefore(region.getParentOp());
183 hoistedOp = probeOp;
184 }
185 }
186 }
187 }
188}
189
190//===----------------------------------------------------------------------===//
191// Drive Operand Tracking
192//===----------------------------------------------------------------------===//
193
194namespace {
195/// An operand value on a drive operation. Can represent constant `IntegerAttr`
196/// or `TimeAttr` operands, regular `Value` operands, a typed dont-care value,
197/// or a null value.
198struct DriveValue {
199 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
200 Data data;
201
202 // Create a null `DriveValue`.
203 DriveValue() : data(Value{}) {}
204
205 // Create a don't care `DriveValue`.
206 static DriveValue dontCare(Type type) { return DriveValue(type); }
207
208 /// Create a `DriveValue` from a non-null constant `IntegerAttr`.
209 DriveValue(IntegerAttr attr) : data(attr) {}
210
211 /// Create a `DriveValue` from a non-null constant `TimeAttr`.
212 DriveValue(TimeAttr attr) : data(attr) {}
213
214 // Create a `DriveValue` from a non-null `Value`. If the value is defined by a
215 // constant-like op, stores the constant attribute instead.
216 DriveValue(Value value) : data(value) {
217 Attribute attr;
218 if (auto *defOp = value.getDefiningOp())
219 if (m_Constant(&attr).match(defOp))
220 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
221 [&](auto attr) { data = attr; });
222 }
223
224 bool operator==(const DriveValue &other) const { return data == other.data; }
225 bool operator!=(const DriveValue &other) const { return data != other.data; }
226
227 bool isDontCare() const { return isa<Type>(data); }
228 explicit operator bool() const { return bool(data); }
229
230 Type getType() const {
231 return TypeSwitch<Data, Type>(data)
232 .Case<Value, IntegerAttr, TimeAttr>([](auto x) { return x.getType(); })
233 .Case<Type>([](auto type) { return type; });
234 }
235
236private:
237 explicit DriveValue(Data data) : data(data) {}
238};
239
240/// The operands of an `llhd.drv`, represented as `DriveValue`s.
241struct DriveOperands {
242 DriveValue value;
243 DriveValue delay;
244 DriveValue enable;
245};
246
247/// A set of drives to a single slot. Tracks the drive operations, the operands
248/// of the drive before each terminator, and which operands have a uniform
249/// value.
250struct DriveSet {
251 /// The drive operations covered by the information in this struct.
252 SmallPtrSet<Operation *, 2> ops;
253 /// The drive operands at each terminator.
255 /// The drive operands that are uniform across all terminators, or null if
256 /// non-uniform.
257 DriveOperands uniform;
258};
259} // namespace
260
261static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
262 const DriveValue &dv) {
263 if (!dv.data)
264 os << "null";
265 else
266 TypeSwitch<DriveValue::Data>(dv.data)
267 .Case<Value, IntegerAttr, TimeAttr>([&](auto x) { os << x; })
268 .Case<Type>([&](auto) { os << "dont-care"; });
269 return os;
270}
271
272//===----------------------------------------------------------------------===//
273// Drive Hoisting
274//===----------------------------------------------------------------------===//
275
276namespace {
277/// The struct performing the hoisting of drives in a process.
278struct DriveHoister {
279 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
280 void hoist();
281
282 void findHoistableSlots();
283 void collectDriveSets();
284 void finalizeDriveSets();
285 void hoistDrives();
286
287 /// The process we are hoisting drives out of.
288 ProcessOp processOp;
289
290 /// The slots for which we are trying to hoist drives. Mostly `llhd.sig` ops
291 /// in practice. This establishes a deterministic order for slots, such that
292 /// everything else in the pass can operate using unordered maps and sets.
293 SmallSetVector<Value, 8> slots;
294 SmallVector<Operation *> suspendOps;
296};
297} // namespace
298
299void DriveHoister::hoist() {
300 findHoistableSlots();
301 collectDriveSets();
302 finalizeDriveSets();
303 hoistDrives();
304}
305
306/// Identify any slots driven under the current region which are candidates for
307/// hoisting. This checks if the slots escape or alias in any way which we
308/// cannot reason about.
309void DriveHoister::findHoistableSlots() {
310 SmallPtrSet<Value, 8> seenSlots;
311 processOp.walk([&](DrvOp op) {
312 auto slot = op.getSignal();
313 if (!seenSlots.insert(slot).second)
314 return;
315
316 // We can only hoist drives to slots declared by a `llhd.sig` op outside the
317 // current region.
318 if (!slot.getDefiningOp<llhd::SignalOp>())
319 return;
320 if (!slot.getParentRegion()->isProperAncestor(&processOp.getBody()))
321 return;
322
323 // Ensure the slot is not used in any way we cannot reason about.
324 if (!llvm::all_of(slot.getUsers(), [&](auto *user) {
325 // Ignore uses outside of the region.
326 if (!processOp.getBody().isAncestor(user->getParentRegion()))
327 return true;
328 return isa<PrbOp, DrvOp>(user);
329 }))
330 return;
331
332 slots.insert(slot);
333 });
334 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size()
335 << " slots for drive hoisting\n");
336}
337
338/// Collect the operands of all hoistable drives into a per-slot drive set.
339/// After this function returns, the `driveSets` contains a drive set for each
340/// slot that has at least one hoistable drive. Each drive set lists the drive
341/// operands for each suspending terminator. If a slot is not driven before a
342/// terminator, the drive set will not contain an entry for that terminator.
343/// Also populates `suspendOps` with all `llhd.wait` and `llhd.halt` ops.
344void DriveHoister::collectDriveSets() {
345 auto trueAttr = BoolAttr::get(processOp.getContext(), true);
346 for (auto &block : processOp.getBody()) {
347 // We can only hoist drives before wait or halt terminators.
348 auto *terminator = block.getTerminator();
349 if (!isa<WaitOp, HaltOp>(terminator))
350 continue;
351 suspendOps.push_back(terminator);
352
353 SmallPtrSet<Value, 8> drivenSlots;
354 for (auto &op : llvm::make_early_inc_range(
355 llvm::reverse(block.without_terminator()))) {
356 auto driveOp = dyn_cast<DrvOp>(op);
357
358 // We can only hoist drives that have no side-effecting ops between
359 // themselves and the terminator of the block. If we see a side-effecting
360 // op, give up on this block.
361 if (!driveOp) {
362 if (isMemoryEffectFree(&op))
363 continue;
364 else
365 break;
366 }
367
368 // Check if we can hoist drives to this signal.
369 if (!slots.contains(driveOp.getSignal())) {
370 LLVM_DEBUG(llvm::dbgs()
371 << "- Skipping (slot unhoistable) " << driveOp << "\n");
372 continue;
373 }
374
375 // Skip this drive if we have already seen a later drive for this slot.
376 if (!drivenSlots.insert(driveOp.getSignal()).second) {
377 LLVM_DEBUG(llvm::dbgs()
378 << "- Skipping (driven later) " << driveOp << "\n");
379 continue;
380 }
381
382 // Add the operands of this drive to the drive set for the driven slot.
383 auto operands = DriveOperands{
384 driveOp.getValue(),
385 driveOp.getTime(),
386 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
387 : DriveValue(trueAttr),
388 };
389 auto &driveSet = driveSets[driveOp.getSignal()];
390 driveSet.ops.insert(driveOp);
391 driveSet.operands.insert({terminator, operands});
392 }
393 }
394}
395
396/// Make sure all drive sets specify a drive value for each terminator. If a
397/// terminator is missing, add a drive with its enable set to false. Also
398/// determine which values are uniform and available outside the process, such
399/// that we don't create unnecessary process results.
400void DriveHoister::finalizeDriveSets() {
401 auto falseAttr = BoolAttr::get(processOp.getContext(), false);
402 for (auto &[slot, driveSet] : driveSets) {
403 // Insert drives with enable set to false for any terminators that are
404 // missing. This ensures that the drive set contains information for every
405 // terminator.
406 for (auto *suspendOp : suspendOps) {
407 auto operands = DriveOperands{
408 DriveValue::dontCare(
409 cast<hw::InOutType>(slot.getType()).getElementType()),
410 DriveValue::dontCare(TimeType::get(processOp.getContext())),
411 DriveValue(falseAttr),
412 };
413 driveSet.operands.insert({suspendOp, operands});
414 }
415
416 // Determine which drive operands have a uniform value across all
417 // terminators. A null `DriveValue` indicates that there is no uniform
418 // value.
419 auto unify = [](DriveValue &accumulator, DriveValue other) {
420 if (other.isDontCare())
421 return;
422 if (accumulator == other)
423 return;
424 accumulator = accumulator.isDontCare() ? other : DriveValue();
425 };
426 assert(!driveSet.operands.empty());
427 driveSet.uniform = driveSet.operands.begin()->second;
428 for (auto [terminator, otherOperands] : driveSet.operands) {
429 unify(driveSet.uniform.value, otherOperands.value);
430 unify(driveSet.uniform.delay, otherOperands.delay);
431 unify(driveSet.uniform.enable, otherOperands.enable);
432 }
433
434 // Discard uniform non-constant values. We cannot directly use SSA values
435 // defined outside the process in the extracted drive, since those values
436 // may change at different times than the current process executes and
437 // updates its results.
438 auto clearIfNonConst = [&](DriveValue &driveValue) {
439 if (isa_and_nonnull<Value>(driveValue.data))
440 driveValue = DriveValue();
441 };
442 clearIfNonConst(driveSet.uniform.value);
443 clearIfNonConst(driveSet.uniform.delay);
444 clearIfNonConst(driveSet.uniform.enable);
445 }
446
447 LLVM_DEBUG({
448 for (auto slot : slots) {
449 const auto &driveSet = driveSets[slot];
450 llvm::dbgs() << "Drives to " << slot << "\n";
451 if (driveSet.uniform.value)
452 llvm::dbgs() << "- Uniform value: " << driveSet.uniform.value << "\n";
453 if (driveSet.uniform.delay)
454 llvm::dbgs() << "- Uniform delay: " << driveSet.uniform.delay << "\n";
455 if (driveSet.uniform.enable)
456 llvm::dbgs() << "- Uniform enable: " << driveSet.uniform.enable << "\n";
457 for (auto *suspendOp : suspendOps) {
458 auto operands = driveSet.operands.lookup(suspendOp);
459 llvm::dbgs() << "- At " << *suspendOp << "\n";
460 if (!driveSet.uniform.value)
461 llvm::dbgs() << " - Value: " << operands.value << "\n";
462 if (!driveSet.uniform.delay)
463 llvm::dbgs() << " - Delay: " << operands.delay << "\n";
464 if (!driveSet.uniform.enable)
465 llvm::dbgs() << " - Enable: " << operands.enable << "\n";
466 }
467 }
468 });
469}
470
471/// Hoist drive operations out of the process. This function adds yield operands
472/// to carry the operands of the hoisted drives out of the process, and adds
473/// corresponding process results. It then creates replacement drives outside of
474/// the process and uses the new result values for the drive operands.
475void DriveHoister::hoistDrives() {
476 if (driveSets.empty())
477 return;
478 LLVM_DEBUG(llvm::dbgs() << "Hoisting drives of " << driveSets.size()
479 << " slots\n");
480
481 // A builder to construct constant values outside the region.
482 OpBuilder builder(processOp);
483 SmallDenseMap<Attribute, Value> materializedConstants;
484
485 auto materialize = [&](DriveValue driveValue) -> Value {
486 OpBuilder builder(processOp);
487 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
488 .Case<Value>([](auto value) { return value; })
489 .Case<IntegerAttr>([&](auto attr) {
490 auto &slot = materializedConstants[attr];
491 if (!slot)
492 slot = builder.create<hw::ConstantOp>(processOp.getLoc(), attr);
493 return slot;
494 })
495 .Case<TimeAttr>([&](auto attr) {
496 auto &slot = materializedConstants[attr];
497 if (!slot)
498 slot =
499 builder.create<llhd::ConstantTimeOp>(processOp.getLoc(), attr);
500 return slot;
501 })
502 .Case<Type>([&](auto type) {
503 // TODO: This should probably create something like a `llhd.dontcare`.
504 unsigned numBits = hw::getBitWidth(type);
505 assert(numBits >= 0);
506 Value value = builder.create<hw::ConstantOp>(
507 processOp.getLoc(), builder.getIntegerType(numBits), 0);
508 if (value.getType() != type)
509 value =
510 builder.create<hw::BitcastOp>(processOp.getLoc(), type, value);
511 return value;
512 });
513 };
514
515 // Add the non-uniform drive operands as yield operands of any `llhd.wait` and
516 // `llhd.halt` terminators.
517 for (auto *suspendOp : suspendOps) {
518 LLVM_DEBUG(llvm::dbgs()
519 << "- Adding yield operands to " << *suspendOp << "\n");
520 MutableOperandRange yieldOperands =
521 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
522 .Case<WaitOp, HaltOp>(
523 [](auto op) { return op.getYieldOperandsMutable(); });
524
525 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
526 if (!uniform)
527 yieldOperands.append(materialize(nonUniform));
528 };
529
530 for (auto slot : slots) {
531 auto &driveSet = driveSets[slot];
532 auto operands = driveSet.operands.lookup(suspendOp);
533 addYieldOperand(driveSet.uniform.value, operands.value);
534 addYieldOperand(driveSet.uniform.delay, operands.delay);
535 addYieldOperand(driveSet.uniform.enable, operands.enable);
536 }
537 }
538
539 // Add process results corresponding to the added yield operands.
540 SmallVector<Type> resultTypes(processOp->getResultTypes());
541 auto oldNumResults = resultTypes.size();
542 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
543 if (!uniform)
544 resultTypes.push_back(nonUniform.getType());
545 };
546 for (auto slot : slots) {
547 auto &driveSet = driveSets[slot];
548 auto operands = driveSet.operands.begin()->second;
549 addResultType(driveSet.uniform.value, operands.value);
550 addResultType(driveSet.uniform.delay, operands.delay);
551 addResultType(driveSet.uniform.enable, operands.enable);
552 }
553 auto newProcessOp = builder.create<ProcessOp>(processOp.getLoc(), resultTypes,
554 processOp->getOperands(),
555 processOp->getAttrs());
556 newProcessOp.getBody().takeBody(processOp.getBody());
557 processOp.replaceAllUsesWith(
558 newProcessOp->getResults().slice(0, oldNumResults));
559 processOp.erase();
560 processOp = newProcessOp;
561
562 // Hoist the actual drive operations. We either materialize uniform values
563 // directly, since they are guaranteed to be able outside the process at this
564 // point, or use the new process results.
565 builder.setInsertionPointAfter(processOp);
566 auto newResultIdx = oldNumResults;
567
568 auto useResultValue = [&](DriveValue uniform) {
569 if (!uniform)
570 return processOp.getResult(newResultIdx++);
571 return materialize(uniform);
572 };
573
574 auto removeIfUnused = [](Value value) {
575 if (value)
576 if (auto *defOp = value.getDefiningOp())
577 if (defOp && isOpTriviallyDead(defOp))
578 defOp->erase();
579 };
580
581 auto trueAttr = builder.getBoolAttr(true);
582 for (auto slot : slots) {
583 auto &driveSet = driveSets[slot];
584
585 // Create the new drive outside of the process.
586 auto value = useResultValue(driveSet.uniform.value);
587 auto delay = useResultValue(driveSet.uniform.delay);
588 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
589 ? useResultValue(driveSet.uniform.enable)
590 : Value{};
591 auto newDrive =
592 builder.create<DrvOp>(slot.getLoc(), slot, value, delay, enable);
593 LLVM_DEBUG(llvm::dbgs() << "- Add " << newDrive << "\n");
594
595 // Remove the old drives inside of the process.
596 for (auto *oldOp : driveSet.ops) {
597 auto oldDrive = cast<DrvOp>(oldOp);
598 LLVM_DEBUG(llvm::dbgs() << "- Remove " << oldDrive << "\n");
599 auto delay = oldDrive.getTime();
600 auto enable = oldDrive.getEnable();
601 oldDrive.erase();
602 removeIfUnused(delay);
603 removeIfUnused(enable);
604 }
605 driveSet.ops.clear();
606 }
607 assert(newResultIdx == processOp->getNumResults());
608}
609
610//===----------------------------------------------------------------------===//
611// Pass Infrastructure
612//===----------------------------------------------------------------------===//
613
614namespace {
615struct HoistSignalsPass
616 : public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
617 void runOnOperation() override;
618};
619} // namespace
620
621void HoistSignalsPass::runOnOperation() {
622 SmallVector<Region *> regions;
623 getOperation()->walk([&](Operation *op) {
624 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
625 for (auto &region : op->getRegions())
626 if (!region.empty())
627 regions.push_back(&region);
628 });
629 for (auto *region : regions) {
630 ProbeHoister(*region).hoist();
631 if (auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
632 DriveHoister(processOp).hoist();
633 }
634}
assert(baseType &&"element must be base type")
static InstancePath empty
create(data_type, value)
Definition hw.py:433
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:35
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:651