Index: include/llvm/IR/ModuleSummaryIndex.h =================================================================== --- include/llvm/IR/ModuleSummaryIndex.h +++ include/llvm/IR/ModuleSummaryIndex.h @@ -659,7 +659,10 @@ continue; // skip over non-root nodes Edges.push_back(std::make_pair(P.first, CalleeInfo{})); } - assert(Edges.size() && "Couldn't find any roots in index callgraph!"); + if (Edges.empty()) { + // Failed to find root - return an empty node + return FunctionSummary::makeDummyFunctionSummary({}); + } auto CallGraphRoot = FunctionSummary::makeDummyFunctionSummary(Edges); return CallGraphRoot; } Index: include/llvm/Transforms/IPO/FunctionImport.h =================================================================== --- include/llvm/Transforms/IPO/FunctionImport.h +++ include/llvm/Transforms/IPO/FunctionImport.h @@ -102,6 +102,15 @@ ModuleSummaryIndex &Index, const DenseSet &GUIDPreservedSymbols); +/// Propagate function attributes for function summaries along the index's +/// callgraph during thinlink +bool propagateFunctionAttrs(ModuleSummaryIndex &Index); + +/// Inserts the FunctionAttr flags from the Index (ReadNone, ReadOnly, +/// NoRecurse, NoAlias) into \p TheModule. +void thinLTOInsertFunctionAttrsForModule(Module &TheModule, + const GVSummaryMapTy &DefinedGlobals); + /// Compute the set of summaries needed for a ThinLTO backend compilation of /// \p ModulePath. // Index: lib/LTO/LTO.cpp =================================================================== --- lib/LTO/LTO.cpp +++ lib/LTO/LTO.cpp @@ -1052,6 +1052,7 @@ if (!ModuleToDefinedGVSummaries.count(Mod.first)) ModuleToDefinedGVSummaries.try_emplace(Mod.first); + propagateFunctionAttrs(ThinLTO.CombinedIndex); StringMap ImportLists( ThinLTO.ModuleMap.size()); StringMap ExportLists( Index: lib/LTO/LTOBackend.cpp =================================================================== --- lib/LTO/LTOBackend.cpp +++ lib/LTO/LTOBackend.cpp @@ -414,6 +414,7 @@ renameModuleForThinLTO(Mod, CombinedIndex); + thinLTOInsertFunctionAttrsForModule(Mod, DefinedGlobals); thinLTOResolveWeakForLinkerModule(Mod, DefinedGlobals); if (Conf.PostPromoteModuleHook && !Conf.PostPromoteModuleHook(Task, Mod)) Index: lib/Transforms/IPO/FunctionImport.cpp =================================================================== --- lib/Transforms/IPO/FunctionImport.cpp +++ lib/Transforms/IPO/FunctionImport.cpp @@ -13,6 +13,7 @@ #include "llvm/Transforms/IPO/FunctionImport.h" +#include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringSet.h" @@ -40,6 +41,8 @@ STATISTIC(NumImportedModules, "Number of modules imported from"); STATISTIC(NumDeadSymbols, "Number of dead stripped symbols in index"); STATISTIC(NumLiveSymbols, "Number of live symbols in index"); +STATISTIC(NumNoRecurse, + "Number of of functions marked norecurse from summary propagation"); /// Limit on instruction count of imported functions. static cl::opt ImportInstrLimit( @@ -410,6 +413,62 @@ #endif } +/// Propagates NoRecurse attributes throughout +/// the Index (uses same method as FunctionAttrs pass) +bool llvm::propagateFunctionAttrs(ModuleSummaryIndex &Index) { + // TODO: implement addNoAliasAttrs once + // there's more information about the return type in the summary + + auto addNoRecurseAttrs = [](std::vector &SCCNodes) { + if (SCCNodes.size() != 1) + return false; + + ValueInfo V = SCCNodes.front(); + + // Bail if we don't have a FunctionSummary to work with + if (!V.getSummaryList().size()) + return false; + + FunctionSummary *F = + cast(V.getSummaryList().front().get()); + + // The dummy root node has guid of 0, so skip it + if (V.getGUID() == 0 || F->fflags().NoRecurse) + return false; + + bool calleesMightRecurse = std::any_of( + F->calls().begin(), F->calls().end(), + [&F](const FunctionSummary::EdgeTy &E) { + if (E.first.getGUID() == 0 || !E.first.getSummaryList().size()) + return true; // might recurse - we can't reason about external + // functions + FunctionSummary *CFS = + cast(E.first.getSummaryList().front().get()); + return !CFS->fflags().NoRecurse; + }); + + if (calleesMightRecurse) + return false; + + if (!F->fflags().NoRecurse) { + F->fflags().NoRecurse = true; + NumNoRecurse++; + return true; + } + return false; + }; + + bool Changed = false; + + // Call propagation functions on each SCC in the Index + for (scc_iterator I = scc_begin(&Index); !I.isAtEnd(); + ++I) { + std::vector Nodes(*I); + Changed |= addNoRecurseAttrs(Nodes); + } + return Changed; +} + void llvm::computeDeadSymbols( ModuleSummaryIndex &Index, const DenseSet &GUIDPreservedSymbols) { @@ -514,6 +573,24 @@ return std::error_code(); } +/// Insert function attributes in the Index back into the \p TheModule. +void llvm::thinLTOInsertFunctionAttrsForModule( + Module &TheModule, const GVSummaryMapTy &DefinedGlobals) { + for (Function &F : TheModule) { + const auto &GV = DefinedGlobals.find(F.getGUID()); + if (GV == DefinedGlobals.end()) + return; + + FunctionSummary *FS = cast(GV->second); + if (FS->fflags().ReadNone) + F.setDoesNotAccessMemory(); + if (FS->fflags().ReadOnly) + F.setOnlyReadsMemory(); + if (FS->fflags().NoRecurse) + F.setDoesNotRecurse(); + } +} + /// Fixup WeakForLinker linkages in \p TheModule based on summary analysis. void llvm::thinLTOResolveWeakForLinkerModule( Module &TheModule, const GVSummaryMapTy &DefinedGlobals) { Index: test/ThinLTO/X86/Inputs/function-attr-prop-a.ll =================================================================== --- /dev/null +++ test/ThinLTO/X86/Inputs/function-attr-prop-a.ll @@ -0,0 +1,6 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define i32 @c() norecurse { + ret i32 4 +} Index: test/ThinLTO/X86/functionattr-prop.ll =================================================================== --- /dev/null +++ test/ThinLTO/X86/functionattr-prop.ll @@ -0,0 +1,15 @@ +; RUN: llvm-as %s -o %t1.o +; RUN: llvm-as %p/Inputs/function-attr-prop-a.ll -o %t2.o +; RUN: llvm-lto2 run -o %t3.o %t2.o %t1.o -r %t2.o,c,px -r %t1.o,d,px -r %t1.o,c,l -save-temps +; RUN: llvm-dis < %t3.o.0.4.opt.bc -o - | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare i32 @c() +; CHECK: define i32 @d() {{.*}} #0 +define i32 @d() { + call i32 @c() + ret i32 1; +} +; CHECK: #0 = { norecurse