[ThinLTO] Implement summary visualizer

Differential revision: https://reviews.llvm.org/D41297

llvm-svn: 323062
This commit is contained in:
Eugene Leviant 2018-01-21 07:27:32 +00:00
parent 2bbac01b81
commit 5a1f095ecc
14 changed files with 431 additions and 31 deletions

View File

@ -69,9 +69,27 @@ class GlobalValueSummary;
using GlobalValueSummaryList = std::vector<std::unique_ptr<GlobalValueSummary>>;
struct GlobalValueSummaryInfo {
/// The GlobalValue corresponding to this summary. This is only used in
/// per-module summaries.
const GlobalValue *GV = nullptr;
union NameOrGV {
NameOrGV(bool IsAnalysis) {
if (IsAnalysis)
GV = nullptr;
else
Name = "";
}
/// The GlobalValue corresponding to this summary. This is only used in
/// per-module summaries, when module analysis is being run.
const GlobalValue *GV;
/// Summary string representation. This StringRef points to BC module
/// string table and is valid until module data is stored in memory.
/// This is guaranteed to happen until runThinLTOBackend function is
/// called, so it is safe to use this field during thin link. This field
/// is only valid if summary index was loaded from BC file.
StringRef Name;
} U;
GlobalValueSummaryInfo(bool IsAnalysis) : U(IsAnalysis) {}
/// List of global value summary structures for a particular value held
/// in the GlobalValueMap. Requires a vector in the case of multiple
@ -91,32 +109,60 @@ using GlobalValueSummaryMapTy =
/// Struct that holds a reference to a particular GUID in a global value
/// summary.
struct ValueInfo {
const GlobalValueSummaryMapTy::value_type *Ref = nullptr;
PointerIntPair<const GlobalValueSummaryMapTy::value_type *, 1, bool>
RefAndFlag;
ValueInfo() = default;
ValueInfo(const GlobalValueSummaryMapTy::value_type *Ref) : Ref(Ref) {}
ValueInfo(bool IsAnalysis, const GlobalValueSummaryMapTy::value_type *R) {
RefAndFlag.setPointer(R);
RefAndFlag.setInt(IsAnalysis);
}
operator bool() const { return Ref; }
operator bool() const { return getRef(); }
GlobalValue::GUID getGUID() const { return Ref->first; }
const GlobalValue *getValue() const { return Ref->second.GV; }
GlobalValue::GUID getGUID() const { return getRef()->first; }
const GlobalValue *getValue() const {
assert(isFromAnalysis());
return getRef()->second.U.GV;
}
ArrayRef<std::unique_ptr<GlobalValueSummary>> getSummaryList() const {
return Ref->second.SummaryList;
return getRef()->second.SummaryList;
}
StringRef name() const {
return isFromAnalysis() ? getRef()->second.U.GV->getName()
: getRef()->second.U.Name;
}
bool isFromAnalysis() const { return RefAndFlag.getInt(); }
const GlobalValueSummaryMapTy::value_type *getRef() const {
return RefAndFlag.getPointer();
}
};
template <> struct DenseMapInfo<ValueInfo> {
static inline ValueInfo getEmptyKey() {
return ValueInfo((GlobalValueSummaryMapTy::value_type *)-1);
return ValueInfo(false, (GlobalValueSummaryMapTy::value_type *)-8);
}
static inline ValueInfo getTombstoneKey() {
return ValueInfo((GlobalValueSummaryMapTy::value_type *)-2);
return ValueInfo(false, (GlobalValueSummaryMapTy::value_type *)-16);
}
static bool isEqual(ValueInfo L, ValueInfo R) { return L.Ref == R.Ref; }
static unsigned getHashValue(ValueInfo I) { return (uintptr_t)I.Ref; }
static inline bool isSpecialKey(ValueInfo V) {
return V == getTombstoneKey() || V == getEmptyKey();
}
static bool isEqual(ValueInfo L, ValueInfo R) {
// We are not supposed to mix ValueInfo(s) with different analysis flag
// in a same container.
assert(isSpecialKey(L) || isSpecialKey(R) ||
(L.isFromAnalysis() == R.isFromAnalysis()));
return L.getRef() == R.getRef();
}
static unsigned getHashValue(ValueInfo I) { return (uintptr_t)I.getRef(); }
};
/// \brief Function and variable summary information to aid decisions and
@ -619,6 +665,11 @@ private:
/// considered live.
bool WithGlobalValueDeadStripping = false;
/// If true then we're performing analysis of IR module, filling summary
/// accordingly. The value of 'false' means we're reading summary from
/// BC or YAML source. Affects the type of value stored in NameOrGV union
bool IsAnalysis;
std::set<std::string> CfiFunctionDefs;
std::set<std::string> CfiFunctionDecls;
@ -627,10 +678,16 @@ private:
GlobalValueSummaryMapTy::value_type *
getOrInsertValuePtr(GlobalValue::GUID GUID) {
return &*GlobalValueMap.emplace(GUID, GlobalValueSummaryInfo{}).first;
return &*GlobalValueMap.emplace(GUID, GlobalValueSummaryInfo(IsAnalysis)).first;
}
public:
// See IsAnalysis variable comment.
ModuleSummaryIndex(bool IsPerformingAnalysis)
: IsAnalysis(IsPerformingAnalysis) {}
bool isPerformingAnalysis() const { return IsAnalysis; }
gvsummary_iterator begin() { return GlobalValueMap.begin(); }
const_gvsummary_iterator begin() const { return GlobalValueMap.begin(); }
gvsummary_iterator end() { return GlobalValueMap.end(); }
@ -652,19 +709,28 @@ public:
/// Return a ValueInfo for GUID if it exists, otherwise return ValueInfo().
ValueInfo getValueInfo(GlobalValue::GUID GUID) const {
auto I = GlobalValueMap.find(GUID);
return ValueInfo(I == GlobalValueMap.end() ? nullptr : &*I);
return ValueInfo(IsAnalysis, I == GlobalValueMap.end() ? nullptr : &*I);
}
/// Return a ValueInfo for \p GUID.
ValueInfo getOrInsertValueInfo(GlobalValue::GUID GUID) {
return ValueInfo(getOrInsertValuePtr(GUID));
return ValueInfo(IsAnalysis, getOrInsertValuePtr(GUID));
}
/// Return a ValueInfo for \p GUID setting value \p Name.
ValueInfo getOrInsertValueInfo(GlobalValue::GUID GUID, StringRef Name) {
assert(!IsAnalysis);
auto VP = getOrInsertValuePtr(GUID);
VP->second.U.Name = Name;
return ValueInfo(IsAnalysis, VP);
}
/// Return a ValueInfo for \p GV and mark it as belonging to GV.
ValueInfo getOrInsertValueInfo(const GlobalValue *GV) {
assert(IsAnalysis);
auto VP = getOrInsertValuePtr(GV->getGUID());
VP->second.GV = GV;
return ValueInfo(VP);
VP->second.U.GV = GV;
return ValueInfo(IsAnalysis, VP);
}
/// Return the GUID for \p OriginalId in the OidGuidMap.
@ -692,7 +758,7 @@ public:
addOriginalName(VI.getGUID(), Summary->getOriginalName());
// Here we have a notionally const VI, but the value it points to is owned
// by the non-const *this.
const_cast<GlobalValueSummaryMapTy::value_type *>(VI.Ref)
const_cast<GlobalValueSummaryMapTy::value_type *>(VI.getRef())
->second.SummaryList.push_back(std::move(Summary));
}
@ -823,6 +889,9 @@ public:
/// Summary).
void collectDefinedGVSummariesPerModule(
StringMap<GVSummaryMapTy> &ModuleToDefinedGVSummaries) const;
/// Export summary to dot file for GraphViz.
void exportToDot(raw_ostream& OS) const;
};
} // end namespace llvm

