1975 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file implements an offload bundling API that bundles different files11/// that relate with the same source code but different targets into a single12/// one. Also the implements the opposite functionality, i.e. unbundle files13/// 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} // namespace76static 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(Components, '-', /*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(':').first;97 if (!TargetId.empty() &&98 clang::StringToOffloadArch(TargetId) != clang::OffloadArch::UNKNOWN)99 this->TargetID = TargetIdWithFeature;100 else101 this->TargetID = "";102 103 this->OffloadKind = Components.front();104 ArrayRef<StringRef> TripleSlice{&Components[1], /*length=*/4};105 llvm::Triple T = llvm::Triple(llvm::join(TripleSlice, "-"));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("hip") &&128 TargetOffloadKind == "openmp";129 bool OpenMPCompatibleWithHIP =130 OffloadKind == "openmp" &&131 TargetOffloadKind.starts_with_insensitive("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(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 runtime149 // assumes the triple to be "amdgcn/spirv64-amd-amdhsa-" (empty environment)150 // instead of "amdgcn/spirv64-amd-amdhsa-unknown". It's gonna be very tricky151 // to patch different layers of runtime.152 if (Triple.getOS() == Triple::OSType::AMDHSA) {153 NormalizedTriple = Triple.normalize(Triple::CanonicalForm::THREE_IDENT);154 NormalizedTriple.push_back('-');155 } else {156 NormalizedTriple = Triple.normalize(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("gfx"))164 return ".bc";165 if (Device.contains("sm_"))166 return ".cubin";167 return sys::path::extension(BundleFileName);168}169 170static std::string getDeviceLibraryFileName(StringRef BundleFileName,171 StringRef Device) {172 StringRef LibName = sys::path::stem(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 bundled194 /// file.195 virtual Error ReadHeader(MemoryBuffer &Input) = 0;196 197 /// Read the marker of the next bundled to be read in the file. The bundle198 /// name is returned if there is one in the file, or `std::nullopt` if there199 /// 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 information210 /// 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 to215 /// \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 \a219 /// 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, [&](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, [&](const BundleInfo &Info) -> Error {247 BundleIds.insert(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{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 format286/// (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/// Bundle1305/// ...306/// BundleN307 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(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, Val, 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 job342 const OffloadBundlerConfig &BundlerConfig;343 344public:345 // TODO: Add error checking from ClangOffloadBundler.cpp346 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(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(FC, 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(FC, ReadChars);380 ReadChars += 8;381 382 // Read size.383 if (ReadChars + 8 > FC.size())384 return Error::success();385 386 uint64_t Size = Read8byteIntegerFromBuffer(FC, 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(FC, 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(FC.data() + CurBundleInfo->second.Offset,433 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 Bundles445 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, BundlerConfig.TargetNames.size());455 456 unsigned Idx = 0;457 for (auto &T : BundlerConfig.TargetNames) {458 MemoryBuffer &MB = *Inputs[Idx++];459 HeaderSize = alignTo(HeaderSize, BundlerConfig.BundleAlignment);460 // Bundle offset.461 Write8byteIntegerToBuffer(OS, HeaderSize);462 // Size of the bundle (adds to the next bundle's offset)463 Write8byteIntegerToBuffer(OS, MB.getBufferSize());464 BundlesInfo[T] = BinaryBundleInfo(MB.getBufferSize(), HeaderSize);465 HeaderSize += MB.getBufferSize();466 // Size of the triple467 Write8byteIntegerToBuffer(OS, T.size());468 // Triple469 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('\0');491 assert(OS.tell() == BI.Offset);492 493 OS.write(Input.getBufferStart(), Input.getBufferSize());494 495 return Error::success();496 }497};498 499// This class implements a list of temporary files that are removed upon500// object destruction.501class TempFileHandlerRAII {502public:503 ~TempFileHandlerRAII() {504 for (const auto &File : Files)505 sys::fs::remove(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("clang-offload-bundler", "tmp", File))513 return createFileError(File, EC);514 Files.push_front(File);515 516 if (Contents) {517 std::error_code EC;518 raw_fd_ostream OS(File, EC);519 if (EC)520 return createFileError(File, EC);521 OS.write(Contents->data(), 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 a531/// 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 offload543 /// 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(*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(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 already562 /// 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 job570 const OffloadBundlerConfig &BundlerConfig;571 572public:573 // TODO: Add error checking from ClangOffloadBundler.cpp574 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. If590 // so, return the triple.591 Expected<std::optional<StringRef>> TripleOrErr =592 IsOffloadSection(*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 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(Content.data(), 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 output655 // fat object. These sections should have 'exclude' flag set which tells656 // link editor to remove them from linker inputs when linking executable or657 // 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 with666 // 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 a675 // standard bundle for the host object since we are going to use fat676 // 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(ArrayRef<char>(0));679 if (!TempFileOrErr)680 return TempFileOrErr.takeError();681 InputFile = *TempFileOrErr;682 }683 684 ObjcopyArgs.push_back(685 SS.save(Twine("--add-section=") + OFFLOAD_BUNDLER_MAGIC_STR +686 BundlerConfig.TargetNames[I] + "=" + InputFile));687 ObjcopyArgs.push_back(688 SS.save(Twine("--set-section-flags=") + OFFLOAD_BUNDLER_MAGIC_STR +689 BundlerConfig.TargetNames[I] + "=readonly,exclude"));690 }691 ObjcopyArgs.push_back("--");692 ObjcopyArgs.push_back(693 BundlerConfig.InputFileNames[BundlerConfig.HostInputIndex]);694 ObjcopyArgs.push_back(BundlerConfig.OutputFileNames.front());695 696 if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, 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 that709 // instead of executing it.710 if (BundlerConfig.PrintExternalCommands) {711 errs() << "\"" << Objcopy << "\"";712 for (StringRef Arg : drop_begin(Args, 1))713 errs() << " \"" << Arg << "\"";714 errs() << "\n";715 } else {716 if (sys::ExecuteAndWait(Objcopy, Args))717 return createStringError(inconvertibleErrorCode(),718 "'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(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("--regex");736 ObjcopyArgs.push_back("--remove-section=__CLANG_OFFLOAD_BUNDLE__.*");737 ObjcopyArgs.push_back("--");738 739 StringRef ObjcopyInputFileName;740 // When unbundling an archive, the content of each object file in the741 // archive is passed to this function by parameter Input, which is different742 // from the content of the original input archive file, therefore it needs743 // 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, therefore745 // temporary file is not needed.746 if (StringRef(BundlerConfig.FilesType).starts_with("a")) {747 auto InputFileOrErr = TempFiles.Create(ArrayRef<char>(Input));748 if (!InputFileOrErr)749 return InputFileOrErr.takeError();750 ObjcopyInputFileName = *InputFileOrErr;751 } else752 ObjcopyInputFileName = BundlerConfig.InputFileNames.front();753 754 ObjcopyArgs.push_back(ObjcopyInputFileName);755 ObjcopyArgs.push_back(ModifiedObjPath);756 757 if (Error Err = executeObjcopy(BundlerConfig.ObjcopyPath, ObjcopyArgs))758 return std::move(Err);759 760 auto BufOrErr = MemoryBuffer::getFile(ModifiedObjPath);761 if (!BufOrErr)762 return createStringError(BufOrErr.getError(),763 "Failed to read back the modified object file");764 765 return BufOrErr->get()->getBuffer().str();766 }767};768 769/// Handler for text files. The bundled file will have the following format.770///771/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple"772/// Bundle 1773/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple"774/// ...775/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple"776/// Bundle N777/// "Comment OFFLOAD_BUNDLER_MAGIC_STR__END__ triple"778class TextFileHandler final : public FileHandler {779 /// String that begins a line comment.780 StringRef Comment;781 782 /// String that initiates a bundle.783 std::string BundleStartString;784 785 /// String that closes a bundle.786 std::string BundleEndString;787 788 /// Number of chars read from input.789 size_t ReadChars = 0u;790 791protected:792 Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); }793 794 Expected<std::optional<StringRef>>795 ReadBundleStart(MemoryBuffer &Input) final {796 StringRef FC = Input.getBuffer();797 798 // Find start of the bundle.799 ReadChars = FC.find(BundleStartString, ReadChars);800 if (ReadChars == FC.npos)801 return std::nullopt;802 803 // Get position of the triple.804 size_t TripleStart = ReadChars = ReadChars + BundleStartString.size();805 806 // Get position that closes the triple.807 size_t TripleEnd = ReadChars = FC.find("\n", ReadChars);808 if (TripleEnd == FC.npos)809 return std::nullopt;810 811 // Next time we read after the new line.812 ++ReadChars;813 814 return StringRef(&FC.data()[TripleStart], TripleEnd - TripleStart);815 }816 817 Error ReadBundleEnd(MemoryBuffer &Input) final {818 StringRef FC = Input.getBuffer();819 820 // Read up to the next new line.821 assert(FC[ReadChars] == '\n' && "The bundle should end with a new line.");822 823 size_t TripleEnd = ReadChars = FC.find("\n", ReadChars + 1);824 if (TripleEnd != FC.npos)825 // Next time we read after the new line.826 ++ReadChars;827 828 return Error::success();829 }830 831 Error ReadBundle(raw_ostream &OS, MemoryBuffer &Input) final {832 StringRef FC = Input.getBuffer();833 size_t BundleStart = ReadChars;834 835 // Find end of the bundle.836 size_t BundleEnd = ReadChars = FC.find(BundleEndString, ReadChars);837 838 StringRef Bundle(&FC.data()[BundleStart], BundleEnd - BundleStart);839 OS << Bundle;840 841 return Error::success();842 }843 844 Error WriteHeader(raw_ostream &OS,845 ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs) final {846 return Error::success();847 }848 849 Error WriteBundleStart(raw_ostream &OS, StringRef TargetTriple) final {850 OS << BundleStartString << TargetTriple << "\n";851 return Error::success();852 }853 854 Error WriteBundleEnd(raw_ostream &OS, StringRef TargetTriple) final {855 OS << BundleEndString << TargetTriple << "\n";856 return Error::success();857 }858 859 Error WriteBundle(raw_ostream &OS, MemoryBuffer &Input) final {860 OS << Input.getBuffer();861 return Error::success();862 }863 864public:865 TextFileHandler(StringRef Comment) : Comment(Comment), ReadChars(0) {866 BundleStartString =867 "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__START__ ";868 BundleEndString =869 "\n" + Comment.str() + " " OFFLOAD_BUNDLER_MAGIC_STR "__END__ ";870 }871 872 Error listBundleIDsCallback(MemoryBuffer &Input,873 const BundleInfo &Info) final {874 // TODO: To list bundle IDs in a bundled text file we need to go through875 // all bundles. The format of bundled text file may need to include a876 // header if the performance of listing bundle IDs of bundled text file is877 // important.878 ReadChars = Input.getBuffer().find(BundleEndString, ReadChars);879 if (Error Err = ReadBundleEnd(Input))880 return Err;881 return Error::success();882 }883};884} // namespace885 886/// Return an appropriate object file handler. We use the specific object887/// handler if we know how to deal with that format, otherwise we use a default888/// binary file handler.889static std::unique_ptr<FileHandler>890CreateObjectFileHandler(MemoryBuffer &FirstInput,891 const OffloadBundlerConfig &BundlerConfig) {892 // Check if the input file format is one that we know how to deal with.893 Expected<std::unique_ptr<Binary>> BinaryOrErr = createBinary(FirstInput);894 895 // We only support regular object files. If failed to open the input as a896 // known binary or this is not an object file use the default binary handler.897 if (errorToBool(BinaryOrErr.takeError()) || !isa<ObjectFile>(*BinaryOrErr))898 return std::make_unique<BinaryFileHandler>(BundlerConfig);899 900 // Otherwise create an object file handler. The handler will be owned by the901 // client of this function.902 return std::make_unique<ObjectFileHandler>(903 std::unique_ptr<ObjectFile>(cast<ObjectFile>(BinaryOrErr->release())),904 BundlerConfig);905}906 907/// Return an appropriate handler given the input files and options.908static Expected<std::unique_ptr<FileHandler>>909CreateFileHandler(MemoryBuffer &FirstInput,910 const OffloadBundlerConfig &BundlerConfig) {911 std::string FilesType = BundlerConfig.FilesType;912 913 if (FilesType == "i")914 return std::make_unique<TextFileHandler>(/*Comment=*/"//");915 if (FilesType == "ii")916 return std::make_unique<TextFileHandler>(/*Comment=*/"//");917 if (FilesType == "cui")918 return std::make_unique<TextFileHandler>(/*Comment=*/"//");919 if (FilesType == "hipi")920 return std::make_unique<TextFileHandler>(/*Comment=*/"//");921 // TODO: `.d` should be eventually removed once `-M` and its variants are922 // handled properly in offload compilation.923 if (FilesType == "d")924 return std::make_unique<TextFileHandler>(/*Comment=*/"#");925 if (FilesType == "ll")926 return std::make_unique<TextFileHandler>(/*Comment=*/";");927 if (FilesType == "bc")928 return std::make_unique<BinaryFileHandler>(BundlerConfig);929 if (FilesType == "s")930 return std::make_unique<TextFileHandler>(/*Comment=*/"#");931 if (FilesType == "o")932 return CreateObjectFileHandler(FirstInput, BundlerConfig);933 if (FilesType == "a")934 return CreateObjectFileHandler(FirstInput, BundlerConfig);935 if (FilesType == "gch")936 return std::make_unique<BinaryFileHandler>(BundlerConfig);937 if (FilesType == "ast")938 return std::make_unique<BinaryFileHandler>(BundlerConfig);939 940 return createStringError(errc::invalid_argument,941 "'" + FilesType + "': invalid file type specified");942}943 944OffloadBundlerConfig::OffloadBundlerConfig()945 : CompressedBundleVersion(CompressedOffloadBundle::DefaultVersion) {946 if (llvm::compression::zstd::isAvailable()) {947 CompressionFormat = llvm::compression::Format::Zstd;948 // Compression level 3 is usually sufficient for zstd since long distance949 // matching is enabled.950 CompressionLevel = 3;951 } else if (llvm::compression::zlib::isAvailable()) {952 CompressionFormat = llvm::compression::Format::Zlib;953 // Use default level for zlib since higher level does not have significant954 // improvement.955 CompressionLevel = llvm::compression::zlib::DefaultCompression;956 }957 auto IgnoreEnvVarOpt =958 llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_IGNORE_ENV_VAR");959 if (IgnoreEnvVarOpt.has_value() && IgnoreEnvVarOpt.value() == "1")960 return;961 auto VerboseEnvVarOpt = llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_VERBOSE");962 if (VerboseEnvVarOpt.has_value())963 Verbose = VerboseEnvVarOpt.value() == "1";964 auto CompressEnvVarOpt =965 llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESS");966 if (CompressEnvVarOpt.has_value())967 Compress = CompressEnvVarOpt.value() == "1";968 auto CompressionLevelEnvVarOpt =969 llvm::sys::Process::GetEnv("OFFLOAD_BUNDLER_COMPRESSION_LEVEL");970 if (CompressionLevelEnvVarOpt.has_value()) {971 llvm::StringRef CompressionLevelStr = CompressionLevelEnvVarOpt.value();972 int Level;973 if (!CompressionLevelStr.getAsInteger(10, Level))974 CompressionLevel = Level;975 else976 llvm::errs()977 << "Warning: Invalid value for OFFLOAD_BUNDLER_COMPRESSION_LEVEL: "978 << CompressionLevelStr.str() << ". Ignoring it.\n";979 }980 auto CompressedBundleFormatVersionOpt =981 llvm::sys::Process::GetEnv("COMPRESSED_BUNDLE_FORMAT_VERSION");982 if (CompressedBundleFormatVersionOpt.has_value()) {983 llvm::StringRef VersionStr = CompressedBundleFormatVersionOpt.value();984 uint16_t Version;985 if (!VersionStr.getAsInteger(10, Version)) {986 if (Version >= 2 && Version <= 3)987 CompressedBundleVersion = Version;988 else989 llvm::errs()990 << "Warning: Invalid value for COMPRESSED_BUNDLE_FORMAT_VERSION: "991 << VersionStr.str()992 << ". Valid values are 2 or 3. Using default version "993 << CompressedBundleVersion << ".\n";994 } else995 llvm::errs()996 << "Warning: Invalid value for COMPRESSED_BUNDLE_FORMAT_VERSION: "997 << VersionStr.str() << ". Using default version "998 << CompressedBundleVersion << ".\n";999 }1000}1001 1002// Utility function to format numbers with commas1003static std::string formatWithCommas(unsigned long long Value) {1004 std::string Num = std::to_string(Value);1005 int InsertPosition = Num.length() - 3;1006 while (InsertPosition > 0) {1007 Num.insert(InsertPosition, ",");1008 InsertPosition -= 3;1009 }1010 return Num;1011}1012 1013llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>1014CompressedOffloadBundle::compress(llvm::compression::Params P,1015 const llvm::MemoryBuffer &Input,1016 uint16_t Version, bool Verbose) {1017 if (!llvm::compression::zstd::isAvailable() &&1018 !llvm::compression::zlib::isAvailable())1019 return createStringError(llvm::inconvertibleErrorCode(),1020 "Compression not supported");1021 llvm::Timer HashTimer("Hash Calculation Timer", "Hash calculation time",1022 *ClangOffloadBundlerTimerGroup);1023 if (Verbose)1024 HashTimer.startTimer();1025 llvm::MD5 Hash;1026 llvm::MD5::MD5Result Result;1027 Hash.update(Input.getBuffer());1028 Hash.final(Result);1029 uint64_t TruncatedHash = Result.low();1030 if (Verbose)1031 HashTimer.stopTimer();1032 1033 SmallVector<uint8_t, 0> CompressedBuffer;1034 auto BufferUint8 = llvm::ArrayRef<uint8_t>(1035 reinterpret_cast<const uint8_t *>(Input.getBuffer().data()),1036 Input.getBuffer().size());1037 llvm::Timer CompressTimer("Compression Timer", "Compression time",1038 *ClangOffloadBundlerTimerGroup);1039 if (Verbose)1040 CompressTimer.startTimer();1041 llvm::compression::compress(P, BufferUint8, CompressedBuffer);1042 if (Verbose)1043 CompressTimer.stopTimer();1044 1045 uint16_t CompressionMethod = static_cast<uint16_t>(P.format);1046 1047 // Store sizes in 64-bit variables first1048 uint64_t UncompressedSize64 = Input.getBuffer().size();1049 uint64_t TotalFileSize64;1050 1051 // Calculate total file size based on version1052 if (Version == 2) {1053 // For V2, ensure the sizes don't exceed 32-bit limit1054 if (UncompressedSize64 > std::numeric_limits<uint32_t>::max())1055 return createStringError(llvm::inconvertibleErrorCode(),1056 "Uncompressed size exceeds version 2 limit");1057 if ((MagicNumber.size() + sizeof(uint32_t) + sizeof(Version) +1058 sizeof(CompressionMethod) + sizeof(uint32_t) + sizeof(TruncatedHash) +1059 CompressedBuffer.size()) > std::numeric_limits<uint32_t>::max())1060 return createStringError(llvm::inconvertibleErrorCode(),1061 "Total file size exceeds version 2 limit");1062 1063 TotalFileSize64 = MagicNumber.size() + sizeof(uint32_t) + sizeof(Version) +1064 sizeof(CompressionMethod) + sizeof(uint32_t) +1065 sizeof(TruncatedHash) + CompressedBuffer.size();1066 } else { // Version 31067 TotalFileSize64 = MagicNumber.size() + sizeof(uint64_t) + sizeof(Version) +1068 sizeof(CompressionMethod) + sizeof(uint64_t) +1069 sizeof(TruncatedHash) + CompressedBuffer.size();1070 }1071 1072 SmallVector<char, 0> FinalBuffer;1073 llvm::raw_svector_ostream OS(FinalBuffer);1074 OS << MagicNumber;1075 OS.write(reinterpret_cast<const char *>(&Version), sizeof(Version));1076 OS.write(reinterpret_cast<const char *>(&CompressionMethod),1077 sizeof(CompressionMethod));1078 1079 // Write size fields according to version1080 if (Version == 2) {1081 uint32_t TotalFileSize32 = static_cast<uint32_t>(TotalFileSize64);1082 uint32_t UncompressedSize32 = static_cast<uint32_t>(UncompressedSize64);1083 OS.write(reinterpret_cast<const char *>(&TotalFileSize32),1084 sizeof(TotalFileSize32));1085 OS.write(reinterpret_cast<const char *>(&UncompressedSize32),1086 sizeof(UncompressedSize32));1087 } else { // Version 31088 OS.write(reinterpret_cast<const char *>(&TotalFileSize64),1089 sizeof(TotalFileSize64));1090 OS.write(reinterpret_cast<const char *>(&UncompressedSize64),1091 sizeof(UncompressedSize64));1092 }1093 1094 OS.write(reinterpret_cast<const char *>(&TruncatedHash),1095 sizeof(TruncatedHash));1096 OS.write(reinterpret_cast<const char *>(CompressedBuffer.data()),1097 CompressedBuffer.size());1098 1099 if (Verbose) {1100 auto MethodUsed =1101 P.format == llvm::compression::Format::Zstd ? "zstd" : "zlib";1102 double CompressionRate =1103 static_cast<double>(UncompressedSize64) / CompressedBuffer.size();1104 double CompressionTimeSeconds = CompressTimer.getTotalTime().getWallTime();1105 double CompressionSpeedMBs =1106 (UncompressedSize64 / (1024.0 * 1024.0)) / CompressionTimeSeconds;1107 llvm::errs() << "Compressed bundle format version: " << Version << "\n"1108 << "Total file size (including headers): "1109 << formatWithCommas(TotalFileSize64) << " bytes\n"1110 << "Compression method used: " << MethodUsed << "\n"1111 << "Compression level: " << P.level << "\n"1112 << "Binary size before compression: "1113 << formatWithCommas(UncompressedSize64) << " bytes\n"1114 << "Binary size after compression: "1115 << formatWithCommas(CompressedBuffer.size()) << " bytes\n"1116 << "Compression rate: "1117 << llvm::format("%.2lf", CompressionRate) << "\n"1118 << "Compression ratio: "1119 << llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"1120 << "Compression speed: "1121 << llvm::format("%.2lf MB/s", CompressionSpeedMBs) << "\n"1122 << "Truncated MD5 hash: "1123 << llvm::format_hex(TruncatedHash, 16) << "\n";1124 }1125 1126 return llvm::MemoryBuffer::getMemBufferCopy(1127 llvm::StringRef(FinalBuffer.data(), FinalBuffer.size()));1128}1129 1130// Use packed structs to avoid padding, such that the structs map the serialized1131// format.1132LLVM_PACKED_START1133union RawCompressedBundleHeader {1134 struct CommonFields {1135 uint32_t Magic;1136 uint16_t Version;1137 uint16_t Method;1138 };1139 1140 struct V1Header {1141 CommonFields Common;1142 uint32_t UncompressedFileSize;1143 uint64_t Hash;1144 };1145 1146 struct V2Header {1147 CommonFields Common;1148 uint32_t FileSize;1149 uint32_t UncompressedFileSize;1150 uint64_t Hash;1151 };1152 1153 struct V3Header {1154 CommonFields Common;1155 uint64_t FileSize;1156 uint64_t UncompressedFileSize;1157 uint64_t Hash;1158 };1159 1160 CommonFields Common;1161 V1Header V1;1162 V2Header V2;1163 V3Header V3;1164};1165LLVM_PACKED_END1166 1167// Helper method to get header size based on version1168static size_t getHeaderSize(uint16_t Version) {1169 switch (Version) {1170 case 1:1171 return sizeof(RawCompressedBundleHeader::V1Header);1172 case 2:1173 return sizeof(RawCompressedBundleHeader::V2Header);1174 case 3:1175 return sizeof(RawCompressedBundleHeader::V3Header);1176 default:1177 llvm_unreachable("Unsupported version");1178 }1179}1180 1181Expected<CompressedOffloadBundle::CompressedBundleHeader>1182CompressedOffloadBundle::CompressedBundleHeader::tryParse(StringRef Blob) {1183 assert(Blob.size() >= sizeof(RawCompressedBundleHeader::CommonFields));1184 assert(llvm::identify_magic(Blob) ==1185 llvm::file_magic::offload_bundle_compressed);1186 1187 RawCompressedBundleHeader Header;1188 memcpy(&Header, Blob.data(), std::min(Blob.size(), sizeof(Header)));1189 1190 CompressedBundleHeader Normalized;1191 Normalized.Version = Header.Common.Version;1192 1193 size_t RequiredSize = getHeaderSize(Normalized.Version);1194 if (Blob.size() < RequiredSize)1195 return createStringError(inconvertibleErrorCode(),1196 "Compressed bundle header size too small");1197 1198 switch (Normalized.Version) {1199 case 1:1200 Normalized.UncompressedFileSize = Header.V1.UncompressedFileSize;1201 Normalized.Hash = Header.V1.Hash;1202 break;1203 case 2:1204 Normalized.FileSize = Header.V2.FileSize;1205 Normalized.UncompressedFileSize = Header.V2.UncompressedFileSize;1206 Normalized.Hash = Header.V2.Hash;1207 break;1208 case 3:1209 Normalized.FileSize = Header.V3.FileSize;1210 Normalized.UncompressedFileSize = Header.V3.UncompressedFileSize;1211 Normalized.Hash = Header.V3.Hash;1212 break;1213 default:1214 return createStringError(inconvertibleErrorCode(),1215 "Unknown compressed bundle version");1216 }1217 1218 // Determine compression format1219 switch (Header.Common.Method) {1220 case static_cast<uint16_t>(compression::Format::Zlib):1221 case static_cast<uint16_t>(compression::Format::Zstd):1222 Normalized.CompressionFormat =1223 static_cast<compression::Format>(Header.Common.Method);1224 break;1225 default:1226 return createStringError(inconvertibleErrorCode(),1227 "Unknown compressing method");1228 }1229 1230 return Normalized;1231}1232 1233llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>1234CompressedOffloadBundle::decompress(const llvm::MemoryBuffer &Input,1235 bool Verbose) {1236 StringRef Blob = Input.getBuffer();1237 1238 // Check minimum header size (using V1 as it's the smallest)1239 if (Blob.size() < sizeof(RawCompressedBundleHeader::CommonFields))1240 return llvm::MemoryBuffer::getMemBufferCopy(Blob);1241 1242 if (llvm::identify_magic(Blob) !=1243 llvm::file_magic::offload_bundle_compressed) {1244 if (Verbose)1245 llvm::errs() << "Uncompressed bundle.\n";1246 return llvm::MemoryBuffer::getMemBufferCopy(Blob);1247 }1248 1249 Expected<CompressedBundleHeader> HeaderOrErr =1250 CompressedBundleHeader::tryParse(Blob);1251 if (!HeaderOrErr)1252 return HeaderOrErr.takeError();1253 1254 const CompressedBundleHeader &Normalized = *HeaderOrErr;1255 unsigned ThisVersion = Normalized.Version;1256 size_t HeaderSize = getHeaderSize(ThisVersion);1257 1258 llvm::compression::Format CompressionFormat = Normalized.CompressionFormat;1259 1260 size_t TotalFileSize = Normalized.FileSize.value_or(0);1261 size_t UncompressedSize = Normalized.UncompressedFileSize;1262 auto StoredHash = Normalized.Hash;1263 1264 llvm::Timer DecompressTimer("Decompression Timer", "Decompression time",1265 *ClangOffloadBundlerTimerGroup);1266 if (Verbose)1267 DecompressTimer.startTimer();1268 1269 SmallVector<uint8_t, 0> DecompressedData;1270 StringRef CompressedData = Blob.substr(HeaderSize);1271 if (llvm::Error DecompressionError = llvm::compression::decompress(1272 CompressionFormat, llvm::arrayRefFromStringRef(CompressedData),1273 DecompressedData, UncompressedSize))1274 return createStringError(inconvertibleErrorCode(),1275 "Could not decompress embedded file contents: " +1276 llvm::toString(std::move(DecompressionError)));1277 1278 if (Verbose) {1279 DecompressTimer.stopTimer();1280 1281 double DecompressionTimeSeconds =1282 DecompressTimer.getTotalTime().getWallTime();1283 1284 // Recalculate MD5 hash for integrity check1285 llvm::Timer HashRecalcTimer("Hash Recalculation Timer",1286 "Hash recalculation time",1287 *ClangOffloadBundlerTimerGroup);1288 HashRecalcTimer.startTimer();1289 llvm::MD5 Hash;1290 llvm::MD5::MD5Result Result;1291 Hash.update(llvm::ArrayRef<uint8_t>(DecompressedData));1292 Hash.final(Result);1293 uint64_t RecalculatedHash = Result.low();1294 HashRecalcTimer.stopTimer();1295 bool HashMatch = (StoredHash == RecalculatedHash);1296 1297 double CompressionRate =1298 static_cast<double>(UncompressedSize) / CompressedData.size();1299 double DecompressionSpeedMBs =1300 (UncompressedSize / (1024.0 * 1024.0)) / DecompressionTimeSeconds;1301 1302 llvm::errs() << "Compressed bundle format version: " << ThisVersion << "\n";1303 if (ThisVersion >= 2)1304 llvm::errs() << "Total file size (from header): "1305 << formatWithCommas(TotalFileSize) << " bytes\n";1306 llvm::errs() << "Decompression method: "1307 << (CompressionFormat == llvm::compression::Format::Zlib1308 ? "zlib"1309 : "zstd")1310 << "\n"1311 << "Size before decompression: "1312 << formatWithCommas(CompressedData.size()) << " bytes\n"1313 << "Size after decompression: "1314 << formatWithCommas(UncompressedSize) << " bytes\n"1315 << "Compression rate: "1316 << llvm::format("%.2lf", CompressionRate) << "\n"1317 << "Compression ratio: "1318 << llvm::format("%.2lf%%", 100.0 / CompressionRate) << "\n"1319 << "Decompression speed: "1320 << llvm::format("%.2lf MB/s", DecompressionSpeedMBs) << "\n"1321 << "Stored hash: " << llvm::format_hex(StoredHash, 16) << "\n"1322 << "Recalculated hash: "1323 << llvm::format_hex(RecalculatedHash, 16) << "\n"1324 << "Hashes match: " << (HashMatch ? "Yes" : "No") << "\n";1325 }1326 1327 return llvm::MemoryBuffer::getMemBufferCopy(1328 llvm::toStringRef(DecompressedData));1329}1330 1331// List bundle IDs. Return true if an error was found.1332Error OffloadBundler::ListBundleIDsInFile(1333 StringRef InputFileName, const OffloadBundlerConfig &BundlerConfig) {1334 // Open Input file.1335 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1336 MemoryBuffer::getFileOrSTDIN(InputFileName, /*IsText=*/true);1337 if (std::error_code EC = CodeOrErr.getError())1338 return createFileError(InputFileName, EC);1339 1340 // Decompress the input if necessary.1341 Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1342 CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);1343 if (!DecompressedBufferOrErr)1344 return createStringError(1345 inconvertibleErrorCode(),1346 "Failed to decompress input: " +1347 llvm::toString(DecompressedBufferOrErr.takeError()));1348 1349 MemoryBuffer &DecompressedInput = **DecompressedBufferOrErr;1350 1351 // Select the right files handler.1352 Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1353 CreateFileHandler(DecompressedInput, BundlerConfig);1354 if (!FileHandlerOrErr)1355 return FileHandlerOrErr.takeError();1356 1357 std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1358 assert(FH);1359 return FH->listBundleIDs(DecompressedInput);1360}1361 1362/// @brief Checks if a code object \p CodeObjectInfo is compatible with a given1363/// target \p TargetInfo.1364/// @link https://clang.llvm.org/docs/ClangOffloadBundler.html#bundle-entry-id1365bool isCodeObjectCompatible(const OffloadTargetInfo &CodeObjectInfo,1366 const OffloadTargetInfo &TargetInfo) {1367 1368 // Compatible in case of exact match.1369 if (CodeObjectInfo == TargetInfo) {1370 DEBUG_WITH_TYPE("CodeObjectCompatibility",1371 dbgs() << "Compatible: Exact match: \t[CodeObject: "1372 << CodeObjectInfo.str()1373 << "]\t:\t[Target: " << TargetInfo.str() << "]\n");1374 return true;1375 }1376 1377 // Incompatible if Kinds or Triples mismatch.1378 if (!CodeObjectInfo.isOffloadKindCompatible(TargetInfo.OffloadKind) ||1379 !CodeObjectInfo.Triple.isCompatibleWith(TargetInfo.Triple)) {1380 DEBUG_WITH_TYPE(1381 "CodeObjectCompatibility",1382 dbgs() << "Incompatible: Kind/Triple mismatch \t[CodeObject: "1383 << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1384 << "]\n");1385 return false;1386 }1387 1388 // Incompatible if Processors mismatch.1389 llvm::StringMap<bool> CodeObjectFeatureMap, TargetFeatureMap;1390 std::optional<StringRef> CodeObjectProc = clang::parseTargetID(1391 CodeObjectInfo.Triple, CodeObjectInfo.TargetID, &CodeObjectFeatureMap);1392 std::optional<StringRef> TargetProc = clang::parseTargetID(1393 TargetInfo.Triple, TargetInfo.TargetID, &TargetFeatureMap);1394 1395 // Both TargetProc and CodeObjectProc can't be empty here.1396 if (!TargetProc || !CodeObjectProc ||1397 CodeObjectProc.value() != TargetProc.value()) {1398 DEBUG_WITH_TYPE("CodeObjectCompatibility",1399 dbgs() << "Incompatible: Processor mismatch \t[CodeObject: "1400 << CodeObjectInfo.str()1401 << "]\t:\t[Target: " << TargetInfo.str() << "]\n");1402 return false;1403 }1404 1405 // Incompatible if CodeObject has more features than Target, irrespective of1406 // type or sign of features.1407 if (CodeObjectFeatureMap.getNumItems() > TargetFeatureMap.getNumItems()) {1408 DEBUG_WITH_TYPE("CodeObjectCompatibility",1409 dbgs() << "Incompatible: CodeObject has more features "1410 "than target \t[CodeObject: "1411 << CodeObjectInfo.str()1412 << "]\t:\t[Target: " << TargetInfo.str() << "]\n");1413 return false;1414 }1415 1416 // Compatible if each target feature specified by target is compatible with1417 // target feature of code object. The target feature is compatible if the1418 // code object does not specify it (meaning Any), or if it specifies it1419 // with the same value (meaning On or Off).1420 for (const auto &CodeObjectFeature : CodeObjectFeatureMap) {1421 auto TargetFeature = TargetFeatureMap.find(CodeObjectFeature.getKey());1422 if (TargetFeature == TargetFeatureMap.end()) {1423 DEBUG_WITH_TYPE(1424 "CodeObjectCompatibility",1425 dbgs()1426 << "Incompatible: Value of CodeObject's non-ANY feature is "1427 "not matching with Target feature's ANY value \t[CodeObject: "1428 << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1429 << "]\n");1430 return false;1431 } else if (TargetFeature->getValue() != CodeObjectFeature.getValue()) {1432 DEBUG_WITH_TYPE(1433 "CodeObjectCompatibility",1434 dbgs() << "Incompatible: Value of CodeObject's non-ANY feature is "1435 "not matching with Target feature's non-ANY value "1436 "\t[CodeObject: "1437 << CodeObjectInfo.str()1438 << "]\t:\t[Target: " << TargetInfo.str() << "]\n");1439 return false;1440 }1441 }1442 1443 // CodeObject is compatible if all features of Target are:1444 // - either, present in the Code Object's features map with the same sign,1445 // - or, the feature is missing from CodeObjects's features map i.e. it is1446 // set to ANY1447 DEBUG_WITH_TYPE(1448 "CodeObjectCompatibility",1449 dbgs() << "Compatible: Target IDs are compatible \t[CodeObject: "1450 << CodeObjectInfo.str() << "]\t:\t[Target: " << TargetInfo.str()1451 << "]\n");1452 return true;1453}1454 1455/// Bundle the files. Return true if an error was found.1456Error OffloadBundler::BundleFiles() {1457 std::error_code EC;1458 1459 // Create a buffer to hold the content before compressing.1460 SmallVector<char, 0> Buffer;1461 llvm::raw_svector_ostream BufferStream(Buffer);1462 1463 // Open input files.1464 SmallVector<std::unique_ptr<MemoryBuffer>, 8u> InputBuffers;1465 InputBuffers.reserve(BundlerConfig.InputFileNames.size());1466 for (auto &I : BundlerConfig.InputFileNames) {1467 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1468 MemoryBuffer::getFileOrSTDIN(I, /*IsText=*/true);1469 if (std::error_code EC = CodeOrErr.getError())1470 return createFileError(I, EC);1471 InputBuffers.emplace_back(std::move(*CodeOrErr));1472 }1473 1474 // Get the file handler. We use the host buffer as reference.1475 assert((BundlerConfig.HostInputIndex != ~0u || BundlerConfig.AllowNoHost) &&1476 "Host input index undefined??");1477 Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr = CreateFileHandler(1478 *InputBuffers[BundlerConfig.AllowNoHost ? 01479 : BundlerConfig.HostInputIndex],1480 BundlerConfig);1481 if (!FileHandlerOrErr)1482 return FileHandlerOrErr.takeError();1483 1484 std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1485 assert(FH);1486 1487 // Write header.1488 if (Error Err = FH->WriteHeader(BufferStream, InputBuffers))1489 return Err;1490 1491 // Write all bundles along with the start/end markers. If an error was found1492 // writing the end of the bundle component, abort the bundle writing.1493 auto Input = InputBuffers.begin();1494 for (auto &Triple : BundlerConfig.TargetNames) {1495 if (Error Err = FH->WriteBundleStart(BufferStream, Triple))1496 return Err;1497 if (Error Err = FH->WriteBundle(BufferStream, **Input))1498 return Err;1499 if (Error Err = FH->WriteBundleEnd(BufferStream, Triple))1500 return Err;1501 ++Input;1502 }1503 1504 raw_fd_ostream OutputFile(BundlerConfig.OutputFileNames.front(), EC,1505 sys::fs::OF_None);1506 if (EC)1507 return createFileError(BundlerConfig.OutputFileNames.front(), EC);1508 1509 SmallVector<char, 0> CompressedBuffer;1510 if (BundlerConfig.Compress) {1511 std::unique_ptr<llvm::MemoryBuffer> BufferMemory =1512 llvm::MemoryBuffer::getMemBufferCopy(1513 llvm::StringRef(Buffer.data(), Buffer.size()));1514 auto CompressionResult = CompressedOffloadBundle::compress(1515 {BundlerConfig.CompressionFormat, BundlerConfig.CompressionLevel,1516 /*zstdEnableLdm=*/true},1517 *BufferMemory, BundlerConfig.CompressedBundleVersion,1518 BundlerConfig.Verbose);1519 if (auto Error = CompressionResult.takeError())1520 return Error;1521 1522 auto CompressedMemBuffer = std::move(CompressionResult.get());1523 CompressedBuffer.assign(CompressedMemBuffer->getBufferStart(),1524 CompressedMemBuffer->getBufferEnd());1525 } else1526 CompressedBuffer = Buffer;1527 1528 OutputFile.write(CompressedBuffer.data(), CompressedBuffer.size());1529 1530 return FH->finalizeOutputFile();1531}1532 1533// Unbundle the files. Return true if an error was found.1534Error OffloadBundler::UnbundleFiles() {1535 // Open Input file.1536 ErrorOr<std::unique_ptr<MemoryBuffer>> CodeOrErr =1537 MemoryBuffer::getFileOrSTDIN(BundlerConfig.InputFileNames.front(),1538 /*IsText=*/true);1539 if (std::error_code EC = CodeOrErr.getError())1540 return createFileError(BundlerConfig.InputFileNames.front(), EC);1541 1542 // Decompress the input if necessary.1543 Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1544 CompressedOffloadBundle::decompress(**CodeOrErr, BundlerConfig.Verbose);1545 if (!DecompressedBufferOrErr)1546 return createStringError(1547 inconvertibleErrorCode(),1548 "Failed to decompress input: " +1549 llvm::toString(DecompressedBufferOrErr.takeError()));1550 1551 MemoryBuffer &Input = **DecompressedBufferOrErr;1552 1553 // Select the right files handler.1554 Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1555 CreateFileHandler(Input, BundlerConfig);1556 if (!FileHandlerOrErr)1557 return FileHandlerOrErr.takeError();1558 1559 std::unique_ptr<FileHandler> &FH = *FileHandlerOrErr;1560 assert(FH);1561 1562 // Read the header of the bundled file.1563 if (Error Err = FH->ReadHeader(Input))1564 return Err;1565 1566 // Create a work list that consist of the map triple/output file.1567 StringMap<StringRef> Worklist;1568 auto Output = BundlerConfig.OutputFileNames.begin();1569 for (auto &Triple : BundlerConfig.TargetNames) {1570 if (!checkOffloadBundleID(Triple))1571 return createStringError(errc::invalid_argument,1572 "invalid bundle id from bundle config");1573 Worklist[Triple] = *Output;1574 ++Output;1575 }1576 1577 // Read all the bundles that are in the work list. If we find no bundles we1578 // assume the file is meant for the host target.1579 bool FoundHostBundle = false;1580 while (!Worklist.empty()) {1581 Expected<std::optional<StringRef>> CurTripleOrErr =1582 FH->ReadBundleStart(Input);1583 if (!CurTripleOrErr)1584 return CurTripleOrErr.takeError();1585 1586 // We don't have more bundles.1587 if (!*CurTripleOrErr)1588 break;1589 1590 StringRef CurTriple = **CurTripleOrErr;1591 assert(!CurTriple.empty());1592 if (!checkOffloadBundleID(CurTriple))1593 return createStringError(errc::invalid_argument,1594 "invalid bundle id read from the bundle");1595 1596 auto Output = Worklist.begin();1597 for (auto E = Worklist.end(); Output != E; Output++) {1598 if (isCodeObjectCompatible(1599 OffloadTargetInfo(CurTriple, BundlerConfig),1600 OffloadTargetInfo((*Output).first(), BundlerConfig))) {1601 break;1602 }1603 }1604 1605 if (Output == Worklist.end())1606 continue;1607 // Check if the output file can be opened and copy the bundle to it.1608 std::error_code EC;1609 raw_fd_ostream OutputFile((*Output).second, EC, sys::fs::OF_None);1610 if (EC)1611 return createFileError((*Output).second, EC);1612 if (Error Err = FH->ReadBundle(OutputFile, Input))1613 return Err;1614 if (Error Err = FH->ReadBundleEnd(Input))1615 return Err;1616 Worklist.erase(Output);1617 1618 // Record if we found the host bundle.1619 auto OffloadInfo = OffloadTargetInfo(CurTriple, BundlerConfig);1620 if (OffloadInfo.hasHostKind())1621 FoundHostBundle = true;1622 }1623 1624 if (!BundlerConfig.AllowMissingBundles && !Worklist.empty()) {1625 std::string ErrMsg = "Can't find bundles for";1626 std::set<StringRef> Sorted;1627 for (auto &E : Worklist)1628 Sorted.insert(E.first());1629 unsigned I = 0;1630 unsigned Last = Sorted.size() - 1;1631 for (auto &E : Sorted) {1632 if (I != 0 && Last > 1)1633 ErrMsg += ",";1634 ErrMsg += " ";1635 if (I == Last && I != 0)1636 ErrMsg += "and ";1637 ErrMsg += E.str();1638 ++I;1639 }1640 return createStringError(inconvertibleErrorCode(), ErrMsg);1641 }1642 1643 // If no bundles were found, assume the input file is the host bundle and1644 // create empty files for the remaining targets.1645 if (Worklist.size() == BundlerConfig.TargetNames.size()) {1646 for (auto &E : Worklist) {1647 std::error_code EC;1648 raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None);1649 if (EC)1650 return createFileError(E.second, EC);1651 1652 // If this entry has a host kind, copy the input file to the output file.1653 // We don't need to check E.getKey() here through checkOffloadBundleID1654 // because the entire WorkList has been checked above.1655 auto OffloadInfo = OffloadTargetInfo(E.getKey(), BundlerConfig);1656 if (OffloadInfo.hasHostKind())1657 OutputFile.write(Input.getBufferStart(), Input.getBufferSize());1658 }1659 return Error::success();1660 }1661 1662 // If we found elements, we emit an error if none of those were for the host1663 // in case host bundle name was provided in command line.1664 if (!(FoundHostBundle || BundlerConfig.HostInputIndex == ~0u ||1665 BundlerConfig.AllowMissingBundles))1666 return createStringError(inconvertibleErrorCode(),1667 "Can't find bundle for the host target");1668 1669 // If we still have any elements in the worklist, create empty files for them.1670 for (auto &E : Worklist) {1671 std::error_code EC;1672 raw_fd_ostream OutputFile(E.second, EC, sys::fs::OF_None);1673 if (EC)1674 return createFileError(E.second, EC);1675 }1676 1677 return Error::success();1678}1679 1680static Archive::Kind getDefaultArchiveKindForHost() {1681 return Triple(sys::getDefaultTargetTriple()).isOSDarwin() ? Archive::K_DARWIN1682 : Archive::K_GNU;1683}1684 1685/// @brief Computes a list of targets among all given targets which are1686/// compatible with this code object1687/// @param [in] CodeObjectInfo Code Object1688/// @param [out] CompatibleTargets List of all compatible targets among all1689/// given targets1690/// @return false, if no compatible target is found.1691static bool1692getCompatibleOffloadTargets(OffloadTargetInfo &CodeObjectInfo,1693 SmallVectorImpl<StringRef> &CompatibleTargets,1694 const OffloadBundlerConfig &BundlerConfig) {1695 if (!CompatibleTargets.empty()) {1696 DEBUG_WITH_TYPE("CodeObjectCompatibility",1697 dbgs() << "CompatibleTargets list should be empty\n");1698 return false;1699 }1700 for (auto &Target : BundlerConfig.TargetNames) {1701 auto TargetInfo = OffloadTargetInfo(Target, BundlerConfig);1702 if (isCodeObjectCompatible(CodeObjectInfo, TargetInfo))1703 CompatibleTargets.push_back(Target);1704 }1705 return !CompatibleTargets.empty();1706}1707 1708// Check that each code object file in the input archive conforms to following1709// rule: for a specific processor, a feature either shows up in all target IDs,1710// or does not show up in any target IDs. Otherwise the target ID combination is1711// invalid.1712static Error1713CheckHeterogeneousArchive(StringRef ArchiveName,1714 const OffloadBundlerConfig &BundlerConfig) {1715 std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;1716 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =1717 MemoryBuffer::getFileOrSTDIN(ArchiveName, true, false);1718 if (std::error_code EC = BufOrErr.getError())1719 return createFileError(ArchiveName, EC);1720 1721 ArchiveBuffers.push_back(std::move(*BufOrErr));1722 Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr =1723 Archive::create(ArchiveBuffers.back()->getMemBufferRef());1724 if (!LibOrErr)1725 return LibOrErr.takeError();1726 1727 auto Archive = std::move(*LibOrErr);1728 1729 Error ArchiveErr = Error::success();1730 auto ChildEnd = Archive->child_end();1731 1732 /// Iterate over all bundled code object files in the input archive.1733 for (auto ArchiveIter = Archive->child_begin(ArchiveErr);1734 ArchiveIter != ChildEnd; ++ArchiveIter) {1735 if (ArchiveErr)1736 return ArchiveErr;1737 auto ArchiveChildNameOrErr = (*ArchiveIter).getName();1738 if (!ArchiveChildNameOrErr)1739 return ArchiveChildNameOrErr.takeError();1740 1741 auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef();1742 if (!CodeObjectBufferRefOrErr)1743 return CodeObjectBufferRefOrErr.takeError();1744 1745 auto CodeObjectBuffer =1746 MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);1747 1748 Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1749 CreateFileHandler(*CodeObjectBuffer, BundlerConfig);1750 if (!FileHandlerOrErr)1751 return FileHandlerOrErr.takeError();1752 1753 std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;1754 assert(FileHandler);1755 1756 std::set<StringRef> BundleIds;1757 auto CodeObjectFileError =1758 FileHandler->getBundleIDs(*CodeObjectBuffer, BundleIds);1759 if (CodeObjectFileError)1760 return CodeObjectFileError;1761 1762 auto &&ConflictingArchs = clang::getConflictTargetIDCombination(BundleIds);1763 if (ConflictingArchs) {1764 std::string ErrMsg =1765 Twine("conflicting TargetIDs [" + ConflictingArchs.value().first +1766 ", " + ConflictingArchs.value().second + "] found in " +1767 ArchiveChildNameOrErr.get() + " of " + ArchiveName)1768 .str();1769 return createStringError(inconvertibleErrorCode(), ErrMsg);1770 }1771 }1772 1773 return ArchiveErr;1774}1775 1776/// UnbundleArchive takes an archive file (".a") as input containing bundled1777/// code object files, and a list of offload targets (not host), and extracts1778/// the code objects into a new archive file for each offload target. Each1779/// resulting archive file contains all code object files corresponding to that1780/// particular offload target. The created archive file does not1781/// contain an index of the symbols and code object files are named as1782/// <<Parent Bundle Name>-<CodeObject's TargetID>>, with ':' replaced with '_'.1783Error OffloadBundler::UnbundleArchive() {1784 std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;1785 1786 /// Map of target names with list of object files that will form the device1787 /// specific archive for that target1788 StringMap<std::vector<NewArchiveMember>> OutputArchivesMap;1789 1790 // Map of target names and output archive filenames1791 StringMap<StringRef> TargetOutputFileNameMap;1792 1793 auto Output = BundlerConfig.OutputFileNames.begin();1794 for (auto &Target : BundlerConfig.TargetNames) {1795 TargetOutputFileNameMap[Target] = *Output;1796 ++Output;1797 }1798 1799 StringRef IFName = BundlerConfig.InputFileNames.front();1800 1801 if (BundlerConfig.CheckInputArchive) {1802 // For a specific processor, a feature either shows up in all target IDs, or1803 // does not show up in any target IDs. Otherwise the target ID combination1804 // is invalid.1805 auto ArchiveError = CheckHeterogeneousArchive(IFName, BundlerConfig);1806 if (ArchiveError) {1807 return ArchiveError;1808 }1809 }1810 1811 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOrErr =1812 MemoryBuffer::getFileOrSTDIN(IFName, true, false);1813 if (std::error_code EC = BufOrErr.getError())1814 return createFileError(BundlerConfig.InputFileNames.front(), EC);1815 1816 ArchiveBuffers.push_back(std::move(*BufOrErr));1817 Expected<std::unique_ptr<llvm::object::Archive>> LibOrErr =1818 Archive::create(ArchiveBuffers.back()->getMemBufferRef());1819 if (!LibOrErr)1820 return LibOrErr.takeError();1821 1822 auto Archive = std::move(*LibOrErr);1823 1824 Error ArchiveErr = Error::success();1825 auto ChildEnd = Archive->child_end();1826 1827 /// Iterate over all bundled code object files in the input archive.1828 for (auto ArchiveIter = Archive->child_begin(ArchiveErr);1829 ArchiveIter != ChildEnd; ++ArchiveIter) {1830 if (ArchiveErr)1831 return ArchiveErr;1832 auto ArchiveChildNameOrErr = (*ArchiveIter).getName();1833 if (!ArchiveChildNameOrErr)1834 return ArchiveChildNameOrErr.takeError();1835 1836 StringRef BundledObjectFile = sys::path::filename(*ArchiveChildNameOrErr);1837 1838 auto CodeObjectBufferRefOrErr = (*ArchiveIter).getMemoryBufferRef();1839 if (!CodeObjectBufferRefOrErr)1840 return CodeObjectBufferRefOrErr.takeError();1841 1842 auto TempCodeObjectBuffer =1843 MemoryBuffer::getMemBuffer(*CodeObjectBufferRefOrErr, false);1844 1845 // Decompress the buffer if necessary.1846 Expected<std::unique_ptr<MemoryBuffer>> DecompressedBufferOrErr =1847 CompressedOffloadBundle::decompress(*TempCodeObjectBuffer,1848 BundlerConfig.Verbose);1849 if (!DecompressedBufferOrErr)1850 return createStringError(1851 inconvertibleErrorCode(),1852 "Failed to decompress code object: " +1853 llvm::toString(DecompressedBufferOrErr.takeError()));1854 1855 MemoryBuffer &CodeObjectBuffer = **DecompressedBufferOrErr;1856 1857 Expected<std::unique_ptr<FileHandler>> FileHandlerOrErr =1858 CreateFileHandler(CodeObjectBuffer, BundlerConfig);1859 if (!FileHandlerOrErr)1860 return FileHandlerOrErr.takeError();1861 1862 std::unique_ptr<FileHandler> &FileHandler = *FileHandlerOrErr;1863 assert(FileHandler &&1864 "FileHandle creation failed for file in the archive!");1865 1866 if (Error ReadErr = FileHandler->ReadHeader(CodeObjectBuffer))1867 return ReadErr;1868 1869 Expected<std::optional<StringRef>> CurBundleIDOrErr =1870 FileHandler->ReadBundleStart(CodeObjectBuffer);1871 if (!CurBundleIDOrErr)1872 return CurBundleIDOrErr.takeError();1873 1874 std::optional<StringRef> OptionalCurBundleID = *CurBundleIDOrErr;1875 // No device code in this child, skip.1876 if (!OptionalCurBundleID)1877 continue;1878 StringRef CodeObject = *OptionalCurBundleID;1879 1880 // Process all bundle entries (CodeObjects) found in this child of input1881 // archive.1882 while (!CodeObject.empty()) {1883 SmallVector<StringRef> CompatibleTargets;1884 if (!checkOffloadBundleID(CodeObject)) {1885 return createStringError(errc::invalid_argument,1886 "Invalid bundle id read from code object");1887 }1888 auto CodeObjectInfo = OffloadTargetInfo(CodeObject, BundlerConfig);1889 if (getCompatibleOffloadTargets(CodeObjectInfo, CompatibleTargets,1890 BundlerConfig)) {1891 std::string BundleData;1892 raw_string_ostream DataStream(BundleData);1893 if (Error Err = FileHandler->ReadBundle(DataStream, CodeObjectBuffer))1894 return Err;1895 1896 for (auto &CompatibleTarget : CompatibleTargets) {1897 SmallString<128> BundledObjectFileName;1898 BundledObjectFileName.assign(BundledObjectFile);1899 auto OutputBundleName =1900 Twine(llvm::sys::path::stem(BundledObjectFileName) + "-" +1901 CodeObject +1902 getDeviceLibraryFileName(BundledObjectFileName,1903 CodeObjectInfo.TargetID))1904 .str();1905 // Replace ':' in optional target feature list with '_' to ensure1906 // cross-platform validity.1907 llvm::replace(OutputBundleName, ':', '_');1908 1909 std::unique_ptr<MemoryBuffer> MemBuf = MemoryBuffer::getMemBufferCopy(1910 DataStream.str(), OutputBundleName);1911 ArchiveBuffers.push_back(std::move(MemBuf));1912 llvm::MemoryBufferRef MemBufRef =1913 MemoryBufferRef(*(ArchiveBuffers.back()));1914 1915 // For inserting <CompatibleTarget, list<CodeObject>> entry in1916 // OutputArchivesMap.1917 OutputArchivesMap[CompatibleTarget].push_back(1918 NewArchiveMember(MemBufRef));1919 }1920 }1921 1922 if (Error Err = FileHandler->ReadBundleEnd(CodeObjectBuffer))1923 return Err;1924 1925 Expected<std::optional<StringRef>> NextTripleOrErr =1926 FileHandler->ReadBundleStart(CodeObjectBuffer);1927 if (!NextTripleOrErr)1928 return NextTripleOrErr.takeError();1929 1930 CodeObject = ((*NextTripleOrErr).has_value()) ? **NextTripleOrErr : "";1931 } // End of processing of all bundle entries of this child of input archive.1932 } // End of while over children of input archive.1933 1934 assert(!ArchiveErr && "Error occurred while reading archive!");1935 1936 /// Write out an archive for each target1937 for (auto &Target : BundlerConfig.TargetNames) {1938 StringRef FileName = TargetOutputFileNameMap[Target];1939 auto CurArchiveMembers = OutputArchivesMap.find(Target);1940 if (CurArchiveMembers != OutputArchivesMap.end()) {1941 if (Error WriteErr = writeArchive(FileName, CurArchiveMembers->getValue(),1942 SymtabWritingMode::NormalSymtab,1943 getDefaultArchiveKindForHost(), true,1944 false, nullptr))1945 return WriteErr;1946 } else if (!BundlerConfig.AllowMissingBundles) {1947 std::string ErrMsg =1948 Twine("no compatible code object found for the target '" + Target +1949 "' in heterogeneous archive library: " + IFName)1950 .str();1951 return createStringError(inconvertibleErrorCode(), ErrMsg);1952 } else { // Create an empty archive file if no compatible code object is1953 // found and "allow-missing-bundles" is enabled. It ensures that1954 // the linker using output of this step doesn't complain about1955 // the missing input file.1956 std::vector<llvm::NewArchiveMember> EmptyArchive;1957 EmptyArchive.clear();1958 if (Error WriteErr = writeArchive(1959 FileName, EmptyArchive, SymtabWritingMode::NormalSymtab,1960 getDefaultArchiveKindForHost(), true, false, nullptr))1961 return WriteErr;1962 }1963 }1964 1965 return Error::success();1966}1967 1968bool clang::checkOffloadBundleID(const llvm::StringRef Str) {1969 // <kind>-<triple>[-<target id>[:target features]]1970 // <triple> := <arch>-<vendor>-<os>-<env>1971 SmallVector<StringRef, 6> Components;1972 Str.split(Components, '-', /*MaxSplit=*/5);1973 return Components.size() == 5 || Components.size() == 6;1974}1975