12#include "mlir/IR/Matchers.h"
13#include "mlir/Interfaces/LoopLikeInterface.h"
14#include "llvm/ADT/STLExtras.h"
23#define GEN_PASS_DEF_AIEANNOTATESHIMSHARING
24#include "aie/Dialect/AIE/Transforms/AIEPasses.h.inc"
28 DeviceOp device, function_ref<std::optional<End>(StringAttr)> endOf) {
29 analyze(device, [&](DeviceOp owner, StringAttr symbol) {
30 return owner == device ? endOf(symbol) : std::nullopt;
36 function_ref<std::optional<End>(DeviceOp, StringAttr)> endOf) {
37 analyze(module, endOf);
40void ShimTransferSpans::analyze(
42 function_ref<std::optional<End>(DeviceOp, StringAttr)> endOf) {
48 SmallVector<Span> spans;
51 DenseMap<Operation *, Summary> summaries;
52 DenseSet<Operation *> active;
53 NamedOpTableCollection names;
54 std::function<Summary(RuntimeSequenceOp)> summarize =
55 [&](RuntimeSequenceOp sequence) -> Summary {
56 if (active.contains(sequence)) {
60 if (
auto found = summaries.find(sequence); found != summaries.end())
62 active.insert(sequence);
63 DeviceOp device = sequence->getParentOfType<DeviceOp>();
65 DenseMap<Operation *, int64_t> position, end;
66 std::function<void(Operation *)> number = [&](Operation *op) {
67 position[op] = result.duration++;
68 if (
auto run = dyn_cast<AIEX::RunOp>(op)) {
69 if (
auto callee = run.getCalleeRuntimeSequenceOp(&names))
70 result.duration += summarize(callee).duration;
74 for (Region ®ion : op->getRegions())
75 for (Block &block : region)
76 for (Operation &nested : block)
78 end[op] = result.duration++;
81 auto interval = [&](Operation *op, int64_t from, int64_t to) {
82 for (Operation *parent = op->getParentOp(); parent != sequence;
83 parent = parent->getParentOp())
84 if (isa<LoopLikeOpInterface>(parent)) {
85 from = position[parent];
88 return std::make_pair(from, to);
91 auto note = [&](StringAttr endpoint, int64_t at,
92 std::optional<size_t> completion = std::nullopt) {
94 result.spans[*completion].last = at;
97 used.insert(endpoint);
98 result.spans.push_back({endpoint, at, end[sequence]});
99 return result.spans.size() - 1;
101 DenseMap<StringAttr, SmallVector<size_t>> pending;
102 DenseMap<Value, StringAttr> tasks;
103 DenseMap<size_t, Value> owners;
104 auto transfer = [&](Operation *op, StringAttr symbol,
bool starts,
105 bool all =
false, Value owner = {}) {
106 auto endpoint = endOf(device, symbol);
109 auto [from, to] = interval(op, position[op], position[op]);
110 auto &queue = pending[symbol];
112 size_t start = note(endpoint->fifo, from);
113 owners[start] = owner;
114 queue.push_back(start);
115 }
else if (!endpoint->sends) {
116 for (
auto it = queue.begin(); it != queue.end();) {
117 if (owner && owners.lookup(*it) != owner) {
121 note(endpoint->fifo, to, *it);
122 it = queue.erase(it);
128 auto synchronous = [&](Operation *op, StringAttr endpoint, int64_t from,
130 std::tie(from, to) = interval(op, from, to);
131 auto start = note(endpoint, from);
132 note(endpoint, to, start);
134 auto configuration = [&](Operation *op, DeviceOp configured,
135 std::optional<int> column = {}) {
138 for (
auto alloc : configured.getOps<ShimDMAAllocationOp>()) {
139 if (!AIEDialect::IsCtrlPktOverlayAttrHelper(root->getContext())
140 .isAttrPresent(alloc) ||
141 (column && alloc.getTileOp().colIndex() != *column))
143 if (
auto endpoint = endOf(configured, alloc.getSymNameAttr()))
144 synchronous(op, endpoint->fifo, position[op], position[op]);
147 sequence.walk<WalkOrder::PreOrder>([&](Operation *op) {
148 if (
auto run = dyn_cast<AIEX::RunOp>(op)) {
149 if (
auto callee = run.getCalleeRuntimeSequenceOp(&names))
150 for (
const Span &span : summarize(callee).spans)
151 synchronous(op, span.endpoint, position[op] + 1 + span.first,
152 position[op] + 1 + span.last);
153 }
else if (
auto configure = dyn_cast<AIEX::ConfigureOp>(op)) {
154 configuration(op, configure.getReferencedDeviceOp());
155 }
else if (isa<AIEX::NpuLoadPdiOp, AIEX::NpuWriteConfigOp,
156 AIEX::NpuWriteTeardownOp>(op)) {
157 if (isa<AIEX::NpuLoadPdiOp>(op) && op->getParentOp() == sequence) {
158 for (
auto &[symbol, queue] : pending) {
159 for (
size_t start : queue)
160 note(result.spans[start].endpoint, position[op] - 1, start);
164 auto ref = op->getAttrOfType<FlatSymbolRefAttr>(
"device_ref");
166 op, ref ? SymbolTable::lookupNearestSymbolFrom<DeviceOp>(op, ref)
168 }
else if (
auto packet = dyn_cast<AIEX::NpuControlPacketOp>(op)) {
169 configuration(op, device, packet.getColumnFromAddr());
170 }
else if (
auto sync = dyn_cast<AIEX::NpuSyncOp>(op)) {
171 SmallVector<int> values;
172 for (Value operand : sync->getOperands()) {
174 if (!matchPattern(operand, m_ConstantInt(&value)))
176 values.push_back(value.getSExtValue());
178 if (values[4] != 1 || values[5] != 1)
181 size_t first = std::numeric_limits<size_t>::max();
182 for (
const auto &[name, queue] : pending) {
183 if (queue.empty() || queue.front() >= first)
185 Operation *owner = device.lookupSymbol(name);
187 std::optional<int> channel;
188 DMAChannelDir direction;
189 if (
auto alloc = dyn_cast_or_null<ShimDMAAllocationOp>(owner)) {
190 tile = alloc.getTileOp();
191 channel = alloc.getChannelIndex();
192 direction = alloc.getChannelDir();
193 }
else if (
auto endpoint = dyn_cast_or_null<RouteEndpointOp>(owner)) {
194 tile = endpoint.getTile().getDefiningOp<TileOp>();
195 channel = endpoint.getChannelIndex();
196 direction = endpoint.getRouteDirection();
198 if (tile && channel && tile.colIndex() == values[0] &&
199 tile.rowIndex() == values[1] &&
200 static_cast<int>(direction) == values[2] &&
201 *channel == values[3]) {
202 first = queue.front();
207 transfer(op, symbol,
false);
208 }
else if (
auto memcpy = dyn_cast<AIEX::NpuDmaMemcpyNdOp>(op)) {
209 transfer(op, memcpy.getMetadata().getRootReference(),
true);
210 }
else if (
auto wait = dyn_cast<AIEX::NpuDmaWaitOp>(op)) {
211 transfer(op, wait.getSymbolAttr().getAttr(),
false,
true);
212 }
else if (
auto configure = dyn_cast<AIEX::DMAConfigureTaskForOp>(op)) {
213 tasks[configure.getResult()] = configure.getAlloc().getRootReference();
214 }
else if (isa<AIEX::DMAStartTaskOp, AIEX::DMAAwaitTaskOp,
215 AIEX::DMAFreeTaskOp>(op)) {
216 if (
auto symbol = tasks.lookup(op->getOperand(0)))
217 transfer(op, symbol, isa<AIEX::DMAStartTaskOp>(op),
218 isa<AIEX::DMAFreeTaskOp>(op),
219 isa<AIEX::DMAAwaitTaskOp>(op) ? Value{} : op->getOperand(0));
222 llvm::stable_sort(result.spans, [](
const Span &a,
const Span &b) {
223 return a.first < b.first;
225 DenseMap<StringAttr, int64_t> live;
226 for (
const Span &span : result.spans) {
227 lifetimes[span.endpoint].emplace_back(summaries.size(), span.first,
229 for (
const auto &[other, last] : live) {
230 if (last < span.first)
232 overlaps.insert({span.endpoint, other});
233 overlaps.insert({other, span.endpoint});
235 live[span.endpoint] = std::max(live.lookup(span.endpoint), span.last);
237 active.erase(sequence);
238 summaries[sequence] = result;
241 root->walk([&](Operation *op) {
242 if (!isa<ObjectFifoCreateOp, RouteEndpointOp, ShimDMAAllocationOp>(op))
244 DeviceOp device = op->getParentOfType<DeviceOp>();
245 auto endpoint = endOf(device, SymbolTable::getSymbolName(op));
248 bool sends = endpoint->sends;
249 if (
auto alloc = dyn_cast<ShimDMAAllocationOp>(op)) {
250 sends = alloc.getChannelDir() == DMAChannelDir::MM2S;
251 used.insert(endpoint->fifo);
252 }
else if (
auto allowed =
253 AIEDialect::ShareableWithAttrHelper(op->getContext())
255 auto &peers = permissions[endpoint->fifo];
256 for (Attribute attr : allowed)
257 if (
auto ref = dyn_cast<FlatSymbolRefAttr>(attr))
258 if (
auto other = endOf(device, ref.getAttr()))
259 peers.push_back(other->fifo);
261 if (
auto fifo = dyn_cast<ObjectFifoCreateOp>(op))
262 if (llvm::any_of(fifo.getConsumerTiles(), [](Value tile) {
263 return cast<TileLike>(tile.getDefiningOp()).isShimTile();
268 permissions.try_emplace(endpoint->fifo);
270 root->walk([&](RuntimeSequenceOp sequence) { summarize(sequence); });
274 return used.contains(endpoint);
278 auto permits = [&](StringAttr from, StringAttr to) {
279 if (
auto it = permissions.find(from); it != permissions.end())
280 return llvm::is_contained(it->second, to);
282 !overlaps.contains({from, to});
284 return a != b && permits(a, b) && permits(b, a);
287SmallVector<SmallVector<unsigned>>
289 auto when = llvm::map_to_vector(
290 ends, [&](StringAttr end) {
return lifetimes.lookup(end); });
291 auto order = llvm::to_vector(llvm::seq<unsigned>(0, ends.size()));
292 llvm::stable_sort(order,
293 [&](
unsigned x,
unsigned y) {
return when[x] < when[y]; });
294 SmallVector<SmallVector<unsigned>> result;
295 for (
unsigned end : order) {
296 auto *group = llvm::find_if(result, [&](ArrayRef<unsigned> members) {
298 members, [&](
unsigned m) {
return apart(ends[m], ends[end]); });
300 if (group == result.end())
301 result.push_back({end});
303 group->push_back(end);
309struct AIEAnnotateShimSharingPass
310 : xilinx::AIE::impl::AIEAnnotateShimSharingBase<
311 AIEAnnotateShimSharingPass> {
312 void runOnOperation()
override {
313 ModuleOp module = getOperation();
314 AIEDialect::IsCtrlPktOverlayAttrHelper overlay(&getContext());
315 AIEDialect::ShareableWithAttrHelper sharing(&getContext());
316 DenseMap<Operation *, StringAttr> keys;
317 auto key = [&](Operation *op) {
318 auto [it, inserted] = keys.try_emplace(op);
321 StringAttr::get(&getContext(), std::to_string(keys.size()));
327 StringAttr symbol) -> std::optional<ShimTransferSpans::End> {
328 Operation *op = device.lookupSymbol(symbol);
329 if (
auto endpoint = dyn_cast_or_null<RouteEndpointOp>(op)) {
330 if (endpoint.getBundle() != WireBundle::DMA ||
331 !endpoint.getTileLike().isShimTile())
334 endpoint.getRouteDirection() ==
335 DMAChannelDir::MM2S};
337 auto alloc = dyn_cast_or_null<ShimDMAAllocationOp>(op);
340 if (overlay.isAttrPresent(alloc)) {
341 if (
auto ref = device.getOverlayAttr())
342 if (
auto definition =
343 module.lookupSymbol<DeviceOp>(ref.getValue()))
344 if (Operation *original = definition.lookupSymbol(symbol))
346 return ShimTransferSpans::End{key(op),
false};
348 return ShimTransferSpans::End{key(op), alloc.getChannelDir() ==
349 DMAChannelDir::MM2S};
351 for (DeviceOp device : module.getOps<DeviceOp>()) {
352 auto annotate = [&](Operation *op, Value tile, DMAChannelDir direction,
353 StringAttr identity) {
354 if (sharing.isAttrPresent(op))
356 SmallVector<Attribute> allowed;
357 for (
auto other : device.getOps<RouteEndpointOp>())
358 if (other != op && other.getBundle() == WireBundle::DMA &&
359 other.getTile() == tile &&
360 other.getRouteDirection() == direction &&
361 spans.apart(identity, keys.lookup(other)))
362 allowed.push_back(FlatSymbolRefAttr::get(other.getSymNameAttr()));
363 sharing.setAttr(op, ArrayAttr::get(&getContext(), allowed));
365 for (
auto endpoint : device.getOps<RouteEndpointOp>())
366 if (endpoint.getBundle() == WireBundle::DMA &&
367 endpoint.getTileLike().isShimTile())
368 annotate(endpoint, endpoint.getTile(), endpoint.getRouteDirection(),
369 keys.lookup(endpoint));
370 for (
auto alloc : device.getOps<ShimDMAAllocationOp>())
371 if (overlay.isAttrPresent(alloc)) {
372 Operation *original = alloc;
373 if (
auto ref = device.getOverlayAttr())
374 if (
auto definition = module.lookupSymbol<DeviceOp>(ref.getValue()))
375 if (Operation *source =
376 definition.lookupSymbol(alloc.getSymName()))
378 annotate(alloc, alloc.getTile(), alloc.getChannelDir(),
379 keys.lookup(original));
386std::unique_ptr<OperationPass<ModuleOp>>
388 return std::make_unique<AIEAnnotateShimSharingPass>();
Shim endpoint lifetimes, including calls to self-contained runtime sequences.
ShimTransferSpans(DeviceOp device, llvm::function_ref< std::optional< End >(mlir::StringAttr)> endOf)
endOf maps a symbol a runtime sequence transfers through to the objectFIFO end it belongs to,...
bool isUsed(mlir::StringAttr end) const
bool apart(mlir::StringAttr a, mlir::StringAttr b) const
Whether both ends permit sharing, explicitly or by MM2S lifetime analysis.
llvm::SmallVector< llvm::SmallVector< unsigned > > groups(llvm::ArrayRef< mlir::StringAttr > ends) const
Splits ends into groups whose members are pairwise apart, each group taking one channel: first fit,...
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createAIEAnnotateShimSharingPass()
A shim end a runtime sequence transfers through.