diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -110,6 +110,7 @@ llvm::StringRef optRemarksPasses; llvm::StringRef optRemarksFormat; llvm::StringRef progName; + llvm::StringRef printArchiveStats; llvm::StringRef printSymbolOrder; llvm::StringRef soName; llvm::StringRef sysroot; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -90,6 +90,7 @@ inputSections.clear(); outputSections.clear(); + archiveFiles.clear(); binaryFiles.clear(); bitcodeFiles.clear(); lazyObjFiles.clear(); @@ -955,6 +956,7 @@ args.hasFlag(OPT_print_icf_sections, OPT_no_print_icf_sections, false); config->printGcSections = args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false); + config->printArchiveStats = args.getLastArgValue(OPT_print_archive_stats); config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order); config->rpath = getRpath(args); diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -326,6 +326,9 @@ // more than once.) void fetch(const Archive::Symbol &sym); + size_t getMemberCount() const; + size_t getFetchedMemberCount() const { return seen.size(); } + private: std::unique_ptr file; llvm::DenseSet seen; @@ -387,6 +390,7 @@ std::string replaceThinLTOSuffix(StringRef path); +extern std::vector archiveFiles; extern std::vector binaryFiles; extern std::vector bitcodeFiles; extern std::vector lazyObjFiles; diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -55,6 +55,7 @@ namespace elf { bool InputFile::isInGroup; uint32_t InputFile::nextGroupId; +std::vector archiveFiles; std::vector binaryFiles; std::vector bitcodeFiles; std::vector lazyObjFiles; @@ -173,6 +174,7 @@ // .a file if (auto *f = dyn_cast(file)) { + archiveFiles.push_back(f); f->parse(); return; } @@ -1165,6 +1167,19 @@ parseFile(file); } +size_t ArchiveFile::getMemberCount() const { + size_t count = 0; + Error err = Error::success(); + for (const Archive::Child &c : file->children(err)) { + (void)c; + ++count; + } + // This function is used by --print-archive-stats=, where an error does not + // really matter. + consumeError(std::move(err)); + return count; +} + unsigned SharedFile::vernauxNum; // Parse the version definitions in the object file if present, and return a diff --git a/lld/ELF/MapFile.h b/lld/ELF/MapFile.h --- a/lld/ELF/MapFile.h +++ b/lld/ELF/MapFile.h @@ -13,6 +13,7 @@ namespace elf { void writeMapFile(); void writeCrossReferenceTable(); +void writeArchiveStats(); } // namespace elf } // namespace lld diff --git a/lld/ELF/MapFile.cpp b/lld/ELF/MapFile.cpp --- a/lld/ELF/MapFile.cpp +++ b/lld/ELF/MapFile.cpp @@ -259,5 +259,23 @@ } } +void writeArchiveStats() { + if (config->printArchiveStats.empty()) + return; + + std::error_code ec; + raw_fd_ostream os(config->printArchiveStats, ec, sys::fs::OF_None); + if (ec) { + error("--print-archive-stats=: cannot open " + config->printArchiveStats + + ": " + ec.message()); + return; + } + + os << "members\tfetched\tarchive\n"; + for (const ArchiveFile *f : archiveFiles) + os << f->getMemberCount() << '\t' << f->getFetchedMemberCount() << '\t' + << f->getName() << '\n'; +} + } // namespace elf } // namespace lld diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -294,6 +294,10 @@ "List identical folded sections", "Do not list identical folded sections (default)">; +def print_archive_stats: J<"print-archive-stats=">, + HelpText<"Write archive usage statistics to the specified file. " + "Print the numbers of members and fetched members for each archive">; + defm print_symbol_order: Eq<"print-symbol-order", "Print a symbol order specified by --call-graph-ordering-file into the specified file">; diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -601,11 +601,13 @@ for (OutputSection *sec : outputSections) sec->addr = 0; - // Handle --print-map(-M)/--Map and --cref. Dump them before checkSections() - // because the files may be useful in case checkSections() or openFile() - // fails, for example, due to an erroneous file size. + // Handle --print-map(-M)/--Map, --cref and --print-archive-stats=. Dump them + // before checkSections() because the files may be useful in case + // checkSections() or openFile() fails, for example, due to an erroneous file + // size. writeMapFile(); writeCrossReferenceTable(); + writeArchiveStats(); if (config->checkSections) checkSections(); diff --git a/lld/docs/ld.lld.1 b/lld/docs/ld.lld.1 --- a/lld/docs/ld.lld.1 +++ b/lld/docs/ld.lld.1 @@ -414,6 +414,9 @@ List identical folded sections. .It Fl -print-map Print a link map to the standard output. +.It Fl -print-archive-stats Ns = Ns Ar file +Write archive usage statistics to the specified file. +Print the numbers of members and fetched members for each archive. .It Fl -push-state Save the current state of .Fl -as-needed , diff --git a/lld/test/ELF/print-archive-stats.s b/lld/test/ELF/print-archive-stats.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/print-archive-stats.s @@ -0,0 +1,38 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o +# RUN: echo '.globl weak; weak:' | llvm-mc -filetype=obj -triple=x86_64 - -o %tweak.o +# RUN: echo '.global foo; foo:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: echo '.global bar; bar:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t2.o +# RUN: echo '.global baz; baz:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t3.o +# RUN: rm -f %tweak.a && llvm-ar rc %tweak.a %tweak.o +# RUN: rm -f %t1.a && llvm-ar rc %t1.a %t1.o %t2.o %t3.o + +# RUN: ld.lld %t.o %tweak.a %t1.a --print-archive-stats=%t.txt -o /dev/null +# RUN: FileCheck --input-file=%t.txt -DT=%t %s --match-full-lines --strict-whitespace + +## Fetches 0 member from %tweak.a and 2 members from %t1.a +# CHECK:members fetched archive +# CHECK-NEXT:1 0 [[T]]weak.a +# CHECK-NEXT:3 2 [[T]]1.a + +## - means stdout. +# RUN: ld.lld %t.o %tweak.a %t1.a --print-archive-stats=- -o /dev/null | diff %t.txt - + +## The second %t1.a has 0 fetched member. +# RUN: ld.lld %t.o %tweak.a %t1.a %t1.a --print-archive-stats=- -o /dev/null | \ +# RUN: FileCheck --check-prefix=CHECK2 %s +# CHECK2: members fetched archive +# CHECK2-NEXT: 1 0 {{.*}}weak.a +# CHECK2-NEXT: 3 2 {{.*}}1.a +# CHECK2-NEXT: 3 0 {{.*}}1.a + +# RUN: not ld.lld -shared %t.o --print-archive-stats=/ -o /dev/null 2>&1 | FileCheck --check-prefix=ERR %s +# ERR: error: --print-archive-stats=: cannot open /: {{.*}} + +.globl _start +.weak weak +_start: + call foo + call bar + call weak