Skip to content

Instantly share code, notes, and snippets.

@MaskRay
Last active September 7, 2026 19:51
Show Gist options
  • Select an option

  • Save MaskRay/f1fb88fcbaa51404ff0f746dcca67d6a to your computer and use it in GitHub Desktop.

Select an option

Save MaskRay/f1fb88fcbaa51404ff0f746dcca67d6a to your computer and use it in GitHub Desktop.
lld/ELF parallel file parsing pipeline
From c29fbaee28ba8399fd5225a974c7333048fc5195 Mon Sep 17 00:00:00 2001
From: Fangrui Song <hidden>
Date: Wed, 17 Jun 2026 23:28:54 -0700
Subject: [PATCH] [ELF] Parallelize input file parsing and symbol resolution
(order-independent extraction)
Replace the serial elf::parseFiles loop with a parallel pipeline. Per
batch the pipeline reads symbol records (parallel per file,
counting-sorted into 32 hash buckets), builds a per-bucket name
database, activates archive members, creates and resolves symbols
(parallel per bucket), and wires global symbols into per-file arrays.
Archive member activation uses order-independent extraction: a non-weak
undefined reference pulls in its lazy member, to a fixpoint; the whole
link behaves like one implicit group. Each name resolves in its
strongest tier (strong over weak); within a tier an eager regular
(non-lazy, non-shared) definition is preferred and suppresses
extraction, otherwise the first-seen definition wins and a lazy member
is pulled in. So a regular archive definition is preferred over a shared
one when seen first, an eager regular definition always beats a lazy
one, and a weak archive member is pulled only when nothing stronger
defines the name -- matching ld.bfd. Determinism comes from file index,
not scan order. This abandons GNU positional semantics: backward
references resolve and --start-group is a no-op. Output is deterministic
across thread counts; all of lld/test/ELF passes, with order-dependent
tests (shared-lazy, trace-symbols) updated to the new behavior.
--fortran-common is preserved: when a tentative COMMON is active (in an
eager file or an extracted member) and a lazy member provides a
non-tentative definition of the same name, that member is pulled in to
override the COMMON, tracked by a per-name override worklist in the
activation fixpoint.
Order-independence makes several serial-path constructs dead, removed
here: the actDefIdx lazy-definition cutoff (a member is fully eager once
extracted, so a definition anchors at its file's rank), and the separate
fullRank/lazyRank arrays (merged into one file-index rank).
--warn-backrefs is removed too: with one implicit group every reference
resolves, so backward-reference detection is meaningless; the option,
ctx.backwardReferences, the recordExtractions backref pass, and
InputFile::groupId go with it. --why-extract is retained.
Experimental variant of the parallel-pipeline patch; not for merge.
---
lld/ELF/Config.h | 14 +-
lld/ELF/Driver.cpp | 273 ++-
lld/ELF/InputFiles.cpp | 2040 ++++++++++++-----
lld/ELF/InputFiles.h | 82 +-
lld/ELF/LinkerScript.cpp | 8 +-
lld/ELF/Options.td | 14 +-
lld/ELF/Relocations.cpp | 9 +-
lld/ELF/ScriptParser.cpp | 2 -
lld/ELF/SymbolTable.cpp | 39 +-
lld/ELF/SymbolTable.h | 65 +-
lld/ELF/Symbols.cpp | 142 +-
lld/ELF/Symbols.h | 12 +-
lld/ELF/SyntheticSections.cpp | 9 +-
lld/ELF/Writer.cpp | 3 +-
lld/docs/ELF/warn_backrefs.md | 100 -
lld/docs/index.md | 1 -
lld/docs/ld.lld.1 | 13 -
lld/test/ELF/fortran-common-extract.s | 84 +
lld/test/ELF/interconnected-lazy.s | 12 +-
lld/test/ELF/lto/archive-mixed.test | 5 +-
lld/test/ELF/lto/comdat-mixed-archive.test | 33 +-
lld/test/ELF/lto/lazy-internal.ll | 5 +-
.../lto/thinlto-emit-index-thin-archive.ll | 15 +-
lld/test/ELF/lto/warn-backrefs.ll | 30 -
lld/test/ELF/shared-lazy.s | 22 +-
lld/test/ELF/trace-symbols.s | 21 +-
lld/test/ELF/warn-backrefs.s | 112 -
lld/test/ELF/why-extract.s | 7 +-
lld/test/ELF/wrap-extract-real.s | 20 +
29 files changed, 1963 insertions(+), 1229 deletions(-)
delete mode 100644 lld/docs/ELF/warn_backrefs.md
create mode 100644 lld/test/ELF/fortran-common-extract.s
delete mode 100644 lld/test/ELF/lto/warn-backrefs.ll
delete mode 100644 lld/test/ELF/warn-backrefs.s
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 54e0aa58591a..f315369c3725 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -196,7 +196,6 @@ struct LoadJob {
bool lazy;
bool asNeeded;
bool withLOption;
- uint32_t groupId;
SmallVector<std::unique_ptr<InputFile>, 0> out;
std::vector<std::unique_ptr<llvm::MemoryBuffer>> thinBufs;
SmallVector<std::pair<std::string, llvm::StringRef>, 0> tarEntries;
@@ -232,8 +231,10 @@ private:
SmallVector<std::unique_ptr<InputFile>, 0> files, ltoObjectFiles;
public:
- // See InputFile::groupId.
- uint32_t nextGroupId;
+ // The command-line input files and dependent libraries, some of which may
+ // still be lazy.
+ ArrayRef<std::unique_ptr<InputFile>> getFiles() const { return files; }
+
bool isInGroup;
std::unique_ptr<InputFile> armCmseImpLib;
SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
@@ -422,8 +423,6 @@ struct Config {
bool undefinedVersion;
bool unique;
bool useAndroidRelrTags = false;
- bool warnBackrefs;
- llvm::SmallVector<llvm::GlobPattern, 0> warnBackrefsExclude;
bool warnCommon;
bool warnMissingEntry;
bool warnSymbolOrdering;
@@ -739,11 +738,6 @@ struct Ctx : CommonLinkerContext {
// A tuple of (reference, extractedFile, sym). Used by --why-extract=.
SmallVector<std::tuple<std::string, const InputFile *, const Symbol &>, 0>
whyExtractRecords;
- // A mapping from a symbol to an InputFile referencing it backward. Used by
- // --warn-backrefs.
- llvm::DenseMap<const Symbol *,
- std::pair<const InputFile *, const InputFile *>>
- backwardReferences;
llvm::SmallSet<llvm::StringRef, 0> auxiliaryFiles;
// If --reproduce is specified, all input files are written to this tar
// archive.
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 0393410b35d4..b2c319c2c71a 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -242,7 +242,6 @@ void LinkerDriver::addFile(StringRef path, bool withLOption) {
/*lazy=*/false,
/*asNeeded=*/false,
/*withLOption=*/false,
- nextGroupId,
{},
{},
{}});
@@ -281,13 +280,10 @@ void LinkerDriver::addFile(StringRef path, bool withLOption) {
inLib,
ctx.arg.asNeeded,
withLOption,
- nextGroupId,
{},
{},
{}});
}
- if (!isInGroup)
- ++nextGroupId;
if (!deferLoad)
loadFiles();
}
@@ -1633,8 +1629,6 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
ctx.arg.unique = args.hasArg(OPT_unique);
ctx.arg.useAndroidRelrTags = args.hasFlag(
OPT_use_android_relr_tags, OPT_no_use_android_relr_tags, false);
- ctx.arg.warnBackrefs =
- args.hasFlag(OPT_warn_backrefs, OPT_no_warn_backrefs, false);
ctx.arg.warnCommon = args.hasFlag(OPT_warn_common, OPT_no_warn_common, false);
ctx.arg.warnSymbolOrdering =
args.hasFlag(OPT_warn_symbol_ordering, OPT_no_warn_symbol_ordering, true);
@@ -1979,15 +1973,6 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
ctx.arg.retainSymbols->insert(s);
}
- for (opt::Arg *arg : args.filtered(OPT_warn_backrefs_exclude)) {
- StringRef pattern(arg->getValue());
- if (Expected<GlobPattern> pat = GlobPattern::create(pattern))
- ctx.arg.warnBackrefsExclude.push_back(std::move(*pat));
- else
- ErrAlways(ctx) << arg->getSpelling() << ": " << pat.takeError() << ": "
- << pattern;
- }
-
// For -no-pie and -pie, --export-dynamic-symbol specifies defined symbols
// which should be exported. For -shared, references to matched non-local
// STV_DEFAULT symbols are not bound to definitions within the shared object,
@@ -2161,9 +2146,7 @@ void LinkerDriver::loadFiles() {
case LoadJob::Archive: {
// Scan all archive members rather than using the archive symbol
// index. We assume the archive symbol table order matches the order
- // of symbols in the member symbol tables. All files within the
- // archive share the same group ID to allow mutual references for
- // --warn-backrefs.
+ // of symbols in the member symbol tables.
auto members = getArchiveMembers(ctx, job);
job.out.reserve(members.size());
bool lazy = !job.inWholeArchive;
@@ -2196,8 +2179,6 @@ void LinkerDriver::loadFiles() {
job.out.push_back(std::make_unique<BinaryFile>(ctx, job.mbref));
break;
}
- for (auto &m : job.out)
- m->groupId = job.groupId;
});
}
@@ -2230,7 +2211,6 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
// Iterate over argv to process input files and positional arguments.
std::optional<MemoryBufferRef> defaultScript;
- nextGroupId = 0;
isInGroup = false;
bool hasInput = false, hasScript = false;
for (auto *arg : args) {
@@ -2309,7 +2289,6 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
if (!isInGroup)
ErrAlways(ctx) << "stray --end-group";
isInGroup = false;
- ++nextGroupId;
break;
case OPT_start_lib:
if (inLib)
@@ -2324,7 +2303,6 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
ErrAlways(ctx) << "stray --end-lib";
inLib = false;
isInGroup = false;
- ++nextGroupId;
break;
case OPT_push_state:
stack.emplace_back(ctx.arg.asNeeded, ctx.arg.isStatic, inWholeArchive);
@@ -2480,19 +2458,6 @@ static void excludeLibs(Ctx &ctx, opt::InputArgList &args) {
visit(file);
}
-// Force Sym to be entered in the output.
-static void handleUndefined(Ctx &ctx, Symbol *sym, const char *option) {
- // Since a symbol may not be used inside the program, LTO may
- // eliminate it. Mark the symbol as "used" to prevent it.
- sym->isUsedInRegularObj = true;
-
- if (!sym->isLazy())
- return;
- sym->extract(ctx);
- if (!ctx.arg.whyExtract.empty())
- ctx.whyExtractRecords.emplace_back(option, sym->file, *sym);
-}
-
// As an extension to GNU linkers, lld supports a variant of `-u`
// which accepts wildcard patterns. All symbols that match a given
// pattern are handled as if they were given by `-u`.
@@ -2503,24 +2468,19 @@ static void handleUndefinedGlob(Ctx &ctx, StringRef arg) {
return;
}
- // Calling sym->extract() in the loop is not safe because it may add new
- // symbols to the symbol table, invalidating the current iterator.
- SmallVector<Symbol *, 0> syms;
+ // Mark all matches as used (so LTO does not eliminate them) and extract
+ // the lazy ones in a single batch.
+ SmallVector<Symbol *, 0> lazy;
for (Symbol *sym : ctx.symtab->getSymbols())
- if (!sym->isPlaceholder() && pat->match(sym->getName()))
- syms.push_back(sym);
-
- for (Symbol *sym : syms)
- handleUndefined(ctx, sym, "--undefined-glob");
-}
-
-static void handleLibcall(Ctx &ctx, StringRef name) {
- Symbol *sym = ctx.symtab->find(name);
- if (sym && sym->isLazy() && isa<BitcodeFile>(sym->file)) {
- if (!ctx.arg.whyExtract.empty())
- ctx.whyExtractRecords.emplace_back("<libcall>", sym->file, *sym);
- sym->extract(ctx);
- }
+ if (!sym->isPlaceholder() && pat->match(sym->getName())) {
+ sym->isUsedInRegularObj = true;
+ if (sym->isLazy())
+ lazy.push_back(sym);
+ }
+ reactivate(ctx, lazy);
+ if (!ctx.arg.whyExtract.empty())
+ for (Symbol *sym : lazy)
+ ctx.whyExtractRecords.emplace_back("--undefined-glob", sym->file, *sym);
}
static void writeArchiveStats(Ctx &ctx) {
@@ -2571,25 +2531,6 @@ static void writeWhyExtract(Ctx &ctx) {
}
}
-static void reportBackrefs(Ctx &ctx) {
- for (auto &ref : ctx.backwardReferences) {
- const Symbol &sym = *ref.first;
- std::string to = toStr(ctx, ref.second.second);
- // Some libraries have known problems and can cause noise. Filter them out
- // with --warn-backrefs-exclude=. The value may look like (for --start-lib)
- // *.o or (archive member) *.a(*.o).
- bool exclude = false;
- for (const llvm::GlobPattern &pat : ctx.arg.warnBackrefsExclude)
- if (pat.match(to)) {
- exclude = true;
- break;
- }
- if (!exclude)
- Warn(ctx) << "backward reference detected: " << sym.getName() << " in "
- << ref.second.first << " refers to " << to;
- }
-}
-
// Handle --dependency-file=<path>. If that option is given, lld creates a
// file at a given path with the following contents:
//
@@ -2812,10 +2753,18 @@ void LinkerDriver::compileBitcodeFiles(bool skipLinkedOutput) {
markBuffersAsDontNeed(ctx, skipLinkedOutput);
ltoObjectFiles = lto->compile();
+
+ // Resolve the LTO outputs' symbols against the symbol table and register
+ // them in ctx.objectFiles (the parse pipeline, shared with regular objects),
+ // pulling in archive members newly referenced by the LTO outputs (e.g.
+ // runtime libcalls).
+ SmallVector<InputFile *, 0> ltoFiles;
+ for (auto &file : ltoObjectFiles)
+ ltoFiles.push_back(file.get());
+ parseLtoObjectFiles(ctx, ltoFiles);
+
for (auto &file : ltoObjectFiles) {
auto *obj = cast<ObjFile<ELFT>>(file.get());
- obj->parse(/*ignoreComdats=*/true);
-
// This is only needed for AArch64 PAuth to set correct key in AUTH GOT
// entry based on symbol type (STT_FUNC or not).
// TODO: check if PAuth is actually used.
@@ -2839,7 +2788,6 @@ void LinkerDriver::compileBitcodeFiles(bool skipLinkedOutput) {
if (sym->hasVersionSuffix)
sym->parseSymbolVersion(ctx);
}
- ctx.objectFiles.push_back(obj);
}
}
@@ -2867,6 +2815,22 @@ static std::vector<WrappedSymbol> addWrappedSymbols(Ctx &ctx,
std::vector<WrappedSymbol> v;
DenseSet<StringRef> seen;
auto &ss = ctx.saver;
+ // A wrapper (or, when __real_ is referenced, the wrapped symbol) may live in
+ // a lazy archive member. Collect the references of one phase and pull the
+ // members in with a single activation pass.
+ SmallVector<Symbol *, 0> triggers;
+ auto addUndef = [&](StringRef name, uint8_t binding = llvm::ELF::STB_GLOBAL) {
+ Symbol *s = ctx.symtab->addUnusedUndefined(name, binding);
+ if (s->isLazy() && !s->isWeak())
+ triggers.push_back(s);
+ return s;
+ };
+
+ struct Wrap {
+ Symbol *sym, *wrap;
+ StringRef name, realName;
+ };
+ SmallVector<Wrap, 0> wraps;
for (auto *arg : args.filtered(OPT_wrap)) {
StringRef name = arg->getValue();
if (!seen.insert(name).second)
@@ -2876,40 +2840,59 @@ static std::vector<WrappedSymbol> addWrappedSymbols(Ctx &ctx,
if (!sym)
continue;
- Symbol *wrap =
- ctx.symtab->addUnusedUndefined(ss.save("__wrap_" + name), sym->binding);
+ wraps.push_back({sym, addUndef(ss.save("__wrap_" + name), sym->binding),
+ name, ss.save("__real_" + name)});
+ }
+ // Extract the wrappers first, so that a __real_ reference inside one of them
+ // is seen by the check below.
+ reactivate(ctx, triggers);
+ triggers.clear();
- // If __real_ is referenced, pull in the symbol if it is lazy. Do this after
- // processing __wrap_ as that may have referenced __real_.
- StringRef realName = ctx.saver.save("__real_" + name);
- if (Symbol *real = ctx.symtab->find(realName)) {
- ctx.symtab->addUnusedUndefined(name, sym->binding);
+ // If __real_ is referenced, pull in the symbol if it is lazy. A member
+ // extracted for one entry may reference another's __real_, so iterate.
+ SmallVector<uint8_t, 0> hasReal(wraps.size());
+ for (bool changed = true; changed;) {
+ changed = false;
+ for (auto [i, w] : llvm::enumerate(wraps)) {
+ Symbol *real;
+ if (hasReal[i] || !(real = ctx.symtab->find(w.realName)))
+ continue;
+ hasReal[i] = 1;
+ addUndef(w.name, w.sym->binding);
// Update sym's binding, which will replace real's later in
// SymbolTable::wrap.
- sym->binding = real->binding;
+ w.sym->binding = real->binding;
+ changed = true;
}
+ reactivate(ctx, triggers);
+ triggers.clear();
+ }
- Symbol *real = ctx.symtab->addUnusedUndefined(realName);
- v.push_back({sym, real, wrap});
+ for (const Wrap &w : wraps) {
+ Symbol *real = addUndef(w.realName);
+ v.push_back({w.sym, real, w.wrap});
// We want to tell LTO not to inline symbols to be overwritten
// because LTO doesn't know the final symbol contents after renaming.
real->scriptDefined = true;
- sym->scriptDefined = true;
+ w.sym->scriptDefined = true;
+ }
+ reactivate(ctx, triggers);
- // If a symbol is referenced in any object file, bitcode file or shared
- // object, mark its redirection target (foo for __real_foo and __wrap_foo
- // for foo) as referenced after redirection, which will be used to tell LTO
- // to not eliminate the redirection target. If the object file defining the
- // symbol also references it, we cannot easily distinguish the case from
- // cases where the symbol is not referenced. Retain the redirection target
- // in this case because we choose to wrap symbol references regardless of
- // whether the symbol is defined
- // (https://sourceware.org/bugzilla/show_bug.cgi?id=26358).
- if (real->referenced || real->isDefined())
- sym->referencedAfterWrap = true;
- if (sym->referenced || sym->isDefined())
- wrap->referencedAfterWrap = true;
+ // If a symbol is referenced in any object file, bitcode file or shared
+ // object, mark its redirection target (foo for __real_foo and __wrap_foo
+ // for foo) as referenced after redirection, which will be used to tell LTO
+ // to not eliminate the redirection target. If the object file defining the
+ // symbol also references it, we cannot easily distinguish the case from
+ // cases where the symbol is not referenced. Retain the redirection target
+ // in this case because we choose to wrap symbol references regardless of
+ // whether the symbol is defined
+ // (https://sourceware.org/bugzilla/show_bug.cgi?id=26358).
+ for (const WrappedSymbol &w : v) {
+ if (w.real->referenced || w.real->isDefined())
+ w.sym->referencedAfterWrap = true;
+ if (w.sym->referenced || w.sym->isDefined())
+ w.wrap->referencedAfterWrap = true;
}
return v;
}
@@ -3202,25 +3185,6 @@ static void readSecurityNotes(Ctx &ctx) {
<< "dependencies have the GCS marking.";
}
-static void initSectionsAndLocalSyms(ELFFileBase *file, bool ignoreComdats) {
- switch (file->ekind) {
- case ELF32LEKind:
- cast<ObjFile<ELF32LE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
- break;
- case ELF32BEKind:
- cast<ObjFile<ELF32BE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
- break;
- case ELF64LEKind:
- cast<ObjFile<ELF64LE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
- break;
- case ELF64BEKind:
- cast<ObjFile<ELF64BE>>(file)->initSectionsAndLocalSyms(ignoreComdats);
- break;
- default:
- llvm_unreachable("");
- }
-}
-
static void postParseObjectFile(ELFFileBase *file) {
switch (file->ekind) {
case ELF32LEKind:
@@ -3246,6 +3210,7 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
llvm::TimeTraceScope timeScope("Link", StringRef("LinkerDriver::Link"));
// Handle --trace-symbol.
+ ctx.symtab->hasTracedSymbol = args.hasArg(OPT_trace_symbol);
for (auto *arg : args.filtered(OPT_trace_symbol))
ctx.symtab->insert(arg->getValue())->traced = true;
@@ -3270,9 +3235,16 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
ctx.sharedFiles.size() || ctx.arg.shared) &&
ctx.hasDynsym;
- // If an entry symbol is in a static archive, pull out that file now.
- if (Symbol *sym = ctx.symtab->find(ctx.arg.entry))
- handleUndefined(ctx, sym, "--entry");
+ // If an entry symbol is in a static archive, pull out that file now. Mark it
+ // used so that LTO does not eliminate it.
+ if (Symbol *sym = ctx.symtab->find(ctx.arg.entry)) {
+ sym->isUsedInRegularObj = true;
+ if (sym->isLazy()) {
+ reactivate(ctx, ArrayRef(sym));
+ if (!ctx.arg.whyExtract.empty())
+ ctx.whyExtractRecords.emplace_back("--entry", sym->file, *sym);
+ }
+ }
// Handle the `--undefined-glob <pattern>` options.
for (StringRef pat : args::getStrings(args, OPT_undefined_glob))
@@ -3313,8 +3285,16 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
// object file to the link.
if (!ctx.bitcodeFiles.empty()) {
llvm::Triple TT(ctx.bitcodeFiles.front()->obj->getTargetTriple());
- for (auto *s : lto::LTO::getRuntimeLibcallSymbols(TT))
- handleLibcall(ctx, s);
+ SmallVector<Symbol *, 0> libcalls;
+ for (auto *s : lto::LTO::getRuntimeLibcallSymbols(TT)) {
+ Symbol *sym = ctx.symtab->find(s);
+ if (sym && sym->isLazy() && isa<BitcodeFile>(sym->file)) {
+ if (!ctx.arg.whyExtract.empty())
+ ctx.whyExtractRecords.emplace_back("<libcall>", sym->file, *sym);
+ libcalls.push_back(sym);
+ }
+ }
+ reactivate(ctx, libcalls);
}
// Archive members defining __wrap symbols may be extracted.
@@ -3322,9 +3302,6 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
// No more lazy bitcode can be extracted at this point. Do post parse work
// like checking duplicate symbols.
- parallelForEach(ctx.objectFiles, [](ELFFileBase *file) {
- initSectionsAndLocalSyms(file, /*ignoreComdats=*/false);
- });
parallelForEach(ctx.objectFiles, postParseObjectFile);
parallelForEach(ctx.bitcodeFiles,
[](BitcodeFile *file) { file->postParse(); });
@@ -3401,9 +3378,8 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
const size_t numInputFilesBeforeLTO = ctx.driver.files.size();
compileBitcodeFiles<ELFT>(skipLinkedOutput);
- // Symbol resolution finished. Report backward reference problems,
- // --print-archive-stats=, and --why-extract=.
- reportBackrefs(ctx);
+ // Symbol resolution finished. Report --print-archive-stats= and
+ // --why-extract=.
writeArchiveStats(ctx);
writeWhyExtract(ctx);
if (errCount(ctx))
@@ -3416,9 +3392,6 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
// compileBitcodeFiles may have produced lto.tmp object files. After this, no
// more file will be added.
auto newObjectFiles = ArrayRef(ctx.objectFiles).slice(numObjsBeforeLTO);
- parallelForEach(newObjectFiles, [](ELFFileBase *file) {
- initSectionsAndLocalSyms(file, /*ignoreComdats=*/true);
- });
parallelForEach(newObjectFiles, postParseObjectFile);
for (const DuplicateSymbol &d : ctx.duplicates)
reportDuplicate(ctx, *d.sym, d.file, d.section, d.value);
@@ -3455,17 +3428,43 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
llvm::TimeTraceScope timeScope("Aggregate sections");
// Now that we have a complete list of input files.
// Beyond this point, no new files are added.
- // Aggregate all input sections into one place.
- for (InputFile *f : ctx.objectFiles) {
- for (InputSectionBase *s : f->getSections()) {
+ // Aggregate all input sections into one place, preserving the
+ // file-then-section order: count per file in parallel, prefix-sum, then
+ // place each file's sections into its slice.
+ size_t numFiles = ctx.objectFiles.size();
+ SmallVector<uint32_t, 0> secOff(numFiles + 1), ehOff(numFiles + 1);
+ parallelFor(0, numFiles, [&](size_t i) {
+ uint32_t sn = 0, en = 0;
+ for (InputSectionBase *s : ctx.objectFiles[i]->getSections()) {
if (!s || s == &InputSection::discarded)
continue;
if (LLVM_UNLIKELY(isa<EhInputSection>(s)))
- ctx.ehInputSections.push_back(cast<EhInputSection>(s));
+ ++en;
else
- ctx.inputSections.push_back(s);
+ ++sn;
}
+ secOff[i + 1] = sn;
+ ehOff[i + 1] = en;
+ });
+ for (size_t i = 0; i != numFiles; ++i) {
+ secOff[i + 1] += secOff[i];
+ ehOff[i + 1] += ehOff[i];
}
+ size_t secBase = ctx.inputSections.size(),
+ ehBase = ctx.ehInputSections.size();
+ ctx.inputSections.resize(secBase + secOff[numFiles]);
+ ctx.ehInputSections.resize(ehBase + ehOff[numFiles]);
+ parallelFor(0, numFiles, [&](size_t i) {
+ size_t sn = secBase + secOff[i], en = ehBase + ehOff[i];
+ for (InputSectionBase *s : ctx.objectFiles[i]->getSections()) {
+ if (!s || s == &InputSection::discarded)
+ continue;
+ if (LLVM_UNLIKELY(isa<EhInputSection>(s)))
+ ctx.ehInputSections[en++] = cast<EhInputSection>(s);
+ else
+ ctx.inputSections[sn++] = s;
+ }
+ });
for (BinaryFile *f : ctx.binaryFiles)
for (InputSectionBase *s : f->getSections())
ctx.inputSections.push_back(cast<InputSection>(s));
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 23649d620071..21f51248bfbc 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -26,9 +26,11 @@
#include "llvm/Support/ARMBuildAttributes.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
+#include <numeric>
#include <optional>
using namespace llvm;
@@ -262,8 +264,9 @@ std::optional<MemoryBufferRef> elf::readFile(Ctx &ctx, StringRef path) {
// All input object files must be for the same architecture
// (e.g. it does not make sense to link x86 object files with
-// MIPS object files.) This function checks for that error.
-static bool isCompatible(Ctx &ctx, InputFile *file) {
+// MIPS object files.) This function checks for that error. existing is an
+// already-accepted file named by the fallback diagnostic.
+static bool isCompatible(Ctx &ctx, InputFile *file, InputFile *existing) {
if (!file->isElf() && !isa<BitcodeFile>(file))
return true;
@@ -281,13 +284,6 @@ static bool isCompatible(Ctx &ctx, InputFile *file) {
return false;
}
- InputFile *existing = nullptr;
- if (!ctx.objectFiles.empty())
- existing = ctx.objectFiles[0];
- else if (!ctx.sharedFiles.empty())
- existing = ctx.sharedFiles[0];
- else if (!ctx.bitcodeFiles.empty())
- existing = ctx.bitcodeFiles[0];
auto diag = Err(ctx);
diag << file << " is incompatible";
if (existing)
@@ -295,71 +291,6 @@ static bool isCompatible(Ctx &ctx, InputFile *file) {
return false;
}
-template <class ELFT> static void doParseFile(Ctx &ctx, InputFile *file) {
- if (!isCompatible(ctx, file))
- return;
-
- // Lazy object file
- if (file->lazy) {
- if (auto *f = dyn_cast<BitcodeFile>(file)) {
- ctx.lazyBitcodeFiles.push_back(f);
- f->parseLazy();
- } else {
- cast<ObjFile<ELFT>>(file)->parseLazy();
- }
- return;
- }
-
- if (ctx.arg.trace)
- Msg(ctx) << file;
-
- if (file->kind() == InputFile::ObjKind) {
- ctx.objectFiles.push_back(cast<ELFFileBase>(file));
- cast<ObjFile<ELFT>>(file)->parse();
- } else if (auto *f = dyn_cast<SharedFile>(file)) {
- f->parse<ELFT>();
- } else if (auto *f = dyn_cast<BitcodeFile>(file)) {
- ctx.bitcodeFiles.push_back(f);
- f->parse();
- } else {
- ctx.binaryFiles.push_back(cast<BinaryFile>(file));
- cast<BinaryFile>(file)->parse();
- }
-}
-
-// Add symbols in File to the symbol table.
-void elf::parseFile(Ctx &ctx, InputFile *file) {
- invokeELFT(doParseFile, ctx, file);
-}
-
-// This function is explicitly instantiated in ARM.cpp. Mark it extern here,
-// to avoid warnings when building with MSVC.
-extern template void ObjFile<ELF32LE>::importCmseSymbols();
-extern template void ObjFile<ELF32BE>::importCmseSymbols();
-extern template void ObjFile<ELF64LE>::importCmseSymbols();
-extern template void ObjFile<ELF64BE>::importCmseSymbols();
-
-template <class ELFT>
-static void
-doParseFiles(Ctx &ctx,
- const SmallVector<std::unique_ptr<InputFile>, 0> &files) {
- // Add all files to the symbol table. This will add almost all symbols that we
- // need to the symbol table. This process might add files to the link due to
- // addDependentLibrary.
- for (size_t i = 0; i < files.size(); ++i) {
- llvm::TimeTraceScope timeScope("Parse input files", files[i]->getName());
- doParseFile<ELFT>(ctx, files[i].get());
- }
- if (ctx.driver.armCmseImpLib)
- cast<ObjFile<ELFT>>(*ctx.driver.armCmseImpLib).importCmseSymbols();
-}
-
-void elf::parseFiles(Ctx &ctx,
- const SmallVector<std::unique_ptr<InputFile>, 0> &files) {
- llvm::TimeTraceScope timeScope("Parse input files");
- invokeELFT(doParseFiles, ctx, files);
-}
-
// Concatenates arguments to construct a string representing an error location.
StringRef InputFile::getNameForScript() const {
if (archiveName.empty())
@@ -571,134 +502,112 @@ handleAArch64BAAndGnuProperties(ObjFile<ELFT> *file, Ctx &ctx,
}
}
-template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
- object::ELFFile<ELFT> obj = this->getObj();
- // Read a section table. justSymbols is usually false.
- if (this->justSymbols) {
- initializeJustSymbols();
- initializeSymbols(obj);
- return;
- }
-
- // Handle dependent libraries and selection of section groups as these are not
- // done in parallel.
+template <class ELFT>
+void ObjFile<ELFT>::scanEarlySections() {
ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>();
- StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this);
- uint64_t size = objSections.size();
- sections.resize(size);
- for (size_t i = 0; i != size; ++i) {
+ const llvm::object::ELFFile<ELFT> obj = getObj();
+ typename ELFT::SymRange eSyms = this->getELFSyms<ELFT>();
+ for (size_t i = 0, size = objSections.size(); i != size; ++i) {
const Elf_Shdr &sec = objSections[i];
-
if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
continue;
- if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
- StringRef signature = getShtGroupSignature(objSections, sec);
- ArrayRef<Elf_Word> entries =
- CHECK2(obj.template getSectionContentsAsArray<Elf_Word>(sec), this);
- if (entries.empty())
- Fatal(ctx) << this << ": empty SHT_GROUP";
-
- Elf_Word flag = entries[0];
- if (flag && flag != GRP_COMDAT)
- Fatal(ctx) << this << ": unsupported SHT_GROUP format";
-
- bool keepGroup = !flag || ignoreComdats ||
- ctx.symtab->comdatGroups
- .try_emplace(CachedHashStringRef(signature), this)
- .second;
- if (keepGroup) {
- keptGroups.push_back(i);
- if (!ctx.arg.resolveGroups)
- sections[i] = createInputSection(
- i, sec, check(obj.getSectionName(sec, shstrtab)));
- } else {
- // Otherwise, discard group members.
- for (uint32_t secIndex : entries.slice(1)) {
- if (secIndex >= size)
- Fatal(ctx) << this
- << ": invalid section index in group: " << secIndex;
- sections[secIndex] = &InputSection::discarded;
- }
- }
+ if (sec.sh_type == SHT_GROUP) {
+ // Tolerantly decode the group; initializeSections diagnoses. The
+ // signature name (a strlen and a hash) is deferred to the caller, which
+ // reuses the symbol records; validate st_name so it cannot fail there.
+ Expected<ArrayRef<Elf_Word>> entries =
+ obj.template getSectionContentsAsArray<Elf_Word>(sec);
+ uint32_t sigSym = UINT32_MAX;
+ if (!entries)
+ consumeError(entries.takeError());
+ else if (!entries->empty() && (*entries)[0] == Elf_Word(GRP_COMDAT) &&
+ sec.sh_info < eSyms.size() &&
+ eSyms[sec.sh_info].st_name < stringTable.size())
+ sigSym = sec.sh_info;
+ comdatSecs.push_back({(uint32_t)i, sigSym});
continue;
}
+ if ((sec.sh_type == SHT_LLVM_DEPENDENT_LIBRARIES && !ctx.arg.relocatable) ||
+ (sec.sh_type == SHT_ARM_ATTRIBUTES && ctx.arg.emachine == EM_ARM))
+ needsSerialScan = true;
+ }
+}
+template <class ELFT>
+void ObjFile<ELFT>::processEarlySections() {
+ if (!needsSerialScan)
+ return;
+ object::ELFFile<ELFT> obj = this->getObj();
+ ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>();
+ StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this);
+ for (auto [i, sec] : llvm::enumerate(objSections)) {
if (sec.sh_type == SHT_LLVM_DEPENDENT_LIBRARIES && !ctx.arg.relocatable) {
StringRef name = check(obj.getSectionName(sec, shstrtab));
- ArrayRef<char> data = CHECK2(
- this->getObj().template getSectionContentsAsArray<char>(sec), this);
+ ArrayRef<char> data =
+ CHECK2(obj.template getSectionContentsAsArray<char>(sec), this);
if (!data.empty() && data.back() != '\0') {
Err(ctx)
<< this
<< ": corrupted dependent libraries section (unterminated string): "
<< name;
- } else {
- for (const char *d = data.begin(), *e = data.end(); d < e;) {
- StringRef s(d);
- addDependentLibrary(ctx, s, this);
- d += s.size() + 1;
- }
+ continue;
+ }
+ for (const char *d = data.begin(), *e = data.end(); d < e;) {
+ StringRef s(d);
+ addDependentLibrary(ctx, s, this);
+ d += s.size() + 1;
}
- sections[i] = &InputSection::discarded;
continue;
}
+ if (sec.sh_type != SHT_ARM_ATTRIBUTES || ctx.arg.emachine != EM_ARM)
+ continue;
+ ARMAttributeParser attributes;
+ ArrayRef<uint8_t> contents = check(obj.getSectionContents(sec));
+ StringRef name = check(obj.getSectionName(sec, shstrtab));
+ if (Error e = attributes.parse(contents, ekind == ELF32LEKind
+ ? llvm::endianness::little
+ : llvm::endianness::big)) {
+ InputSection isec(*this, sec, name);
+ Warn(ctx) << &isec << ": " << std::move(e);
+ } else {
+ updateSupportedARMFeatures(ctx, attributes);
+ updateARMVFPArgs(ctx, attributes, this);
- switch (ctx.arg.emachine) {
- case EM_ARM:
- if (sec.sh_type == SHT_ARM_ATTRIBUTES) {
- ARMAttributeParser attributes;
- ArrayRef<uint8_t> contents =
- check(this->getObj().getSectionContents(sec));
- StringRef name = check(obj.getSectionName(sec, shstrtab));
- sections[i] = &InputSection::discarded;
- if (Error e = attributes.parse(contents, ekind == ELF32LEKind
- ? llvm::endianness::little
- : llvm::endianness::big)) {
- InputSection isec(*this, sec, name);
- Warn(ctx) << &isec << ": " << std::move(e);
- } else {
- updateSupportedARMFeatures(ctx, attributes);
- updateARMVFPArgs(ctx, attributes, this);
-
- // FIXME: Retain the first attribute section we see. The eglibc ARM
- // dynamic loaders require the presence of an attribute section for
- // dlopen to work. In a full implementation we would merge all
- // attribute sections.
- if (ctx.in.attributes == nullptr) {
- ctx.in.attributes =
- std::make_unique<InputSection>(*this, sec, name);
- sections[i] = ctx.in.attributes.get();
- }
- }
+ // FIXME: Retain the first attribute section we see. The eglibc ARM
+ // dynamic loaders require the presence of an attribute section for
+ // dlopen to work. In a full implementation we would merge all
+ // attribute sections.
+ if (ctx.in.attributes == nullptr) {
+ ctx.in.attributes = std::make_unique<InputSection>(*this, sec, name);
+ armAttrSecIdx = i;
}
- break;
- case EM_AARCH64:
- // Producing a static binary with MTE globals is not currently supported,
- // remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as they're unused
- // medatada, and we don't want them to end up in the output file for
- // static executables.
- if (sec.sh_type == SHT_AARCH64_MEMTAG_GLOBALS_STATIC &&
- !canHaveMemtagGlobals(ctx))
- sections[i] = &InputSection::discarded;
- break;
}
}
-
- // Read a symbol table.
- initializeSymbols(obj);
}
// Sections with SHT_GROUP and comdat bits define comdat section groups.
-// They are identified and deduplicated by group name. This function
-// returns a group name.
+// They are identified and deduplicated by group name. Decode a SHT_GROUP
+// section's signature name and entries. scanEarlySections consumes errors;
+// initializeSections diagnoses them.
template <class ELFT>
-StringRef ObjFile<ELFT>::getShtGroupSignature(ArrayRef<Elf_Shdr> sections,
- const Elf_Shdr &sec) {
- typename ELFT::SymRange symbols = this->getELFSyms<ELFT>();
- if (sec.sh_info >= symbols.size())
- Fatal(ctx) << this << ": invalid symbol index";
- const typename ELFT::Sym &sym = symbols[sec.sh_info];
- return CHECK2(sym.getName(this->stringTable), this);
+Expected<std::pair<StringRef, ArrayRef<typename ELFT::Word>>>
+ObjFile<ELFT>::getGroup(const Elf_Shdr &sec) {
+ typename ELFT::SymRange eSyms = this->getELFSyms<ELFT>();
+ if (sec.sh_info >= eSyms.size())
+ return createStringError("invalid symbol index");
+ Expected<StringRef> signature = eSyms[sec.sh_info].getName(stringTable);
+ if (!signature)
+ return signature.takeError();
+ Expected<ArrayRef<Elf_Word>> entries =
+ getObj().template getSectionContentsAsArray<Elf_Word>(sec);
+ if (!entries)
+ return entries.takeError();
+ if (entries->empty())
+ return createStringError("empty SHT_GROUP");
+ Elf_Word flag = (*entries)[0];
+ if (flag && flag != Elf_Word(GRP_COMDAT))
+ return createStringError("unsupported SHT_GROUP format");
+ return std::make_pair(*signature, *entries);
}
template <class ELFT>
@@ -772,9 +681,58 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>();
StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this);
uint64_t size = objSections.size();
+ this->sections.resize(size);
+
+ // First pass over the SHT_GROUP sections scanned by scanEarlySections:
+ // diagnose malformed groups and discard members of non-prevailing comdat
+ // groups. Comdat group ownership was registered by the parse pipeline
+ // (Pipeline::registerComdats). keptGroups memoizes the verdict for the main
+ // loop below (recomputing it there would re-decode and re-hash every
+ // signature). A decodable GRP_COMDAT group reuses the cached signature hash
+ // and re-reads only the entries.
+ SmallVector<std::pair<uint32_t, ArrayRef<Elf_Word>>, 0> keptGroups;
+ for (const ComdatSec &cs : comdatSecs) {
+ uint32_t i = cs.secIdx;
+ const Elf_Shdr &sec = objSections[i];
+ bool keepGroup;
+ ArrayRef<Elf_Word> entries;
+ if (cs.sigSym != UINT32_MAX) {
+ entries = cantFail(
+ this->getObj().template getSectionContentsAsArray<Elf_Word>(sec));
+ keepGroup = ignoreComdats || cs.prevailing;
+ } else {
+ // Malformed or non-COMDAT group: take the diagnosing path.
+ Expected<std::pair<StringRef, ArrayRef<Elf_Word>>> group = getGroup(sec);
+ if (!group) {
+ Err(ctx) << this << ": " << group.takeError();
+ this->sections[i] = &InputSection::discarded;
+ continue;
+ }
+ entries = group->second;
+ keepGroup = !entries[0] || ignoreComdats ||
+ ctx.symtab->findComdatGroup(
+ CachedHashStringRef(group->first)) == this;
+ }
+ if (keepGroup) {
+ keptGroups.push_back({i, entries});
+ if (!ctx.arg.resolveGroups)
+ this->sections[i] = createInputSection(
+ i, sec, check(obj.getSectionName(sec, shstrtab)));
+ continue;
+ }
+ // Otherwise, discard group members.
+ for (uint32_t secIndex : entries.slice(1)) {
+ if (secIndex >= size) {
+ Err(ctx) << this << ": invalid section index in group: " << secIndex;
+ continue;
+ }
+ this->sections[secIndex] = &InputSection::discarded;
+ }
+ }
+ comdatSecs = {};
+
SmallVector<ArrayRef<Elf_Word>, 0> selectedGroups;
- ArrayRef<uint32_t> keptGroups = this->keptGroups;
- size_t keptIdx = 0;
+ size_t keptGroupIdx = 0;
AArch64BuildAttrSubsections aarch64BAsubSections;
bool hasAArch64BuildAttributes = false;
for (size_t i = 0; i != size; ++i) {
@@ -828,17 +786,40 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
this->sections[i] = &InputSection::discarded;
continue;
}
+ if (type == SHT_ARM_ATTRIBUTES && ctx.arg.emachine == EM_ARM) {
+ // The retained attribute section (if this file provides it) was created
+ // by processEarlySections.
+ this->sections[i] = i == armAttrSecIdx
+ ? ctx.in.attributes.get()
+ : (InputSectionBase *)&InputSection::discarded;
+ continue;
+ }
+ // Producing a static binary with MTE globals is not currently supported,
+ // remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as they're unused
+ // medatada, and we don't want them to end up in the output file for
+ // static executables.
+ if (type == SHT_AARCH64_MEMTAG_GLOBALS_STATIC &&
+ ctx.arg.emachine == EM_AARCH64 && !canHaveMemtagGlobals(ctx)) {
+ this->sections[i] = &InputSection::discarded;
+ continue;
+ }
+ if (type == SHT_LLVM_DEPENDENT_LIBRARIES && !ctx.arg.relocatable) {
+ // The contents were processed by processEarlySections.
+ this->sections[i] = &InputSection::discarded;
+ continue;
+ }
switch (type) {
case SHT_GROUP: {
if (!ctx.arg.relocatable)
sections[i] = &InputSection::discarded;
- // Use the verdict parse() recorded for this group instead of repeating
- // the signature hashing and comdatGroups lookup.
- while (keptIdx != keptGroups.size() && keptGroups[keptIdx] < i)
- ++keptIdx;
- if (keptIdx != keptGroups.size() && keptGroups[keptIdx] == i)
- selectedGroups.push_back(
- cantFail(obj.template getSectionContentsAsArray<Elf_Word>(sec)));
+ // The verdict was computed by the first pass above. Kept groups may
+ // have been discarded since (e.g. as a member of another group).
+ while (keptGroupIdx != keptGroups.size() &&
+ keptGroups[keptGroupIdx].first < i)
+ ++keptGroupIdx;
+ if (keptGroupIdx != keptGroups.size() &&
+ keptGroups[keptGroupIdx].first == i)
+ selectedGroups.push_back(keptGroups[keptGroupIdx++].second);
break;
}
case SHT_SYMTAB_SHNDX:
@@ -1188,73 +1169,42 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(uint32_t idx,
return makeThreadLocal<InputSection>(*this, sec, name);
}
-// Initialize symbols. symbols is a parallel array to the corresponding ELF
-// symbol table.
+// Resolve a global symbol: issue the resolve() call for its definition,
+// COMMON, or undefined reference, with the per-symbol side effects of symbol
+// resolution. Called by the parallel parse pipeline.
template <class ELFT>
-void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
- ArrayRef<Elf_Sym> eSyms = this->getELFSyms<ELFT>();
- if (!symbols)
- symbols = std::make_unique<Symbol *[]>(numSymbols);
-
- // Some entries have been filled by LazyObjFile.
- auto *symtab = ctx.symtab.get();
- for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i)
- if (!symbols[i])
- symbols[i] = symtab->insert(CHECK2(eSyms[i].getName(stringTable), this));
-
- // Perform symbol resolution on non-local symbols.
- SmallVector<unsigned, 32> undefineds;
- for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
- const Elf_Sym &eSym = eSyms[i];
- uint32_t secIdx = eSym.st_shndx;
- if (secIdx == SHN_UNDEF) {
- undefineds.push_back(i);
- continue;
- }
-
- uint8_t binding = eSym.getBinding();
- uint8_t stOther = eSym.st_other;
- uint8_t type = eSym.getType();
+static void resolveSymbol(Ctx &ctx, ObjFile<ELFT> *f,
+ const typename ELFT::Sym &eSym, Symbol &sym) {
+ if (eSym.st_shndx == SHN_UNDEF) {
+ sym.resolve(ctx, Undefined{f, StringRef(), eSym.getBinding(), eSym.st_other,
+ eSym.getType()});
+ sym.isUsedInRegularObj = true;
+ sym.referenced = true;
+ return;
+ }
+ sym.isUsedInRegularObj = true;
+ if (LLVM_UNLIKELY(eSym.st_shndx == SHN_COMMON)) {
uint64_t value = eSym.st_value;
- uint64_t size = eSym.st_size;
-
- Symbol *sym = symbols[i];
- sym->isUsedInRegularObj = true;
- if (LLVM_UNLIKELY(eSym.st_shndx == SHN_COMMON)) {
- if (value == 0 || value >= UINT32_MAX)
- Err(ctx) << this << ": common symbol '" << sym->getName()
- << "' has invalid alignment: " << value;
- hasCommonSyms = true;
- sym->resolve(ctx, CommonSymbol{ctx, this, StringRef(), binding, stOther,
- type, value, size});
- continue;
- }
-
- // Handle global defined symbols. Defined::section will be set in postParse.
- sym->resolve(ctx, Defined{ctx, this, StringRef(), binding, stOther, type,
- value, size, nullptr});
- }
-
- // Undefined symbols (excluding those defined relative to non-prevailing
- // sections) can trigger recursive extract. Process defined symbols first so
- // that the relative order between a defined symbol and an undefined symbol
- // does not change the symbol resolution behavior. In addition, a set of
- // interconnected symbols will all be resolved to the same file, instead of
- // being resolved to different files.
- for (unsigned i : undefineds) {
- const Elf_Sym &eSym = eSyms[i];
- Symbol *sym = symbols[i];
- sym->resolve(ctx, Undefined{this, StringRef(), eSym.getBinding(),
- eSym.st_other, eSym.getType()});
- sym->isUsedInRegularObj = true;
- sym->referenced = true;
+ if (value == 0 || value >= UINT32_MAX)
+ Err(ctx) << f << ": common symbol '" << sym.getName()
+ << "' has invalid alignment: " << value;
+ sym.resolve(ctx, CommonSymbol{ctx, f, StringRef(), eSym.getBinding(),
+ eSym.st_other, eSym.getType(), value,
+ eSym.st_size});
+ return;
}
+ // Defined::section will be set in postParse.
+ sym.resolve(ctx,
+ Defined{ctx, f, StringRef(), eSym.getBinding(), eSym.st_other,
+ eSym.getType(), eSym.st_value, eSym.st_size, nullptr});
}
template <class ELFT>
void ObjFile<ELFT>::initSectionsAndLocalSyms(bool ignoreComdats) {
if (!justSymbols)
initializeSections(ignoreComdats, getObj());
+ else
+ initializeJustSymbols();
if (!firstGlobal)
return;
@@ -1365,73 +1315,6 @@ template <class ELFT> void ObjFile<ELFT>::postParse() {
}
}
-// The handling of tentative definitions (COMMON symbols) in archives is murky.
-// A tentative definition will be promoted to a global definition if there are
-// no non-tentative definitions to dominate it. When we hold a tentative
-// definition to a symbol and are inspecting archive members for inclusion
-// there are 2 ways we can proceed:
-//
-// 1) Consider the tentative definition a 'real' definition (ie promotion from
-// tentative to real definition has already happened) and not inspect
-// archive members for Global/Weak definitions to replace the tentative
-// definition. An archive member would only be included if it satisfies some
-// other undefined symbol. This is the behavior Gold uses.
-//
-// 2) Consider the tentative definition as still undefined (ie the promotion to
-// a real definition happens only after all symbol resolution is done).
-// The linker searches archive members for STB_GLOBAL definitions to
-// replace the tentative definition with. This is the behavior used by
-// GNU ld.
-//
-// The second behavior is inherited from SysVR4, which based it on the FORTRAN
-// COMMON BLOCK model. This behavior is needed for proper initialization in old
-// (pre F90) FORTRAN code that is packaged into an archive.
-//
-// The following functions search archive members for definitions to replace
-// tentative definitions (implementing behavior 2).
-static bool isBitcodeNonCommonDef(MemoryBufferRef mb, StringRef symName,
- StringRef archiveName) {
- IRSymtabFile symtabFile = check(readIRSymtab(mb));
- for (const irsymtab::Reader::SymbolRef &sym :
- symtabFile.TheReader.symbols()) {
- if (sym.isGlobal() && sym.getName() == symName)
- return !sym.isUndefined() && !sym.isWeak() && !sym.isCommon();
- }
- return false;
-}
-
-template <class ELFT>
-static bool isNonCommonDef(Ctx &ctx, ELFKind ekind, MemoryBufferRef mb,
- StringRef symName, StringRef archiveName) {
- ObjFile<ELFT> *obj = make<ObjFile<ELFT>>(ctx, ekind, mb, archiveName);
- obj->init();
- StringRef stringtable = obj->getStringTable();
-
- for (auto sym : obj->template getGlobalELFSyms<ELFT>()) {
- Expected<StringRef> name = sym.getName(stringtable);
- if (name && name.get() == symName)
- return sym.isDefined() && sym.getBinding() == STB_GLOBAL &&
- !sym.isCommon();
- }
- return false;
-}
-
-static bool isNonCommonDef(Ctx &ctx, MemoryBufferRef mb, StringRef symName,
- StringRef archiveName) {
- switch (getELFKind(ctx, mb, archiveName)) {
- case ELF32LEKind:
- return isNonCommonDef<ELF32LE>(ctx, ELF32LEKind, mb, symName, archiveName);
- case ELF32BEKind:
- return isNonCommonDef<ELF32BE>(ctx, ELF32BEKind, mb, symName, archiveName);
- case ELF64LEKind:
- return isNonCommonDef<ELF64LE>(ctx, ELF64LEKind, mb, symName, archiveName);
- case ELF64BEKind:
- return isNonCommonDef<ELF64BE>(ctx, ELF64BEKind, mb, symName, archiveName);
- default:
- llvm_unreachable("getELFKind");
- }
-}
-
SharedFile::SharedFile(Ctx &ctx, MemoryBufferRef m, StringRef defaultSoName)
: ELFFileBase(ctx, SharedKind, getELFKind(ctx, m, ""), m),
soName(defaultSoName), isNeeded(!ctx.arg.asNeeded) {}
@@ -1537,203 +1420,27 @@ static uint64_t getAlignment(ArrayRef<typename ELFT::Shdr> sections,
return (ret > UINT32_MAX) ? 0 : ret;
}
-// Fully parse the shared object file.
-//
-// This function parses symbol versions. If a DSO has version information,
-// the file has a ".gnu.version_d" section which contains symbol version
-// definitions. Each symbol is associated to one version through a table in
-// ".gnu.version" section. That table is a parallel array for the symbol
-// table, and each table entry contains an index in ".gnu.version_d".
-//
-// The special index 0 is reserved for VERF_NDX_LOCAL and 1 is for
-// VER_NDX_GLOBAL. There's no table entry for these special versions in
-// ".gnu.version_d".
-//
-// The file format for symbol versioning is perhaps a bit more complicated
-// than necessary, but you can easily understand the code if you wrap your
-// head around the data structure described above.
-template <class ELFT> void SharedFile::parse() {
- using Elf_Dyn = typename ELFT::Dyn;
- using Elf_Shdr = typename ELFT::Shdr;
- using Elf_Sym = typename ELFT::Sym;
- using Elf_Verdef = typename ELFT::Verdef;
- using Elf_Versym = typename ELFT::Versym;
-
- ArrayRef<Elf_Dyn> dynamicTags;
- const ELFFile<ELFT> obj = this->getObj<ELFT>();
- ArrayRef<Elf_Shdr> sections = getELFShdrs<ELFT>();
-
- const Elf_Shdr *versymSec = nullptr;
- const Elf_Shdr *verdefSec = nullptr;
- const Elf_Shdr *verneedSec = nullptr;
- symbols = std::make_unique<Symbol *[]>(numSymbols);
-
- // Search for .dynsym, .dynamic, .symtab, .gnu.version and .gnu.version_d.
- for (const Elf_Shdr &sec : sections) {
- switch (sec.sh_type) {
- default:
- continue;
- case SHT_DYNAMIC:
- dynamicTags =
- CHECK2(obj.template getSectionContentsAsArray<Elf_Dyn>(sec), this);
- break;
- case SHT_GNU_versym:
- versymSec = &sec;
- break;
- case SHT_GNU_verdef:
- verdefSec = &sec;
- break;
- case SHT_GNU_verneed:
- verneedSec = &sec;
- break;
- }
- }
-
- if (versymSec && numSymbols == 0) {
- ErrAlways(ctx) << "SHT_GNU_versym should be associated with symbol table";
- return;
- }
-
- // Search for a DT_SONAME tag to initialize this->soName.
- for (const Elf_Dyn &dyn : dynamicTags) {
- if (dyn.d_tag == DT_NEEDED) {
- uint64_t val = dyn.getVal();
- if (val >= this->stringTable.size()) {
- Err(ctx) << this << ": invalid DT_NEEDED entry";
- return;
- }
- dtNeeded.push_back(this->stringTable.data() + val);
- } else if (dyn.d_tag == DT_SONAME) {
- uint64_t val = dyn.getVal();
- if (val >= this->stringTable.size()) {
- Err(ctx) << this << ": invalid DT_SONAME entry";
- return;
- }
- soName = this->stringTable.data() + val;
- }
- }
-
- // DSOs are uniquified not by filename but by soname.
- StringSaver &ss = ctx.saver;
- DenseMap<CachedHashStringRef, SharedFile *>::iterator it;
- bool wasInserted;
- std::tie(it, wasInserted) =
- ctx.symtab->soNames.try_emplace(CachedHashStringRef(soName), this);
-
- // If a DSO appears more than once on the command line with and without
- // --as-needed, --no-as-needed takes precedence over --as-needed because a
- // user can add an extra DSO with --no-as-needed to force it to be added to
- // the dependency list.
- if (isNeeded)
- it->second->isNeeded.store(true, std::memory_order_relaxed);
- if (!wasInserted)
- return;
-
- ctx.sharedFiles.push_back(this);
-
- verdefs = parseVerdefs<ELFT>(obj.base(), verdefSec);
- std::vector<uint32_t> verneeds = parseVerneed<ELFT>(obj, verneedSec);
- parseGnuAndFeatures<ELFT>(obj);
-
- // Parse ".gnu.version" section which is a parallel array for the symbol
- // table. If a given file doesn't have a ".gnu.version" section, we use
- // VER_NDX_GLOBAL.
- size_t size = numSymbols - firstGlobal;
- std::vector<uint16_t> versyms(size, VER_NDX_GLOBAL);
- if (versymSec) {
- ArrayRef<Elf_Versym> versym =
- CHECK2(obj.template getSectionContentsAsArray<Elf_Versym>(*versymSec),
- this)
- .slice(firstGlobal);
- for (size_t i = 0; i < size; ++i)
- versyms[i] = versym[i].vs_index;
- }
-
- // System libraries can have a lot of symbols with versions. Using a
- // fixed buffer for computing the versions name (foo@ver) can save a
- // lot of allocations.
- SmallString<0> versionedNameBuffer;
-
- // Add symbols to the symbol table.
- ArrayRef<Elf_Sym> syms = this->getGlobalELFSyms<ELFT>();
- for (size_t i = 0, e = syms.size(); i != e; ++i) {
- const Elf_Sym &sym = syms[i];
-
- // ELF spec requires that all local symbols precede weak or global
- // symbols in each symbol table, and the index of first non-local symbol
- // is stored to sh_info. If a local symbol appears after some non-local
- // symbol, that's a violation of the spec.
- StringRef name = CHECK2(sym.getName(stringTable), this);
- if (sym.getBinding() == STB_LOCAL) {
- Err(ctx) << this << ": invalid local symbol '" << name
- << "' in global part of symbol table";
- continue;
- }
-
- const uint16_t ver = versyms[i], idx = ver & ~VERSYM_HIDDEN;
- if (sym.isUndefined()) {
- // Index 0 (VER_NDX_LOCAL) is used for unversioned undefined symbols.
- // GNU ld versions between 2.35 and 2.45 also generate VER_NDX_GLOBAL
- // for this case (https://sourceware.org/PR33577).
- if (ver != VER_NDX_LOCAL && ver != VER_NDX_GLOBAL) {
- if (idx >= verneeds.size()) {
- ErrAlways(ctx) << "corrupt input file: version need index " << idx
- << " for symbol " << name
- << " is out of bounds\n>>> defined in " << this;
- continue;
- }
- StringRef verName = stringTable.data() + verneeds[idx];
- versionedNameBuffer.clear();
- name = ss.save((name + "@" + verName).toStringRef(versionedNameBuffer));
- }
- Symbol *s = ctx.symtab->addSymbol(
- Undefined{this, name, sym.getBinding(), sym.st_other, sym.getType()});
- s->isExported = true;
- if (sym.getBinding() != STB_WEAK &&
- ctx.arg.unresolvedSymbolsInShlib != UnresolvedPolicy::Ignore)
- requiredSymbols.push_back(s);
- continue;
- }
-
- if (ver == VER_NDX_LOCAL ||
- (ver != VER_NDX_GLOBAL && idx >= verdefs.size())) {
- // In GNU ld < 2.31 (before 3be08ea4728b56d35e136af4e6fd3086ade17764), the
- // MIPS port puts _gp_disp symbol into DSO files and incorrectly assigns
- // VER_NDX_LOCAL. Workaround this bug.
- if (ctx.arg.emachine == EM_MIPS && name == "_gp_disp")
- continue;
- ErrAlways(ctx) << "corrupt input file: version definition index " << idx
- << " for symbol " << name
- << " is out of bounds\n>>> defined in " << this;
- continue;
- }
-
- uint32_t alignment = getAlignment<ELFT>(sections, sym);
- if (ver == idx) {
- auto *s = ctx.symtab->addSymbol(
- SharedSymbol{*this, name, sym.getBinding(), sym.st_other,
- sym.getType(), sym.st_value, sym.st_size, alignment});
- s->dsoDefined = true;
- if (s->file == this)
- s->versionId = ver;
- }
-
- // Also add the symbol with the versioned name to handle undefined symbols
- // with explicit versions.
- if (ver == VER_NDX_GLOBAL)
- continue;
-
- StringRef verName =
- stringTable.data() +
- reinterpret_cast<const Elf_Verdef *>(verdefs[idx])->getAux()->vda_name;
- versionedNameBuffer.clear();
- name = (name + "@" + verName).toStringRef(versionedNameBuffer);
- auto *s = ctx.symtab->addSymbol(
- SharedSymbol{*this, ss.save(name), sym.getBinding(), sym.st_other,
- sym.getType(), sym.st_value, sym.st_size, alignment});
- s->dsoDefined = true;
- if (s->file == this)
- s->versionId = idx;
+// Resolve one dynsym entry of a shared file into sym, mirroring the per-symbol
+// body of SharedFile::parse. Shared by the parallel resolution and the -y
+// traced replay. sym already holds the (possibly versioned) name.
+template <class ELFT>
+static void resolveSharedSymbol(Ctx &ctx, Symbol &sym, SharedFile &sf,
+ uint32_t elfIdx, bool isDef,
+ uint16_t versionId) {
+ const typename ELFT::Sym &eSym = sf.template getELFSyms<ELFT>()[elfIdx];
+ if (!isDef) {
+ sym.resolve(ctx, Undefined{&sf, sym.getName(), eSym.getBinding(),
+ eSym.st_other, eSym.getType()});
+ sym.isExported = true;
+ } else {
+ uint32_t alignment =
+ getAlignment<ELFT>(sf.template getELFShdrs<ELFT>(), eSym);
+ sym.resolve(ctx, SharedSymbol{sf, sym.getName(), eSym.getBinding(),
+ eSym.st_other, eSym.getType(), eSym.st_value,
+ eSym.st_size, alignment});
+ sym.dsoDefined = true;
+ if (sym.file == &sf)
+ sym.versionId = versionId;
}
}
@@ -1894,47 +1601,13 @@ static void createBitcodeSymbol(Ctx &ctx, Symbol *&sym,
}
}
-void BitcodeFile::parse() {
- for (std::pair<StringRef, Comdat::SelectionKind> s : obj->getComdatTable()) {
+// addComdatGroup is owner-idempotent: the parallel parse pipeline may have
+// already registered this file as the owner.
+void BitcodeFile::parseComdats() {
+ for (std::pair<StringRef, Comdat::SelectionKind> s : obj->getComdatTable())
keptComdats.push_back(
s.second == Comdat::NoDeduplicate ||
- ctx.symtab->comdatGroups.try_emplace(CachedHashStringRef(s.first), this)
- .second);
- }
-
- if (numSymbols == 0) {
- numSymbols = obj->symbols().size();
- symbols = std::make_unique<Symbol *[]>(numSymbols);
- }
- // Process defined symbols first. See the comment in
- // ObjFile<ELFT>::initializeSymbols.
- for (auto [i, irSym] : llvm::enumerate(obj->symbols()))
- if (!irSym.isUndefined())
- createBitcodeSymbol(ctx, symbols[i], irSym, *this);
- for (auto [i, irSym] : llvm::enumerate(obj->symbols()))
- if (irSym.isUndefined())
- createBitcodeSymbol(ctx, symbols[i], irSym, *this);
-
- for (auto l : obj->getDependentLibraries())
- addDependentLibrary(ctx, l, this);
-}
-
-void BitcodeFile::parseLazy() {
- numSymbols = obj->symbols().size();
- symbols = std::make_unique<Symbol *[]>(numSymbols);
- for (auto [i, irSym] : llvm::enumerate(obj->symbols())) {
- // Symbols can be duplicated in bitcode files because of '#include' and
- // linkonce_odr. Use uniqueSaver to save symbol names for de-duplication.
- // Update objSym.Name to reference (via StringRef) the string saver's copy;
- // this way LTO can reference the same string saver's copy rather than
- // keeping copies of its own.
- irSym.Name = ctx.uniqueSaver.save(irSym.getName());
- if (!irSym.isUndefined()) {
- auto *sym = ctx.symtab->insert(irSym.getName());
- sym->resolve(ctx, LazySymbol{*this});
- symbols[i] = sym;
- }
- }
+ ctx.symtab->addComdatGroup(CachedHashStringRef(s.first), this) == this);
}
void BitcodeFile::postParse() {
@@ -1981,9 +1654,6 @@ void BinaryFile::parse() {
InputFile *elf::createInternalFile(Ctx &ctx, StringRef name) {
auto *file =
make<InputFile>(ctx, InputFile::InternalKind, MemoryBufferRef("", name));
- // References from an internal file do not lead to --warn-backrefs
- // diagnostics.
- file->groupId = 0;
return file;
}
@@ -2012,32 +1682,6 @@ std::unique_ptr<ELFFileBase> elf::createObjFile(Ctx &ctx, MemoryBufferRef mb,
return f;
}
-template <class ELFT> void ObjFile<ELFT>::parseLazy() {
- const ArrayRef<typename ELFT::Sym> eSyms = this->getELFSyms<ELFT>();
- numSymbols = eSyms.size();
- symbols = std::make_unique<Symbol *[]>(numSymbols);
-
- // resolve() may trigger this->extract() if an existing symbol is an undefined
- // symbol. If that happens, this function has served its purpose, and we can
- // exit from the loop early.
- auto *symtab = ctx.symtab.get();
- for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
- if (eSyms[i].st_shndx == SHN_UNDEF)
- continue;
- symbols[i] = symtab->insert(CHECK2(eSyms[i].getName(stringTable), this));
- symbols[i]->resolve(ctx, LazySymbol{*this});
- if (!lazy)
- break;
- }
-}
-
-bool InputFile::shouldExtractForCommon(StringRef name) const {
- if (isa<BitcodeFile>(this))
- return isBitcodeNonCommonDef(mb, name, archiveName);
-
- return isNonCommonDef(ctx, mb, name, archiveName);
-}
-
std::string elf::replaceThinLTOSuffix(Ctx &ctx, StringRef path) {
auto [suffix, repl] = ctx.arg.thinLTOObjectSuffixReplace;
if (path.consume_back(suffix))
@@ -2045,12 +1689,1248 @@ std::string elf::replaceThinLTOSuffix(Ctx &ctx, StringRef path) {
return std::string(path);
}
+//===----------------------------------------------------------------------===//
+// Parallel input file parsing and symbol resolution pipeline.
+//
+// Pipeline::run drives one batch of input files: POD symbol records are read
+// per file in parallel and counting-sorted into hash buckets, archive members
+// are extracted to a fixpoint (order-independent, mold/wild-style), and
+// symbols are created and resolved per bucket, then ordered by their serial
+// insertion point so that the output .symtab matches the serial linker.
+// Pipeline::epilogue runs the order-sensitive side effects serially.
+//
+//===----------------------------------------------------------------------===//
+
+namespace {
+constexpr uint32_t numShards = SymbolTable::numShards;
+
+// SymRecord flags.
+enum : uint8_t {
+ FDef = 1, // defined, including COMMON
+ FWeak = 2, // STB_WEAK
+ FBitcode = 4, // from a bitcode file
+ FShared = 8, // from a shared file
+ FCommon = 16, // COMMON definition
+ FHasAt = 32, // the name contains '@'
+};
+
+struct SymRecord {
+ const char *name;
+ uint32_t stemLen; // bucket key length (name minus a @@ suffix)
+ uint32_t nameLen; // full name length
+ uint32_t hash; // DenseMap hash of the stem
+ uint32_t nameId; // index into Bucket::names; written in phase 2
+ uint32_t elfIdx; // symbol index within the file's symbol/IR table
+ uint16_t versionId; // shared symbol version (FShared records only)
+ uint8_t flags;
+
+ StringRef stem() const { return StringRef(name, stemLen); }
+};
+
+// POD mirror of CachedHashStringRef (default-constructible).
+struct CachedName {
+ const char *data;
+ uint32_t size;
+ uint32_t hash;
+ // Index into the source file's comdatSecs (comdat signatures only).
+ uint32_t srcIdx = 0;
+ CachedHashStringRef ref() const {
+ return CachedHashStringRef(StringRef(data, size), hash);
+ }
+};
+
+struct FileData {
+ SmallVector<SymRecord, 0> records; // stably bucketed by hash % numShards
+ uint32_t bucketStart[numShards + 1] = {};
+ // GRP_COMDAT signatures, bucketed by hash; section order within a bucket.
+ SmallVector<CachedName, 0> comdats;
+ uint32_t comdatStart[numShards + 1] = {};
+ bool eligible = false; // participates in the pipeline
+ bool compatible = false; // passed the compatibility check
+ bool dupSoname = false; // DSO whose soname is already registered
+};
+
+// A node of a per-name singly-linked chain. Each name has two disjoint
+// chains, definitions and undefined references, so one link field suffices.
+struct RefNode {
+ uint32_t fileIdx;
+ uint32_t recIdx;
+ uint32_t next = UINT32_MAX;
+};
+
+struct NameInfo {
+ // The last @@-versioned spelling; serial insert() renames on each such
+ // insertion.
+ const char *verName = nullptr;
+ Symbol *sym = nullptr;
+ // Output order key: the earliest resolution event; seeds use (0, seedIdx).
+ // UINT32_MAX rank means no file inserted the name, so it is dropped.
+ uint64_t anchorSub = UINT64_MAX;
+ uint32_t anchorRank = UINT32_MAX;
+ uint32_t firstUndef = UINT32_MAX, lastUndef = UINT32_MAX;
+ uint32_t firstDef = UINT32_MAX, lastDef = UINT32_MAX;
+ uint32_t verNameLen = 0;
+ uint32_t seedIdx = UINT32_MAX; // pre-parseFiles symVector index
+ uint32_t outIdx = UINT32_MAX; // final symVector index
+};
+
+struct Bucket {
+ DenseMap<CachedHashStringRef, int> map;
+ SmallVector<NameInfo, 0> names;
+ SmallVector<RefNode, 0> refs;
+};
+
+// A symbol's serial insertion point, ordering the installed symbol vector.
+struct OrderItem {
+ uint32_t ord;
+ uint64_t sub;
+ uint32_t bucket, nameId;
+ bool operator<(const OrderItem &o) const {
+ return std::tie(ord, sub, bucket, nameId) <
+ std::tie(o.ord, o.sub, o.bucket, o.nameId);
+ }
+};
+
+template <class ELFT> struct Pipeline {
+ Ctx &ctx;
+ SmallVector<InputFile *, 0> files; // the batch to process
+ SmallVector<FileData, 0> fd;
+ std::array<Bucket, numShards> buckets;
+ // Extractions in command-line order, for --why-extract.
+ struct Extraction {
+ uint32_t member, trigFile, bucket, nameId;
+ };
+ SmallVector<Extraction, 0> extractions;
+ uint32_t bucketBase[numShards + 1]; // global name id = base[bucket] + nameId
+ size_t firstObjFile = 0; // this batch's start in ctx.objectFiles
+ // A late batch (dependent libraries, LTO outputs, reactivate) extends the
+ // installed symbol table in place.
+ bool incremental;
+ // LTO outputs are parsed with ignoreComdats: their comdat groups were already
+ // resolved before LTO and must not be re-registered.
+ bool ignoreComdats;
+ // Reactivate: lazy symbols whose members should be extracted. activate seeds
+ // these (only) as pending references, so their members are pulled in.
+ ArrayRef<Symbol *> triggers;
+
+ Pipeline(Ctx &ctx, SmallVector<InputFile *, 0> files, bool incremental,
+ ArrayRef<Symbol *> triggers = {}, bool ignoreComdats = false)
+ : ctx(ctx), files(std::move(files)), incremental(incremental),
+ ignoreComdats(ignoreComdats), triggers(triggers) {}
+
+ void run();
+ void readSymbols();
+ void readObj(uint32_t i);
+ void readShared(uint32_t i);
+ void readBitcode(uint32_t i);
+ void buildNameDB();
+ void activate();
+ void registerComdats();
+ void resolveSymbols();
+ void resolveName(Bucket &b, uint32_t nameId);
+ void applyRecord(Symbol *sym, InputFile *file, const SymRecord &rec,
+ bool isDef);
+ void replayTraced(InputFile *file, const FileData &d, bool phaseSplit,
+ bool defsOnly);
+
+ // The resolved symbol for a record, located via its hash bucket and name id.
+ Symbol *symOf(const SymRecord &rec) {
+ return buckets[rec.hash % numShards].names[rec.nameId].sym;
+ }
+
+ // Any record of the name (all records of a name share the stem and hash).
+ const SymRecord &recOf(const Bucket &bu, const NameInfo &ni) const {
+ const RefNode &node =
+ bu.refs[ni.firstDef != UINT32_MAX ? ni.firstDef : ni.firstUndef];
+ return fd[node.fileIdx].records[node.recIdx];
+ }
+
+ void wireSymbols();
+ void recordExtractions();
+ void epilogue();
+ void initSections();
+
+ void addRecord(SmallVectorImpl<SymRecord> &tmp, StringRef name,
+ uint32_t elfIdx, uint8_t flags, uint16_t versionId = 0) {
+ SymRecord r;
+ r.name = name.data();
+ r.nameLen = name.size();
+ auto [stemLen, hasAt] = getSymbolStem(name);
+ r.stemLen = stemLen;
+ if (hasAt)
+ flags |= FHasAt;
+ r.hash = CachedHashStringRef(StringRef(r.name, r.stemLen)).hash();
+ r.nameId = UINT32_MAX;
+ r.elfIdx = elfIdx;
+ r.versionId = versionId;
+ r.flags = flags;
+ tmp.push_back(r);
+ }
+};
+
+// Stable counting sort into numShards buckets by hash, recording the bucket
+// boundaries in start[].
+template <class T, class HashFn>
+static void bucketSort(SmallVectorImpl<T> &dst, ArrayRef<T> tmp,
+ uint32_t (&start)[numShards + 1], HashFn hash) {
+ uint32_t count[numShards] = {};
+ for (const T &r : tmp)
+ ++count[hash(r) % numShards];
+ uint32_t sum = 0;
+ for (uint32_t i = 0; i != numShards; ++i) {
+ start[i] = sum;
+ sum += count[i];
+ }
+ start[numShards] = sum;
+ uint32_t cursor[numShards];
+ memcpy(cursor, start, sizeof(cursor));
+ dst.resize_for_overwrite(tmp.size());
+ for (const T &r : tmp)
+ dst[cursor[hash(r) % numShards]++] = r;
+}
+
+// Group the records by hash bucket for the parallel phases.
+static void bucketize(FileData &d, ArrayRef<SymRecord> tmp) {
+ bucketSort(d.records, tmp, d.bucketStart,
+ [](const SymRecord &r) { return r.hash; });
+}
+
+static void bucketizeComdats(FileData &d, ArrayRef<CachedName> tmp) {
+ bucketSort(d.comdats, tmp, d.comdatStart,
+ [](const CachedName &s) { return s.hash; });
+}
+
+// Record indices in symbol table order, which the bucketing loses. Only the
+// serial order-sensitive passes need it.
+static SmallVector<uint32_t, 0> symbolOrder(const FileData &d) {
+ SmallVector<uint32_t, 0> order;
+ order.resize_for_overwrite(d.records.size());
+ std::iota(order.begin(), order.end(), 0u);
+ llvm::stable_sort(order, [&d](uint32_t a, uint32_t b) {
+ const SymRecord &x = d.records[a], &y = d.records[b];
+ // A shared file's default-versioned definition emits two records at one
+ // symbol index; the unversioned name is inserted first.
+ return std::make_pair(x.elfIdx, x.flags & FHasAt) <
+ std::make_pair(y.elfIdx, y.flags & FHasAt);
+ });
+ return order;
+}
+
+} // namespace
+
+void elf::parallelForLPT(size_t numItems,
+ llvm::function_ref<uint64_t(uint32_t)> cost,
+ llvm::function_ref<void(uint32_t)> fn) {
+ SmallVector<std::pair<uint64_t, uint32_t>, 0> order;
+ order.resize_for_overwrite(numItems);
+ for (uint32_t i = 0; i != numItems; ++i)
+ order[i] = {cost(i), i};
+ llvm::stable_sort(
+ order, [](const auto &a, const auto &b) { return a.first > b.first; });
+ std::atomic<size_t> next{0};
+ auto worker = [&]() {
+ for (size_t i;
+ (i = next.fetch_add(1, std::memory_order_relaxed)) < numItems;)
+ fn(order[i].second);
+ };
+ parallel::TaskGroup tg;
+ for (size_t i = 0, e = std::min<size_t>(numItems, parallel::getThreadCount());
+ i != e; ++i)
+ tg.spawn(worker);
+}
+
+template <class ELFT> void Pipeline<ELFT>::readSymbols() {
+ fd.resize(files.size());
+
+ // Diagnose incompatible files in command-line order. Bitcode symbol names
+ // are saved here because the string savers are not thread-safe.
+ InputFile *firstObj = nullptr, *firstShared = nullptr, *firstBc = nullptr;
+ for (auto [i, f] : llvm::enumerate(files)) {
+ InputFile *first = firstObj ? firstObj : firstShared;
+ fd[i].compatible = isCompatible(ctx, f, first ? first : firstBc);
+ if (!fd[i].compatible)
+ continue;
+ if (auto *bf = dyn_cast<BitcodeFile>(f)) {
+ for (const lto::InputFile::Symbol &irSym : bf->obj->symbols())
+ irSym.Name = ctx.uniqueSaver.save(irSym.getName());
+ fd[i].eligible = true;
+ } else if (isa<SharedFile>(f) || f->kind() == InputFile::ObjKind) {
+ fd[i].eligible = true;
+ }
+ if (f->lazy)
+ continue;
+ if (f->kind() == InputFile::ObjKind) {
+ if (!firstObj)
+ firstObj = f;
+ } else if (f->kind() == InputFile::SharedKind) {
+ if (!firstShared)
+ firstShared = f;
+ } else if (f->kind() == InputFile::BitcodeKind) {
+ if (!firstBc)
+ firstBc = f;
+ }
+ }
+
+ // Largest symbol tables first: reading a big file last leaves the other
+ // workers idle for its whole duration.
+ auto cost = [&](uint32_t i) -> uint64_t {
+ if (!fd[i].eligible)
+ return 0;
+ if (auto *bf = dyn_cast<BitcodeFile>(files[i]))
+ return bf->obj->symbols().size();
+ return cast<ELFFileBase>(files[i])->template getELFSyms<ELFT>().size();
+ };
+ parallelForLPT(files.size(), cost, [&](uint32_t i) {
+ if (!fd[i].eligible)
+ return;
+ switch (files[i]->kind()) {
+ case InputFile::ObjKind:
+ readObj(i);
+ break;
+ case InputFile::SharedKind:
+ readShared(i);
+ break;
+ case InputFile::BitcodeKind:
+ readBitcode(i);
+ break;
+ default:
+ llvm_unreachable("unexpected file kind");
+ }
+ });
+
+ // DSOs are uniquified by soname; a duplicate only merges isNeeded into the
+ // canonical file. Registering here lets the later phases skip its records.
+ for (auto [i, f] : llvm::enumerate(files)) {
+ auto *sf = dyn_cast<SharedFile>(f);
+ if (!sf || !fd[i].eligible)
+ continue;
+ auto [it, inserted] =
+ ctx.symtab->soNames.try_emplace(CachedHashStringRef(sf->soName), sf);
+ if (sf->isNeeded)
+ it->second->isNeeded.store(true, std::memory_order_relaxed);
+ if (inserted)
+ continue;
+ fd[i].dupSoname = true;
+ fd[i].records.clear();
+ memset(fd[i].bucketStart, 0, sizeof(fd[i].bucketStart));
+ }
+}
+
+template <class ELFT> void Pipeline<ELFT>::readObj(uint32_t i) {
+ auto *f = cast<ObjFile<ELFT>>(files[i]);
+ ArrayRef<typename ELFT::Sym> eSyms = f->template getELFSyms<ELFT>();
+ uint32_t firstGlobal = f->firstGlobal;
+ StringRef strtab = f->getStringTable();
+ SmallVector<SymRecord, 0> tmp;
+ tmp.reserve(eSyms.size() - firstGlobal);
+ for (size_t j = firstGlobal, e = eSyms.size(); j != e; ++j) {
+ const typename ELFT::Sym &eSym = eSyms[j];
+ Expected<StringRef> name = eSym.getName(strtab);
+ if (!name) {
+ Err(ctx) << f << ": " << name.takeError();
+ break;
+ }
+ uint8_t flags = 0;
+ if (eSym.st_shndx != SHN_UNDEF)
+ flags |= FDef;
+ if (eSym.st_shndx == SHN_COMMON) {
+ flags |= FCommon;
+ f->hasCommonSyms = true;
+ }
+ if (eSym.getBinding() == STB_WEAK)
+ flags |= FWeak;
+ addRecord(tmp, *name, j, flags);
+ }
+ if (!f->justSymbols)
+ f->scanEarlySections();
+ // Derive the comdat signatures. A global signature symbol reuses its record's
+ // name and hash; the hash is of the stem, so a '@'-containing name rehashes.
+ auto sigOf = [&](uint32_t symIdx) {
+ if (uint32_t k = symIdx - firstGlobal; k < tmp.size()) {
+ const SymRecord &rec = tmp[k];
+ StringRef name(rec.name, rec.nameLen);
+ return rec.flags & FHasAt ? CachedHashStringRef(name)
+ : CachedHashStringRef(name, rec.hash);
+ }
+ return CachedHashStringRef(
+ StringRef(strtab.data() + eSyms[symIdx].st_name));
+ };
+ SmallVector<CachedName, 0> sigs;
+ sigs.reserve(f->comdatSecs.size());
+ for (auto [j, cs] : llvm::enumerate(f->comdatSecs)) {
+ if (cs.sigSym == UINT32_MAX)
+ continue;
+ CachedHashStringRef sig = sigOf(cs.sigSym);
+ sigs.push_back({sig.val().data(), (uint32_t)sig.val().size(), sig.hash(),
+ (uint32_t)j});
+ }
+ bucketizeComdats(fd[i], sigs);
+ bucketize(fd[i], tmp);
+}
+
+// Read a shared file's dynamic tags and version sections and record its dynsym
+// entries. Resolution runs in resolveName, registration in the epilogue.
+template <class ELFT> void Pipeline<ELFT>::readShared(uint32_t i) {
+ using Elf_Dyn = typename ELFT::Dyn;
+ using Elf_Shdr = typename ELFT::Shdr;
+ using Elf_Sym = typename ELFT::Sym;
+ using Elf_Verdef = typename ELFT::Verdef;
+ using Elf_Versym = typename ELFT::Versym;
+ auto *f = cast<SharedFile>(files[i]);
+ const ELFFile<ELFT> obj = f->template getObj<ELFT>();
+ ArrayRef<Elf_Shdr> sections = f->template getELFShdrs<ELFT>();
+ const Elf_Shdr *versymSec = nullptr, *verdefSec = nullptr,
+ *verneedSec = nullptr;
+ ArrayRef<Elf_Dyn> dynamicTags;
+ for (const Elf_Shdr &sec : sections) {
+ switch (sec.sh_type) {
+ case SHT_DYNAMIC:
+ dynamicTags =
+ CHECK2(obj.template getSectionContentsAsArray<Elf_Dyn>(sec), f);
+ break;
+ case SHT_GNU_versym:
+ versymSec = &sec;
+ break;
+ case SHT_GNU_verdef:
+ verdefSec = &sec;
+ break;
+ case SHT_GNU_verneed:
+ verneedSec = &sec;
+ break;
+ }
+ }
+
+ if (versymSec && f->template getELFSyms<ELFT>().empty()) {
+ ErrAlways(ctx) << "SHT_GNU_versym should be associated with symbol table";
+ return;
+ }
+
+ StringRef strtab = f->getStringTable();
+ // DT_SONAME (the deduplication key) and DT_NEEDED.
+ for (const Elf_Dyn &dyn : dynamicTags) {
+ if (dyn.d_tag == DT_NEEDED) {
+ uint64_t val = dyn.getVal();
+ if (val >= strtab.size()) {
+ Err(ctx) << f << ": invalid DT_NEEDED entry";
+ return;
+ }
+ f->dtNeeded.push_back(strtab.data() + val);
+ } else if (dyn.d_tag == DT_SONAME) {
+ uint64_t val = dyn.getVal();
+ if (val >= strtab.size()) {
+ Err(ctx) << f << ": invalid DT_SONAME entry";
+ return;
+ }
+ f->soName = strtab.data() + val;
+ }
+ }
+
+ f->verdefs = parseVerdefs<ELFT>(obj.base(), verdefSec);
+ std::vector<uint32_t> verneeds =
+ f->template parseVerneed<ELFT>(obj, verneedSec);
+
+ uint32_t firstGlobal = f->firstGlobal;
+ size_t size = f->template getELFSyms<ELFT>().size() - firstGlobal;
+ std::vector<uint16_t> versyms(size, VER_NDX_GLOBAL);
+ if (versymSec && size) {
+ ArrayRef<Elf_Versym> v =
+ CHECK2(obj.template getSectionContentsAsArray<Elf_Versym>(*versymSec),
+ f)
+ .slice(firstGlobal);
+ for (size_t j = 0; j < size; ++j)
+ versyms[j] = v[j].vs_index;
+ }
+
+ // Versioned names (foo@ver) are built in the thread-local arena so they
+ // outlive this parallel phase without touching the shared string saver.
+ auto saveVersioned = [](StringRef name, StringRef ver) {
+ size_t n = name.size() + 1 + ver.size();
+ char *buf = makeThreadLocalN<char>(n);
+ memcpy(buf, name.data(), name.size());
+ buf[name.size()] = '@';
+ memcpy(buf + name.size() + 1, ver.data(), ver.size());
+ return StringRef(buf, n);
+ };
+
+ ArrayRef<Elf_Sym> syms = f->template getGlobalELFSyms<ELFT>();
+ SmallVector<SymRecord, 0> tmp;
+ tmp.reserve(syms.size());
+ for (size_t j = 0, e = syms.size(); j != e; ++j) {
+ const Elf_Sym &sym = syms[j];
+ StringRef name = CHECK2(sym.getName(strtab), f);
+ if (sym.getBinding() == STB_LOCAL) {
+ Err(ctx) << f << ": invalid local symbol '" << name
+ << "' in global part of symbol table";
+ continue;
+ }
+ uint32_t elfIdx = firstGlobal + j;
+ const uint16_t ver = versyms[j], idx = ver & ~VERSYM_HIDDEN;
+ uint8_t base = FShared | (sym.getBinding() == STB_WEAK ? FWeak : 0);
+
+ if (sym.isUndefined()) {
+ // Index 0 (VER_NDX_LOCAL) is used for unversioned undefined symbols. GNU
+ // ld versions between 2.35 and 2.45 also generate VER_NDX_GLOBAL for
+ // this case (https://sourceware.org/PR33577).
+ if (ver != VER_NDX_LOCAL && ver != VER_NDX_GLOBAL) {
+ if (idx >= verneeds.size()) {
+ ErrAlways(ctx) << "corrupt input file: version need index " << idx
+ << " for symbol " << name
+ << " is out of bounds\n>>> defined in " << f;
+ continue;
+ }
+ name = saveVersioned(name, strtab.data() + verneeds[idx]);
+ }
+ addRecord(tmp, name, elfIdx, base);
+ continue;
+ }
+
+ if (ver == VER_NDX_LOCAL ||
+ (ver != VER_NDX_GLOBAL && idx >= f->verdefs.size())) {
+ // In GNU ld < 2.31 the MIPS port put _gp_disp with VER_NDX_LOCAL.
+ if (ctx.arg.emachine == EM_MIPS && name == "_gp_disp")
+ continue;
+ ErrAlways(ctx) << "corrupt input file: version definition index " << idx
+ << " for symbol " << name
+ << " is out of bounds\n>>> defined in " << f;
+ continue;
+ }
+
+ if (ver == idx)
+ addRecord(tmp, name, elfIdx, base | FDef, ver);
+
+ // Also register the versioned name to satisfy explicitly versioned refs.
+ if (ver == VER_NDX_GLOBAL)
+ continue;
+ StringRef verName =
+ strtab.data() + reinterpret_cast<const Elf_Verdef *>(f->verdefs[idx])
+ ->getAux()
+ ->vda_name;
+ addRecord(tmp, saveVersioned(name, verName), elfIdx, base | FDef, idx);
+ }
+ bucketize(fd[i], tmp);
+}
+
+template <class ELFT> void Pipeline<ELFT>::readBitcode(uint32_t i) {
+ auto *f = cast<BitcodeFile>(files[i]);
+ SmallVector<SymRecord, 0> tmp;
+ for (auto [j, irSym] : llvm::enumerate(f->obj->symbols())) {
+ uint8_t flags = FBitcode;
+ if (!irSym.isUndefined())
+ flags |= FDef;
+ if (irSym.isWeak())
+ flags |= FWeak;
+ if (irSym.isCommon())
+ flags |= FCommon;
+ addRecord(tmp, irSym.getName(), j, flags);
+ }
+ bucketize(fd[i], tmp);
+ SmallVector<CachedName, 0> sigs;
+ for (auto s : f->obj->getComdatTable())
+ if (s.second != Comdat::NoDeduplicate)
+ sigs.push_back({s.first.data(), (uint32_t)s.first.size(),
+ CachedHashStringRef(s.first).hash()});
+ bucketizeComdats(fd[i], sigs);
+}
+
+template <class ELFT> void Pipeline<ELFT>::buildNameDB() {
+ // Each bucket draws its seeds from the shard of the same index: shard routing
+ // and bucket routing share the name hash, and both key by the stem. A first
+ // batch replaces the symbol table, so it copies the whole shard; a late batch
+ // only extends it, so it looks up the names it actually sees.
+ ArrayRef<Symbol *> symVec = ctx.symtab->getSymbols();
+ size_t total = 0;
+ uint32_t bucketTotals[numShards] = {};
+ for (const FileData &d : fd) {
+ total += d.records.size();
+ for (uint32_t b = 0; b != numShards; ++b)
+ bucketTotals[b] += d.bucketStart[b + 1] - d.bucketStart[b];
+ }
+
+ parallelFor(0, numShards, [&](size_t b) {
+ Bucket &bu = buckets[b];
+ const auto &shard = ctx.symtab->getShards()[b];
+ // Shard loads deviate several percent from the mean, so size refs exactly
+ // rather than paying a mid-build reallocation.
+ size_t seeds = incremental ? 0 : shard.size();
+ bu.names.reserve(total / numShards / 4 + seeds);
+ bu.refs.reserve(bucketTotals[b]);
+ bu.map.reserve(total / numShards / 4 + seeds);
+ if (!incremental)
+ for (const auto &kv : shard) {
+ bu.map.try_emplace(kv.first, bu.names.size());
+ NameInfo &ni = bu.names.emplace_back();
+ ni.seedIdx = kv.second;
+ ni.sym = symVec[kv.second];
+ }
+ auto append = [&bu](uint32_t &first, uint32_t &last, uint32_t idx) {
+ if (first == UINT32_MAX)
+ first = idx;
+ else
+ bu.refs[last].next = idx;
+ last = idx;
+ };
+ for (auto [i, d] : llvm::enumerate(fd)) {
+ for (uint32_t r = d.bucketStart[b], e = d.bucketStart[b + 1]; r != e;
+ ++r) {
+ SymRecord &rec = d.records[r];
+ CachedHashStringRef key(rec.stem(), rec.hash);
+ auto [it, inserted] = bu.map.try_emplace(key, bu.names.size());
+ if (inserted) {
+ NameInfo &n = bu.names.emplace_back();
+ if (incremental) {
+ if (auto sit = shard.find(key); sit != shard.end()) {
+ n.seedIdx = sit->second;
+ n.sym = symVec[sit->second];
+ }
+ }
+ }
+ uint32_t nameId = it->second;
+ rec.nameId = nameId;
+ NameInfo &ni = bu.names[nameId];
+ uint32_t refIdx = bu.refs.size();
+ RefNode &node = bu.refs.emplace_back();
+ node.fileIdx = i;
+ node.recIdx = r;
+ if (rec.flags & FDef)
+ append(ni.firstDef, ni.lastDef, refIdx);
+ else
+ append(ni.firstUndef, ni.lastUndef, refIdx);
+ if (rec.stemLen != rec.nameLen) {
+ ni.verName = rec.name;
+ ni.verNameLen = rec.nameLen;
+ }
+ }
+ }
+ });
+
+ bucketBase[0] = 0;
+ for (uint32_t b = 0; b != numShards; ++b)
+ bucketBase[b + 1] = bucketBase[b] + buckets[b].names.size();
+}
+
+template <class ELFT> void Pipeline<ELFT>::activate() {
+ // A lazy member is pulled in if a non-weak undefined reference anywhere names
+ // a symbol whose first definition is that member, to a fixpoint. Determinism
+ // comes from file index, not scan order.
+ uint32_t numNames = bucketBase[numShards];
+ const bool fortranCommon = ctx.arg.fortranCommon;
+ auto globalId = [&](const SymRecord &rec) {
+ return bucketBase[rec.hash % numShards] + rec.nameId;
+ };
+ // Snapshot the lazy flags: extraction clears files[i]->lazy, but the
+ // decisions must stay a function of the pre-activation state.
+ SmallVector<uint8_t, 0> wasLazy(files.size());
+ for (auto [i, f] : llvm::enumerate(files))
+ wasLazy[i] = f->lazy;
+
+ // Walk a name's definition chain, summarizing each tier (0 = strong/regular,
+ // 1 = weak/tentative) and the first lazy non-tentative definition (the
+ // --fortran-common override target).
+ struct DefInfo {
+ uint32_t firstFile[2] = {UINT32_MAX, UINT32_MAX};
+ uint32_t lazyStrongDef = UINT32_MAX;
+ bool firstLazy[2] = {false, false};
+ bool eagerReg[2] = {false, false};
+ bool eagerCommon = false; // an eager or extracted file defines it as COMMON
+ };
+ auto summarize = [&](uint32_t b, uint32_t nameId) {
+ DefInfo di;
+ const Bucket &bu = buckets[b];
+ // A late batch does not contain the files parsed before it; the already
+ // resolved symbol stands in for their definitions.
+ const Symbol *seed = bu.names[nameId].seedIdx == UINT32_MAX
+ ? nullptr
+ : bu.names[nameId].sym;
+ if (seed && (seed->isDefined() || seed->isCommon() || seed->isShared())) {
+ unsigned t = seed->isWeak() || seed->isCommon() ? 1 : 0;
+ di.firstFile[t] = files.size();
+ di.eagerReg[t] = !seed->isShared();
+ di.eagerCommon = seed->isCommon();
+ }
+ for (uint32_t r = bu.names[nameId].firstDef; r != UINT32_MAX;
+ r = bu.refs[r].next) {
+ const RefNode &node = bu.refs[r];
+ const SymRecord &rec = fd[node.fileIdx].records[node.recIdx];
+ bool lazy = wasLazy[node.fileIdx];
+ unsigned t = (rec.flags & (FWeak | FCommon)) ? 1 : 0;
+ if (di.firstFile[t] == UINT32_MAX) {
+ di.firstFile[t] = node.fileIdx;
+ di.firstLazy[t] = lazy;
+ }
+ if (!lazy && !(rec.flags & FShared))
+ di.eagerReg[t] = true;
+ // --fortran-common: a COMMON is active once its file is eager or
+ // extracted; only a STB_GLOBAL non-tentative definition overrides it.
+ if (rec.flags & FCommon)
+ di.eagerCommon |= !files[node.fileIdx]->lazy;
+ else if (lazy && di.lazyStrongDef == UINT32_MAX && !(rec.flags & FWeak))
+ di.lazyStrongDef = node.fileIdx;
+ }
+ return di;
+ };
+ // The lazy member to pull in when the name is referenced. A reference
+ // resolves in its strongest tier; within that tier an eager regular
+ // definition wins and suppresses extraction, otherwise the first-seen
+ // definition wins. This matches mold/ld.bfd.
+ auto extractTarget = [](const DefInfo &di) -> uint32_t {
+ unsigned t = di.firstFile[0] != UINT32_MAX ? 0 : 1;
+ if (di.firstFile[t] != UINT32_MAX && !di.eagerReg[t] && di.firstLazy[t])
+ return di.firstFile[t];
+ return UINT32_MAX;
+ };
+
+ // Worklist of names referenced by a live non-weak undefined symbol. Each name
+ // is resolved once; if unsatisfied it extracts its first-seen lazy member,
+ // whose own references join the worklist. trig records the first referrer:
+ // UINT32_MAX until the name is queued, and files.size() for ctx.internalFile.
+ SmallVector<uint32_t, 0> trig(numNames, UINT32_MAX);
+ struct WorkItem {
+ uint32_t bucket, nameId;
+ };
+ SmallVector<WorkItem, 0> work;
+ auto pushName = [&](uint32_t id, uint32_t bucket, uint32_t nameId,
+ uint32_t file) {
+ if (trig[id] == UINT32_MAX) {
+ trig[id] = file;
+ work.push_back({bucket, nameId});
+ }
+ };
+ auto seedKey = [&](CachedHashStringRef key) {
+ uint32_t b = key.hash() % numShards;
+ auto it = buckets[b].map.find(key);
+ if (it != buckets[b].map.end())
+ pushName(bucketBase[b] + it->second, b, it->second,
+ (uint32_t)files.size());
+ };
+ if (!triggers.empty()) {
+ // A reactivate batch pulls in only the members defining the triggers.
+ for (Symbol *t : triggers) {
+ StringRef stem =
+ t->getName().take_front(getSymbolStem(t->getName()).first);
+ seedKey(CachedHashStringRef(stem));
+ }
+ } else {
+ // Already resolved non-weak undefined references (e.g. -u) and every
+ // non-weak undefined reference of an eager (non-lazy) file.
+ for (uint32_t b = 0; b != numShards; ++b)
+ for (auto [id, ni] : llvm::enumerate(buckets[b].names))
+ if (ni.seedIdx != UINT32_MAX && ni.sym->isUndefined() &&
+ !ni.sym->isWeak())
+ pushName(bucketBase[b] + id, b, id, (uint32_t)files.size());
+ for (auto [i, f] : llvm::enumerate(files)) {
+ if (!fd[i].compatible || f->lazy)
+ continue;
+ for (const SymRecord &rec : fd[i].records)
+ if (!(rec.flags & (FDef | FWeak)) ||
+ (fortranCommon && (rec.flags & FCommon)))
+ pushName(globalId(rec), rec.hash % numShards, rec.nameId, i);
+ }
+ }
+
+ while (!work.empty()) {
+ auto [b, nameId] = work.pop_back_val();
+ uint32_t id = bucketBase[b] + nameId;
+ DefInfo di = summarize(b, nameId);
+ uint32_t m = extractTarget(di);
+ // --fortran-common: a lazy non-tentative definition overrides an active
+ // COMMON that nothing else displaces.
+ if (fortranCommon && m == UINT32_MAX && di.eagerCommon &&
+ di.lazyStrongDef != UINT32_MAX)
+ m = di.lazyStrongDef;
+ if (m == UINT32_MAX || !files[m]->lazy)
+ continue; // satisfied, no lazy definition, or already extracted
+ files[m]->lazy = false;
+ extractions.push_back({m, trig[id], b, nameId});
+ for (const SymRecord &rec : fd[m].records)
+ if (!(rec.flags & (FDef | FWeak)) ||
+ (fortranCommon && (rec.flags & FCommon)))
+ pushName(globalId(rec), rec.hash % numShards, rec.nameId, m);
+ }
+}
+
+template <class ELFT> void Pipeline<ELFT>::registerComdats() {
+ if (ignoreComdats)
+ return;
+ // First-parsed file in serial order owns each comdat group.
+ llvm::TimeTraceScope comdatScope("Pre-populate comdat groups");
+ size_t numComdats = 0;
+ for (const FileData &d : fd)
+ numComdats += d.comdats.size();
+ parallelFor(0, numShards, [&](size_t s) {
+ ctx.symtab->comdatGroups[s].reserve(numComdats / numShards / 2);
+ for (auto [fi, f] : llvm::enumerate(files)) {
+ if (!fd[fi].compatible || f->lazy)
+ continue;
+ const FileData &d = fd[fi];
+ // Record the verdict so that initializeSections needs no lookup. Comdat
+ // entries are sharded by hash, so writes to comdatSecs are disjoint.
+ auto *obj =
+ f->kind() == InputFile::ObjKind ? cast<ObjFile<ELFT>>(f) : nullptr;
+ for (uint32_t i = d.comdatStart[s], e = d.comdatStart[s + 1]; i != e;
+ ++i) {
+ const CachedName &cn = d.comdats[i];
+ if (ctx.symtab->addComdatGroup(cn.ref(), f) != f && obj)
+ obj->comdatSecs[cn.srcIdx].prevailing = 0;
+ }
+ }
+ });
+}
+
+// The sole symbol-resolution dispatch, shared by the parallel resolveName and
+// the serial -y traced replay, so the two cannot diverge.
+template <class ELFT>
+void Pipeline<ELFT>::applyRecord(Symbol *sym, InputFile *file,
+ const SymRecord &rec, bool isDef) {
+ if (rec.flags & FShared) {
+ resolveSharedSymbol<ELFT>(ctx, *sym, *cast<SharedFile>(file), rec.elfIdx,
+ isDef, rec.versionId);
+ } else if (file->lazy) {
+ // An unextracted lazy file contributes only LazySymbol definitions; an
+ // earlier shared or bitcode definition suppresses them.
+ sym->resolve(ctx, LazySymbol{*file});
+ } else if (rec.flags & FBitcode) {
+ auto *bf = cast<BitcodeFile>(file);
+ createBitcodeSymbol(ctx, sym, bf->obj->symbols()[rec.elfIdx], *bf);
+ } else {
+ auto *obj = cast<ObjFile<ELFT>>(file);
+ resolveSymbol(ctx, obj, obj->template getELFSyms<ELFT>()[rec.elfIdx], *sym);
+ }
+}
+
+// Replay resolution events for -y traced names in symbol-table order, deferred
+// by resolveName so that the trace output is deterministic. phaseSplit visits
+// definitions first; defsOnly restricts to definitions.
+template <class ELFT>
+void Pipeline<ELFT>::replayTraced(InputFile *file, const FileData &d,
+ bool phaseSplit, bool defsOnly) {
+ SmallVector<uint32_t, 0> order = symbolOrder(d);
+ for (int phase = 0, end = phaseSplit ? 2 : 1; phase != end; ++phase)
+ for (uint32_t ri : order) {
+ const SymRecord &rec = d.records[ri];
+ bool isDef = rec.flags & FDef;
+ if ((defsOnly && !isDef) || (phaseSplit && (phase == 0) != isDef))
+ continue;
+ Symbol *sym = symOf(rec);
+ if (sym->traced)
+ applyRecord(sym, file, rec, isDef);
+ }
+}
+
+template <class ELFT>
+void Pipeline<ELFT>::resolveName(Bucket &bu, uint32_t nameId) {
+ NameInfo &ni = bu.names[nameId];
+ Symbol *sym = ni.sym;
+
+ // Apply the @@ rename and default flags. Serial insert() renames the symbol
+ // on every versioned insertion and flags any name containing '@'.
+ if (ni.verName) {
+ sym->setName(StringRef(ni.verName, ni.verNameLen));
+ sym->hasVersionSuffix = true;
+ } else if (ni.seedIdx == UINT32_MAX && (recOf(bu, ni).flags & FHasAt)) {
+ sym->hasVersionSuffix = true;
+ }
+
+ // Merge the definition and undefined-reference chains into one event stream
+ // ordered by (file, definitions first, symbol index), matching the serial
+ // linker. The output anchor ignores the phase: the file that first inserts
+ // the symbol.
+ uint32_t anchorRank = ni.seedIdx == UINT32_MAX ? UINT32_MAX : 0;
+ uint64_t anchorSub = ni.seedIdx == UINT32_MAX ? UINT64_MAX : ni.seedIdx;
+ const bool traced = sym->traced;
+ bool inserted = ni.seedIdx != UINT32_MAX;
+ auto key = [&](uint32_t r, uint64_t undefPhase) {
+ const RefNode &node = bu.refs[r];
+ return (uint64_t(node.fileIdx + 1) << 33) | (undefPhase << 32) |
+ fd[node.fileIdx].records[node.recIdx].elfIdx;
+ };
+ uint32_t dIt = ni.firstDef, uIt = ni.firstUndef;
+ for (;;) {
+ // An unextracted lazy file never inserts its undefined references.
+ while (uIt != UINT32_MAX && files[bu.refs[uIt].fileIdx]->lazy)
+ uIt = bu.refs[uIt].next;
+ if (dIt == UINT32_MAX && uIt == UINT32_MAX)
+ break;
+ bool isDef =
+ uIt == UINT32_MAX || (dIt != UINT32_MAX && key(dIt, 0) < key(uIt, 1));
+ uint32_t r = isDef ? dIt : uIt;
+ (isDef ? dIt : uIt) = bu.refs[r].next;
+ const RefNode &node = bu.refs[r];
+ uint32_t f = node.fileIdx;
+ const SymRecord &rec = fd[f].records[node.recIdx];
+ uint32_t rank = f + 1;
+ // Sub-order within a file: a bitcode file inserts every definition before
+ // its undefined references; a shared file inserts a symbol's unversioned
+ // name before its versioned one ('@').
+ uint64_t asub;
+ if (rec.flags & FBitcode)
+ asub = (uint64_t(!isDef) << 32) | rec.elfIdx;
+ else if (rec.flags & FShared)
+ asub = (uint64_t(rec.elfIdx) << 1) | bool(rec.flags & FHasAt);
+ else
+ asub = rec.elfIdx;
+ if (rank < anchorRank || (rank == anchorRank && asub < anchorSub)) {
+ anchorRank = rank;
+ anchorSub = asub;
+ }
+ if (traced)
+ continue;
+ inserted = true;
+ applyRecord(sym, files[f], rec, isDef);
+ }
+ ni.anchorRank = anchorRank;
+ ni.anchorSub = anchorSub;
+ if (!traced && !inserted)
+ ni.anchorRank = UINT32_MAX;
+}
+
+template <class ELFT> void Pipeline<ELFT>::resolveSymbols() {
+ // A late batch extends an already-installed symbol table: seeds are resolved
+ // in place and only the new names are ordered and appended.
+ auto isLive = [&](const NameInfo &ni) {
+ return ni.anchorRank != UINT32_MAX &&
+ !(incremental && ni.seedIdx != UINT32_MAX);
+ };
+
+ uint32_t counts[numShards];
+ {
+ llvm::TimeTraceScope scope1("Resolve buckets");
+ parallelFor(0, numShards, [&](size_t b) {
+ Bucket &bu = buckets[b];
+ size_t n = bu.names.size();
+ counts[b] = 0;
+ if (!n)
+ return;
+ SymbolUnion *storage = makeThreadLocalN<SymbolUnion>(n);
+ uint32_t live = 0;
+ for (size_t id = 0; id != n; ++id) {
+ NameInfo &ni = bu.names[id];
+ if (ni.seedIdx != UINT32_MAX) {
+ // A late batch resolves a pre-existing symbol in place so that
+ // references from already-parsed files stay valid.
+ if (!incremental) {
+ SymbolUnion *su = &storage[id];
+ memcpy(static_cast<void *>(su), ni.sym, sizeof(SymbolUnion));
+ ni.sym = reinterpret_cast<Symbol *>(su);
+ }
+ } else {
+ SymbolUnion *su = &storage[id];
+ memset(static_cast<void *>(su), 0, sizeof(SymbolUnion));
+ auto *s = reinterpret_cast<Symbol *>(su);
+ // The map key (any record's stem) is the initial name.
+ s->versionId = VER_NDX_GLOBAL;
+ s->setName(recOf(bu, ni).stem());
+ ni.sym = reinterpret_cast<Symbol *>(su);
+ }
+ resolveName(bu, id);
+ live += isLive(bu.names[id]);
+ }
+ counts[b] = live;
+ });
+ }
+
+ llvm::TimeTraceScope scope2("Order symbols");
+ // Order the live symbols by their serial insertion point and move them into
+ // the final storage. Names that no serial insertion event would have created
+ // are dropped before the sort.
+ uint32_t liveStart[numShards + 1];
+ uint32_t sum = 0;
+ for (uint32_t b = 0; b != numShards; ++b) {
+ liveStart[b] = sum;
+ sum += counts[b];
+ }
+ liveStart[numShards] = sum;
+ size_t live = sum;
+ SmallVector<OrderItem, 0> items;
+ items.resize_for_overwrite(live);
+ parallelFor(0, numShards, [&](size_t b) {
+ Bucket &bu = buckets[b];
+ OrderItem *out = items.begin() + liveStart[b];
+ for (auto [id, ni] : llvm::enumerate(bu.names))
+ if (isLive(ni))
+ *out++ = {ni.anchorRank, ni.anchorSub, (uint32_t)b, (uint32_t)id};
+ });
+ parallelSort(items.begin(), items.end());
+
+ SymbolUnion *out = getSpecificAllocSingleton<SymbolUnion>().Allocate(live);
+ if (incremental) {
+ // Seeds were resolved in place; append the new names, registering each in
+ // its hash shard.
+ for (auto [i, it] : llvm::enumerate(items)) {
+ Bucket &bu = buckets[it.bucket];
+ NameInfo &ni = bu.names[it.nameId];
+ memcpy(static_cast<void *>(&out[i]), ni.sym, sizeof(SymbolUnion));
+ ni.sym = reinterpret_cast<Symbol *>(&out[i]);
+ const SymRecord &rec = recOf(bu, ni);
+ ctx.symtab->appendShardedSymbol(CachedHashStringRef(rec.stem(), rec.hash),
+ ni.sym);
+ }
+ recordExtractions();
+ return;
+ }
+
+ SmallVector<Symbol *, 0> symVector(live);
+ parallelFor(0, live, [&](size_t i) {
+ const OrderItem &it = items[i];
+ NameInfo &ni = buckets[it.bucket].names[it.nameId];
+ memcpy(static_cast<void *>(&out[i]), ni.sym, sizeof(SymbolUnion));
+ ni.sym = reinterpret_cast<Symbol *>(&out[i]);
+ ni.outIdx = i;
+ symVector[i] = ni.sym;
+ });
+
+ // Rewrite the bucket map values to symVector indices and install.
+ parallelFor(0, numShards, [&](size_t b) {
+ Bucket &bu = buckets[b];
+ for (const NameInfo &ni : bu.names)
+ if (ni.outIdx == UINT32_MAX) {
+ const SymRecord &rec = recOf(bu, ni);
+ bu.map.erase(CachedHashStringRef(rec.stem(), rec.hash));
+ }
+ for (auto &kv : bu.map)
+ kv.second = bu.names[kv.second].outIdx;
+ });
+ std::array<DenseMap<CachedHashStringRef, int>, numShards> maps;
+ for (size_t b = 0; b != numShards; ++b)
+ maps[b] = std::move(buckets[b].map);
+ ctx.symtab->installShardedSymbols(maps, std::move(symVector));
+
+ recordExtractions();
+}
+
+template <class ELFT> void Pipeline<ELFT>::recordExtractions() {
+ if (ctx.arg.whyExtract.empty())
+ return;
+ for (const Extraction &ex : extractions) {
+ Symbol *sym = buckets[ex.bucket].names[ex.nameId].sym;
+ InputFile *trigger =
+ ex.trigFile == files.size() ? ctx.internalFile : files[ex.trigFile];
+ ctx.whyExtractRecords.emplace_back(toStr(ctx, trigger), files[ex.member],
+ *sym);
+ }
+}
+
+template <class ELFT> void Pipeline<ELFT>::wireSymbols() {
+ auto cost = [&](uint32_t i) -> uint64_t { return fd[i].records.size(); };
+ parallelForLPT(files.size(), cost, [&](uint32_t i) {
+ if (!fd[i].eligible)
+ return;
+ InputFile *f = files[i];
+ if (f->kind() == InputFile::ObjKind) {
+ bool inactiveLazy = f->lazy;
+ f->allocateSymbols();
+ MutableArrayRef<Symbol *> syms = f->getMutableSymbols();
+ for (const SymRecord &rec : fd[i].records) {
+ // An unextracted lazy file has only its definitions wired.
+ if (inactiveLazy && !(rec.flags & FDef))
+ continue;
+ Bucket &bu = buckets[rec.hash % numShards];
+ syms[rec.elfIdx] = bu.names[rec.nameId].sym;
+ }
+ } else if (f->kind() == InputFile::BitcodeKind) {
+ // resolveName resolved the bitcode symbols in place; wire the array.
+ auto *bf = cast<BitcodeFile>(f);
+ bool inactiveLazy = f->lazy;
+ bf->allocateSymbols(bf->obj->symbols().size());
+ MutableArrayRef<Symbol *> syms = bf->getMutableSymbols();
+ for (const SymRecord &rec : fd[i].records) {
+ if (inactiveLazy && !(rec.flags & FDef))
+ continue;
+ syms[rec.elfIdx] = symOf(rec);
+ }
+ } else if (f->kind() == InputFile::SharedKind &&
+ ctx.arg.unresolvedSymbolsInShlib != UnresolvedPolicy::Ignore) {
+ // Collect the DSO's strong undefined references for
+ // reportUndefinedSymbols.
+ auto *sf = cast<SharedFile>(f);
+ for (uint32_t ri : symbolOrder(fd[i])) {
+ const SymRecord &rec = fd[i].records[ri];
+ if (!(rec.flags & (FDef | FWeak)))
+ sf->requiredSymbols.push_back(symOf(rec));
+ }
+ }
+ });
+}
+
+template <class ELFT> void Pipeline<ELFT>::epilogue() {
+ // Run the order-sensitive side effects in command-line order: file
+ // registration (which determines output section order), dependent libraries,
+ // ARM attributes and -y traced replay.
+ firstObjFile = ctx.objectFiles.size();
+ for (auto [i, f] : llvm::enumerate(files)) {
+ if (!fd[i].compatible)
+ continue;
+ FileData &d = fd[i];
+ if (f->lazy) {
+ if (auto *bf = dyn_cast<BitcodeFile>(f)) {
+ // Unlike a lazy object, parseLazy has no early exit, so all
+ // definitions are visible.
+ ctx.lazyBitcodeFiles.push_back(bf);
+ if (ctx.symtab->hasTracedSymbol)
+ replayTraced(f, d, /*phaseSplit=*/false, /*defsOnly=*/true);
+ continue;
+ }
+ if (ctx.symtab->hasTracedSymbol && f->kind() == InputFile::ObjKind)
+ replayTraced(f, d, /*phaseSplit=*/false, /*defsOnly=*/true);
+ continue;
+ }
+ // -t traces the input files and extracted members, but not LTO outputs.
+ if (ctx.arg.trace && !ignoreComdats)
+ Msg(ctx) << f;
+ if (auto *bf = dyn_cast<BitcodeFile>(f)) {
+ ctx.bitcodeFiles.push_back(bf);
+ bf->parseComdats();
+ if (ctx.symtab->hasTracedSymbol)
+ replayTraced(f, d, /*phaseSplit=*/true, /*defsOnly=*/false);
+ for (auto l : bf->obj->getDependentLibraries())
+ addDependentLibrary(ctx, l, bf);
+ continue;
+ }
+ if (auto *sf = dyn_cast<SharedFile>(f)) {
+ if (d.dupSoname)
+ continue;
+ ctx.sharedFiles.push_back(sf);
+ sf->allocateSymbols();
+ sf->parseGnuAndFeatures<ELFT>(sf->getObj<ELFT>());
+ if (ctx.symtab->hasTracedSymbol)
+ replayTraced(f, d, /*phaseSplit=*/false, /*defsOnly=*/false);
+ continue;
+ }
+ if (auto *bin = dyn_cast<BinaryFile>(f)) {
+ // A binary blob defines _binary_<name>_{start,end,size} directly.
+ ctx.binaryFiles.push_back(bin);
+ bin->parse();
+ continue;
+ }
+ auto *obj = cast<ObjFile<ELFT>>(f);
+ ctx.objectFiles.push_back(obj);
+ if (ctx.symtab->hasTracedSymbol)
+ replayTraced(f, d, /*phaseSplit=*/true, /*defsOnly=*/false);
+ obj->processEarlySections();
+ }
+}
+
+template <class ELFT> void Pipeline<ELFT>::initSections() {
+ // Runs after the epilogue, so that comdat group ownership and
+ // ctx.in.attributes are final, and after the phase-local data is freed.
+ ArrayRef<ELFFileBase *> objs = ArrayRef(ctx.objectFiles).slice(firstObjFile);
+ auto cost = [&](uint32_t i) -> uint64_t {
+ auto *f = cast<ObjFile<ELFT>>(objs[i]);
+ return f->template getELFShdrs<ELFT>().size() + f->firstGlobal;
+ };
+ parallelForLPT(objs.size(), cost, [&](uint32_t i) {
+ cast<ObjFile<ELFT>>(objs[i])->initSectionsAndLocalSyms(ignoreComdats);
+ });
+}
+
+template <class ELFT> void Pipeline<ELFT>::run() {
+ {
+ llvm::TimeTraceScope timeScope("Read symbols");
+ readSymbols();
+ }
+ {
+ llvm::TimeTraceScope timeScope("Build symbol database");
+ buildNameDB();
+ }
+ {
+ llvm::TimeTraceScope timeScope("Activate archive members");
+ activate();
+ }
+ registerComdats();
+ {
+ llvm::TimeTraceScope timeScope("Resolve symbols");
+ resolveSymbols();
+ }
+ {
+ llvm::TimeTraceScope timeScope("Wire symbols");
+ wireSymbols();
+ }
+ {
+ llvm::TimeTraceScope timeScope("Parse non-object files");
+ epilogue();
+ }
+ // Free phase-local data in parallel; serial destruction measurably
+ // serializes the frees.
+ parallelFor(0, fd.size() + numShards, [&](size_t i) {
+ if (i < fd.size()) {
+ fd[i] = FileData();
+ } else {
+ Bucket &bu = buckets[i - fd.size()];
+ bu.names = {};
+ bu.refs = {};
+ }
+ });
+ {
+ llvm::TimeTraceScope timeScope("Initialize sections");
+ initSections();
+ }
+}
+
+template <class ELFT>
+static void runPipeline(Ctx &ctx, SmallVector<InputFile *, 0> files,
+ bool incremental, ArrayRef<Symbol *> triggers = {},
+ bool ignoreComdats = false) {
+ Pipeline<ELFT> p(ctx, std::move(files), incremental, triggers, ignoreComdats);
+ p.run();
+}
+
+template <class ELFT>
+static void
+doParseFiles(Ctx &ctx,
+ const SmallVector<std::unique_ptr<InputFile>, 0> &files) {
+ // Parsing may append files (addDependentLibrary); a new batch seeds its name
+ // database from the symbols resolved so far.
+ for (size_t done = 0; done < files.size();) {
+ size_t end = files.size();
+ SmallVector<InputFile *, 0> batch;
+ batch.reserve(end - done);
+ for (size_t i = done; i != end; ++i)
+ batch.push_back(files[i].get());
+ runPipeline<ELFT>(ctx, std::move(batch), /*incremental=*/done != 0);
+ done = end;
+ }
+ if (ctx.driver.armCmseImpLib)
+ cast<ObjFile<ELFT>>(*ctx.driver.armCmseImpLib).importCmseSymbols();
+}
+
+void elf::parseFiles(Ctx &ctx,
+ const SmallVector<std::unique_ptr<InputFile>, 0> &files) {
+ llvm::TimeTraceScope timeScope("Parse input files");
+ invokeELFT(doParseFiles, ctx, files);
+}
+
+void elf::parseLtoObjectFiles(Ctx &ctx, ArrayRef<InputFile *> files) {
+ if (files.empty())
+ return;
+ invokeELFT(runPipeline, ctx,
+ SmallVector<InputFile *, 0>(files.begin(), files.end()),
+ /*incremental=*/true, /*triggers=*/ArrayRef<Symbol *>(),
+ /*ignoreComdats=*/true);
+
+ // An LTO output may reference a runtime libcall defined in an archive member
+ // not loaded before LTO; extract such members.
+ SmallVector<Symbol *, 0> triggers;
+ for (InputFile *f : files)
+ for (Symbol *sym : cast<ELFFileBase>(f)->getGlobalSymbols())
+ if (sym && sym->isLazy() && !sym->isWeak())
+ triggers.push_back(sym);
+ reactivate(ctx, triggers);
+}
+
+void elf::reactivate(Ctx &ctx, ArrayRef<Symbol *> triggers) {
+ if (triggers.empty())
+ return;
+ SmallVector<InputFile *, 0> lazyFiles;
+ for (auto &f : ctx.driver.getFiles())
+ if (f->lazy)
+ lazyFiles.push_back(f.get());
+ if (lazyFiles.empty())
+ return;
+ invokeELFT(runPipeline, ctx, std::move(lazyFiles), /*incremental=*/true,
+ triggers);
+}
+
template class elf::ObjFile<ELF32LE>;
template class elf::ObjFile<ELF32BE>;
template class elf::ObjFile<ELF64LE>;
template class elf::ObjFile<ELF64BE>;
-template void SharedFile::parse<ELF32LE>();
-template void SharedFile::parse<ELF32BE>();
-template void SharedFile::parse<ELF64LE>();
-template void SharedFile::parse<ELF64BE>();
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 0ded9b2fa38e..975031aebd85 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -15,6 +15,7 @@
#include "lld/Common/LLVM.h"
#include "lld/Common/Reproduce.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Object/ELF.h"
#include "llvm/Support/MemoryBufferRef.h"
@@ -43,8 +44,12 @@ const ELFSyncStream &operator<<(const ELFSyncStream &, const InputFile *);
std::optional<MemoryBufferRef> readFile(Ctx &, StringRef path);
// Add symbols in File to the symbol table.
-void parseFile(Ctx &, InputFile *file);
void parseFiles(Ctx &, const SmallVector<std::unique_ptr<InputFile>, 0> &);
+// Resolve the symbols of LTO output objects against the symbol table.
+void parseLtoObjectFiles(Ctx &, ArrayRef<InputFile *>);
+// Extract still-lazy members defining the trigger symbols and resolve them.
+// References within an extracted member pull in further members transitively.
+void reactivate(Ctx &, ArrayRef<Symbol *> triggers);
// The root class of input files.
class InputFile {
@@ -100,6 +105,19 @@ public:
return {symbols.get(), numSymbols};
}
+ // Allocate the symbols array (zero-initialized) if not already present.
+ void allocateSymbols() {
+ if (!symbols)
+ symbols = std::make_unique<Symbol *[]>(numSymbols);
+ }
+
+ void allocateSymbols(size_t n) {
+ if (!symbols) {
+ numSymbols = n;
+ symbols = std::make_unique<Symbol *[]>(n);
+ }
+ }
+
Symbol &getSymbol(uint32_t symbolIndex) const {
assert(fileKind == ObjKind);
if (symbolIndex >= numSymbols)
@@ -115,10 +133,6 @@ public:
// Get filename to use for linker script processing.
StringRef getNameForScript() const;
- // Check if a non-common symbol should be extracted to override a common
- // definition.
- bool shouldExtractForCommon(StringRef name) const;
-
// .got2 in the current file. This is used by PPC32 -fPIC/-fPIE to compute
// offsets in PLT call stubs.
InputSection *ppc32Got2 = nullptr;
@@ -126,12 +140,6 @@ public:
// Index of MIPS GOT built for this file.
uint32_t mipsGotIndex = -1;
- // groupId is used for --warn-backrefs which is an optional error
- // checking feature. All files within the same --{start,end}-group or
- // --{start,end}-lib get the same group ID. Otherwise, each file gets a new
- // group ID. For more info, see checkDependency() in SymbolTable.cpp.
- uint32_t groupId = 0;
-
// If this is an architecture-specific file, the following members
// have ELF type (i.e. ELF{32,64}{LE,BE}) and target machine type.
uint16_t emachine = llvm::ELF::EM_NONE;
@@ -221,7 +229,6 @@ protected:
const void *elfShdrs = nullptr;
const void *elfSyms = nullptr;
uint32_t numELFShdrs = 0;
- uint32_t firstGlobal = 0;
// Below are ObjFile specific members.
@@ -235,11 +242,19 @@ protected:
public:
// Name of source file obtained from STT_FILE, if present.
StringRef sourceFile;
+ uint32_t firstGlobal = 0;
uint32_t andFeatures = 0;
bool hasCommonSyms = false;
std::optional<AArch64PauthAbiCoreInfo> aarch64PauthAbiCoreInfo;
};
+// Run fn over each item with unit-size dynamic grabs, largest cost first.
+// parallelFor's fixed-size chunks leave a straggler tail when the per-item
+// cost is heavy-tailed.
+void parallelForLPT(size_t numItems,
+ llvm::function_ref<uint64_t(uint32_t)> cost,
+ llvm::function_ref<void(uint32_t)> fn);
+
// .o file.
template <class ELFT> class ObjFile : public ELFFileBase {
LLVM_ELF_IMPORT_TYPES_ELFT(ELFT)
@@ -256,11 +271,8 @@ public:
this->archiveName = archiveName;
}
- void parse(bool ignoreComdats = false);
- void parseLazy();
-
- StringRef getShtGroupSignature(ArrayRef<Elf_Shdr> sections,
- const Elf_Shdr &sec);
+ llvm::Expected<std::pair<StringRef, ArrayRef<Elf_Word>>>
+ getGroup(const Elf_Shdr &sec);
uint32_t getSectionIndex(const Elf_Sym &sym) const;
@@ -290,10 +302,33 @@ public:
void postParse();
void importCmseSymbols();
+ // Tolerantly scan the section headers; diagnostics for malformed groups are
+ // emitted later, in initializeSections.
+ void scanEarlySections();
+ // Process dependent libraries and SHT_ARM_ATTRIBUTES (serial contexts
+ // only: may add input files and create the singleton attributes section).
+ void processEarlySections();
+
+ // Set when the file has a section processEarlySections must handle.
+ bool needsSerialScan = false;
+
+ // A SHT_GROUP section: its index, its signature symbol (UINT32_MAX unless
+ // the group is a decodable GRP_COMDAT), and whether this file owns the
+ // group. initializeSections consumes and frees this.
+ struct ComdatSec {
+ uint32_t secIdx;
+ uint32_t sigSym;
+ uint32_t prevailing = 1;
+ };
+ SmallVector<ComdatSec, 0> comdatSecs;
+
private:
+ // Retained SHT_ARM_ATTRIBUTES section, if this file provides
+ // ctx.in.attributes.
+ uint32_t armAttrSecIdx = UINT32_MAX;
+
void initializeSections(bool ignoreComdats,
const llvm::object::ELFFile<ELFT> &obj);
- void initializeSymbols(const llvm::object::ELFFile<ELFT> &obj);
void initializeJustSymbols();
InputSectionBase *getRelocTarget(uint32_t idx, uint32_t info);
@@ -315,10 +350,6 @@ private:
// The following variable contains the contents of .symtab_shndx.
// If the section does not exist (which is common), the array is empty.
ArrayRef<Elf_Word> shndxTable;
-
- // Section indices of kept SHT_GROUP sections, recorded by parse() in
- // ascending order, to be used by the parallel initializeSections().
- SmallVector<uint32_t, 0> keptGroups;
};
class BitcodeFile : public InputFile {
@@ -326,8 +357,7 @@ public:
BitcodeFile(Ctx &, MemoryBufferRef m, StringRef archiveName,
uint64_t offsetInArchive, bool lazy);
static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
- void parse();
- void parseLazy();
+ void parseComdats();
void postParse();
std::unique_ptr<llvm::lto::InputFile> obj;
std::vector<bool> keptComdats;
@@ -357,8 +387,6 @@ public:
static bool classof(const InputFile *f) { return f->kind() == SharedKind; }
- template <typename ELFT> void parse();
-
// Used for --as-needed
std::atomic<bool> isNeeded;
@@ -366,7 +394,7 @@ public:
// parsed. Only filled for `--no-allow-shlib-undefined`.
SmallVector<Symbol *, 0> requiredSymbols;
-private:
+ // Called by the parallel parse pipeline (Pipeline::readShared/epilogue).
template <typename ELFT>
std::vector<uint32_t> parseVerneed(const llvm::object::ELFFile<ELFT> &obj,
const typename ELFT::Shdr *sec);
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index dcc84b8755db..89ff71e184de 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -1937,10 +1937,15 @@ void LinkerScript::checkFinalScriptConditions() const {
void LinkerScript::addScriptReferencedSymbolsToSymTable() {
// Some symbols (such as __ehdr_start) are defined lazily only when there
// are undefined symbols for them, so we add these to trigger that logic.
- auto reference = [&ctx = ctx](StringRef name) {
+ // A reference may also name a still-lazy archive member; collect those and
+ // pull them in below.
+ SmallVector<Symbol *, 0> triggers;
+ auto reference = [&](StringRef name) {
Symbol *sym = ctx.symtab->addUnusedUndefined(name);
sym->isUsedInRegularObj = true;
sym->referenced = true;
+ if (sym->isLazy())
+ triggers.push_back(sym);
};
for (StringRef name : referencedSymbols)
reference(name);
@@ -1964,6 +1969,7 @@ void LinkerScript::addScriptReferencedSymbolsToSymTable() {
}
}
}
+ reactivate(ctx, triggers);
}
bool LinkerScript::shouldAddProvideSym(StringRef symName) {
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index 64c42eb49607..3463908a0f20 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -222,7 +222,7 @@ def enable_non_contiguous_regions : FF<"enable-non-contiguous-regions">,
HelpText<"Spill input sections to later matching output sections to avoid memory region overflow">;
def end_group: F<"end-group">,
- HelpText<"Ignored for compatibility with GNU unless you pass --warn-backrefs">;
+ HelpText<"Ignored for compatibility with GNU">;
def end_lib: F<"end-lib">,
HelpText<"End a grouping of objects that should be treated as if they were together in an archive">;
@@ -470,7 +470,7 @@ defm sort_section:
Eq<"sort-section", "Specifies sections sorting rule when linkerscript is used">;
def start_group: F<"start-group">,
- HelpText<"Ignored for compatibility with GNU unless you pass --warn-backrefs">;
+ HelpText<"Ignored for compatibility with GNU">;
def start_lib: F<"start-lib">,
HelpText<"Start a grouping of objects that should be treated as if they were together in an archive">;
@@ -551,16 +551,6 @@ def no_power10_stubs: FF<"no-power10-stubs">, Alias<power10_stubs_eq>, AliasArgs
defm version_script: Eq<"version-script", "Read a version script">;
-defm warn_backrefs: BB<"warn-backrefs",
- "Warn about backward symbol references to extract archive members",
- "Do not warn about backward symbol references to extract archive members (default)">;
-
-defm warn_backrefs_exclude
- : EEq<"warn-backrefs-exclude",
- "Glob describing an archive (or an object file within --start-lib) "
- "which should be ignored for --warn-backrefs.">,
- MetaVarName<"<glob>">;
-
defm warn_common: B<"warn-common",
"Warn about duplicate common symbols",
"Do not warn about duplicate common symbols (default)">;
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 7aaae802f4d7..948f5a29c52b 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -401,9 +401,14 @@ static void maybeReportDiscarded(Ctx &ctx, ELFSyncStream &msg, Undefined &sym) {
return;
// If the discarded section is a COMDAT.
- StringRef signature = file->getShtGroupSignature(objSections, elfSec);
+ auto group = file->getGroup(elfSec);
+ if (!group) {
+ consumeError(group.takeError());
+ return;
+ }
+ StringRef signature = group->first;
if (const InputFile *prevailing =
- ctx.symtab->comdatGroups.lookup(CachedHashStringRef(signature))) {
+ ctx.symtab->findComdatGroup(CachedHashStringRef(signature))) {
msg << "\n>>> section group signature: " << signature
<< "\n>>> prevailing definition is in " << prevailing;
if (sym.nonPrevailing) {
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 0c54fde48829..1916fb4d6e87 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -391,8 +391,6 @@ void ScriptParser::readExtern() {
void ScriptParser::readGroup() {
SaveAndRestore saved(ctx.driver.isInGroup, true);
readInput();
- if (!saved.get())
- ++ctx.driver.nextGroupId;
}
void ScriptParser::readInclude(llvm::function_ref<void()> parse) {
diff --git a/lld/ELF/SymbolTable.cpp b/lld/ELF/SymbolTable.cpp
index da0293e9ec83..290021d7d220 100644
--- a/lld/ELF/SymbolTable.cpp
+++ b/lld/ELF/SymbolTable.cpp
@@ -30,9 +30,11 @@ using namespace lld::elf;
void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
// Redirect __real_foo to the original foo and foo to the original __wrap_foo.
- int &idx1 = symMap[CachedHashStringRef(sym->getName())];
- int &idx2 = symMap[CachedHashStringRef(real->getName())];
- int &idx3 = symMap[CachedHashStringRef(wrap->getName())];
+ CachedHashStringRef name1(sym->getName()), name2(real->getName()),
+ name3(wrap->getName());
+ int &idx1 = getMap(name1)[name1];
+ int &idx2 = getMap(name2)[name2];
+ int &idx3 = getMap(name3)[name3];
idx2 = idx1;
idx1 = idx3;
@@ -61,18 +63,11 @@ void SymbolTable::wrap(Symbol *sym, Symbol *real, Symbol *wrap) {
// Find an existing symbol or create a new one.
Symbol *SymbolTable::insert(StringRef name) {
- // <name>@@<version> means the symbol is the default version. In that
- // case <name>@@<version> will be used to resolve references to <name>.
- //
- // Since this is a hot path, the following string search code is
- // optimized for speed. StringRef::find(char) is much faster than
- // StringRef::find(StringRef).
- StringRef stem = name;
- size_t pos = name.find('@');
- if (pos != StringRef::npos && pos + 1 < name.size() && name[pos + 1] == '@')
- stem = name.take_front(pos);
+ auto [stemLen, hasAt] = getSymbolStem(name);
+ StringRef stem = name.take_front(stemLen);
- auto p = symMap.insert({CachedHashStringRef(stem), (int)symVector.size()});
+ CachedHashStringRef chr(stem);
+ auto p = getMap(chr).insert({chr, (int)symVector.size()});
if (!p.second) {
Symbol *sym = symVector[p.first->second];
if (stem.size() != name.size()) {
@@ -89,7 +84,7 @@ Symbol *SymbolTable::insert(StringRef name) {
// are zero. Set the ones that need a non-zero value.
sym->setName(name);
sym->versionId = VER_NDX_GLOBAL;
- if (pos != StringRef::npos)
+ if (hasAt)
sym->hasVersionSuffix = true;
return sym;
}
@@ -106,12 +101,22 @@ Symbol *SymbolTable::addAndCheckDuplicate(Ctx &ctx, const Defined &newSym) {
}
Symbol *SymbolTable::find(StringRef name) {
- auto it = symMap.find(CachedHashStringRef(name));
- if (it == symMap.end())
+ CachedHashStringRef chr(name);
+ auto &map = getMap(chr);
+ auto it = map.find(chr);
+ if (it == map.end())
return nullptr;
return symVector[it->second];
}
+void SymbolTable::installShardedSymbols(
+ MutableArrayRef<DenseMap<CachedHashStringRef, int>> maps,
+ SmallVector<Symbol *, 0> &&syms) {
+ assert(maps.size() == numShards);
+ llvm::move(maps, shards.begin());
+ symVector = std::move(syms);
+}
+
// A version script/dynamic list is only meaningful for a Defined symbol.
// A CommonSymbol will be converted to a Defined in replaceCommonSymbols().
// A lazy symbol may be made Defined if an LTO libcall extracts it.
diff --git a/lld/ELF/SymbolTable.h b/lld/ELF/SymbolTable.h
index e485ad2edb00..b38bf492b21a 100644
--- a/lld/ELF/SymbolTable.h
+++ b/lld/ELF/SymbolTable.h
@@ -13,6 +13,7 @@
#include "llvm/ADT/CachedHashString.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Compiler.h"
+#include <array>
namespace lld::elf {
struct Ctx;
@@ -24,6 +25,16 @@ struct ArmCmseEntryFunction {
Symbol *sym;
};
+// The symbol table key of <name>@@<version> is <name>: a default version
+// resolves references to the unversioned name. Returns the stem length and
+// whether the name contains '@'.
+inline std::pair<size_t, bool> getSymbolStem(StringRef name) {
+ size_t pos = name.find('@');
+ if (pos != StringRef::npos && pos + 1 < name.size() && name[pos + 1] == '@')
+ return {pos, true};
+ return {name.size(), pos != StringRef::npos};
+}
+
// SymbolTable is a bucket of all known symbols, including defined,
// undefined, or lazy symbols (the last one is symbols in archive
// files whose archive members are not yet loaded).
@@ -38,13 +49,33 @@ struct ArmCmseEntryFunction {
// is one add* function per symbol type.
class SymbolTable {
public:
+ static constexpr size_t numShards = 32;
+
SymbolTable(Ctx &ctx) : ctx(ctx) {}
ArrayRef<Symbol *> getSymbols() const { return symVector; }
+ // The per-shard name maps (key -> symVector index), keyed by the original
+ // registration stem, which may differ from a symbol's current name.
+ ArrayRef<llvm::DenseMap<llvm::CachedHashStringRef, int>> getShards() const {
+ return ArrayRef(shards);
+ }
+
void wrap(Symbol *sym, Symbol *real, Symbol *wrap);
Symbol *insert(StringRef name);
+ // Install the pre-ordered maps and symbol vector built by the parse
+ // pipeline.
+ void installShardedSymbols(
+ MutableArrayRef<llvm::DenseMap<llvm::CachedHashStringRef, int>> maps,
+ SmallVector<Symbol *, 0> &&syms);
+
+ // Append a symbol resolved by a late parse batch, in symVector order.
+ void appendShardedSymbol(llvm::CachedHashStringRef key, Symbol *sym) {
+ shards[key.hash() % numShards].try_emplace(key, (int)symVector.size());
+ symVector.push_back(sym);
+ }
+
template <typename T> Symbol *addSymbol(const T &newSym) {
Symbol *sym = insert(newSym.getName());
sym->resolve(ctx, newSym);
@@ -65,9 +96,22 @@ public:
llvm::DenseMap<llvm::CachedHashStringRef, SharedFile *> soNames;
// Comdat groups define "link once" sections. If two comdat groups have the
- // same name, only one of them is linked, and the other is ignored. This map
- // is used to uniquify them.
- llvm::DenseMap<llvm::CachedHashStringRef, const InputFile *> comdatGroups;
+ // same name, only one of them is linked, and the other is ignored. The maps
+ // are sharded by signature hash so that the parallel parse pipeline can
+ // pre-populate them in parallel; the first registered file owns the group.
+ // Returns the owner.
+ const InputFile *addComdatGroup(llvm::CachedHashStringRef sig,
+ const InputFile *f) {
+ return comdatGroups[sig.hash() % numShards]
+ .try_emplace(sig, f)
+ .first->second;
+ }
+ const InputFile *findComdatGroup(llvm::CachedHashStringRef sig) const {
+ return comdatGroups[sig.hash() % numShards].lookup(sig);
+ }
+ std::array<llvm::DenseMap<llvm::CachedHashStringRef, const InputFile *>,
+ numShards>
+ comdatGroups;
// The Map of __acle_se_<sym>, <sym> pairs found in the input objects.
// Key is the <sym> name.
@@ -81,6 +125,8 @@ public:
// output Arm CMSE import library.
llvm::StringMap<bool> inCMSEOutImpLib;
+ bool hasTracedSymbol = false;
+
private:
SmallVector<Symbol *, 0> findByVersion(SymbolVersion ver);
SmallVector<Symbol *, 0> findAllByVersion(SymbolVersion ver,
@@ -92,10 +138,15 @@ private:
Ctx &ctx;
- // Global symbols and a map from symbol name to the index. The order is not
- // defined. We can use an arbitrary order, but it has to be deterministic even
- // when cross linking.
- llvm::DenseMap<llvm::CachedHashStringRef, int> symMap;
+ llvm::DenseMap<llvm::CachedHashStringRef, int> &
+ getMap(llvm::CachedHashStringRef name) {
+ return shards[name.hash() % numShards];
+ }
+
+ // Global symbols: per-shard maps from name (registration stem) to symVector
+ // index, routed by name hash. The order is not defined. We can use an
+ // arbitrary order, but it has to be deterministic even when cross linking.
+ std::array<llvm::DenseMap<llvm::CachedHashStringRef, int>, numShards> shards;
SmallVector<Symbol *, 0> symVector;
// A map from demangled symbol names to their symbol objects.
diff --git a/lld/ELF/Symbols.cpp b/lld/ELF/Symbols.cpp
index 951041466cec..928164f02442 100644
--- a/lld/ELF/Symbols.cpp
+++ b/lld/ELF/Symbols.cpp
@@ -279,12 +279,6 @@ void Symbol::parseSymbolVersion(Ctx &ctx) {
<< verstr;
}
-void Symbol::extract(Ctx &ctx) const {
- assert(file->lazy);
- file->lazy = false;
- parseFile(ctx, file);
-}
-
uint8_t Symbol::computeBinding(Ctx &ctx) const {
auto v = visibility();
if ((v != STV_DEFAULT && v != STV_PROTECTED) || versionId == VER_NDX_LOCAL)
@@ -311,11 +305,6 @@ void elf::printTraceSymbol(const Symbol &sym, StringRef name) {
Msg(sym.file->ctx) << sym.file << s << name;
}
-static void recordWhyExtract(Ctx &ctx, const InputFile *reference,
- const InputFile &extracted, const Symbol &sym) {
- ctx.whyExtractRecords.emplace_back(toStr(ctx, reference), &extracted, sym);
-}
-
void elf::maybeWarnUnorderableSymbol(Ctx &ctx, const Symbol *sym) {
if (!ctx.arg.warnSymbolOrdering)
return;
@@ -414,17 +403,12 @@ void elf::parseVersionAndComputeIsPreemptible(Ctx &ctx) {
// were not chosen still affect some symbol properties.
void Symbol::mergeProperties(const Symbol &other) {
// DSO symbols do not affect visibility in the output.
- if (!other.isShared() && other.visibility() != STV_DEFAULT) {
- uint8_t v = visibility(), ov = other.visibility();
- setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
- }
+ if (!other.isShared())
+ mergeVisibility(other.visibility());
}
void Symbol::resolve(Ctx &ctx, const Undefined &other) {
- if (other.visibility() != STV_DEFAULT) {
- uint8_t v = visibility(), ov = other.visibility();
- setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
- }
+ mergeVisibility(other.visibility());
// An undefined symbol with non default visibility must be satisfied
// in the same DSO.
//
@@ -440,81 +424,14 @@ void Symbol::resolve(Ctx &ctx, const Undefined &other) {
printTraceSymbol(other, getName());
if (isLazy()) {
- // An undefined weak will not extract archive members. See comment on Lazy
- // in Symbols.h for the details.
+ // An undefined weak does not extract archive members (see the comment on
+ // Lazy in Symbols.h). A strong reference leaves the lazy symbol in place;
+ // extraction is decided by Pipeline::activate, which also records
+ // --why-extract.
if (other.binding == STB_WEAK) {
binding = STB_WEAK;
type = other.type;
- return;
}
-
- // Do extra check for --warn-backrefs.
- //
- // --warn-backrefs is an option to prevent an undefined reference from
- // extracting an archive member written earlier in the command line. It can
- // be used to keep compatibility with GNU linkers to some degree. I'll
- // explain the feature and why you may find it useful in this comment.
- //
- // lld's symbol resolution semantics is more relaxed than traditional Unix
- // linkers. For example,
- //
- // ld.lld foo.a bar.o
- //
- // succeeds even if bar.o contains an undefined symbol that has to be
- // resolved by some object file in foo.a. Traditional Unix linkers don't
- // allow this kind of backward reference, as they visit each file only once
- // from left to right in the command line while resolving all undefined
- // symbols at the moment of visiting.
- //
- // In the above case, since there's no undefined symbol when a linker visits
- // foo.a, no files are pulled out from foo.a, and because the linker forgets
- // about foo.a after visiting, it can't resolve undefined symbols in bar.o
- // that could have been resolved otherwise.
- //
- // That lld accepts more relaxed form means that (besides it'd make more
- // sense) you can accidentally write a command line or a build file that
- // works only with lld, even if you have a plan to distribute it to wider
- // users who may be using GNU linkers. With --warn-backrefs, you can detect
- // a library order that doesn't work with other Unix linkers.
- //
- // The option is also useful to detect cyclic dependencies between static
- // archives. Again, lld accepts
- //
- // ld.lld foo.a bar.a
- //
- // even if foo.a and bar.a depend on each other. With --warn-backrefs, it is
- // handled as an error.
- //
- // Here is how the option works. We assign a group ID to each file. A file
- // with a smaller group ID can pull out object files from an archive file
- // with an equal or greater group ID. Otherwise, it is a reverse dependency
- // and an error.
- //
- // A file outside --{start,end}-group gets a fresh ID when instantiated. All
- // files within the same --{start,end}-group get the same group ID. E.g.
- //
- // ld.lld A B --start-group C D --end-group E
- //
- // A forms group 0. B form group 1. C and D (including their member object
- // files) form group 2. E forms group 3. I think that you can see how this
- // group assignment rule simulates the traditional linker's semantics.
- bool backref = ctx.arg.warnBackrefs && file->groupId < other.file->groupId;
- extract(ctx);
-
- if (!ctx.arg.whyExtract.empty())
- recordWhyExtract(ctx, other.file, *file, *this);
-
- // We don't report backward references to weak symbols as they can be
- // overridden later.
- //
- // A traditional linker does not error for -ldef1 -lref -ldef2 (linking
- // sandwich), where def2 may or may not be the same as def1. We don't want
- // to warn for this case, so dismiss the warning if we see a subsequent lazy
- // definition. this->file needs to be saved because in the case of LTO it
- // may be reset to internalFile or be replaced with a file named lto.tmp.
- if (backref && !isWeak())
- ctx.backwardReferences.try_emplace(this,
- std::make_pair(other.file, file));
return;
}
@@ -605,10 +522,7 @@ void Symbol::checkDuplicate(Ctx &ctx, const Defined &other) const {
}
void Symbol::resolve(Ctx &ctx, const CommonSymbol &other) {
- if (other.visibility() != STV_DEFAULT) {
- uint8_t v = visibility(), ov = other.visibility();
- setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
- }
+ mergeVisibility(other.visibility());
if (isDefined() && !isWeak()) {
if (ctx.arg.warnCommon)
Warn(ctx) << "common " << getName() << " is overridden";
@@ -641,49 +555,27 @@ void Symbol::resolve(Ctx &ctx, const CommonSymbol &other) {
}
void Symbol::resolve(Ctx &ctx, const Defined &other) {
- if (other.visibility() != STV_DEFAULT) {
- uint8_t v = visibility(), ov = other.visibility();
- setVisibility(v == STV_DEFAULT ? ov : std::min(v, ov));
- }
+ mergeVisibility(other.visibility());
if (shouldReplace(ctx, other))
other.overwrite(*this);
}
void Symbol::resolve(Ctx &ctx, const LazySymbol &other) {
+ // A lazy definition takes over only a placeholder or a weak undefined,
+ // which keeps its binding and type (it does not extract archive members;
+ // see the comment on Lazy in Symbols.h) but records the member for
+ // extract-on-demand (e.g. --entry). Anything else leaves the symbol in
+ // place: extraction decisions (including --fortran-common overrides) are
+ // made by Pipeline::activate, and an extracted member resolves as a regular
+ // file.
if (isPlaceholder()) {
other.overwrite(*this);
- return;
- }
-
- if (LLVM_UNLIKELY(!isUndefined())) {
- // See the comment in resolve(Ctx &, const Undefined &).
- if (isDefined()) {
- ctx.backwardReferences.erase(this);
- } else if (isCommon() && ctx.arg.fortranCommon &&
- other.file->shouldExtractForCommon(getName())) {
- // For common objects, we want to look for global or weak definitions that
- // should be extracted as the canonical definition instead.
- ctx.backwardReferences.erase(this);
- other.overwrite(*this);
- other.extract(ctx);
- }
- return;
- }
-
- // An undefined weak will not extract archive members. See comment on Lazy in
- // Symbols.h for the details.
- if (isWeak()) {
+ } else if (isUndefined() && isWeak()) {
uint8_t ty = type;
other.overwrite(*this);
type = ty;
binding = STB_WEAK;
- return;
}
-
- const InputFile *oldFile = file;
- other.extract(ctx);
- if (!ctx.arg.whyExtract.empty())
- recordWhyExtract(ctx, oldFile, *file, *this);
}
void Symbol::resolve(Ctx &ctx, const SharedSymbol &other) {
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index c489416e1f4d..00cc68d04719 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -151,6 +151,13 @@ public:
void setVisibility(uint8_t visibility) {
stOther = (stOther & ~3) | visibility;
}
+ // Merge an input symbol's visibility: the most constraining one wins.
+ void mergeVisibility(uint8_t ov) {
+ if (ov != llvm::ELF::STV_DEFAULT) {
+ uint8_t v = visibility();
+ setVisibility(v == llvm::ELF::STV_DEFAULT ? ov : std::min(v, ov));
+ }
+ }
uint8_t computeBinding(Ctx &) const;
bool isGlobal() const { return binding == llvm::ELF::STB_GLOBAL; }
@@ -226,11 +233,6 @@ public:
void resolve(Ctx &, const LazySymbol &other);
void resolve(Ctx &, const SharedSymbol &other);
- // If this is a lazy symbol, extract an input file and add the symbol
- // in the file to the symbol table. Calling this function on
- // non-lazy object causes a runtime error.
- void extract(Ctx &) const;
-
void checkDuplicate(Ctx &, const Defined &other) const;
private:
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index aa7a859a9da9..2748789e4ebc 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -3805,8 +3805,13 @@ void MergeNoTailSection::finalizeContents() {
template <class ELFT> void elf::splitSections(Ctx &ctx) {
llvm::TimeTraceScope timeScope("Split sections");
// splitIntoPieces needs to be called on each MergeInputSection
- // before calling finalizeContents().
- parallelForEach(ctx.objectFiles, [](ELFFileBase *file) {
+ // before calling finalizeContents(). Schedule the biggest files first: the
+ // per-file cost is heavy-tailed (one big .debug_str can dominate).
+ auto cost = [&](uint32_t i) -> uint64_t {
+ return ctx.objectFiles[i]->mb.getBufferSize();
+ };
+ parallelForLPT(ctx.objectFiles.size(), cost, [&](uint32_t i) {
+ ELFFileBase *file = ctx.objectFiles[i];
for (InputSectionBase *sec : file->getSections()) {
if (!sec)
continue;
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 8cf88cd99cf8..cf70c1c9adae 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -455,7 +455,8 @@ static void demoteAndCopyLocalSymbols(Ctx &ctx) {
parallelFor(0, ctx.objectFiles.size(), [&](size_t i) {
DenseMap<SectionBase *, size_t> sectionIndexMap;
for (Symbol *b : ctx.objectFiles[i]->getLocalSymbols()) {
- assert(b->isLocal() && "should have been caught in initializeSymbols()");
+ assert(b->isLocal() &&
+ "should have been caught in initSectionsAndLocalSyms()");
auto *dr = dyn_cast<Defined>(b);
if (!dr)
continue;
diff --git a/lld/docs/ELF/warn_backrefs.md b/lld/docs/ELF/warn_backrefs.md
deleted file mode 100644
index 84c43cd21ffd..000000000000
--- a/lld/docs/ELF/warn_backrefs.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# --warn-backrefs
-
-`--warn-backrefs` gives a warning when an undefined symbol reference is
-resolved by a definition in an archive to the left of it on the command line.
-
-A linker such as GNU ld makes a single pass over the input files from left to
-right maintaining the set of undefined symbol references from the files loaded
-so far. When encountering an archive or an object file surrounded by
-`--start-lib` and `--end-lib` that archive will be searched for resolving
-symbol definitions; this may result in input files being loaded, updating the
-set of undefined symbol references. When all resolving definitions have been
-loaded from the archive, the linker moves on the next file and will not return
-to it. This means that if an input file to the right of an archive cannot have
-an undefined symbol resolved by an archive to the left of it. For example:
-
-> ld def.a ref.o
-
-will result in an `undefined reference` error. If there are no cyclic
-references, the archives can be ordered in such a way that there are no
-backward references. If there are cyclic references then the `--start-group`
-and `--end-group` options can be used, or the same archive can be placed on
-the command line twice.
-
-LLD remembers the symbol table of archives that it has previously seen, so if
-there is a reference from an input file to the right of an archive, LLD will
-still search that archive for resolving any undefined references. This means
-that an archive only needs to be included once on the command line and the
-`--start-group` and `--end-group` options are redundant.
-
-A consequence of the differing archive searching semantics is that the same
-linker command line can result in different outcomes. A link may succeed with
-LLD that will fail with GNU ld, or even worse both links succeed but they have
-selected different objects from different archives that both define the same
-symbols.
-
-The `warn-backrefs` option provides information that helps identify cases
-where LLD and GNU ld archive selection may differ.
-
-```console
-% ld.lld --warn-backrefs ... -lB -lA
-ld.lld: warning: backward reference detected: system in A.a(a.o) refers to B.a(b.o)
-
-% ld.lld --warn-backrefs ... --start-lib B/b.o --end-lib --start-lib A/a.o --end-lib
-ld.lld: warning: backward reference detected: system in A/a.o refers to B/b.o
-
-# To suppress the warning, you can specify --warn-backrefs-exclude=<glob> to match B/b.o or B.a(b.o)
-```
-
-The `--warn-backrefs` option can also provide a check to enforce a
-topological order of archives, which can be useful to detect layering
-violations (albeit unable to catch all cases). There are two cases where GNU ld
-will result in an `undefined reference` error:
-
-- If adding the dependency does not form a cycle: conceptually `A` is higher
- level library while `B` is at a lower level. When you are developing an
- application `P` which depends on `A`, but does not directly depend on
- `B`, your link may fail surprisingly with `undefined symbol:
- symbol_defined_in_B` if the used/linked part of `A` happens to need some
- components of `B`. It is inappropriate for `P` to add a dependency on
- `B` since `P` does not use `B` directly.
-- If adding the dependency forms a cycle, e.g. `B->C->A ~> B`. `A`
- is supposed to be at the lowest level while `B` is supposed to be at the
- highest level. When you are developing `C_test` testing `C`, your link may
- fail surprisingly with `undefined symbol` if there is somehow a dependency on
- some components of `B`. You could fix the issue by adding the missing
- dependency (`B`), however, then every test (`A_test`, `B_test`,
- `C_test`) will link against every library. This breaks the motivation
- of splitting `B`, `C` and `A` into separate libraries and makes binaries
- unnecessarily large. Moreover, the layering violation makes lower-level
- libraries (e.g. `A`) vulnerable to changes to higher-level libraries (e.g.
- `B`, `C`).
-
-Resolution:
-
-- Add a dependency from `A` to `B`.
-- The reference may be unintended and can be removed.
-- The dependency may be intentionally omitted because there are multiple
- libraries like `B`. Consider linking `B` with object semantics by
- surrounding it with `--whole-archive` and `--no-whole-archive`.
-- In the case of circular dependency, sometimes merging the libraries are the best.
-
-There are two cases like a library sandwich where GNU ld will select a
-different object.
-
-- `A.a B A2.so`: `A.a` may be used as an interceptor (e.g. it provides some
- optimized libc functions and `A2` is libc). `B` does not need to know
- about `A.a`, and `A.a` may be pulled into the link by other part of the
- program. For linker portability, consider `--whole-archive` and
- `--no-whole-archive`.
-
-- `A.a B A2.a`: similar to the above case but `--warn-backrefs` does not
- flag the problem, because `A2.a` may be a replicate of `A.a`, which is
- redundant but benign. In some cases `A.a` and `B` should be surrounded by
- a pair of `--start-group` and `--end-group`. This is especially common
- among system libraries (e.g. `-lc __isnanl references -lm`, `-lc
- _IO_funlockfile references -lpthread`, `-lc __gcc_personality_v0 references
- -lgcc_eh`, and `-lpthread _Unwind_GetCFA references -lunwind`).
-
- In C++, this is likely an ODR violation. We probably need a dedicated option
- for ODR detection.
diff --git a/lld/docs/index.md b/lld/docs/index.md
index 7dba6f479e99..b699c056d32e 100644
--- a/lld/docs/index.md
+++ b/lld/docs/index.md
@@ -128,7 +128,6 @@ ReleaseNotes
ELF/large_sections
ELF/linker_script
ELF/start-stop-gc
-ELF/warn_backrefs
MachO/index
DTLTO
```
diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1
index 7dedf69881b3..1f43c3d99fd3 100644
--- a/lld/docs/ld.lld.1
+++ b/lld/docs/ld.lld.1
@@ -748,14 +748,6 @@ Verbose mode.
.It Fl -version-script Ns = Ns Ar file
Read version script from
.Ar file .
-.It Fl -warn-backrefs
-Warn about reverse or cyclic dependencies to or between static archives.
-This can be used to ensure linker invocation remains compatible with
-traditional Unix-like linkers.
-.It Fl -warn-backrefs-exclude Ns = Ns Ar glob
-Glob describing an archive (or an object file within --start-lib)
-which should be ignored for
-.Fl -warn-backrefs
.It Fl -warn-common
Warn about duplicate common symbols.
.It Fl -warn-ifunc-textrel
@@ -1076,8 +1068,3 @@ may produce different results compared to traditional linkers.
In practice, large bodies of third party software have been linked with
.Nm
without material issues.
-.Pp
-The
-.Fl -warn-backrefs
-option may be used to identify a linker invocation that may be incompatible
-with traditional Unix-like linker behavior.
diff --git a/lld/test/ELF/fortran-common-extract.s b/lld/test/ELF/fortran-common-extract.s
new file mode 100644
index 000000000000..11929548fcf6
--- /dev/null
+++ b/lld/test/ELF/fortran-common-extract.s
@@ -0,0 +1,84 @@
+# REQUIRES: x86
+## --fortran-common pulls in an archive member whose STB_GLOBAL definition
+## overrides an active tentative (COMMON) definition. Check that the decision
+## does not depend on where the COMMON appears among the definitions, and that
+## a member providing only a weak definition (which would not override the
+## COMMON) is not extracted.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 main.s -o main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 common.s -o common.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 weak.s -o weak.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 ref.s -o ref.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 def.s -o def.o
+# RUN: ld.lld -shared def.o -o d.so
+# RUN: llvm-ar rc cw.a common.o weak.o
+# RUN: llvm-ar rc w.a weak.o
+# RUN: llvm-ar rc d.a def.o
+
+## The COMMON is in an unextracted member, so no COMMON is active.
+# RUN: ld.lld --fortran-common main.o cw.a weak.o ref.o -o a1 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=NONE
+
+## A weak definition does not override a COMMON, so its member stays lazy.
+# RUN: ld.lld --fortran-common main.o common.o w.a -o a2 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=NONE
+
+# NONE: reference{{.*}}extracted{{.*}}symbol
+# NONE-NOT: {{.}}
+
+## The override target is found whether the COMMON precedes or follows the weak
+## definition.
+# RUN: ld.lld --fortran-common main.o weak.o common.o d.so d.a -o a3 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=EXTRACT
+# RUN: ld.lld --fortran-common main.o common.o weak.o d.so d.a -o a4 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=EXTRACT
+
+# EXTRACT: reference{{.*}}extracted{{.*}}symbol
+# EXTRACT-NEXT: common.o d.a(def.o) foo
+# EXTRACT-NOT: {{.}}
+
+## The COMMON becomes active when its member is extracted for another symbol.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 commonbar.s -o commonbar.o
+# RUN: llvm-ar rc cb.a commonbar.o def.o
+# RUN: ld.lld --fortran-common -u bar main.o d.so cb.a -o a5 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=LATE
+# RUN: ld.lld --fortran-common -u bar main.o cb.a d.so -o a6 --why-extract=- | \
+# RUN: FileCheck %s --check-prefix=LATE
+
+# LATE: reference{{.*}}extracted{{.*}}symbol
+# LATE-NEXT: <internal> cb.a(commonbar.o) bar
+# LATE-NEXT: cb.a(commonbar.o) cb.a(def.o) foo
+# LATE-NOT: {{.}}
+
+#--- main.s
+.globl _start
+_start:
+ ret
+
+#--- common.s
+.comm foo,4,4
+
+#--- commonbar.s
+.comm foo,4,4
+.globl bar
+bar:
+ ret
+
+#--- weak.s
+.data
+.weak foo
+foo:
+ .long 1
+
+#--- def.s
+.data
+.globl foo
+foo:
+ .long 2
+
+#--- ref.s
+.globl ref
+ref:
+ movl foo(%rip), %eax
+ ret
diff --git a/lld/test/ELF/interconnected-lazy.s b/lld/test/ELF/interconnected-lazy.s
index 0d67318b3fa0..fff77e6727f4 100644
--- a/lld/test/ELF/interconnected-lazy.s
+++ b/lld/test/ELF/interconnected-lazy.s
@@ -7,15 +7,15 @@
## foo and __foo are interconnected and defined in two lazy object files.
## Test we resolve both to the same file.
+## With order-independent archive extraction the live set is computed to a
+## fixpoint and ties break by file index, so the weak foo/__foo resolve to the
+## lower-indexed a.o (instead of b.o's later positional definition winning).
# RUN: ld.lld -y a -y foo -y __foo %t/main.o --start-lib %t/a.o %t/b.o --end-lib -o /dev/null | FileCheck %s
-# CHECK: a.o: lazy definition of a
-# CHECK-NEXT: a.o: lazy definition of foo
-# CHECK-NEXT: a.o: lazy definition of __foo
-# CHECK-NEXT: b.o: definition of foo
-# CHECK-NEXT: b.o: definition of __foo
+# CHECK: a.o: definition of a
+# CHECK-NEXT: a.o: definition of foo
+# CHECK-NEXT: a.o: definition of __foo
# CHECK-NEXT: b.o: reference to a
-# CHECK-NEXT: a.o: definition of a
#--- main.s
.globl _start
diff --git a/lld/test/ELF/lto/archive-mixed.test b/lld/test/ELF/lto/archive-mixed.test
index 6f1db87c89ca..4f174515ea4f 100644
--- a/lld/test/ELF/lto/archive-mixed.test
+++ b/lld/test/ELF/lto/archive-mixed.test
@@ -37,9 +37,12 @@
; RUN: ld.lld --trace ref.o a.o.b.o.a other.o.a | \
; RUN: FileCheck %s --implicit-check-not={{.}}
+;; With order-independent extraction, both members are still pulled regardless
+;; of the bitcode/object mix; they are traced in archive-member order (a then b)
+;; rather than reference-discovery order.
; CHECK: ref.o
-; CHECK-NEXT: a.{{.*}}.b.{{.*}}.a(b.{{.*}})
; CHECK-NEXT: a.{{.*}}.b.{{.*}}.a(a.{{.*}})
+; CHECK-NEXT: a.{{.*}}.b.{{.*}}.a(b.{{.*}})
;--- a.ll
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
diff --git a/lld/test/ELF/lto/comdat-mixed-archive.test b/lld/test/ELF/lto/comdat-mixed-archive.test
index 88e294ed9872..9e8678562367 100644
--- a/lld/test/ELF/lto/comdat-mixed-archive.test
+++ b/lld/test/ELF/lto/comdat-mixed-archive.test
@@ -1,14 +1,15 @@
REQUIRES: x86
;; This checks a case when an archive contains a bitcode and a regular object
-;; files, and a comdat symbol is defined and used in both of them. Previously,
-;; lld could lose the flag that the symbol is used in a regular object file
-;; which led to the LTO backend internalizing the symbol and the linker
-;; reporting an "undefined symbol" error.
+;; files, and a comdat symbol is defined and used in both of them. lld must not
+;; lose the flag that the symbol is used in a regular object file, which would
+;; lead to the LTO backend internalizing the symbol and the linker reporting an
+;; "undefined symbol" error.
-;; In this test, group "foo" in "obj.o" is rejected in favor of "bc.bc" but we
-;; need to prevent LTO from internalizing "foo" as there is still a reference
-;; from outside the group in "obj.o".
+;; With order-independent extraction, both "obj.o" and "bc.bc" are pulled from
+;; the archive. The comdat group for "foo" in "obj.o" wins, but LTO must still
+;; not internalize "foo" as there is a reference from outside the group in
+;; "obj.o". The link succeeds with "foo" defined.
RUN: rm -rf %t.dir
RUN: split-file %s %t.dir
@@ -21,21 +22,17 @@ RUN: llvm-nm bc.bc --no-sort | FileCheck %s --check-prefix=BCSYM
RUN: llvm-ar rc lib.a obj.o bc.bc
RUN: ld.lld start.o lib.a -y foo -y bar -o /dev/null | FileCheck %s --check-prefix=TRACE
-;; "bar" should be encountered before "foo" so that it triggers the loading of
-;; "obj.o" while "foo" is still lazy.
BCSYM: U bar
BCSYM-NEXT: W foo
-;; Check that the symbols are handled in the expected order.
-TRACE: lib.a(obj.o): lazy definition of foo
-TRACE-NEXT: lib.a(obj.o): lazy definition of bar
-TRACE-NEXT: lib.a(bc.bc): definition of foo
-TRACE-NEXT: lib.a(bc.bc): reference to bar
+;; With order-independent extraction both members are pulled. "foo" is defined
+;; by the regular object "obj.o" (its comdat group wins over "bc.bc"); "bar" is
+;; also defined there. The reference to "foo" from outside the group keeps it
+;; from being internalized, so the LTO result references "bar" and the link
+;; succeeds with "foo" defined.
+TRACE: lib.a(obj.o): definition of foo
TRACE-NEXT: lib.a(obj.o): definition of bar
-TRACE-NEXT: lib.a(obj.o): reference to foo
-TRACE-NEXT: <internal>: reference to foo
-;; The definition of "foo" is visible outside the LTO result.
-TRACE-NEXT: {{.*}}.lto.o: definition of foo
+TRACE-NEXT: lib.a(bc.bc): reference to bar
TRACE-NEXT: {{.*}}.lto.o: reference to bar
;--- start.s
diff --git a/lld/test/ELF/lto/lazy-internal.ll b/lld/test/ELF/lto/lazy-internal.ll
index dc1ba45c0d71..26f8cf9a6111 100644
--- a/lld/test/ELF/lto/lazy-internal.ll
+++ b/lld/test/ELF/lto/lazy-internal.ll
@@ -6,8 +6,11 @@
; RUN: ld.lld %t2.a %t1.o -o %t.so -shared -save-temps
; RUN: llvm-dis %t.so.0.2.internalize.bc -o - | FileCheck %s
-; CHECK: define internal void @foo()
+;; Both the lazily-extracted @bar (from %t2.a, the first input) and @foo (from
+;; %t1.o) are internalized. With order-independent extraction the combined LTO
+;; module is ordered by input file index, so @bar precedes @foo.
; CHECK: define internal void @bar()
+; CHECK: define internal void @foo()
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
diff --git a/lld/test/ELF/lto/thinlto-emit-index-thin-archive.ll b/lld/test/ELF/lto/thinlto-emit-index-thin-archive.ll
index bcb90e21a872..d9d274a576b7 100644
--- a/lld/test/ELF/lto/thinlto-emit-index-thin-archive.ll
+++ b/lld/test/ELF/lto/thinlto-emit-index-thin-archive.ll
@@ -18,14 +18,19 @@
; CHECK-UNUSED: lib.a(unused.o at {{[1-9][0-9]+}})
;; Index files emitted from object files in a thin archive should have the
-;; offset in the archive specified to avoid collisions
+;; offset in the archive specified to avoid collisions. The two same-basename
+;; "thin.o" members must get distinct offsets.
+;; With order-independent extraction the archive members are resolved before
+;; ./dir1/main.o (the archive precedes main.o on the command line).
; RUN: FileCheck %s < c.resolution.txt --check-prefix CHECK-COLLISION
+; CHECK-COLLISION: dir2/lib.a(thin.o at [[#%u,FOO:]])
+; CHECK-COLLISION-NEXT: -r=./dir2/lib.a(thin.o at [[#FOO]]),foo,pl
+;; The second "thin.o" member must use a different offset (no collision).
+; CHECK-COLLISION-NOT: thin.o at [[#FOO]])
+; CHECK-COLLISION: dir2/lib.a(thin.o at [[#%u,BLAH:]])
+; CHECK-COLLISION-NEXT: -r=./dir2/lib.a(thin.o at [[#BLAH]]),blah,pl
; CHECK-COLLISION: dir1/main.o
-; CHECK-COLLISION: dir2/lib.a(thin.o at {{[1-9][0-9]+}})
-; CHECK-COLLISION-NEXT: -r=./dir2/lib.a(thin.o at {{[1-9][0-9]+}}),blah,pl
-; CHECK-COLLISION: dir2/lib.a(thin.o at {{[1-9][0-9]+}})
-; CHECK-COLLISION-NEXT: -r=./dir2/lib.a(thin.o at {{[1-9][0-9]+}}),foo,pl
;; Clean up
; RUN: rm -rf ./dir1/*.thinlto.bc
diff --git a/lld/test/ELF/lto/warn-backrefs.ll b/lld/test/ELF/lto/warn-backrefs.ll
deleted file mode 100644
index c63e47a79cdf..000000000000
--- a/lld/test/ELF/lto/warn-backrefs.ll
+++ /dev/null
@@ -1,30 +0,0 @@
-; REQUIRES: x86
-;; Test that the referenced filename is correct (not <internal> or lto.tmp).
-
-; RUN: split-file %s %t
-; RUN: llvm-as %t/a.ll -o %ta.o
-; RUN: llvm-as %t/b.ll -o %tb.o
-; RUN: ld.lld --warn-backrefs --start-lib %tb.o --end-lib %ta.o -o /dev/null 2>&1 | FileCheck %s
-
-; CHECK: warning: backward reference detected: f in {{.*}}a.o refers to {{.*}}b.o
-
-;--- a.ll
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-declare void @f()
-
-define void @_start() {
-entry:
- call void () @f()
- ret void
-}
-
-;--- b.ll
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-define void @f() {
-entry:
- ret void
-}
diff --git a/lld/test/ELF/shared-lazy.s b/lld/test/ELF/shared-lazy.s
index 0fa8f06884a3..20d21c7b991a 100644
--- a/lld/test/ELF/shared-lazy.s
+++ b/lld/test/ELF/shared-lazy.s
@@ -6,30 +6,32 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux ref.s -o ref.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux ref2.s -o ref2.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux weakref2.s -o weakref2.o
-# RUN: ld.lld a.a b.so ref.o -shared -o 1.so
-# RUN: llvm-readelf --dyn-syms 1.so | FileCheck %s
+## A shared definition seen before the archive satisfies the reference, so the
+## archive member is not extracted; the shared definitions are used.
# RUN: ld.lld a.so a.a ref.o -shared -o 1.so
# RUN: llvm-readelf --dyn-syms 1.so | FileCheck %s
-## The definitions from a.so are used and we don't extract a member from the
-## archive.
-
# CHECK: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND x1
# CHECK-NEXT: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND x2
-## The extracted x1 is defined as STB_GLOBAL.
+## The archive is extracted in preference to a shared definition.
+# RUN: ld.lld a.a b.so ref.o -o 2.so -shared
+# RUN: llvm-readelf --dyn-symbols 2.so | FileCheck %s --check-prefix=CHECK2
# RUN: ld.lld ref.o a.a b.so -o 2.so -shared
# RUN: llvm-readelf --dyn-symbols 2.so | FileCheck %s --check-prefix=CHECK2
# RUN: ld.lld a.a ref.o b.so -o 2.so -shared
# RUN: llvm-readelf --dyn-symbols 2.so | FileCheck %s --check-prefix=CHECK2
+# RUN: ld.lld a.a a.so ref2.o -o 2.so -shared
+# RUN: llvm-readelf --dyn-symbols 2.so | FileCheck %s --check-prefix=CHECK2
# CHECK2: {{.*}} 0 NOTYPE GLOBAL DEFAULT [[#]] x1
# CHECK2-NEXT: {{.*}} 0 NOTYPE WEAK DEFAULT [[#]] x2
-## The extracted x2 is defined as STB_WEAK. x1 is not referenced by any relocatable object file.
-# RUN: ld.lld a.a ref2.o b.so -o 2.so -shared
-# RUN: llvm-readelf --dyn-syms 2.so | FileCheck %s --check-prefix=CHECK2
-# RUN: ld.lld a.a a.so ref2.o -o 3.so -shared
+## ref2.o references only x2. The member is not pulled in when a stronger
+## definition exists (b.so's strong x2) or a shared definition of equal strength
+## is seen first (a.so before a.a); x2 resolves to the shared definition and x1
+## is absent.
+# RUN: ld.lld a.a ref2.o b.so -o 3.so -shared
# RUN: llvm-readelf --dyn-syms 3.so | FileCheck %s --check-prefix=CHECK3
# RUN: ld.lld a.so a.a ref2.o -o 3.so -shared
# RUN: llvm-readelf --dyn-syms 3.so | FileCheck %s --check-prefix=CHECK3
diff --git a/lld/test/ELF/trace-symbols.s b/lld/test/ELF/trace-symbols.s
index 785414e2bd5b..5770086cf43b 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -68,9 +68,13 @@
# ARCHIVEDCOMMON-NOT: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of \
# common
+## Extraction is order-independent: the strong foo from %t2.so satisfies the
+## reference, so the archive member providing a weak foo is not extracted and
+## does not appear in the trace.
# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
-# RUN: FileCheck -check-prefix=ARCHIVED1FOO %s
-# ARCHIVED1FOO: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of foo
+# RUN: FileCheck -check-prefix=ARCHIVED1FOO %s --implicit-check-not=foo
+# ARCHIVED1FOO: trace-symbols.s.tmp: reference to foo
+# ARCHIVED1FOO-NEXT: trace-symbols.s.tmp2.so: shared definition of foo
# RUN: ld.lld -y foo %t %t1.a %t2.a -o %t3 | \
# RUN: FileCheck -check-prefix=ARCHIVED2FOO %s
@@ -84,9 +88,20 @@
# RUN: FileCheck -check-prefix=SHLIBRBAR %s
# SHLIBRBAR: trace-symbols.s.tmp1.so: reference to bar
+## A shared file's default-versioned definition is traced under the unversioned
+## name first.
+# RUN: echo 'v1 { global: foo; local: *; };' > %t.ver
+# RUN: ld.lld -shared --version-script=%t.ver %t2 -o %t4.so
+# RUN: ld.lld -y foo -y foo@v1 %t %t4.so -o %t3 | \
+# RUN: FileCheck -check-prefix=SHLIBDVER %s --implicit-check-not=foo
+# SHLIBDVER: trace-symbols.s.tmp: reference to foo
+# SHLIBDVER-NEXT: trace-symbols.s.tmp4.so: shared definition of foo
+# SHLIBDVER-NEXT: trace-symbols.s.tmp4.so: shared definition of foo@v1
+
# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o %t3 | \
# RUN: FileCheck -check-prefix=STARTLIB %s
-# STARTLIB: trace-symbols.s.tmp1: reference to bar
+## bar is defined by %t2 (the strong definition wins), so -u bar extracts %t2.
+# STARTLIB: trace-symbols.s.tmp2: definition of bar
## Check we do not crash when trying to trace special symbol.
# RUN: ld.lld -trace-symbol=_end %t %t1 %t2 -o /dev/null
diff --git a/lld/test/ELF/warn-backrefs.s b/lld/test/ELF/warn-backrefs.s
deleted file mode 100644
index 453017eb1c8e..000000000000
--- a/lld/test/ELF/warn-backrefs.s
+++ /dev/null
@@ -1,112 +0,0 @@
-# REQUIRES: x86
-
-# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t1.o
-# RUN: echo '.globl foo; foo:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t2.o
-# RUN: rm -f %t2.a
-# RUN: llvm-ar rcs %t2.a %t2.o
-# RUN: ld.lld -shared %t2.o -o %t2.so
-
-## A forward reference is accepted by a traditional Unix linker.
-# RUN: ld.lld --fatal-warnings %t1.o %t2.a -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t1.o %t2.a -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t1.o --start-lib %t2.o --end-lib -o /dev/null
-
-# RUN: echo 'INPUT("%t1.o" "%t2.a")' > %t1.lds
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t1.lds -o /dev/null
-
-## A backward reference from %t1.o to %t2.a
-## Warn unless the archive is excluded by --warn-backrefs-exclude
-# RUN: ld.lld --fatal-warnings %t2.a %t1.o -o /dev/null
-# RUN: ld.lld --warn-backrefs %t2.a %t1.o -o /dev/null 2>&1 | FileCheck %s
-# RUN: ld.lld --warn-backrefs --no-warn-backrefs %t2.a %t1.o -o /dev/null 2>&1 | count 0
-# RUN: ld.lld --warn-backrefs %t2.a '-(' %t1.o '-)' -o /dev/null 2>&1 | FileCheck %s
-# RUN: ld.lld --warn-backrefs --warn-backrefs-exclude='*3.a' %t2.a %t1.o -o /dev/null 2>&1 | FileCheck %s
-# RUN: ld.lld --fatal-warnings --warn-backrefs --warn-backrefs-exclude='*2.a(*2.o)' %t2.a %t1.o -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs --warn-backrefs-exclude '*2.a(*2.o)' \
-# RUN: --warn-backrefs-exclude not_exist %t2.a %t1.o -o /dev/null
-## Without --warn-backrefs, --warn-backrefs-exclude is ignored.
-# RUN: ld.lld --fatal-warnings --warn-backrefs-exclude=not_exist %t2.a %t1.o -o /dev/null
-
-## Placing the definition and the backward reference in a group can suppress the warning.
-# RUN: echo 'GROUP("%t2.a" "%t1.o")' > %t2.lds
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t2.lds -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs '-(' %t2.a %t1.o '-)' -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs --start-group %t2.a %t1.o --end-group -o /dev/null
-
-## A backward reference from %t1.o to %t2.a (added by %t3.lds).
-# RUN: echo 'GROUP("%t2.a")' > %t3.lds
-# RUN: ld.lld --warn-backrefs %t3.lds %t1.o -o /dev/null 2>&1 | FileCheck %s
-# RUN: ld.lld --fatal-warnings --warn-backrefs '-(' %t3.lds %t1.o '-)' -o /dev/null
-# RUN: ld.lld --fatal-warnings --warn-backrefs --warn-backrefs-exclude='*2.a(*2.o)' -o /dev/null %t3.lds %t1.o
-## If a lazy definition appears after the backward reference, don't warn.
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t3.lds %t1.o %t3.lds -o /dev/null
-
-# CHECK: warning: backward reference detected: foo in {{.*}}1.o refers to {{.*}}2.a
-
-## A backward reference from %t1.o to %t2.o
-## --warn-backrefs-exclude= applies to --start-lib covered object files.
-# RUN: ld.lld --warn-backrefs --start-lib %t2.o --end-lib %t1.o -o /dev/null 2>&1 | \
-# RUN: FileCheck --check-prefix=OBJECT %s
-# RUN: ld.lld --fatal-warnings --warn-backrefs --warn-backrefs-exclude=%/t2.o --start-lib %/t2.o --end-lib %t1.o -o /dev/null
-## If a lazy definition appears after the backward reference, don't warn.
-# RUN: ld.lld --fatal-warnings --warn-backrefs --start-lib %t2.o --end-lib %t1.o --start-lib %t2.o --end-lib -o /dev/null
-
-# OBJECT: warning: backward reference detected: foo in {{.*}}1.o refers to {{.*}}2.o
-
-## Back reference from an fetched --start-lib to a previous --start-lib.
-# RUN: ld.lld -m elf_x86_64 -u _start --warn-backrefs --start-lib %/t2.o --end-lib \
-# RUN: --start-lib %t1.o --end-lib -o /dev/null 2>&1 | FileCheck --check-prefix=OBJECT %s
-## --warn-backrefs-exclude=%/t2.o can be used for a fetched --start-lib.
-# RUN: ld.lld --fatal-warnings -m elf_x86_64 -u _start --warn-backrefs --warn-backrefs-exclude=%/t2.o --start-lib %/t2.o --end-lib --start-lib %t1.o --end-lib -o /dev/null
-
-## Don't warn if the definition and the backward reference are in a group.
-# RUN: echo '.globl bar; bar:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t3.o
-# RUN: echo '.globl foo; foo: call bar' | llvm-mc -filetype=obj -triple=x86_64 - -o %t4.o
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t1.o --start-lib %t3.o %t4.o --end-lib -o /dev/null
-# RUN: rm -f %t34.a && llvm-ar rcS %t34.a %t3.o %t4.o
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t1.o %t34.a -o /dev/null
-
-## We don't report backward references to weak symbols as they can be overridden later.
-# RUN: echo '.weak foo; foo:' | llvm-mc -filetype=obj -triple=x86_64 - -o %tweak.o
-# RUN: ld.lld --fatal-warnings --warn-backrefs --start-lib %tweak.o --end-lib %t1.o %t2.o -o /dev/null
-
-## Check common symbols. A common sym might later be replaced by a non-common definition.
-# RUN: echo '.comm obj, 4' | llvm-mc -filetype=obj -triple=x86_64 -o %tcomm.o
-# RUN: echo '.type obj,@object; .data; .globl obj; .p2align 2; obj: .long 55; .size obj, 4' | llvm-mc -filetype=obj -triple=x86_64 -o %tstrong.o
-# RUN: echo '.globl foo; foo: movl obj(%rip), %eax' | llvm-mc -triple=x86_64 -filetype=obj -o %t5.o
-# RUN: llvm-ar rcs %tcomm.a %tcomm.o
-# RUN: llvm-ar rcs %tstrong.a %tstrong.o
-# RUN: ld.lld --warn-backrefs %tcomm.a %t1.o %t5.o 2>&1 -o /dev/null | FileCheck --check-prefix=COMM %s
-# RUN: ld.lld --fatal-warnings --fortran-common --warn-backrefs %tcomm.a %t1.o %t5.o %tstrong.a 2>&1 -o /dev/null
-# RUN: ld.lld --warn-backrefs --no-fortran-common %tcomm.a %t1.o %t5.o %tstrong.a 2>&1 -o /dev/null | FileCheck --check-prefix=COMM %s
-
-# COMM: ld.lld: warning: backward reference detected: obj in {{.*}}5.o refers to {{.*}}comm.a
-
-## If a lazy definition appears after the backward reference, don't warn.
-## A traditional Unix linker will resolve the reference to the later definition.
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t2.a %t1.o %t2.a -o /dev/null
-
-## lld fetches the archive while GNU ld resolves the reference to the shared definition.
-## Warn because the resolution rules are different.
-# RUN: ld.lld --warn-backrefs %t2.a %t1.o %t2.so -o /dev/null 2>&1 | FileCheck %s
-
-## This is a limitation. The resolution rules are different but
-## --warn-backrefs does not warn.
-# RUN: ld.lld --fatal-warnings --warn-backrefs %t2.a %t1.o %t2.so %t2.a -o /dev/null
-
-## In GNU linkers, -u does not make a backward reference.
-# RUN: ld.lld --fatal-warnings --warn-backrefs -u foo %t2.a %t1.o -o /dev/null
-
-## -u does not make a backward reference.
-# RUN: ld.lld --fatal-warnings --warn-backrefs -u foo %t2.a %t1.o -o /dev/null
-
-## --defsym does not make a backward reference, but it does not suppress the warning due to another file.
-# RUN: ld.lld --fatal-warnings --warn-backrefs --defsym=x=foo -e 0 %t2.a -o /dev/null
-# RUN: ld.lld --warn-backrefs --defsym=x=foo %t2.a %t1.o -o /dev/null 2>&1 | FileCheck %s
-
-# RUN: not ld.lld --warn-backrefs-exclude='[' 2>&1 | FileCheck --check-prefix=INVALID %s
-# INVALID: error: --warn-backrefs-exclude: invalid glob pattern, unmatched '[': [
-
-.globl _start, foo
-_start:
- call foo
diff --git a/lld/test/ELF/why-extract.s b/lld/test/ELF/why-extract.s
index 3235bce5a716..d1270192a4ae 100644
--- a/lld/test/ELF/why-extract.s
+++ b/lld/test/ELF/why-extract.s
@@ -26,6 +26,11 @@
# RUN: rm -f why2.txt && not ld.lld main.o a_b.a b.a err.o --why-extract=why2.txt
# RUN: FileCheck %s --input-file=why2.txt --check-prefix=CHECK2 --match-full-lines --strict-whitespace
+## -y replays a traced symbol's resolution serially; the extraction is still
+## recorded.
+# RUN: rm -f why2.txt && ld.lld main.o a_b.a b.a -y a -y _Z1bv --why-extract=why2.txt
+# RUN: FileCheck %s --input-file=why2.txt --check-prefix=CHECK2 --match-full-lines --strict-whitespace
+
# CHECK2:reference extracted symbol
# CHECK2-NEXT:main.o a_b.a(a_b.o) a
# CHECK2-NEXT:a_b.a(a_b.o) b.a(b.o) b()
@@ -42,8 +47,8 @@
# CHECK3-NEXT:main.o a_b.a(a_b.o) a
# CHECK4:reference extracted symbol
-# CHECK4-NEXT:a_b.a(a_b.o) b.a(b.o) b()
# CHECK4-NEXT:main.o a_b.a(a_b.o) a
+# CHECK4-NEXT:a_b.a(a_b.o) b.a(b.o) b()
# RUN: ld.lld main.o a_b.a b.a --no-demangle --why-extract=- | FileCheck %s --check-prefix=MANGLED
diff --git a/lld/test/ELF/wrap-extract-real.s b/lld/test/ELF/wrap-extract-real.s
index 52f98e5a82fc..ad08d850e319 100644
--- a/lld/test/ELF/wrap-extract-real.s
+++ b/lld/test/ELF/wrap-extract-real.s
@@ -29,6 +29,20 @@
# WRAP_REAL-NEXT: {{.*}} 0 NOTYPE GLOBAL DEFAULT [[#]] __wrap_foo
# WRAP_REAL-NEXT: {{.*}} 0 NOTYPE GLOBAL DEFAULT [[#]] foo
+## A __real_ reference inside a member extracted for another --wrap entry is
+## seen as well, whatever the --wrap order.
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo_real_bar.s -o foo_real_bar.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 bar.s -o bar.o
+# RUN: ld.lld _start.o ref__real_foo.o --start-lib foo_real_bar.o --end-lib \
+# RUN: --start-lib bar.o --end-lib --wrap foo --wrap bar -o cross1.elf
+# RUN: llvm-readelf --symbols cross1.elf | FileCheck %s --check-prefix=CROSS
+# RUN: ld.lld _start.o ref__real_foo.o --start-lib foo_real_bar.o --end-lib \
+# RUN: --start-lib bar.o --end-lib --wrap bar --wrap foo -o cross2.elf
+# RUN: llvm-readelf --symbols cross2.elf | FileCheck %s --check-prefix=CROSS
+
+# CROSS-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT [[#]] foo
+# CROSS-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT [[#]] bar
+
#--- _start.s
.global _start; _start:; ret
@@ -40,3 +54,9 @@ call __real_foo
#--- foo.s
.global foo; foo:; ret
+
+#--- foo_real_bar.s
+.global foo; foo:; call __real_bar
+
+#--- bar.s
+.global bar; bar:; ret
base-commit: 271af38dfc3aa02e329821daf46730686e52fb1c
--
2.55.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment