From 95fcd802801ca71cd697b7d930f4c4a80160f515 Mon Sep 17 00:00:00 2001 From: Kamil Rytarowski Date: Sat, 2 Nov 2019 05:22:11 +0100 Subject: [PATCH] [LLD] Add NetBSD support as a new flavor of LLD (nb.lld) The NetBSD target wraps the default Linux/ELF target with OS specific customization. It is implemented as a light nb.lld program that mutates arguments in argv[] and spawns ld.lld. This flavor detects the native/current and target Triple based on argv[0] parsing. This is prerequisite for cross-compilation, in particular the NetBSD distribution is cross-built always. The default configuration of the ELF target is tuned for Linux and there is no way to costomize in-place for the NetBSD target in the same way as FreeBSD/OpenBSD. FreeBSD whenever needed can check emulation name ("*_fbsd") and OpenBSD calls its extensions "PT_OPENBSD_*". This distinct flavor is needed for NetBSD as: - the linker MUST work standalone - it must be useful with gcc/pcc/other out of the box - clang NetBSD driver shall not hardcode LLD specific options - the linker must have support for cross-building - LLD shall be a drop-in replacement for (NetBSD-patched) GNU ld nb.lld calls internally ld.lld and there is no code-duplication between nb.lld and ld.lld. There is no functional or code maintenance change for other Operating Systems, especially the ELF ones. Equivalent customization is done already for the Darwin mode. For instance there are hardcoded default search paths such as "/usr/lib" and /Library/Frameworks in DarwinLdDriver.cpp. This change is a starting point for development of NetBSD specific extensions in LLD. --- clang/lib/Driver/ToolChain.cpp | 2 + lld/CMakeLists.txt | 1 + lld/tools/lld/lld.cpp | 61 ++++++++- lld/tools/nb.lld/CMakeLists.txt | 21 +++ lld/tools/nb.lld/Options.td | 7 + lld/tools/nb.lld/nb.lld.cpp | 218 ++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 lld/tools/nb.lld/CMakeLists.txt create mode 100644 lld/tools/nb.lld/Options.td create mode 100644 lld/tools/nb.lld/nb.lld.cpp diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp index a014c611ee2..e0ab3977442 100644 --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -534,6 +534,8 @@ std::string ToolChain::GetLinkerPath() const { llvm::SmallString<8> LinkerName; if (Triple.isOSDarwin()) LinkerName.append("ld64."); + else if (Triple.isOSNetBSD()) + LinkerName.append("nb."); else LinkerName.append("ld."); LinkerName.append(UseLinker); diff --git a/lld/CMakeLists.txt b/lld/CMakeLists.txt index 641f71c114a..2bd91f9094d 100644 --- a/lld/CMakeLists.txt +++ b/lld/CMakeLists.txt @@ -212,6 +212,7 @@ endif() add_subdirectory(Common) add_subdirectory(lib) add_subdirectory(tools/lld) +add_subdirectory(tools/nb.lld) if (LLVM_INCLUDE_TESTS) add_subdirectory(test) diff --git a/lld/tools/lld/lld.cpp b/lld/tools/lld/lld.cpp index 14dcc95899e..653ffbd7ef5 100644 --- a/lld/tools/lld/lld.cpp +++ b/lld/tools/lld/lld.cpp @@ -10,12 +10,13 @@ // function is a thin wrapper which dispatches to the platform specific // driver. // -// lld is a single executable that contains four different linkers for ELF, -// COFF, WebAssembly and Mach-O. The main function dispatches according to -// argv[0] (i.e. command name). The most common name for each target is shown +// lld is a single executable that contains five different linkers for ELF, +// NetBSD, COFF, WebAssembly and Mach-O. The main function dispatches according +// to argv[0] (i.e. command name). The most common name for each target is shown // below: // // - ld.lld: ELF (Unix) +// - nb.lld: ELF (NetBSD) // - ld64: Mach-O (macOS) // - lld-link: COFF (Windows) // - ld-wasm: WebAssembly @@ -36,6 +37,9 @@ #include "llvm/Support/Host.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" #include using namespace lld; @@ -45,6 +49,7 @@ using namespace llvm::sys; enum Flavor { Invalid, Gnu, // -flavor gnu + NetBSD, // -flavor netbsd WinLink, // -flavor link Darwin, // -flavor darwin Wasm, // -flavor wasm @@ -58,6 +63,7 @@ LLVM_ATTRIBUTE_NORETURN static void die(const Twine &s) { static Flavor getFlavor(StringRef s) { return StringSwitch(s) .CasesLower("ld", "ld.lld", "gnu", Gnu) + .CasesLower("nb.lld", "netbsd", NetBSD) .CasesLower("wasm", "ld-wasm", Wasm) .CaseLower("link", WinLink) .CasesLower("ld64", "ld64.lld", "darwin", Darwin) @@ -100,10 +106,15 @@ static Flavor parseProgname(StringRef progname) { #endif #if LLVM_ON_UNIX - // Use GNU driver for "ld" on other Unix-like system. - if (progname == "ld") + // Use GNU or NetBSD driver for "ld" on other Unix-like system. + if (progname == "ld") { +#if defined(__NetBSD__) + return NetBSD; +#else return Gnu; #endif + } +#endif // Progname may be something like "lld-gnu". Parse it. SmallVector v; @@ -133,6 +144,39 @@ static Flavor parseFlavor(std::vector &v) { return parseProgname(arg0); } +// The NetBSD linker flavor will mutate arguments and call the GNU linker. +// This is required as the linker for this OS MUST be usable standalone. +static int exec_nb_lld(int argc, const char **argv) { + auto Program = sys::findProgramByName("nb.lld"); + if (!Program) { + WithColor::error() << "unable to find `nb.lld' in PATH: " + << Program.getError().message() << "\n"; + return 1; + } + + std::vector Argv; + Argv.push_back("nb.lld"); + + // Trim -flavor option. + if (argc > 1 && argv[0] == StringRef("-flavor")) { + if (argc <= 2) + die("missing arg value for '-flavor'"); + argc -= 2; + argv += 2; + } + + for (int i = 0; i < argc; ++i) + Argv.push_back(argv[i]); + + std::string ErrMsg; + int Result = sys::ExecuteAndWait(*Program, Argv, None, {}, 0, 0, &ErrMsg); + if (Result < 0) { + WithColor::error() << ErrMsg << "\n"; + return 1; + } + return Result; +} + // If this function returns true, lld calls _exit() so that it quickly // exits without invoking destructors of globally allocated objects. // @@ -141,7 +185,7 @@ static Flavor parseFlavor(std::vector &v) { // and we use it to detect whether we are running tests or not. static bool canExitEarly() { return StringRef(getenv("LLD_IN_TEST")) != "1"; } -/// Universal linker main(). This linker emulates the gnu, darwin, or +/// Universal linker main(). This linker emulates the gnu, netbsd, darwin, or /// windows linker based on the argv[0] or -flavor option. int main(int argc, const char **argv) { InitLLVM x(argc, argv); @@ -152,6 +196,8 @@ int main(int argc, const char **argv) { if (isPETarget(args)) return !mingw::link(args); return !elf::link(args, canExitEarly()); + case NetBSD: + return exec_nb_lld(argc - 1, argv + 1); case WinLink: return !coff::link(args, canExitEarly()); case Darwin: @@ -160,7 +206,8 @@ int main(int argc, const char **argv) { return !wasm::link(args, canExitEarly()); default: die("lld is a generic driver.\n" - "Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld" + "Invoke ld.lld (Unix), nb.lld (NetBSD), ld64.lld (macOS), lld-link " + "(Windows), wasm-ld" " (WebAssembly) instead"); } } diff --git a/lld/tools/nb.lld/CMakeLists.txt b/lld/tools/nb.lld/CMakeLists.txt new file mode 100644 index 00000000000..090342e3a90 --- /dev/null +++ b/lld/tools/nb.lld/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_TARGET_DEFINITIONS Options.td) +tablegen(LLVM Options.inc -gen-opt-parser-defs) +add_public_tablegen_target(SLLDOptionsTableGen) + +set(LLVM_LINK_COMPONENTS + Support + Option + Target + ${LLVM_TARGETS_TO_BUILD} + ) + +add_lld_tool(nb.lld + nb.lld.cpp + + DEPENDS + SLLDOptionsTableGen + ) + +install(TARGETS nb.lld + RUNTIME DESTINATION bin + ) diff --git a/lld/tools/nb.lld/Options.td b/lld/tools/nb.lld/Options.td new file mode 100644 index 00000000000..bc132b29978 --- /dev/null +++ b/lld/tools/nb.lld/Options.td @@ -0,0 +1,7 @@ +include "llvm/Option/OptParser.td" + +class F: Flag<["--", "-"], name>; + +def version: F<"version">, HelpText<"Display the version number and exit">; +def v: Flag<["-"], "v">, HelpText<"Display the version number">; +def: Flag<["-"], "V">, Alias, HelpText<"Alias for --version">; diff --git a/lld/tools/nb.lld/nb.lld.cpp b/lld/tools/nb.lld/nb.lld.cpp new file mode 100644 index 00000000000..58b62d0dd8d --- /dev/null +++ b/lld/tools/nb.lld/nb.lld.cpp @@ -0,0 +1,218 @@ +//===- nb.lld.cpp - NetBSD LLD standalone 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 +// +//===----------------------------------------------------------------------===// +// +// The NetBSD linker MUST be functional as standalone. +// +// This code wraps the default ELF/UNIX lld variation with NetBSD specific +// customization. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Triple.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Program.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/WithColor.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; +using namespace llvm::sys; + +#define LLD_PROGNAME "ld.lld" + +namespace { +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Options.inc" +#undef OPTION +}; + +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "Options.inc" +#undef PREFIX + +const opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Options.inc" +#undef OPTION +}; + +class SLLDOptTable : public opt::OptTable { +public: + SLLDOptTable() : OptTable(InfoTable) {} +}; +} // namespace + +static Triple targetTriple; + +static void setTargetTriple(StringRef argv0, opt::InputArgList &args) { + std::string targetError; + + // Firstly, try to get it from program name prefix + std::string ProgName = llvm::sys::path::stem(argv0); + size_t lastComponent = ProgName.rfind('-'); + if (lastComponent != std::string::npos) { + std::string prefix = ProgName.substr(0, lastComponent); + if (llvm::TargetRegistry::lookupTarget(prefix, targetError)) { + targetTriple = llvm::Triple(prefix); + return; + } + } + + // Secondly, use the default target triple + targetTriple = llvm::Triple(getDefaultTargetTriple()); +} + +static void appendSearchPath(std::vector &args, const char *path) { + args.push_back("--library-path"); + args.push_back(path); +} + +static void appendTargetCustomization(std::vector &args) { + // force-disable RO segment on NetBSD due to ld.elf_so limitations + args.push_back("--no-rosegment"); + + // disable superfluous RUNPATH on NetBSD + args.push_back("--disable-new-dtags"); + + // set default image base address + switch (targetTriple.getArch()) { + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_be: + args.push_back("--image-base=0x200100000"); + break; + default: + break; + } + + // NetBSD driver relies on the linker knowing the default search paths. + // Please keep this in sync with clang/lib/Driver/ToolChains/NetBSD.cpp + // (NetBSD::NetBSD constructor) + switch (targetTriple.getArch()) { + case llvm::Triple::x86: + appendSearchPath(args, "=/usr/lib/i386"); + break; + case llvm::Triple::arm: + case llvm::Triple::armeb: + case llvm::Triple::thumb: + case llvm::Triple::thumbeb: + switch (targetTriple.getEnvironment()) { + case llvm::Triple::EABI: + case llvm::Triple::GNUEABI: + appendSearchPath(args, "=/usr/lib/eabi"); + break; + case llvm::Triple::EABIHF: + case llvm::Triple::GNUEABIHF: + appendSearchPath(args, "=/usr/lib/eabihf"); + break; + default: + appendSearchPath(args, "=/usr/lib/oabi"); + break; + } + break; +#if 0 // TODO + case llvm::Triple::mips64: + case llvm::Triple::mips64el: + if (tools::mips::hasMipsAbiArg(Args, "o32")) { + appendSearchPath(args, "=/usr/lib/o32"); + } else if (tools::mips::hasMipsAbiArg(Args, "64")) { + appendSearchPath(args, "=/usr/lib/64"); + } + break; +#endif + case llvm::Triple::ppc: + appendSearchPath(args, "=/usr/lib/powerpc"); + break; + case llvm::Triple::sparc: + appendSearchPath(args, "=/usr/lib/sparc"); + break; + default: + break; + } + + appendSearchPath(args, "=/usr/lib"); +} + +int main(int argc, const char **argv) { + bool printTarget = false; + + InitLLVM X(argc, argv); + + auto Program = sys::findProgramByName(LLD_PROGNAME); + if (!Program) { + WithColor::error() << "unable to find `" << LLD_PROGNAME + << "' in PATH: " << Program.getError().message() << "\n"; + return 1; + } + + ArrayRef argsArr = makeArrayRef(argv, argc); + + SLLDOptTable parser; + unsigned MAI; + unsigned MAC; + opt::InputArgList args = parser.ParseArgs(argsArr.slice(1), MAI, MAC); + + // Append to -v or -version the target information from slld. + if (args.hasArg(OPT_v) || args.hasArg(OPT_version)) + printTarget = true; + + InitializeAllTargets(); + setTargetTriple(argsArr[0], args); + + if (!targetTriple.isOSNetBSD()) { + WithColor::error() << "invalid NetBSD target: " << targetTriple.str() + << "\n"; + } + + argc--; + argv++; + + std::vector Argv; + Argv.push_back(*Program); + + // Prepend original arguments with the target options. + appendTargetCustomization(Argv); + + // Append original options. + // Trim -flavor option. + if (argc > 1 && argv[0] == StringRef("-flavor")) { + if (argc <= 2) + WithColor::error() << "missing arg value for '-flavor'\n"; + argc -= 2; + argv += 2; + } + + for (int i = 0; i < argc; ++i) + Argv.push_back(argv[i]); + + std::string ErrMsg; + int Result = sys::ExecuteAndWait(*Program, Argv, None, {}, 0, 0, &ErrMsg); + if (Result < 0) { + WithColor::error() << ErrMsg << "\n"; + return 1; + } + + if (printTarget) + WithColor::note() << "Target: " << targetTriple.str() << "\n"; + + return Result; +} -- 2.23.0