// This file implements a mark-sweep garbage collector for -gc-sections. // In this algorithm, vertices are sections and edges are relocations. // Any section that is reachable from a root section is considered alive. #include "mold.h" #include #include template using Feeder = tbb::parallel_do_feeder *>; template static bool is_init_fini(const InputSection &isec) { return isec.shdr.sh_type == SHT_INIT_ARRAY || isec.shdr.sh_type == SHT_FINI_ARRAY || isec.shdr.sh_type == SHT_PREINIT_ARRAY || isec.name().starts_with(".ctors") || isec.name().starts_with(".dtors") || isec.name().starts_with(".init") || isec.name().starts_with(".fini"); } template static bool mark_section(InputSection *isec) { return isec && isec->is_alive && !isec->is_visited.exchange(true); } template static void visit(Context &ctx, InputSection *isec, Feeder &feeder, i64 depth) { assert(isec->is_visited); // A relocation can refer either a section fragment (i.e. a piece of // string in a mergeable string section) or a symbol. Mark all // section fragments as alive. if (isec->rel_fragments) for (i64 i = 0; isec->rel_fragments[i].idx >= 0; i++) isec->rel_fragments[i].frag->is_alive = true; // If this is a text section, .eh_frame may contain records // describing how to handle exceptions for that function. // We want to keep associated .eh_frame records. for (FdeRecord &fde : isec->get_fdes()) for (ElfRel &rel : fde.get_rels().subspan(1)) if (Symbol *sym = isec->file.symbols[rel.r_sym]) if (mark_section(sym->input_section)) feeder.add(sym->input_section); for (ElfRel &rel : isec->get_rels(ctx)) { Symbol &sym = *isec->file.symbols[rel.r_sym]; // Symbol can refer either a section fragment or an input section. // Mark a fragment as alive. if (SectionFragment *frag = sym.get_frag()) { frag->is_alive = true; continue; } if (!mark_section(sym.input_section)) continue; // Mark a section alive. For better performacne, we don't call // `feeder.add` too often. if (depth < 3) visit(ctx, sym.input_section, feeder, depth + 1); else feeder.add(sym.input_section); } } template static tbb::concurrent_vector *> collect_root_set(Context &ctx) { Timer t(ctx, "collect_root_set"); tbb::concurrent_vector *> roots; auto enqueue_section = [&](InputSection *isec) { if (mark_section(isec)) roots.push_back(isec); }; auto enqueue_symbol = [&](Symbol *sym) { if (sym) { if (SectionFragment *frag = sym->get_frag()) frag->is_alive = true; else enqueue_section(sym->input_section); } }; // Add sections that are not subject to garbage collection. tbb::parallel_for_each(ctx.objs, [&](ObjectFile *file) { for (std::unique_ptr> &isec : file->sections) { if (!isec || !isec->is_alive) continue; // -gc-sections discards only SHF_ALLOC sections. If you want to // reduce the amount of non-memory-mapped segments, you should // use `strip` command, compile without debug info or use // -strip-all linker option. if (!(isec->shdr.sh_flags & SHF_ALLOC)) isec->is_visited = true; if (is_init_fini(*isec) || isec->shdr.sh_type == SHT_NOTE) enqueue_section(isec.get()); } }); // Add sections containing exported symbols tbb::parallel_for_each(ctx.objs, [&](ObjectFile *file) { for (Symbol *sym : file->symbols) if (sym->file == file && sym->is_exported) enqueue_symbol(sym); }); // Add sections referenced by root symbols. enqueue_symbol(Symbol::intern(ctx, ctx.arg.entry)); for (std::string_view name : ctx.arg.undefined) enqueue_symbol(Symbol::intern(ctx, name)); // .eh_frame consists of variable-length records called CIE and FDE // records, and they are a unit of inclusion or exclusion. // We just keep all CIEs and everything that are referenced by them. tbb::parallel_for_each(ctx.objs, [&](ObjectFile *file) { for (CieRecord &cie : file->cies) for (ElfRel &rel : cie.get_rels()) enqueue_symbol(file->symbols[rel.r_sym]); }); return roots; } // Mark all reachable sections template static void mark(Context &ctx, tbb::concurrent_vector *> &roots) { Timer t(ctx, "mark"); tbb::parallel_do(roots, [&](InputSection *isec, Feeder &feeder) { visit(ctx, isec, feeder, 0); }); } // Remove unreachable sections template static void sweep(Context &ctx) { Timer t(ctx, "sweep"); static Counter counter("garbage_sections"); tbb::parallel_for_each(ctx.objs, [&](ObjectFile *file) { for (i64 i = 0; i < file->sections.size(); i++) { std::unique_ptr> &isec = file->sections[i]; if (isec && isec->is_alive && !isec->is_visited) { if (ctx.arg.print_gc_sections) SyncOut(ctx) << "removing unused section " << *isec; isec->kill(); counter++; } } }); } // Non-alloc section fragments are not subject of garbage collection. // This function marks such fragments. template static void mark_nonalloc_fragments(Context &ctx) { Timer t(ctx, "mark_nonalloc_fragments"); tbb::parallel_for_each(ctx.objs, [](ObjectFile *file) { for (SectionFragment *frag : file->fragments) if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC)) frag->is_alive = true; }); } template void gc_sections(Context &ctx) { Timer t(ctx, "gc"); mark_nonalloc_fragments(ctx); tbb::concurrent_vector *> roots = collect_root_set(ctx); mark(ctx, roots); sweep(ctx); } template void gc_sections(Context &ctx); template void gc_sections(Context &ctx);