CIRCT 23.0.0git
Loading...
Searching...
No Matches
Mem2Reg.cpp
Go to the documentation of this file.
1//===- Mem2Reg.cpp - Promote signal/memory slots to values ----------------===//
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
14#include "mlir/Analysis/Liveness.h"
15#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
16#include "mlir/IR/Dominance.h"
17#include "llvm/Support/Debug.h"
18#include "llvm/Support/GenericIteratedDominanceFrontier.h"
19
20#define DEBUG_TYPE "llhd-mem2reg"
21
22namespace circt {
23namespace llhd {
24#define GEN_PASS_DEF_MEM2REGPASS
25#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
26} // namespace llhd
27} // namespace circt
28
29using namespace mlir;
30using namespace circt;
31using namespace llhd;
32using llvm::PointerIntPair;
33using llvm::SmallDenseSet;
34using llvm::SmallSetVector;
35using llvm::SpecificBumpPtrAllocator;
36
37/// Check whether a value is defined by `llhd.constant_time <0ns, 0d, 1e>`.
38static bool isEpsilonDelay(Value value) {
39 if (auto timeOp = value.getDefiningOp<ConstantTimeOp>()) {
40 auto t = timeOp.getValue();
41 return t.getTime() == 0 && t.getDelta() == 0 && t.getEpsilon() == 1;
42 }
43 return false;
44}
45
46/// Check whether a value is defined by `llhd.constant_time <0ns, 1d, 0e>`.
47static bool isDeltaDelay(Value value) {
48 if (auto timeOp = value.getDefiningOp<ConstantTimeOp>()) {
49 auto t = timeOp.getValue();
50 return t.getTime() == 0 && t.getDelta() == 1 && t.getEpsilon() == 0;
51 }
52 return false;
53}
54
55/// Check whether an operation is a `llhd.drive` with an epsilon delay. This
56/// corresponds to a blocking assignment in Verilog.
57static bool isBlockingDrive(Operation *op) {
58 if (auto driveOp = dyn_cast<DriveOp>(op))
59 return isEpsilonDelay(driveOp.getTime());
60 return false;
61}
62
63/// Check whether an operation is a `llhd.drive` with a delta delay. This
64/// corresponds to a non-blocking assignment in Verilog.
65static bool isDeltaDrive(Operation *op) {
66 if (auto driveOp = dyn_cast<DriveOp>(op))
67 return isDeltaDelay(driveOp.getTime());
68 return false;
69}
70
71//===----------------------------------------------------------------------===//
72// Reaching Definitions and Placeholders
73//===----------------------------------------------------------------------===//
74
75namespace {
76/// Information about whether a definition is driven back onto its signal. For
77/// example, probes provide a definition for their signal that does not have to
78/// be driven back onto the signal. Drives on the other hand provide a
79/// definition that eventually must be driven onto the signal.
80struct DriveCondition {
81 static DriveCondition never() { return ConditionAndMode(Value{}, Never); }
82 static DriveCondition always() { return ConditionAndMode(Value{}, Always); }
83 static DriveCondition conditional(Value condition = {}) {
84 return ConditionAndMode(condition, Conditional);
85 }
86
87 bool isNever() const { return conditionAndMode.getInt() == Never; }
88 bool isAlways() const { return conditionAndMode.getInt() == Always; }
89 bool isConditional() const {
90 return conditionAndMode.getInt() == Conditional;
91 }
92
93 Value getCondition() const { return conditionAndMode.getPointer(); }
94 void setCondition(Value condition) { conditionAndMode.setPointer(condition); }
95
96 bool operator==(const DriveCondition &other) const {
97 return conditionAndMode == other.conditionAndMode;
98 }
99 bool operator!=(const DriveCondition &other) const {
100 return conditionAndMode != other.conditionAndMode;
101 }
102
103private:
104 enum {
105 Never,
106 Always,
107 Conditional,
108 };
109 typedef PointerIntPair<Value, 2> ConditionAndMode;
110 ConditionAndMode conditionAndMode;
111
112 DriveCondition(ConditionAndMode conditionAndMode)
113 : conditionAndMode(conditionAndMode) {}
114 friend DenseMapInfo<DriveCondition>;
115};
116
117/// A definition for a memory slot that may not yet have a concrete SSA value.
118/// These are created for blocks which need to merge distinct definitions for
119/// the same slot coming from its predecssors, as a standin before block
120/// arguments are created. They are also created for drives, where a concrete
121/// value is already available in the form of the driven value.
122struct Def {
123 Block *block;
124 Type type;
125 Value value;
126 DriveCondition condition;
127 bool valueIsPlaceholder = false;
128 bool conditionIsPlaceholder = false;
129
130 Def(Value value, DriveCondition condition)
131 : block(value.getParentBlock()), type(value.getType()), value(value),
132 condition(condition) {}
133 Def(Block *block, Type type, DriveCondition condition)
134 : block(block), type(type), condition(condition) {}
135
136 Value getValueOrPlaceholder();
137 Value getConditionOrPlaceholder();
138};
139} // namespace
140
141/// Return the SSA value for this definition if it already has one, or create
142/// a placeholder value if no value exists yet.
143Value Def::getValueOrPlaceholder() {
144 if (!value) {
145 auto builder = OpBuilder::atBlockBegin(block);
146 value = UnrealizedConversionCastOp::create(builder, builder.getUnknownLoc(),
147 type, ValueRange{})
148 .getResult(0);
149 valueIsPlaceholder = true;
150 }
151 return value;
152}
153
154/// Return the drive condition for this definition. Creates a constant false or
155/// true SSA value if the drive mode is "never" or "always", respectively. If
156/// the mode is "conditional", return the its condition value if it already has
157/// one, or create a placeholder value if no value exists yet.
158Value Def::getConditionOrPlaceholder() {
159 if (!condition.getCondition()) {
160 auto builder = OpBuilder::atBlockBegin(block);
161 Value value;
162 if (condition.isNever()) {
163 value = hw::ConstantOp::create(builder, builder.getUnknownLoc(),
164 builder.getI1Type(), 0);
165 } else if (condition.isAlways()) {
166 value = hw::ConstantOp::create(builder, builder.getUnknownLoc(),
167 builder.getI1Type(), 1);
168 } else {
169 value =
170 UnrealizedConversionCastOp::create(builder, builder.getUnknownLoc(),
171 builder.getI1Type(), ValueRange{})
172 .getResult(0);
173 conditionIsPlaceholder = true;
174 }
175 condition.setCondition(value);
176 }
177 return condition.getCondition();
178}
179
180// Allow `DriveCondition` to be used as hash map key.
181template <>
182struct llvm::DenseMapInfo<DriveCondition> {
189 static unsigned getHashValue(DriveCondition d) {
191 d.conditionAndMode);
192 }
193 static bool isEqual(DriveCondition lhs, DriveCondition rhs) {
194 return lhs == rhs;
195 }
196};
197
198//===----------------------------------------------------------------------===//
199// Lattice to Propagate Needed and Reaching Definitions
200//===----------------------------------------------------------------------===//
201
202/// The slot a reaching definition specifies a value for, alongside a bit
203/// indicating whether the definition is from a delayed drive or a blocking
204/// drive.
205using DefSlot = PointerIntPair<Value, 1>;
206static DefSlot blockingSlot(Value slot) { return {slot, 0}; }
207static DefSlot delayedSlot(Value slot) { return {slot, 1}; }
208static Value getSlot(DefSlot slot) { return slot.getPointer(); }
209static bool isDelayed(DefSlot slot) { return slot.getInt(); }
210static Type getStoredType(Value slot) {
211 return cast<RefType>(slot.getType()).getNestedType();
212}
213static Type getStoredType(DefSlot slot) {
214 return getStoredType(slot.getPointer());
215}
216static Location getLoc(DefSlot slot) { return slot.getPointer().getLoc(); }
217
218namespace {
219
220struct LatticeNode;
221struct BlockExit;
222struct ProbeNode;
223struct DriveNode;
224struct SignalNode;
225
226struct LatticeValue {
227 LatticeNode *nodeBefore = nullptr;
228 LatticeNode *nodeAfter = nullptr;
229 SmallDenseSet<Value, 1> neededDefs;
231};
232
233struct LatticeNode {
234 enum class Kind { BlockEntry, BlockExit, Probe, Drive, Signal };
235 const Kind kind;
236 LatticeNode(Kind kind) : kind(kind) {}
237};
238
239struct BlockEntry : public LatticeNode {
240 Block *block;
241 LatticeValue *valueAfter;
242 SmallVector<BlockExit *, 2> predecessors;
243 SmallVector<std::pair<Value, Def *>, 0> insertedProbes;
245
246 BlockEntry(Block *block, LatticeValue *valueAfter)
247 : LatticeNode(Kind::BlockEntry), block(block), valueAfter(valueAfter) {
248 assert(!valueAfter->nodeBefore);
249 valueAfter->nodeBefore = this;
250 }
251
252 static bool classof(const LatticeNode *n) {
253 return n->kind == Kind::BlockEntry;
254 }
255};
256
257struct BlockExit : public LatticeNode {
258 Block *block;
259 LatticeValue *valueBefore;
260 SmallVector<BlockEntry *, 2> successors;
261 Operation *terminator;
262 bool suspends;
263
264 BlockExit(Block *block, LatticeValue *valueBefore)
265 : LatticeNode(Kind::BlockExit), block(block), valueBefore(valueBefore),
266 terminator(block->getTerminator()),
267 suspends(isa<HaltOp, WaitOp>(terminator)) {
268 assert(!valueBefore->nodeAfter);
269 valueBefore->nodeAfter = this;
270 }
271
272 static bool classof(const LatticeNode *n) {
273 return n->kind == Kind::BlockExit;
274 }
275};
276
277struct OpNode : public LatticeNode {
278 Operation *op;
279 LatticeValue *valueBefore;
280 LatticeValue *valueAfter;
281
282 OpNode(Kind kind, Operation *op, LatticeValue *valueBefore,
283 LatticeValue *valueAfter)
284 : LatticeNode(kind), op(op), valueBefore(valueBefore),
285 valueAfter(valueAfter) {
286 assert(!valueBefore->nodeAfter);
287 assert(!valueAfter->nodeBefore);
288 valueBefore->nodeAfter = this;
289 valueAfter->nodeBefore = this;
290 }
291
292 static bool classof(const LatticeNode *n) {
293 return isa<ProbeNode, DriveNode, SignalNode>(n);
294 }
295};
296
297struct ProbeNode : public OpNode {
298 Value slot;
299
300 ProbeNode(ProbeOp op, Value slot, LatticeValue *valueBefore,
301 LatticeValue *valueAfter)
302 : OpNode(Kind::Probe, op, valueBefore, valueAfter), slot(slot) {}
303
304 static bool classof(const LatticeNode *n) { return n->kind == Kind::Probe; }
305};
306
307struct DriveNode : public OpNode {
308 DefSlot slot;
309 Def *def;
310
311 DriveNode(DriveOp op, Value slot, Def *def, LatticeValue *valueBefore,
312 LatticeValue *valueAfter)
313 : OpNode(Kind::Drive, op, valueBefore, valueAfter),
314 slot(isDeltaDrive(op) ? delayedSlot(slot) : blockingSlot(slot)),
315 def(def) {
317 }
318
319 /// Returns true if the op does not drive an entire slot, but only a part of a
320 /// slot through a projection.
321 bool drivesProjection() const { return op->getOperand(0) != getSlot(slot); }
322
323 /// Return the drive op.
324 DriveOp getDriveOp() const { return cast<DriveOp>(op); }
325
326 static bool classof(const LatticeNode *n) { return n->kind == Kind::Drive; }
327};
328
329struct SignalNode : public OpNode {
330 Def *def;
331
332 SignalNode(SignalOp op, Def *def, LatticeValue *valueBefore,
333 LatticeValue *valueAfter)
334 : OpNode(Kind::Signal, op, valueBefore, valueAfter), def(def) {}
335
336 SignalOp getSignalOp() const { return cast<SignalOp>(op); }
337 DefSlot getSlot() const { return blockingSlot(getSignalOp()); }
338
339 static bool classof(const LatticeNode *n) { return n->kind == Kind::Signal; }
340};
341
342/// A lattice of block entry and exit nodes, nodes for relevant operations such
343/// as probes and drives, and values flowing between the nodes.
344struct Lattice {
345 /// Create a new value on the lattice.
346 LatticeValue *createValue() {
347 auto *value = new (valueAllocator.Allocate()) LatticeValue();
348 values.push_back(value);
349 return value;
350 }
351
352 /// Create a new node on the lattice.
353 template <class T, typename... Args>
354 T *createNode(Args... args) {
355 auto *node =
356 new (getAllocator<T>().Allocate()) T(std::forward<Args>(args)...);
357 nodes.push_back(node);
358 return node;
359 }
360
361 /// Create a new reaching definition.
362 template <typename... Args>
363 Def *createDef(Args... args) {
364 auto *def = new (defAllocator.Allocate()) Def(std::forward<Args>(args)...);
365 defs.push_back(def);
366 return def;
367 }
368
369 /// Create a new reaching definition for an existing value in the IR.
370 Def *createDef(Value value, DriveCondition mode) {
371 auto &slot = defsForValues[{value, mode}];
372 if (!slot) {
373 slot = new (defAllocator.Allocate()) Def(value, mode);
374 defs.push_back(slot);
375 }
376 return slot;
377 }
378
379#ifndef NDEBUG
380 void dump(llvm::raw_ostream &os = llvm::dbgs());
381#endif
382
383 /// All nodes in the lattice.
384 std::vector<LatticeNode *> nodes;
385 /// All values in the lattice.
386 std::vector<LatticeValue *> values;
387 /// All reaching defs in the lattice.
388 std::vector<Def *> defs;
389 /// The reaching defs for concrete values in the IR. This map is used to
390 /// create a single def for the same SSA value to allow for pointer equality
391 /// comparisons.
392 DenseMap<std::pair<Value, DriveCondition>, Def *> defsForValues;
393
394private:
395 SpecificBumpPtrAllocator<LatticeValue> valueAllocator;
396 SpecificBumpPtrAllocator<Def> defAllocator;
397 SpecificBumpPtrAllocator<BlockEntry> blockEntryAllocator;
398 SpecificBumpPtrAllocator<BlockExit> blockExitAllocator;
399 SpecificBumpPtrAllocator<ProbeNode> probeAllocator;
400 SpecificBumpPtrAllocator<DriveNode> driveAllocator;
401 SpecificBumpPtrAllocator<SignalNode> signalAllocator;
402
403 // Helper function to get the correct allocator given a lattice node class.
404 template <class T>
405 SpecificBumpPtrAllocator<T> &getAllocator();
406};
407
408// Specializations for the `getAllocator` template that map node types to the
409// correct allocator.
410template <>
411SpecificBumpPtrAllocator<BlockEntry> &Lattice::getAllocator() {
412 return blockEntryAllocator;
413}
414template <>
415SpecificBumpPtrAllocator<BlockExit> &Lattice::getAllocator() {
416 return blockExitAllocator;
417}
418template <>
419SpecificBumpPtrAllocator<ProbeNode> &Lattice::getAllocator() {
420 return probeAllocator;
421}
422template <>
423SpecificBumpPtrAllocator<DriveNode> &Lattice::getAllocator() {
424 return driveAllocator;
425}
426template <>
427SpecificBumpPtrAllocator<SignalNode> &Lattice::getAllocator() {
428 return signalAllocator;
429}
430
431} // namespace
432
433#ifndef NDEBUG
434/// Print the lattice in human-readable form. Useful for debugging.
435void Lattice::dump(llvm::raw_ostream &os) {
436 // Helper functions to quickly come up with unique names for things.
437 llvm::MapVector<Block *, unsigned> blockNames;
438 llvm::MapVector<Value, unsigned> memNames;
439 llvm::MapVector<Def *, unsigned> defNames;
440
441 auto blockName = [&](Block *block) {
442 unsigned id = blockNames.insert({block, blockNames.size()}).first->second;
443 return std::string("bb") + llvm::utostr(id);
444 };
445
446 auto memName = [&](DefSlot value) {
447 unsigned id =
448 memNames.insert({getSlot(value), memNames.size()}).first->second;
449 return std::string("mem") + llvm::utostr(id) +
450 (isDelayed(value) ? "#" : "");
451 };
452
453 auto defName = [&](Def *def) {
454 unsigned id = defNames.insert({def, defNames.size()}).first->second;
455 return std::string("def") + llvm::utostr(id);
456 };
457
458 // Ensure the blocks are named in the order they were created.
459 for (auto *node : nodes)
460 if (auto *entry = dyn_cast<BlockEntry>(node))
461 blockName(entry->block);
462
463 // Iterate over all block entry nodes.
464 os << "lattice {\n";
465 for (auto *node : nodes) {
466 auto *entry = dyn_cast<BlockEntry>(node);
467 if (!entry)
468 continue;
469
470 // Print the opening braces and predecessors for the block.
471 os << " " << blockName(entry->block) << ":";
472 if (entry->predecessors.empty()) {
473 os << " // no predecessors";
474 } else {
475 os << " // from";
476 for (auto *node : entry->predecessors)
477 os << " " << blockName(node->block);
478 }
479 os << "\n";
480
481 // Print all nodes following the block entry, up until the block exit.
482 auto *value = entry->valueAfter;
483 while (true) {
484 // Print the needed defs at this lattice point.
485 if (!value->neededDefs.empty()) {
486 os << " -> need";
487 for (auto mem : value->neededDefs)
488 os << " " << memName(blockingSlot(mem));
489 os << "\n";
490 }
491 if (!value->reachingDefs.empty()) {
492 os << " -> def";
493 for (auto [mem, def] : value->reachingDefs) {
494 os << " " << memName(mem) << "=" << defName(def);
495 if (def->condition.isNever())
496 os << "[N]";
497 else if (def->condition.isAlways())
498 os << "[A]";
499 else
500 os << "[C]";
501 }
502 os << "\n";
503 }
504 if (isa<BlockExit>(value->nodeAfter))
505 break;
506
507 // Print the node.
508 if (auto *node = dyn_cast<ProbeNode>(value->nodeAfter))
509 os << " probe " << memName(blockingSlot(node->slot)) << "\n";
510 else if (auto *node = dyn_cast<DriveNode>(value->nodeAfter))
511 os << " drive " << memName(node->slot) << "\n";
512 else if (auto *node = dyn_cast<SignalNode>(value->nodeAfter))
513 os << " signal " << memName(node->getSlot()) << "\n";
514 else
515 os << " unknown\n";
516
517 // Advance to the next node.
518 value = cast<OpNode>(value->nodeAfter)->valueAfter;
519 }
520
521 // Print the closing braces and successors for the block.
522 auto *exit = cast<BlockExit>(value->nodeAfter);
523 if (isa<WaitOp>(exit->terminator))
524 os << " wait";
525 else if (exit->successors.empty())
526 os << " halt";
527 else
528 os << " goto";
529 for (auto *node : exit->successors)
530 os << " " << blockName(node->block);
531 if (exit->suspends)
532 os << " // suspends";
533 os << "\n";
534 }
535
536 // Dump the memories.
537 for (auto [mem, id] : memNames)
538 os << " mem" << id << ": " << mem << "\n";
539
540 os << "}\n";
541}
542#endif
543
544//===----------------------------------------------------------------------===//
545// Projection Utilities
546//===----------------------------------------------------------------------===//
547
548namespace {
549/// A single projection operation, together with the concrete value of the
550/// signal being projected into. This helper is useful to unpack a stack of
551/// projections to get the targeted value, change it, and then pack the stack
552/// back up into an updated value.
553struct Projection {
554 /// The projection operation.
555 Operation *op;
556 /// The value being projected into. This is not the op's target signal, but
557 /// rather the value of the op's target signal.
558 Value into;
559};
560} // namespace
561
562/// A stack of projection operations.
563using ProjectionStack = SmallVector<Projection>;
564
565/// Collect the `llhd.sig.*` projection ops between `fromSignal` and `toSlot`.
566/// The `fromSignal` value must be derived from `toSlot` through only
567/// `llhd.sig.*` operations. The result is a stack; the op producing
568/// `fromSignal` appears first in the vector, while the final op projecting into
569/// `toSlot` appears last in the vector.
570static ProjectionStack getProjections(Value fromSignal, Value toSlot) {
571 ProjectionStack stack;
572 while (fromSignal != toSlot) {
573 auto *op = cast<OpResult>(fromSignal).getOwner();
574 stack.push_back({op, Value()});
575 fromSignal = op->getOperand(0);
576 }
577 return stack;
578}
579
580/// Resolve a stack of projections by taking a value and descending into its
581/// subelements until the final value targeted by the projection stack remains,
582/// which is then returned. Also updates the `into` fields of the projections in
583/// the stack to represent the concrete value of intermediate projections. This
584/// allows a later `packProjections` to reconstruct the root value with the
585/// field targeted by the projection updated to a different value.
586static Value unpackProjections(OpBuilder &builder, Value value,
587 ProjectionStack &projections) {
588 for (auto &projection : llvm::reverse(projections)) {
589 projection.into = value;
590 value = TypeSwitch<Operation *, Value>(projection.op)
591 .Case<SigArrayGetOp>([&](auto op) {
592 return builder.createOrFold<hw::ArrayGetOp>(
593 op.getLoc(), value, op.getIndex());
594 })
595 .Case<SigStructExtractOp>([&](auto op) {
596 return builder.createOrFold<hw::StructExtractOp>(
597 op.getLoc(), value, op.getFieldAttr());
598 })
599 .Case<SigExtractOp>([&](auto op) {
600 auto type = cast<RefType>(op.getType()).getNestedType();
601 auto width = type.getIntOrFloatBitWidth();
602 return comb::createDynamicExtract(builder, op.getLoc(), value,
603 op.getLowBit(), width);
604 });
605 }
606 return value;
607}
608
609/// Undo a stack of projections by taking the value of the projected field and
610/// injecting it into the surrounding aggregate value that the projection
611/// targets. This requires the `into` fields to be set to the concrete value of
612/// the intermediate projections.
613///
614/// Example:
615/// ```
616/// auto projections = getProjections(...);
617/// auto fieldValue = unpackProjections(..., aggregateValue, projections);
618/// fieldValue = update(fieldValue);
619/// aggregateValue = packProjections(..., fieldValue, projections);
620/// ```
621static Value packProjections(OpBuilder &builder, Value value,
622 const ProjectionStack &projections) {
623 for (auto projection : projections) {
624 value = TypeSwitch<Operation *, Value>(projection.op)
625 .Case<SigArrayGetOp>([&](auto op) {
626 return builder.createOrFold<hw::ArrayInjectOp>(
627 op.getLoc(), projection.into, op.getIndex(), value);
628 })
629 .Case<SigStructExtractOp>([&](auto op) {
630 return builder.createOrFold<hw::StructInjectOp>(
631 op.getLoc(), projection.into, op.getFieldAttr(), value);
632 })
633 .Case<SigExtractOp>([&](auto op) {
634 return comb::createDynamicInject(builder, op.getLoc(),
635 projection.into,
636 op.getLowBit(), value);
637 });
638 }
639 return value;
640}
641
642//===----------------------------------------------------------------------===//
643// Drive/Probe to SSA Value Promotion
644//===----------------------------------------------------------------------===//
645
646namespace {
647/// The main promoter forwarding drives to probes within a region.
648struct Promoter {
649 Promoter(Region &region) : region(region) {}
650 LogicalResult promote();
651
652 void findPromotableSlots();
653 Value resolveSlot(Value projectionOrSlot);
654
655 void captureAcrossWait();
656 void captureAcrossWait(Value value, ArrayRef<WaitOp> waitOps,
657 Liveness &liveness, DominanceInfo &dominance);
658
659 void constructLattice();
660 void propagateBackward();
661 void propagateBackward(LatticeNode *node);
662 void propagateForward();
663 void propagateForward(bool optimisticMerges, DominanceInfo &dominance);
664 void propagateForward(LatticeNode *node, bool optimisticMerges,
665 DominanceInfo &dominance);
666 void markDirty(LatticeNode *node);
667
668 void insertProbeBlocks();
669 void insertProbes();
670 void insertProbes(BlockEntry *node);
671
672 void insertDriveBlocks();
673 void insertDrives();
674 void insertDrives(BlockExit *node);
675 void insertDrives(DriveNode *node);
676
677 void resolveDefinitions();
678 void resolveDefinitions(ProbeNode *node);
679 void resolveDefinitions(DriveNode *node);
680 void resolveDefinitionValue(DriveNode *node);
681 void resolveDefinitionCondition(DriveNode *node);
682
683 void insertBlockArgs();
684 bool insertBlockArgs(BlockEntry *node);
685 void replaceValueWith(Value oldValue, Value newValue);
686
687 void removeUnusedLocalSignals();
688 void removeUnusedLocalSignal(SignalNode *signal);
689
690 /// The region we are promoting in.
691 Region &region;
692
693 /// The slots we are promoting. Mostly `llhd.sig` ops in practice. This
694 /// establishes a deterministic order for slot allocations, such that
695 /// everything else in the pass can operate using unordered maps and sets.
696 SmallVector<Value> slots;
697 /// A mapping from projections to the root slot they are projecting into.
698 SmallDenseMap<Value, Value> projections;
699 /// A set of all promotable signal SSA values. This is the union of `slots`
700 /// and `projections` of those slots.
701 SmallDenseSet<Value> promotable;
702
703 /// The lattice used to propagate needed definitions backwards and reaching
704 /// definitions forwards.
705 Lattice lattice;
706 /// A worklist of lattice nodes used within calls to `propagate*`.
707 SmallPtrSet<LatticeNode *, 4> dirtyNodes;
708
709 /// Helper to clean up unused ops.
710 UnusedOpPruner pruner;
711};
712} // namespace
713
714LogicalResult Promoter::promote() {
715 if (region.empty())
716 return success();
717
718 findPromotableSlots();
719 captureAcrossWait();
720
721 // If there are no promotable slots we can still return after ensuring any
722 // values live across waits were captured as block arguments above. This
723 // keeps the pass semantics while allowing wait capturing to run
724 // independently of slot promotion.
725 if (slots.empty())
726 return success();
727
728 constructLattice();
729 LLVM_DEBUG({
730 llvm::dbgs() << "Initial lattice:\n";
731 lattice.dump();
732 });
733
734 // Propagate the needed definitions backward across the lattice.
735 propagateBackward();
736 LLVM_DEBUG({
737 llvm::dbgs() << "After backward propagation:\n";
738 lattice.dump();
739 });
740
741 // Insert probes wherever a def is needed for the first time.
742 insertProbeBlocks();
743 insertProbes();
744 LLVM_DEBUG({
745 llvm::dbgs() << "After probe insertion:\n";
746 lattice.dump();
747 });
748
749 // Propagate the reaching definitions forward across the lattice.
750 propagateForward();
751 LLVM_DEBUG({
752 llvm::dbgs() << "After forward propagation:\n";
753 lattice.dump();
754 });
755
756 // Resolve definitions.
757 resolveDefinitions();
758
759 // Insert drives wherever a reaching def can no longer propagate.
760 insertDriveBlocks();
761 insertDrives();
762 LLVM_DEBUG({
763 llvm::dbgs() << "After def resolution and drive insertion:\n";
764 lattice.dump();
765 });
766
767 // Insert the necessary block arguments.
768 insertBlockArgs();
769
770 // Remove local signals that are never probed.
771 removeUnusedLocalSignals();
772
773 // Erase operations that have become unused.
774 pruner.eraseNow();
775
776 return success();
777}
778
779/// Identify any promotable slots probed or driven under the current region.
780void Promoter::findPromotableSlots() {
781 SmallPtrSet<Value, 8> seenSlots;
782 SmallPtrSet<Operation *, 8> checkedUsers;
783 SmallVector<Operation *, 8> userWorklist;
784
785 region.walk([&](Operation *op) {
786 for (auto operand : op->getOperands()) {
787 if (!seenSlots.insert(operand).second)
788 continue;
789
790 // We can only promote probes and drives on a locally-defined signal.
791 // Other signals, such as the ones brought into a module through a port,
792 // have an unknown aliasing relationship with the other ports.
793 if (!operand.getDefiningOp<llhd::SignalOp>())
794 continue;
795
796 // Ensure the slot is not used in any way we cannot reason about.
797 auto checkUser = [&](Operation *user) -> bool {
798 // We don't support nested probes and drives.
799 if (region.isProperAncestor(user->getParentRegion()))
800 return false;
801 // Ignore uses outside of the region.
802 if (user->getParentRegion() != &region)
803 return true;
804 // Projection operations are okay.
805 if (isa<SigArrayGetOp, SigExtractOp, SigStructExtractOp>(user)) {
806 for (auto *projectionUser : user->getUsers())
807 if (checkedUsers.insert(projectionUser).second)
808 userWorklist.push_back(projectionUser);
809 projections.insert({user->getResult(0), operand});
810 return true;
811 }
812 return isa<ProbeOp>(user) || isBlockingDrive(user) ||
813 isDeltaDrive(user);
814 };
815 checkedUsers.clear();
816 if (!llvm::all_of(operand.getUsers(), [&](auto *user) {
817 auto allOk = true;
818 if (checkedUsers.insert(user).second)
819 userWorklist.push_back(user);
820 while (!userWorklist.empty() && allOk)
821 allOk &= checkUser(userWorklist.pop_back_val());
822 userWorklist.clear();
823 return allOk;
824 }))
825 continue;
826
827 slots.push_back(operand);
828 }
829 });
830
831 // Populate `promotable` with the slots and projections we are promoting.
832 promotable.insert(slots.begin(), slots.end());
833 for (auto [projection, slot] : llvm::make_early_inc_range(projections))
834 if (!promotable.contains(slot))
835 projections.erase(projection);
836 for (auto [projection, slot] : projections)
837 promotable.insert(projection);
838
839 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size() << " promotable slots, "
840 << promotable.size() << " promotable values\n");
841}
842
843/// Resolve SSA values in `projection` to the `slot` they are projecting into.
844/// Simply returns the given value if it is not in `projections`.
845Value Promoter::resolveSlot(Value projectionOrSlot) {
846 if (auto slot = projections.lookup(projectionOrSlot))
847 return slot;
848 return projectionOrSlot;
849}
850
851/// Explicitly capture any probes that are live across an `llhd.wait` as block
852/// arguments and destination operand of that wait. This ensures that replacing
853/// the probe with a reaching definition later on will capture the value of the
854/// reaching definition before the wait.
855void Promoter::captureAcrossWait() {
856 if (region.hasOneBlock())
857 return;
858
859 SmallVector<WaitOp> waitOps;
860 for (auto &block : region)
861 if (auto waitOp = dyn_cast<WaitOp>(block.getTerminator()))
862 waitOps.push_back(waitOp);
863
864 DominanceInfo dominance(region.getParentOp());
865 Liveness liveness(region.getParentOp());
866
867 llvm::DenseSet<Value> alreadyCaptured;
868
869 auto isDefinedInRegion = [&](Value v) {
870 return v.getParentRegion() == &region;
871 };
872
873 for (auto waitOp : waitOps) {
874 Block *waitBlock = waitOp->getBlock();
875 const auto &liveOutValues = liveness.getLiveOut(waitBlock);
876
877 for (Value v : liveOutValues) {
878 if (!isDefinedInRegion(v))
879 continue;
880
881 if (!alreadyCaptured.insert(v).second)
882 continue;
883
884 captureAcrossWait(v, waitOps, liveness, dominance);
885 }
886 }
887}
888
889/// Add a probe as block argument to a list of wait ops and update uses of the
890/// probe to use the added block arguments as appropriate. This may insert
891/// additional block arguments in case the probe and added block arguments both
892/// reach the same block.
893void Promoter::captureAcrossWait(Value value, ArrayRef<WaitOp> waitOps,
894 Liveness &liveness, DominanceInfo &dominance) {
895 LLVM_DEBUG({
896 llvm::dbgs() << "Capture " << value << "\n";
897 for (auto waitOp : waitOps)
898 llvm::dbgs() << "- Across " << waitOp << "\n";
899 });
900
901 // Calculate the merge points for this probe once it gets promoted to block
902 // arguments across the wait ops.
903 auto &domTree = dominance.getDomTree(&region);
904 llvm::IDFCalculatorBase<Block, false> idfCalculator(domTree);
905
906 // Calculate the set of blocks which will define this probe as a distinct
907 // value.
908 SmallPtrSet<Block *, 4> definingBlocks;
909 definingBlocks.insert(value.getParentBlock());
910 for (auto waitOp : waitOps)
911 definingBlocks.insert(waitOp.getDest());
912 idfCalculator.setDefiningBlocks(definingBlocks);
913
914 // Calculate where the probe is live.
915 SmallPtrSet<Block *, 16> liveInBlocks;
916 for (auto &block : region)
917 if (liveness.getLiveness(&block)->isLiveIn(value))
918 liveInBlocks.insert(&block);
919 idfCalculator.setLiveInBlocks(liveInBlocks);
920
921 // Calculate the merge points where we will have to insert block arguments for
922 // this probe.
923 SmallVector<Block *> mergePointsVec;
924 idfCalculator.calculate(mergePointsVec);
925 SmallPtrSet<Block *, 16> mergePoints(mergePointsVec.begin(),
926 mergePointsVec.end());
927 for (auto waitOp : waitOps)
928 mergePoints.insert(waitOp.getDest());
929 LLVM_DEBUG(llvm::dbgs() << "- " << mergePoints.size() << " merge points\n");
930
931 // Perform a depth-first search starting at the block containing the probe,
932 // which dominates all its uses. When we encounter a block that is a merge
933 // point, insert a block argument.
934 struct WorklistItem {
935 DominanceInfoNode *domNode;
936 Value reachingDef;
937 };
938 SmallVector<WorklistItem> worklist;
939 worklist.push_back({domTree.getNode(value.getParentBlock()), value});
940
941 while (!worklist.empty()) {
942 auto item = worklist.pop_back_val();
943 auto *block = item.domNode->getBlock();
944
945 // If this block is a merge point, insert a block argument for the probe.
946 if (mergePoints.contains(block))
947 item.reachingDef = block->addArgument(value.getType(), value.getLoc());
948
949 // Replace any uses of the probe in this block with the current reaching
950 // definition.
951 for (auto &op : *block)
952 op.replaceUsesOfWith(value, item.reachingDef);
953
954 // If the terminator of this block branches to a merge point, add the
955 // current reaching definition as a destination operand.
956 if (auto branchOp = dyn_cast<BranchOpInterface>(block->getTerminator())) {
957 for (auto &blockOperand : branchOp->getBlockOperands())
958 if (mergePoints.contains(blockOperand.get()))
959 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
960 .append(item.reachingDef);
961 } else if (auto waitOp = dyn_cast<WaitOp>(block->getTerminator())) {
962 if (mergePoints.contains(waitOp.getDest()))
963 waitOp.getDestOperandsMutable().append(item.reachingDef);
964 }
965
966 for (auto *child : item.domNode->children())
967 worklist.push_back({child, item.reachingDef});
968 }
969}
970
971//===----------------------------------------------------------------------===//
972// Lattice Construction and Propagation
973//===----------------------------------------------------------------------===//
974
975/// Populate the lattice with nodes and values corresponding to the blocks and
976/// relevant operations in the region we're promoting.
977void Promoter::constructLattice() {
978 // Create entry nodes for each block.
980 for (auto &block : region) {
981 auto *entry = lattice.createNode<BlockEntry>(&block, lattice.createValue());
982 blockEntries.insert({&block, entry});
983 }
984
985 // Create nodes for each operation that is relevant for the pass.
986 for (auto &block : region) {
987 auto *valueBefore = blockEntries.lookup(&block)->valueAfter;
988
989 // Handle operations.
990 for (auto &op : block.without_terminator()) {
991 // Handle probes.
992 if (auto probeOp = dyn_cast<ProbeOp>(op)) {
993 if (!promotable.contains(probeOp.getSignal()))
994 continue;
995 auto *node = lattice.createNode<ProbeNode>(
996 probeOp, resolveSlot(probeOp.getSignal()), valueBefore,
997 lattice.createValue());
998 valueBefore = node->valueAfter;
999 continue;
1000 }
1001
1002 // Handle drives.
1003 if (auto driveOp = dyn_cast<DriveOp>(op)) {
1004 if (!isBlockingDrive(&op) && !isDeltaDrive(&op))
1005 continue;
1006 if (!promotable.contains(driveOp.getSignal()))
1007 continue;
1008 auto condition = DriveCondition::always();
1009 if (auto enable = driveOp.getEnable())
1010 condition = DriveCondition::conditional(enable);
1011 // Drives that target a slot directly provide the driven value as a
1012 // definition for the slot. Drives to projections have their value
1013 // calculated later on.
1014 auto slot = resolveSlot(driveOp.getSignal());
1015 auto *def = driveOp.getSignal() == slot
1016 ? lattice.createDef(driveOp.getValue(), condition)
1017 : lattice.createDef(driveOp->getBlock(),
1018 getStoredType(slot), condition);
1019 auto *node = lattice.createNode<DriveNode>(
1020 driveOp, slot, def, valueBefore, lattice.createValue());
1021 valueBefore = node->valueAfter;
1022 continue;
1023 }
1024
1025 // Handle local signals.
1026 if (auto signalOp = dyn_cast<SignalOp>(op)) {
1027 if (!promotable.contains(signalOp))
1028 continue;
1029 auto *def =
1030 lattice.createDef(signalOp.getInit(), DriveCondition::never());
1031 auto *node = lattice.createNode<SignalNode>(signalOp, def, valueBefore,
1032 lattice.createValue());
1033 valueBefore = node->valueAfter;
1034 continue;
1035 }
1036 }
1037
1038 // Create the exit node for the block.
1039 auto *exit = lattice.createNode<BlockExit>(&block, valueBefore);
1040 for (auto *otherBlock : exit->terminator->getSuccessors()) {
1041 auto *otherEntry = blockEntries.lookup(otherBlock);
1042 exit->successors.push_back(otherEntry);
1043 otherEntry->predecessors.push_back(exit);
1044 }
1045 }
1046}
1047
1048/// Propagate the lattice values backwards against control flow until a fixed
1049/// point is reached.
1050void Promoter::propagateBackward() {
1051 for (auto *node : lattice.nodes)
1052 propagateBackward(node);
1053 while (!dirtyNodes.empty()) {
1054 auto *node = *dirtyNodes.begin();
1055 dirtyNodes.erase(node);
1056 propagateBackward(node);
1057 }
1058}
1059
1060/// Propagate the lattice value after a node backward to the value before a
1061/// node.
1062void Promoter::propagateBackward(LatticeNode *node) {
1063 auto update = [&](LatticeValue *value, auto &neededDefs) {
1064 if (value->neededDefs != neededDefs) {
1065 value->neededDefs = neededDefs;
1066 markDirty(value->nodeBefore);
1067 }
1068 };
1069
1070 // Probes need a definition for the probed slot to be available.
1071 if (auto *probe = dyn_cast<ProbeNode>(node)) {
1072 auto needed = probe->valueAfter->neededDefs;
1073 needed.insert(probe->slot);
1074 update(probe->valueBefore, needed);
1075 return;
1076 }
1077
1078 // Blocking unconditional drives to an entire slot kill the need for a
1079 // definition to be available, since they provide a definition themselves.
1080 // Conditional drives propagate the need for a definition, since they have to
1081 // forward an incoming reaching def in case they are disabled. Drives to
1082 // projections need a definition to be available such that they can partially
1083 // update it.
1084 if (auto *drive = dyn_cast<DriveNode>(node)) {
1085 auto needed = drive->valueAfter->neededDefs;
1086 if (drive->drivesProjection())
1087 needed.insert(getSlot(drive->slot));
1088 else if (!isDelayed(drive->slot) && !drive->getDriveOp().getEnable())
1089 needed.erase(getSlot(drive->slot));
1090 update(drive->valueBefore, needed);
1091 return;
1092 }
1093
1094 // Local signal declarations kill the need for a definition to be available,
1095 // since the op is the first time a signal becomes available and the op
1096 // provides an initial value as a definition.
1097 if (auto *signal = dyn_cast<SignalNode>(node)) {
1098 auto needed = signal->valueAfter->neededDefs;
1099 needed.erase(signal->getSignalOp());
1100 update(signal->valueBefore, needed);
1101 return;
1102 }
1103
1104 // Block entries simply trigger updates to all their predecessors.
1105 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1106 for (auto *predecessor : entry->predecessors)
1107 markDirty(predecessor);
1108 return;
1109 }
1110
1111 // Block exits merge any needed definitions from their successors.
1112 if (auto *exit = dyn_cast<BlockExit>(node)) {
1113 if (exit->suspends)
1114 return;
1115 SmallDenseSet<Value, 1> needed;
1116 for (auto *successors : exit->successors)
1117 needed.insert(successors->valueAfter->neededDefs.begin(),
1118 successors->valueAfter->neededDefs.end());
1119 update(exit->valueBefore, needed);
1120 return;
1121 }
1122
1123 assert(false && "unhandled node in backward propagation");
1124}
1125
1126void Promoter::propagateForward() {
1127 DominanceInfo dominance(region.getParentOp());
1128 propagateForward(true, dominance);
1129 propagateForward(false, dominance);
1130}
1131
1132/// Propagate the lattice values forwards along with control flow until a fixed
1133/// point is reached. If `optimisticMerges` is true, block entry points
1134/// propagate definitions from their predecessors into the block without
1135/// creating a merge definition even if the definition is not available in all
1136/// predecessors. This is overly optimistic, but initially helps definitions
1137/// propagate through loop structures. If `optimisticMerges` is false, block
1138/// entry points create merge definitions for definitions that are not available
1139/// in all predecessors.
1140void Promoter::propagateForward(bool optimisticMerges,
1141 DominanceInfo &dominance) {
1142 for (auto *node : lattice.nodes)
1143 propagateForward(node, optimisticMerges, dominance);
1144 while (!dirtyNodes.empty()) {
1145 auto *node = *dirtyNodes.begin();
1146 dirtyNodes.erase(node);
1147 propagateForward(node, optimisticMerges, dominance);
1148 }
1149}
1150
1151/// Propagate the lattice value before a node forward to the value after a node.
1152void Promoter::propagateForward(LatticeNode *node, bool optimisticMerges,
1153 DominanceInfo &dominance) {
1154 auto update = [&](LatticeValue *value, auto &reachingDefs) {
1155 if (value->reachingDefs != reachingDefs) {
1156 value->reachingDefs = reachingDefs;
1157 markDirty(value->nodeAfter);
1158 }
1159 };
1160
1161 // Probes simply propagate any reaching defs.
1162 if (auto *probe = dyn_cast<ProbeNode>(node)) {
1163 update(probe->valueAfter, probe->valueBefore->reachingDefs);
1164 return;
1165 }
1166
1167 // Drives propagate the driven value as a reaching def. Blocking drives kill
1168 // earlier non-blocking drives. This reflects Verilog and VHDL behaviour,
1169 // where a drive sequence like
1170 //
1171 // a <= #10ns 42; // A
1172 // a <= 43; // B
1173 // a = 44; // C
1174 //
1175 // would see (B) override (A), because it happens earlier, and (C) override
1176 // (B), because it in turn happens earlier.
1177 if (auto *drive = dyn_cast<DriveNode>(node)) {
1178 auto reaching = drive->valueBefore->reachingDefs;
1179
1180 // If the drive is conditional or driving a projection of a slot, merge any
1181 // incoming reaching def into the reaching def created by the drive. This is
1182 // necessary since a conditional drive following an unconditional drive may
1183 // have to insert a multiplexer to forward to subsequent probes.
1184 if (drive->drivesProjection() || drive->getDriveOp().getEnable()) {
1185 auto *inDef = reaching.lookup(drive->slot);
1186 if (inDef) {
1187 if (drive->def->value != inDef->value)
1188 drive->def->value = {};
1189 if (inDef->condition.isAlways() || drive->def->condition.isAlways())
1190 drive->def->condition = DriveCondition::always();
1191 else if (drive->def->condition != inDef->condition)
1192 drive->def->condition = DriveCondition::conditional();
1193 }
1194 }
1195
1196 reaching[drive->slot] = drive->def;
1197 if (!isDelayed(drive->slot))
1198 reaching.erase(delayedSlot(getSlot(drive->slot)));
1199 update(drive->valueAfter, reaching);
1200 return;
1201 }
1202
1203 // Signals propagate their initial value as a reaching def. They also kill any
1204 // earlier definitions, which should not be able to reach the signal in the
1205 // first place.
1206 if (auto *signal = dyn_cast<SignalNode>(node)) {
1207 auto reaching = signal->valueBefore->reachingDefs;
1208 reaching[signal->getSlot()] = signal->def;
1209 reaching.erase(delayedSlot(signal->getSignalOp()));
1210 update(signal->valueAfter, reaching);
1211 return;
1212 }
1213
1214 // Block entry points propagate any reaching definitions available in all
1215 // predecessors, plus any probes inserted locally.
1216 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1217 // Propagate reaching definitions for each inserted probe.
1219 for (auto [slot, insertedProbe] : entry->insertedProbes) {
1220 reaching[blockingSlot(slot)] = insertedProbe;
1221 reaching[delayedSlot(slot)] = insertedProbe;
1222 }
1223
1224 // Propagate reaching definitions from predecessors, creating new
1225 // definitions in case of a merge.
1227 for (auto *predecessor : entry->predecessors) {
1228 if (predecessor->suspends)
1229 continue;
1230 // Propogate signal definitions only to blocks that are dominated by the
1231 // signal itself.
1232 for (auto &defEntry : predecessor->valueBefore->reachingDefs) {
1233 auto slot = getSlot(defEntry.getFirst());
1234 auto *slotBlock = slot.getDefiningOp()->getBlock();
1235 if (!dominance.dominates(slotBlock, entry->block))
1236 continue;
1237 reachingDefs.insert(defEntry);
1238 }
1239 }
1240
1241 for (auto pair : reachingDefs) {
1242 DefSlot slot = pair.first;
1243 Def *reachingDef = pair.second;
1244 DriveCondition reachingDefCondition = reachingDef->condition;
1245
1246 // Do not override inserted probes.
1247 if (reaching.contains(slot))
1248 continue;
1249
1250 // Check if all predecessors provide a definition for this slot. If any
1251 // multiple definitions for the same slot reach us, simply set the
1252 // `reachingDef` to null such that we can insert a new merge definition.
1253 // Separately track whether the drive mode of all definitions is
1254 // identical. This is often the case, for example when the definitions of
1255 // two unconditional drives converge, and we would like to preserve that
1256 // both drives were unconditional, even if the driven value differs.
1257 if (llvm::any_of(entry->predecessors, [&](auto *predecessor) {
1258 return predecessor->suspends;
1259 }))
1260 continue;
1261
1262 for (auto *predecessor : entry->predecessors) {
1263 auto otherDef = predecessor->valueBefore->reachingDefs.lookup(slot);
1264 if (!otherDef && optimisticMerges)
1265 continue;
1266 // If the definitions are not identical, indicate that we will have
1267 // to create a new merge def.
1268 if (reachingDef != otherDef)
1269 reachingDef = nullptr;
1270 // If the definitions have different modes, indicate that we will
1271 // have to create a conditional drive later.
1272 auto condition =
1273 otherDef ? otherDef->condition : DriveCondition::never();
1274 if (reachingDefCondition != condition)
1275 reachingDefCondition = DriveCondition::conditional();
1276 }
1277
1278 // Create a merge definition if different definitions reach us from our
1279 // predecessors.
1280 if (!reachingDef)
1281 reachingDef = entry->mergedDefs.lookup(slot);
1282 if (!reachingDef) {
1283 reachingDef = lattice.createDef(entry->block, getStoredType(slot),
1284 reachingDefCondition);
1285 entry->mergedDefs.insert({slot, reachingDef});
1286 } else {
1287 reachingDef->condition = reachingDefCondition;
1288 }
1289 reaching.insert({slot, reachingDef});
1290 }
1291
1292 update(entry->valueAfter, reaching);
1293 return;
1294 }
1295
1296 // Block exits simply trigger updates to all their successors.
1297 if (auto *exit = dyn_cast<BlockExit>(node)) {
1298 for (auto *successor : exit->successors)
1299 markDirty(successor);
1300 return;
1301 }
1302
1303 assert(false && "unhandled node in forward propagation");
1304}
1305
1306/// Mark a lattice node to be updated during propagation.
1307void Promoter::markDirty(LatticeNode *node) {
1308 assert(node);
1309 dirtyNodes.insert(node);
1310}
1311
1312//===----------------------------------------------------------------------===//
1313// Drive/Probe Insertion
1314//===----------------------------------------------------------------------===//
1315
1316/// Insert additional probe blocks where needed. This can happen if a definition
1317/// is needed in a block which has a suspending and non-suspending predecessor.
1318/// In that case we would like to insert probes in the predecessor blocks, but
1319/// cannot do so because of the suspending predecessor.
1320void Promoter::insertProbeBlocks() {
1321 // Find all blocks that have any needed definition that can't propagate beyond
1322 // one of its predecessors. If that's the case, we need an additional probe
1323 // block after that predecessor.
1324 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1325 for (auto *node : lattice.nodes) {
1326 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1327 SmallVector<Value> partialSlots;
1328 for (auto slot : entry->valueAfter->neededDefs) {
1329 unsigned numIncoming = 0;
1330 for (auto *predecessor : entry->predecessors)
1331 if (predecessor->valueBefore->neededDefs.contains(slot))
1332 ++numIncoming;
1333 if (numIncoming != 0 && numIncoming != entry->predecessors.size())
1334 partialSlots.push_back(slot);
1335 }
1336 for (auto *predecessor : entry->predecessors)
1337 if (llvm::any_of(partialSlots, [&](auto slot) {
1338 return !predecessor->valueBefore->neededDefs.contains(slot);
1339 }))
1340 worklist.insert({predecessor, entry});
1341 }
1342 }
1343
1344 // Insert probe blocks after all blocks we have identified.
1345 for (auto [predecessor, successor] : worklist) {
1346 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe block towards " << successor
1347 << " after " << *predecessor->terminator << "\n");
1348 OpBuilder builder(predecessor->terminator);
1349 auto *newBlock = builder.createBlock(successor->block);
1350 for (auto oldArg : successor->block->getArguments())
1351 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1352 cf::BranchOp::create(builder, predecessor->terminator->getLoc(),
1353 successor->block, newBlock->getArguments());
1354 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1355 if (blockOp.get() == successor->block)
1356 blockOp.set(newBlock);
1357
1358 // Create new nodes in the lattice for the added block.
1359 auto *value = lattice.createValue();
1360 value->neededDefs = successor->valueAfter->neededDefs;
1361 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1362 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1363 newEntry->predecessors.push_back(predecessor);
1364 newExit->successors.push_back(successor);
1365 llvm::replace(successor->predecessors, predecessor, newExit);
1366 llvm::replace(predecessor->successors, successor, newEntry);
1367 }
1368}
1369
1370/// Insert probes wherever a definition is needed for the first time. This is
1371/// the case in the entry block, after any suspensions, and after operations
1372/// that have unknown effects on memory slots.
1373void Promoter::insertProbes() {
1374 for (auto *node : lattice.nodes) {
1375 if (auto *entry = dyn_cast<BlockEntry>(node))
1376 insertProbes(entry);
1377 }
1378}
1379
1380/// Insert probes at the beginning of a block for definitions that are needed in
1381/// this block but not in its predecessors.
1382void Promoter::insertProbes(BlockEntry *node) {
1383 auto builder = OpBuilder::atBlockBegin(node->block);
1384 for (auto neededDef : slots) {
1385 if (!node->valueAfter->neededDefs.contains(neededDef))
1386 continue;
1387 if (!node->predecessors.empty() &&
1388 llvm::all_of(node->predecessors, [&](auto *predecessor) {
1389 return predecessor->valueBefore->neededDefs.contains(neededDef);
1390 }))
1391 continue;
1392 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe for " << neededDef
1393 << " in block " << node->block << "\n");
1394 OpBuilder::InsertionGuard guard(builder);
1395 // If the neededDef is in the same block, one can't just insert at top.
1396 if (Operation *op = neededDef.getDefiningOp()) {
1397 if (op->getBlock() == node->block)
1398 builder.setInsertionPointAfterValue(neededDef);
1399 }
1400 auto value = ProbeOp::create(builder, neededDef.getLoc(), neededDef);
1401 auto *def = lattice.createDef(value, DriveCondition::never());
1402 node->insertedProbes.push_back({neededDef, def});
1403 }
1404}
1405
1406/// Insert additional drive blocks where needed. This can happen if a definition
1407/// continues into some of a block's successors, but not all of them.
1408void Promoter::insertDriveBlocks() {
1409 // Find all blocks that have any reaching definition that can't propagate
1410 // beyond one of its successors. If that's the case, we need an additional
1411 // drive block before that successor.
1412 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1413 for (auto *node : lattice.nodes) {
1414 if (auto *exit = dyn_cast<BlockExit>(node)) {
1415 SmallVector<DefSlot> partialSlots;
1416 for (auto [slot, reachingDef] : exit->valueBefore->reachingDefs) {
1417 if (reachingDef->condition.isNever())
1418 continue;
1419 unsigned numContinues = 0;
1420 for (auto *successor : exit->successors)
1421 if (successor->valueAfter->reachingDefs.contains(slot))
1422 ++numContinues;
1423 if (numContinues != 0 && numContinues != exit->successors.size())
1424 partialSlots.push_back(slot);
1425 }
1426 for (auto *successor : exit->successors)
1427 if (llvm::any_of(partialSlots, [&](auto slot) {
1428 return !successor->valueAfter->reachingDefs.contains(slot);
1429 }))
1430 worklist.insert({exit, successor});
1431 }
1432 }
1433
1434 // Insert drive blocks before all blocks we have identified.
1435 for (auto [predecessor, successor] : worklist) {
1436 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive block towards " << successor
1437 << " after " << *predecessor->terminator << "\n");
1438 OpBuilder builder(predecessor->terminator);
1439 auto *newBlock = builder.createBlock(successor->block);
1440 for (auto oldArg : successor->block->getArguments())
1441 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1442 cf::BranchOp::create(builder, predecessor->terminator->getLoc(),
1443 successor->block, newBlock->getArguments());
1444 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1445 if (blockOp.get() == successor->block)
1446 blockOp.set(newBlock);
1447
1448 // Create new nodes in the lattice for the added block.
1449 auto *value = lattice.createValue();
1450 value->neededDefs = successor->valueAfter->neededDefs;
1451 value->reachingDefs = predecessor->valueBefore->reachingDefs;
1452 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1453 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1454 newEntry->predecessors.push_back(predecessor);
1455 newExit->successors.push_back(successor);
1456 llvm::replace(successor->predecessors, predecessor, newExit);
1457 llvm::replace(predecessor->successors, successor, newEntry);
1458 }
1459}
1460
1461/// Insert drives wherever a reaching definition can no longer propagate. This
1462/// is the before any suspensions and before operations that have unknown
1463/// effects on memory slots.
1464void Promoter::insertDrives() {
1465 for (auto *node : lattice.nodes) {
1466 if (auto *exit = dyn_cast<BlockExit>(node))
1467 insertDrives(exit);
1468 else if (auto *drive = dyn_cast<DriveNode>(node))
1469 insertDrives(drive);
1470 }
1471}
1472
1473/// Insert drives at block terminators for definitions that do not propagate
1474/// into successors.
1475void Promoter::insertDrives(BlockExit *node) {
1476 auto builder = OpBuilder::atBlockTerminator(node->block);
1477
1478 ConstantTimeOp epsilonTime;
1479 ConstantTimeOp deltaTime;
1480 auto getTime = [&](bool delta) {
1481 if (delta) {
1482 if (!deltaTime)
1483 deltaTime = ConstantTimeOp::create(builder, node->terminator->getLoc(),
1484 0, "ns", 1, 0);
1485 return deltaTime;
1486 }
1487 if (!epsilonTime)
1488 epsilonTime = ConstantTimeOp::create(builder, node->terminator->getLoc(),
1489 0, "ns", 0, 1);
1490 return epsilonTime;
1491 };
1492
1493 auto insertDriveForSlot = [&](DefSlot slot) {
1494 auto reachingDef = node->valueBefore->reachingDefs.lookup(slot);
1495 if (!reachingDef || reachingDef->condition.isNever())
1496 return;
1497 if (!node->suspends && !node->successors.empty() &&
1498 llvm::all_of(node->successors, [&](auto *successor) {
1499 return successor->valueAfter->reachingDefs.contains(slot);
1500 }))
1501 return;
1502 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive for " << getSlot(slot) << " "
1503 << (isDelayed(slot) ? "(delayed)" : "(blocking)")
1504 << " before " << *node->terminator << "\n");
1505 auto time = getTime(isDelayed(slot));
1506 auto value = reachingDef->getValueOrPlaceholder();
1507 auto enable = reachingDef->condition.isConditional()
1508 ? reachingDef->getConditionOrPlaceholder()
1509 : Value{};
1510 DriveOp::create(builder, getLoc(slot), getSlot(slot), value, time, enable);
1511 };
1512
1513 for (auto slot : slots)
1514 insertDriveForSlot(blockingSlot(slot));
1515 for (auto slot : slots)
1516 insertDriveForSlot(delayedSlot(slot));
1517}
1518
1519/// Remove drives to slots that we are promoting. These have been replaced with
1520/// new drives at block exits.
1521void Promoter::insertDrives(DriveNode *node) {
1522 if (!promotable.contains(getSlot(node->slot)))
1523 return;
1524 LLVM_DEBUG(llvm::dbgs() << "- Removing drive " << *node->op << "\n");
1525 pruner.eraseNow(node->op);
1526 node->op = nullptr;
1527}
1528
1529//===----------------------------------------------------------------------===//
1530// Drive-to-Probe Forwarding
1531//===----------------------------------------------------------------------===//
1532
1533/// Forward definitions throughout the IR.
1534void Promoter::resolveDefinitions() {
1535 for (auto *node : lattice.nodes) {
1536 if (auto *probe = dyn_cast<ProbeNode>(node))
1537 resolveDefinitions(probe);
1538 else if (auto *drive = dyn_cast<DriveNode>(node))
1539 resolveDefinitions(drive);
1540 }
1541}
1542
1543/// Replace probes with the corresponding reaching definition.
1544void Promoter::resolveDefinitions(ProbeNode *node) {
1545 if (!promotable.contains(node->slot))
1546 return;
1547 auto *def = node->valueBefore->reachingDefs.lookup(blockingSlot(node->slot));
1548 assert(def && "no definition reaches probe");
1549
1550 // Gather any projections between the probe and the underlying slot.
1551 auto projections = getProjections(node->op->getOperand(0), node->slot);
1552
1553 // Extract the projected value from the aggregate.
1554 auto builder = OpBuilder(node->op);
1555 auto value = def->getValueOrPlaceholder();
1556 value = unpackProjections(builder, value, projections);
1557
1558 // Replace the probe result with the projected value.
1559 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << *node->op << " with " << value
1560 << "\n");
1561 replaceValueWith(node->op->getResult(0), value);
1562 pruner.eraseNow(node->op);
1563 node->op = nullptr;
1564}
1565
1566/// Mutate reaching definitions for conditional drives or drives that target
1567/// projections of a slot.
1568void Promoter::resolveDefinitions(DriveNode *node) {
1569 if (!promotable.contains(getSlot(node->slot)))
1570 return;
1571
1572 // Check whether the drive already has a value and condition set for its
1573 // reaching def. This is the case for drives to an entire slot. Conditional
1574 // drives and drives to a projection will have no value set initially, in
1575 // which case either the value is null or it is a placeholder.
1576 if (!node->def->value || node->def->valueIsPlaceholder)
1577 resolveDefinitionValue(node);
1578 if (node->def->condition.isConditional() &&
1579 (!node->def->condition.getCondition() ||
1580 node->def->conditionIsPlaceholder))
1581 resolveDefinitionCondition(node);
1582}
1583
1584void Promoter::resolveDefinitionValue(DriveNode *node) {
1585 // Get the slot definition reaching the drive. This is the value the drive has
1586 // to update.
1587 auto *inDef = node->valueBefore->reachingDefs.lookup(node->slot);
1588 assert(inDef && "no definition reaches drive");
1589 auto driveOp = node->getDriveOp();
1590 LLVM_DEBUG(llvm::dbgs() << "- Injecting value for " << driveOp << "\n");
1591
1592 // Gather any projections between the drive and the underlying slot.
1593 auto projections = getProjections(driveOp.getSignal(), getSlot(node->slot));
1594
1595 // Extract the current value from the aggregate.
1596 auto builder = OpBuilder(driveOp);
1597 auto value = inDef->getValueOrPlaceholder();
1598 value = unpackProjections(builder, value, projections);
1599
1600 // Update the value according to the drive.
1601 pruner.eraseLaterIfUnused(value);
1602 if (!driveOp.getEnable())
1603 value = driveOp.getValue();
1604 else
1605 value = builder.createOrFold<comb::MuxOp>(
1606 driveOp.getLoc(), driveOp.getEnable(), driveOp.getValue(), value);
1607
1608 // Inject the updated value into the aggregate.
1609 value = packProjections(builder, value, projections);
1610
1611 // Track the updated slot value in the drive's reaching def.
1612 if (node->def->valueIsPlaceholder) {
1613 auto *placeholder = node->def->value.getDefiningOp();
1614 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1615 "placeholder replaced but valueIsPlaceholder still set");
1616 replaceValueWith(placeholder->getResult(0), value);
1617 pruner.eraseNow(placeholder);
1618 node->def->valueIsPlaceholder = false;
1619 }
1620 node->def->value = value;
1621}
1622
1623void Promoter::resolveDefinitionCondition(DriveNode *node) {
1624 // Get the slot definition reaching the drive. This contains the condition the
1625 // drive has to update.
1626 auto *inDef = node->valueBefore->reachingDefs.lookup(node->slot);
1627 assert(inDef && "no definition reaches drive");
1628 auto driveOp = node->getDriveOp();
1629 LLVM_DEBUG(llvm::dbgs() << "- Mutating condition for " << driveOp << "\n");
1630 OpBuilder builder(driveOp);
1631
1632 // Adjust the drive condition by combining the incoming condition with the
1633 // enable of this drive op.
1634 Value condition;
1635 if (inDef->condition.isNever())
1636 condition = driveOp.getEnable();
1637 else
1638 condition =
1639 builder.createOrFold<comb::OrOp>(driveOp.getLoc(), driveOp.getEnable(),
1640 inDef->getConditionOrPlaceholder());
1641
1642 // Track the updated drive condition in the drive's reaching def.
1643 if (node->def->conditionIsPlaceholder) {
1644 auto *placeholder = node->def->condition.getCondition().getDefiningOp();
1645 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1646 "placeholder replaced but conditionIsPlaceholder still set");
1647 replaceValueWith(placeholder->getResult(0), condition);
1648 pruner.eraseNow(placeholder);
1649 node->def->conditionIsPlaceholder = false;
1650 }
1651 node->def->condition.setCondition(condition);
1652}
1653
1654//===----------------------------------------------------------------------===//
1655// Block Argument Insertion
1656//===----------------------------------------------------------------------===//
1657
1658/// Insert block arguments into the IR.
1659void Promoter::insertBlockArgs() {
1660 bool anyArgsInserted = true;
1661 while (anyArgsInserted) {
1662 anyArgsInserted = false;
1663 for (auto *node : lattice.nodes)
1664 if (auto *entry = dyn_cast<BlockEntry>(node))
1665 anyArgsInserted |= insertBlockArgs(entry);
1666 }
1667}
1668
1669/// Insert block arguments for any merging definitions for which a placeholder
1670/// value has been created. Also insert corresponding successor operands to any
1671/// ops branching here. Returns true if any arguments were inserted.
1672///
1673/// This function may create additional placeholders in predecessor blocks.
1674/// Creating block arguments in a later block may uncover additional arguments
1675/// to be inserted in a previous one. Therefore this function must be called
1676/// until no more block arguments are inserted.
1677bool Promoter::insertBlockArgs(BlockEntry *node) {
1678 // Determine which slots require a merging definition. Use the `slots` array
1679 // for this to have a deterministic order for the block arguments. We only
1680 // insert block arguments for the def's value or drive condition if
1681 // placeholders have been created for them, indicating that they are actually
1682 // used.
1683 enum class Which { Value, Condition };
1684 SmallVector<std::pair<DefSlot, Which>> neededSlots;
1685 auto addNeededSlot = [&](DefSlot slot) {
1686 if (auto *def = node->mergedDefs.lookup(slot)) {
1687 if (node->valueAfter->reachingDefs.contains(slot)) {
1688 if (def->valueIsPlaceholder)
1689 neededSlots.push_back({slot, Which::Value});
1690 if (def->conditionIsPlaceholder)
1691 neededSlots.push_back({slot, Which::Condition});
1692 }
1693 }
1694 };
1695 for (auto slot : slots) {
1696 addNeededSlot(blockingSlot(slot));
1697 addNeededSlot(delayedSlot(slot));
1698 }
1699 if (neededSlots.empty())
1700 return false;
1701 LLVM_DEBUG(llvm::dbgs() << "- Adding " << neededSlots.size()
1702 << " args to block " << node->block << "\n");
1703
1704 // Add the block arguments.
1705 for (auto [slot, which] : neededSlots) {
1706 auto *def = node->mergedDefs.lookup(slot);
1707 assert(def);
1708 switch (which) {
1709 case Which::Value: {
1710 // Create an argument for the definition's value and replace any
1711 // placeholder we might have created earlier.
1712 auto *placeholder = def->value.getDefiningOp();
1713 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1714 "placeholder replaced but valueIsPlaceholder still set");
1715 auto arg = node->block->addArgument(getStoredType(slot), getLoc(slot));
1716 replaceValueWith(placeholder->getResult(0), arg);
1717 pruner.eraseNow(placeholder);
1718 def->value = arg;
1719 def->valueIsPlaceholder = false;
1720 break;
1721 }
1722 case Which::Condition: {
1723 // If the definition's drive mode is conditional, create an argument for
1724 // the drive condition and replace any placeholder we might have created
1725 // earlier.
1726 auto *placeholder = def->condition.getCondition().getDefiningOp();
1727 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1728 "placeholder replaced but conditionIsPlaceholder still set");
1729 auto conditionArg = node->block->addArgument(
1730 IntegerType::get(region.getContext(), 1), getLoc(slot));
1731 replaceValueWith(placeholder->getResult(0), conditionArg);
1732 pruner.eraseNow(placeholder);
1733 def->condition.setCondition(conditionArg);
1734 def->conditionIsPlaceholder = false;
1735 break;
1736 }
1737 }
1738 }
1739
1740 // Add successor operands to the predecessor terminators.
1741 for (auto *predecessor : node->predecessors) {
1742 // Collect the interesting reaching definitions in the predecessor.
1743 SmallVector<Value> args;
1744 for (auto [slot, which] : neededSlots) {
1745 auto *def = predecessor->valueBefore->reachingDefs.lookup(slot);
1746 auto builder = OpBuilder::atBlockTerminator(predecessor->block);
1747 switch (which) {
1748 case Which::Value:
1749 if (def) {
1750 args.push_back(def->getValueOrPlaceholder());
1751 } else {
1752 auto type = getStoredType(slot);
1753 auto flatType = builder.getIntegerType(hw::getBitWidth(type));
1754 Value value =
1755 hw::ConstantOp::create(builder, getLoc(slot), flatType, 0);
1756 if (type != flatType)
1757 value = hw::BitcastOp::create(builder, getLoc(slot), type, value);
1758 args.push_back(value);
1759 }
1760 break;
1761 case Which::Condition:
1762 if (def) {
1763 args.push_back(def->getConditionOrPlaceholder());
1764 } else {
1765 args.push_back(hw::ConstantOp::create(builder, getLoc(slot),
1766 builder.getI1Type(), 0));
1767 }
1768 break;
1769 }
1770 }
1771
1772 // Add the reaching definitions to the branch op.
1773 auto branchOp = cast<BranchOpInterface>(predecessor->terminator);
1774 for (auto &blockOperand : branchOp->getBlockOperands())
1775 if (blockOperand.get() == node->block)
1776 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
1777 .append(args);
1778 }
1779
1780 return true;
1781}
1782
1783/// Replace all uses of an old value with a new value in the IR, and update all
1784/// mentions of the old value in the lattice to the new value.
1785void Promoter::replaceValueWith(Value oldValue, Value newValue) {
1786 oldValue.replaceAllUsesWith(newValue);
1787 for (auto *def : lattice.defs) {
1788 if (def->value == oldValue)
1789 def->value = newValue;
1790 if (def->condition.isConditional() &&
1791 def->condition.getCondition() == oldValue)
1792 def->condition.setCondition(newValue);
1793 }
1794}
1795
1796//===----------------------------------------------------------------------===//
1797// Cleanup
1798//===----------------------------------------------------------------------===//
1799
1800/// Remove all unused local signals.
1801void Promoter::removeUnusedLocalSignals() {
1802 for (auto *node : lattice.nodes)
1803 if (auto *signal = dyn_cast<SignalNode>(node))
1804 removeUnusedLocalSignal(signal);
1805}
1806
1807/// Remove a local signal if it is only used by projection ops and drives, but
1808/// never probed. Since the signal is local and cannot be observed in any other
1809/// way, we can safely remove it along with any projection ops and drives.
1810void Promoter::removeUnusedLocalSignal(SignalNode *signal) {
1811 // Check if the signal is only ever projected into and driven, but never
1812 // probed.
1813 SmallSetVector<Operation *, 8> users;
1814 SmallVector<Operation *> worklist;
1815 worklist.push_back(signal->op);
1816 while (!worklist.empty()) {
1817 auto *op = worklist.pop_back_val();
1818 if (!isa<SignalOp, DriveOp, SigArrayGetOp, SigArraySliceOp, SigExtractOp,
1819 SigStructExtractOp>(op))
1820 return;
1821 for (auto *user : op->getUsers())
1822 if (users.insert(user))
1823 worklist.push_back(user);
1824 }
1825
1826 // If we get here, the local signal is never probed. This means we can safely
1827 // remove it.
1828 LLVM_DEBUG(llvm::dbgs() << "- Removing local signal " << *signal->op << "\n");
1829 for (auto *op : llvm::reverse(users))
1830 pruner.eraseNow(op);
1831}
1832
1833//===----------------------------------------------------------------------===//
1834// Pass Infrastructure
1835//===----------------------------------------------------------------------===//
1836
1837namespace {
1838struct Mem2RegPass : public llhd::impl::Mem2RegPassBase<Mem2RegPass> {
1839 void runOnOperation() override;
1840};
1841} // namespace
1842
1843void Mem2RegPass::runOnOperation() {
1844 SmallVector<Region *> regions;
1845 getOperation()->walk<WalkOrder::PreOrder>([&](Operation *op) {
1846 if (isa<ProcessOp, FinalOp, CombinationalOp>(op)) {
1847 auto &region = op->getRegion(0);
1848 if (!region.empty())
1849 regions.push_back(&region);
1850 return WalkResult::skip();
1851 }
1852 return WalkResult::advance();
1853 });
1854 for (auto *region : regions)
1855 if (failed(Promoter(*region).promote()))
1856 return signalPassFailure();
1857}
static bool isAlways(Attribute attr, bool expected)
Definition ArcFolds.cpp:22
assert(baseType &&"element must be base type")
static void dump(DIModule &module, raw_indented_ostream &os)
static bool isDelayed(DefSlot slot)
Definition Mem2Reg.cpp:209
static Value packProjections(OpBuilder &builder, Value value, const ProjectionStack &projections)
Undo a stack of projections by taking the value of the projected field and injecting it into the surr...
Definition Mem2Reg.cpp:621
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static bool isDeltaDrive(Operation *op)
Check whether an operation is a llhd.drive with a delta delay.
Definition Mem2Reg.cpp:65
static ProjectionStack getProjections(Value fromSignal, Value toSlot)
Collect the llhd.sig.
Definition Mem2Reg.cpp:570
static bool isDeltaDelay(Value value)
Check whether a value is defined by llhd.constant_time <0ns, 1d, 0e>.
Definition Mem2Reg.cpp:47
static DefSlot blockingSlot(Value slot)
Definition Mem2Reg.cpp:206
static Value unpackProjections(OpBuilder &builder, Value value, ProjectionStack &projections)
Resolve a stack of projections by taking a value and descending into its subelements until the final ...
Definition Mem2Reg.cpp:586
static DefSlot delayedSlot(Value slot)
Definition Mem2Reg.cpp:207
static Value getSlot(DefSlot slot)
Definition Mem2Reg.cpp:208
SmallVector< Projection > ProjectionStack
A stack of projection operations.
Definition Mem2Reg.cpp:563
static bool isBlockingDrive(Operation *op)
Check whether an operation is a llhd.drive with an epsilon delay.
Definition Mem2Reg.cpp:57
static Type getStoredType(Value slot)
Definition Mem2Reg.cpp:210
static bool isEpsilonDelay(Value value)
Check whether a value is defined by llhd.constant_time <0ns, 0d, 1e>.
Definition Mem2Reg.cpp:38
PointerIntPair< Value, 1 > DefSlot
The slot a reaching definition specifies a value for, alongside a bit indicating whether the definiti...
Definition Mem2Reg.cpp:205
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
create(data_type, value)
Definition hw.py:441
create(data_type, value)
Definition hw.py:433
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
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
Utility that tracks operations that have potentially become unused and allows them to be cleaned up a...
static DriveCondition getEmptyKey()
Definition Mem2Reg.cpp:183
static DriveCondition getTombstoneKey()
Definition Mem2Reg.cpp:186
static bool isEqual(DriveCondition lhs, DriveCondition rhs)
Definition Mem2Reg.cpp:193
static unsigned getHashValue(DriveCondition d)
Definition Mem2Reg.cpp:189