12#include "mlir/Analysis/Liveness.h"
13#include "mlir/Dialect/Arith/IR/Arith.h"
14#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
15#include "mlir/Dialect/SCF/IR/SCF.h"
16#include "mlir/IR/Matchers.h"
17#include "llvm/Support/Debug.h"
19#define DEBUG_TYPE "llhd-hoist-signals"
23#define GEN_PASS_DEF_HOISTSIGNALSPASS
24#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
31using llvm::PointerUnion;
41 ProbeHoister(Region ®ion) : region(region) {}
44 void findValuesLiveAcrossWait(Liveness &liveness);
52 DenseSet<Value> liveAcrossWait;
55 DenseMap<Value, ProbeOp> hoistedProbes;
59void ProbeHoister::hoist() {
60 Liveness liveness(region.getParentOp());
61 findValuesLiveAcrossWait(liveness);
76void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
79 SmallVector<Value> worklist;
80 for (
auto &block : region)
81 if (isa<WaitOp>(block.getTerminator()))
82 for (
auto value : liveness.getLiveOut(&block))
83 if (value.getParentRegion() == ®ion)
84 if (liveAcrossWait.insert(value).second)
85 worklist.push_back(value);
91 while (!worklist.empty()) {
92 auto value = worklist.pop_back_val();
93 if (
auto *defOp = value.getDefiningOp()) {
94 for (
auto operand : defOp->getOperands())
95 if (operand.getParentRegion() == ®ion)
96 if (liveAcrossWait.insert(operand).second)
97 worklist.push_back(operand);
99 auto blockArg = cast<BlockArgument>(value);
100 for (
auto &use : blockArg.getOwner()->getUses()) {
101 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
104 auto operand = branch.getSuccessorOperands(
105 use.getOperandNumber())[blockArg.getArgNumber()];
106 if (operand.getParentRegion() == ®ion)
107 if (liveAcrossWait.insert(operand).second)
108 worklist.push_back(operand);
113 LLVM_DEBUG(llvm::dbgs() <<
"Found " << liveAcrossWait.size()
114 <<
" values live across wait\n");
122void ProbeHoister::hoistProbes() {
123 auto findExistingProbe = [&](Value signal) {
124 for (
auto *user : signal.getUsers())
125 if (
auto probeOp = dyn_cast<ProbeOp>(user))
126 if (probeOp->getParentRegion()->isProperAncestor(®ion))
131 for (
auto &block : region) {
134 if (!llvm::all_of(block.getPredecessors(), [](
auto *predecessor) {
135 return isa<WaitOp>(predecessor->getTerminator());
139 for (
auto &op : llvm::make_early_inc_range(block)) {
140 auto probeOp = dyn_cast<ProbeOp>(op);
146 if (isMemoryEffectFree(&op))
153 if (liveAcrossWait.contains(probeOp)) {
154 LLVM_DEBUG(llvm::dbgs()
155 <<
"- Skipping (live across wait) " << probeOp <<
"\n");
161 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(®ion)) {
162 LLVM_DEBUG(llvm::dbgs()
163 <<
"- Skipping (local signal) " << probeOp <<
"\n");
169 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
171 LLVM_DEBUG(llvm::dbgs() <<
"- Replacing " << probeOp <<
"\n");
172 probeOp.replaceAllUsesWith(hoistedOp.getResult());
175 LLVM_DEBUG(llvm::dbgs() <<
"- Hoisting " << probeOp <<
"\n");
176 if (
auto existingOp = findExistingProbe(probeOp.getSignal())) {
177 probeOp.replaceAllUsesWith(existingOp.getResult());
179 hoistedOp = existingOp;
181 if (
auto *defOp = probeOp.getSignal().getDefiningOp())
182 probeOp->moveAfter(defOp);
184 probeOp->moveBefore(region.getParentOp());
201 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
205 DriveValue() :
data(Value{}) {}
208 static DriveValue dontCare(Type type) {
return DriveValue(type); }
211 DriveValue(IntegerAttr attr) :
data(attr) {}
214 DriveValue(TimeAttr attr) :
data(attr) {}
218 DriveValue(Value value) :
data(value) {
220 if (
auto *defOp = value.getDefiningOp())
221 if (m_Constant(&attr).match(defOp))
222 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
223 [&](
auto attr) {
data = attr; });
226 bool operator==(
const DriveValue &other)
const {
return data == other.data; }
227 bool operator!=(
const DriveValue &other)
const {
return data != other.data; }
229 bool isDontCare()
const {
return isa<Type>(data); }
230 explicit operator bool()
const {
return bool(data); }
232 Type getType()
const {
233 return TypeSwitch<Data, Type>(data)
234 .Case<Value, IntegerAttr, TimeAttr>([](
auto x) {
return x.getType(); })
235 .Case<Type>([](
auto type) {
return type; });
239 explicit DriveValue(Data data) :
data(
data) {}
244struct DriveOperands {
256 bool isAssign =
false;
258 SmallPtrSet<Operation *, 2> ops;
260 SmallDenseMap<Operation *, DriveOperands, 2> operands;
263 DriveOperands uniform;
268 const DriveValue &dv) {
272 TypeSwitch<DriveValue::Data>(dv.data)
273 .Case<Value, IntegerAttr, TimeAttr>([&](
auto x) { os << x; })
274 .Case<Type>([&](
auto) { os <<
"dont-care"; });
285 using RangedValue = std::pair<Value, std::optional<ProjectionRange>>;
287 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
290 void findHoistableSlots();
291 void collectDriveSets();
292 void finalizeDriveSets();
301 SmallSetVector<Value, 8> slots;
302 SmallVector<Operation *> suspendOps;
303 SmallDenseMap<Value, DriveSet> driveSets;
307void DriveHoister::hoist() {
308 findHoistableSlots();
317void DriveHoister::findHoistableSlots() {
318 SmallPtrSet<Value, 8> seenSlots;
319 SmallDenseMap<Operation *, SmallVector<RangedValue>, 8> slotsByRoot;
321 processOp.walk([&](DriveLikeOp op) {
322 auto slot = op.getDriven();
323 if (!seenSlots.insert(slot).second)
331 if (!rootOp->getParentRegion()->isProperAncestor(&processOp.getBody()))
336 auto &rootSlots = slotsByRoot[rootOp];
337 bool overlaps =
false;
338 for (
auto &[otherSlot, otherRange] : rootSlots) {
340 slots.remove(otherSlot);
344 rootSlots.push_back({slot, range});
349 if (!slot.getParentRegion()->isProperAncestor(&processOp.getBody()))
354 SmallVector<Value> worklist{rootOp->getResult(0)};
355 SmallPtrSet<Value, 8> seenProjections;
356 while (!worklist.empty()) {
357 for (
auto *user : worklist.pop_back_val().getUsers()) {
359 if (seenProjections.insert(user->getResult(0)).second)
360 worklist.push_back(user->getResult(0));
364 if (!processOp.getBody().isAncestor(user->getParentRegion()))
367 if (!isa<ProbeOp, DriveLikeOp>(user))
374 auto sigType = cast<RefType>(slot.getType()).getNestedType();
375 if (!isa<TimeType>(sigType) && !isa<FloatType>(sigType) &&
376 hw::getBitWidth(sigType) < 0)
381 LLVM_DEBUG(llvm::dbgs() <<
"Found " << slots.size()
382 <<
" slots for drive hoisting\n");
391void DriveHoister::collectDriveSets() {
392 SmallPtrSet<Value, 8> unhoistableSlots;
393 SmallDenseMap<Value, DriveLikeOp, 8> laterDrives;
395 auto trueAttr = BoolAttr::get(processOp.getContext(),
true);
396 for (
auto &block : processOp.getBody()) {
398 auto *terminator = block.getTerminator();
399 if (!isa<WaitOp, HaltOp>(terminator))
401 suspendOps.push_back(terminator);
403 bool beyondSideEffect =
false;
406 for (
auto &op : llvm::make_early_inc_range(
407 llvm::reverse(block.without_terminator()))) {
408 auto driveOp = dyn_cast<DriveLikeOp>(op);
414 if (!isMemoryEffectFree(&op))
415 beyondSideEffect =
true;
419 bool isAssign = isa<AssignOp>(op);
422 auto slot = driveOp.getDriven();
423 if (!slots.contains(slot) || unhoistableSlots.contains(slot)) {
424 LLVM_DEBUG(llvm::dbgs()
425 <<
"- Skipping (slot unhoistable): " << driveOp <<
"\n");
431 if (beyondSideEffect) {
432 LLVM_DEBUG(llvm::dbgs()
433 <<
"- Aborting slot (drive across side-effect): " << driveOp
435 unhoistableSlots.insert(slot);
440 auto &laterDrive = laterDrives[slot];
444 if (laterDrive.getTime() == driveOp.getTime() &&
445 laterDrive.getEnable() == driveOp.getEnable()) {
446 LLVM_DEBUG(llvm::dbgs()
447 <<
"- Skipping (driven later): " << driveOp <<
"\n");
454 LLVM_DEBUG(llvm::dbgs()
455 <<
"- Aborting slot (multiple drives): " << driveOp <<
"\n");
456 unhoistableSlots.insert(slot);
459 laterDrive = driveOp;
463 auto &driveSet = driveSets[slot];
464 if (!driveSet.ops.empty() && driveSet.isAssign != isAssign) {
465 LLVM_DEBUG(llvm::dbgs()
466 <<
"- Aborting slot (mixed drives and assigns): " << driveOp
468 unhoistableSlots.insert(slot);
471 driveSet.isAssign = isAssign;
474 auto operands = DriveOperands{
476 isAssign ? DriveValue::dontCare(TimeType::get(processOp.getContext()))
477 : DriveValue(driveOp.getTime()),
478 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
479 : DriveValue(trueAttr),
481 driveSet.ops.insert(&op);
482 driveSet.operands.insert({terminator, operands});
487 slots.remove_if([&](
auto slot) {
488 if (unhoistableSlots.contains(slot)) {
489 driveSets.erase(slot);
500void DriveHoister::finalizeDriveSets() {
501 auto falseAttr = BoolAttr::get(processOp.getContext(),
false);
502 for (
auto &[slot, driveSet] : driveSets) {
506 for (
auto *suspendOp : suspendOps) {
507 auto operands = DriveOperands{
508 DriveValue::dontCare(cast<RefType>(slot.getType()).getNestedType()),
509 DriveValue::dontCare(TimeType::get(processOp.getContext())),
510 DriveValue(falseAttr),
512 driveSet.operands.insert({suspendOp, operands});
518 auto unify = [](DriveValue &accumulator, DriveValue other) {
519 if (other.isDontCare())
521 if (accumulator == other)
523 accumulator = accumulator.isDontCare() ? other : DriveValue();
525 assert(!driveSet.operands.empty());
526 driveSet.uniform = driveSet.operands.begin()->second;
527 for (
auto [terminator, otherOperands] : driveSet.operands) {
528 unify(driveSet.uniform.value, otherOperands.value);
529 unify(driveSet.uniform.delay, otherOperands.delay);
530 unify(driveSet.uniform.enable, otherOperands.enable);
537 auto clearIfNonConst = [&](DriveValue &driveValue) {
538 if (isa_and_nonnull<Value>(driveValue.data))
539 driveValue = DriveValue();
541 clearIfNonConst(driveSet.uniform.value);
542 clearIfNonConst(driveSet.uniform.delay);
543 clearIfNonConst(driveSet.uniform.enable);
547 for (
auto slot : slots) {
548 const auto &driveSet = driveSets[slot];
549 llvm::dbgs() <<
"Drives to " << slot <<
"\n";
550 if (driveSet.uniform.value)
551 llvm::dbgs() <<
"- Uniform value: " << driveSet.uniform.value <<
"\n";
552 if (driveSet.uniform.delay)
553 llvm::dbgs() <<
"- Uniform delay: " << driveSet.uniform.delay <<
"\n";
554 if (driveSet.uniform.enable)
555 llvm::dbgs() <<
"- Uniform enable: " << driveSet.uniform.enable <<
"\n";
556 for (
auto *suspendOp : suspendOps) {
557 auto operands = driveSet.operands.lookup(suspendOp);
558 llvm::dbgs() <<
"- At " << *suspendOp <<
"\n";
559 if (!driveSet.uniform.value)
560 llvm::dbgs() <<
" - Value: " << operands.value <<
"\n";
561 if (!driveSet.uniform.delay)
562 llvm::dbgs() <<
" - Delay: " << operands.delay <<
"\n";
563 if (!driveSet.uniform.enable)
564 llvm::dbgs() <<
" - Enable: " << operands.enable <<
"\n";
574void DriveHoister::hoistDrives() {
575 if (driveSets.empty())
580 slots.remove_if([&](
auto slot) {
return !driveSets.count(slot); });
584 LLVM_DEBUG(llvm::dbgs() <<
"Hoisting drives of " << driveSets.size()
588 OpBuilder builder(processOp);
589 SmallDenseMap<Attribute, Value> materializedConstants;
591 auto materialize = [&](DriveValue driveValue) -> Value {
592 OpBuilder builder(processOp);
593 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
594 .Case<Value>([](
auto value) {
return value; })
595 .Case<IntegerAttr>([&](
auto attr) {
596 auto &slot = materializedConstants[attr];
601 .Case<TimeAttr>([&](
auto attr) {
602 auto &slot = materializedConstants[attr];
604 slot = ConstantTimeOp::create(builder, processOp.getLoc(), attr);
607 .Case<Type>([&](
auto type) -> Value {
609 if (isa<TimeType>(type)) {
610 auto attr = TimeAttr::get(builder.getContext(), 0,
"ns", 0);
611 auto &slot = materializedConstants[attr];
613 slot = ConstantTimeOp::create(builder, processOp.getLoc(), attr);
616 if (
auto floatTy = dyn_cast<FloatType>(type)) {
617 auto attr = FloatAttr::get(floatTy, 0.0);
618 auto &slot = materializedConstants[attr];
621 arith::ConstantOp::create(builder, processOp.getLoc(), attr);
624 auto numBits = hw::getBitWidth(type);
627 builder, processOp.getLoc(), builder.getIntegerType(numBits), 0);
628 if (value.getType() != type)
637 for (
auto *suspendOp : suspendOps) {
638 LLVM_DEBUG(llvm::dbgs()
639 <<
"- Adding yield operands to " << *suspendOp <<
"\n");
640 MutableOperandRange yieldOperands =
641 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
642 .Case<WaitOp, HaltOp>(
643 [](
auto op) {
return op.getYieldOperandsMutable(); });
645 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
647 yieldOperands.append(materialize(nonUniform));
650 for (
auto slot : slots) {
651 auto &driveSet = driveSets[slot];
652 auto operands = driveSet.operands.lookup(suspendOp);
653 addYieldOperand(driveSet.uniform.value, operands.value);
654 if (!driveSet.isAssign)
655 addYieldOperand(driveSet.uniform.delay, operands.delay);
656 addYieldOperand(driveSet.uniform.enable, operands.enable);
661 SmallVector<Type> resultTypes(processOp->getResultTypes());
662 auto oldNumResults = resultTypes.size();
663 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
665 resultTypes.push_back(nonUniform.getType());
667 for (
auto slot : slots) {
668 auto &driveSet = driveSets[slot];
669 auto operands = driveSet.operands.begin()->second;
670 addResultType(driveSet.uniform.value, operands.value);
671 if (!driveSet.isAssign)
672 addResultType(driveSet.uniform.delay, operands.delay);
673 addResultType(driveSet.uniform.enable, operands.enable);
676 ProcessOp::create(builder, processOp.getLoc(), resultTypes,
677 processOp->getOperands(), processOp->getAttrs());
678 newProcessOp.getBody().takeBody(processOp.getBody());
679 processOp.replaceAllUsesWith(
680 newProcessOp->getResults().slice(0, oldNumResults));
682 processOp = newProcessOp;
687 builder.setInsertionPointAfter(processOp);
688 auto newResultIdx = oldNumResults;
690 auto useResultValue = [&](DriveValue uniform) {
692 return processOp.getResult(newResultIdx++);
693 return materialize(uniform);
696 auto removeIfUnused = [](Value value) {
698 if (
auto *defOp = value.getDefiningOp())
699 if (defOp && isOpTriviallyDead(defOp))
703 auto trueAttr = builder.getBoolAttr(
true);
704 for (
auto slot : slots) {
705 auto &driveSet = driveSets[slot];
708 auto value = useResultValue(driveSet.uniform.value);
710 driveSet.isAssign ? Value{} : useResultValue(driveSet.uniform.delay);
711 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
712 ? useResultValue(driveSet.uniform.enable)
714 [[maybe_unused]] Operation *newDrive;
715 if (driveSet.isAssign)
716 newDrive = AssignOp::create(builder, slot.getLoc(), slot, value, enable);
719 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
720 LLVM_DEBUG(llvm::dbgs() <<
"- Add " << *newDrive <<
"\n");
723 for (
auto *oldOp : driveSet.ops) {
724 auto oldDrive = cast<DriveLikeOp>(oldOp);
725 LLVM_DEBUG(llvm::dbgs() <<
"- Remove " << oldDrive <<
"\n");
726 auto delay = oldDrive.getTime();
727 auto enable = oldDrive.getEnable();
729 removeIfUnused(delay);
730 removeIfUnused(enable);
732 driveSet.ops.clear();
734 assert(newResultIdx == processOp->getNumResults());
742struct HoistSignalsPass
743 :
public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
744 void runOnOperation()
override;
748void HoistSignalsPass::runOnOperation() {
749 SmallVector<Region *> regions;
750 getOperation()->walk([&](Operation *op) {
751 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
752 for (
auto ®ion : op->getRegions())
754 regions.push_back(®ion);
756 for (
auto *region : regions) {
757 ProbeHoister(*region).hoist();
758 if (
auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
759 DriveHoister(processOp).hoist();
assert(baseType &&"element must be base type")
if(parseOptionalKeywordOrOptionalString(parser, result.rawName, found)) return failure()
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
std::pair< Operation *, std::optional< ProjectionRange > > getRootSignalAndRange(Value sig)
Given a signal or a projection, find the root signal declaration by traversing the projection chain.
bool isProjection(const Operation *op)
Check if an op is one of the supported projection types.
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)