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;
311 processOp.walk([&](DriveOp op) {
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 auto trueAttr = BoolAttr::get(processOp.getContext(),
true);
346 for (
auto &block : processOp.getBody()) {
348 auto *terminator = block.getTerminator();
349 if (!isa<WaitOp, HaltOp>(terminator))
351 suspendOps.push_back(terminator);
353 SmallPtrSet<Value, 8> drivenSlots;
354 for (
auto &op :
llvm::make_early_inc_range(
355 llvm::reverse(block.without_terminator()))) {
356 auto driveOp = dyn_cast<DriveOp>(op);
362 if (isMemoryEffectFree(&op))
369 if (!slots.contains(driveOp.getSignal())) {
370 LLVM_DEBUG(llvm::dbgs()
371 <<
"- Skipping (slot unhoistable) " << driveOp <<
"\n");
376 if (!drivenSlots.insert(driveOp.getSignal()).second) {
377 LLVM_DEBUG(llvm::dbgs()
378 <<
"- Skipping (driven later) " << driveOp <<
"\n");
383 auto operands = DriveOperands{
386 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
387 : DriveValue(trueAttr),
389 auto &driveSet = driveSets[driveOp.getSignal()];
390 driveSet.ops.insert(driveOp);
391 driveSet.operands.insert({terminator, operands});
400void DriveHoister::finalizeDriveSets() {
401 auto falseAttr = BoolAttr::get(processOp.getContext(),
false);
402 for (
auto &[slot, driveSet] : driveSets) {
406 for (
auto *suspendOp : suspendOps) {
407 auto operands = DriveOperands{
408 DriveValue::dontCare(cast<RefType>(slot.getType()).getNestedType()),
409 DriveValue::dontCare(TimeType::get(processOp.getContext())),
410 DriveValue(falseAttr),
412 driveSet.operands.insert({suspendOp, operands});
418 auto unify = [](DriveValue &accumulator, DriveValue other) {
419 if (other.isDontCare())
421 if (accumulator == other)
423 accumulator = accumulator.isDontCare() ? other : DriveValue();
425 assert(!driveSet.operands.empty());
426 driveSet.uniform = driveSet.operands.begin()->second;
427 for (
auto [terminator, otherOperands] : driveSet.operands) {
428 unify(driveSet.uniform.value, otherOperands.value);
429 unify(driveSet.uniform.delay, otherOperands.delay);
430 unify(driveSet.uniform.enable, otherOperands.enable);
437 auto clearIfNonConst = [&](DriveValue &driveValue) {
438 if (isa_and_nonnull<Value>(driveValue.data))
439 driveValue = DriveValue();
441 clearIfNonConst(driveSet.uniform.value);
442 clearIfNonConst(driveSet.uniform.delay);
443 clearIfNonConst(driveSet.uniform.enable);
447 for (
auto slot : slots) {
448 const auto &driveSet = driveSets[slot];
449 llvm::dbgs() <<
"Drives to " << slot <<
"\n";
450 if (driveSet.uniform.value)
451 llvm::dbgs() <<
"- Uniform value: " << driveSet.uniform.value <<
"\n";
452 if (driveSet.uniform.delay)
453 llvm::dbgs() <<
"- Uniform delay: " << driveSet.uniform.delay <<
"\n";
454 if (driveSet.uniform.enable)
455 llvm::dbgs() <<
"- Uniform enable: " << driveSet.uniform.enable <<
"\n";
456 for (
auto *suspendOp : suspendOps) {
457 auto operands = driveSet.operands.lookup(suspendOp);
458 llvm::dbgs() <<
"- At " << *suspendOp <<
"\n";
459 if (!driveSet.uniform.value)
460 llvm::dbgs() <<
" - Value: " << operands.value <<
"\n";
461 if (!driveSet.uniform.delay)
462 llvm::dbgs() <<
" - Delay: " << operands.delay <<
"\n";
463 if (!driveSet.uniform.enable)
464 llvm::dbgs() <<
" - Enable: " << operands.enable <<
"\n";
474void DriveHoister::hoistDrives() {
475 if (driveSets.empty())
477 LLVM_DEBUG(llvm::dbgs() <<
"Hoisting drives of " << driveSets.size()
481 OpBuilder builder(processOp);
484 auto materialize = [&](DriveValue driveValue) -> Value {
485 OpBuilder builder(processOp);
486 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
487 .Case<Value>([](
auto value) {
return value; })
488 .Case<IntegerAttr>([&](
auto attr) {
489 auto &slot = materializedConstants[attr];
494 .Case<TimeAttr>([&](
auto attr) {
495 auto &slot = materializedConstants[attr];
498 llhd::ConstantTimeOp::create(builder, processOp.getLoc(), attr);
501 .Case<Type>([&](
auto type) {
503 unsigned numBits = hw::getBitWidth(type);
506 builder, processOp.getLoc(), builder.getIntegerType(numBits), 0);
507 if (value.getType() != type)
516 for (
auto *suspendOp : suspendOps) {
517 LLVM_DEBUG(llvm::dbgs()
518 <<
"- Adding yield operands to " << *suspendOp <<
"\n");
519 MutableOperandRange yieldOperands =
520 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
521 .Case<WaitOp, HaltOp>(
522 [](
auto op) {
return op.getYieldOperandsMutable(); });
524 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
526 yieldOperands.append(materialize(nonUniform));
529 for (
auto slot : slots) {
530 auto &driveSet = driveSets[slot];
531 auto operands = driveSet.operands.lookup(suspendOp);
532 addYieldOperand(driveSet.uniform.value, operands.value);
533 addYieldOperand(driveSet.uniform.delay, operands.delay);
534 addYieldOperand(driveSet.uniform.enable, operands.enable);
539 SmallVector<Type> resultTypes(processOp->getResultTypes());
540 auto oldNumResults = resultTypes.size();
541 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
543 resultTypes.push_back(nonUniform.getType());
545 for (
auto slot : slots) {
546 auto &driveSet = driveSets[slot];
547 auto operands = driveSet.operands.begin()->second;
548 addResultType(driveSet.uniform.value, operands.value);
549 addResultType(driveSet.uniform.delay, operands.delay);
550 addResultType(driveSet.uniform.enable, operands.enable);
553 ProcessOp::create(builder, processOp.getLoc(), resultTypes,
554 processOp->getOperands(), processOp->getAttrs());
555 newProcessOp.getBody().takeBody(processOp.getBody());
556 processOp.replaceAllUsesWith(
557 newProcessOp->getResults().slice(0, oldNumResults));
559 processOp = newProcessOp;
564 builder.setInsertionPointAfter(processOp);
565 auto newResultIdx = oldNumResults;
567 auto useResultValue = [&](DriveValue uniform) {
569 return processOp.getResult(newResultIdx++);
570 return materialize(uniform);
573 auto removeIfUnused = [](Value value) {
575 if (
auto *defOp = value.getDefiningOp())
576 if (defOp && isOpTriviallyDead(defOp))
580 auto trueAttr = builder.getBoolAttr(
true);
581 for (
auto slot : slots) {
582 auto &driveSet = driveSets[slot];
585 auto value = useResultValue(driveSet.uniform.value);
586 auto delay = useResultValue(driveSet.uniform.delay);
587 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
588 ? useResultValue(driveSet.uniform.enable)
590 [[maybe_unused]]
auto newDrive =
591 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
592 LLVM_DEBUG(llvm::dbgs() <<
"- Add " << newDrive <<
"\n");
595 for (
auto *oldOp : driveSet.ops) {
596 auto oldDrive = cast<DriveOp>(oldOp);
597 LLVM_DEBUG(llvm::dbgs() <<
"- Remove " << oldDrive <<
"\n");
598 auto delay = oldDrive.getTime();
599 auto enable = oldDrive.getEnable();
601 removeIfUnused(delay);
602 removeIfUnused(enable);
604 driveSet.ops.clear();
606 assert(newResultIdx == processOp->getNumResults());
614struct HoistSignalsPass
615 :
public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
616 void runOnOperation()
override;
620void HoistSignalsPass::runOnOperation() {
621 SmallVector<Region *> regions;
622 getOperation()->walk([&](Operation *op) {
623 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
624 for (
auto ®ion : op->getRegions())
626 regions.push_back(®ion);
628 for (
auto *region : regions) {
629 ProbeHoister(*region).hoist();
630 if (
auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
631 DriveHoister(processOp).hoist();
assert(baseType &&"element must be base type")
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)