MLIR-AIE 1.4.6.dev
AIEObjectFifoLowerCores.cpp
Go to the documentation of this file.
1//===- AIEObjectFifoLowerCores.cpp ------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
10
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"
21
22#include "llvm/ADT/DenseSet.h"
23
24#include <numeric>
25
26using namespace mlir;
27using namespace xilinx;
28using namespace xilinx::AIE;
29
30namespace xilinx::AIE {
31#define GEN_PASS_DEF_AIEOBJECTFIFOLOWERCORES
32#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
33} // namespace xilinx::AIE
34
35namespace {
36
37// Marks the `memref.alloca`s emitted here for bookkeeping only (objects held,
38// current object index). Memrefs thread through control flow more easily than
39// SSA values; mem2reg turns them back into SSA at the end of the pass, and the
40// marker keeps that sweep off allocas this pass did not create. The slots
41// holding the objects themselves carry kObjectFifoObjectSlotAttrName instead
42// and are left in memory.
43constexpr llvm::StringLiteral kBookkeepingSlotAttrName =
44 "aie.objectfifo.bookkeeping_slot";
45
46/// Build an `scf.index_switch` on `idx` with `n` cases (0..n-1) plus a default;
47/// case k (and the default) yields `elem(k)`. The runtime selection folds to a
48/// constant once the enclosing loops are unrolled. Leaves the builder after the
49/// switch and returns its result.
50Value buildRotatingSwitch(OpBuilder &builder, Location loc, Value idx,
51 Type resultTy, int n,
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);
56 }
57 auto cases = DenseI64ArrayAttr::get(builder.getContext(), caseValues);
58 auto switchOp = scf::IndexSwitchOp::create(builder, loc, TypeRange{resultTy},
59 idx, cases, n);
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));
67 }
68 builder.setInsertionPointAfter(switchOp);
69 return switchOp.getResult(0);
70}
71
72/// Rotate the objects in `slots` left by `released`, so that the first slot
73/// again holds the next object to acquire.
74void emitRotateObjects(OpBuilder &builder, Location loc,
75 ArrayRef<memref::AllocaOp> slots, int released) {
76 if (slots.empty() || released % static_cast<int>(slots.size()) == 0) {
77 return;
78 }
79 SmallVector<Value> objects;
80 for (memref::AllocaOp slot : slots) {
81 objects.push_back(
82 memref::LoadOp::create(builder, loc, slot.getResult(), ValueRange{}));
83 }
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,
88 ValueRange{});
89 }
90}
91
92/// Advance a rotating object-index counter in its slot:
93/// counter = (counter + released) mod depth.
94void emitAdvanceObjectIndex(OpBuilder &builder, Location loc, Value slot,
95 int depth, int released) {
96 Value size =
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);
102 Value wrapping =
103 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::sge, sum, size);
104 // The counter stays in [0, depth), so releasing a single object can only ever
105 // wrap to exactly 0.
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{});
112}
113
114/// Locks a core endpoint toggles for `action`, one per segment it works: a
115/// filler waits on free objects and hands back full ones, a drainer the
116/// reverse.
117SmallVector<FlatSymbolRefAttr>
118segmentLocksFor(ObjectFifoCoreEndpointOp endpoint, LockAction action) {
119 bool acquiring = action == LockAction::AcquireGreaterEqual;
120 bool wantsProduce = acquiring != endpoint.drains();
121
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);
127 }
128 }
129 return locks;
130}
131
132/// Everything a rewrite pattern needs that is not local to its op, resolved
133/// before the driver runs so that no matchAndRewrite scans the IR.
134struct LoweringContext {
135 DeviceOp device;
136 bool usesSemaphoreLocks;
137 DenseMap<Operation *, SmallVector<Value>> poolBuffers{};
138 /// Per-(core, endpoint) runtime bookkeeping: the rotating object index (which
139 /// selects binary locks), the objects in rotation order, starting with the
140 /// next one to acquire, and, where locks count, the number of objects
141 /// currently held. The object of a single-object pool never moves, so it is
142 /// handed over directly instead.
143 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> objectIndex{};
144 DenseMap<std::pair<Operation *, Operation *>, SmallVector<memref::AllocaOp>>
145 objectSlots{};
146 DenseMap<std::pair<Operation *, Operation *>, Value> soleObject{};
147 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> heldCount{};
148 /// The slot a core-pushed endpoint releases next, where the core pushes the
149 /// descriptor of each object it releases.
150 DenseMap<std::pair<Operation *, Operation *>, memref::AllocaOp> sendSlot{};
151 /// Maximum outstanding objects across all pools sharing a tile's MM2S
152 /// queue. S2MM queues are independent and do not participate in this bound.
153 DenseMap<std::pair<Value, int>, int64_t> sendQueueObjects{};
154 llvm::DenseSet<std::pair<Value, int>> unsynchronizedSendQueues{};
155 bool sawError = false;
156
157 ObjectFifoCoreEndpointOp endpointOf(Operation *op, StringRef name) {
158 return SymbolTable::lookupNearestSymbolFrom<ObjectFifoCoreEndpointOp>(
159 device, StringAttr::get(op->getContext(), name));
160 }
161
162 LockOp lockOf(FlatSymbolRefAttr name) {
163 return lookupNamedOp<LockOp>(device, name.getAttr());
164 }
165};
166
167/// Emit the rotating UseLocks for a binary-lock (AIE1) acquire or release. One
168/// lock travels with each object, and the starting index is only known at
169/// runtime, so each of the `count` locks is selected by an scf.index_switch on
170/// the rotation counter -- folded to a concrete lock once the loops unroll.
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()) {
175 return;
176 }
177 // The value carries the direction: 1 for a filler's release or a drainer's
178 // acquire, 0 for the opposite.
179 bool full = (!drains && action == LockAction::Release) ||
180 (drains && action == LockAction::Acquire);
181 Value counter =
182 memref::LoadOp::create(builder, loc, counterSlot, ValueRange{});
183 Value index =
184 arith::IndexCastOp::create(builder, loc, builder.getIndexType(), counter);
185 Type lockTy = locks[0].getType();
186 for (int i = 0; i < count; i++) {
187 Value lock =
188 buildRotatingSwitch(builder, loc, index, lockTy, depth, [&](int c) {
189 return locks[(i + c) % depth].getResult();
190 });
191 UseLockOp::create(builder, loc, lock, action, full ? 1 : 0);
192 }
193}
194
195struct LowerRelease : OpRewritePattern<ObjectFifoReleaseOp> {
196 LoweringContext &ctx;
197 LowerRelease(MLIRContext *context, LoweringContext &ctx)
198 : OpRewritePattern(context), ctx(ctx) {}
199
200 /// Push the descriptor of each object this release frees, for a pool
201 /// whose sending channel the core drives. Nothing else starts those
202 /// descriptors, so an object the core does not push is never sent.
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())
208 return;
209 ArrayRef<int32_t> tags = push.getBdTags();
210 int channel = push.getChannel();
211 int depth = pool.getDepth();
212
213 auto key = std::make_pair(core.getOperation(), endpoint.getOperation());
214 memref::AllocaOp slot = ctx.sendSlot.lookup(key);
215 if (!slot) {
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(),
223 ValueRange{});
224 ctx.sendSlot[key] = slot;
225 }
226
227 // A pool's locks bound its outstanding tasks, but all pools on this
228 // channel contribute to the same queue. Poll unless their combined bound
229 // fits in the hardware queue.
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());
235 Value current =
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);
244 }
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(),
249 ValueRange{});
250 }
251
252 /// Push the descriptor `tags[which]` names, selected at runtime. The switch
253 /// folds to one push once the enclosing loops unroll.
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)
259 cases.push_back(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) {
264 if (waitForRoom)
265 DmaPollForQueueSpaceOp::create(rewriter, loc, DMAChannelDir::MM2S,
266 channel);
267 DmaPushQueueOp::create(rewriter, loc, DMAChannelDir::MM2S, channel,
268 /*bd_id=*/nullptr, rewriter.getI32IntegerAttr(tag),
269 repeatCount, /*out_of_order=*/false);
270 scf::YieldOp::create(rewriter, loc);
271 };
272 rewriter.createBlock(&switchOp.getDefaultRegion());
273 rewriter.setInsertionPointToStart(&switchOp.getDefaultBlock());
274 emit(tags[0]);
275 for (size_t c = 0; c < tags.size(); ++c) {
276 rewriter.createBlock(&switchOp.getCaseRegions()[c]);
277 rewriter.setInsertionPointToStart(&switchOp.getCaseBlock(c));
278 emit(tags[c]);
279 }
280 rewriter.setInsertionPointAfter(switchOp);
281 }
282
283 LogicalResult matchAndRewrite(ObjectFifoReleaseOp releaseOp,
284 PatternRewriter &rewriter) const override {
285 auto endpoint = ctx.endpointOf(releaseOp, releaseOp.getObjFifoName());
286 if (!endpoint) {
287 return failure();
288 }
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);
293
294 int count = releaseOp.relNumber() * pool.getRepeatCount().value_or(1);
295 rewriter.setInsertionPointAfter(releaseOp);
296 Location loc = releaseOp.getLoc();
297
298 if (!ctx.usesSemaphoreLocks) {
299 // The rotation counter is created by the matching acquire, so a release
300 // without one is releasing objects never acquired on this tile.
301 if (!index) {
302 releaseOp->emitOpError(
303 "objectFifo release has no corresponding acquire on this tile");
304 ctx.sawError = true;
305 return failure();
306 }
307 SmallVector<LockOp> locks = pool.getLockOps();
308 emitBinaryUseLocks(rewriter, loc, locks, pool.getDepth(),
309 endpoint.drains(), index.getResult(), count,
310 LockAction::Release);
311 } else {
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,
317 amount);
318 }
319
320 memref::AllocaOp held = ctx.heldCount.lookup(key);
321 assert(held && "a held counter is created for every released endpoint");
322 Value current =
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(),
326 ValueRange{});
327 }
328
329 emitSendPushes(rewriter, loc, core, endpoint, pool, releaseOp.getSize(),
330 releaseOp.getAssumeQueueSpace());
331
332 if (index) {
333 emitAdvanceObjectIndex(rewriter, loc, index.getResult(), pool.getDepth(),
334 releaseOp.getSize());
335 }
336 auto slots = ctx.objectSlots.find(key);
337 if (slots != ctx.objectSlots.end()) {
338 emitRotateObjects(rewriter, loc, slots->second, releaseOp.getSize());
339 }
340 return success();
341 }
342};
343
344struct LowerAcquire : OpRewritePattern<ObjectFifoAcquireOp> {
345 LoweringContext &ctx;
346 LowerAcquire(MLIRContext *context, LoweringContext &ctx)
347 : OpRewritePattern(context), ctx(ctx) {}
348
349 LogicalResult matchAndRewrite(ObjectFifoAcquireOp acquireOp,
350 PatternRewriter &rewriter) const override {
351 auto endpoint = ctx.endpointOf(acquireOp, acquireOp.getObjFifoName());
352 if (!endpoint) {
353 return failure();
354 }
355 ObjectFifoPoolOp pool = endpoint.getPoolOp();
356 CoreOp core = acquireOp->getParentOfType<CoreOp>();
357 auto key = std::make_pair(core.getOperation(), endpoint.getOperation());
358
359 int wanted = acquireOp.acqNumber();
360 if (wanted > pool.getDepth()) {
361 acquireOp->emitOpError("acquires ")
362 << wanted << " objects from a pool of " << pool.getDepth();
363 ctx.sawError = true;
364 return failure();
365 }
366 int repeat = pool.getRepeatCount().value_or(1);
367 rewriter.setInsertionPointAfter(acquireOp);
368 Location loc = acquireOp.getLoc();
369
370 if (!ctx.usesSemaphoreLocks) {
371 // Each object has its own rotating binary lock; while the loops are
372 // rolled the offset of the held locks is unknown, so acquire the whole
373 // window [counter, counter + wanted). Redundant re-acquires of
374 // still-held locks are pruned after unrolling.
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);
382 }
383
384 // An acquire names every object the core wants to hold, so only the ones
385 // not already held are taken.
386 memref::AllocaOp held = ctx.heldCount.lookup(key);
387 assert(held && "a held counter is created for every acquire");
388 Value current =
389 memref::LoadOp::create(rewriter, loc, held.getResult(), ValueRange{});
390 Value target = arith::ConstantOp::create(
391 rewriter, loc, rewriter.getI32IntegerAttr(wanted));
392 Value zero =
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);
396 if (repeat > 1) {
397 Value repeatVal = arith::ConstantOp::create(
398 rewriter, loc, rewriter.getI32IntegerAttr(repeat));
399 delta = arith::MulIOp::create(rewriter, loc, delta, repeatVal);
400 }
401 for (FlatSymbolRefAttr name :
402 segmentLocksFor(endpoint, LockAction::AcquireGreaterEqual)) {
403 UseLockOp::create(rewriter, loc, ctx.lockOf(name),
404 LockAction::AcquireGreaterEqual, delta);
405 }
406
407 Value next = arith::AddIOp::create(rewriter, loc, current, delta);
408 memref::StoreOp::create(rewriter, loc, next, held.getResult(),
409 ValueRange{});
410 return replaceWithObjects(acquireOp, key, rewriter);
411 }
412
413 /// Hand back the objects at the front of the rotation, one per result.
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);
420 }
421 return success();
422 }
423 auto slots = ctx.objectSlots.find(key);
424 if (slots == ctx.objectSlots.end() || slots->second.empty()) {
425 return success();
426 }
427 for (auto [k, result] : llvm::enumerate(acquireOp.getObjects())) {
428 Value object =
429 memref::LoadOp::create(rewriter, acquireOp.getLoc(),
430 slots->second[k].getResult(), ValueRange{});
431 rewriter.replaceAllUsesWith(result, object);
432 }
433 return success();
434 }
435};
436
437struct AIEObjectFifoLowerCoresPass
438 : public xilinx::AIE::impl::AIEObjectFifoLowerCoresBase<
439 AIEObjectFifoLowerCoresPass> {
440
441 /// A rank-0 `memref.alloca` initialized to `initial` and tagged `marker`.
442 /// mem2reg later threads a bookkeeping slot through the enclosing loops as an
443 /// iter_arg.
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(),
450 ValueRange{});
451 return slot;
452 }
453
454 /// One slot per object of the pool, in rotation order from `start`, or the
455 /// object itself if the pool has only one. The slots are not promoted here;
456 /// see kObjectFifoObjectSlotAttrName.
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());
463 // An endpoint holding one side of a join or distribute reaches only its
464 // own run of each object.
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)});
473 }
474 }
475 if (buffers.size() == 1) {
476 ctx.soleObject[key] = buffers.front();
477 return;
478 }
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()],
484 }
485 }
486
487 void emitCounters(CoreOp coreOp, LoweringContext &ctx, OpBuilder &builder) {
488 builder.setInsertionPointToStart(&coreOp.getBody().front());
489 Operation *core = coreOp.getOperation();
490 Value zero;
491
492 // A pool whose initial contents fill only its first objects is next
493 // written at the first object they leave empty.
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) {
498 return 0;
499 }
500 return static_cast<int64_t>(initValues->size()) % pool.getDepth();
501 };
502
503 auto record = [&](DenseMap<std::pair<Operation *, Operation *>,
504 memref::AllocaOp> &slots,
505 StringRef name, bool rotating) {
506 auto endpoint = ctx.endpointOf(coreOp, name);
507 if (!endpoint) {
508 return;
509 }
510 auto key = std::make_pair(core, endpoint.getOperation());
511 if (slots.count(key)) {
512 return;
513 }
514 int64_t start = rotating ? firstObject(endpoint) : 0;
515 Value initial;
516 if (start != 0) {
517 initial = arith::ConstantOp::create(builder, coreOp.getLoc(),
518 builder.getI32IntegerAttr(start));
519 } else {
520 if (!zero) {
521 zero = arith::ConstantOp::create(builder, coreOp.getLoc(),
522 builder.getI32IntegerAttr(0));
523 }
524 initial = zero;
525 }
526 slots[key] = makeSlot(builder, coreOp.getLoc(), initial);
527 };
528
529 // Objects are only tracked where the core uses one.
530 coreOp.walk([&](ObjectFifoAcquireOp a) {
531 auto endpoint = ctx.endpointOf(coreOp, a.getObjFifoName());
532 if (!endpoint) {
533 return;
534 }
535 ObjectFifoPoolOp pool = endpoint.getPoolOp();
536 auto [buffers, inserted] =
537 ctx.poolBuffers.try_emplace(pool.getOperation());
538 if (inserted) {
539 for (BufferLike buffer : pool.getBufferOps()) {
540 buffers->second.push_back(buffer.getBuffer());
541 }
542 if (static_cast<int64_t>(buffers->second.size()) != pool.getDepth()) {
543 pool.emitOpError(
544 "expects every one of its 'depth' buffers to resolve");
545 ctx.sawError = true;
546 }
547 }
548 if (ctx.sawError || a->use_empty()) {
549 return;
550 }
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),
554 key, ctx);
555 }
556 });
557 if (!ctx.usesSemaphoreLocks) {
558 // Binary locks travel with their objects, so they rotate by index.
559 coreOp.walk([&](ObjectFifoAcquireOp a) {
560 record(ctx.objectIndex, a.getObjFifoName(), /*rotating=*/true);
561 });
562 return;
563 }
564 coreOp.walk([&](ObjectFifoAcquireOp a) {
565 record(ctx.heldCount, a.getObjFifoName(), /*rotating=*/false);
566 });
567 coreOp.walk([&](ObjectFifoReleaseOp r) {
568 record(ctx.heldCount, r.getObjFifoName(), /*rotating=*/false);
569 });
570 }
571
572 /// Tell `--aie-objectFifo-unroll` how far a loop must be unrolled for the
573 /// rotating indices inside it to fold: the least common multiple of the
574 /// depths it touches. Accesses nested in a child loop drive that child, not
575 /// this one, so an ancestor is not over-unrolled.
576 void annotateUnrollHints(DeviceOp device, LoweringContext &ctx,
577 OpBuilder &builder) {
578 for (auto coreOp : device.getOps<CoreOp>()) {
579 coreOp.walk([&](Operation *loop) {
580 if (!isObjectFifoLoop(loop)) {
581 return;
582 }
583 int64_t period = 1;
584 bool touched = false;
585 auto account = [&](Operation *op, StringRef name) {
586 if (getObjectFifoLoopParent(op) != loop) {
587 return;
588 }
589 auto endpoint = ctx.endpointOf(op, name);
590 if (!endpoint) {
591 return;
592 }
593 touched = true;
594 period = std::lcm(period, (int64_t)endpoint.getPoolOp().getDepth());
595 };
596 loop->walk(
597 [&](ObjectFifoAcquireOp a) { account(a, a.getObjFifoName()); });
598 loop->walk(
599 [&](ObjectFifoReleaseOp r) { account(r, r.getObjFifoName()); });
600 if (touched) {
601 loop->setAttr(kObjectFifoUnrollHintAttrName,
602 builder.getI64IntegerAttr(period));
603 }
604 });
605 }
606 }
607
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()));
614 }
615 });
616 if (allocators.empty()) {
617 return success();
618 }
619
620 DataLayout dataLayout = DataLayout::closest(device);
621 DominanceInfo dominance(device);
622 OpBuilder builder(device.getContext());
623 (void)tryToPromoteMemorySlots(allocators, builder, dataLayout, dominance);
624
625 WalkResult leftover = device.walk([&](memref::AllocaOp allocaOp) {
626 if (!allocaOp->hasAttr(kBookkeepingSlotAttrName)) {
627 return WalkResult::advance();
628 }
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();
634 });
635 return failure(leftover.wasInterrupted());
636 }
637
638 void runOnOperation() override {
639 DeviceOp device = getOperation();
640 OpBuilder builder(device.getContext());
641
642 LoweringContext ctx{device, device.getTargetModel().hasProperty(
644
645 // Only the recorded producer submits tasks for a sending pool.
646 for (auto endpoint : device.getOps<ObjectFifoCoreEndpointOp>()) {
647 ObjectFifoPoolOp pool = endpoint.getPoolOp();
648 if (endpoint.drains())
649 continue;
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();
657 });
658 if (pool.getDisableSynchronization() || missingLocks)
659 ctx.unsynchronizedSendQueues.insert(queue);
660 }
661 }
662
663 annotateUnrollHints(device, ctx, builder);
664 for (auto coreOp : device.getOps<CoreOp>()) {
665 emitCounters(coreOp, ctx, builder);
666 }
667 if (ctx.sawError) {
668 return signalPassFailure();
669 }
670
671 RewritePatternSet patterns(device.getContext());
672 patterns.insert<LowerAcquire, LowerRelease>(device.getContext(), ctx);
673 walkAndApplyPatterns(device, std::move(patterns));
674 if (ctx.sawError) {
675 return signalPassFailure();
676 }
677
678 SmallVector<Operation *> toErase;
679 device.walk([&](Operation *op) {
680 if (isa<ObjectFifoAcquireOp, ObjectFifoReleaseOp>(op)) {
681 toErase.push_back(op);
682 }
683 });
684 for (Operation *op : toErase) {
685 op->dropAllUses();
686 op->erase();
687 }
688
689 for (auto endpoint : llvm::make_early_inc_range(
690 device.getOps<ObjectFifoCoreEndpointOp>())) {
691 endpoint.erase();
692 }
693
694 if (failed(promoteBookkeepingSlots(device))) {
695 return signalPassFailure();
696 }
697 }
698};
699
700} // namespace
701
702std::unique_ptr<OperationPass<DeviceOp>>
704 return std::make_unique<AIEObjectFifoLowerCoresPass>();
705}
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...
Definition AIEPasses.h:36
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....
Definition AIEPasses.h:27
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...