diff --git a/clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll b/clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll --- a/clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll +++ b/clang/test/CodeGen/thinlto-distributed-cfi-devirt.ll @@ -7,7 +7,7 @@ ; RUN: opt -thinlto-bc -thinlto-split-lto-unit -o %t.o %s ; FIXME: Fix machine verifier issues and remove -verify-machineinstrs=0. PR39436. -; RUN: llvm-lto2 run -thinlto-distributed-indexes %t.o \ +; RUN: llvm-lto2 run -thinlto-distributed-indexes -disable-thinlto-funcattrs=0 %t.o \ ; RUN: -whole-program-visibility \ ; RUN: -verify-machineinstrs=0 \ ; RUN: -o %t2.index \ @@ -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: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), 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 \ diff --git a/clang/test/CodeGen/thinlto-distributed-cfi.ll b/clang/test/CodeGen/thinlto-distributed-cfi.ll --- a/clang/test/CodeGen/thinlto-distributed-cfi.ll +++ b/clang/test/CodeGen/thinlto-distributed-cfi.ll @@ -4,7 +4,7 @@ ; RUN: opt -thinlto-bc -thinlto-split-lto-unit -o %t.o %s -; RUN: llvm-lto2 run -thinlto-distributed-indexes %t.o \ +; RUN: llvm-lto2 run -thinlto-distributed-indexes -disable-thinlto-funcattrs=0 %t.o \ ; RUN: -o %t2.index \ ; RUN: -r=%t.o,test,px \ ; RUN: -r=%t.o,_ZTV1B, \ @@ -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, noUnwind: 1, mayThrow: 0, hasUnknownCall: 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 \ diff --git a/clang/test/CodeGen/thinlto-funcattr-prop.ll b/clang/test/CodeGen/thinlto-funcattr-prop.ll new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/thinlto-funcattr-prop.ll @@ -0,0 +1,39 @@ +; REQUIRES: x86-registered-target + +; Test that FunctionAttr Propagation is generating correct summaries + +; RUN: split-file %s %t +; RUN: opt -module-summary %t/a.ll -o %t/a.bc +; RUN: opt -module-summary %t/b.ll -o %t/b.bc + +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t/a.bc %t/b.bc -o %t1 -save-temps \ +; RUN: -r=%t/a.bc,call_extern,plx \ +; RUN: -r=%t/a.bc,extern, \ +; RUN: -r=%t/b.bc,extern,p + +; RUN: llvm-dis %t/b.bc -o - | FileCheck %s + +; CHECK: ^0 = module: (path: "{{.*}}b.bc", hash: ({{.*}}, {{.*}}, {{.*}}, {{.*}}, {{.*}})) +; CHECK: ^1 = gv: (name: "extern", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0)))) ; guid = 14959766916849974397 +; CHECK: ^2 = blockcount: 1 + +;--- a.ll +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" + +declare void @extern() + +define void @call_extern() { + call void @extern() + ret void +} + +;--- b.ll +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" + +attributes #0 = { nounwind norecurse } + +define void @extern() #0 { + ret void +} \ No newline at end of file diff --git a/llvm/include/llvm/AsmParser/LLToken.h b/llvm/include/llvm/AsmParser/LLToken.h --- a/llvm/include/llvm/AsmParser/LLToken.h +++ b/llvm/include/llvm/AsmParser/LLToken.h @@ -404,6 +404,9 @@ kw_returnDoesNotAlias, kw_noInline, kw_alwaysInline, + kw_noUnwind, + kw_mayThrow, + kw_hasUnknownCall, kw_calls, kw_callee, kw_params, diff --git a/llvm/include/llvm/IR/GlobalValue.h b/llvm/include/llvm/IR/GlobalValue.h --- a/llvm/include/llvm/IR/GlobalValue.h +++ b/llvm/include/llvm/IR/GlobalValue.h @@ -302,11 +302,14 @@ static bool isAvailableExternallyLinkage(LinkageTypes Linkage) { return Linkage == AvailableExternallyLinkage; } + static bool isLinkOnceAnyLinkage(LinkageTypes Linkage) { + return Linkage == LinkOnceAnyLinkage; + } static bool isLinkOnceODRLinkage(LinkageTypes Linkage) { return Linkage == LinkOnceODRLinkage; } static bool isLinkOnceLinkage(LinkageTypes Linkage) { - return Linkage == LinkOnceAnyLinkage || Linkage == LinkOnceODRLinkage; + return isLinkOnceAnyLinkage(Linkage) || isLinkOnceODRLinkage(Linkage); } static bool isWeakAnyLinkage(LinkageTypes Linkage) { return Linkage == WeakAnyLinkage; @@ -433,6 +436,9 @@ return isAvailableExternallyLinkage(getLinkage()); } bool hasLinkOnceLinkage() const { return isLinkOnceLinkage(getLinkage()); } + bool hasLinkOnceAnyLinkage() const { + return isLinkOnceAnyLinkage(getLinkage()); + } bool hasLinkOnceODRLinkage() const { return isLinkOnceODRLinkage(getLinkage()); } diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h --- a/llvm/include/llvm/IR/ModuleSummaryIndex.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -572,6 +572,48 @@ unsigned NoInline : 1; // Indicate if function should be always inlined. unsigned AlwaysInline : 1; + unsigned NoUnwind : 1; + // Indicate if function contains instructions that mayThrow + unsigned MayThrow : 1; + + // If there are calls to unknown targets (e.g. indirect) + unsigned hasUnknownCall : 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; + this->MayThrow &= RHS.MayThrow; + this->hasUnknownCall &= RHS.hasUnknownCall; + return *this; + } + + bool anyFlagSet() { + return this->ReadNone | this->ReadOnly | this->NoRecurse | + this->ReturnDoesNotAlias | this->NoInline | this->AlwaysInline | + this->NoUnwind | this->MayThrow | this->hasUnknownCall; + } + + 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 << ", mayThrow: " << this->MayThrow; + OS << ", hasUnknownCall: " << this->hasUnknownCall; + OS << ")"; + return OS.str(); + } }; /// Describes the uses of a parameter by the function. @@ -688,6 +730,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; } diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h --- a/llvm/include/llvm/LTO/LTO.h +++ b/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 { diff --git a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h b/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h --- a/llvm/include/llvm/Transforms/IPO/FunctionAttrs.h +++ b/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 diff --git a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp --- a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp +++ b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp @@ -265,6 +265,8 @@ bool HasInlineAsmMaybeReferencingInternal = false; bool HasIndirBranchToBlockAddress = false; + bool HasUnknownCall = false; + bool MayThrow = false; for (const BasicBlock &BB : F) { // We don't allow inlining of function with indirect branch to blockaddress. // If the blockaddress escapes the function, e.g., via a global variable, @@ -282,6 +284,7 @@ if (isa(I)) continue; ++NumInsts; + // Regular LTO module doesn't participate in ThinLTO import, // so no reference from it can be read/writeonly, since this // would require importing variable as local copy @@ -313,8 +316,11 @@ } findRefEdges(Index, &I, RefEdges, Visited); const auto *CB = dyn_cast(&I); - if (!CB) + if (!CB) { + if (I.mayThrow()) + MayThrow = true; continue; + } const auto *CI = dyn_cast(&I); // Since we don't know exactly which local values are referenced in inline @@ -370,6 +376,7 @@ ValueInfo.updateRelBlockFreq(BBFreq, EntryFreq); } } else { + HasUnknownCall = true; // Skip inline assembly calls. if (CI && CI->isInlineAsm()) continue; @@ -480,7 +487,8 @@ // FIXME: refactor this to use the same code that inliner is using. // Don't try to import functions with noinline attribute. F.getAttributes().hasFnAttr(Attribute::NoInline), - F.hasFnAttribute(Attribute::AlwaysInline)}; + F.hasFnAttribute(Attribute::AlwaysInline), + F.hasFnAttribute(Attribute::NoUnwind), MayThrow, HasUnknownCall}; std::vector ParamAccesses; if (auto *SSI = GetSSICallback(F)) ParamAccesses = SSI->getParamAccesses(Index); @@ -726,7 +734,10 @@ F->hasFnAttribute(Attribute::NoRecurse), F->returnDoesNotAlias(), /* NoInline = */ false, - F->hasFnAttribute(Attribute::AlwaysInline)}, + F->hasFnAttribute(Attribute::AlwaysInline), + F->hasFnAttribute(Attribute::NoUnwind), + /* MayThrow */ true, + /* HasUnknownCall */ true}, /*EntryCount=*/0, ArrayRef{}, ArrayRef{}, ArrayRef{}, diff --git a/llvm/lib/AsmParser/LLLexer.cpp b/llvm/lib/AsmParser/LLLexer.cpp --- a/llvm/lib/AsmParser/LLLexer.cpp +++ b/llvm/lib/AsmParser/LLLexer.cpp @@ -770,6 +770,9 @@ KEYWORD(returnDoesNotAlias); KEYWORD(noInline); KEYWORD(alwaysInline); + KEYWORD(noUnwind); + KEYWORD(mayThrow); + KEYWORD(hasUnknownCall); KEYWORD(calls); KEYWORD(callee); KEYWORD(params); diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -8521,6 +8521,9 @@ /// [',' 'returnDoesNotAlias' ':' Flag]? ')' /// [',' 'noInline' ':' Flag]? ')' /// [',' 'alwaysInline' ':' Flag]? ')' +/// [',' 'noUnwind' ':' Flag]? ')' +/// [',' 'mayThrow' ':' Flag]? ')' +/// [',' 'hasUnknownCall' ':' Flag]? ')' bool LLParser::parseOptionalFFlags(FunctionSummary::FFlags &FFlags) { assert(Lex.getKind() == lltok::kw_funcFlags); @@ -8569,6 +8572,24 @@ return true; FFlags.AlwaysInline = Val; break; + case lltok::kw_noUnwind: + Lex.Lex(); + if (parseToken(lltok::colon, "expected ':'") || parseFlag(Val)) + return true; + FFlags.NoUnwind = Val; + break; + case lltok::kw_mayThrow: + Lex.Lex(); + if (parseToken(lltok::colon, "expected ':'") || parseFlag(Val)) + return true; + FFlags.MayThrow = Val; + break; + case lltok::kw_hasUnknownCall: + Lex.Lex(); + if (parseToken(lltok::colon, "expected ':'") || parseFlag(Val)) + return true; + FFlags.hasUnknownCall = Val; + break; default: return error(Lex.getLoc(), "expected function flag type"); } diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -933,6 +933,9 @@ Flags.ReturnDoesNotAlias = (RawFlags >> 3) & 0x1; Flags.NoInline = (RawFlags >> 4) & 0x1; Flags.AlwaysInline = (RawFlags >> 5) & 0x1; + Flags.NoUnwind = (RawFlags >> 6) & 0x1; + Flags.MayThrow = (RawFlags >> 7) & 0x1; + Flags.hasUnknownCall = (RawFlags >> 8) & 0x1; return Flags; } diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -1066,6 +1066,9 @@ RawFlags |= (Flags.ReturnDoesNotAlias << 3); RawFlags |= (Flags.NoInline << 4); RawFlags |= (Flags.AlwaysInline << 5); + RawFlags |= (Flags.NoUnwind << 6); + RawFlags |= (Flags.MayThrow << 7); + RawFlags |= (Flags.hasUnknownCall << 8); return RawFlags; } diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp --- a/llvm/lib/IR/AsmWriter.cpp +++ b/llvm/lib/IR/AsmWriter.cpp @@ -3199,19 +3199,9 @@ void AssemblyWriter::printFunctionSummary(const FunctionSummary *FS) { Out << ", insts: " << FS->instCount(); + if (FS->fflags().anyFlagSet()) + Out << ", " << FS->fflags(); - 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 << ")"; - } if (!FS->calls().empty()) { Out << ", calls: ("; FieldSeparator IFS; diff --git a/llvm/lib/IR/ModuleSummaryIndex.cpp b/llvm/lib/IR/ModuleSummaryIndex.cpp --- a/llvm/lib/IR/ModuleSummaryIndex.cpp +++ b/llvm/lib/IR/ModuleSummaryIndex.cpp @@ -446,9 +446,11 @@ 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), FlagValue(F.MayThrow), + FlagValue(F.hasUnknownCall), 0}; return FlagRep; } diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -1516,6 +1516,8 @@ thinLTOResolvePrevailingInIndex(Conf, ThinLTO.CombinedIndex, isPrevailing, recordNewLinkage, GUIDPreservedSymbols); + thinLTOPropagateFunctionAttrs(ThinLTO.CombinedIndex, isPrevailing); + generateParamAccessSummary(ThinLTO.CombinedIndex); if (llvm::timeTraceProfilerEnabled()) diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp --- a/llvm/lib/LTO/LTOBackend.cpp +++ b/llvm/lib/LTO/LTOBackend.cpp @@ -608,6 +608,8 @@ thinLTOResolvePrevailingInModule(Mod, DefinedGlobals); + thinLTOInsertFunctionAttrsForModule(Mod, DefinedGlobals); + if (Conf.PostPromoteModuleHook && !Conf.PostPromoteModuleHook(Task, Mod)) return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile)); diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp --- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -55,6 +55,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" @@ -506,6 +507,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"); } @@ -1130,6 +1133,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. diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -14,6 +14,7 @@ #include "llvm/Transforms/IPO/FunctionAttrs.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.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(NumThinLinkNoRecurse, + "Number of functions marked as norecurse during thinlink"); +STATISTIC(NumThinLinkNoUnwind, + "Number of functions marked as nounwind during thinlink"); + 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,15 @@ "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")); + +static cl::opt OptimisticIndirectCalls( + "thinlto-funcattrs-optimistic-indirect", cl::init(false), cl::Hidden, + cl::desc("Optimistically assume indirect calls don't exist in thinlink " + "attribute propagation. Testing only")); + namespace { using SCCNodeSet = SmallSetVector; @@ -322,6 +337,225 @@ return MadeChange; } +// Compute definitive function attributes for a function taking into account +// prevailing definitions and linkage types +static FunctionSummary *calculateDefinitiveAttributes( + ValueInfo VI, DenseMap &CachedAttributes, + function_ref + IsPrevailing) { + + if (CachedAttributes.count(VI)) + return CachedAttributes[VI]; + + /// At this point, prevailing symbols have been resolved. The following leads + /// to returning a conservative result: + /// - 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. + + /// These next 2 cases should not happen and will assert: + /// - Multiple instances with external linkage. This should be caught in + /// symbol resolution + /// - Non-existent FunctionSummary for Aliasee. This presents a hole in our + /// knowledge meaning we have to go conservative. + + /// Otherwise, we calculate attributes for a function as: + /// 1. If we have a local linkage, take its attributes. If there's somehow + /// multiple, bail and go conservative. + /// 2. If we have an external/WeakODR/LinkOnceODR linkage check that it is + /// prevailing, take its attributes. + /// 3. If we have a Weak/LinkOnce linkage the copies can have semantic + /// differences. However, if the prevailing copy is known it will be used + /// so take its attributes. If the prevailing copy is in a native file + /// all IR copies will be dead and propagation will go conservative. + /// 4. AvailableExternally summaries without a prevailing copy are known to + /// occur in a couple of circumstances: + /// a. An internal function gets imported due to its caller getting + /// imported, it becomes AvailableExternally but no prevailing + /// definition exists. Because it has to get imported along with its + /// caller the attributes will be captured by propagating on its + /// caller. + /// b. C++11 [temp.explicit]p10 can generate AvailableExternally + /// definitions of explicitly instanced template declarations + /// for inlining which are ultimately dropped from the TU. Since this + /// is localized to the TU the attributes will have already made it to + /// the callers. + /// These are edge cases and already captured by their callers so we + /// ignore these for now. If they become relevant to optimize in the + /// future this can be revisited. + /// 5. Otherwise, go conservative. + + CachedAttributes[VI] = nullptr; + FunctionSummary *Local = nullptr; + FunctionSummary *Prevailing = nullptr; + + for (const auto &GVS : VI.getSummaryList()) { + if (!GVS->isLive()) + continue; + + FunctionSummary *FS = dyn_cast(GVS->getBaseObject()); + // Virtual and Unknown (e.g. indirect) calls require going conservative + if (!FS || (!OptimisticIndirectCalls && FS->fflags().hasUnknownCall)) + return nullptr; + + const auto &Linkage = GVS->linkage(); + if (GlobalValue::isLocalLinkage(Linkage)) { + if (Local) { + LLVM_DEBUG( + dbgs() + << "ThinLTO FunctionAttrs: Multiple Local Linkage, bailing on " + "function " + << VI.name() << " from " << FS->modulePath() << ". Previous module " + << Local->modulePath() << "\n"); + return nullptr; + } + Local = FS; + } else if (GlobalValue::isExternalLinkage(Linkage)) { + assert(IsPrevailing(VI.getGUID(), GVS.get())); + Prevailing = FS; + break; + } else if (GlobalValue::isWeakODRLinkage(Linkage) || + GlobalValue::isLinkOnceODRLinkage(Linkage) || + GlobalValue::isWeakAnyLinkage(Linkage) || + GlobalValue::isLinkOnceAnyLinkage(Linkage)) { + if (IsPrevailing(VI.getGUID(), GVS.get())) { + Prevailing = FS; + break; + } + } else if (GlobalValue::isAvailableExternallyLinkage(Linkage)) { + // TODO: Handle these cases if they become meaningful + continue; + } + } + + if (Local) { + assert(!Prevailing); + CachedAttributes[VI] = Local; + } else if (Prevailing) { + assert(!Local); + CachedAttributes[VI] = Prevailing; + } + + 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; + bool Changed = false; + + auto PropagateAttributes = [&](std::vector &SCCNodes) { + // Assume we can propagate unless we discover otherwise + FunctionSummary::FFlags InferredFlags; + InferredFlags.NoRecurse = (SCCNodes.size() == 1); + InferredFlags.NoUnwind = true; + + for (auto &V : SCCNodes) { + FunctionSummary *CallerSummary = + calculateDefinitiveAttributes(V, CachedAttributes, IsPrevailing); + + // Function summaries can fail to contain information such as declarations + if (!CallerSummary) + return; + + if (CallerSummary->fflags().MayThrow) + InferredFlags.NoUnwind = false; + + for (const auto &Callee : CallerSummary->calls()) { + FunctionSummary *CalleeSummary = calculateDefinitiveAttributes( + Callee.first, CachedAttributes, IsPrevailing); + + if (!CalleeSummary) + return; + + if (!CalleeSummary->fflags().NoRecurse) + InferredFlags.NoRecurse = false; + + if (!CalleeSummary->fflags().NoUnwind || + CallerSummary->fflags().MayThrow) + InferredFlags.NoUnwind = false; + + if (!InferredFlags.NoUnwind && !InferredFlags.NoRecurse) + break; + } + } + + if (InferredFlags.NoUnwind || InferredFlags.NoRecurse) { + Changed = true; + for (auto &V : SCCNodes) { + if (InferredFlags.NoRecurse) { + LLVM_DEBUG(dbgs() << "ThinLTO FunctionAttrs: Propagated NoRecurse to " + << V.name() << "\n"); + ++NumThinLinkNoRecurse; + CachedAttributes[V]->setNoRecurse(); + } + + if (InferredFlags.NoUnwind) { + LLVM_DEBUG(dbgs() << "ThinLTO FunctionAttrs: Propagated NoUnwind to " + << V.name() << "\n"); + ++NumThinLinkNoUnwind; + CachedAttributes[V]->setNoUnwind(); + } + + for (auto &S : V.getSummaryList()) { + if (auto *FS = dyn_cast(S.get())) { + if (InferredFlags.NoRecurse) + FS->setNoRecurse(); + + if (InferredFlags.NoUnwind) + FS->setNoUnwind(); + } + } + } + } + }; + + // Call propagation functions on each SCC in the Index + for (scc_iterator I = scc_begin(&Index); !I.isAtEnd(); + ++I) { + std::vector Nodes(*I); + 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)) { + // TODO: propagate ReadNone and ReadOnly in thinlink. + if (FS->fflags().ReadNone && !F.doesNotAccessMemory()) + F.setDoesNotAccessMemory(); + + if (FS->fflags().ReadOnly && !F.onlyReadsMemory()) + F.setOnlyReadsMemory(); + + if (FS->fflags().NoRecurse && !F.doesNotRecurse()) + F.setDoesNotRecurse(); + + if (FS->fflags().NoUnwind && !F.doesNotThrow()) + F.setDoesNotThrow(); + } + } +} + namespace { /// For a given pointer Argument, this retains a list of Arguments of functions diff --git a/llvm/test/Assembler/thinlto-summary.ll b/llvm/test/Assembler/thinlto-summary.ll --- a/llvm/test/Assembler/thinlto-summary.ll +++ b/llvm/test/Assembler/thinlto-summary.ll @@ -38,7 +38,7 @@ ; Functions with various flag combinations (notEligibleToImport, Live, ; combinations of optional function flags). ^15 = gv: (guid: 14, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 1, live: 1, dsoLocal: 0), insts: 1, funcFlags: (noInline: 1)))) -^16 = gv: (guid: 15, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 1, funcFlags: (readNone: 1, noRecurse: 1, alwaysInline: 1)))) +^16 = gv: (guid: 15, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 1, funcFlags: (readNone: 1, noRecurse: 1, alwaysInline: 1, noUnwind : 1, mayThrow : 1, hasUnknownCall : 1)))) ; This one also tests backwards reference in calls. ^17 = gv: (guid: 16, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0), insts: 1, funcFlags: (readOnly: 1, returnDoesNotAlias: 1), calls: ((callee: ^15))))) @@ -82,9 +82,9 @@ ; CHECK: ^12 = gv: (guid: 11, summaries: (variable: (module: ^0, flags: (linkage: appending, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0), refs: (^4)))) ; CHECK: ^13 = gv: (guid: 12, summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 1, writeonly: 0, constant: 0)))) ; CHECK: ^14 = gv: (guid: 13, summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0)))) -; CHECK: ^15 = gv: (guid: 14, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 1, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0)))) -; CHECK: ^16 = gv: (guid: 15, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 1, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 1)))) -; CHECK: ^17 = gv: (guid: 16, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 1, noRecurse: 0, returnDoesNotAlias: 1, noInline: 0, alwaysInline: 0), calls: ((callee: ^15))))) +; CHECK: ^15 = gv: (guid: 14, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 1, live: 1, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 1, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 0)))) +; CHECK: ^16 = gv: (guid: 15, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 1, readOnly: 0, noRecurse: 1, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 1, noUnwind: 1, mayThrow: 1, hasUnknownCall: 1)))) +; CHECK: ^17 = gv: (guid: 16, summaries: (function: (module: ^1, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 1, noRecurse: 0, returnDoesNotAlias: 1, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 0), calls: ((callee: ^15))))) ; CHECK: ^18 = gv: (guid: 17, summaries: (alias: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), aliasee: ^14))) ; CHECK: ^19 = gv: (guid: 18, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 4, typeIdInfo: (typeTests: (^24, ^26))))) ; CHECK: ^20 = gv: (guid: 19, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 8, typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (^27, offset: 16)))))) diff --git a/llvm/test/Bitcode/thinlto-function-summary-refgraph.ll b/llvm/test/Bitcode/thinlto-function-summary-refgraph.ll --- a/llvm/test/Bitcode/thinlto-function-summary-refgraph.ll +++ b/llvm/test/Bitcode/thinlto-function-summary-refgraph.ll @@ -158,7 +158,7 @@ ; DIS-DAG: = gv: (name: "globalvar", summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 1, writeonly: 0, constant: 1)))) ; guid = 12887606300320728018 ; DIS-DAG: = gv: (name: "func2") ; guid = 14069196320850861797 ; DIS-DAG: = gv: (name: "llvm.ctpop.i8") ; guid = 15254915475081819833 -; DIS-DAG: = gv: (name: "main", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 9, calls: ((callee: ^{{.*}})), refs: (^{{.*}})))) ; guid = 15822663052811949562 +; DIS-DAG: = gv: (name: "main", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 9, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), calls: ((callee: ^{{.*}})), refs: (^{{.*}})))) ; guid = 15822663052811949562 ; DIS-DAG: = gv: (name: "bar", summaries: (variable: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), varFlags: (readonly: 1, writeonly: 1, constant: 0), refs: (^{{.*}})))) ; guid = 16434608426314478903 ; Don't try to match the exact GUID. Since it is private, the file path ; will get hashed, and that will be test dependent. diff --git a/llvm/test/Bitcode/thinlto-type-vcalls.ll b/llvm/test/Bitcode/thinlto-type-vcalls.ll --- a/llvm/test/Bitcode/thinlto-type-vcalls.ll +++ b/llvm/test/Bitcode/thinlto-type-vcalls.ll @@ -112,19 +112,19 @@ ; DIS: ^0 = module: (path: "{{.*}}", hash: (0, 0, 0, 0, 0)) ; DIS: ^1 = gv: (name: "llvm.type.test") ; guid = 608142985856744218 -; DIS: ^2 = gv: (name: "f1", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 8, typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) ; guid = 2072045998141807037 -; DIS: ^3 = gv: (name: "f3", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, typeIdInfo: (typeCheckedLoadVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) ; guid = 4197650231481825559 +; DIS: ^2 = gv: (name: "f1", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 8, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) ; guid = 2072045998141807037 +; DIS: ^3 = gv: (name: "f3", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeCheckedLoadVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) ; guid = 4197650231481825559 ; DIS: ^4 = gv: (name: "llvm.type.checked.load") ; guid = 5568222536364573403 ; DIS: ^5 = gv: (name: "llvm.assume") ; guid = 6385187066495850096 -; DIS: ^6 = gv: (name: "f2", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 24), vFuncId: (guid: 16434608426314478903, offset: 32)))))) ; guid = 8471399308421654326 -; DIS: ^7 = gv: (name: "f4", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, typeIdInfo: (typeTestAssumeConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42)), (vFuncId: (guid: 6699318081062747564, offset: 24), args: (43))))))) ; guid = 10064745020953272174 -; DIS: ^8 = gv: (name: "f5", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, typeIdInfo: (typeCheckedLoadConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42))))))) ; guid = 11686717102184386164 +; DIS: ^6 = gv: (name: "f2", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 24), vFuncId: (guid: 16434608426314478903, offset: 32)))))) ; guid = 8471399308421654326 +; DIS: ^7 = gv: (name: "f4", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42)), (vFuncId: (guid: 6699318081062747564, offset: 24), args: (43))))))) ; guid = 10064745020953272174 +; DIS: ^8 = gv: (name: "f5", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeCheckedLoadConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42))))))) ; guid = 11686717102184386164 ; DIS: ^9 = gv: (name: "f6", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 2, typeIdInfo: (typeTests: (7546896869197086323))))) ; guid = 11834966808443348068 ; COMBINED-DIS: ^0 = module: (path: "{{.*}}thinlto-type-vcalls.ll.tmp.o", hash: (0, 0, 0, 0, 0)) -; COMBINED-DIS: ^1 = gv: (guid: 2072045998141807037, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 8, typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) -; COMBINED-DIS: ^2 = gv: (guid: 4197650231481825559, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, typeIdInfo: (typeCheckedLoadVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) -; COMBINED-DIS: ^3 = gv: (guid: 8471399308421654326, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 24), vFuncId: (guid: 16434608426314478903, offset: 32)))))) -; COMBINED-DIS: ^4 = gv: (guid: 10064745020953272174, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, typeIdInfo: (typeTestAssumeConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42)), (vFuncId: (guid: 6699318081062747564, offset: 24), args: (43))))))) -; COMBINED-DIS: ^5 = gv: (guid: 11686717102184386164, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, typeIdInfo: (typeCheckedLoadConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42))))))) +; COMBINED-DIS: ^1 = gv: (guid: 2072045998141807037, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 8, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) +; COMBINED-DIS: ^2 = gv: (guid: 4197650231481825559, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeCheckedLoadVCalls: (vFuncId: (guid: 6699318081062747564, offset: 16)))))) +; COMBINED-DIS: ^3 = gv: (guid: 8471399308421654326, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeVCalls: (vFuncId: (guid: 6699318081062747564, offset: 24), vFuncId: (guid: 16434608426314478903, offset: 32)))))) +; COMBINED-DIS: ^4 = gv: (guid: 10064745020953272174, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 15, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeTestAssumeConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42)), (vFuncId: (guid: 6699318081062747564, offset: 24), args: (43))))))) +; COMBINED-DIS: ^5 = gv: (guid: 11686717102184386164, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 0, hasUnknownCall: 1), typeIdInfo: (typeCheckedLoadConstVCalls: ((vFuncId: (guid: 6699318081062747564, offset: 16), args: (42))))))) ; COMBINED-DIS: ^6 = gv: (guid: 11834966808443348068, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 2, typeIdInfo: (typeTests: (7546896869197086323))))) diff --git a/llvm/test/ThinLTO/X86/deadstrip.ll b/llvm/test/ThinLTO/X86/deadstrip.ll --- a/llvm/test/ThinLTO/X86/deadstrip.ll +++ b/llvm/test/ThinLTO/X86/deadstrip.ll @@ -29,6 +29,7 @@ ; RUN: -r %t2.bc,_another_dead_func,pl \ ; RUN: -r %t2.bc,_linkonceodrfuncwithalias,pl \ ; RUN: -thinlto-threads=1 \ +; RUN: -disable-thinlto-funcattrs=0 \ ; RUN: -debug-only=function-import 2>&1 | FileCheck %s --check-prefix=DEBUG --check-prefix=STATS ; RUN: llvm-dis < %t.out.1.3.import.bc | FileCheck %s --check-prefix=LTO2 ; RUN: llvm-dis < %t.out.2.3.import.bc | FileCheck %s --check-prefix=LTO2-CHECK2 @@ -66,7 +67,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:#[0-9]+]] { ; LTO2: define internal void @bar_internal() ; LTO2-NOT: @dead_func() ; LTO2-NOT: available_externally {{.*}} @baz() @@ -78,7 +79,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:#[0-9]+]] { ; We should have eventually removed @baz since it was internalized and unused ; CHECK2-NM-NOT: _baz @@ -98,6 +99,8 @@ ; DEBUG-DAG: Initialize import for 15611644523426561710 (boo) ; DEBUG-DAG: Ignores Dead GUID: 2384416018110111308 (another_dead_func) +; LTO2-DAG: attributes [[ATTR]] = { norecurse nounwind } + ; 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, diff --git a/llvm/test/ThinLTO/X86/dot-dumper.ll b/llvm/test/ThinLTO/X86/dot-dumper.ll --- a/llvm/test/ThinLTO/X86/dot-dumper.ll +++ b/llvm/test/ThinLTO/X86/dot-dumper.ll @@ -21,7 +21,7 @@ ; PERMODULE-NEXT: label = ""; ; PERMODULE-NEXT: node [style=filled,fillcolor=lightblue]; ; PERMODULE-NEXT: M0_[[MAIN_ALIAS:[0-9]+]] [style="dotted,filled",shape="box",label="main_alias",fillcolor="red"]; // alias, dead -; PERMODULE-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 4, ffl: 000000)}",fillcolor="red"]; // function, dead +; PERMODULE-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 4, ffl: 000000000)}",fillcolor="red"]; // function, dead ; PERMODULE-NEXT: // Edges: ; PERMODULE-NEXT: M0_[[MAIN_ALIAS]] -> M0_[[MAIN]] [style=dotted]; // alias ; PERMODULE-NEXT: } @@ -40,7 +40,7 @@ ; COMBINED-NEXT: label = "dot-dumper{{.*}}1.bc"; ; COMBINED-NEXT: node [style=filled,fillcolor=lightblue]; ; COMBINED-NEXT: M0_[[MAIN_ALIAS:[0-9]+]] [style="dotted,filled",shape="box",label="main_alias",fillcolor="red"]; // alias, dead -; COMBINED-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 4, ffl: 000000)}"]; // function, preserved +; COMBINED-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 4, ffl: 000000000)}"]; // function, preserved ; COMBINED-NEXT: // Edges: ; COMBINED-NEXT: M0_[[MAIN_ALIAS]] -> M0_[[MAIN]] [style=dotted]; // alias ; COMBINED-NEXT: } @@ -50,10 +50,10 @@ ; COMBINED-NEXT: color = lightgrey; ; COMBINED-NEXT: label = "dot-dumper{{.*}}2.bc"; ; COMBINED-NEXT: node [style=filled,fillcolor=lightblue]; -; COMBINED-NEXT: M1_[[FOO:[0-9]+]] [shape="record",label="foo|extern (inst: 4, ffl: 000010)}"]; // function +; COMBINED-NEXT: M1_[[FOO:[0-9]+]] [shape="record",label="foo|extern (inst: 4, ffl: 000010000)}"]; // function ; COMBINED-NEXT: M1_[[A:[0-9]+]] [shape="Mrecord",label="A|extern}"]; // variable, immutable ; COMBINED-NEXT: M1_[[B:[0-9]+]] [shape="Mrecord",label="B|extern}"]; // variable, immutable, constant -; COMBINED-NEXT: M1_{{[0-9]+}} [shape="record",label="bar|extern (inst: 1, ffl: 000000)}",fillcolor="red"]; // function, dead +; COMBINED-NEXT: M1_{{[0-9]+}} [shape="record",label="bar|extern (inst: 1, ffl: 000000000)}",fillcolor="red"]; // function, dead ; COMBINED-NEXT: // Edges: ; COMBINED-NEXT: M1_[[FOO]] -> M1_[[B]] [style=dashed,color=forestgreen]; // const-ref ; COMBINED-NEXT: M1_[[FOO]] -> M1_[[A]] [style=dashed,color=forestgreen]; // const-ref diff --git a/llvm/test/ThinLTO/X86/dot-dumper2.ll b/llvm/test/ThinLTO/X86/dot-dumper2.ll --- a/llvm/test/ThinLTO/X86/dot-dumper2.ll +++ b/llvm/test/ThinLTO/X86/dot-dumper2.ll @@ -15,7 +15,7 @@ ; COMBINED-NEXT: color = lightgrey; ; COMBINED-NEXT: label = ; COMBINED-NEXT: node [style=filled,fillcolor=lightblue]; -; COMBINED-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 2, ffl: 000000)}"]; // function +; COMBINED-NEXT: M0_[[MAIN:[0-9]+]] [shape="record",label="main|extern (inst: 2, ffl: 000000000)}"]; // function ; COMBINED-NEXT: // Edges: ; COMBINED-NEXT: } ; COMBINED-NEXT: // Module: diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop-exported-internal.ll b/llvm/test/ThinLTO/X86/funcattrs-prop-exported-internal.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop-exported-internal.ll @@ -0,0 +1,58 @@ +; Function import can promote an internal function to external but not mark it as prevailing. +; Given that the internal function's attributes would have already propagated to its callers +; that are part of the import chain there's no need to actually propagate off this copy as +; propagating the caller performs the same thing. +; RUN: split-file %s %t +; RUN: opt -thinlto-bc %t/main.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: opt -thinlto-bc %t/callees.ll -thin-link-bitcode-file=%t2.thinlink.bc -o %t2.bc +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 \ +; RUN: %t1.bc %t2.bc -o %t.o \ +; RUN: -r %t1.bc,caller,l -r %t1.bc,caller_noattr,l -r %t1.bc,importer,px -r %t1.bc,importer_noattr,px \ +; RUN: -r %t2.bc,caller,px -r %t2.bc,caller_noattr,px \ +; RUN: -save-temps +; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s --match-full-lines + +;--- main.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @caller() +declare void @caller_noattr() + +; CHECK: define void @importer() [[ATTR_PROP:#[0-9]+]] { +define void @importer() { + call void @caller() + ret void +} + +; If somehow the caller doesn't get the attributes, we +; shouldn't propagate from the internal callee. +; CHECK: define void @importer_noattr() { +define void @importer_noattr() { + call void @caller_noattr() + ret void +} + +; CHECK: define available_externally hidden void @callee{{.*}} + +; CHECK-DAG: attributes [[ATTR_PROP]] = { norecurse nounwind } + +;--- callees.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +attributes #0 = { nounwind norecurse } + +define void @caller() #0 { + call void @callee() + ret void +} + +define void @caller_noattr() { + call void @callee() + ret void +} + +define internal void @callee() #0 { + ret void +} diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop-indirect.ll b/llvm/test/ThinLTO/X86/funcattrs-prop-indirect.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop-indirect.ll @@ -0,0 +1,14 @@ +; Indirect calls returns conservative results from function propagation +; RUN: opt -thinlto-bc %s -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t1.bc -o %t.o -r %t1.bc,indirect,px -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" + +; CHECK-NOT: ; Function Attrs: norecurse nounwind +; CHECK: define i32 @indirect(i32 ()* nocapture %0) { +define i32 @indirect(i32 ()* nocapture) { + %2 = tail call i32 %0() + ret i32 %2 +} diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop-maythrow.ll b/llvm/test/ThinLTO/X86/funcattrs-prop-maythrow.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop-maythrow.ll @@ -0,0 +1,91 @@ +; For instructions explicitly defined as mayThrow, make sure they prevent nounwind propagation +; RUN: split-file %s %t +; RUN: opt -thinlto-bc %t/main.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: opt -thinlto-bc %t/callees.ll -thin-link-bitcode-file=%t2.thinlink.bc -o %t2.bc +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc -o %t.o -r %t1.bc,caller,px -r %t1.bc,caller1,px -r %t1.bc,caller2,px \ +; RUN: -r %t1.bc,cleanupret,l -r %t1.bc,catchret,l -r %t1.bc,resume,l \ +; RUN: -r %t2.bc,cleanupret,px -r %t2.bc,catchret,px -r %t2.bc,resume,px -r %t2.bc,nonThrowing,px -r %t2.bc,__gxx_personality_v0,px -save-temps +; RUN: llvm-dis -o - %t2.bc | FileCheck %s --check-prefix=SUMMARY +; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s + +;--- main.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @cleanupret() +declare void @catchret() +declare void @resume() + + +; CHECK: define void @caller() [[ATTR:#[0-9]+]] +define void @caller() { + call void @cleanupret() + ret void +} + +; CHECK: define void @caller1() [[ATTR:#[0-9]+]] +define void @caller1() { + call void @catchret() + ret void +} + +; CHECK: define void @caller2() [[ATTR:#[0-9]+]] +define void @caller2() { + call void @resume() + ret void +} + +; CHECK-DAG: attributes [[ATTR]] = { norecurse } + +; SUMMARY-DAG: = gv: (name: "cleanupret", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 4, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 1, hasUnknownCall: 0), calls: ((callee: ^{{.*}})), refs: (^{{.*}})))) +; SUMMARY-DAG: = gv: (name: "resume", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 4, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 1, hasUnknownCall: 0), calls: ((callee: ^{{.*}})), refs: (^{{.*}})))) +; SUMMARY-DAG: = gv: (name: "catchret", summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 0, canAutoHide: 0), insts: 5, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 0, noUnwind: 0, mayThrow: 1, hasUnknownCall: 0), calls: ((callee: ^{{.*}})), refs: (^{{.*}})))) + +;--- callees.ll +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" +attributes #0 = { nounwind } + +define void @nonThrowing() #0 { + ret void +} + +declare i32 @__gxx_personality_v0(...) + +define void @cleanupret() personality i32 (...)* @__gxx_personality_v0 { +entry: + invoke void @nonThrowing() + to label %exit unwind label %pad +pad: + %cp = cleanuppad within none [i7 4] + cleanupret from %cp unwind to caller +exit: + ret void +} + +define void @catchret() personality i32 (...)* @__gxx_personality_v0 { +entry: + invoke void @nonThrowing() + to label %exit unwind label %pad +pad: + %cs1 = catchswitch within none [label %catch] unwind to caller +catch: + %cp = catchpad within %cs1 [i7 4] + catchret from %cp to label %exit +exit: + ret void +} + +define void @resume() uwtable optsize ssp personality i32 (...)* @__gxx_personality_v0 { +entry: + invoke void @nonThrowing() + to label %try.cont unwind label %lpad + +try.cont: ; preds = %entry, %invoke.cont4 + ret void + +lpad: ; preds = %entry + %exn = landingpad {i8*, i32} + cleanup + resume { i8*, i32 } %exn +} \ No newline at end of file diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop-undefined.ll b/llvm/test/ThinLTO/X86/funcattrs-prop-undefined.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop-undefined.ll @@ -0,0 +1,31 @@ +; Callee1 isn't defined, propagation goes conservative +; RUN: split-file %s %t +; RUN: opt -thinlto-bc %t/main.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: opt -thinlto-bc %t/callees.ll -thin-link-bitcode-file=%t2.thinlink.bc -o %t2.bc +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc -o %t.o -r %t1.bc,caller,px -r %t1.bc,callee,l -r %t1.bc,callee1,l -r %t2.bc,callee,px -save-temps +; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s + +;--- main.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @callee() +declare void @callee1() + +; CHECK-NOT: Function Attrs: +; CHECK: define void @caller() +define void @caller() { + call void @callee() + call void @callee1() + ret void +} + +;--- callees.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +attributes #0 = { nounwind norecurse } + +define void @callee() #0 { + ret void +} diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop-weak.ll b/llvm/test/ThinLTO/X86/funcattrs-prop-weak.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop-weak.ll @@ -0,0 +1,60 @@ +; RUN: split-file %s %t +; RUN: opt -thinlto-bc %t/a.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t1.bc +; RUN: opt -thinlto-bc %t/b.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t2.bc +; RUN: opt -thinlto-bc %t/c.ll -thin-link-bitcode-file=%t1.thinlink.bc -o %t3.bc + +; If the prevailing weak symbol is defined in a native file, the IR copies should be dead and propagation should not occur +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc %t3.bc -o %t.o \ +; RUN: -r %t1.bc,caller,px -r %t1.bc,callee,lx \ +; RUN: -r %t2.bc,callee,x \ +; RUN: -r %t3.bc,callee,x \ +; RUN: -save-temps + +; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s + +; If the prevailing weak symbol is in an IR file, it should be the one used in the final binary and thus propagation +; should be based off of that copy +; RUN: llvm-lto2 run -O3 -disable-thinlto-funcattrs=0 %t1.bc %t2.bc %t3.bc -o %t.2.o \ +; RUN: -r %t1.bc,caller,px -r %t1.bc,callee,lx \ +; RUN: -r %t2.bc,callee,px \ +; RUN: -r %t3.bc,callee,x \ +; RUN: -save-temps + +; RUN: llvm-dis -o - %t.2.o.1.3.import.bc | FileCheck %s --check-prefix=PREVAILING +; RUN: llvm-dis -o - %t.2.o.2.3.import.bc | FileCheck %s --check-prefix=PREVAILING-B +; RUN: llvm-dis -o - %t.2.o.3.3.import.bc | FileCheck %s --check-prefix=PREVAILING-C + +;--- a.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare i32 @callee() + +; CHECK-NOT: Function Attrs: +; CHECK: define i32 @caller() + +; PREVAILING: Function Attrs: norecurse nounwind +; PREVAILING-NEXT: define i32 @caller() +define i32 @caller() { + %res = call i32 @callee() + ret i32 %res +} + +;--- b.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; PREVAILING-B: define weak i32 @callee() +define weak i32 @callee() { + ret i32 5 +} + +;--- c.ll +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; PREVAILING-C: declare i32 @callee() +define weak i32 @callee() { + ret i32 6 +} + diff --git a/llvm/test/ThinLTO/X86/funcattrs-prop.ll b/llvm/test/ThinLTO/X86/funcattrs-prop.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/funcattrs-prop.ll @@ -0,0 +1,149 @@ +; RUN: split-file %s %t +; RUN: opt -module-summary %t/a.ll -o %t/a.bc +; RUN: opt -module-summary %t/b.ll -o %t/b.bc +; RUN: opt -module-summary %t/c.ll -o %t/c.bc + +;; Function attribute propagation +;; 1. If external, linkonce_odr or weak_odr. We capture the attributes of the prevailing symbol for propagation. +;; 2. If linkonce or weak, individual callers may optimize using different copies. However, the prevailing symbol will be what's in the final binary and thus we can propagate based off of that +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t/a.bc %t/b.bc %t/c.bc -o %t1 -save-temps \ +; RUN: -r=%t/a.bc,call_extern,plx -r=%t/a.bc,call_linkonceodr,plx -r=%t/a.bc,call_weakodr,plx -r=%t/a.bc,call_linkonce,plx -r=%t/a.bc,call_weak,plx -r=%t/a.bc,call_linkonce_may_unwind,plx -r=%t/a.bc,call_weak_may_unwind,plx \ +; RUN: -r=%t/a.bc,extern, -r=%t/a.bc,linkonceodr, -r=%t/a.bc,weakodr, -r=%t/a.bc,linkonce, -r=%t/a.bc,weak, -r=%t/a.bc,linkonce_may_unwind, -r=%t/a.bc,weak_may_unwind, \ +; RUN: -r=%t/b.bc,extern,p -r=%t/b.bc,linkonceodr,p -r=%t/b.bc,weakodr,p -r=%t/b.bc,linkonce,p -r=%t/b.bc,weak,p -r=%t/b.bc,linkonce_may_unwind,p -r=%t/b.bc,weak_may_unwind, \ +; RUN: -r=%t/c.bc,extern, -r=%t/c.bc,linkonceodr, -r=%t/c.bc,weakodr, -r=%t/c.bc,linkonce, -r=%t/c.bc,weak, -r=%t/c.bc,linkonce_may_unwind, -r=%t/c.bc,weak_may_unwind,p -r=%t/c.bc,may_throw, + +; RUN: llvm-dis %t1.1.3.import.bc -o - | FileCheck %s + +;--- a.ll +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" + +declare void @extern() +declare void @linkonceodr() +declare void @weakodr() + +declare void @linkonce() +declare void @weak() + +; b.ll contains non-recursing copies +; c.ll contains recursing copies +declare void @linkonce_may_unwind() +declare void @weak_may_unwind() + +; CHECK: define dso_local void @call_extern() [[ATTR_PROP:#[0-9]+]] +define void @call_extern() { + call void @extern() + ret void +} + +; CHECK: define dso_local void @call_linkonceodr() [[ATTR_PROP:#[0-9]+]] +define void @call_linkonceodr() { + call void @linkonceodr() + ret void +} + +; CHECK: define dso_local void @call_weakodr() [[ATTR_PROP:#[0-9]+]] +define void @call_weakodr() { + call void @weakodr() + ret void +} + +; CHECK: define dso_local void @call_linkonce() [[ATTR_PROP:#[0-9]+]] +define void @call_linkonce() { + call void @linkonce() + ret void +} + +; CHECK: define dso_local void @call_weak() [[ATTR_PROP:#[0-9]+]] +define void @call_weak() { + call void @weak() + ret void +} + +; CHECK: define dso_local void @call_linkonce_may_unwind() [[ATTR_PROP:#[0-9]+]] +define void @call_linkonce_may_unwind() { + call void @linkonce_may_unwind() + ret void +} + +; CHECK: define dso_local void @call_weak_may_unwind() [[ATTR_NORECURSE:#[0-9]+]] +define void @call_weak_may_unwind() { + call void @weak_may_unwind() + ret void +} + +; CHECK-DAG: attributes [[ATTR_PROP]] = { norecurse nounwind } +; CHECK-DAG: attributes [[ATTR_NORECURSE]] = { norecurse } + +;--- b.ll +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" + +attributes #0 = { nounwind norecurse } + +define void @extern() #0 { + ret void +} + +define linkonce_odr void @linkonceodr() #0 { + ret void +} + +define weak_odr void @weakodr() #0 { + ret void +} + +define linkonce void @linkonce() #0 { + ret void +} + +define weak void @weak() #0 { + ret void +} + +define linkonce void @linkonce_may_unwind() #0 { + ret void +} + +define weak void @weak_may_unwind() #0 { + ret void +} + +;--- c.ll +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" + +attributes #0 = { nounwind norecurse } +attributes #1 = { norecurse } + +define void @extern() #0 { + ret void +} + +define linkonce_odr void @linkonceodr() #0 { + ret void +} + +define weak_odr void @weakodr() #0 { + ret void +} + +define linkonce void @linkonce() #0 { + ret void +} + +define weak void @weak() #0 { + ret void +} + +declare void @may_throw() + +define linkonce void @linkonce_may_unwind() #1 { + call void @may_throw() + ret void +} + +define weak void @weak_may_unwind() #1 { + call void @may_throw() + ret void +} diff --git a/llvm/test/ThinLTO/X86/funcimport_alwaysinline.ll b/llvm/test/ThinLTO/X86/funcimport_alwaysinline.ll --- a/llvm/test/ThinLTO/X86/funcimport_alwaysinline.ll +++ b/llvm/test/ThinLTO/X86/funcimport_alwaysinline.ll @@ -1,7 +1,7 @@ ; RUN: opt -module-summary %s -o %t1.bc ; RUN: opt -module-summary %p/Inputs/funcimport_alwaysinline.ll -o %t2.bc -; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t.o -save-temps \ +; RUN: llvm-lto2 run -disable-thinlto-funcattrs=0 %t1.bc %t2.bc -o %t.o -save-temps \ ; RUN: -r=%t1.bc,foo,plx \ ; RUN: -r=%t2.bc,main,plx \ ; RUN: -r=%t2.bc,foo,l \ @@ -23,4 +23,4 @@ } attributes #0 = { alwaysinline nounwind uwtable } -; CHECK2: ^2 = gv: (guid: {{.*}}, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 1, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 1)))) +; CHECK2: ^2 = gv: (guid: {{.*}}, summaries: (function: (module: ^0, flags: (linkage: external, visibility: default, notEligibleToImport: 0, live: 1, dsoLocal: 1, canAutoHide: 0), insts: 1, funcFlags: (readNone: 0, readOnly: 0, noRecurse: 0, returnDoesNotAlias: 0, noInline: 0, alwaysInline: 1, noUnwind: 1, mayThrow: 0, hasUnknownCall: 0)))) diff --git a/llvm/test/ThinLTO/X86/function_entry_count.ll b/llvm/test/ThinLTO/X86/function_entry_count.ll --- a/llvm/test/ThinLTO/X86/function_entry_count.ll +++ b/llvm/test/ThinLTO/X86/function_entry_count.ll @@ -2,7 +2,7 @@ ; RUN: opt -thinlto-bc %p/Inputs/function_entry_count.ll -write-relbf-to-summary -thin-link-bitcode-file=%t2.thinlink.bc -o %t2.bc ; First perform the thin link on the normal bitcode file. -; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t.o -save-temps -thinlto-synthesize-entry-counts \ +; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t.o -save-temps -disable-thinlto-funcattrs=0 -thinlto-synthesize-entry-counts \ ; RUN: -r=%t1.bc,g, \ ; RUN: -r=%t1.bc,f,px \ ; RUN: -r=%t1.bc,h,px \ @@ -10,15 +10,16 @@ ; RUN: -r=%t2.bc,g,px ; RUN: llvm-dis -o - %t.o.1.3.import.bc | FileCheck %s -; RUN: llvm-lto -thinlto-action=run -thinlto-synthesize-entry-counts -exported-symbol=f \ +; RUN: llvm-lto -thinlto-action=run -disable-thinlto-funcattrs=0 -thinlto-synthesize-entry-counts -exported-symbol=f \ ; 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:#[0-9]+]] !prof ![[PROF2:[0-9]+]] +; CHECK: define void @f(i32{{.*}}) [[ATTR:#[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 nounwind } 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" diff --git a/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll b/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll --- a/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll +++ b/llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll @@ -3,15 +3,17 @@ ; verification error. ; RUN: opt -module-summary %s -o %t1.bc ; RUN: opt -module-summary %p/Inputs/linkonce_resolution_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-lto -thinlto-action=run -disable-thinlto-funcattrs=0 %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. -; 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:#[0-9]+]] comdat($c1) { +; IMPORT2: define available_externally i32 @f(i8* %0) unnamed_addr [[ATTR:#[0-9]+]] { + +; CHECK-DAG: attributes [[ATTR]] = { norecurse nounwind } ; RUN: llvm-nm -o - < %t1.bc.thinlto.o | FileCheck %s --check-prefix=NM1 ; NM1: W f