1 //===-- ModuleSummaryIndex.cpp - Module Summary Index ---------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This file implements the module index and summary classes for the
12 //===----------------------------------------------------------------------===//
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"
23 #define DEBUG_TYPE "module-summary-index"
25 STATISTIC(ReadOnlyLiveGVars
,
26 "Number of live global variables marked read only");
27 STATISTIC(WriteOnlyLiveGVars
,
28 "Number of live global variables marked write only");
30 static cl::opt
<bool> PropagateAttrs("propagate-attrs", cl::init(true),
32 cl::desc("Propagate attributes in index"));
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"));
38 constexpr uint32_t FunctionSummary::ParamAccess::RangeWidth
;
40 FunctionSummary
FunctionSummary::ExternalNode
=
41 FunctionSummary::makeDummyFunctionSummary({});
43 GlobalValue::VisibilityTypes
ValueInfo::getELFVisibility() const {
44 bool HasProtected
= false;
45 for (const auto &S
: make_pointee_range(getSummaryList())) {
46 if (S
.getVisibility() == GlobalValue::HiddenVisibility
)
47 return GlobalValue::HiddenVisibility
;
48 if (S
.getVisibility() == GlobalValue::ProtectedVisibility
)
51 return HasProtected
? GlobalValue::ProtectedVisibility
52 : GlobalValue::DefaultVisibility
;
55 bool ValueInfo::isDSOLocal(bool WithDSOLocalPropagation
) const {
56 // With DSOLocal propagation done, the flag in evey summary is the same.
57 // Check the first one is enough.
58 return WithDSOLocalPropagation
59 ? getSummaryList().size() && getSummaryList()[0]->isDSOLocal()
60 : getSummaryList().size() &&
63 [](const std::unique_ptr
<GlobalValueSummary
> &Summary
) {
64 return Summary
->isDSOLocal();
68 bool ValueInfo::canAutoHide() const {
69 // Can only auto hide if all copies are eligible to auto hide.
70 return getSummaryList().size() &&
71 llvm::all_of(getSummaryList(),
72 [](const std::unique_ptr
<GlobalValueSummary
> &Summary
) {
73 return Summary
->canAutoHide();
77 // Gets the number of readonly and writeonly refs in RefEdgeList
78 std::pair
<unsigned, unsigned> FunctionSummary::specialRefCounts() const {
79 // Here we take advantage of having all readonly and writeonly references
80 // located in the end of the RefEdgeList.
82 unsigned RORefCnt
= 0, WORefCnt
= 0;
84 for (I
= Refs
.size() - 1; I
>= 0 && Refs
[I
].isWriteOnly(); --I
)
86 for (; I
>= 0 && Refs
[I
].isReadOnly(); --I
)
88 return {RORefCnt
, WORefCnt
};
91 constexpr uint64_t ModuleSummaryIndex::BitcodeSummaryVersion
;
93 uint64_t ModuleSummaryIndex::getFlags() const {
95 if (withGlobalValueDeadStripping())
97 if (skipModuleByDistributedBackend())
99 if (hasSyntheticEntryCounts())
101 if (enableSplitLTOUnit())
103 if (partiallySplitLTOUnits())
105 if (withAttributePropagation())
107 if (withDSOLocalPropagation())
112 void ModuleSummaryIndex::setFlags(uint64_t Flags
) {
113 assert(Flags
<= 0x7f && "Unexpected bits in flag");
114 // 1 bit: WithGlobalValueDeadStripping flag.
115 // Set on combined index only.
117 setWithGlobalValueDeadStripping();
118 // 1 bit: SkipModuleByDistributedBackend flag.
119 // Set on combined index only.
121 setSkipModuleByDistributedBackend();
122 // 1 bit: HasSyntheticEntryCounts flag.
123 // Set on combined index only.
125 setHasSyntheticEntryCounts();
126 // 1 bit: DisableSplitLTOUnit flag.
127 // Set on per module indexes. It is up to the client to validate
128 // the consistency of this flag across modules being linked.
130 setEnableSplitLTOUnit();
131 // 1 bit: PartiallySplitLTOUnits flag.
132 // Set on combined index only.
134 setPartiallySplitLTOUnits();
135 // 1 bit: WithAttributePropagation flag.
136 // Set on combined index only.
138 setWithAttributePropagation();
139 // 1 bit: WithDSOLocalPropagation flag.
140 // Set on combined index only.
142 setWithDSOLocalPropagation();
145 // Collect for the given module the list of function it defines
146 // (GUID -> Summary).
147 void ModuleSummaryIndex::collectDefinedFunctionsForModule(
148 StringRef ModulePath
, GVSummaryMapTy
&GVSummaryMap
) const {
149 for (auto &GlobalList
: *this) {
150 auto GUID
= GlobalList
.first
;
151 for (auto &GlobSummary
: GlobalList
.second
.SummaryList
) {
152 auto *Summary
= dyn_cast_or_null
<FunctionSummary
>(GlobSummary
.get());
154 // Ignore global variable, focus on functions
156 // Ignore summaries from other modules.
157 if (Summary
->modulePath() != ModulePath
)
159 GVSummaryMap
[GUID
] = Summary
;
165 ModuleSummaryIndex::getGlobalValueSummary(uint64_t ValueGUID
,
166 bool PerModuleIndex
) const {
167 auto VI
= getValueInfo(ValueGUID
);
168 assert(VI
&& "GlobalValue not found in index");
169 assert((!PerModuleIndex
|| VI
.getSummaryList().size() == 1) &&
170 "Expected a single entry per global value in per-module index");
171 auto &Summary
= VI
.getSummaryList()[0];
172 return Summary
.get();
175 bool ModuleSummaryIndex::isGUIDLive(GlobalValue::GUID GUID
) const {
176 auto VI
= getValueInfo(GUID
);
179 const auto &SummaryList
= VI
.getSummaryList();
180 if (SummaryList
.empty())
182 for (auto &I
: SummaryList
)
183 if (isGlobalValueLive(I
.get()))
189 propagateAttributesToRefs(GlobalValueSummary
*S
,
190 DenseSet
<ValueInfo
> &MarkedNonReadWriteOnly
) {
191 // If reference is not readonly or writeonly then referenced summary is not
192 // read/writeonly either. Note that:
193 // - All references from GlobalVarSummary are conservatively considered as
194 // not readonly or writeonly. Tracking them properly requires more complex
195 // analysis then we have now.
197 // - AliasSummary objects have no refs at all so this function is a no-op
199 for (auto &VI
: S
->refs()) {
200 assert(VI
.getAccessSpecifier() == 0 || isa
<FunctionSummary
>(S
));
201 if (!VI
.getAccessSpecifier()) {
202 if (!MarkedNonReadWriteOnly
.insert(VI
).second
)
204 } else if (MarkedNonReadWriteOnly
.contains(VI
))
206 for (auto &Ref
: VI
.getSummaryList())
207 // If references to alias is not read/writeonly then aliasee
208 // is not read/writeonly
209 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(Ref
->getBaseObject())) {
210 if (!VI
.isReadOnly())
211 GVS
->setReadOnly(false);
212 if (!VI
.isWriteOnly())
213 GVS
->setWriteOnly(false);
218 // Do the access attribute and DSOLocal propagation in combined index.
219 // The goal of attribute propagation is internalization of readonly (RO)
220 // or writeonly (WO) variables. To determine which variables are RO or WO
221 // and which are not we take following steps:
222 // - During analysis we speculatively assign readonly and writeonly
223 // attribute to all variables which can be internalized. When computing
224 // function summary we also assign readonly or writeonly attribute to a
225 // reference if function doesn't modify referenced variable (readonly)
226 // or doesn't read it (writeonly).
228 // - After computing dead symbols in combined index we do the attribute
229 // and DSOLocal propagation. During this step we:
230 // a. clear RO and WO attributes from variables which are preserved or
232 // b. clear RO and WO attributes from variables referenced by any global
233 // variable initializer
234 // c. clear RO attribute from variable referenced by a function when
235 // reference is not readonly
236 // d. clear WO attribute from variable referenced by a function when
237 // reference is not writeonly
238 // e. clear IsDSOLocal flag in every summary if any of them is false.
240 // Because of (c, d) we don't internalize variables read by function A
241 // and modified by function B.
243 // Internalization itself happens in the backend after import is finished
244 // See internalizeGVsAfterImport.
245 void ModuleSummaryIndex::propagateAttributes(
246 const DenseSet
<GlobalValue::GUID
> &GUIDPreservedSymbols
) {
249 DenseSet
<ValueInfo
> MarkedNonReadWriteOnly
;
250 for (auto &P
: *this) {
251 bool IsDSOLocal
= true;
252 for (auto &S
: P
.second
.SummaryList
) {
253 if (!isGlobalValueLive(S
.get())) {
254 // computeDeadSymbols should have marked all copies live. Note that
255 // it is possible that there is a GUID collision between internal
256 // symbols with the same name in different files of the same name but
257 // not enough distinguishing path. Because computeDeadSymbols should
258 // conservatively mark all copies live we can assert here that all are
259 // dead if any copy is dead.
260 assert(llvm::none_of(
261 P
.second
.SummaryList
,
262 [&](const std::unique_ptr
<GlobalValueSummary
> &Summary
) {
263 return isGlobalValueLive(Summary
.get());
265 // We don't examine references from dead objects
269 // Global variable can't be marked read/writeonly if it is not eligible
270 // to import since we need to ensure that all external references get
271 // a local (imported) copy. It also can't be marked read/writeonly if
272 // it or any alias (since alias points to the same memory) are preserved
273 // or notEligibleToImport, since either of those means there could be
274 // writes (or reads in case of writeonly) that are not visible (because
275 // preserved means it could have external to DSO writes or reads, and
276 // notEligibleToImport means it could have writes or reads via inline
277 // assembly leading it to be in the @llvm.*used).
278 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(S
->getBaseObject()))
279 // Here we intentionally pass S.get() not GVS, because S could be
280 // an alias. We don't analyze references here, because we have to
281 // know exactly if GV is readonly to do so.
282 if (!canImportGlobalVar(S
.get(), /* AnalyzeRefs */ false) ||
283 GUIDPreservedSymbols
.count(P
.first
)) {
284 GVS
->setReadOnly(false);
285 GVS
->setWriteOnly(false);
287 propagateAttributesToRefs(S
.get(), MarkedNonReadWriteOnly
);
289 // If the flag from any summary is false, the GV is not DSOLocal.
290 IsDSOLocal
&= S
->isDSOLocal();
293 // Mark the flag in all summaries false so that we can do quick check
294 // without going through the whole list.
295 for (const std::unique_ptr
<GlobalValueSummary
> &Summary
:
296 P
.second
.SummaryList
)
297 Summary
->setDSOLocal(false);
299 setWithAttributePropagation();
300 setWithDSOLocalPropagation();
301 if (llvm::AreStatisticsEnabled())
302 for (auto &P
: *this)
303 if (P
.second
.SummaryList
.size())
304 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(
305 P
.second
.SummaryList
[0]->getBaseObject()))
306 if (isGlobalValueLive(GVS
)) {
307 if (GVS
->maybeReadOnly())
309 if (GVS
->maybeWriteOnly())
310 WriteOnlyLiveGVars
++;
314 bool ModuleSummaryIndex::canImportGlobalVar(GlobalValueSummary
*S
,
315 bool AnalyzeRefs
) const {
316 auto HasRefsPreventingImport
= [this](const GlobalVarSummary
*GVS
) {
317 // We don't analyze GV references during attribute propagation, so
318 // GV with non-trivial initializer can be marked either read or
320 // Importing definiton of readonly GV with non-trivial initializer
321 // allows us doing some extra optimizations (like converting indirect
323 // Definition of writeonly GV with non-trivial initializer should also
324 // be imported. Not doing so will result in:
325 // a) GV internalization in source module (because it's writeonly)
326 // b) Importing of GV declaration to destination module as a result
328 // c) Link error (external declaration with internal definition).
329 // However we do not promote objects referenced by writeonly GV
330 // initializer by means of converting it to 'zeroinitializer'
331 return !(ImportConstantsWithRefs
&& GVS
->isConstant()) &&
332 !isReadOnly(GVS
) && !isWriteOnly(GVS
) && GVS
->refs().size();
334 auto *GVS
= cast
<GlobalVarSummary
>(S
->getBaseObject());
336 // Global variable with non-trivial initializer can be imported
337 // if it's readonly. This gives us extra opportunities for constant
338 // folding and converting indirect calls to direct calls. We don't
339 // analyze GV references during attribute propagation, because we
340 // don't know yet if it is readonly or not.
341 return !GlobalValue::isInterposableLinkage(S
->linkage()) &&
342 !S
->notEligibleToImport() &&
343 (!AnalyzeRefs
|| !HasRefsPreventingImport(GVS
));
346 // TODO: write a graphviz dumper for SCCs (see ModuleSummaryIndex::exportToDot)
347 // then delete this function and update its tests
349 void ModuleSummaryIndex::dumpSCCs(raw_ostream
&O
) {
350 for (scc_iterator
<ModuleSummaryIndex
*> I
=
351 scc_begin
<ModuleSummaryIndex
*>(this);
353 O
<< "SCC (" << utostr(I
->size()) << " node" << (I
->size() == 1 ? "" : "s")
355 for (const ValueInfo
&V
: *I
) {
356 FunctionSummary
*F
= nullptr;
357 if (V
.getSummaryList().size())
358 F
= cast
<FunctionSummary
>(V
.getSummaryList().front().get());
359 O
<< " " << (F
== nullptr ? "External" : "") << " " << utostr(V
.getGUID())
360 << (I
.hasCycle() ? " (has cycle)" : "") << "\n";
368 void add(const Twine
&Name
, const Twine
&Value
,
369 const Twine
&Comment
= Twine());
370 void addComment(const Twine
&Comment
);
371 std::string
getAsString() const;
373 std::vector
<std::string
> Attrs
;
374 std::string Comments
;
380 GlobalValue::GUID Src
;
381 GlobalValue::GUID Dst
;
385 void Attributes::add(const Twine
&Name
, const Twine
&Value
,
386 const Twine
&Comment
) {
387 std::string A
= Name
.str();
395 void Attributes::addComment(const Twine
&Comment
) {
396 if (!Comment
.isTriviallyEmpty()) {
397 if (Comments
.empty())
401 Comments
+= Comment
.str();
405 std::string
Attributes::getAsString() const {
409 std::string Ret
= "[";
410 for (auto &A
: Attrs
)
418 static std::string
linkageToString(GlobalValue::LinkageTypes LT
) {
420 case GlobalValue::ExternalLinkage
:
422 case GlobalValue::AvailableExternallyLinkage
:
424 case GlobalValue::LinkOnceAnyLinkage
:
426 case GlobalValue::LinkOnceODRLinkage
:
427 return "linkonce_odr";
428 case GlobalValue::WeakAnyLinkage
:
430 case GlobalValue::WeakODRLinkage
:
432 case GlobalValue::AppendingLinkage
:
434 case GlobalValue::InternalLinkage
:
436 case GlobalValue::PrivateLinkage
:
438 case GlobalValue::ExternalWeakLinkage
:
439 return "extern_weak";
440 case GlobalValue::CommonLinkage
:
447 static std::string
fflagsToString(FunctionSummary::FFlags F
) {
448 auto FlagValue
= [](unsigned V
) { return V
? '1' : '0'; };
449 char FlagRep
[] = {FlagValue(F
.ReadNone
), FlagValue(F
.ReadOnly
),
450 FlagValue(F
.NoRecurse
), FlagValue(F
.ReturnDoesNotAlias
),
451 FlagValue(F
.NoInline
), FlagValue(F
.AlwaysInline
), 0};
456 // Get string representation of function instruction count and flags.
457 static std::string
getSummaryAttributes(GlobalValueSummary
* GVS
) {
458 auto *FS
= dyn_cast_or_null
<FunctionSummary
>(GVS
);
462 return std::string("inst: ") + std::to_string(FS
->instCount()) +
463 ", ffl: " + fflagsToString(FS
->fflags());
466 static std::string
getNodeVisualName(GlobalValue::GUID Id
) {
467 return std::string("@") + std::to_string(Id
);
470 static std::string
getNodeVisualName(const ValueInfo
&VI
) {
471 return VI
.name().empty() ? getNodeVisualName(VI
.getGUID()) : VI
.name().str();
474 static std::string
getNodeLabel(const ValueInfo
&VI
, GlobalValueSummary
*GVS
) {
475 if (isa
<AliasSummary
>(GVS
))
476 return getNodeVisualName(VI
);
478 std::string Attrs
= getSummaryAttributes(GVS
);
480 getNodeVisualName(VI
) + "|" + linkageToString(GVS
->linkage());
482 Label
+= std::string(" (") + Attrs
+ ")";
488 // Write definition of external node, which doesn't have any
489 // specific module associated with it. Typically this is function
490 // or variable defined in native object or library.
491 static void defineExternalNode(raw_ostream
&OS
, const char *Pfx
,
492 const ValueInfo
&VI
, GlobalValue::GUID Id
) {
493 auto StrId
= std::to_string(Id
);
494 OS
<< " " << StrId
<< " [label=\"";
497 OS
<< getNodeVisualName(VI
);
499 OS
<< getNodeVisualName(Id
);
501 OS
<< "\"]; // defined externally\n";
504 static bool hasReadOnlyFlag(const GlobalValueSummary
*S
) {
505 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(S
))
506 return GVS
->maybeReadOnly();
510 static bool hasWriteOnlyFlag(const GlobalValueSummary
*S
) {
511 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(S
))
512 return GVS
->maybeWriteOnly();
516 static bool hasConstantFlag(const GlobalValueSummary
*S
) {
517 if (auto *GVS
= dyn_cast
<GlobalVarSummary
>(S
))
518 return GVS
->isConstant();
522 void ModuleSummaryIndex::exportToDot(
524 const DenseSet
<GlobalValue::GUID
> &GUIDPreservedSymbols
) const {
525 std::vector
<Edge
> CrossModuleEdges
;
526 DenseMap
<GlobalValue::GUID
, std::vector
<uint64_t>> NodeMap
;
527 using GVSOrderedMapTy
= std::map
<GlobalValue::GUID
, GlobalValueSummary
*>;
528 std::map
<StringRef
, GVSOrderedMapTy
> ModuleToDefinedGVS
;
529 collectDefinedGVSummariesPerModule(ModuleToDefinedGVS
);
531 // Get node identifier in form MXXX_<GUID>. The MXXX prefix is required,
532 // because we may have multiple linkonce functions summaries.
533 auto NodeId
= [](uint64_t ModId
, GlobalValue::GUID Id
) {
534 return ModId
== (uint64_t)-1 ? std::to_string(Id
)
535 : std::string("M") + std::to_string(ModId
) +
536 "_" + std::to_string(Id
);
539 auto DrawEdge
= [&](const char *Pfx
, uint64_t SrcMod
, GlobalValue::GUID SrcId
,
540 uint64_t DstMod
, GlobalValue::GUID DstId
,
544 // 2 - constant reference
545 // 3 - writeonly reference
546 // Other value: (hotness - 4).
548 static const char *EdgeAttrs
[] = {
549 " [style=dotted]; // alias",
550 " [style=dashed]; // ref",
551 " [style=dashed,color=forestgreen]; // const-ref",
552 " [style=dashed,color=violetred]; // writeOnly-ref",
553 " // call (hotness : Unknown)",
554 " [color=blue]; // call (hotness : Cold)",
555 " // call (hotness : None)",
556 " [color=brown]; // call (hotness : Hot)",
557 " [style=bold,color=red]; // call (hotness : Critical)"};
559 assert(static_cast<size_t>(TypeOrHotness
) <
560 sizeof(EdgeAttrs
) / sizeof(EdgeAttrs
[0]));
561 OS
<< Pfx
<< NodeId(SrcMod
, SrcId
) << " -> " << NodeId(DstMod
, DstId
)
562 << EdgeAttrs
[TypeOrHotness
] << "\n";
565 OS
<< "digraph Summary {\n";
566 for (auto &ModIt
: ModuleToDefinedGVS
) {
567 auto ModId
= getModuleId(ModIt
.first
);
568 OS
<< " // Module: " << ModIt
.first
<< "\n";
569 OS
<< " subgraph cluster_" << std::to_string(ModId
) << " {\n";
570 OS
<< " style = filled;\n";
571 OS
<< " color = lightgrey;\n";
572 OS
<< " label = \"" << sys::path::filename(ModIt
.first
) << "\";\n";
573 OS
<< " node [style=filled,fillcolor=lightblue];\n";
575 auto &GVSMap
= ModIt
.second
;
576 auto Draw
= [&](GlobalValue::GUID IdFrom
, GlobalValue::GUID IdTo
, int Hotness
) {
577 if (!GVSMap
.count(IdTo
)) {
578 CrossModuleEdges
.push_back({ModId
, Hotness
, IdFrom
, IdTo
});
581 DrawEdge(" ", ModId
, IdFrom
, ModId
, IdTo
, Hotness
);
584 for (auto &SummaryIt
: GVSMap
) {
585 NodeMap
[SummaryIt
.first
].push_back(ModId
);
586 auto Flags
= SummaryIt
.second
->flags();
588 if (isa
<FunctionSummary
>(SummaryIt
.second
)) {
589 A
.add("shape", "record", "function");
590 } else if (isa
<AliasSummary
>(SummaryIt
.second
)) {
591 A
.add("style", "dotted,filled", "alias");
592 A
.add("shape", "box");
594 A
.add("shape", "Mrecord", "variable");
595 if (Flags
.Live
&& hasReadOnlyFlag(SummaryIt
.second
))
596 A
.addComment("immutable");
597 if (Flags
.Live
&& hasWriteOnlyFlag(SummaryIt
.second
))
598 A
.addComment("writeOnly");
599 if (Flags
.Live
&& hasConstantFlag(SummaryIt
.second
))
600 A
.addComment("constant");
602 if (Flags
.Visibility
)
603 A
.addComment("visibility");
605 A
.addComment("dsoLocal");
606 if (Flags
.CanAutoHide
)
607 A
.addComment("canAutoHide");
608 if (GUIDPreservedSymbols
.count(SummaryIt
.first
))
609 A
.addComment("preserved");
611 auto VI
= getValueInfo(SummaryIt
.first
);
612 A
.add("label", getNodeLabel(VI
, SummaryIt
.second
));
614 A
.add("fillcolor", "red", "dead");
615 else if (Flags
.NotEligibleToImport
)
616 A
.add("fillcolor", "yellow", "not eligible to import");
618 OS
<< " " << NodeId(ModId
, SummaryIt
.first
) << " " << A
.getAsString()
621 OS
<< " // Edges:\n";
623 for (auto &SummaryIt
: GVSMap
) {
624 auto *GVS
= SummaryIt
.second
;
625 for (auto &R
: GVS
->refs())
626 Draw(SummaryIt
.first
, R
.getGUID(),
627 R
.isWriteOnly() ? -1 : (R
.isReadOnly() ? -2 : -3));
629 if (auto *AS
= dyn_cast_or_null
<AliasSummary
>(SummaryIt
.second
)) {
630 Draw(SummaryIt
.first
, AS
->getAliaseeGUID(), -4);
634 if (auto *FS
= dyn_cast_or_null
<FunctionSummary
>(SummaryIt
.second
))
635 for (auto &CGEdge
: FS
->calls())
636 Draw(SummaryIt
.first
, CGEdge
.first
.getGUID(),
637 static_cast<int>(CGEdge
.second
.Hotness
));
642 OS
<< " // Cross-module edges:\n";
643 for (auto &E
: CrossModuleEdges
) {
644 auto &ModList
= NodeMap
[E
.Dst
];
645 if (ModList
.empty()) {
646 defineExternalNode(OS
, " ", getValueInfo(E
.Dst
), E
.Dst
);
647 // Add fake module to the list to draw an edge to an external node
648 // in the loop below.
649 ModList
.push_back(-1);
651 for (auto DstMod
: ModList
)
652 // The edge representing call or ref is drawn to every module where target
653 // symbol is defined. When target is a linkonce symbol there can be
654 // multiple edges representing a single call or ref, both intra-module and
655 // cross-module. As we've already drawn all intra-module edges before we
657 if (DstMod
!= E
.SrcMod
)
658 DrawEdge(" ", E
.SrcMod
, E
.Src
, DstMod
, E
.Dst
, E
.Hotness
);