diff --git a/llvm/docs/CommandGuide/dsymutil.rst b/llvm/docs/CommandGuide/dsymutil.rst --- a/llvm/docs/CommandGuide/dsymutil.rst +++ b/llvm/docs/CommandGuide/dsymutil.rst @@ -77,7 +77,7 @@ Specifies an alternate ``path`` to place the dSYM bundle. The default dSYM bundle path is created by appending ``.dSYM`` to the executable name. - + .. option:: --papertrail When running dsymutil as part of your build system, it can be desirable for @@ -93,6 +93,14 @@ Specify a directory to prepend the paths of the external remark files. +.. option:: --statistics + + Print statistics about the contribution of each object file to the linked + debug info. This prints a table after linking with the object file name, the + size of the debug info in the object file (in bytes) and the size contributed + (in bytes) to the linked dSYM. The table is sorted by the output size listing + the obj ect files with the largest contribution first. + .. option:: --symbol-map Update the existing dSYMs inplace using symbol map specified. diff --git a/llvm/include/llvm/DWARFLinker/DWARFLinker.h b/llvm/include/llvm/DWARFLinker/DWARFLinker.h --- a/llvm/include/llvm/DWARFLinker/DWARFLinker.h +++ b/llvm/include/llvm/DWARFLinker/DWARFLinker.h @@ -260,6 +260,9 @@ /// Allows to generate log of linking process to the standard output. void setVerbosity(bool Verbose) { Options.Verbose = Verbose; } + /// Print statistics to standard output. + void setStatistics(bool Statistics) { Options.Statistics = Statistics; } + /// Do not emit linked dwarf info. void setNoOutput(bool NoOut) { Options.NoOutput = NoOut; } @@ -556,9 +559,10 @@ /// Construct the output DIE tree by cloning the DIEs we /// chose to keep above. If there are no valid relocs, then there's /// nothing to clone/emit. - void cloneAllCompileUnits(DWARFContext &DwarfContext, const DwarfFile &File, - OffsetsStringPool &StringPool, - bool IsLittleEndian); + uint64_t cloneAllCompileUnits(DWARFContext &DwarfContext, + const DwarfFile &File, + OffsetsStringPool &StringPool, + bool IsLittleEndian); private: using AttributeSpec = DWARFAbbreviationDeclaration::AttributeSpec; @@ -757,6 +761,9 @@ /// Generate processing log to the standard output. bool Verbose = false; + /// Print statistics. + bool Statistics = false; + /// Skip emitting output bool NoOutput = false; diff --git a/llvm/lib/DWARFLinker/DWARFLinker.cpp b/llvm/lib/DWARFLinker/DWARFLinker.cpp --- a/llvm/lib/DWARFLinker/DWARFLinker.cpp +++ b/llvm/lib/DWARFLinker/DWARFLinker.cpp @@ -25,6 +25,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorOr.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/LEB128.h" #include "llvm/Support/Path.h" #include "llvm/Support/ThreadPool.h" @@ -32,6 +33,21 @@ namespace llvm { +/// Hold the input and output of the debug info size in bytes. +struct DebugInfoSize { + uint64_t Input; + uint64_t Output; +}; + +/// Compute the total size of the debug info. +static uint64_t getDebugInfoSize(DWARFContext &Dwarf) { + uint64_t Size = 0; + for (auto &Unit : Dwarf.compile_units()) { + Size += Unit->getLength(); + } + return Size; +} + /// Similar to DWARFUnitSection::getUnitForOffset(), but returning our /// CompileUnit object instead. static CompileUnit *getUnitForOffset(const UnitListTy &Units, uint64_t Offset) { @@ -2071,12 +2087,13 @@ return Error::success(); } -void DWARFLinker::DIECloner::cloneAllCompileUnits(DWARFContext &DwarfContext, - const DwarfFile &File, - OffsetsStringPool &StringPool, - bool IsLittleEndian) { +uint64_t DWARFLinker::DIECloner::cloneAllCompileUnits( + DWARFContext &DwarfContext, const DwarfFile &File, + OffsetsStringPool &StringPool, bool IsLittleEndian) { uint64_t OutputDebugInfoSize = Linker.Options.NoOutput ? 0 : Emitter->getDebugInfoSectionSize(); + const uint64_t StartOutputDebugInfoSize = OutputDebugInfoSize; + for (auto &CurrentUnit : CompileUnits) { auto InputDIE = CurrentUnit->getOrigUnit().getUnitDIE(); CurrentUnit->setStartOffset(OutputDebugInfoSize); @@ -2141,6 +2158,8 @@ CurrentUnit->computeNextUnitOffset()); } } + + return OutputDebugInfoSize - StartOutputDebugInfoSize; } void DWARFLinker::updateAccelKind(DWARFContext &Dwarf) { @@ -2393,6 +2412,9 @@ } }; + // For each object file map how many bytes were emitted. + StringMap SizeByObject; + // And then the remaining work in serial again. // Note, although this loop runs in serial, it can run in parallel with // the analyzeContextInfo loop so long as we process files with indices >= @@ -2425,11 +2447,14 @@ // need to reset the NextValidReloc index to the beginning. if (OptContext.File.Addresses->hasValidRelocs() || LLVM_UNLIKELY(Options.Update)) { - DIECloner(*this, TheDwarfEmitter, OptContext.File, DIEAlloc, - OptContext.CompileUnits, Options.Update) - .cloneAllCompileUnits(*OptContext.File.Dwarf, OptContext.File, - OffsetsStringPool, - OptContext.File.Dwarf->isLittleEndian()); + SizeByObject[OptContext.File.FileName].Input = + getDebugInfoSize(*OptContext.File.Dwarf); + SizeByObject[OptContext.File.FileName].Output = + DIECloner(*this, TheDwarfEmitter, OptContext.File, DIEAlloc, + OptContext.CompileUnits, Options.Update) + .cloneAllCompileUnits(*OptContext.File.Dwarf, OptContext.File, + OffsetsStringPool, + OptContext.File.Dwarf->isLittleEndian()); } if (!Options.NoOutput && !OptContext.CompileUnits.empty() && LLVM_LIKELY(!Options.Update)) @@ -2505,6 +2530,53 @@ Pool.wait(); } + if (Options.Statistics) { + // Create a vector sorted in descending order by output size. + std::vector> Sorted; + for (auto &E : SizeByObject) + Sorted.emplace_back(E.first(), E.second); + sort(Sorted.begin(), Sorted.end(), [](auto &LHS, auto &RHS) { + return LHS.second.Output > RHS.second.Output; + }); + + auto ComputePercentange = [](int64_t Input, int64_t Output) -> float { + const float Difference = Output - Input; + const float Sum = Input + Output; + if (Sum == 0) + return 0; + return (Difference / (Sum / 2)); + }; + + int64_t InputTotal = 0; + int64_t OutputTotal = 0; + const char *FormatStr = "{0,-45} {1,10}b {2,10}b {3,8:P}\n"; + + // Print header. + outs() << ".debug_info section size (in bytes)\n"; + outs() << "----------------------------------------------------------------" + "---------------\n"; + outs() << "Filename Object " + " dSYM Change\n"; + outs() << "----------------------------------------------------------------" + "---------------\n"; + + // Print body. + for (auto &E : Sorted) { + InputTotal += E.second.Input; + OutputTotal += E.second.Output; + llvm::outs() << formatv( + FormatStr, sys::path::filename(E.first).take_back(45), E.second.Input, + E.second.Output, ComputePercentange(E.second.Input, E.second.Output)); + } + // Print total and footer. + outs() << "----------------------------------------------------------------" + "---------------\n"; + llvm::outs() << formatv(FormatStr, "Total", InputTotal, OutputTotal, + ComputePercentange(InputTotal, OutputTotal)); + outs() << "----------------------------------------------------------------" + "---------------\n\n"; + } + return true; } diff --git a/llvm/test/tools/dsymutil/X86/statistics.test b/llvm/test/tools/dsymutil/X86/statistics.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/dsymutil/X86/statistics.test @@ -0,0 +1,21 @@ +# RUN: dsymutil -statistics -oso-prepend-path=%p/.. %p/../Inputs/basic.macho.x86_64 %p/../Inputs/basic-archive.macho.x86_64 %p/../Inputs/basic-lto.macho.x86_64 %p/../Inputs/basic-lto-dw4.macho.x86_64 -o %t 2>&1 | FileCheck %s +# +# CHECK: ------------------------------------------------------------------------------- +# CHECK-NEXT: Filename Object dSYM Change +# CHECK-NEXT: ------------------------------------------------------------------------------- +# CHECK-DAG: basic2.macho.x86_64.o {{[0-9]+}}b {{[0-9]+}}b{{.*}}{{[0-9]+}}.{{[0-9]+}}% +# CHECK-DAG: basic3.macho.x86_64.o {{[0-9]+}}b {{[0-9]+}}b{{.*}}{{[0-9]+}}.{{[0-9]+}}% +# CHECK-DAG: basic1.macho.x86_64.o {{[0-9]+}}b {{[0-9]+}}b{{.*}}{{[0-9]+}}.{{[0-9]+}}% +# CHECK: ------------------------------------------------------------------------------- +# CHECK-NEXT: Total {{[0-9]+}}b {{[0-9]+}}b{{.*}}{{[0-9]+}}.{{[0-9]+}}% +# CHECK-NEXT: ------------------------------------------------------------------------------- + +--- +triple: 'x86_64-apple-darwin' +objects: + - filename: invalid.o + timestamp: 1518197670 + symbols: + - { sym: _main, objAddr: 0x0000000000000010, binAddr: 0x0000000100000FB0, size: 0x00000008 } + - { sym: _g, objAddr: 0x0000000000000000, binAddr: 0x0000000100000FA0, size: 0x00000010 } +... diff --git a/llvm/test/tools/dsymutil/cmdline.test b/llvm/test/tools/dsymutil/cmdline.test --- a/llvm/test/tools/dsymutil/cmdline.test +++ b/llvm/test/tools/dsymutil/cmdline.test @@ -18,6 +18,7 @@ HELP: -papertrail HELP: -remarks-output-format HELP: -remarks-prepend-path +HELP: -statistics HELP: -symbol-map HELP: -symtab HELP: {{ -S }} diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp --- a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp +++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp @@ -315,6 +315,7 @@ }; GeneralLinker.setVerbosity(Options.Verbose); + GeneralLinker.setStatistics(Options.Statistics); GeneralLinker.setNoOutput(Options.NoOutput); GeneralLinker.setNoODR(Options.NoODR); GeneralLinker.setUpdate(Options.Update); diff --git a/llvm/tools/dsymutil/LinkUtils.h b/llvm/tools/dsymutil/LinkUtils.h --- a/llvm/tools/dsymutil/LinkUtils.h +++ b/llvm/tools/dsymutil/LinkUtils.h @@ -27,6 +27,9 @@ /// Verbosity bool Verbose = false; + /// Statistics + bool Statistics = false; + /// Skip emitting output bool NoOutput = false; diff --git a/llvm/tools/dsymutil/Options.td b/llvm/tools/dsymutil/Options.td --- a/llvm/tools/dsymutil/Options.td +++ b/llvm/tools/dsymutil/Options.td @@ -24,6 +24,15 @@ HelpText<"Enable verbose mode.">, Group; +def statistics: F<"statistics">, + HelpText<"Print statistics about the contribution of each object file to " + "the linked debug info. This prints a table after linking with the " + "object file name, the size of the debug info in the object file " + "(in bytes) and the size contributed (in bytes) to the linked dSYM. " + "The table is sorted by the output size listing the object files " + "with the largest contribution first.">, + Group; + def verify: F<"verify">, HelpText<"Run the DWARF verifier on the linked DWARF debug info.">, Group; diff --git a/llvm/tools/dsymutil/dsymutil.cpp b/llvm/tools/dsymutil/dsymutil.cpp --- a/llvm/tools/dsymutil/dsymutil.cpp +++ b/llvm/tools/dsymutil/dsymutil.cpp @@ -220,6 +220,7 @@ Options.LinkOpts.NoTimestamp = Args.hasArg(OPT_no_swiftmodule_timestamp); Options.LinkOpts.Update = Args.hasArg(OPT_update); Options.LinkOpts.Verbose = Args.hasArg(OPT_verbose); + Options.LinkOpts.Statistics = Args.hasArg(OPT_statistics); if (Expected AccelKind = getAccelTableKind(Args)) { Options.LinkOpts.TheAccelTableKind = *AccelKind; @@ -548,7 +549,11 @@ // Shared a single binary holder for all the link steps. BinaryHolder BinHolder(Options.LinkOpts.VFS); - ThreadPoolStrategy S = hardware_concurrency(Options.LinkOpts.Threads); + // Statistics only require different architectures to be processed + // sequentially, the link itself can still happen in parallel. Change the + // thread pool strategy here instead of modifying LinkOpts.Threads. + ThreadPoolStrategy S = hardware_concurrency( + Options.LinkOpts.Statistics ? 1 : Options.LinkOpts.Threads); if (Options.LinkOpts.Threads == 0) { // If NumThreads is not specified, create one thread for each input, up to // the number of hardware threads.