11#include "mlir/Dialect/Arith/IR/Arith.h"
12#include "mlir/Dialect/MemRef/IR/MemRef.h"
13#include "mlir/Dialect/SCF/IR/SCF.h"
14#include "mlir/Dialect/SCF/Transforms/Transforms.h"
15#include "mlir/Dialect/SCF/Utils/Utils.h"
16#include "mlir/Dialect/Utils/StaticValueUtils.h"
17#include "mlir/IR/Dominance.h"
18#include "mlir/IR/IRMapping.h"
19#include "mlir/IR/Matchers.h"
20#include "mlir/IR/PatternMatch.h"
21#include "mlir/Interfaces/DataLayoutInterfaces.h"
22#include "mlir/Interfaces/MemorySlotInterfaces.h"
23#include "mlir/Interfaces/SideEffectInterfaces.h"
24#include "mlir/Pass/Pass.h"
25#include "mlir/Pass/PassManager.h"
26#include "mlir/Transforms/Mem2Reg.h"
27#include "mlir/Transforms/Passes.h"
29#include "llvm/ADT/DenseSet.h"
34#define GEN_PASS_DEF_AIEOBJECTFIFOUNROLL
35#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
38#define DEBUG_TYPE "aie-objectFifo-unroll"
45 scf::ConditionOp condition = whileOp.getConditionOp();
46 return matchPattern(condition.getCondition(), m_One()) &&
47 llvm::equal(condition.getArgs(), whileOp.getBeforeArguments()) &&
48 llvm::all_of(whileOp.getBeforeBody()->without_terminator(),
49 [](Operation &op) { return isPure(&op); });
53 return forOp.getInductionVar().use_empty() &&
54 isConstantIntValue(forOp.getLowerBound(), 0) &&
55 isConstantIntValue(forOp.getStep(), 1) &&
56 isConstantIntValue(forOp.getUpperBound(),
57 std::numeric_limits<int64_t>::max());
61 OpBuilder builder(forOp);
62 auto whileOp = scf::WhileOp::create(
63 builder, forOp.getLoc(), forOp.getResultTypes(), forOp.getInitArgs(),
64 [](OpBuilder &b, Location loc, ValueRange
args) {
65 Value always = arith::ConstantIntOp::create(b, loc, 1, 1);
66 scf::ConditionOp::create(b, loc, always, args);
68 [](OpBuilder &, Location, ValueRange) {});
69 Block *forBody = forOp.getBody();
70 Block *after = whileOp.getAfterBody();
71 for (
auto [from, to] :
72 llvm::zip_equal(forOp.getRegionIterArgs(), after->getArguments())) {
73 from.replaceAllUsesWith(to);
75 after->getOperations().splice(after->end(), forBody->getOperations());
76 forOp.replaceAllUsesWith(whileOp.getResults());
82 if (
auto whileOp = dyn_cast<scf::WhileOp>(op)) {
85 return isa<scf::ForOp>(op);
89 for (Operation *parent = op->getParentOp(); parent;
90 parent = parent->getParentOp()) {
101static std::optional<int64_t> getStaticTripCount(scf::ForOp forOp) {
102 if (forOp.getSingleLowerBound() && forOp.getSingleUpperBound() &&
103 forOp.getSingleStep()) {
104 if (std::optional<llvm::APInt> tc = forOp.getStaticTripCount()) {
105 return tc->getSExtValue();
114static void unrollForeverLoop(scf::WhileOp whileOp, int64_t factor) {
115 Block *body = whileOp.getAfterBody();
116 auto yield = cast<scf::YieldOp>(body->getTerminator());
117 SmallVector<Operation *> bodyOps;
118 for (Operation &op : body->without_terminator()) {
119 bodyOps.push_back(&op);
121 OpBuilder builder(yield);
122 SmallVector<Value> yielded(yield.getOperands());
123 SmallVector<Value> carried = yielded;
124 for (int64_t i = 1; i < factor; ++i) {
126 mapping.map(body->getArguments(), carried);
127 for (Operation *op : bodyOps) {
128 builder.clone(*op, mapping);
130 for (
auto [next, value] : llvm::zip_equal(carried, yielded)) {
131 next = mapping.lookupOrDefault(value);
134 yield->setOperands(carried);
148static bool removeRedundantBinaryAcquires(DeviceOp device) {
150 device.walk([&](Block *block) {
151 llvm::DenseSet<Value> held;
152 SmallVector<UseLockOp> toErase;
153 for (Operation &op : *block) {
154 auto useLock = dyn_cast<UseLockOp>(&op);
158 Value lock = useLock.getLock();
159 if (useLock.acquire()) {
161 if (!held.insert(lock).second) {
162 toErase.push_back(useLock);
164 }
else if (useLock.release()) {
168 for (UseLockOp op : toErase) {
171 erased |= !toErase.empty();
178static constexpr llvm::StringLiteral kPeelTriedAttrName =
"aie.peel_tried";
183static int64_t countRuntimeLockValues(Operation *op) {
185 op->walk([&](UseLockOp useLockOp) {
186 if (!getConstantIntValue(useLockOp.getValue())) {
195static int64_t countRuntimeMemrefs(Operation *op) {
197 auto isMemref = llvm::IsaPred<BaseMemRefType>;
198 op->walk([&](Block *block) {
199 count += llvm::count_if(block->getArgumentTypes(), isMemref);
201 op->walk([&](Operation *nested) {
203 (nested->getNumRegions() > 0 || isa<arith::SelectOp>(nested))) {
204 count += llvm::count_if(nested->getResultTypes(), isMemref);
213static OpPassManager buildFoldPipeline() {
214 GreedyRewriteConfig config;
215 config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Disabled);
216 OpPassManager pipeline(DeviceOp::getOperationName());
217 pipeline.addPass(mlir::createCanonicalizerPass(config));
218 pipeline.addPass(mlir::createSCCPPass());
219 pipeline.addPass(mlir::createCanonicalizerPass(config));
223struct AIEObjectFifoUnrollPass
224 : xilinx::AIE::impl::AIEObjectFifoUnrollBase<AIEObjectFifoUnrollPass> {
227 void getDependentDialects(DialectRegistry ®istry)
const override {
228 registry.insert<AIEDialect>();
229 registry.insert<scf::SCFDialect>();
249 LogicalResult peelObjectFifoLoops(DeviceOp device) {
250 OpPassManager foldPipeline = buildFoldPipeline();
252 IRRewriter rewriter(device.getContext());
256 int64_t runtimeLocksBefore;
260 SmallVector<Trial> trials;
261 for (CoreOp core : SmallVector<CoreOp>(device.getOps<CoreOp>())) {
264 core.walk([&](scf::ForOp forOp) {
265 if (forOp->hasAttr(kPeelTriedAttrName) ||
266 countRuntimeLockValues(forOp) == 0) {
267 return WalkResult::advance();
270 std::optional<int64_t> trip = getStaticTripCount(forOp);
271 if (trip && *trip <= 1) {
272 return WalkResult::advance();
275 return WalkResult::interrupt();
282 target->setAttr(kPeelTriedAttrName, rewriter.getUnitAttr());
284 Trial trial{core, core->clone(), countRuntimeLockValues(core)};
285 scf::ForOp firstIteration;
286 if (failed(scf::peelForLoopFirstIteration(rewriter, target,
288 trial.unpeeled->erase();
291 trials.push_back(trial);
293 if (trials.empty()) {
297 if (failed(runPipeline(foldPipeline, device))) {
298 for (Trial &trial : trials) {
299 trial.unpeeled->erase();
304 for (Trial &trial : trials) {
305 if (countRuntimeLockValues(trial.core) < trial.runtimeLocksBefore) {
306 trial.unpeeled->erase();
309 rewriter.setInsertionPoint(trial.core);
310 Operation *restored = rewriter.insert(trial.unpeeled);
311 rewriter.replaceOp(trial.core, restored->getResults());
316 [&](scf::ForOp forOp) { forOp->removeAttr(kPeelTriedAttrName); });
331 LogicalResult foldPromotingObjectSlots(DeviceOp device,
332 ArrayRef<memref::AllocaOp> slots,
333 OpPassManager &foldPipeline) {
335 return runPipeline(foldPipeline, device);
339 Operation *unpromoted;
341 SmallVector<Trial> trials;
342 llvm::SmallPtrSet<Operation *, 8> seen;
343 SmallVector<PromotableAllocationOpInterface> allocators;
344 for (memref::AllocaOp slot : slots) {
345 auto core = slot->getParentOfType<CoreOp>();
346 if (seen.insert(core).second) {
347 trials.push_back({core, core->clone()});
349 allocators.push_back(
350 cast<PromotableAllocationOpInterface>(slot.getOperation()));
352 DataLayout dataLayout = DataLayout::closest(device);
353 DominanceInfo dominance(device);
354 OpBuilder builder(device.getContext());
355 (void)tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance);
357 if (failed(runPipeline(foldPipeline, device))) {
358 for (Trial &trial : trials) {
359 trial.unpromoted->erase();
364 IRRewriter rewriter(device.getContext());
366 auto swap = [&](Operation *out, Operation *in) {
367 rewriter.setInsertionPoint(out);
369 rewriter.replaceAllOpUsesWith(out, in->getResults());
374 Operation *unpromoted;
375 int64_t promotedRuntimeMemrefs;
377 SmallVector<Contest> contests;
378 bool restoredAny =
false;
379 for (Trial &trial : trials) {
381 WalkResult leftover = trial.core.walk([&](memref::AllocaOp allocaOp) {
383 ? WalkResult::interrupt()
384 : WalkResult::advance();
386 int64_t runtimeMemrefs = countRuntimeMemrefs(trial.core);
387 if (!leftover.wasInterrupted() && runtimeMemrefs == 0) {
388 trial.unpromoted->erase();
391 swap(trial.core, trial.unpromoted);
392 trial.unpromoted->walk([&](memref::AllocaOp allocaOp) {
395 if (leftover.wasInterrupted()) {
398 contests.push_back({trial.core, trial.unpromoted, runtimeMemrefs});
407 if (failed(runPipeline(foldPipeline, device))) {
408 for (Contest &contest : contests) {
409 contest.promoted->erase();
413 for (Contest &contest : contests) {
414 if (contest.promotedRuntimeMemrefs <=
415 countRuntimeMemrefs(contest.unpromoted)) {
416 swap(contest.unpromoted, contest.promoted);
417 contest.unpromoted->erase();
419 contest.promoted->erase();
425 void runOnOperation()
override {
426 DeviceOp device = getOperation();
428 bool peelFirstIteration =
false;
429 if (clPeelFirstIteration ==
"auto") {
430 peelFirstIteration = !clDefaultDynamic;
431 }
else if (clPeelFirstIteration ==
"true") {
432 peelFirstIteration =
true;
433 }
else if (clPeelFirstIteration !=
"false") {
434 device.emitOpError(
"invalid peel-first-iteration value '")
435 << clPeelFirstIteration << R
"('; expected "true", "false" or "auto")";
436 return signalPassFailure();
439 SmallVector<memref::AllocaOp> objectSlots;
440 for (
auto coreOp : device.getOps<CoreOp>()) {
447 if (coreOp.getDynamicObjfifoLowering().value_or(clDefaultDynamic)) {
448 coreOp.walk([&](Operation *op) {
451 bool straightLine = coreOp.getBody().hasOneBlock();
452 coreOp.walk([&](memref::AllocaOp allocaOp) {
457 llvm::all_of(allocaOp->getUsers(), [&](Operation *user) {
458 return user->getParentOp() == coreOp.getOperation();
460 objectSlots.push_back(allocaOp);
467 coreOp.walk([&](memref::AllocaOp allocaOp) {
469 objectSlots.push_back(allocaOp);
472 SmallVector<Operation *> loops;
473 coreOp.walk([&](Operation *op) {
484 for (Operation *loop : loops) {
489 int64_t unrollFactor = hint ? hint.getInt() : 1;
490 if (unrollFactor <= 1) {
496 auto forOp = dyn_cast<scf::ForOp>(loop);
498 unrollForeverLoop(cast<scf::WhileOp>(loop), unrollFactor);
506 std::optional<int64_t> trip = getStaticTripCount(forOp);
510 if (trip && *trip <= unrollFactor) {
511 if (failed(mlir::loopUnrollFull(forOp))) {
513 <<
"failed to fully unroll objectFifo loop (trip count "
515 return signalPassFailure();
523 FailureOr<mlir::UnrolledLoopInfo> info = mlir::loopUnrollByFactor(
524 forOp,
static_cast<uint64_t
>(unrollFactor));
527 <<
"failed to unroll objectFifo loop by factor " << unrollFactor;
528 return signalPassFailure();
535 std::optional<scf::ForOp> epilogue =
536 info->epilogueLoopOp;
538 (void)mlir::loopUnrollFull(*epilogue);
543 coreOp.walk([&](Operation *op) {
556 OpPassManager foldPipeline = buildFoldPipeline();
557 if (failed(foldPromotingObjectSlots(device, objectSlots, foldPipeline))) {
558 return signalPassFailure();
561 if (peelFirstIteration && failed(peelObjectFifoLoops(device))) {
562 return signalPassFailure();
569 if (!removeRedundantBinaryAcquires(device)) {
572 OpPassManager cleanupPipeline(DeviceOp::getOperationName());
573 cleanupPipeline.addPass(mlir::createCanonicalizerPass());
574 if (failed(runPipeline(cleanupPipeline, device))) {
575 return signalPassFailure();
582std::unique_ptr<mlir::OperationPass<xilinx::AIE::DeviceOp>>
584 return std::make_unique<AIEObjectFifoUnrollPass>();
587std::unique_ptr<OperationPass<DeviceOp>>
589 const AIEObjectFifoUnrollOptions &options) {
590 return std::make_unique<AIEObjectFifoUnrollPass>(options);
bool isForeverForLoop(mlir::scf::ForOp forOp)
Whether forOp counts from 0 to INT64_MAX in steps of 1 and never reads its induction variable.
constexpr llvm::StringLiteral kObjectFifoObjectSlotAttrName
Discardable attribute set by --aie-objectfifo-lower-cores on the rank-0 memref.allocas that hold a co...
mlir::scf::WhileOp replaceWithForeverLoop(mlir::scf::ForOp forOp)
Replace forOp, which isForeverForLoop accepts, with an scf.while that isForeverLoop accepts and that ...
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoUnrollPass()
constexpr llvm::StringLiteral kObjectFifoUnrollHintAttrName
Discardable attribute set by --aie-objectfifo-lower-cores on scf.for and forever scf....
bool isForeverLoop(mlir::scf::WhileOp whileOp)
Whether whileOp repeats forever: its condition is a constant true, and its condition region does noth...
mlir::Operation * getObjectFifoLoopParent(mlir::Operation *op)
The innermost loop around op that isObjectFifoLoop accepts, or null.
bool isObjectFifoLoop(mlir::Operation *op)
Whether op is a loop that objectFifo lowering unrolls by the rotation period of the objectFifos acces...