[ORC] LinkGraphLinkingLayer::registerDependencies improvements. (#189298)
This commit moves the bulk of
LinkGraphLinkingLayer::registerDependencies into a new static method,
LinkGraphLinkingLayer::calculateDepGroups, where the behavior can be
unit tested.
The new method returns a list of LinkGraphLinkingLayer::SymbolDepGroups:
```
struct SymbolDepGroup {
SmallVector<jitlink::Symbol*> Defs;
DenseSet<jitlink::Symbol*> Deps;
};
```
The existing registerDependencies method converts these into
orc::SymbolDependenceGroups for registration with the ExecutionSession.
The calculateDepGroups method uses a new algorithm for calculating
dependencies between symbols in the LinkGraph. As before, the goal is to
compute dependencies of non-locally-scoped symbols defined within the
graph on other non-locally-scoped symbols in the graph (whether defined
by the graph or external). It is sufficient to record the first
non-locally-scoped symbol defined for each block reached (since such
symbols will have their own dependencies reported, and all such symbols
for a given block will have the same dependencies). The new algorithm
uses SCCIterator to visit strongly connected components within the
subgraph formed by edges to "anonymous" blocks (i.e. blocks that do not
define any non-locally-scoped symbols). These are visited in reverse-DFS
order, allowing dependencies to be efficiently propagated.
This change results in a ~2x speedup when JIT-loading clang (as tested
on a 2023 MacBook Pro, 12-core M3 Pro, 18Gb).
This commit is contained in:
parent
c963ce446c
commit
75eae603ff
@ -29,6 +29,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class LinkGraphLinkingLayerTests;
|
||||
|
||||
namespace llvm {
|
||||
|
||||
namespace jitlink {
|
||||
@ -43,6 +45,7 @@ namespace orc {
|
||||
/// serves as a base for the ObjectLinkingLayer that can link object files.
|
||||
class LLVM_ABI LinkGraphLinkingLayer : public LinkGraphLayer,
|
||||
private ResourceManager {
|
||||
friend class ::LinkGraphLinkingLayerTests;
|
||||
class JITLinkCtx;
|
||||
|
||||
public:
|
||||
@ -158,9 +161,16 @@ protected:
|
||||
private:
|
||||
using FinalizedAlloc = jitlink::JITLinkMemoryManager::FinalizedAlloc;
|
||||
|
||||
struct SymbolDepGroup {
|
||||
SmallVector<jitlink::Symbol *> Defs;
|
||||
DenseSet<jitlink::Symbol *> Deps;
|
||||
};
|
||||
|
||||
// Provides the basis for calculating orc::SymbolDependenceGroups.
|
||||
static SmallVector<SymbolDepGroup> calculateDepGroups(jitlink::LinkGraph &G);
|
||||
|
||||
Error recordFinalizedAlloc(MaterializationResponsibility &MR,
|
||||
FinalizedAlloc FA);
|
||||
|
||||
Error handleRemoveResources(JITDylib &JD, ResourceKey K) override;
|
||||
void handleTransferResources(JITDylib &JD, ResourceKey DstKey,
|
||||
ResourceKey SrcKey) override;
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h"
|
||||
|
||||
#include "llvm/ADT/SCCIterator.h"
|
||||
#include "llvm/ExecutionEngine/JITLink/EHFrameSupport.h"
|
||||
#include "llvm/ExecutionEngine/JITLink/aarch32.h"
|
||||
#include "llvm/ExecutionEngine/Orc/DebugUtils.h"
|
||||
@ -19,6 +21,75 @@ using namespace llvm;
|
||||
using namespace llvm::jitlink;
|
||||
using namespace llvm::orc;
|
||||
|
||||
namespace llvm {
|
||||
|
||||
struct BlockDepInfo;
|
||||
|
||||
using BlockDepInfoMap = DenseMap<jitlink::Block *, BlockDepInfo>;
|
||||
|
||||
struct BlockDepInfo {
|
||||
using SymbolDefList = SmallVector<jitlink::Symbol *>;
|
||||
using SymbolDepSet = DenseSet<jitlink::Symbol *>;
|
||||
using AnonBlockDepSet = DenseSet<jitlink::Block *>;
|
||||
|
||||
BlockDepInfoMap *Graph = nullptr;
|
||||
SymbolDefList SymbolDefs;
|
||||
SymbolDepSet SymbolDeps;
|
||||
AnonBlockDepSet AnonBlockDeps;
|
||||
BlockDepInfo *SCCRoot = nullptr;
|
||||
std::optional<size_t> DepGroupIndex;
|
||||
};
|
||||
|
||||
template <> struct GraphTraits<BlockDepInfo *> {
|
||||
using NodeRef = BlockDepInfo *;
|
||||
|
||||
class ChildIteratorType {
|
||||
using impl_iterator = BlockDepInfo::AnonBlockDepSet::iterator;
|
||||
|
||||
public:
|
||||
ChildIteratorType(NodeRef Parent, impl_iterator I)
|
||||
: Parent(Parent), I(std::move(I)) {}
|
||||
|
||||
friend bool operator==(const ChildIteratorType &LHS,
|
||||
const ChildIteratorType &RHS) {
|
||||
return LHS.I == RHS.I;
|
||||
}
|
||||
friend bool operator!=(const ChildIteratorType &LHS,
|
||||
const ChildIteratorType &RHS) {
|
||||
return LHS.I != RHS.I;
|
||||
}
|
||||
|
||||
ChildIteratorType &operator++() {
|
||||
++I;
|
||||
return *this;
|
||||
}
|
||||
ChildIteratorType operator++(int) {
|
||||
auto Tmp = *this;
|
||||
++I;
|
||||
return Tmp;
|
||||
}
|
||||
NodeRef operator*() {
|
||||
assert(Parent->Graph && "No pointer to BlockDepInfoMap");
|
||||
return &(*Parent->Graph)[*I];
|
||||
}
|
||||
|
||||
private:
|
||||
NodeRef Parent;
|
||||
BlockDepInfo::AnonBlockDepSet::iterator I;
|
||||
};
|
||||
|
||||
static NodeRef getEntryNode(NodeRef N) { return N; }
|
||||
|
||||
static ChildIteratorType child_begin(NodeRef N) {
|
||||
return ChildIteratorType(N, N->AnonBlockDeps.begin());
|
||||
}
|
||||
static ChildIteratorType child_end(NodeRef N) {
|
||||
return ChildIteratorType(N, N->AnonBlockDeps.end());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace llvm
|
||||
|
||||
namespace {
|
||||
|
||||
ExecutorAddr getJITSymbolPtrForSymbol(Symbol &Sym, const Triple &TT) {
|
||||
@ -309,155 +380,25 @@ private:
|
||||
}
|
||||
|
||||
Error registerDependencies(LinkGraph &G) {
|
||||
|
||||
struct BlockInfo {
|
||||
bool InWorklist = false;
|
||||
DenseSet<Symbol *> Defs;
|
||||
DenseSet<Symbol *> SymbolDeps;
|
||||
DenseSet<Block *> AnonEdges, AnonBackEdges;
|
||||
};
|
||||
|
||||
DenseMap<Block *, BlockInfo> BlockInfos;
|
||||
|
||||
// Reserve space so that BlockInfos doesn't need to resize. This is
|
||||
// essential to avoid invalidating pointers to entries below.
|
||||
{
|
||||
size_t NumBlocks = 0;
|
||||
for (auto &Sec : G.sections())
|
||||
NumBlocks += Sec.blocks_size();
|
||||
BlockInfos.reserve(NumBlocks);
|
||||
}
|
||||
|
||||
// Identify non-locally-scoped symbols defined by each block.
|
||||
for (auto *Sym : G.defined_symbols()) {
|
||||
if (Sym->getScope() != Scope::Local)
|
||||
BlockInfos[&Sym->getBlock()].Defs.insert(Sym);
|
||||
}
|
||||
|
||||
// Identify the symbolic and anonymous-block dependencies for each block.
|
||||
for (auto *B : G.blocks()) {
|
||||
auto &BI = BlockInfos[B];
|
||||
|
||||
for (auto &E : B->edges()) {
|
||||
|
||||
// External symbols are trivially depended on.
|
||||
if (E.getTarget().isExternal()) {
|
||||
BI.SymbolDeps.insert(&E.getTarget());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Anonymous symbols aren't depended on at all (they're assumed to be
|
||||
// already available).
|
||||
if (E.getTarget().isAbsolute())
|
||||
continue;
|
||||
|
||||
// If we get here then we depend on a symbol defined by some other
|
||||
// block.
|
||||
auto &TgtBI = BlockInfos[&E.getTarget().getBlock()];
|
||||
|
||||
// If that block has any definitions then use the first one as the
|
||||
// "effective" dependence here (all symbols in TgtBI will become
|
||||
// ready at the same time, and choosing a single symbol to represent
|
||||
// the block keeps the SymbolDepGroup size small).
|
||||
if (!TgtBI.Defs.empty()) {
|
||||
BI.SymbolDeps.insert(*TgtBI.Defs.begin());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise we've got a dependence on an anonymous block. Record it
|
||||
// here for back-propagating symbol dependencies below.
|
||||
BI.AnonEdges.insert(&E.getTarget().getBlock());
|
||||
TgtBI.AnonBackEdges.insert(B);
|
||||
}
|
||||
}
|
||||
|
||||
// Prune anonymous blocks.
|
||||
{
|
||||
std::vector<Block *> BlocksToRemove;
|
||||
for (auto &[B, BI] : BlockInfos) {
|
||||
// Skip blocks with defs. We only care about anonyous blocks.
|
||||
if (!BI.Defs.empty())
|
||||
continue;
|
||||
|
||||
BlocksToRemove.push_back(B);
|
||||
|
||||
for (auto *FB : BI.AnonEdges)
|
||||
BlockInfos[FB].AnonBackEdges.erase(B);
|
||||
|
||||
for (auto *BB : BI.AnonBackEdges)
|
||||
BlockInfos[BB].AnonEdges.erase(B);
|
||||
|
||||
for (auto *FB : BI.AnonEdges) {
|
||||
auto &FBI = BlockInfos[FB];
|
||||
FBI.AnonBackEdges.insert_range(BI.AnonBackEdges);
|
||||
}
|
||||
|
||||
for (auto *BB : BI.AnonBackEdges) {
|
||||
auto &BBI = BlockInfos[BB];
|
||||
BBI.SymbolDeps.insert_range(BI.SymbolDeps);
|
||||
BBI.AnonEdges.insert_range(BI.AnonEdges);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto *B : BlocksToRemove)
|
||||
BlockInfos.erase(B);
|
||||
}
|
||||
|
||||
// Build the initial dependence propagation worklist.
|
||||
std::deque<Block *> Worklist;
|
||||
for (auto &[B, BI] : BlockInfos) {
|
||||
if (!BI.SymbolDeps.empty() && !BI.AnonBackEdges.empty()) {
|
||||
Worklist.push_back(B);
|
||||
BI.InWorklist = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate symbol deps through the graph.
|
||||
while (!Worklist.empty()) {
|
||||
auto *B = Worklist.front();
|
||||
Worklist.pop_front();
|
||||
|
||||
auto &BI = BlockInfos[B];
|
||||
BI.InWorklist = false;
|
||||
|
||||
for (auto *DB : BI.AnonBackEdges) {
|
||||
auto &DBI = BlockInfos[DB];
|
||||
for (auto *Sym : BI.SymbolDeps) {
|
||||
if (DBI.SymbolDeps.insert(Sym).second && !DBI.InWorklist) {
|
||||
Worklist.push_back(DB);
|
||||
DBI.InWorklist = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform our local dependence information into a list of
|
||||
// SymbolDependenceGroups (in the SymbolDepGroups member), ready for use in
|
||||
// the upcoming notifyFinalized call.
|
||||
auto &TargetJD = MR->getTargetJITDylib();
|
||||
|
||||
for (auto &[B, BI] : BlockInfos) {
|
||||
if (!BI.Defs.empty()) {
|
||||
SymbolDepGroups.push_back(SymbolDependenceGroup());
|
||||
auto &SDG = SymbolDepGroups.back();
|
||||
|
||||
for (auto *Def : BI.Defs)
|
||||
SDG.Symbols.insert(Def->getName());
|
||||
|
||||
for (auto *Dep : BI.SymbolDeps) {
|
||||
auto DepName = Dep->getName();
|
||||
if (Dep->isDefined())
|
||||
SDG.Dependencies[&TargetJD].insert(std::move(DepName));
|
||||
else {
|
||||
auto SourceJDItr =
|
||||
SymbolSourceJDs.find(NonOwningSymbolStringPtr(DepName));
|
||||
if (SourceJDItr != SymbolSourceJDs.end())
|
||||
SDG.Dependencies[SourceJDItr->second].insert(std::move(DepName));
|
||||
for (auto &[Defs, Deps] : calculateDepGroups(G)) {
|
||||
SymbolDepGroups.push_back(SymbolDependenceGroup());
|
||||
auto &SDG = SymbolDepGroups.back();
|
||||
for (auto *Def : Defs)
|
||||
SDG.Symbols.insert(Def->getName());
|
||||
for (auto *Dep : Deps) {
|
||||
if (Dep->isDefined())
|
||||
SDG.Dependencies[&TargetJD].insert(Dep->getName());
|
||||
else {
|
||||
auto I =
|
||||
SymbolSourceJDs.find(NonOwningSymbolStringPtr(Dep->getName()));
|
||||
if (I != SymbolSourceJDs.end()) {
|
||||
auto &SymJD = *I->second;
|
||||
SDG.Dependencies[&SymJD].insert(Dep->getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
@ -518,6 +459,181 @@ void LinkGraphLinkingLayer::emit(
|
||||
link(std::move(G), std::move(Ctx));
|
||||
}
|
||||
|
||||
SmallVector<LinkGraphLinkingLayer::SymbolDepGroup>
|
||||
LinkGraphLinkingLayer::calculateDepGroups(LinkGraph &G) {
|
||||
|
||||
// Step 1.
|
||||
// Build initial map entries and symbol def lists.
|
||||
BlockDepInfoMap BlockDepInfos;
|
||||
for (auto *Sym : G.defined_symbols())
|
||||
if (Sym->getScope() != Scope::Local)
|
||||
BlockDepInfos[&Sym->getBlock()].SymbolDefs.push_back(Sym);
|
||||
|
||||
// Step 2.
|
||||
// Complete the BlockDepInfos "graph" by adding symbol and block dependencies
|
||||
// for each block.
|
||||
{
|
||||
SmallVector<Block *> Worklist;
|
||||
Worklist.reserve(BlockDepInfos.size());
|
||||
|
||||
// Build worklist, link each BlockDepInfo "node" back to the BlockInfos map
|
||||
// "graph" for our GraphTraits specialization above. This will allow us to
|
||||
// walk the SCCs of the anonymous-block-dependence graph.
|
||||
for (auto &[B, BDInfo] : BlockDepInfos) {
|
||||
BDInfo.Graph = &BlockDepInfos;
|
||||
Worklist.push_back(B);
|
||||
}
|
||||
|
||||
// Calculate the relevant symbol and block dependencies for each block:
|
||||
// 1. Absolute symbols are ignored.
|
||||
// 2. External symbols are included in a block's symbol dep set.
|
||||
// 3. Blocks that do not define any symbols are included in the anonymous
|
||||
// block dependence sets.
|
||||
// 4. For blocks that do define symbols we add only the first defined
|
||||
// symbol to the symbol dep set (since all symbols for the block will
|
||||
// have the same dependencies).
|
||||
while (!Worklist.empty()) {
|
||||
auto *B = Worklist.pop_back_val();
|
||||
BlockDepInfo *BDInfo = nullptr; // Populated lazily.
|
||||
|
||||
for (auto &E : B->edges()) {
|
||||
if (E.getTarget().isAbsolute()) // skip: absolutes are assumed ready
|
||||
continue;
|
||||
|
||||
if (!BDInfo) // Populate -- we'll need it below.
|
||||
BDInfo = &BlockDepInfos[B];
|
||||
|
||||
if (E.getTarget().isExternal()) { // include and continue
|
||||
BDInfo->SymbolDeps.insert(&E.getTarget());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Target must be defined.
|
||||
auto *TgtB = &E.getTarget().getBlock();
|
||||
auto I = BlockDepInfos.find(TgtB);
|
||||
|
||||
if (I != BlockDepInfos.end()) {
|
||||
// TgtB is in BlockInfos. Record a symbol dependence (if it defines
|
||||
// any symbols) or anonymous block dependence.
|
||||
auto &TgtBInfo = I->second;
|
||||
if (!TgtBInfo.SymbolDefs.empty())
|
||||
BDInfo->SymbolDeps.insert(TgtBInfo.SymbolDefs.front());
|
||||
else
|
||||
BDInfo->AnonBlockDeps.insert(TgtB);
|
||||
} else {
|
||||
// TgtB not in BlockInfos. It must be anonymous. We need to:
|
||||
// 1. Record the dependence.
|
||||
// 2. Add BlockInfos and Worklist entries for TgtB.
|
||||
// 3. Reset BInfo, since step (2) may have invalidated the pointer.
|
||||
BDInfo->AnonBlockDeps.insert(TgtB);
|
||||
Worklist.push_back(TgtB);
|
||||
BlockDepInfos[TgtB].Graph = &BlockDepInfos;
|
||||
BDInfo = nullptr;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
// Convert block deps to SCC deps.
|
||||
SmallVector<SymbolDepGroup> DGs;
|
||||
for (auto &[B, BDInfo] : BlockDepInfos) {
|
||||
for (auto &SCC : make_range(scc_begin(&BDInfo), scc_end(&BDInfo))) {
|
||||
|
||||
auto &SCCRootInfo = *SCC.front();
|
||||
|
||||
// Continue if already visited. The loop over the SCC elements below
|
||||
// deletes the SCCs below as it goes, so this early continue just saves
|
||||
// us looking at a bunch of empty sets below that.
|
||||
if (SCCRootInfo.SCCRoot)
|
||||
continue;
|
||||
SCCRootInfo.SCCRoot = &SCCRootInfo;
|
||||
|
||||
// Collect all symbol defs, deps, and anonymous block deps, and remove
|
||||
// the links to already visited SCCs.
|
||||
auto SCCSymbolDefs = std::move(SCCRootInfo.SymbolDefs);
|
||||
auto SCCSymbolDeps = std::move(SCCRootInfo.SymbolDeps);
|
||||
auto SCCAnonBlockDeps = std::move(SCCRootInfo.AnonBlockDeps);
|
||||
for (auto *SCCBInfo : make_range(std::next(SCC.begin()), SCC.end())) {
|
||||
SCCBInfo->SCCRoot = &SCCRootInfo;
|
||||
SCCSymbolDefs.append(SCCBInfo->SymbolDefs);
|
||||
SCCBInfo->SymbolDefs.clear();
|
||||
SCCSymbolDeps.insert(SCCBInfo->SymbolDeps.begin(),
|
||||
SCCBInfo->SymbolDeps.end());
|
||||
SCCBInfo->SymbolDeps.clear();
|
||||
SCCAnonBlockDeps.insert(SCCBInfo->AnonBlockDeps.begin(),
|
||||
SCCBInfo->AnonBlockDeps.end());
|
||||
SCCBInfo->AnonBlockDeps.clear();
|
||||
}
|
||||
|
||||
// Identify DepGroups emitted for previously visited SCCs that this
|
||||
// SCC depends on.
|
||||
DenseSet<size_t> SrcDepGroups;
|
||||
for (auto *DepB : SCCAnonBlockDeps) {
|
||||
assert(BlockDepInfos.count(DepB) && "Unrecognized block");
|
||||
auto &DepBRootInfo = *BlockDepInfos[DepB].SCCRoot;
|
||||
if (DepBRootInfo.DepGroupIndex)
|
||||
SrcDepGroups.insert(*DepBRootInfo.DepGroupIndex);
|
||||
}
|
||||
|
||||
// If this SCC doesn't depend on any existing dep groups then check
|
||||
// whether it has direct symbol deps of its own.
|
||||
if (SrcDepGroups.empty()) {
|
||||
|
||||
// If this SCC has its own symbol deps then add a dep-group and
|
||||
// continue.
|
||||
if (!SCCSymbolDeps.empty()) {
|
||||
SCCRootInfo.DepGroupIndex = DGs.size();
|
||||
DGs.push_back({});
|
||||
DGs.back().Defs = std::move(SCCSymbolDefs);
|
||||
DGs.back().Deps = std::move(SCCSymbolDeps);
|
||||
}
|
||||
// Otherwise just continue.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Special case: If we only depend on one dep group and this SCC
|
||||
// doesn't have any symbol deps of its own then just merge this SCC's
|
||||
// defs into the existing dep group and continue.
|
||||
if (SrcDepGroups.size() == 1 && SCCSymbolDeps.empty()) {
|
||||
SCCRootInfo.DepGroupIndex = *SrcDepGroups.begin();
|
||||
DGs[*SCCRootInfo.DepGroupIndex].Defs.append(SCCSymbolDefs);
|
||||
continue;
|
||||
}
|
||||
|
||||
// General case: This SCC depends on multiple dep groups, and/or has
|
||||
// its own symbol deps. Build a new dep group for it.
|
||||
SCCRootInfo.DepGroupIndex = DGs.size();
|
||||
DGs.push_back({});
|
||||
auto &DG = DGs.back();
|
||||
DG.Defs = std::move(SCCSymbolDefs);
|
||||
for (auto &DGIndex : SrcDepGroups)
|
||||
DG.Deps.insert(DGs[DGIndex].Deps.begin(), DGs[DGIndex].Deps.end());
|
||||
DG.Deps.insert(SCCSymbolDeps.begin(), SCCSymbolDeps.end());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove self-reference from each dep group, and filter out any dep groups
|
||||
// whose resulting deps or defs are empty.
|
||||
for (size_t I = 0; I != DGs.size();) {
|
||||
auto &DG = DGs[I];
|
||||
|
||||
// Remove self-deps.
|
||||
for (auto &Def : DG.Defs)
|
||||
DG.Deps.erase(Def);
|
||||
|
||||
// Remove groups with empty defs or deps.
|
||||
if (DG.Defs.empty() || DG.Deps.empty()) {
|
||||
std::swap(DG, DGs.back());
|
||||
DGs.pop_back();
|
||||
} else
|
||||
++I;
|
||||
}
|
||||
|
||||
return DGs;
|
||||
}
|
||||
|
||||
Error LinkGraphLinkingLayer::recordFinalizedAlloc(
|
||||
MaterializationResponsibility &MR, FinalizedAlloc FA) {
|
||||
auto Err = MR.withResourceKeyDo(
|
||||
|
||||
@ -29,6 +29,7 @@ add_llvm_unittest(OrcJITTests
|
||||
JITTargetMachineBuilderTest.cpp
|
||||
LazyCallThroughAndReexportsTest.cpp
|
||||
LibraryResolverTest.cpp
|
||||
LinkGraphLinkingLayerTest.cpp
|
||||
LookupAndRecordAddrsTest.cpp
|
||||
MachOPlatformTest.cpp
|
||||
MapperJITLinkMemoryManagerTest.cpp
|
||||
|
||||
633
llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp
Normal file
633
llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp
Normal file
@ -0,0 +1,633 @@
|
||||
//===--- LinkGraphLinkingLayerTest.cpp - Unit tests for dep group calc ----===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h"
|
||||
|
||||
#include "llvm/ExecutionEngine/JITLink/JITLink.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::jitlink;
|
||||
using namespace llvm::orc;
|
||||
|
||||
static const char BlockContentBytes[] = {0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0};
|
||||
static ArrayRef<char> BlockContent(BlockContentBytes);
|
||||
|
||||
/// Test wrapper class that accesses LinkGraphLinkingLayer's private members
|
||||
/// via the friend declaration.
|
||||
class LinkGraphLinkingLayerTests : public testing::Test {
|
||||
protected:
|
||||
using SymbolDepGroup = LinkGraphLinkingLayer::SymbolDepGroup;
|
||||
|
||||
static SmallVector<SymbolDepGroup> calculateDepGroups(LinkGraph &G) {
|
||||
return LinkGraphLinkingLayer::calculateDepGroups(G);
|
||||
}
|
||||
|
||||
static std::unique_ptr<LinkGraph> makeGraph(StringRef Name = "test") {
|
||||
return std::make_unique<LinkGraph>(
|
||||
Name.str(), std::make_shared<SymbolStringPool>(),
|
||||
Triple("x86_64-apple-darwin"), SubtargetFeatures(),
|
||||
getGenericEdgeKindName);
|
||||
}
|
||||
};
|
||||
|
||||
// No blocks with non-local symbols: should produce no dep groups.
|
||||
TEST_F(LinkGraphLinkingLayerTests, EmptyGraph) {
|
||||
auto G = makeGraph();
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
EXPECT_TRUE(DGs.empty());
|
||||
}
|
||||
|
||||
// A single block with a defined symbol and no edges: no deps, so no dep group.
|
||||
TEST_F(LinkGraphLinkingLayerTests, SingleBlockNoDeps) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &B =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
G->addDefinedSymbol(B, 0, "foo", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
EXPECT_TRUE(DGs.empty());
|
||||
}
|
||||
|
||||
// A single block with a defined symbol that depends on an external symbol.
|
||||
TEST_F(LinkGraphLinkingLayerTests, SingleBlockExternalDep) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &B =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
G->addDefinedSymbol(B, 0, "foo", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &ExtSym = G->addExternalSymbol("bar", 0, false);
|
||||
B.addEdge(Edge::FirstRelocation, 0, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("foo"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// Two blocks, each with a defined symbol. Block A depends on Block B's symbol.
|
||||
TEST_F(LinkGraphLinkingLayerTests, TwoBlocksDirectDep) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
// A -> B edge (through SymB).
|
||||
BA.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
// The dep group should contain A's def and B as a dependency.
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&SymB));
|
||||
}
|
||||
|
||||
// Two independent blocks with no edges between them: no dep groups.
|
||||
TEST_F(LinkGraphLinkingLayerTests, TwoIndependentBlocks) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
EXPECT_TRUE(DGs.empty());
|
||||
}
|
||||
|
||||
// Block A -> anonymous block -> Block B. The anonymous block should be
|
||||
// transparent, and A should transitively depend on B.
|
||||
TEST_F(LinkGraphLinkingLayerTests, TransitiveThroughAnonymousBlock) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
// BAnon has no named symbols (anonymous block).
|
||||
auto &AnonSym = G->addAnonymousSymbol(BAnon, 0, 4, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
|
||||
// A -> AnonSym (in BAnon), BAnon -> SymB (in BB).
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&SymB));
|
||||
}
|
||||
|
||||
// Block A depends on an external and an absolute symbol. Only the external
|
||||
// should appear in deps (absolutes are assumed ready).
|
||||
TEST_F(LinkGraphLinkingLayerTests, AbsoluteSymbolIgnored) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &B =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
G->addDefinedSymbol(B, 0, "foo", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
auto &AbsSym = G->addAbsoluteSymbol("abs", ExecutorAddr(0x42), 0,
|
||||
Linkage::Strong, Scope::Default, true);
|
||||
B.addEdge(Edge::FirstRelocation, 0, ExtSym, 0);
|
||||
B.addEdge(Edge::FirstRelocation, 4, AbsSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// Local-scoped symbols should not appear in dep groups.
|
||||
TEST_F(LinkGraphLinkingLayerTests, LocalScopeSymbolsIgnored) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &B =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
G->addDefinedSymbol(B, 0, "local_sym", 4, Linkage::Strong, Scope::Local,
|
||||
false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
B.addEdge(Edge::FirstRelocation, 0, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
// Local symbol shouldn't be tracked, so no dep group.
|
||||
EXPECT_TRUE(DGs.empty());
|
||||
}
|
||||
|
||||
// Multiple symbols in the same block should end up in the same dep group.
|
||||
TEST_F(LinkGraphLinkingLayerTests, MultipleSymbolsSameBlock) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &B =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
G->addDefinedSymbol(B, 0, "foo", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
G->addDefinedSymbol(B, 4, "bar", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
B.addEdge(Edge::FirstRelocation, 0, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 2u);
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// A -> B -> A cycle (through named symbols). Named blocks don't participate
|
||||
// in the SCC graph — only anonymous blocks do. So A and B each get their own
|
||||
// dep group, with the other as a dependency.
|
||||
TEST_F(LinkGraphLinkingLayerTests, CycleTwoNamedBlocks) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &SymA = G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
// A -> B, B -> A.
|
||||
BA.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 0, SymA, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
// Each named block is its own SCC. A depends on B and B depends on A.
|
||||
ASSERT_EQ(DGs.size(), 2u);
|
||||
SymbolDepGroup *DGA = nullptr, *DGB = nullptr;
|
||||
for (auto &DG : DGs) {
|
||||
ASSERT_EQ(DG.Defs.size(), 1u);
|
||||
if (DG.Defs[0]->getName() == G->intern("A"))
|
||||
DGA = &DG;
|
||||
else if (DG.Defs[0]->getName() == G->intern("B"))
|
||||
DGB = &DG;
|
||||
}
|
||||
ASSERT_NE(DGA, nullptr);
|
||||
ASSERT_NE(DGB, nullptr);
|
||||
EXPECT_EQ(DGA->Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGA->Deps.count(&SymB));
|
||||
EXPECT_EQ(DGB->Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGB->Deps.count(&SymA));
|
||||
}
|
||||
|
||||
// A -> B -> A cycle where A also depends on external "ext". Since named
|
||||
// blocks don't form SCCs, A and B get separate dep groups.
|
||||
TEST_F(LinkGraphLinkingLayerTests, CycleWithExternalDep) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &SymA = G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
BA.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
BA.addEdge(Edge::FirstRelocation, 4, ExtSym, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 0, SymA, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 2u);
|
||||
SymbolDepGroup *DGA = nullptr, *DGB = nullptr;
|
||||
for (auto &DG : DGs) {
|
||||
ASSERT_EQ(DG.Defs.size(), 1u);
|
||||
if (DG.Defs[0]->getName() == G->intern("A"))
|
||||
DGA = &DG;
|
||||
else if (DG.Defs[0]->getName() == G->intern("B"))
|
||||
DGB = &DG;
|
||||
}
|
||||
ASSERT_NE(DGA, nullptr);
|
||||
ASSERT_NE(DGB, nullptr);
|
||||
// A depends on B and ext.
|
||||
EXPECT_EQ(DGA->Deps.size(), 2u);
|
||||
EXPECT_TRUE(DGA->Deps.count(&SymB));
|
||||
EXPECT_TRUE(DGA->Deps.count(&ExtSym));
|
||||
// B depends on A.
|
||||
EXPECT_EQ(DGB->Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGB->Deps.count(&SymA));
|
||||
}
|
||||
|
||||
// Chain through multiple anonymous blocks:
|
||||
// A -> anon1 -> anon2 -> B
|
||||
TEST_F(LinkGraphLinkingLayerTests, ChainThroughMultipleAnonymousBlocks) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon1 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon2 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x4000), 8, 0);
|
||||
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym1 = G->addAnonymousSymbol(BAnon1, 0, 4, false, true);
|
||||
auto &AnonSym2 = G->addAnonymousSymbol(BAnon2, 0, 4, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 0, AnonSym2, 0);
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&SymB));
|
||||
}
|
||||
|
||||
// Diamond through anonymous blocks:
|
||||
// A -> anon1 -> B
|
||||
// A -> anon2 -> B
|
||||
// A should depend on B (once).
|
||||
TEST_F(LinkGraphLinkingLayerTests, DiamondThroughAnonymousBlocks) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon1 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon2 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x4000), 8, 0);
|
||||
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym1 = G->addAnonymousSymbol(BAnon1, 0, 4, false, true);
|
||||
auto &AnonSym2 = G->addAnonymousSymbol(BAnon2, 0, 4, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0);
|
||||
BA.addEdge(Edge::FirstRelocation, 4, AnonSym2, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 0, SymB, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&SymB));
|
||||
}
|
||||
|
||||
// Cycle through anonymous blocks: A -> anon -> A.
|
||||
// The anonymous block creates a path back to A, but since A is a named symbol
|
||||
// the back-edge is a symbol dep (not an anonymous block dep). The SCC graph
|
||||
// is A -> anon (no back-edge). The anon block's dep on A gets transitively
|
||||
// merged into A's dep group, then filtered out (since A is a def), leaving
|
||||
// an empty dep set. The group is then removed entirely.
|
||||
TEST_F(LinkGraphLinkingLayerTests, CycleThroughAnonymousBlock) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &SymA = G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &AnonSym = G->addAnonymousSymbol(BAnon, 0, 4, false, true);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 0, SymA, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
// A's self-dep gets filtered, leaving an empty dep set. Groups with empty
|
||||
// deps are removed, so no dep groups remain.
|
||||
EXPECT_TRUE(DGs.empty());
|
||||
}
|
||||
|
||||
// Cycle through anonymous blocks with an external dep hanging off the anon
|
||||
// block: A -> anon -> A, anon -> ext. A should depend on ext.
|
||||
TEST_F(LinkGraphLinkingLayerTests, CycleThroughAnonymousBlockWithExternalDep) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &SymA = G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &AnonSym = G->addAnonymousSymbol(BAnon, 0, 4, false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 0, SymA, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 4, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// Two separate dep groups: A -> ext1, B -> ext2 (no connection between A and
|
||||
// B).
|
||||
TEST_F(LinkGraphLinkingLayerTests, TwoSeparateDepGroups) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &Ext1 = G->addExternalSymbol("ext1", 0, false);
|
||||
auto &Ext2 = G->addExternalSymbol("ext2", 0, false);
|
||||
BA.addEdge(Edge::FirstRelocation, 0, Ext1, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 0, Ext2, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 2u);
|
||||
|
||||
// Find which group is which (order may vary).
|
||||
SymbolDepGroup *DGA = nullptr, *DGB = nullptr;
|
||||
for (auto &DG : DGs) {
|
||||
ASSERT_EQ(DG.Defs.size(), 1u);
|
||||
if (DG.Defs[0]->getName() == G->intern("A"))
|
||||
DGA = &DG;
|
||||
else if (DG.Defs[0]->getName() == G->intern("B"))
|
||||
DGB = &DG;
|
||||
}
|
||||
ASSERT_NE(DGA, nullptr);
|
||||
ASSERT_NE(DGB, nullptr);
|
||||
EXPECT_EQ(DGA->Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGA->Deps.count(&Ext1));
|
||||
EXPECT_EQ(DGB->Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGB->Deps.count(&Ext2));
|
||||
}
|
||||
|
||||
// Dep group merging: A and B both transitively depend on ext through the same
|
||||
// anonymous block, and neither BA nor BB introduces any additional deps of its
|
||||
// own. This allows the second block processed to merge into the first's dep
|
||||
// group via the single-source-group fast path.
|
||||
// A -> anon -> ext
|
||||
// B -> anon -> ext (same anon block)
|
||||
// A and B should be in the same dep group.
|
||||
TEST_F(LinkGraphLinkingLayerTests, SharedAnonymousBlockMergesDepGroups) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym = G->addAnonymousSymbol(BAnon, 0, 4, false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 0, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 2u);
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// Same as SharedAnonymousBlockMergesDepGroups, but BA and BB each introduce
|
||||
// an additional direct dep (even the same one). This prevents merging — each
|
||||
// block triggers the general-case merge path and gets its own dep group.
|
||||
// A -> anon -> ext1, A -> ext2
|
||||
// B -> anon -> ext1, B -> ext2
|
||||
TEST_F(LinkGraphLinkingLayerTests,
|
||||
SharedAnonBlockWithExtraDepsSeparatesGroups) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym = G->addAnonymousSymbol(BAnon, 0, 4, false, true);
|
||||
auto &Ext1 = G->addExternalSymbol("ext1", 0, false);
|
||||
auto &Ext2 = G->addExternalSymbol("ext2", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BA.addEdge(Edge::FirstRelocation, 4, Ext2, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 0, AnonSym, 0);
|
||||
BB.addEdge(Edge::FirstRelocation, 4, Ext2, 0);
|
||||
BAnon.addEdge(Edge::FirstRelocation, 0, Ext1, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 2u);
|
||||
|
||||
SymbolDepGroup *DGA = nullptr, *DGB = nullptr;
|
||||
for (auto &DG : DGs) {
|
||||
ASSERT_EQ(DG.Defs.size(), 1u);
|
||||
if (DG.Defs[0]->getName() == G->intern("A"))
|
||||
DGA = &DG;
|
||||
else if (DG.Defs[0]->getName() == G->intern("B"))
|
||||
DGB = &DG;
|
||||
}
|
||||
ASSERT_NE(DGA, nullptr);
|
||||
ASSERT_NE(DGB, nullptr);
|
||||
// Both groups should have ext1 (transitive) and ext2 (direct).
|
||||
EXPECT_EQ(DGA->Deps.size(), 2u);
|
||||
EXPECT_TRUE(DGA->Deps.count(&Ext1));
|
||||
EXPECT_TRUE(DGA->Deps.count(&Ext2));
|
||||
EXPECT_EQ(DGB->Deps.size(), 2u);
|
||||
EXPECT_TRUE(DGB->Deps.count(&Ext1));
|
||||
EXPECT_TRUE(DGB->Deps.count(&Ext2));
|
||||
}
|
||||
|
||||
// Multi-node SCC in the anonymous block graph:
|
||||
// A -> anon1 -> anon2 -> anon1 (cycle), anon2 -> ext
|
||||
// anon1 and anon2 form a two-node SCC. A depends transitively on ext through
|
||||
// that SCC.
|
||||
TEST_F(LinkGraphLinkingLayerTests, MultiNodeSCC) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon1 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon2 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym1 = G->addAnonymousSymbol(BAnon1, 0, 4, false, true);
|
||||
auto &AnonSym2 = G->addAnonymousSymbol(BAnon2, 0, 4, false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 0, AnonSym2, 0);
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0); // cycle
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 4, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 1u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
|
||||
// Multi-node SCC where different nodes in the cycle contribute different
|
||||
// external deps:
|
||||
// A -> anon1 -> anon2 -> anon1 (cycle), anon1 -> ext1, anon2 -> ext2
|
||||
// Both ext1 and ext2 should be merged into A's dep group.
|
||||
TEST_F(LinkGraphLinkingLayerTests, MultiNodeSCCMergesExternalDeps) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon1 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon2 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym1 = G->addAnonymousSymbol(BAnon1, 0, 4, false, true);
|
||||
auto &AnonSym2 = G->addAnonymousSymbol(BAnon2, 0, 4, false, true);
|
||||
auto &Ext1 = G->addExternalSymbol("ext1", 0, false);
|
||||
auto &Ext2 = G->addExternalSymbol("ext2", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 0, AnonSym2, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 4, Ext1, 0);
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0); // cycle
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 4, Ext2, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 2u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&Ext1));
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&Ext2));
|
||||
}
|
||||
|
||||
// Multi-node SCC where nodes in the cycle contribute a mix of external and
|
||||
// named-block deps:
|
||||
// A -> anon1 -> anon2 -> anon1 (cycle), anon1 -> B (named), anon2 -> ext
|
||||
// A should depend on both B and ext.
|
||||
TEST_F(LinkGraphLinkingLayerTests, MultiNodeSCCMergesMixedDeps) {
|
||||
auto G = makeGraph();
|
||||
auto &Sec = G->createSection("__data", MemProt::Read | MemProt::Write);
|
||||
auto &BA =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x1000), 8, 0);
|
||||
auto &BAnon1 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x2000), 8, 0);
|
||||
auto &BAnon2 =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x3000), 8, 0);
|
||||
auto &BB =
|
||||
G->createContentBlock(Sec, BlockContent, ExecutorAddr(0x4000), 8, 0);
|
||||
|
||||
G->addDefinedSymbol(BA, 0, "A", 4, Linkage::Strong, Scope::Default, false,
|
||||
true);
|
||||
auto &AnonSym1 = G->addAnonymousSymbol(BAnon1, 0, 4, false, true);
|
||||
auto &AnonSym2 = G->addAnonymousSymbol(BAnon2, 0, 4, false, true);
|
||||
auto &SymB = G->addDefinedSymbol(BB, 0, "B", 4, Linkage::Strong,
|
||||
Scope::Default, false, true);
|
||||
auto &ExtSym = G->addExternalSymbol("ext", 0, false);
|
||||
|
||||
BA.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 0, AnonSym2, 0);
|
||||
BAnon1.addEdge(Edge::FirstRelocation, 4, SymB, 0);
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 0, AnonSym1, 0); // cycle
|
||||
BAnon2.addEdge(Edge::FirstRelocation, 4, ExtSym, 0);
|
||||
|
||||
auto DGs = calculateDepGroups(*G);
|
||||
ASSERT_EQ(DGs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs.size(), 1u);
|
||||
EXPECT_EQ(DGs[0].Defs[0]->getName(), G->intern("A"));
|
||||
EXPECT_EQ(DGs[0].Deps.size(), 2u);
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&SymB));
|
||||
EXPECT_TRUE(DGs[0].Deps.count(&ExtSym));
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user