1
1
mirror of https://github.com/rui314/mold.git synced 2024-10-26 21:20:46 +03:00
mold/elf/relocatable.cc
Rui Ueyama b93ad52a73 Enable separate compilation for each source file and target pair
Almost all functions and data types are template in mold, which are
parameterized to target type (e.g. X86_64 or ARM32). I'm generally
happy with that design, but there's one drawback; as we add more
targets, compilation gets slower.

Now we support more than 10 targets, so the compiler has to do 10x
more work than it originall did. As a result, compiling output-chunks.cc
(one of our largest file) took more than 30 seconds to compile on a
Threadripper machine. I hasitated to add a support for a new target
because of this problem. We needed to fix it.

This commit tweaks the build system configs so that the build system
generates bunch of intermediate .cc files. Each generated .cc file
instantiates an original .cc file for one target.

The total amount of required computation doesn't change by this hack,
but now we can parallelize the compilation step. That works well on a
modern multi-core machine.

I generally don't like to tweak build system to workaround a tool's
issue (that's why I'm creating mold in the first place), but it looks
like there's no way to speed up compilation other than this. Or,
maybe we can write a faster C++ compiler someday, but that's another
topic.
2022-09-23 22:28:40 +08:00

596 lines
17 KiB
C++

// This file implements the -r or ---relocatable option. If the option
// is given to a linker, the linker creates not an executable nor a
// shared library file but instead create another object file by
// combining input object files.
//
// The behavior of a linker with -r is pretty different from the one
// without -r, and adding code for -r to the regular code path could
// severely complicate it. Therefore, we implement -r as an
// independent feature from others. That's why this file share only a
// small amount of code with other files.
//
// Since the -r option is a minor feature, we don't aim for speed in
// this file. This is a "better than nothing" implementation of the -r
// option.
//
// Here is the strategy as to how to combine multiple object files
// into one:
//
// - Regular sections containing opaque data (e.g. ".text" or
// ".data") are just copied as-is without merging.
//
// - .symtab, .strtab and .shstrtab are merged.
//
// - COMDAT groups are uniquified.
//
// - Relocation sections are copied one by one, but we need to fix
// symbol indices.
#include "mold.h"
#include "../archive-file.h"
#include "../output-file.h"
#include <unordered_map>
#include <unordered_set>
namespace mold::elf {
template <typename E> class RObjectFile;
template <typename E>
class RChunk {
public:
RChunk() {
out_shdr.sh_addralign = 1;
}
virtual ~RChunk() = default;
virtual void update_shdr(Context<E> &ctx) {}
virtual void write_to(Context<E> &ctx) = 0;
std::string_view name;
i64 shndx = 0;
ElfShdr<E> in_shdr = {};
ElfShdr<E> out_shdr = {};
};
template <typename E>
class RInputSection : public RChunk<E> {
public:
RInputSection(Context<E> &ctx, RObjectFile<E> &file, const ElfShdr<E> &shdr);
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
RObjectFile<E> &file;
};
template <typename E>
class RSymtabSection : public RChunk<E> {
public:
RSymtabSection() {
this->name = ".symtab";
this->out_shdr.sh_type = SHT_SYMTAB;
this->out_shdr.sh_entsize = sizeof(ElfSym<E>);
this->out_shdr.sh_addralign = sizeof(Word<E>);
}
void add_local_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
void add_global_symbol(Context<E> &ctx, RObjectFile<E> &file, i64 idx);
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
std::unordered_map<std::string_view, i64> sym_map;
std::vector<ElfSym<E>> syms{1};
};
template <typename E>
class RStrtabSection : public RChunk<E> {
public:
RStrtabSection(std::string_view name) {
this->name = name;
this->out_shdr.sh_type = SHT_STRTAB;
this->out_shdr.sh_size = 1;
}
i64 add_string(std::string_view str);
void write_to(Context<E> &ctx) override;
std::unordered_map<std::string_view, i64> strings;
};
template <typename E>
class ROutputEhdr : public RChunk<E> {
public:
ROutputEhdr() {
this->out_shdr.sh_size = sizeof(ElfEhdr<E>);
}
void write_to(Context<E> &ctx) override;
};
template <typename E>
class ROutputShdr : public RChunk<E> {
public:
ROutputShdr() {
this->out_shdr.sh_size = sizeof(ElfShdr<E>);
}
void update_shdr(Context<E> &ctx) override;
void write_to(Context<E> &ctx) override;
};
template <typename E>
class RObjectFile {
public:
RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf, bool is_alive);
void remove_comdats(Context<E> &ctx,
std::unordered_set<std::string_view> &groups);
template <typename T>
std::span<T> get_data(Context<E> &ctx, const ElfShdr<E> &shdr);
MappedFile<Context<E>> &mf;
std::span<ElfShdr<E>> elf_sections;
std::vector<std::unique_ptr<RInputSection<E>>> sections;
std::span<const ElfSym<E>> syms;
std::vector<i64> symidx;
std::unordered_set<std::string_view> defined_syms;
std::unordered_set<std::string_view> undef_syms;
i64 symtab_shndx = 0;
i64 first_global = 0;
bool is_alive;
const char *strtab = nullptr;
const char *shstrtab = nullptr;
};
template <typename E>
void RSymtabSection<E>::add_local_symbol(Context<E> &ctx, RObjectFile<E> &file,
i64 idx) {
ElfSym<E> sym = file.syms[idx];
assert(sym.st_bind == STB_LOCAL);
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common()) {
if (!file.sections[sym.st_shndx])
return;
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
}
std::string_view name = file.strtab + sym.st_name;
sym.st_name = ctx.r_strtab->add_string(name);
file.symidx[idx] = syms.size();
syms.push_back(sym);
}
template <typename E>
void RSymtabSection<E>::add_global_symbol(Context<E> &ctx, RObjectFile<E> &file,
i64 idx) {
ElfSym<E> sym = file.syms[idx];
assert(sym.st_bind != STB_LOCAL);
std::string_view name = file.strtab + sym.st_name;
auto [it, inserted] = sym_map.insert({name, syms.size()});
if (inserted) {
if (!sym.is_undef() && !sym.is_abs() && !sym.is_common())
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
sym.st_name = ctx.r_strtab->add_string(name);
file.symidx[idx] = syms.size();
syms.push_back(sym);
return;
}
file.symidx[idx] = it->second;
ElfSym<E> &existing = syms[it->second];
if (existing.is_undef() && !sym.is_undef()) {
if (!sym.is_abs() && !sym.is_common())
sym.st_shndx = file.sections[sym.st_shndx]->shndx;
sym.st_name = existing.st_name;
existing = sym;
}
}
template <typename E>
void RSymtabSection<E>::update_shdr(Context<E> &ctx) {
this->out_shdr.sh_size = syms.size() * sizeof(ElfSym<E>);
this->out_shdr.sh_link = ctx.r_strtab->shndx;
}
template <typename E>
void RSymtabSection<E>::write_to(Context<E> &ctx) {
ElfSym<E> *buf = (ElfSym<E> *)(ctx.buf + this->out_shdr.sh_offset);
for (i64 i = 1; i < syms.size(); i++)
buf[i] = syms[i];
}
template <typename E>
RInputSection<E>::RInputSection(Context<E> &ctx, RObjectFile<E> &file,
const ElfShdr<E> &shdr)
: file(file) {
this->name = file.shstrtab + shdr.sh_name;
this->in_shdr = shdr;
this->out_shdr = shdr;
}
template <typename E>
void RInputSection<E>::update_shdr(Context<E> &ctx) {
switch (this->in_shdr.sh_type) {
case SHT_GROUP:
this->out_shdr.sh_link = ctx.r_symtab->shndx;
this->out_shdr.sh_info = file.symidx[this->in_shdr.sh_info];
break;
case SHT_REL:
case SHT_RELA:
this->out_shdr.sh_link = ctx.r_symtab->shndx;
this->out_shdr.sh_info = file.sections[this->in_shdr.sh_info]->shndx;
break;
default:
if (this->in_shdr.sh_link) {
std::unique_ptr<RInputSection<E>> &sec =
file.sections[this->in_shdr.sh_info];
if (sec)
this->out_shdr.sh_link = sec->shndx;
else if (this->in_shdr.sh_link == file.symtab_shndx)
this->out_shdr.sh_link = ctx.r_symtab->shndx;
}
}
}
template <typename E>
void RInputSection<E>::write_to(Context<E> &ctx) {
if (this->in_shdr.sh_type == SHT_NOBITS)
return;
std::span<u8> contents = file.template get_data<u8>(ctx, this->in_shdr);
memcpy(ctx.buf + this->out_shdr.sh_offset, contents.data(), contents.size());
switch (this->in_shdr.sh_type) {
case SHT_GROUP: {
U32<E> *mem = (U32<E> *)(ctx.buf + this->out_shdr.sh_offset);
for (i64 i = 1; i < this->out_shdr.sh_size / sizeof(u32); i++)
mem[i] = file.sections[mem[i]]->shndx;
break;
}
case SHT_REL:
case SHT_RELA: {
ElfRel<E> *rel = (ElfRel<E> *)(ctx.buf + this->out_shdr.sh_offset);
i64 size = this->out_shdr.sh_size / sizeof(ElfRel<E>);
for (i64 i = 0; i < size; i++) {
const ElfSym<E> &sym = file.syms[rel[i].r_sym];
if (sym.is_undef() || sym.is_abs() || sym.is_common() ||
file.sections[sym.st_shndx])
rel[i].r_sym = file.symidx[rel[i].r_sym];
else
memset(rel + i, 0, sizeof(ElfRel<E>));
}
i64 i = 0;
i64 j = 0;
for (; j < size; j++)
if (rel[j].r_type)
rel[i++] = rel[j];
for (; i < size; i++)
memset(rel + i, 0, sizeof(ElfRel<E>));
break;
}
}
}
template <typename E>
i64 RStrtabSection<E>::add_string(std::string_view str) {
auto [it, inserted] = strings.insert({str, this->out_shdr.sh_size});
if (inserted)
this->out_shdr.sh_size += str.size() + 1;
return it->second;
}
template <typename E>
void RStrtabSection<E>::write_to(Context<E> &ctx) {
for (auto [str, offset] : strings)
memcpy(ctx.buf + this->out_shdr.sh_offset + offset, str.data(), str.size());
}
template <typename E>
void ROutputEhdr<E>::write_to(Context<E> &ctx) {
ElfEhdr<E> &hdr = *(ElfEhdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
memcpy(&hdr.e_ident, "\177ELF", 4);
hdr.e_ident[EI_CLASS] = E::is_64 ? ELFCLASS64 : ELFCLASS32;
hdr.e_ident[EI_DATA] = E::is_le ? ELFDATA2LSB : ELFDATA2MSB;
hdr.e_ident[EI_VERSION] = EV_CURRENT;
hdr.e_type = ET_REL;
hdr.e_machine = E::e_machine;
hdr.e_version = EV_CURRENT;
hdr.e_shoff = ctx.r_shdr->out_shdr.sh_offset;
hdr.e_ehsize = sizeof(ElfEhdr<E>);
hdr.e_shentsize = sizeof(ElfShdr<E>);
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
hdr.e_shnum = ctx.r_shdr->out_shdr.sh_size / sizeof(ElfShdr<E>);
hdr.e_shstrndx = ctx.r_shstrtab->shndx;
}
template <typename E>
void ROutputShdr<E>::update_shdr(Context<E> &ctx) {
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
this->out_shdr.sh_size += sizeof(ElfShdr<E>);
}
template <typename E>
void ROutputShdr<E>::write_to(Context<E> &ctx) {
ElfShdr<E> *hdr = (ElfShdr<E> *)(ctx.buf + this->out_shdr.sh_offset);
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
hdr[chunk->shndx] = chunk->out_shdr;
}
template <typename E>
RObjectFile<E>::RObjectFile(Context<E> &ctx, MappedFile<Context<E>> &mf,
bool is_alive)
: mf(mf), is_alive(is_alive) {
// Read ELF header and section header
ElfEhdr<E> &ehdr = *(ElfEhdr<E> *)mf.data;
ElfShdr<E> *sh_begin = (ElfShdr<E> *)(mf.data + ehdr.e_shoff);
i64 num_sections = (ehdr.e_shnum == 0) ? sh_begin->sh_size : ehdr.e_shnum;
elf_sections = {sh_begin, sh_begin + num_sections};
sections.resize(num_sections);
// Read .shstrtab
i64 shstrtab_idx = (ehdr.e_shstrndx == SHN_XINDEX)
? sh_begin->sh_link : ehdr.e_shstrndx;
shstrtab = (char *)(mf.data + elf_sections[shstrtab_idx].sh_offset);
// Read .symtab
for (i64 i = 1; i < elf_sections.size(); i++) {
ElfShdr<E> &shdr = elf_sections[i];
if (shdr.sh_type == SHT_SYMTAB) {
syms = get_data<const ElfSym<E>>(ctx, shdr);
strtab = (char *)(mf.data + elf_sections[shdr.sh_link].sh_offset);
symtab_shndx = i;
first_global = shdr.sh_info;
break;
}
}
symidx.resize(syms.size());
// Read sections
for (i64 i = 1; i < elf_sections.size(); i++) {
ElfShdr<E> &shdr = elf_sections[i];
switch (shdr.sh_type) {
case SHT_NULL:
case SHT_SYMTAB:
case SHT_STRTAB:
break;
default:
sections[i].reset(new RInputSection(ctx, *this, shdr));
}
}
// Read global symbols
for (i64 i = first_global; i < syms.size(); i++) {
std::string_view name = strtab + syms[i].st_name;
if (syms[i].is_undef())
undef_syms.insert(name);
else
defined_syms.insert(name);
}
}
// Remove duplicate comdat groups
template <typename E>
void RObjectFile<E>::remove_comdats(Context<E> &ctx,
std::unordered_set<std::string_view> &groups) {
for (i64 i = 1; i < sections.size(); i++) {
if (!sections[i])
continue;
ElfShdr<E> &shdr = sections[i]->in_shdr;
if (shdr.sh_type != SHT_GROUP)
continue;
// Get a comdat group signature and insert it into a set.
const ElfSym<E> &sym = syms[shdr.sh_info];
std::string_view signature = strtab + sym.st_name;
if (groups.insert(signature).second)
continue;
// If it is a duplicate, remove it and its members.
for (i64 j : this->template get_data<U32<E>>(ctx, shdr).subspan(1))
sections[j] = nullptr;
sections[i] = nullptr;
}
}
template <typename E>
template <typename T>
std::span<T> RObjectFile<E>::get_data(Context<E> &ctx, const ElfShdr<E> &shdr) {
T *begin = (T *)(mf.data + shdr.sh_offset);
T *end = (T *)(mf.data + shdr.sh_offset + shdr.sh_size);
return {begin, end};
}
template <typename E>
static std::vector<std::unique_ptr<RObjectFile<E>>>
open_files(Context<E> &ctx, std::span<std::string> args) {
std::vector<std::unique_ptr<RObjectFile<E>>> files;
bool whole_archive = false;
while (!args.empty()) {
const std::string &arg = args[0];
args = args.subspan(1);
if (arg == "--whole-archive") {
whole_archive = true;
continue;
}
if (arg == "--no-whole-archive") {
whole_archive = false;
continue;
}
if (arg.starts_with("--version-script=") ||
arg.starts_with("--dynamic-list=") ||
arg.starts_with("--export-dynamic-symbol=") ||
arg.starts_with("--export-dynamic-symbol-list="))
continue;
MappedFile<Context<E>> *mf = nullptr;
if (arg == "-l") {
mf = find_library(ctx, arg.substr(2));
} else {
if (arg.starts_with('-'))
continue;
mf = MappedFile<Context<E>>::must_open(ctx, arg);
}
switch (get_file_type(mf)) {
case FileType::ELF_OBJ:
files.emplace_back(new RObjectFile<E>(ctx, *mf, true));
break;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
files.emplace_back(new RObjectFile<E>(ctx, *child, whole_archive));
break;
default:
break;
}
}
return files;
}
template <typename E>
static i64 assign_offsets(Context<E> &ctx) {
i64 offset = 0;
for (RChunk<E> *chunk : ctx.r_chunks) {
offset = align_to(offset, chunk->out_shdr.sh_addralign);
chunk->out_shdr.sh_offset = offset;
offset += chunk->out_shdr.sh_size;
}
return offset;
}
static bool contains(std::unordered_set<std::string_view> &a,
std::unordered_set<std::string_view> &b) {
for (std::string_view x : b)
if (a.contains(x))
return true;
return false;
}
template <typename E>
void combine_objects(Context<E> &ctx, std::span<std::string> file_args) {
// Read object files
std::vector<std::unique_ptr<RObjectFile<E>>> files = open_files(ctx, file_args);
// Identify needed objects
std::unordered_set<std::string_view> undef_syms;
auto add_syms = [&](RObjectFile<E> &file) {
undef_syms.insert(file.undef_syms.begin(), file.undef_syms.end());
for (std::string_view name : file.defined_syms)
undef_syms.erase(name);
file.is_alive = true;
};
for (std::unique_ptr<RObjectFile<E>> &file : files)
if (file->is_alive)
add_syms(*file);
for (;;) {
bool added = false;
for (std::unique_ptr<RObjectFile<E>> &file : files) {
if (!file->is_alive && contains(undef_syms, file->defined_syms)) {
add_syms(*file);
added = true;
}
}
if (!added)
break;
}
std::erase_if(files, [](std::unique_ptr<RObjectFile<E>> &file) {
return !file->is_alive;
});
// Remove duplicate comdat groups
std::unordered_set<std::string_view> comdat_groups;
for (std::unique_ptr<RObjectFile<E>> &file : files)
file->remove_comdats(ctx, comdat_groups);
// Create headers and linker-synthesized sections
ROutputEhdr<E> ehdr;
ROutputShdr<E> shdr;
RSymtabSection<E> symtab;
RStrtabSection<E> shstrtab(".shstrtab");
RStrtabSection<E> strtab(".strtab");
ctx.r_chunks.push_back(&ehdr);
ctx.r_chunks.push_back(&shstrtab);
ctx.r_chunks.push_back(&strtab);
ctx.r_ehdr = &ehdr;
ctx.r_shdr = &shdr;
ctx.r_shstrtab = &shstrtab;
ctx.r_strtab = &strtab;
ctx.r_symtab = &symtab;
// Add input sections to output sections
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (std::unique_ptr<RInputSection<E>> &sec : file->sections)
if (sec)
ctx.r_chunks.push_back(sec.get());
ctx.r_chunks.push_back(&symtab);
ctx.r_chunks.push_back(&shdr);
// Assign output section indices
i64 shndx = 1;
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk != &ehdr && chunk != &shdr)
chunk->shndx = shndx++;
// Add section names to .shstrtab
for (RChunk<E> *chunk : ctx.r_chunks)
if (chunk->shndx)
chunk->out_shdr.sh_name = shstrtab.add_string(chunk->name);
// Copy symbols from input objects to an output object
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (i64 i = 1; i < file->first_global; i++)
symtab.add_local_symbol(ctx, *file, i);
symtab.out_shdr.sh_info = symtab.syms.size();
for (std::unique_ptr<RObjectFile<E>> &file : files)
for (i64 i = file->first_global; i < file->syms.size(); i++)
symtab.add_global_symbol(ctx, *file, i);
// Finalize section header
for (RChunk<E> *chunk : ctx.r_chunks)
chunk->update_shdr(ctx);
// Open an output file
i64 filesize = assign_offsets(ctx);
std::unique_ptr<OutputFile<Context<E>>> out =
OutputFile<Context<E>>::open(ctx, ctx.arg.output, filesize, 0666);
memset(out->buf, 0, filesize);
ctx.buf = out->buf;
// Write to the output file
for (RChunk<E> *chunk : ctx.r_chunks)
chunk->write_to(ctx);
out->close(ctx);
}
using E = MOLD_TARGET;
template void combine_objects(Context<E> &, std::span<std::string>);
} // namespace mold::elf