From e53f3c1bec6b2949416edfe94285e81571b090fc Mon Sep 17 00:00:00 2001 From: Teresa Johnson Date: Thu, 6 Feb 2020 13:28:41 -0800 Subject: [PATCH] [Inliner] Inlining should honor nobuiltin attributes Summary: Final patch in series to fix inlining between functions with different nobuiltin attributes/options, which was specifically an issue in LTO. See discussion on D61634 for background. The prior patch in this series (D67923) enabled per-Function TLI construction that identified the nobuiltin attributes. Here I have allowed inlining to proceed if the callee's nobuiltins are a subset of the caller's nobuiltins, but not in the reverse case, which should be conservatively correct. This is controlled by a new option, -inline-caller-superset-nobuiltin, which is enabled by default. Reviewers: hfinkel, gchatelet, chandlerc, davidxl Subscribers: arsenm, jvesely, nhaehnle, mehdi_amini, eraman, hiraditya, haicheng, dexonsmith, kerbowa, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D74162 --- include/llvm/Analysis/InlineCost.h | 3 + include/llvm/Analysis/TargetLibraryInfo.h | 15 +++ include/llvm/Transforms/IPO/Inliner.h | 1 + lib/Analysis/InlineCost.cpp | 26 ++++- lib/Target/AMDGPU/AMDGPUInline.cpp | 4 +- lib/Transforms/IPO/InlineSimple.cpp | 2 +- lib/Transforms/IPO/Inliner.cpp | 9 +- lib/Transforms/IPO/PartialInlining.cpp | 24 ++++- lib/Transforms/IPO/SampleProfile.cpp | 42 ++++++--- .../Inline/inline-no-builtin-compatible.ll | 94 +++++++++++++++++++ 10 files changed, 190 insertions(+), 30 deletions(-) create mode 100644 test/Transforms/Inline/inline-no-builtin-compatible.ll diff --git a/include/llvm/Analysis/InlineCost.h b/include/llvm/Analysis/InlineCost.h index fcb63054a24..0900162fa9d 100644 --- a/include/llvm/Analysis/InlineCost.h +++ b/include/llvm/Analysis/InlineCost.h @@ -27,6 +27,7 @@ class DataLayout; class Function; class ProfileSummaryInfo; class TargetTransformInfo; +class TargetLibraryInfo; namespace InlineConstants { // Various thresholds used by inline cost analysis. @@ -219,6 +220,7 @@ InlineCost getInlineCost( CallBase &Call, const InlineParams &Params, TargetTransformInfo &CalleeTTI, std::function &GetAssumptionCache, Optional> GetBFI, + function_ref GetTLI, ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE = nullptr); /// Get an InlineCost with the callee explicitly specified. @@ -231,6 +233,7 @@ getInlineCost(CallBase &Call, Function *Callee, const InlineParams &Params, TargetTransformInfo &CalleeTTI, std::function &GetAssumptionCache, Optional> GetBFI, + function_ref GetTLI, ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE); /// Minimal filter to detect invalid constructs for inlining. diff --git a/include/llvm/Analysis/TargetLibraryInfo.h b/include/llvm/Analysis/TargetLibraryInfo.h index fdcb44893a8..0860b7244df 100644 --- a/include/llvm/Analysis/TargetLibraryInfo.h +++ b/include/llvm/Analysis/TargetLibraryInfo.h @@ -260,6 +260,21 @@ public: return *this; } + /// Determine whether a callee with the given TLI can be inlined into + /// caller with this TLI, based on 'nobuiltin' attributes. When requested, + /// allow inlining into a caller with a superset of the callee's nobuiltin + /// attributes, which is conservatively correct. + bool areInlineCompatible(const TargetLibraryInfo &CalleeTLI, + bool AllowCallerSuperset) const { + if (!AllowCallerSuperset) + return OverrideAsUnavailable == CalleeTLI.OverrideAsUnavailable; + BitVector B = OverrideAsUnavailable; + B |= CalleeTLI.OverrideAsUnavailable; + // We can inline if the union of the caller and callee's nobuiltin + // attributes is no stricter than the caller's nobuiltin attributes. + return B == OverrideAsUnavailable; + } + /// Searches for a particular function name. /// /// If it is one of the known library functions, return true and set F to the diff --git a/include/llvm/Transforms/IPO/Inliner.h b/include/llvm/Transforms/IPO/Inliner.h index 8202b94d5a9..38137e9d6ce 100644 --- a/include/llvm/Transforms/IPO/Inliner.h +++ b/include/llvm/Transforms/IPO/Inliner.h @@ -74,6 +74,7 @@ private: protected: AssumptionCacheTracker *ACT; ProfileSummaryInfo *PSI; + std::function GetTLI; ImportedFunctionsInliningStatistics ImportedFunctionsStats; }; diff --git a/lib/Analysis/InlineCost.cpp b/lib/Analysis/InlineCost.cpp index 41824524395..b4aa2fb3c6e 100644 --- a/lib/Analysis/InlineCost.cpp +++ b/lib/Analysis/InlineCost.cpp @@ -24,6 +24,7 @@ #include "llvm/Analysis/InstructionSimplify.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/Analysis/ProfileSummaryInfo.h" +#include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/Config/llvm-config.h" @@ -103,6 +104,12 @@ static cl::opt OptComputeFullInlineCost( cl::desc("Compute the full inline cost of a call site even when the cost " "exceeds the threshold.")); +static cl::opt InlineCallerSupersetNoBuiltin( + "inline-caller-superset-nobuiltin", cl::Hidden, cl::init(true), + cl::ZeroOrMore, + cl::desc("Allow inlining when caller has a superset of callee's nobuiltin " + "attributes.")); + namespace { class InlineCostCallAnalyzer; @@ -2146,10 +2153,17 @@ LLVM_DUMP_METHOD void InlineCostCallAnalyzer::dump() { /// Test that there are no attribute conflicts between Caller and Callee /// that prevent inlining. -static bool functionsHaveCompatibleAttributes(Function *Caller, - Function *Callee, - TargetTransformInfo &TTI) { +static bool functionsHaveCompatibleAttributes( + Function *Caller, Function *Callee, TargetTransformInfo &TTI, + function_ref &GetTLI) { + // Note that CalleeTLI must be a copy not a reference. The legacy pass manager + // caches the most recently created TLI in the TargetLibraryInfoWrapperPass + // object, and always returns the same object (which is overwritten on each + // GetTLI call). Therefore we copy the first result. + auto CalleeTLI = GetTLI(*Callee); return TTI.areInlineCompatible(Caller, Callee) && + GetTLI(*Caller).areInlineCompatible(CalleeTLI, + InlineCallerSupersetNoBuiltin) && AttributeFuncs::areInlineCompatible(*Caller, *Callee); } @@ -2190,9 +2204,10 @@ InlineCost llvm::getInlineCost( CallBase &Call, const InlineParams &Params, TargetTransformInfo &CalleeTTI, std::function &GetAssumptionCache, Optional> GetBFI, + function_ref GetTLI, ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE) { return getInlineCost(Call, Call.getCalledFunction(), Params, CalleeTTI, - GetAssumptionCache, GetBFI, PSI, ORE); + GetAssumptionCache, GetBFI, GetTLI, PSI, ORE); } InlineCost llvm::getInlineCost( @@ -2200,6 +2215,7 @@ InlineCost llvm::getInlineCost( TargetTransformInfo &CalleeTTI, std::function &GetAssumptionCache, Optional> GetBFI, + function_ref GetTLI, ProfileSummaryInfo *PSI, OptimizationRemarkEmitter *ORE) { // Cannot inline indirect calls. @@ -2232,7 +2248,7 @@ InlineCost llvm::getInlineCost( // Never inline functions with conflicting attributes (unless callee has // always-inline attribute). Function *Caller = Call.getCaller(); - if (!functionsHaveCompatibleAttributes(Caller, Callee, CalleeTTI)) + if (!functionsHaveCompatibleAttributes(Caller, Callee, CalleeTTI, GetTLI)) return llvm::InlineCost::getNever("conflicting attributes"); // Don't inline this call if the caller has the optnone attribute. diff --git a/lib/Target/AMDGPU/AMDGPUInline.cpp b/lib/Target/AMDGPU/AMDGPUInline.cpp index 91a78edb022..8a63740ac95 100644 --- a/lib/Target/AMDGPU/AMDGPUInline.cpp +++ b/lib/Target/AMDGPU/AMDGPUInline.cpp @@ -215,8 +215,8 @@ InlineCost AMDGPUInliner::getInlineCost(CallSite CS) { }; auto IC = llvm::getInlineCost(cast(*CS.getInstruction()), Callee, - LocalParams, TTI, GetAssumptionCache, None, PSI, - RemarksEnabled ? &ORE : nullptr); + LocalParams, TTI, GetAssumptionCache, None, + GetTLI, PSI, RemarksEnabled ? &ORE : nullptr); if (IC && !IC.isAlways() && !Callee->hasFnAttribute(Attribute::InlineHint)) { // Single BB does not increase total BB amount, thus subtract 1 diff --git a/lib/Transforms/IPO/InlineSimple.cpp b/lib/Transforms/IPO/InlineSimple.cpp index e818743544e..57af4533953 100644 --- a/lib/Transforms/IPO/InlineSimple.cpp +++ b/lib/Transforms/IPO/InlineSimple.cpp @@ -71,7 +71,7 @@ public: }; return llvm::getInlineCost( cast(*CS.getInstruction()), Params, TTI, GetAssumptionCache, - /*GetBFI=*/None, PSI, RemarksEnabled ? &ORE : nullptr); + /*GetBFI=*/None, GetTLI, PSI, RemarksEnabled ? &ORE : nullptr); } bool runOnSCC(CallGraphSCC &SCC) override; diff --git a/lib/Transforms/IPO/Inliner.cpp b/lib/Transforms/IPO/Inliner.cpp index 4f9f4bd1cd0..d35d8f562b3 100644 --- a/lib/Transforms/IPO/Inliner.cpp +++ b/lib/Transforms/IPO/Inliner.cpp @@ -528,7 +528,7 @@ static bool inlineCallsImpl(CallGraphSCC &SCC, CallGraph &CG, std::function GetAssumptionCache, ProfileSummaryInfo *PSI, - std::function GetTLI, + std::function GetTLI, bool InsertLifetime, function_ref GetInlineCost, function_ref AARGetter, @@ -761,7 +761,7 @@ bool LegacyInlinerBase::inlineCalls(CallGraphSCC &SCC) { CallGraph &CG = getAnalysis().getCallGraph(); ACT = &getAnalysis(); PSI = &getAnalysis().getPSI(); - auto GetTLI = [&](Function &F) -> TargetLibraryInfo & { + GetTLI = [&](Function &F) -> const TargetLibraryInfo & { return getAnalysis().getTLI(F); }; auto GetAssumptionCache = [&](Function &F) -> AssumptionCache & { @@ -1008,6 +1008,9 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC, auto GetBFI = [&](Function &F) -> BlockFrequencyInfo & { return FAM.getResult(F); }; + auto GetTLI = [&](Function &F) -> const TargetLibraryInfo & { + return FAM.getResult(F); + }; auto GetInlineCost = [&](CallSite CS) { Function &Callee = *CS.getCalledFunction(); @@ -1016,7 +1019,7 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC, Callee.getContext().getDiagHandlerPtr()->isMissedOptRemarkEnabled( DEBUG_TYPE); return getInlineCost(cast(*CS.getInstruction()), Params, - CalleeTTI, GetAssumptionCache, {GetBFI}, PSI, + CalleeTTI, GetAssumptionCache, {GetBFI}, GetTLI, PSI, RemarksEnabled ? &ORE : nullptr); }; diff --git a/lib/Transforms/IPO/PartialInlining.cpp b/lib/Transforms/IPO/PartialInlining.cpp index a122e4737b6..4f8c131b98a 100644 --- a/lib/Transforms/IPO/PartialInlining.cpp +++ b/lib/Transforms/IPO/PartialInlining.cpp @@ -203,9 +203,10 @@ struct PartialInlinerImpl { function_ref LookupAC, std::function *GTTI, Optional> GBFI, + std::function *GTLI, ProfileSummaryInfo *ProfSI) : GetAssumptionCache(GetAC), LookupAssumptionCache(LookupAC), - GetTTI(GTTI), GetBFI(GBFI), PSI(ProfSI) {} + GetTTI(GTTI), GetBFI(GBFI), GetTLI(GTLI), PSI(ProfSI) {} bool run(Module &M); // Main part of the transformation that calls helper functions to find @@ -274,6 +275,7 @@ private: function_ref LookupAssumptionCache; std::function *GetTTI; Optional> GetBFI; + std::function *GetTLI; ProfileSummaryInfo *PSI; // Return the frequency of the OutlininingBB relative to F's entry point. @@ -355,6 +357,7 @@ struct PartialInlinerLegacyPass : public ModulePass { AU.addRequired(); AU.addRequired(); AU.addRequired(); + AU.addRequired(); } bool runOnModule(Module &M) override { @@ -381,8 +384,13 @@ struct PartialInlinerLegacyPass : public ModulePass { return TTIWP->getTTI(F); }; + std::function GetTLI = + [this](Function &F) -> TargetLibraryInfo & { + return this->getAnalysis().getTLI(F); + }; + return PartialInlinerImpl(&GetAssumptionCache, LookupAssumptionCache, - &GetTTI, NoneType::None, PSI) + &GetTTI, NoneType::None, &GetTLI, PSI) .run(M); } }; @@ -778,8 +786,8 @@ bool PartialInlinerImpl::shouldPartialInline( DEBUG_TYPE); assert(Call && "invalid callsite for partial inline"); InlineCost IC = getInlineCost(cast(*Call), getInlineParams(), - CalleeTTI, *GetAssumptionCache, GetBFI, PSI, - RemarksEnabled ? &ORE : nullptr); + CalleeTTI, *GetAssumptionCache, GetBFI, *GetTLI, + PSI, RemarksEnabled ? &ORE : nullptr); if (IC.isAlways()) { ORE.emit([&]() { @@ -1493,6 +1501,7 @@ INITIALIZE_PASS_BEGIN(PartialInlinerLegacyPass, "partial-inliner", INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker) INITIALIZE_PASS_DEPENDENCY(ProfileSummaryInfoWrapperPass) INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass) +INITIALIZE_PASS_DEPENDENCY(TargetLibraryInfoWrapperPass) INITIALIZE_PASS_END(PartialInlinerLegacyPass, "partial-inliner", "Partial Inliner", false, false) @@ -1523,10 +1532,15 @@ PreservedAnalyses PartialInlinerPass::run(Module &M, return FAM.getResult(F); }; + std::function GetTLI = + [&FAM](Function &F) -> TargetLibraryInfo & { + return FAM.getResult(F); + }; + ProfileSummaryInfo *PSI = &AM.getResult(M); if (PartialInlinerImpl(&GetAssumptionCache, LookupAssumptionCache, &GetTTI, - {GetBFI}, PSI) + {GetBFI}, &GetTLI, PSI) .run(M)) return PreservedAnalyses::none(); return PreservedAnalyses::all(); diff --git a/lib/Transforms/IPO/SampleProfile.cpp b/lib/Transforms/IPO/SampleProfile.cpp index 719b18cbb28..b5b834030a3 100644 --- a/lib/Transforms/IPO/SampleProfile.cpp +++ b/lib/Transforms/IPO/SampleProfile.cpp @@ -42,6 +42,7 @@ #include "llvm/Analysis/OptimizationRemarkEmitter.h" #include "llvm/Analysis/PostDominators.h" #include "llvm/Analysis/ProfileSummaryInfo.h" +#include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFG.h" @@ -307,10 +308,12 @@ public: SampleProfileLoader( StringRef Name, StringRef RemapName, bool IsThinLTOPreLink, std::function GetAssumptionCache, - std::function GetTargetTransformInfo) + std::function GetTargetTransformInfo, + std::function GetTLI) : GetAC(std::move(GetAssumptionCache)), - GetTTI(std::move(GetTargetTransformInfo)), CoverageTracker(*this), - Filename(std::string(Name)), RemappingFilename(std::string(RemapName)), + GetTTI(std::move(GetTargetTransformInfo)), GetTLI(std::move(GetTLI)), + CoverageTracker(*this), Filename(std::string(Name)), + RemappingFilename(std::string(RemapName)), IsThinLTOPreLink(IsThinLTOPreLink) {} bool doInitialization(Module &M); @@ -397,6 +400,7 @@ protected: std::function GetAC; std::function GetTTI; + std::function GetTLI; /// Predecessors for each basic block in the CFG. BlockEdgeMap Predecessors; @@ -474,14 +478,17 @@ public: SampleProfileLoaderLegacyPass(StringRef Name = SampleProfileFile, bool IsThinLTOPreLink = false) - : ModulePass(ID), - SampleLoader(Name, SampleProfileRemappingFile, IsThinLTOPreLink, - [&](Function &F) -> AssumptionCache & { - return ACT->getAssumptionCache(F); - }, - [&](Function &F) -> TargetTransformInfo & { - return TTIWP->getTTI(F); - }) { + : ModulePass(ID), SampleLoader( + Name, SampleProfileRemappingFile, IsThinLTOPreLink, + [&](Function &F) -> AssumptionCache & { + return ACT->getAssumptionCache(F); + }, + [&](Function &F) -> TargetTransformInfo & { + return TTIWP->getTTI(F); + }, + [&](Function &F) -> TargetLibraryInfo & { + return TLIWP->getTLI(F); + }) { initializeSampleProfileLoaderLegacyPassPass( *PassRegistry::getPassRegistry()); } @@ -498,6 +505,7 @@ public: void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired(); AU.addRequired(); + AU.addRequired(); AU.addRequired(); } @@ -505,6 +513,7 @@ private: SampleProfileLoader SampleLoader; AssumptionCacheTracker *ACT = nullptr; TargetTransformInfoWrapperPass *TTIWP = nullptr; + TargetLibraryInfoWrapperPass *TLIWP = nullptr; }; } // end anonymous namespace @@ -902,7 +911,7 @@ bool SampleProfileLoader::inlineCallInstruction(Instruction *I) { // see if it is legal to inline the callsite. InlineCost Cost = getInlineCost(cast(*I), Params, GetTTI(*CalledFunction), GetAC, - None, nullptr, nullptr); + None, GetTLI, nullptr, nullptr); if (Cost.isNever()) { ORE->emit(OptimizationRemarkAnalysis(CSINLINE_DEBUG, "InlineFail", DLoc, BB) << "incompatible inlining"); @@ -929,7 +938,7 @@ bool SampleProfileLoader::shouldInlineColdCallee(Instruction &CallInst) { InlineCost Cost = getInlineCost(cast(CallInst), getInlineParams(), - GetTTI(*Callee), GetAC, None, nullptr, nullptr); + GetTTI(*Callee), GetAC, None, GetTLI, nullptr, nullptr); return Cost.getCost() <= SampleColdCallSiteThreshold; } @@ -1770,6 +1779,7 @@ INITIALIZE_PASS_BEGIN(SampleProfileLoaderLegacyPass, "sample-profile", "Sample Profile loader", false, false) INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker) INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass) +INITIALIZE_PASS_DEPENDENCY(TargetLibraryInfoWrapperPass) INITIALIZE_PASS_DEPENDENCY(ProfileSummaryInfoWrapperPass) INITIALIZE_PASS_END(SampleProfileLoaderLegacyPass, "sample-profile", "Sample Profile loader", false, false) @@ -1890,6 +1900,7 @@ bool SampleProfileLoader::runOnModule(Module &M, ModuleAnalysisManager *AM, bool SampleProfileLoaderLegacyPass::runOnModule(Module &M) { ACT = &getAnalysis(); TTIWP = &getAnalysis(); + TLIWP = &getAnalysis(); ProfileSummaryInfo *PSI = &getAnalysis().getPSI(); return SampleLoader.runOnModule(M, nullptr, PSI, nullptr); @@ -1966,12 +1977,15 @@ PreservedAnalyses SampleProfileLoaderPass::run(Module &M, auto GetTTI = [&](Function &F) -> TargetTransformInfo & { return FAM.getResult(F); }; + auto GetTLI = [&](Function &F) -> const TargetLibraryInfo & { + return FAM.getResult(F); + }; SampleProfileLoader SampleLoader( ProfileFileName.empty() ? SampleProfileFile : ProfileFileName, ProfileRemappingFileName.empty() ? SampleProfileRemappingFile : ProfileRemappingFileName, - IsThinLTOPreLink, GetAssumptionCache, GetTTI); + IsThinLTOPreLink, GetAssumptionCache, GetTTI, GetTLI); if (!SampleLoader.doInitialization(M)) return PreservedAnalyses::all(); diff --git a/test/Transforms/Inline/inline-no-builtin-compatible.ll b/test/Transforms/Inline/inline-no-builtin-compatible.ll new file mode 100644 index 00000000000..2568cef1550 --- /dev/null +++ b/test/Transforms/Inline/inline-no-builtin-compatible.ll @@ -0,0 +1,94 @@ +; Test to ensure no inlining is allowed into a caller with fewer nobuiltin attributes. +; RUN: opt < %s -mtriple=x86_64-unknown-linux-gnu -S -inline | FileCheck %s +; RUN: opt < %s -mtriple=x86_64-unknown-linux-gnu -S -passes='cgscc(inline)' | FileCheck %s + +; Make sure we don't inline callees into a caller with a superset of the +; no builtin attributes when -inline-caller-superset-nobuiltin=false. +; RUN: opt < %s -inline-caller-superset-nobuiltin=false -mtriple=x86_64-unknown-linux-gnu -S -passes='cgscc(inline)' | FileCheck %s --check-prefix=NOSUPERSET + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define i32 @allbuiltins() { +entry: + %call = call i32 (...) @externalfunc() + ret i32 %call +; CHECK-LABEL: allbuiltins +; CHECK: call i32 (...) @externalfunc() +} +declare i32 @externalfunc(...) + +; We can inline a function that allows all builtins into one with a single +; nobuiltin. +define i32 @nobuiltinmemcpy() #0 { +entry: + %call = call i32 @allbuiltins() + ret i32 %call +; CHECK-LABEL: nobuiltinmemcpy +; CHECK-NOT: call i32 @allbuiltins() +; NOSUPERSET-LABEL: nobuiltinmemcpy +; NOSUPERSET: call i32 @allbuiltins() +} + +; We can inline a function that allows all builtins into one with all +; nobuiltins. +define i32 @nobuiltins() #1 { +entry: + %call = call i32 @allbuiltins() + ret i32 %call +; CHECK-LABEL: nobuiltins +; CHECK-NOT: call i32 @allbuiltins() +; NOSUPERSET-LABEL: nobuiltins +; NOSUPERSET: call i32 @allbuiltins() +} + +; We can inline a function with a single nobuiltin into one with all nobuiltins. +define i32 @nobuiltins2() #1 { +entry: + %call = call i32 @nobuiltinmemcpy() + ret i32 %call +; CHECK-LABEL: nobuiltins2 +; CHECK-NOT: call i32 @nobuiltinmemcpy() +; NOSUPERSET-LABEL: nobuiltins2 +; NOSUPERSET: call i32 @nobuiltinmemcpy() +} + +; We can't inline a function with any given nobuiltin into one that allows all +; builtins. +define i32 @allbuiltins2() { +entry: + %call = call i32 @nobuiltinmemcpy() + ret i32 %call +; CHECK-LABEL: allbuiltins2 +; CHECK: call i32 @nobuiltinmemcpy() +; NOSUPERSET-LABEL: allbuiltins2 +; NOSUPERSET: call i32 @nobuiltinmemcpy() +} + +; We can't inline a function with all nobuiltins into one that allows all +; builtins. +define i32 @allbuiltins3() { +entry: + %call = call i32 @nobuiltins() + ret i32 %call +; CHECK-LABEL: allbuiltins3 +; CHECK: call i32 @nobuiltins() +; NOSUPERSET-LABEL: allbuiltins3 +; NOSUPERSET: call i32 @nobuiltins() +} + +; We can't inline a function with a specific nobuiltin into one with a +; different specific nobuiltin. +define i32 @nobuiltinmemset() #2 { +entry: + %call = call i32 @nobuiltinmemcpy() + ret i32 %call +; CHECK-LABEL: nobuiltinmemset +; CHECK: call i32 @nobuiltinmemcpy() +; NOSUPERSET-LABEL: nobuiltinmemset +; NOSUPERSET: call i32 @nobuiltinmemcpy() +} + +attributes #0 = { "no-builtin-memcpy" } +attributes #1 = { "no-builtins" } +attributes #2 = { "no-builtin-memset" }