View File

@ -207,7 +207,8 @@ template <> struct CustomMappingTraits<GlobalValueSummaryMapTy> {
io.setError("key not an integer");
return;
}
auto &Elem = V[KeyInt];
auto P = V.emplace(KeyInt, /*IsAnalysis=*/false);
auto &Elem = (*P.first).second;
for (auto &FSum : FSums) {
Elem.SummaryList.push_back(llvm::make_unique<FunctionSummary>(
GlobalValueSummary::GVFlags(

View File

@ -372,7 +372,7 @@ ModuleSummaryIndex llvm::buildModuleSummaryIndex(
std::function<BlockFrequencyInfo *(const Function &F)> GetBFICallback,
ProfileSummaryInfo *PSI) {
assert(PSI);
ModuleSummaryIndex Index;
ModuleSummaryIndex Index(/*IsPerformingAnalysis=*/true);
// Identify the local values in the llvm.used and llvm.compiler.used sets,
// which should not be exported as they would then require renaming and

View File

@ -4813,8 +4813,12 @@ void ModuleSummaryIndexBitcodeReader::setValueGUID(
if (PrintSummaryGUIDs)
dbgs() << "GUID " << ValueGUID << "(" << OriginalNameID << ") is "
<< ValueName << "\n";
ValueIdToValueInfoMap[ValueID] =
std::make_pair(TheIndex.getOrInsertValueInfo(ValueGUID), OriginalNameID);
// UseStrtab is false for legacy summary formats and value names are
// created on stack. We can't use them outside of parseValueSymbolTable.
ValueIdToValueInfoMap[ValueID] = std::make_pair(
TheIndex.getOrInsertValueInfo(ValueGUID, UseStrtab ? ValueName : ""),
OriginalNameID);
}
// Specialized value symbol table parser used when reading module index
@ -5679,7 +5683,8 @@ Expected<std::unique_ptr<ModuleSummaryIndex>> BitcodeModule::getSummary() {
BitstreamCursor Stream(Buffer);
Stream.JumpToBit(ModuleBit);
auto Index = llvm::make_unique<ModuleSummaryIndex>();
auto Index =
llvm::make_unique<ModuleSummaryIndex>(/*IsPerformingAnalysis=*/false);
ModuleSummaryIndexBitcodeReader R(std::move(Stream), Strtab, *Index,
ModuleIdentifier, 0);

View File

@ -14,6 +14,7 @@
#include "llvm/IR/ModuleSummaryIndex.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/Path.h"
using namespace llvm;
// Collect for the given module the list of function it defines
@ -69,3 +70,246 @@ bool ModuleSummaryIndex::isGUIDLive(GlobalValue::GUID GUID) const {
return true;
return false;
}
namespace {
struct Attributes {
void add(const Twine &Name, const Twine &Value,
const Twine &Comment = Twine());
std::string getAsString() const;
std::vector<std::string> Attrs;
std::string Comments;
};
struct Edge {
uint64_t SrcMod;
int Hotness;
GlobalValue::GUID Src;
GlobalValue::GUID Dst;
};
}
void Attributes::add(const Twine &Name, const Twine &Value,
const Twine &Comment) {
std::string A = Name.str();
A += "=\"";
A += Value.str();
A += "\"";
Attrs.push_back(A);
if (!Comment.isTriviallyEmpty()) {
if (Comments.empty())
Comments = " // ";
else
Comments += ", ";
Comments += Comment.str();
}
}
std::string Attributes::getAsString() const {
if (Attrs.empty())
return "";
std::string Ret = "[";
for (auto &A : Attrs)
Ret += A + ",";
Ret.pop_back();
Ret += "];";
Ret += Comments;
return Ret;
}
static std::string linkageToString(GlobalValue::LinkageTypes LT) {
switch (LT) {
case GlobalValue::ExternalLinkage:
return "extern";
case GlobalValue::AvailableExternallyLinkage:
return "av_ext";
case GlobalValue::LinkOnceAnyLinkage:
return "linkonce";
case GlobalValue::LinkOnceODRLinkage:
return "linkonce_odr";
case GlobalValue::WeakAnyLinkage:
return "weak";
case GlobalValue::WeakODRLinkage:
return "weak_odr";
case GlobalValue::AppendingLinkage:
return "appending";
case GlobalValue::InternalLinkage:
return "internal";
case GlobalValue::PrivateLinkage:
return "private";
case GlobalValue::ExternalWeakLinkage:
return "extern_weak";
case GlobalValue::CommonLinkage:
return "common";
}
return "<unknown>";
}
static std::string fflagsToString(FunctionSummary::FFlags F) {
auto FlagValue = [](unsigned V) { return V ? '1' : '0'; };
char FlagRep[] = {FlagValue(F.ReadNone), FlagValue(F.ReadOnly),
FlagValue(F.NoRecurse), FlagValue(F.ReturnDoesNotAlias), 0};
return FlagRep;
}
// Get string representation of function instruction count and flags.
static std::string getSummaryAttributes(GlobalValueSummary* GVS) {
auto *FS = dyn_cast_or_null<FunctionSummary>(GVS);
if (!FS)
return "";
return std::string("inst: ") + std::to_string(FS->instCount()) +
", ffl: " + fflagsToString(FS->fflags());
}
static std::string getNodeVisualName(const ValueInfo &VI) {
return VI.name().empty() ? std::string("@") + std::to_string(VI.getGUID())
: VI.name().str();
}
static std::string getNodeLabel(const ValueInfo &VI, GlobalValueSummary *GVS) {
if (isa<AliasSummary>(GVS))
return getNodeVisualName(VI);
std::string Attrs = getSummaryAttributes(GVS);
std::string Label =
getNodeVisualName(VI) + "|" + linkageToString(GVS->linkage());
if (!Attrs.empty())
Label += std::string(" (") + Attrs + ")";
Label += "}";
return Label;
}
// Write definition of external node, which doesn't have any
// specific module associated with it. Typically this is function
// or variable defined in native object or library.
static void defineExternalNode(raw_ostream &OS, const char *Pfx,
const ValueInfo &VI) {
auto StrId = std::to_string(VI.getGUID());
OS << " " << StrId << " [label=\"" << getNodeVisualName(VI)
<< "\"]; // defined externally\n";
}
void ModuleSummaryIndex::exportToDot(raw_ostream& OS) const {
std::vector<Edge> CrossModuleEdges;
DenseMap<GlobalValue::GUID, std::vector<uint64_t>> NodeMap;
StringMap<GVSummaryMapTy> ModuleToDefinedGVS;
collectDefinedGVSummariesPerModule(ModuleToDefinedGVS);
// Get node identifier in form MXXX_<GUID>. The MXXX prefix is required,
// because we may have multiple linkonce functions summaries.
auto NodeId = [](uint64_t ModId, GlobalValue::GUID Id) {
return ModId == (uint64_t)-1 ? std::to_string(Id)
: std::string("M") + std::to_string(ModId) +
"_" + std::to_string(Id);
};
auto DrawEdge = [&](const char *Pfx, int SrcMod, GlobalValue::GUID SrcId,
int DstMod, GlobalValue::GUID DstId, int TypeOrHotness) {
// 0 corresponds to alias edge, 1 to ref edge, 2 to call with unknown
// hotness, ...
TypeOrHotness += 2;
static const char *EdgeAttrs[] = {
" [style=dotted]; // alias",
" [style=dashed]; // ref",
" // call (hotness : Unknown)",
" [color=blue]; // call (hotness : Cold)",
" // call (hotness : None)",
" [color=brown]; // call (hotness : Hot)",
" [style=bold,color=red]; // call (hotness : Critical)"};
assert(static_cast<size_t>(TypeOrHotness) <
sizeof(EdgeAttrs) / sizeof(EdgeAttrs[0]));
OS << Pfx << NodeId(SrcMod, SrcId) << " -> " << NodeId(DstMod, DstId)
<< EdgeAttrs[TypeOrHotness] << "\n";
};
OS << "digraph Summary {\n";
for (auto &ModIt : ModuleToDefinedGVS) {
auto ModId = getModuleId(ModIt.first());
OS << " // Module: " << ModIt.first() << "\n";
OS << " subgraph cluster_" << std::to_string(ModId) << " {\n";
OS << " style = filled;\n";
OS << " color = lightgrey;\n";
OS << " label = \"" << sys::path::filename(ModIt.first()) << "\";\n";
OS << " node [style=filled,fillcolor=lightblue];\n";
auto &GVSMap = ModIt.second;
auto Draw = [&](GlobalValue::GUID IdFrom, GlobalValue::GUID IdTo, int Hotness) {
if (!GVSMap.count(IdTo)) {
CrossModuleEdges.push_back({ModId, Hotness, IdFrom, IdTo});
return;
}
DrawEdge(" ", ModId, IdFrom, ModId, IdTo, Hotness);
};
for (auto &SummaryIt : GVSMap) {
NodeMap[SummaryIt.first].push_back(ModId);
auto Flags = SummaryIt.second->flags();
Attributes A;
if (isa<FunctionSummary>(SummaryIt.second)) {
A.add("shape", "record", "function");
} else if (isa<AliasSummary>(SummaryIt.second)) {
A.add("style", "dotted,filled", "alias");
A.add("shape", "box");
} else {
A.add("shape", "Mrecord", "variable");
}
auto VI = getValueInfo(SummaryIt.first);
A.add("label", getNodeLabel(VI, SummaryIt.second));
if (!Flags.Live)
A.add("fillcolor", "red", "dead");
else if (Flags.NotEligibleToImport)
A.add("fillcolor", "yellow", "not eligible to import");
OS << " " << NodeId(ModId, SummaryIt.first) << " " << A.getAsString()
<< "\n";
}
OS << " // Edges:\n";
for (auto &SummaryIt : GVSMap) {
auto *GVS = SummaryIt.second;
for (auto &R : GVS->refs())
Draw(SummaryIt.first, R.getGUID(), -1);
if (auto *AS = dyn_cast_or_null<AliasSummary>(SummaryIt.second)) {
auto AliaseeOrigId = AS->getAliasee().getOriginalName();
auto AliaseeId = getGUIDFromOriginalID(AliaseeOrigId);
Draw(SummaryIt.first, AliaseeId ? AliaseeId : AliaseeOrigId, -2);
continue;
}
if (auto *FS = dyn_cast_or_null<FunctionSummary>(SummaryIt.second))
for (auto &CGEdge : FS->calls())
Draw(SummaryIt.first, CGEdge.first.getGUID(),
static_cast<int>(CGEdge.second.Hotness));
}
OS << " }\n";
}
for (auto &E : CrossModuleEdges) {
auto &ModList = NodeMap[E.Dst];
if (ModList.empty()) {
defineExternalNode(OS, " ", getValueInfo(E.Dst));
// Add fake module to the list to draw an edge to an external node
// in the loop below.
ModList.push_back(-1);
}
for (auto DstMod : ModList)
// The edge representing call or ref is drawn to every module where target
// symbol is defined. When target is a linkonce symbol there can be
// multiple edges representing a single call or ref, both intra-module and
// cross-module. As we've already drawn all intra-module edges before we
// skip it here.
if (DstMod != E.SrcMod)
DrawEdge(" ", E.SrcMod, E.Src, DstMod, E.Dst, E.Hotness);
}
OS << "}";
}

View File

@ -388,7 +388,8 @@ LTO::RegularLTOState::RegularLTOState(unsigned ParallelCodeGenParallelismLevel,
Ctx(Conf), CombinedModule(llvm::make_unique<Module>("ld-temp.o", Ctx)),
Mover(llvm::make_unique<IRMover>(*CombinedModule)) {}
LTO::ThinLTOState::ThinLTOState(ThinBackend Backend) : Backend(Backend) {
LTO::ThinLTOState::ThinLTOState(ThinBackend Backend)
: Backend(Backend), CombinedIndex(/*IsPeformingAnalysis*/ false) {
if (!Backend)
this->Backend =
createInProcessThinBackend(llvm::heavyweight_hardware_concurrency());

View File

@ -103,6 +103,12 @@ Error Config::addSaveTemps(std::string OutputFileName,
if (EC)
reportOpenError(Path, EC.message());
WriteIndexToFile(Index, OS);
Path = OutputFileName + "index.dot";
raw_fd_ostream OSDot(Path, EC, sys::fs::OpenFlags::F_None);
if (EC)
reportOpenError(Path, EC.message());
Index.exportToDot(OSDot);
return true;
};

View File

@ -592,7 +592,7 @@ std::unique_ptr<TargetMachine> TargetMachineBuilder::create() const {
*/
std::unique_ptr<ModuleSummaryIndex> ThinLTOCodeGenerator::linkCombinedIndex() {
std::unique_ptr<ModuleSummaryIndex> CombinedIndex =
llvm::make_unique<ModuleSummaryIndex>();
llvm::make_unique<ModuleSummaryIndex>(/*IsPeformingAnalysis=*/false);
uint64_t NextModuleId = 0;
for (auto &ModuleBuffer : Modules) {
if (Error Err = readModuleSummaryIndex(ModuleBuffer.getMemBuffer(),

View File

@ -231,7 +231,7 @@ updateValueInfoForIndirectCalls(const ModuleSummaryIndex &Index, ValueInfo VI) {
// it, rather than needing to perform this mapping on each walk.
auto GUID = Index.getGUIDFromOriginalID(VI.getGUID());
if (GUID == 0)
return nullptr;
return ValueInfo();
return Index.getValueInfo(GUID);
}
@ -517,7 +517,7 @@ void llvm::computeDeadSymbols(
for (auto &S : Entry.second.SummaryList)
if (S->isLive()) {
DEBUG(dbgs() << "Live root: " << Entry.first << "\n");
Worklist.push_back(ValueInfo(&Entry));
Worklist.push_back(ValueInfo(/*IsAnalysis=*/false, &Entry));
++LiveSymbols;
break;
}

View File

@ -1529,7 +1529,7 @@ LowerTypeTestsModule::LowerTypeTestsModule(
}
bool LowerTypeTestsModule::runForTesting(Module &M) {
ModuleSummaryIndex Summary;
ModuleSummaryIndex Summary(/*IsPerformingAnalysis=*/false);
// Handle the command-line summary arguments. This code is for testing
// purposes only, so we handle errors directly.

View File

@ -584,7 +584,7 @@ PreservedAnalyses WholeProgramDevirtPass::run(Module &M,
bool DevirtModule::runForTesting(
Module &M, function_ref<AAResults &(Function &)> AARGetter,
function_ref<OptimizationRemarkEmitter &(Function *)> OREGetter) {
ModuleSummaryIndex Summary;
ModuleSummaryIndex Summary(/*IsPerformingAnalysis=*/false);
// Handle the command-line summary arguments. This code is for testing
// purposes only, so we handle errors directly.

View File

@ -0,0 +1,20 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
@A = local_unnamed_addr global i32 10, align 4
@B = local_unnamed_addr global i32 20, align 4
; Function Attrs: norecurse nounwind readonly uwtable
define i32 @foo() local_unnamed_addr #0 {
%1 = load i32, i32* @B, align 4
%2 = load i32, i32* @A, align 4
%3 = add nsw i32 %2, %1
ret i32 %3
}
; Function Attrs: norecurse nounwind readnone uwtable
define i32 @bar() local_unnamed_addr {
ret i32 42
}
attributes #0 = { noinline }

View File

@ -0,0 +1,54 @@
; RUN: opt -module-summary %s -o %t1.bc
; RUN: opt -module-summary %p/Inputs/dot-dumper.ll -o %t2.bc
; RUN: llvm-lto2 run -save-temps %t1.bc %t2.bc -o %t3 \
; RUN: -r=%t1.bc,main,px \
; RUN: -r=%t1.bc,main_alias,p \
; RUN: -r=%t1.bc,foo, \
; RUN: -r=%t1.bc,A, \
; RUN: -r=%t2.bc,foo,p \
; RUN: -r=%t2.bc,bar,p \
; RUN: -r=%t2.bc,A,p \
; RUN: -r=%t2.bc,B,p
; RUN: cat %t3.index.dot | FileCheck %s
; CHECK: digraph Summary
; CHECK-NEXT: Module:
; CHECK-LABEL: subgraph cluster_0
; Node definitions can appear in any order, but they should go before edge list.
; CHECK-DAG: M0_[[MAIN_ALIAS:[0-9]+]] [{{.*}}main_alias{{.*}}]; // alias, dead
; CHECK-DAG: M0_[[MAIN:[0-9]+]] [{{.*}}main|extern{{.*}}]; // function
; CHECK: // Edges:
; CHECK-NEXT: M0_[[MAIN_ALIAS]] -> M0_[[MAIN]] [{{.*}}]; // alias
; CHECK-LABEL: subgraph cluster_1 {
; CHECK: M1_[[A:[0-9]+]] [{{.*}}A|extern{{.*}}]; // variable
; CHECK-DAG: M1_[[FOO:[0-9]+]] [{{.*}}foo|extern{{.*}}]; // function, not eligible to import
; CHECK-DAG: M1_[[B:[0-9]+]] [{{.*}}B|extern{{.*}}]; // variable
; CHECK-DAG: M1_[[BAR:[0-9]+]] [{{.*}}bar|extern{{.*}}]; // function, dead
; CHECK: Edges:
; Order of edges in dot file is undefined
; CHECK-DAG: M1_[[FOO]] -> M1_[[B]] [{{.*}}]; // ref
; CHECK-DAG: M1_[[FOO]] -> M1_[[A]] [{{.*}}]; // ref
; CHECK: }
; Cross-module edges
; CHECK-DAG: M0_[[MAIN]] -> M1_[[FOO]] // call
; CHECK-DAG: M0_[[MAIN]] -> M1_[[A]] [{{.*}}]; // ref
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
@A = external local_unnamed_addr global i32, align 4
; Function Attrs: nounwind uwtable
define i32 @main() local_unnamed_addr {
%1 = tail call i32 (...) @foo()
%2 = load i32, i32* @A, align 4
%3 = add nsw i32 %2, %1
ret i32 %3
}
@main_alias = weak_odr alias i32 (), i32 ()* @main
declare i32 @foo(...) local_unnamed_addr

View File

@ -367,7 +367,7 @@ static void listSymbols(const TargetOptions &Options) {
/// This is meant to enable testing of ThinLTO combined index generation,
/// currently available via the gold plugin via -thinlto.
static void createCombinedModuleSummaryIndex() {
ModuleSummaryIndex CombinedIndex;
ModuleSummaryIndex CombinedIndex(/*IsPerformingAnalysis=*/false);
uint64_t NextModuleId = 0;
for (auto &Filename : InputFilenames) {
ExitOnError ExitOnErr("llvm-lto: error loading file '" + Filename + "': ");