xref: /llvm-project/llvm/lib/IR/ModuleSummaryIndex.cpp (revision 21390eab4c05e0ed7e7d13ada9e85f62b87ea484)
1 //===-- ModuleSummaryIndex.cpp - Module Summary Index ---------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements the module index and summary classes for the
10 // IR library.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "llvm/IR/ModuleSummaryIndex.h"
15 #include "llvm/ADT/SCCIterator.h"
16 #include "llvm/ADT/Statistic.h"
17 #include "llvm/ADT/StringMap.h"
18 #include "llvm/Support/CommandLine.h"
19 #include "llvm/Support/Path.h"
20 #include "llvm/Support/raw_ostream.h"
21 using namespace llvm;
22 
23 #define DEBUG_TYPE "module-summary-index"
24 
25 STATISTIC(ReadOnlyLiveGVars,
26           "Number of live global variables marked read only");
27 STATISTIC(WriteOnlyLiveGVars,
28           "Number of live global variables marked write only");
29 
30 static cl::opt<bool> PropagateAttrs("propagate-attrs", cl::init(true),
31                                     cl::Hidden,
32                                     cl::desc("Propagate attributes in index"));
33 
34 static cl::opt<bool> ImportConstantsWithRefs(
35     "import-constants-with-refs", cl::init(true), cl::Hidden,
36     cl::desc("Import constant global variables with references"));
37 
38 FunctionSummary FunctionSummary::ExternalNode =
39     FunctionSummary::makeDummyFunctionSummary({});
40 
41 bool ValueInfo::isDSOLocal() const {
42   // Need to check all summaries are local in case of hash collisions.
43   return getSummaryList().size() &&
44          llvm::all_of(getSummaryList(),
45                       [](const std::unique_ptr<GlobalValueSummary> &Summary) {
46                         return Summary->isDSOLocal();
47                       });
48 }
49 
50 bool ValueInfo::canAutoHide() const {
51   // Can only auto hide if all copies are eligible to auto hide.
52   return getSummaryList().size() &&
53          llvm::all_of(getSummaryList(),
54                       [](const std::unique_ptr<GlobalValueSummary> &Summary) {
55                         return Summary->canAutoHide();
56                       });
57 }
58 
59 // Gets the number of readonly and writeonly refs in RefEdgeList
60 std::pair<unsigned, unsigned> FunctionSummary::specialRefCounts() const {
61   // Here we take advantage of having all readonly and writeonly references
62   // located in the end of the RefEdgeList.
63   auto Refs = refs();
64   unsigned RORefCnt = 0, WORefCnt = 0;
65   int I;
66   for (I = Refs.size() - 1; I >= 0 && Refs[I].isWriteOnly(); --I)
67     WORefCnt++;
68   for (; I >= 0 && Refs[I].isReadOnly(); --I)
69     RORefCnt++;
70   return {RORefCnt, WORefCnt};
71 }
72 
73 constexpr uint64_t ModuleSummaryIndex::BitcodeSummaryVersion;
74 
75 uint64_t ModuleSummaryIndex::getFlags() const {
76   uint64_t Flags = 0;
77   if (withGlobalValueDeadStripping())
78     Flags |= 0x1;
79   if (skipModuleByDistributedBackend())
80     Flags |= 0x2;
81   if (hasSyntheticEntryCounts())
82     Flags |= 0x4;
83   if (enableSplitLTOUnit())
84     Flags |= 0x8;
85   if (partiallySplitLTOUnits())
86     Flags |= 0x10;
87   if (withAttributePropagation())
88     Flags |= 0x20;
89   return Flags;
90 }
91 
92 void ModuleSummaryIndex::setFlags(uint64_t Flags) {
93   assert(Flags <= 0x3f && "Unexpected bits in flag");
94   // 1 bit: WithGlobalValueDeadStripping flag.
95   // Set on combined index only.
96   if (Flags & 0x1)
97     setWithGlobalValueDeadStripping();
98   // 1 bit: SkipModuleByDistributedBackend flag.
99   // Set on combined index only.
100   if (Flags & 0x2)
101     setSkipModuleByDistributedBackend();
102   // 1 bit: HasSyntheticEntryCounts flag.
103   // Set on combined index only.
104   if (Flags & 0x4)
105     setHasSyntheticEntryCounts();
106   // 1 bit: DisableSplitLTOUnit flag.
107   // Set on per module indexes. It is up to the client to validate
108   // the consistency of this flag across modules being linked.
109   if (Flags & 0x8)
110     setEnableSplitLTOUnit();
111   // 1 bit: PartiallySplitLTOUnits flag.
112   // Set on combined index only.
113   if (Flags & 0x10)
114     setPartiallySplitLTOUnits();
115   // 1 bit: WithAttributePropagation flag.
116   // Set on combined index only.
117   if (Flags & 0x20)
118     setWithAttributePropagation();
119 }
120 
121 // Collect for the given module the list of function it defines
122 // (GUID -> Summary).
123 void ModuleSummaryIndex::collectDefinedFunctionsForModule(
124     StringRef ModulePath, GVSummaryMapTy &GVSummaryMap) const {
125   for (auto &GlobalList : *this) {
126     auto GUID = GlobalList.first;
127     for (auto &GlobSummary : GlobalList.second.SummaryList) {
128       auto *Summary = dyn_cast_or_null<FunctionSummary>(GlobSummary.get());
129       if (!Summary)
130         // Ignore global variable, focus on functions
131         continue;
132       // Ignore summaries from other modules.
133       if (Summary->modulePath() != ModulePath)
134         continue;
135       GVSummaryMap[GUID] = Summary;
136     }
137   }
138 }
139 
140 GlobalValueSummary *
141 ModuleSummaryIndex::getGlobalValueSummary(uint64_t ValueGUID,
142                                           bool PerModuleIndex) const {
143   auto VI = getValueInfo(ValueGUID);
144   assert(VI && "GlobalValue not found in index");
145   assert((!PerModuleIndex || VI.getSummaryList().size() == 1) &&
146          "Expected a single entry per global value in per-module index");
147   auto &Summary = VI.getSummaryList()[0];
148   return Summary.get();
149 }
150 
151 bool ModuleSummaryIndex::isGUIDLive(GlobalValue::GUID GUID) const {
152   auto VI = getValueInfo(GUID);
153   if (!VI)
154     return true;
155   const auto &SummaryList = VI.getSummaryList();
156   if (SummaryList.empty())
157     return true;
158   for (auto &I : SummaryList)
159     if (isGlobalValueLive(I.get()))
160       return true;
161   return false;
162 }
163 
164 static void propagateAttributesToRefs(GlobalValueSummary *S) {
165   // If reference is not readonly or writeonly then referenced summary is not
166   // read/writeonly either. Note that:
167   // - All references from GlobalVarSummary are conservatively considered as
168   //   not readonly or writeonly. Tracking them properly requires more complex
169   //   analysis then we have now.
170   //
171   // - AliasSummary objects have no refs at all so this function is a no-op
172   //   for them.
173   for (auto &VI : S->refs()) {
174     assert(VI.getAccessSpecifier() == 0 || isa<FunctionSummary>(S));
175     for (auto &Ref : VI.getSummaryList())
176       // If references to alias is not read/writeonly then aliasee
177       // is not read/writeonly
178       if (auto *GVS = dyn_cast<GlobalVarSummary>(Ref->getBaseObject())) {
179         if (!VI.isReadOnly())
180           GVS->setReadOnly(false);
181         if (!VI.isWriteOnly())
182           GVS->setWriteOnly(false);
183       }
184   }
185 }
186 
187 // Do the access attribute propagation in combined index.
188 // The goal of attribute propagation is internalization of readonly (RO)
189 // or writeonly (WO) variables. To determine which variables are RO or WO
190 // and which are not we take following steps:
191 // - During analysis we speculatively assign readonly and writeonly
192 //   attribute to all variables which can be internalized. When computing
193 //   function summary we also assign readonly or writeonly attribute to a
194 //   reference if function doesn't modify referenced variable (readonly)
195 //   or doesn't read it (writeonly).
196 //
197 // - After computing dead symbols in combined index we do the attribute
198 //   propagation. During this step we:
199 //   a. clear RO and WO attributes from variables which are preserved or
200 //      can't be imported
201 //   b. clear RO and WO attributes from variables referenced by any global
202 //      variable initializer
203 //   c. clear RO attribute from variable referenced by a function when
204 //      reference is not readonly
205 //   d. clear WO attribute from variable referenced by a function when
206 //      reference is not writeonly
207 //
208 //   Because of (c, d) we don't internalize variables read by function A
209 //   and modified by function B.
210 //
211 // Internalization itself happens in the backend after import is finished
212 // See internalizeGVsAfterImport.
213 void ModuleSummaryIndex::propagateAttributes(
214     const DenseSet<GlobalValue::GUID> &GUIDPreservedSymbols) {
215   if (!PropagateAttrs)
216     return;
217   for (auto &P : *this)
218     for (auto &S : P.second.SummaryList) {
219       if (!isGlobalValueLive(S.get()))
220         // We don't examine references from dead objects
221         continue;
222 
223       // Global variable can't be marked read/writeonly if it is not eligible
224       // to import since we need to ensure that all external references get
225       // a local (imported) copy. It also can't be marked read/writeonly if
226       // it or any alias (since alias points to the same memory) are preserved
227       // or notEligibleToImport, since either of those means there could be
228       // writes (or reads in case of writeonly) that are not visible (because
229       // preserved means it could have external to DSO writes or reads, and
230       // notEligibleToImport means it could have writes or reads via inline
231       // assembly leading it to be in the @llvm.*used).
232       if (auto *GVS = dyn_cast<GlobalVarSummary>(S->getBaseObject()))
233         // Here we intentionally pass S.get() not GVS, because S could be
234         // an alias. We don't analyze references here, because we have to
235         // know exactly if GV is readonly to do so.
236         if (!canImportGlobalVar(S.get(), /* AnalyzeRefs */ false) ||
237             GUIDPreservedSymbols.count(P.first)) {
238           GVS->setReadOnly(false);
239           GVS->setWriteOnly(false);
240         }
241       propagateAttributesToRefs(S.get());
242     }
243   setWithAttributePropagation();
244   if (llvm::AreStatisticsEnabled())
245     for (auto &P : *this)
246       if (P.second.SummaryList.size())
247         if (auto *GVS = dyn_cast<GlobalVarSummary>(
248                 P.second.SummaryList[0]->getBaseObject()))
249           if (isGlobalValueLive(GVS)) {
250             if (GVS->maybeReadOnly())
251               ReadOnlyLiveGVars++;
252             if (GVS->maybeWriteOnly())
253               WriteOnlyLiveGVars++;
254           }
255 }
256 
257 bool ModuleSummaryIndex::canImportGlobalVar(GlobalValueSummary *S,
258                                             bool AnalyzeRefs) const {
259   auto HasRefsPreventingImport = [this](const GlobalVarSummary *GVS) {
260     // We don't analyze GV references during attribute propagation, so
261     // GV with non-trivial initializer can be marked either read or
262     // write-only.
263     // Importing definiton of readonly GV with non-trivial initializer
264     // allows us doing some extra optimizations (like converting indirect
265     // calls to direct).
266     // Definition of writeonly GV with non-trivial initializer should also
267     // be imported. Not doing so will result in:
268     // a) GV internalization in source module (because it's writeonly)
269     // b) Importing of GV declaration to destination module as a result
270     //    of promotion.
271     // c) Link error (external declaration with internal definition).
272     // However we do not promote objects referenced by writeonly GV
273     // initializer by means of converting it to 'zeroinitializer'
274     return !(ImportConstantsWithRefs && GVS->isConstant()) &&
275            !isReadOnly(GVS) && !isWriteOnly(GVS) && GVS->refs().size();
276   };
277   auto *GVS = cast<GlobalVarSummary>(S->getBaseObject());
278 
279   // Global variable with non-trivial initializer can be imported
280   // if it's readonly. This gives us extra opportunities for constant
281   // folding and converting indirect calls to direct calls. We don't
282   // analyze GV references during attribute propagation, because we
283   // don't know yet if it is readonly or not.
284   return !GlobalValue::isInterposableLinkage(S->linkage()) &&
285          !S->notEligibleToImport() &&
286          (!AnalyzeRefs || !HasRefsPreventingImport(GVS));
287 }
288 
289 // TODO: write a graphviz dumper for SCCs (see ModuleSummaryIndex::exportToDot)
290 // then delete this function and update its tests
291 LLVM_DUMP_METHOD
292 void ModuleSummaryIndex::dumpSCCs(raw_ostream &O) {
293   for (scc_iterator<ModuleSummaryIndex *> I =
294            scc_begin<ModuleSummaryIndex *>(this);
295        !I.isAtEnd(); ++I) {
296     O << "SCC (" << utostr(I->size()) << " node" << (I->size() == 1 ? "" : "s")
297       << ") {\n";
298     for (const ValueInfo &V : *I) {
299       FunctionSummary *F = nullptr;
300       if (V.getSummaryList().size())
301         F = cast<FunctionSummary>(V.getSummaryList().front().get());
302       O << " " << (F == nullptr ? "External" : "") << " " << utostr(V.getGUID())
303         << (I.hasCycle() ? " (has cycle)" : "") << "\n";
304     }
305     O << "}\n";
306   }
307 }
308 
309 namespace {
310 struct Attributes {
311   void add(const Twine &Name, const Twine &Value,
312            const Twine &Comment = Twine());
313   void addComment(const Twine &Comment);
314   std::string getAsString() const;
315 
316   std::vector<std::string> Attrs;
317   std::string Comments;
318 };
319 
320 struct Edge {
321   uint64_t SrcMod;
322   int Hotness;
323   GlobalValue::GUID Src;
324   GlobalValue::GUID Dst;
325 };
326 }
327 
328 void Attributes::add(const Twine &Name, const Twine &Value,
329                      const Twine &Comment) {
330   std::string A = Name.str();
331   A += "=\"";
332   A += Value.str();
333   A += "\"";
334   Attrs.push_back(A);
335   addComment(Comment);
336 }
337 
338 void Attributes::addComment(const Twine &Comment) {
339   if (!Comment.isTriviallyEmpty()) {
340     if (Comments.empty())
341       Comments = " // ";
342     else
343       Comments += ", ";
344     Comments += Comment.str();
345   }
346 }
347 
348 std::string Attributes::getAsString() const {
349   if (Attrs.empty())
350     return "";
351 
352   std::string Ret = "[";
353   for (auto &A : Attrs)
354     Ret += A + ",";
355   Ret.pop_back();
356   Ret += "];";
357   Ret += Comments;
358   return Ret;
359 }
360 
361 static std::string linkageToString(GlobalValue::LinkageTypes LT) {
362   switch (LT) {
363   case GlobalValue::ExternalLinkage:
364     return "extern";
365   case GlobalValue::AvailableExternallyLinkage:
366     return "av_ext";
367   case GlobalValue::LinkOnceAnyLinkage:
368     return "linkonce";
369   case GlobalValue::LinkOnceODRLinkage:
370     return "linkonce_odr";
371   case GlobalValue::WeakAnyLinkage:
372     return "weak";
373   case GlobalValue::WeakODRLinkage:
374     return "weak_odr";
375   case GlobalValue::AppendingLinkage:
376     return "appending";
377   case GlobalValue::InternalLinkage:
378     return "internal";
379   case GlobalValue::PrivateLinkage:
380     return "private";
381   case GlobalValue::ExternalWeakLinkage:
382     return "extern_weak";
383   case GlobalValue::CommonLinkage:
384     return "common";
385   }
386 
387   return "<unknown>";
388 }
389 
390 static std::string fflagsToString(FunctionSummary::FFlags F) {
391   auto FlagValue = [](unsigned V) { return V ? '1' : '0'; };
392   char FlagRep[] = {FlagValue(F.ReadNone),     FlagValue(F.ReadOnly),
393                     FlagValue(F.NoRecurse),    FlagValue(F.ReturnDoesNotAlias),
394                     FlagValue(F.NoInline), FlagValue(F.AlwaysInline), 0};
395 
396   return FlagRep;
397 }
398 
399 // Get string representation of function instruction count and flags.
400 static std::string getSummaryAttributes(GlobalValueSummary* GVS) {
401   auto *FS = dyn_cast_or_null<FunctionSummary>(GVS);
402   if (!FS)
403     return "";
404 
405   return std::string("inst: ") + std::to_string(FS->instCount()) +
406          ", ffl: " + fflagsToString(FS->fflags());
407 }
408 
409 static std::string getNodeVisualName(GlobalValue::GUID Id) {
410   return std::string("@") + std::to_string(Id);
411 }
412 
413 static std::string getNodeVisualName(const ValueInfo &VI) {
414   return VI.name().empty() ? getNodeVisualName(VI.getGUID()) : VI.name().str();
415 }
416 
417 static std::string getNodeLabel(const ValueInfo &VI, GlobalValueSummary *GVS) {
418   if (isa<AliasSummary>(GVS))
419     return getNodeVisualName(VI);
420 
421   std::string Attrs = getSummaryAttributes(GVS);
422   std::string Label =
423       getNodeVisualName(VI) + "|" + linkageToString(GVS->linkage());
424   if (!Attrs.empty())
425     Label += std::string(" (") + Attrs + ")";
426   Label += "}";
427 
428   return Label;
429 }
430 
431 // Write definition of external node, which doesn't have any
432 // specific module associated with it. Typically this is function
433 // or variable defined in native object or library.
434 static void defineExternalNode(raw_ostream &OS, const char *Pfx,
435                                const ValueInfo &VI, GlobalValue::GUID Id) {
436   auto StrId = std::to_string(Id);
437   OS << "  " << StrId << " [label=\"";
438 
439   if (VI) {
440     OS << getNodeVisualName(VI);
441   } else {
442     OS << getNodeVisualName(Id);
443   }
444   OS << "\"]; // defined externally\n";
445 }
446 
447 static bool hasReadOnlyFlag(const GlobalValueSummary *S) {
448   if (auto *GVS = dyn_cast<GlobalVarSummary>(S))
449     return GVS->maybeReadOnly();
450   return false;
451 }
452 
453 static bool hasWriteOnlyFlag(const GlobalValueSummary *S) {
454   if (auto *GVS = dyn_cast<GlobalVarSummary>(S))
455     return GVS->maybeWriteOnly();
456   return false;
457 }
458 
459 static bool hasConstantFlag(const GlobalValueSummary *S) {
460   if (auto *GVS = dyn_cast<GlobalVarSummary>(S))
461     return GVS->isConstant();
462   return false;
463 }
464 
465 void ModuleSummaryIndex::exportToDot(
466     raw_ostream &OS,
467     const DenseSet<GlobalValue::GUID> &GUIDPreservedSymbols) const {
468   std::vector<Edge> CrossModuleEdges;
469   DenseMap<GlobalValue::GUID, std::vector<uint64_t>> NodeMap;
470   using GVSOrderedMapTy = std::map<GlobalValue::GUID, GlobalValueSummary *>;
471   std::map<StringRef, GVSOrderedMapTy> ModuleToDefinedGVS;
472   collectDefinedGVSummariesPerModule(ModuleToDefinedGVS);
473 
474   // Get node identifier in form MXXX_<GUID>. The MXXX prefix is required,
475   // because we may have multiple linkonce functions summaries.
476   auto NodeId = [](uint64_t ModId, GlobalValue::GUID Id) {
477     return ModId == (uint64_t)-1 ? std::to_string(Id)
478                                  : std::string("M") + std::to_string(ModId) +
479                                        "_" + std::to_string(Id);
480   };
481 
482   auto DrawEdge = [&](const char *Pfx, uint64_t SrcMod, GlobalValue::GUID SrcId,
483                       uint64_t DstMod, GlobalValue::GUID DstId,
484                       int TypeOrHotness) {
485     // 0 - alias
486     // 1 - reference
487     // 2 - constant reference
488     // 3 - writeonly reference
489     // Other value: (hotness - 4).
490     TypeOrHotness += 4;
491     static const char *EdgeAttrs[] = {
492         " [style=dotted]; // alias",
493         " [style=dashed]; // ref",
494         " [style=dashed,color=forestgreen]; // const-ref",
495         " [style=dashed,color=violetred]; // writeOnly-ref",
496         " // call (hotness : Unknown)",
497         " [color=blue]; // call (hotness : Cold)",
498         " // call (hotness : None)",
499         " [color=brown]; // call (hotness : Hot)",
500         " [style=bold,color=red]; // call (hotness : Critical)"};
501 
502     assert(static_cast<size_t>(TypeOrHotness) <
503            sizeof(EdgeAttrs) / sizeof(EdgeAttrs[0]));
504     OS << Pfx << NodeId(SrcMod, SrcId) << " -> " << NodeId(DstMod, DstId)
505        << EdgeAttrs[TypeOrHotness] << "\n";
506   };
507 
508   OS << "digraph Summary {\n";
509   for (auto &ModIt : ModuleToDefinedGVS) {
510     auto ModId = getModuleId(ModIt.first);
511     OS << "  // Module: " << ModIt.first << "\n";
512     OS << "  subgraph cluster_" << std::to_string(ModId) << " {\n";
513     OS << "    style = filled;\n";
514     OS << "    color = lightgrey;\n";
515     OS << "    label = \"" << sys::path::filename(ModIt.first) << "\";\n";
516     OS << "    node [style=filled,fillcolor=lightblue];\n";
517 
518     auto &GVSMap = ModIt.second;
519     auto Draw = [&](GlobalValue::GUID IdFrom, GlobalValue::GUID IdTo, int Hotness) {
520       if (!GVSMap.count(IdTo)) {
521         CrossModuleEdges.push_back({ModId, Hotness, IdFrom, IdTo});
522         return;
523       }
524       DrawEdge("    ", ModId, IdFrom, ModId, IdTo, Hotness);
525     };
526 
527     for (auto &SummaryIt : GVSMap) {
528       NodeMap[SummaryIt.first].push_back(ModId);
529       auto Flags = SummaryIt.second->flags();
530       Attributes A;
531       if (isa<FunctionSummary>(SummaryIt.second)) {
532         A.add("shape", "record", "function");
533       } else if (isa<AliasSummary>(SummaryIt.second)) {
534         A.add("style", "dotted,filled", "alias");
535         A.add("shape", "box");
536       } else {
537         A.add("shape", "Mrecord", "variable");
538         if (Flags.Live && hasReadOnlyFlag(SummaryIt.second))
539           A.addComment("immutable");
540         if (Flags.Live && hasWriteOnlyFlag(SummaryIt.second))
541           A.addComment("writeOnly");
542         if (Flags.Live && hasConstantFlag(SummaryIt.second))
543           A.addComment("constant");
544       }
545       if (Flags.DSOLocal)
546         A.addComment("dsoLocal");
547       if (Flags.CanAutoHide)
548         A.addComment("canAutoHide");
549       if (GUIDPreservedSymbols.count(SummaryIt.first))
550         A.addComment("preserved");
551 
552       auto VI = getValueInfo(SummaryIt.first);
553       A.add("label", getNodeLabel(VI, SummaryIt.second));
554       if (!Flags.Live)
555         A.add("fillcolor", "red", "dead");
556       else if (Flags.NotEligibleToImport)
557         A.add("fillcolor", "yellow", "not eligible to import");
558 
559       OS << "    " << NodeId(ModId, SummaryIt.first) << " " << A.getAsString()
560          << "\n";
561     }
562     OS << "    // Edges:\n";
563 
564     for (auto &SummaryIt : GVSMap) {
565       auto *GVS = SummaryIt.second;
566       for (auto &R : GVS->refs())
567         Draw(SummaryIt.first, R.getGUID(),
568              R.isWriteOnly() ? -1 : (R.isReadOnly() ? -2 : -3));
569 
570       if (auto *AS = dyn_cast_or_null<AliasSummary>(SummaryIt.second)) {
571         Draw(SummaryIt.first, AS->getAliaseeGUID(), -4);
572         continue;
573       }
574 
575       if (auto *FS = dyn_cast_or_null<FunctionSummary>(SummaryIt.second))
576         for (auto &CGEdge : FS->calls())
577           Draw(SummaryIt.first, CGEdge.first.getGUID(),
578                static_cast<int>(CGEdge.second.Hotness));
579     }
580     OS << "  }\n";
581   }
582 
583   OS << "  // Cross-module edges:\n";
584   for (auto &E : CrossModuleEdges) {
585     auto &ModList = NodeMap[E.Dst];
586     if (ModList.empty()) {
587       defineExternalNode(OS, "  ", getValueInfo(E.Dst), E.Dst);
588       // Add fake module to the list to draw an edge to an external node
589       // in the loop below.
590       ModList.push_back(-1);
591     }
592     for (auto DstMod : ModList)
593       // The edge representing call or ref is drawn to every module where target
594       // symbol is defined. When target is a linkonce symbol there can be
595       // multiple edges representing a single call or ref, both intra-module and
596       // cross-module. As we've already drawn all intra-module edges before we
597       // skip it here.
598       if (DstMod != E.SrcMod)
599         DrawEdge("  ", E.SrcMod, E.Src, DstMod, E.Dst, E.Hotness);
600   }
601 
602   OS << "}";
603 }
604