11#include "mlir/Analysis/Liveness.h"
12#include "mlir/Dialect/Arith/IR/Arith.h"
13#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
14#include "mlir/Dialect/SCF/IR/SCF.h"
15#include "mlir/IR/Matchers.h"
16#include "llvm/Support/Debug.h"
18#define DEBUG_TYPE "llhd-hoist-signals"
22#define GEN_PASS_DEF_HOISTSIGNALSPASS
23#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
30using llvm::PointerUnion;
31using llvm::SmallSetVector;
40 ProbeHoister(Region ®ion) : region(region) {}
43 void findValuesLiveAcrossWait(Liveness &liveness);
51 DenseSet<Value> liveAcrossWait;
54 DenseMap<Value, ProbeOp> hoistedProbes;
58void ProbeHoister::hoist() {
59 Liveness liveness(region.getParentOp());
60 findValuesLiveAcrossWait(liveness);
75void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
78 SmallVector<Value> worklist;
79 for (
auto &block : region)
80 if (isa<WaitOp>(block.getTerminator()))
81 for (auto value : liveness.getLiveOut(&block))
82 if (value.getParentRegion() == ®ion)
83 if (liveAcrossWait.insert(value).second)
84 worklist.push_back(value);
90 while (!worklist.empty()) {
91 auto value = worklist.pop_back_val();
92 if (
auto *defOp = value.getDefiningOp()) {
93 for (
auto operand : defOp->getOperands())
94 if (operand.getParentRegion() == ®ion)
95 if (liveAcrossWait.insert(operand).second)
96 worklist.push_back(operand);
98 auto blockArg = cast<BlockArgument>(value);
99 for (
auto &use : blockArg.getOwner()->getUses()) {
100 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
103 auto operand = branch.getSuccessorOperands(
104 use.getOperandNumber())[blockArg.getArgNumber()];
105 if (operand.getParentRegion() == ®ion)
106 if (liveAcrossWait.insert(operand).second)
107 worklist.push_back(operand);
112 LLVM_DEBUG(llvm::dbgs() <<
"Found " << liveAcrossWait.size()
113 <<
" values live across wait\n");
121void ProbeHoister::hoistProbes() {
122 auto findExistingProbe = [&](Value signal) {
123 for (
auto *user : signal.getUsers())
124 if (auto probeOp = dyn_cast<ProbeOp>(user))
125 if (probeOp->getParentRegion()->isProperAncestor(®ion))
130 for (
auto &block : region) {
133 if (!llvm::all_of(block.getPredecessors(), [](
auto *predecessor) {
134 return isa<WaitOp>(predecessor->getTerminator());
138 for (
auto &op :
llvm::make_early_inc_range(block)) {
139 auto probeOp = dyn_cast<ProbeOp>(op);
145 if (isMemoryEffectFree(&op))
152 if (liveAcrossWait.contains(probeOp)) {
153 LLVM_DEBUG(llvm::dbgs()
154 <<
"- Skipping (live across wait) " << probeOp <<
"\n");
160 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(®ion)) {
161 LLVM_DEBUG(llvm::dbgs()
162 <<
"- Skipping (local signal) " << probeOp <<
"\n");
168 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
170 LLVM_DEBUG(llvm::dbgs() <<
"- Replacing " << probeOp <<
"\n");
171 probeOp.replaceAllUsesWith(hoistedOp.getResult());
174 LLVM_DEBUG(llvm::dbgs() <<
"- Hoisting " << probeOp <<
"\n");
175 if (
auto existingOp = findExistingProbe(probeOp.getSignal())) {
176 probeOp.replaceAllUsesWith(existingOp.getResult());
178 hoistedOp = existingOp;
180 if (
auto *defOp = probeOp.getSignal().getDefiningOp())
181 probeOp->moveAfter(defOp);
183 probeOp->moveBefore(region.getParentOp());
200 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
204 DriveValue() :
data(Value{}) {}
207 static DriveValue dontCare(Type type) {
return DriveValue(type); }
210 DriveValue(IntegerAttr attr) :
data(attr) {}
213 DriveValue(TimeAttr attr) :
data(attr) {}
217 DriveValue(Value value) :
data(value) {
219 if (
auto *defOp = value.getDefiningOp())
220 if (m_Constant(&attr).match(defOp))
221 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
222 [&](
auto attr) {
data = attr; });
225 bool operator==(
const DriveValue &other)
const {
return data == other.data; }
226 bool operator!=(
const DriveValue &other)
const {
return data != other.data; }
228 bool isDontCare()
const {
return isa<Type>(data); }
229 explicit operator bool()
const {
return bool(data); }
231 Type getType()
const {
232 return TypeSwitch<Data, Type>(data)
233 .Case<Value, IntegerAttr, TimeAttr>([](
auto x) {
return x.getType(); })
234 .Case<Type>([](
auto type) {
return type; });
238 explicit DriveValue(Data data) :
data(
data) {}
242struct DriveOperands {
253 SmallPtrSet<Operation *, 2> ops;
258 DriveOperands uniform;
263 const DriveValue &dv) {
267 TypeSwitch<DriveValue::Data>(dv.data)
268 .Case<Value, IntegerAttr, TimeAttr>([&](
auto x) { os << x; })
269 .Case<Type>([&](
auto) { os <<
"dont-care"; });
280 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
283 void findHoistableSlots();
284 void collectDriveSets();
285 void finalizeDriveSets();
294 SmallSetVector<Value, 8> slots;
295 SmallVector<Operation *> suspendOps;
300void DriveHoister::hoist() {
301 findHoistableSlots();
310void DriveHoister::findHoistableSlots() {
311 SmallPtrSet<Value, 8> seenSlots;
313 auto slot = op.getSignal();
314 if (!seenSlots.insert(slot).second)
319 if (!slot.getDefiningOp<llhd::SignalOp>())
321 if (!slot.getParentRegion()->isProperAncestor(&
processOp.getBody()))
325 if (!llvm::all_of(slot.getUsers(), [&](
auto *user) {
327 if (!processOp.getBody().isAncestor(user->getParentRegion()))
329 return isa<ProbeOp, DriveOp>(user);
335 auto sigType = cast<RefType>(slot.getType()).getNestedType();
336 if (!isa<TimeType>(sigType) && !isa<FloatType>(sigType) &&
337 hw::getBitWidth(sigType) < 0)
342 LLVM_DEBUG(llvm::dbgs() <<
"Found " << slots.size()
343 <<
" slots for drive hoisting\n");
352void DriveHoister::collectDriveSets() {
353 SmallPtrSet<Value, 8> unhoistableSlots;
356 auto trueAttr = BoolAttr::get(
processOp.getContext(),
true);
357 for (
auto &block :
processOp.getBody()) {
359 auto *terminator = block.getTerminator();
360 if (!isa<WaitOp, HaltOp>(terminator))
362 suspendOps.push_back(terminator);
364 bool beyondSideEffect =
false;
367 for (
auto &op :
llvm::make_early_inc_range(
368 llvm::reverse(block.without_terminator()))) {
369 auto driveOp = dyn_cast<DriveOp>(op);
375 if (!isMemoryEffectFree(&op))
376 beyondSideEffect =
true;
381 if (!slots.contains(driveOp.getSignal()) ||
382 unhoistableSlots.contains(driveOp.getSignal())) {
383 LLVM_DEBUG(llvm::dbgs()
384 <<
"- Skipping (slot unhoistable): " << driveOp <<
"\n");
390 if (beyondSideEffect) {
391 LLVM_DEBUG(llvm::dbgs()
392 <<
"- Aborting slot (drive across side-effect): " << driveOp
394 unhoistableSlots.insert(driveOp.getSignal());
399 auto &laterDrive = laterDrives[driveOp.getSignal()];
403 if (laterDrive.getTime() == driveOp.getTime() &&
404 laterDrive.getEnable() == driveOp.getEnable()) {
405 LLVM_DEBUG(llvm::dbgs()
406 <<
"- Skipping (driven later): " << driveOp <<
"\n");
413 LLVM_DEBUG(llvm::dbgs()
414 <<
"- Aborting slot (multiple drives): " << driveOp <<
"\n");
415 unhoistableSlots.insert(driveOp.getSignal());
418 laterDrive = driveOp;
421 auto operands = DriveOperands{
424 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
425 : DriveValue(trueAttr),
427 auto &driveSet = driveSets[driveOp.getSignal()];
428 driveSet.ops.insert(driveOp);
429 driveSet.operands.insert({terminator, operands});
434 slots.remove_if([&](
auto slot) {
435 if (unhoistableSlots.contains(slot)) {
436 driveSets.erase(slot);
447void DriveHoister::finalizeDriveSets() {
448 auto falseAttr = BoolAttr::get(
processOp.getContext(),
false);
449 for (
auto &[slot, driveSet] : driveSets) {
453 for (
auto *suspendOp : suspendOps) {
454 auto operands = DriveOperands{
455 DriveValue::dontCare(cast<RefType>(slot.getType()).getNestedType()),
456 DriveValue::dontCare(TimeType::get(
processOp.getContext())),
457 DriveValue(falseAttr),
459 driveSet.operands.insert({suspendOp, operands});
465 auto unify = [](DriveValue &accumulator, DriveValue other) {
466 if (other.isDontCare())
468 if (accumulator == other)
470 accumulator = accumulator.isDontCare() ? other : DriveValue();
472 assert(!driveSet.operands.empty());
473 driveSet.uniform = driveSet.operands.begin()->second;
474 for (
auto [terminator, otherOperands] : driveSet.operands) {
475 unify(driveSet.uniform.value, otherOperands.value);
476 unify(driveSet.uniform.delay, otherOperands.delay);
477 unify(driveSet.uniform.enable, otherOperands.enable);
484 auto clearIfNonConst = [&](DriveValue &driveValue) {
485 if (isa_and_nonnull<Value>(driveValue.data))
486 driveValue = DriveValue();
488 clearIfNonConst(driveSet.uniform.value);
489 clearIfNonConst(driveSet.uniform.delay);
490 clearIfNonConst(driveSet.uniform.enable);
494 for (
auto slot : slots) {
495 const auto &driveSet = driveSets[slot];
496 llvm::dbgs() <<
"Drives to " << slot <<
"\n";
497 if (driveSet.uniform.value)
498 llvm::dbgs() <<
"- Uniform value: " << driveSet.uniform.value <<
"\n";
499 if (driveSet.uniform.delay)
500 llvm::dbgs() <<
"- Uniform delay: " << driveSet.uniform.delay <<
"\n";
501 if (driveSet.uniform.enable)
502 llvm::dbgs() <<
"- Uniform enable: " << driveSet.uniform.enable <<
"\n";
503 for (
auto *suspendOp : suspendOps) {
504 auto operands = driveSet.operands.lookup(suspendOp);
505 llvm::dbgs() <<
"- At " << *suspendOp <<
"\n";
506 if (!driveSet.uniform.value)
507 llvm::dbgs() <<
" - Value: " << operands.value <<
"\n";
508 if (!driveSet.uniform.delay)
509 llvm::dbgs() <<
" - Delay: " << operands.delay <<
"\n";
510 if (!driveSet.uniform.enable)
511 llvm::dbgs() <<
" - Enable: " << operands.enable <<
"\n";
521void DriveHoister::hoistDrives() {
522 if (driveSets.empty())
527 slots.remove_if([&](
auto slot) {
return !driveSets.count(slot); });
531 LLVM_DEBUG(llvm::dbgs() <<
"Hoisting drives of " << driveSets.size()
538 auto materialize = [&](DriveValue driveValue) -> Value {
540 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
541 .Case<Value>([](
auto value) {
return value; })
542 .Case<IntegerAttr>([&](
auto attr) {
543 auto &slot = materializedConstants[attr];
548 .Case<TimeAttr>([&](
auto attr) {
549 auto &slot = materializedConstants[attr];
551 slot = ConstantTimeOp::create(builder,
processOp.getLoc(), attr);
554 .Case<Type>([&](
auto type) -> Value {
556 if (isa<TimeType>(type)) {
557 auto attr = TimeAttr::get(builder.getContext(), 0,
"ns", 0, 0);
558 auto &slot = materializedConstants[attr];
560 slot = ConstantTimeOp::create(builder,
processOp.getLoc(), attr);
563 if (
auto floatTy = dyn_cast<FloatType>(type)) {
564 auto attr = FloatAttr::get(floatTy, 0.0);
565 auto &slot = materializedConstants[attr];
568 arith::ConstantOp::create(builder,
processOp.getLoc(), attr);
571 auto numBits = hw::getBitWidth(type);
574 builder,
processOp.getLoc(), builder.getIntegerType(numBits), 0);
575 if (value.getType() != type)
584 for (
auto *suspendOp : suspendOps) {
585 LLVM_DEBUG(llvm::dbgs()
586 <<
"- Adding yield operands to " << *suspendOp <<
"\n");
587 MutableOperandRange yieldOperands =
588 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
589 .Case<WaitOp, HaltOp>(
590 [](
auto op) {
return op.getYieldOperandsMutable(); });
592 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
594 yieldOperands.append(materialize(nonUniform));
597 for (
auto slot : slots) {
598 auto &driveSet = driveSets[slot];
599 auto operands = driveSet.operands.lookup(suspendOp);
600 addYieldOperand(driveSet.uniform.value, operands.value);
601 addYieldOperand(driveSet.uniform.delay, operands.delay);
602 addYieldOperand(driveSet.uniform.enable, operands.enable);
607 SmallVector<Type> resultTypes(
processOp->getResultTypes());
608 auto oldNumResults = resultTypes.size();
609 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
611 resultTypes.push_back(nonUniform.getType());
613 for (
auto slot : slots) {
614 auto &driveSet = driveSets[slot];
615 auto operands = driveSet.operands.begin()->second;
616 addResultType(driveSet.uniform.value, operands.value);
617 addResultType(driveSet.uniform.delay, operands.delay);
618 addResultType(driveSet.uniform.enable, operands.enable);
621 ProcessOp::create(builder,
processOp.getLoc(), resultTypes,
623 newProcessOp.getBody().takeBody(
processOp.getBody());
625 newProcessOp->getResults().slice(0, oldNumResults));
632 builder.setInsertionPointAfter(
processOp);
633 auto newResultIdx = oldNumResults;
635 auto useResultValue = [&](DriveValue uniform) {
637 return processOp.getResult(newResultIdx++);
638 return materialize(uniform);
641 auto removeIfUnused = [](Value value) {
643 if (
auto *defOp = value.getDefiningOp())
644 if (defOp && isOpTriviallyDead(defOp))
648 auto trueAttr = builder.getBoolAttr(
true);
649 for (
auto slot : slots) {
650 auto &driveSet = driveSets[slot];
653 auto value = useResultValue(driveSet.uniform.value);
654 auto delay = useResultValue(driveSet.uniform.delay);
655 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
656 ? useResultValue(driveSet.uniform.enable)
658 [[maybe_unused]]
auto newDrive =
659 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
660 LLVM_DEBUG(llvm::dbgs() <<
"- Add " << newDrive <<
"\n");
663 for (
auto *oldOp : driveSet.ops) {
664 auto oldDrive = cast<DriveOp>(oldOp);
665 LLVM_DEBUG(llvm::dbgs() <<
"- Remove " << oldDrive <<
"\n");
666 auto delay = oldDrive.getTime();
667 auto enable = oldDrive.getEnable();
669 removeIfUnused(delay);
670 removeIfUnused(enable);
672 driveSet.ops.clear();
682struct HoistSignalsPass
683 :
public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
684 void runOnOperation()
override;
688void HoistSignalsPass::runOnOperation() {
689 SmallVector<Region *> regions;
690 getOperation()->walk([&](Operation *op) {
691 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
692 for (
auto ®ion : op->getRegions())
694 regions.push_back(®ion);
696 for (
auto *region : regions) {
697 ProbeHoister(*region).hoist();
698 if (
auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
assert(baseType &&"element must be base type")
static LogicalResult unify(Term *lhs, Term *rhs)
static LogicalResult processOp(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, const ModuleUpdateTable &updateTable, FInstanceLike op)
static InstancePath empty
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)