forked from OSchip/llvm-project
[COFF] Port CallGraphSort to COFF from ELF
This commit is contained in:
parent
a34a8d5260
commit
763671f387
|
@ -3,6 +3,7 @@ tablegen(LLVM Options.inc -gen-opt-parser-defs)
|
||||||
add_public_tablegen_target(COFFOptionsTableGen)
|
add_public_tablegen_target(COFFOptionsTableGen)
|
||||||
|
|
||||||
add_lld_library(lldCOFF
|
add_lld_library(lldCOFF
|
||||||
|
CallGraphSort.cpp
|
||||||
Chunks.cpp
|
Chunks.cpp
|
||||||
DebugTypes.cpp
|
DebugTypes.cpp
|
||||||
DLL.cpp
|
DLL.cpp
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
//===- CallGraphSort.cpp --------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
///
|
||||||
|
/// This is based on the ELF port, see ELF/CallGraphSort.cpp for the details
|
||||||
|
/// about the algorithm.
|
||||||
|
///
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#include "CallGraphSort.h"
|
||||||
|
#include "InputFiles.h"
|
||||||
|
#include "SymbolTable.h"
|
||||||
|
#include "Symbols.h"
|
||||||
|
#include "lld/Common/ErrorHandler.h"
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
using namespace lld;
|
||||||
|
using namespace lld::coff;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Edge {
|
||||||
|
int from;
|
||||||
|
uint64_t weight;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Cluster {
|
||||||
|
Cluster(int sec, size_t s) : next(sec), prev(sec), size(s) {}
|
||||||
|
|
||||||
|
double getDensity() const {
|
||||||
|
if (size == 0)
|
||||||
|
return 0;
|
||||||
|
return double(weight) / double(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int next;
|
||||||
|
int prev;
|
||||||
|
uint64_t size;
|
||||||
|
uint64_t weight = 0;
|
||||||
|
uint64_t initialWeight = 0;
|
||||||
|
Edge bestPred = {-1, 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
class CallGraphSort {
|
||||||
|
public:
|
||||||
|
CallGraphSort();
|
||||||
|
|
||||||
|
DenseMap<const SectionChunk *, int> run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Cluster> clusters;
|
||||||
|
std::vector<const SectionChunk *> sections;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maximum amount the combined cluster density can be worse than the original
|
||||||
|
// cluster to consider merging.
|
||||||
|
constexpr int MAX_DENSITY_DEGRADATION = 8;
|
||||||
|
|
||||||
|
// Maximum cluster size in bytes.
|
||||||
|
constexpr uint64_t MAX_CLUSTER_SIZE = 1024 * 1024;
|
||||||
|
} // end anonymous namespace
|
||||||
|
|
||||||
|
using SectionPair = std::pair<const SectionChunk *, const SectionChunk *>;
|
||||||
|
|
||||||
|
// Take the edge list in Config->CallGraphProfile, resolve symbol names to
|
||||||
|
// Symbols, and generate a graph between InputSections with the provided
|
||||||
|
// weights.
|
||||||
|
CallGraphSort::CallGraphSort() {
|
||||||
|
MapVector<SectionPair, uint64_t> &profile = config->callGraphProfile;
|
||||||
|
DenseMap<const SectionChunk *, int> secToCluster;
|
||||||
|
|
||||||
|
auto getOrCreateNode = [&](const SectionChunk *isec) -> int {
|
||||||
|
auto res = secToCluster.try_emplace(isec, clusters.size());
|
||||||
|
if (res.second) {
|
||||||
|
sections.push_back(isec);
|
||||||
|
clusters.emplace_back(clusters.size(), isec->getSize());
|
||||||
|
}
|
||||||
|
return res.first->second;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the graph.
|
||||||
|
for (std::pair<SectionPair, uint64_t> &c : profile) {
|
||||||
|
const auto *fromSec = cast<SectionChunk>(c.first.first->repl);
|
||||||
|
const auto *toSec = cast<SectionChunk>(c.first.second->repl);
|
||||||
|
uint64_t weight = c.second;
|
||||||
|
|
||||||
|
// Ignore edges between input sections belonging to different output
|
||||||
|
// sections. This is done because otherwise we would end up with clusters
|
||||||
|
// containing input sections that can't actually be placed adjacently in the
|
||||||
|
// output. This messes with the cluster size and density calculations. We
|
||||||
|
// would also end up moving input sections in other output sections without
|
||||||
|
// moving them closer to what calls them.
|
||||||
|
if (fromSec->getOutputSection() != toSec->getOutputSection())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int from = getOrCreateNode(fromSec);
|
||||||
|
int to = getOrCreateNode(toSec);
|
||||||
|
|
||||||
|
clusters[to].weight += weight;
|
||||||
|
|
||||||
|
if (from == to)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Remember the best edge.
|
||||||
|
Cluster &toC = clusters[to];
|
||||||
|
if (toC.bestPred.from == -1 || toC.bestPred.weight < weight) {
|
||||||
|
toC.bestPred.from = from;
|
||||||
|
toC.bestPred.weight = weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Cluster &c : clusters)
|
||||||
|
c.initialWeight = c.weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's bad to merge clusters which would degrade the density too much.
|
||||||
|
static bool isNewDensityBad(Cluster &a, Cluster &b) {
|
||||||
|
double newDensity = double(a.weight + b.weight) / double(a.size + b.size);
|
||||||
|
return newDensity < a.getDensity() / MAX_DENSITY_DEGRADATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the leader of V's belonged cluster (represented as an equivalence
|
||||||
|
// class). We apply union-find path-halving technique (simple to implement) in
|
||||||
|
// the meantime as it decreases depths and the time complexity.
|
||||||
|
static int getLeader(std::vector<int> &leaders, int v) {
|
||||||
|
while (leaders[v] != v) {
|
||||||
|
leaders[v] = leaders[leaders[v]];
|
||||||
|
v = leaders[v];
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mergeClusters(std::vector<Cluster> &cs, Cluster &into, int intoIdx,
|
||||||
|
Cluster &from, int fromIdx) {
|
||||||
|
int tail1 = into.prev, tail2 = from.prev;
|
||||||
|
into.prev = tail2;
|
||||||
|
cs[tail2].next = intoIdx;
|
||||||
|
from.prev = tail1;
|
||||||
|
cs[tail1].next = fromIdx;
|
||||||
|
into.size += from.size;
|
||||||
|
into.weight += from.weight;
|
||||||
|
from.size = 0;
|
||||||
|
from.weight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group InputSections into clusters using the Call-Chain Clustering heuristic
|
||||||
|
// then sort the clusters by density.
|
||||||
|
DenseMap<const SectionChunk *, int> CallGraphSort::run() {
|
||||||
|
std::vector<int> sorted(clusters.size());
|
||||||
|
std::vector<int> leaders(clusters.size());
|
||||||
|
|
||||||
|
std::iota(leaders.begin(), leaders.end(), 0);
|
||||||
|
std::iota(sorted.begin(), sorted.end(), 0);
|
||||||
|
llvm::stable_sort(sorted, [&](int a, int b) {
|
||||||
|
return clusters[a].getDensity() > clusters[b].getDensity();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int l : sorted) {
|
||||||
|
// The cluster index is the same as the index of its leader here because
|
||||||
|
// clusters[L] has not been merged into another cluster yet.
|
||||||
|
Cluster &c = clusters[l];
|
||||||
|
|
||||||
|
// Don't consider merging if the edge is unlikely.
|
||||||
|
if (c.bestPred.from == -1 || c.bestPred.weight * 10 <= c.initialWeight)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int predL = getLeader(leaders, c.bestPred.from);
|
||||||
|
if (l == predL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Cluster *predC = &clusters[predL];
|
||||||
|
if (c.size + predC->size > MAX_CLUSTER_SIZE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isNewDensityBad(*predC, c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
leaders[l] = predL;
|
||||||
|
mergeClusters(clusters, *predC, predL, c, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort remaining non-empty clusters by density.
|
||||||
|
sorted.clear();
|
||||||
|
for (int i = 0, e = (int)clusters.size(); i != e; ++i)
|
||||||
|
if (clusters[i].size > 0)
|
||||||
|
sorted.push_back(i);
|
||||||
|
llvm::stable_sort(sorted, [&](int a, int b) {
|
||||||
|
return clusters[a].getDensity() > clusters[b].getDensity();
|
||||||
|
});
|
||||||
|
|
||||||
|
DenseMap<const SectionChunk *, int> orderMap;
|
||||||
|
// Sections will be sorted by increasing order. Absent sections will have
|
||||||
|
// priority 0 and be placed at the end of sections.
|
||||||
|
int curOrder = INT_MIN;
|
||||||
|
for (int leader : sorted) {
|
||||||
|
for (int i = leader;;) {
|
||||||
|
orderMap[sections[i]] = curOrder++;
|
||||||
|
i = clusters[i].next;
|
||||||
|
if (i == leader)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!config->printSymbolOrder.empty()) {
|
||||||
|
std::error_code ec;
|
||||||
|
raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
|
||||||
|
if (ec) {
|
||||||
|
error("cannot open " + config->printSymbolOrder + ": " + ec.message());
|
||||||
|
return orderMap;
|
||||||
|
}
|
||||||
|
// Print the symbols ordered by C3, in the order of increasing curOrder
|
||||||
|
// Instead of sorting all the orderMap, just repeat the loops above.
|
||||||
|
for (int leader : sorted)
|
||||||
|
for (int i = leader;;) {
|
||||||
|
const SectionChunk *sc = sections[i];
|
||||||
|
|
||||||
|
// Search all the symbols in the file of the section
|
||||||
|
// and find out a DefinedCOFF symbol with name that is within the
|
||||||
|
// section.
|
||||||
|
for (Symbol *sym : sc->file->getSymbols())
|
||||||
|
if (auto *d = dyn_cast_or_null<DefinedCOFF>(sym))
|
||||||
|
// Filter out non-COMDAT symbols and section symbols.
|
||||||
|
if (d->isCOMDAT && !d->getCOFFSymbol().isSection() &&
|
||||||
|
sc == d->getChunk())
|
||||||
|
os << sym->getName() << "\n";
|
||||||
|
i = clusters[i].next;
|
||||||
|
if (i == leader)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return orderMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sections by the profile data provided by /call-graph-ordering-file
|
||||||
|
//
|
||||||
|
// This first builds a call graph based on the profile data then merges sections
|
||||||
|
// according to the C³ heuristic. All clusters are then sorted by a density
|
||||||
|
// metric to further improve locality.
|
||||||
|
DenseMap<const SectionChunk *, int> coff::computeCallGraphProfileOrder() {
|
||||||
|
return CallGraphSort().run();
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
//===- CallGraphSort.h ------------------------------------------*- C++ -*-===//
|
||||||
|
//
|
||||||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#ifndef LLD_COFF_CALL_GRAPH_SORT_H
|
||||||
|
#define LLD_COFF_CALL_GRAPH_SORT_H
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
|
namespace lld {
|
||||||
|
namespace coff {
|
||||||
|
class SectionChunk;
|
||||||
|
|
||||||
|
llvm::DenseMap<const SectionChunk *, int> computeCallGraphProfileOrder();
|
||||||
|
} // namespace coff
|
||||||
|
} // namespace lld
|
||||||
|
|
||||||
|
#endif
|
|
@ -9,6 +9,7 @@
|
||||||
#ifndef LLD_COFF_CONFIG_H
|
#ifndef LLD_COFF_CONFIG_H
|
||||||
#define LLD_COFF_CONFIG_H
|
#define LLD_COFF_CONFIG_H
|
||||||
|
|
||||||
|
#include "llvm/ADT/MapVector.h"
|
||||||
#include "llvm/ADT/StringMap.h"
|
#include "llvm/ADT/StringMap.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include "llvm/Object/COFF.h"
|
#include "llvm/Object/COFF.h"
|
||||||
|
@ -29,6 +30,7 @@ class DefinedRelative;
|
||||||
class StringChunk;
|
class StringChunk;
|
||||||
class Symbol;
|
class Symbol;
|
||||||
class InputFile;
|
class InputFile;
|
||||||
|
class SectionChunk;
|
||||||
|
|
||||||
// Short aliases.
|
// Short aliases.
|
||||||
static const auto AMD64 = llvm::COFF::IMAGE_FILE_MACHINE_AMD64;
|
static const auto AMD64 = llvm::COFF::IMAGE_FILE_MACHINE_AMD64;
|
||||||
|
@ -201,6 +203,15 @@ struct Configuration {
|
||||||
// Used for /lto-obj-path:
|
// Used for /lto-obj-path:
|
||||||
llvm::StringRef ltoObjPath;
|
llvm::StringRef ltoObjPath;
|
||||||
|
|
||||||
|
// Used for /call-graph-ordering-file:
|
||||||
|
llvm::MapVector<std::pair<const SectionChunk *, const SectionChunk *>,
|
||||||
|
uint64_t>
|
||||||
|
callGraphProfile;
|
||||||
|
bool callGraphProfileSort = false;
|
||||||
|
|
||||||
|
// Used for /print-symbol-order:
|
||||||
|
StringRef printSymbolOrder;
|
||||||
|
|
||||||
uint64_t align = 4096;
|
uint64_t align = 4096;
|
||||||
uint64_t imageBase = -1;
|
uint64_t imageBase = -1;
|
||||||
uint64_t fileAlign = 512;
|
uint64_t fileAlign = 512;
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include "llvm/Option/Arg.h"
|
#include "llvm/Option/Arg.h"
|
||||||
#include "llvm/Option/ArgList.h"
|
#include "llvm/Option/ArgList.h"
|
||||||
#include "llvm/Option/Option.h"
|
#include "llvm/Option/Option.h"
|
||||||
|
#include "llvm/Support/BinaryStreamReader.h"
|
||||||
#include "llvm/Support/CommandLine.h"
|
#include "llvm/Support/CommandLine.h"
|
||||||
#include "llvm/Support/Debug.h"
|
#include "llvm/Support/Debug.h"
|
||||||
#include "llvm/Support/LEB128.h"
|
#include "llvm/Support/LEB128.h"
|
||||||
|
@ -924,6 +925,75 @@ static void parseOrderFile(StringRef arg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parseCallGraphFile(StringRef path) {
|
||||||
|
std::unique_ptr<MemoryBuffer> mb = CHECK(
|
||||||
|
MemoryBuffer::getFile(path, -1, false, true), "could not open " + path);
|
||||||
|
|
||||||
|
// Build a map from symbol name to section.
|
||||||
|
DenseMap<StringRef, Symbol *> map;
|
||||||
|
for (ObjFile *file : ObjFile::instances)
|
||||||
|
for (Symbol *sym : file->getSymbols())
|
||||||
|
if (sym)
|
||||||
|
map[sym->getName()] = sym;
|
||||||
|
|
||||||
|
auto findSection = [&](StringRef name) -> SectionChunk * {
|
||||||
|
Symbol *sym = map.lookup(name);
|
||||||
|
if (!sym) {
|
||||||
|
if (config->warnMissingOrderSymbol)
|
||||||
|
warn(path + ": no such symbol: " + name);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefinedCOFF *dr = dyn_cast_or_null<DefinedCOFF>(sym))
|
||||||
|
return dyn_cast_or_null<SectionChunk>(dr->getChunk());
|
||||||
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (StringRef line : args::getLines(*mb)) {
|
||||||
|
SmallVector<StringRef, 3> fields;
|
||||||
|
line.split(fields, ' ');
|
||||||
|
uint64_t count;
|
||||||
|
|
||||||
|
if (fields.size() != 3 || !to_integer(fields[2], count)) {
|
||||||
|
error(path + ": parse error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SectionChunk *from = findSection(fields[0]))
|
||||||
|
if (SectionChunk *to = findSection(fields[1]))
|
||||||
|
config->callGraphProfile[{from, to}] += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void readCallGraphsFromObjectFiles() {
|
||||||
|
for (ObjFile *obj : ObjFile::instances) {
|
||||||
|
if (obj->callgraphSec) {
|
||||||
|
ArrayRef<uint8_t> contents;
|
||||||
|
cantFail(
|
||||||
|
obj->getCOFFObj()->getSectionContents(obj->callgraphSec, contents));
|
||||||
|
BinaryStreamReader reader(contents, support::little);
|
||||||
|
while (!reader.empty()) {
|
||||||
|
uint32_t fromIndex, toIndex;
|
||||||
|
uint64_t count;
|
||||||
|
if (Error err = reader.readInteger(fromIndex))
|
||||||
|
fatal(toString(obj) + ": Expected 32-bit integer");
|
||||||
|
if (Error err = reader.readInteger(toIndex))
|
||||||
|
fatal(toString(obj) + ": Expected 32-bit integer");
|
||||||
|
if (Error err = reader.readInteger(count))
|
||||||
|
fatal(toString(obj) + ": Expected 64-bit integer");
|
||||||
|
auto *fromSym = dyn_cast_or_null<Defined>(obj->getSymbol(fromIndex));
|
||||||
|
auto *toSym = dyn_cast_or_null<Defined>(obj->getSymbol(toIndex));
|
||||||
|
if (!fromSym || !toSym)
|
||||||
|
continue;
|
||||||
|
auto *from = dyn_cast_or_null<SectionChunk>(fromSym->getChunk());
|
||||||
|
auto *to = dyn_cast_or_null<SectionChunk>(toSym->getChunk());
|
||||||
|
if (from && to)
|
||||||
|
config->callGraphProfile[{from, to}] += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void markAddrsig(Symbol *s) {
|
static void markAddrsig(Symbol *s) {
|
||||||
if (auto *d = dyn_cast_or_null<Defined>(s))
|
if (auto *d = dyn_cast_or_null<Defined>(s))
|
||||||
if (SectionChunk *c = dyn_cast_or_null<SectionChunk>(d->getChunk()))
|
if (SectionChunk *c = dyn_cast_or_null<SectionChunk>(d->getChunk()))
|
||||||
|
@ -1587,9 +1657,11 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
|
||||||
args.hasFlag(OPT_auto_import, OPT_auto_import_no, config->mingw);
|
args.hasFlag(OPT_auto_import, OPT_auto_import_no, config->mingw);
|
||||||
config->pseudoRelocs = args.hasFlag(
|
config->pseudoRelocs = args.hasFlag(
|
||||||
OPT_runtime_pseudo_reloc, OPT_runtime_pseudo_reloc_no, config->mingw);
|
OPT_runtime_pseudo_reloc, OPT_runtime_pseudo_reloc_no, config->mingw);
|
||||||
|
config->callGraphProfileSort = args.hasFlag(
|
||||||
|
OPT_call_graph_profile_sort, OPT_call_graph_profile_sort_no, true);
|
||||||
|
|
||||||
// Don't warn about long section names, such as .debug_info, for mingw or when
|
// Don't warn about long section names, such as .debug_info, for mingw or
|
||||||
// -debug:dwarf is requested.
|
// when -debug:dwarf is requested.
|
||||||
if (config->mingw || config->debugDwarf)
|
if (config->mingw || config->debugDwarf)
|
||||||
config->warnLongSectionNames = false;
|
config->warnLongSectionNames = false;
|
||||||
|
|
||||||
|
@ -2024,8 +2096,24 @@ void LinkerDriver::link(ArrayRef<const char *> argsArr) {
|
||||||
// Handle /order. We want to do this at this moment because we
|
// Handle /order. We want to do this at this moment because we
|
||||||
// need a complete list of comdat sections to warn on nonexistent
|
// need a complete list of comdat sections to warn on nonexistent
|
||||||
// functions.
|
// functions.
|
||||||
if (auto *arg = args.getLastArg(OPT_order))
|
if (auto *arg = args.getLastArg(OPT_order)) {
|
||||||
|
if (args.hasArg(OPT_call_graph_ordering_file))
|
||||||
|
error("/order and /call-graph-order-file may not be used together");
|
||||||
parseOrderFile(arg->getValue());
|
parseOrderFile(arg->getValue());
|
||||||
|
config->callGraphProfileSort = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle /call-graph-ordering-file and /call-graph-profile-sort (default on).
|
||||||
|
if (config->callGraphProfileSort) {
|
||||||
|
if (auto *arg = args.getLastArg(OPT_call_graph_ordering_file)) {
|
||||||
|
parseCallGraphFile(arg->getValue());
|
||||||
|
}
|
||||||
|
readCallGraphsFromObjectFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle /print-symbol-order.
|
||||||
|
if (auto *arg = args.getLastArg(OPT_print_symbol_order))
|
||||||
|
config->printSymbolOrder = arg->getValue();
|
||||||
|
|
||||||
// Identify unreferenced COMDAT sections.
|
// Identify unreferenced COMDAT sections.
|
||||||
if (config->doGC)
|
if (config->doGC)
|
||||||
|
|
|
@ -249,6 +249,11 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name == ".llvm.call-graph-profile") {
|
||||||
|
callgraphSec = sec;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// Object files may have DWARF debug info or MS CodeView debug info
|
// Object files may have DWARF debug info or MS CodeView debug info
|
||||||
// (or both).
|
// (or both).
|
||||||
//
|
//
|
||||||
|
|
|
@ -191,6 +191,8 @@ public:
|
||||||
|
|
||||||
const coff_section *addrsigSec = nullptr;
|
const coff_section *addrsigSec = nullptr;
|
||||||
|
|
||||||
|
const coff_section *callgraphSec = nullptr;
|
||||||
|
|
||||||
// When using Microsoft precompiled headers, this is the PCH's key.
|
// When using Microsoft precompiled headers, this is the PCH's key.
|
||||||
// The same key is used by both the precompiled object, and objects using the
|
// The same key is used by both the precompiled object, and objects using the
|
||||||
// precompiled object. Any difference indicates out-of-date objects.
|
// precompiled object. Any difference indicates out-of-date objects.
|
||||||
|
|
|
@ -235,6 +235,17 @@ def dash_dash_version : Flag<["--"], "version">,
|
||||||
def threads
|
def threads
|
||||||
: P<"threads", "Number of threads. '1' disables multi-threading. By "
|
: P<"threads", "Number of threads. '1' disables multi-threading. By "
|
||||||
"default all available hardware threads are used">;
|
"default all available hardware threads are used">;
|
||||||
|
def call_graph_ordering_file: P<
|
||||||
|
"call-graph-ordering-file",
|
||||||
|
"Layout sections to optimize the given callgraph">;
|
||||||
|
defm call_graph_profile_sort: B<
|
||||||
|
"call-graph-profile-sort",
|
||||||
|
"Reorder sections with call graph profile (default)",
|
||||||
|
"Do not reorder sections with call graph profile">;
|
||||||
|
def print_symbol_order: P<
|
||||||
|
"print-symbol-order",
|
||||||
|
"Print a symbol order specified by /call-graph-ordering-file and "
|
||||||
|
"/call-graph-profile-sort into the specified file">;
|
||||||
|
|
||||||
// Flags for debugging
|
// Flags for debugging
|
||||||
def lldmap : F<"lldmap">;
|
def lldmap : F<"lldmap">;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#include "Writer.h"
|
#include "Writer.h"
|
||||||
|
#include "CallGraphSort.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "DLL.h"
|
#include "DLL.h"
|
||||||
#include "InputFiles.h"
|
#include "InputFiles.h"
|
||||||
|
@ -229,6 +230,7 @@ private:
|
||||||
void setSectionPermissions();
|
void setSectionPermissions();
|
||||||
void writeSections();
|
void writeSections();
|
||||||
void writeBuildId();
|
void writeBuildId();
|
||||||
|
void sortSections();
|
||||||
void sortExceptionTable();
|
void sortExceptionTable();
|
||||||
void sortCRTSectionChunks(std::vector<Chunk *> &chunks);
|
void sortCRTSectionChunks(std::vector<Chunk *> &chunks);
|
||||||
void addSyntheticIdata();
|
void addSyntheticIdata();
|
||||||
|
@ -798,6 +800,19 @@ static bool shouldStripSectionSuffix(SectionChunk *sc, StringRef name) {
|
||||||
name.startswith(".xdata$") || name.startswith(".eh_frame$");
|
name.startswith(".xdata$") || name.startswith(".eh_frame$");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Writer::sortSections() {
|
||||||
|
if (!config->callGraphProfile.empty()) {
|
||||||
|
DenseMap<const SectionChunk *, int> order = computeCallGraphProfileOrder();
|
||||||
|
for (auto it : order) {
|
||||||
|
if (DefinedRegular *sym = it.first->sym)
|
||||||
|
config->order[sym->getName()] = it.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!config->order.empty())
|
||||||
|
for (auto it : partialSections)
|
||||||
|
sortBySectionOrder(it.second->chunks);
|
||||||
|
}
|
||||||
|
|
||||||
// Create output section objects and add them to OutputSections.
|
// Create output section objects and add them to OutputSections.
|
||||||
void Writer::createSections() {
|
void Writer::createSections() {
|
||||||
// First, create the builtin sections.
|
// First, create the builtin sections.
|
||||||
|
@ -861,10 +876,7 @@ void Writer::createSections() {
|
||||||
if (hasIdata)
|
if (hasIdata)
|
||||||
addSyntheticIdata();
|
addSyntheticIdata();
|
||||||
|
|
||||||
// Process an /order option.
|
sortSections();
|
||||||
if (!config->order.empty())
|
|
||||||
for (auto it : partialSections)
|
|
||||||
sortBySectionOrder(it.second->chunks);
|
|
||||||
|
|
||||||
if (hasIdata)
|
if (hasIdata)
|
||||||
locateImportTables();
|
locateImportTables();
|
||||||
|
|
|
@ -68,7 +68,7 @@ struct Cluster {
|
||||||
|
|
||||||
int next;
|
int next;
|
||||||
int prev;
|
int prev;
|
||||||
size_t size = 0;
|
uint64_t size;
|
||||||
uint64_t weight = 0;
|
uint64_t weight = 0;
|
||||||
uint64_t initialWeight = 0;
|
uint64_t initialWeight = 0;
|
||||||
Edge bestPred = {-1, 0};
|
Edge bestPred = {-1, 0};
|
||||||
|
@ -223,14 +223,14 @@ DenseMap<const InputSectionBase *, int> CallGraphSort::run() {
|
||||||
|
|
||||||
DenseMap<const InputSectionBase *, int> orderMap;
|
DenseMap<const InputSectionBase *, int> orderMap;
|
||||||
int curOrder = 1;
|
int curOrder = 1;
|
||||||
for (int leader : sorted)
|
for (int leader : sorted) {
|
||||||
for (int i = leader;;) {
|
for (int i = leader;;) {
|
||||||
orderMap[sections[i]] = curOrder++;
|
orderMap[sections[i]] = curOrder++;
|
||||||
i = clusters[i].next;
|
i = clusters[i].next;
|
||||||
if (i == leader)
|
if (i == leader)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!config->printSymbolOrder.empty()) {
|
if (!config->printSymbolOrder.empty()) {
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
|
raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
# This test checks that CallGraphSort ignores edges that would form "bad"
|
||||||
|
# clusters.
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
# RUN: echo "A C 1" > %t.call_graph
|
||||||
|
# RUN: echo "E B 4" >> %t.call_graph
|
||||||
|
# RUN: echo "C D 2" >> %t.call_graph
|
||||||
|
# RUN: echo "B D 1" >> %t.call_graph
|
||||||
|
# RUN: echo "F G 6" >> %t.call_graph
|
||||||
|
# RUN: echo "G H 5" >> %t.call_graph
|
||||||
|
# RUN: echo "H I 4" >> %t.call_graph
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /call-graph-ordering-file:%t.call_graph /out:%t2 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,A
|
||||||
|
.globl A
|
||||||
|
A:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,D
|
||||||
|
D:
|
||||||
|
.fill 1000, 1, 0
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,E
|
||||||
|
E:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,C
|
||||||
|
C:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,B
|
||||||
|
B:
|
||||||
|
.fill 1000, 1, 0
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,F
|
||||||
|
F:
|
||||||
|
.fill (1024 * 1024) - 1, 1, 0
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,G
|
||||||
|
G:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,H
|
||||||
|
H:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax",one_only,I
|
||||||
|
I:
|
||||||
|
.fill 13, 1, 0
|
||||||
|
|
||||||
|
# CHECK: 140001000 t H
|
||||||
|
# CHECK: 140001001 t I
|
||||||
|
# CHECK: 14000100e T A
|
||||||
|
# CHECK: 14000100f t C
|
||||||
|
# CHECK: 140001010 t E
|
||||||
|
# CHECK: 140001011 t B
|
||||||
|
# CHECK: 1400013f9 t D
|
||||||
|
# CHECK: 1400017e1 t F
|
||||||
|
# CHECK: 1401017e0 t G
|
|
@ -0,0 +1,11 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
|
||||||
|
# RUN: echo "A B C 100" > %t.call_graph
|
||||||
|
# RUN: not lld-link /dll /noentry /subsystem:console %t /call-graph-ordering-file:%t.call_graph /out:/dev/null 2>&1 | FileCheck %s
|
||||||
|
|
||||||
|
# CHECK: {{.*}}.call_graph: parse error
|
||||||
|
|
||||||
|
# RUN: echo "A B C" > %t.call_graph
|
||||||
|
# RUN: not lld-link /dll /noentry /subsystem:console %t /call-graph-ordering-file:%t.call_graph /out:/dev/null 2>&1 | FileCheck %s
|
|
@ -0,0 +1,45 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
# Test the compatibility of ICF and cgprofile.
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
# RUN: echo "A B 100" > %t.call_graph
|
||||||
|
# RUN: echo "A C 40" >> %t.call_graph
|
||||||
|
# RUN: echo "C D 61" >> %t.call_graph
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /call-graph-ordering-file:%t.call_graph /out:%t2 /debug:symtab /opt:icf
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /call-graph-ordering-file:%t.call_graph /out:%t2 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s --check-prefix=NOICF
|
||||||
|
|
||||||
|
.section .text,"x",one_only,D
|
||||||
|
.globl D
|
||||||
|
D:
|
||||||
|
mov $60, %rax
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"x",one_only,C
|
||||||
|
.globl C
|
||||||
|
C:
|
||||||
|
mov $60, %rax
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"x",one_only,B
|
||||||
|
.globl B
|
||||||
|
B:
|
||||||
|
mov $2, %rax
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"x",one_only,A
|
||||||
|
.globl A
|
||||||
|
A:
|
||||||
|
mov $42, %rax
|
||||||
|
retq
|
||||||
|
|
||||||
|
# CHECK: 140001000 T A
|
||||||
|
# CHECK: 140001008 T C
|
||||||
|
# CHECK: 140001008 T D
|
||||||
|
# CHECK: 140001010 T B
|
||||||
|
|
||||||
|
# NOICF: 140001000 T A
|
||||||
|
# NOICF: 140001008 T B
|
||||||
|
# NOICF: 140001010 T C
|
||||||
|
# NOICF: 140001018 T D
|
|
@ -0,0 +1,45 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /out:%t2 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s
|
||||||
|
# RUN: lld-link /call-graph-profile-sort:no /subsystem:console /entry:A %t /out:%t3 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t3 | FileCheck %s --check-prefix=NO-CG
|
||||||
|
|
||||||
|
.section .text,"ax", one_only, D
|
||||||
|
D:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax", one_only, C
|
||||||
|
.globl C
|
||||||
|
C:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax", one_only, B
|
||||||
|
.globl B
|
||||||
|
B:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.section .text,"ax", one_only, A
|
||||||
|
.globl A
|
||||||
|
A:
|
||||||
|
Aa:
|
||||||
|
retq
|
||||||
|
|
||||||
|
.cg_profile A, B, 10
|
||||||
|
.cg_profile A, B, 10
|
||||||
|
.cg_profile Aa, B, 80
|
||||||
|
.cg_profile A, C, 40
|
||||||
|
.cg_profile B, C, 30
|
||||||
|
.cg_profile C, D, 90
|
||||||
|
|
||||||
|
# CHECK: 140001000 T A
|
||||||
|
# CHECK: 140001001 T B
|
||||||
|
# CHECK: 140001002 T C
|
||||||
|
# CHECK: 140001003 t D
|
||||||
|
|
||||||
|
|
||||||
|
# NO-CG: 140001000 t D
|
||||||
|
# NO-CG: 140001001 T C
|
||||||
|
# NO-CG: 140001002 T B
|
||||||
|
# NO-CG: 140001003 T A
|
|
@ -0,0 +1,34 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
# RUN: echo "A B 5" > %t.call_graph
|
||||||
|
# RUN: echo "B C 50" >> %t.call_graph
|
||||||
|
# RUN: echo "C D 40" >> %t.call_graph
|
||||||
|
# RUN: echo "D B 10" >> %t.call_graph
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /call-graph-ordering-file:%t.call_graph /out:%t2 /print-symbol-order:%t3
|
||||||
|
# RUN: FileCheck %s --input-file %t3
|
||||||
|
|
||||||
|
# CHECK: B
|
||||||
|
# CHECK-NEXT: C
|
||||||
|
# CHECK-NEXT: D
|
||||||
|
# CHECK-NEXT: A
|
||||||
|
|
||||||
|
.section .text, "x", one_only, A
|
||||||
|
.globl A
|
||||||
|
A:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, B
|
||||||
|
.globl B
|
||||||
|
B:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, C
|
||||||
|
.globl C
|
||||||
|
C:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, D
|
||||||
|
.globl D
|
||||||
|
D:
|
||||||
|
nop
|
|
@ -0,0 +1,43 @@
|
||||||
|
# REQUIRES: x86
|
||||||
|
# Test correctness of call graph ordering.
|
||||||
|
|
||||||
|
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-win32 %s -o %t
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /out:%t2 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s --check-prefix=NOSORT
|
||||||
|
|
||||||
|
# RUN: echo "A B 5" > %t.call_graph
|
||||||
|
# RUN: echo "B C 50" >> %t.call_graph
|
||||||
|
# RUN: echo "C D 40" >> %t.call_graph
|
||||||
|
# RUN: echo "D B 10" >> %t.call_graph
|
||||||
|
# RUN: lld-link /subsystem:console /entry:A %t /call-graph-ordering-file:%t.call_graph /out:%t2 /debug:symtab
|
||||||
|
# RUN: llvm-nm --numeric-sort %t2 | FileCheck %s
|
||||||
|
|
||||||
|
# NOSORT: 140001000 T A
|
||||||
|
# NOSORT: 140001001 T B
|
||||||
|
# NOSORT: 140001002 T C
|
||||||
|
# NOSORT: 140001003 T D
|
||||||
|
|
||||||
|
# CHECK: 140001000 T B
|
||||||
|
# CHECK: 140001001 T C
|
||||||
|
# CHECK: 140001002 T D
|
||||||
|
# CHECK: 140001003 T A
|
||||||
|
|
||||||
|
.section .text, "x", one_only, A
|
||||||
|
.globl A
|
||||||
|
A:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, B
|
||||||
|
.globl B
|
||||||
|
B:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, C
|
||||||
|
.globl C
|
||||||
|
C:
|
||||||
|
nop
|
||||||
|
|
||||||
|
.section .text, "x", one_only, D
|
||||||
|
.globl D
|
||||||
|
D:
|
||||||
|
nop
|
Loading…
Reference in New Issue