diff --git a/clang/test/Driver/clang-offload-bundler-missing-size-section.cpp b/clang/test/Driver/clang-offload-bundler-missing-size-section.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Driver/clang-offload-bundler-missing-size-section.cpp @@ -0,0 +1,44 @@ +// REQUIRES: x86-registered-target +// RUN: %clangxx -c %s -o %t_fat.o +// RUN: %clangxx %t_fat.o -o %t.exe +// RUN: clang-offload-bundler -type=o -targets=host-x86_64-unknown-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t_host.o,%t_device.o -inputs=%t_fat.o -unbundle +// RUN: %t.exe %t_device.o | FileCheck %s +// CHECK:11 + +#include +#include +#include +using namespace std; + +#define BUNDLE_SECTION_PREFIX "__CLANG_OFFLOAD_BUNDLE__" + +#define TARGET0 "host-x86_64-unknown-linux-gnu" +#define TARGET1 "openmp-x86_64-pc-linux-gnu" + +// Populate section with special names recognized by the bundler; +// this emulates fat object with one host and one device section. +// The size sections are missing, emulating the old fat object format. +char str0[] __attribute__((section(BUNDLE_SECTION_PREFIX TARGET0))) = { 0 }; +char str1[] __attribute__((section(BUNDLE_SECTION_PREFIX TARGET1))) = { "11\n" }; + +// main is invoked with the bundler output file as argument - +// read this file and print their contents to stdout. +int main(int argc, char **argv) { + string DeviceObj(argv[1]); + string Line; + ifstream F(DeviceObj); + + if (F.is_open()) { + while (getline(F, Line)) { + cout << Line; + } + F.close(); + } + else { + cout << "Unable to open file " << DeviceObj; + return 1; + } + + return 0; +} + diff --git a/clang/test/Driver/clang-offload-bundler-oo.cpp b/clang/test/Driver/clang-offload-bundler-oo.cpp new file mode 100755 --- /dev/null +++ b/clang/test/Driver/clang-offload-bundler-oo.cpp @@ -0,0 +1,69 @@ +// REQUIRES: x86-registered-target +// RUN: %clangxx -c %s -o %t_fat.o +// RUN: %clangxx %t_fat.o -o %t.exe +// RUN: clang-offload-bundler -type=oo -targets=host-x86_64-unknown-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.o,%t_list.txt -inputs=%t_fat.o -unbundle +// RUN: %t.exe %t_list.txt | FileCheck %s +// CHECK:11 +// CHECK:222 +// CHECK:3333 + +#include +#include +#include +#include +#include +using namespace std; + +#define BUNDLE_SECTION_PREFIX "__CLANG_OFFLOAD_BUNDLE__" +#define BUNDLE_SIZE_SECTION_PREFIX "__CLANG_OFFLOAD_BUNDLE_SIZE__" + +#define TARGET0 "host-x86_64-unknown-linux-gnu" +#define TARGET1 "openmp-x86_64-pc-linux-gnu" + +// Populate section with special names recognized by the bundler; +// this emulates fat object partially linked from 3 other fat objects. +// The test uses the bundler to split the bundle into 3 objects and then prints +// their contents to stdout. +char str0[] __attribute__((section(BUNDLE_SECTION_PREFIX TARGET0))) = { 0, 0, 0 }; +int64_t size0[] __attribute__((section(BUNDLE_SIZE_SECTION_PREFIX TARGET0))) = { 1, 1, 1 }; + +char str1[] __attribute__((section(BUNDLE_SECTION_PREFIX TARGET1))) = { "11\n222\n3333\n" }; +int64_t size1[] __attribute__((section(BUNDLE_SIZE_SECTION_PREFIX TARGET1))) = { 3, 4, 5 }; + +void cat(const string& File) { + string Line; + ifstream F(File); + if (F.is_open()) { + while (getline(F, Line)) { + cout << Line << '\n'; + } + F.close(); + } + else cout << "Unable to open file " << File; +} + +// main is invoked with the bundler output file as argument - +// read this file and print their contents to stdout. +int main(int argc, char **argv) { + string ListFile(argv[1]); + string Line; + ifstream F(ListFile); + vector OutFiles; + + if (F.is_open()) { + while (getline(F, Line)) { + OutFiles.push_back(Line); + } + F.close(); + } + else { + cout << "Unable to open file " << ListFile; + return 1; + } + + for (const auto &File : OutFiles) { + cat(File); + } + return 0; +} + diff --git a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp --- a/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ b/clang/tools/clang-offload-bundler/ClangOffloadBundler.cpp @@ -26,6 +26,7 @@ #include "llvm/Object/ObjectFile.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/Endian.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/ErrorOr.h" @@ -80,6 +81,7 @@ " bc - llvm-bc\n" " s - assembler\n" " o - object\n" + " oo - object; output file is a list of unbundled objects\n" " gch - precompiled-header\n" " ast - clang AST file"), cl::cat(ClangOffloadBundlerCategory)); @@ -97,6 +99,9 @@ /// Magic string that marks the existence of offloading data. #define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" +/// Prefix of an added section name with bundle size. +#define SIZE_SECTION_PREFIX "__CLANG_OFFLOAD_BUNDLE_SIZE__" + /// The index of the host input in the list of inputs. static unsigned HostInputIndex = ~0u; @@ -141,6 +146,18 @@ /// Read the current bundle and write the result into the stream \a OS. virtual Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) = 0; + /// Read the current bundle and write the result into the file \a FileName. + /// The meaning of \a FileName depends on unbundling type - in some + /// cases (type="oo") it will contain a list of actual outputs. + virtual Error ReadBundle(StringRef FileName, MemoryBuffer &Input) { + std::error_code EC; + raw_fd_ostream OS(FileName, EC, sys::fs::OF_None); + + if (EC) + return createFileError(FileName, EC); + return ReadBundle(OS, Input); + } + /// Write the header of the bundled file to \a OS based on the information /// gathered from \a Inputs. virtual Error WriteHeader(raw_fd_ostream &OS, @@ -157,6 +174,13 @@ /// Write the bundle from \a Input into \a OS. virtual Error WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) = 0; + + /// Sets a base name for temporary filename generation. + void SetTempFileNameBase(StringRef Base) { TempFileNameBase = Base.data(); } + +protected: + /// Serves as a base name for temporary filename generation. + std::string TempFileNameBase; }; /// Handler for binary files. The bundled file will have the following format @@ -308,6 +332,8 @@ return Error::success(); } + using FileHandler::ReadBundle; // to avoid hiding via the overload below + Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { assert(CurBundleInfo != BundlesInfo.end() && "Invalid reader info!"); StringRef FC = Input.getBuffer(); @@ -403,27 +429,99 @@ /// designated name. /// /// To unbundle, we just copy the contents of the designated section. +/// +/// The bundler produces object file in host target native format (e.g. ELF for +/// Linux). The sections it creates are: +/// +/// +/// | +/// | binary data for the 's bundle +/// | +/// +/// | size of the 's bundle (8 bytes) +/// ... +/// +/// | +/// | binary data for the 's bundle +/// | +/// +/// | size of the 's bundle (8 bytes) +/// ... +/// +/// | 0 (1 byte long) +/// +/// | 1 (8 bytes) +/// ... +/// +/// Further, these fat objects can be "partially" linked by a platform linker: +/// 1) ld -r a_fat.o b_fat.o c_fat.o -o abc_fat.o +/// 2) ld -r a_fat.o -L. -lbc -o abc_fat.o +/// where libbc.a is a static library created from b_fat.o and c_fat.o. +/// This will still result in a fat object. But this object will have bundle and +/// size sections for the same triple concatenated: +/// ... +/// +/// | binary data for the 's bundle (from a_fat.o) +/// | binary data for the 's bundle (from b_fat.o) +/// | binary data for the 's bundle (from c_fat.o) +/// +/// | size of the 's bundle (8 bytes) (from a_fat.o) +/// | size of the 's bundle (8 bytes) (from b_fat.o) +/// | size of the 's bundle (8 bytes) (from c_fat.o) +/// ... +/// +/// The alignment of all the added sections is set to one to avoid padding +/// between concatenated parts. +/// +/// The unbundler is able to unbundle both kinds of fat objects. The first one +/// (non-partially linked) can be handled either with -type=o or -type=oo, +/// whereas the second one with -type=oo option only. In the latter case +/// unbundling may result in multiple output files per target and the output +/// file specified in the command line contains a list with the names of the +/// actual outputs. +/// class ObjectFileHandler final : public FileHandler { + /// Keeps infomation about a bundle for a particular target. + struct BundleInfo final { + /// The section that contains bundle data, can be a concatenation of a + /// number of individual bundles if produced via partial linkage of multiple + /// fat objects. + section_iterator BundleSection; + /// The sizes (in correct order) of the individual bundles constituting + /// bundle data. + SmallVector ObjectSizes; + + BundleInfo(section_iterator S) : BundleSection(S) {} + }; + /// The object file we are currently dealing with. std::unique_ptr Obj; + /// Maps triple string to its bundle information + StringMap> TripleToBundleInfo; + /// The two iterators below are to support the + /// ReadBundleStart/ReadBundle/ReadBundleEnd iteration mechanism + StringMap>::iterator CurBundle; + StringMap>::iterator NextBundle; + /// Return the input file contents. StringRef getInputFileContents() const { return Obj->getData(); } /// Return bundle name (-) if the provided section is an offload /// section. - static Expected> IsOffloadSection(SectionRef CurSection) { + static Expected> IsOffloadSection(SectionRef CurSection, + StringRef NamePrefix) { Expected NameOrErr = CurSection.getName(); if (!NameOrErr) return NameOrErr.takeError(); - // If it does not start with the reserved suffix, just skip this section. - if (!NameOrErr->startswith(OFFLOAD_BUNDLER_MAGIC_STR)) + // If it does not start with given prefix, just skip this section. + if (!NameOrErr->startswith(NamePrefix)) return None; - // Return the triple that is right after the reserved prefix. - return NameOrErr->substr(sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); + // Return the suffix (the triple that is right after the reserved prefix). + return NameOrErr->substr(NamePrefix.size()); } /// Total number of inputs. @@ -433,50 +531,194 @@ /// read from the buffers. unsigned NumberOfProcessedInputs = 0; - /// Iterator of the current and next section. - section_iterator CurrentSection; - section_iterator NextSection; + /// Input sizes. + SmallVector InputSizes; public: ObjectFileHandler(std::unique_ptr ObjIn) : FileHandler(), Obj(std::move(ObjIn)), - CurrentSection(Obj->section_begin()), - NextSection(Obj->section_begin()) {} + CurBundle(TripleToBundleInfo.end()), + NextBundle(TripleToBundleInfo.end()) {} ~ObjectFileHandler() final {} - Error ReadHeader(MemoryBuffer &Input) final { return Error::success(); } + // Iterate through sections and create a map from triple to relevant bundle + // information. + Error ReadHeader(MemoryBuffer &Input) final { + for (section_iterator Sec = Obj->section_begin(); Sec != Obj->section_end(); + ++Sec) { + // Test if current section is an offload bundle section + Expected> BundleOrErr = + IsOffloadSection(*Sec, OFFLOAD_BUNDLER_MAGIC_STR); + if (!BundleOrErr) + return BundleOrErr.takeError(); + if (*BundleOrErr) { + StringRef OffloadTriple = **BundleOrErr; + std::unique_ptr &BI = TripleToBundleInfo[OffloadTriple]; + // A BundleInfo entry for this bundle should either: + // 1) not exist yet in which case we allocate it and initialize the + // BundleSection iterator to Sec, while leaving ObjectSizes empty. + // 2) have been created if we previously encountered the size section + // for this target in which case the BundleSection iterator should + // point to Obj->section_end() and we initialize it now to Sec. + assert(!BI.get() || BI->BundleSection == Obj->section_end()); + + if (!BI.get()) { + BI.reset(new BundleInfo(Sec)); + } else { + BI->BundleSection = Sec; + } + continue; + } + // Test if current section is an offload bundle size section + BundleOrErr = IsOffloadSection(*Sec, SIZE_SECTION_PREFIX); + if (!BundleOrErr) + return BundleOrErr.takeError(); + if (*BundleOrErr) { + StringRef OffloadTriple = **BundleOrErr; + + // yes, it is - parse object sizes + Expected Content = Sec->getContents(); + if (!Content) + return Content.takeError(); + unsigned int ElemSize = sizeof(uint64_t); + + // the size of the size section must be a multiple of ElemSize + if (Content->size() % ElemSize != 0) + return createStringError( + errc::invalid_argument, + "invalid size of the bundle size section for triple " + + OffloadTriple + ": " + Twine(Content->size())); + // read sizes + llvm::support::endianness E = Obj->isLittleEndian() + ? llvm::support::endianness::little + : llvm::support::endianness::big; + std::unique_ptr &BI = TripleToBundleInfo[OffloadTriple]; + // A BundleInfo entry for this bundle should either: + // 1) not exist yet in which case we allocate it and initialize the + // BundleSection iterator to Obj->section_end(). + // 2) have been created if we previously encountered the data section + // for this target in which case ObjectSizes should be empty. + assert(!BI.get() || BI->ObjectSizes.size() == 0); + + if (!BI.get()) { + BI.reset(new BundleInfo(Obj->section_end())); + } + for (const char *Ptr = Content->data(); + Ptr < Content->data() + Content->size(); Ptr += ElemSize) { + uint64_t Size = support::endian::read64(Ptr, E); + BI->ObjectSizes.push_back(Size); + } + } + } + NextBundle = TripleToBundleInfo.begin(); + return Error::success(); + } Expected> ReadBundleStart(MemoryBuffer &Input) final { - while (NextSection != Obj->section_end()) { - CurrentSection = NextSection; - ++NextSection; - - // Check if the current section name starts with the reserved prefix. If - // so, return the triple. - Expected> TripleOrErr = - IsOffloadSection(*CurrentSection); - if (!TripleOrErr) - return TripleOrErr.takeError(); - if (*TripleOrErr) - return **TripleOrErr; - } - return None; + if (NextBundle == TripleToBundleInfo.end()) + return None; + CurBundle = NextBundle; + NextBundle++; + return CurBundle->getKey(); } Error ReadBundleEnd(MemoryBuffer &Input) final { return Error::success(); } Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { - Expected ContentOrErr = CurrentSection->getContents(); - if (!ContentOrErr) - return ContentOrErr.takeError(); - StringRef Content = *ContentOrErr; + llvm_unreachable("must not be called for the ObjectFileHandler"); + } - // Copy fat object contents to the output when extracting host bundle. - if (Content.size() == 1u && Content.front() == 0) - Content = StringRef(Input.getBufferStart(), Input.getBufferSize()); + Error ReadBundle(StringRef OutName, MemoryBuffer &Input) final { + assert(CurBundle != TripleToBundleInfo.end() && + "all bundles have been read already"); - OS.write(Content.data(), Content.size()); + // TODO: temporary workaround to copy the entire fat object to the host + // output until driver is fixed to correctly handle list file for the host + // bundle in 'oo' mode. + if (FilesType == "oo" && hasHostKind(CurBundle->getKey())) { + std::error_code EC; + raw_fd_ostream OS(OutName, EC); + + if (EC) + return createFileError(OutName, EC); + OS.write(Input.getBufferStart(), Input.getBufferSize()); + return Error::success(); + } + + // Read content of the section representing the bundle + Expected Content = + CurBundle->second->BundleSection->getContents(); + if (!Content) + return Content.takeError(); + + // Backwards compatibility adjustment: object files created with older + // versions of clang-offload-bundler (before support for partially-linked + // objects) do not contain a sizes section and rightfully so because such a + // section was not necessary. However, the current version of the bundler + // expects the object to have a sizes section, even if it is a + // non-partially-linked one. Emulate the existence of a sizes section by + // adding the related number into ObjectSizes manually. + if (CurBundle->second->ObjectSizes.empty() && FilesType == "o") { + CurBundle->second->ObjectSizes.push_back(Content->size()); + } + + const char *ObjData = Content->data(); + // Determine the number of "device objects" (or individual bundles + // concatenated by partial linkage) in the bundle: + const auto &SizeVec = CurBundle->second->ObjectSizes; + auto NumObjects = SizeVec.size(); + bool FileListMode = FilesType == "oo"; + + if (NumObjects > 1 && !FileListMode) + return createStringError( + errc::invalid_argument, + "'o' file type is requested, but the fat object contains multiple " + "device objects; use 'oo' instead"); + std::string FileList; + + // Iterate through individual objects and extract them + for (size_t I = 0; I < NumObjects; ++I) { + uint64_t ObjSize = SizeVec[I]; + StringRef ObjFileName = OutName; + SmallString<128> Path; + + // If not in file list mode there is no need for a temporary file - output + // goes directly to what was specified in -outputs. The same is true for + // the host triple. + if (FileListMode) { + std::error_code EC = + sys::fs::createTemporaryFile(TempFileNameBase, "devo", Path); + ObjFileName = Path.data(); + + if (EC) + return createFileError(ObjFileName, EC); + } + std::error_code EC; + raw_fd_ostream OS(ObjFileName, EC); + + if (EC) + return createFileError(ObjFileName, EC); + OS.write(ObjData, ObjSize); + + if (FileListMode) { + // add the written file name to the output list of files + FileList = (Twine(FileList) + Twine(ObjFileName) + Twine("\n")).str(); + } + // Move "object data" pointer to the next object within the concatenated + // bundle. + ObjData += ObjSize; + } + if (FileListMode) { + // dump the list of files into the file list specified in -outputs for the + // current target + std::error_code EC; + raw_fd_ostream OS1(OutName, EC); + + if (EC) + return createFileError(OutName, EC); + OS1.write(FileList.data(), FileList.size()); + } return Error::success(); } @@ -486,6 +728,10 @@ // Record number of inputs. NumberOfInputs = Inputs.size(); + + // And input sizes. + for (unsigned I = 0; I < NumberOfInputs; ++I) + InputSizes.push_back(Inputs[I]->getBufferSize()); return Error::success(); } @@ -555,6 +801,17 @@ ObjcopyArgs.push_back(SS.save(Twine("--add-section=") + OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + "=" + InputFile)); + + // Create temporary file with the section size contents. + Expected SizeFileOrErr = TempFiles.Create(makeArrayRef( + reinterpret_cast(&InputSizes[I]), sizeof(InputSizes[I]))); + if (!SizeFileOrErr) + return SizeFileOrErr.takeError(); + + // And add one more section with target object size. + ObjcopyArgs.push_back(SS.save(Twine("--add-section=") + + SIZE_SECTION_PREFIX + TargetNames[I] + "=" + + *SizeFileOrErr)); } ObjcopyArgs.push_back(InputFileNames[HostInputIndex]); ObjcopyArgs.push_back(IntermediateObj); @@ -564,10 +821,14 @@ // And run llvm-objcopy for the second time to update section flags. ObjcopyArgs.resize(1); - for (unsigned I = 0; I < NumberOfInputs; ++I) + for (unsigned I = 0; I < NumberOfInputs; ++I) { ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + OFFLOAD_BUNDLER_MAGIC_STR + TargetNames[I] + "=readonly,exclude")); + ObjcopyArgs.push_back(SS.save(Twine("--set-section-flags=") + + SIZE_SECTION_PREFIX + TargetNames[I] + + "=readonly,exclude")); + } ObjcopyArgs.push_back(IntermediateObj); ObjcopyArgs.push_back(OutputFileNames.front()); @@ -660,6 +921,8 @@ return Error::success(); } + using FileHandler::ReadBundle; // to avoid hiding via the overload below + Error ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) final { StringRef FC = Input.getBuffer(); size_t BundleStart = ReadChars; @@ -741,7 +1004,7 @@ return std::make_unique(); if (FilesType == "s") return std::make_unique(/*Comment=*/"#"); - if (FilesType == "o") + if (FilesType == "o" || FilesType == "oo") return CreateObjectFileHandler(FirstInput); if (FilesType == "gch") return std::make_unique(); @@ -820,6 +1083,9 @@ std::unique_ptr &FH = *FileHandlerOrErr; assert(FH); + // Seed temporary filename generation with the stem of the input file. + FH->SetTempFileNameBase(llvm::sys::path::stem(InputFileNames.front())); + // Read the header of the bundled file. if (Error Err = FH->ReadHeader(Input)) return Err; @@ -854,11 +1120,7 @@ continue; // Check if the output file can be opened and copy the bundle to it. - std::error_code EC; - raw_fd_ostream OutputFile(Output->second, EC, sys::fs::OF_None); - if (EC) - return createFileError(Output->second, EC); - if (Error Err = FH->ReadBundle(OutputFile, Input)) + if (Error Err = FH->ReadBundle(Output->second, Input)) return Err; if (Error Err = FH->ReadBundleEnd(Input)) return Err; @@ -940,6 +1202,10 @@ reportError(createStringError(errc::invalid_argument, "number of output files and targets should " "match in unbundling mode")); + if (FilesType == "oo") { + Error = true; + errs() << "error: type \"oo\" cannot be used in bundling mode\n."; + } } } else { if (OutputFileNames.size() != 1) {