319 changes: 191 additions & 128 deletions lld/COFF/Driver.cpp

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lld/COFF/DriverUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/WindowsManifest/WindowsManifestMerger.h"
#include <limits>
Expand Down Expand Up @@ -661,6 +662,7 @@ static StringRef exportSourceName(ExportSource s) {
// Performs error checking on all /export arguments.
// It also sets ordinals.
void LinkerDriver::fixupExports() {
llvm::TimeTraceScope timeScope("Fixup exports");
// Symbol ordinals must be unique.
std::set<uint16_t> ords;
for (Export &e : ctx.config.exports) {
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/ICF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "llvm/ADT/Hashing.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/xxhash.h"
#include <algorithm>
Expand Down Expand Up @@ -246,6 +247,7 @@ void ICF::forEachClass(std::function<void(size_t, size_t)> fn) {
// Two sections are considered the same if their section headers,
// contents and relocations are all the same.
void ICF::run() {
llvm::TimeTraceScope timeScope("ICF");
ScopedTimer t(ctx.icfTimer);

// Collect only mergeable sections and group by hash value.
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/LLDMapFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "Writer.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
Expand Down Expand Up @@ -92,6 +93,7 @@ void lld::coff::writeLLDMapFile(const COFFLinkerContext &ctx) {
if (ctx.config.lldmapFile.empty())
return;

llvm::TimeTraceScope timeScope(".lldmap file");
std::error_code ec;
raw_fd_ostream os(ctx.config.lldmapFile, ec, sys::fs::OF_None);
if (ec)
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/LTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ lto::Config BitcodeCompiler::createConfig() {
c.CSIRProfile = std::string(ctx.config.ltoCSProfileFile);
c.RunCSIRInstr = ctx.config.ltoCSProfileGenerate;
c.PGOWarnMismatch = ctx.config.ltoPGOWarnMismatch;
c.TimeTraceEnabled = ctx.config.timeTraceEnabled;
c.TimeTraceGranularity = ctx.config.timeTraceGranularity;

if (ctx.config.emit == EmitKind::LLVM) {
c.PostInternalizeModuleHook = [this](size_t task, const Module &m) {
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/MapFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "lld/Common/Timer.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
Expand Down Expand Up @@ -203,6 +204,7 @@ void lld::coff::writeMapFile(COFFLinkerContext &ctx) {
if (ctx.config.mapFile.empty())
return;

llvm::TimeTraceScope timeScope("Map file");
std::error_code ec;
raw_fd_ostream os(ctx.config.mapFile, ec, sys::fs::OF_None);
if (ec)
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/MarkLive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "Symbols.h"
#include "lld/Common/Timer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/TimeProfiler.h"
#include <vector>

namespace lld::coff {
Expand All @@ -19,6 +20,7 @@ namespace lld::coff {
// COMDAT chunks will be ignored by Writer, so they will be excluded
// from the final output.
void markLive(COFFLinkerContext &ctx) {
llvm::TimeTraceScope timeScope("Mark live");
ScopedTimer t(ctx.gcTimer);

// We build up a worklist of sections which have been marked as live. We only
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/MinGW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "llvm/Object/COFF.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;
Expand Down Expand Up @@ -172,6 +173,7 @@ bool AutoExporter::shouldExport(Defined *sym) const {

void lld::coff::writeDefFile(StringRef name,
const std::vector<Export> &exports) {
llvm::TimeTraceScope timeScope("Write .def file");
std::error_code ec;
raw_fd_ostream os(name, ec, sys::fs::OF_None);
if (ec)
Expand Down
8 changes: 8 additions & 0 deletions lld/COFF/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ def wrap : P_priv<"wrap">;

def vfsoverlay : P<"vfsoverlay", "Path to a vfsoverlay yaml file to optionally look for /defaultlib's in">;

def time_trace_eq: Joined<["--"], "time-trace=">, MetaVarName<"<file>">,
HelpText<"Record time trace to <file>">;
def : Flag<["--"], "time-trace">, Alias<time_trace_eq>,
HelpText<"Record time trace to file next to output">;

def time_trace_granularity_eq: Joined<["--"], "time-trace-granularity=">,
HelpText<"Minimum time granularity (in microseconds) traced by time profiler">;

// Flags for debugging
def lldmap : F<"lldmap">;
def lldmap_file : P_priv<"lldmap">;
Expand Down
122 changes: 76 additions & 46 deletions lld/COFF/PDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/TimeProfiler.h"
#include <memory>
#include <optional>

Expand Down Expand Up @@ -1027,6 +1028,7 @@ void PDBLinker::addDebugSymbols(TpiSource *source) {
if (!source->file)
return;

llvm::TimeTraceScope timeScope("Merge symbols");
ScopedTimer t(ctx.symbolMergingTimer);
ExitOnError exitOnErr;
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
Expand Down Expand Up @@ -1101,6 +1103,7 @@ void PDBLinker::addDebug(TpiSource *source) {
// indices to PDB type and item indices. If we are using ghashes, types have
// already been merged.
if (!ctx.config.debugGHashes) {
llvm::TimeTraceScope timeScope("Merge types (Non-GHASH)");
ScopedTimer t(ctx.typeMergingTimer);
if (Error e = source->mergeDebugT(&tMerger)) {
// If type merging failed, ignore the symbols.
Expand Down Expand Up @@ -1145,39 +1148,49 @@ static pdb::BulkPublic createPublic(COFFLinkerContext &ctx, Defined *def) {
// Add all object files to the PDB. Merge .debug$T sections into IpiData and
// TpiData.
void PDBLinker::addObjectsToPDB() {
ScopedTimer t1(ctx.addObjectsTimer);

// Create module descriptors
for (ObjFile *obj : ctx.objFileInstances)
createModuleDBI(obj);

// Reorder dependency type sources to come first.
tMerger.sortDependencies();

// Merge type information from input files using global type hashing.
if (ctx.config.debugGHashes)
tMerger.mergeTypesWithGHash();

// Merge dependencies and then regular objects.
for (TpiSource *source : tMerger.dependencySources)
addDebug(source);
for (TpiSource *source : tMerger.objectSources)
addDebug(source);
{
llvm::TimeTraceScope timeScope("Add objects to PDB");
ScopedTimer t1(ctx.addObjectsTimer);

// Create module descriptors
for (ObjFile *obj : ctx.objFileInstances)
createModuleDBI(obj);

// Reorder dependency type sources to come first.
tMerger.sortDependencies();

// Merge type information from input files using global type hashing.
if (ctx.config.debugGHashes)
tMerger.mergeTypesWithGHash();

// Merge dependencies and then regular objects.
{
llvm::TimeTraceScope timeScope("Merge debug info (dependencies)");
for (TpiSource *source : tMerger.dependencySources)
addDebug(source);
}
{
llvm::TimeTraceScope timeScope("Merge debug info (objects)");
for (TpiSource *source : tMerger.objectSources)
addDebug(source);
}

builder.getStringTableBuilder().setStrings(pdbStrTab);
t1.stop();
builder.getStringTableBuilder().setStrings(pdbStrTab);
}

// Construct TPI and IPI stream contents.
ScopedTimer t2(ctx.tpiStreamLayoutTimer);
{
llvm::TimeTraceScope timeScope("TPI/IPI stream layout");
ScopedTimer t2(ctx.tpiStreamLayoutTimer);

// Collect all the merged types.
if (ctx.config.debugGHashes) {
addGHashTypeInfo(ctx, builder);
} else {
addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable());
addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable());
// Collect all the merged types.
if (ctx.config.debugGHashes) {
addGHashTypeInfo(ctx, builder);
} else {
addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable());
addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable());
}
}
t2.stop();

if (ctx.config.showSummary) {
for (TpiSource *source : ctx.tpiSourceList) {
Expand All @@ -1188,6 +1201,7 @@ void PDBLinker::addObjectsToPDB() {
}

void PDBLinker::addPublicsToPDB() {
llvm::TimeTraceScope timeScope("Publics layout");
ScopedTimer t3(ctx.publicsLayoutTimer);
// Compute the public symbols.
auto &gsiBuilder = builder.getGsiBuilder();
Expand Down Expand Up @@ -1306,6 +1320,7 @@ void PDBLinker::printStats() {
}

void PDBLinker::addNatvisFiles() {
llvm::TimeTraceScope timeScope("Natvis files");
for (StringRef file : ctx.config.natvisFiles) {
ErrorOr<std::unique_ptr<MemoryBuffer>> dataOrErr =
MemoryBuffer::getFile(file);
Expand All @@ -1325,6 +1340,7 @@ void PDBLinker::addNatvisFiles() {
}

void PDBLinker::addNamedStreams() {
llvm::TimeTraceScope timeScope("Named streams");
ExitOnError exitOnErr;
for (const auto &streamFile : ctx.config.namedStreams) {
const StringRef stream = streamFile.getKey(), file = streamFile.getValue();
Expand Down Expand Up @@ -1499,6 +1515,7 @@ void PDBLinker::addImportFilesToPDB() {
if (ctx.importFileInstances.empty())
return;

llvm::TimeTraceScope timeScope("Import files");
ExitOnError exitOnErr;
std::map<std::string, llvm::pdb::DbiModuleDescriptorBuilder *> dllToModuleDbi;

Expand Down Expand Up @@ -1588,25 +1605,37 @@ void PDBLinker::addImportFilesToPDB() {
void lld::coff::createPDB(COFFLinkerContext &ctx,
ArrayRef<uint8_t> sectionTable,
llvm::codeview::DebugInfo *buildId) {
llvm::TimeTraceScope timeScope("PDB file");
ScopedTimer t1(ctx.totalPdbLinkTimer);
PDBLinker pdb(ctx);

pdb.initialize(buildId);
pdb.addObjectsToPDB();
pdb.addImportFilesToPDB();
pdb.addSections(sectionTable);
pdb.addNatvisFiles();
pdb.addNamedStreams();
pdb.addPublicsToPDB();

ScopedTimer t2(ctx.diskCommitTimer);
codeview::GUID guid;
pdb.commit(&guid);
memcpy(&buildId->PDB70.Signature, &guid, 16);

t2.stop();
t1.stop();
pdb.printStats();
{
PDBLinker pdb(ctx);

pdb.initialize(buildId);
pdb.addObjectsToPDB();
pdb.addImportFilesToPDB();
pdb.addSections(sectionTable);
pdb.addNatvisFiles();
pdb.addNamedStreams();
pdb.addPublicsToPDB();

{
llvm::TimeTraceScope timeScope("Commit PDB file to disk");
ScopedTimer t2(ctx.diskCommitTimer);
codeview::GUID guid;
pdb.commit(&guid);
memcpy(&buildId->PDB70.Signature, &guid, 16);
}

t1.stop();
pdb.printStats();

// Manually start this profile point to measure ~PDBLinker().
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerBegin("PDBLinker destructor", StringRef(""));
}
// Manually end this profile point to measure ~PDBLinker().
if (getTimeTraceProfilerInstance() != nullptr)
timeTraceProfilerEnd();
}

void PDBLinker::initialize(llvm::codeview::DebugInfo *buildId) {
Expand Down Expand Up @@ -1641,6 +1670,7 @@ void PDBLinker::initialize(llvm::codeview::DebugInfo *buildId) {
}

void PDBLinker::addSections(ArrayRef<uint8_t> sectionTable) {
llvm::TimeTraceScope timeScope("PDB output sections");
ExitOnError exitOnErr;
// It's not entirely clear what this is, but the * Linker * module uses it.
pdb::DbiStreamBuilder &dbiBuilder = builder.getDbiBuilder();
Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ void SymbolTable::reportUnresolvable() {
}

void SymbolTable::resolveRemainingUndefines() {
llvm::TimeTraceScope timeScope("Resolve remaining undefined symbols");
SmallPtrSet<Symbol *, 8> undefs;
DenseMap<Symbol *, Symbol *> localImports;

Expand Down Expand Up @@ -878,6 +879,7 @@ void SymbolTable::compileBitcodeFiles() {
if (ctx.bitcodeFileInstances.empty())
return;

llvm::TimeTraceScope timeScope("Compile bitcode");
ScopedTimer t(ctx.ltoTimer);
lto.reset(new BitcodeCompiler(ctx));
for (BitcodeFile *f : ctx.bitcodeFileInstances)
Expand Down
121 changes: 78 additions & 43 deletions lld/COFF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/RandomNumberGenerator.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"
#include <algorithm>
#include <cstdio>
Expand Down Expand Up @@ -320,7 +321,10 @@ class Writer {
};
} // anonymous namespace

void lld::coff::writeResult(COFFLinkerContext &ctx) { Writer(ctx).run(); }
void lld::coff::writeResult(COFFLinkerContext &ctx) {
llvm::TimeTraceScope timeScope("Write output(s)");
Writer(ctx).run();
}

void OutputSection::addChunk(Chunk *c) {
chunks.push_back(c);
Expand Down Expand Up @@ -563,16 +567,21 @@ void Writer::finalizeAddresses() {
int pass = 0;
int margin = 1024 * 100;
while (true) {
llvm::TimeTraceScope timeScope2("Add thunks pass");

// First check whether we need thunks at all, or if the previous pass of
// adding them turned out ok.
bool rangesOk = true;
size_t numChunks = 0;
for (OutputSection *sec : ctx.outputSections) {
if (!verifyRanges(sec->chunks)) {
rangesOk = false;
break;
{
llvm::TimeTraceScope timeScope3("Verify ranges");
for (OutputSection *sec : ctx.outputSections) {
if (!verifyRanges(sec->chunks)) {
rangesOk = false;
break;
}
numChunks += sec->chunks.size();
}
numChunks += sec->chunks.size();
}
if (rangesOk) {
if (pass > 0)
Expand All @@ -596,8 +605,11 @@ void Writer::finalizeAddresses() {
// Try adding thunks everywhere where it is needed, with a margin
// to avoid things going out of range due to the added thunks.
bool addressesChanged = false;
for (OutputSection *sec : ctx.outputSections)
addressesChanged |= createThunks(sec, margin);
{
llvm::TimeTraceScope timeScope3("Create thunks");
for (OutputSection *sec : ctx.outputSections)
addressesChanged |= createThunks(sec, margin);
}
// If the verification above thought we needed thunks, we should have
// added some.
assert(addressesChanged);
Expand All @@ -616,6 +628,8 @@ void Writer::writePEChecksum() {
return;
}

llvm::TimeTraceScope timeScope("PE checksum");

// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#checksum
uint32_t *buf = (uint32_t *)buffer->getBufferStart();
uint32_t size = (uint32_t)(buffer->getBufferSize());
Expand Down Expand Up @@ -650,42 +664,44 @@ void Writer::writePEChecksum() {

// The main function of the writer.
void Writer::run() {
ScopedTimer t1(ctx.codeLayoutTimer);

createImportTables();
createSections();
appendImportThunks();
// Import thunks must be added before the Control Flow Guard tables are added.
createMiscChunks();
createExportTable();
mergeSections();
removeUnusedSections();
finalizeAddresses();
removeEmptySections();
assignOutputSectionIndices();
setSectionPermissions();
createSymbolAndStringTable();

if (fileSize > UINT32_MAX)
fatal("image size (" + Twine(fileSize) + ") " +
"exceeds maximum allowable size (" + Twine(UINT32_MAX) + ")");

openFile(ctx.config.outputFile);
if (ctx.config.is64()) {
writeHeader<pe32plus_header>();
} else {
writeHeader<pe32_header>();
}
writeSections();
checkLoadConfig();
sortExceptionTable();

// Fix up the alignment in the TLS Directory's characteristic field,
// if a specific alignment value is needed
if (tlsAlignment)
fixTlsAlignment();
{
llvm::TimeTraceScope timeScope("Write PE");
ScopedTimer t1(ctx.codeLayoutTimer);

createImportTables();
createSections();
appendImportThunks();
// Import thunks must be added before the Control Flow Guard tables are
// added.
createMiscChunks();
createExportTable();
mergeSections();
removeUnusedSections();
finalizeAddresses();
removeEmptySections();
assignOutputSectionIndices();
setSectionPermissions();
createSymbolAndStringTable();

if (fileSize > UINT32_MAX)
fatal("image size (" + Twine(fileSize) + ") " +
"exceeds maximum allowable size (" + Twine(UINT32_MAX) + ")");

openFile(ctx.config.outputFile);
if (ctx.config.is64()) {
writeHeader<pe32plus_header>();
} else {
writeHeader<pe32_header>();
}
writeSections();
checkLoadConfig();
sortExceptionTable();

t1.stop();
// Fix up the alignment in the TLS Directory's characteristic field,
// if a specific alignment value is needed
if (tlsAlignment)
fixTlsAlignment();
}

if (!ctx.config.pdbPath.empty() && ctx.config.debug) {
assert(buildId);
Expand All @@ -701,6 +717,7 @@ void Writer::run() {
if (errorCount())
return;

llvm::TimeTraceScope timeScope("Commit PE to disk");
ScopedTimer t2(ctx.outputCommitTimer);
if (auto e = buffer->commit())
fatal("failed to write output '" + buffer->getPath() +
Expand Down Expand Up @@ -878,6 +895,7 @@ void Writer::sortSections() {

// Create output section objects and add them to OutputSections.
void Writer::createSections() {
llvm::TimeTraceScope timeScope("Output sections");
// First, create the builtin sections.
const uint32_t data = IMAGE_SCN_CNT_INITIALIZED_DATA;
const uint32_t bss = IMAGE_SCN_CNT_UNINITIALIZED_DATA;
Expand Down Expand Up @@ -1003,6 +1021,7 @@ void Writer::createSections() {
}

void Writer::createMiscChunks() {
llvm::TimeTraceScope timeScope("Misc chunks");
Configuration *config = &ctx.config;

for (MergeChunk *p : ctx.mergeChunkInstances) {
Expand Down Expand Up @@ -1068,6 +1087,7 @@ void Writer::createMiscChunks() {
// IdataContents class abstracted away the details for us,
// so we just let it create chunks and add them to the section.
void Writer::createImportTables() {
llvm::TimeTraceScope timeScope("Import tables");
// Initialize DLLOrder so that import entries are ordered in
// the same order as in the command line. (That affects DLL
// initialization order, and this ordering is MSVC-compatible.)
Expand Down Expand Up @@ -1097,6 +1117,7 @@ void Writer::appendImportThunks() {
if (ctx.importFileInstances.empty())
return;

llvm::TimeTraceScope timeScope("Import thunks");
for (ImportFile *file : ctx.importFileInstances) {
if (!file->live)
continue;
Expand Down Expand Up @@ -1128,6 +1149,7 @@ void Writer::appendImportThunks() {
}

void Writer::createExportTable() {
llvm::TimeTraceScope timeScope("Export table");
if (!edataSec->chunks.empty()) {
// Allow using a custom built export table from input object files, instead
// of having the linker synthesize the tables.
Expand All @@ -1148,6 +1170,7 @@ void Writer::createExportTable() {
}

void Writer::removeUnusedSections() {
llvm::TimeTraceScope timeScope("Remove unused sections");
// Remove sections that we can be sure won't get content, to avoid
// allocating space for their section headers.
auto isUnused = [this](OutputSection *s) {
Expand All @@ -1163,11 +1186,13 @@ void Writer::removeUnusedSections() {
// The Windows loader doesn't seem to like empty sections,
// so we remove them if any.
void Writer::removeEmptySections() {
llvm::TimeTraceScope timeScope("Remove empty sections");
auto isEmpty = [](OutputSection *s) { return s->getVirtualSize() == 0; };
llvm::erase_if(ctx.outputSections, isEmpty);
}

void Writer::assignOutputSectionIndices() {
llvm::TimeTraceScope timeScope("Output sections indices");
// Assign final output section indices, and assign each chunk to its output
// section.
uint32_t idx = 1;
Expand Down Expand Up @@ -1258,6 +1283,7 @@ std::optional<coff_symbol16> Writer::createSymbol(Defined *def) {
}

void Writer::createSymbolAndStringTable() {
llvm::TimeTraceScope timeScope("Symbol and string table");
// PE/COFF images are limited to 8 byte section names. Longer names can be
// supported by writing a non-standard string table, but this string table is
// not mapped at runtime and the long names will therefore be inaccessible.
Expand Down Expand Up @@ -1320,6 +1346,7 @@ void Writer::createSymbolAndStringTable() {
}

void Writer::mergeSections() {
llvm::TimeTraceScope timeScope("Merge sections");
if (!pdataSec->chunks.empty()) {
firstPdata = pdataSec->chunks.front();
lastPdata = pdataSec->chunks.back();
Expand Down Expand Up @@ -1353,6 +1380,7 @@ void Writer::mergeSections() {
// Visits all sections to assign incremental, non-overlapping RVAs and
// file offsets.
void Writer::assignAddresses() {
llvm::TimeTraceScope timeScope("Assign addresses");
Configuration *config = &ctx.config;

sizeOfHeaders = dosStubSize + sizeof(PEMagic) + sizeof(coff_file_header) +
Expand All @@ -1367,6 +1395,7 @@ void Writer::assignAddresses() {
uint64_t rva = alignTo(sizeOfHeaders, config->align);

for (OutputSection *sec : ctx.outputSections) {
llvm::TimeTraceScope timeScope("Section: ", sec->name);
if (sec == relocSec)
addBaserels();
uint64_t rawSize = 0, virtualSize = 0;
Expand Down Expand Up @@ -1947,6 +1976,7 @@ void Writer::insertCtorDtorSymbols() {
// Handles /section options to allow users to overwrite
// section attributes.
void Writer::setSectionPermissions() {
llvm::TimeTraceScope timeScope("Sections permissions");
for (auto &p : ctx.config.section) {
StringRef name = p.first;
uint32_t perm = p.second;
Expand All @@ -1958,6 +1988,7 @@ void Writer::setSectionPermissions() {

// Write section contents to a mmap'ed file.
void Writer::writeSections() {
llvm::TimeTraceScope timeScope("Write sections");
uint8_t *buf = buffer->getBufferStart();
for (OutputSection *sec : ctx.outputSections) {
uint8_t *secBuf = buf + sec->getFileOff();
Expand All @@ -1974,6 +2005,8 @@ void Writer::writeSections() {
}

void Writer::writeBuildId() {
llvm::TimeTraceScope timeScope("Write build ID");

// There are two important parts to the build ID.
// 1) If building with debug info, the COFF debug directory contains a
// timestamp as well as a Guid and Age of the PDB.
Expand Down Expand Up @@ -2030,6 +2063,7 @@ void Writer::writeBuildId() {
void Writer::sortExceptionTable() {
if (!firstPdata)
return;
llvm::TimeTraceScope timeScope("Sort exception table");
// We assume .pdata contains function table entries only.
auto bufAddr = [&](Chunk *c) {
OutputSection *os = ctx.getOutputSection(c);
Expand Down Expand Up @@ -2123,6 +2157,7 @@ void Writer::addBaserels() {
for (OutputSection *sec : ctx.outputSections) {
if (sec->header.Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
continue;
llvm::TimeTraceScope timeScope("Base relocations: ", sec->name);
// Collect all locations for base relocations.
for (Chunk *c : sec->chunks)
c->getBaserels(&v);
Expand Down
2 changes: 2 additions & 0 deletions lld/Common/Filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
#if LLVM_ON_UNIX
#include <unistd.h>
#endif
Expand Down Expand Up @@ -122,6 +123,7 @@ void lld::unlinkAsync(StringRef path) {
// is called. We use that class without calling commit() to predict
// if the given file is writable.
std::error_code lld::tryCreateFile(StringRef path) {
llvm::TimeTraceScope timeScope("Try create output file");
if (path.empty())
return std::error_code();
if (path == "-")
Expand Down
3 changes: 3 additions & 0 deletions lld/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Breaking changes
COFF Improvements
-----------------

* Added support for ``--time-trace`` and associated ``--time-trace-granularity``.
This generates a .json profile trace of the linker execution.

MinGW Improvements
------------------

Expand Down
44 changes: 44 additions & 0 deletions lld/test/COFF/time-trace.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# REQUIRES: x86

# RUN: llvm-mc -triple=x86_64-windows-msvc -filetype=obj -o %t.obj %s

# Test implicit trace file name
# RUN: lld-link %t.obj /entry:main /out:%t1.exe --time-trace --time-trace-granularity=0
# RUN: cat %t1.exe.time-trace \
# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \
# RUN: | FileCheck %s

# Test specified trace file name
# RUN: lld-link %t.obj /entry:main /out:%t2.exe --time-trace=%t2.json --time-trace-granularity=0
# RUN: cat %t2.json \
# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \
# RUN: | FileCheck %s

# Test trace requested to stdout
# RUN: env LLD_IN_TEST=1 lld-link %t.obj /entry:main /out:%t3.exe --time-trace=- --time-trace-granularity=0 \
# RUN: | %python -c 'import json, sys; json.dump(json.loads(sys.stdin.read()), sys.stdout, sort_keys=True, indent=2)' \
# RUN: | FileCheck %s

# CHECK: "beginningOfTime": {{[0-9]{16},}}
# CHECK-NEXT: "traceEvents": [

# Check one event has correct fields
# CHECK: "dur":
# CHECK-NEXT: "name":
# CHECK-NEXT: "ph":
# CHECK-NEXT: "pid":
# CHECK-NEXT: "tid":
# CHECK-NEXT: "ts":

# Check there are events
# CHECK: "name": "Read input files"

# Check process_name entry field
# CHECK: "name": "lld-link{{(.exe)?}}"
# CHECK: "name": "process_name"
# CHECK: "name": "thread_name"

.text
.global main
main:
ret
5 changes: 5 additions & 0 deletions llvm/lib/DebugInfo/MSF/MSFBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FileOutputBuffer.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/TimeProfiler.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
Expand Down Expand Up @@ -248,6 +249,8 @@ uint32_t MSFBuilder::computeDirectoryByteSize() const {
}

Expected<MSFLayout> MSFBuilder::generateLayout() {
llvm::TimeTraceScope timeScope("MSF: Generate layout");

SuperBlock *SB = Allocator.Allocate<SuperBlock>();
MSFLayout L;
L.SB = SB;
Expand Down Expand Up @@ -336,6 +339,8 @@ static void commitFpm(WritableBinaryStream &MsfBuffer, const MSFLayout &Layout,

Expected<FileBufferByteStream> MSFBuilder::commit(StringRef Path,
MSFLayout &Layout) {
llvm::TimeTraceScope timeScope("Commit MSF");

Expected<MSFLayout> L = generateLayout();
if (!L)
return L.takeError();
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/DebugInfo/PDB/Native/DbiStreamBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvm/Object/COFF.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"

using namespace llvm;
using namespace llvm::codeview;
Expand Down Expand Up @@ -381,6 +382,7 @@ void DbiStreamBuilder::createSectionMap(

Error DbiStreamBuilder::commit(const msf::MSFLayout &Layout,
WritableBinaryStreamRef MsfBuffer) {
llvm::TimeTraceScope timeScope("Commit DBI stream");
if (auto EC = finalize())
return EC;

Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "llvm/Support/BinaryItemStream.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"
#include <algorithm>
#include <vector>
Expand Down Expand Up @@ -478,6 +479,7 @@ Error GSIStreamBuilder::commitGlobalsHashStream(

Error GSIStreamBuilder::commit(const msf::MSFLayout &Layout,
WritableBinaryStreamRef Buffer) {
llvm::TimeTraceScope timeScope("Commit GSI stream");
auto GS = WritableMappedBlockStream::createIndexedStream(
Layout, Buffer, getGlobalsStreamIndex(), Msf.getAllocator());
auto PS = WritableMappedBlockStream::createIndexedStream(
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/DebugInfo/PDB/Native/InfoStreamBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/TimeProfiler.h"

using namespace llvm;
using namespace llvm::codeview;
Expand Down Expand Up @@ -55,6 +56,7 @@ Error InfoStreamBuilder::finalizeMsfLayout() {

Error InfoStreamBuilder::commit(const msf::MSFLayout &Layout,
WritableBinaryStreamRef Buffer) const {
llvm::TimeTraceScope timeScope("Commit info stream");
auto InfoS = WritableMappedBlockStream::createIndexedStream(
Layout, Buffer, StreamPDB, Msf.getAllocator());
BinaryStreamWriter Writer(*InfoS);
Expand Down
26 changes: 17 additions & 9 deletions llvm/lib/DebugInfo/PDB/Native/PDBFileBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/CRC.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"

#include <ctime>
Expand Down Expand Up @@ -129,6 +130,7 @@ void PDBFileBuilder::addInjectedSource(StringRef Name,
}

Error PDBFileBuilder::finalizeMsfLayout() {
llvm::TimeTraceScope timeScope("MSF layout");

if (Ipi && Ipi->getRecordCount() > 0) {
// In theory newer PDBs always have an ID stream, but by saying that we're
Expand Down Expand Up @@ -254,6 +256,7 @@ void PDBFileBuilder::commitInjectedSources(WritableBinaryStream &MsfBuffer,
if (InjectedSourceTable.empty())
return;

llvm::TimeTraceScope timeScope("Commit injected sources");
commitSrcHeaderBlock(MsfBuffer, Layout);

for (const auto &IS : InjectedSources) {
Expand Down Expand Up @@ -290,15 +293,18 @@ Error PDBFileBuilder::commit(StringRef Filename, codeview::GUID *Guid) {
if (auto EC = Strings.commit(NSWriter))
return EC;

for (const auto &NSE : NamedStreamData) {
if (NSE.second.empty())
continue;

auto NS = WritableMappedBlockStream::createIndexedStream(
Layout, Buffer, NSE.first, Allocator);
BinaryStreamWriter NSW(*NS);
if (auto EC = NSW.writeBytes(arrayRefFromStringRef(NSE.second)))
return EC;
{
llvm::TimeTraceScope timeScope("Named stream data");
for (const auto &NSE : NamedStreamData) {
if (NSE.second.empty())
continue;

auto NS = WritableMappedBlockStream::createIndexedStream(
Layout, Buffer, NSE.first, Allocator);
BinaryStreamWriter NSW(*NS);
if (auto EC = NSW.writeBytes(arrayRefFromStringRef(NSE.second)))
return EC;
}
}

if (Info) {
Expand Down Expand Up @@ -338,6 +344,8 @@ Error PDBFileBuilder::commit(StringRef Filename, codeview::GUID *Guid) {
// Set the build id at the very end, after every other byte of the PDB
// has been written.
if (Info->hashPDBContentsToGUID()) {
llvm::TimeTraceScope timeScope("Compute build ID");

// Compute a hash of all sections of the output file.
uint64_t Digest =
xxh3_64bits({Buffer.getBufferStart(), Buffer.getBufferEnd()});
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/DebugInfo/PDB/Native/PDBStringTableBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "llvm/DebugInfo/PDB/Native/RawTypes.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/TimeProfiler.h"

#include <map>

Expand Down Expand Up @@ -207,6 +208,7 @@ Error PDBStringTableBuilder::writeEpilogue(BinaryStreamWriter &Writer) const {
}

Error PDBStringTableBuilder::commit(BinaryStreamWriter &Writer) const {
llvm::TimeTraceScope timeScope("Commit strings table");
BinaryStreamWriter SectionWriter;

std::tie(SectionWriter, Writer) = Writer.split(sizeof(PDBStringTableHeader));
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/DebugInfo/PDB/Native/TpiStreamBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/TimeProfiler.h"
#include <algorithm>
#include <cstdint>
#include <numeric>
Expand Down Expand Up @@ -171,6 +172,7 @@ Error TpiStreamBuilder::finalizeMsfLayout() {

Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout,
WritableBinaryStreamRef Buffer) {
llvm::TimeTraceScope timeScope("Commit TPI stream");
if (auto EC = finalize())
return EC;

Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Support/FileOutputBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Memory.h"
#include "llvm/Support/TimeProfiler.h"
#include <system_error>

#if !defined(_MSC_VER) && !defined(__MINGW32__)
Expand Down Expand Up @@ -43,6 +44,8 @@ class OnDiskBuffer : public FileOutputBuffer {
size_t getBufferSize() const override { return Buffer.size(); }

Error commit() override {
llvm::TimeTraceScope timeScope("Commit buffer to disk");

// Unmap buffer, letting OS flush dirty pages to file on disk.
Buffer.unmap();

Expand Down