MLIR-AIE 1.4.5.dev
AIETargetLdScript.cpp
Go to the documentation of this file.
1//===- AIETargetLdScript.cpp -----------------------------------*- C++ -*-===//
2//
3// Copyright (C) 2023 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7
13
14#include <optional>
15
16using namespace mlir;
17using namespace xilinx;
18using namespace xilinx::AIE;
19
20// Output the memorymap in gnu linker format for the given buffer operations,
21// with the given offset. The offset is different depending on where the buffers
22// are accessed from.
23static void writeLDScriptMap(raw_ostream &output, BufferOp buf, int offset) {
24 std::string bufName(buf.name().getValue());
25 int bufferBaseAddr = getBufferBaseAddress(buf);
26 int numBytes = buf.getAllocationSize();
27 output << ". = 0x" << llvm::utohexstr(offset + bufferBaseAddr) << ";\n";
28 output << bufName << " = .;\n";
29 output << ". += 0x" << llvm::utohexstr(numBytes) << ";\n";
30}
31
32///// ld.script format:
33//
34// MEMORY
35// {
36// program (RX) : ORIGIN = 0, LENGTH = 0x0020000
37// data (!RX) : ORIGIN = 0x20000, LENGTH = 0x0020000
38// }
39// ENTRY(__start)
40// INPUT(something.o)
41// SECTIONS
42// {
43// . = 0x0;
44// .text : {
45// // the __start symbol has to come at address zero.
46// *crt0.o(.text*)
47// __ctors_start__ = .;
48// __init_array_start = .;
49// KEEP(SORT(*)(.init_array))
50// __ctors_end__ = .;
51// __init_array_end = .;
52// __dtors_start__ = .;
53// __dtors_end__ = .;
54// *(.text*)
55// } > program
56// .data : { *(.data*) } > data
57// . = 0x20000;
58// _sp_start_value_DM_stack = .;
59// . = 0x24000;
60// a = .;
61// . += 1024;
62// .bss : { *(.bss*) } > data
63// }
64// PROVIDE(main = core_3_3);
65
67 ModuleOp module, raw_ostream &output, int tileCol, int tileRow,
68 llvm::StringRef deviceName, bool probe,
69 llvm::function_ref<std::string(llvm::StringRef)> resolveLinkFile) {
70 DenseMap<TileID, Operation *> tiles;
71 DenseMap<Operation *, SmallVector<BufferOp, 4>> buffers;
72
73 DeviceOp targetOp =
74 AIE::DeviceOp::getForSymbolInModuleOrError(module, deviceName);
75
76 if (!targetOp) {
77 return failure();
78 }
79
80 collectTiles(targetOp, tiles);
81 collectBuffers(targetOp, buffers);
82
83 for (auto tile : targetOp.getOps<TileOp>()) {
84 if (tile.colIndex() == tileCol && tile.rowIndex() == tileRow) {
85 TileID srcCoord = {tile.colIndex(), tile.rowIndex()};
86 // The "data" region below holds the sections the core compiler generates
87 // itself (.data, .rodata and .bss) and that nothing places explicitly. It
88 // is one contiguous region, so its size bounds what the core can link,
89 // and the total free memory on the tile does not.
90 const auto &targetModel = getTargetModel(tile);
91 MemoryRun stackRun;
92 if (auto core = tile.getCoreOp()) {
93 if (!probe && core.getStackBank() && !core.getStackAddress())
94 return core.emitOpError(
95 "stack_bank has no assigned stack_address; run "
96 "--aie-assign-buffer-addresses with bank-aware allocation");
97 stackRun = core.getStackRun();
98 }
99 MemoryRun dataRun =
100 probe ? MemoryRun{stackRun.end(),
101 targetModel.getLocalMemorySize() - stackRun.end()}
102 : coreDataRegion(tile, buffers[tiles[srcCoord]]);
103 // The checks below all ask whether placement is stale. A probe link runs
104 // before placement, so there is nothing yet to be stale.
105 if (!probe) {
106 if (dataRun.start < 0 ||
107 dataRun.end() > targetModel.getLocalMemorySize())
108 return tile.emitOpError(
109 "data region runs past this tile's local memory; the buffer "
110 "allocator's placement is stale. Re-run "
111 "--aie-assign-buffer-addresses");
112 if (dataRun.size > 0 && stackRun.size > 0 &&
113 dataRun.start < stackRun.end() && stackRun.start < dataRun.end())
114 return tile.emitOpError(
115 "data region overlaps the stack; the buffer allocator's "
116 "placement is stale. Re-run --aie-assign-buffer-addresses");
117
118 // A pass that adds or moves a buffer after the allocator ran leaves the
119 // region aliasing it, and the core compiler and that buffer would then
120 // write over each other. Report the alias here.
121 for (auto buf : buffers[tiles[srcCoord]]) {
122 if (buf.getCoreData() || buf.getAllocationSize() == 0 ||
123 dataRun.size == 0) {
124 continue;
125 }
126 int64_t bufStart = getBufferBaseAddress(buf);
127 int64_t bufEnd = bufStart + buf.getAllocationSize();
128 if (bufStart < dataRun.end() && dataRun.start < bufEnd) {
129 return tile.emitOpError("data region 0x")
130 << llvm::utohexstr(dataRun.start) << "-0x"
131 << llvm::utohexstr(dataRun.end() - 1) << " overlaps buffer '"
132 << buf.name().getValue() << "' at 0x"
133 << llvm::utohexstr(bufStart)
134 << "; the buffer allocator's placement is stale. Re-run "
135 "--aie-assign-buffer-addresses";
136 }
137 }
138 }
139
140 // Was hardcoded to 0x20000 -- eight times the real 0x4000 -- which let
141 // an overflowing core link cleanly and fail much later in aie-rt's ELF
142 // loader instead of here, at the linker, naming the section.
143 int origin =
144 targetModel.getMemInternalBaseAddress(srcCoord) + dataRun.start;
145 int length = dataRun.size;
146 bool reservedData =
147 !probe && llvm::any_of(buffers[tiles[srcCoord]], [](BufferOp buf) {
148 return buf.getCoreData();
149 });
150 llvm::SmallVector<MemoryRun> bankRuns;
151 if (probe) {
152 // A probe measures; it does not judge. Section sizes come from the
153 // objects and the garbage collection, never from how much room a region
154 // offers, so every region is given the whole tile and the regions are
155 // allowed to overlap. A design too big to link still probes cleanly,
156 // and the real link reports it with the diagnostic that belongs there.
157 int numBanks = targetModel.getNumBanks(srcCoord.col, srcCoord.row);
158 int64_t bankSize =
159 numBanks > 0 ? targetModel.getLocalMemorySize() / numBanks : 0;
160 for (int bank = 0; bank < numBanks; ++bank)
161 bankRuns.push_back(
162 MemoryRun{bankSize * bank, targetModel.getLocalMemorySize()});
163 } else {
164 bankRuns = coreBankRegions(tile, buffers[tiles[srcCoord]],
165 reservedData ? dataRun : MemoryRun{});
166 }
167 std::string dataOrigin = "0x" + llvm::utohexstr(origin);
168 std::string dataEnd = "0x" + llvm::utohexstr(origin + length);
169 if (!reservedData) {
170 // Bank sections have fixed regions. LLD resolves their sizes before
171 // assigning ordinary data, including BSS-only cores and empty banks.
172 for (auto [bank, run] : llvm::enumerate(bankRuns)) {
173 int64_t bankStart =
174 targetModel.getMemInternalBaseAddress(srcCoord) + run.start;
175 if (run.size == 0 || bankStart >= origin + length ||
176 bankStart + run.size <= origin) {
177 continue;
178 }
179 std::string sec = bankSectionName(bank);
180 std::string end = "(ADDR(" + sec + ") + SIZEOF(" + sec + "))";
181 dataOrigin = "MAX(" + dataOrigin + ", (SIZEOF(" + sec +
182 ") != 0 ? MIN(" + dataEnd + ", " + end + ") : 0))";
183 }
184 }
185 output << R"THESCRIPT(
186MEMORY
187{
188)THESCRIPT";
189 output << " program (RX) : ORIGIN = 0, LENGTH = 0x"
190 << llvm::utohexstr(targetModel.getProgramMemorySize()) << "\n";
191 output << " data (!RX) : ORIGIN = " << dataOrigin << ", LENGTH = ";
192 if (reservedData) {
193 output << "0x" << llvm::utohexstr(length);
194 } else {
195 output << dataEnd << " - " << dataOrigin;
196 }
197 output << "\n";
198 // One region per bank, for statics a kernel pins with a bank attribute.
199 // A bank with nothing spare gets a zero-length region rather than being
200 // left out, so a section aimed at it overflows by name instead of
201 // becoming an orphan.
202 for (auto [bank, run] : llvm::enumerate(bankRuns)) {
203 output << " " << bankRegionName(bank) << " (!RX) : ORIGIN = 0x"
204 << llvm::utohexstr(
205 targetModel.getMemInternalBaseAddress(srcCoord) +
206 run.start)
207 << ", LENGTH = 0x" << llvm::utohexstr(run.size) << "\n";
208 }
209 output << R"THESCRIPT(}
210ENTRY(__start)
211SECTIONS
212{
213 . = 0x0;
214 .text : {
215 /* the __start symbol has to come at address zero. */
216 *crt0.o(.text*)
217 _ctors_start = .;
218 _init_array_start = .;
219 KEEP(SORT(*.init_array))
220 _ctors_end = .;
221 _init_array_end = .;
222 _dtors_start = .;
223 _dtors_end = .;
224 *(.text*)
225 } > program
226)THESCRIPT";
227 // Ahead of .data and .bss: lld assigns an input section to the first
228 // description that matches it, and the wildcards below would otherwise
229 // take the bank-pinned ones.
230 for (auto [bank, run] : llvm::enumerate(bankRuns)) {
231 std::string sec = bankSectionName(bank);
232 output << " " << sec << " : { *(" << sec << " " << sec << ".*)"
233 << " *(*.DM_bank" << bankLetter(bank) << "*) } > "
234 << bankRegionName(bank) << "\n";
235 }
236 output << R"THESCRIPT( .data : {
237 *(.data*)
238 *(.rodata*)
239 } > data
240 .comment : {
241 *(.comment*)
242 }
243 .symtab : {
244 *(.symtab)
245 }
246 .shstrtab : {
247 *(.shstrtab)
248 }
249 .strtab : {
250 *(.strtab)
251 }
252 .stack_sizes : {
253 *(.stack_sizes)
254 }
255 /* Bitcode an object carries for aiecc's own analysis. It describes the core
256 rather than running on it, and --orphan-handling=error rejects any section
257 with no home, so name it here to drop it. */
258 /DISCARD/ : {
259 *(.llvmbc)
260 *(.llvmcmd)
261 }
262
263)THESCRIPT";
264 auto doBuffer = [&](std::optional<TileID> tile, int offset,
265 const std::string &dir) {
266 if (tile) {
267 // A probe has no addresses yet. Each symbol stands at its memory's
268 // base, a data address no function starts at, so the probe resolves
269 // exactly the symbols the real link does.
270 if (tiles.count(*tile)) {
271 for (auto buf : buffers[tiles[*tile]]) {
272 if (buf.getCoreData()) {
273 continue;
274 }
275 if (probe) {
276 output << buf.name().getValue() << " = 0x"
277 << llvm::utohexstr(offset) << ";\n";
278 } else {
279 writeLDScriptMap(output, buf, offset);
280 }
281 }
282 }
283 } else {
284 output << "/* No tile with memory exists to the " << dir << ". */\n";
285 output << ". = 0x" << llvm::utohexstr(offset) << ";\n";
286 uint32_t localMemSize = targetModel.getLocalMemorySize();
287 output << ". += 0x" << llvm::utohexstr(localMemSize) << ";\n";
288 }
289 };
290
291 // Stack
292 if (auto core = tile.getCoreOp()) {
293 MemoryRun stackRun = core.getStackRun();
294 output << ". = 0x"
295 << llvm::utohexstr(
296 targetModel.getMemInternalBaseAddress(srcCoord) +
297 stackRun.start)
298 << ";\n";
299 output << "_sp_start_value_DM_stack = .;\n";
300 output << ". += 0x" << llvm::utohexstr(stackRun.size)
301 << "; /* stack */\n";
302 } else {
303 output << ". = 0x"
304 << llvm::utohexstr(
305 targetModel.getMemInternalBaseAddress(srcCoord))
306 << ";\n";
307 output << "_sp_start_value_DM_stack = .;\n";
308 output << "/* no stack allocated */\n";
309 }
310
311 doBuffer(targetModel.getMemSouth(srcCoord),
312 targetModel.getMemSouthBaseAddress(), std::string("south"));
313 doBuffer(targetModel.getMemWest(srcCoord),
314 targetModel.getMemWestBaseAddress(), std::string("west"));
315 doBuffer(targetModel.getMemNorth(srcCoord),
316 targetModel.getMemNorthBaseAddress(), std::string("north"));
317 doBuffer(targetModel.getMemEast(srcCoord),
318 targetModel.getMemEastBaseAddress(), std::string("east"));
319
320 output << " .bss : { *(.bss*) } > data\n";
321 // INPUT() directives must follow the closing brace of SECTIONS; placing
322 // them inside SECTIONS is invalid linker script syntax.
323 output << "}\n";
324 if (auto coreOp = tile.getCoreOp()) {
325 // `link_files` holds the ordinary final-link inputs (object files)
326 if (auto filesAttr = coreOp.getLinkFiles()) {
327 // Canonical path: link_files populated by aie-assign-core-link-files.
328 for (auto f : filesAttr->getAsRange<mlir::StringAttr>()) {
329 output << "INPUT("
330 << (resolveLinkFile ? resolveLinkFile(f.getValue())
331 : f.getValue().str())
332 << ")\n";
333 }
334 } else if (auto fileAttr = coreOp.getLinkWith()) {
335 // Deprecated fallback: core-level link_with was not migrated by
336 // aie-assign-core-link-files (e.g., the pass was not run). It carries
337 // no mode, so it is always an ordinary link input.
338 output << "INPUT(" << fileAttr.value().str() << ")\n";
339 }
340
341 output << "PROVIDE(main = core_" << tile.getCol() << "_"
342 << tile.getRow() << ");\n";
343 }
344 }
345 }
346 return success();
347}
char bankLetter(int bank)
void collectTiles(DeviceOp &device, llvm::DenseMap< TileID, mlir::Operation * > &tiles)
void collectBuffers(DeviceOp &device, llvm::DenseMap< mlir::Operation *, llvm::SmallVector< BufferOp, 4 > > &buffers)
std::string bankSectionName(int bank)
llvm::SmallVector< MemoryRun > coreBankRegions(TileOp tile, llvm::ArrayRef< BufferOp > buffers, MemoryRun dataRun)
One extent per memory bank for the core's bank-pinned statics, tile-relative and indexed by bank.
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
int32_t getBufferBaseAddress(mlir::Operation *bufOp)
std::string bankRegionName(int bank)
const AIETargetModel & getTargetModel(mlir::Operation *op)
MemoryRun coreDataRegion(TileOp tile, llvm::ArrayRef< BufferOp > buffers)
The extent the core compiler gets for its own .data, .rodata and .bss, tile-relative.
mlir::LogicalResult AIETranslateToLdScript(mlir::ModuleOp module, llvm::raw_ostream &output, int tileCol, int tileRow, llvm::StringRef deviceName="", bool probe=false, llvm::function_ref< std::string(llvm::StringRef)> resolveLinkFile=nullptr)
int64_t end() const