126ab5772STeresa Johnson //===-- ModuleSummaryIndex.cpp - Module Summary Index ---------------------===// 226ab5772STeresa Johnson // 326ab5772STeresa Johnson // The LLVM Compiler Infrastructure 426ab5772STeresa Johnson // 526ab5772STeresa Johnson // This file is distributed under the University of Illinois Open Source 626ab5772STeresa Johnson // License. See LICENSE.TXT for details. 726ab5772STeresa Johnson // 826ab5772STeresa Johnson //===----------------------------------------------------------------------===// 926ab5772STeresa Johnson // 1026ab5772STeresa Johnson // This file implements the module index and summary classes for the 1126ab5772STeresa Johnson // IR library. 1226ab5772STeresa Johnson // 1326ab5772STeresa Johnson //===----------------------------------------------------------------------===// 1426ab5772STeresa Johnson 1526ab5772STeresa Johnson #include "llvm/IR/ModuleSummaryIndex.h" 16b040fcc6SCharles Saternos #include "llvm/ADT/SCCIterator.h" 1726ab5772STeresa Johnson #include "llvm/ADT/StringMap.h" 1828d8a49fSEugene Leviant #include "llvm/Support/Path.h" 19b040fcc6SCharles Saternos #include "llvm/Support/raw_ostream.h" 2026ab5772STeresa Johnson using namespace llvm; 2126ab5772STeresa Johnson 22b040fcc6SCharles Saternos FunctionSummary FunctionSummary::ExternalNode = 23b040fcc6SCharles Saternos FunctionSummary::makeDummyFunctionSummary({}); 24b4edfb9aSPeter Collingbourne bool ValueInfo::isDSOLocal() const { 25b4edfb9aSPeter Collingbourne // Need to check all summaries are local in case of hash collisions. 26b4edfb9aSPeter Collingbourne return getSummaryList().size() && 27b4edfb9aSPeter Collingbourne llvm::all_of(getSummaryList(), 28b4edfb9aSPeter Collingbourne [](const std::unique_ptr<GlobalValueSummary> &Summary) { 29b4edfb9aSPeter Collingbourne return Summary->isDSOLocal(); 30b4edfb9aSPeter Collingbourne }); 31b4edfb9aSPeter Collingbourne } 32b4edfb9aSPeter Collingbourne 33c86af334STeresa Johnson // Collect for the given module the list of function it defines 34c86af334STeresa Johnson // (GUID -> Summary). 35c86af334STeresa Johnson void ModuleSummaryIndex::collectDefinedFunctionsForModule( 36c851d216STeresa Johnson StringRef ModulePath, GVSummaryMapTy &GVSummaryMap) const { 37c86af334STeresa Johnson for (auto &GlobalList : *this) { 38c86af334STeresa Johnson auto GUID = GlobalList.first; 399667b91bSPeter Collingbourne for (auto &GlobSummary : GlobalList.second.SummaryList) { 4028e457bcSTeresa Johnson auto *Summary = dyn_cast_or_null<FunctionSummary>(GlobSummary.get()); 41c86af334STeresa Johnson if (!Summary) 42c86af334STeresa Johnson // Ignore global variable, focus on functions 43c86af334STeresa Johnson continue; 44c86af334STeresa Johnson // Ignore summaries from other modules. 45c86af334STeresa Johnson if (Summary->modulePath() != ModulePath) 46c86af334STeresa Johnson continue; 4728e457bcSTeresa Johnson GVSummaryMap[GUID] = Summary; 48c86af334STeresa Johnson } 49c86af334STeresa Johnson } 50c86af334STeresa Johnson } 51c86af334STeresa Johnson 521aafabf7SMehdi Amini // Collect for each module the list of function it defines (GUID -> Summary). 531aafabf7SMehdi Amini void ModuleSummaryIndex::collectDefinedGVSummariesPerModule( 54c851d216STeresa Johnson StringMap<GVSummaryMapTy> &ModuleToDefinedGVSummaries) const { 551aafabf7SMehdi Amini for (auto &GlobalList : *this) { 561aafabf7SMehdi Amini auto GUID = GlobalList.first; 579667b91bSPeter Collingbourne for (auto &Summary : GlobalList.second.SummaryList) { 5828e457bcSTeresa Johnson ModuleToDefinedGVSummaries[Summary->modulePath()][GUID] = Summary.get(); 591aafabf7SMehdi Amini } 601aafabf7SMehdi Amini } 611aafabf7SMehdi Amini } 621aafabf7SMehdi Amini 6328e457bcSTeresa Johnson GlobalValueSummary * 6428e457bcSTeresa Johnson ModuleSummaryIndex::getGlobalValueSummary(uint64_t ValueGUID, 65fb7c7644STeresa Johnson bool PerModuleIndex) const { 669667b91bSPeter Collingbourne auto VI = getValueInfo(ValueGUID); 679667b91bSPeter Collingbourne assert(VI && "GlobalValue not found in index"); 689667b91bSPeter Collingbourne assert((!PerModuleIndex || VI.getSummaryList().size() == 1) && 69fb7c7644STeresa Johnson "Expected a single entry per global value in per-module index"); 709667b91bSPeter Collingbourne auto &Summary = VI.getSummaryList()[0]; 7128e457bcSTeresa Johnson return Summary.get(); 72fb7c7644STeresa Johnson } 73dbd2fed6SPeter Collingbourne 74dbd2fed6SPeter Collingbourne bool ModuleSummaryIndex::isGUIDLive(GlobalValue::GUID GUID) const { 75dbd2fed6SPeter Collingbourne auto VI = getValueInfo(GUID); 76dbd2fed6SPeter Collingbourne if (!VI) 774d4ee93dSEvgeniy Stepanov return true; 784d4ee93dSEvgeniy Stepanov const auto &SummaryList = VI.getSummaryList(); 794d4ee93dSEvgeniy Stepanov if (SummaryList.empty()) 804d4ee93dSEvgeniy Stepanov return true; 814d4ee93dSEvgeniy Stepanov for (auto &I : SummaryList) 82dbd2fed6SPeter Collingbourne if (isGlobalValueLive(I.get())) 83dbd2fed6SPeter Collingbourne return true; 84dbd2fed6SPeter Collingbourne return false; 85dbd2fed6SPeter Collingbourne } 8628d8a49fSEugene Leviant 87b040fcc6SCharles Saternos // TODO: write a graphviz dumper for SCCs (see ModuleSummaryIndex::exportToDot) 88b040fcc6SCharles Saternos // then delete this function and update its tests 89b040fcc6SCharles Saternos LLVM_DUMP_METHOD 90b040fcc6SCharles Saternos void ModuleSummaryIndex::dumpSCCs(raw_ostream &O) { 91b040fcc6SCharles Saternos for (scc_iterator<ModuleSummaryIndex *> I = 92b040fcc6SCharles Saternos scc_begin<ModuleSummaryIndex *>(this); 93b040fcc6SCharles Saternos !I.isAtEnd(); ++I) { 94b040fcc6SCharles Saternos O << "SCC (" << utostr(I->size()) << " node" << (I->size() == 1 ? "" : "s") 95b040fcc6SCharles Saternos << ") {\n"; 96b040fcc6SCharles Saternos for (const ValueInfo V : *I) { 97b040fcc6SCharles Saternos FunctionSummary *F = nullptr; 98b040fcc6SCharles Saternos if (V.getSummaryList().size()) 99b040fcc6SCharles Saternos F = cast<FunctionSummary>(V.getSummaryList().front().get()); 100b040fcc6SCharles Saternos O << " " << (F == nullptr ? "External" : "") << " " << utostr(V.getGUID()) 101b040fcc6SCharles Saternos << (I.hasLoop() ? " (has loop)" : "") << "\n"; 102b040fcc6SCharles Saternos } 103b040fcc6SCharles Saternos O << "}\n"; 104b040fcc6SCharles Saternos } 105b040fcc6SCharles Saternos } 106b040fcc6SCharles Saternos 10728d8a49fSEugene Leviant namespace { 10828d8a49fSEugene Leviant struct Attributes { 10928d8a49fSEugene Leviant void add(const Twine &Name, const Twine &Value, 11028d8a49fSEugene Leviant const Twine &Comment = Twine()); 11128d8a49fSEugene Leviant std::string getAsString() const; 11228d8a49fSEugene Leviant 11328d8a49fSEugene Leviant std::vector<std::string> Attrs; 11428d8a49fSEugene Leviant std::string Comments; 11528d8a49fSEugene Leviant }; 11628d8a49fSEugene Leviant 11728d8a49fSEugene Leviant struct Edge { 11828d8a49fSEugene Leviant uint64_t SrcMod; 11928d8a49fSEugene Leviant int Hotness; 12028d8a49fSEugene Leviant GlobalValue::GUID Src; 12128d8a49fSEugene Leviant GlobalValue::GUID Dst; 12228d8a49fSEugene Leviant }; 12328d8a49fSEugene Leviant } 12428d8a49fSEugene Leviant 12528d8a49fSEugene Leviant void Attributes::add(const Twine &Name, const Twine &Value, 12628d8a49fSEugene Leviant const Twine &Comment) { 12728d8a49fSEugene Leviant std::string A = Name.str(); 12828d8a49fSEugene Leviant A += "=\""; 12928d8a49fSEugene Leviant A += Value.str(); 13028d8a49fSEugene Leviant A += "\""; 13128d8a49fSEugene Leviant Attrs.push_back(A); 13228d8a49fSEugene Leviant if (!Comment.isTriviallyEmpty()) { 13328d8a49fSEugene Leviant if (Comments.empty()) 13428d8a49fSEugene Leviant Comments = " // "; 13528d8a49fSEugene Leviant else 13628d8a49fSEugene Leviant Comments += ", "; 13728d8a49fSEugene Leviant Comments += Comment.str(); 13828d8a49fSEugene Leviant } 13928d8a49fSEugene Leviant } 14028d8a49fSEugene Leviant 14128d8a49fSEugene Leviant std::string Attributes::getAsString() const { 14228d8a49fSEugene Leviant if (Attrs.empty()) 14328d8a49fSEugene Leviant return ""; 14428d8a49fSEugene Leviant 14528d8a49fSEugene Leviant std::string Ret = "["; 14628d8a49fSEugene Leviant for (auto &A : Attrs) 14728d8a49fSEugene Leviant Ret += A + ","; 14828d8a49fSEugene Leviant Ret.pop_back(); 14928d8a49fSEugene Leviant Ret += "];"; 15028d8a49fSEugene Leviant Ret += Comments; 15128d8a49fSEugene Leviant return Ret; 15228d8a49fSEugene Leviant } 15328d8a49fSEugene Leviant 15428d8a49fSEugene Leviant static std::string linkageToString(GlobalValue::LinkageTypes LT) { 15528d8a49fSEugene Leviant switch (LT) { 15628d8a49fSEugene Leviant case GlobalValue::ExternalLinkage: 15728d8a49fSEugene Leviant return "extern"; 15828d8a49fSEugene Leviant case GlobalValue::AvailableExternallyLinkage: 15928d8a49fSEugene Leviant return "av_ext"; 16028d8a49fSEugene Leviant case GlobalValue::LinkOnceAnyLinkage: 16128d8a49fSEugene Leviant return "linkonce"; 16228d8a49fSEugene Leviant case GlobalValue::LinkOnceODRLinkage: 16328d8a49fSEugene Leviant return "linkonce_odr"; 16428d8a49fSEugene Leviant case GlobalValue::WeakAnyLinkage: 16528d8a49fSEugene Leviant return "weak"; 16628d8a49fSEugene Leviant case GlobalValue::WeakODRLinkage: 16728d8a49fSEugene Leviant return "weak_odr"; 16828d8a49fSEugene Leviant case GlobalValue::AppendingLinkage: 16928d8a49fSEugene Leviant return "appending"; 17028d8a49fSEugene Leviant case GlobalValue::InternalLinkage: 17128d8a49fSEugene Leviant return "internal"; 17228d8a49fSEugene Leviant case GlobalValue::PrivateLinkage: 17328d8a49fSEugene Leviant return "private"; 17428d8a49fSEugene Leviant case GlobalValue::ExternalWeakLinkage: 17528d8a49fSEugene Leviant return "extern_weak"; 17628d8a49fSEugene Leviant case GlobalValue::CommonLinkage: 17728d8a49fSEugene Leviant return "common"; 17828d8a49fSEugene Leviant } 17928d8a49fSEugene Leviant 18028d8a49fSEugene Leviant return "<unknown>"; 18128d8a49fSEugene Leviant } 18228d8a49fSEugene Leviant 18328d8a49fSEugene Leviant static std::string fflagsToString(FunctionSummary::FFlags F) { 18428d8a49fSEugene Leviant auto FlagValue = [](unsigned V) { return V ? '1' : '0'; }; 18528d8a49fSEugene Leviant char FlagRep[] = {FlagValue(F.ReadNone), FlagValue(F.ReadOnly), 18628d8a49fSEugene Leviant FlagValue(F.NoRecurse), FlagValue(F.ReturnDoesNotAlias), 0}; 18728d8a49fSEugene Leviant 18828d8a49fSEugene Leviant return FlagRep; 18928d8a49fSEugene Leviant } 19028d8a49fSEugene Leviant 19128d8a49fSEugene Leviant // Get string representation of function instruction count and flags. 19228d8a49fSEugene Leviant static std::string getSummaryAttributes(GlobalValueSummary* GVS) { 19328d8a49fSEugene Leviant auto *FS = dyn_cast_or_null<FunctionSummary>(GVS); 19428d8a49fSEugene Leviant if (!FS) 19528d8a49fSEugene Leviant return ""; 19628d8a49fSEugene Leviant 19728d8a49fSEugene Leviant return std::string("inst: ") + std::to_string(FS->instCount()) + 19828d8a49fSEugene Leviant ", ffl: " + fflagsToString(FS->fflags()); 19928d8a49fSEugene Leviant } 20028d8a49fSEugene Leviant 20128d8a49fSEugene Leviant static std::string getNodeVisualName(const ValueInfo &VI) { 20228d8a49fSEugene Leviant return VI.name().empty() ? std::string("@") + std::to_string(VI.getGUID()) 20328d8a49fSEugene Leviant : VI.name().str(); 20428d8a49fSEugene Leviant } 20528d8a49fSEugene Leviant 20628d8a49fSEugene Leviant static std::string getNodeLabel(const ValueInfo &VI, GlobalValueSummary *GVS) { 20728d8a49fSEugene Leviant if (isa<AliasSummary>(GVS)) 20828d8a49fSEugene Leviant return getNodeVisualName(VI); 20928d8a49fSEugene Leviant 21028d8a49fSEugene Leviant std::string Attrs = getSummaryAttributes(GVS); 21128d8a49fSEugene Leviant std::string Label = 21228d8a49fSEugene Leviant getNodeVisualName(VI) + "|" + linkageToString(GVS->linkage()); 21328d8a49fSEugene Leviant if (!Attrs.empty()) 21428d8a49fSEugene Leviant Label += std::string(" (") + Attrs + ")"; 21528d8a49fSEugene Leviant Label += "}"; 21628d8a49fSEugene Leviant 21728d8a49fSEugene Leviant return Label; 21828d8a49fSEugene Leviant } 21928d8a49fSEugene Leviant 22028d8a49fSEugene Leviant // Write definition of external node, which doesn't have any 22128d8a49fSEugene Leviant // specific module associated with it. Typically this is function 22228d8a49fSEugene Leviant // or variable defined in native object or library. 22328d8a49fSEugene Leviant static void defineExternalNode(raw_ostream &OS, const char *Pfx, 22428d8a49fSEugene Leviant const ValueInfo &VI) { 22528d8a49fSEugene Leviant auto StrId = std::to_string(VI.getGUID()); 22628d8a49fSEugene Leviant OS << " " << StrId << " [label=\"" << getNodeVisualName(VI) 22728d8a49fSEugene Leviant << "\"]; // defined externally\n"; 22828d8a49fSEugene Leviant } 22928d8a49fSEugene Leviant 23028d8a49fSEugene Leviant void ModuleSummaryIndex::exportToDot(raw_ostream& OS) const { 23128d8a49fSEugene Leviant std::vector<Edge> CrossModuleEdges; 23228d8a49fSEugene Leviant DenseMap<GlobalValue::GUID, std::vector<uint64_t>> NodeMap; 23328d8a49fSEugene Leviant StringMap<GVSummaryMapTy> ModuleToDefinedGVS; 23428d8a49fSEugene Leviant collectDefinedGVSummariesPerModule(ModuleToDefinedGVS); 23528d8a49fSEugene Leviant 23628d8a49fSEugene Leviant // Get node identifier in form MXXX_<GUID>. The MXXX prefix is required, 23728d8a49fSEugene Leviant // because we may have multiple linkonce functions summaries. 23828d8a49fSEugene Leviant auto NodeId = [](uint64_t ModId, GlobalValue::GUID Id) { 23928d8a49fSEugene Leviant return ModId == (uint64_t)-1 ? std::to_string(Id) 24028d8a49fSEugene Leviant : std::string("M") + std::to_string(ModId) + 24128d8a49fSEugene Leviant "_" + std::to_string(Id); 24228d8a49fSEugene Leviant }; 24328d8a49fSEugene Leviant 244*1f54500aSEugene Leviant auto DrawEdge = [&](const char *Pfx, uint64_t SrcMod, GlobalValue::GUID SrcId, 24528d8a49fSEugene Leviant int DstMod, GlobalValue::GUID DstId, int TypeOrHotness) { 24628d8a49fSEugene Leviant // 0 corresponds to alias edge, 1 to ref edge, 2 to call with unknown 24728d8a49fSEugene Leviant // hotness, ... 24828d8a49fSEugene Leviant TypeOrHotness += 2; 24928d8a49fSEugene Leviant static const char *EdgeAttrs[] = { 25028d8a49fSEugene Leviant " [style=dotted]; // alias", 25128d8a49fSEugene Leviant " [style=dashed]; // ref", 25228d8a49fSEugene Leviant " // call (hotness : Unknown)", 25328d8a49fSEugene Leviant " [color=blue]; // call (hotness : Cold)", 25428d8a49fSEugene Leviant " // call (hotness : None)", 25528d8a49fSEugene Leviant " [color=brown]; // call (hotness : Hot)", 25628d8a49fSEugene Leviant " [style=bold,color=red]; // call (hotness : Critical)"}; 25728d8a49fSEugene Leviant 25828d8a49fSEugene Leviant assert(static_cast<size_t>(TypeOrHotness) < 25928d8a49fSEugene Leviant sizeof(EdgeAttrs) / sizeof(EdgeAttrs[0])); 26028d8a49fSEugene Leviant OS << Pfx << NodeId(SrcMod, SrcId) << " -> " << NodeId(DstMod, DstId) 26128d8a49fSEugene Leviant << EdgeAttrs[TypeOrHotness] << "\n"; 26228d8a49fSEugene Leviant }; 26328d8a49fSEugene Leviant 26428d8a49fSEugene Leviant OS << "digraph Summary {\n"; 26528d8a49fSEugene Leviant for (auto &ModIt : ModuleToDefinedGVS) { 26628d8a49fSEugene Leviant auto ModId = getModuleId(ModIt.first()); 26728d8a49fSEugene Leviant OS << " // Module: " << ModIt.first() << "\n"; 26828d8a49fSEugene Leviant OS << " subgraph cluster_" << std::to_string(ModId) << " {\n"; 26928d8a49fSEugene Leviant OS << " style = filled;\n"; 27028d8a49fSEugene Leviant OS << " color = lightgrey;\n"; 27128d8a49fSEugene Leviant OS << " label = \"" << sys::path::filename(ModIt.first()) << "\";\n"; 27228d8a49fSEugene Leviant OS << " node [style=filled,fillcolor=lightblue];\n"; 27328d8a49fSEugene Leviant 27428d8a49fSEugene Leviant auto &GVSMap = ModIt.second; 27528d8a49fSEugene Leviant auto Draw = [&](GlobalValue::GUID IdFrom, GlobalValue::GUID IdTo, int Hotness) { 27628d8a49fSEugene Leviant if (!GVSMap.count(IdTo)) { 27728d8a49fSEugene Leviant CrossModuleEdges.push_back({ModId, Hotness, IdFrom, IdTo}); 27828d8a49fSEugene Leviant return; 27928d8a49fSEugene Leviant } 28028d8a49fSEugene Leviant DrawEdge(" ", ModId, IdFrom, ModId, IdTo, Hotness); 28128d8a49fSEugene Leviant }; 28228d8a49fSEugene Leviant 28328d8a49fSEugene Leviant for (auto &SummaryIt : GVSMap) { 28428d8a49fSEugene Leviant NodeMap[SummaryIt.first].push_back(ModId); 28528d8a49fSEugene Leviant auto Flags = SummaryIt.second->flags(); 28628d8a49fSEugene Leviant Attributes A; 28728d8a49fSEugene Leviant if (isa<FunctionSummary>(SummaryIt.second)) { 28828d8a49fSEugene Leviant A.add("shape", "record", "function"); 28928d8a49fSEugene Leviant } else if (isa<AliasSummary>(SummaryIt.second)) { 29028d8a49fSEugene Leviant A.add("style", "dotted,filled", "alias"); 29128d8a49fSEugene Leviant A.add("shape", "box"); 29228d8a49fSEugene Leviant } else { 29328d8a49fSEugene Leviant A.add("shape", "Mrecord", "variable"); 29428d8a49fSEugene Leviant } 29528d8a49fSEugene Leviant 29628d8a49fSEugene Leviant auto VI = getValueInfo(SummaryIt.first); 29728d8a49fSEugene Leviant A.add("label", getNodeLabel(VI, SummaryIt.second)); 29828d8a49fSEugene Leviant if (!Flags.Live) 29928d8a49fSEugene Leviant A.add("fillcolor", "red", "dead"); 30028d8a49fSEugene Leviant else if (Flags.NotEligibleToImport) 30128d8a49fSEugene Leviant A.add("fillcolor", "yellow", "not eligible to import"); 30228d8a49fSEugene Leviant 30328d8a49fSEugene Leviant OS << " " << NodeId(ModId, SummaryIt.first) << " " << A.getAsString() 30428d8a49fSEugene Leviant << "\n"; 30528d8a49fSEugene Leviant } 30628d8a49fSEugene Leviant OS << " // Edges:\n"; 30728d8a49fSEugene Leviant 30828d8a49fSEugene Leviant for (auto &SummaryIt : GVSMap) { 30928d8a49fSEugene Leviant auto *GVS = SummaryIt.second; 31028d8a49fSEugene Leviant for (auto &R : GVS->refs()) 31128d8a49fSEugene Leviant Draw(SummaryIt.first, R.getGUID(), -1); 31228d8a49fSEugene Leviant 31328d8a49fSEugene Leviant if (auto *AS = dyn_cast_or_null<AliasSummary>(SummaryIt.second)) { 31428d8a49fSEugene Leviant auto AliaseeOrigId = AS->getAliasee().getOriginalName(); 31528d8a49fSEugene Leviant auto AliaseeId = getGUIDFromOriginalID(AliaseeOrigId); 31628d8a49fSEugene Leviant 31728d8a49fSEugene Leviant Draw(SummaryIt.first, AliaseeId ? AliaseeId : AliaseeOrigId, -2); 31828d8a49fSEugene Leviant continue; 31928d8a49fSEugene Leviant } 32028d8a49fSEugene Leviant 32128d8a49fSEugene Leviant if (auto *FS = dyn_cast_or_null<FunctionSummary>(SummaryIt.second)) 32228d8a49fSEugene Leviant for (auto &CGEdge : FS->calls()) 32328d8a49fSEugene Leviant Draw(SummaryIt.first, CGEdge.first.getGUID(), 32428d8a49fSEugene Leviant static_cast<int>(CGEdge.second.Hotness)); 32528d8a49fSEugene Leviant } 32628d8a49fSEugene Leviant OS << " }\n"; 32728d8a49fSEugene Leviant } 32828d8a49fSEugene Leviant 32928d8a49fSEugene Leviant OS << " // Cross-module edges:\n"; 33028d8a49fSEugene Leviant for (auto &E : CrossModuleEdges) { 33128d8a49fSEugene Leviant auto &ModList = NodeMap[E.Dst]; 33228d8a49fSEugene Leviant if (ModList.empty()) { 33328d8a49fSEugene Leviant defineExternalNode(OS, " ", getValueInfo(E.Dst)); 33428d8a49fSEugene Leviant // Add fake module to the list to draw an edge to an external node 33528d8a49fSEugene Leviant // in the loop below. 33628d8a49fSEugene Leviant ModList.push_back(-1); 33728d8a49fSEugene Leviant } 33828d8a49fSEugene Leviant for (auto DstMod : ModList) 33928d8a49fSEugene Leviant // The edge representing call or ref is drawn to every module where target 34028d8a49fSEugene Leviant // symbol is defined. When target is a linkonce symbol there can be 34128d8a49fSEugene Leviant // multiple edges representing a single call or ref, both intra-module and 34228d8a49fSEugene Leviant // cross-module. As we've already drawn all intra-module edges before we 34328d8a49fSEugene Leviant // skip it here. 34428d8a49fSEugene Leviant if (DstMod != E.SrcMod) 34528d8a49fSEugene Leviant DrawEdge(" ", E.SrcMod, E.Src, DstMod, E.Dst, E.Hotness); 34628d8a49fSEugene Leviant } 34728d8a49fSEugene Leviant 34828d8a49fSEugene Leviant OS << "}"; 34928d8a49fSEugene Leviant } 350