//===-- clang-linker-wrapper/ClangLinkerWrapper.cpp - wrapper over linker-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===---------------------------------------------------------------------===// // // This tool works as a wrapper over a linking job. This tool is used to create // linked device images for offloading. It scans the linker's input for embedded // device offloading data stored in sections `.llvm.offloading..` // and extracts it as a temporary file. The extracted device files will then be // passed to a device linking job to create a final device image. // //===---------------------------------------------------------------------===// #include "OffloadWrapper.h" #include "clang/Basic/Version.h" #include "llvm/BinaryFormat/Magic.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/CodeGen/CommandFlags.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/IR/Module.h" #include "llvm/IRReader/IRReader.h" #include "llvm/LTO/LTO.h" #include "llvm/MC/TargetRegistry.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ArchiveWriter.h" #include "llvm/Object/Binary.h" #include "llvm/Object/ObjectFile.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Host.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/WithColor.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" using namespace llvm; using namespace llvm::object; static cl::opt Help("h", cl::desc("Alias for -help"), cl::Hidden); enum DebugKind { NoDebugInfo, DirectivesOnly, FullDebugInfo, }; // Mark all our options with this category, everything else (except for -help) // will be hidden. static cl::OptionCategory ClangLinkerWrapperCategory("clang-linker-wrapper options"); static cl::opt StripSections( "strip-sections", cl::ZeroOrMore, cl::desc("Strip offloading sections from the host object file."), cl::init(true), cl::cat(ClangLinkerWrapperCategory)); static cl::opt LinkerUserPath("linker-path", cl::Required, cl::desc("Path of linker binary"), cl::cat(ClangLinkerWrapperCategory)); static cl::opt TargetFeatures("target-feature", cl::ZeroOrMore, cl::desc("Target features for triple"), cl::cat(ClangLinkerWrapperCategory)); static cl::opt OptLevel("opt-level", cl::ZeroOrMore, cl::desc("Optimization level for LTO"), cl::init("O2"), cl::cat(ClangLinkerWrapperCategory)); static cl::list BitcodeLibraries("target-library", cl::ZeroOrMore, cl::desc("Path for the target bitcode library"), cl::cat(ClangLinkerWrapperCategory)); static cl::opt EmbedBitcode( "target-embed-bc", cl::ZeroOrMore, cl::desc("Embed linked bitcode instead of an executable device image"), cl::init(false), cl::cat(ClangLinkerWrapperCategory)); static cl::opt HostTriple("host-triple", cl::ZeroOrMore, cl::desc("Triple to use for the host compilation"), cl::init(sys::getDefaultTargetTriple()), cl::cat(ClangLinkerWrapperCategory)); static cl::list PtxasArgs("ptxas-args", cl::ZeroOrMore, cl::desc("Argument to pass to the ptxas invocation"), cl::cat(ClangLinkerWrapperCategory)); static cl::opt Verbose("v", cl::ZeroOrMore, cl::desc("Verbose output from tools"), cl::init(false), cl::cat(ClangLinkerWrapperCategory)); static cl::opt DebugInfo( cl::desc("Choose debugging level:"), cl::init(NoDebugInfo), cl::values(clEnumValN(NoDebugInfo, "g0", "No debug information"), clEnumValN(DirectivesOnly, "gline-directives-only", "Direction information"), clEnumValN(FullDebugInfo, "g", "Full debugging support"))); static cl::opt SaveTemps("save-temps", cl::ZeroOrMore, cl::desc("Save intermediary results."), cl::cat(ClangLinkerWrapperCategory)); static cl::opt CudaPath("cuda-path", cl::ZeroOrMore, cl::desc("Save intermediary results."), cl::cat(ClangLinkerWrapperCategory)); // Do not parse linker options. static cl::list HostLinkerArgs(cl::Positional, cl::desc("...")); /// Path of the current binary. static const char *LinkerExecutable; /// Filename of the executable being created. static StringRef ExecutableName; /// System root if passed in to the linker via. '--sysroot='. static StringRef Sysroot = ""; /// Binary path for the CUDA installation. static std::string CudaBinaryPath; /// Temporary files created by the linker wrapper. static SmallVector TempFiles; /// Codegen flags for LTO backend. static codegen::RegisterCodeGenFlags CodeGenFlags; /// Magic section string that marks the existence of offloading data. The /// section string will be formatted as `.llvm.offloading..`. #define OFFLOAD_SECTION_MAGIC_STR ".llvm.offloading." /// Information for a device offloading file extracted from the host. struct DeviceFile { DeviceFile(StringRef Kind, StringRef TheTriple, StringRef Arch, StringRef Filename) : Kind(Kind), TheTriple(TheTriple), Arch(Arch), Filename(Filename) {} std::string Kind; std::string TheTriple; std::string Arch; std::string Filename; }; namespace llvm { /// Helper that allows DeviceFile to be used as a key in a DenseMap. template <> struct DenseMapInfo { static DeviceFile getEmptyKey() { return {DenseMapInfo::getEmptyKey(), DenseMapInfo::getEmptyKey(), DenseMapInfo::getEmptyKey(), DenseMapInfo::getEmptyKey()}; } static DeviceFile getTombstoneKey() { return {DenseMapInfo::getTombstoneKey(), DenseMapInfo::getTombstoneKey(), DenseMapInfo::getTombstoneKey(), DenseMapInfo::getTombstoneKey()}; } static unsigned getHashValue(const DeviceFile &I) { return DenseMapInfo::getHashValue(I.TheTriple) ^ DenseMapInfo::getHashValue(I.Arch); } static bool isEqual(const DeviceFile &LHS, const DeviceFile &RHS) { return LHS.TheTriple == RHS.TheTriple && LHS.Arch == RHS.Arch; } }; } // namespace llvm namespace { Expected> extractFromBuffer(std::unique_ptr Buffer, SmallVectorImpl &DeviceFiles); void printCommands(ArrayRef CmdArgs) { if (CmdArgs.empty()) return; llvm::errs() << " \"" << CmdArgs.front() << "\" "; for (auto IC = std::next(CmdArgs.begin()), IE = CmdArgs.end(); IC != IE; ++IC) llvm::errs() << *IC << (std::next(IC) != IE ? " " : "\n"); } static StringRef getDeviceFileExtension(StringRef DeviceTriple, bool IsBitcode = false) { Triple TheTriple(DeviceTriple); if (TheTriple.isAMDGPU() || IsBitcode) return "bc"; if (TheTriple.isNVPTX()) return "cubin"; return "o"; } std::string getMainExecutable(const char *Name) { void *Ptr = (void *)(intptr_t)&getMainExecutable; auto COWPath = sys::fs::getMainExecutable(Name, Ptr); return sys::path::parent_path(COWPath).str(); } /// Extract the device file from the string '--='. DeviceFile getBitcodeLibrary(StringRef LibraryStr) { auto DeviceAndPath = StringRef(LibraryStr).split('='); auto StringAndArch = DeviceAndPath.first.rsplit('-'); auto KindAndTriple = StringAndArch.first.split('-'); return DeviceFile(KindAndTriple.first, KindAndTriple.second, StringAndArch.second, DeviceAndPath.second); } /// Get a temporary filename suitable for output. Error createOutputFile(const Twine &Prefix, StringRef Extension, SmallString<128> &NewFilename) { if (!SaveTemps) { if (std::error_code EC = sys::fs::createTemporaryFile(Prefix, Extension, NewFilename)) return createFileError(NewFilename, EC); TempFiles.push_back(static_cast(NewFilename)); } else { const Twine &Filename = Prefix + "." + Extension; Filename.toNullTerminatedStringRef(NewFilename); } return Error::success(); } Error runLinker(std::string &LinkerPath, SmallVectorImpl &Args) { std::vector LinkerArgs; LinkerArgs.push_back(LinkerPath); for (auto &Arg : Args) LinkerArgs.push_back(Arg); if (Verbose) printCommands(LinkerArgs); if (sys::ExecuteAndWait(LinkerPath, LinkerArgs)) return createStringError(inconvertibleErrorCode(), "'linker' failed"); return Error::success(); } void PrintVersion(raw_ostream &OS) { OS << clang::getClangToolFullVersion("clang-linker-wrapper") << '\n'; } void removeFromCompilerUsed(Module &M, GlobalValue &Value) { GlobalVariable *GV = M.getGlobalVariable("llvm.compiler.used"); Type *Int8PtrTy = Type::getInt8PtrTy(M.getContext()); Constant *ValueToRemove = ConstantExpr::getPointerBitCastOrAddrSpaceCast(&Value, Int8PtrTy); SmallPtrSet InitAsSet; SmallVector Init; if (GV) { if (GV->hasInitializer()) { auto *CA = cast(GV->getInitializer()); for (auto &Op : CA->operands()) { Constant *C = cast_or_null(Op); if (C != ValueToRemove && InitAsSet.insert(C).second) Init.push_back(C); } } GV->eraseFromParent(); } if (Init.empty()) return; ArrayType *ATy = ArrayType::get(Int8PtrTy, Init.size()); GV = new llvm::GlobalVariable(M, ATy, false, GlobalValue::AppendingLinkage, ConstantArray::get(ATy, Init), "llvm.compiler.used"); GV->setSection("llvm.metadata"); } Expected> extractFromBinary(const ObjectFile &Obj, SmallVectorImpl &DeviceFiles) { StringRef Extension = sys::path::extension(Obj.getFileName()).drop_front(); StringRef Prefix = sys::path::stem(Obj.getFileName()); SmallVector ToBeStripped; // Extract data from sections of the form `.llvm.offloading..`. for (const SectionRef &Sec : Obj.sections()) { Expected Name = Sec.getName(); if (!Name || !Name->startswith(OFFLOAD_SECTION_MAGIC_STR)) continue; SmallVector SectionFields; Name->split(SectionFields, '.'); StringRef Kind = SectionFields[3]; StringRef DeviceTriple = SectionFields[4]; StringRef Arch = SectionFields[5]; if (Expected Contents = Sec.getContents()) { SmallString<128> TempFile; StringRef DeviceExtension = getDeviceFileExtension( DeviceTriple, identify_magic(*Contents) == file_magic::bitcode); if (Error Err = createOutputFile(Prefix + "-" + Kind + "-" + DeviceTriple + "-" + Arch, DeviceExtension, TempFile)) return std::move(Err); Expected> OutputOrErr = FileOutputBuffer::create(TempFile, Sec.getSize()); if (!OutputOrErr) return OutputOrErr.takeError(); std::unique_ptr Output = std::move(*OutputOrErr); std::copy(Contents->begin(), Contents->end(), Output->getBufferStart()); if (Error E = Output->commit()) return std::move(E); DeviceFiles.emplace_back(Kind, DeviceTriple, Arch, TempFile); ToBeStripped.push_back(*Name); } } if (ToBeStripped.empty() || !StripSections) return None; // If the object file to strip doesn't exist we need to write it so we can // pass it to llvm-strip. SmallString<128> StripFile = Obj.getFileName(); if (!sys::fs::exists(StripFile)) { SmallString<128> TempFile; if (Error Err = createOutputFile( sys::path::stem(StripFile), sys::path::extension(StripFile).drop_front(), TempFile)) return std::move(Err); auto Contents = Obj.getMemoryBufferRef().getBuffer(); Expected> OutputOrErr = FileOutputBuffer::create(TempFile, Contents.size()); if (!OutputOrErr) return OutputOrErr.takeError(); std::unique_ptr Output = std::move(*OutputOrErr); std::copy(Contents.begin(), Contents.end(), Output->getBufferStart()); if (Error E = Output->commit()) return std::move(E); StripFile = TempFile; } // We will use llvm-strip to remove the now unneeded section containing the // offloading code. ErrorOr StripPath = sys::findProgramByName("llvm-strip", {getMainExecutable("llvm-strip")}); if (!StripPath) StripPath = sys::findProgramByName("llvm-strip"); if (!StripPath) return None; SmallString<128> TempFile; if (Error Err = createOutputFile(Prefix + "-host", Extension, TempFile)) return std::move(Err); SmallVector StripArgs; StripArgs.push_back(*StripPath); StripArgs.push_back("--no-strip-all"); StripArgs.push_back(StripFile); for (auto &Section : ToBeStripped) { StripArgs.push_back("--remove-section"); StripArgs.push_back(Section); } StripArgs.push_back("-o"); StripArgs.push_back(TempFile); if (Verbose) printCommands(StripArgs); if (sys::ExecuteAndWait(*StripPath, StripArgs)) return createStringError(inconvertibleErrorCode(), "'llvm-strip' failed"); return static_cast(TempFile); } Expected> extractFromBitcode(std::unique_ptr Buffer, SmallVectorImpl &DeviceFiles) { LLVMContext Context; SMDiagnostic Err; std::unique_ptr M = getLazyIRModule(std::move(Buffer), Err, Context); if (!M) return createStringError(inconvertibleErrorCode(), "Failed to create module"); StringRef Extension = sys::path::extension(M->getName()).drop_front(); StringRef Prefix = sys::path::stem(M->getName()).take_until([](char C) { return C == '-'; }); SmallVector ToBeDeleted; // Extract data from the global string containing a section of the form // `.llvm.offloading..`. for (GlobalVariable &GV : M->globals()) { if (!GV.hasSection() || !GV.getSection().startswith(OFFLOAD_SECTION_MAGIC_STR)) continue; auto *CDS = dyn_cast(GV.getInitializer()); if (!CDS) continue; SmallVector SectionFields; GV.getSection().split(SectionFields, '.'); StringRef Kind = SectionFields[3]; StringRef DeviceTriple = SectionFields[4]; StringRef Arch = SectionFields[5]; StringRef Contents = CDS->getAsString(); SmallString<128> TempFile; StringRef DeviceExtension = getDeviceFileExtension( DeviceTriple, identify_magic(Contents) == file_magic::bitcode); if (Error Err = createOutputFile(Prefix + "-" + Kind + "-" + DeviceTriple + "-" + Arch, DeviceExtension, TempFile)) return std::move(Err); Expected> OutputOrErr = FileOutputBuffer::create(TempFile, Contents.size()); if (!OutputOrErr) return OutputOrErr.takeError(); std::unique_ptr Output = std::move(*OutputOrErr); std::copy(Contents.begin(), Contents.end(), Output->getBufferStart()); if (Error E = Output->commit()) return std::move(E); DeviceFiles.emplace_back(Kind, DeviceTriple, Arch, TempFile); ToBeDeleted.push_back(&GV); } if (ToBeDeleted.empty() || !StripSections) return None; // We need to materialize the lazy module before we make any changes. if (Error Err = M->materializeAll()) return std::move(Err); // Remove the global from the module and write it to a new file. for (GlobalVariable *GV : ToBeDeleted) { removeFromCompilerUsed(*M, *GV); GV->eraseFromParent(); } SmallString<128> TempFile; if (Error Err = createOutputFile(Prefix + "-host", Extension, TempFile)) return std::move(Err); std::error_code EC; raw_fd_ostream HostOutput(TempFile, EC, sys::fs::OF_None); if (EC) return createFileError(TempFile, EC); WriteBitcodeToFile(*M, HostOutput); return static_cast(TempFile); } Expected> extractFromArchive(const Archive &Library, SmallVectorImpl &DeviceFiles) { bool NewMembers = false; SmallVector Members; // Try to extract device code from each file stored in the static archive. // Save the stripped archive members to create a new host archive with the // offloading code removed. Error Err = Error::success(); for (auto Child : Library.children(Err)) { auto ChildBufferRefOrErr = Child.getMemoryBufferRef(); if (!ChildBufferRefOrErr) return ChildBufferRefOrErr.takeError(); std::unique_ptr ChildBuffer = MemoryBuffer::getMemBuffer(*ChildBufferRefOrErr, false); auto FileOrErr = extractFromBuffer(std::move(ChildBuffer), DeviceFiles); if (!FileOrErr) return FileOrErr.takeError(); // If we created a new stripped host file, use it to create a new archive // member, otherwise use the old member. if (!FileOrErr->hasValue()) { Expected NewMember = NewArchiveMember::getOldMember(Child, true); if (!NewMember) return NewMember.takeError(); Members.push_back(std::move(*NewMember)); } else { Expected NewMember = NewArchiveMember::getFile(**FileOrErr, true); if (!NewMember) return NewMember.takeError(); Members.push_back(std::move(*NewMember)); NewMembers = true; // We no longer need the stripped file, remove it. if (std::error_code EC = sys::fs::remove(**FileOrErr)) return createFileError(**FileOrErr, EC); } } if (Err) return std::move(Err); if (!NewMembers || !StripSections) return None; // Create a new static library using the stripped host files. SmallString<128> TempFile; StringRef Prefix = sys::path::stem(Library.getFileName()); if (Error Err = createOutputFile(Prefix + "-host", "a", TempFile)) return std::move(Err); std::unique_ptr Buffer = MemoryBuffer::getMemBuffer(Library.getMemoryBufferRef(), false); if (Error Err = writeArchive(TempFile, Members, true, Library.kind(), true, Library.isThin(), std::move(Buffer))) return std::move(Err); return static_cast(TempFile); } /// Extracts embedded device offloading code from a memory \p Buffer to a list /// of \p DeviceFiles. If device code was extracted a new file with the embedded /// device code stripped from the buffer will be returned. Expected> extractFromBuffer(std::unique_ptr Buffer, SmallVectorImpl &DeviceFiles) { file_magic Type = identify_magic(Buffer->getBuffer()); switch (Type) { case file_magic::bitcode: return extractFromBitcode(std::move(Buffer), DeviceFiles); case file_magic::elf_relocatable: case file_magic::macho_object: case file_magic::coff_object: { Expected> ObjFile = ObjectFile::createObjectFile(*Buffer, Type); if (!ObjFile) return ObjFile.takeError(); return extractFromBinary(*ObjFile->get(), DeviceFiles); } case file_magic::archive: { Expected> LibFile = object::Archive::create(*Buffer); if (!LibFile) return LibFile.takeError(); return extractFromArchive(*LibFile->get(), DeviceFiles); } default: return None; } } // TODO: Move these to a separate file. namespace nvptx { Expected assemble(StringRef InputFile, Triple TheTriple, StringRef Arch) { // NVPTX uses the ptxas binary to create device object files. ErrorOr PtxasPath = sys::findProgramByName("ptxas", {CudaBinaryPath}); if (!PtxasPath) PtxasPath = sys::findProgramByName("ptxas"); if (!PtxasPath) return createStringError(PtxasPath.getError(), "Unable to find 'ptxas' in path"); // Create a new file to write the linked device image to. SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-device-" + TheTriple.getArchName() + "-" + Arch, "cubin", TempFile)) return std::move(Err); SmallVector CmdArgs; std::string Opt = "-" + OptLevel; CmdArgs.push_back(*PtxasPath); CmdArgs.push_back(TheTriple.isArch64Bit() ? "-m64" : "-m32"); if (Verbose) CmdArgs.push_back("-v"); if (DebugInfo == DirectivesOnly && OptLevel[1] == '0') CmdArgs.push_back("-lineinfo"); else if (DebugInfo == FullDebugInfo && OptLevel[1] == '0') CmdArgs.push_back("-g"); for (auto &Arg : PtxasArgs) CmdArgs.push_back(Arg); CmdArgs.push_back("-o"); CmdArgs.push_back(TempFile); CmdArgs.push_back(Opt); CmdArgs.push_back("--gpu-name"); CmdArgs.push_back(Arch); CmdArgs.push_back("-c"); CmdArgs.push_back(InputFile); if (Verbose) printCommands(CmdArgs); if (sys::ExecuteAndWait(*PtxasPath, CmdArgs)) return createStringError(inconvertibleErrorCode(), "'ptxas' failed"); return static_cast(TempFile); } Expected link(ArrayRef InputFiles, Triple TheTriple, StringRef Arch) { // NVPTX uses the nvlink binary to link device object files. ErrorOr NvlinkPath = sys::findProgramByName("nvlink", {CudaBinaryPath}); if (!NvlinkPath) NvlinkPath = sys::findProgramByName("nvlink"); if (!NvlinkPath) return createStringError(NvlinkPath.getError(), "Unable to find 'nvlink' in path"); // Create a new file to write the linked device image to. SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-device-" + TheTriple.getArchName() + "-" + Arch, "out", TempFile)) return std::move(Err); SmallVector CmdArgs; CmdArgs.push_back(*NvlinkPath); CmdArgs.push_back(TheTriple.isArch64Bit() ? "-m64" : "-m32"); if (Verbose) CmdArgs.push_back("-v"); if (DebugInfo != NoDebugInfo) CmdArgs.push_back("-g"); CmdArgs.push_back("-o"); CmdArgs.push_back(TempFile); CmdArgs.push_back("-arch"); CmdArgs.push_back(Arch); // Add extracted input files. for (StringRef Input : InputFiles) CmdArgs.push_back(Input); if (Verbose) printCommands(CmdArgs); if (sys::ExecuteAndWait(*NvlinkPath, CmdArgs)) return createStringError(inconvertibleErrorCode(), "'nvlink' failed"); return static_cast(TempFile); } } // namespace nvptx namespace amdgcn { Expected link(ArrayRef InputFiles, Triple TheTriple, StringRef Arch) { // AMDGPU uses lld to link device object files. ErrorOr LLDPath = sys::findProgramByName("lld", {getMainExecutable("lld")}); if (!LLDPath) LLDPath = sys::findProgramByName("lld"); if (!LLDPath) return createStringError(LLDPath.getError(), "Unable to find 'lld' in path"); // Create a new file to write the linked device image to. SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-" + TheTriple.getArchName() + "-" + Arch, "out", TempFile)) return std::move(Err); SmallVector CmdArgs; CmdArgs.push_back(*LLDPath); CmdArgs.push_back("-flavor"); CmdArgs.push_back("gnu"); CmdArgs.push_back("--no-undefined"); CmdArgs.push_back("-shared"); CmdArgs.push_back("-o"); CmdArgs.push_back(TempFile); // Add extracted input files. for (StringRef Input : InputFiles) CmdArgs.push_back(Input); if (Verbose) printCommands(CmdArgs); if (sys::ExecuteAndWait(*LLDPath, CmdArgs)) return createStringError(inconvertibleErrorCode(), "'lld' failed"); return static_cast(TempFile); } } // namespace amdgcn namespace generic { const char *getLDMOption(const llvm::Triple &T) { switch (T.getArch()) { case llvm::Triple::x86: if (T.isOSIAMCU()) return "elf_iamcu"; return "elf_i386"; case llvm::Triple::aarch64: return "aarch64linux"; case llvm::Triple::aarch64_be: return "aarch64linuxb"; case llvm::Triple::ppc64: return "elf64ppc"; case llvm::Triple::ppc64le: return "elf64lppc"; case llvm::Triple::x86_64: if (T.isX32()) return "elf32_x86_64"; return "elf_x86_64"; case llvm::Triple::ve: return "elf64ve"; default: return nullptr; } } Expected link(ArrayRef InputFiles, Triple TheTriple, StringRef Arch) { // Create a new file to write the linked device image to. SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-" + TheTriple.getArchName() + "-" + Arch, "out", TempFile)) return std::move(Err); // Use the host linker to perform generic offloading. Use the same libraries // and paths as the host application does. SmallVector CmdArgs; CmdArgs.push_back(LinkerUserPath); CmdArgs.push_back("-m"); CmdArgs.push_back(getLDMOption(TheTriple)); CmdArgs.push_back("-shared"); for (auto AI = HostLinkerArgs.begin(), AE = HostLinkerArgs.end(); AI != AE; ++AI) { StringRef Arg = *AI; if (Arg.startswith("-L")) CmdArgs.push_back(Arg); else if (Arg.startswith("-l")) CmdArgs.push_back(Arg); else if (Arg.startswith("--as-needed")) CmdArgs.push_back(Arg); else if (Arg.startswith("--no-as-needed")) CmdArgs.push_back(Arg); else if (Arg.startswith("-rpath")) { CmdArgs.push_back(Arg); CmdArgs.push_back(*std::next(AI)); } else if (Arg.startswith("-dynamic-linker")) { CmdArgs.push_back(Arg); CmdArgs.push_back(*std::next(AI)); } } CmdArgs.push_back("-Bsymbolic"); CmdArgs.push_back("-o"); CmdArgs.push_back(TempFile); // Add extracted input files. for (StringRef Input : InputFiles) CmdArgs.push_back(Input); if (Verbose) printCommands(CmdArgs); if (sys::ExecuteAndWait(LinkerUserPath, CmdArgs)) return createStringError(inconvertibleErrorCode(), "'linker' failed"); return static_cast(TempFile); } } // namespace generic Expected linkDevice(ArrayRef InputFiles, Triple TheTriple, StringRef Arch) { switch (TheTriple.getArch()) { case Triple::nvptx: case Triple::nvptx64: return nvptx::link(InputFiles, TheTriple, Arch); case Triple::amdgcn: return amdgcn::link(InputFiles, TheTriple, Arch); case Triple::x86: case Triple::x86_64: case Triple::aarch64: case Triple::aarch64_be: case Triple::ppc64: case Triple::ppc64le: return generic::link(InputFiles, TheTriple, Arch); default: return createStringError(inconvertibleErrorCode(), TheTriple.getArchName() + " linking is not supported"); } } void diagnosticHandler(const DiagnosticInfo &DI) { std::string ErrStorage; raw_string_ostream OS(ErrStorage); DiagnosticPrinterRawOStream DP(OS); DI.print(DP); switch (DI.getSeverity()) { case DS_Error: WithColor::error(errs(), LinkerExecutable) << ErrStorage << "\n"; break; case DS_Warning: WithColor::warning(errs(), LinkerExecutable) << ErrStorage << "\n"; break; case DS_Note: WithColor::note(errs(), LinkerExecutable) << ErrStorage << "\n"; break; case DS_Remark: WithColor::remark(errs()) << ErrStorage << "\n"; break; } } // Get the target features passed in from the driver as =. std::vector getTargetFeatures(const Triple &TheTriple) { std::vector Features; auto TargetAndFeatures = StringRef(TargetFeatures).split('='); if (TargetAndFeatures.first != TheTriple.getTriple()) return Features; for (auto Feature : llvm::split(TargetAndFeatures.second, ',')) Features.push_back(Feature.str()); return Features; } CodeGenOpt::Level getCGOptLevel(unsigned OptLevel) { switch (OptLevel) { case 0: return CodeGenOpt::None; case 1: return CodeGenOpt::Less; case 2: return CodeGenOpt::Default; case 3: return CodeGenOpt::Aggressive; } llvm_unreachable("Invalid optimization level"); } template > std::unique_ptr createLTO( const Triple &TheTriple, StringRef Arch, bool WholeProgram, ModuleHook Hook = [](size_t, const Module &) { return true; }) { lto::Config Conf; lto::ThinBackend Backend; // TODO: Handle index-only thin-LTO Backend = lto::createInProcessThinBackend(llvm::heavyweight_hardware_concurrency()); Conf.UseDefaultPipeline = true; Conf.CPU = Arch.str(); Conf.Options = codegen::InitTargetOptionsFromCodeGenFlags(TheTriple); Conf.MAttrs = getTargetFeatures(TheTriple); Conf.CGOptLevel = getCGOptLevel(OptLevel[1] - '0'); Conf.OptLevel = OptLevel[1] - '0'; Conf.DefaultTriple = TheTriple.getTriple(); Conf.DiagHandler = diagnosticHandler; Conf.PTO.LoopVectorization = Conf.OptLevel > 1; Conf.PTO.SLPVectorization = Conf.OptLevel > 1; if (SaveTemps) { auto HandleError = [&](Error Err) { logAllUnhandledErrors(std::move(Err), WithColor::error(errs(), LinkerExecutable)); exit(1); }; Conf.PostInternalizeModuleHook = [&](size_t, const Module &M) { SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-device-" + TheTriple.getTriple(), "bc", TempFile)) HandleError(std::move(Err)); std::error_code EC; raw_fd_ostream LinkedBitcode(TempFile, EC, sys::fs::OF_None); if (EC) HandleError(errorCodeToError(EC)); WriteBitcodeToFile(M, LinkedBitcode); return true; }; } Conf.PostOptModuleHook = Hook; if (TheTriple.isNVPTX()) Conf.CGFileType = CGFT_AssemblyFile; else Conf.CGFileType = CGFT_ObjectFile; // TODO: Handle remark files Conf.HasWholeProgramVisibility = WholeProgram; return std::make_unique(std::move(Conf), Backend); } // Returns true if \p S is valid as a C language identifier and will be given // `__start_` and `__stop_` symbols. bool isValidCIdentifier(StringRef S) { return !S.empty() && (isAlpha(S[0]) || S[0] == '_') && std::all_of(S.begin() + 1, S.end(), [](char C) { return C == '_' || isAlnum(C); }); } Error linkBitcodeFiles(SmallVectorImpl &InputFiles, const Triple &TheTriple, StringRef Arch) { SmallVector, 4> SavedBuffers; SmallVector, 4> BitcodeFiles; SmallVector NewInputFiles; DenseSet UsedInRegularObj; DenseSet UsedInSharedLib; BumpPtrAllocator Alloc; StringSaver Saver(Alloc); // Search for bitcode files in the input and create an LTO input file. If it // is not a bitcode file, scan its symbol table for symbols we need to // save. for (StringRef File : InputFiles) { ErrorOr> BufferOrErr = MemoryBuffer::getFileOrSTDIN(File); if (std::error_code EC = BufferOrErr.getError()) return createFileError(File, EC); file_magic Type = identify_magic((*BufferOrErr)->getBuffer()); if (Type != file_magic::bitcode) { Expected> ObjFile = ObjectFile::createObjectFile(**BufferOrErr, Type); if (!ObjFile) return ObjFile.takeError(); NewInputFiles.push_back(File.str()); for (auto &Sym : (*ObjFile)->symbols()) { Expected Name = Sym.getName(); if (!Name) return Name.takeError(); // Record if we've seen these symbols in any object or shared libraries. if ((*ObjFile)->isRelocatableObject()) UsedInRegularObj.insert(Saver.save(*Name)); else UsedInSharedLib.insert(Saver.save(*Name)); } } else { Expected> InputFileOrErr = llvm::lto::InputFile::create(**BufferOrErr); if (!InputFileOrErr) return InputFileOrErr.takeError(); // Save the input file and the buffer associated with its memory. BitcodeFiles.push_back(std::move(*InputFileOrErr)); SavedBuffers.push_back(std::move(*BufferOrErr)); } } if (BitcodeFiles.empty()) return Error::success(); auto HandleError = [&](Error Err) { logAllUnhandledErrors(std::move(Err), WithColor::error(errs(), LinkerExecutable)); exit(1); }; // LTO Module hook to output bitcode without running the backend. auto OutputBitcode = [&](size_t Task, const Module &M) { SmallString<128> TempFile; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-jit-" + TheTriple.getTriple(), "bc", TempFile)) HandleError(std::move(Err)); std::error_code EC; raw_fd_ostream LinkedBitcode(TempFile, EC, sys::fs::OF_None); if (EC) HandleError(errorCodeToError(EC)); WriteBitcodeToFile(M, LinkedBitcode); NewInputFiles.push_back(static_cast(TempFile)); return false; }; // We assume visibility of the whole program if every input file was bitcode. bool WholeProgram = BitcodeFiles.size() == InputFiles.size(); auto LTOBackend = (EmbedBitcode) ? createLTO(TheTriple, Arch, WholeProgram, OutputBitcode) : createLTO(TheTriple, Arch, WholeProgram); // We need to resolve the symbols so the LTO backend knows which symbols need // to be kept or can be internalized. This is a simplified symbol resolution // scheme to approximate the full resolution a linker would do. DenseSet PrevailingSymbols; for (auto &BitcodeFile : BitcodeFiles) { const auto Symbols = BitcodeFile->symbols(); SmallVector Resolutions(Symbols.size()); size_t Idx = 0; for (auto &Sym : Symbols) { lto::SymbolResolution &Res = Resolutions[Idx++]; // We will use this as the prevailing symbol definition in LTO unless // it is undefined or another definition has already been used. Res.Prevailing = !Sym.isUndefined() && PrevailingSymbols.insert(Saver.save(Sym.getName())).second; // We need LTO to preseve the following global symbols: // 1) Symbols used in regular objects. // 2) Sections that will be given a __start/__stop symbol. // 3) Prevailing symbols that are needed visible to external libraries. Res.VisibleToRegularObj = UsedInRegularObj.contains(Sym.getName()) || isValidCIdentifier(Sym.getSectionName()) || (Res.Prevailing && (Sym.getVisibility() != GlobalValue::HiddenVisibility && !Sym.canBeOmittedFromSymbolTable())); // Identify symbols that must be exported dynamically and can be // referenced by other files. Res.ExportDynamic = Sym.getVisibility() != GlobalValue::HiddenVisibility && (UsedInSharedLib.contains(Sym.getName()) || !Sym.canBeOmittedFromSymbolTable()); // The final definition will reside in this linkage unit if the symbol is // defined and local to the module. This only checks for bitcode files, // full assertion will require complete symbol resolution. Res.FinalDefinitionInLinkageUnit = Sym.getVisibility() != GlobalValue::DefaultVisibility && (!Sym.isUndefined() && !Sym.isCommon()); // We do not support linker redefined symbols (e.g. --wrap) for device // image linking, so the symbols will not be changed after LTO. Res.LinkerRedefined = false; } // Add the bitcode file with its resolved symbols to the LTO job. if (Error Err = LTOBackend->add(std::move(BitcodeFile), Resolutions)) return Err; } // Run the LTO job to compile the bitcode. size_t MaxTasks = LTOBackend->getMaxTasks(); std::vector> Files(MaxTasks); auto AddStream = [&](size_t Task) -> std::unique_ptr { int FD = -1; auto &TempFile = Files[Task]; StringRef Extension = (TheTriple.isNVPTX()) ? "s" : "o"; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "-device-" + TheTriple.getTriple(), Extension, TempFile)) HandleError(std::move(Err)); if (std::error_code EC = sys::fs::openFileForWrite(TempFile, FD)) HandleError(errorCodeToError(EC)); return std::make_unique( std::make_unique(FD, true)); }; if (Error Err = LTOBackend->run(AddStream)) return Err; // Is we are compiling for NVPTX we need to run the assembler first. if (TheTriple.isNVPTX() && !EmbedBitcode) { for (auto &File : Files) { auto FileOrErr = nvptx::assemble(File, TheTriple, Arch); if (!FileOrErr) return FileOrErr.takeError(); File = *FileOrErr; } } // Append the new inputs to the device linker input. for (auto &File : Files) NewInputFiles.push_back(static_cast(File)); InputFiles = NewInputFiles; return Error::success(); } /// Runs the appropriate linking action on all the device files specified in \p /// DeviceFiles. The linked device images are returned in \p LinkedImages. Error linkDeviceFiles(ArrayRef DeviceFiles, SmallVectorImpl &LinkedImages) { // Get the list of inputs for a specific device. DenseMap> LinkerInputMap; for (auto &File : DeviceFiles) LinkerInputMap[File].push_back(File.Filename); // Try to link each device toolchain. for (auto &LinkerInput : LinkerInputMap) { DeviceFile &File = LinkerInput.getFirst(); Triple TheTriple = Triple(File.TheTriple); // Run LTO on any bitcode files and replace the input with the result. if (Error Err = linkBitcodeFiles(LinkerInput.getSecond(), TheTriple, File.Arch)) return Err; // If we are embedding bitcode for JIT, skip the final device linking. if (EmbedBitcode) { assert(!LinkerInput.getSecond().empty() && "No bitcode image to embed"); LinkedImages.push_back(LinkerInput.getSecond().front()); continue; } auto ImageOrErr = linkDevice(LinkerInput.getSecond(), TheTriple, File.Arch); if (!ImageOrErr) return ImageOrErr.takeError(); LinkedImages.push_back(*ImageOrErr); } return Error::success(); } // Compile the module to an object file using the appropriate target machine for // the host triple. Expected compileModule(Module &M) { std::string Msg; const Target *T = TargetRegistry::lookupTarget(M.getTargetTriple(), Msg); if (!T) return createStringError(inconvertibleErrorCode(), Msg); auto Options = codegen::InitTargetOptionsFromCodeGenFlags(Triple(M.getTargetTriple())); StringRef CPU = ""; StringRef Features = ""; std::unique_ptr TM(T->createTargetMachine( HostTriple, CPU, Features, Options, Reloc::PIC_, M.getCodeModel())); if (M.getDataLayout().isDefault()) M.setDataLayout(TM->createDataLayout()); SmallString<128> ObjectFile; int FD = -1; if (Error Err = createOutputFile(sys::path::filename(ExecutableName) + "offload-wrapper", "o", ObjectFile)) return std::move(Err); if (std::error_code EC = sys::fs::openFileForWrite(ObjectFile, FD)) return errorCodeToError(EC); auto OS = std::make_unique(FD, true); legacy::PassManager CodeGenPasses; TargetLibraryInfoImpl TLII(Triple(M.getTargetTriple())); CodeGenPasses.add(new TargetLibraryInfoWrapperPass(TLII)); if (TM->addPassesToEmitFile(CodeGenPasses, *OS, nullptr, CGFT_ObjectFile)) return createStringError(inconvertibleErrorCode(), "Failed to execute host backend"); CodeGenPasses.run(M); return static_cast(ObjectFile); } /// Creates the object file containing the device image and runtime registration /// code from the device images stored in \p Images. Expected wrapDeviceImages(ArrayRef Images) { SmallVector, 4> SavedBuffers; SmallVector, 4> ImagesToWrap; for (StringRef ImageFilename : Images) { llvm::ErrorOr> ImageOrError = llvm::MemoryBuffer::getFileOrSTDIN(ImageFilename); if (std::error_code EC = ImageOrError.getError()) return createFileError(ImageFilename, EC); ImagesToWrap.emplace_back((*ImageOrError)->getBufferStart(), (*ImageOrError)->getBufferSize()); SavedBuffers.emplace_back(std::move(*ImageOrError)); } LLVMContext Context; Module M("offload.wrapper.module", Context); M.setTargetTriple(HostTriple); if (Error Err = wrapBinaries(M, ImagesToWrap)) return std::move(Err); return compileModule(M); } Optional findFile(StringRef Dir, const Twine &Name) { SmallString<128> Path; if (Dir.startswith("=")) sys::path::append(Path, Sysroot, Dir.substr(1), Name); else sys::path::append(Path, Dir, Name); if (sys::fs::exists(Path)) return static_cast(Path); return None; } Optional findFromSearchPaths(StringRef Name, ArrayRef SearchPaths) { for (StringRef Dir : SearchPaths) if (Optional File = findFile(Dir, Name)) return File; return None; } Optional searchLibraryBaseName(StringRef Name, ArrayRef SearchPaths) { for (StringRef Dir : SearchPaths) { if (Optional File = findFile(Dir, "lib" + Name + ".so")) return None; if (Optional File = findFile(Dir, "lib" + Name + ".a")) return File; } return None; } /// Search for static libraries in the linker's library path given input like /// `-lfoo` or `-l:libfoo.a`. Optional searchLibrary(StringRef Input, ArrayRef SearchPaths) { if (!Input.startswith("-l")) return None; StringRef Name = Input.drop_front(2); if (Name.startswith(":")) return findFromSearchPaths(Name.drop_front(), SearchPaths); return searchLibraryBaseName(Name, SearchPaths); } } // namespace int main(int argc, const char **argv) { InitLLVM X(argc, argv); InitializeAllTargetInfos(); InitializeAllTargets(); InitializeAllTargetMCs(); InitializeAllAsmParsers(); InitializeAllAsmPrinters(); LinkerExecutable = argv[0]; sys::PrintStackTraceOnErrorSignal(argv[0]); cl::SetVersionPrinter(PrintVersion); cl::HideUnrelatedOptions(ClangLinkerWrapperCategory); cl::ParseCommandLineOptions( argc, argv, "A wrapper utility over the host linker. It scans the input files for\n" "sections that require additional processing prior to linking. The tool\n" "will then transparently pass all arguments and input to the specified\n" "host linker to create the final binary.\n"); if (Help) { cl::PrintHelpMessage(); return EXIT_SUCCESS; } auto reportError = [argv](Error E) { logAllUnhandledErrors(std::move(E), WithColor::error(errs(), argv[0])); return EXIT_FAILURE; }; if (!CudaPath.empty()) CudaBinaryPath = CudaPath + "/bin"; auto RootIt = llvm::find_if(HostLinkerArgs, [](StringRef Arg) { return Arg.startswith("--sysroot="); }); if (RootIt != HostLinkerArgs.end()) Sysroot = StringRef(*RootIt).split('=').second; ExecutableName = *std::next(llvm::find(HostLinkerArgs, "-o")); SmallVector LinkerArgs; for (const std::string &Arg : HostLinkerArgs) LinkerArgs.push_back(Arg); SmallVector LibraryPaths; for (StringRef Arg : LinkerArgs) { if (Arg.startswith("-L")) LibraryPaths.push_back(Arg.drop_front(2)); } // Try to extract device code from the linker input and replace the linker // input with a new file that has the device section stripped. SmallVector DeviceFiles; for (std::string &Arg : LinkerArgs) { if (Arg == ExecutableName) continue; // Search for static libraries in the library link path. std::string Filename = Arg; if (Optional Library = searchLibrary(Arg, LibraryPaths)) Filename = *Library; if (sys::fs::exists(Filename) && !sys::fs::is_directory(Filename)) { ErrorOr> BufferOrErr = MemoryBuffer::getFileOrSTDIN(Filename); if (std::error_code EC = BufferOrErr.getError()) return reportError(createFileError(Filename, EC)); auto NewFileOrErr = extractFromBuffer(std::move(*BufferOrErr), DeviceFiles); if (!NewFileOrErr) return reportError(NewFileOrErr.takeError()); if (NewFileOrErr->hasValue()) Arg = **NewFileOrErr; } } // Add the device bitcode libraries to the device files if any were passed in. for (StringRef LibraryStr : BitcodeLibraries) DeviceFiles.push_back(getBitcodeLibrary(LibraryStr)); // Link the device images extracted from the linker input. SmallVector LinkedImages; if (Error Err = linkDeviceFiles(DeviceFiles, LinkedImages)) return reportError(std::move(Err)); // Wrap each linked device image into a linkable host binary and add it to the // link job's inputs. auto FileOrErr = wrapDeviceImages(LinkedImages); if (!FileOrErr) return reportError(FileOrErr.takeError()); LinkerArgs.push_back(*FileOrErr); // Run the host linking job. if (Error Err = runLinker(LinkerUserPath, LinkerArgs)) return reportError(std::move(Err)); // Remove the temporary files created. for (const auto &TempFile : TempFiles) if (std::error_code EC = sys::fs::remove(TempFile)) reportError(createFileError(TempFile, EC)); return EXIT_SUCCESS; }