1 | //===- OffloadBundler.cpp - File Bundling and Unbundling ------------------===// |
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 | /// \file |
10 | /// This file implements an offload bundling API that bundles different files |
11 | /// that relate with the same source code but different targets into a single |
12 | /// one. Also the implements the opposite functionality, i.e. unbundle files |
13 | /// previous created by this API. |
14 | /// |
15 | //===----------------------------------------------------------------------===// |
16 | |
17 | #include "clang/Driver/OffloadBundler.h" |
18 | #include "clang/Basic/Cuda.h" |
19 | #include "clang/Basic/TargetID.h" |
20 | #include "clang/Basic/Version.h" |
21 | #include "llvm/ADT/ArrayRef.h" |
22 | #include "llvm/ADT/SmallString.h" |
23 | #include "llvm/ADT/SmallVector.h" |
24 | #include "llvm/ADT/StringExtras.h" |
25 | #include "llvm/ADT/StringMap.h" |
26 | #include "llvm/ADT/StringRef.h" |
27 | #include "llvm/BinaryFormat/Magic.h" |
28 | #include "llvm/Object/Archive.h" |
29 | #include "llvm/Object/ArchiveWriter.h" |
30 | #include "llvm/Object/Binary.h" |
31 | #include "llvm/Object/ObjectFile.h" |
32 | #include "llvm/Support/Casting.h" |
33 | #include "llvm/Support/Compression.h" |
34 | #include "llvm/Support/Debug.h" |
35 | #include "llvm/Support/EndianStream.h" |
36 | #include "llvm/Support/Errc.h" |
37 | #include "llvm/Support/Error.h" |
38 | #include "llvm/Support/ErrorOr.h" |
39 | #include "llvm/Support/FileSystem.h" |
40 | #include "llvm/Support/MD5.h" |
41 | #include "llvm/Support/MemoryBuffer.h" |
42 | #include "llvm/Support/Path.h" |
43 | #include "llvm/Support/Program.h" |
44 | #include "llvm/Support/Signals.h" |
45 | #include "llvm/Support/StringSaver.h" |
46 | #include "llvm/Support/Timer.h" |
47 | #include "llvm/Support/WithColor.h" |
48 | #include "llvm/Support/raw_ostream.h" |
49 | #include "llvm/TargetParser/Host.h" |
50 | #include "llvm/TargetParser/Triple.h" |
51 | #include <algorithm> |
52 | #include <cassert> |
53 | #include <cstddef> |
54 | #include <cstdint> |
55 | #include <forward_list> |
56 | #include <llvm/Support/Process.h> |
57 | #include <memory> |
58 | #include <set> |
59 | #include <string> |
60 | #include <system_error> |
61 | #include <utility> |
62 | |
63 | using namespace llvm; |
64 | using namespace llvm::object; |
65 | using namespace clang; |
66 | |
67 | static llvm::TimerGroup |
68 | ClangOffloadBundlerTimerGroup("Clang Offload Bundler Timer Group" , |
69 | "Timer group for clang offload bundler" ); |
70 | |
71 | /// Magic string that marks the existence of offloading data. |
72 | #define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" |
73 | |
74 | OffloadTargetInfo::OffloadTargetInfo(const StringRef Target, |
75 | const OffloadBundlerConfig &BC) |
76 | : BundlerConfig(BC) { |
77 | |
78 | // TODO: Add error checking from ClangOffloadBundler.cpp |
79 | auto TargetFeatures = Target.split(Separator: ':'); |
80 | auto TripleOrGPU = TargetFeatures.first.rsplit(Separator: '-'); |
81 | |
82 | if (clang::StringToCudaArch(S: TripleOrGPU.second) != clang::CudaArch::UNKNOWN) { |
83 | auto KindTriple = TripleOrGPU.first.split(Separator: '-'); |
84 | this->OffloadKind = KindTriple.first; |
85 | |
86 | // Enforce optional env field to standardize bundles |
87 | llvm::Triple t = llvm::Triple(KindTriple.second); |
88 | this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(), |
89 | t.getOSName(), t.getEnvironmentName()); |
90 | |
91 | this->TargetID = Target.substr(Start: Target.find(Str: TripleOrGPU.second)); |
92 | } else { |
93 | auto KindTriple = TargetFeatures.first.split(Separator: '-'); |
94 | this->OffloadKind = KindTriple.first; |
95 | |
96 | // Enforce optional env field to standardize bundles |
97 | llvm::Triple t = llvm::Triple(KindTriple.second); |
98 | this->Triple = llvm::Triple(t.getArchName(), t.getVendorName(), |
99 | t.getOSName(), t.getEnvironmentName()); |
100 | |
101 | this->TargetID = "" ; |
102 | } |
103 | } |
104 | |
105 | bool OffloadTargetInfo::hasHostKind() const { |
106 | return this->OffloadKind == "host" ; |
107 | } |
108 | |
109 | bool OffloadTargetInfo::isOffloadKindValid() const { |
110 | return OffloadKind == "host" || OffloadKind == "openmp" || |
111 | OffloadKind == "hip" || OffloadKind == "hipv4" ; |
112 | } |
113 | |
114 | bool OffloadTargetInfo::isOffloadKindCompatible( |
115 | const StringRef TargetOffloadKind) const { |
116 | if (OffloadKind == TargetOffloadKind) |
117 | return true; |
118 | if (BundlerConfig.HipOpenmpCompatible) { |
119 | bool HIPCompatibleWithOpenMP = OffloadKind.starts_with_insensitive(Prefix: "hip" ) && |
120 | TargetOffloadKind == "openmp" ; |
121 | bool OpenMPCompatibleWithHIP = |
122 | OffloadKind == "openmp" && |
123 | TargetOffloadKind.starts_with_insensitive(Prefix: "hip" ); |
124 | return HIPCompatibleWithOpenMP || OpenMPCompatibleWithHIP; |
125 | } |
126 | return false; |
127 | } |
128 | |
129 | bool OffloadTargetInfo::isTripleValid() const { |
130 | return !Triple.str().empty() && Triple.getArch() != Triple::UnknownArch; |
131 | } |
132 | |
133 | bool OffloadTargetInfo::operator==(const OffloadTargetInfo &Target) const { |
134 | return OffloadKind == Target.OffloadKind && |
135 | Triple.isCompatibleWith(Other: Target.Triple) && TargetID == Target.TargetID; |
136 | } |
137 | |
138 | std::string OffloadTargetInfo::str() const { |
139 | return Twine(OffloadKind + "-" + Triple.str() + "-" + TargetID).str(); |
140 | } |
141 | |
142 | static StringRef getDeviceFileExtension(StringRef Device, |
143 | StringRef BundleFileName) { |
144 | if (Device.contains(Other: "gfx" )) |
145 | return ".bc" ; |
146 | if (Device.contains(Other: "sm_" )) |
147 | return ".cubin" ; |
148 | return sys::path::extension(path: BundleFileName); |
149 | } |
150 | |
151 | static std::string getDeviceLibraryFileName(StringRef BundleFileName, |
152 | StringRef Device) { |
153 | StringRef LibName = sys::path::stem(path: BundleFileName); |
154 | StringRef Extension = getDeviceFileExtension(Device, BundleFileName); |
155 | |
156 | std::string Result; |
157 | Result += LibName; |
158 | Result += Extension; |
159 | return Result; |
160 | } |
161 | |
162 | namespace { |
163 | /// Generic file handler interface. |
164 | class FileHandler { |
165 | public: |
166 | struct BundleInfo { |
167 | StringRef BundleID; |
168 | }; |
169 | |
170 | FileHandler() {} |
171 | |
172 | virtual ~FileHandler() {} |
173 | |
174 | /// Update the file handler with information from the header of the bundled |
175 | /// file. |
176 | virtual Error ReadHeader(MemoryBuffer &Input) = 0; |
177 | |
178 | /// Read the marker of the next bundled to be read in the file. The bundle |
179 | /// name is returned if there is one in the file, or `std::nullopt` if there |
180 | /// are no more bundles to be read. |
181 | virtual Expected<std::optional<StringRef>> |
182 | ReadBundleStart(MemoryBuffer &Input) = 0; |
183 | |
184 | /// Read the marker that closes the current bundle. |
185 | virtual Error ReadBundleEnd(MemoryBuffer &Input) = 0; |
186 | |
187 | /// Read the current bundle and write the result into the stream \a OS. |
188 | virtual Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) = 0; |
189 | |
190 | /// Write the header of the bundled file to \a OS based on the information |
191 | /// gathered from \a Inputs. |
192 | virtual Error WriteHeader(raw_ostream &OS, |
193 | ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) = 0; |
194 | |
195 | /// Write the marker that initiates a bundle for the triple \a TargetTriple to |
196 | /// \a OS. |
197 | virtual Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) = 0; |
198 | |
199 | /// Write the marker that closes a bundle for the triple \a TargetTriple to \a |
200 | /// OS. |
201 | virtual Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) = 0; |
202 | |
203 | /// Write the bundle from \a Input into \a OS. |
204 | virtual Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) = 0; |
205 | |
206 | /// Finalize output file. |
207 | virtual Error finalizeOutputFile() { return Error::success(); } |
208 | |
209 | /// List bundle IDs in \a Input. |
210 | virtual Error listBundleIDs(MemoryBuffer &Input) { |
211 | if (Error Err = ReadHeader(Input)) |
212 | return Err; |
213 | return forEachBundle(Input, Func: [&](const BundleInfo &Info) -> Error { |
214 | llvm::outs() << Info.BundleID << '\n'; |
215 | Error Err = listBundleIDsCallback(Input, Info); |
216 | if (Err) |
217 | return Err; |
218 | return Error::success(); |
219 | }); |
220 | } |
221 | |
222 | /// Get bundle IDs in \a Input in \a BundleIds. |
223 | virtual Error getBundleIDs(MemoryBuffer &Input, |
224 | std::set<StringRef> &BundleIds) { |
225 | if (Error Err = ReadHeader(Input)) |
226 | return Err; |
227 | return forEachBundle(Input, Func: [&](const BundleInfo &Info) -> Error { |
228 | BundleIds.insert(x: Info.BundleID); |
229 | Error Err = listBundleIDsCallback(Input, Info); |
230 | if (Err) |
231 | return Err; |
232 | return Error::success(); |
233 | }); |
234 | } |
235 | |
236 | /// For each bundle in \a Input, do \a Func. |
237 | Error forEachBundle(MemoryBuffer &Input, |
238 | std::function<Error(const BundleInfo &)> Func) { |
239 | while (true) { |
240 | Expected<std::optional<StringRef>> CurTripleOrErr = |
241 | ReadBundleStart(Input); |
242 | if (!CurTripleOrErr) |
243 | return CurTripleOrErr.takeError(); |
244 | |
245 | // No more bundles. |
246 | if (!*CurTripleOrErr) |
247 | break; |
248 | |
249 | StringRef CurTriple = **CurTripleOrErr; |
250 | assert(!CurTriple.empty()); |
251 | |
252 | BundleInfo Info{.BundleID: CurTriple}; |
253 | if (Error Err = Func(Info)) |
254 | return Err; |
255 | } |
256 | return Error::success(); |
257 | } |
258 | |
259 | protected: |
260 | virtual Error listBundleIDsCallback(MemoryBuffer &Input, |
261 | const BundleInfo &Info) { |
262 | return Error::success(); |
263 | } |
264 | }; |
265 | |
266 | /// Handler for binary files. The bundled file will have the following format |
267 | /// (all integers are stored in little-endian format): |
268 | /// |
269 | /// "OFFLOAD_BUNDLER_MAGIC_STR" (ASCII encoding of the string) |
270 | /// |
271 | /// NumberOfOffloadBundles (8-byte integer) |
272 | /// |
273 | /// OffsetOfBundle1 (8-byte integer) |
274 | /// SizeOfBundle1 (8-byte integer) |
275 | /// NumberOfBytesInTripleOfBundle1 (8-byte integer) |
276 | /// TripleOfBundle1 (byte length defined before) |
277 | /// |
278 | /// ... |
279 | /// |
280 | /// OffsetOfBundleN (8-byte integer) |
281 | /// SizeOfBundleN (8-byte integer) |
282 | /// NumberOfBytesInTripleOfBundleN (8-byte integer) |
283 | /// TripleOfBundleN (byte length defined before) |
284 | /// |
285 | /// Bundle1 |
286 | /// ... |
287 | /// BundleN |
288 | |
289 | /// Read 8-byte integers from a buffer in little-endian format. |
290 | static uint64_t Read8byteIntegerFromBuffer(StringRef Buffer, size_t pos) { |
291 | return llvm::support::endian::read64le(P: Buffer.data() + pos); |
292 | } |
293 | |
294 | /// Write 8-byte integers to a buffer in little-endian format. |
295 | static void Write8byteIntegerToBuffer(raw_ostream &OS, uint64_t Val) { |
296 | llvm::support::endian::write(os&: OS, value: Val, endian: llvm::endianness::little); |
297 | } |
298 | |
299 | class BinaryFileHandler final : public FileHandler { |
300 | /// Information about the bundles extracted from the header. |
301 | struct BinaryBundleInfo final : public BundleInfo { |
302 | /// Size of the bundle. |
303 | uint64_t Size = 0u; |
304 | /// Offset at which the bundle starts in the bundled file. |
305 | uint64_t Offset = 0u; |
306 | |
307 | BinaryBundleInfo() {} |
308 | BinaryBundleInfo(uint64_t Size, uint64_t Offset) |
309 | : Size(Size), Offset(Offset) {} |
310 | }; |
311 | |
312 | /// Map between a triple and the corresponding bundle information. |
313 | StringMap<BinaryBundleInfo> BundlesInfo; |
314 | |
315 | /// Iterator for the bundle information that is being read. |
316 | StringMap<BinaryBundleInfo>::iterator CurBundleInfo; |
317 | StringMap<BinaryBundleInfo>::iterator NextBundleInfo; |
318 | |
319 | /// Current bundle target to be written. |
320 | std::string CurWriteBundleTarget; |
321 | |
322 | /// Configuration options and arrays for this bundler job |
323 | const OffloadBundlerConfig &BundlerConfig; |
324 | |
325 | public: |
326 | // TODO: Add error checking from ClangOffloadBundler.cpp |
327 | BinaryFileHandler(const OffloadBundlerConfig &BC) : BundlerConfig(BC) {} |
328 | |
329 | ~BinaryFileHandler() final {} |
330 | |
331 | Error ReadHeader(MemoryBuffer &Input) final { |
332 | StringRef FC = Input.getBuffer(); |
333 | |
334 | // Initialize the current bundle with the end of the container. |
335 | CurBundleInfo = BundlesInfo.end(); |
336 | |
337 | // Check if buffer is smaller than magic string. |
338 | size_t ReadChars = sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; |
339 | if (ReadChars > FC.size()) |
340 | return Error::success(); |
341 | |
342 | // Check if no magic was found. |
343 | if (llvm::identify_magic(magic: FC) != llvm::file_magic::offload_bundle) |
344 | return Error::success(); |
345 | |
346 | // Read number of bundles. |
347 | if (ReadChars + 8 > FC.size()) |
348 | return Error::success(); |
349 | |
350 | uint64_t NumberOfBundles = Read8byteIntegerFromBuffer(Buffer: FC, pos: ReadChars); |
351 | ReadChars += 8; |
352 | |
353 | // Read bundle offsets, sizes and triples. |
354 | for (uint64_t i = 0; i < NumberOfBundles; ++i) { |
355 | |
356 | // Read offset. |
357 | if (ReadChars + 8 > FC.size()) |
358 | return Error::success(); |
359 | |
360 | uint64_t Offset = Read8byteIntegerFromBuffer(Buffer: FC, pos: ReadChars); |
361 | ReadChars += 8; |
362 | |
363 | // Read size. |
364 | if (ReadChars + 8 > FC.size()) |
365 | return Error::success(); |
366 | |
367 | uint64_t Size = Read8byteIntegerFromBuffer(Buffer: FC, pos: ReadChars); |
368 | ReadChars += 8; |
369 | |
370 | // Read triple size. |
371 | if (ReadChars + 8 > FC.size()) |
372 | return Error::success(); |
373 | |
374 | uint64_t TripleSize = Read8byteIntegerFromBuffer(Buffer: FC, pos: ReadChars); |
375 | ReadChars += 8; |
376 | |
377 | // Read triple. |
378 | if (ReadChars + TripleSize > FC.size()) |
379 | return Error::success(); |
380 | |
381 | StringRef Triple(&FC.data()[ReadChars], TripleSize); |
382 | ReadChars += TripleSize; |
383 | |
384 | // Check if the offset and size make sense. |
385 | if (!Offset || Offset + Size > FC.size()) |
386 | return Error::success(); |
387 | |
388 | assert(!BundlesInfo.contains(Triple) && "Triple is duplicated??" ); |
389 | BundlesInfo[Triple] = BinaryBundleInfo(Size, Offset); |
390 | } |
391 | // Set the iterator to where we will start to read. |
392 | CurBundleInfo = BundlesInfo.end(); |
393 | NextBundleInfo = BundlesInfo.begin(); |
394 | return Error::success(); |
395 | } |
396 | |
397 | Expected<std::optional<StringRef>> |
398 | ReadBundleStart(MemoryBuffer &Input) final { |
399 | if (NextBundleInfo == BundlesInfo.end()) |
400 | return std::nullopt; |
401 | CurBundleInfo = NextBundleInfo++; |
402 | return CurBundleInfo->first(); |
403 | } |
404 | |
405 | Error ReadBundleEnd(MemoryBuffer &Input) final { |
406 | assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!" ); |
407 | return Error::success(); |
408 | } |
409 | |
410 | Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
411 | assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!" ); |
412 | StringRef FC = Input.getBuffer(); |
413 | OS.write(Ptr: FC.data() + CurBundleInfo->second.Offset, |
414 | Size: CurBundleInfo->second.Size); |
415 | return Error::success(); |
416 | } |
417 | |
418 | Error WriteHeader(raw_ostream &OS, |
419 | ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final { |
420 | |
421 | // Compute size of the header. |
422 | uint64_t = 0; |
423 | |
424 | HeaderSize += sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1; |
425 | HeaderSize += 8; // Number of Bundles |
426 | |
427 | for (auto &T : BundlerConfig.TargetNames) { |
428 | HeaderSize += 3 * 8; // Bundle offset, Size of bundle and size of triple. |
429 | HeaderSize += T.size(); // The triple. |
430 | } |
431 | |
432 | // Write to the buffer the header. |
433 | OS << OFFLOAD_BUNDLER_MAGIC_STR; |
434 | |
435 | Write8byteIntegerToBuffer(OS, Val: BundlerConfig.TargetNames.size()); |
436 | |
437 | unsigned Idx = 0; |
438 | for (auto &T : BundlerConfig.TargetNames) { |
439 | MemoryBuffer &MB = *Inputs[Idx++]; |
440 | HeaderSize = alignTo(Value: HeaderSize, Align: BundlerConfig.BundleAlignment); |
441 | // Bundle offset. |
442 | Write8byteIntegerToBuffer(OS, Val: HeaderSize); |
443 | // Size of the bundle (adds to the next bundle's offset) |
444 | Write8byteIntegerToBuffer(OS, Val: MB.getBufferSize()); |
445 | BundlesInfo[T] = BinaryBundleInfo(MB.getBufferSize(), HeaderSize); |
446 | HeaderSize += MB.getBufferSize(); |
447 | // Size of the triple |
448 | Write8byteIntegerToBuffer(OS, Val: T.size()); |
449 | // Triple |
450 | OS << T; |
451 | } |
452 | return Error::success(); |
453 | } |
454 | |
455 | Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final { |
456 | CurWriteBundleTarget = TargetTriple.str(); |
457 | return Error::success(); |
458 | } |
459 | |
460 | Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final { |
461 | return Error::success(); |
462 | } |
463 | |
464 | Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
465 | auto BI = BundlesInfo[CurWriteBundleTarget]; |
466 | |
467 | // Pad with 0 to reach specified offset. |
468 | size_t CurrentPos = OS.tell(); |
469 | size_t PaddingSize = BI.Offset > CurrentPos ? BI.Offset - CurrentPos : 0; |
470 | for (size_t I = 0; I < PaddingSize; ++I) |
471 | OS.write(C: '\0'); |
472 | assert(OS.tell() == BI.Offset); |
473 | |
474 | OS.write(Ptr: Input.getBufferStart(), Size: Input.getBufferSize()); |
475 | |
476 | return Error::success(); |
477 | } |
478 | }; |
479 | |
480 | // This class implements a list of temporary files that are removed upon |
481 | // object destruction. |
482 | class TempFileHandlerRAII { |
483 | public: |
484 | ~TempFileHandlerRAII() { |
485 | for (const auto &File : Files) |
486 | sys::fs::remove(path: File); |
487 | } |
488 | |
489 | // Creates temporary file with given contents. |
490 | Expected<StringRef> Create(std::optional<ArrayRef<char>> Contents) { |
491 | SmallString<128u> File; |
492 | if (std::error_code EC = |
493 | sys::fs::createTemporaryFile(Prefix: "clang-offload-bundler" , Suffix: "tmp" , ResultPath&: File)) |
494 | return createFileError(F: File, EC); |
495 | Files.push_front(val: File); |
496 | |
497 | if (Contents) { |
498 | std::error_code EC; |
499 | raw_fd_ostream OS(File, EC); |
500 | if (EC) |
501 | return createFileError(F: File, EC); |
502 | OS.write(Ptr: Contents->data(), Size: Contents->size()); |
503 | } |
504 | return Files.front().str(); |
505 | } |
506 | |
507 | private: |
508 | std::forward_list<SmallString<128u>> Files; |
509 | }; |
510 | |
511 | /// Handler for object files. The bundles are organized by sections with a |
512 | /// designated name. |
513 | /// |
514 | /// To unbundle, we just copy the contents of the designated section. |
515 | class ObjectFileHandler final : public FileHandler { |
516 | |
517 | /// The object file we are currently dealing with. |
518 | std::unique_ptr<ObjectFile> Obj; |
519 | |
520 | /// Return the input file contents. |
521 | StringRef getInputFileContents() const { return Obj->getData(); } |
522 | |
523 | /// Return bundle name (<kind>-<triple>) if the provided section is an offload |
524 | /// section. |
525 | static Expected<std::optional<StringRef>> |
526 | IsOffloadSection(SectionRef CurSection) { |
527 | Expected<StringRef> NameOrErr = CurSection.getName(); |
528 | if (!NameOrErr) |
529 | return NameOrErr.takeError(); |
530 | |
531 | // If it does not start with the reserved suffix, just skip this section. |
532 | if (llvm::identify_magic(magic: *NameOrErr) != llvm::file_magic::offload_bundle) |
533 | return std::nullopt; |
534 | |
535 | // Return the triple that is right after the reserved prefix. |
536 | return NameOrErr->substr(Start: sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); |
537 | } |
538 | |
539 | /// Total number of inputs. |
540 | unsigned NumberOfInputs = 0; |
541 | |
542 | /// Total number of processed inputs, i.e, inputs that were already |
543 | /// read from the buffers. |
544 | unsigned NumberOfProcessedInputs = 0; |
545 | |
546 | /// Iterator of the current and next section. |
547 | section_iterator CurrentSection; |
548 | section_iterator NextSection; |
549 | |
550 | /// Configuration options and arrays for this bundler job |
551 | const OffloadBundlerConfig &BundlerConfig; |
552 | |
553 | public: |
554 | // TODO: Add error checking from ClangOffloadBundler.cpp |
555 | ObjectFileHandler(std::unique_ptr<ObjectFile> ObjIn, |
556 | const OffloadBundlerConfig &BC) |
557 | : Obj(std::move(ObjIn)), CurrentSection(Obj->section_begin()), |
558 | NextSection(Obj->section_begin()), BundlerConfig(BC) {} |
559 | |
560 | ~ObjectFileHandler() final {} |
561 | |
562 | Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } |
563 | |
564 | Expected<std::optional<StringRef>> |
565 | ReadBundleStart(MemoryBuffer &Input) final { |
566 | while (NextSection != Obj->section_end()) { |
567 | CurrentSection = NextSection; |
568 | ++NextSection; |
569 | |
570 | // Check if the current section name starts with the reserved prefix. If |
571 | // so, return the triple. |
572 | Expected<std::optional<StringRef>> TripleOrErr = |
573 | IsOffloadSection(CurSection: *CurrentSection); |
574 | if (!TripleOrErr) |
575 | return TripleOrErr.takeError(); |
576 | if (*TripleOrErr) |
577 | return **TripleOrErr; |
578 | } |
579 | return std::nullopt; |
580 | } |
581 | |
582 | Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); } |
583 | |
584 | Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
585 | Expected<StringRef> ContentOrErr = CurrentSection->getContents(); |
586 | if (!ContentOrErr) |
587 | return ContentOrErr.takeError(); |
588 | StringRef Content = *ContentOrErr; |
589 | |
590 | // Copy fat object contents to the output when extracting host bundle. |
591 | std::string ModifiedContent; |
592 | if (Content.size() == 1u && Content.front() == 0) { |
593 | auto HostBundleOrErr = getHostBundle( |
594 | Input: StringRef(Input.getBufferStart(), Input.getBufferSize())); |
595 | if (!HostBundleOrErr) |
596 | return HostBundleOrErr.takeError(); |
597 | |
598 | ModifiedContent = std::move(*HostBundleOrErr); |
599 | Content = ModifiedContent; |
600 | } |
601 | |
602 | OS.write(Ptr: Content.data(), Size: Content.size()); |
603 | return Error::success(); |
604 | } |
605 | |
606 | Error WriteHeader(raw_ostream &OS, |
607 | ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final { |
608 | assert(BundlerConfig.HostInputIndex != ~0u && |
609 | "Host input index not defined." ); |
610 | |
611 | // Record number of inputs. |
612 | NumberOfInputs = Inputs.size(); |
613 | return Error::success(); |
614 | } |
615 | |
616 | Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final { |
617 | ++NumberOfProcessedInputs; |
618 | return Error::success(); |
619 | } |
620 | |
621 | Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final { |
622 | return Error::success(); |
623 | } |
624 | |
625 | Error finalizeOutputFile() final { |
626 | assert(NumberOfProcessedInputs <= NumberOfInputs && |
627 | "Processing more inputs that actually exist!" ); |
628 | assert(BundlerConfig.HostInputIndex != ~0u && |
629 | "Host input index not defined." ); |
630 | |
631 | // If this is not the last output, we don't have to do anything. |
632 | if (NumberOfProcessedInputs != NumberOfInputs) |
633 | return Error::success(); |
634 | |
635 | // We will use llvm-objcopy to add target objects sections to the output |
636 | // fat object. These sections should have 'exclude' flag set which tells |
637 | // link editor to remove them from linker inputs when linking executable or |
638 | // shared library. |
639 | |
640 | assert(BundlerConfig.ObjcopyPath != "" && |
641 | "llvm-objcopy path not specified" ); |
642 | |
643 | // Temporary files that need to be removed. |
644 | TempFileHandlerRAII TempFiles; |
645 | |
646 | // Compose llvm-objcopy command line for add target objects' sections with |
647 | // appropriate flags. |
648 | BumpPtrAllocator Alloc; |
649 | StringSaver SS{Alloc}; |
650 | SmallVector<StringRef, 8u> ObjcopyArgs{"llvm-objcopy" }; |
651 | |
652 | for (unsigned I = 0; I < NumberOfInputs; ++I) { |
653 | StringRef InputFile = BundlerConfig.InputFileNames[I]; |
654 | if (I == BundlerConfig.HostInputIndex) { |
655 | // Special handling for the host bundle. We do not need to add a |
656 | // standard bundle for the host object since we are going to use fat |
657 | // object as a host object. Therefore use dummy contents (one zero byte) |
658 | // when creating section for the host bundle. |
659 | Expected<StringRef> TempFileOrErr = TempFiles.Create(Contents: ArrayRef<char>(0)); |
660 | if (!TempFileOrErr) |
661 | return TempFileOrErr.takeError(); |
662 | InputFile = *TempFileOrErr; |
663 | } |
664 | |
665 | ObjcopyArgs.push_back( |
666 | Elt: SS.save(S: Twine("--add-section=" ) + OFFLOAD_BUNDLER_MAGIC_STR + |
667 | BundlerConfig.TargetNames[I] + "=" + InputFile)); |
668 | ObjcopyArgs.push_back( |
669 | Elt: SS.save(S: Twine("--set-section-flags=" ) + OFFLOAD_BUNDLER_MAGIC_STR + |
670 | BundlerConfig.TargetNames[I] + "=readonly,exclude" )); |
671 | } |
672 | ObjcopyArgs.push_back(Elt: "--" ); |
673 | ObjcopyArgs.push_back( |
674 | Elt: BundlerConfig.InputFileNames[BundlerConfig.HostInputIndex]); |
675 | ObjcopyArgs.push_back(Elt: BundlerConfig.OutputFileNames.front()); |
676 | |
677 | if (Error Err = executeObjcopy(Objcopy: BundlerConfig.ObjcopyPath, Args: ObjcopyArgs)) |
678 | return Err; |
679 | |
680 | return Error::success(); |
681 | } |
682 | |
683 | Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
684 | return Error::success(); |
685 | } |
686 | |
687 | private: |
688 | Error executeObjcopy(StringRef Objcopy, ArrayRef<StringRef> Args) { |
689 | // If the user asked for the commands to be printed out, we do that |
690 | // instead of executing it. |
691 | if (BundlerConfig.PrintExternalCommands) { |
692 | errs() << "\"" << Objcopy << "\"" ; |
693 | for (StringRef Arg : drop_begin(RangeOrContainer&: Args, N: 1)) |
694 | errs() << " \"" << Arg << "\"" ; |
695 | errs() << "\n" ; |
696 | } else { |
697 | if (sys::ExecuteAndWait(Program: Objcopy, Args)) |
698 | return createStringError(EC: inconvertibleErrorCode(), |
699 | Msg: "'llvm-objcopy' tool failed" ); |
700 | } |
701 | return Error::success(); |
702 | } |
703 | |
704 | Expected<std::string> getHostBundle(StringRef Input) { |
705 | TempFileHandlerRAII TempFiles; |
706 | |
707 | auto ModifiedObjPathOrErr = TempFiles.Create(Contents: std::nullopt); |
708 | if (!ModifiedObjPathOrErr) |
709 | return ModifiedObjPathOrErr.takeError(); |
710 | StringRef ModifiedObjPath = *ModifiedObjPathOrErr; |
711 | |
712 | BumpPtrAllocator Alloc; |
713 | StringSaver SS{Alloc}; |
714 | SmallVector<StringRef, 16> ObjcopyArgs{"llvm-objcopy" }; |
715 | |
716 | ObjcopyArgs.push_back(Elt: "--regex" ); |
717 | ObjcopyArgs.push_back(Elt: "--remove-section=__CLANG_OFFLOAD_BUNDLE__.*" ); |
718 | ObjcopyArgs.push_back(Elt: "--" ); |
719 | |
720 | StringRef ObjcopyInputFileName; |
721 | // When unbundling an archive, the content of each object file in the |
722 | // archive is passed to this function by parameter Input, which is different |
723 | // from the content of the original input archive file, therefore it needs |
724 | // to be saved to a temporary file before passed to llvm-objcopy. Otherwise, |
725 | // Input is the same as the content of the original input file, therefore |
726 | // temporary file is not needed. |
727 | if (StringRef(BundlerConfig.FilesType).starts_with(Prefix: "a" )) { |
728 | auto InputFileOrErr = |
729 | TempFiles.Create(Contents: ArrayRef<char>(Input.data(), Input.size())); |
730 | if (!InputFileOrErr) |
731 | return InputFileOrErr.takeError(); |
732 | ObjcopyInputFileName = *InputFileOrErr; |
733 | } else |
734 | ObjcopyInputFileName = BundlerConfig.InputFileNames.front(); |
735 | |
736 | ObjcopyArgs.push_back(Elt: ObjcopyInputFileName); |
737 | ObjcopyArgs.push_back(Elt: ModifiedObjPath); |
738 | |
739 | if (Error Err = executeObjcopy(Objcopy: BundlerConfig.ObjcopyPath, Args: ObjcopyArgs)) |
740 | return std::move(Err); |
741 | |
742 | auto BufOrErr = MemoryBuffer::getFile(Filename: ModifiedObjPath); |
743 | if (!BufOrErr) |
744 | return createStringError(EC: BufOrErr.getError(), |
745 | Msg: "Failed to read back the modified object file" ); |
746 | |
747 | return BufOrErr->get()->getBuffer().str(); |
748 | } |
749 | }; |
750 | |
751 | /// Handler for text files. The bundled file will have the following format. |
752 | /// |
753 | /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" |
754 | /// Bundle 1 |
755 | /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" |
756 | /// ... |
757 | /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" |
758 | /// Bundle N |
759 | /// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple" |
760 | class TextFileHandler final : public FileHandler { |
761 | /// String that begins a line comment. |
762 | StringRef Comment; |
763 | |
764 | /// String that initiates a bundle. |
765 | std::string BundleStartString; |
766 | |
767 | /// String that closes a bundle. |
768 | std::string BundleEndString; |
769 | |
770 | /// Number of chars read from input. |
771 | size_t ReadChars = 0u; |
772 | |
773 | protected: |
774 | Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } |
775 | |
776 | Expected<std::optional<StringRef>> |
777 | ReadBundleStart(MemoryBuffer &Input) final { |
778 | StringRef FC = Input.getBuffer(); |
779 | |
780 | // Find start of the bundle. |
781 | ReadChars = FC.find(Str: BundleStartString, From: ReadChars); |
782 | if (ReadChars == FC.npos) |
783 | return std::nullopt; |
784 | |
785 | // Get position of the triple. |
786 | size_t TripleStart = ReadChars = ReadChars + BundleStartString.size(); |
787 | |
788 | // Get position that closes the triple. |
789 | size_t TripleEnd = ReadChars = FC.find(Str: "\n" , From: ReadChars); |
790 | if (TripleEnd == FC.npos) |
791 | return std::nullopt; |
792 | |
793 | // Next time we read after the new line. |
794 | ++ReadChars; |
795 | |
796 | return StringRef(&FC.data()[TripleStart], TripleEnd - TripleStart); |
797 | } |
798 | |
799 | Error ReadBundleEnd(MemoryBuffer &Input) final { |
800 | StringRef FC = Input.getBuffer(); |
801 | |
802 | // Read up to the next new line. |
803 | assert(FC[ReadChars] == '\n' && "The bundle should end with a new line." ); |
804 | |
805 | size_t TripleEnd = ReadChars = FC.find(Str: "\n" , From: ReadChars + 1); |
806 | if (TripleEnd != FC.npos) |
807 | // Next time we read after the new line. |
808 | ++ReadChars; |
809 | |
810 | return Error::success(); |
811 | } |
812 | |
813 | Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
814 | StringRef FC = Input.getBuffer(); |
815 | size_t BundleStart = ReadChars; |
816 | |
817 | // Find end of the bundle. |
818 | size_t BundleEnd = ReadChars = FC.find(Str: BundleEndString, From: ReadChars); |
819 | |
820 | StringRef Bundle(&FC.data()[BundleStart], BundleEnd - BundleStart); |
821 | OS << Bundle; |
822 | |
823 | return Error::success(); |
824 | } |
825 | |
826 | Error WriteHeader(raw_ostream &OS, |
827 | ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final { |
828 | return Error::success(); |
829 | } |
830 | |
831 | Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final { |
832 | OS << BundleStartString << TargetTriple << "\n" ; |
833 | return Error::success(); |
834 | } |
835 | |
836 | Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final { |
837 | OS << BundleEndString << TargetTriple << "\n" ; |
838 | return Error::success(); |
839 | } |
840 | |
841 | Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final { |
842 | OS << Input.getBuffer(); |
843 | return Error::success(); |
844 | } |
845 | |
846 | public: |
847 | TextFileHandler(StringRef ) : Comment(Comment), ReadChars(0) { |
848 | BundleStartString = |
849 | "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__START__ " ; |
850 | BundleEndString = |
851 | "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__END__ " ; |
852 | } |
853 | |
854 | Error listBundleIDsCallback(MemoryBuffer &Input, |
855 | const BundleInfo &Info) final { |
856 | // TODO: To list bundle IDs in a bundled text file we need to go through |
857 | // all bundles. The format of bundled text file may need to include a |
858 | // header if the performance of listing bundle IDs of bundled text file is |
859 | // important. |
860 | ReadChars = Input.getBuffer().find(Str: BundleEndString, From: ReadChars); |
861 | if (Error Err = ReadBundleEnd(Input)) |
862 | return Err; |
863 | return Error::success(); |
864 | } |
865 | }; |
866 | } // namespace |
867 | |
868 | /// Return an appropriate object file handler. We use the specific object |
869 | /// handler if we know how to deal with that format, otherwise we use a default |
870 | /// binary file handler. |
871 | static std::unique_ptr<FileHandler> |
872 | CreateObjectFileHandler(MemoryBuffer &FirstInput, |
873 | const OffloadBundlerConfig &BundlerConfig) { |
874 | // Check if the input file format is one that we know how to deal with. |
875 | Expected<std::unique_ptr<Binary>> BinaryOrErr = createBinary(Source: FirstInput); |
876 | |
877 | // We only support regular object files. If failed to open the input as a |
878 | // known binary or this is not an object file use the default binary handler. |
879 | if (errorToBool(Err: BinaryOrErr.takeError()) || !isa<ObjectFile>(Val: *BinaryOrErr)) |
880 | return std::make_unique<BinaryFileHandler>(args: BundlerConfig); |
881 | |
882 | // Otherwise create an object file handler. The handler will be owned by the |
883 | // client of this function. |
884 | return std::make_unique<ObjectFileHandler>( |
885 | args: std::unique_ptr<ObjectFile>(cast<ObjectFile>(Val: BinaryOrErr->release())), |
886 | args: BundlerConfig); |
887 | } |
888 | |
889 | /// Return an appropriate handler given the input files and options. |
890 | static Expected<std::unique_ptr<FileHandler>> |
891 | CreateFileHandler(MemoryBuffer &FirstInput, |
892 | const OffloadBundlerConfig &BundlerConfig) { |
893 | std::string FilesType = BundlerConfig.FilesType; |
894 | |
895 | if (FilesType == "i" ) |
896 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "//" ); |
897 | if (FilesType == "ii" ) |
898 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "//" ); |
899 | if (FilesType == "cui" ) |
900 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "//" ); |
901 | if (FilesType == "hipi" ) |
902 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "//" ); |
903 | // TODO: `.d` should be eventually removed once `-M` and its variants are |
904 | // handled properly in offload compilation. |
905 | if (FilesType == "d" ) |
906 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "#" ); |
907 | if (FilesType == "ll" ) |
908 | return std::make_unique<TextFileHandler>(/*Comment=*/args: ";" ); |
909 | if (FilesType == "bc" ) |
910 | return std::make_unique<BinaryFileHandler>(args: BundlerConfig); |
911 | if (FilesType == "s" ) |
912 | return std::make_unique<TextFileHandler>(/*Comment=*/args: "#" ); |
913 | if (FilesType == "o" ) |
914 | return CreateObjectFileHandler(FirstInput, BundlerConfig); |
915 | if (FilesType == "a" ) |
916 | return CreateObjectFileHandler(FirstInput, BundlerConfig); |
917 | if (FilesType == "gch" ) |
918 | return std::make_unique<BinaryFileHandler>(args: BundlerConfig); |
919 | if (FilesType == "ast" ) |
920 | return std::make_unique<BinaryFileHandler>(args: BundlerConfig); |
921 | |
922 | return createStringError(EC: errc::invalid_argument, |
923 | S: "'" + FilesType + "': invalid file type specified" ); |
924 | } |
925 | |
926 | OffloadBundlerConfig::OffloadBundlerConfig() { |
927 | if (llvm::compression::zstd::isAvailable()) { |
928 | CompressionFormat = llvm::compression::Format::Zstd; |
929 | // Compression level 3 is usually sufficient for zstd since long distance |
930 | // matching is enabled. |
931 | CompressionLevel = 3; |
932 | } else if (llvm::compression::zlib::isAvailable()) { |
933 | CompressionFormat = llvm::compression::Format::Zlib; |
934 | // Use default level for zlib since higher level does not have significant |
935 | // improvement. |
936 | CompressionLevel = llvm::compression::zlib::DefaultCompression; |
937 | } |
938 | auto IgnoreEnvVarOpt = |
939 | llvm::sys::Process::GetEnv(name: "OFFLOAD_BUNDLER_IGNORE_ENV_VAR" ); |
940 | if (IgnoreEnvVarOpt.has_value() && IgnoreEnvVarOpt.value() == "1" ) |
941 | return; |
942 | |
943 | auto VerboseEnvVarOpt = llvm::sys::Process::GetEnv(name: "OFFLOAD_BUNDLER_VERBOSE" ); |
944 | if (VerboseEnvVarOpt.has_value()) |
945 | Verbose = VerboseEnvVarOpt.value() == "1" ; |
946 | |
947 | auto CompressEnvVarOpt = |
948 | llvm::sys::Process::GetEnv(name: "OFFLOAD_BUNDLER_COMPRESS" ); |
949 | if (CompressEnvVarOpt.has_value()) |
950 | Compress = CompressEnvVarOpt.value() == "1" ; |
951 | |
952 | auto CompressionLevelEnvVarOpt = |
953 | llvm::sys::Process::GetEnv(name: "OFFLOAD_BUNDLER_COMPRESSION_LEVEL" ); |
954 | if (CompressionLevelEnvVarOpt.has_value()) { |
955 | llvm::StringRef CompressionLevelStr = CompressionLevelEnvVarOpt.value(); |
956 | int Level; |
957 | if (!CompressionLevelStr.getAsInteger(Radix: 10, Result&: Level)) |
958 | CompressionLevel = Level; |
959 | else |
960 | llvm::errs() |
961 | << "Warning: Invalid value for OFFLOAD_BUNDLER_COMPRESSION_LEVEL: " |
962 | << CompressionLevelStr.str() << ". Ignoring it.\n" ; |
963 | } |
964 | } |
965 | |
966 | // Utility function to format numbers with commas |
967 | static std::string formatWithCommas(unsigned long long Value) { |
968 | std::string Num = std::to_string(val: Value); |
969 | int InsertPosition = Num.length() - 3; |
970 | while (InsertPosition > 0) { |
971 | Num.insert(pos: InsertPosition, s: "," ); |
972 | InsertPosition -= 3; |
973 | } |
974 | return Num; |
975 | } |
976 | |
977 | llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>> |
978 | CompressedOffloadBundle::compress(llvm::compression::Params P, |
979 | const llvm::MemoryBuffer &Input, |
980 | bool Verbose) { |
981 | if (!llvm::compression::zstd::isAvailable() && |
982 | !llvm::compression::zlib::isAvailable()) |
983 | return createStringError(EC: llvm::inconvertibleErrorCode(), |
984 | Msg: "Compression not supported" ); |
985 | |
986 | llvm::Timer HashTimer("Hash Calculation Timer" , "Hash calculation time" , |
987 | ClangOffloadBundlerTimerGroup); |
988 | if (Verbose) |
989 | HashTimer.startTimer(); |
990 | llvm::MD5 Hash; |
991 | llvm::MD5::MD5Result Result; |
992 | Hash.update(Str: Input.getBuffer()); |
993 | Hash.final(Result); |
994 | uint64_t TruncatedHash = Result.low(); |
995 | if (Verbose) |
996 | HashTimer.stopTimer(); |
997 | |
998 | SmallVector<uint8_t, 0> CompressedBuffer; |
999 | auto BufferUint8 = llvm::ArrayRef<uint8_t>( |
1000 | reinterpret_cast<const uint8_t *>(Input.getBuffer().data()), |
1001 | Input.getBuffer().size()); |
1002 | |
1003 | llvm::Timer CompressTimer("Compression Timer" , "Compression time" , |
1004 | ClangOffloadBundlerTimerGroup); |
1005 | if (Verbose) |
1006 | CompressTimer.startTimer(); |
1007 | llvm::compression::compress(P, Input: BufferUint8, Output&: CompressedBuffer); |
1008 | if (Verbose) |
1009 | CompressTimer.stopTimer(); |
1010 | |
1011 | uint16_t CompressionMethod = static_cast<uint16_t>(P.format); |
1012 | uint32_t UncompressedSize = Input.getBuffer().size(); |
1013 | uint32_t TotalFileSize = MagicNumber.size() + sizeof(TotalFileSize) + |
1014 | sizeof(Version) + sizeof(CompressionMethod) + |
1015 | sizeof(UncompressedSize) + sizeof(TruncatedHash) + |
1016 | CompressedBuffer.size(); |
1017 | |
1018 | SmallVector<char, 0> FinalBuffer; |
1019 | llvm::raw_svector_ostream OS(FinalBuffer); |
1020 | OS << MagicNumber; |
1021 | OS.write(Ptr: reinterpret_cast<const char *>(&Version), Size: sizeof(Version)); |
1022 | OS.write(Ptr: reinterpret_cast<const char *>(&CompressionMethod), |
1023 | Size: sizeof(CompressionMethod)); |
1024 | OS.write(Ptr: reinterpret_cast<const char *>(&TotalFileSize), |
1025 | Size: sizeof(TotalFileSize)); |
1026 | OS.write(Ptr: reinterpret_cast<const char *>(&UncompressedSize), |
1027 | Size: sizeof(UncompressedSize)); |
1028 | OS.write(Ptr: reinterpret_cast<const char *>(&TruncatedHash), |
1029 | Size: sizeof(TruncatedHash)); |
1030 | OS.write(Ptr: reinterpret_cast<const char *>(CompressedBuffer.data()), |
1031 | Size: CompressedBuffer.size()); |
1032 | |
1033 | if (Verbose) { |
1034 | auto MethodUsed = |
1035 | P.format == llvm::compression::Format::Zstd ? "zstd" : "zlib" ; |
1036 | double CompressionRate = |
1037 | static_cast<double>(UncompressedSize) / CompressedBuffer.size(); |
1038 | double CompressionTimeSeconds = CompressTimer.getTotalTime().getWallTime(); |
1039 | double CompressionSpeedMBs = |
1040 | (UncompressedSize / (1024.0 * 1024.0)) / CompressionTimeSeconds; |
1041 | |
1042 | llvm::errs() << "Compressed bundle format version: " << Version << "\n" |
1043 | << "Total file size (including headers): " |
1044 | << formatWithCommas(Value: TotalFileSize) << " bytes\n" |
1045 | << "Compression method used: " << MethodUsed << "\n" |
1046 | << "Compression level: " << P.level << "\n" |
1047 | << "Binary size before compression: " |
1048 | << formatWithCommas(Value: UncompressedSize) << " bytes\n" |
1049 | << "Binary size after compression: " |
1050 | << formatWithCommas(Value: CompressedBuffer.size()) << " bytes\n" |
1051 | << "Compression rate: " |
1052 | << llvm::format(Fmt: "%.2lf" , Vals: CompressionRate) << "\n" |
1053 | << "Compression ratio: " |
1054 | << llvm::format(Fmt: "%.2lf%%" , Vals: 100.0 / CompressionRate) << "\n" |
1055 | << "Compression speed: " |
1056 | << llvm::format(Fmt: "%.2lf MB/s" , Vals: CompressionSpeedMBs) << "\n" |
1057 | << "Truncated MD5 hash: " |
1058 | << llvm::format_hex(N: TruncatedHash, Width: 16) << "\n" ; |
1059 | } |
1060 | return llvm::MemoryBuffer::getMemBufferCopy( |
1061 | InputData: llvm::StringRef(FinalBuffer.data(), FinalBuffer.size())); |
1062 | } |
1063 | |
1064 | llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>> |
1065 | CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input, |
1066 | bool Verbose) { |
1067 | |
1068 | StringRef Blob = Input.getBuffer(); |
1069 | |
1070 | if (Blob.size() < V1HeaderSize) |
1071 | return llvm::MemoryBuffer::getMemBufferCopy(InputData: Blob); |
1072 | |
1073 | if (llvm::identify_magic(magic: Blob) != |
1074 | llvm::file_magic::offload_bundle_compressed) { |
1075 | if (Verbose) |
1076 | llvm::errs() << "Uncompressed bundle.\n" ; |
1077 | return llvm::MemoryBuffer::getMemBufferCopy(InputData: Blob); |
1078 | } |
1079 | |
1080 | size_t CurrentOffset = MagicSize; |
1081 | |
1082 | uint16_t ThisVersion; |
1083 | memcpy(dest: &ThisVersion, src: Blob.data() + CurrentOffset, n: sizeof(uint16_t)); |
1084 | CurrentOffset += VersionFieldSize; |
1085 | |
1086 | uint16_t CompressionMethod; |
1087 | memcpy(dest: &CompressionMethod, src: Blob.data() + CurrentOffset, n: sizeof(uint16_t)); |
1088 | CurrentOffset += MethodFieldSize; |
1089 | |
1090 | uint32_t TotalFileSize; |
1091 | if (ThisVersion >= 2) { |
1092 | if (Blob.size() < V2HeaderSize) |
1093 | return createStringError(EC: inconvertibleErrorCode(), |
1094 | Msg: "Compressed bundle header size too small" ); |
1095 | memcpy(dest: &TotalFileSize, src: Blob.data() + CurrentOffset, n: sizeof(uint32_t)); |
1096 | CurrentOffset += FileSizeFieldSize; |
1097 | } |
1098 | |
1099 | uint32_t UncompressedSize; |
1100 | memcpy(dest: &UncompressedSize, src: Blob.data() + CurrentOffset, n: sizeof(uint32_t)); |
1101 | CurrentOffset += UncompressedSizeFieldSize; |
1102 | |
1103 | uint64_t StoredHash; |
1104 | memcpy(dest: &StoredHash, src: Blob.data() + CurrentOffset, n: sizeof(uint64_t)); |
1105 | CurrentOffset += HashFieldSize; |
1106 | |
1107 | llvm::compression::Format CompressionFormat; |
1108 | if (CompressionMethod == |
1109 | static_cast<uint16_t>(llvm::compression::Format::Zlib)) |
1110 | CompressionFormat = llvm::compression::Format::Zlib; |
1111 | else if (CompressionMethod == |
1112 | static_cast<uint16_t>(llvm::compression::Format::Zstd)) |
1113 | CompressionFormat = llvm::compression::Format::Zstd; |
1114 | else |
1115 | return createStringError(EC: inconvertibleErrorCode(), |
1116 | Msg: "Unknown compressing method" ); |
1117 | |
1118 | llvm::Timer DecompressTimer("Decompression Timer" , "Decompression time" , |
1119 | ClangOffloadBundlerTimerGroup); |
1120 | if (Verbose) |
1121 | DecompressTimer.startTimer(); |
1122 | |
1123 | SmallVector<uint8_t, 0> DecompressedData; |
1124 | StringRef CompressedData = Blob.substr(Start: CurrentOffset); |
1125 | if (llvm::Error DecompressionError = llvm::compression::decompress( |
1126 | F: CompressionFormat, Input: llvm::arrayRefFromStringRef(Input: CompressedData), |
1127 | Output&: DecompressedData, UncompressedSize)) |
1128 | return createStringError(EC: inconvertibleErrorCode(), |
1129 | S: "Could not decompress embedded file contents: " + |
1130 | llvm::toString(E: std::move(DecompressionError))); |
1131 | |
1132 | if (Verbose) { |
1133 | DecompressTimer.stopTimer(); |
1134 | |
1135 | double DecompressionTimeSeconds = |
1136 | DecompressTimer.getTotalTime().getWallTime(); |
1137 | |
1138 | // Recalculate MD5 hash for integrity check |
1139 | llvm::Timer HashRecalcTimer("Hash Recalculation Timer" , |
1140 | "Hash recalculation time" , |
1141 | ClangOffloadBundlerTimerGroup); |
1142 | HashRecalcTimer.startTimer(); |
1143 | llvm::MD5 Hash; |
1144 | llvm::MD5::MD5Result Result; |
1145 | Hash.update(Data: llvm::ArrayRef<uint8_t>(DecompressedData.data(), |
1146 | DecompressedData.size())); |
1147 | Hash.final(Result); |
1148 | uint64_t RecalculatedHash = Result.low(); |
1149 | HashRecalcTimer.stopTimer(); |
1150 | bool HashMatch = (StoredHash == RecalculatedHash); |
1151 | |
1152 | double CompressionRate = |
1153 | static_cast<double>(UncompressedSize) / CompressedData.size(); |
1154 | double DecompressionSpeedMBs = |
1155 | (UncompressedSize / (1024.0 * 1024.0)) / DecompressionTimeSeconds; |
1156 | |
1157 | llvm::errs() << "Compressed bundle format version: " << ThisVersion << "\n" ; |
1158 | if (ThisVersion >= 2) |
1159 | llvm::errs() << "Total file size (from header): " |
1160 | << formatWithCommas(Value: TotalFileSize) << " bytes\n" ; |
1161 | llvm::errs() << "Decompression method: " |
1162 | << (CompressionFormat == llvm::compression::Format::Zlib |
1163 | ? "zlib" |
1164 | : "zstd" ) |
1165 | << "\n" |
1166 | << "Size before decompression: " |
1167 | << formatWithCommas(Value: CompressedData.size()) << " bytes\n" |
1168 | << "Size after decompression: " |
1169 | << formatWithCommas(Value: UncompressedSize) << " bytes\n" |
1170 | << "Compression rate: " |
1171 | << llvm::format(Fmt: "%.2lf" , Vals: CompressionRate) << "\n" |
1172 | << "Compression ratio: " |
1173 | << llvm::format(Fmt: "%.2lf%%" , Vals: 100.0 / CompressionRate) << "\n" |
1174 | << "Decompression speed: " |
1175 | << llvm::format(Fmt: "%.2lf MB/s" , Vals: DecompressionSpeedMBs) << "\n" |
1176 | << "Stored hash: " << llvm::format_hex(N: StoredHash, Width: 16) << "\n" |
1177 | << "Recalculated hash: " |
1178 | << llvm::format_hex(N: RecalculatedHash, Width: 16) << "\n" |
1179 | << "Hashes match: " << (HashMatch ? "Yes" : "No" ) << "\n" ; |
1180 | } |
1181 | |
1182 | return llvm::MemoryBuffer::getMemBufferCopy( |
1183 | InputData: llvm::toStringRef(Input: DecompressedData)); |
1184 | } |
1185 | |
1186 | // List bundle IDs. Return true if an error was found. |
1187 | Error OffloadBundler::ListBundleIDsInFile( |
1188 | StringRef InputFileName, const OffloadBundlerConfig &BundlerConfig) { |
1189 | // Open Input file. |
1190 | ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = |
1191 | MemoryBuffer::getFileOrSTDIN(Filename: InputFileName); |
1192 | if (std::error_code EC = CodeOrErr.getError()) |
1193 | return createFileError(F: InputFileName, EC); |
1194 | |
1195 | // Decompress the input if necessary. |
1196 | Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr = |
1197 | CompressedOffloadBundle::decompress(Input: **CodeOrErr, Verbose: BundlerConfig.Verbose); |
1198 | if (!DecompressedBufferOrErr) |
1199 | return createStringError( |
1200 | EC: inconvertibleErrorCode(), |
1201 | S: "Failed to decompress input: " + |
1202 | llvm::toString(E: DecompressedBufferOrErr.takeError())); |
1203 | |
1204 | MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr; |
1205 | |
1206 | // Select the right files handler. |
1207 | Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = |
1208 | CreateFileHandler(FirstInput&: DecompressedInput, BundlerConfig); |
1209 | if (!FileHandlerOrErr) |
1210 | return FileHandlerOrErr.takeError(); |
1211 | |
1212 | std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr; |
1213 | assert(FH); |
1214 | return FH->listBundleIDs(Input&: DecompressedInput); |
1215 | } |
1216 | |
1217 | /// @brief Checks if a code object \p CodeObjectInfo is compatible with a given |
1218 | /// target \p TargetInfo. |
1219 | /// @link https://clang.llvm.org/docs/ClangOffloadBundler.html#bundle-entry-id |
1220 | bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo, |
1221 | const OffloadTargetInfo &TargetInfo) { |
1222 | |
1223 | // Compatible in case of exact match. |
1224 | if (CodeObjectInfo == TargetInfo) { |
1225 | DEBUG_WITH_TYPE("CodeObjectCompatibility" , |
1226 | dbgs() << "Compatible: Exact match: \t[CodeObject: " |
1227 | << CodeObjectInfo.str() |
1228 | << "]\t:\t[Target: " << TargetInfo.str() << "]\n" ); |
1229 | return true; |
1230 | } |
1231 | |
1232 | // Incompatible if Kinds or Triples mismatch. |
1233 | if (!CodeObjectInfo.isOffloadKindCompatible(TargetOffloadKind: TargetInfo.OffloadKind) || |
1234 | !CodeObjectInfo.Triple.isCompatibleWith(Other: TargetInfo.Triple)) { |
1235 | DEBUG_WITH_TYPE( |
1236 | "CodeObjectCompatibility" , |
1237 | dbgs() << "Incompatible: Kind/Triple mismatch \t[CodeObject: " |
1238 | << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() |
1239 | << "]\n" ); |
1240 | return false; |
1241 | } |
1242 | |
1243 | // Incompatible if Processors mismatch. |
1244 | llvm::StringMap<bool> CodeObjectFeatureMap, TargetFeatureMap; |
1245 | std::optional<StringRef> CodeObjectProc = clang::parseTargetID( |
1246 | T: CodeObjectInfo.Triple, OffloadArch: CodeObjectInfo.TargetID, FeatureMap: &CodeObjectFeatureMap); |
1247 | std::optional<StringRef> TargetProc = clang::parseTargetID( |
1248 | T: TargetInfo.Triple, OffloadArch: TargetInfo.TargetID, FeatureMap: &TargetFeatureMap); |
1249 | |
1250 | // Both TargetProc and CodeObjectProc can't be empty here. |
1251 | if (!TargetProc || !CodeObjectProc || |
1252 | CodeObjectProc.value() != TargetProc.value()) { |
1253 | DEBUG_WITH_TYPE("CodeObjectCompatibility" , |
1254 | dbgs() << "Incompatible: Processor mismatch \t[CodeObject: " |
1255 | << CodeObjectInfo.str() |
1256 | << "]\t:\t[Target: " << TargetInfo.str() << "]\n" ); |
1257 | return false; |
1258 | } |
1259 | |
1260 | // Incompatible if CodeObject has more features than Target, irrespective of |
1261 | // type or sign of features. |
1262 | if (CodeObjectFeatureMap.getNumItems() > TargetFeatureMap.getNumItems()) { |
1263 | DEBUG_WITH_TYPE("CodeObjectCompatibility" , |
1264 | dbgs() << "Incompatible: CodeObject has more features " |
1265 | "than target \t[CodeObject: " |
1266 | << CodeObjectInfo.str() |
1267 | << "]\t:\t[Target: " << TargetInfo.str() << "]\n" ); |
1268 | return false; |
1269 | } |
1270 | |
1271 | // Compatible if each target feature specified by target is compatible with |
1272 | // target feature of code object. The target feature is compatible if the |
1273 | // code object does not specify it (meaning Any), or if it specifies it |
1274 | // with the same value (meaning On or Off). |
1275 | for (const auto &CodeObjectFeature : CodeObjectFeatureMap) { |
1276 | auto TargetFeature = TargetFeatureMap.find(Key: CodeObjectFeature.getKey()); |
1277 | if (TargetFeature == TargetFeatureMap.end()) { |
1278 | DEBUG_WITH_TYPE( |
1279 | "CodeObjectCompatibility" , |
1280 | dbgs() |
1281 | << "Incompatible: Value of CodeObject's non-ANY feature is " |
1282 | "not matching with Target feature's ANY value \t[CodeObject: " |
1283 | << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() |
1284 | << "]\n" ); |
1285 | return false; |
1286 | } else if (TargetFeature->getValue() != CodeObjectFeature.getValue()) { |
1287 | DEBUG_WITH_TYPE( |
1288 | "CodeObjectCompatibility" , |
1289 | dbgs() << "Incompatible: Value of CodeObject's non-ANY feature is " |
1290 | "not matching with Target feature's non-ANY value " |
1291 | "\t[CodeObject: " |
1292 | << CodeObjectInfo.str() |
1293 | << "]\t:\t[Target: " << TargetInfo.str() << "]\n" ); |
1294 | return false; |
1295 | } |
1296 | } |
1297 | |
1298 | // CodeObject is compatible if all features of Target are: |
1299 | // - either, present in the Code Object's features map with the same sign, |
1300 | // - or, the feature is missing from CodeObjects's features map i.e. it is |
1301 | // set to ANY |
1302 | DEBUG_WITH_TYPE( |
1303 | "CodeObjectCompatibility" , |
1304 | dbgs() << "Compatible: Target IDs are compatible \t[CodeObject: " |
1305 | << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str() |
1306 | << "]\n" ); |
1307 | return true; |
1308 | } |
1309 | |
1310 | /// Bundle the files. Return true if an error was found. |
1311 | Error OffloadBundler::BundleFiles() { |
1312 | std::error_code EC; |
1313 | |
1314 | // Create a buffer to hold the content before compressing. |
1315 | SmallVector<char, 0> Buffer; |
1316 | llvm::raw_svector_ostream BufferStream(Buffer); |
1317 | |
1318 | // Open input files. |
1319 | SmallVector<std::unique_ptr<MemoryBuffer>, 8u> InputBuffers; |
1320 | InputBuffers.reserve(N: BundlerConfig.InputFileNames.size()); |
1321 | for (auto &I : BundlerConfig.InputFileNames) { |
1322 | ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = |
1323 | MemoryBuffer::getFileOrSTDIN(Filename: I); |
1324 | if (std::error_code EC = CodeOrErr.getError()) |
1325 | return createFileError(F: I, EC); |
1326 | InputBuffers.emplace_back(Args: std::move(*CodeOrErr)); |
1327 | } |
1328 | |
1329 | // Get the file handler. We use the host buffer as reference. |
1330 | assert((BundlerConfig.HostInputIndex != ~0u || BundlerConfig.AllowNoHost) && |
1331 | "Host input index undefined??" ); |
1332 | Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = CreateFileHandler( |
1333 | FirstInput&: *InputBuffers[BundlerConfig.AllowNoHost ? 0 |
1334 | : BundlerConfig.HostInputIndex], |
1335 | BundlerConfig); |
1336 | if (!FileHandlerOrErr) |
1337 | return FileHandlerOrErr.takeError(); |
1338 | |
1339 | std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr; |
1340 | assert(FH); |
1341 | |
1342 | // Write header. |
1343 | if (Error Err = FH->WriteHeader(OS&: BufferStream, Inputs: InputBuffers)) |
1344 | return Err; |
1345 | |
1346 | // Write all bundles along with the start/end markers. If an error was found |
1347 | // writing the end of the bundle component, abort the bundle writing. |
1348 | auto Input = InputBuffers.begin(); |
1349 | for (auto &Triple : BundlerConfig.TargetNames) { |
1350 | if (Error Err = FH->WriteBundleStart(OS&: BufferStream, TargetTriple: Triple)) |
1351 | return Err; |
1352 | if (Error Err = FH->WriteBundle(OS&: BufferStream, Input&: **Input)) |
1353 | return Err; |
1354 | if (Error Err = FH->WriteBundleEnd(OS&: BufferStream, TargetTriple: Triple)) |
1355 | return Err; |
1356 | ++Input; |
1357 | } |
1358 | |
1359 | raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), EC, |
1360 | sys::fs::OF_None); |
1361 | if (EC) |
1362 | return createFileError(F: BundlerConfig.OutputFileNames.front(), EC); |
1363 | |
1364 | SmallVector<char, 0> CompressedBuffer; |
1365 | if (BundlerConfig.Compress) { |
1366 | std::unique_ptr<llvm::MemoryBuffer> BufferMemory = |
1367 | llvm::MemoryBuffer::getMemBufferCopy( |
1368 | InputData: llvm::StringRef(Buffer.data(), Buffer.size())); |
1369 | auto CompressionResult = CompressedOffloadBundle::compress( |
1370 | P: {BundlerConfig.CompressionFormat, BundlerConfig.CompressionLevel, |
1371 | /*zstdEnableLdm=*/true}, |
1372 | Input: *BufferMemory, Verbose: BundlerConfig.Verbose); |
1373 | if (auto Error = CompressionResult.takeError()) |
1374 | return Error; |
1375 | |
1376 | auto CompressedMemBuffer = std::move(CompressionResult.get()); |
1377 | CompressedBuffer.assign(in_start: CompressedMemBuffer->getBufferStart(), |
1378 | in_end: CompressedMemBuffer->getBufferEnd()); |
1379 | } else |
1380 | CompressedBuffer = Buffer; |
1381 | |
1382 | OutputFile.write(Ptr: CompressedBuffer.data(), Size: CompressedBuffer.size()); |
1383 | |
1384 | return FH->finalizeOutputFile(); |
1385 | } |
1386 | |
1387 | // Unbundle the files. Return true if an error was found. |
1388 | Error OffloadBundler::UnbundleFiles() { |
1389 | // Open Input file. |
1390 | ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr = |
1391 | MemoryBuffer::getFileOrSTDIN(Filename: BundlerConfig.InputFileNames.front()); |
1392 | if (std::error_code EC = CodeOrErr.getError()) |
1393 | return createFileError(F: BundlerConfig.InputFileNames.front(), EC); |
1394 | |
1395 | // Decompress the input if necessary. |
1396 | Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr = |
1397 | CompressedOffloadBundle::decompress(Input: **CodeOrErr, Verbose: BundlerConfig.Verbose); |
1398 | if (!DecompressedBufferOrErr) |
1399 | return createStringError( |
1400 | EC: inconvertibleErrorCode(), |
1401 | S: "Failed to decompress input: " + |
1402 | llvm::toString(E: DecompressedBufferOrErr.takeError())); |
1403 | |
1404 | MemoryBuffer &Input = **DecompressedBufferOrErr; |
1405 | |
1406 | // Select the right files handler. |
1407 | Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = |
1408 | CreateFileHandler(FirstInput&: Input, BundlerConfig); |
1409 | if (!FileHandlerOrErr) |
1410 | return FileHandlerOrErr.takeError(); |
1411 | |
1412 | std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr; |
1413 | assert(FH); |
1414 | |
1415 | // Read the header of the bundled file. |
1416 | if (Error Err = FH->ReadHeader(Input)) |
1417 | return Err; |
1418 | |
1419 | // Create a work list that consist of the map triple/output file. |
1420 | StringMap<StringRef> Worklist; |
1421 | auto Output = BundlerConfig.OutputFileNames.begin(); |
1422 | for (auto &Triple : BundlerConfig.TargetNames) { |
1423 | Worklist[Triple] = *Output; |
1424 | ++Output; |
1425 | } |
1426 | |
1427 | // Read all the bundles that are in the work list. If we find no bundles we |
1428 | // assume the file is meant for the host target. |
1429 | bool FoundHostBundle = false; |
1430 | while (!Worklist.empty()) { |
1431 | Expected<std::optional<StringRef>> CurTripleOrErr = |
1432 | FH->ReadBundleStart(Input); |
1433 | if (!CurTripleOrErr) |
1434 | return CurTripleOrErr.takeError(); |
1435 | |
1436 | // We don't have more bundles. |
1437 | if (!*CurTripleOrErr) |
1438 | break; |
1439 | |
1440 | StringRef CurTriple = **CurTripleOrErr; |
1441 | assert(!CurTriple.empty()); |
1442 | |
1443 | auto Output = Worklist.begin(); |
1444 | for (auto E = Worklist.end(); Output != E; Output++) { |
1445 | if (isCodeObjectCompatible( |
1446 | CodeObjectInfo: OffloadTargetInfo(CurTriple, BundlerConfig), |
1447 | TargetInfo: OffloadTargetInfo((*Output).first(), BundlerConfig))) { |
1448 | break; |
1449 | } |
1450 | } |
1451 | |
1452 | if (Output == Worklist.end()) |
1453 | continue; |
1454 | // Check if the output file can be opened and copy the bundle to it. |
1455 | std::error_code EC; |
1456 | raw_fd_ostream OutputFile((*Output).second, EC, sys::fs::OF_None); |
1457 | if (EC) |
1458 | return createFileError(F: (*Output).second, EC); |
1459 | if (Error Err = FH->ReadBundle(OS&: OutputFile, Input)) |
1460 | return Err; |
1461 | if (Error Err = FH->ReadBundleEnd(Input)) |
1462 | return Err; |
1463 | Worklist.erase(I: Output); |
1464 | |
1465 | // Record if we found the host bundle. |
1466 | auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig); |
1467 | if (OffloadInfo.hasHostKind()) |
1468 | FoundHostBundle = true; |
1469 | } |
1470 | |
1471 | if (!BundlerConfig.AllowMissingBundles && !Worklist.empty()) { |
1472 | std::string ErrMsg = "Can't find bundles for" ; |
1473 | std::set<StringRef> Sorted; |
1474 | for (auto &E : Worklist) |
1475 | Sorted.insert(x: E.first()); |
1476 | unsigned I = 0; |
1477 | unsigned Last = Sorted.size() - 1; |
1478 | for (auto &E : Sorted) { |
1479 | if (I != 0 && Last > 1) |
1480 | ErrMsg += "," ; |
1481 | ErrMsg += " " ; |
1482 | if (I == Last && I != 0) |
1483 | ErrMsg += "and " ; |
1484 | ErrMsg += E.str(); |
1485 | ++I; |
1486 | } |
1487 | return createStringError(EC: inconvertibleErrorCode(), S: ErrMsg); |
1488 | } |
1489 | |
1490 | // If no bundles were found, assume the input file is the host bundle and |
1491 | // create empty files for the remaining targets. |
1492 | if (Worklist.size() == BundlerConfig.TargetNames.size()) { |
1493 | for (auto &E : Worklist) { |
1494 | std::error_code EC; |
1495 | raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); |
1496 | if (EC) |
1497 | return createFileError(F: E.second, EC); |
1498 | |
1499 | // If this entry has a host kind, copy the input file to the output file. |
1500 | auto OffloadInfo = OffloadTargetInfo(E.getKey(), BundlerConfig); |
1501 | if (OffloadInfo.hasHostKind()) |
1502 | OutputFile.write(Ptr: Input.getBufferStart(), Size: Input.getBufferSize()); |
1503 | } |
1504 | return Error::success(); |
1505 | } |
1506 | |
1507 | // If we found elements, we emit an error if none of those were for the host |
1508 | // in case host bundle name was provided in command line. |
1509 | if (!(FoundHostBundle || BundlerConfig.HostInputIndex == ~0u || |
1510 | BundlerConfig.AllowMissingBundles)) |
1511 | return createStringError(EC: inconvertibleErrorCode(), |
1512 | Msg: "Can't find bundle for the host target" ); |
1513 | |
1514 | // If we still have any elements in the worklist, create empty files for them. |
1515 | for (auto &E : Worklist) { |
1516 | std::error_code EC; |
1517 | raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None); |
1518 | if (EC) |
1519 | return createFileError(F: E.second, EC); |
1520 | } |
1521 | |
1522 | return Error::success(); |
1523 | } |
1524 | |
1525 | static Archive::Kind getDefaultArchiveKindForHost() { |
1526 | return Triple(sys::getDefaultTargetTriple()).isOSDarwin() ? Archive::K_DARWIN |
1527 | : Archive::K_GNU; |
1528 | } |
1529 | |
1530 | /// @brief Computes a list of targets among all given targets which are |
1531 | /// compatible with this code object |
1532 | /// @param [in] CodeObjectInfo Code Object |
1533 | /// @param [out] CompatibleTargets List of all compatible targets among all |
1534 | /// given targets |
1535 | /// @return false, if no compatible target is found. |
1536 | static bool |
1537 | getCompatibleOffloadTargets(OffloadTargetInfo &CodeObjectInfo, |
1538 | SmallVectorImpl<StringRef> &CompatibleTargets, |
1539 | const OffloadBundlerConfig &BundlerConfig) { |
1540 | if (!CompatibleTargets.empty()) { |
1541 | DEBUG_WITH_TYPE("CodeObjectCompatibility" , |
1542 | dbgs() << "CompatibleTargets list should be empty\n" ); |
1543 | return false; |
1544 | } |
1545 | for (auto &Target : BundlerConfig.TargetNames) { |
1546 | auto TargetInfo = OffloadTargetInfo(Target, BundlerConfig); |
1547 | if (isCodeObjectCompatible(CodeObjectInfo, TargetInfo)) |
1548 | CompatibleTargets.push_back(Elt: Target); |
1549 | } |
1550 | return !CompatibleTargets.empty(); |
1551 | } |
1552 | |
1553 | // Check that each code object file in the input archive conforms to following |
1554 | // rule: for a specific processor, a feature either shows up in all target IDs, |
1555 | // or does not show up in any target IDs. Otherwise the target ID combination is |
1556 | // invalid. |
1557 | static Error |
1558 | CheckHeterogeneousArchive(StringRef ArchiveName, |
1559 | const OffloadBundlerConfig &BundlerConfig) { |
1560 | std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers; |
1561 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = |
1562 | MemoryBuffer::getFileOrSTDIN(Filename: ArchiveName, IsText: true, RequiresNullTerminator: false); |
1563 | if (std::error_code EC = BufOrErr.getError()) |
1564 | return createFileError(F: ArchiveName, EC); |
1565 | |
1566 | ArchiveBuffers.push_back(x: std::move(*BufOrErr)); |
1567 | Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr = |
1568 | Archive::create(Source: ArchiveBuffers.back()->getMemBufferRef()); |
1569 | if (!LibOrErr) |
1570 | return LibOrErr.takeError(); |
1571 | |
1572 | auto Archive = std::move(*LibOrErr); |
1573 | |
1574 | Error ArchiveErr = Error::success(); |
1575 | auto ChildEnd = Archive->child_end(); |
1576 | |
1577 | /// Iterate over all bundled code object files in the input archive. |
1578 | for (auto ArchiveIter = Archive->child_begin(Err&: ArchiveErr); |
1579 | ArchiveIter != ChildEnd; ++ArchiveIter) { |
1580 | if (ArchiveErr) |
1581 | return ArchiveErr; |
1582 | auto ArchiveChildNameOrErr = (*ArchiveIter).getName(); |
1583 | if (!ArchiveChildNameOrErr) |
1584 | return ArchiveChildNameOrErr.takeError(); |
1585 | |
1586 | auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef(); |
1587 | if (!CodeObjectBufferRefOrErr) |
1588 | return CodeObjectBufferRefOrErr.takeError(); |
1589 | |
1590 | auto CodeObjectBuffer = |
1591 | MemoryBuffer::getMemBuffer(Ref: *CodeObjectBufferRefOrErr, RequiresNullTerminator: false); |
1592 | |
1593 | Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = |
1594 | CreateFileHandler(FirstInput&: *CodeObjectBuffer, BundlerConfig); |
1595 | if (!FileHandlerOrErr) |
1596 | return FileHandlerOrErr.takeError(); |
1597 | |
1598 | std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr; |
1599 | assert(FileHandler); |
1600 | |
1601 | std::set<StringRef> BundleIds; |
1602 | auto CodeObjectFileError = |
1603 | FileHandler->getBundleIDs(Input&: *CodeObjectBuffer, BundleIds); |
1604 | if (CodeObjectFileError) |
1605 | return CodeObjectFileError; |
1606 | |
1607 | auto &&ConflictingArchs = clang::getConflictTargetIDCombination(TargetIDs: BundleIds); |
1608 | if (ConflictingArchs) { |
1609 | std::string ErrMsg = |
1610 | Twine("conflicting TargetIDs [" + ConflictingArchs.value().first + |
1611 | ", " + ConflictingArchs.value().second + "] found in " + |
1612 | ArchiveChildNameOrErr.get() + " of " + ArchiveName) |
1613 | .str(); |
1614 | return createStringError(EC: inconvertibleErrorCode(), S: ErrMsg); |
1615 | } |
1616 | } |
1617 | |
1618 | return ArchiveErr; |
1619 | } |
1620 | |
1621 | /// UnbundleArchive takes an archive file (".a") as input containing bundled |
1622 | /// code object files, and a list of offload targets (not host), and extracts |
1623 | /// the code objects into a new archive file for each offload target. Each |
1624 | /// resulting archive file contains all code object files corresponding to that |
1625 | /// particular offload target. The created archive file does not |
1626 | /// contain an index of the symbols and code object files are named as |
1627 | /// <<Parent Bundle Name>-<CodeObject's TargetID>>, with ':' replaced with '_'. |
1628 | Error OffloadBundler::UnbundleArchive() { |
1629 | std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers; |
1630 | |
1631 | /// Map of target names with list of object files that will form the device |
1632 | /// specific archive for that target |
1633 | StringMap<std::vector<NewArchiveMember>> OutputArchivesMap; |
1634 | |
1635 | // Map of target names and output archive filenames |
1636 | StringMap<StringRef> TargetOutputFileNameMap; |
1637 | |
1638 | auto Output = BundlerConfig.OutputFileNames.begin(); |
1639 | for (auto &Target : BundlerConfig.TargetNames) { |
1640 | TargetOutputFileNameMap[Target] = *Output; |
1641 | ++Output; |
1642 | } |
1643 | |
1644 | StringRef IFName = BundlerConfig.InputFileNames.front(); |
1645 | |
1646 | if (BundlerConfig.CheckInputArchive) { |
1647 | // For a specific processor, a feature either shows up in all target IDs, or |
1648 | // does not show up in any target IDs. Otherwise the target ID combination |
1649 | // is invalid. |
1650 | auto ArchiveError = CheckHeterogeneousArchive(ArchiveName: IFName, BundlerConfig); |
1651 | if (ArchiveError) { |
1652 | return ArchiveError; |
1653 | } |
1654 | } |
1655 | |
1656 | ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr = |
1657 | MemoryBuffer::getFileOrSTDIN(Filename: IFName, IsText: true, RequiresNullTerminator: false); |
1658 | if (std::error_code EC = BufOrErr.getError()) |
1659 | return createFileError(F: BundlerConfig.InputFileNames.front(), EC); |
1660 | |
1661 | ArchiveBuffers.push_back(x: std::move(*BufOrErr)); |
1662 | Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr = |
1663 | Archive::create(Source: ArchiveBuffers.back()->getMemBufferRef()); |
1664 | if (!LibOrErr) |
1665 | return LibOrErr.takeError(); |
1666 | |
1667 | auto Archive = std::move(*LibOrErr); |
1668 | |
1669 | Error ArchiveErr = Error::success(); |
1670 | auto ChildEnd = Archive->child_end(); |
1671 | |
1672 | /// Iterate over all bundled code object files in the input archive. |
1673 | for (auto ArchiveIter = Archive->child_begin(Err&: ArchiveErr); |
1674 | ArchiveIter != ChildEnd; ++ArchiveIter) { |
1675 | if (ArchiveErr) |
1676 | return ArchiveErr; |
1677 | auto ArchiveChildNameOrErr = (*ArchiveIter).getName(); |
1678 | if (!ArchiveChildNameOrErr) |
1679 | return ArchiveChildNameOrErr.takeError(); |
1680 | |
1681 | StringRef BundledObjectFile = sys::path::filename(path: *ArchiveChildNameOrErr); |
1682 | |
1683 | auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef(); |
1684 | if (!CodeObjectBufferRefOrErr) |
1685 | return CodeObjectBufferRefOrErr.takeError(); |
1686 | |
1687 | auto TempCodeObjectBuffer = |
1688 | MemoryBuffer::getMemBuffer(Ref: *CodeObjectBufferRefOrErr, RequiresNullTerminator: false); |
1689 | |
1690 | // Decompress the buffer if necessary. |
1691 | Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr = |
1692 | CompressedOffloadBundle::decompress(Input: *TempCodeObjectBuffer, |
1693 | Verbose: BundlerConfig.Verbose); |
1694 | if (!DecompressedBufferOrErr) |
1695 | return createStringError( |
1696 | EC: inconvertibleErrorCode(), |
1697 | S: "Failed to decompress code object: " + |
1698 | llvm::toString(E: DecompressedBufferOrErr.takeError())); |
1699 | |
1700 | MemoryBuffer &CodeObjectBuffer = **DecompressedBufferOrErr; |
1701 | |
1702 | Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = |
1703 | CreateFileHandler(FirstInput&: CodeObjectBuffer, BundlerConfig); |
1704 | if (!FileHandlerOrErr) |
1705 | return FileHandlerOrErr.takeError(); |
1706 | |
1707 | std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr; |
1708 | assert(FileHandler && |
1709 | "FileHandle creation failed for file in the archive!" ); |
1710 | |
1711 | if (Error ReadErr = FileHandler->ReadHeader(Input&: CodeObjectBuffer)) |
1712 | return ReadErr; |
1713 | |
1714 | Expected<std::optional<StringRef>> CurBundleIDOrErr = |
1715 | FileHandler->ReadBundleStart(Input&: CodeObjectBuffer); |
1716 | if (!CurBundleIDOrErr) |
1717 | return CurBundleIDOrErr.takeError(); |
1718 | |
1719 | std::optional<StringRef> OptionalCurBundleID = *CurBundleIDOrErr; |
1720 | // No device code in this child, skip. |
1721 | if (!OptionalCurBundleID) |
1722 | continue; |
1723 | StringRef CodeObject = *OptionalCurBundleID; |
1724 | |
1725 | // Process all bundle entries (CodeObjects) found in this child of input |
1726 | // archive. |
1727 | while (!CodeObject.empty()) { |
1728 | SmallVector<StringRef> CompatibleTargets; |
1729 | auto CodeObjectInfo = OffloadTargetInfo(CodeObject, BundlerConfig); |
1730 | if (getCompatibleOffloadTargets(CodeObjectInfo, CompatibleTargets, |
1731 | BundlerConfig)) { |
1732 | std::string BundleData; |
1733 | raw_string_ostream DataStream(BundleData); |
1734 | if (Error Err = FileHandler->ReadBundle(OS&: DataStream, Input&: CodeObjectBuffer)) |
1735 | return Err; |
1736 | |
1737 | for (auto &CompatibleTarget : CompatibleTargets) { |
1738 | SmallString<128> BundledObjectFileName; |
1739 | BundledObjectFileName.assign(RHS: BundledObjectFile); |
1740 | auto OutputBundleName = |
1741 | Twine(llvm::sys::path::stem(path: BundledObjectFileName) + "-" + |
1742 | CodeObject + |
1743 | getDeviceLibraryFileName(BundleFileName: BundledObjectFileName, |
1744 | Device: CodeObjectInfo.TargetID)) |
1745 | .str(); |
1746 | // Replace ':' in optional target feature list with '_' to ensure |
1747 | // cross-platform validity. |
1748 | std::replace(first: OutputBundleName.begin(), last: OutputBundleName.end(), old_value: ':', |
1749 | new_value: '_'); |
1750 | |
1751 | std::unique_ptr<MemoryBuffer> MemBuf = MemoryBuffer::getMemBufferCopy( |
1752 | InputData: DataStream.str(), BufferName: OutputBundleName); |
1753 | ArchiveBuffers.push_back(x: std::move(MemBuf)); |
1754 | llvm::MemoryBufferRef MemBufRef = |
1755 | MemoryBufferRef(*(ArchiveBuffers.back())); |
1756 | |
1757 | // For inserting <CompatibleTarget, list<CodeObject>> entry in |
1758 | // OutputArchivesMap. |
1759 | if (!OutputArchivesMap.contains(Key: CompatibleTarget)) { |
1760 | |
1761 | std::vector<NewArchiveMember> ArchiveMembers; |
1762 | ArchiveMembers.push_back(x: NewArchiveMember(MemBufRef)); |
1763 | OutputArchivesMap.insert_or_assign(Key: CompatibleTarget, |
1764 | Val: std::move(ArchiveMembers)); |
1765 | } else { |
1766 | OutputArchivesMap[CompatibleTarget].push_back( |
1767 | x: NewArchiveMember(MemBufRef)); |
1768 | } |
1769 | } |
1770 | } |
1771 | |
1772 | if (Error Err = FileHandler->ReadBundleEnd(Input&: CodeObjectBuffer)) |
1773 | return Err; |
1774 | |
1775 | Expected<std::optional<StringRef>> NextTripleOrErr = |
1776 | FileHandler->ReadBundleStart(Input&: CodeObjectBuffer); |
1777 | if (!NextTripleOrErr) |
1778 | return NextTripleOrErr.takeError(); |
1779 | |
1780 | CodeObject = ((*NextTripleOrErr).has_value()) ? **NextTripleOrErr : "" ; |
1781 | } // End of processing of all bundle entries of this child of input archive. |
1782 | } // End of while over children of input archive. |
1783 | |
1784 | assert(!ArchiveErr && "Error occurred while reading archive!" ); |
1785 | |
1786 | /// Write out an archive for each target |
1787 | for (auto &Target : BundlerConfig.TargetNames) { |
1788 | StringRef FileName = TargetOutputFileNameMap[Target]; |
1789 | StringMapIterator<std::vector<llvm::NewArchiveMember>> CurArchiveMembers = |
1790 | OutputArchivesMap.find(Key: Target); |
1791 | if (CurArchiveMembers != OutputArchivesMap.end()) { |
1792 | if (Error WriteErr = writeArchive(ArcName: FileName, NewMembers: CurArchiveMembers->getValue(), |
1793 | WriteSymtab: SymtabWritingMode::NormalSymtab, |
1794 | Kind: getDefaultArchiveKindForHost(), Deterministic: true, |
1795 | Thin: false, OldArchiveBuf: nullptr)) |
1796 | return WriteErr; |
1797 | } else if (!BundlerConfig.AllowMissingBundles) { |
1798 | std::string ErrMsg = |
1799 | Twine("no compatible code object found for the target '" + Target + |
1800 | "' in heterogeneous archive library: " + IFName) |
1801 | .str(); |
1802 | return createStringError(EC: inconvertibleErrorCode(), S: ErrMsg); |
1803 | } else { // Create an empty archive file if no compatible code object is |
1804 | // found and "allow-missing-bundles" is enabled. It ensures that |
1805 | // the linker using output of this step doesn't complain about |
1806 | // the missing input file. |
1807 | std::vector<llvm::NewArchiveMember> EmptyArchive; |
1808 | EmptyArchive.clear(); |
1809 | if (Error WriteErr = writeArchive( |
1810 | ArcName: FileName, NewMembers: EmptyArchive, WriteSymtab: SymtabWritingMode::NormalSymtab, |
1811 | Kind: getDefaultArchiveKindForHost(), Deterministic: true, Thin: false, OldArchiveBuf: nullptr)) |
1812 | return WriteErr; |
1813 | } |
1814 | } |
1815 | |
1816 | return Error::success(); |
1817 | } |
1818 | |