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