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