blob: faf73a7c2f19388219069c430a90b9468f7f5006 [file] [log] [blame]
//===-- 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) {
std::string CudaPath = Args.getLastArgValue(OPT_cuda_path_EQ).str();
std::string GivenPath = Args.getLastArgValue(OPT_ptxas_path_EQ).str();
Expected<std::string> PTXAsPath =
findProgram(Args, "ptxas", {CudaPath + "/bin", GivenPath});
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 = RemarksFilename;
Conf.RemarksPasses = RemarksPasses;
Conf.RemarksWithHotness = RemarksWithHotness;
Conf.RemarksHotnessThreshold = RemarksHotnessThreshold;
Conf.RemarksFormat = RemarksFormat;
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 &LTOBackend = **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();
std::string CudaPath = Args.getLastArgValue(OPT_cuda_path_EQ).str();
Expected<std::string> NVLinkPath =
findProgram(Args, "nvlink", {CudaPath + "/bin"});
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;
}