[clang] [Clang] Introduce 'clang-nvlink-wrapper' to work around 'nvlink' (PR #96561)
Artem Belevich via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 22 15:12:30 PDT 2024
================
@@ -0,0 +1,778 @@
+//===-- 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 cl::opt<std::string> PassPipeline(
+ "passes",
+ cl::desc(
+ "A textual description of the pass pipeline. To have analysis passes "
+ "available before a certain pass, add 'require<foo-analysis>'. "
+ "'-passes' overrides the pass pipeline (but not all effects) from "
+ "specifying '--opt-level=O?' (O2 is the default) to "
+ "clang-linker-wrapper. Be sure to include the corresponding "
+ "'default<O?>' in '-passes'."));
+static cl::alias PassPipeline2("p", cl::aliasopt(PassPipeline),
+ cl::desc("Alias for -passes"));
+
+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 PREFIX(NAME, VALUE) \
+ static constexpr StringLiteral NAME##_init[] = VALUE; \
+ static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \
+ std::size(NAME##_init) - 1);
+#include "NVLinkOpts.inc"
+#undef PREFIX
+
+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(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(StringRef Name, ArrayRef<StringRef> Paths) {
+ 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;
+
+ llvm::errs() << " \"" << CmdArgs.front() << "\" ";
+ llvm::errs() << llvm::join(std::next(CmdArgs.begin(), " ") << "\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(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();
+ Expected<std::string> PTXAsPath = findProgram("ptxas", {CudaPath + "/bin"});
+ if (!PTXAsPath)
+ return PTXAsPath.takeError();
+
+ 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 (!llvm::to_integer(Arg->getValue(), Jobs) || Jobs == 0)
+ reportError(createStringError("%s: expected a positive integer, got '%s'",
+ Arg->getSpelling().data(),
+ Arg->getValue()));
+ Backend = lto::createInProcessThinBackend(
+ llvm::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 = {Args.getLastArgValue(OPT_feature, "").str()};
+ 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';
----------------
Artem-B wrote:
Fair enough, In that case, if -Os is unknown/unsupported, it should probably be rejected.
https://github.com/llvm/llvm-project/pull/96561
More information about the cfe-commits
mailing list