From 75eae603ff90b83ad702acaf6a7a09cb978785b6 Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Mon, 30 Mar 2026 15:18:36 +1100 Subject: [PATCH] [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 Defs; DenseSet 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). --- .../Orc/LinkGraphLinkingLayer.h | 12 +- .../Orc/LinkGraphLinkingLayer.cpp | 404 +++++++---- .../ExecutionEngine/Orc/CMakeLists.txt | 1 + .../Orc/LinkGraphLinkingLayerTest.cpp | 633 ++++++++++++++++++ 4 files changed, 905 insertions(+), 145 deletions(-) create mode 100644 llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp diff --git a/llvm/include/llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h b/llvm/include/llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h index 2079a359e1e2..4413c71145fe 100644 --- a/llvm/include/llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h @@ -29,6 +29,8 @@ #include #include +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 Defs; + DenseSet Deps; + }; + + // Provides the basis for calculating orc::SymbolDependenceGroups. + static SmallVector 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; diff --git a/llvm/lib/ExecutionEngine/Orc/LinkGraphLinkingLayer.cpp b/llvm/lib/ExecutionEngine/Orc/LinkGraphLinkingLayer.cpp index ed1682cf9da9..c6aee74c6dc1 100644 --- a/llvm/lib/ExecutionEngine/Orc/LinkGraphLinkingLayer.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LinkGraphLinkingLayer.cpp @@ -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; + +struct BlockDepInfo { + using SymbolDefList = SmallVector; + using SymbolDepSet = DenseSet; + using AnonBlockDepSet = DenseSet; + + BlockDepInfoMap *Graph = nullptr; + SymbolDefList SymbolDefs; + SymbolDepSet SymbolDeps; + AnonBlockDepSet AnonBlockDeps; + BlockDepInfo *SCCRoot = nullptr; + std::optional DepGroupIndex; +}; + +template <> struct GraphTraits { + 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 Defs; - DenseSet SymbolDeps; - DenseSet AnonEdges, AnonBackEdges; - }; - - DenseMap 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 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 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::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 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 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 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( diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt index ea45a0313007..03df3d66f78e 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -29,6 +29,7 @@ add_llvm_unittest(OrcJITTests JITTargetMachineBuilderTest.cpp LazyCallThroughAndReexportsTest.cpp LibraryResolverTest.cpp + LinkGraphLinkingLayerTest.cpp LookupAndRecordAddrsTest.cpp MachOPlatformTest.cpp MapperJITLinkMemoryManagerTest.cpp diff --git a/llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp b/llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp new file mode 100644 index 000000000000..913cb0b702a4 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/LinkGraphLinkingLayerTest.cpp @@ -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 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 calculateDepGroups(LinkGraph &G) { + return LinkGraphLinkingLayer::calculateDepGroups(G); + } + + static std::unique_ptr makeGraph(StringRef Name = "test") { + return std::make_unique( + Name.str(), std::make_shared(), + 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)); +}