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