15#include "mlir/IR/Attributes.h"
17#include "llvm/ADT/BitVector.h"
24#define GEN_PASS_DEF_AIEASSIGNBUFFERADDRESSES
25#define GEN_PASS_DEF_AIEPREPAREBUFFERS
26#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
29#define DEBUG_TYPE "aie-assign-buffers"
36static std::optional<int64_t> getMeasuredStackSize(TileOp tile) {
37 CoreOp core = tile.getCoreOp();
41 if (
auto measured = core.getMeasuredStackSize()) {
42 return static_cast<int64_t
>(*measured);
48static void sortBuffersByAddress(SmallVectorImpl<BufferOp> &buffers) {
49 llvm::sort(buffers, [](BufferOp a, BufferOp b) {
50 assert(a.getAddress().has_value() &&
"buffer must have address assigned");
51 assert(b.getAddress().has_value() &&
"buffer must have address assigned");
52 return a.getAddress().value() < b.getAddress().value();
59static std::string bufferLabel(BufferOp buffer) {
60 if (buffer.getCoreData()) {
61 return "this core's data sections (data_size)";
63 if (buffer.getBankReserved()) {
64 return "this core's static data pinned to bank " +
65 std::to_string(buffer.getMemBank().value_or(0));
67 return (
"buffer \"" + buffer.name().getValue() +
"\"").str();
71static StringRef mapLabel(BufferOp buffer) {
72 if (buffer.getCoreData()) {
73 return StringRef(
"(core data sections)");
75 if (buffer.getBankReserved()) {
76 return StringRef(
"(bank-pinned static data)");
78 return buffer.name().getValue();
84static void materializeCoreDataBuffers(DeviceOp device) {
85 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
86 for (
auto core : llvm::to_vector(device.getOps<CoreOp>())) {
91 int64_t size = core.getDataSize().value_or(
92 static_cast<int64_t
>(core.getMeasuredDataSize().value_or(0)));
96 auto tile = cast<TileOp>(core.getTile().getDefiningOp());
98 device.walk([&](BufferOp buffer) {
99 if (buffer.getCoreData() && buffer.getTileOp() == tile) {
106 builder.setInsertionPoint(core);
107 auto type = MemRefType::get({size}, builder.getI8Type());
109 BufferOp::create(builder, core.getLoc(), type, tile.getResult(),
113 builder.getUnitAttr(),
116 buffer->setAttr(SymbolTable::getSymbolAttrName(),
117 builder.getStringAttr(
"core_data_" +
118 std::to_string(tile.getCol()) +
"_" +
119 std::to_string(tile.getRow())));
127static void materializeBankReservations(DeviceOp device) {
128 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
129 for (
auto core : llvm::to_vector(device.getOps<CoreOp>())) {
130 auto sizes = core.getMeasuredBankSizes();
134 auto tile = cast<TileOp>(core.getTile().getDefiningOp());
135 for (
auto [bank, size] : llvm::enumerate(*sizes)) {
139 std::string name =
"bank_reserved_" + std::to_string(tile.getCol()) +
140 "_" + std::to_string(tile.getRow()) +
"_" +
141 std::to_string(bank);
142 bool present =
false;
143 device.walk([&](BufferOp buffer) {
144 if (buffer.name() == name) {
151 builder.setInsertionPoint(core);
152 auto type = MemRefType::get({size}, builder.getI8Type());
154 BufferOp::create(builder, core.getLoc(), type, tile.getResult(),
157 builder.getI32IntegerAttr(bank),
159 builder.getUnitAttr(),
161 buffer->setAttr(SymbolTable::getSymbolAttrName(),
162 builder.getStringAttr(name));
174static void materializePrebakedRanges(DeviceOp device) {
175 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
176 for (
auto core : llvm::to_vector(device.getOps<CoreOp>())) {
177 auto ranges = core.getMeasuredDataRanges();
181 auto tile = cast<TileOp>(core.getTile().getDefiningOp());
182 for (
size_t i = 0; i + 1 < ranges->size(); i += 2) {
183 int32_t address = (*ranges)[i], size = (*ranges)[i + 1];
187 std::string name =
"prebaked_" + std::to_string(tile.getCol()) +
"_" +
188 std::to_string(tile.getRow()) +
"_" +
189 std::to_string(address);
190 bool present =
false;
191 device.walk([&](BufferOp buffer) {
192 if (buffer.name() == name) {
199 builder.setInsertionPoint(core);
200 auto type = MemRefType::get({size}, builder.getI8Type());
202 BufferOp::create(builder, core.getLoc(), type, tile.getResult(),
204 builder.getI32IntegerAttr(address),
208 builder.getUnitAttr(),
209 builder.getBoolAttr(
false));
210 buffer->setAttr(SymbolTable::getSymbolAttrName(),
211 builder.getStringAttr(name));
220static SmallVector<int, 2> banksFromMemorySpace(Type type) {
221 auto memref = dyn_cast<MemRefType>(type);
225 auto space = dyn_cast_or_null<IntegerAttr>(
memref.getMemorySpace());
229 int64_t value = space.getInt();
230 if (value >= 5 && value <= 8) {
231 return {
static_cast<int>(value - 5)};
259static LogicalResult applySignatureBankConstraints(DeviceOp device) {
260 auto result = success();
261 device.walk([&](BufferOp buffer) {
262 auto banks = banksFromMemorySpace(buffer.getType());
266 if (
auto existing = buffer.getMemBank();
267 existing && !llvm::is_contained(banks, *existing)) {
268 auto diag = buffer.emitOpError(
"has address space for bank ");
269 diag << banks.front();
270 if (banks.size() > 1)
271 diag <<
" or " << banks.back();
272 diag <<
" but is pinned to bank " << *existing
273 <<
". These constraints disagree";
277 if (banks.size() == 1)
278 buffer.setMemBankAttr(IntegerAttr::get(
279 IntegerType::get(device.getContext(), 32), banks.front()));
284static bool isBufferPreAllocated(BufferOp buffer) {
285 auto addr = buffer.getAddress();
286 auto memBank = buffer.getMemBank();
287 return (addr != std::nullopt || memBank != std::nullopt);
290static int64_t getMeasuredDataAlignBytes(BufferOp buffer) {
291 if (!buffer.getCoreData() && !buffer.getBankReserved())
293 CoreOp core = buffer.getTileOp().getCoreOp();
296 if (buffer.getCoreData())
297 return core.getMeasuredDataAlignment().value_or(1);
298 auto bank = buffer.getMemBank();
299 auto alignments = core.getMeasuredBankAlignments();
300 if (bank && alignments && *bank >= 0 &&
301 static_cast<size_t>(*bank) < alignments->size())
302 return std::max<int64_t>((*alignments)[*bank], 1);
309static bool checkAndPrintBufferOverlap(ArrayRef<BufferOp> sortedBuffers,
310 uint32_t tileAlignBitWidth,
311 uint32_t maxVecAlignBits) {
312 BufferOp prev =
nullptr;
313 for (
auto cur : sortedBuffers) {
314 auto curAddrOpt = cur.getAddress();
315 assert(curAddrOpt.has_value() &&
"buffer must have address assigned");
316 int64_t curAddr = *curAddrOpt;
320 uint32_t reqAlignBits =
321 isBufferPreAllocated(cur)
324 cur.getAllocationSize());
325 uint32_t alignByteWidth = reqAlignBits / 8;
326 if (cur.getAligned() && alignByteWidth != 0 &&
327 curAddr % alignByteWidth != 0) {
328 cur.emitOpError(
"") << bufferLabel(cur) <<
" at address 0x"
329 << llvm::utohexstr(curAddr)
330 <<
" is not aligned to the required " << reqAlignBits
337 if (cur.getAllocationSize() == 0) {
342 auto prevAddrOpt = prev.getAddress();
343 assert(prevAddrOpt.has_value() &&
"buffer must have address assigned");
344 int64_t prevAddr = *prevAddrOpt;
345 int64_t prevEnd = prevAddr + prev.getAllocationSize();
346 if (curAddr < prevEnd) {
348 << bufferLabel(cur) <<
" at address 0x" << llvm::utohexstr(curAddr)
349 <<
" overlaps with " << bufferLabel(prev) <<
" at address 0x"
350 << llvm::utohexstr(prevAddr)
351 <<
" (size: " << prev.getAllocationSize() <<
" bytes)";
360static bool checkAndPrintOverlapStackframe(
MemoryRun stackRun,
361 ArrayRef<BufferOp> buffers) {
362 for (
auto buf : buffers) {
364 if (buf.getAllocationSize() == 0) {
367 auto bufAddrOpt = buf.getAddress();
368 assert(bufAddrOpt.has_value() &&
"buffer must have address assigned");
369 int64_t bufAddr = *bufAddrOpt;
370 int64_t bufEnd = bufAddr + buf.getAllocationSize();
371 if (bufAddr < stackRun.
end() && stackRun.
start < bufEnd) {
372 buf.emitOpError(
"") << bufferLabel(buf) <<
" at address 0x"
373 << llvm::utohexstr(bufAddr) <<
" overlaps the stack ("
374 << stackRun.
size <<
" bytes at 0x"
375 << llvm::utohexstr(stackRun.
start) <<
")";
384static void printMemoryMapEntry(Diagnostic ¬e, StringRef name,
385 int64_t address, int64_t size,
int indent,
386 StringRef suffix =
"") {
387 for (
int i = 0; i < indent; ++i) {
390 int64_t end = size == 0 ? address : address + size - 1;
391 note << name <<
" \t"
392 <<
": 0x" << llvm::utohexstr(address) <<
"-0x" << llvm::utohexstr(end)
393 <<
" \t(" << size <<
" bytes)" << suffix <<
"\n";
400static void fillBankLimits(
int numBanks, int64_t bankSize,
401 std::vector<MemoryRun> &bankLimits) {
402 for (
int i = 0; i < numBanks; i++) {
403 bankLimits.push_back({bankSize * i, bankSize});
410class MemoryOccupancy {
412 explicit MemoryOccupancy(int64_t size) : occupied(size, false) {}
414 int64_t size()
const {
return occupied.size(); }
417 bool isRangeFree(int64_t start, int64_t end)
const {
418 if (start < 0 || end > size() || start > end) {
421 return start == end || occupied.find_first_in(start, end) == -1;
424 void markOccupied(int64_t start, int64_t end) {
425 assert(start >= 0 && end <= size() && start <= end &&
426 "range must lie inside the tile");
428 occupied.set(start, end);
435 void markFree(int64_t start, int64_t end) {
436 assert(start >= 0 && end <= size() && start <= end &&
437 "range must lie inside the tile");
439 occupied.reset(start, end);
450 std::optional<int64_t> findLeastFragmentingGap(int64_t lo, int64_t hi,
452 int64_t alignBytes)
const {
453 assert(alignBytes > 0 &&
"alignment must be positive");
454 std::optional<int64_t> best;
455 int64_t bestRun = -1;
456 auto consider = [&](int64_t start) {
457 MemoryOccupancy trial = *
this;
458 trial.markOccupied(start, start + size);
459 int64_t run = trial.largestGap(0, this->size());
461 if (run > bestRun || (run == bestRun && best && start < *best)) {
466 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
467 int64_t low = llvm::alignTo(gapStart, alignBytes);
468 if (low + size > gapEnd) {
474 std::max<int64_t>(llvm::alignDown(gapEnd - size, alignBytes), low));
482 std::optional<int64_t> findGap(int64_t lo, int64_t hi, int64_t size,
483 int64_t alignBytes)
const {
484 assert(alignBytes > 0 &&
"alignment must be positive");
485 std::optional<int64_t> best;
486 int64_t bestSlack = 0;
487 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
488 int64_t start = llvm::alignTo(gapStart, alignBytes);
489 if (start + size <= gapEnd) {
492 int64_t slack = (gapEnd - gapStart) - size;
493 if (!best || slack < bestSlack) {
503 int64_t freeBytes(int64_t lo, int64_t hi)
const {
505 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
506 total += gapEnd - gapStart;
513 int64_t slackAt(int64_t addr, int64_t size)
const {
515 forEachGap(0, this->size(), [&](int64_t gapStart, int64_t gapEnd) {
516 if (addr >= gapStart && addr + size <= gapEnd) {
517 slack = (gapEnd - gapStart) - size;
524 MemoryRun gapAt(int64_t addr)
const {
526 forEachGap(0, this->size(), [&](int64_t gapStart, int64_t gapEnd) {
527 if (addr >= gapStart && addr < gapEnd) {
528 found = {gapStart, gapEnd - gapStart};
537 std::pair<int64_t, int64_t> topTwoGaps(int64_t lo, int64_t hi)
const {
538 int64_t first = 0, second = 0;
539 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
540 int64_t run = gapEnd - gapStart;
544 }
else if (run > second) {
548 return {first, second};
553 SmallVector<MemoryRun> gapsIn(int64_t lo, int64_t hi)
const {
554 SmallVector<MemoryRun> runs;
555 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
556 runs.push_back({gapStart, gapEnd - gapStart});
563 int64_t largestGap(int64_t lo, int64_t hi)
const {
565 forEachGap(lo, hi, [&](int64_t gapStart, int64_t gapEnd) {
566 best = std::max(best, gapEnd - gapStart);
573 template <
typename Fn>
574 void forEachGap(int64_t lo, int64_t hi, Fn fn)
const {
575 lo = std::max<int64_t>(lo, 0);
576 hi = std::min(hi, size());
577 for (int64_t cursor = lo; cursor < hi;) {
578 int gapStart = occupied.find_first_unset_in(cursor, hi);
579 if (gapStart == -1) {
582 int nextTaken = occupied.find_first_in(gapStart, hi);
584 int64_t gapEnd = nextTaken == -1 ? hi : nextTaken;
585 fn(gapStart, gapEnd);
590 llvm::BitVector occupied;
595static int64_t getBufferAlignBytes(BufferOp buffer, uint32_t tileAlignBitWidth,
596 uint32_t maxVecAlignBits) {
598 int64_t measuredAlign = getMeasuredDataAlignBytes(buffer);
599 if (!buffer.getAligned()) {
600 return measuredAlign;
604 buffer.getAllocationSize()) /
610static int getBankContaining(int64_t addr,
int numBanks,
611 ArrayRef<MemoryRun> bankLimits) {
612 for (
int i = 0; i < numBanks; i++) {
613 if (bankLimits[i].contains(addr)) {
622static void placeBuffer(BufferOp buffer, int64_t startAddr,
int bank,
623 MemoryOccupancy &occupancy) {
624 buffer.setAddress(startAddr);
625 buffer.setMemBank(bank);
626 occupancy.markOccupied(startAddr, startAddr + buffer.getAllocationSize());
649static FailureOr<bool>
651 MemoryOccupancy &occupancy) {
652 auto addrOpt = buffer.getAddress();
657 int64_t addr = *addrOpt;
658 int64_t measuredAlign = getMeasuredDataAlignBytes(buffer);
659 if (addr % measuredAlign != 0)
660 return buffer.emitOpError(
"data reservation address must be aligned to ")
662 <<
" bytes to satisfy its measured section alignment";
667 return buffer->emitOpError(
"address attribute value must be aligned to ")
669 <<
" bits when the aligned attribute is set";
676 if (buffer.getAllocationSize() == 0 &&
680 return buffer->emitOpError(
681 "address attribute does not fall within any bank range");
685 int64_t endAddr = addr + buffer.getAllocationSize();
686 if (endAddr > occupancy.size()) {
687 return buffer->emitOpError(
"address attribute would place the buffer past "
688 "the end of the tile's memory");
691 auto banks = banksFromMemorySpace(buffer.getType());
693 buffer.getAllocationSize() == 0
696 for (
int touched = bank; !banks.empty() && touched <= lastBank; ++touched) {
697 if (!llvm::is_contained(banks, touched))
698 return buffer.emitOpError(
"address attribute places the buffer in bank ")
699 << touched <<
", which is excluded by its memory space";
701 if (
auto requested = buffer.getMemBank(); requested && *requested != bank)
702 return buffer.emitOpError(
"address attribute lies in bank ")
703 << bank <<
", but mem_bank requests bank " << *requested;
707 if (!occupancy.isRangeFree(addr, endAddr)) {
708 return buffer->emitOpError(
"would override allocated address");
711 placeBuffer(buffer, addr, bank, occupancy);
723static LogicalResult recordRequiredBank(BufferOp buffer,
int numBanks,
725 auto memBankOpt = buffer.getMemBank();
726 if (memBankOpt && (*memBankOpt < 0 || *memBankOpt >= numBanks)) {
727 return buffer->emitOpError(
"mem_bank attribute value is out of range");
729 auto banks = banksFromMemorySpace(buffer.getType());
731 banks = {*memBankOpt};
732 if (!banks.empty()) {
733 if (banks.back() >= numBanks)
734 return buffer.emitOpError(
735 "memory space requests a bank that does not exist");
736 requiredBanks[buffer] = std::move(banks);
741static void printMemMap(TileOp tile, ArrayRef<BufferOp> allocatedBuffers,
742 ArrayRef<BufferOp> preAllocatedBuffers,
744 InFlightDiagnostic error = tile.emitWarning(
745 "Not all requested buffers fit in the available memory.\n");
746 auto ¬e = error.attachNote()
747 <<
"Current configuration of buffers in bank(s) : ";
748 note <<
"MemoryMap:\n";
749 auto printbuffer = [&](StringRef name, int64_t address, int64_t size,
750 StringRef suffix =
"") {
751 printMemoryMapEntry(note, name, address, size, 2, suffix);
754 note <<
"\t(no stack allocated)\n";
756 if (
auto measured = getMeasuredStackSize(tile)) {
757 note <<
"\t(aiecc measured this core's stack requirement as " << *measured
760 for (
int i = 0; i < ctx.
numBanks; i++) {
761 note <<
"\t" <<
"bank : " << i <<
"\t" <<
"0x"
762 << llvm::utohexstr(ctx.
bankLimits[i].start) <<
"-0x"
763 << llvm::utohexstr(ctx.
bankLimits[i].end() - 1) <<
"\n";
773 SmallPtrSet<Operation *, 8> listed;
774 auto printPlaced = [&](ArrayRef<BufferOp> buffers) {
775 for (
auto buffer : buffers) {
776 auto addrOpt = buffer.getAddress();
777 auto memBankOpt = buffer.getMemBank();
778 if (!addrOpt || !memBankOpt || *memBankOpt != i ||
779 !listed.insert(buffer).second) {
782 int64_t size = buffer.getAllocationSize();
786 if (*addrOpt + size > ctx.
bankLimits[i].end()) {
787 suffix = (
" (straddles into bank " + llvm::Twine(i + 1) +
")").str();
789 printbuffer(mapLabel(buffer), *addrOpt, size, suffix);
792 printPlaced(preAllocatedBuffers);
793 printPlaced(allocatedBuffers);
800constexpr int64_t kFallbackRank = std::numeric_limits<int64_t>::max();
815 int64_t banksTouched;
816 int64_t negLargestRunLeft;
820 std::tuple<int64_t, int64_t, int64_t, int64_t, int64_t> rank()
const {
821 return {banksTouched, negLargestRunLeft, rrDistance, slack, addr};
831static SmallVector<Placement>
834 const MemoryOccupancy &tileOccupancy, int64_t contiguityCap) {
835 assert(startBankIndex < ctx.
numBanks &&
836 "Unexpected input value for startBankIndex");
839 std::optional<MemoryOccupancy> guarded;
840 if (ctx.
initGuardBytes > 0 && buffer.getInitialValue().has_value()) {
841 guarded = tileOccupancy;
842 guarded->markOccupied(0, std::min(ctx.
initGuardBytes, guarded->size()));
844 const MemoryOccupancy &occupancy = guarded ? *guarded : tileOccupancy;
845 int64_t size = buffer.getAllocationSize();
848 SmallVector<Placement> out;
853 auto required = requiredBanks.find(buffer);
854 if (required != requiredBanks.end()) {
855 auto &banks = required->second;
856 SmallVector<MemoryRun> ranges;
857 for (
int bank : banks)
861 if (banks.size() == 2 && banks.back() == banks.front() + 1)
862 ranges.push_back({ctx.
bankLimits[banks.front()].start,
866 for (
MemoryRun gap : occupancy.gapsIn(limits.start, limits.end())) {
867 int64_t low = llvm::alignTo(gap.start, alignBytes);
868 if (low + size > gap.end()) {
872 {low, std::max<int64_t>(
873 llvm::alignDown(gap.end() - size, alignBytes), low)}) {
874 MemoryOccupancy trial = occupancy;
875 trial.markOccupied(addr, addr + size);
877 if (!llvm::is_contained(banks, bank))
879 int lastBank = size == 0
881 : getBankContaining(addr + size - 1, ctx.
numBanks,
883 out.push_back({addr, bank, lastBank - bank + 1,
884 -trial.largestGap(0, trial.size()), 0,
890 if (out.empty() && size == 0) {
891 int bank = banks.front();
892 out.push_back({ctx.
bankLimits[bank].start, bank, 1, 0, 0, 0});
895 int64_t bankedEnd = ctx.
bankLimits.back().end();
896 int64_t bankSize = bankedEnd / ctx.
numBanks;
901 std::pair<int64_t, int64_t> topGaps =
903 int64_t largestRun = topGaps.first, secondRun = topGaps.second;
904 auto consider = [&](std::optional<int64_t> addr) {
913 size == 0 ? 1 : (*addr + size - 1) / bankSize - *addr / bankSize + 1;
918 int64_t elsewhere = gap.
size == largestRun ? secondRun : largestRun;
919 int64_t leftPiece = *addr - gap.
start;
920 int64_t rightPiece = gap.
end() - (*addr + size);
922 std::min(std::max({elsewhere, leftPiece, rightPiece}), contiguityCap);
923 out.push_back({*addr, bank, touched, ctx.
hasCore ? -runLeft : 0,
925 occupancy.slackAt(*addr, size)});
928 for (
int i = 0; i < ctx.
numBanks; i++) {
929 consider(occupancy.findGap(ctx.
bankLimits[i].start,
934 consider(occupancy.findGap(0, bankedEnd, size, alignBytes));
937 if (bankSize % alignBytes == 0) {
938 consider(occupancy.findGap(0, bankedEnd, size, bankSize));
941 if (out.empty() && size == 0) {
942 out.push_back({0, 0, 1, 0, 0, 0});
946 llvm::stable_sort(out, [](
const Placement &a,
const Placement &b) {
947 return a.rank() < b.rank();
956 if (size > 0 && requiredBanks.find(buffer) == requiredBanks.end()) {
958 int64_t low = llvm::alignTo(gap.
start, alignBytes);
959 if (low + size > gap.
end()) {
963 {low, std::max<int64_t>(
964 llvm::alignDown(gap.
end() - size, alignBytes), low)}) {
969 out.push_back({addr, bank, kFallbackRank, 0, 0, 0});
979 llvm::stable_sort(out, [](
const Placement &a,
const Placement &b) {
980 return a.addr < b.addr;
983 llvm::unique(out, [](
const Placement &a,
984 const Placement &b) {
return a.addr == b.addr; }),
986 llvm::stable_sort(out, [](
const Placement &a,
const Placement &b) {
987 return a.rank() < b.rank();
994struct PlacementStats {
995 int64_t backtracks = 0;
996 int64_t tilesSearched = 0;
997 int64_t budgetExhausted = 0;
998 int64_t tilesCompacted = 0;
1010struct PlacementSearch {
1011 ArrayRef<BufferOp> order;
1012 const BankAwareContext &ctx;
1014 MemoryOccupancy &occupancy;
1015 SmallVectorImpl<BufferOp> &placed;
1016 PlacementStats &stats;
1026 bool exhausted =
false;
1034 size_t deepestIndex = 0;
1035 BufferOp deepest =
nullptr;
1036 SmallVector<std::pair<BufferOp, Placement>> deepestLayout = {};
1043 std::set<SmallVector<int64_t>> failedStates = {};
1045 bool run(
size_t index,
int startBankIndex, int64_t remaining) {
1046 if (index == order.size()) {
1049 SmallVector<int64_t> state = {
static_cast<int64_t
>(index)};
1050 for (MemoryRun gap : occupancy.gapsIn(0, occupancy.size())) {
1053 if (failedStates.count(state)) {
1056 BufferOp buffer = order[index];
1057 int64_t size = buffer.getAllocationSize();
1058 int64_t rest = remaining - size;
1060 SmallVector<Placement> candidates = rankedPlacements(
1061 buffer, ctx, startBankIndex, requiredBanks, occupancy, rest);
1062 if (candidates.empty() && (!deepest || index > deepestIndex)) {
1063 recordDeepest(index, buffer);
1065 for (
const Placement &candidate : candidates) {
1069 if (!deepest || index > deepestIndex) {
1070 recordDeepest(index, buffer);
1076 placeBuffer(buffer, candidate.addr, candidate.bank, occupancy);
1077 placed.push_back(buffer);
1079 int nextBank = requiredBanks.count(buffer)
1081 : (candidate.bank + 1) % ctx.numBanks;
1082 if (run(index + 1, nextBank, rest)) {
1087 occupancy.markFree(candidate.addr, candidate.addr + size);
1088 buffer->removeAttr(
"address");
1089 auto required = requiredBanks.find(buffer);
1090 if (required == requiredBanks.end() || required->second.size() != 1) {
1091 buffer->removeAttr(
"mem_bank");
1093 buffer.setMemBank(required->second.front());
1097 failedStates.insert(std::move(state));
1104 void restoreDeepest() {
1105 for (
auto &[buffer, at] : deepestLayout) {
1106 placeBuffer(buffer, at.addr, at.bank, occupancy);
1107 placed.push_back(buffer);
1112 void recordDeepest(
size_t index, BufferOp buffer) {
1113 deepestIndex = index;
1115 deepestLayout.clear();
1116 for (BufferOp done : placed) {
1117 auto address = done.getAddress();
1118 auto bank = done.getMemBank();
1119 assert(address && bank &&
"placed buffer must have an address and bank");
1120 deepestLayout.push_back({done, Placement{*address, *bank, 0, 0, 0, 0}});
1138struct CompactionSearch {
1139 ArrayRef<BufferOp> order;
1140 const BankAwareContext &ctx;
1141 SmallVector<MemoryRun> gaps;
1144 SmallVector<int64_t> sizes, aligns;
1145 SmallVector<SmallVector<MemoryRun, 2>> ranges;
1146 SmallVector<unsigned> classes;
1147 SmallVector<int64_t> addrs;
1149 SmallVector<int64_t> freeAbove, runAbove;
1150 llvm::DenseSet<std::pair<int64_t, llvm::BitVector>> failed;
1152 bool exhausted =
false;
1154 CompactionSearch(ArrayRef<BufferOp> order,
const BankAwareContext &ctx,
1156 const MemoryOccupancy &occupancy, int64_t budget)
1157 : order(order), ctx(ctx),
1158 gaps(occupancy.gapsIn(0, ctx.maxDataMemorySize)),
1159 addrs(order.size(), -1), budget(budget) {
1160 for (
size_t i = 0; i < order.size(); ++i) {
1161 BufferOp buffer = order[i];
1162 sizes.push_back(buffer.getAllocationSize());
1163 aligns.push_back(getBufferAlignBytes(buffer, ctx.tileAlignBitWidth,
1164 ctx.maxVecAlignBits));
1165 SmallVector<MemoryRun, 2> allowed;
1166 auto required = requiredBanks.find(buffer);
1167 if (required == requiredBanks.end()) {
1168 allowed.push_back({0, ctx.maxDataMemorySize});
1170 auto &banks = required->second;
1171 for (
int bank : banks)
1172 allowed.push_back(ctx.bankLimits[bank]);
1173 if (banks.size() == 2 && banks.back() == banks.front() + 1)
1174 allowed = {{ctx.bankLimits[banks.front()].start,
1175 ctx.bankLimits[banks.front()].size +
1176 ctx.bankLimits[banks.back()].size}};
1184 if (ctx.initGuardBytes > 0 && buffer.getInitialValue().has_value()) {
1185 SmallVector<MemoryRun, 2> clamped;
1186 for (MemoryRun range : allowed) {
1187 int64_t start = std::max(range.start, ctx.initGuardBytes);
1188 if (start < range.end())
1189 clamped.push_back({start, range.end() - start});
1191 allowed = std::move(clamped);
1193 ranges.push_back(allowed);
1195 for (
size_t j = 0; j < i; ++j) {
1196 if (sizes[j] == sizes[i] && aligns[j] == aligns[i] &&
1197 llvm::equal(ranges[j], ranges[i], [](MemoryRun a, MemoryRun b) {
1204 classes.push_back(cls);
1206 freeAbove.assign(gaps.size() + 1, 0);
1207 runAbove.assign(gaps.size() + 1, 0);
1208 for (
size_t g = gaps.size(); g-- > 0;) {
1209 freeAbove[g] = freeAbove[g + 1] + gaps[g].size;
1210 runAbove[g] = std::max(runAbove[g + 1], gaps[g].size);
1215 std::optional<int64_t> lowest(
size_t i,
size_t g, int64_t from)
const {
1216 std::optional<int64_t> best;
1217 for (MemoryRun range : ranges[i]) {
1218 int64_t at = llvm::alignTo(std::max(from, range.start), aligns[i]);
1219 if (at + sizes[i] <= std::min(gaps[g].end(), range.end()) &&
1220 getBankContaining(at, ctx.numBanks, ctx.bankLimits) >= 0 &&
1221 (!best || at < *best))
1228 llvm::BitVector left(order.size());
1230 for (
size_t i = 0; i < order.size(); ++i) {
1231 if (sizes[i] == 0) {
1233 for (
size_t g = 0; g < gaps.size() && addrs[i] < 0; ++g)
1234 if (
auto at = lowest(i, g, gaps[g].start))
1237 addrs[i] = ranges[i].front().start;
1243 return gaps.empty() ? left.none()
1244 : place(0, gaps.front().start, left, bytes);
1248 bool place(
size_t g, int64_t from, llvm::BitVector &left, int64_t bytes) {
1251 int64_t largest = 0;
1252 for (
int i : left.set_bits())
1253 largest = std::max(largest, sizes[i]);
1254 int64_t here = gaps[g].end() - from;
1255 if (bytes > here + freeAbove[g + 1] ||
1256 largest > std::max(here, runAbove[g + 1]) ||
1257 failed.contains({from, left}))
1259 SmallVector<unsigned> tried;
1260 for (
int i : left.set_bits()) {
1261 if (llvm::is_contained(tried, classes[i]))
1263 tried.push_back(classes[i]);
1264 std::optional<int64_t> at = lowest(i, g, from);
1267 if (budget-- <= 0) {
1273 bool done = place(g, *at + sizes[i], left, bytes - sizes[i]);
1280 if (g + 1 < gaps.size() && place(g + 1, gaps[g + 1].start, left, bytes))
1283 failed.insert({from, left});
1293placeFreeBuffers(ArrayRef<BufferOp> buffersToAlloc,
const BankAwareContext &ctx,
1294 const RequiredBanks &requiredBanks, MemoryOccupancy &occupancy,
1295 SmallVectorImpl<BufferOp> &placed, int64_t budget,
1296 bool &exhausted, PlacementStats &stats) {
1297 int64_t remaining = 0;
1298 for (
auto buffer : buffersToAlloc) {
1299 remaining += buffer.getAllocationSize();
1301 PlacementSearch search{buffersToAlloc, ctx, requiredBanks, occupancy,
1302 placed, stats, budget};
1303 int64_t before = stats.backtracks;
1304 bool solved = search.run(0, 0, remaining);
1305 if (stats.backtracks > before) {
1306 ++stats.tilesSearched;
1313 CompactionSearch complete(buffersToAlloc, ctx, requiredBanks, occupancy,
1315 if (complete.run()) {
1316 ++stats.tilesCompacted;
1317 for (
auto [buffer, addr] : llvm::zip(buffersToAlloc, complete.addrs)) {
1318 placeBuffer(buffer, addr,
1321 placed.push_back(buffer);
1325 search.restoreDeepest();
1326 exhausted = complete.exhausted;
1327 if (complete.exhausted) {
1328 ++stats.budgetExhausted;
1330 return search.deepest;
1335static void deAllocationBuffers(SmallVectorImpl<BufferOp> &buffers,
1337 for (
auto buffer : buffers) {
1338 buffer->removeAttr(
"address");
1339 auto required = requiredBanks.find(buffer);
1340 if (required == requiredBanks.end() || required->second.size() != 1) {
1341 buffer->removeAttr(
"mem_bank");
1343 buffer.setMemBank(required->second.front());
1352static LogicalResult placePreAllocatedBuffers(
1353 SmallVectorImpl<BufferOp> &preAllocatedBuffers,
const BankAwareContext &ctx,
1355 SmallVectorImpl<BufferOp> &buffersToAlloc) {
1359 llvm::stable_sort(preAllocatedBuffers, [](BufferOp a, BufferOp b) ->
bool {
1360 auto a_addr = a.getAddress();
1361 auto b_addr = b.getAddress();
1362 if (a_addr.has_value() && b_addr.has_value()) {
1363 return a_addr.value() < b_addr.value();
1365 return a_addr.has_value() && !b_addr.has_value();
1368 for (
auto buffer : preAllocatedBuffers) {
1369 auto has_addr = checkAndAddBufferWithAddress(buffer, ctx, occupancy);
1370 if (failed(has_addr)) {
1379 if (failed(recordRequiredBank(buffer, ctx.
numBanks, requiredBanks))) {
1382 buffersToAlloc.push_back(buffer);
1389static SmallVector<BufferOp>
1390placementOrder(ArrayRef<BufferOp> buffersToAlloc,
1392 SmallVector<BufferOp> order(buffersToAlloc.begin(), buffersToAlloc.end());
1393 llvm::stable_sort(order, [&](BufferOp a, BufferOp b) {
1394 if (requiredBanks.count(a) != requiredBanks.count(b)) {
1395 return requiredBanks.count(a) > requiredBanks.count(b);
1397 if (requiredBanks.count(a) &&
1398 requiredBanks.lookup(a).size() != requiredBanks.lookup(b).size())
1399 return requiredBanks.lookup(a).size() < requiredBanks.lookup(b).size();
1400 return a.getAllocationSize() > b.getAllocationSize();
1405static LogicalResult allocateTile(TileOp tile, int64_t budget,
1406 PlacementStats &stats) {
1407 auto device = tile->getParentOfType<AIE::DeviceOp>();
1412 std::vector<MemoryRun> bankLimits;
1415 auto [maxDataMemorySize, tileAlignBitWidth, maxVecAlignBits] =
1418 int numBanks = targetModel.getNumBanks(tile.getCol(), tile.getRow());
1419 int64_t bankSize = maxDataMemorySize / numBanks;
1421 fillBankLimits(numBanks, bankSize, bankLimits);
1427 MemoryOccupancy occupancy(maxDataMemorySize);
1428 CoreOp coreOp = tile.getCoreOp();
1430 stackRun = coreOp.getStackRun();
1431 auto stackBank = coreOp.getStackBank();
1432 if (!coreOp.getStackAddress() && stackBank) {
1433 int bank = *stackBank;
1434 int64_t align = targetModel.getCoreStackAlignment();
1438 MemoryOccupancy stackOccupancy(maxDataMemorySize);
1439 device.walk([&, maxDataMemorySize = maxDataMemorySize](BufferOp buffer) {
1440 if (buffer.getTileOp() != tile || !buffer.getAddress())
1442 int64_t start = *buffer.getAddress();
1443 int64_t end = start + buffer.getAllocationSize();
1444 if (start >= 0 && end <= maxDataMemorySize)
1445 stackOccupancy.markOccupied(start, end);
1447 std::optional<int64_t> at = stackOccupancy.findLeastFragmentingGap(
1448 bankLimits[bank].start, bankLimits[bank].end(), stackRun.
size, align);
1450 coreOp.emitOpError(
"requires a ")
1451 << stackRun.
size <<
"-byte stack in bank " << bank
1452 <<
", but no contiguous aligned space remains after address-pinned "
1456 stackRun.
start = *at;
1457 coreOp.setStackAddress(*at);
1459 occupancy.markOccupied(stackRun.
start,
1460 std::min(stackRun.
end(), maxDataMemorySize));
1463 numBanks, tileAlignBitWidth, maxVecAlignBits, bankLimits,
1464 maxDataMemorySize, stackRun, (bool)tile.getCoreOp()};
1467 SmallVector<BufferOp> preAllocatedBuffers;
1468 SmallVector<BufferOp> buffersToAlloc;
1469 SmallVector<BufferOp> allBuffers_on_tile;
1470 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
1471 if (buffer.getTileOp() == tile) {
1472 if (!isBufferPreAllocated(buffer) &&
1473 banksFromMemorySpace(buffer.getType()).empty()) {
1474 buffersToAlloc.push_back(buffer);
1476 preAllocatedBuffers.push_back(buffer);
1478 allBuffers_on_tile.push_back(buffer);
1482 if (failed(placePreAllocatedBuffers(preAllocatedBuffers, ctx, occupancy,
1483 requiredBanks, buffersToAlloc))) {
1498 if (tile.isMemTile() && tile.getCol() == 0 &&
1500 targetModel.getTargetArch() == AIEArch::AIE2p) {
1506 SmallVector<BufferOp> allocatedBuffers;
1513 SmallVector<BufferOp> order = placementOrder(buffersToAlloc, requiredBanks);
1514 bool searchExhausted =
false;
1515 BufferOp failedBuffer =
1516 placeFreeBuffers(order, ctx, requiredBanks, occupancy, allocatedBuffers,
1517 budget, searchExhausted, stats);
1519 if (BufferOp failed = failedBuffer) {
1523 if (!searchExhausted && requiredBanks.count(failed)) {
1524 auto banks = requiredBanks.lookup(failed);
1525 if (banks.size() > 1) {
1526 failed.emitOpError(
"")
1527 << bufferLabel(failed) <<
" requires " << failed.getAllocationSize()
1528 <<
" bytes, but no contiguous aligned space remains within allowed "
1530 << banks.front() <<
" and " << banks.back();
1531 deAllocationBuffers(allocatedBuffers, requiredBanks);
1534 int bank = banks.front();
1535 int64_t need = failed.getAllocationSize();
1536 int64_t bankCapacity = bankLimits[bank].size;
1537 if (need > bankCapacity) {
1538 failed->emitOpError(
"") << bufferLabel(failed) <<
" requires " << need
1539 <<
" bytes, which cannot fit in bank " << bank
1540 <<
" (" << bankCapacity <<
" bytes total)";
1542 failed->emitOpError(
"")
1543 << bufferLabel(failed) <<
" requires " << need <<
" bytes in bank "
1544 << bank <<
", but only "
1545 << occupancy.freeBytes(bankLimits[bank].start,
1546 bankLimits[bank].end())
1547 <<
" of " << bankCapacity <<
" bytes are free there";
1549 deAllocationBuffers(allocatedBuffers, requiredBanks);
1552 InFlightDiagnostic diag = failed.emitOpError(
"could not be placed: ")
1553 << bufferLabel(failed) <<
" needs "
1554 << failed.getAllocationSize()
1555 <<
" bytes and this tile has no room left for it";
1556 if (searchExhausted) {
1557 diag.attachNote() <<
"the search hit its " << budget
1558 <<
"-placement budget with arrangements still untried, "
1559 "so a layout may exist that it did not reach (raise "
1560 "it with placement-budget)";
1563 printMemMap(tile, allocatedBuffers, preAllocatedBuffers, ctx);
1564 deAllocationBuffers(allocatedBuffers, requiredBanks);
1567 assert(allocatedBuffers.size() == buffersToAlloc.size());
1569 sortBuffersByAddress(allBuffers_on_tile);
1572 if (!checkAndPrintOverlapStackframe(stackRun, allBuffers_on_tile) ||
1573 !checkAndPrintBufferOverlap(allBuffers_on_tile, tileAlignBitWidth,
1580static LogicalResult checkBufferScope(BufferOp buffer, DeviceOp device) {
1585 Operation *parent = buffer->getParentOp();
1586 if (!isa<DeviceOp>(parent) && !isa<MemTileDMAOp>(parent)) {
1587 auto tile = buffer.getTileOp();
1588 tile->emitOpError(
"Buffer '")
1590 <<
"' must be defined directly under the device scope. Currently it "
1591 "is nested inside a core tile.";
1598struct AIEAssignBufferAddressesPass
1599 : xilinx::AIE::impl::AIEAssignBufferAddressesBase<
1600 AIEAssignBufferAddressesPass> {
1602 AIEAssignBufferAddressesPass() =
default;
1604 AIEAssignBufferAddressesPass(
const AIEAssignBufferAddressesOptions &options)
1605 : AIEAssignBufferAddressesBase(options) {}
1607 void getDependentDialects(DialectRegistry ®istry)
const override {
1608 registry.insert<func::FuncDialect>();
1609 registry.insert<AIEDialect>();
1612 void runOnOperation()
override {
1613 DeviceOp device = getOperation();
1614 int64_t budget = clPlacementBudget;
1616 device.emitError(
"placement-budget must be positive, got ") << budget;
1617 return signalPassFailure();
1619 if (failed(applySignatureBankConstraints(device))) {
1620 return signalPassFailure();
1622 materializeCoreDataBuffers(device);
1623 materializeBankReservations(device);
1624 materializePrebakedRanges(device);
1631 PlacementStats stats;
1632 bool ok = llvm::all_of(device.getOps<TileOp>(), [&](TileOp tile) {
1633 return succeeded(allocateTile(tile, budget, stats));
1636 numTilesSearched += stats.tilesSearched;
1637 numBacktracks += stats.backtracks;
1638 numBudgetExhausted += stats.budgetExhausted;
1639 numTilesCompacted += stats.tilesCompacted;
1641 signalPassFailure();
1647struct AIEPrepareBuffersPass
1648 : xilinx::AIE::impl::AIEPrepareBuffersBase<AIEPrepareBuffersPass> {
1649 void getDependentDialects(DialectRegistry ®istry)
const override {
1650 registry.insert<func::FuncDialect>();
1651 registry.insert<AIEDialect>();
1654 void runOnOperation()
override {
1655 DeviceOp device = getOperation();
1659 OpBuilder builder = OpBuilder::atBlockTerminator(device.getBody());
1660 unsigned counter = 0;
1661 NamedOpTable names(device);
1662 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
1663 if (!buffer.hasName()) {
1665 SymbolTable::getSymbolAttrName(),
1666 builder.getStringAttr(names.uniqueName(
"_anonymous", counter)));
1669 device.walk<WalkOrder::PreOrder>([&](BufferOp buffer) {
1670 if (failed(checkBufferScope(buffer, device))) {
1671 return signalPassFailure();
1679 return std::make_unique<AIEPrepareBuffersPass>();
1682std::unique_ptr<OperationPass<DeviceOp>>
1684 return std::make_unique<AIEAssignBufferAddressesPass>();
1687std::unique_ptr<OperationPass<DeviceOp>>
1689 const AIEAssignBufferAddressesOptions &options) {
1690 return std::make_unique<AIEAssignBufferAddressesPass>(options);
DenseMap< Operation *, SmallVector< int, 2 > > RequiredBanks
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEPrepareBuffersPass()
const AIETargetModel & getTargetModel(mlir::Operation *op)
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEAssignBufferAddressesPass()
uint32_t requiredBufferAlignBits(uint32_t busAlignBits, uint32_t maxVecAlignBits, int64_t sizeBytes)
Return the alignment (in bits) a buffer of sizeBytes must satisfy.
TileMemoryLimits tileMemoryLimits(const AIETargetModel &targetModel, AIETileType tileType)
uint32_t tileAlignBitWidth
int64_t maxDataMemorySize
ArrayRef< MemoryRun > bankLimits