Index: clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll =================================================================== --- clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll +++ clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll @@ -36,7 +36,7 @@ ; Round trip it through llvm-as ; RUN: llvm-dis %t.o.thinlto.bc -o - | llvm-as -o - | llvm-dis -o - | FileCheck %s --check-prefix=CHECK-DIS ; CHECK-DIS: ^0 = module: (path: "{{.*}}thinlto-distributed-cfi-devirt.ll.tmp.o", hash: ({{.*}}, {{.*}}, {{.*}}, {{.*}}, {{.*}})) -; CHECK-DIS: ^1 = gv: (guid: 8346051122425466633, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 18, typeIdInfo: (typeTests: (^2), typeCheckedLoadVCalls: (vFuncId: (^2, offset: 8), vFuncId: (^2, offset: 0)))))) +; CHECK-DIS: ^1 = gv: (guid: 8346051122425466633, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 18, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0), typeIdInfo: (typeTests: (^2), typeCheckedLoadVCalls: (vFuncId: (^2, offset: 8), vFuncId: (^2, offset: 0)))))) ; CHECK-DIS: ^2 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: allOnes, sizeM1BitWidth: 7), wpdResolutions: ((offset: 0, wpdRes: (kind: branchFunnel)), (offset: 8, wpdRes: (kind: singleImpl, singleImplName: "_ZN1A1nEi"))))) ; guid = 7004155349499253778 ; RUN: %clang_cc1 -triple x86_64-grtev4-linux-gnu \ Index: clang/test/CodeGen/thinlto-distributed-cfi.ll =================================================================== --- clang/test/CodeGen/thinlto-distributed-cfi.ll +++ clang/test/CodeGen/thinlto-distributed-cfi.ll @@ -24,7 +24,7 @@ ; Round trip it through llvm-as ; RUN: llvm-dis %t.o.thinlto.bc -o - | llvm-as -o - | llvm-dis -o - | FileCheck %s --check-prefix=CHECK-DIS ; CHECK-DIS: ^0 = module: (path: "{{.*}}thinlto-distributed-cfi.ll.tmp.o", hash: ({{.*}}, {{.*}}, {{.*}}, {{.*}}, {{.*}})) -; CHECK-DIS: ^1 = gv: (guid: 8346051122425466633, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 7, typeIdInfo: (typeTests: (^2))))) +; CHECK-DIS: ^1 = gv: (guid: 8346051122425466633, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 7, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0), typeIdInfo: (typeTests: (^2))))) ; CHECK-DIS: ^2 = typeid: (name: "_ZTS1A", summary: (typeTestRes: (kind: single, sizeM1BitWidth: 0))) ; guid = 7004155349499253778 ; RUN: %clang_cc1 -triple x86_64-grtev4-linux-gnu \ Index: llvm/include/llvm/IR/ModuleSummaryIndex.h =================================================================== --- llvm/include/llvm/IR/ModuleSummaryIndex.h +++ llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -572,6 +572,33 @@ unsigned NoInline : 1; // Indicate if function should be always inlined. unsigned AlwaysInline : 1; + unsigned NoUnwind : 1; + + FFlags &operator|=(const FFlags &RHS) { + this->ReadNone |= RHS.ReadNone; + this->ReadOnly |= RHS.ReadOnly; + this->NoRecurse |= RHS.NoRecurse; + this->ReturnDoesNotAlias |= RHS.ReturnDoesNotAlias; + this->NoInline |= RHS.NoInline; + this->AlwaysInline |= RHS.AlwaysInline; + this->NoUnwind |= RHS.NoUnwind; + return *this; + } + + operator std::string() { + std::string Output; + raw_string_ostream OS(Output); + OS << "funcFlags: ("; + OS << "ReadNone: " << this->ReadNone; + OS << ", ReadOnly: " << this->ReadOnly; + OS << ", NoRecurse: " << this->NoRecurse; + OS << ", ReturnDoesNotAlias: " << this->ReturnDoesNotAlias; + OS << ", NoInline: " << this->NoInline; + OS << ", AlwaysInline: " << this->AlwaysInline; + OS << ", NoUnwind: " << this->NoUnwind; + OS << ")"; + return OS.str(); + } }; /// Describes the uses of a parameter by the function. @@ -688,6 +715,10 @@ /// Get function summary flags. FFlags fflags() const { return FunFlags; } + void setNoRecurse() { FunFlags.NoRecurse = true; } + + void setNoUnwind() { FunFlags.NoUnwind = true; } + /// Get the instruction count recorded for this function. unsigned instCount() const { return InstCount; } Index: llvm/include/llvm/LTO/LTO.h =================================================================== --- llvm/include/llvm/LTO/LTO.h +++ llvm/include/llvm/LTO/LTO.h @@ -23,6 +23,7 @@ #include "llvm/Object/IRSymtab.h" #include "llvm/Support/Error.h" #include "llvm/Support/thread.h" +#include "llvm/Transforms/IPO/FunctionAttrs.h" #include "llvm/Transforms/IPO/FunctionImport.h" namespace llvm { Index: llvm/include/llvm/Transforms/IPO/FunctionAttrs.h =================================================================== --- llvm/include/llvm/Transforms/IPO/FunctionAttrs.h +++ llvm/include/llvm/Transforms/IPO/FunctionAttrs.h @@ -17,6 +17,7 @@ #include "llvm/Analysis/CGSCCPassManager.h" #include "llvm/Analysis/LazyCallGraph.h" +#include "llvm/IR/ModuleSummaryIndex.h" #include "llvm/IR/PassManager.h" namespace llvm { @@ -38,6 +39,17 @@ /// Returns the memory access properties of this copy of the function. MemoryAccessKind computeFunctionBodyMemoryAccess(Function &F, AAResults &AAR); +/// Propagate function attributes for function summaries along the index's +/// callgraph during thinlink +bool thinLTOPropagateFunctionAttrs( + ModuleSummaryIndex &Index, + function_ref + isPrevailing); + +/// Inserts the FunctionAttr flags from the Index into \p TheModule. +void thinLTOInsertFunctionAttrsForModule(Module &TheModule, + const GVSummaryMapTy &DefinedGlobals); + /// Computes function attributes in post-order over the call graph. /// /// By operating in post-order, this pass computes precise attributes for Index: llvm/lib/Analysis/ModuleSummaryAnalysis.cpp =================================================================== --- llvm/lib/Analysis/ModuleSummaryAnalysis.cpp +++ llvm/lib/Analysis/ModuleSummaryAnalysis.cpp @@ -465,7 +465,8 @@ // FIXME: refactor this to use the same code that inliner is using. // Don't try to import functions with noinline attribute. F.getAttributes().hasFnAttribute(Attribute::NoInline), - F.hasFnAttribute(Attribute::AlwaysInline)}; + F.hasFnAttribute(Attribute::AlwaysInline), + F.hasFnAttribute(Attribute::NoUnwind)}; std::vector ParamAccesses; if (auto *SSI = GetSSICallback(F)) ParamAccesses = SSI->getParamAccesses(Index); @@ -711,7 +712,8 @@ F->hasFnAttribute(Attribute::NoRecurse), F->returnDoesNotAlias(), /* NoInline = */ false, - F->hasFnAttribute(Attribute::AlwaysInline)}, + F->hasFnAttribute(Attribute::AlwaysInline), + F->hasFnAttribute(Attribute::NoUnwind)}, /*EntryCount=*/0, ArrayRef{}, ArrayRef{}, ArrayRef{}, Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -932,6 +932,7 @@ Flags.ReturnDoesNotAlias = (RawFlags >> 3) & 0x1; Flags.NoInline = (RawFlags >> 4) & 0x1; Flags.AlwaysInline = (RawFlags >> 5) & 0x1; + Flags.NoUnwind = (RawFlags >> 6) & 0x1; return Flags; } Index: llvm/lib/IR/AsmWriter.cpp =================================================================== --- llvm/lib/IR/AsmWriter.cpp +++ llvm/lib/IR/AsmWriter.cpp @@ -3197,20 +3197,8 @@ } void AssemblyWriter::printFunctionSummary(const FunctionSummary *FS) { - Out << ", insts: " << FS->instCount(); - - FunctionSummary::FFlags FFlags = FS->fflags(); - if (FFlags.ReadNone | FFlags.ReadOnly | FFlags.NoRecurse | - FFlags.ReturnDoesNotAlias | FFlags.NoInline | FFlags.AlwaysInline) { - Out << ", funcFlags: ("; - Out << "readNone: " << FFlags.ReadNone; - Out << ", readOnly: " << FFlags.ReadOnly; - Out << ", noRecurse: " << FFlags.NoRecurse; - Out << ", returnDoesNotAlias: " << FFlags.ReturnDoesNotAlias; - Out << ", noInline: " << FFlags.NoInline; - Out << ", alwaysInline: " << FFlags.AlwaysInline; - Out << ")"; - } + Out << ", insts: " << FS->instCount() << ", " << FS->fflags(); + if (!FS->calls().empty()) { Out << ", calls: ("; FieldSeparator IFS; Index: llvm/lib/IR/ModuleSummaryIndex.cpp =================================================================== --- llvm/lib/IR/ModuleSummaryIndex.cpp +++ llvm/lib/IR/ModuleSummaryIndex.cpp @@ -446,9 +446,10 @@ 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), - FlagValue(F.NoInline), FlagValue(F.AlwaysInline), 0}; + char FlagRep[] = {FlagValue(F.ReadNone), FlagValue(F.ReadOnly), + FlagValue(F.NoRecurse), FlagValue(F.ReturnDoesNotAlias), + FlagValue(F.NoInline), FlagValue(F.AlwaysInline), + FlagValue(F.NoUnwind), 0}; return FlagRep; } Index: llvm/lib/LTO/LTO.cpp =================================================================== --- llvm/lib/LTO/LTO.cpp +++ llvm/lib/LTO/LTO.cpp @@ -1510,6 +1510,8 @@ thinLTOResolvePrevailingInIndex(Conf, ThinLTO.CombinedIndex, isPrevailing, recordNewLinkage, GUIDPreservedSymbols); + thinLTOPropagateFunctionAttrs(ThinLTO.CombinedIndex, isPrevailing); + generateParamAccessSummary(ThinLTO.CombinedIndex); std::unique_ptr BackendProc = Index: llvm/lib/LTO/LTOBackend.cpp =================================================================== --- llvm/lib/LTO/LTOBackend.cpp +++ llvm/lib/LTO/LTOBackend.cpp @@ -601,6 +601,8 @@ thinLTOResolvePrevailingInModule(Mod, DefinedGlobals); + thinLTOInsertFunctionAttrsForModule(Mod, DefinedGlobals); + if (Conf.PostPromoteModuleHook && !Conf.PostPromoteModuleHook(Task, Mod)) return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile)); Index: llvm/lib/LTO/ThinLTOCodeGenerator.cpp =================================================================== --- llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -54,6 +54,7 @@ #include "llvm/Support/ToolOutputFile.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/IPO.h" +#include "llvm/Transforms/IPO/FunctionAttrs.h" #include "llvm/Transforms/IPO/FunctionImport.h" #include "llvm/Transforms/IPO/Internalize.h" #include "llvm/Transforms/IPO/PassManagerBuilder.h" @@ -505,6 +506,8 @@ // Apply summary-based prevailing-symbol resolution decisions. thinLTOResolvePrevailingInModule(TheModule, DefinedGlobals); + thinLTOInsertFunctionAttrsForModule(TheModule, DefinedGlobals); + // Save temps: after promotion. saveTempBitcode(TheModule, SaveTempsDir, count, ".1.promoted.bc"); } @@ -1124,6 +1127,8 @@ *Index, IsExported(ExportLists, GUIDPreservedSymbols), IsPrevailing(PrevailingCopy)); + thinLTOPropagateFunctionAttrs(*Index, IsPrevailing(PrevailingCopy)); + // Make sure that every module has an entry in the ExportLists, ImportList, // GVSummary and ResolvedODR maps to enable threaded access to these maps // below. Index: llvm/lib/Transforms/IPO/FunctionAttrs.cpp =================================================================== --- llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -14,6 +14,7 @@ #include "llvm/Transforms/IPO/FunctionAttrs.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SCCIterator.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SetVector.h" @@ -82,6 +83,11 @@ STATISTIC(NumWillReturn, "Number of functions marked as willreturn"); STATISTIC(NumNoSync, "Number of functions marked as nosync"); +STATISTIC(NumThinLTONoRecurse, + "Number of functions marked as norecurse during thinLTO"); +STATISTIC(NumThinLTONoUnwind, + "Number of functions marked as nounwind during thinLTO"); + static cl::opt EnableNonnullArgPropagation( "enable-nonnull-arg-prop", cl::init(true), cl::Hidden, cl::desc("Try to propagate nonnull argument attributes from callsites to " @@ -95,6 +101,10 @@ "disable-nofree-inference", cl::Hidden, cl::desc("Stop inferring nofree attribute during function-attrs pass")); +static cl::opt DisableThinLTOPropagation( + "disable-thinlto-funcattrs", cl::init(true), cl::Hidden, + cl::desc("Don't propagate function-attrs in thinLTO")); + namespace { using SCCNodeSet = SmallSetVector; @@ -322,6 +332,258 @@ return MadeChange; } +namespace { +struct ThinLTOAttributeSummary { + FunctionSummary::FFlags Flags; + DenseSet Callees; +}; +} // namespace + +// Compute definitive function attributes for a function taking into account +// prevailing definitions and linkage types +static ThinLTOAttributeSummary &calculateDefinitiveAttributes( + ValueInfo VI, + DenseMap &CachedAttributes, + function_ref + isPrevailing) { + + if (CachedAttributes.count(VI)) + return CachedAttributes[VI]; + + // At this point, prevailing symbols have been resolved. + // Abort conditions where we go conservative. + // 1. Multiple instances with local linkage. Normally local linkage would be + // unique per module + // as the GUID includes the module path. We could have a guid alias if + // there wasn't any distinguishing path when each file was compiled, but + // that should be rare so we'll punt on those. + // 2. Multiple instances with external linkage. This should be caught in + // symbol resolution + // 3. Non-existent FunctionSummary for Aliasee. This presents a hole in our + // knowledge meaning we have to + // go conservative. + + // Otherwise we calculate combined attributes for a function as: + // 1. If we have a local linkage, take its attributes + // 2. If we have an external linkage check that it is prevailing, take its + // attributes + // 3. If we have a Weak/LinkOnce/AvailableExternally + // a. If there's a prevailing copy, pick it + // b. If there's an ODR copy, pick it + // c. Otherwise, merge all the weak copies together + + CachedAttributes[VI] = {}; + FunctionSummary::FFlags nonODRCombinedFlags; + bool hasNonODR = false; + const FunctionSummary *Local = nullptr; + const FunctionSummary *External = nullptr; + const FunctionSummary *Prevailing = nullptr; + const FunctionSummary *ODR = nullptr; + + auto AddToCalleesSet = [&](const FunctionSummary *FS) { + for (const auto &Callee : FS->calls()) { + CachedAttributes[VI].Callees.insert(Callee.first); + } + }; + + for (const auto &GVS : VI.getSummaryList()) { + if (!GVS->isLive()) + continue; + + const FunctionSummary *FS = nullptr; + + if (const AliasSummary *AS = dyn_cast(GVS.get())) { + assert(AS->hasAliasee()); + + FS = dyn_cast(AS->getBaseObject()); + if (!FS) + return CachedAttributes[VI]; + } else { + FS = dyn_cast(GVS.get()); + } + + if (FS) { + const auto &Linkage = GVS->linkage(); + if (GlobalValue::isLocalLinkage(Linkage)) { + if (Local) { + errs() << "ThinLTO FunctionAttrs: Multiple Local Linkage, bailing on " + "function " + << VI.name() << "\n"; + errs() << "ThinLTO FunctionAttrs: Current module " << FS->modulePath() + << "\n"; + errs() << "ThinLTO FunctionAttrs: Previous module " + << Local->modulePath() << "\n"; + return CachedAttributes[VI]; + } + Local = FS; + } else if (GlobalValue::isExternalLinkage(Linkage)) { + assert(isPrevailing(VI.getGUID(), GVS.get())); + if (External) { + errs() << "ThinLTO FunctionAttrs: Multiple External Linkage, bailing " + "on function " + << VI.name() << "\n"; + errs() << "ThinLTO FunctionAttrs: Current module " << FS->modulePath() + << "\n"; + errs() << "ThinLTO FunctionAttrs: Previous module " + << External->modulePath() << "\n"; + return CachedAttributes[VI]; + } + External = FS; + } else if (GlobalValue::isWeakLinkage(Linkage) || + GlobalValue::isAvailableExternallyLinkage(Linkage) || + GlobalValue::isLinkOnceLinkage(Linkage)) { + if (isPrevailing(VI.getGUID(), GVS.get())) { + assert(!Prevailing && "Cannot have more than one prevailing symbol"); + Prevailing = FS; + } else if (GlobalValue::isWeakODRLinkage(Linkage) || + GlobalValue::isLinkOnceODRLinkage(Linkage)) { + // Multiple copies of ODR functions can remain due to copies + // being potentially aliased. Since language-wise we can keep any + // we'll keep the last one seen. + ODR = FS; + } else if (!Prevailing && !ODR) { + hasNonODR = true; + nonODRCombinedFlags |= FS->fflags(); + AddToCalleesSet(FS); + } + } + } + } + + if (Local) { + assert(!External && !ODR); + CachedAttributes[VI].Flags = Local->fflags(); + AddToCalleesSet(Local); + } else if (External) { + CachedAttributes[VI].Flags = External->fflags(); + AddToCalleesSet(External); + } else if (Prevailing) { + CachedAttributes[VI].Flags = Prevailing->fflags(); + AddToCalleesSet(Prevailing); + } else if (ODR) { + CachedAttributes[VI].Flags = ODR->fflags(); + AddToCalleesSet(ODR); + } else if (hasNonODR) { + LLVM_DEBUG(dbgs() << "ThinLTO FunctionAttrs: merging function flags " + << nonODRCombinedFlags << " from from nonODR " + << VI.name() << "\n"); + CachedAttributes[VI].Flags = nonODRCombinedFlags; + } + + return CachedAttributes[VI]; +} + +bool llvm::thinLTOPropagateFunctionAttrs( + ModuleSummaryIndex &Index, + function_ref + isPrevailing) { + // TODO: implement addNoAliasAttrs once + // there's more information about the return type in the summary + if (DisableThinLTOPropagation) + return false; + + DenseMap CachedAttributes; + + auto PropagateAttributes = [&](std::vector &SCCNodes) { + // TODO: handle non-trivial SCC nodes + if (SCCNodes.size() != 1) + return false; + + ValueInfo V = SCCNodes.front(); + ThinLTOAttributeSummary &Summary = + calculateDefinitiveAttributes(V, CachedAttributes, isPrevailing); + + // TODO: make these composable + bool CanAddNoRecurse = true; + bool CanAddNoUnwind = true; + for (const auto &Callee : Summary.Callees) { + ThinLTOAttributeSummary &CalleeSummary = + calculateDefinitiveAttributes(Callee, CachedAttributes, isPrevailing); + // TODO: make these composable + if (!CalleeSummary.Flags.NoRecurse) + CanAddNoRecurse = false; + + if (!CalleeSummary.Flags.NoUnwind) + CanAddNoUnwind = false; + + if (!CanAddNoRecurse && !CanAddNoUnwind) + break; + } + + bool Changed = false; + if (CanAddNoRecurse || CanAddNoUnwind) { + Changed = true; + + if (CanAddNoRecurse) { + LLVM_DEBUG(dbgs() << "ThinLTO FunctionAttrs: Propagated NoRecurse to " + << V.name() << "\n"); + ++NumThinLTONoRecurse; + CachedAttributes[V].Flags.NoRecurse = true; + } + + if (CanAddNoUnwind) { + LLVM_DEBUG(dbgs() << "ThinLTO FunctionAttrs: Propagated NoUnwind to " + << V.name() << "\n"); + ++NumThinLTONoUnwind; + CachedAttributes[V].Flags.NoUnwind = true; + } + + for (auto &S : V.getSummaryList()) { + if (auto *FS = dyn_cast(S.get())) { + if (CanAddNoRecurse) + FS->setNoRecurse(); + + if (CanAddNoUnwind) + FS->setNoUnwind(); + } + } + } + + return Changed; + }; + + 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 |= PropagateAttributes(Nodes); + } + return Changed; +} + +/// Insert function attributes in the Index back into the \p TheModule +void llvm::thinLTOInsertFunctionAttrsForModule( + Module &TheModule, const GVSummaryMapTy &DefinedGlobals) { + if (DisableThinLTOPropagation) + return; + + for (Function &F : TheModule) { + const auto &GV = DefinedGlobals.find(F.getGUID()); + if (GV == DefinedGlobals.end()) + continue; + + if (FunctionSummary *FS = dyn_cast(GV->second)) { + if (FS->fflags().ReadNone) + if (!F.doesNotAccessMemory()) + F.setDoesNotAccessMemory(); + + if (FS->fflags().ReadOnly) + if (!F.onlyReadsMemory()) + F.setOnlyReadsMemory(); + + if (FS->fflags().NoRecurse) + if (!F.doesNotRecurse()) + F.setDoesNotRecurse(); + + if (FS->fflags().NoUnwind) + if (!F.doesNotThrow()) + F.setDoesNotThrow(); + } + } +} + namespace { /// For a given pointer Argument, this retains a list of Arguments of functions Index: llvm/test/ThinLTO/X86/Inputs/functionattr-prop.ll =================================================================== --- /dev/null +++ llvm/test/ThinLTO/X86/Inputs/functionattr-prop.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 void @callee_norecurse() { + ret void +} Index: llvm/test/ThinLTO/X86/Inputs/linkonce_functionattrs_comdat.ll =================================================================== --- /dev/null +++ llvm/test/ThinLTO/X86/Inputs/linkonce_functionattrs_comdat.ll @@ -0,0 +1,14 @@ +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$c2 = comdat any + +define linkonce_odr i32 @f(i8*) unnamed_addr comdat($c2) { + %i = call i32 @f(i8* null) + ret i32 41 +} + +define i32 @g() { + %i = call i32 @f(i8* null) + ret i32 %i +} Index: llvm/test/ThinLTO/X86/deadstrip.ll =================================================================== --- llvm/test/ThinLTO/X86/deadstrip.ll +++ llvm/test/ThinLTO/X86/deadstrip.ll @@ -66,7 +66,7 @@ ; LTO2-NOT: available_externally {{.*}} @baz() ; LTO2: @llvm.global_ctors = ; LTO2: define internal void @_GLOBAL__I_a() -; LTO2: define internal void @bar() { +; LTO2: define internal void @bar() [[ATTR_NORECURSE:#[0-9]+]] { ; LTO2: define internal void @bar_internal() ; LTO2-NOT: @dead_func() ; LTO2-NOT: available_externally {{.*}} @baz() @@ -78,7 +78,7 @@ ; Make sure we keep @linkonceodrfuncwithalias in Input/deadstrip.ll alive as it ; is reachable from @main. -; LTO2-CHECK2: define weak_odr dso_local void @linkonceodrfuncwithalias() { +; LTO2-CHECK2: define weak_odr dso_local void @linkonceodrfuncwithalias() [[ATTR_NORECURSE:#[0-9]+]] { ; We should have eventually removed @baz since it was internalized and unused ; CHECK2-NM-NOT: _baz @@ -98,6 +98,8 @@ ; DEBUG-DAG: Initialize import for 15611644523426561710 (boo) ; DEBUG-DAG: Ignores Dead GUID: 2384416018110111308 (another_dead_func) +; LTO2-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } + ; STATS: 3 function-import - Number of dead stripped symbols in index ; Next test the case where Inputs/deadstrip.ll does not get a module index, Index: llvm/test/ThinLTO/X86/function_entry_count.ll =================================================================== --- llvm/test/ThinLTO/X86/function_entry_count.ll +++ llvm/test/ThinLTO/X86/function_entry_count.ll @@ -14,11 +14,12 @@ ; RUN: -exported-symbol=g -exported-symbol=h -thinlto-save-temps=%t3. %t1.bc %t2.bc ; RUN: llvm-dis %t3.0.3.imported.bc -o - | FileCheck %s -; CHECK: define void @h() !prof ![[PROF2:[0-9]+]] -; CHECK: define void @f(i32{{.*}}) !prof ![[PROF1:[0-9]+]] +; CHECK: define void @h() [[ATTR_NORECURSE:#[0-9]+]] !prof ![[PROF2:[0-9]+]] +; CHECK: define void @f(i32{{.*}}) [[ATTR_NORECURSE:#[0-9]+]] !prof ![[PROF1:[0-9]+]] ; CHECK: define available_externally void @g() !prof ![[PROF2]] ; CHECK-DAG: ![[PROF1]] = !{!"synthetic_function_entry_count", i64 10} ; CHECK-DAG: ![[PROF2]] = !{!"synthetic_function_entry_count", i64 198} +; CHECK-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } target triple = "x86_64-unknown-linux-gnu" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" Index: llvm/test/ThinLTO/X86/functionattr-prop.ll =================================================================== --- /dev/null +++ llvm/test/ThinLTO/X86/functionattr-prop.ll @@ -0,0 +1,18 @@ +; RUN: opt -thinlto-bc %s -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: opt -thinlto-bc %p/Inputs/functionattr-prop.ll -thin-link-bitcode-file=%t2.thinlink.bc -o %t2.bc + +; First perform the thin link on the normal bitcode file. +; RUN: llvm-lto2 run -O0 %t1.bc %t2.bc -o %t.o -r %t2.bc,callee_norecurse,px -r %t1.bc,caller_norecurse,px -r %t1.bc,callee_norecurse,l -save-temps +; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @callee_norecurse() + +; CHECK: Function Attrs: norecurse +; CHECK-NEXT: define void @caller_norecurse() +define void @caller_norecurse() { + call void @callee_norecurse() + ret void +} Index: llvm/test/ThinLTO/X86/linkonce_functionattrs_comdat.ll =================================================================== --- /dev/null +++ llvm/test/ThinLTO/X86/linkonce_functionattrs_comdat.ll @@ -0,0 +1,24 @@ +; Tests that function attribute propagation takes the conservative set when propagating +; through linkonce_odr functions with different attributes. +; RUN: opt -module-summary %s -o %t1.bc +; RUN: opt -module-summary %p/Inputs/linkonce_functionattrs_comdat.ll -o %t2.bc +; RUN: llvm-lto -thinlto-action=run %t1.bc %t2.bc -exported-symbol=f -exported-symbol=g -thinlto-save-temps=%t3. + +; RUN: llvm-dis %t3.0.3.imported.bc -o - | FileCheck %s --check-prefix=IMPORT1 +; RUN: llvm-dis %t3.1.3.imported.bc -o - | FileCheck %s --check-prefix=IMPORT2 +; Copy from first module is prevailing and converted to weak_odr, copy +; from second module is preempted and converted to available_externally and +; removed from comdat. +; Note that lack of attributes on all of these functions +; IMPORT1: define weak_odr i32 @f(i8* %0) unnamed_addr comdat($c1) { +; IMPORT2: define available_externally i32 @f(i8* %0) unnamed_addr { +; IMPORT2: define i32 @g() { + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$c1 = comdat any + +define linkonce_odr i32 @f(i8*) unnamed_addr comdat($c1) { + ret i32 43 +} Index: llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll =================================================================== --- llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll +++ llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll @@ -10,8 +10,10 @@ ; Copy from first module is prevailing and converted to weak_odr, copy ; from second module is preempted and converted to available_externally and ; removed from comdat. -; IMPORT1: define weak_odr i32 @f(i8* %0) unnamed_addr comdat($c1) { -; IMPORT2: define available_externally i32 @f(i8* %0) unnamed_addr { +; IMPORT1: define weak_odr i32 @f(i8* %0) unnamed_addr [[ATTR_NORECURSE:#[0-9]+]] comdat($c1) { +; IMPORT2: define available_externally i32 @f(i8* %0) unnamed_addr [[ATTR_NORECURSE:#[0-9]+]] { + +; CHECK-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } ; RUN: llvm-nm -o - < %t1.bc.thinlto.o | FileCheck %s --check-prefix=NM1 ; NM1: W f Index: llvm/test/ThinLTO/X86/not-internalized.ll =================================================================== --- llvm/test/ThinLTO/X86/not-internalized.ll +++ llvm/test/ThinLTO/X86/not-internalized.ll @@ -15,7 +15,9 @@ ; Thin LTO internalization shouldn't internalize `bar` as well ; RUN: llvm-dis %t.out.1.2.internalize.bc -o - | FileCheck %s -; CHECK: define linkonce_odr dso_local i32 @bar() comdat($foo) +; CHECK: define linkonce_odr dso_local i32 @bar() [[ATTR_NORECURSE:#[0-9]+]] comdat($foo) + +; CHECK-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } $foo = comdat any Index: llvm/test/ThinLTO/X86/weak_externals.ll =================================================================== --- llvm/test/ThinLTO/X86/weak_externals.ll +++ llvm/test/ThinLTO/X86/weak_externals.ll @@ -11,8 +11,10 @@ ; CHECK: @_ZZN9SingletonI1SE11getInstanceEvE8instance = available_externally dso_local global %struct.S zeroinitializer ; CHECK: @_ZZN9SingletonI1SE11getInstanceEvE13instance_weak = available_externally dso_local global %struct.S* null, align 8 -; CHECK: define linkonce_odr dso_local dereferenceable(16) %struct.S* @_ZN9SingletonI1SE11getInstanceEv() comdat -; INTERNALIZE: define internal dereferenceable(16) %struct.S* @_ZN9SingletonI1SE11getInstanceEv() +; CHECK: define linkonce_odr dso_local dereferenceable(16) %struct.S* @_ZN9SingletonI1SE11getInstanceEv() [[ATTR_NORECURSE:#[0-9]+]] comdat +; INTERNALIZE: define internal dereferenceable(16) %struct.S* @_ZN9SingletonI1SE11getInstanceEv() [[ATTR_NORECURSE:#[0-9]+]] + +; CHECK-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @@ -40,4 +42,3 @@ define linkonce_odr dso_local dereferenceable(16) %struct.S* @_ZN9SingletonI1SE11getInstanceEv() #0 comdat align 2 { ret %struct.S* @_ZZN9SingletonI1SE11getInstanceEvE8instance } -