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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang/lib/Driver/OffloadBundler.cpp