
Summary: We always want to use the detected path. The clang driver's detection is far more sophisticated so we should use that whenever possible. Also update the usage so we properly fall back to path instead of incorrectly using the `/bin` if not provided.
800 lines
28 KiB
C++
800 lines
28 KiB
C++
//===-- clang-nvlink-wrapper/ClangNVLinkWrapper.cpp - NVIDIA linker util --===//
|
|
//
|
|
// 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 wraps around the NVIDIA linker called 'nvlink'. The NVIDIA linker
|
|
// is required to create NVPTX applications, but does not support common
|
|
// features like LTO or archives. This utility wraps around the tool to cover
|
|
// its deficiencies. This tool can be removed once NVIDIA improves their linker
|
|
// or ports it to `ld.lld`.
|
|
//
|
|
//===---------------------------------------------------------------------===//
|
|
|
|
#include "clang/Basic/Version.h"
|
|
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/BinaryFormat/Magic.h"
|
|
#include "llvm/Bitcode/BitcodeWriter.h"
|
|
#include "llvm/CodeGen/CommandFlags.h"
|
|
#include "llvm/IR/DiagnosticPrinter.h"
|
|
#include "llvm/LTO/LTO.h"
|
|
#include "llvm/Object/Archive.h"
|
|
#include "llvm/Object/ArchiveWriter.h"
|
|
#include "llvm/Object/Binary.h"
|
|
#include "llvm/Object/ELFObjectFile.h"
|
|
#include "llvm/Object/IRObjectFile.h"
|
|
#include "llvm/Object/ObjectFile.h"
|
|
#include "llvm/Object/OffloadBinary.h"
|
|
#include "llvm/Option/ArgList.h"
|
|
#include "llvm/Option/OptTable.h"
|
|
#include "llvm/Option/Option.h"
|
|
#include "llvm/Remarks/HotnessThresholdParser.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/FileOutputBuffer.h"
|
|
#include "llvm/Support/FileSystem.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/StringSaver.h"
|
|
#include "llvm/Support/TargetSelect.h"
|
|
#include "llvm/Support/WithColor.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::opt;
|
|
using namespace llvm::object;
|
|
|
|
// Various tools (e.g., llc and opt) duplicate this series of declarations for
|
|
// options related to passes and remarks.
|
|
static cl::opt<bool> RemarksWithHotness(
|
|
"pass-remarks-with-hotness",
|
|
cl::desc("With PGO, include profile count in optimization remarks"),
|
|
cl::Hidden);
|
|
|
|
static cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser>
|
|
RemarksHotnessThreshold(
|
|
"pass-remarks-hotness-threshold",
|
|
cl::desc("Minimum profile count required for "
|
|
"an optimization remark to be output. "
|
|
"Use 'auto' to apply the threshold from profile summary."),
|
|
cl::value_desc("N or 'auto'"), cl::init(0), cl::Hidden);
|
|
|
|
static cl::opt<std::string>
|
|
RemarksFilename("pass-remarks-output",
|
|
cl::desc("Output filename for pass remarks"),
|
|
cl::value_desc("filename"));
|
|
|
|
static cl::opt<std::string>
|
|
RemarksPasses("pass-remarks-filter",
|
|
cl::desc("Only record optimization remarks from passes whose "
|
|
"names match the given regular expression"),
|
|
cl::value_desc("regex"));
|
|
|
|
static cl::opt<std::string> RemarksFormat(
|
|
"pass-remarks-format",
|
|
cl::desc("The format used for serializing remarks (default: YAML)"),
|
|
cl::value_desc("format"), cl::init("yaml"));
|
|
|
|
static cl::list<std::string>
|
|
PassPlugins("load-pass-plugin",
|
|
cl::desc("Load passes from plugin library"));
|
|
|
|
static void printVersion(raw_ostream &OS) {
|
|
OS << clang::getClangToolFullVersion("clang-nvlink-wrapper") << '\n';
|
|
}
|
|
|
|
/// The value of `argv[0]` when run.
|
|
static const char *Executable;
|
|
|
|
/// Temporary files to be cleaned up.
|
|
static SmallVector<SmallString<128>> TempFiles;
|
|
|
|
/// Codegen flags for LTO backend.
|
|
static codegen::RegisterCodeGenFlags CodeGenFlags;
|
|
|
|
namespace {
|
|
// Must not overlap with llvm::opt::DriverFlag.
|
|
enum WrapperFlags { WrapperOnlyOption = (1 << 4) };
|
|
|
|
enum ID {
|
|
OPT_INVALID = 0, // This is not an option ID.
|
|
#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
|
|
#include "NVLinkOpts.inc"
|
|
LastOption
|
|
#undef OPTION
|
|
};
|
|
|
|
#define OPTTABLE_STR_TABLE_CODE
|
|
#include "NVLinkOpts.inc"
|
|
#undef OPTTABLE_STR_TABLE_CODE
|
|
|
|
#define OPTTABLE_PREFIXES_TABLE_CODE
|
|
#include "NVLinkOpts.inc"
|
|
#undef OPTTABLE_PREFIXES_TABLE_CODE
|
|
|
|
static constexpr OptTable::Info InfoTable[] = {
|
|
#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
|
|
#include "NVLinkOpts.inc"
|
|
#undef OPTION
|
|
};
|
|
|
|
class WrapperOptTable : public opt::GenericOptTable {
|
|
public:
|
|
WrapperOptTable()
|
|
: opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}
|
|
};
|
|
|
|
const OptTable &getOptTable() {
|
|
static const WrapperOptTable *Table = []() {
|
|
auto Result = std::make_unique<WrapperOptTable>();
|
|
return Result.release();
|
|
}();
|
|
return *Table;
|
|
}
|
|
|
|
[[noreturn]] void reportError(Error E) {
|
|
outs().flush();
|
|
logAllUnhandledErrors(std::move(E), WithColor::error(errs(), Executable));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
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(), Executable) << ErrStorage << "\n";
|
|
break;
|
|
case DS_Warning:
|
|
WithColor::warning(errs(), Executable) << ErrStorage << "\n";
|
|
break;
|
|
case DS_Note:
|
|
WithColor::note(errs(), Executable) << ErrStorage << "\n";
|
|
break;
|
|
case DS_Remark:
|
|
WithColor::remark(errs()) << ErrStorage << "\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
Expected<StringRef> createTempFile(const ArgList &Args, const Twine &Prefix,
|
|
StringRef Extension) {
|
|
SmallString<128> OutputFile;
|
|
if (Args.hasArg(OPT_save_temps)) {
|
|
(Prefix + "." + Extension).toNullTerminatedStringRef(OutputFile);
|
|
} else {
|
|
if (std::error_code EC =
|
|
sys::fs::createTemporaryFile(Prefix, Extension, OutputFile))
|
|
return createFileError(OutputFile, EC);
|
|
}
|
|
|
|
TempFiles.emplace_back(std::move(OutputFile));
|
|
return TempFiles.back();
|
|
}
|
|
|
|
Expected<std::string> findProgram(const ArgList &Args, StringRef Name,
|
|
ArrayRef<StringRef> Paths) {
|
|
if (Args.hasArg(OPT_dry_run))
|
|
return Name.str();
|
|
ErrorOr<std::string> Path = sys::findProgramByName(Name, Paths);
|
|
if (!Path)
|
|
Path = sys::findProgramByName(Name);
|
|
if (!Path)
|
|
return createStringError(Path.getError(),
|
|
"Unable to find '" + Name + "' in path");
|
|
return *Path;
|
|
}
|
|
|
|
std::optional<std::string> findFile(StringRef Dir, StringRef Root,
|
|
const Twine &Name) {
|
|
SmallString<128> Path;
|
|
if (Dir.starts_with("="))
|
|
sys::path::append(Path, Root, Dir.substr(1), Name);
|
|
else
|
|
sys::path::append(Path, Dir, Name);
|
|
|
|
if (sys::fs::exists(Path))
|
|
return static_cast<std::string>(Path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string>
|
|
findFromSearchPaths(StringRef Name, StringRef Root,
|
|
ArrayRef<StringRef> SearchPaths) {
|
|
for (StringRef Dir : SearchPaths)
|
|
if (std::optional<std::string> File = findFile(Dir, Root, Name))
|
|
return File;
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string>
|
|
searchLibraryBaseName(StringRef Name, StringRef Root,
|
|
ArrayRef<StringRef> SearchPaths) {
|
|
for (StringRef Dir : SearchPaths)
|
|
if (std::optional<std::string> File =
|
|
findFile(Dir, Root, "lib" + Name + ".a"))
|
|
return File;
|
|
return std::nullopt;
|
|
}
|
|
|
|
/// Search for static libraries in the linker's library path given input like
|
|
/// `-lfoo` or `-l:libfoo.a`.
|
|
std::optional<std::string> searchLibrary(StringRef Input, StringRef Root,
|
|
ArrayRef<StringRef> SearchPaths) {
|
|
if (Input.starts_with(":"))
|
|
return findFromSearchPaths(Input.drop_front(), Root, SearchPaths);
|
|
return searchLibraryBaseName(Input, Root, SearchPaths);
|
|
}
|
|
|
|
void printCommands(ArrayRef<StringRef> CmdArgs) {
|
|
if (CmdArgs.empty())
|
|
return;
|
|
|
|
errs() << " \"" << CmdArgs.front() << "\" ";
|
|
errs() << join(std::next(CmdArgs.begin()), CmdArgs.end(), " ") << "\n";
|
|
}
|
|
|
|
/// A minimum symbol interface that provides the necessary information to
|
|
/// extract archive members and resolve LTO symbols.
|
|
struct Symbol {
|
|
enum Flags {
|
|
None = 0,
|
|
Undefined = 1 << 0,
|
|
Weak = 1 << 1,
|
|
};
|
|
|
|
Symbol() : File(), Flags(None), UsedInRegularObj(false) {}
|
|
Symbol(Symbol::Flags Flags) : File(), Flags(Flags), UsedInRegularObj(true) {}
|
|
|
|
Symbol(MemoryBufferRef File, const irsymtab::Reader::SymbolRef Sym)
|
|
: File(File), Flags(0), UsedInRegularObj(false) {
|
|
if (Sym.isUndefined())
|
|
Flags |= Undefined;
|
|
if (Sym.isWeak())
|
|
Flags |= Weak;
|
|
}
|
|
|
|
Symbol(MemoryBufferRef File, const SymbolRef Sym)
|
|
: File(File), Flags(0), UsedInRegularObj(false) {
|
|
auto FlagsOrErr = Sym.getFlags();
|
|
if (!FlagsOrErr)
|
|
reportError(FlagsOrErr.takeError());
|
|
if (*FlagsOrErr & SymbolRef::SF_Undefined)
|
|
Flags |= Undefined;
|
|
if (*FlagsOrErr & SymbolRef::SF_Weak)
|
|
Flags |= Weak;
|
|
|
|
auto NameOrErr = Sym.getName();
|
|
if (!NameOrErr)
|
|
reportError(NameOrErr.takeError());
|
|
}
|
|
|
|
bool isWeak() const { return Flags & Weak; }
|
|
bool isUndefined() const { return Flags & Undefined; }
|
|
|
|
MemoryBufferRef File;
|
|
uint32_t Flags;
|
|
bool UsedInRegularObj;
|
|
};
|
|
|
|
Expected<StringRef> runPTXAs(StringRef File, const ArgList &Args) {
|
|
SmallVector<StringRef, 1> SearchPaths;
|
|
if (Arg *A = Args.getLastArg(OPT_cuda_path_EQ))
|
|
SearchPaths.push_back(Args.MakeArgString(A->getValue() + Twine("/bin")));
|
|
if (Arg *A = Args.getLastArg(OPT_ptxas_path_EQ))
|
|
SearchPaths.push_back(Args.MakeArgString(A->getValue()));
|
|
|
|
Expected<std::string> PTXAsPath = findProgram(Args, "ptxas", SearchPaths);
|
|
if (!PTXAsPath)
|
|
return PTXAsPath.takeError();
|
|
|
|
if (!Args.hasArg(OPT_arch))
|
|
return createStringError(
|
|
"must pass in an explicit nvptx64 gpu architecture to 'ptxas'");
|
|
|
|
auto TempFileOrErr = createTempFile(
|
|
Args, sys::path::stem(Args.getLastArgValue(OPT_o, "a.out")), "cubin");
|
|
if (!TempFileOrErr)
|
|
return TempFileOrErr.takeError();
|
|
|
|
SmallVector<StringRef> AssemblerArgs({*PTXAsPath, "-m64", "-c", File});
|
|
if (Args.hasArg(OPT_verbose))
|
|
AssemblerArgs.push_back("-v");
|
|
if (Args.hasArg(OPT_g)) {
|
|
if (Args.hasArg(OPT_O))
|
|
WithColor::warning(errs(), Executable)
|
|
<< "Optimized debugging not supported, overriding to '-O0'\n";
|
|
AssemblerArgs.push_back("-O0");
|
|
} else
|
|
AssemblerArgs.push_back(
|
|
Args.MakeArgString("-O" + Args.getLastArgValue(OPT_O, "3")));
|
|
AssemblerArgs.append({"-arch", Args.getLastArgValue(OPT_arch)});
|
|
AssemblerArgs.append({"-o", *TempFileOrErr});
|
|
|
|
if (Args.hasArg(OPT_dry_run) || Args.hasArg(OPT_verbose))
|
|
printCommands(AssemblerArgs);
|
|
if (Args.hasArg(OPT_dry_run))
|
|
return Args.MakeArgString(*TempFileOrErr);
|
|
if (sys::ExecuteAndWait(*PTXAsPath, AssemblerArgs))
|
|
return createStringError("'" + sys::path::filename(*PTXAsPath) + "'" +
|
|
" failed");
|
|
return Args.MakeArgString(*TempFileOrErr);
|
|
}
|
|
|
|
Expected<std::unique_ptr<lto::LTO>> createLTO(const ArgList &Args) {
|
|
const llvm::Triple Triple("nvptx64-nvidia-cuda");
|
|
lto::Config Conf;
|
|
lto::ThinBackend Backend;
|
|
unsigned Jobs = 0;
|
|
if (auto *Arg = Args.getLastArg(OPT_jobs))
|
|
if (!to_integer(Arg->getValue(), Jobs) || Jobs == 0)
|
|
reportError(createStringError("%s: expected a positive integer, got '%s'",
|
|
Arg->getSpelling().data(),
|
|
Arg->getValue()));
|
|
Backend =
|
|
lto::createInProcessThinBackend(heavyweight_hardware_concurrency(Jobs));
|
|
|
|
Conf.CPU = Args.getLastArgValue(OPT_arch);
|
|
Conf.Options = codegen::InitTargetOptionsFromCodeGenFlags(Triple);
|
|
|
|
Conf.RemarksFilename =
|
|
Args.getLastArgValue(OPT_opt_remarks_filename, RemarksFilename);
|
|
Conf.RemarksPasses =
|
|
Args.getLastArgValue(OPT_opt_remarks_filter, RemarksPasses);
|
|
Conf.RemarksFormat =
|
|
Args.getLastArgValue(OPT_opt_remarks_format, RemarksFormat);
|
|
|
|
Conf.RemarksWithHotness =
|
|
Args.hasArg(OPT_opt_remarks_with_hotness) || RemarksWithHotness;
|
|
Conf.RemarksHotnessThreshold = RemarksHotnessThreshold;
|
|
|
|
Conf.MAttrs = llvm::codegen::getMAttrs();
|
|
std::optional<CodeGenOptLevel> CGOptLevelOrNone =
|
|
CodeGenOpt::parseLevel(Args.getLastArgValue(OPT_O, "2")[0]);
|
|
assert(CGOptLevelOrNone && "Invalid optimization level");
|
|
Conf.CGOptLevel = *CGOptLevelOrNone;
|
|
Conf.OptLevel = Args.getLastArgValue(OPT_O, "2")[0] - '0';
|
|
Conf.DefaultTriple = Triple.getTriple();
|
|
|
|
Conf.OptPipeline = Args.getLastArgValue(OPT_lto_newpm_passes, "");
|
|
Conf.PassPlugins = PassPlugins;
|
|
Conf.DebugPassManager = Args.hasArg(OPT_lto_debug_pass_manager);
|
|
|
|
Conf.DiagHandler = diagnosticHandler;
|
|
Conf.CGFileType = CodeGenFileType::AssemblyFile;
|
|
|
|
if (Args.hasArg(OPT_lto_emit_llvm)) {
|
|
Conf.PreCodeGenModuleHook = [&](size_t, const Module &M) {
|
|
std::error_code EC;
|
|
raw_fd_ostream LinkedBitcode(Args.getLastArgValue(OPT_o, "a.out"), EC);
|
|
if (EC)
|
|
reportError(errorCodeToError(EC));
|
|
WriteBitcodeToFile(M, LinkedBitcode);
|
|
return false;
|
|
};
|
|
}
|
|
|
|
if (Args.hasArg(OPT_save_temps))
|
|
if (Error Err = Conf.addSaveTemps(
|
|
(Args.getLastArgValue(OPT_o, "a.out") + ".").str()))
|
|
return Err;
|
|
|
|
unsigned Partitions = 1;
|
|
if (auto *Arg = Args.getLastArg(OPT_lto_partitions))
|
|
if (!to_integer(Arg->getValue(), Partitions) || Partitions == 0)
|
|
reportError(createStringError("%s: expected a positive integer, got '%s'",
|
|
Arg->getSpelling().data(),
|
|
Arg->getValue()));
|
|
lto::LTO::LTOKind Kind = Args.hasArg(OPT_thinlto) ? lto::LTO::LTOK_UnifiedThin
|
|
: lto::LTO::LTOK_Default;
|
|
return std::make_unique<lto::LTO>(std::move(Conf), Backend, Partitions, Kind);
|
|
}
|
|
|
|
Expected<bool> getSymbolsFromBitcode(MemoryBufferRef Buffer,
|
|
StringMap<Symbol> &SymTab, bool IsLazy) {
|
|
Expected<IRSymtabFile> IRSymtabOrErr = readIRSymtab(Buffer);
|
|
if (!IRSymtabOrErr)
|
|
return IRSymtabOrErr.takeError();
|
|
bool Extracted = !IsLazy;
|
|
StringMap<Symbol> PendingSymbols;
|
|
for (unsigned I = 0; I != IRSymtabOrErr->Mods.size(); ++I) {
|
|
for (const auto &IRSym : IRSymtabOrErr->TheReader.module_symbols(I)) {
|
|
if (IRSym.isFormatSpecific() || !IRSym.isGlobal())
|
|
continue;
|
|
|
|
Symbol &OldSym = !SymTab.count(IRSym.getName()) && IsLazy
|
|
? PendingSymbols[IRSym.getName()]
|
|
: SymTab[IRSym.getName()];
|
|
Symbol Sym = Symbol(Buffer, IRSym);
|
|
if (OldSym.File.getBuffer().empty())
|
|
OldSym = Sym;
|
|
|
|
bool ResolvesReference =
|
|
!Sym.isUndefined() &&
|
|
(OldSym.isUndefined() || (OldSym.isWeak() && !Sym.isWeak())) &&
|
|
!(OldSym.isWeak() && OldSym.isUndefined() && IsLazy);
|
|
Extracted |= ResolvesReference;
|
|
|
|
Sym.UsedInRegularObj = OldSym.UsedInRegularObj;
|
|
if (ResolvesReference)
|
|
OldSym = Sym;
|
|
}
|
|
}
|
|
if (Extracted)
|
|
for (const auto &[Name, Symbol] : PendingSymbols)
|
|
SymTab[Name] = Symbol;
|
|
return Extracted;
|
|
}
|
|
|
|
Expected<bool> getSymbolsFromObject(ObjectFile &ObjFile,
|
|
StringMap<Symbol> &SymTab, bool IsLazy) {
|
|
bool Extracted = !IsLazy;
|
|
StringMap<Symbol> PendingSymbols;
|
|
for (SymbolRef ObjSym : ObjFile.symbols()) {
|
|
auto NameOrErr = ObjSym.getName();
|
|
if (!NameOrErr)
|
|
return NameOrErr.takeError();
|
|
|
|
Symbol &OldSym = !SymTab.count(*NameOrErr) && IsLazy
|
|
? PendingSymbols[*NameOrErr]
|
|
: SymTab[*NameOrErr];
|
|
Symbol Sym = Symbol(ObjFile.getMemoryBufferRef(), ObjSym);
|
|
if (OldSym.File.getBuffer().empty())
|
|
OldSym = Sym;
|
|
|
|
bool ResolvesReference = OldSym.isUndefined() && !Sym.isUndefined() &&
|
|
(!OldSym.isWeak() || !IsLazy);
|
|
Extracted |= ResolvesReference;
|
|
|
|
if (ResolvesReference)
|
|
OldSym = Sym;
|
|
OldSym.UsedInRegularObj = true;
|
|
}
|
|
if (Extracted)
|
|
for (const auto &[Name, Symbol] : PendingSymbols)
|
|
SymTab[Name] = Symbol;
|
|
return Extracted;
|
|
}
|
|
|
|
Expected<bool> getSymbols(MemoryBufferRef Buffer, StringMap<Symbol> &SymTab,
|
|
bool IsLazy) {
|
|
switch (identify_magic(Buffer.getBuffer())) {
|
|
case file_magic::bitcode: {
|
|
return getSymbolsFromBitcode(Buffer, SymTab, IsLazy);
|
|
}
|
|
case file_magic::elf_relocatable: {
|
|
Expected<std::unique_ptr<ObjectFile>> ObjFile =
|
|
ObjectFile::createObjectFile(Buffer);
|
|
if (!ObjFile)
|
|
return ObjFile.takeError();
|
|
return getSymbolsFromObject(**ObjFile, SymTab, IsLazy);
|
|
}
|
|
default:
|
|
return createStringError("Unsupported file type");
|
|
}
|
|
}
|
|
|
|
Expected<SmallVector<StringRef>> getInput(const ArgList &Args) {
|
|
SmallVector<StringRef> LibraryPaths;
|
|
for (const opt::Arg *Arg : Args.filtered(OPT_library_path))
|
|
LibraryPaths.push_back(Arg->getValue());
|
|
|
|
bool WholeArchive = false;
|
|
SmallVector<std::pair<std::unique_ptr<MemoryBuffer>, bool>> InputFiles;
|
|
for (const opt::Arg *Arg : Args.filtered(
|
|
OPT_INPUT, OPT_library, OPT_whole_archive, OPT_no_whole_archive)) {
|
|
if (Arg->getOption().matches(OPT_whole_archive) ||
|
|
Arg->getOption().matches(OPT_no_whole_archive)) {
|
|
WholeArchive = Arg->getOption().matches(OPT_whole_archive);
|
|
continue;
|
|
}
|
|
|
|
std::optional<std::string> Filename =
|
|
Arg->getOption().matches(OPT_library)
|
|
? searchLibrary(Arg->getValue(), /*Root=*/"", LibraryPaths)
|
|
: std::string(Arg->getValue());
|
|
|
|
if (!Filename && Arg->getOption().matches(OPT_library))
|
|
return createStringError("unable to find library -l%s", Arg->getValue());
|
|
|
|
if (!Filename || !sys::fs::exists(*Filename) ||
|
|
sys::fs::is_directory(*Filename))
|
|
continue;
|
|
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr =
|
|
MemoryBuffer::getFileOrSTDIN(*Filename);
|
|
if (std::error_code EC = BufferOrErr.getError())
|
|
return createFileError(*Filename, EC);
|
|
|
|
MemoryBufferRef Buffer = **BufferOrErr;
|
|
switch (identify_magic(Buffer.getBuffer())) {
|
|
case file_magic::bitcode:
|
|
case file_magic::elf_relocatable:
|
|
InputFiles.emplace_back(std::move(*BufferOrErr), /*IsLazy=*/false);
|
|
break;
|
|
case file_magic::archive: {
|
|
Expected<std::unique_ptr<object::Archive>> LibFile =
|
|
object::Archive::create(Buffer);
|
|
if (!LibFile)
|
|
return LibFile.takeError();
|
|
Error Err = Error::success();
|
|
for (auto Child : (*LibFile)->children(Err)) {
|
|
auto ChildBufferOrErr = Child.getMemoryBufferRef();
|
|
if (!ChildBufferOrErr)
|
|
return ChildBufferOrErr.takeError();
|
|
std::unique_ptr<MemoryBuffer> ChildBuffer =
|
|
MemoryBuffer::getMemBufferCopy(
|
|
ChildBufferOrErr->getBuffer(),
|
|
ChildBufferOrErr->getBufferIdentifier());
|
|
InputFiles.emplace_back(std::move(ChildBuffer), !WholeArchive);
|
|
}
|
|
if (Err)
|
|
return Err;
|
|
break;
|
|
}
|
|
default:
|
|
return createStringError("Unsupported file type");
|
|
}
|
|
}
|
|
|
|
bool Extracted = true;
|
|
StringMap<Symbol> SymTab;
|
|
for (auto &Sym : Args.getAllArgValues(OPT_u))
|
|
SymTab[Sym] = Symbol(Symbol::Undefined);
|
|
SmallVector<std::unique_ptr<MemoryBuffer>> LinkerInput;
|
|
while (Extracted) {
|
|
Extracted = false;
|
|
for (auto &[Input, IsLazy] : InputFiles) {
|
|
if (!Input)
|
|
continue;
|
|
|
|
// Archive members only extract if they define needed symbols. We will
|
|
// re-scan all the inputs if any files were extracted for the link job.
|
|
Expected<bool> ExtractOrErr = getSymbols(*Input, SymTab, IsLazy);
|
|
if (!ExtractOrErr)
|
|
return ExtractOrErr.takeError();
|
|
|
|
Extracted |= *ExtractOrErr;
|
|
if (!*ExtractOrErr)
|
|
continue;
|
|
|
|
LinkerInput.emplace_back(std::move(Input));
|
|
}
|
|
}
|
|
InputFiles.clear();
|
|
|
|
// Extract any bitcode files to be passed to the LTO pipeline.
|
|
SmallVector<std::unique_ptr<MemoryBuffer>> BitcodeFiles;
|
|
for (auto &Input : LinkerInput)
|
|
if (identify_magic(Input->getBuffer()) == file_magic::bitcode)
|
|
BitcodeFiles.emplace_back(std::move(Input));
|
|
erase_if(LinkerInput, [](const auto &F) { return !F; });
|
|
|
|
// Run the LTO pipeline on the extracted inputs.
|
|
SmallVector<StringRef> Files;
|
|
if (!BitcodeFiles.empty()) {
|
|
auto LTOBackendOrErr = createLTO(Args);
|
|
if (!LTOBackendOrErr)
|
|
return LTOBackendOrErr.takeError();
|
|
lto::LTO <OBackend = **LTOBackendOrErr;
|
|
for (auto &BitcodeFile : BitcodeFiles) {
|
|
Expected<std::unique_ptr<lto::InputFile>> BitcodeFileOrErr =
|
|
lto::InputFile::create(*BitcodeFile);
|
|
if (!BitcodeFileOrErr)
|
|
return BitcodeFileOrErr.takeError();
|
|
|
|
const auto Symbols = (*BitcodeFileOrErr)->symbols();
|
|
SmallVector<lto::SymbolResolution, 16> Resolutions(Symbols.size());
|
|
size_t Idx = 0;
|
|
for (auto &Sym : Symbols) {
|
|
lto::SymbolResolution &Res = Resolutions[Idx++];
|
|
Symbol ObjSym = SymTab[Sym.getName()];
|
|
// We will use this as the prevailing symbol in LTO if it is not
|
|
// undefined and it is from the file that contained the canonical
|
|
// definition.
|
|
Res.Prevailing = !Sym.isUndefined() && ObjSym.File == *BitcodeFile;
|
|
|
|
// We need LTO to preseve the following global symbols:
|
|
// 1) All symbols during a relocatable link.
|
|
// 2) Symbols used in regular objects.
|
|
// 3) Prevailing symbols that are needed visible to the gpu runtime.
|
|
Res.VisibleToRegularObj =
|
|
Args.hasArg(OPT_relocatable) || ObjSym.UsedInRegularObj ||
|
|
(Res.Prevailing &&
|
|
(Sym.getVisibility() != GlobalValue::HiddenVisibility &&
|
|
!Sym.canBeOmittedFromSymbolTable()));
|
|
|
|
// Identify symbols that must be exported dynamically and can be
|
|
// referenced by other files, (i.e. the runtime).
|
|
Res.ExportDynamic =
|
|
Sym.getVisibility() != GlobalValue::HiddenVisibility &&
|
|
!Sym.canBeOmittedFromSymbolTable();
|
|
|
|
// The NVIDIA platform does not support any symbol preemption.
|
|
Res.FinalDefinitionInLinkageUnit = true;
|
|
|
|
// 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(*BitcodeFileOrErr), Resolutions))
|
|
return Err;
|
|
}
|
|
|
|
// Run the LTO job to compile the bitcode.
|
|
size_t MaxTasks = LTOBackend.getMaxTasks();
|
|
SmallVector<StringRef> LTOFiles(MaxTasks);
|
|
auto AddStream =
|
|
[&](size_t Task,
|
|
const Twine &ModuleName) -> std::unique_ptr<CachedFileStream> {
|
|
int FD = -1;
|
|
auto &TempFile = LTOFiles[Task];
|
|
if (Args.hasArg(OPT_lto_emit_asm))
|
|
TempFile = Args.getLastArgValue(OPT_o, "a.out");
|
|
else {
|
|
auto TempFileOrErr = createTempFile(
|
|
Args, sys::path::stem(Args.getLastArgValue(OPT_o, "a.out")), "s");
|
|
if (!TempFileOrErr)
|
|
reportError(TempFileOrErr.takeError());
|
|
TempFile = Args.MakeArgString(*TempFileOrErr);
|
|
}
|
|
if (std::error_code EC = sys::fs::openFileForWrite(TempFile, FD))
|
|
reportError(errorCodeToError(EC));
|
|
return std::make_unique<CachedFileStream>(
|
|
std::make_unique<raw_fd_ostream>(FD, true));
|
|
};
|
|
|
|
if (Error Err = LTOBackend.run(AddStream))
|
|
return Err;
|
|
|
|
if (Args.hasArg(OPT_lto_emit_llvm) || Args.hasArg(OPT_lto_emit_asm))
|
|
return Files;
|
|
|
|
for (StringRef LTOFile : LTOFiles) {
|
|
auto FileOrErr = runPTXAs(LTOFile, Args);
|
|
if (!FileOrErr)
|
|
return FileOrErr.takeError();
|
|
Files.emplace_back(*FileOrErr);
|
|
}
|
|
}
|
|
|
|
// Create a copy for each file to a new file ending in `.cubin`. The 'nvlink'
|
|
// linker requires all NVPTX inputs to have this extension for some reason.
|
|
// We don't use a symbolic link because it's not supported on Windows and some
|
|
// of this input files could be extracted from an archive.
|
|
for (auto &Input : LinkerInput) {
|
|
auto TempFileOrErr = createTempFile(
|
|
Args, sys::path::stem(Input->getBufferIdentifier()), "cubin");
|
|
if (!TempFileOrErr)
|
|
return TempFileOrErr.takeError();
|
|
Expected<std::unique_ptr<FileOutputBuffer>> OutputOrErr =
|
|
FileOutputBuffer::create(*TempFileOrErr, Input->getBuffer().size());
|
|
if (!OutputOrErr)
|
|
return OutputOrErr.takeError();
|
|
std::unique_ptr<FileOutputBuffer> Output = std::move(*OutputOrErr);
|
|
copy(Input->getBuffer(), Output->getBufferStart());
|
|
if (Error E = Output->commit())
|
|
return E;
|
|
Files.emplace_back(Args.MakeArgString(*TempFileOrErr));
|
|
}
|
|
|
|
return Files;
|
|
}
|
|
|
|
Error runNVLink(ArrayRef<StringRef> Files, const ArgList &Args) {
|
|
if (Args.hasArg(OPT_lto_emit_asm) || Args.hasArg(OPT_lto_emit_llvm))
|
|
return Error::success();
|
|
|
|
SmallVector<StringRef, 1> SearchPaths;
|
|
if (Arg *A = Args.getLastArg(OPT_cuda_path_EQ))
|
|
SearchPaths.push_back(Args.MakeArgString(A->getValue() + Twine("/bin")));
|
|
|
|
Expected<std::string> NVLinkPath = findProgram(Args, "nvlink", SearchPaths);
|
|
if (!NVLinkPath)
|
|
return NVLinkPath.takeError();
|
|
|
|
if (!Args.hasArg(OPT_arch))
|
|
return createStringError(
|
|
"must pass in an explicit nvptx64 gpu architecture to 'nvlink'");
|
|
|
|
ArgStringList NewLinkerArgs;
|
|
for (const opt::Arg *Arg : Args) {
|
|
// Do not forward arguments only intended for the linker wrapper.
|
|
if (Arg->getOption().hasFlag(WrapperOnlyOption))
|
|
continue;
|
|
|
|
// Do not forward any inputs that we have processed.
|
|
if (Arg->getOption().matches(OPT_INPUT) ||
|
|
Arg->getOption().matches(OPT_library))
|
|
continue;
|
|
|
|
Arg->render(Args, NewLinkerArgs);
|
|
}
|
|
|
|
transform(Files, std::back_inserter(NewLinkerArgs),
|
|
[&](StringRef Arg) { return Args.MakeArgString(Arg); });
|
|
|
|
SmallVector<StringRef> LinkerArgs({*NVLinkPath});
|
|
if (!Args.hasArg(OPT_o))
|
|
LinkerArgs.append({"-o", "a.out"});
|
|
for (StringRef Arg : NewLinkerArgs)
|
|
LinkerArgs.push_back(Arg);
|
|
|
|
if (Args.hasArg(OPT_dry_run) || Args.hasArg(OPT_verbose))
|
|
printCommands(LinkerArgs);
|
|
if (Args.hasArg(OPT_dry_run))
|
|
return Error::success();
|
|
if (sys::ExecuteAndWait(*NVLinkPath, LinkerArgs))
|
|
return createStringError("'" + sys::path::filename(*NVLinkPath) + "'" +
|
|
" failed");
|
|
return Error::success();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv) {
|
|
InitLLVM X(argc, argv);
|
|
InitializeAllTargetInfos();
|
|
InitializeAllTargets();
|
|
InitializeAllTargetMCs();
|
|
InitializeAllAsmParsers();
|
|
InitializeAllAsmPrinters();
|
|
|
|
Executable = argv[0];
|
|
sys::PrintStackTraceOnErrorSignal(argv[0]);
|
|
|
|
const OptTable &Tbl = getOptTable();
|
|
BumpPtrAllocator Alloc;
|
|
StringSaver Saver(Alloc);
|
|
auto Args = Tbl.parseArgs(argc, argv, OPT_INVALID, Saver, [&](StringRef Err) {
|
|
reportError(createStringError(inconvertibleErrorCode(), Err));
|
|
});
|
|
|
|
if (Args.hasArg(OPT_help) || Args.hasArg(OPT_help_hidden)) {
|
|
Tbl.printHelp(
|
|
outs(), "clang-nvlink-wrapper [options] <options to passed to nvlink>",
|
|
"A utility that wraps around the NVIDIA 'nvlink' linker.\n"
|
|
"This enables static linking and LTO handling for NVPTX targets.",
|
|
Args.hasArg(OPT_help_hidden), Args.hasArg(OPT_help_hidden));
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if (Args.hasArg(OPT_version))
|
|
printVersion(outs());
|
|
|
|
// This forwards '-mllvm' arguments to LLVM if present.
|
|
SmallVector<const char *> NewArgv = {argv[0]};
|
|
for (const opt::Arg *Arg : Args.filtered(OPT_mllvm))
|
|
NewArgv.push_back(Arg->getValue());
|
|
for (const opt::Arg *Arg : Args.filtered(OPT_plugin_opt))
|
|
NewArgv.push_back(Arg->getValue());
|
|
cl::ParseCommandLineOptions(NewArgv.size(), &NewArgv[0]);
|
|
|
|
// Get the input files to pass to 'nvlink'.
|
|
auto FilesOrErr = getInput(Args);
|
|
if (!FilesOrErr)
|
|
reportError(FilesOrErr.takeError());
|
|
|
|
// Run 'nvlink' on the generated inputs.
|
|
if (Error Err = runNVLink(*FilesOrErr, Args))
|
|
reportError(std::move(Err));
|
|
|
|
// Remove the temporary files created.
|
|
if (!Args.hasArg(OPT_save_temps))
|
|
for (const auto &TempFile : TempFiles)
|
|
if (std::error_code EC = sys::fs::remove(TempFile))
|
|
reportError(createFileError(TempFile, EC));
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|