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 // SigStructExtractOp is used for both struct and union
597 // member access; use the appropriate HW extract op.
598 if (isa<hw::UnionType>(value.getType()))
599 return builder.createOrFold<hw::UnionExtractOp>(
600 op.getLoc(), value, op.getFieldAttr());
601 return builder.createOrFold<hw::StructExtractOp>(
602 op.getLoc(), value, op.getFieldAttr());
603 })
604 .Case<SigExtractOp>([&](auto op) {
605 auto type = cast<RefType>(op.getType()).getNestedType();
606 auto width = type.getIntOrFloatBitWidth();
607 return comb::createDynamicExtract(builder, op.getLoc(), value,
608 op.getLowBit(), width);
609 });
610 }
611 return value;
612}
613
614/// Undo a stack of projections by taking the value of the projected field and
615/// injecting it into the surrounding aggregate value that the projection
616/// targets. This requires the `into` fields to be set to the concrete value of
617/// the intermediate projections.
618///
619/// Example:
620/// ```
621/// auto projections = getProjections(...);
622/// auto fieldValue = unpackProjections(..., aggregateValue, projections);
623/// fieldValue = update(fieldValue);
624/// aggregateValue = packProjections(..., fieldValue, projections);
625/// ```
626static Value packProjections(OpBuilder &builder, Value value,
627 const ProjectionStack &projections) {
628 for (auto projection : projections) {
629 value = TypeSwitch<Operation *, Value>(projection.op)
630 .Case<SigArrayGetOp>([&](auto op) {
631 return builder.createOrFold<hw::ArrayInjectOp>(
632 op.getLoc(), projection.into, op.getIndex(), value);
633 })
634 .Case<SigStructExtractOp>([&](auto op) {
635 // For unions, creating a new value from a single field
636 // replaces the entire union (all fields share storage).
637 if (auto unionTy =
638 dyn_cast<hw::UnionType>(projection.into.getType()))
639 return builder.createOrFold<hw::UnionCreateOp>(
640 op.getLoc(), unionTy, op.getFieldAttr(), value);
641 return builder.createOrFold<hw::StructInjectOp>(
642 op.getLoc(), projection.into, op.getFieldAttr(), value);
643 })
644 .Case<SigExtractOp>([&](auto op) {
645 return comb::createDynamicInject(builder, op.getLoc(),
646 projection.into,
647 op.getLowBit(), value);
648 });
649 }
650 return value;
651}
652
653//===----------------------------------------------------------------------===//
654// Drive/Probe to SSA Value Promotion
655//===----------------------------------------------------------------------===//
656
657namespace {
658/// The main promoter forwarding drives to probes within a region.
659struct Promoter {
660 Promoter(Region &region) : region(region) {}
661 LogicalResult promote();
662
663 void findPromotableSlots();
664 Value resolveSlot(Value projectionOrSlot);
665
666 void captureAcrossWait();
667 void captureAcrossWait(Value value, ArrayRef<WaitOp> waitOps,
668 Liveness &liveness, DominanceInfo &dominance);
669
670 void constructLattice();
671 void propagateBackward();
672 void propagateBackward(LatticeNode *node);
673 void propagateForward();
674 void propagateForward(bool optimisticMerges, DominanceInfo &dominance);
675 void propagateForward(LatticeNode *node, bool optimisticMerges,
676 DominanceInfo &dominance);
677 void markDirty(LatticeNode *node);
678
679 void insertProbeBlocks();
680 void insertProbes();
681 void insertProbes(BlockEntry *node);
682
683 void insertDriveBlocks();
684 void insertDrives();
685 void insertDrives(BlockExit *node);
686 void insertDrives(DriveNode *node);
687
688 void resolveDefinitions();
689 void resolveDefinitions(ProbeNode *node);
690 void resolveDefinitions(DriveNode *node);
691 void resolveDefinitionValue(DriveNode *node);
692 void resolveDefinitionCondition(DriveNode *node);
693
694 void insertBlockArgs();
695 bool insertBlockArgs(BlockEntry *node);
696 void replaceValueWith(Value oldValue, Value newValue);
697
698 void removeUnusedLocalSignals();
699 void removeUnusedLocalSignal(SignalNode *signal);
700
701 /// The region we are promoting in.
702 Region &region;
703
704 /// The slots we are promoting. Mostly `llhd.sig` ops in practice. This
705 /// establishes a deterministic order for slot allocations, such that
706 /// everything else in the pass can operate using unordered maps and sets.
707 SmallVector<Value> slots;
708 /// A mapping from projections to the root slot they are projecting into.
709 SmallDenseMap<Value, Value> projections;
710 /// A set of all promotable signal SSA values. This is the union of `slots`
711 /// and `projections` of those slots.
712 SmallDenseSet<Value> promotable;
713
714 /// The lattice used to propagate needed definitions backwards and reaching
715 /// definitions forwards.
716 Lattice lattice;
717 /// A worklist of lattice nodes used within calls to `propagate*`.
718 SmallPtrSet<LatticeNode *, 4> dirtyNodes;
719
720 /// Helper to clean up unused ops.
721 UnusedOpPruner pruner;
722};
723} // namespace
724
725LogicalResult Promoter::promote() {
726 if (region.empty())
727 return success();
728
729 findPromotableSlots();
730 captureAcrossWait();
731
732 // If there are no promotable slots we can still return after ensuring any
733 // values live across waits were captured as block arguments above. This
734 // keeps the pass semantics while allowing wait capturing to run
735 // independently of slot promotion.
736 if (slots.empty())
737 return success();
738
739 constructLattice();
740 LLVM_DEBUG({
741 llvm::dbgs() << "Initial lattice:\n";
742 lattice.dump();
743 });
744
745 // Propagate the needed definitions backward across the lattice.
746 propagateBackward();
747 LLVM_DEBUG({
748 llvm::dbgs() << "After backward propagation:\n";
749 lattice.dump();
750 });
751
752 // Insert probes wherever a def is needed for the first time.
753 insertProbeBlocks();
754 insertProbes();
755 LLVM_DEBUG({
756 llvm::dbgs() << "After probe insertion:\n";
757 lattice.dump();
758 });
759
760 // Propagate the reaching definitions forward across the lattice.
761 propagateForward();
762 LLVM_DEBUG({
763 llvm::dbgs() << "After forward propagation:\n";
764 lattice.dump();
765 });
766
767 // Resolve definitions.
768 resolveDefinitions();
769
770 // Insert drives wherever a reaching def can no longer propagate.
771 insertDriveBlocks();
772 insertDrives();
773 LLVM_DEBUG({
774 llvm::dbgs() << "After def resolution and drive insertion:\n";
775 lattice.dump();
776 });
777
778 // Insert the necessary block arguments.
779 insertBlockArgs();
780
781 // Remove local signals that are never probed.
782 removeUnusedLocalSignals();
783
784 // Erase operations that have become unused.
785 pruner.eraseNow();
786
787 return success();
788}
789
790/// Identify any promotable slots probed or driven under the current region.
791void Promoter::findPromotableSlots() {
792 SmallPtrSet<Value, 8> seenSlots;
793 SmallPtrSet<Operation *, 8> checkedUsers;
794 SmallVector<Operation *, 8> userWorklist;
795
796 region.walk([&](Operation *op) {
797 for (auto operand : op->getOperands()) {
798 if (!seenSlots.insert(operand).second)
799 continue;
800
801 // We can only promote probes and drives on a locally-defined signal.
802 // Other signals, such as the ones brought into a module through a port,
803 // have an unknown aliasing relationship with the other ports.
804 if (!operand.getDefiningOp<llhd::SignalOp>())
805 continue;
806
807 // Ensure the slot is not used in any way we cannot reason about.
808 bool hasProjection = false;
809 bool hasBlockingDrive = false;
810 bool hasDeltaDrive = false;
811 auto checkUser = [&](Operation *user) -> bool {
812 // We don't support nested probes and drives.
813 if (region.isProperAncestor(user->getParentRegion()))
814 return false;
815 // Ignore uses outside of the region.
816 if (user->getParentRegion() != &region)
817 return true;
818 // Projection operations are okay, as long as nested projections
819 // stay in the same block. Cross-block nested projections would break
820 // during promotion because the projection chain gets severed when
821 // Mem2Reg rewrites signal references into SSA block arguments.
822 if (isa<SigArrayGetOp, SigExtractOp, SigStructExtractOp>(user)) {
823 hasProjection = true;
824 for (auto *projectionUser : user->getUsers()) {
825 if (isa<SigArrayGetOp, SigExtractOp, SigStructExtractOp>(
826 projectionUser) &&
827 projectionUser->getBlock() != user->getBlock())
828 return false;
829 hasBlockingDrive |= isBlockingDrive(projectionUser);
830 hasDeltaDrive |= isDeltaDrive(projectionUser);
831 if (checkedUsers.insert(projectionUser).second)
832 userWorklist.push_back(projectionUser);
833 }
834 projections.insert({user->getResult(0), operand});
835 return true;
836 }
837 hasBlockingDrive |= isBlockingDrive(user);
838 hasDeltaDrive |= isDeltaDrive(user);
839 return isa<ProbeOp>(user) || isBlockingDrive(user) ||
840 isDeltaDrive(user);
841 };
842 checkedUsers.clear();
843 if (!llvm::all_of(operand.getUsers(), [&](auto *user) {
844 auto allOk = true;
845 if (checkedUsers.insert(user).second)
846 userWorklist.push_back(user);
847 while (!userWorklist.empty() && allOk)
848 allOk &= checkUser(userWorklist.pop_back_val());
849 userWorklist.clear();
850 return allOk;
851 }))
852 continue;
853
854 // Don't promote slots that have projections and a mix of blocking and
855 // delta drives. A blocking drive erases the delayed reaching definition,
856 // which leaves delta projection drives without a reaching definition.
857 if (hasProjection && hasBlockingDrive && hasDeltaDrive)
858 continue;
859
860 // Mem2Reg needs to be able to materialize integer constants for promoted
861 // slots, which requires the bit width to fit in an IntegerType. Skip
862 // signals that are too wide.
863 auto bitWidth = hw::getBitWidth(getStoredType(operand));
864 if (bitWidth < 0 ||
865 static_cast<unsigned>(bitWidth) > IntegerType::kMaxWidth)
866 continue;
867
868 slots.push_back(operand);
869 }
870 });
871
872 // Populate `promotable` with the slots and projections we are promoting.
873 promotable.insert(slots.begin(), slots.end());
874 for (auto [projection, slot] : llvm::make_early_inc_range(projections))
875 if (!promotable.contains(slot))
876 projections.erase(projection);
877 for (auto [projection, slot] : projections)
878 promotable.insert(projection);
879
880 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size() << " promotable slots, "
881 << promotable.size() << " promotable values\n");
882}
883
884/// Resolve SSA values in `projection` to the `slot` they are projecting into.
885/// Simply returns the given value if it is not in `projections`.
886Value Promoter::resolveSlot(Value projectionOrSlot) {
887 if (auto slot = projections.lookup(projectionOrSlot))
888 return slot;
889 return projectionOrSlot;
890}
891
892/// Explicitly capture any probes that are live across an `llhd.wait` as block
893/// arguments and destination operand of that wait. This ensures that replacing
894/// the probe with a reaching definition later on will capture the value of the
895/// reaching definition before the wait.
896void Promoter::captureAcrossWait() {
897 if (region.hasOneBlock())
898 return;
899
900 SmallVector<WaitOp> waitOps;
901 for (auto &block : region)
902 if (auto waitOp = dyn_cast<WaitOp>(block.getTerminator()))
903 waitOps.push_back(waitOp);
904
905 DominanceInfo dominance(region.getParentOp());
906 Liveness liveness(region.getParentOp());
907
908 llvm::DenseSet<Value> alreadyCaptured;
909
910 auto isDefinedInRegion = [&](Value v) {
911 return v.getParentRegion() == &region;
912 };
913
914 for (auto waitOp : waitOps) {
915 Block *waitBlock = waitOp->getBlock();
916 const auto &liveOutValues = liveness.getLiveOut(waitBlock);
917
918 for (Value v : liveOutValues) {
919 if (!isDefinedInRegion(v))
920 continue;
921
922 if (!alreadyCaptured.insert(v).second)
923 continue;
924
925 captureAcrossWait(v, waitOps, liveness, dominance);
926 }
927 }
928}
929
930/// Add a probe as block argument to a list of wait ops and update uses of the
931/// probe to use the added block arguments as appropriate. This may insert
932/// additional block arguments in case the probe and added block arguments both
933/// reach the same block.
934void Promoter::captureAcrossWait(Value value, ArrayRef<WaitOp> waitOps,
935 Liveness &liveness, DominanceInfo &dominance) {
936 LLVM_DEBUG({
937 llvm::dbgs() << "Capture " << value << "\n";
938 for (auto waitOp : waitOps)
939 llvm::dbgs() << "- Across " << waitOp << "\n";
940 });
941
942 // Calculate the merge points for this probe once it gets promoted to block
943 // arguments across the wait ops.
944 auto &domTree = dominance.getDomTree(&region);
945 llvm::IDFCalculatorBase<Block, false> idfCalculator(domTree);
946
947 // Calculate the set of blocks which will define this probe as a distinct
948 // value.
949 SmallPtrSet<Block *, 4> definingBlocks;
950 definingBlocks.insert(value.getParentBlock());
951 for (auto waitOp : waitOps)
952 definingBlocks.insert(waitOp.getDest());
953 idfCalculator.setDefiningBlocks(definingBlocks);
954
955 // Calculate where the probe is live.
956 SmallPtrSet<Block *, 16> liveInBlocks;
957 for (auto &block : region)
958 if (liveness.getLiveness(&block)->isLiveIn(value))
959 liveInBlocks.insert(&block);
960 idfCalculator.setLiveInBlocks(liveInBlocks);
961
962 // Calculate the merge points where we will have to insert block arguments for
963 // this probe.
964 SmallVector<Block *> mergePointsVec;
965 idfCalculator.calculate(mergePointsVec);
966 SmallPtrSet<Block *, 16> mergePoints(mergePointsVec.begin(),
967 mergePointsVec.end());
968 for (auto waitOp : waitOps)
969 mergePoints.insert(waitOp.getDest());
970 LLVM_DEBUG(llvm::dbgs() << "- " << mergePoints.size() << " merge points\n");
971
972 // Perform a depth-first search starting at the block containing the probe,
973 // which dominates all its uses. When we encounter a block that is a merge
974 // point, insert a block argument.
975 struct WorklistItem {
976 DominanceInfoNode *domNode;
977 Value reachingDef;
978 };
979 SmallVector<WorklistItem> worklist;
980 worklist.push_back({domTree.getNode(value.getParentBlock()), value});
981
982 while (!worklist.empty()) {
983 auto item = worklist.pop_back_val();
984 auto *block = item.domNode->getBlock();
985
986 // If this block is a merge point, insert a block argument for the probe.
987 if (mergePoints.contains(block))
988 item.reachingDef = block->addArgument(value.getType(), value.getLoc());
989
990 // Replace any uses of the probe in this block with the current reaching
991 // definition.
992 for (auto &op : *block)
993 op.replaceUsesOfWith(value, item.reachingDef);
994
995 // If the terminator of this block branches to a merge point, add the
996 // current reaching definition as a destination operand.
997 if (auto branchOp = dyn_cast<BranchOpInterface>(block->getTerminator())) {
998 for (auto &blockOperand : branchOp->getBlockOperands())
999 if (mergePoints.contains(blockOperand.get()))
1000 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
1001 .append(item.reachingDef);
1002 } else if (auto waitOp = dyn_cast<WaitOp>(block->getTerminator())) {
1003 if (mergePoints.contains(waitOp.getDest()))
1004 waitOp.getDestOperandsMutable().append(item.reachingDef);
1005 }
1006
1007 for (auto *child : item.domNode->children())
1008 worklist.push_back({child, item.reachingDef});
1009 }
1010}
1011
1012//===----------------------------------------------------------------------===//
1013// Lattice Construction and Propagation
1014//===----------------------------------------------------------------------===//
1015
1016/// Populate the lattice with nodes and values corresponding to the blocks and
1017/// relevant operations in the region we're promoting.
1018void Promoter::constructLattice() {
1019 // Create entry nodes for each block.
1021 for (auto &block : region) {
1022 auto *entry = lattice.createNode<BlockEntry>(&block, lattice.createValue());
1023 blockEntries.insert({&block, entry});
1024 }
1025
1026 // Create nodes for each operation that is relevant for the pass.
1027 for (auto &block : region) {
1028 auto *valueBefore = blockEntries.lookup(&block)->valueAfter;
1029
1030 // Handle operations.
1031 for (auto &op : block.without_terminator()) {
1032 // Handle probes.
1033 if (auto probeOp = dyn_cast<ProbeOp>(op)) {
1034 if (!promotable.contains(probeOp.getSignal()))
1035 continue;
1036 auto *node = lattice.createNode<ProbeNode>(
1037 probeOp, resolveSlot(probeOp.getSignal()), valueBefore,
1038 lattice.createValue());
1039 valueBefore = node->valueAfter;
1040 continue;
1041 }
1042
1043 // Handle drives.
1044 if (auto driveOp = dyn_cast<DriveOp>(op)) {
1045 if (!isBlockingDrive(&op) && !isDeltaDrive(&op))
1046 continue;
1047 if (!promotable.contains(driveOp.getSignal()))
1048 continue;
1049 auto condition = DriveCondition::always();
1050 if (auto enable = driveOp.getEnable())
1051 condition = DriveCondition::conditional(enable);
1052 // Drives that target a slot directly provide the driven value as a
1053 // definition for the slot. Drives to projections have their value
1054 // calculated later on.
1055 auto slot = resolveSlot(driveOp.getSignal());
1056 auto *def = driveOp.getSignal() == slot
1057 ? lattice.createDef(driveOp.getValue(), condition)
1058 : lattice.createDef(driveOp->getBlock(),
1059 getStoredType(slot), condition);
1060 auto *node = lattice.createNode<DriveNode>(
1061 driveOp, slot, def, valueBefore, lattice.createValue());
1062 valueBefore = node->valueAfter;
1063 continue;
1064 }
1065
1066 // Handle local signals.
1067 if (auto signalOp = dyn_cast<SignalOp>(op)) {
1068 if (!promotable.contains(signalOp))
1069 continue;
1070 auto *def =
1071 lattice.createDef(signalOp.getInit(), DriveCondition::never());
1072 auto *node = lattice.createNode<SignalNode>(signalOp, def, valueBefore,
1073 lattice.createValue());
1074 valueBefore = node->valueAfter;
1075 continue;
1076 }
1077 }
1078
1079 // Create the exit node for the block.
1080 auto *exit = lattice.createNode<BlockExit>(&block, valueBefore);
1081 for (auto *otherBlock : exit->terminator->getSuccessors()) {
1082 auto *otherEntry = blockEntries.lookup(otherBlock);
1083 exit->successors.push_back(otherEntry);
1084 otherEntry->predecessors.push_back(exit);
1085 }
1086 }
1087}
1088
1089/// Propagate the lattice values backwards against control flow until a fixed
1090/// point is reached.
1091void Promoter::propagateBackward() {
1092 for (auto *node : lattice.nodes)
1093 propagateBackward(node);
1094 while (!dirtyNodes.empty()) {
1095 auto *node = *dirtyNodes.begin();
1096 dirtyNodes.erase(node);
1097 propagateBackward(node);
1098 }
1099}
1100
1101/// Propagate the lattice value after a node backward to the value before a
1102/// node.
1103void Promoter::propagateBackward(LatticeNode *node) {
1104 auto update = [&](LatticeValue *value, auto &neededDefs) {
1105 if (value->neededDefs != neededDefs) {
1106 value->neededDefs = neededDefs;
1107 markDirty(value->nodeBefore);
1108 }
1109 };
1110
1111 // Probes need a definition for the probed slot to be available.
1112 if (auto *probe = dyn_cast<ProbeNode>(node)) {
1113 auto needed = probe->valueAfter->neededDefs;
1114 needed.insert(probe->slot);
1115 update(probe->valueBefore, needed);
1116 return;
1117 }
1118
1119 // Blocking unconditional drives to an entire slot kill the need for a
1120 // definition to be available, since they provide a definition themselves.
1121 // Conditional drives propagate the need for a definition, since they have to
1122 // forward an incoming reaching def in case they are disabled. Drives to
1123 // projections need a definition to be available such that they can partially
1124 // update it.
1125 if (auto *drive = dyn_cast<DriveNode>(node)) {
1126 auto needed = drive->valueAfter->neededDefs;
1127 if (drive->drivesProjection())
1128 needed.insert(getSlot(drive->slot));
1129 else if (!isDelayed(drive->slot) && !drive->getDriveOp().getEnable())
1130 needed.erase(getSlot(drive->slot));
1131 update(drive->valueBefore, needed);
1132 return;
1133 }
1134
1135 // Local signal declarations kill the need for a definition to be available,
1136 // since the op is the first time a signal becomes available and the op
1137 // provides an initial value as a definition.
1138 if (auto *signal = dyn_cast<SignalNode>(node)) {
1139 auto needed = signal->valueAfter->neededDefs;
1140 needed.erase(signal->getSignalOp());
1141 update(signal->valueBefore, needed);
1142 return;
1143 }
1144
1145 // Block entries simply trigger updates to all their predecessors.
1146 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1147 for (auto *predecessor : entry->predecessors)
1148 markDirty(predecessor);
1149 return;
1150 }
1151
1152 // Block exits merge any needed definitions from their successors.
1153 if (auto *exit = dyn_cast<BlockExit>(node)) {
1154 if (exit->suspends)
1155 return;
1156 SmallDenseSet<Value, 1> needed;
1157 for (auto *successors : exit->successors)
1158 needed.insert(successors->valueAfter->neededDefs.begin(),
1159 successors->valueAfter->neededDefs.end());
1160 update(exit->valueBefore, needed);
1161 return;
1162 }
1163
1164 assert(false && "unhandled node in backward propagation");
1165}
1166
1167void Promoter::propagateForward() {
1168 DominanceInfo dominance(region.getParentOp());
1169 propagateForward(true, dominance);
1170 propagateForward(false, dominance);
1171}
1172
1173/// Propagate the lattice values forwards along with control flow until a fixed
1174/// point is reached. If `optimisticMerges` is true, block entry points
1175/// propagate definitions from their predecessors into the block without
1176/// creating a merge definition even if the definition is not available in all
1177/// predecessors. This is overly optimistic, but initially helps definitions
1178/// propagate through loop structures. If `optimisticMerges` is false, block
1179/// entry points create merge definitions for definitions that are not available
1180/// in all predecessors.
1181void Promoter::propagateForward(bool optimisticMerges,
1182 DominanceInfo &dominance) {
1183 for (auto *node : lattice.nodes)
1184 propagateForward(node, optimisticMerges, dominance);
1185 while (!dirtyNodes.empty()) {
1186 auto *node = *dirtyNodes.begin();
1187 dirtyNodes.erase(node);
1188 propagateForward(node, optimisticMerges, dominance);
1189 }
1190}
1191
1192/// Propagate the lattice value before a node forward to the value after a node.
1193void Promoter::propagateForward(LatticeNode *node, bool optimisticMerges,
1194 DominanceInfo &dominance) {
1195 auto update = [&](LatticeValue *value, auto &reachingDefs) {
1196 if (value->reachingDefs != reachingDefs) {
1197 value->reachingDefs = reachingDefs;
1198 markDirty(value->nodeAfter);
1199 }
1200 };
1201
1202 // Probes simply propagate any reaching defs.
1203 if (auto *probe = dyn_cast<ProbeNode>(node)) {
1204 update(probe->valueAfter, probe->valueBefore->reachingDefs);
1205 return;
1206 }
1207
1208 // Drives propagate the driven value as a reaching def. Blocking drives kill
1209 // earlier non-blocking drives. This reflects Verilog and VHDL behaviour,
1210 // where a drive sequence like
1211 //
1212 // a <= #10ns 42; // A
1213 // a <= 43; // B
1214 // a = 44; // C
1215 //
1216 // would see (B) override (A), because it happens earlier, and (C) override
1217 // (B), because it in turn happens earlier.
1218 if (auto *drive = dyn_cast<DriveNode>(node)) {
1219 auto reaching = drive->valueBefore->reachingDefs;
1220
1221 // If the drive is conditional or driving a projection of a slot, merge any
1222 // incoming reaching def into the reaching def created by the drive. This is
1223 // necessary since a conditional drive following an unconditional drive may
1224 // have to insert a multiplexer to forward to subsequent probes.
1225 if (drive->drivesProjection() || drive->getDriveOp().getEnable()) {
1226 auto *inDef = reaching.lookup(drive->slot);
1227 if (inDef) {
1228 if (drive->def->value != inDef->value)
1229 drive->def->value = {};
1230 if (inDef->condition.isAlways() || drive->def->condition.isAlways())
1231 drive->def->condition = DriveCondition::always();
1232 else if (drive->def->condition != inDef->condition)
1233 drive->def->condition = DriveCondition::conditional();
1234 }
1235 }
1236
1237 reaching[drive->slot] = drive->def;
1238 if (!isDelayed(drive->slot))
1239 reaching.erase(delayedSlot(getSlot(drive->slot)));
1240 update(drive->valueAfter, reaching);
1241 return;
1242 }
1243
1244 // Signals propagate their initial value as a reaching def. They also kill any
1245 // earlier definitions, which should not be able to reach the signal in the
1246 // first place.
1247 if (auto *signal = dyn_cast<SignalNode>(node)) {
1248 auto reaching = signal->valueBefore->reachingDefs;
1249 reaching[signal->getSlot()] = signal->def;
1250 reaching.erase(delayedSlot(signal->getSignalOp()));
1251 update(signal->valueAfter, reaching);
1252 return;
1253 }
1254
1255 // Block entry points propagate any reaching definitions available in all
1256 // predecessors, plus any probes inserted locally.
1257 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1258 // Propagate reaching definitions for each inserted probe.
1260 for (auto [slot, insertedProbe] : entry->insertedProbes) {
1261 reaching[blockingSlot(slot)] = insertedProbe;
1262 reaching[delayedSlot(slot)] = insertedProbe;
1263 }
1264
1265 // Propagate reaching definitions from predecessors, creating new
1266 // definitions in case of a merge.
1268 for (auto *predecessor : entry->predecessors) {
1269 if (predecessor->suspends)
1270 continue;
1271 // Propogate signal definitions only to blocks that are dominated by the
1272 // signal itself.
1273 for (auto &defEntry : predecessor->valueBefore->reachingDefs) {
1274 auto slot = getSlot(defEntry.getFirst());
1275 auto *slotBlock = slot.getDefiningOp()->getBlock();
1276 if (!dominance.dominates(slotBlock, entry->block))
1277 continue;
1278 reachingDefs.insert(defEntry);
1279 }
1280 }
1281
1282 for (auto pair : reachingDefs) {
1283 DefSlot slot = pair.first;
1284 Def *reachingDef = pair.second;
1285 DriveCondition reachingDefCondition = reachingDef->condition;
1286
1287 // Do not override inserted probes.
1288 if (reaching.contains(slot))
1289 continue;
1290
1291 // Check if all predecessors provide a definition for this slot. If any
1292 // multiple definitions for the same slot reach us, simply set the
1293 // `reachingDef` to null such that we can insert a new merge definition.
1294 // Separately track whether the drive mode of all definitions is
1295 // identical. This is often the case, for example when the definitions of
1296 // two unconditional drives converge, and we would like to preserve that
1297 // both drives were unconditional, even if the driven value differs.
1298 if (llvm::any_of(entry->predecessors, [&](auto *predecessor) {
1299 return predecessor->suspends;
1300 }))
1301 continue;
1302
1303 for (auto *predecessor : entry->predecessors) {
1304 auto otherDef = predecessor->valueBefore->reachingDefs.lookup(slot);
1305 if (!otherDef && optimisticMerges)
1306 continue;
1307 // If the definitions are not identical, indicate that we will have
1308 // to create a new merge def.
1309 if (reachingDef != otherDef)
1310 reachingDef = nullptr;
1311 // If the definitions have different modes, indicate that we will
1312 // have to create a conditional drive later.
1313 auto condition =
1314 otherDef ? otherDef->condition : DriveCondition::never();
1315 if (reachingDefCondition != condition)
1316 reachingDefCondition = DriveCondition::conditional();
1317 }
1318
1319 // Create a merge definition if different definitions reach us from our
1320 // predecessors.
1321 if (!reachingDef)
1322 reachingDef = entry->mergedDefs.lookup(slot);
1323 if (!reachingDef) {
1324 reachingDef = lattice.createDef(entry->block, getStoredType(slot),
1325 reachingDefCondition);
1326 entry->mergedDefs.insert({slot, reachingDef});
1327 } else {
1328 reachingDef->condition = reachingDefCondition;
1329 }
1330 reaching.insert({slot, reachingDef});
1331 }
1332
1333 update(entry->valueAfter, reaching);
1334 return;
1335 }
1336
1337 // Block exits simply trigger updates to all their successors.
1338 if (auto *exit = dyn_cast<BlockExit>(node)) {
1339 for (auto *successor : exit->successors)
1340 markDirty(successor);
1341 return;
1342 }
1343
1344 assert(false && "unhandled node in forward propagation");
1345}
1346
1347/// Mark a lattice node to be updated during propagation.
1348void Promoter::markDirty(LatticeNode *node) {
1349 assert(node);
1350 dirtyNodes.insert(node);
1351}
1352
1353//===----------------------------------------------------------------------===//
1354// Drive/Probe Insertion
1355//===----------------------------------------------------------------------===//
1356
1357/// Insert additional probe blocks where needed. This can happen if a definition
1358/// is needed in a block which has a suspending and non-suspending predecessor.
1359/// In that case we would like to insert probes in the predecessor blocks, but
1360/// cannot do so because of the suspending predecessor.
1361void Promoter::insertProbeBlocks() {
1362 // Find all blocks that have any needed definition that can't propagate beyond
1363 // one of its predecessors. If that's the case, we need an additional probe
1364 // block after that predecessor.
1365 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1366 for (auto *node : lattice.nodes) {
1367 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1368 SmallVector<Value> partialSlots;
1369 for (auto slot : entry->valueAfter->neededDefs) {
1370 unsigned numIncoming = 0;
1371 for (auto *predecessor : entry->predecessors)
1372 if (predecessor->valueBefore->neededDefs.contains(slot))
1373 ++numIncoming;
1374 if (numIncoming != 0 && numIncoming != entry->predecessors.size())
1375 partialSlots.push_back(slot);
1376 }
1377 for (auto *predecessor : entry->predecessors)
1378 if (llvm::any_of(partialSlots, [&](auto slot) {
1379 return !predecessor->valueBefore->neededDefs.contains(slot);
1380 }))
1381 worklist.insert({predecessor, entry});
1382 }
1383 }
1384
1385 // Insert probe blocks after all blocks we have identified.
1386 for (auto [predecessor, successor] : worklist) {
1387 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe block towards " << successor
1388 << " after " << *predecessor->terminator << "\n");
1389 OpBuilder builder(predecessor->terminator);
1390 auto *newBlock = builder.createBlock(successor->block);
1391 for (auto oldArg : successor->block->getArguments())
1392 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1393 cf::BranchOp::create(builder, predecessor->terminator->getLoc(),
1394 successor->block, newBlock->getArguments());
1395 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1396 if (blockOp.get() == successor->block)
1397 blockOp.set(newBlock);
1398
1399 // Create new nodes in the lattice for the added block.
1400 auto *value = lattice.createValue();
1401 value->neededDefs = successor->valueAfter->neededDefs;
1402 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1403 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1404 newEntry->predecessors.push_back(predecessor);
1405 newExit->successors.push_back(successor);
1406 llvm::replace(successor->predecessors, predecessor, newExit);
1407 llvm::replace(predecessor->successors, successor, newEntry);
1408 }
1409}
1410
1411/// Insert probes wherever a definition is needed for the first time. This is
1412/// the case in the entry block, after any suspensions, and after operations
1413/// that have unknown effects on memory slots.
1414void Promoter::insertProbes() {
1415 for (auto *node : lattice.nodes) {
1416 if (auto *entry = dyn_cast<BlockEntry>(node))
1417 insertProbes(entry);
1418 }
1419}
1420
1421/// Insert probes at the beginning of a block for definitions that are needed in
1422/// this block but not in its predecessors.
1423void Promoter::insertProbes(BlockEntry *node) {
1424 auto builder = OpBuilder::atBlockBegin(node->block);
1425 for (auto neededDef : slots) {
1426 if (!node->valueAfter->neededDefs.contains(neededDef))
1427 continue;
1428 if (!node->predecessors.empty() &&
1429 llvm::all_of(node->predecessors, [&](auto *predecessor) {
1430 return predecessor->valueBefore->neededDefs.contains(neededDef);
1431 }))
1432 continue;
1433 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe for " << neededDef
1434 << " in block " << node->block << "\n");
1435 OpBuilder::InsertionGuard guard(builder);
1436 // If the neededDef is in the same block, one can't just insert at top.
1437 if (Operation *op = neededDef.getDefiningOp()) {
1438 if (op->getBlock() == node->block)
1439 builder.setInsertionPointAfterValue(neededDef);
1440 }
1441 auto value = ProbeOp::create(builder, neededDef.getLoc(), neededDef);
1442 auto *def = lattice.createDef(value, DriveCondition::never());
1443 node->insertedProbes.push_back({neededDef, def});
1444 }
1445}
1446
1447/// Insert additional drive blocks where needed. This can happen if a definition
1448/// continues into some of a block's successors, but not all of them.
1449void Promoter::insertDriveBlocks() {
1450 // Find all blocks that have any reaching definition that can't propagate
1451 // beyond one of its successors. If that's the case, we need an additional
1452 // drive block before that successor.
1453 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1454 for (auto *node : lattice.nodes) {
1455 if (auto *exit = dyn_cast<BlockExit>(node)) {
1456 SmallVector<DefSlot> partialSlots;
1457 for (auto [slot, reachingDef] : exit->valueBefore->reachingDefs) {
1458 if (reachingDef->condition.isNever())
1459 continue;
1460 unsigned numContinues = 0;
1461 for (auto *successor : exit->successors)
1462 if (successor->valueAfter->reachingDefs.contains(slot))
1463 ++numContinues;
1464 if (numContinues != 0 && numContinues != exit->successors.size())
1465 partialSlots.push_back(slot);
1466 }
1467 for (auto *successor : exit->successors)
1468 if (llvm::any_of(partialSlots, [&](auto slot) {
1469 return !successor->valueAfter->reachingDefs.contains(slot);
1470 }))
1471 worklist.insert({exit, successor});
1472 }
1473 }
1474
1475 // Insert drive blocks before all blocks we have identified.
1476 for (auto [predecessor, successor] : worklist) {
1477 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive block towards " << successor
1478 << " after " << *predecessor->terminator << "\n");
1479 OpBuilder builder(predecessor->terminator);
1480 auto *newBlock = builder.createBlock(successor->block);
1481 for (auto oldArg : successor->block->getArguments())
1482 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1483 cf::BranchOp::create(builder, predecessor->terminator->getLoc(),
1484 successor->block, newBlock->getArguments());
1485 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1486 if (blockOp.get() == successor->block)
1487 blockOp.set(newBlock);
1488
1489 // Create new nodes in the lattice for the added block.
1490 auto *value = lattice.createValue();
1491 value->neededDefs = successor->valueAfter->neededDefs;
1492 value->reachingDefs = predecessor->valueBefore->reachingDefs;
1493 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1494 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1495 newEntry->predecessors.push_back(predecessor);
1496 newExit->successors.push_back(successor);
1497 llvm::replace(successor->predecessors, predecessor, newExit);
1498 llvm::replace(predecessor->successors, successor, newEntry);
1499 }
1500}
1501
1502/// Insert drives wherever a reaching definition can no longer propagate. This
1503/// is the before any suspensions and before operations that have unknown
1504/// effects on memory slots.
1505void Promoter::insertDrives() {
1506 for (auto *node : lattice.nodes) {
1507 if (auto *exit = dyn_cast<BlockExit>(node))
1508 insertDrives(exit);
1509 else if (auto *drive = dyn_cast<DriveNode>(node))
1510 insertDrives(drive);
1511 }
1512}
1513
1514/// Insert drives at block terminators for definitions that do not propagate
1515/// into successors.
1516void Promoter::insertDrives(BlockExit *node) {
1517 auto builder = OpBuilder::atBlockTerminator(node->block);
1518
1519 ConstantTimeOp epsilonTime;
1520 ConstantTimeOp deltaTime;
1521 auto getTime = [&](bool delta) {
1522 if (delta) {
1523 if (!deltaTime)
1524 deltaTime = ConstantTimeOp::create(builder, node->terminator->getLoc(),
1525 0, "ns", 1, 0);
1526 return deltaTime;
1527 }
1528 if (!epsilonTime)
1529 epsilonTime = ConstantTimeOp::create(builder, node->terminator->getLoc(),
1530 0, "ns", 0, 1);
1531 return epsilonTime;
1532 };
1533
1534 auto insertDriveForSlot = [&](DefSlot slot) {
1535 auto reachingDef = node->valueBefore->reachingDefs.lookup(slot);
1536 if (!reachingDef || reachingDef->condition.isNever())
1537 return;
1538 if (!node->suspends && !node->successors.empty() &&
1539 llvm::all_of(node->successors, [&](auto *successor) {
1540 return successor->valueAfter->reachingDefs.contains(slot);
1541 }))
1542 return;
1543 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive for " << getSlot(slot) << " "
1544 << (isDelayed(slot) ? "(delayed)" : "(blocking)")
1545 << " before " << *node->terminator << "\n");
1546 auto time = getTime(isDelayed(slot));
1547 auto value = reachingDef->getValueOrPlaceholder();
1548 auto enable = reachingDef->condition.isConditional()
1549 ? reachingDef->getConditionOrPlaceholder()
1550 : Value{};
1551 DriveOp::create(builder, getLoc(slot), getSlot(slot), value, time, enable);
1552 };
1553
1554 for (auto slot : slots)
1555 insertDriveForSlot(blockingSlot(slot));
1556 for (auto slot : slots)
1557 insertDriveForSlot(delayedSlot(slot));
1558}
1559
1560/// Remove drives to slots that we are promoting. These have been replaced with
1561/// new drives at block exits.
1562void Promoter::insertDrives(DriveNode *node) {
1563 if (!promotable.contains(getSlot(node->slot)))
1564 return;
1565 LLVM_DEBUG(llvm::dbgs() << "- Removing drive " << *node->op << "\n");
1566 pruner.eraseNow(node->op);
1567 node->op = nullptr;
1568}
1569
1570//===----------------------------------------------------------------------===//
1571// Drive-to-Probe Forwarding
1572//===----------------------------------------------------------------------===//
1573
1574/// Forward definitions throughout the IR.
1575void Promoter::resolveDefinitions() {
1576 for (auto *node : lattice.nodes) {
1577 if (auto *probe = dyn_cast<ProbeNode>(node))
1578 resolveDefinitions(probe);
1579 else if (auto *drive = dyn_cast<DriveNode>(node))
1580 resolveDefinitions(drive);
1581 }
1582}
1583
1584/// Replace probes with the corresponding reaching definition.
1585void Promoter::resolveDefinitions(ProbeNode *node) {
1586 if (!promotable.contains(node->slot))
1587 return;
1588 auto *def = node->valueBefore->reachingDefs.lookup(blockingSlot(node->slot));
1589 assert(def && "no definition reaches probe");
1590
1591 // Gather any projections between the probe and the underlying slot.
1592 auto projections = getProjections(node->op->getOperand(0), node->slot);
1593
1594 // Extract the projected value from the aggregate.
1595 auto builder = OpBuilder(node->op);
1596 auto value = def->getValueOrPlaceholder();
1597 value = unpackProjections(builder, value, projections);
1598
1599 // Replace the probe result with the projected value.
1600 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << *node->op << " with " << value
1601 << "\n");
1602 replaceValueWith(node->op->getResult(0), value);
1603 pruner.eraseNow(node->op);
1604 node->op = nullptr;
1605}
1606
1607/// Mutate reaching definitions for conditional drives or drives that target
1608/// projections of a slot.
1609void Promoter::resolveDefinitions(DriveNode *node) {
1610 if (!promotable.contains(getSlot(node->slot)))
1611 return;
1612
1613 // Check whether the drive already has a value and condition set for its
1614 // reaching def. This is the case for drives to an entire slot. Conditional
1615 // drives and drives to a projection will have no value set initially, in
1616 // which case either the value is null or it is a placeholder.
1617 if (!node->def->value || node->def->valueIsPlaceholder)
1618 resolveDefinitionValue(node);
1619 if (node->def->condition.isConditional() &&
1620 (!node->def->condition.getCondition() ||
1621 node->def->conditionIsPlaceholder))
1622 resolveDefinitionCondition(node);
1623}
1624
1625void Promoter::resolveDefinitionValue(DriveNode *node) {
1626 // Get the slot definition reaching the drive. This is the value the drive has
1627 // to update.
1628 auto *inDef = node->valueBefore->reachingDefs.lookup(node->slot);
1629 assert(inDef && "no definition reaches drive");
1630 auto driveOp = node->getDriveOp();
1631 LLVM_DEBUG(llvm::dbgs() << "- Injecting value for " << driveOp << "\n");
1632
1633 // Gather any projections between the drive and the underlying slot.
1634 auto projections = getProjections(driveOp.getSignal(), getSlot(node->slot));
1635
1636 // Extract the current value from the aggregate.
1637 auto builder = OpBuilder(driveOp);
1638 auto value = inDef->getValueOrPlaceholder();
1639 value = unpackProjections(builder, value, projections);
1640
1641 // Update the value according to the drive.
1642 pruner.eraseLaterIfUnused(value);
1643 if (!driveOp.getEnable())
1644 value = driveOp.getValue();
1645 else
1646 value = builder.createOrFold<comb::MuxOp>(
1647 driveOp.getLoc(), driveOp.getEnable(), driveOp.getValue(), value);
1648
1649 // Inject the updated value into the aggregate.
1650 value = packProjections(builder, value, projections);
1651
1652 // Track the updated slot value in the drive's reaching def.
1653 if (node->def->valueIsPlaceholder) {
1654 auto *placeholder = node->def->value.getDefiningOp();
1655 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1656 "placeholder replaced but valueIsPlaceholder still set");
1657 replaceValueWith(placeholder->getResult(0), value);
1658 pruner.eraseNow(placeholder);
1659 node->def->valueIsPlaceholder = false;
1660 }
1661 node->def->value = value;
1662}
1663
1664void Promoter::resolveDefinitionCondition(DriveNode *node) {
1665 // Get the slot definition reaching the drive. This contains the condition the
1666 // drive has to update.
1667 auto *inDef = node->valueBefore->reachingDefs.lookup(node->slot);
1668 assert(inDef && "no definition reaches drive");
1669 auto driveOp = node->getDriveOp();
1670 LLVM_DEBUG(llvm::dbgs() << "- Mutating condition for " << driveOp << "\n");
1671 OpBuilder builder(driveOp);
1672
1673 // Adjust the drive condition by combining the incoming condition with the
1674 // enable of this drive op.
1675 Value condition;
1676 if (inDef->condition.isNever())
1677 condition = driveOp.getEnable();
1678 else
1679 condition =
1680 builder.createOrFold<comb::OrOp>(driveOp.getLoc(), driveOp.getEnable(),
1681 inDef->getConditionOrPlaceholder());
1682
1683 // Track the updated drive condition in the drive's reaching def.
1684 if (node->def->conditionIsPlaceholder) {
1685 auto *placeholder = node->def->condition.getCondition().getDefiningOp();
1686 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1687 "placeholder replaced but conditionIsPlaceholder still set");
1688 replaceValueWith(placeholder->getResult(0), condition);
1689 pruner.eraseNow(placeholder);
1690 node->def->conditionIsPlaceholder = false;
1691 }
1692 node->def->condition.setCondition(condition);
1693}
1694
1695//===----------------------------------------------------------------------===//
1696// Block Argument Insertion
1697//===----------------------------------------------------------------------===//
1698
1699/// Insert block arguments into the IR.
1700void Promoter::insertBlockArgs() {
1701 bool anyArgsInserted = true;
1702 while (anyArgsInserted) {
1703 anyArgsInserted = false;
1704 for (auto *node : lattice.nodes)
1705 if (auto *entry = dyn_cast<BlockEntry>(node))
1706 anyArgsInserted |= insertBlockArgs(entry);
1707 }
1708}
1709
1710/// Insert block arguments for any merging definitions for which a placeholder
1711/// value has been created. Also insert corresponding successor operands to any
1712/// ops branching here. Returns true if any arguments were inserted.
1713///
1714/// This function may create additional placeholders in predecessor blocks.
1715/// Creating block arguments in a later block may uncover additional arguments
1716/// to be inserted in a previous one. Therefore this function must be called
1717/// until no more block arguments are inserted.
1718bool Promoter::insertBlockArgs(BlockEntry *node) {
1719 // Determine which slots require a merging definition. Use the `slots` array
1720 // for this to have a deterministic order for the block arguments. We only
1721 // insert block arguments for the def's value or drive condition if
1722 // placeholders have been created for them, indicating that they are actually
1723 // used.
1724 enum class Which { Value, Condition };
1725 SmallVector<std::pair<DefSlot, Which>> neededSlots;
1726 auto addNeededSlot = [&](DefSlot slot) {
1727 if (auto *def = node->mergedDefs.lookup(slot)) {
1728 if (node->valueAfter->reachingDefs.contains(slot)) {
1729 if (def->valueIsPlaceholder)
1730 neededSlots.push_back({slot, Which::Value});
1731 if (def->conditionIsPlaceholder)
1732 neededSlots.push_back({slot, Which::Condition});
1733 }
1734 }
1735 };
1736 for (auto slot : slots) {
1737 addNeededSlot(blockingSlot(slot));
1738 addNeededSlot(delayedSlot(slot));
1739 }
1740 if (neededSlots.empty())
1741 return false;
1742 LLVM_DEBUG(llvm::dbgs() << "- Adding " << neededSlots.size()
1743 << " args to block " << node->block << "\n");
1744
1745 // Add the block arguments.
1746 for (auto [slot, which] : neededSlots) {
1747 auto *def = node->mergedDefs.lookup(slot);
1748 assert(def);
1749 switch (which) {
1750 case Which::Value: {
1751 // Create an argument for the definition's value and replace any
1752 // placeholder we might have created earlier.
1753 auto *placeholder = def->value.getDefiningOp();
1754 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1755 "placeholder replaced but valueIsPlaceholder still set");
1756 auto arg = node->block->addArgument(getStoredType(slot), getLoc(slot));
1757 replaceValueWith(placeholder->getResult(0), arg);
1758 pruner.eraseNow(placeholder);
1759 def->value = arg;
1760 def->valueIsPlaceholder = false;
1761 break;
1762 }
1763 case Which::Condition: {
1764 // If the definition's drive mode is conditional, create an argument for
1765 // the drive condition and replace any placeholder we might have created
1766 // earlier.
1767 auto *placeholder = def->condition.getCondition().getDefiningOp();
1768 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1769 "placeholder replaced but conditionIsPlaceholder still set");
1770 auto conditionArg = node->block->addArgument(
1771 IntegerType::get(region.getContext(), 1), getLoc(slot));
1772 replaceValueWith(placeholder->getResult(0), conditionArg);
1773 pruner.eraseNow(placeholder);
1774 def->condition.setCondition(conditionArg);
1775 def->conditionIsPlaceholder = false;
1776 break;
1777 }
1778 }
1779 }
1780
1781 // Add successor operands to the predecessor terminators.
1782 for (auto *predecessor : node->predecessors) {
1783 // Collect the interesting reaching definitions in the predecessor.
1784 SmallVector<Value> args;
1785 for (auto [slot, which] : neededSlots) {
1786 auto *def = predecessor->valueBefore->reachingDefs.lookup(slot);
1787 auto builder = OpBuilder::atBlockTerminator(predecessor->block);
1788 switch (which) {
1789 case Which::Value:
1790 if (def) {
1791 args.push_back(def->getValueOrPlaceholder());
1792 } else {
1793 auto type = getStoredType(slot);
1794 auto flatType = builder.getIntegerType(hw::getBitWidth(type));
1795 Value value =
1796 hw::ConstantOp::create(builder, getLoc(slot), flatType, 0);
1797 if (type != flatType)
1798 value = hw::BitcastOp::create(builder, getLoc(slot), type, value);
1799 args.push_back(value);
1800 }
1801 break;
1802 case Which::Condition:
1803 if (def) {
1804 args.push_back(def->getConditionOrPlaceholder());
1805 } else {
1806 args.push_back(hw::ConstantOp::create(builder, getLoc(slot),
1807 builder.getI1Type(), 0));
1808 }
1809 break;
1810 }
1811 }
1812
1813 // Add the reaching definitions to the branch op.
1814 auto branchOp = cast<BranchOpInterface>(predecessor->terminator);
1815 for (auto &blockOperand : branchOp->getBlockOperands())
1816 if (blockOperand.get() == node->block)
1817 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
1818 .append(args);
1819 }
1820
1821 return true;
1822}
1823
1824/// Replace all uses of an old value with a new value in the IR, and update all
1825/// mentions of the old value in the lattice to the new value.
1826void Promoter::replaceValueWith(Value oldValue, Value newValue) {
1827 oldValue.replaceAllUsesWith(newValue);
1828 for (auto *def : lattice.defs) {
1829 if (def->value == oldValue)
1830 def->value = newValue;
1831 if (def->condition.isConditional() &&
1832 def->condition.getCondition() == oldValue)
1833 def->condition.setCondition(newValue);
1834 }
1835}
1836
1837//===----------------------------------------------------------------------===//
1838// Cleanup
1839//===----------------------------------------------------------------------===//
1840
1841/// Remove all unused local signals.
1842void Promoter::removeUnusedLocalSignals() {
1843 for (auto *node : lattice.nodes)
1844 if (auto *signal = dyn_cast<SignalNode>(node))
1845 removeUnusedLocalSignal(signal);
1846}
1847
1848/// Remove a local signal if it is only used by projection ops and drives, but
1849/// never probed. Since the signal is local and cannot be observed in any other
1850/// way, we can safely remove it along with any projection ops and drives.
1851void Promoter::removeUnusedLocalSignal(SignalNode *signal) {
1852 // Check if the signal is only ever projected into and driven, but never
1853 // probed.
1854 SmallSetVector<Operation *, 8> users;
1855 SmallVector<Operation *> worklist;
1856 worklist.push_back(signal->op);
1857 while (!worklist.empty()) {
1858 auto *op = worklist.pop_back_val();
1859 if (!isa<SignalOp, DriveOp, SigArrayGetOp, SigArraySliceOp, SigExtractOp,
1860 SigStructExtractOp>(op))
1861 return;
1862 for (auto *user : op->getUsers())
1863 if (users.insert(user))
1864 worklist.push_back(user);
1865 }
1866
1867 // If we get here, the local signal is never probed. This means we can safely
1868 // remove it.
1869 LLVM_DEBUG(llvm::dbgs() << "- Removing local signal " << *signal->op << "\n");
1870 for (auto *op : llvm::reverse(users))
1871 pruner.eraseNow(op);
1872}
1873
1874//===----------------------------------------------------------------------===//
1875// Pass Infrastructure
1876//===----------------------------------------------------------------------===//
1877
1878namespace {
1879struct Mem2RegPass : public llhd::impl::Mem2RegPassBase<Mem2RegPass> {
1880 void runOnOperation() override;
1881};
1882} // namespace
1883
1884void Mem2RegPass::runOnOperation() {
1885 SmallVector<Region *> regions;
1886 getOperation()->walk<WalkOrder::PreOrder>([&](Operation *op) {
1887 if (isa<ProcessOp, FinalOp, CombinationalOp>(op)) {
1888 auto &region = op->getRegion(0);
1889 if (!region.empty())
1890 regions.push_back(&region);
1891 return WalkResult::skip();
1892 }
1893 return WalkResult::advance();
1894 });
1895 for (auto *region : regions)
1896 if (failed(Promoter(*region).promote()))
1897 return signalPassFailure();
1898}
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:626
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