Skip to content

Commit

Permalink
[ELF] Make symbol resolution deterministic
Browse files Browse the repository at this point in the history
Previously, our parallel symbol resolution algorithm was not
deterministic in edge cases. As an example, consider the following two
source files:

  foo.c:
    inline void fn1() { ... }

  bar.c:
    inline void fn1() { ... }
    void fn2() { ... }

Let's say you compile these files and put them into an archive file.
If mold decided to pull out `foo.o` first for `fn1` and then `bar.o`
for `fn2`, then both `foo.o` and `bar.o` are included into a result.
However, if mold pulled out `bar.o` first, then there's no chance for
`foo.o` to be pulled out, so only `foo.o` would be included into a
result.

The algorithm implemented in this commit should be deterministic.
We do not override symbols when we mark live objects.
  • Loading branch information
rui314 committed Jan 12, 2022
1 parent b6316ef commit ce5749c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 185 deletions.
2 changes: 1 addition & 1 deletion elf/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ static int elf_main(int argc, char **argv) {
// Create a dummy file containing linker-synthesized symbols
// (e.g. `__bss_start`).
ctx.internal_obj = create_internal_file(ctx);
ctx.internal_obj->resolve_regular_symbols(ctx);
ctx.internal_obj->resolve_obj_symbols(ctx, true);
ctx.objs.push_back(ctx.internal_obj);

// Beyond this point, no new files will be added to ctx.objs
Expand Down
22 changes: 16 additions & 6 deletions elf/mold.h
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,12 @@ class InputFile {

ElfShdr<E> *find_section(i64 type);

virtual void
mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) = 0;

virtual std::span<Symbol<E> *> get_global_syms() = 0;

MappedFile<Context<E>> *mf;
std::span<ElfShdr<E>> elf_sections;
std::vector<Symbol<E> *> symbols;
Expand All @@ -910,11 +916,9 @@ class ObjectFile : public InputFile<E> {

void parse(Context<E> &ctx);
void register_section_pieces(Context<E> &ctx);
void resolve_lazy_symbols(Context<E> &ctx);
void resolve_regular_symbols(Context<E> &ctx);
void resolve_obj_symbols(Context<E> &ctx, bool register_common);
void mark_live_objects(Context<E> &ctx,
std::function<void(ObjectFile<E> *)> feeder);
void resolve_common_symbols(Context<E> &ctx);
std::function<void(InputFile<E> *)> feeder) override;
void convert_undefined_weak_symbols(Context<E> &ctx);
void resolve_comdat_groups();
void eliminate_duplicate_comdat_groups();
Expand All @@ -926,7 +930,7 @@ class ObjectFile : public InputFile<E> {

i64 get_shndx(const ElfSym<E> &esym);
InputSection<E> *get_section(const ElfSym<E> &esym);
std::span<Symbol<E> *> get_global_syms();
std::span<Symbol<E> *> get_global_syms() override;

std::string archive_name;
std::vector<std::unique_ptr<InputSection<E>>> sections;
Expand Down Expand Up @@ -992,6 +996,13 @@ class SharedFile : public InputFile<E> {
std::vector<Symbol<E> *> find_aliases(Symbol<E> *sym);
bool is_readonly(Context<E> &ctx, Symbol<E> *sym);

void mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) override;

std::span<Symbol<E> *> get_global_syms() override {
return globals;
}

std::string soname;
std::vector<std::string_view> version_strings;
std::vector<Symbol<E> *> globals;
Expand Down Expand Up @@ -1534,7 +1545,6 @@ class Symbol {
tbb::spin_mutex mu;
std::atomic_uint8_t visibility = STV_DEFAULT;

u8 is_lazy : 1 = false;
u8 is_weak : 1 = false;
u8 write_to_symtab : 1 = false;
u8 traced : 1 = false;
Expand Down
178 changes: 61 additions & 117 deletions elf/object-file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -768,20 +768,17 @@ void ObjectFile<E>::parse(Context<E> &ctx) {
//
// 1. Strong defined symbol
// 2. Weak defined symbol
// 3. Strong defined symbol in a DSO
// 4. Weak defined symbol in a DSO
// 5. Strong or weak defined symbol in an archive
// 6. Common symbol
// 7. Unclaimed (nonexistent) symbol
// 3. Strong defined symbol in a DSO/archive
// 4. Weak Defined symbol in a DSO/archive
// 5. Common symbol
// 6. Unclaimed (nonexistent) symbol
//
// Ties are broken by file priority.
template <typename E>
static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
if (esym.is_common())
return (6 << 24) + file->priority;
if (is_lazy)
return (5 << 24) + file->priority;
if (file->is_dso) {
if (file->is_dso || is_lazy) {
if (esym.is_weak())
return (4 << 24) + file->priority;
return (3 << 24) + file->priority;
Expand All @@ -794,27 +791,8 @@ static u64 get_rank(InputFile<E> *file, const ElfSym<E> &esym, bool is_lazy) {
template <typename E>
static u64 get_rank(const Symbol<E> &sym) {
if (!sym.file)
return 7 << 24;
return get_rank(sym.file, sym.esym(), sym.is_lazy);
}

template <typename E>
void ObjectFile<E>::override_symbol(Context<E> &ctx, Symbol<E> &sym,
const ElfSym<E> &esym, i64 symidx) {
sym.file = this;
sym.input_section = esym.is_abs() ? nullptr : get_section(esym);

if (SectionFragmentRef<E> &ref = sym_fragments[symidx]; ref.frag)
sym.value = ref.addend;
else
sym.value = esym.st_value;

sym.sym_idx = symidx;
sym.ver_idx = ctx.arg.default_version;
sym.is_lazy = false;
sym.is_weak = esym.is_weak();
sym.is_imported = false;
sym.is_exported = false;
return 6 << 24;
return get_rank(sym.file, sym.esym(), !sym.file->is_alive);
}

template <typename E>
Expand Down Expand Up @@ -844,58 +822,53 @@ void ObjectFile<E>::merge_visibility(Context<E> &ctx, Symbol<E> &sym,
}

template <typename E>
void ObjectFile<E>::resolve_lazy_symbols(Context<E> &ctx) {
assert(is_in_lib);

void ObjectFile<E>::resolve_obj_symbols(Context<E> &ctx, bool register_common) {
for (i64 i = first_global; i < this->symbols.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
const ElfSym<E> &esym = elf_syms[i];

if (esym.is_undef())
continue;
if (!register_common && esym.is_common())
continue;

std::lock_guard lock(sym.mu);
if (get_rank(this, esym, true) < get_rank(sym)) {
if (get_rank(this, esym, !this->is_alive) < get_rank(sym)) {
sym.file = this;
sym.sym_idx = i;
sym.is_lazy = true;
sym.is_weak = false;
if (sym.traced)
SyncOut(ctx) << "trace-symbol: " << *this
<< ": lazy definition of " << sym;
}
}
}

template <typename E>
void ObjectFile<E>::resolve_regular_symbols(Context<E> &ctx) {
assert(!is_in_lib);
if (esym.is_abs() || esym.is_common())
sym.input_section = nullptr;
else
sym.input_section = get_section(esym);

for (i64 i = first_global; i < this->symbols.size(); i++) {
Symbol<E> &sym = *this->symbols[i];
const ElfSym<E> &esym = elf_syms[i];
if (esym.is_undef() || esym.is_common())
continue;
if (SectionFragmentRef<E> &ref = sym_fragments[i]; ref.frag)
sym.value = ref.addend;
else
sym.value = esym.st_value;

std::lock_guard lock(sym.mu);
if (get_rank(this, esym, false) < get_rank(sym))
override_symbol(ctx, sym, esym, i);
sym.sym_idx = i;
sym.ver_idx = ctx.arg.default_version;
sym.is_weak = esym.is_weak();
sym.is_imported = false;
sym.is_exported = false;
}
}
}

template <typename E>
void
ObjectFile<E>::mark_live_objects(Context<E> &ctx,
std::function<void(ObjectFile<E> *)> feeder) {
std::function<void(InputFile<E> *)> feeder) {
assert(this->is_alive);

for (i64 i = first_global; i < this->symbols.size(); i++) {
const ElfSym<E> &esym = elf_syms[i];
Symbol<E> &sym = *this->symbols[i];

u8 visibility = esym.st_visibility;
if (esym.is_defined() && exclude_libs)
visibility = STV_HIDDEN;
merge_visibility(ctx, sym, visibility);
merge_visibility(ctx, sym, STV_HIDDEN);
else
merge_visibility(ctx, sym, esym.st_visibility);

if (sym.traced) {
if (esym.is_defined())
Expand All @@ -906,66 +879,20 @@ ObjectFile<E>::mark_live_objects(Context<E> &ctx,
SyncOut(ctx) << "trace-symbol: " << *this << ": reference to " << sym;
}

std::lock_guard lock(sym.mu);

if (esym.is_undef()) {
if (!esym.is_weak() && sym.file && !sym.file->is_alive.exchange(true)) {
feeder((ObjectFile<E> *)sym.file);
if (sym.traced)
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
}
continue;
}

// A common symbol pulls in other object file from an archive if and
// only if doing so makes it to be resolved to a non-common symbol.
// In other words, we don't pull out an object file from an archive
// if doing so would end up overwriting a common symbol with a
// common symbol.
if (esym.is_common()) {
if (sym.file && !sym.esym().is_common() &&
!sym.file->is_alive.exchange(true)) {
feeder((ObjectFile<E> *)sym.file);
if (sym.traced)
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
}
if (esym.is_weak())
continue;
}

if (get_rank(this, esym, false) < get_rank(sym))
override_symbol(ctx, sym, esym, i);
}
}

template <typename E>
void ObjectFile<E>::resolve_common_symbols(Context<E> &ctx) {
if (!has_common_symbol)
return;

for (i64 i = first_global; i < this->symbols.size(); i++) {
const ElfSym<E> &esym = elf_syms[i];
if (!esym.is_common())
std::lock_guard lock(sym.mu);
if (!sym.file)
continue;

Symbol<E> &sym = *this->symbols[i];
std::lock_guard lock(sym.mu);

if (get_rank(this, esym, false) < get_rank(sym)) {
sym.file = this;
sym.input_section = nullptr;
sym.value = esym.st_value;
sym.sym_idx = i;
sym.ver_idx = ctx.arg.default_version;
sym.is_lazy = false;
sym.is_weak = false;
sym.is_imported = false;
sym.is_exported = false;
bool keep = esym.is_undef() || (esym.is_common() && !sym.esym().is_common());
if (keep && !sym.file->is_alive.exchange(true)) {
feeder(sym.file);

if (sym.traced)
SyncOut(ctx) << "trace-symbol: " << *this
<< ": common definition of " << sym;
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
}
}
}
Expand Down Expand Up @@ -1029,7 +956,6 @@ void ObjectFile<E>::claim_unresolved_symbols(Context<E> &ctx) {
sym.input_section = nullptr;
sym.value = 0;
sym.sym_idx = i;
sym.is_lazy = false;
sym.is_weak = false;
sym.is_exported = false;
};
Expand Down Expand Up @@ -1135,7 +1061,6 @@ void ObjectFile<E>::convert_common_symbols(Context<E> &ctx) {
sym.value = 0;
sym.sym_idx = i;
sym.ver_idx = ctx.arg.default_version;
sym.is_lazy = false;
sym.is_weak = false;
sym.is_imported = false;
sym.is_exported = false;
Expand Down Expand Up @@ -1373,19 +1298,38 @@ void SharedFile<E>::resolve_dso_symbols(Context<E> &ctx) {

std::lock_guard lock(sym.mu);

if (!sym.file || this->priority < sym.file->priority) {
if (get_rank(this, esym, false) <= get_rank(sym)) {
sym.file = this;
sym.input_section = nullptr;
sym.value = esym.st_value;
sym.sym_idx = i;
sym.ver_idx = versyms[i];
sym.is_weak = true;
sym.is_weak = false;
sym.is_imported = false;
sym.is_exported = false;
}
}
}

template <typename E>
void
SharedFile<E>::mark_live_objects(Context<E> &ctx,
std::function<void(InputFile<E> *)> feeder) {
i64 first_global = symtab_sec->sh_info;
std::span<ElfSym<E>> esyms =
this->template get_data<ElfSym<E>>(ctx, *symtab_sec);

for (i64 i = first_global; i < esyms.size(); i++) {
Symbol<E> &sym = *globals[i - first_global];
const ElfSym<E> &esym = esyms[i];

if (esym.is_undef() && sym.file && sym.file != this &&
!sym.file->is_alive.exchange(true)) {
feeder(sym.file);

if (sym.traced)
SyncOut(ctx) << "trace-symbol: " << *sym.file << ": definition of "
<< sym;
SyncOut(ctx) << "trace-symbol: " << *this << " keeps " << *sym.file
<< " for " << sym;
}
}
}
Expand Down
Loading

0 comments on commit ce5749c

Please sign in to comment.