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