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