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