Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
12#include "mlir/Analysis/Liveness.h"
13#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
14#include "mlir/IR/Dominance.h"
15#include "llvm/Support/Debug.h"
16#include "llvm/Support/GenericIteratedDominanceFrontier.h"
17
18#define DEBUG_TYPE "llhd-mem2reg"
19
20namespace circt {
21namespace llhd {
22#define GEN_PASS_DEF_MEM2REGPASS
23#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
24} // namespace llhd
25} // namespace circt
26
27using namespace mlir;
28using namespace circt;
29using namespace llhd;
30using llvm::PointerIntPair;
31using llvm::SmallDenseSet;
32using llvm::SpecificBumpPtrAllocator;
33
34/// Check whether a value is defined by `llhd.constant_time <0ns, 0d, 1e>`.
35static bool isEpsilonDelay(Value value) {
36 if (auto timeOp = value.getDefiningOp<ConstantTimeOp>()) {
37 auto t = timeOp.getValue();
38 return t.getTime() == 0 && t.getDelta() == 0 && t.getEpsilon() == 1;
39 }
40 return false;
41}
42
43/// Check whether a value is defined by `llhd.constant_time <0ns, 1d, 0e>`.
44static bool isDeltaDelay(Value value) {
45 if (auto timeOp = value.getDefiningOp<ConstantTimeOp>()) {
46 auto t = timeOp.getValue();
47 return t.getTime() == 0 && t.getDelta() == 1 && t.getEpsilon() == 0;
48 }
49 return false;
50}
51
52/// Check whether an operation is a `llhd.drive` with an epsilon delay. This
53/// corresponds to a blocking assignment in Verilog.
54static bool isBlockingDrive(Operation *op) {
55 if (auto driveOp = dyn_cast<DrvOp>(op))
56 return isEpsilonDelay(driveOp.getTime());
57 return false;
58}
59
60/// Check whether an operation is a `llhd.drive` with a delta delay. This
61/// corresponds to a non-blocking assignment in Verilog.
62static bool isDeltaDrive(Operation *op) {
63 if (auto driveOp = dyn_cast<DrvOp>(op))
64 return isDeltaDelay(driveOp.getTime());
65 return false;
66}
67
68//===----------------------------------------------------------------------===//
69// Reaching Definitions and Placeholders
70//===----------------------------------------------------------------------===//
71
72namespace {
73/// Information about whether a definition is driven back onto its signal. For
74/// example, probes provide a definition for their signal that does not have to
75/// be driven back onto the signal. Drives on the other hand provide a
76/// definition that eventually must be driven onto the signal.
77struct DriveCondition {
78 static DriveCondition never() { return ConditionAndMode(Value{}, Never); }
79 static DriveCondition always() { return ConditionAndMode(Value{}, Always); }
80 static DriveCondition conditional(Value condition = {}) {
81 return ConditionAndMode(condition, Conditional);
82 }
83
84 bool isNever() const { return conditionAndMode.getInt() == Never; }
85 bool isAlways() const { return conditionAndMode.getInt() == Always; }
86 bool isConditional() const {
87 return conditionAndMode.getInt() == Conditional;
88 }
89
90 Value getCondition() const { return conditionAndMode.getPointer(); }
91 void setCondition(Value condition) { conditionAndMode.setPointer(condition); }
92
93 bool operator==(const DriveCondition &other) const {
94 return conditionAndMode == other.conditionAndMode;
95 }
96 bool operator!=(const DriveCondition &other) const {
97 return conditionAndMode != other.conditionAndMode;
98 }
99
100private:
101 enum {
102 Never,
103 Always,
104 Conditional,
105 };
106 typedef PointerIntPair<Value, 2> ConditionAndMode;
107 ConditionAndMode conditionAndMode;
108
109 DriveCondition(ConditionAndMode conditionAndMode)
110 : conditionAndMode(conditionAndMode) {}
111 friend DenseMapInfo<DriveCondition>;
112};
113
114/// A definition for a memory slot that may not yet have a concrete SSA value.
115/// These are created for blocks which need to merge distinct definitions for
116/// the same slot coming from its predecssors, as a standin before block
117/// arguments are created. They are also created for drives, where a concrete
118/// value is already available in the form of the driven value.
119struct Def {
120 Block *block;
121 Type type;
122 Value value;
123 DriveCondition condition;
124 bool valueIsPlaceholder = false;
125 bool conditionIsPlaceholder = false;
126
127 Def(Value value, DriveCondition condition)
128 : block(value.getParentBlock()), type(value.getType()), value(value),
129 condition(condition) {}
130 Def(Block *block, Type type, DriveCondition condition)
131 : block(block), type(type), condition(condition) {}
132
133 Value getValueOrPlaceholder();
134 Value getConditionOrPlaceholder();
135};
136} // namespace
137
138/// Return the SSA value for this definition if it already has one, or create
139/// a placeholder value if no value exists yet.
140Value Def::getValueOrPlaceholder() {
141 if (!value) {
142 auto builder = OpBuilder::atBlockBegin(block);
143 value = builder
144 .create<UnrealizedConversionCastOp>(builder.getUnknownLoc(),
145 type, ValueRange{})
146 .getResult(0);
147 valueIsPlaceholder = true;
148 }
149 return value;
150}
151
152/// Return the drive condition for this definition. Creates a constant false or
153/// true SSA value if the drive mode is "never" or "always", respectively. If
154/// the mode is "conditional", return the its condition value if it already has
155/// one, or create a placeholder value if no value exists yet.
156Value Def::getConditionOrPlaceholder() {
157 if (!condition.getCondition()) {
158 auto builder = OpBuilder::atBlockBegin(block);
159 Value value;
160 if (condition.isNever()) {
161 value = builder.create<hw::ConstantOp>(builder.getUnknownLoc(),
162 builder.getI1Type(), 0);
163 } else if (condition.isAlways()) {
164 value = builder.create<hw::ConstantOp>(builder.getUnknownLoc(),
165 builder.getI1Type(), 1);
166 } else {
167 value = builder
168 .create<UnrealizedConversionCastOp>(builder.getUnknownLoc(),
169 builder.getI1Type(),
170 ValueRange{})
171 .getResult(0);
172 conditionIsPlaceholder = true;
173 }
174 condition.setCondition(value);
175 }
176 return condition.getCondition();
177}
178
179// Allow `DriveCondition` to be used as hash map key.
180template <>
181struct llvm::DenseMapInfo<DriveCondition> {
188 static unsigned getHashValue(DriveCondition d) {
190 d.conditionAndMode);
191 }
192 static bool isEqual(DriveCondition lhs, DriveCondition rhs) {
193 return lhs == rhs;
194 }
195};
196
197//===----------------------------------------------------------------------===//
198// Lattice to Propagate Needed and Reaching Definitions
199//===----------------------------------------------------------------------===//
200
201/// The slot a reaching definition specifies a value for, alongside a bit
202/// indicating whether the definition is from a delayed drive or a blocking
203/// drive.
204using DefSlot = PointerIntPair<Value, 1>;
205static DefSlot blockingSlot(Value slot) { return {slot, 0}; }
206static DefSlot delayedSlot(Value slot) { return {slot, 1}; }
207static Value getSlot(DefSlot slot) { return slot.getPointer(); }
208static bool isDelayed(DefSlot slot) { return slot.getInt(); }
209static Type getStoredType(DefSlot slot) {
210 return cast<hw::InOutType>(slot.getPointer().getType()).getElementType();
211}
212static Location getLoc(DefSlot slot) { return slot.getPointer().getLoc(); }
213
214namespace {
215
216struct LatticeNode;
217struct BlockExit;
218struct ProbeNode;
219struct DriveNode;
220
221struct LatticeValue {
222 LatticeNode *nodeBefore = nullptr;
223 LatticeNode *nodeAfter = nullptr;
224 SmallDenseSet<Value, 1> neededDefs;
226};
227
228struct LatticeNode {
229 enum class Kind { BlockEntry, BlockExit, Probe, Drive };
230 const Kind kind;
231 LatticeNode(Kind kind) : kind(kind) {}
232};
233
234struct BlockEntry : public LatticeNode {
235 Block *block;
236 LatticeValue *valueAfter;
237 SmallVector<BlockExit *, 2> predecessors;
238 SmallVector<std::pair<Value, Def *>, 0> insertedProbes;
240
241 BlockEntry(Block *block, LatticeValue *valueAfter)
242 : LatticeNode(Kind::BlockEntry), block(block), valueAfter(valueAfter) {
243 assert(!valueAfter->nodeBefore);
244 valueAfter->nodeBefore = this;
245 }
246
247 static bool classof(const LatticeNode *n) {
248 return n->kind == Kind::BlockEntry;
249 }
250};
251
252struct BlockExit : public LatticeNode {
253 Block *block;
254 LatticeValue *valueBefore;
255 SmallVector<BlockEntry *, 2> successors;
256 Operation *terminator;
257 bool suspends;
258
259 BlockExit(Block *block, LatticeValue *valueBefore)
260 : LatticeNode(Kind::BlockExit), block(block), valueBefore(valueBefore),
261 terminator(block->getTerminator()),
262 suspends(isa<HaltOp, WaitOp>(terminator)) {
263 assert(!valueBefore->nodeAfter);
264 valueBefore->nodeAfter = this;
265 }
266
267 static bool classof(const LatticeNode *n) {
268 return n->kind == Kind::BlockExit;
269 }
270};
271
272struct OpNode : public LatticeNode {
273 Operation *op;
274 LatticeValue *valueBefore;
275 LatticeValue *valueAfter;
276
277 OpNode(Kind kind, Operation *op, LatticeValue *valueBefore,
278 LatticeValue *valueAfter)
279 : LatticeNode(kind), op(op), valueBefore(valueBefore),
280 valueAfter(valueAfter) {
281 assert(!valueBefore->nodeAfter);
282 assert(!valueAfter->nodeBefore);
283 valueBefore->nodeAfter = this;
284 valueAfter->nodeBefore = this;
285 }
286
287 static bool classof(const LatticeNode *n) {
288 return isa<ProbeNode, DriveNode>(n);
289 }
290};
291
292struct ProbeNode : public OpNode {
293 Value slot;
294
295 ProbeNode(PrbOp op, LatticeValue *valueBefore, LatticeValue *valueAfter)
296 : OpNode(Kind::Probe, op, valueBefore, valueAfter), slot(op.getSignal()) {
297 }
298
299 static bool classof(const LatticeNode *n) { return n->kind == Kind::Probe; }
300};
301
302struct DriveNode : public OpNode {
303 DefSlot slot;
304 Def *def;
305
306 DriveNode(DrvOp op, Def *def, LatticeValue *valueBefore,
307 LatticeValue *valueAfter)
308 : OpNode(Kind::Drive, op, valueBefore, valueAfter),
309 slot(isDeltaDrive(op) ? delayedSlot(op.getSignal())
310 : blockingSlot(op.getSignal())),
311 def(def) {
313 }
314
315 static bool classof(const LatticeNode *n) { return n->kind == Kind::Drive; }
316};
317
318/// A lattice of block entry and exit nodes, nodes for relevant operations such
319/// as probes and drives, and values flowing between the nodes.
320struct Lattice {
321 /// Create a new value on the lattice.
322 LatticeValue *createValue() {
323 auto *value = new (valueAllocator.Allocate()) LatticeValue();
324 values.push_back(value);
325 return value;
326 }
327
328 /// Create a new node on the lattice.
329 template <class T, typename... Args>
330 T *createNode(Args... args) {
331 auto *node =
332 new (getAllocator<T>().Allocate()) T(std::forward<Args>(args)...);
333 nodes.push_back(node);
334 return node;
335 }
336
337 /// Create a new reaching definition.
338 template <typename... Args>
339 Def *createDef(Args... args) {
340 auto *def = new (defAllocator.Allocate()) Def(std::forward<Args>(args)...);
341 defs.push_back(def);
342 return def;
343 }
344
345 /// Create a new reaching definition for an existing value in the IR.
346 Def *createDef(Value value, DriveCondition mode) {
347 auto &slot = defsForValues[{value, mode}];
348 if (!slot) {
349 slot = new (defAllocator.Allocate()) Def(value, mode);
350 defs.push_back(slot);
351 }
352 return slot;
353 }
354
355#ifndef NDEBUG
356 void dump(llvm::raw_ostream &os = llvm::dbgs());
357#endif
358
359 /// All nodes in the lattice.
360 std::vector<LatticeNode *> nodes;
361 /// All values in the lattice.
362 std::vector<LatticeValue *> values;
363 /// All reaching defs in the lattice.
364 std::vector<Def *> defs;
365 /// The reaching defs for concrete values in the IR. This map is used to
366 /// create a single def for the same SSA value to allow for pointer equality
367 /// comparisons.
368 DenseMap<std::pair<Value, DriveCondition>, Def *> defsForValues;
369
370private:
371 SpecificBumpPtrAllocator<LatticeValue> valueAllocator;
372 SpecificBumpPtrAllocator<Def> defAllocator;
373 SpecificBumpPtrAllocator<BlockEntry> blockEntryAllocator;
374 SpecificBumpPtrAllocator<BlockExit> blockExitAllocator;
375 SpecificBumpPtrAllocator<ProbeNode> probeAllocator;
376 SpecificBumpPtrAllocator<DriveNode> driveAllocator;
377
378 // Helper function to get the correct allocator given a lattice node class.
379 template <class T>
380 SpecificBumpPtrAllocator<T> &getAllocator();
381};
382
383// Specializations for the `getAllocator` template that map node types to the
384// correct allocator.
385template <>
386SpecificBumpPtrAllocator<BlockEntry> &Lattice::getAllocator() {
387 return blockEntryAllocator;
388}
389template <>
390SpecificBumpPtrAllocator<BlockExit> &Lattice::getAllocator() {
391 return blockExitAllocator;
392}
393template <>
394SpecificBumpPtrAllocator<ProbeNode> &Lattice::getAllocator() {
395 return probeAllocator;
396}
397template <>
398SpecificBumpPtrAllocator<DriveNode> &Lattice::getAllocator() {
399 return driveAllocator;
400}
401
402} // namespace
403
404#ifndef NDEBUG
405/// Print the lattice in human-readable form. Useful for debugging.
406void Lattice::dump(llvm::raw_ostream &os) {
407 // Helper functions to quickly come up with unique names for things.
408 llvm::MapVector<Block *, unsigned> blockNames;
409 llvm::MapVector<Value, unsigned> memNames;
410 llvm::MapVector<Def *, unsigned> defNames;
411
412 auto blockName = [&](Block *block) {
413 unsigned id = blockNames.insert({block, blockNames.size()}).first->second;
414 return std::string("bb") + llvm::utostr(id);
415 };
416
417 auto memName = [&](DefSlot value) {
418 unsigned id =
419 memNames.insert({getSlot(value), memNames.size()}).first->second;
420 return std::string("mem") + llvm::utostr(id) +
421 (isDelayed(value) ? "#" : "");
422 };
423
424 auto defName = [&](Def *def) {
425 unsigned id = defNames.insert({def, defNames.size()}).first->second;
426 return std::string("def") + llvm::utostr(id);
427 };
428
429 // Ensure the blocks are named in the order they were created.
430 for (auto *node : nodes)
431 if (auto *entry = dyn_cast<BlockEntry>(node))
432 blockName(entry->block);
433
434 // Iterate over all block entry nodes.
435 os << "lattice {\n";
436 for (auto *node : nodes) {
437 auto *entry = dyn_cast<BlockEntry>(node);
438 if (!entry)
439 continue;
440
441 // Print the opening braces and predecessors for the block.
442 os << " " << blockName(entry->block) << ":";
443 if (entry->predecessors.empty()) {
444 os << " // no predecessors";
445 } else {
446 os << " // from";
447 for (auto *node : entry->predecessors)
448 os << " " << blockName(node->block);
449 }
450 os << "\n";
451
452 // Print all nodes following the block entry, up until the block exit.
453 auto *value = entry->valueAfter;
454 while (true) {
455 // Print the needed defs at this lattice point.
456 if (!value->neededDefs.empty()) {
457 os << " -> need";
458 for (auto mem : value->neededDefs)
459 os << " " << memName(blockingSlot(mem));
460 os << "\n";
461 }
462 if (!value->reachingDefs.empty()) {
463 os << " -> def";
464 for (auto [mem, def] : value->reachingDefs) {
465 os << " " << memName(mem) << "=" << defName(def);
466 if (def->condition.isNever())
467 os << "[N]";
468 else if (def->condition.isAlways())
469 os << "[A]";
470 else
471 os << "[C]";
472 }
473 os << "\n";
474 }
475 if (isa<BlockExit>(value->nodeAfter))
476 break;
477
478 // Print the node.
479 if (auto *node = dyn_cast<ProbeNode>(value->nodeAfter))
480 os << " probe " << memName(blockingSlot(node->slot)) << "\n";
481 else if (auto *node = dyn_cast<DriveNode>(value->nodeAfter))
482 os << " drive " << memName(node->slot) << "\n";
483 else
484 os << " unknown\n";
485
486 // Advance to the next node.
487 value = cast<OpNode>(value->nodeAfter)->valueAfter;
488 }
489
490 // Print the closing braces and successors for the block.
491 auto *exit = cast<BlockExit>(value->nodeAfter);
492 if (isa<WaitOp>(exit->terminator))
493 os << " wait";
494 else if (exit->successors.empty())
495 os << " halt";
496 else
497 os << " goto";
498 for (auto *node : exit->successors)
499 os << " " << blockName(node->block);
500 if (exit->suspends)
501 os << " // suspends";
502 os << "\n";
503 }
504
505 // Dump the memories.
506 for (auto [mem, id] : memNames)
507 os << " mem" << id << ": " << mem << "\n";
508
509 os << "}\n";
510}
511#endif
512
513//===----------------------------------------------------------------------===//
514// Drive/Probe to SSA Value Promotion
515//===----------------------------------------------------------------------===//
516
517namespace {
518/// The main promoter forwarding drives to probes within a region.
519struct Promoter {
520 Promoter(Region &region) : region(region) {}
521 LogicalResult promote();
522
523 void findPromotableSlots();
524
525 void captureAcrossWait();
526 void captureAcrossWait(PrbOp probeOp, ArrayRef<WaitOp> waitOps,
527 Liveness &liveness, DominanceInfo &dominance);
528
529 void constructLattice();
530 void propagateBackward();
531 void propagateBackward(LatticeNode *node);
532 void propagateForward(bool optimisticMerges);
533 void propagateForward(LatticeNode *node, bool optimisticMerges);
534 void markDirty(LatticeNode *node);
535
536 void insertProbeBlocks();
537 void insertProbes();
538 void insertProbes(BlockEntry *node);
539
540 void insertDriveBlocks();
541 void insertDrives();
542 void insertDrives(BlockExit *node);
543 void insertDrives(DriveNode *node);
544
545 void resolveDefinitions();
546 void resolveDefinitions(ProbeNode *node);
547
548 void insertBlockArgs();
549 bool insertBlockArgs(BlockEntry *node);
550 void replaceValueWith(Value oldValue, Value newValue);
551
552 /// The region we are promoting in.
553 Region &region;
554
555 /// The slots we are promoting. Mostly `llhd.sig` ops in practice. This
556 /// establishes a deterministic order for slot allocations, such that
557 /// everything else in the pass can operate using unordered maps and sets.
558 SmallVector<Value> slots;
559 /// The inverse of `slots`.
561
562 /// The lattice used to propagate needed definitions backwards and reaching
563 /// definitions forwards.
564 Lattice lattice;
565 /// A worklist of lattice nodes used within calls to `propagate*`.
566 SmallPtrSet<LatticeNode *, 4> dirtyNodes;
567};
568} // namespace
569
570LogicalResult Promoter::promote() {
571 if (region.empty())
572 return success();
573
574 findPromotableSlots();
575 if (slots.empty())
576 return success();
577
578 captureAcrossWait();
579
580 constructLattice();
581 LLVM_DEBUG({
582 llvm::dbgs() << "Initial lattice:\n";
583 lattice.dump();
584 });
585
586 // Propagate the needed definitions backward across the lattice.
587 propagateBackward();
588 LLVM_DEBUG({
589 llvm::dbgs() << "After backward propagation:\n";
590 lattice.dump();
591 });
592
593 // Insert probes wherever a def is needed for the first time.
594 insertProbeBlocks();
595 insertProbes();
596 LLVM_DEBUG({
597 llvm::dbgs() << "After probe insertion:\n";
598 lattice.dump();
599 });
600
601 // Propagate the reaching definitions forward across the lattice.
602 propagateForward(true);
603 propagateForward(false);
604 LLVM_DEBUG({
605 llvm::dbgs() << "After forward propagation:\n";
606 lattice.dump();
607 });
608
609 // Resolve definitions.
610 resolveDefinitions();
611
612 // Insert drives wherever a reaching def can no longer propagate.
613 insertDriveBlocks();
614 insertDrives();
615 LLVM_DEBUG({
616 llvm::dbgs() << "After def resolution and drive insertion:\n";
617 lattice.dump();
618 });
619
620 // Insert the necessary block arguments.
621 insertBlockArgs();
622
623 return success();
624}
625
626/// Identify any promotable slots probed or driven under the current region.
627void Promoter::findPromotableSlots() {
628 SmallPtrSet<Value, 8> seenSlots;
629 region.walk([&](Operation *op) {
630 for (auto operand : op->getOperands()) {
631 if (!seenSlots.insert(operand).second)
632 continue;
633
634 // Ensure the slot is not used in any way we cannot reason about.
635 if (!operand.getDefiningOp<llhd::SignalOp>())
636 continue;
637 if (!llvm::all_of(operand.getUsers(), [&](auto *user) {
638 // We don't support nested probes and drives.
639 if (region.isProperAncestor(user->getParentRegion()))
640 return false;
641 // Ignore uses outside of the region.
642 if (user->getParentRegion() != &region)
643 return true;
644 return isa<PrbOp>(user) || isBlockingDrive(user) ||
645 isDeltaDrive(user);
646 }))
647 continue;
648
649 slotOrder.insert({operand, slots.size()});
650 slots.push_back(operand);
651 }
652 });
653
654 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size() << " promotable slots\n");
655}
656
657/// Explicitly capture any probes that are live across an `llhd.wait` as block
658/// arguments and destination operand of that wait. This ensures that replacing
659/// the probe with a reaching definition later on will capture the value of the
660/// reaching definition before the wait.
661void Promoter::captureAcrossWait() {
662 if (region.hasOneBlock())
663 return;
664
665 SmallVector<WaitOp> waitOps;
666 for (auto &block : region)
667 if (auto waitOp = dyn_cast<WaitOp>(block.getTerminator()))
668 waitOps.push_back(waitOp);
669
670 DominanceInfo dominance(region.getParentOp());
671 Liveness liveness(region.getParentOp());
672
673 SmallVector<WaitOp> crossingWaitOps;
674 for (auto &block : region) {
675 for (auto probeOp : block.getOps<PrbOp>()) {
676 for (auto waitOp : waitOps)
677 if (liveness.getLiveness(waitOp->getBlock())->isLiveOut(probeOp))
678 crossingWaitOps.push_back(waitOp);
679 if (!crossingWaitOps.empty()) {
680 captureAcrossWait(probeOp, crossingWaitOps, liveness, dominance);
681 crossingWaitOps.clear();
682 }
683 }
684 }
685}
686
687/// Add a probe as block argument to a list of wait ops and update uses of the
688/// probe to use the added block arguments as appropriate. This may insert
689/// additional block arguments in case the probe and added block arguments both
690/// reach the same block.
691void Promoter::captureAcrossWait(PrbOp probeOp, ArrayRef<WaitOp> waitOps,
692 Liveness &liveness, DominanceInfo &dominance) {
693 LLVM_DEBUG({
694 llvm::dbgs() << "Capture " << probeOp << "\n";
695 for (auto waitOp : waitOps)
696 llvm::dbgs() << "- Across " << waitOp << "\n";
697 });
698
699 // Calculate the merge points for this probe once it gets promoted to block
700 // arguments across the wait ops.
701 auto &domTree = dominance.getDomTree(&region);
702 llvm::IDFCalculatorBase<Block, false> idfCalculator(domTree);
703
704 // Calculate the set of blocks which will define this probe as a distinct
705 // value.
706 SmallPtrSet<Block *, 4> definingBlocks;
707 definingBlocks.insert(probeOp->getBlock());
708 for (auto waitOp : waitOps)
709 definingBlocks.insert(waitOp.getDest());
710 idfCalculator.setDefiningBlocks(definingBlocks);
711
712 // Calculate where the probe is live.
713 SmallPtrSet<Block *, 16> liveInBlocks;
714 for (auto &block : region)
715 if (liveness.getLiveness(&block)->isLiveIn(probeOp))
716 liveInBlocks.insert(&block);
717 idfCalculator.setLiveInBlocks(liveInBlocks);
718
719 // Calculate the merge points where we will have to insert block arguments for
720 // this probe.
721 SmallVector<Block *> mergePointsVec;
722 idfCalculator.calculate(mergePointsVec);
723 SmallPtrSet<Block *, 16> mergePoints(mergePointsVec.begin(),
724 mergePointsVec.end());
725 for (auto waitOp : waitOps)
726 mergePoints.insert(waitOp.getDest());
727 LLVM_DEBUG(llvm::dbgs() << "- " << mergePoints.size() << " merge points\n");
728
729 // Perform a depth-first search starting at the block containing the probe,
730 // which dominates all its uses. When we encounter a block that is a merge
731 // point, insert a block argument.
732 struct WorklistItem {
733 DominanceInfoNode *domNode;
734 Value reachingDef;
735 };
736 SmallVector<WorklistItem> worklist;
737 worklist.push_back({domTree.getNode(probeOp->getBlock()), probeOp});
738
739 while (!worklist.empty()) {
740 auto item = worklist.pop_back_val();
741 auto *block = item.domNode->getBlock();
742
743 // If this block is a merge point, insert a block argument for the probe.
744 if (mergePoints.contains(block))
745 item.reachingDef =
746 block->addArgument(probeOp.getType(), probeOp.getLoc());
747
748 // Replace any uses of the probe in this block with the current reaching
749 // definition.
750 for (auto &op : *block)
751 op.replaceUsesOfWith(probeOp, item.reachingDef);
752
753 // If the terminator of this block branches to a merge point, add the
754 // current reaching definition as a destination operand.
755 if (auto branchOp = dyn_cast<BranchOpInterface>(block->getTerminator())) {
756 for (auto &blockOperand : branchOp->getBlockOperands())
757 if (mergePoints.contains(blockOperand.get()))
758 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
759 .append(item.reachingDef);
760 } else if (auto waitOp = dyn_cast<WaitOp>(block->getTerminator())) {
761 if (mergePoints.contains(waitOp.getDest()))
762 waitOp.getDestOperandsMutable().append(item.reachingDef);
763 }
764
765 for (auto *child : item.domNode->children())
766 worklist.push_back({child, item.reachingDef});
767 }
768}
769
770//===----------------------------------------------------------------------===//
771// Lattice Construction and Propagation
772//===----------------------------------------------------------------------===//
773
774/// Populate the lattice with nodes and values corresponding to the blocks and
775/// relevant operations in the region we're promoting.
776void Promoter::constructLattice() {
777 // Create entry nodes for each block.
779 for (auto &block : region) {
780 auto *entry = lattice.createNode<BlockEntry>(&block, lattice.createValue());
781 blockEntries.insert({&block, entry});
782 }
783
784 // Create nodes for each operation that is relevant for the pass.
785 for (auto &block : region) {
786 auto *valueBefore = blockEntries.lookup(&block)->valueAfter;
787
788 // Handle operations.
789 for (auto &op : block.without_terminator()) {
790 // Handle probes.
791 if (auto probeOp = dyn_cast<PrbOp>(op)) {
792 if (!slotOrder.contains(probeOp.getSignal()))
793 continue;
794 auto *node = lattice.createNode<ProbeNode>(probeOp, valueBefore,
795 lattice.createValue());
796 valueBefore = node->valueAfter;
797 continue;
798 }
799
800 // Handle drives.
801 if (auto driveOp = dyn_cast<DrvOp>(op)) {
802 if (!isBlockingDrive(&op) && !isDeltaDrive(&op))
803 continue;
804 if (!slotOrder.contains(driveOp.getSignal()))
805 continue;
806 auto condition = DriveCondition::always();
807 if (auto enable = driveOp.getEnable())
808 condition = DriveCondition::conditional(enable);
809 auto *def = lattice.createDef(driveOp.getValue(), condition);
810 auto *node = lattice.createNode<DriveNode>(driveOp, def, valueBefore,
811 lattice.createValue());
812 valueBefore = node->valueAfter;
813 continue;
814 }
815 }
816
817 // Create the exit node for the block.
818 auto *exit = lattice.createNode<BlockExit>(&block, valueBefore);
819 for (auto *otherBlock : exit->terminator->getSuccessors()) {
820 auto *otherEntry = blockEntries.lookup(otherBlock);
821 exit->successors.push_back(otherEntry);
822 otherEntry->predecessors.push_back(exit);
823 }
824 }
825}
826
827/// Propagate the lattice values backwards against control flow until a fixed
828/// point is reached.
829void Promoter::propagateBackward() {
830 for (auto *node : lattice.nodes)
831 propagateBackward(node);
832 while (!dirtyNodes.empty()) {
833 auto *node = *dirtyNodes.begin();
834 dirtyNodes.erase(node);
835 propagateBackward(node);
836 }
837}
838
839/// Propagate the lattice value after a node backward to the value before a
840/// node.
841void Promoter::propagateBackward(LatticeNode *node) {
842 auto update = [&](LatticeValue *value, auto &neededDefs) {
843 if (value->neededDefs != neededDefs) {
844 value->neededDefs = neededDefs;
845 markDirty(value->nodeBefore);
846 }
847 };
848
849 // Probes need a definition for the probed slot to be available.
850 if (auto *probe = dyn_cast<ProbeNode>(node)) {
851 auto needed = probe->valueAfter->neededDefs;
852 needed.insert(probe->slot);
853 update(probe->valueBefore, needed);
854 return;
855 }
856
857 // Blocking drives kill the need for a definition to be available, since they
858 // provide a definition themselves.
859 if (auto *drive = dyn_cast<DriveNode>(node)) {
860 auto needed = drive->valueAfter->neededDefs;
861 if (!isDelayed(drive->slot))
862 needed.erase(getSlot(drive->slot));
863 update(drive->valueBefore, needed);
864 return;
865 }
866
867 // Block entries simply trigger updates to all their predecessors.
868 if (auto *entry = dyn_cast<BlockEntry>(node)) {
869 for (auto *predecessor : entry->predecessors)
870 markDirty(predecessor);
871 return;
872 }
873
874 // Block exits merge any needed definitions from their successors.
875 if (auto *exit = dyn_cast<BlockExit>(node)) {
876 if (exit->suspends)
877 return;
878 SmallDenseSet<Value, 1> needed;
879 for (auto *successors : exit->successors)
880 needed.insert(successors->valueAfter->neededDefs.begin(),
881 successors->valueAfter->neededDefs.end());
882 update(exit->valueBefore, needed);
883 return;
884 }
885
886 assert(false && "unhandled node in backward propagation");
887}
888
889/// Propagate the lattice values forwards along with control flow until a fixed
890/// point is reached. If `optimisticMerges` is true, block entry points
891/// propagate definitions from their predecessors into the block without
892/// creating a merge definition even if the definition is not available in all
893/// predecessors. This is overly optimistic, but initially helps definitions
894/// propagate through loop structures. If `optimisticMerges` is false, block
895/// entry points create merge definitions for definitions that are not available
896/// in all predecessors.
897void Promoter::propagateForward(bool optimisticMerges) {
898 for (auto *node : lattice.nodes)
899 propagateForward(node, optimisticMerges);
900 while (!dirtyNodes.empty()) {
901 auto *node = *dirtyNodes.begin();
902 dirtyNodes.erase(node);
903 propagateForward(node, optimisticMerges);
904 }
905}
906
907/// Propagate the lattice value before a node forward to the value after a node.
908void Promoter::propagateForward(LatticeNode *node, bool optimisticMerges) {
909 auto update = [&](LatticeValue *value, auto &reachingDefs) {
910 if (value->reachingDefs != reachingDefs) {
911 value->reachingDefs = reachingDefs;
912 markDirty(value->nodeAfter);
913 }
914 };
915
916 // Probes simply propagate any reaching defs.
917 if (auto *probe = dyn_cast<ProbeNode>(node)) {
918 update(probe->valueAfter, probe->valueBefore->reachingDefs);
919 return;
920 }
921
922 // Drives propagate the driven value as a reaching def. Blocking drives kill
923 // earlier non-blocking drives. This reflects Verilog and VHDL behaviour,
924 // where a drive sequence like
925 //
926 // a <= #10ns 42; // A
927 // a <= 43; // B
928 // a = 44; // C
929 //
930 // would see (B) override (A), because it happens earlier, and (C) override
931 // (B), because it in turn happens earlier.
932 if (auto *drive = dyn_cast<DriveNode>(node)) {
933 auto reaching = drive->valueBefore->reachingDefs;
934 reaching[drive->slot] = drive->def;
935 if (!isDelayed(drive->slot))
936 reaching.erase(delayedSlot(getSlot(drive->slot)));
937 update(drive->valueAfter, reaching);
938 return;
939 }
940
941 // Block entry points propagate any reaching definitions available in all
942 // predecessors, plus any probes inserted locally.
943 if (auto *entry = dyn_cast<BlockEntry>(node)) {
944 // Propagate reaching definitions for each inserted probe.
946 for (auto [slot, insertedProbe] : entry->insertedProbes)
947 reaching[blockingSlot(slot)] = insertedProbe;
948
949 // Propagate reaching definitions from predecessors, creating new
950 // definitions in case of a merge.
952 for (auto *predecessor : entry->predecessors)
953 if (!predecessor->suspends)
954 reachingDefs.insert(predecessor->valueBefore->reachingDefs.begin(),
955 predecessor->valueBefore->reachingDefs.end());
956
957 for (auto pair : reachingDefs) {
958 DefSlot slot = pair.first;
959 Def *reachingDef = pair.second;
960 DriveCondition reachingDefCondition = reachingDef->condition;
961
962 // Do not override inserted probes.
963 if (reaching.contains(slot))
964 continue;
965
966 // Check if all predecessors provide a definition for this slot. If any
967 // multiple definitions for the same slot reach us, simply set the
968 // `reachingDef` to null such that we can insert a new merge definition.
969 // Separately track whether the drive mode of all definitions is
970 // identical. This is often the case, for example when the definitions of
971 // two unconditional drives converge, and we would like to preserve that
972 // both drives were unconditional, even if the driven value differs.
973 if (llvm::any_of(entry->predecessors, [&](auto *predecessor) {
974 return predecessor->suspends;
975 }))
976 continue;
977 for (auto *predecessor : entry->predecessors) {
978 auto otherDef = predecessor->valueBefore->reachingDefs.lookup(slot);
979 if (!otherDef && optimisticMerges)
980 continue;
981 // If the definitions are not identical, indicate that we will have
982 // to create a new merge def.
983 if (reachingDef != otherDef)
984 reachingDef = nullptr;
985 // If the definitions have different modes, indicate that we will
986 // have to create a conditional drive later.
987 auto condition =
988 otherDef ? otherDef->condition : DriveCondition::never();
989 if (reachingDefCondition != condition)
990 reachingDefCondition = DriveCondition::conditional();
991 }
992
993 // Create a merge definition if different definitions reach us from our
994 // predecessors.
995 if (!reachingDef)
996 reachingDef = entry->mergedDefs.lookup(slot);
997 if (!reachingDef) {
998 reachingDef = lattice.createDef(entry->block, getStoredType(slot),
999 reachingDefCondition);
1000 entry->mergedDefs.insert({slot, reachingDef});
1001 } else {
1002 reachingDef->condition = reachingDefCondition;
1003 }
1004 reaching.insert({slot, reachingDef});
1005 }
1006
1007 update(entry->valueAfter, reaching);
1008 return;
1009 }
1010
1011 // Block exits simply trigger updates to all their successors.
1012 if (auto *exit = dyn_cast<BlockExit>(node)) {
1013 for (auto *successor : exit->successors)
1014 markDirty(successor);
1015 return;
1016 }
1017
1018 assert(false && "unhandled node in forward propagation");
1019}
1020
1021/// Mark a lattice node to be updated during propagation.
1022void Promoter::markDirty(LatticeNode *node) {
1023 assert(node);
1024 dirtyNodes.insert(node);
1025}
1026
1027//===----------------------------------------------------------------------===//
1028// Drive/Probe Insertion
1029//===----------------------------------------------------------------------===//
1030
1031/// Insert additional probe blocks where needed. This can happen if a definition
1032/// is needed in a block which has a suspending and non-suspending predecessor.
1033/// In that case we would like to insert probes in the predecessor blocks, but
1034/// cannot do so because of the suspending predecessor.
1035void Promoter::insertProbeBlocks() {
1036 // Find all blocks that have any needed definition that can't propagate beyond
1037 // one of its predecessors. If that's the case, we need an additional probe
1038 // block after that predecessor.
1039 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1040 for (auto *node : lattice.nodes) {
1041 if (auto *entry = dyn_cast<BlockEntry>(node)) {
1042 SmallVector<Value> partialSlots;
1043 for (auto slot : entry->valueAfter->neededDefs) {
1044 unsigned numIncoming = 0;
1045 for (auto *predecessor : entry->predecessors)
1046 if (predecessor->valueBefore->neededDefs.contains(slot))
1047 ++numIncoming;
1048 if (numIncoming != 0 && numIncoming != entry->predecessors.size())
1049 partialSlots.push_back(slot);
1050 }
1051 for (auto *predecessor : entry->predecessors)
1052 if (llvm::any_of(partialSlots, [&](auto slot) {
1053 return !predecessor->valueBefore->neededDefs.contains(slot);
1054 }))
1055 worklist.insert({predecessor, entry});
1056 }
1057 }
1058
1059 // Insert probe blocks after all blocks we have identified.
1060 for (auto [predecessor, successor] : worklist) {
1061 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe block towards " << successor
1062 << " after " << *predecessor->terminator << "\n");
1063 OpBuilder builder(predecessor->terminator);
1064 auto *newBlock = builder.createBlock(successor->block);
1065 for (auto oldArg : successor->block->getArguments())
1066 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1067 builder.create<cf::BranchOp>(predecessor->terminator->getLoc(),
1068 successor->block, newBlock->getArguments());
1069 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1070 if (blockOp.get() == successor->block)
1071 blockOp.set(newBlock);
1072
1073 // Create new nodes in the lattice for the added block.
1074 auto *value = lattice.createValue();
1075 value->neededDefs = successor->valueAfter->neededDefs;
1076 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1077 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1078 newEntry->predecessors.push_back(predecessor);
1079 newExit->successors.push_back(successor);
1080 llvm::replace(successor->predecessors, predecessor, newExit);
1081 llvm::replace(predecessor->successors, successor, newEntry);
1082 }
1083}
1084
1085/// Insert probes wherever a definition is needed for the first time. This is
1086/// the case in the entry block, after any suspensions, and after operations
1087/// that have unknown effects on memory slots.
1088void Promoter::insertProbes() {
1089 for (auto *node : lattice.nodes) {
1090 if (auto *entry = dyn_cast<BlockEntry>(node))
1091 insertProbes(entry);
1092 }
1093}
1094
1095/// Insert probes at the beginning of a block for definitions that are needed in
1096/// this block but not in its predecessors.
1097void Promoter::insertProbes(BlockEntry *node) {
1098 auto builder = OpBuilder::atBlockBegin(node->block);
1099 for (auto neededDef : slots) {
1100 if (!node->valueAfter->neededDefs.contains(neededDef))
1101 continue;
1102 if (!node->predecessors.empty() &&
1103 llvm::all_of(node->predecessors, [&](auto *predecessor) {
1104 return predecessor->valueBefore->neededDefs.contains(neededDef);
1105 }))
1106 continue;
1107 LLVM_DEBUG(llvm::dbgs() << "- Inserting probe for " << neededDef
1108 << " in block " << node->block << "\n");
1109 auto value = builder.create<PrbOp>(neededDef.getLoc(), neededDef);
1110 auto *def = lattice.createDef(value, DriveCondition::never());
1111 node->insertedProbes.push_back({neededDef, def});
1112 }
1113}
1114
1115/// Insert additional drive blocks where needed. This can happen if a definition
1116/// continues into some of a block's successors, but not all of them.
1117void Promoter::insertDriveBlocks() {
1118 // Find all blocks that have any reaching definition that can't propagate
1119 // beyond one of its successors. If that's the case, we need an additional
1120 // drive block before that successor.
1121 SmallDenseSet<std::pair<BlockExit *, BlockEntry *>, 1> worklist;
1122 for (auto *node : lattice.nodes) {
1123 if (auto *exit = dyn_cast<BlockExit>(node)) {
1124 SmallVector<DefSlot> partialSlots;
1125 for (auto [slot, reachingDef] : exit->valueBefore->reachingDefs) {
1126 if (reachingDef->condition.isNever())
1127 continue;
1128 unsigned numContinues = 0;
1129 for (auto *successor : exit->successors)
1130 if (successor->valueAfter->reachingDefs.contains(slot))
1131 ++numContinues;
1132 if (numContinues != 0 && numContinues != exit->successors.size())
1133 partialSlots.push_back(slot);
1134 }
1135 for (auto *successor : exit->successors)
1136 if (llvm::any_of(partialSlots, [&](auto slot) {
1137 return !successor->valueAfter->reachingDefs.contains(slot);
1138 }))
1139 worklist.insert({exit, successor});
1140 }
1141 }
1142
1143 // Insert drive blocks before all blocks we have identified.
1144 for (auto [predecessor, successor] : worklist) {
1145 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive block towards " << successor
1146 << " after " << *predecessor->terminator << "\n");
1147 OpBuilder builder(predecessor->terminator);
1148 auto *newBlock = builder.createBlock(successor->block);
1149 for (auto oldArg : successor->block->getArguments())
1150 newBlock->addArgument(oldArg.getType(), oldArg.getLoc());
1151 builder.create<cf::BranchOp>(predecessor->terminator->getLoc(),
1152 successor->block, newBlock->getArguments());
1153 for (auto &blockOp : predecessor->terminator->getBlockOperands())
1154 if (blockOp.get() == successor->block)
1155 blockOp.set(newBlock);
1156
1157 // Create new nodes in the lattice for the added block.
1158 auto *value = lattice.createValue();
1159 value->neededDefs = successor->valueAfter->neededDefs;
1160 value->reachingDefs = predecessor->valueBefore->reachingDefs;
1161 auto *newEntry = lattice.createNode<BlockEntry>(newBlock, value);
1162 auto *newExit = lattice.createNode<BlockExit>(newBlock, value);
1163 newEntry->predecessors.push_back(predecessor);
1164 newExit->successors.push_back(successor);
1165 llvm::replace(successor->predecessors, predecessor, newExit);
1166 llvm::replace(predecessor->successors, successor, newEntry);
1167 }
1168}
1169
1170/// Insert drives wherever a reaching definition can no longer propagate. This
1171/// is the before any suspensions and before operations that have unknown
1172/// effects on memory slots.
1173void Promoter::insertDrives() {
1174 for (auto *node : lattice.nodes) {
1175 if (auto *exit = dyn_cast<BlockExit>(node))
1176 insertDrives(exit);
1177 else if (auto *drive = dyn_cast<DriveNode>(node))
1178 insertDrives(drive);
1179 }
1180}
1181
1182/// Insert drives at block terminators for definitions that do not propagate
1183/// into successors.
1184void Promoter::insertDrives(BlockExit *node) {
1185 auto builder = OpBuilder::atBlockTerminator(node->block);
1186
1187 ConstantTimeOp epsilonTime;
1188 ConstantTimeOp deltaTime;
1189 auto getTime = [&](bool delta) {
1190 if (delta) {
1191 if (!deltaTime)
1192 deltaTime = builder.create<ConstantTimeOp>(node->terminator->getLoc(),
1193 0, "ns", 1, 0);
1194 return deltaTime;
1195 }
1196 if (!epsilonTime)
1197 epsilonTime = builder.create<ConstantTimeOp>(node->terminator->getLoc(),
1198 0, "ns", 0, 1);
1199 return epsilonTime;
1200 };
1201
1202 auto insertDriveForSlot = [&](DefSlot slot) {
1203 auto reachingDef = node->valueBefore->reachingDefs.lookup(slot);
1204 if (!reachingDef || reachingDef->condition.isNever())
1205 return;
1206 if (!node->suspends && !node->successors.empty() &&
1207 llvm::all_of(node->successors, [&](auto *successor) {
1208 return successor->valueAfter->reachingDefs.contains(slot);
1209 }))
1210 return;
1211 LLVM_DEBUG(llvm::dbgs() << "- Inserting drive for " << getSlot(slot) << " "
1212 << (isDelayed(slot) ? "(delayed)" : "(blocking)")
1213 << " before " << *node->terminator << "\n");
1214 auto time = getTime(isDelayed(slot));
1215 auto value = reachingDef->getValueOrPlaceholder();
1216 auto enable = reachingDef->condition.isConditional()
1217 ? reachingDef->getConditionOrPlaceholder()
1218 : Value{};
1219 builder.create<DrvOp>(getLoc(slot), getSlot(slot), value, time, enable);
1220 };
1221
1222 for (auto slot : slots)
1223 insertDriveForSlot(blockingSlot(slot));
1224 for (auto slot : slots)
1225 insertDriveForSlot(delayedSlot(slot));
1226}
1227
1228/// Remove drives to slots that we are promoting. These have been replaced with
1229/// new drives at block exits.
1230void Promoter::insertDrives(DriveNode *node) {
1231 if (!slotOrder.contains(getSlot(node->slot)))
1232 return;
1233 LLVM_DEBUG(llvm::dbgs() << "- Removing drive " << *node->op << "\n");
1234 auto *delayOp = cast<DrvOp>(node->op).getTime().getDefiningOp();
1235 node->op->erase();
1236 node->op = nullptr;
1237 if (delayOp && isOpTriviallyDead(delayOp))
1238 delayOp->erase();
1239}
1240
1241//===----------------------------------------------------------------------===//
1242// Drive-to-Probe Forwarding
1243//===----------------------------------------------------------------------===//
1244
1245/// Forward definitions throughout the IR.
1246void Promoter::resolveDefinitions() {
1247 for (auto *node : lattice.nodes)
1248 if (auto *probe = dyn_cast<ProbeNode>(node))
1249 resolveDefinitions(probe);
1250}
1251
1252/// Replace probes with the corresponding reaching definition.
1253void Promoter::resolveDefinitions(ProbeNode *node) {
1254 if (!slotOrder.contains(node->slot))
1255 return;
1256 auto *def = node->valueBefore->reachingDefs.lookup(blockingSlot(node->slot));
1257 assert(def && "no definition reaches probe");
1258 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << *node->op << "\n");
1259 replaceValueWith(node->op->getResult(0), def->getValueOrPlaceholder());
1260 node->op->erase();
1261 node->op = nullptr;
1262}
1263
1264//===----------------------------------------------------------------------===//
1265// Block Argument Insertion
1266//===----------------------------------------------------------------------===//
1267
1268/// Insert block arguments into the IR.
1269void Promoter::insertBlockArgs() {
1270 bool anyArgsInserted = true;
1271 while (anyArgsInserted) {
1272 anyArgsInserted = false;
1273 for (auto *node : lattice.nodes)
1274 if (auto *entry = dyn_cast<BlockEntry>(node))
1275 anyArgsInserted |= insertBlockArgs(entry);
1276 }
1277}
1278
1279/// Insert block arguments for any merging definitions for which a placeholder
1280/// value has been created. Also insert corresponding successor operands to any
1281/// ops branching here. Returns true if any arguments were inserted.
1282///
1283/// This function may create additional placeholders in predecessor blocks.
1284/// Creating block arguments in a later block may uncover additional arguments
1285/// to be inserted in a previous one. Therefore this function must be called
1286/// until no more block arguments are inserted.
1287bool Promoter::insertBlockArgs(BlockEntry *node) {
1288 // Determine which slots require a merging definition. Use the `slots` array
1289 // for this to have a deterministic order for the block arguments. We only
1290 // insert block arguments for the def's value or drive condition if
1291 // placeholders have been created for them, indicating that they are actually
1292 // used.
1293 enum class Which { Value, Condition };
1294 SmallVector<std::pair<DefSlot, Which>> neededSlots;
1295 auto addNeededSlot = [&](DefSlot slot) {
1296 if (auto *def = node->mergedDefs.lookup(slot)) {
1297 if (node->valueAfter->reachingDefs.contains(slot)) {
1298 if (def->valueIsPlaceholder)
1299 neededSlots.push_back({slot, Which::Value});
1300 if (def->conditionIsPlaceholder)
1301 neededSlots.push_back({slot, Which::Condition});
1302 }
1303 }
1304 };
1305 for (auto slot : slots) {
1306 addNeededSlot(blockingSlot(slot));
1307 addNeededSlot(delayedSlot(slot));
1308 }
1309 if (neededSlots.empty())
1310 return false;
1311 LLVM_DEBUG(llvm::dbgs() << "- Adding " << neededSlots.size()
1312 << " args to block " << node->block << "\n");
1313
1314 // Add the block arguments.
1315 for (auto [slot, which] : neededSlots) {
1316 auto *def = node->mergedDefs.lookup(slot);
1317 assert(def);
1318 switch (which) {
1319 case Which::Value: {
1320 // Create an argument for the definition's value and replace any
1321 // placeholder we might have created earlier.
1322 auto *placeholder = def->value.getDefiningOp();
1323 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1324 "placeholder replaced but valueIsPlaceholder still set");
1325 auto arg = node->block->addArgument(getStoredType(slot), getLoc(slot));
1326 replaceValueWith(placeholder->getResult(0), arg);
1327 placeholder->erase();
1328 def->value = arg;
1329 def->valueIsPlaceholder = false;
1330 break;
1331 }
1332 case Which::Condition: {
1333 // If the definition's drive mode is conditional, create an argument for
1334 // the drive condition and replace any placeholder we might have created
1335 // earlier.
1336 auto *placeholder = def->condition.getCondition().getDefiningOp();
1337 assert(isa_and_nonnull<UnrealizedConversionCastOp>(placeholder) &&
1338 "placeholder replaced but conditionIsPlaceholder still set");
1339 auto conditionArg = node->block->addArgument(
1340 IntegerType::get(region.getContext(), 1), getLoc(slot));
1341 replaceValueWith(placeholder->getResult(0), conditionArg);
1342 placeholder->erase();
1343 def->condition.setCondition(conditionArg);
1344 def->conditionIsPlaceholder = false;
1345 break;
1346 }
1347 }
1348 }
1349
1350 // Add successor operands to the predecessor terminators.
1351 for (auto *predecessor : node->predecessors) {
1352 // Collect the interesting reaching definitions in the predecessor.
1353 SmallVector<Value> args;
1354 for (auto [slot, which] : neededSlots) {
1355 auto *def = predecessor->valueBefore->reachingDefs.lookup(slot);
1356 auto builder = OpBuilder::atBlockTerminator(predecessor->block);
1357 switch (which) {
1358 case Which::Value:
1359 if (def) {
1360 args.push_back(def->getValueOrPlaceholder());
1361 } else {
1362 auto type = getStoredType(slot);
1363 auto flatType = builder.getIntegerType(hw::getBitWidth(type));
1364 Value value =
1365 builder.create<hw::ConstantOp>(getLoc(slot), flatType, 0);
1366 if (type != flatType)
1367 value = builder.create<hw::BitcastOp>(getLoc(slot), type, value);
1368 args.push_back(value);
1369 }
1370 break;
1371 case Which::Condition:
1372 if (def) {
1373 args.push_back(def->getConditionOrPlaceholder());
1374 } else {
1375 args.push_back(builder.create<hw::ConstantOp>(
1376 getLoc(slot), builder.getI1Type(), 0));
1377 }
1378 break;
1379 }
1380 }
1381
1382 // Add the reaching definitions to the branch op.
1383 auto branchOp = cast<BranchOpInterface>(predecessor->terminator);
1384 for (auto &blockOperand : branchOp->getBlockOperands())
1385 if (blockOperand.get() == node->block)
1386 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
1387 .append(args);
1388 }
1389
1390 return true;
1391}
1392
1393/// Replace all uses of an old value with a new value in the IR, and update all
1394/// mentions of the old value in the lattice to the new value.
1395void Promoter::replaceValueWith(Value oldValue, Value newValue) {
1396 oldValue.replaceAllUsesWith(newValue);
1397 for (auto *def : lattice.defs) {
1398 if (def->value == oldValue)
1399 def->value = newValue;
1400 if (def->condition.isConditional() &&
1401 def->condition.getCondition() == oldValue)
1402 def->condition.setCondition(newValue);
1403 }
1404}
1405
1406//===----------------------------------------------------------------------===//
1407// Pass Infrastructure
1408//===----------------------------------------------------------------------===//
1409
1410namespace {
1411struct Mem2RegPass : public llhd::impl::Mem2RegPassBase<Mem2RegPass> {
1412 void runOnOperation() override;
1413};
1414} // namespace
1415
1416void Mem2RegPass::runOnOperation() {
1417 SmallVector<Region *> regions;
1418 getOperation()->walk<WalkOrder::PreOrder>([&](Operation *op) {
1419 if (isa<ProcessOp, FinalOp>(op)) {
1420 auto &region = op->getRegion(0);
1421 if (!region.empty())
1422 regions.push_back(&region);
1423 return WalkResult::skip();
1424 }
1425 return WalkResult::advance();
1426 });
1427 for (auto *region : regions)
1428 if (failed(Promoter(*region).promote()))
1429 return signalPassFailure();
1430}
static bool isAlways(Attribute attr, bool expected)
Definition ArcFolds.cpp:22
static bool isLiveOut(Value val)
assert(baseType &&"element must be base type")
static void dump(DIModule &module, raw_indented_ostream &os)
static bool isDelayed(DefSlot slot)
Definition Mem2Reg.cpp:208
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:212
static bool isDeltaDrive(Operation *op)
Check whether an operation is a llhd.drive with a delta delay.
Definition Mem2Reg.cpp:62
static bool isDeltaDelay(Value value)
Check whether a value is defined by llhd.constant_time <0ns, 1d, 0e>.
Definition Mem2Reg.cpp:44
static DefSlot blockingSlot(Value slot)
Definition Mem2Reg.cpp:205
static DefSlot delayedSlot(Value slot)
Definition Mem2Reg.cpp:206
static Value getSlot(DefSlot slot)
Definition Mem2Reg.cpp:207
static Type getStoredType(DefSlot slot)
Definition Mem2Reg.cpp:209
static bool isBlockingDrive(Operation *op)
Check whether an operation is a llhd.drive with an epsilon delay.
Definition Mem2Reg.cpp:54
static bool isEpsilonDelay(Value value)
Check whether a value is defined by llhd.constant_time <0ns, 0d, 1e>.
Definition Mem2Reg.cpp:35
PointerIntPair< Value, 1 > DefSlot
The slot a reaching definition specifies a value for, alongside a bit indicating whether the definiti...
Definition Mem2Reg.cpp:204
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
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:651
static DriveCondition getEmptyKey()
Definition Mem2Reg.cpp:182
static DriveCondition getTombstoneKey()
Definition Mem2Reg.cpp:185
static bool isEqual(DriveCondition lhs, DriveCondition rhs)
Definition Mem2Reg.cpp:192
static unsigned getHashValue(DriveCondition d)
Definition Mem2Reg.cpp:188