MLIR-AIE 1.4.5.dev
AIETokenAnalysis.cpp
Go to the documentation of this file.
1//===- AIETokenAnalysis.cpp -------------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2019-2022 Xilinx, Inc.
4// Copyright (C) 2022-2024 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
12
13#include "mlir/Pass/Pass.h"
14#include "mlir/Tools/mlir-translate/MlirTranslateMain.h"
15
16using namespace mlir;
17using namespace xilinx;
18using namespace xilinx::AIE;
19using namespace xilinx::AIEX;
20
21static void tokenMapPushBack(
22 std::vector<std::pair<llvm::StringRef, std::vector<mlir::Operation *>>>
23 &map,
24 StringRef tokenName, Operation *op) {
25 for (auto &pair : map) {
26 if (pair.first == tokenName) {
27 pair.second.push_back(op);
28 return;
29 }
30 }
31 map.push_back({tokenName, {op}});
32}
33
35
36 // Collecting token symbols
37 for (auto op : device.getOps<TokenOp>()) {
38 StringRef tokenName =
39 op->getAttrOfType<StringAttr>(::mlir::SymbolTable::getSymbolAttrName())
40 .getValue();
41 int value = op.getTokenValue();
42 tokenSymbols[tokenName] = value;
43 }
44
45 // collect all the UseTokenOps and MemcpyOps
46 std::map<StringRef, SmallVector<UseTokenOp, 4>> visitors;
47 device.getBodyRegion().walk([&](Operation *Op) {
48 if (auto op = dyn_cast<UseTokenOp>(Op)) {
49 StringRef tokenName = op.getTokenName();
50 assert(tokenSymbols.find(tokenName) != tokenSymbols.end() &&
51 "Token not found!");
52 if (op.acquire()) {
53 tokenMapPushBack(tokenAcqMap, tokenName, op.getOperation());
54 visitors[tokenName].push_back(op);
55 } else {
56 tokenMapPushBack(tokenRelMap, tokenName, op.getOperation());
57 if (!visitors[tokenName].empty()) {
58 Operation *Op = visitors[tokenName].pop_back_val();
59 tokenPairs.push_back({Op, op.getOperation()});
60 }
61 }
62 } else if (auto op = dyn_cast<MemcpyOp>(Op)) {
63 StringRef tokenName = op.getTokenName();
64 assert(tokenSymbols.find(tokenName) != tokenSymbols.end() &&
65 "Token not found!");
66 Operation *Op = op.getOperation();
67 tokenMapPushBack(tokenAcqMap, tokenName, op.getOperation());
68 tokenMapPushBack(tokenRelMap, tokenName, op.getOperation());
69 tokenPairs.push_back({Op, Op});
70 }
71 });
72
73 // sanity check: ensure that acquiring a token is followed by releasing a
74 // token
75 for (const auto &map : tokenAcqMap) {
76 StringRef tokenName = map.first;
77 for (auto *Op : map.second) {
78 [[maybe_unused]] bool isReleased = false;
79 if (auto op = dyn_cast<MemcpyOp>(Op))
80 isReleased = true;
81
82 for (auto pair : tokenPairs) {
83 if (UseTokenOp aop = dyn_cast<UseTokenOp>(pair.first)) {
84 if (tokenName == aop.getTokenName() && Op == aop.getOperation()) {
85 isReleased = true;
86 break;
87 }
88 }
89 }
90
91 assert(isReleased && "No release found for acquire!"
92 "This might potentially lead to deadlock");
93 }
94 }
95
96 int releaseValue = 0;
97 int acquireValue = 0;
98 // Look for a pair of UseTokenOps (or UseTokenOp and MemcpyOp) such that one
99 // releases and one acquires the same token + value. They form a chain of
100 // releasing and acquiring a token. From the chains of tokens collected, we
101 // can infer the dependency of the parentOps
102 for (const auto &map : tokenRelMap) {
103 StringRef tokenName = map.first;
104 auto tokenRels = map.second;
105 auto tokenAcqs = [&]() {
106 for (auto &pair : tokenAcqMap)
107 if (pair.first == tokenName)
108 return pair.second;
109 return std::vector<Operation *>();
110 }();
111 for (auto *ROp : tokenRels) {
112
113 if (auto op = dyn_cast<UseTokenOp>(ROp))
114 releaseValue = op.getTokenValue();
115 else if (auto op = dyn_cast<MemcpyOp>(ROp))
116 releaseValue = op.getReleaseTokenValue();
117
118 for (auto *AOp : tokenAcqs) {
119
120 if (auto op = dyn_cast<UseTokenOp>(AOp))
121 acquireValue = op.getTokenValue();
122 else if (auto op = dyn_cast<MemcpyOp>(AOp))
123 acquireValue = op.getAcquireTokenValue();
124 else
125 continue;
126
127 // Release and Acquire form a chain if they set/get the token with the
128 // same value
129 if (releaseValue != acquireValue)
130 continue;
131
132 tokenChains.push_back({ROp, AOp});
133 }
134 }
135 }
136
137 for (auto tile : device.getOps<TileOp>()) {
138 int colIndex = tile.colIndex();
139 int rowIndex = tile.rowIndex();
140 tiles[{colIndex, rowIndex}] = tile;
141 }
142}
143
145
146 if (UseTokenOp op = dyn_cast<UseTokenOp>(Op)) {
147 while (Operation *parentOp = op->getParentOp()) {
148 if (isa<CoreOp>(parentOp) || isa<MemOp>(parentOp) ||
149 isa<ShimDMAOp>(parentOp))
150 return parentOp;
151 }
152 }
153
154 return nullptr;
155}
156
158 int colIndex = 0;
159 int rowIndex = 0;
160
161 if (CoreOp core = dyn_cast<CoreOp>(Op)) {
162 colIndex = core.colIndex();
163 rowIndex = core.rowIndex();
164 } else if (MemOp mem = dyn_cast<MemOp>(Op)) {
165 colIndex = mem.colIndex();
166 rowIndex = mem.rowIndex();
167 } else if (ShimDMAOp shimDma = dyn_cast<ShimDMAOp>(Op)) {
168 colIndex = shimDma.colIndex();
169 rowIndex = shimDma.rowIndex();
170 }
171
172 return {colIndex, rowIndex};
173}
174
176 Operation *Op2) {
177 bool IsOp1Mem = isa<MemOp>(Op1) || isa<ShimDMAOp>(Op1);
178 bool IsOp2Mem = isa<MemOp>(Op2) || isa<ShimDMAOp>(Op2);
179
180 assert((!IsOp1Mem || !IsOp2Mem) &&
181 "Op1 and Op2 cannot be both Mem operation!");
182
183 TileID coord1 = getCoord(Op1);
184 TileID coord2 = getCoord(Op2);
185
186 int col1 = coord1.col;
187 int row1 = coord1.row;
188 int col2 = coord2.col;
189 int row2 = coord2.row;
190
191 const auto &targetModel = xilinx::AIE::getTargetModel(Op1);
192
193 bool IsOp1ShareableMem =
194 IsOp1Mem && targetModel.isLegalMemAffinity(col2, row2, col1, row1);
195 bool IsOp2ShareableMem =
196 IsOp2Mem && targetModel.isLegalMemAffinity(col1, row1, col2, row2);
197
198 if (IsOp1ShareableMem)
199 return tiles[coord1];
200 if (IsOp2ShareableMem)
201 return tiles[coord2];
202
203 // both Op1 and Op2 are core ops
204 if (!IsOp1Mem && !IsOp2Mem) {
205 bool IsS = targetModel.isSouth(col1, row1, col2, row2);
206 bool IsW = targetModel.isWest(col1, row1, col2, row2);
207 bool IsN = targetModel.isNorth(col1, row1, col2, row2);
208 bool IsE = targetModel.isEast(col1, row1, col2, row2);
209 bool IsInternal = targetModel.isInternal(col1, row1, col2, row2);
210 bool IsEvenRow = ((row1 % 2) == 0);
211
212 // FIXME: This logic appears AIE1 specific.
213 if (IsS || IsN || (IsW && !IsEvenRow) || (IsE && IsEvenRow))
214 return tiles[coord2];
215 if ((IsW && IsEvenRow) || (IsE && !IsEvenRow) || IsInternal)
216 return tiles[coord1];
217 }
218
219 return nullptr;
220}
221
223 os << "\n=====tokenPairs: \n";
224 for (auto pair : tokenPairs) {
225 Operation *acquire = pair.first;
226 Operation *release = pair.second;
227 acquire->print(os);
228 os << " -> ";
229 release->print(os);
230 os << "\n";
231 }
232
233 os << "\n=====tokenChains: \n";
234 for (auto pair : tokenChains) {
235 Operation *release = pair.first;
236 Operation *acquire = pair.second;
237 release->print(os);
238 os << " -> ";
239 acquire->print(os);
240 os << "\n";
241 }
242}
mlir::Operation * getShareableTileOp(mlir::Operation *Op1, mlir::Operation *Op2)
xilinx::AIE::TileID getCoord(mlir::Operation *Op)
mlir::Operation * getTokenUserOp(mlir::Operation *Op)
void print(llvm::raw_ostream &os)
struct TileID { friend std::ostream &operator<<(std::ostream &os, const TileID &s) { os<< "TileID("<< s.col<< ", "<< s.row<< ")"; return os; } friend std::string to_string(const TileID &s) { std::ostringstream ss; ss<< s; return ss.str(); } friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const TileID &s) { os<< to_string(s); return os; } inline bool operator<(const TileID &rhs) const { return std::tie(col, row)< std::tie(rhs.col, rhs.row); } bool operator==(const TileID &rhs) const { return std::tie(col, row)==std::tie(rhs.col, rhs.row); } bool operator!=(const TileID &rhs) const { return !(*this==rhs);} int col, row;} TileID
const AIETargetModel & getTargetModel(mlir::Operation *op)