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)); +}