60#include "mlir/Analysis/Liveness.h"
61#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
62#include "mlir/Dialect/Func/IR/FuncOps.h"
63#include "mlir/Dialect/UB/IR/UBOps.h"
64#include "mlir/IR/AttrTypeSubElements.h"
65#include "mlir/IR/Dominance.h"
66#include "mlir/Pass/Pass.h"
67#include "llvm/ADT/TypeSwitch.h"
68#include "llvm/Support/GenericIteratedDominanceFrontier.h"
72#define GEN_PASS_DEF_LOWERCOROUTINESPASS
73#include "circt/Dialect/Arc/ArcPasses.h.inc"
105 SmallPtrSet<Block *, 8> resumeBlockSet;
106 for (
auto &block : region)
107 if (
auto yieldOp = dyn_cast<CoroutineYieldOp>(block.getTerminator()))
108 resumeBlockSet.insert(yieldOp.getDest());
109 SmallVector<Block *> resumeBlocks;
110 for (
auto &block : region)
111 if (resumeBlockSet.contains(&block))
112 resumeBlocks.push_back(&block);
119 ArrayRef<Block *> captureBlocks,
121 DominanceInfo &dominance) {
122 auto *defBlock = value.getParentBlock();
130 auto &domTree = dominance.getDomTree(®ion);
131 llvm::IDFCalculatorBase<Block, false> idfCalculator(domTree);
133 SmallPtrSet<Block *, 8> definingBlocks(captureBlocks.begin(),
134 captureBlocks.end());
135 definingBlocks.insert(defBlock);
136 idfCalculator.setDefiningBlocks(definingBlocks);
138 SmallPtrSet<Block *, 16> liveInBlocks;
139 for (
auto &block : region)
140 if (liveness.getLiveness(&block)->isLiveIn(value))
141 liveInBlocks.insert(&block);
142 idfCalculator.setLiveInBlocks(liveInBlocks);
144 SmallVector<Block *> mergeBlocks;
145 idfCalculator.calculate(mergeBlocks);
147 SmallPtrSet<Block *, 16> argBlocks(mergeBlocks.begin(), mergeBlocks.end());
148 argBlocks.insert(captureBlocks.begin(), captureBlocks.end());
156 struct WorklistItem {
157 DominanceInfoNode *domNode;
160 SmallVector<WorklistItem> worklist;
161 worklist.push_back({domTree.getNode(defBlock), value});
163 while (!worklist.empty()) {
164 auto item = worklist.pop_back_val();
165 auto *block = item.domNode->getBlock();
167 if (argBlocks.contains(block))
168 item.reachingDef = block->addArgument(value.getType(), value.getLoc());
171 if (item.reachingDef != value)
172 block->walk([&](Operation *nestedOp) {
173 nestedOp->replaceUsesOfWith(value, item.reachingDef);
178 auto *terminator = block->getTerminator();
179 auto branchOp = dyn_cast<BranchOpInterface>(terminator);
180 for (
auto &blockOperand : terminator->getBlockOperands()) {
181 if (!argBlocks.contains(blockOperand.get()))
184 return terminator->emitOpError()
185 <<
"does not implement `BranchOpInterface`; cannot pass value "
186 "into successor block";
187 branchOp.getSuccessorOperands(blockOperand.getOperandNumber())
188 .
append(item.reachingDef);
191 for (
auto *child : item.domNode->children())
192 worklist.push_back({child, item.reachingDef});
202 ArrayRef<Block *> captureBlocks,
203 Liveness &liveness) {
206 DenseSet<Block *> resumedBlocks(captureBlocks.begin(), captureBlocks.end());
207 SmallVector<Block *> worklist(captureBlocks.begin(), captureBlocks.end());
208 while (!worklist.empty())
209 for (
auto *successor : worklist.pop_back_val()->getSuccessors())
210 if (liveness.getLiveIn(successor).contains(value) &&
211 resumedBlocks.insert(successor).second)
212 worklist.push_back(successor);
215 auto *defOp = value.getDefiningOp();
216 DenseMap<Block *, Value> clones;
217 for (
auto &use : llvm::make_early_inc_range(value.getUses())) {
218 auto *block = region.findAncestorBlockInRegion(*use.getOwner()->getBlock());
219 if (!resumedBlocks.contains(block))
221 auto &clone = clones[block];
223 OpBuilder builder(block, block->begin());
224 clone = builder.clone(*defOp)->getResult(0);
228 if (defOp->use_empty())
236 Region ®ion = defineOp.getBody();
237 if (region.hasOneBlock())
241 if (resumeBlocks.empty())
249 Liveness liveness(defineOp);
250 DominanceInfo dominance(defineOp);
254 SmallVector<Value> values;
255 for (
auto &block : region) {
256 llvm::append_range(values, block.getArguments());
257 for (
auto &op : block)
258 llvm::append_range(values, op.getResults());
261 for (
auto value : values) {
265 SmallVector<Block *> captureBlocks;
266 for (
auto *block : resumeBlocks)
267 if (liveness.getLiveIn(block).contains(value))
268 captureBlocks.push_back(block);
269 if (captureBlocks.empty())
276 auto *defOp = value.getDefiningOp();
277 if (defOp && (defOp->hasTrait<OpTrait::ConstantLike>() ||
278 isa<InferredContextOp>(defOp))) {
282 if (failed(
captureValue(value, region, captureBlocks, liveness, dominance)))
297struct CoroutineLowering {
299 CoroutineDefineOp defineOp;
302 SmallVector<Block *> resumeBlocks;
305 SmallVector<std::optional<unsigned>> variantIndices;
314 uint64_t getReturnPC() {
return getHaltPC() - 1; }
316 uint64_t getHaltPC() {
317 return APInt::getAllOnes(pcType.getWidth()).getZExtValue();
321 std::optional<hw::UnionType::FieldInfo> getVariant(
unsigned resumeIndex) {
322 if (
auto fieldIndex = variantIndices[resumeIndex])
323 return cast<hw::UnionType>(stateType).getElements()[*fieldIndex];
334 auto *
context = defineOp.getContext();
335 CoroutineLowering lowering;
336 lowering.defineOp = defineOp;
343 unsigned pcWidth = llvm::Log2_64_Ceil(lowering.resumeBlocks.size() + 3);
344 lowering.pcType = IntegerType::get(
context, pcWidth);
349 unsigned numCoroutineArgs = defineOp.getArgumentTypes().size();
350 SmallVector<hw::UnionType::FieldInfo> variants;
351 for (
auto [index, block] : llvm::enumerate(lowering.resumeBlocks)) {
352 auto persistedArgs = block->getArguments().drop_front(numCoroutineArgs);
353 if (persistedArgs.empty()) {
354 lowering.variantIndices.push_back(std::nullopt);
357 SmallVector<hw::StructType::FieldInfo> fields;
358 for (
auto [fieldIndex, arg] : llvm::enumerate(persistedArgs))
360 {StringAttr::get(
context,
"f" + Twine(fieldIndex)), arg.getType()});
361 lowering.variantIndices.push_back(variants.size());
362 variants.push_back({StringAttr::get(
context,
"r" + Twine(index + 1)),
363 hw::StructType::get(
context, fields), 0});
365 if (variants.empty())
366 lowering.stateType = hw::StructType::get(
context, {});
368 lowering.stateType = hw::UnionType::get(
context, variants);
384 auto defineOp = lowering.defineOp;
385 auto loc = defineOp.getLoc();
386 auto *
context = defineOp.getContext();
387 unsigned pcWidth = lowering.pcType.getWidth();
391 SmallVector<Type> inputTypes{lowering.stateType, lowering.pcType};
392 llvm::append_range(inputTypes, defineOp.getArgumentTypes());
393 SmallVector<Type> resultTypes{lowering.stateType, lowering.pcType};
394 llvm::append_range(resultTypes, defineOp.getResultTypes());
395 OpBuilder builder(defineOp);
397 func::FuncOp::create(builder, loc, defineOp.getSymName(),
398 builder.getFunctionType(inputTypes, resultTypes));
402 funcOp.getBody().getBlocks().splice(funcOp.getBody().end(),
403 defineOp.getBody().getBlocks());
404 auto *entryBlock = &funcOp.getBody().front();
405 auto *dispatchBlock =
406 builder.createBlock(&funcOp.getBody(), funcOp.getBody().begin());
407 Value stateArg = dispatchBlock->addArgument(lowering.stateType, loc);
408 Value pcArg = dispatchBlock->addArgument(lowering.pcType, loc);
409 SmallVector<Value> callerArgs;
410 for (
auto arg : entryBlock->getArguments())
411 callerArgs.push_back(
412 dispatchBlock->addArgument(arg.getType(), arg.getLoc()));
417 SmallVector<APInt> caseValues;
418 SmallVector<Block *> caseBlocks;
419 for (
auto [index, resumeBlock] : llvm::enumerate(lowering.resumeBlocks)) {
420 auto *trampolineBlock = builder.createBlock(resumeBlock);
421 SmallVector<Value> operands = callerArgs;
422 if (
auto variant = lowering.getVariant(index)) {
424 hw::UnionExtractOp::create(builder, loc, stateArg, variant->name);
425 auto explodeOp = hw::StructExplodeOp::create(builder, loc, variantValue);
426 llvm::append_range(operands, explodeOp.getResults());
428 cf::BranchOp::create(builder, loc, resumeBlock, operands);
429 caseValues.push_back(APInt(pcWidth, index + 1));
430 caseBlocks.push_back(trampolineBlock);
436 builder.setInsertionPointToEnd(dispatchBlock);
437 if (lowering.resumeBlocks.empty()) {
438 cf::BranchOp::create(builder, loc, entryBlock, callerArgs);
440 SmallVector<ValueRange> caseOperands(caseBlocks.size());
441 cf::SwitchOp::create(builder, loc, pcArg, entryBlock, callerArgs,
442 caseValues, caseBlocks, caseOperands);
447 DenseMap<Block *, unsigned> resumePCs;
448 for (
auto [index, block] : llvm::enumerate(lowering.resumeBlocks))
449 resumePCs[block] = index + 1;
451 auto lowerTerminator = [&](Operation *op, Value state, uint64_t pc,
452 ValueRange yieldOperands) {
453 OpBuilder builder(op);
455 state = ub::PoisonOp::create(builder, op->getLoc(), lowering.stateType,
459 SmallVector<Value> operands{state, pcValue};
460 llvm::append_range(operands, yieldOperands);
461 func::ReturnOp::create(builder, op->getLoc(), operands);
465 for (
auto &block : funcOp.getBody()) {
466 TypeSwitch<Operation *>(block.getTerminator())
467 .Case<CoroutineYieldOp>([&](CoroutineYieldOp op) {
471 unsigned pc = resumePCs.lookup(op.getDest());
473 if (
auto variant = lowering.getVariant(pc - 1)) {
474 OpBuilder builder(op);
476 builder, op.getLoc(), variant->type, op.getDestOperands());
477 state = hw::UnionCreateOp::create(builder, op.getLoc(),
478 lowering.stateType, variant->name,
481 lowerTerminator(op, state, pc, op.getYieldOperands());
483 .Case<CoroutineReturnOp>([&](CoroutineReturnOp op) {
484 lowerTerminator(op, Value{}, lowering.getReturnPC(),
485 op.getYieldOperands());
487 .Case<CoroutineHaltOp>([&](CoroutineHaltOp op) {
488 lowerTerminator(op, Value{}, lowering.getHaltPC(),
489 op.getYieldOperands());
501struct LowerCoroutinesPass
502 :
public arc::impl::LowerCoroutinesPassBase<LowerCoroutinesPass> {
503 void runOnOperation()
override;
507void LowerCoroutinesPass::runOnOperation() {
508 auto module = getOperation();
514 SmallVector<CoroutineDefineOp> defineOps;
515 auto walkResult =
module->walk([&](Operation *op) {
516 if (auto instanceOp = dyn_cast<CoroutineInstanceOp>(op)) {
517 instanceOp.emitOpError("must be lowered before LowerCoroutines");
518 return WalkResult::interrupt();
520 if (
auto defineOp = dyn_cast<CoroutineDefineOp>(op))
521 defineOps.push_back(defineOp);
522 return WalkResult::advance();
524 if (walkResult.wasInterrupted())
525 return signalPassFailure();
532 DenseMap<StringAttr, CoroutineLowering> lowerings;
533 for (
auto defineOp : defineOps) {
535 return signalPassFailure();
545 enum class Color { Unvisited, InProgress, Done };
546 DenseMap<StringAttr, Color> colors;
547 std::function<LogicalResult(StringAttr)> checkCycles =
548 [&](StringAttr name) -> LogicalResult {
549 auto it = lowerings.find(name);
550 if (it == lowerings.end())
552 if (colors.lookup(name) == Color::Done)
554 if (colors.lookup(name) == Color::InProgress)
555 return it->second.defineOp.emitOpError(
556 "recursive coroutines are not supported");
557 colors[name] = Color::InProgress;
558 auto result = success();
559 it->second.stateType.walk([&](CoroutineStateType type) {
560 if (failed(checkCycles(type.getCoroutine().getAttr())))
563 colors[name] = Color::Done;
566 for (
auto defineOp : defineOps)
567 if (failed(checkCycles(defineOp.getSymNameAttr())))
568 return signalPassFailure();
572 for (
auto defineOp : defineOps)
580 bool hasUnknownCoroutines =
false;
581 auto lookupLowering =
582 [&](FlatSymbolRefAttr coroutine) -> CoroutineLowering * {
583 auto it = lowerings.find(coroutine.getAttr());
584 if (it != lowerings.end())
586 hasUnknownCoroutines =
true;
587 mlir::emitError(module.getLoc())
588 <<
"coroutine type references unknown coroutine " << coroutine;
591 AttrTypeReplacer replacer;
592 replacer.addReplacement([&](CoroutineStateType type) -> std::optional<Type> {
593 if (
auto *lowering = lookupLowering(type.getCoroutine()))
594 return lowering->stateType;
597 replacer.addReplacement([&](CoroutinePCType type) -> std::optional<Type> {
598 if (
auto *lowering = lookupLowering(type.getCoroutine()))
599 return lowering->pcType;
606 SmallVector<Operation *> opsToLower;
607 module->walk([&](Operation *op) {
608 if (isa<CoroutineCallOp, CoroutineStartPCOp, CoroutineUndefinedStateOp,
609 CoroutinePCIsReturnOp, CoroutinePCIsHaltOp>(op))
610 opsToLower.push_back(op);
616 auto getPCWidth = [&](Value pc) -> std::optional<unsigned> {
617 if (
auto intType = dyn_cast<IntegerType>(pc.getType()))
618 return intType.getWidth();
619 if (
auto intType = dyn_cast<IntegerType>(replacer.replace(pc.getType())))
620 return intType.getWidth();
624 for (
auto *op : opsToLower) {
625 OpBuilder builder(op);
626 TypeSwitch<Operation *>(op)
627 .Case<CoroutineCallOp>([&](CoroutineCallOp op) {
631 llvm::map_to_vector(op.getResultTypes(), [&](Type type) {
632 return replacer.replace(type);
635 func::CallOp::create(builder, op.getLoc(), op.getCalleeAttr(),
636 resultTypes, op.getOperands());
637 op->replaceAllUsesWith(callOp);
640 .Case<CoroutineStartPCOp>([&](CoroutineStartPCOp op) {
641 auto pcType = dyn_cast<IntegerType>(replacer.replace(op.getType()));
645 APInt(pcType.getWidth(), 0));
646 op->replaceAllUsesWith(ValueRange{value});
649 .Case<CoroutineUndefinedStateOp>([&](CoroutineUndefinedStateOp op) {
652 auto stateType = replacer.replace(op.getType());
653 if (stateType == op.getType())
656 ub::PoisonOp::create(builder, op.getLoc(), stateType,
657 ub::PoisonAttr::get(builder.getContext()));
658 op->replaceAllUsesWith(ValueRange{value});
661 .Case<CoroutinePCIsReturnOp, CoroutinePCIsHaltOp>([&](
auto op) {
664 Value pc = op->getOperand(0);
665 auto pcWidth = getPCWidth(pc);
668 auto sentinel = APInt::getAllOnes(*pcWidth);
669 if (isa<CoroutinePCIsReturnOp>(op))
673 Value cmpValue = comb::ICmpOp::create(
674 builder, op.getLoc(), comb::ICmpPredicate::eq, pc, constValue);
675 op->replaceAllUsesWith(ValueRange{cmpValue});
683 replacer.recursivelyReplaceElementsIn(module,
true,
686 if (hasUnknownCoroutines)
687 return signalPassFailure();
static std::unique_ptr< Context > context
static CoroutineLowering analyzeDefinition(CoroutineDefineOp defineOp)
Determine the resume blocks of a coroutine and derive its concrete PC and state types.
static void lowerDefinition(CoroutineLowering &lowering)
Replace a coroutine definition with a state machine function.
static LogicalResult captureValue(Value value, Region ®ion, ArrayRef< Block * > captureBlocks, Liveness &liveness, DominanceInfo &dominance)
Capture a single value as a trailing block argument of each of the given resume blocks and rewrite al...
static SmallVector< Block * > collectResumeBlocks(Region ®ion)
Collect the resume blocks of a coroutine body, i.e.
static LogicalResult captureValuesAcrossSuspension(CoroutineDefineOp defineOp)
Rewrite the body of a coroutine such that every resume block captures the values that are live across...
static void rematerializeConstant(Value value, Region ®ion, ArrayRef< Block * > captureBlocks, Liveness &liveness)
Clone a constant into the using blocks that are reachable from a capturing resume block,...
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
create(elements, Type result_type=None)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.