Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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/IR/Matchers.h"
14#include "llvm/Support/Debug.h"
15
16#define DEBUG_TYPE "llhd-hoist-signals"
17
18namespace circt {
19namespace llhd {
20#define GEN_PASS_DEF_HOISTSIGNALSPASS
21#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
22} // namespace llhd
23} // namespace circt
24
25using namespace mlir;
26using namespace circt;
27using namespace llhd;
28using llvm::PointerUnion;
29using llvm::SmallSetVector;
30
31//===----------------------------------------------------------------------===//
32// Probe Hoisting
33//===----------------------------------------------------------------------===//
34
35namespace {
36/// The struct performing the hoisting of probes in a single region.
37struct ProbeHoister {
38 ProbeHoister(Region &region) : region(region) {}
39 void hoist();
40
41 void findValuesLiveAcrossWait(Liveness &liveness);
42 void hoistProbes();
43
44 /// The region we are hoisting ops out of.
45 Region &region;
46
47 /// The set of values that are alive across wait ops themselves, or that have
48 /// transitive users that are live across wait ops.
49 DenseSet<Value> liveAcrossWait;
50
51 /// A lookup table of probes we have already hoisted, for deduplication.
52 DenseMap<Value, PrbOp> hoistedProbes;
53};
54} // namespace
55
56void ProbeHoister::hoist() {
57 Liveness liveness(region.getParentOp());
58 findValuesLiveAcrossWait(liveness);
59 hoistProbes();
60}
61
62/// Find all values in the region that are alive across `llhd.wait` operations,
63/// or that have transitive uses that are alive across waits. We can only hoist
64/// probes that do not feed data flow graphs that are alive across such wait
65/// ops. Since control flow edges in `cf.br` and `cf.cond_br` ops are
66/// side-effect free, we have no guarantee that moving a probe out of a process
67/// could potentially cause other ops to become eligible for a move out of the
68/// process. Therefore, if such ops are moved outside of the process, they are
69/// effectively moved across the waits and thus sample their operands at
70/// different points in time. Only values that are explicitly carried across
71/// `llhd.wait`, where the LLHD dialect has control over the control flow
72/// semantics, may have probes in their fan-in cone hoisted out.
73void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
74 // First find all values that are live across `llhd.wait` operations. We are
75 // only interested in values defined in the current region.
76 SmallVector<Value> worklist;
77 for (auto &block : region)
78 if (isa<WaitOp>(block.getTerminator()))
79 for (auto value : liveness.getLiveOut(&block))
80 if (value.getParentRegion() == &region)
81 if (liveAcrossWait.insert(value).second)
82 worklist.push_back(value);
83
84 // Propagate liveness information along the use-def chain and across control
85 // flow. This will allow us to check `liveAcrossWait` to know if a value
86 // escapes across a wait along its use-def chain that isn't an explicit
87 // successor operand of the wait op.
88 while (!worklist.empty()) {
89 auto value = worklist.pop_back_val();
90 if (auto *defOp = value.getDefiningOp()) {
91 for (auto operand : defOp->getOperands())
92 if (operand.getParentRegion() == &region)
93 if (liveAcrossWait.insert(operand).second)
94 worklist.push_back(operand);
95 } else {
96 auto blockArg = cast<BlockArgument>(value);
97 for (auto &use : blockArg.getOwner()->getUses()) {
98 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
99 if (!branch)
100 continue;
101 auto operand = branch.getSuccessorOperands(
102 use.getOperandNumber())[blockArg.getArgNumber()];
103 if (operand.getParentRegion() == &region)
104 if (liveAcrossWait.insert(operand).second)
105 worklist.push_back(operand);
106 }
107 }
108 }
109
110 LLVM_DEBUG(llvm::dbgs() << "Found " << liveAcrossWait.size()
111 << " values live across wait\n");
112}
113
114/// Hoist any probes at the beginning of resuming blocks out of the process if
115/// their values do not leak across wait ops. Resuming blocks are blocks where
116/// all predecessors are `llhd.wait` ops, and the entry block. Only waits
117/// without any side-effecting op in between themselves and the beginning of the
118/// block can be hoisted.
119void ProbeHoister::hoistProbes() {
120 auto findExistingProbe = [&](Value signal) {
121 for (auto *user : signal.getUsers())
122 if (auto probeOp = dyn_cast<PrbOp>(user))
123 if (probeOp->getParentRegion()->isProperAncestor(&region))
124 return probeOp;
125 return PrbOp{};
126 };
127
128 for (auto &block : region) {
129 // We can only hoist probes in blocks where all predecessors have wait
130 // terminators.
131 if (!llvm::all_of(block.getPredecessors(), [](auto *predecessor) {
132 return isa<WaitOp>(predecessor->getTerminator());
133 }))
134 continue;
135
136 for (auto &op : llvm::make_early_inc_range(block)) {
137 auto probeOp = dyn_cast<PrbOp>(op);
138
139 // We can only hoist probes that have no side-effecting ops between
140 // themselves and the beginning of a block. If we see a side-effecting op,
141 // give up on this block.
142 if (!probeOp) {
143 if (isMemoryEffectFree(&op))
144 continue;
145 else
146 break;
147 }
148
149 // Only hoist probes that don't leak across wait ops.
150 if (liveAcrossWait.contains(probeOp)) {
151 LLVM_DEBUG(llvm::dbgs()
152 << "- Skipping (live across wait) " << probeOp << "\n");
153 continue;
154 }
155
156 // We can only hoist probes of signals that are declared outside the
157 // process.
158 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(&region)) {
159 LLVM_DEBUG(llvm::dbgs()
160 << "- Skipping (local signal) " << probeOp << "\n");
161 continue;
162 }
163
164 // Move the probe out of the process, trying to reuse any previous probe
165 // that we've already hoisted.
166 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
167 if (hoistedOp) {
168 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << probeOp << "\n");
169 probeOp.replaceAllUsesWith(hoistedOp.getResult());
170 probeOp.erase();
171 } else {
172 LLVM_DEBUG(llvm::dbgs() << "- Hoisting " << probeOp << "\n");
173 if (auto existingOp = findExistingProbe(probeOp.getSignal())) {
174 probeOp.replaceAllUsesWith(existingOp.getResult());
175 probeOp.erase();
176 hoistedOp = existingOp;
177 } else {
178 if (auto *defOp = probeOp.getSignal().getDefiningOp())
179 probeOp->moveAfter(defOp);
180 else
181 probeOp->moveBefore(region.getParentOp());
182 hoistedOp = probeOp;
183 }
184 }
185 }
186 }
187}
188
189//===----------------------------------------------------------------------===//
190// Drive Operand Tracking
191//===----------------------------------------------------------------------===//
192
193namespace {
194/// An operand value on a drive operation. Can represent constant `IntegerAttr`
195/// or `TimeAttr` operands, regular `Value` operands, a typed dont-care value,
196/// or a null value.
197struct DriveValue {
198 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
199 Data data;
200
201 // Create a null `DriveValue`.
202 DriveValue() : data(Value{}) {}
203
204 // Create a don't care `DriveValue`.
205 static DriveValue dontCare(Type type) { return DriveValue(type); }
206
207 /// Create a `DriveValue` from a non-null constant `IntegerAttr`.
208 DriveValue(IntegerAttr attr) : data(attr) {}
209
210 /// Create a `DriveValue` from a non-null constant `TimeAttr`.
211 DriveValue(TimeAttr attr) : data(attr) {}
212
213 // Create a `DriveValue` from a non-null `Value`. If the value is defined by a
214 // constant-like op, stores the constant attribute instead.
215 DriveValue(Value value) : data(value) {
216 Attribute attr;
217 if (auto *defOp = value.getDefiningOp())
218 if (m_Constant(&attr).match(defOp))
219 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
220 [&](auto attr) { data = attr; });
221 }
222
223 bool operator==(const DriveValue &other) const { return data == other.data; }
224 bool operator!=(const DriveValue &other) const { return data != other.data; }
225
226 bool isDontCare() const { return isa<Type>(data); }
227 explicit operator bool() const { return bool(data); }
228
229 Type getType() const {
230 return TypeSwitch<Data, Type>(data)
231 .Case<Value, IntegerAttr, TimeAttr>([](auto x) { return x.getType(); })
232 .Case<Type>([](auto type) { return type; });
233 }
234
235private:
236 explicit DriveValue(Data data) : data(data) {}
237};
238
239/// The operands of an `llhd.drv`, represented as `DriveValue`s.
240struct DriveOperands {
241 DriveValue value;
242 DriveValue delay;
243 DriveValue enable;
244};
245
246/// A set of drives to a single slot. Tracks the drive operations, the operands
247/// of the drive before each terminator, and which operands have a uniform
248/// value.
249struct DriveSet {
250 /// The drive operations covered by the information in this struct.
251 SmallPtrSet<Operation *, 2> ops;
252 /// The drive operands at each terminator.
254 /// The drive operands that are uniform across all terminators, or null if
255 /// non-uniform.
256 DriveOperands uniform;
257};
258} // namespace
259
260static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
261 const DriveValue &dv) {
262 if (!dv.data)
263 os << "null";
264 else
265 TypeSwitch<DriveValue::Data>(dv.data)
266 .Case<Value, IntegerAttr, TimeAttr>([&](auto x) { os << x; })
267 .Case<Type>([&](auto) { os << "dont-care"; });
268 return os;
269}
270
271//===----------------------------------------------------------------------===//
272// Drive Hoisting
273//===----------------------------------------------------------------------===//
274
275namespace {
276/// The struct performing the hoisting of drives in a process.
277struct DriveHoister {
278 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
279 void hoist();
280
281 void findHoistableSlots();
282 void collectDriveSets();
283 void finalizeDriveSets();
284 void hoistDrives();
285
286 /// The process we are hoisting drives out of.
287 ProcessOp processOp;
288
289 /// The slots for which we are trying to hoist drives. Mostly `llhd.sig` ops
290 /// in practice. This establishes a deterministic order for slots, such that
291 /// everything else in the pass can operate using unordered maps and sets.
292 SmallSetVector<Value, 8> slots;
293 SmallVector<Operation *> suspendOps;
295};
296} // namespace
297
298void DriveHoister::hoist() {
299 findHoistableSlots();
300 collectDriveSets();
301 finalizeDriveSets();
302 hoistDrives();
303}
304
305/// Identify any slots driven under the current region which are candidates for
306/// hoisting. This checks if the slots escape or alias in any way which we
307/// cannot reason about.
308void DriveHoister::findHoistableSlots() {
309 SmallPtrSet<Value, 8> seenSlots;
310 processOp.walk([&](DrvOp op) {
311 auto slot = op.getSignal();
312 if (!seenSlots.insert(slot).second)
313 return;
314
315 // We can only hoist drives to slots declared by a `llhd.sig` op outside the
316 // current region.
317 if (!slot.getDefiningOp<llhd::SignalOp>())
318 return;
319 if (!slot.getParentRegion()->isProperAncestor(&processOp.getBody()))
320 return;
321
322 // Ensure the slot is not used in any way we cannot reason about.
323 if (!llvm::all_of(slot.getUsers(), [&](auto *user) {
324 // Ignore uses outside of the region.
325 if (!processOp.getBody().isAncestor(user->getParentRegion()))
326 return true;
327 return isa<PrbOp, DrvOp>(user);
328 }))
329 return;
330
331 slots.insert(slot);
332 });
333 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size()
334 << " slots for drive hoisting\n");
335}
336
337/// Collect the operands of all hoistable drives into a per-slot drive set.
338/// After this function returns, the `driveSets` contains a drive set for each
339/// slot that has at least one hoistable drive. Each drive set lists the drive
340/// operands for each suspending terminator. If a slot is not driven before a
341/// terminator, the drive set will not contain an entry for that terminator.
342/// Also populates `suspendOps` with all `llhd.wait` and `llhd.halt` ops.
343void DriveHoister::collectDriveSets() {
344 auto trueAttr = BoolAttr::get(processOp.getContext(), true);
345 for (auto &block : processOp.getBody()) {
346 // We can only hoist drives before wait or halt terminators.
347 auto *terminator = block.getTerminator();
348 if (!isa<WaitOp, HaltOp>(terminator))
349 continue;
350 suspendOps.push_back(terminator);
351
352 SmallPtrSet<Value, 8> drivenSlots;
353 for (auto &op : llvm::make_early_inc_range(
354 llvm::reverse(block.without_terminator()))) {
355 auto driveOp = dyn_cast<DrvOp>(op);
356
357 // We can only hoist drives that have no side-effecting ops between
358 // themselves and the terminator of the block. If we see a side-effecting
359 // op, give up on this block.
360 if (!driveOp) {
361 if (isMemoryEffectFree(&op))
362 continue;
363 else
364 break;
365 }
366
367 // Check if we can hoist drives to this signal.
368 if (!slots.contains(driveOp.getSignal())) {
369 LLVM_DEBUG(llvm::dbgs()
370 << "- Skipping (slot unhoistable) " << driveOp << "\n");
371 continue;
372 }
373
374 // Skip this drive if we have already seen a later drive for this slot.
375 if (!drivenSlots.insert(driveOp.getSignal()).second) {
376 LLVM_DEBUG(llvm::dbgs()
377 << "- Skipping (driven later) " << driveOp << "\n");
378 continue;
379 }
380
381 // Add the operands of this drive to the drive set for the driven slot.
382 auto operands = DriveOperands{
383 driveOp.getValue(),
384 driveOp.getTime(),
385 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
386 : DriveValue(trueAttr),
387 };
388 auto &driveSet = driveSets[driveOp.getSignal()];
389 driveSet.ops.insert(driveOp);
390 driveSet.operands.insert({terminator, operands});
391 }
392 }
393}
394
395/// Make sure all drive sets specify a drive value for each terminator. If a
396/// terminator is missing, add a drive with its enable set to false. Also
397/// determine which values are uniform and available outside the process, such
398/// that we don't create unnecessary process results.
399void DriveHoister::finalizeDriveSets() {
400 auto falseAttr = BoolAttr::get(processOp.getContext(), false);
401 for (auto &[slot, driveSet] : driveSets) {
402 // Insert drives with enable set to false for any terminators that are
403 // missing. This ensures that the drive set contains information for every
404 // terminator.
405 for (auto *suspendOp : suspendOps) {
406 auto operands = DriveOperands{
407 DriveValue::dontCare(
408 cast<hw::InOutType>(slot.getType()).getElementType()),
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 = builder.create<hw::ConstantOp>(processOp.getLoc(), attr);
492 return slot;
493 })
494 .Case<TimeAttr>([&](auto attr) {
495 auto &slot = materializedConstants[attr];
496 if (!slot)
497 slot =
498 builder.create<llhd::ConstantTimeOp>(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 = builder.create<hw::ConstantOp>(
506 processOp.getLoc(), builder.getIntegerType(numBits), 0);
507 if (value.getType() != type)
508 value =
509 builder.create<hw::BitcastOp>(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 = builder.create<ProcessOp>(processOp.getLoc(), resultTypes,
553 processOp->getOperands(),
554 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 auto newDrive =
591 builder.create<DrvOp>(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<DrvOp>(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<WalkOrder::PreOrder>([&](Operation *op) {
623 if (isa<ProcessOp, FinalOp>(op)) {
624 auto &region = op->getRegion(0);
625 if (!region.empty())
626 regions.push_back(&region);
627 return WalkResult::skip();
628 }
629 return WalkResult::advance();
630 });
631 for (auto *region : regions) {
632 ProbeHoister(*region).hoist();
633 if (auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
634 DriveHoister(processOp).hoist();
635 }
636}
assert(baseType &&"element must be base type")
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