15#include "mlir/Dialect/Arith/IR/Arith.h"
16#include "mlir/Dialect/MemRef/IR/MemRef.h"
17#include "mlir/IR/IRMapping.h"
18#include "mlir/IR/PatternMatch.h"
19#include "mlir/Pass/Pass.h"
20#include "mlir/Transforms/DialectConversion.h"
21#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
22#include "mlir/Transforms/WalkPatternRewriteDriver.h"
23#include "llvm/ADT/STLExtras.h"
26#define GEN_PASS_DEF_AIEMATERIALIZERUNTIMESEQUENCES
27#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h.inc"
30#define DEBUG_TYPE "aie-materialize-runtime-sequence"
47 AIE::RuntimeSequenceOp runtimeSequenceOp =
48 llvm::dyn_cast<AIE::RuntimeSequenceOp>(op);
49 if (!runtimeSequenceOp) {
50 op->emitError(
"RuntimeCallGraphCyclicityAnalysis can only be called on "
51 "aiex.runtime_sequence operations.");
57 llvm::DenseSet<AIE::RuntimeSequenceOp> callStack;
58 llvm::DenseSet<AIE::RuntimeSequenceOp> visited;
61 std::function<bool(AIE::RuntimeSequenceOp)> hasCycle =
62 [&](AIE::RuntimeSequenceOp seq) ->
bool {
63 if (callStack.contains(seq)) {
66 if (visited.contains(seq)) {
70 callStack.insert(seq);
74 bool foundCycle =
false;
75 seq.walk([&](RunOp runOp) {
76 if (AIE::RuntimeSequenceOp callee =
77 runOp.getCalleeRuntimeSequenceOp(&calleeNames)) {
78 if (hasCycle(callee)) {
80 return WalkResult::interrupt();
83 return WalkResult::advance();
90 if (hasCycle(runtimeSequenceOp)) {
108 PatternBenefit benefit = 1)
109 : RewritePattern(ConfigureOp::getOperationName(), benefit, context) {}
112 PatternRewriter &rewriter)
const override {
113 ConfigureOp configureOp = llvm::dyn_cast<ConfigureOp>(op);
120 if (!llvm::isa<AIE::RuntimeSequenceOp>(configureOp->getParentOp())) {
124 AIE::DeviceOp referencedDevice = configureOp.getReferencedDeviceOp();
125 if (!referencedDevice) {
126 configureOp.emitError(
"Referenced symbol is not a device");
130 Block *configureBlock;
131 if (configureOp.getBody().empty()) {
132 configureBlock = rewriter.createBlock(&configureOp.getBody());
134 configureBlock = &configureOp.getBody().front();
137 rewriter.setInsertionPointToStart(configureBlock);
138 AIEX::NpuLoadPdiOp::create(
139 rewriter, configureOp.getLoc(),
140 FlatSymbolRefAttr::get(referencedDevice.getSymNameAttr()),
141 nullptr,
nullptr,
nullptr,
142 configureOp.getExpandModeAttr());
156collectReferencedSSAValues(Operation *op,
const IRMapping &argMap,
157 llvm::SetVector<Value> &referencedValues) {
159 auto processValue = [&](Value operand) {
160 if (argMap.contains(operand)) {
167 if (!argMap.contains(traceResult->rootArg)) {
168 referencedValues.insert(traceResult->rootArg);
174 referencedValues.insert(operand);
178 for (Value operand : op->getOperands()) {
179 processValue(operand);
183 for (Region ®ion : op->getRegions()) {
184 region.walk([&](Operation *nestedOp) {
185 for (Value operand : nestedOp->getOperands()) {
186 if (argMap.contains(operand)) {
191 if (Operation *defOp = operand.getDefiningOp()) {
192 if (op->isProperAncestor(defOp)) {
195 }
else if (
auto blockArg = llvm::dyn_cast<BlockArgument>(operand)) {
202 Operation *owner = blockArg.getOwner()->getParentOp();
203 if (owner && (owner == op || op->isProperAncestor(owner))) {
208 processValue(operand);
217uniqueSymbolName(MLIRContext *ctx, llvm::StringRef base,
218 llvm::SetVector<SymbolRefAttr> &allSymbolNames) {
219 std::string uniqueName = base.str();
220 unsigned uniquingCounter = 0;
221 while (allSymbolNames.count(SymbolRefAttr::get(ctx, uniqueName))) {
222 uniqueName = base.str() +
"_" + std::to_string(uniquingCounter);
225 allSymbolNames.insert(SymbolRefAttr::get(ctx, uniqueName));
233getOrClone(PatternRewriter &rewriter, Operation *op, IRMapping &argMap,
234 llvm::DenseMap<Operation *, Operation *> &clonedDefs,
235 mlir::OpBuilder::InsertPoint &insertPoint,
236 llvm::SetVector<SymbolRefAttr> &allSymbolNames) {
237 auto it = clonedDefs.find(op);
238 if (it == clonedDefs.end()) {
239 rewriter.restoreInsertionPoint(insertPoint);
240 Operation *clone = rewriter.clone(*op, argMap);
241 if (
auto name = clone->getAttrOfType<StringAttr>(
242 SymbolTable::getSymbolAttrName())) {
243 clone->setAttr(SymbolTable::getSymbolAttrName(),
244 rewriter.getStringAttr(uniqueSymbolName(
245 op->getContext(), name.getValue(), allSymbolNames)));
247 it = clonedDefs.try_emplace(op, clone).first;
248 insertPoint = rewriter.saveInsertionPoint();
250 argMap.map(op->getResults(), it->second->getResults());
257static LogicalResult copyReferencedSSAValues(
258 PatternRewriter &rewriter,
const llvm::SetVector<Value> &referencedValues,
259 AIE::DeviceOp callerDevice, IRMapping &argMap,
260 llvm::DenseMap<Operation *, Operation *> &clonedDefs,
261 mlir::OpBuilder::InsertPoint &clonedSSAInsertPoint,
262 llvm::SetVector<SymbolRefAttr> &allSymbolNames, Operation *errorReportOp) {
264 llvm::SetVector<Value> referencedValuesToVisit = referencedValues;
265 std::vector<Operation *> referencedOpsToClone = {};
266 while (!referencedValuesToVisit.empty()) {
267 Value referencedValue = referencedValuesToVisit.pop_back_val();
268 Operation *definingOp = referencedValue.getDefiningOp();
270 return errorReportOp->emitError()
271 <<
"Referenced value is not defined by an operation";
273 if (llvm::find(referencedOpsToClone, definingOp) !=
274 referencedOpsToClone.end()) {
278 if (
auto tileOp = llvm::dyn_cast<AIE::TileOp>(definingOp)) {
279 referencedOpsToClone.insert(referencedOpsToClone.begin(), definingOp);
280 }
else if (
auto lockOp = llvm::dyn_cast<AIE::LockOp>(definingOp)) {
281 Value lockTile = lockOp.getTile();
283 referencedValuesToVisit.insert(lockTile);
285 referencedOpsToClone.push_back(definingOp);
286 }
else if (
auto bufferOp = llvm::dyn_cast<AIE::BufferOp>(definingOp)) {
287 referencedValuesToVisit.insert(bufferOp.getTile());
288 referencedOpsToClone.push_back(definingOp);
290 return errorReportOp->emitError()
291 <<
"Referenced SSA value defined by unsupported operation type: "
292 << definingOp->getName().getStringRef()
293 <<
". Currently only aie.tile, aie.lock and aie.buffer "
294 "operations are supported.";
298 for (Operation *definingOp : referencedOpsToClone) {
299 if (
auto tileOp = llvm::dyn_cast<AIE::TileOp>(definingOp)) {
300 int col = tileOp.getCol();
301 int row = tileOp.getRow();
305 AIE::TileOp existingTile =
nullptr;
306 for (AIE::TileOp tile : callerDevice.getOps<AIE::TileOp>()) {
307 if (tile.getCol() == col && tile.getRow() == row) {
315 if (tileOp->getAttrDictionary() != existingTile->getAttrDictionary()) {
318 auto filterAttrs = [](DictionaryAttr dict) -> DictionaryAttr {
319 SmallVector<NamedAttribute> filteredAttrs;
320 for (
auto namedAttr : dict) {
321 StringRef name = namedAttr.getName().getValue();
322 if (name !=
"col" && name !=
"row") {
323 filteredAttrs.push_back(namedAttr);
326 return DictionaryAttr::get(dict.getContext(), filteredAttrs);
329 DictionaryAttr tileAttrs = filterAttrs(tileOp->getAttrDictionary());
330 DictionaryAttr existingAttrs =
331 filterAttrs(existingTile->getAttrDictionary());
333 if (tileAttrs != existingAttrs) {
334 return errorReportOp->emitError()
335 <<
"aie.tile(" << col <<
", " << row
336 <<
") already exists in the device with different "
340 clonedDefs[definingOp] = existingTile.getOperation();
344 Operation *clonedOp = getOrClone(rewriter, definingOp, argMap, clonedDefs,
345 clonedSSAInsertPoint, allSymbolNames);
346 rewriter.replaceOpUsesWithIf(
347 definingOp, clonedOp->getResult(0), [&](OpOperand &operand) {
348 return operand.getOwner()->getParentOfType<AIE::DeviceOp>() ==
361static LogicalResult inlineReferencedSymbolDefinitions(
362 PatternRewriter &rewriter, Operation *op, Operation *lookupFrom,
364 llvm::DenseMap<SymbolRefAttr, SymbolRefAttr> &previouslyInlinedSymbolMap,
365 AIE::DeviceOp callerDevice,
366 llvm::DenseMap<Operation *, Operation *> &clonedDefs,
368 mlir::OpBuilder::InsertPoint &clonedDefOpsInsertionPoint,
369 llvm::SetVector<SymbolRefAttr> &allSymbolNames) {
370 for (NamedAttribute namedAttr : op->getAttrs()) {
371 Attribute attr = namedAttr.getValue();
372 auto newAttr = attr.replace([&](SymbolRefAttr oldSymbolRef) {
373 auto inlined = previouslyInlinedSymbolMap.find(oldSymbolRef);
374 if (inlined != previouslyInlinedSymbolMap.end())
375 return std::make_pair(inlined->second, WalkResult::advance());
384 Operation *symbolDefOp =
nullptr;
385 Operation *calleeTable = SymbolTable::getNearestSymbolTable(lookupFrom);
386 if (calleeTable && calleeTable != callerDevice &&
387 oldSymbolRef.getNestedReferences().empty()) {
388 symbolDefOp = calleeNames.
getTable(calleeTable)
389 .
lookup(oldSymbolRef.getRootReference());
392 SymbolTable::lookupNearestSymbolFrom(lookupFrom, oldSymbolRef);
393 if (!symbolDefOp && oldSymbolRef.getNestedReferences().empty()) {
399 if (ModuleOp moduleOp = lookupFrom->getParentOfType<ModuleOp>()) {
400 symbolDefOp = SymbolTable::lookupSymbolIn(moduleOp, oldSymbolRef);
404 return std::make_pair(oldSymbolRef, WalkResult::interrupt());
409 if (llvm::isa<AIE::DeviceOp>(symbolDefOp)) {
410 previouslyInlinedSymbolMap[oldSymbolRef] = oldSymbolRef;
411 return std::make_pair(oldSymbolRef, WalkResult::advance());
415 llvm::SetVector<Value> symbolReferencedValues;
416 collectReferencedSSAValues(symbolDefOp, argMap, symbolReferencedValues);
421 if (failed(copyReferencedSSAValues(
422 rewriter, symbolReferencedValues, callerDevice, argMap,
423 clonedDefs, clonedDefOpsInsertionPoint, allSymbolNames, op))) {
424 return std::make_pair(oldSymbolRef, WalkResult::interrupt());
429 Operation *clonedSymbolDefOp =
430 getOrClone(rewriter, symbolDefOp, argMap, clonedDefs,
431 clonedDefOpsInsertionPoint, allSymbolNames);
432 SymbolRefAttr newSymbolRef =
433 FlatSymbolRefAttr::get(clonedSymbolDefOp->getAttrOfType<StringAttr>(
434 SymbolTable::getSymbolAttrName()));
435 previouslyInlinedSymbolMap[oldSymbolRef] = newSymbolRef;
436 return std::make_pair(newSymbolRef, WalkResult::advance());
441 op->setAttr(namedAttr.getName(), newAttr);
458 llvm::DenseMap<Operation *, Operation *> &
clonedDefs,
460 : RewritePattern(RunOp::getOperationName(), PatternBenefit(1), ctx),
467 PatternRewriter &rewriter)
const override {
468 llvm::DenseMap<SymbolRefAttr, SymbolRefAttr> previouslyInlinedSymbolMap;
470 RunOp runOp = llvm::dyn_cast<RunOp>(op);
475 AIE::DeviceOp calleeDevice = runOp.getCalleeDeviceOp();
476 AIE::RuntimeSequenceOp calleeRuntimeSequence =
478 if (!calleeDevice || !calleeRuntimeSequence) {
486 Region &calleeBody = calleeRuntimeSequence.getBody();
487 AIE::DeviceOp callerDevice =
488 runOp.getOperation()->getParentOfType<AIE::DeviceOp>();
490 runOp.emitError() <<
"needs to be in a DeviceOp";
496 ValueRange values = runOp.getArgs();
497 for (
unsigned i = 0, n = calleeBody.getNumArguments(); i < n; i++) {
498 BlockArgument arg = calleeBody.getArgument(i);
499 Value val = values[i];
500 argMap.map(arg, val);
509 llvm::SetVector<Value> referencedValues;
510 for (Operation &op : calleeBody.getOps()) {
511 collectReferencedSSAValues(&op, argMap, referencedValues);
513 llvm::SetVector<Value> filteredValues;
514 for (Value val : referencedValues) {
515 if (val.getParentRegion() != &calleeBody) {
516 filteredValues.insert(val);
519 referencedValues = std::move(filteredValues);
522 if (failed(copyReferencedSSAValues(rewriter, referencedValues, callerDevice,
530 AIE::RuntimeSequenceOp callerRuntimeSequence =
531 runOp.getOperation()->getParentOfType<AIE::RuntimeSequenceOp>();
532 if (!callerRuntimeSequence) {
533 runOp.emitError() <<
"needs to be (transitively) inside a "
534 "aie.runtime_sequence operation";
537 Block &callerBodyBlock = callerRuntimeSequence.getBody().front();
538 mlir::Block::iterator hoistPos = callerBodyBlock.begin();
548 rewriter.setInsertionPoint(runOp);
549 mlir::OpBuilder::InsertPoint clonedOpInsertionPoint =
550 rewriter.saveInsertionPoint();
551 for (Operation &op : calleeBody.getOps()) {
552 bool shouldHoist = llvm::isa<NpuCreateScratchpadOp>(&op);
554 rewriter.setInsertionPoint(&callerBodyBlock, hoistPos);
555 rewriter.clone(op, argMap);
559 rewriter.restoreInsertionPoint(clonedOpInsertionPoint);
560 Operation *clonedOp = rewriter.clone(op, argMap);
561 clonedOpInsertionPoint = rewriter.saveInsertionPoint();
564 WalkResult symbolWalk = clonedOp->walk([&](Operation *nestedOp) {
565 if (failed(inlineReferencedSymbolDefinitions(
566 rewriter, nestedOp, calleeRuntimeSequence.getOperation(),
567 argMap, previouslyInlinedSymbolMap, callerDevice,
clonedDefs,
569 return WalkResult::interrupt();
571 return WalkResult::advance();
573 if (symbolWalk.wasInterrupted()) {
579 rewriter.eraseOp(runOp);
592verifyRunOpsInConfigureOp(ConfigureOp configureOp, AIE::DeviceOp referencedDev,
594 if (configureOp.getBody().empty())
597 for (RunOp runOp : configureOp.getBody().front().getOps<RunOp>()) {
598 auto seqName = runOp.getRuntimeSequenceSymbol();
599 Operation *maybeSeq =
600 devNames.
lookup(runOp.getRuntimeSequenceSymbolAttr().getAttr());
602 auto err = runOp.emitError()
603 <<
"No such runtime sequence for device '"
604 << referencedDev.getSymName() <<
"': '" << seqName <<
"'";
605 err.attachNote(referencedDev.getLoc())
606 <<
"This device does not have a '" << seqName <<
"' runtime sequence";
609 auto runtimeSeq = llvm::dyn_cast<AIE::RuntimeSequenceOp>(maybeSeq);
611 return runOp.emitError()
612 <<
"'" << seqName <<
"' is not a runtime sequence";
617 Region &calleeRegion = runtimeSeq.getBody();
618 unsigned numCalleeArgs =
619 calleeRegion.empty() ? 0 : calleeRegion.getNumArguments();
620 ValueRange
args = runOp.getArgs();
621 if (numCalleeArgs !=
args.size())
622 return runOp.emitOpError() <<
"argument count mismatch: expected "
623 << numCalleeArgs <<
" but got " <<
args.size();
624 for (
unsigned i = 0; i < numCalleeArgs; i++) {
625 Type expected = calleeRegion.front().getArgument(i).getType();
626 Type actual =
args[i].getType();
627 if (expected != actual)
628 return runOp.emitOpError()
629 <<
"argument " << i <<
" type mismatch: expected " << expected
630 <<
" but got " << actual;
638 AIEMaterializeRuntimeSequencesPass> {
647 if (runtimeSeqOp.getBody().empty())
650 Block &body = runtimeSeqOp.getBody().front();
651 NpuCreateScratchpadOp firstScratchpad;
652 SmallVector<NpuCreateScratchpadOp> duplicates;
653 for (NpuCreateScratchpadOp scratchpadOp :
654 body.getOps<NpuCreateScratchpadOp>()) {
655 if (!firstScratchpad) {
656 firstScratchpad = scratchpadOp;
659 if (firstScratchpad.getSize() != scratchpadOp.getSize()) {
660 scratchpadOp.emitError(
661 "create_scratchpad size mismatch after inlining: ")
662 << scratchpadOp.getSize() <<
" != " << firstScratchpad.getSize();
665 duplicates.push_back(scratchpadOp);
667 for (NpuCreateScratchpadOp dup : duplicates)
674 ModuleOp moduleOp = getOperation();
679 for (AIE::DeviceOp deviceOp : moduleOp.getOps<AIE::DeviceOp>()) {
682 for (AIE::RuntimeSequenceOp runtimeSequenceOp :
683 deviceOp.getOps<AIE::RuntimeSequenceOp>()) {
684 if (failed(runtimeSequenceOp.verifyBeforeMaterialization())) {
685 return signalPassFailure();
693 for (ConfigureOp configureOp :
694 runtimeSequenceOp.getOps<ConfigureOp>()) {
695 AIE::DeviceOp referencedDev = configureOp.getReferencedDeviceOp();
696 if (!referencedDev) {
700 return signalPassFailure();
702 if (failed(verifyRunOpsInConfigureOp(configureOp, referencedDev,
703 referencedDevNames)))
704 return signalPassFailure();
709 for (AIE::RuntimeSequenceOp runtimeSequenceOp :
710 deviceOp.getOps<AIE::RuntimeSequenceOp>()) {
712 getAnalysisManager().nest(deviceOp).nest(runtimeSequenceOp);
716 return signalPassFailure();
719 runtimeSequenceOp.emitError(
720 "Runtime sequence call graph contains a cycle");
721 return signalPassFailure();
729 mlir::Block &deviceBodyFirstBlock = deviceOp.getBodyRegion().front();
730 auto runtimeSequenceOps = deviceOp.getOps<AIE::RuntimeSequenceOp>();
731 if (runtimeSequenceOps.begin() == runtimeSequenceOps.end()) {
735 AIE::RuntimeSequenceOp firstRuntimeSequenceOp =
736 *runtimeSequenceOps.begin();
737 mlir::OpBuilder::InsertPoint ssaDefInsertPoint(
738 &deviceBodyFirstBlock, deviceBodyFirstBlock.begin());
739 mlir::OpBuilder::InsertPoint symbolDefInsertPoint(
740 &deviceBodyFirstBlock, mlir::Block::iterator(firstRuntimeSequenceOp));
741 llvm::SetVector<SymbolRefAttr> allSymbolNames = {};
742 for (Operation &op : deviceBodyFirstBlock) {
743 if (
auto symbolName = op.getAttrOfType<StringAttr>(
744 SymbolTable::getSymbolAttrName())) {
745 allSymbolNames.insert(SymbolRefAttr::get(symbolName));
749 MLIRContext *ctx = &getContext();
750 GreedyRewriteConfig rewriter_config = GreedyRewriteConfig();
751 rewriter_config.setRegionSimplificationLevel(
752 GreedySimplifyRegionLevel::Disabled);
754 RewritePatternSet patterns_0(ctx);
755 llvm::DenseMap<Operation *, Operation *> clonedDefs;
758 ctx, ssaDefInsertPoint, symbolDefInsertPoint, allSymbolNames,
759 clonedDefs, calleeNames);
760 if (failed(applyPatternsGreedily(deviceOp, std::move(patterns_0),
762 return signalPassFailure();
766 for (AIE::RuntimeSequenceOp runtimeSeqOp :
767 deviceOp.getOps<AIE::RuntimeSequenceOp>()) {
769 return signalPassFailure();
773 RewritePatternSet patterns_1(ctx);
775 walkAndApplyPatterns(deviceOp, std::move(patterns_1));
779 RewritePatternSet canonicalize_patterns(ctx);
780 AIEX::NpuLoadPdiOp::getCanonicalizationPatterns(canonicalize_patterns,
782 SmallVector<Operation *> loadPdis;
783 deviceOp.walk([&](NpuLoadPdiOp op) { loadPdis.push_back(op); });
784 GreedyRewriteConfig loadPdiConfig = rewriter_config;
785 loadPdiConfig.setStrictness(GreedyRewriteStrictness::ExistingOps);
786 if (failed(applyOpPatternsGreedily(
787 loadPdis, std::move(canonicalize_patterns), loadPdiConfig))) {
788 return signalPassFailure();
793 for (AIE::RuntimeSequenceOp runtimeSequenceOp :
794 deviceOp.getOps<AIE::RuntimeSequenceOp>()) {
795 SmallVector<ConfigureOp> configureOps;
797 for (ConfigureOp configureOp :
798 runtimeSequenceOp.getOps<ConfigureOp>()) {
799 configureOps.push_back(configureOp);
802 IRRewriter rewriter(ctx);
803 for (ConfigureOp configureOp : configureOps) {
804 Block &configureBlock = configureOp.getBody().front();
807 SmallVector<Operation *> opsToHoist;
808 for (Operation &op : configureBlock) {
809 opsToHoist.push_back(&op);
813 rewriter.setInsertionPoint(configureOp);
814 for (Operation *op : opsToHoist) {
815 op->moveBefore(configureOp);
819 rewriter.eraseOp(configureOp);
827std::unique_ptr<OperationPass<ModuleOp>>
829 return std::make_unique<AIEMaterializeRuntimeSequencesPass>();
NamedOpTable & getTable(mlir::Operation *symbolTableOp)
mlir::Operation * lookup(mlir::StringAttr name) const
std::optional< SubviewTraceResult > traceSubviewToBlockArgument(Value value)
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIEMaterializeRuntimeSequencesPass()
mlir::Operation * lookupNamedOp(mlir::Operation *from, mlir::StringAttr name)
void runOnOperation() override
LogicalResult deduplicateCreateScratchpadOps(AIE::RuntimeSequenceOp runtimeSeqOp)
LogicalResult matchAndRewrite(Operation *op, PatternRewriter &rewriter) const override
mlir::OpBuilder::InsertPoint & ssaDefInsertPoint
InlineRuntimeCallsPattern(MLIRContext *ctx, mlir::OpBuilder::InsertPoint &ssaDefInsertPoint, mlir::OpBuilder::InsertPoint &symbolDefInsertPoint, llvm::SetVector< SymbolRefAttr > &allSymbolNames, llvm::DenseMap< Operation *, Operation * > &clonedDefs, AIE::NamedOpTableCollection &calleeNames)
llvm::DenseMap< Operation *, Operation * > & clonedDefs
mlir::OpBuilder::InsertPoint & symbolDefInsertPoint
llvm::SetVector< SymbolRefAttr > & allSymbolNames
AIE::NamedOpTableCollection & calleeNames
RuntimeCallGraphCyclicityAnalysis(Operation *op, AnalysisManager &am)
AnalysisManager & analysisManager