11#include "mlir/Analysis/Liveness.h"
12#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
13#include "mlir/Dialect/SCF/IR/SCF.h"
14#include "mlir/IR/Matchers.h"
15#include "llvm/Support/Debug.h"
17#define DEBUG_TYPE "llhd-hoist-signals"
21#define GEN_PASS_DEF_HOISTSIGNALSPASS
22#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
29using llvm::PointerUnion;
30using llvm::SmallSetVector;
39 ProbeHoister(Region ®ion) : region(region) {}
42 void findValuesLiveAcrossWait(Liveness &liveness);
50 DenseSet<Value> liveAcrossWait;
53 DenseMap<Value, ProbeOp> hoistedProbes;
57void ProbeHoister::hoist() {
58 Liveness liveness(region.getParentOp());
59 findValuesLiveAcrossWait(liveness);
74void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
77 SmallVector<Value> worklist;
78 for (
auto &block : region)
79 if (isa<WaitOp>(block.getTerminator()))
80 for (auto value : liveness.getLiveOut(&block))
81 if (value.getParentRegion() == ®ion)
82 if (liveAcrossWait.insert(value).second)
83 worklist.push_back(value);
89 while (!worklist.empty()) {
90 auto value = worklist.pop_back_val();
91 if (
auto *defOp = value.getDefiningOp()) {
92 for (
auto operand : defOp->getOperands())
93 if (operand.getParentRegion() == ®ion)
94 if (liveAcrossWait.insert(operand).second)
95 worklist.push_back(operand);
97 auto blockArg = cast<BlockArgument>(value);
98 for (
auto &use : blockArg.getOwner()->getUses()) {
99 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
102 auto operand = branch.getSuccessorOperands(
103 use.getOperandNumber())[blockArg.getArgNumber()];
104 if (operand.getParentRegion() == ®ion)
105 if (liveAcrossWait.insert(operand).second)
106 worklist.push_back(operand);
111 LLVM_DEBUG(llvm::dbgs() <<
"Found " << liveAcrossWait.size()
112 <<
" values live across wait\n");
120void ProbeHoister::hoistProbes() {
121 auto findExistingProbe = [&](Value signal) {
122 for (
auto *user : signal.getUsers())
123 if (auto probeOp = dyn_cast<ProbeOp>(user))
124 if (probeOp->getParentRegion()->isProperAncestor(®ion))
129 for (
auto &block : region) {
132 if (!llvm::all_of(block.getPredecessors(), [](
auto *predecessor) {
133 return isa<WaitOp>(predecessor->getTerminator());
137 for (
auto &op :
llvm::make_early_inc_range(block)) {
138 auto probeOp = dyn_cast<ProbeOp>(op);
144 if (isMemoryEffectFree(&op))
151 if (liveAcrossWait.contains(probeOp)) {
152 LLVM_DEBUG(llvm::dbgs()
153 <<
"- Skipping (live across wait) " << probeOp <<
"\n");
159 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(®ion)) {
160 LLVM_DEBUG(llvm::dbgs()
161 <<
"- Skipping (local signal) " << probeOp <<
"\n");
167 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
169 LLVM_DEBUG(llvm::dbgs() <<
"- Replacing " << probeOp <<
"\n");
170 probeOp.replaceAllUsesWith(hoistedOp.getResult());
173 LLVM_DEBUG(llvm::dbgs() <<
"- Hoisting " << probeOp <<
"\n");
174 if (
auto existingOp = findExistingProbe(probeOp.getSignal())) {
175 probeOp.replaceAllUsesWith(existingOp.getResult());
177 hoistedOp = existingOp;
179 if (
auto *defOp = probeOp.getSignal().getDefiningOp())
180 probeOp->moveAfter(defOp);
182 probeOp->moveBefore(region.getParentOp());
199 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
203 DriveValue() :
data(Value{}) {}
206 static DriveValue dontCare(Type type) {
return DriveValue(type); }
209 DriveValue(IntegerAttr attr) :
data(attr) {}
212 DriveValue(TimeAttr attr) :
data(attr) {}
216 DriveValue(Value value) :
data(value) {
218 if (
auto *defOp = value.getDefiningOp())
219 if (m_Constant(&attr).match(defOp))
220 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
221 [&](
auto attr) {
data = attr; });
224 bool operator==(
const DriveValue &other)
const {
return data == other.data; }
225 bool operator!=(
const DriveValue &other)
const {
return data != other.data; }
227 bool isDontCare()
const {
return isa<Type>(data); }
228 explicit operator bool()
const {
return bool(data); }
230 Type getType()
const {
231 return TypeSwitch<Data, Type>(data)
232 .Case<Value, IntegerAttr, TimeAttr>([](
auto x) {
return x.getType(); })
233 .Case<Type>([](
auto type) {
return type; });
237 explicit DriveValue(Data data) :
data(
data) {}
241struct DriveOperands {
252 SmallPtrSet<Operation *, 2> ops;
257 DriveOperands uniform;
262 const DriveValue &dv) {
266 TypeSwitch<DriveValue::Data>(dv.data)
267 .Case<Value, IntegerAttr, TimeAttr>([&](
auto x) { os << x; })
268 .Case<Type>([&](
auto) { os <<
"dont-care"; });
279 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
282 void findHoistableSlots();
283 void collectDriveSets();
284 void finalizeDriveSets();
293 SmallSetVector<Value, 8> slots;
294 SmallVector<Operation *> suspendOps;
299void DriveHoister::hoist() {
300 findHoistableSlots();
309void DriveHoister::findHoistableSlots() {
310 SmallPtrSet<Value, 8> seenSlots;
312 auto slot = op.getSignal();
313 if (!seenSlots.insert(slot).second)
318 if (!slot.getDefiningOp<llhd::SignalOp>())
320 if (!slot.getParentRegion()->isProperAncestor(&
processOp.getBody()))
324 if (!llvm::all_of(slot.getUsers(), [&](
auto *user) {
326 if (!processOp.getBody().isAncestor(user->getParentRegion()))
328 return isa<ProbeOp, DriveOp>(user);
334 LLVM_DEBUG(llvm::dbgs() <<
"Found " << slots.size()
335 <<
" slots for drive hoisting\n");
344void DriveHoister::collectDriveSets() {
345 SmallPtrSet<Value, 8> unhoistableSlots;
348 auto trueAttr = BoolAttr::get(
processOp.getContext(),
true);
349 for (
auto &block :
processOp.getBody()) {
351 auto *terminator = block.getTerminator();
352 if (!isa<WaitOp, HaltOp>(terminator))
354 suspendOps.push_back(terminator);
356 bool beyondSideEffect =
false;
359 for (
auto &op :
llvm::make_early_inc_range(
360 llvm::reverse(block.without_terminator()))) {
361 auto driveOp = dyn_cast<DriveOp>(op);
367 if (!isMemoryEffectFree(&op))
368 beyondSideEffect =
true;
373 if (!slots.contains(driveOp.getSignal()) ||
374 unhoistableSlots.contains(driveOp.getSignal())) {
375 LLVM_DEBUG(llvm::dbgs()
376 <<
"- Skipping (slot unhoistable): " << driveOp <<
"\n");
382 if (beyondSideEffect) {
383 LLVM_DEBUG(llvm::dbgs()
384 <<
"- Aborting slot (drive across side-effect): " << driveOp
386 unhoistableSlots.insert(driveOp.getSignal());
391 auto &laterDrive = laterDrives[driveOp.getSignal()];
395 if (laterDrive.getTime() == driveOp.getTime() &&
396 laterDrive.getEnable() == driveOp.getEnable()) {
397 LLVM_DEBUG(llvm::dbgs()
398 <<
"- Skipping (driven later): " << driveOp <<
"\n");
405 LLVM_DEBUG(llvm::dbgs()
406 <<
"- Aborting slot (multiple drives): " << driveOp <<
"\n");
407 unhoistableSlots.insert(driveOp.getSignal());
410 laterDrive = driveOp;
413 auto operands = DriveOperands{
416 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
417 : DriveValue(trueAttr),
419 auto &driveSet = driveSets[driveOp.getSignal()];
420 driveSet.ops.insert(driveOp);
421 driveSet.operands.insert({terminator, operands});
426 slots.remove_if([&](
auto slot) {
427 if (unhoistableSlots.contains(slot)) {
428 driveSets.erase(slot);
439void DriveHoister::finalizeDriveSets() {
440 auto falseAttr = BoolAttr::get(
processOp.getContext(),
false);
441 for (
auto &[slot, driveSet] : driveSets) {
445 for (
auto *suspendOp : suspendOps) {
446 auto operands = DriveOperands{
447 DriveValue::dontCare(cast<RefType>(slot.getType()).getNestedType()),
448 DriveValue::dontCare(TimeType::get(
processOp.getContext())),
449 DriveValue(falseAttr),
451 driveSet.operands.insert({suspendOp, operands});
457 auto unify = [](DriveValue &accumulator, DriveValue other) {
458 if (other.isDontCare())
460 if (accumulator == other)
462 accumulator = accumulator.isDontCare() ? other : DriveValue();
464 assert(!driveSet.operands.empty());
465 driveSet.uniform = driveSet.operands.begin()->second;
466 for (
auto [terminator, otherOperands] : driveSet.operands) {
467 unify(driveSet.uniform.value, otherOperands.value);
468 unify(driveSet.uniform.delay, otherOperands.delay);
469 unify(driveSet.uniform.enable, otherOperands.enable);
476 auto clearIfNonConst = [&](DriveValue &driveValue) {
477 if (isa_and_nonnull<Value>(driveValue.data))
478 driveValue = DriveValue();
480 clearIfNonConst(driveSet.uniform.value);
481 clearIfNonConst(driveSet.uniform.delay);
482 clearIfNonConst(driveSet.uniform.enable);
486 for (
auto slot : slots) {
487 const auto &driveSet = driveSets[slot];
488 llvm::dbgs() <<
"Drives to " << slot <<
"\n";
489 if (driveSet.uniform.value)
490 llvm::dbgs() <<
"- Uniform value: " << driveSet.uniform.value <<
"\n";
491 if (driveSet.uniform.delay)
492 llvm::dbgs() <<
"- Uniform delay: " << driveSet.uniform.delay <<
"\n";
493 if (driveSet.uniform.enable)
494 llvm::dbgs() <<
"- Uniform enable: " << driveSet.uniform.enable <<
"\n";
495 for (
auto *suspendOp : suspendOps) {
496 auto operands = driveSet.operands.lookup(suspendOp);
497 llvm::dbgs() <<
"- At " << *suspendOp <<
"\n";
498 if (!driveSet.uniform.value)
499 llvm::dbgs() <<
" - Value: " << operands.value <<
"\n";
500 if (!driveSet.uniform.delay)
501 llvm::dbgs() <<
" - Delay: " << operands.delay <<
"\n";
502 if (!driveSet.uniform.enable)
503 llvm::dbgs() <<
" - Enable: " << operands.enable <<
"\n";
513void DriveHoister::hoistDrives() {
514 if (driveSets.empty())
516 LLVM_DEBUG(llvm::dbgs() <<
"Hoisting drives of " << driveSets.size()
523 auto materialize = [&](DriveValue driveValue) -> Value {
525 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
526 .Case<Value>([](
auto value) {
return value; })
527 .Case<IntegerAttr>([&](
auto attr) {
528 auto &slot = materializedConstants[attr];
533 .Case<TimeAttr>([&](
auto attr) {
534 auto &slot = materializedConstants[attr];
536 slot = ConstantTimeOp::create(builder,
processOp.getLoc(), attr);
539 .Case<Type>([&](
auto type) {
541 if (isa<TimeType>(type)) {
542 auto attr = TimeAttr::get(builder.getContext(), 0,
"ns", 0, 0);
543 auto &slot = materializedConstants[attr];
545 slot = ConstantTimeOp::create(builder,
processOp.getLoc(), attr);
548 auto numBits = hw::getBitWidth(type);
551 builder,
processOp.getLoc(), builder.getIntegerType(numBits), 0);
552 if (value.getType() != type)
561 for (
auto *suspendOp : suspendOps) {
562 LLVM_DEBUG(llvm::dbgs()
563 <<
"- Adding yield operands to " << *suspendOp <<
"\n");
564 MutableOperandRange yieldOperands =
565 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
566 .Case<WaitOp, HaltOp>(
567 [](
auto op) {
return op.getYieldOperandsMutable(); });
569 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
571 yieldOperands.append(materialize(nonUniform));
574 for (
auto slot : slots) {
575 auto &driveSet = driveSets[slot];
576 auto operands = driveSet.operands.lookup(suspendOp);
577 addYieldOperand(driveSet.uniform.value, operands.value);
578 addYieldOperand(driveSet.uniform.delay, operands.delay);
579 addYieldOperand(driveSet.uniform.enable, operands.enable);
584 SmallVector<Type> resultTypes(
processOp->getResultTypes());
585 auto oldNumResults = resultTypes.size();
586 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
588 resultTypes.push_back(nonUniform.getType());
590 for (
auto slot : slots) {
591 auto &driveSet = driveSets[slot];
592 auto operands = driveSet.operands.begin()->second;
593 addResultType(driveSet.uniform.value, operands.value);
594 addResultType(driveSet.uniform.delay, operands.delay);
595 addResultType(driveSet.uniform.enable, operands.enable);
598 ProcessOp::create(builder,
processOp.getLoc(), resultTypes,
600 newProcessOp.getBody().takeBody(
processOp.getBody());
602 newProcessOp->getResults().slice(0, oldNumResults));
609 builder.setInsertionPointAfter(
processOp);
610 auto newResultIdx = oldNumResults;
612 auto useResultValue = [&](DriveValue uniform) {
614 return processOp.getResult(newResultIdx++);
615 return materialize(uniform);
618 auto removeIfUnused = [](Value value) {
620 if (
auto *defOp = value.getDefiningOp())
621 if (defOp && isOpTriviallyDead(defOp))
625 auto trueAttr = builder.getBoolAttr(
true);
626 for (
auto slot : slots) {
627 auto &driveSet = driveSets[slot];
630 auto value = useResultValue(driveSet.uniform.value);
631 auto delay = useResultValue(driveSet.uniform.delay);
632 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
633 ? useResultValue(driveSet.uniform.enable)
635 [[maybe_unused]]
auto newDrive =
636 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
637 LLVM_DEBUG(llvm::dbgs() <<
"- Add " << newDrive <<
"\n");
640 for (
auto *oldOp : driveSet.ops) {
641 auto oldDrive = cast<DriveOp>(oldOp);
642 LLVM_DEBUG(llvm::dbgs() <<
"- Remove " << oldDrive <<
"\n");
643 auto delay = oldDrive.getTime();
644 auto enable = oldDrive.getEnable();
646 removeIfUnused(delay);
647 removeIfUnused(enable);
649 driveSet.ops.clear();
659struct HoistSignalsPass
660 :
public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
661 void runOnOperation()
override;
665void HoistSignalsPass::runOnOperation() {
666 SmallVector<Region *> regions;
667 getOperation()->walk([&](Operation *op) {
668 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
669 for (
auto ®ion : op->getRegions())
671 regions.push_back(®ion);
673 for (
auto *region : regions) {
674 ProbeHoister(*region).hoist();
675 if (
auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
assert(baseType &&"element must be base type")
static LogicalResult processOp(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, const ModuleUpdateTable &updateTable, InstanceOp op)
static LogicalResult unify(Term *lhs, Term *rhs)
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)