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/IR/Dominance.h"
15#include "mlir/IR/PatternMatch.h"
16#include "mlir/Interfaces/DataLayoutInterfaces.h"
17#include "mlir/Interfaces/MemorySlotInterfaces.h"
18#include "mlir/Pass/Pass.h"
19#include "mlir/Transforms/Mem2Reg.h"
20#include "mlir/Transforms/WalkPatternRewriteDriver.h"
22#include "llvm/ADT/DenseSet.h"
31#define GEN_PASS_DEF_AIEOBJECTFIFOLOWERCORES
32#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
43constexpr llvm::StringLiteral kBookkeepingSlotAttrName =
44 "aie.objectfifo.bookkeeping_slot";
50Value buildRotatingSwitch(OpBuilder &builder, Location loc, Value idx,
52 llvm::function_ref<Value(
int)> elem) {
53 SmallVector<int64_t, 4> caseValues;
54 for (
int c = 0; c < n; ++c) {
55 caseValues.push_back(c);
57 auto cases = DenseI64ArrayAttr::get(builder.getContext(), caseValues);
58 auto switchOp = scf::IndexSwitchOp::create(builder, loc, TypeRange{resultTy},
60 builder.createBlock(&switchOp.getDefaultRegion());
61 builder.setInsertionPointToStart(&switchOp.getDefaultBlock());
62 scf::YieldOp::create(builder, loc, elem(0));
63 for (
int c = 0; c < n; ++c) {
64 builder.createBlock(&switchOp.getCaseRegions()[c]);
65 builder.setInsertionPointToStart(&switchOp.getCaseBlock(c));
66 scf::YieldOp::create(builder, loc, elem(c));
68 builder.setInsertionPointAfter(switchOp);
69 return switchOp.getResult(0);
74void emitRotateObjects(OpBuilder &builder, Location loc,
75 ArrayRef<memref::AllocaOp> slots,
int released) {
76 if (slots.empty() || released %
static_cast<int>(slots.size()) == 0) {
79 SmallVector<Value> objects;
80 for (memref::AllocaOp slot : slots) {
82 memref::LoadOp::create(builder, loc, slot.getResult(), ValueRange{}));
84 for (
size_t k = 0; k < slots.size(); ++k) {
85 memref::AllocaOp slot = slots[k];
86 memref::StoreOp::create(builder, loc,
87 objects[(k + released) % objects.size()], slot,
94void emitAdvanceObjectIndex(OpBuilder &builder, Location loc, Value slot,
95 int depth,
int released) {
97 arith::ConstantOp::create(builder, loc, builder.getI32IntegerAttr(depth));
98 Value old = memref::LoadOp::create(builder, loc, slot, ValueRange{});
99 Value step = arith::ConstantOp::create(builder, loc,
100 builder.getI32IntegerAttr(released));
101 Value sum = arith::AddIOp::create(builder, loc, old, step);
103 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sge, sum, size);
106 Value wrapped = released == 1
107 ? Value(arith::ConstantOp::create(
108 builder, loc, builder.getI32IntegerAttr(0)))
109 : Value(
arith::SubIOp::create(builder, loc, sum, size));
110 Value next = arith::SelectOp::create(builder, loc, wrapping, wrapped, sum);
111 memref::StoreOp::create(builder, loc, next, slot, ValueRange{});
117SmallVector<FlatSymbolRefAttr>
118segmentLocksFor(ObjectFifoCoreEndpointOp endpoint, LockAction action) {
119 bool acquiring = action == LockAction::AcquireGreaterEqual;
120 bool wantsProduce = acquiring != endpoint.drains();
122 SmallVector<FlatSymbolRefAttr> locks;
123 for (ObjectFifoSegmentOp segment : endpoint.getSelectedSegments()) {
124 if (
auto lock = wantsProduce ? segment.getProduceLockAttr()
125 : segment.getConsumeLockAttr()) {
126 locks.push_back(lock);
134struct LoweringContext {
136 bool usesSemaphoreLocks;
137 DenseMap<Operation *, SmallVector<Value>> poolBuffers{};
143 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> objectIndex{};
144 DenseMap<std::pair<Operation *, Operation *>, SmallVector<memref::AllocaOp>>
146 DenseMap<std::pair<Operation *, Operation *>, Value> soleObject{};
147 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> heldCount{};
150 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> sendSlot{};
153 DenseMap<std::pair<Value, int>, int64_t> sendQueueObjects{};
154 llvm::DenseSet<std::pair<Value, int>> unsynchronizedSendQueues{};
155 bool sawError =
false;
157 ObjectFifoCoreEndpointOp endpointOf(Operation *op, StringRef name) {
158 return SymbolTable::lookupNearestSymbolFrom<ObjectFifoCoreEndpointOp>(
159 device, StringAttr::get(op->getContext(), name));
162 LockOp lockOf(FlatSymbolRefAttr name) {
171void emitBinaryUseLocks(OpBuilder &builder, Location loc,
172 MutableArrayRef<LockOp> locks,
int depth,
bool drains,
173 Value counterSlot,
int count, LockAction action) {
174 if (count == 0 || locks.empty()) {
179 bool full = (!drains && action == LockAction::Release) ||
180 (drains && action == LockAction::Acquire);
182 memref::LoadOp::create(builder, loc, counterSlot, ValueRange{});
184 arith::IndexCastOp::create(builder, loc, builder.getIndexType(), counter);
185 Type lockTy = locks[0].getType();
186 for (
int i = 0; i < count; i++) {
188 buildRotatingSwitch(builder, loc, index, lockTy, depth, [&](
int c) {
189 return locks[(i + c) % depth].getResult();
191 UseLockOp::create(builder, loc, lock, action, full ? 1 : 0);
196 LoweringContext &ctx;
197 LowerRelease(MLIRContext *context, LoweringContext &ctx)
198 : OpRewritePattern(context), ctx(ctx) {}
203 void emitSendPushes(PatternRewriter &rewriter, Location loc, CoreOp core,
204 ObjectFifoCoreEndpointOp endpoint, ObjectFifoPoolOp pool,
205 int size,
bool assumeQueueSpace)
const {
206 ObjectFifoCorePushAttr push = endpoint.getCorePushAttr();
207 if (!push || endpoint.drains())
209 ArrayRef<int32_t> tags = push.getBdTags();
210 int channel = push.getChannel();
211 int depth = pool.getDepth();
213 auto key = std::make_pair(core.getOperation(), endpoint.getOperation());
214 memref::AllocaOp slot = ctx.sendSlot.lookup(key);
216 OpBuilder::InsertionGuard guard(rewriter);
217 rewriter.setInsertionPointToStart(&core.getBody().front());
218 slot = memref::AllocaOp::create(
219 rewriter, loc, MemRefType::get({}, rewriter.getIndexType()));
220 slot->setAttr(kBookkeepingSlotAttrName, rewriter.getUnitAttr());
221 Value zero = arith::ConstantIndexOp::create(rewriter, loc, 0);
222 memref::StoreOp::create(rewriter, loc, zero, slot.getResult(),
224 ctx.sendSlot[key] = slot;
230 auto queue = std::make_pair(core.getTile(), channel);
231 bool waitForRoom = !assumeQueueSpace &&
232 (ctx.unsynchronizedSendQueues.contains(queue) ||
233 ctx.sendQueueObjects.lookup(queue) >
234 ctx.device.getTargetModel().getDmaTaskQueueDepth());
236 memref::LoadOp::create(rewriter, loc, slot.getResult(), ValueRange{});
237 Value depthValue = arith::ConstantIndexOp::create(rewriter, loc, depth);
238 for (
int step = 0; step < size; ++step) {
239 Value offset = arith::ConstantIndexOp::create(rewriter, loc, step);
240 Value raw = arith::AddIOp::create(rewriter, loc, current, offset);
241 Value which = arith::RemUIOp::create(rewriter, loc, raw, depthValue);
242 emitPushSwitch(rewriter, loc, which, tags, channel,
243 pool.getRepeatCount().value_or(1) - 1, waitForRoom);
245 Value taken = arith::ConstantIndexOp::create(rewriter, loc, size);
246 Value raw = arith::AddIOp::create(rewriter, loc, current, taken);
247 Value next = arith::RemUIOp::create(rewriter, loc, raw, depthValue);
248 memref::StoreOp::create(rewriter, loc, next, slot.getResult(),
254 void emitPushSwitch(PatternRewriter &rewriter, Location loc, Value which,
255 ArrayRef<int32_t> tags,
int channel,
int repeatCount,
256 bool waitForRoom)
const {
257 SmallVector<int64_t> cases;
258 for (
size_t c = 0; c < tags.size(); ++c)
260 auto switchOp = scf::IndexSwitchOp::create(
261 rewriter, loc, TypeRange{}, which,
262 DenseI64ArrayAttr::get(rewriter.getContext(), cases), tags.size());
263 auto emit = [&](int32_t tag) {
265 DmaPollForQueueSpaceOp::create(rewriter, loc, DMAChannelDir::MM2S,
267 DmaPushQueueOp::create(rewriter, loc, DMAChannelDir::MM2S, channel,
268 nullptr, rewriter.getI32IntegerAttr(tag),
270 scf::YieldOp::create(rewriter, loc);
272 rewriter.createBlock(&switchOp.getDefaultRegion());
273 rewriter.setInsertionPointToStart(&switchOp.getDefaultBlock());
275 for (
size_t c = 0; c < tags.size(); ++c) {
276 rewriter.createBlock(&switchOp.getCaseRegions()[c]);
277 rewriter.setInsertionPointToStart(&switchOp.getCaseBlock(c));
280 rewriter.setInsertionPointAfter(switchOp);
283 LogicalResult matchAndRewrite(ObjectFifoReleaseOp releaseOp,
284 PatternRewriter &rewriter)
const override {
285 auto endpoint = ctx.endpointOf(releaseOp, releaseOp.getObjFifoName());
289 ObjectFifoPoolOp pool = endpoint.getPoolOp();
290 CoreOp core = releaseOp->getParentOfType<CoreOp>();
291 auto key = std::make_pair(core.getOperation(), endpoint.getOperation());
292 memref::AllocaOp index = ctx.objectIndex.lookup(key);
294 int count = releaseOp.relNumber() * pool.getRepeatCount().value_or(1);
295 rewriter.setInsertionPointAfter(releaseOp);
296 Location loc = releaseOp.getLoc();
298 if (!ctx.usesSemaphoreLocks) {
302 releaseOp->emitOpError(
303 "objectFifo release has no corresponding acquire on this tile");
307 SmallVector<LockOp> locks = pool.getLockOps();
308 emitBinaryUseLocks(rewriter, loc, locks, pool.getDepth(),
309 endpoint.drains(), index.getResult(), count,
310 LockAction::Release);
312 Value amount = arith::ConstantOp::create(
313 rewriter, loc, rewriter.getI32IntegerAttr(count));
314 for (FlatSymbolRefAttr name :
315 segmentLocksFor(endpoint, LockAction::Release)) {
316 UseLockOp::create(rewriter, loc, ctx.lockOf(name), LockAction::Release,
320 memref::AllocaOp held = ctx.heldCount.lookup(key);
321 assert(held &&
"a held counter is created for every released endpoint");
323 memref::LoadOp::create(rewriter, loc, held.getResult(), ValueRange{});
324 Value next = arith::SubIOp::create(rewriter, loc, current, amount);
325 memref::StoreOp::create(rewriter, loc, next, held.getResult(),
329 emitSendPushes(rewriter, loc, core, endpoint, pool, releaseOp.getSize(),
330 releaseOp.getAssumeQueueSpace());
333 emitAdvanceObjectIndex(rewriter, loc, index.getResult(), pool.getDepth(),
334 releaseOp.getSize());
336 auto slots = ctx.objectSlots.find(key);
337 if (slots != ctx.objectSlots.end()) {
338 emitRotateObjects(rewriter, loc, slots->second, releaseOp.getSize());
345 LoweringContext &ctx;
346 LowerAcquire(MLIRContext *context, LoweringContext &ctx)
347 : OpRewritePattern(context), ctx(ctx) {}
349 LogicalResult matchAndRewrite(ObjectFifoAcquireOp acquireOp,
350 PatternRewriter &rewriter)
const override {
351 auto endpoint = ctx.endpointOf(acquireOp, acquireOp.getObjFifoName());
355 ObjectFifoPoolOp pool = endpoint.getPoolOp();
356 CoreOp core = acquireOp->getParentOfType<CoreOp>();
357 auto key = std::make_pair(core.getOperation(), endpoint.getOperation());
359 int wanted = acquireOp.acqNumber();
360 if (wanted > pool.getDepth()) {
361 acquireOp->emitOpError(
"acquires ")
362 << wanted <<
" objects from a pool of " << pool.getDepth();
366 int repeat = pool.getRepeatCount().value_or(1);
367 rewriter.setInsertionPointAfter(acquireOp);
368 Location loc = acquireOp.getLoc();
370 if (!ctx.usesSemaphoreLocks) {
375 memref::AllocaOp index = ctx.objectIndex.lookup(key);
376 assert(index &&
"a rotation counter is created for every acquire");
377 SmallVector<LockOp> locks = pool.getLockOps();
378 emitBinaryUseLocks(rewriter, loc, locks, pool.getDepth(),
379 endpoint.drains(), index.getResult(), wanted * repeat,
380 LockAction::Acquire);
381 return replaceWithObjects(acquireOp, key, rewriter);
386 memref::AllocaOp held = ctx.heldCount.lookup(key);
387 assert(held &&
"a held counter is created for every acquire");
389 memref::LoadOp::create(rewriter, loc, held.getResult(), ValueRange{});
390 Value target = arith::ConstantOp::create(
391 rewriter, loc, rewriter.getI32IntegerAttr(wanted));
393 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
394 Value shortfall = arith::SubIOp::create(rewriter, loc, target, current);
395 Value delta = arith::MaxSIOp::create(rewriter, loc, shortfall, zero);
397 Value repeatVal = arith::ConstantOp::create(
398 rewriter, loc, rewriter.getI32IntegerAttr(repeat));
399 delta = arith::MulIOp::create(rewriter, loc, delta, repeatVal);
401 for (FlatSymbolRefAttr name :
402 segmentLocksFor(endpoint, LockAction::AcquireGreaterEqual)) {
403 UseLockOp::create(rewriter, loc, ctx.lockOf(name),
404 LockAction::AcquireGreaterEqual, delta);
407 Value next = arith::AddIOp::create(rewriter, loc, current, delta);
408 memref::StoreOp::create(rewriter, loc, next, held.getResult(),
410 return replaceWithObjects(acquireOp, key, rewriter);
414 LogicalResult replaceWithObjects(ObjectFifoAcquireOp acquireOp,
415 std::pair<Operation *, Operation *> key,
416 PatternRewriter &rewriter)
const {
417 if (Value
object = ctx.soleObject.lookup(key)) {
418 for (Value result : acquireOp.getObjects()) {
419 rewriter.replaceAllUsesWith(result,
object);
423 auto slots = ctx.objectSlots.find(key);
424 if (slots == ctx.objectSlots.end() || slots->second.empty()) {
427 for (
auto [k, result] : llvm::enumerate(acquireOp.getObjects())) {
429 memref::LoadOp::create(rewriter, acquireOp.getLoc(),
430 slots->second[k].getResult(), ValueRange{});
431 rewriter.replaceAllUsesWith(result,
object);
437struct AIEObjectFifoLowerCoresPass
438 :
public xilinx::AIE::impl::AIEObjectFifoLowerCoresBase<
439 AIEObjectFifoLowerCoresPass> {
444 memref::AllocaOp makeSlot(OpBuilder &builder, Location loc, Value initial,
445 StringRef marker = kBookkeepingSlotAttrName) {
446 auto slotTy = MemRefType::get(SmallVector<int64_t>{}, initial.getType());
447 auto slot = memref::AllocaOp::create(builder, loc, slotTy);
448 slot->setAttr(marker, builder.getUnitAttr());
449 memref::StoreOp::create(builder, loc, initial, slot.getResult(),
457 void trackObjects(OpBuilder &builder, Location loc,
458 ObjectFifoCoreEndpointOp endpoint, int64_t start,
459 std::pair<Operation *, Operation *> key,
460 LoweringContext &ctx) {
461 ObjectFifoPoolOp pool = endpoint.getPoolOp();
462 SmallVector<Value> buffers = ctx.poolBuffers.lookup(pool.getOperation());
465 MemRefType accessType = endpoint.getAccessType();
466 if (accessType != pool.getElemType()) {
467 auto [offset, size] = endpoint.getExtent();
468 for (Value &buffer : buffers) {
469 buffer = memref::SubViewOp::create(
470 builder, loc, accessType, buffer,
471 ArrayRef<OpFoldResult>{builder.getIndexAttr(offset)},
472 {builder.getIndexAttr(size)}, {builder.getIndexAttr(1)});
475 if (buffers.size() == 1) {
476 ctx.soleObject[key] = buffers.front();
479 SmallVector<memref::AllocaOp> &slots = ctx.objectSlots[key];
480 for (
size_t k = 0; k < buffers.size(); ++k) {
481 slots.push_back(makeSlot(builder, loc,
482 buffers[(start + k) % buffers.size()],
487 void emitCounters(CoreOp coreOp, LoweringContext &ctx, OpBuilder &builder) {
488 builder.setInsertionPointToStart(&coreOp.getBody().front());
489 Operation *core = coreOp.getOperation();
494 auto firstObject = [](ObjectFifoCoreEndpointOp endpoint) -> int64_t {
495 ObjectFifoPoolOp pool = endpoint.getPoolOp();
496 auto initValues = pool.getInitValues();
497 if (endpoint.drains() || !initValues || pool.getDepth() <= 0) {
500 return static_cast<int64_t
>(initValues->size()) % pool.getDepth();
503 auto record = [&](DenseMap<std::pair<Operation *, Operation *>,
504 memref::AllocaOp> &slots,
505 StringRef name,
bool rotating) {
506 auto endpoint = ctx.endpointOf(coreOp, name);
510 auto key = std::make_pair(core, endpoint.getOperation());
511 if (slots.count(key)) {
514 int64_t start = rotating ? firstObject(endpoint) : 0;
517 initial = arith::ConstantOp::create(builder, coreOp.getLoc(),
518 builder.getI32IntegerAttr(start));
521 zero = arith::ConstantOp::create(builder, coreOp.getLoc(),
522 builder.getI32IntegerAttr(0));
526 slots[key] = makeSlot(builder, coreOp.getLoc(), initial);
530 coreOp.walk([&](ObjectFifoAcquireOp a) {
531 auto endpoint = ctx.endpointOf(coreOp, a.getObjFifoName());
535 ObjectFifoPoolOp pool = endpoint.getPoolOp();
536 auto [buffers, inserted] =
537 ctx.poolBuffers.try_emplace(pool.getOperation());
539 for (BufferLike buffer : pool.getBufferOps()) {
540 buffers->second.push_back(buffer.getBuffer());
542 if (
static_cast<int64_t
>(buffers->second.size()) != pool.getDepth()) {
544 "expects every one of its 'depth' buffers to resolve");
548 if (ctx.sawError || a->use_empty()) {
551 auto key = std::make_pair(core, endpoint.getOperation());
552 if (!ctx.objectSlots.count(key) && !ctx.soleObject.count(key)) {
553 trackObjects(builder, coreOp.getLoc(), endpoint, firstObject(endpoint),
557 if (!ctx.usesSemaphoreLocks) {
559 coreOp.walk([&](ObjectFifoAcquireOp a) {
560 record(ctx.objectIndex, a.getObjFifoName(),
true);
564 coreOp.walk([&](ObjectFifoAcquireOp a) {
565 record(ctx.heldCount, a.getObjFifoName(),
false);
567 coreOp.walk([&](ObjectFifoReleaseOp r) {
568 record(ctx.heldCount, r.getObjFifoName(),
false);
576 void annotateUnrollHints(DeviceOp device, LoweringContext &ctx,
577 OpBuilder &builder) {
578 for (
auto coreOp : device.getOps<CoreOp>()) {
579 coreOp.walk([&](Operation *loop) {
584 bool touched =
false;
585 auto account = [&](Operation *op, StringRef name) {
589 auto endpoint = ctx.endpointOf(op, name);
594 period = std::lcm(period, (int64_t)endpoint.getPoolOp().getDepth());
597 [&](ObjectFifoAcquireOp a) { account(a, a.getObjFifoName()); });
599 [&](ObjectFifoReleaseOp r) { account(r, r.getObjFifoName()); });
602 builder.getI64IntegerAttr(period));
608 LogicalResult promoteBookkeepingSlots(DeviceOp device) {
609 SmallVector<PromotableAllocationOpInterface> allocators;
610 device.walk([&](memref::AllocaOp allocaOp) {
611 if (allocaOp->hasAttr(kBookkeepingSlotAttrName)) {
612 allocators.push_back(
613 cast<PromotableAllocationOpInterface>(allocaOp.getOperation()));
616 if (allocators.empty()) {
620 DataLayout dataLayout = DataLayout::closest(device);
621 DominanceInfo dominance(device);
622 OpBuilder builder(device.getContext());
623 (void)tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance);
625 WalkResult leftover = device.walk([&](memref::AllocaOp allocaOp) {
626 if (!allocaOp->hasAttr(kBookkeepingSlotAttrName)) {
627 return WalkResult::advance();
629 allocaOp.emitOpError()
630 <<
"objectFifo bookkeeping slot could not be promoted to SSA "
631 "(mem2reg left it in place); the objectFifo lowering requires "
632 "all bookkeeping counters to become loop-carried SSA values";
633 return WalkResult::interrupt();
635 return failure(leftover.wasInterrupted());
638 void runOnOperation()
override {
639 DeviceOp device = getOperation();
640 OpBuilder builder(device.getContext());
642 LoweringContext ctx{device, device.getTargetModel().hasProperty(
646 for (
auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
647 ObjectFifoPoolOp pool = endpoint.getPoolOp();
648 if (endpoint.drains())
650 if (
auto push = endpoint.getCorePushAttr()) {
651 auto queue = std::make_pair(endpoint.getTile(),
int(push.getChannel()));
652 ctx.sendQueueObjects[queue] += pool.getDepth();
653 bool missingLocks = llvm::any_of(
654 endpoint.getSelectedSegments(), [](ObjectFifoSegmentOp segment) {
655 return !segment.getProduceLockAttr() ||
656 !segment.getConsumeLockAttr();
658 if (pool.getDisableSynchronization() || missingLocks)
659 ctx.unsynchronizedSendQueues.insert(queue);
663 annotateUnrollHints(device, ctx, builder);
664 for (
auto coreOp : device.getOps<CoreOp>()) {
665 emitCounters(coreOp, ctx, builder);
668 return signalPassFailure();
671 RewritePatternSet patterns(device.getContext());
672 patterns.insert<LowerAcquire, LowerRelease>(device.getContext(), ctx);
673 walkAndApplyPatterns(device, std::move(patterns));
675 return signalPassFailure();
678 SmallVector<Operation *> toErase;
679 device.walk([&](Operation *op) {
680 if (isa<ObjectFifoAcquireOp, ObjectFifoReleaseOp>(op)) {
681 toErase.push_back(op);
684 for (Operation *op : toErase) {
689 for (
auto endpoint : llvm::make_early_inc_range(
690 device.getOps<ObjectFifoCoreEndpointOp>())) {
694 if (failed(promoteBookkeepingSlots(device))) {
695 return signalPassFailure();
702std::unique_ptr<OperationPass<DeviceOp>>
704 return std::make_unique<AIEObjectFifoLowerCoresPass>();
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEObjectFifoLowerCoresPass()
constexpr llvm::StringLiteral kObjectFifoObjectSlotAttrName
Discardable attribute set by --aie-objectfifo-lower-cores on the rank-0 memref.allocas that hold a co...
mlir::Operation * lookupNamedOp(mlir::Operation *from, mlir::StringAttr name)
constexpr llvm::StringLiteral kObjectFifoUnrollHintAttrName
Discardable attribute set by --aie-objectfifo-lower-cores on scf.for and forever scf....
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...