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"
32#define GEN_PASS_DEF_AIEOBJECTFIFOUNROLL
33#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
36#define DEBUG_TYPE "aie-objectFifo-unroll"
43 scf::ConditionOp condition = whileOp.getConditionOp();
44 return matchPattern(condition.getCondition(), m_One()) &&
45 llvm::equal(condition.getArgs(), whileOp.getBeforeArguments()) &&
46 llvm::all_of(whileOp.getBeforeBody()->without_terminator(),
47 [](Operation &op) { return isPure(&op); });
51 if (
auto whileOp = dyn_cast<scf::WhileOp>(op)) {
54 return isa<scf::ForOp>(op);
58 for (Operation *parent = op->getParentOp(); parent;
59 parent = parent->getParentOp()) {
70static std::optional<int64_t> getStaticTripCount(scf::ForOp forOp) {
71 if (forOp.getSingleLowerBound() && forOp.getSingleUpperBound() &&
72 forOp.getSingleStep()) {
73 if (std::optional<llvm::APInt> tc = forOp.getStaticTripCount()) {
74 return tc->getSExtValue();
83static void unrollForeverLoop(scf::WhileOp whileOp, int64_t factor) {
84 Block *body = whileOp.getAfterBody();
85 auto yield = cast<scf::YieldOp>(body->getTerminator());
86 SmallVector<Operation *> bodyOps;
87 for (Operation &op : body->without_terminator()) {
88 bodyOps.push_back(&op);
90 OpBuilder builder(yield);
91 SmallVector<Value> yielded(yield.getOperands());
92 SmallVector<Value> carried = yielded;
93 for (int64_t i = 1; i < factor; ++i) {
95 mapping.map(body->getArguments(), carried);
96 for (Operation *op : bodyOps) {
97 builder.clone(*op, mapping);
99 for (
auto [next, value] : llvm::zip_equal(carried, yielded)) {
100 next = mapping.lookupOrDefault(value);
103 yield->setOperands(carried);
117static bool removeRedundantBinaryAcquires(DeviceOp device) {
119 device.walk([&](Block *block) {
120 llvm::DenseSet<Value> held;
121 SmallVector<UseLockOp> toErase;
122 for (Operation &op : *block) {
123 auto useLock = dyn_cast<UseLockOp>(&op);
127 Value lock = useLock.getLock();
128 if (useLock.acquire()) {
130 if (!held.insert(lock).second) {
131 toErase.push_back(useLock);
133 }
else if (useLock.release()) {
137 for (UseLockOp op : toErase) {
140 erased |= !toErase.empty();
147static constexpr llvm::StringLiteral kPeelTriedAttrName =
"aie.peel_tried";
152static int64_t countRuntimeLockValues(Operation *op) {
154 op->walk([&](UseLockOp useLockOp) {
155 if (!getConstantIntValue(useLockOp.getValue())) {
164static int64_t countRuntimeMemrefs(Operation *op) {
166 auto isMemref = llvm::IsaPred<BaseMemRefType>;
167 op->walk([&](Block *block) {
168 count += llvm::count_if(block->getArgumentTypes(), isMemref);
170 op->walk([&](Operation *nested) {
172 (nested->getNumRegions() > 0 || isa<arith::SelectOp>(nested))) {
173 count += llvm::count_if(nested->getResultTypes(), isMemref);
182static OpPassManager buildFoldPipeline() {
183 GreedyRewriteConfig config;
184 config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Disabled);
185 OpPassManager pipeline(DeviceOp::getOperationName());
186 pipeline.addPass(mlir::createCanonicalizerPass(config));
187 pipeline.addPass(mlir::createSCCPPass());
188 pipeline.addPass(mlir::createCanonicalizerPass(config));
192struct AIEObjectFifoUnrollPass
193 : xilinx::AIE::impl::AIEObjectFifoUnrollBase<AIEObjectFifoUnrollPass> {
196 void getDependentDialects(DialectRegistry ®istry)
const override {
197 registry.insert<AIEDialect>();
198 registry.insert<scf::SCFDialect>();
218 LogicalResult peelObjectFifoLoops(DeviceOp device) {
219 OpPassManager foldPipeline = buildFoldPipeline();
221 IRRewriter rewriter(device.getContext());
225 int64_t runtimeLocksBefore;
229 SmallVector<Trial> trials;
230 for (CoreOp core : SmallVector<CoreOp>(device.getOps<CoreOp>())) {
233 core.walk([&](scf::ForOp forOp) {
234 if (forOp->hasAttr(kPeelTriedAttrName) ||
235 countRuntimeLockValues(forOp) == 0) {
236 return WalkResult::advance();
239 std::optional<int64_t> trip = getStaticTripCount(forOp);
240 if (trip && *trip <= 1) {
241 return WalkResult::advance();
244 return WalkResult::interrupt();
251 target->setAttr(kPeelTriedAttrName, rewriter.getUnitAttr());
253 Trial trial{core, core->clone(), countRuntimeLockValues(core)};
254 scf::ForOp firstIteration;
255 if (failed(scf::peelForLoopFirstIteration(rewriter, target,
257 trial.unpeeled->erase();
260 trials.push_back(trial);
262 if (trials.empty()) {
266 if (failed(runPipeline(foldPipeline, device))) {
267 for (Trial &trial : trials) {
268 trial.unpeeled->erase();
273 for (Trial &trial : trials) {
274 if (countRuntimeLockValues(trial.core) < trial.runtimeLocksBefore) {
275 trial.unpeeled->erase();
278 rewriter.setInsertionPoint(trial.core);
279 Operation *restored = rewriter.insert(trial.unpeeled);
280 rewriter.replaceOp(trial.core, restored->getResults());
285 [&](scf::ForOp forOp) { forOp->removeAttr(kPeelTriedAttrName); });
300 LogicalResult foldPromotingObjectSlots(DeviceOp device,
301 ArrayRef<memref::AllocaOp> slots,
302 OpPassManager &foldPipeline) {
304 return runPipeline(foldPipeline, device);
308 Operation *unpromoted;
310 SmallVector<Trial> trials;
311 llvm::SmallPtrSet<Operation *, 8> seen;
312 SmallVector<PromotableAllocationOpInterface> allocators;
313 for (memref::AllocaOp slot : slots) {
314 auto core = slot->getParentOfType<CoreOp>();
315 if (seen.insert(core).second) {
316 trials.push_back({core, core->clone()});
318 allocators.push_back(
319 cast<PromotableAllocationOpInterface>(slot.getOperation()));
321 DataLayout dataLayout = DataLayout::closest(device);
322 DominanceInfo dominance(device);
323 OpBuilder builder(device.getContext());
324 (void)tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance);
326 if (failed(runPipeline(foldPipeline, device))) {
327 for (Trial &trial : trials) {
328 trial.unpromoted->erase();
333 IRRewriter rewriter(device.getContext());
335 auto swap = [&](Operation *out, Operation *in) {
336 rewriter.setInsertionPoint(out);
338 rewriter.replaceAllOpUsesWith(out, in->getResults());
343 Operation *unpromoted;
344 int64_t promotedRuntimeMemrefs;
346 SmallVector<Contest> contests;
347 bool restoredAny =
false;
348 for (Trial &trial : trials) {
350 WalkResult leftover = trial.core.walk([&](memref::AllocaOp allocaOp) {
352 ? WalkResult::interrupt()
353 : WalkResult::advance();
355 int64_t runtimeMemrefs = countRuntimeMemrefs(trial.core);
356 if (!leftover.wasInterrupted() && runtimeMemrefs == 0) {
357 trial.unpromoted->erase();
360 swap(trial.core, trial.unpromoted);
361 trial.unpromoted->walk([&](memref::AllocaOp allocaOp) {
364 if (leftover.wasInterrupted()) {
367 contests.push_back({trial.core, trial.unpromoted, runtimeMemrefs});
376 if (failed(runPipeline(foldPipeline, device))) {
377 for (Contest &contest : contests) {
378 contest.promoted->erase();
382 for (Contest &contest : contests) {
383 if (contest.promotedRuntimeMemrefs <=
384 countRuntimeMemrefs(contest.unpromoted)) {
385 swap(contest.unpromoted, contest.promoted);
386 contest.unpromoted->erase();
388 contest.promoted->erase();
394 void runOnOperation()
override {
395 DeviceOp device = getOperation();
397 bool peelFirstIteration =
false;
398 if (clPeelFirstIteration ==
"auto") {
399 peelFirstIteration = !clDefaultDynamic;
400 }
else if (clPeelFirstIteration ==
"true") {
401 peelFirstIteration =
true;
402 }
else if (clPeelFirstIteration !=
"false") {
403 device.emitOpError(
"invalid peel-first-iteration value '")
404 << clPeelFirstIteration << R
"('; expected "true", "false" or "auto")";
405 return signalPassFailure();
408 SmallVector<memref::AllocaOp> objectSlots;
409 for (
auto coreOp : device.getOps<CoreOp>()) {
416 if (coreOp.getDynamicObjfifoLowering().value_or(clDefaultDynamic)) {
417 coreOp.walk([&](Operation *op) {
420 bool straightLine = coreOp.getBody().hasOneBlock();
421 coreOp.walk([&](memref::AllocaOp allocaOp) {
426 llvm::all_of(allocaOp->getUsers(), [&](Operation *user) {
427 return user->getParentOp() == coreOp.getOperation();
429 objectSlots.push_back(allocaOp);
436 coreOp.walk([&](memref::AllocaOp allocaOp) {
438 objectSlots.push_back(allocaOp);
441 SmallVector<Operation *> loops;
442 coreOp.walk([&](Operation *op) {
453 for (Operation *loop : loops) {
458 int64_t unrollFactor = hint ? hint.getInt() : 1;
459 if (unrollFactor <= 1) {
465 auto forOp = dyn_cast<scf::ForOp>(loop);
467 unrollForeverLoop(cast<scf::WhileOp>(loop), unrollFactor);
471 std::optional<int64_t> trip = getStaticTripCount(forOp);
475 if (trip && *trip <= unrollFactor) {
476 if (failed(mlir::loopUnrollFull(forOp))) {
478 <<
"failed to fully unroll objectFifo loop (trip count "
480 return signalPassFailure();
488 FailureOr<mlir::UnrolledLoopInfo> info = mlir::loopUnrollByFactor(
489 forOp,
static_cast<uint64_t
>(unrollFactor));
492 <<
"failed to unroll objectFifo loop by factor " << unrollFactor;
493 return signalPassFailure();
500 std::optional<scf::ForOp> epilogue =
501 info->epilogueLoopOp;
503 (void)mlir::loopUnrollFull(*epilogue);
508 coreOp.walk([&](Operation *op) {
521 OpPassManager foldPipeline = buildFoldPipeline();
522 if (failed(foldPromotingObjectSlots(device, objectSlots, foldPipeline))) {
523 return signalPassFailure();
526 if (peelFirstIteration && failed(peelObjectFifoLoops(device))) {
527 return signalPassFailure();
534 if (!removeRedundantBinaryAcquires(device)) {
537 OpPassManager cleanupPipeline(DeviceOp::getOperationName());
538 cleanupPipeline.addPass(mlir::createCanonicalizerPass());
539 if (failed(runPipeline(cleanupPipeline, device))) {
540 return signalPassFailure();
547std::unique_ptr<mlir::OperationPass<xilinx::AIE::DeviceOp>>
549 return std::make_unique<AIEObjectFifoUnrollPass>();
552std::unique_ptr<OperationPass<DeviceOp>>
554 const AIEObjectFifoUnrollOptions &options) {
555 return std::make_unique<AIEObjectFifoUnrollPass>(options);
constexpr llvm::StringLiteral kObjectFifoObjectSlotAttrName
Discardable attribute set by --aie-objectfifo-lower-cores on the rank-0 memref.allocas that hold a co...
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...