CIRCT 22.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, ProbeOp> 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<ProbeOp>(user))
124 if (probeOp->getParentRegion()->isProperAncestor(&region))
125 return probeOp;
126 return ProbeOp{};
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<ProbeOp>(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([&](DriveOp 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<ProbeOp, DriveOp>(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<DriveOp>(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(cast<RefType>(slot.getType()).getNestedType()),
409 DriveValue::dontCare(TimeType::get(processOp.getContext())),
410 DriveValue(falseAttr),
411 };
412 driveSet.operands.insert({suspendOp, operands});
413 }
414
415 // Determine which drive operands have a uniform value across all
416 // terminators. A null `DriveValue` indicates that there is no uniform
417 // value.
418 auto unify = [](DriveValue &accumulator, DriveValue other) {
419 if (other.isDontCare())
420 return;
421 if (accumulator == other)
422 return;
423 accumulator = accumulator.isDontCare() ? other : DriveValue();
424 };
425 assert(!driveSet.operands.empty());
426 driveSet.uniform = driveSet.operands.begin()->second;
427 for (auto [terminator, otherOperands] : driveSet.operands) {
428 unify(driveSet.uniform.value, otherOperands.value);
429 unify(driveSet.uniform.delay, otherOperands.delay);
430 unify(driveSet.uniform.enable, otherOperands.enable);
431 }
432
433 // Discard uniform non-constant values. We cannot directly use SSA values
434 // defined outside the process in the extracted drive, since those values
435 // may change at different times than the current process executes and
436 // updates its results.
437 auto clearIfNonConst = [&](DriveValue &driveValue) {
438 if (isa_and_nonnull<Value>(driveValue.data))
439 driveValue = DriveValue();
440 };
441 clearIfNonConst(driveSet.uniform.value);
442 clearIfNonConst(driveSet.uniform.delay);
443 clearIfNonConst(driveSet.uniform.enable);
444 }
445
446 LLVM_DEBUG({
447 for (auto slot : slots) {
448 const auto &driveSet = driveSets[slot];
449 llvm::dbgs() << "Drives to " << slot << "\n";
450 if (driveSet.uniform.value)
451 llvm::dbgs() << "- Uniform value: " << driveSet.uniform.value << "\n";
452 if (driveSet.uniform.delay)
453 llvm::dbgs() << "- Uniform delay: " << driveSet.uniform.delay << "\n";
454 if (driveSet.uniform.enable)
455 llvm::dbgs() << "- Uniform enable: " << driveSet.uniform.enable << "\n";
456 for (auto *suspendOp : suspendOps) {
457 auto operands = driveSet.operands.lookup(suspendOp);
458 llvm::dbgs() << "- At " << *suspendOp << "\n";
459 if (!driveSet.uniform.value)
460 llvm::dbgs() << " - Value: " << operands.value << "\n";
461 if (!driveSet.uniform.delay)
462 llvm::dbgs() << " - Delay: " << operands.delay << "\n";
463 if (!driveSet.uniform.enable)
464 llvm::dbgs() << " - Enable: " << operands.enable << "\n";
465 }
466 }
467 });
468}
469
470/// Hoist drive operations out of the process. This function adds yield operands
471/// to carry the operands of the hoisted drives out of the process, and adds
472/// corresponding process results. It then creates replacement drives outside of
473/// the process and uses the new result values for the drive operands.
474void DriveHoister::hoistDrives() {
475 if (driveSets.empty())
476 return;
477 LLVM_DEBUG(llvm::dbgs() << "Hoisting drives of " << driveSets.size()
478 << " slots\n");
479
480 // A builder to construct constant values outside the region.
481 OpBuilder builder(processOp);
482 SmallDenseMap<Attribute, Value> materializedConstants;
483
484 auto materialize = [&](DriveValue driveValue) -> Value {
485 OpBuilder builder(processOp);
486 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
487 .Case<Value>([](auto value) { return value; })
488 .Case<IntegerAttr>([&](auto attr) {
489 auto &slot = materializedConstants[attr];
490 if (!slot)
491 slot = hw::ConstantOp::create(builder, processOp.getLoc(), attr);
492 return slot;
493 })
494 .Case<TimeAttr>([&](auto attr) {
495 auto &slot = materializedConstants[attr];
496 if (!slot)
497 slot =
498 llhd::ConstantTimeOp::create(builder, processOp.getLoc(), attr);
499 return slot;
500 })
501 .Case<Type>([&](auto type) {
502 // TODO: This should probably create something like a `llhd.dontcare`.
503 unsigned numBits = hw::getBitWidth(type);
504 assert(numBits >= 0);
505 Value value = hw::ConstantOp::create(
506 builder, processOp.getLoc(), builder.getIntegerType(numBits), 0);
507 if (value.getType() != type)
508 value =
509 hw::BitcastOp::create(builder, processOp.getLoc(), type, value);
510 return value;
511 });
512 };
513
514 // Add the non-uniform drive operands as yield operands of any `llhd.wait` and
515 // `llhd.halt` terminators.
516 for (auto *suspendOp : suspendOps) {
517 LLVM_DEBUG(llvm::dbgs()
518 << "- Adding yield operands to " << *suspendOp << "\n");
519 MutableOperandRange yieldOperands =
520 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
521 .Case<WaitOp, HaltOp>(
522 [](auto op) { return op.getYieldOperandsMutable(); });
523
524 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
525 if (!uniform)
526 yieldOperands.append(materialize(nonUniform));
527 };
528
529 for (auto slot : slots) {
530 auto &driveSet = driveSets[slot];
531 auto operands = driveSet.operands.lookup(suspendOp);
532 addYieldOperand(driveSet.uniform.value, operands.value);
533 addYieldOperand(driveSet.uniform.delay, operands.delay);
534 addYieldOperand(driveSet.uniform.enable, operands.enable);
535 }
536 }
537
538 // Add process results corresponding to the added yield operands.
539 SmallVector<Type> resultTypes(processOp->getResultTypes());
540 auto oldNumResults = resultTypes.size();
541 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
542 if (!uniform)
543 resultTypes.push_back(nonUniform.getType());
544 };
545 for (auto slot : slots) {
546 auto &driveSet = driveSets[slot];
547 auto operands = driveSet.operands.begin()->second;
548 addResultType(driveSet.uniform.value, operands.value);
549 addResultType(driveSet.uniform.delay, operands.delay);
550 addResultType(driveSet.uniform.enable, operands.enable);
551 }
552 auto newProcessOp =
553 ProcessOp::create(builder, processOp.getLoc(), resultTypes,
554 processOp->getOperands(), processOp->getAttrs());
555 newProcessOp.getBody().takeBody(processOp.getBody());
556 processOp.replaceAllUsesWith(
557 newProcessOp->getResults().slice(0, oldNumResults));
558 processOp.erase();
559 processOp = newProcessOp;
560
561 // Hoist the actual drive operations. We either materialize uniform values
562 // directly, since they are guaranteed to be able outside the process at this
563 // point, or use the new process results.
564 builder.setInsertionPointAfter(processOp);
565 auto newResultIdx = oldNumResults;
566
567 auto useResultValue = [&](DriveValue uniform) {
568 if (!uniform)
569 return processOp.getResult(newResultIdx++);
570 return materialize(uniform);
571 };
572
573 auto removeIfUnused = [](Value value) {
574 if (value)
575 if (auto *defOp = value.getDefiningOp())
576 if (defOp && isOpTriviallyDead(defOp))
577 defOp->erase();
578 };
579
580 auto trueAttr = builder.getBoolAttr(true);
581 for (auto slot : slots) {
582 auto &driveSet = driveSets[slot];
583
584 // Create the new drive outside of the process.
585 auto value = useResultValue(driveSet.uniform.value);
586 auto delay = useResultValue(driveSet.uniform.delay);
587 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
588 ? useResultValue(driveSet.uniform.enable)
589 : Value{};
590 [[maybe_unused]] auto newDrive =
591 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
592 LLVM_DEBUG(llvm::dbgs() << "- Add " << newDrive << "\n");
593
594 // Remove the old drives inside of the process.
595 for (auto *oldOp : driveSet.ops) {
596 auto oldDrive = cast<DriveOp>(oldOp);
597 LLVM_DEBUG(llvm::dbgs() << "- Remove " << oldDrive << "\n");
598 auto delay = oldDrive.getTime();
599 auto enable = oldDrive.getEnable();
600 oldDrive.erase();
601 removeIfUnused(delay);
602 removeIfUnused(enable);
603 }
604 driveSet.ops.clear();
605 }
606 assert(newResultIdx == processOp->getNumResults());
607}
608
609//===----------------------------------------------------------------------===//
610// Pass Infrastructure
611//===----------------------------------------------------------------------===//
612
613namespace {
614struct HoistSignalsPass
615 : public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
616 void runOnOperation() override;
617};
618} // namespace
619
620void HoistSignalsPass::runOnOperation() {
621 SmallVector<Region *> regions;
622 getOperation()->walk([&](Operation *op) {
623 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
624 for (auto &region : op->getRegions())
625 if (!region.empty())
626 regions.push_back(&region);
627 });
628 for (auto *region : regions) {
629 ProbeHoister(*region).hoist();
630 if (auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
631 DriveHoister(processOp).hoist();
632 }
633}
assert(baseType &&"element must be base type")
static InstancePath empty
create(data_type, value)
Definition hw.py:441
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:685