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