Index: test/Driver/clang-offload-bundler.c =================================================================== --- test/Driver/clang-offload-bundler.c +++ test/Driver/clang-offload-bundler.c @@ -6,6 +6,7 @@ // RUN: %clang -O0 -target powerpc64le-ibm-linux-gnu %s -S -emit-llvm -o %t.ll // RUN: %clang -O0 -target powerpc64le-ibm-linux-gnu %s -c -emit-llvm -o %t.bc // RUN: %clang -O0 -target powerpc64le-ibm-linux-gnu %s -S -o %t.s +// RUN: %clang -O0 -target powerpc64le-ibm-linux-gnu %s -c -o %t.o // RUN: %clang -O0 -target powerpc64le-ibm-linux-gnu %s -emit-ast -o %t.ast // @@ -181,6 +182,67 @@ // RUN: diff %t.empty %t.res.tgt1 // RUN: diff %t.empty %t.res.tgt2 +// +// Check object bundle/unbundle. The content should be bundled into an ELF section (we are using a PowerPC little-endian host which uses ELF). +// +// RUN: clang-offload-bundler -type=o -targets=host-powerpc64le-ibm-linux-gnu,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -inputs=%t.o,%t.tgt1,%t.tgt2 -outputs=%t.bundle3.o +// RUN: llvm-readobj -sections %t.bundle3.o | FileCheck %s --check-prefix CK-OBJ +// RUN: clang-offload-bundler -type=o -targets=host-powerpc64le-ibm-linux-gnu,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.o,%t.res.tgt1,%t.res.tgt2 -inputs=%t.bundle3.o -unbundle +// RUN: diff %t.bundle3.o %t.res.o +// RUN: diff %t.tgt1 %t.res.tgt1 +// RUN: diff %t.tgt2 %t.res.tgt2 +// CK-OBJ: Section { +// CK-OBJ: Index: +// CK-OBJ: Name: __CLANG_OFFLOAD_BUNDLE__host-powerpc64le-ibm-linux-gnu ( +// CK-OBJ: Type: SHT_PROGBITS (0x1) +// CK-OBJ: Flags [ (0x2) +// CK-OBJ: SHF_ALLOC (0x2) +// CK-OBJ: ] +// CK-OBJ: Address: 0x0 +// CK-OBJ: Offset: 0x +// CK-OBJ: Size: 1 +// CK-OBJ: Link: 0 +// CK-OBJ: Info: 0 +// CK-OBJ: AddressAlignment: +// CK-OBJ: EntrySize: 0 +// CK-OBJ: } +// CK-OBJ: Section { +// CK-OBJ: Index: +// CK-OBJ: Name: __CLANG_OFFLOAD_BUNDLE__openmp-powerpc64le-ibm-linux-gnu ( +// CK-OBJ: Type: SHT_PROGBITS (0x1) +// CK-OBJ: Flags [ (0x2) +// CK-OBJ: SHF_ALLOC (0x2) +// CK-OBJ: ] +// CK-OBJ: Address: 0x0 +// CK-OBJ: Offset: 0x +// CK-OBJ: Size: 25 +// CK-OBJ: Link: 0 +// CK-OBJ: Info: 0 +// CK-OBJ: AddressAlignment: +// CK-OBJ: EntrySize: 0 +// CK-OBJ: } +// CK-OBJ: Section { +// CK-OBJ: Index: +// CK-OBJ: Name: __CLANG_OFFLOAD_BUNDLE__openmp-x86_64-pc-linux-gnu ( +// CK-OBJ: Type: SHT_PROGBITS (0x1) +// CK-OBJ: Flags [ (0x2) +// CK-OBJ: SHF_ALLOC (0x2) +// CK-OBJ: ] +// CK-OBJ: Address: 0x0 +// CK-OBJ: Offset: 0x +// CK-OBJ: Size: 25 +// CK-OBJ: Link: 0 +// CK-OBJ: Info: 0 +// CK-OBJ: AddressAlignment: +// CK-OBJ: EntrySize: 0 +// CK-OBJ: } + +// Check if we can unbundle a file with no magic strings. +// RUN: clang-offload-bundler -type=o -targets=host-powerpc64le-ibm-linux-gnu,openmp-powerpc64le-ibm-linux-gnu,openmp-x86_64-pc-linux-gnu -outputs=%t.res.o,%t.res.tgt1,%t.res.tgt2 -inputs=%t.o -unbundle +// RUN: diff %t.o %t.res.o +// RUN: diff %t.empty %t.res.tgt1 +// RUN: diff %t.empty %t.res.tgt2 + // Some code so that we can create a binary out of this file. int A = 0; void test_func(void) { Index: tools/clang-offload-bundler/ClangOffloadBundler.cpp =================================================================== --- tools/clang-offload-bundler/ClangOffloadBundler.cpp +++ tools/clang-offload-bundler/ClangOffloadBundler.cpp @@ -75,6 +75,9 @@ /// \brief Magic string that marks the existence of offloading data. #define OFFLOAD_BUNDLER_MAGIC_STR "__CLANG_OFFLOAD_BUNDLE__" +/// \brief Path to the current binary. +static std::string BundlerExecutable; + /// \brief Obtain the offload kind and real machine triple out of the target /// information specified by the user. static void getOffloadKindAndTriple(StringRef Target, StringRef &OffloadKind, @@ -83,6 +86,12 @@ OffloadKind = KindTriplePair.first; Triple = KindTriplePair.second; } +static StringRef getTriple(StringRef Target) { + StringRef OffloadKind; + StringRef Triple; + getOffloadKindAndTriple(Target, OffloadKind, Triple); + return Triple; +} static bool hasHostKind(StringRef Target) { StringRef OffloadKind; StringRef Triple; @@ -312,6 +321,211 @@ ~BinaryFileHandler() {} }; +// Handler for object files. The bundles are organized by sections with a +// designated name. +// +// In order to bundle we create an IR file with the content of each section and +// use incremental linking to produce the resulting object. We also add section +// with a single byte to state the name of the component the main object file +// (the one we are bundling into) refers to. +// +// To unbundle, we use just copy the contents of the designated section. If the +// requested bundle refer to the main object file, we just copy it with no +// changes. +class ObjectFileHandler : public FileHandler { + + /// \brief The object file we are currently dealing with. + ObjectFile &Obj; + + /// \brief Return the input file contents. + StringRef getInputFileContents() const { return Obj.getData(); } + + /// \brief Return true if the provided section is an offload section and + /// return the triple by reference. + bool isOffloadSection(SectionRef CurSection, StringRef &OffloadTriple) { + StringRef SectionName; + CurSection.getName(SectionName); + + if (SectionName.empty()) + return false; + + // If it does not start with the reserved suffix, just skip this section. + if (!SectionName.startswith(OFFLOAD_BUNDLER_MAGIC_STR)) + return false; + + // Return the triple that is right after the reserved prefix. + OffloadTriple = SectionName.substr(sizeof(OFFLOAD_BUNDLER_MAGIC_STR) - 1); + return true; + } + + /// \brief Total number of inputs. + unsigned NumberOfInputs = 0; + + /// \brief Total number of processed inputs, i.e, inputs that were already + /// read from the buffers. + unsigned NumberOfProcessedInputs = 0; + + /// \brief LLVM context used to to create the auxiliar modules. + LLVMContext VMContext; + + /// \brief LLVM module used to create an object with all the bundle + /// components. + std::unique_ptr AuxModule; + + /// \brief The current triple we are working with. + StringRef CurrentTriple; + + /// \brief The name of the main input file. + StringRef MainInputFileName; + + /// \brief Iterator of the current and next section. + section_iterator CurrentSection; + section_iterator NextSection; + +public: + void ReadHeader(MemoryBuffer &Input) {} + StringRef ReadBundleStart(MemoryBuffer &Input) { + + while (NextSection != Obj.section_end()) { + CurrentSection = NextSection; + ++NextSection; + + StringRef OffloadTriple; + // Check if the current section name starts with the reserved prefix. If + // so, return the triple. + if (isOffloadSection(*CurrentSection, OffloadTriple)) + return OffloadTriple; + } + return StringRef(); + } + void ReadBundleEnd(MemoryBuffer &Input) {} + void ReadBundle(raw_fd_ostream &OS, MemoryBuffer &Input) { + // If the current section has size one, that means that the content we are + // interested in is the file itself. Otherwise it is the content of the + // section. + // + // TODO: Instead of copying the input file as is, deactivate the section + // that are no longer needed. + + StringRef Content; + CurrentSection->getContents(Content); + + if (Content.size() < 2) + OS.write(Input.getBufferStart(), Input.getBufferSize()); + else + OS.write(Content.data(), Content.size()); + + return; + } + + void WriteHeader(raw_fd_ostream &OS, + ArrayRef> Inputs) { + // Record number of inputs. + NumberOfInputs = Inputs.size(); + + // Create an LLVM module to have the content we need to bundle. + auto *M = new Module("clang-offload-bundle", VMContext); + M->setTargetTriple(getTriple(TargetNames.front())); + AuxModule.reset(M); + } + void WriteBundleStart(raw_fd_ostream &OS, StringRef TargetTriple) { + ++NumberOfProcessedInputs; + + // Record the triple we are using, that will be used to name the section we + // will create. + CurrentTriple = TargetTriple; + } + void WriteBundleEnd(raw_fd_ostream &OS, StringRef TargetTriple) { + assert(NumberOfProcessedInputs <= NumberOfInputs && + "Processing more inputs that actually exist!"); + + // If this is not the last output, we don't have to do anything. + if (NumberOfProcessedInputs != NumberOfInputs) + return; + + // Create the bitcode file name to write the resulting code to. + SmallString<128> BitcodeFileName; + if (sys::fs::createTemporaryFile("clang-offload-bundler", "bc", + BitcodeFileName)) + llvm_unreachable("Error trying to create temporary file!"); + + // Write the bitcode to the temporary file. + { + std::error_code EC; + raw_fd_ostream BitcodeFile(BitcodeFileName, EC, sys::fs::F_None); + if (EC) + llvm_unreachable("Error trying to open temporary file!"); + WriteBitcodeToFile(AuxModule.get(), BitcodeFile); + } + + // Find clang in order to create the bundle binary. + StringRef Dir = llvm::sys::path::parent_path(BundlerExecutable); + + auto ClangBinary = sys::findProgramByName("clang", Dir); + if (ClangBinary.getError()) { + // Remove bitcode file. + sys::fs::remove(BitcodeFileName); + llvm_unreachable("Can't find clang in path!"); + } + + // Do the incremental linking. We write to the output file directly. So, we + // close it and use the name to pass down to clang. + OS.close(); + SmallString<128> TargetName = getTriple(TargetNames.front()); + const char *ClangArgs[] = {"clang", + "-r", + "-target", + TargetName.c_str(), + "-o", + OutputFileNames.front().c_str(), + InputFileNames.front().c_str(), + BitcodeFileName.c_str(), + "-nostdlib", + nullptr}; + + if (sys::ExecuteAndWait(ClangBinary.get(), ClangArgs)) { + // Remove bitcode file. + sys::fs::remove(BitcodeFileName); + llvm_unreachable("Bundle incremental link failed!"); + } + + // Remove bitcode file. + sys::fs::remove(BitcodeFileName); + } + void WriteBundle(raw_fd_ostream &OS, MemoryBuffer &Input) { + Module *M = AuxModule.get(); + + // Create the new section name, it will consist of the reserved prefix + // concatenated with the triple. + std::string SectionName = OFFLOAD_BUNDLER_MAGIC_STR; + SectionName += CurrentTriple; + + // Create the constant with the content of the section. For the input we are + // bundling into (the first input), this is just a place-holder, so a single + // byte is sufficient. + Constant *Content; + if (NumberOfProcessedInputs == 1) { + uint8_t Byte[] = {0}; + Content = ConstantDataArray::get(VMContext, Byte); + } else + Content = ConstantDataArray::get( + VMContext, ArrayRef(reinterpret_cast( + Input.getBufferStart()), + Input.getBufferSize())); + + // Create the global in the desired section. We don't want these globals in + // the symbol table, so we mark them private. + auto *GV = new GlobalVariable(*M, Content->getType(), /*IsConstant=*/true, + GlobalVariable::PrivateLinkage, Content); + GV->setSection(SectionName); + } + + ObjectFileHandler(ObjectFile &Obj) + : FileHandler(), Obj(Obj), CurrentSection(Obj.section_begin()), + NextSection(Obj.section_begin()) {} + ~ObjectFileHandler() {} +}; + // Handler for text files. The bundled file will have the following format. // // "Comment OFFLOAD_BUNDLER_MAGIC_STR__START__ triple" @@ -409,6 +623,28 @@ } }; +/// \brief Return an appropriate object file handler. We use the specific object +/// handler if we know how to deal with that format, otherwise we use a default +/// binary file handler. +static FileHandler *CreateObjectFileHandler(MemoryBuffer &FirstInput) { + // Check if the input file format is one that we know how to deal with. + Expected> BinaryOrErr = createBinary(FirstInput); + + // Failed to open the input as a known binary. Use the default binary handler. + if (!BinaryOrErr) + return new BinaryFileHandler(); + + // We only support regular object files. If this is not an object file, + // default to the binary handler. The handler will be owned by the client of + // this function. + ObjectFile *Obj = dyn_cast(BinaryOrErr.get().release()); + + if (!Obj) + return new BinaryFileHandler(); + + return new ObjectFileHandler(*Obj); +} + /// \brief Return an appropriate handler given the input files and options. static FileHandler *CreateFileHandler(MemoryBuffer &FirstInput) { if (FilesType == "i") @@ -422,7 +658,7 @@ if (FilesType == "s") return new TextFileHandler(/*Comment=*/"#"); if (FilesType == "o") - return new BinaryFileHandler(); + return CreateObjectFileHandler(FirstInput); if (FilesType == "gch") return new BinaryFileHandler(); if (FilesType == "ast") @@ -674,6 +910,11 @@ if (Error) return 1; + // Save the current executable directory as it will be useful to find other + // tools. + BundlerExecutable = + llvm::sys::fs::getMainExecutable(argv[0], &BundlerExecutable); + if (Unbundle) return UnbundleFiles(); else