//===- Driver.cpp ---------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "lld/Common/Driver.h" #include "Config.h" #include "InputChunks.h" #include "InputElement.h" #include "MarkLive.h" #include "SymbolTable.h" #include "Writer.h" #include "lld/Common/Args.h" #include "lld/Common/CommonLinkerContext.h" #include "lld/Common/ErrorHandler.h" #include "lld/Common/Filesystem.h" #include "lld/Common/Memory.h" #include "lld/Common/Reproduce.h" #include "lld/Common/Strings.h" #include "lld/Common/Version.h" #include "llvm/ADT/Twine.h" #include "llvm/Config/llvm-config.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" #include "llvm/Support/GlobPattern.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" #include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" #include "llvm/TargetParser/Host.h" #include #include #define DEBUG_TYPE "lld" using namespace llvm; using namespace llvm::object; using namespace llvm::opt; using namespace llvm::sys; using namespace llvm::wasm; namespace lld::wasm { Ctx ctx; void errorOrWarn(const llvm::Twine &msg) { if (ctx.arg.noinhibitExec) warn(msg); else error(msg); } Ctx::Ctx() {} void Ctx::reset() { arg.~Config(); new (&arg) Config(); objectFiles.clear(); stubFiles.clear(); sharedFiles.clear(); bitcodeFiles.clear(); lazyBitcodeFiles.clear(); syntheticFunctions.clear(); syntheticGlobals.clear(); syntheticTables.clear(); whyExtractRecords.clear(); isPic = false; legacyFunctionTable = false; emitBssSegments = false; sym = WasmSym{}; } namespace { // Create enum with OPT_xxx values for each option in Options.td enum { OPT_INVALID = 0, #define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), #include "Options.inc" #undef OPTION }; // This function is called on startup. We need this for LTO since // LTO calls LLVM functions to compile bitcode files to native code. // Technically this can be delayed until we read bitcode files, but // we don't bother to do lazily because the initialization is fast. static void initLLVM() { InitializeAllTargets(); InitializeAllTargetMCs(); InitializeAllAsmPrinters(); InitializeAllAsmParsers(); } class LinkerDriver { public: LinkerDriver(Ctx &); void linkerMain(ArrayRef argsArr); private: void createFiles(opt::InputArgList &args); void addFile(StringRef path); void addLibrary(StringRef name); Ctx &ctx; // True if we are in --whole-archive and --no-whole-archive. bool inWholeArchive = false; // True if we are in --start-lib and --end-lib. bool inLib = false; std::vector files; }; static bool hasZOption(opt::InputArgList &args, StringRef key) { bool ret = false; for (const auto *arg : args.filtered(OPT_z)) if (key == arg->getValue()) { ret = true; arg->claim(); } return ret; } } // anonymous namespace bool link(ArrayRef args, llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) { // This driver-specific context will be freed later by unsafeLldMain(). auto *context = new CommonLinkerContext; context->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput); context->e.cleanupCallback = []() { ctx.reset(); }; context->e.logName = args::getFilenameWithoutExe(args[0]); context->e.errorLimitExceededMsg = "too many errors emitted, stopping now (use " "-error-limit=0 to see all errors)"; symtab = make(); initLLVM(); LinkerDriver(ctx).linkerMain(args); return errorCount() == 0; } #define OPTTABLE_STR_TABLE_CODE #include "Options.inc" #undef OPTTABLE_STR_TABLE_CODE #define OPTTABLE_PREFIXES_TABLE_CODE #include "Options.inc" #undef OPTTABLE_PREFIXES_TABLE_CODE // Create table mapping all options defined in Options.td static constexpr opt::OptTable::Info optInfo[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, \ VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ VALUES, SUBCOMMANDIDS_OFFSET) \ {PREFIX, \ NAME, \ HELPTEXT, \ HELPTEXTSFORVARIANTS, \ METAVAR, \ OPT_##ID, \ opt::Option::KIND##Class, \ PARAM, \ FLAGS, \ VISIBILITY, \ OPT_##GROUP, \ OPT_##ALIAS, \ ALIASARGS, \ VALUES, \ SUBCOMMANDIDS_OFFSET}, #include "Options.inc" #undef OPTION }; namespace { class WasmOptTable : public opt::GenericOptTable { public: WasmOptTable() : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, optInfo) {} opt::InputArgList parse(ArrayRef argv); }; } // namespace // Set color diagnostics according to -color-diagnostics={auto,always,never} // or -no-color-diagnostics flags. static void handleColorDiagnostics(opt::InputArgList &args) { auto *arg = args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, OPT_no_color_diagnostics); if (!arg) return; auto &errs = errorHandler().errs(); if (arg->getOption().getID() == OPT_color_diagnostics) { errs.enable_colors(true); } else if (arg->getOption().getID() == OPT_no_color_diagnostics) { errs.enable_colors(false); } else { StringRef s = arg->getValue(); if (s == "always") errs.enable_colors(true); else if (s == "never") errs.enable_colors(false); else if (s != "auto") error("unknown option: --color-diagnostics=" + s); } } static cl::TokenizerCallback getQuotingStyle(opt::InputArgList &args) { if (auto *arg = args.getLastArg(OPT_rsp_quoting)) { StringRef s = arg->getValue(); if (s != "windows" && s != "posix") error("invalid response file quoting: " + s); if (s == "windows") return cl::TokenizeWindowsCommandLine; return cl::TokenizeGNUCommandLine; } if (Triple(sys::getProcessTriple()).isOSWindows()) return cl::TokenizeWindowsCommandLine; return cl::TokenizeGNUCommandLine; } // Find a file by concatenating given paths. static std::optional findFile(StringRef path1, const Twine &path2) { SmallString<128> s; path::append(s, path1, path2); if (fs::exists(s)) return std::string(s); return std::nullopt; } opt::InputArgList WasmOptTable::parse(ArrayRef argv) { SmallVector vec(argv.data(), argv.data() + argv.size()); unsigned missingIndex; unsigned missingCount; // We need to get the quoting style for response files before parsing all // options so we parse here before and ignore all the options but // --rsp-quoting. opt::InputArgList args = this->ParseArgs(vec, missingIndex, missingCount); // Expand response files (arguments in the form of @) // and then parse the argument again. cl::ExpandResponseFiles(saver(), getQuotingStyle(args), vec); args = this->ParseArgs(vec, missingIndex, missingCount); handleColorDiagnostics(args); if (missingCount) error(Twine(args.getArgString(missingIndex)) + ": missing argument"); for (auto *arg : args.filtered(OPT_UNKNOWN)) error("unknown argument: " + arg->getAsString(args)); return args; } // Currently we allow a ".imports" to live alongside a library. This can // be used to specify a list of symbols which can be undefined at link // time (imported from the environment. For example libc.a include an // import file that lists the syscall functions it relies on at runtime. // In the long run this information would be better stored as a symbol // attribute/flag in the object file itself. // See: https://github.com/WebAssembly/tool-conventions/issues/35 static void readImportFile(StringRef filename) { if (std::optional buf = readFile(filename)) for (StringRef sym : args::getLines(*buf)) ctx.arg.allowUndefinedSymbols.insert(sym); } static bool hasWildcard(StringRef s) { return s.find_first_of("?*[") != StringRef::npos; } // Strip surrounding double quotes, if present. static StringRef unquoteVersion(StringRef s) { if (s.size() >= 2 && s.front() == '"' && s.back() == '"') return s.substr(1, s.size() - 2); return s; } // Record one symbol pattern from a version script into the right bucket. // `*` is tracked specially as a fast all-match flag; other wildcards are // compiled into GlobPatterns; everything else is an exact-name match. static void addVersionPattern(StringRef tok, bool global) { StringRef name = unquoteVersion(tok); bool &all = global ? ctx.arg.versionGlobalAll : ctx.arg.versionLocalAll; auto &exact = global ? ctx.arg.versionGlobalExact : ctx.arg.versionLocalExact; auto &pats = global ? ctx.arg.versionGlobalPatterns : ctx.arg.versionLocalPatterns; if (name == "*") { all = true; return; } if (hasWildcard(name)) { Expected pat = GlobPattern::create(name); if (pat) pats.push_back(std::move(*pat)); else { // Never silently drop a pattern; fall back to an exact match (so a // malformed glob can only over-export, never wrongly hide a symbol). handleAllErrors(pat.takeError(), [&](const ErrorInfoBase &e) { warn("version script: invalid pattern '" + name + "': " + e.message() + " (treated as a literal name)"); }); exact.insert(name); } return; } exact.insert(name); } // Parse a GNU version script for its export-visibility semantics only. We do // not honor the version *tags* (wasm collapses symbol versioning); we only // collect the global:/local: symbol sets. The grammar handled is the usual // [TAG] { [global:] s; ... [local:] s; ... [extern "C[++]" { ... };] } [parent]; // including anonymous (tag-less) nodes. Tokens are whitespace/`{}`/`;` // separated; `:` is kept as part of `global:`/`local:` keywords. static void readVersionScript(MemoryBufferRef mb) { StringRef s = mb.getBuffer(); size_t i = 0; const size_t n = s.size(); // Tokenizer: returns the next token, or empty StringRef at EOF. `{`, `}`, // and `;` are single-character tokens; everything else is a maximal run of // non-delimiter, non-whitespace characters (quotes are absorbed into the // run). C-style /* */ comments and `#` line comments are skipped. auto next = [&]() -> StringRef { for (;;) { while (i < n && isspace((unsigned char)s[i])) ++i; if (i + 1 < n && s[i] == '/' && s[i + 1] == '*') { size_t e = s.find("*/", i + 2); i = (e == StringRef::npos) ? n : e + 2; continue; } if (i < n && s[i] == '#') { size_t e = s.find('\n', i + 1); i = (e == StringRef::npos) ? n : e + 1; continue; } break; } if (i >= n) return StringRef(); char c = s[i]; if (c == '{' || c == '}' || c == ';') { ++i; return s.substr(i - 1, 1); } size_t start = i; bool inQuote = false; while (i < n) { char d = s[i]; if (d == '"') inQuote = !inQuote; if (!inQuote && (isspace((unsigned char)d) || d == '{' || d == '}' || d == ';')) break; ++i; } return s.substr(start, i - start); }; // Parse the body of a `{ ... }` node, dispatching symbols into global/local. auto parseBody = [&]() { bool global = true; // default scope before any `global:`/`local:` for (;;) { StringRef tok = next(); if (tok.empty() || tok == "}") return; if (tok == ";") continue; if (tok == "global:" || tok == "global") { global = true; continue; } if (tok == "local:" || tok == "local") { global = false; continue; } if (tok == ":") continue; if (tok == "extern") { // extern "C" / "C++" { names }; -- record the names in the current // scope. C++ name patterns are not demangled on wasm (over-export at // worst); this is a documented limitation. StringRef lang = next(); (void)lang; StringRef brace = next(); if (brace != "{") continue; for (;;) { StringRef e = next(); if (e.empty() || e == "}") break; if (e == ";") continue; addVersionPattern(e, global); } continue; } addVersionPattern(tok, global); } }; for (;;) { StringRef tok = next(); if (tok.empty()) break; if (tok == ";") continue; if (tok != "{") { // A version tag name (ignored on wasm). The `{` follows. StringRef brace = next(); if (brace.empty()) break; if (brace != "{") { error("version script: expected '{' after version tag '" + tok + "'"); return; } } parseBody(); // Consume an optional parent-tag list up to the terminating `;`. for (;;) { StringRef t = next(); if (t.empty() || t == ";") break; } } ctx.arg.hasVersionScript = true; } // Returns slices of MB by parsing MB as an archive file. // Each slice consists of a member file in the archive. std::vector> static getArchiveMembers( MemoryBufferRef mb) { std::unique_ptr file = CHECK(Archive::create(mb), mb.getBufferIdentifier() + ": failed to parse archive"); std::vector> v; Error err = Error::success(); for (const Archive::Child &c : file->children(err)) { MemoryBufferRef mbref = CHECK(c.getMemoryBufferRef(), mb.getBufferIdentifier() + ": could not get the buffer for a child of the archive"); v.push_back(std::make_pair(mbref, c.getChildOffset())); } if (err) fatal(mb.getBufferIdentifier() + ": Archive::children failed: " + toString(std::move(err))); // Take ownership of memory buffers created for members of thin archives. for (std::unique_ptr &mb : file->takeThinBuffers()) make>(std::move(mb)); return v; } void LinkerDriver::addFile(StringRef path) { std::optional buffer = readFile(path); if (!buffer) return; MemoryBufferRef mbref = *buffer; switch (identify_magic(mbref.getBuffer())) { case file_magic::archive: { SmallString<128> importFile = path; path::replace_extension(importFile, ".imports"); if (fs::exists(importFile)) readImportFile(importFile.str()); auto members = getArchiveMembers(mbref); // Handle -whole-archive. if (inWholeArchive) { for (const auto &[m, offset] : members) { auto *object = createObjectFile(m, path, offset); files.push_back(object); } return; } std::unique_ptr file = CHECK(Archive::create(mbref), path + ": failed to parse archive"); for (const auto &[m, offset] : members) { auto magic = identify_magic(m.getBuffer()); if (magic == file_magic::wasm_object || magic == file_magic::bitcode) files.push_back(createObjectFile(m, path, offset, true)); else warn(path + ": archive member '" + m.getBufferIdentifier() + "' is neither Wasm object file nor LLVM bitcode"); } return; } case file_magic::bitcode: case file_magic::wasm_object: { auto obj = createObjectFile(mbref, "", 0, inLib); if (ctx.arg.isStatic && isa(obj)) { error("attempted static link of dynamic object " + path); break; } files.push_back(obj); break; } case file_magic::unknown: if (mbref.getBuffer().starts_with("#STUB")) { files.push_back(make(mbref)); break; } [[fallthrough]]; default: error("unknown file type: " + mbref.getBufferIdentifier()); } } static std::optional findFromSearchPaths(StringRef path) { for (StringRef dir : ctx.arg.searchPaths) if (std::optional s = findFile(dir, path)) return s; return std::nullopt; } // This is for -l. We'll look for lib.a from // search paths. static std::optional searchLibraryBaseName(StringRef name) { for (StringRef dir : ctx.arg.searchPaths) { if (!ctx.arg.isStatic) if (std::optional s = findFile(dir, "lib" + name + ".so")) return s; if (std::optional s = findFile(dir, "lib" + name + ".a")) return s; } return std::nullopt; } // This is for -l. static std::optional searchLibrary(StringRef name) { if (name.starts_with(":")) return findFromSearchPaths(name.substr(1)); return searchLibraryBaseName(name); } // Add a given library by searching it from input search paths. void LinkerDriver::addLibrary(StringRef name) { if (std::optional path = searchLibrary(name)) addFile(saver().save(*path)); else error("unable to find library -l" + name, ErrorTag::LibNotFound, {name}); } void LinkerDriver::createFiles(opt::InputArgList &args) { for (auto *arg : args) { switch (arg->getOption().getID()) { case OPT_library: addLibrary(arg->getValue()); break; case OPT_INPUT: addFile(arg->getValue()); break; case OPT_Bstatic: ctx.arg.isStatic = true; break; case OPT_Bdynamic: if (!ctx.arg.relocatable) ctx.arg.isStatic = false; break; case OPT_whole_archive: inWholeArchive = true; break; case OPT_no_whole_archive: inWholeArchive = false; break; case OPT_start_lib: if (inLib) error("nested --start-lib"); inLib = true; break; case OPT_end_lib: if (!inLib) error("stray --end-lib"); inLib = false; break; } } if (files.empty() && errorCount() == 0) error("no input files"); } static StringRef getAliasSpelling(opt::Arg *arg) { if (const opt::Arg *alias = arg->getAlias()) return alias->getSpelling(); return arg->getSpelling(); } static std::pair getOldNewOptions(opt::InputArgList &args, unsigned id) { auto *arg = args.getLastArg(id); if (!arg) return {"", ""}; StringRef s = arg->getValue(); std::pair ret = s.split(';'); if (ret.second.empty()) error(getAliasSpelling(arg) + " expects 'old;new' format, but got " + s); return ret; } // Parse options of the form "old;new[;extra]". static std::tuple getOldNewOptionsExtra(opt::InputArgList &args, unsigned id) { auto [oldDir, second] = getOldNewOptions(args, id); auto [newDir, extraDir] = second.split(';'); return {oldDir, newDir, extraDir}; } static StringRef getEntry(opt::InputArgList &args) { auto *arg = args.getLastArg(OPT_entry, OPT_no_entry); if (!arg) { if (args.hasArg(OPT_relocatable)) return ""; if (args.hasArg(OPT_shared)) return "__wasm_call_ctors"; return "_start"; } if (arg->getOption().getID() == OPT_no_entry) return ""; return arg->getValue(); } // Determines what we should do if there are remaining unresolved // symbols after the name resolution. static UnresolvedPolicy getUnresolvedSymbolPolicy(opt::InputArgList &args) { UnresolvedPolicy errorOrWarn = args.hasFlag(OPT_error_unresolved_symbols, OPT_warn_unresolved_symbols, true) ? UnresolvedPolicy::ReportError : UnresolvedPolicy::Warn; if (auto *arg = args.getLastArg(OPT_unresolved_symbols)) { StringRef s = arg->getValue(); if (s == "ignore-all") return UnresolvedPolicy::Ignore; if (s == "import-dynamic") return UnresolvedPolicy::ImportDynamic; if (s == "report-all") return errorOrWarn; error("unknown --unresolved-symbols value: " + s); } return errorOrWarn; } // Parse --build-id or --build-id=