1 | //===- Offloading.cpp - Utilities for handling offloading code -*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/Object/OffloadBinary.h" |
10 | |
11 | #include "llvm/ADT/StringSwitch.h" |
12 | #include "llvm/BinaryFormat/Magic.h" |
13 | #include "llvm/IR/Constants.h" |
14 | #include "llvm/IR/Module.h" |
15 | #include "llvm/IRReader/IRReader.h" |
16 | #include "llvm/MC/StringTableBuilder.h" |
17 | #include "llvm/Object/Archive.h" |
18 | #include "llvm/Object/ArchiveWriter.h" |
19 | #include "llvm/Object/Binary.h" |
20 | #include "llvm/Object/COFF.h" |
21 | #include "llvm/Object/ELFObjectFile.h" |
22 | #include "llvm/Object/Error.h" |
23 | #include "llvm/Object/IRObjectFile.h" |
24 | #include "llvm/Object/ObjectFile.h" |
25 | #include "llvm/Support/Alignment.h" |
26 | #include "llvm/Support/FileOutputBuffer.h" |
27 | #include "llvm/Support/SourceMgr.h" |
28 | |
29 | using namespace llvm; |
30 | using namespace llvm::object; |
31 | |
32 | namespace { |
33 | |
34 | /// Attempts to extract all the embedded device images contained inside the |
35 | /// buffer \p Contents. The buffer is expected to contain a valid offloading |
36 | /// binary format. |
37 | Error (MemoryBufferRef Contents, |
38 | SmallVectorImpl<OffloadFile> &Binaries) { |
39 | uint64_t Offset = 0; |
40 | // There could be multiple offloading binaries stored at this section. |
41 | while (Offset < Contents.getBuffer().size()) { |
42 | std::unique_ptr<MemoryBuffer> Buffer = |
43 | MemoryBuffer::getMemBuffer(InputData: Contents.getBuffer().drop_front(N: Offset), BufferName: "" , |
44 | /*RequiresNullTerminator*/ false); |
45 | if (!isAddrAligned(Lhs: Align(OffloadBinary::getAlignment()), |
46 | Addr: Buffer->getBufferStart())) |
47 | Buffer = MemoryBuffer::getMemBufferCopy(InputData: Buffer->getBuffer(), |
48 | BufferName: Buffer->getBufferIdentifier()); |
49 | auto BinaryOrErr = OffloadBinary::create(*Buffer); |
50 | if (!BinaryOrErr) |
51 | return BinaryOrErr.takeError(); |
52 | OffloadBinary &Binary = **BinaryOrErr; |
53 | |
54 | // Create a new owned binary with a copy of the original memory. |
55 | std::unique_ptr<MemoryBuffer> BufferCopy = MemoryBuffer::getMemBufferCopy( |
56 | InputData: Binary.getData().take_front(N: Binary.getSize()), |
57 | BufferName: Contents.getBufferIdentifier()); |
58 | auto NewBinaryOrErr = OffloadBinary::create(*BufferCopy); |
59 | if (!NewBinaryOrErr) |
60 | return NewBinaryOrErr.takeError(); |
61 | Binaries.emplace_back(Args: std::move(*NewBinaryOrErr), Args: std::move(BufferCopy)); |
62 | |
63 | Offset += Binary.getSize(); |
64 | } |
65 | |
66 | return Error::success(); |
67 | } |
68 | |
69 | // Extract offloading binaries from an Object file \p Obj. |
70 | Error (const ObjectFile &Obj, |
71 | SmallVectorImpl<OffloadFile> &Binaries) { |
72 | assert((Obj.isELF() || Obj.isCOFF()) && "Invalid file type" ); |
73 | |
74 | for (SectionRef Sec : Obj.sections()) { |
75 | // ELF files contain a section with the LLVM_OFFLOADING type. |
76 | if (Obj.isELF() && |
77 | static_cast<ELFSectionRef>(Sec).getType() != ELF::SHT_LLVM_OFFLOADING) |
78 | continue; |
79 | |
80 | // COFF has no section types so we rely on the name of the section. |
81 | if (Obj.isCOFF()) { |
82 | Expected<StringRef> NameOrErr = Sec.getName(); |
83 | if (!NameOrErr) |
84 | return NameOrErr.takeError(); |
85 | |
86 | if (!NameOrErr->starts_with(Prefix: ".llvm.offloading" )) |
87 | continue; |
88 | } |
89 | |
90 | Expected<StringRef> Buffer = Sec.getContents(); |
91 | if (!Buffer) |
92 | return Buffer.takeError(); |
93 | |
94 | MemoryBufferRef Contents(*Buffer, Obj.getFileName()); |
95 | if (Error Err = extractOffloadFiles(Contents, Binaries)) |
96 | return Err; |
97 | } |
98 | |
99 | return Error::success(); |
100 | } |
101 | |
102 | Error (MemoryBufferRef Buffer, |
103 | SmallVectorImpl<OffloadFile> &Binaries) { |
104 | LLVMContext Context; |
105 | SMDiagnostic Err; |
106 | std::unique_ptr<Module> M = getLazyIRModule( |
107 | Buffer: MemoryBuffer::getMemBuffer(Ref: Buffer, /*RequiresNullTerminator=*/false), Err, |
108 | Context); |
109 | if (!M) |
110 | return createStringError(EC: inconvertibleErrorCode(), |
111 | S: "Failed to create module" ); |
112 | |
113 | // Extract offloading data from globals referenced by the |
114 | // `llvm.embedded.object` metadata with the `.llvm.offloading` section. |
115 | auto *MD = M->getNamedMetadata(Name: "llvm.embedded.objects" ); |
116 | if (!MD) |
117 | return Error::success(); |
118 | |
119 | for (const MDNode *Op : MD->operands()) { |
120 | if (Op->getNumOperands() < 2) |
121 | continue; |
122 | |
123 | MDString *SectionID = dyn_cast<MDString>(Val: Op->getOperand(I: 1)); |
124 | if (!SectionID || SectionID->getString() != ".llvm.offloading" ) |
125 | continue; |
126 | |
127 | GlobalVariable *GV = |
128 | mdconst::dyn_extract_or_null<GlobalVariable>(MD: Op->getOperand(I: 0)); |
129 | if (!GV) |
130 | continue; |
131 | |
132 | auto *CDS = dyn_cast<ConstantDataSequential>(Val: GV->getInitializer()); |
133 | if (!CDS) |
134 | continue; |
135 | |
136 | MemoryBufferRef Contents(CDS->getAsString(), M->getName()); |
137 | if (Error Err = extractOffloadFiles(Contents, Binaries)) |
138 | return Err; |
139 | } |
140 | |
141 | return Error::success(); |
142 | } |
143 | |
144 | Error (const Archive &Library, |
145 | SmallVectorImpl<OffloadFile> &Binaries) { |
146 | // Try to extract device code from each file stored in the static archive. |
147 | Error Err = Error::success(); |
148 | for (auto Child : Library.children(Err)) { |
149 | auto ChildBufferOrErr = Child.getMemoryBufferRef(); |
150 | if (!ChildBufferOrErr) |
151 | return ChildBufferOrErr.takeError(); |
152 | std::unique_ptr<MemoryBuffer> ChildBuffer = |
153 | MemoryBuffer::getMemBuffer(Ref: *ChildBufferOrErr, RequiresNullTerminator: false); |
154 | |
155 | // Check if the buffer has the required alignment. |
156 | if (!isAddrAligned(Lhs: Align(OffloadBinary::getAlignment()), |
157 | Addr: ChildBuffer->getBufferStart())) |
158 | ChildBuffer = MemoryBuffer::getMemBufferCopy( |
159 | InputData: ChildBufferOrErr->getBuffer(), |
160 | BufferName: ChildBufferOrErr->getBufferIdentifier()); |
161 | |
162 | if (Error Err = extractOffloadBinaries(Buffer: *ChildBuffer, Binaries)) |
163 | return Err; |
164 | } |
165 | |
166 | if (Err) |
167 | return Err; |
168 | return Error::success(); |
169 | } |
170 | |
171 | } // namespace |
172 | |
173 | Expected<std::unique_ptr<OffloadBinary>> |
174 | OffloadBinary::create(MemoryBufferRef Buf) { |
175 | if (Buf.getBufferSize() < sizeof(Header) + sizeof(Entry)) |
176 | return errorCodeToError(EC: object_error::parse_failed); |
177 | |
178 | // Check for 0x10FF1OAD magic bytes. |
179 | if (identify_magic(magic: Buf.getBuffer()) != file_magic::offload_binary) |
180 | return errorCodeToError(EC: object_error::parse_failed); |
181 | |
182 | // Make sure that the data has sufficient alignment. |
183 | if (!isAddrAligned(Lhs: Align(getAlignment()), Addr: Buf.getBufferStart())) |
184 | return errorCodeToError(EC: object_error::parse_failed); |
185 | |
186 | const char *Start = Buf.getBufferStart(); |
187 | const Header * = reinterpret_cast<const Header *>(Start); |
188 | if (TheHeader->Version != OffloadBinary::Version) |
189 | return errorCodeToError(EC: object_error::parse_failed); |
190 | |
191 | if (TheHeader->Size > Buf.getBufferSize() || |
192 | TheHeader->Size < sizeof(Entry) || TheHeader->Size < sizeof(Header)) |
193 | return errorCodeToError(EC: object_error::unexpected_eof); |
194 | |
195 | if (TheHeader->EntryOffset > TheHeader->Size - sizeof(Entry) || |
196 | TheHeader->EntrySize > TheHeader->Size - sizeof(Header)) |
197 | return errorCodeToError(EC: object_error::unexpected_eof); |
198 | |
199 | const Entry *TheEntry = |
200 | reinterpret_cast<const Entry *>(&Start[TheHeader->EntryOffset]); |
201 | |
202 | if (TheEntry->ImageOffset > Buf.getBufferSize() || |
203 | TheEntry->StringOffset > Buf.getBufferSize()) |
204 | return errorCodeToError(EC: object_error::unexpected_eof); |
205 | |
206 | return std::unique_ptr<OffloadBinary>( |
207 | new OffloadBinary(Buf, TheHeader, TheEntry)); |
208 | } |
209 | |
210 | SmallString<0> OffloadBinary::write(const OffloadingImage &OffloadingData) { |
211 | // Create a null-terminated string table with all the used strings. |
212 | StringTableBuilder StrTab(StringTableBuilder::ELF); |
213 | for (auto &KeyAndValue : OffloadingData.StringData) { |
214 | StrTab.add(S: KeyAndValue.first); |
215 | StrTab.add(S: KeyAndValue.second); |
216 | } |
217 | StrTab.finalize(); |
218 | |
219 | uint64_t StringEntrySize = |
220 | sizeof(StringEntry) * OffloadingData.StringData.size(); |
221 | |
222 | // Make sure the image we're wrapping around is aligned as well. |
223 | uint64_t BinaryDataSize = alignTo(Value: sizeof(Header) + sizeof(Entry) + |
224 | StringEntrySize + StrTab.getSize(), |
225 | Align: getAlignment()); |
226 | |
227 | // Create the header and fill in the offsets. The entry will be directly |
228 | // placed after the header in memory. Align the size to the alignment of the |
229 | // header so this can be placed contiguously in a single section. |
230 | Header ; |
231 | TheHeader.Size = alignTo( |
232 | Value: BinaryDataSize + OffloadingData.Image->getBufferSize(), Align: getAlignment()); |
233 | TheHeader.EntryOffset = sizeof(Header); |
234 | TheHeader.EntrySize = sizeof(Entry); |
235 | |
236 | // Create the entry using the string table offsets. The string table will be |
237 | // placed directly after the entry in memory, and the image after that. |
238 | Entry TheEntry; |
239 | TheEntry.TheImageKind = OffloadingData.TheImageKind; |
240 | TheEntry.TheOffloadKind = OffloadingData.TheOffloadKind; |
241 | TheEntry.Flags = OffloadingData.Flags; |
242 | TheEntry.StringOffset = sizeof(Header) + sizeof(Entry); |
243 | TheEntry.NumStrings = OffloadingData.StringData.size(); |
244 | |
245 | TheEntry.ImageOffset = BinaryDataSize; |
246 | TheEntry.ImageSize = OffloadingData.Image->getBufferSize(); |
247 | |
248 | SmallString<0> Data; |
249 | Data.reserve(N: TheHeader.Size); |
250 | raw_svector_ostream OS(Data); |
251 | OS << StringRef(reinterpret_cast<char *>(&TheHeader), sizeof(Header)); |
252 | OS << StringRef(reinterpret_cast<char *>(&TheEntry), sizeof(Entry)); |
253 | for (auto &KeyAndValue : OffloadingData.StringData) { |
254 | uint64_t Offset = sizeof(Header) + sizeof(Entry) + StringEntrySize; |
255 | StringEntry Map{.KeyOffset: Offset + StrTab.getOffset(S: KeyAndValue.first), |
256 | .ValueOffset: Offset + StrTab.getOffset(S: KeyAndValue.second)}; |
257 | OS << StringRef(reinterpret_cast<char *>(&Map), sizeof(StringEntry)); |
258 | } |
259 | StrTab.write(OS); |
260 | // Add padding to required image alignment. |
261 | OS.write_zeros(NumZeros: TheEntry.ImageOffset - OS.tell()); |
262 | OS << OffloadingData.Image->getBuffer(); |
263 | |
264 | // Add final padding to required alignment. |
265 | assert(TheHeader.Size >= OS.tell() && "Too much data written?" ); |
266 | OS.write_zeros(NumZeros: TheHeader.Size - OS.tell()); |
267 | assert(TheHeader.Size == OS.tell() && "Size mismatch" ); |
268 | |
269 | return Data; |
270 | } |
271 | |
272 | Error object::(MemoryBufferRef Buffer, |
273 | SmallVectorImpl<OffloadFile> &Binaries) { |
274 | file_magic Type = identify_magic(magic: Buffer.getBuffer()); |
275 | switch (Type) { |
276 | case file_magic::bitcode: |
277 | return extractFromBitcode(Buffer, Binaries); |
278 | case file_magic::elf_relocatable: |
279 | case file_magic::elf_executable: |
280 | case file_magic::elf_shared_object: |
281 | case file_magic::coff_object: { |
282 | Expected<std::unique_ptr<ObjectFile>> ObjFile = |
283 | ObjectFile::createObjectFile(Object: Buffer, Type); |
284 | if (!ObjFile) |
285 | return ObjFile.takeError(); |
286 | return extractFromObject(Obj: *ObjFile->get(), Binaries); |
287 | } |
288 | case file_magic::archive: { |
289 | Expected<std::unique_ptr<llvm::object::Archive>> LibFile = |
290 | object::Archive::create(Source: Buffer); |
291 | if (!LibFile) |
292 | return LibFile.takeError(); |
293 | return extractFromArchive(Library: *LibFile->get(), Binaries); |
294 | } |
295 | case file_magic::offload_binary: |
296 | return extractOffloadFiles(Contents: Buffer, Binaries); |
297 | default: |
298 | return Error::success(); |
299 | } |
300 | } |
301 | |
302 | OffloadKind object::getOffloadKind(StringRef Name) { |
303 | return llvm::StringSwitch<OffloadKind>(Name) |
304 | .Case(S: "openmp" , Value: OFK_OpenMP) |
305 | .Case(S: "cuda" , Value: OFK_Cuda) |
306 | .Case(S: "hip" , Value: OFK_HIP) |
307 | .Default(Value: OFK_None); |
308 | } |
309 | |
310 | StringRef object::getOffloadKindName(OffloadKind Kind) { |
311 | switch (Kind) { |
312 | case OFK_OpenMP: |
313 | return "openmp" ; |
314 | case OFK_Cuda: |
315 | return "cuda" ; |
316 | case OFK_HIP: |
317 | return "hip" ; |
318 | default: |
319 | return "none" ; |
320 | } |
321 | } |
322 | |
323 | ImageKind object::getImageKind(StringRef Name) { |
324 | return llvm::StringSwitch<ImageKind>(Name) |
325 | .Case(S: "o" , Value: IMG_Object) |
326 | .Case(S: "bc" , Value: IMG_Bitcode) |
327 | .Case(S: "cubin" , Value: IMG_Cubin) |
328 | .Case(S: "fatbin" , Value: IMG_Fatbinary) |
329 | .Case(S: "s" , Value: IMG_PTX) |
330 | .Default(Value: IMG_None); |
331 | } |
332 | |
333 | StringRef object::getImageKindName(ImageKind Kind) { |
334 | switch (Kind) { |
335 | case IMG_Object: |
336 | return "o" ; |
337 | case IMG_Bitcode: |
338 | return "bc" ; |
339 | case IMG_Cubin: |
340 | return "cubin" ; |
341 | case IMG_Fatbinary: |
342 | return "fatbin" ; |
343 | case IMG_PTX: |
344 | return "s" ; |
345 | default: |
346 | return "" ; |
347 | } |
348 | } |
349 | |
350 | bool object::areTargetsCompatible(const OffloadFile::TargetID &LHS, |
351 | const OffloadFile::TargetID &RHS) { |
352 | // Exact matches are not considered compatible because they are the same |
353 | // target. We are interested in different targets that are compatible. |
354 | if (LHS == RHS) |
355 | return false; |
356 | |
357 | // The triples must match at all times. |
358 | if (LHS.first != RHS.first) |
359 | return false; |
360 | |
361 | // If the architecture is "all" we assume it is always compatible. |
362 | if (LHS.second == "generic" || RHS.second == "generic" ) |
363 | return true; |
364 | |
365 | // Only The AMDGPU target requires additional checks. |
366 | llvm::Triple T(LHS.first); |
367 | if (!T.isAMDGPU()) |
368 | return false; |
369 | |
370 | // The base processor must always match. |
371 | if (LHS.second.split(Separator: ":" ).first != RHS.second.split(Separator: ":" ).first) |
372 | return false; |
373 | |
374 | // Check combintions of on / off features that must match. |
375 | if (LHS.second.contains(Other: "xnack+" ) && RHS.second.contains(Other: "xnack-" )) |
376 | return false; |
377 | if (LHS.second.contains(Other: "xnack-" ) && RHS.second.contains(Other: "xnack+" )) |
378 | return false; |
379 | if (LHS.second.contains(Other: "sramecc-" ) && RHS.second.contains(Other: "sramecc+" )) |
380 | return false; |
381 | if (LHS.second.contains(Other: "sramecc+" ) && RHS.second.contains(Other: "sramecc-" )) |
382 | return false; |
383 | return true; |
384 | } |
385 | |