Index: include/llvm/Analysis/CGSCCPassManager.h =================================================================== --- include/llvm/Analysis/CGSCCPassManager.h +++ include/llvm/Analysis/CGSCCPassManager.h @@ -293,7 +293,9 @@ InvalidSCCSet, nullptr, nullptr}; PreservedAnalyses PA = PreservedAnalyses::all(); - for (LazyCallGraph::RefSCC &InitialRC : CG.postorder_ref_sccs()) { + for (auto RCI = CG.postorder_ref_scc_begin(), + RCE = CG.postorder_ref_scc_end(); + RCI != RCE;) { assert(RCWorklist.empty() && "Should always start with an empty RefSCC worklist"); // The postorder_ref_sccs range we are walking is lazily constructed, so @@ -304,7 +306,10 @@ // to update as the program is simplified and allows us to have greater // cache locality as forming a RefSCC touches all the parts of all the // functions within that RefSCC. - RCWorklist.insert(&InitialRC); + // + // We also eagerly increment the iterator to the next position because + // the CGSCC passes below may delete the current RefSCC. + RCWorklist.insert(&*RCI++); do { LazyCallGraph::RefSCC *RC = RCWorklist.pop_back_val(); @@ -381,6 +386,10 @@ dbgs() << "Re-running SCC passes after a refinement of the " "current SCC: " << *UR.UpdatedC << "\n"; + + // Note that both `C` and `RC` may at this point refer to deleted, + // invalid SCC and RefSCCs respectively. But we will short circuit + // the processing when we check them in the loop above. } while (UR.UpdatedC); } while (!CWorklist.empty()); Index: include/llvm/Transforms/IPO/Inliner.h =================================================================== --- include/llvm/Transforms/IPO/Inliner.h +++ include/llvm/Transforms/IPO/Inliner.h @@ -1,4 +1,4 @@ -//===- InlinerPass.h - Code common to all inliners --------------*- C++ -*-===// +//===- LegacyInlinerBase.h - Inliner pass and infrastructure ----*- C++ -*-===// // // The LLVM Compiler Infrastructure // @@ -6,19 +6,14 @@ // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// -// -// This file defines a simple policy-based bottom-up inliner. This file -// implements all of the boring mechanics of the bottom-up inlining, while the -// subclass determines WHAT to inline, which is the much more interesting -// component. -// -//===----------------------------------------------------------------------===// -#ifndef LLVM_TRANSFORMS_IPO_INLINERPASS_H -#define LLVM_TRANSFORMS_IPO_INLINERPASS_H +#ifndef LLVM_TRANSFORMS_IPO_INLINER_H +#define LLVM_TRANSFORMS_IPO_INLINER_H +#include "llvm/Analysis/CGSCCPassManager.h" #include "llvm/Analysis/CallGraphSCCPass.h" #include "llvm/Analysis/InlineCost.h" +#include "llvm/Analysis/LazyCallGraph.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Transforms/Utils/ImportedFunctionsInliningStatistics.h" @@ -29,13 +24,13 @@ class InlineCost; class OptimizationRemarkEmitter; class ProfileSummaryInfo; -template class SmallPtrSet; /// This class contains all of the helper code which is used to perform the -/// inlining operations that do not depend on the policy. -struct Inliner : public CallGraphSCCPass { - explicit Inliner(char &ID); - explicit Inliner(char &ID, bool InsertLifetime); +/// inlining operations that do not depend on the policy. It contains the core +/// bottom-up inlining infrastructure that specific inliner passes use. +struct LegacyInlinerBase : public CallGraphSCCPass { + explicit LegacyInlinerBase(char &ID); + explicit LegacyInlinerBase(char &ID, bool InsertLifetime); /// For this class, we declare that we require and preserve the call graph. /// If the derived class implements this method, it should always explicitly @@ -82,6 +77,33 @@ ImportedFunctionsInliningStatistics ImportedFunctionsStats; }; +/// The inliner pass for the new pass manager. +/// +/// This pass wires together the inlining utilities and the inline cost +/// analysis into a CGSCC pass. It considers every call in every function in +/// the SCC and tries to inline if profitable. It can be tuned with a number of +/// parameters to control what cost model is used and what tradeoffs are made +/// when making the decision. +/// +/// It should be noted that the legacy inliners do considerably more than this +/// inliner pass does. They provide logic for manually merging allocas, and +/// doing considerable DCE including the DCE of dead functions. This pass makes +/// every attempt to be simpler. DCE of functions requires complex reasoning +/// about comdat groups, etc. Instead, it is expected that other more focused +/// passes be composed to achieve the same end result. +class InlinerPass : public PassInfoMixin { +public: + InlinerPass(InlineParams Params = llvm::getInlineParams()) + : Params(std::move(Params)) {} + + PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, + LazyCallGraph &CG, CGSCCUpdateResult &UR); + +private: + InlineParams Params; +}; + + } // End llvm namespace #endif Index: include/llvm/Transforms/Utils/Cloning.h =================================================================== --- include/llvm/Transforms/Utils/Cloning.h +++ include/llvm/Transforms/Utils/Cloning.h @@ -194,9 +194,17 @@ /// inlined from the callee. This is only filled in if CG is non-null. SmallVector InlinedCalls; + /// All of the new call sites inlined into the caller. + /// + /// 'InlineFunction' fills this in by scanning the inlined instructions, and + /// only of CG is null. If CG is non-null, instead the value handle + /// `InlinedCalls` above is used. + SmallVector InlinedCallSites; + void reset() { StaticAllocas.clear(); InlinedCalls.clear(); + InlinedCallSites.clear(); } }; Index: lib/Analysis/InlineCost.cpp =================================================================== --- lib/Analysis/InlineCost.cpp +++ lib/Analysis/InlineCost.cpp @@ -638,7 +638,7 @@ bool HotCallsite = false; uint64_t TotalWeight; - if (CS.getInstruction()->extractProfTotalWeight(TotalWeight) && + if (PSI && CS.getInstruction()->extractProfTotalWeight(TotalWeight) && PSI->isHotCount(TotalWeight)) { HotCallsite = true; } @@ -647,14 +647,14 @@ // when it would increase the threshold and the caller does not need to // minimize its size. bool InlineHint = Callee.hasFnAttribute(Attribute::InlineHint) || - PSI->isFunctionEntryHot(&Callee); + (PSI && PSI->isFunctionEntryHot(&Callee)); if (InlineHint && !Caller->optForMinSize()) Threshold = MaxIfValid(Threshold, Params.HintThreshold); if (HotCallsite && !Caller->optForMinSize()) Threshold = MaxIfValid(Threshold, Params.HotCallSiteThreshold); - bool ColdCallee = PSI->isFunctionEntryCold(&Callee); + bool ColdCallee = PSI && PSI->isFunctionEntryCold(&Callee); // For cold callees, use the ColdThreshold knob if it is available and reduces // the threshold. if (ColdCallee) Index: lib/Passes/PassBuilder.cpp =================================================================== --- lib/Passes/PassBuilder.cpp +++ lib/Passes/PassBuilder.cpp @@ -72,6 +72,7 @@ #include "llvm/Transforms/IPO/GlobalOpt.h" #include "llvm/Transforms/IPO/GlobalSplit.h" #include "llvm/Transforms/IPO/InferFunctionAttrs.h" +#include "llvm/Transforms/IPO/Inliner.h" #include "llvm/Transforms/IPO/Internalize.h" #include "llvm/Transforms/IPO/LowerTypeTests.h" #include "llvm/Transforms/IPO/PartialInlining.h" Index: lib/Passes/PassRegistry.def =================================================================== --- lib/Passes/PassRegistry.def +++ lib/Passes/PassRegistry.def @@ -86,6 +86,7 @@ #endif CGSCC_PASS("invalidate", InvalidateAllAnalysesPass()) CGSCC_PASS("function-attrs", PostOrderFunctionAttrsPass()) +CGSCC_PASS("inline", InlinerPass()) CGSCC_PASS("no-op-cgscc", NoOpCGSCCPass()) #undef CGSCC_PASS Index: lib/Transforms/IPO/AlwaysInliner.cpp =================================================================== --- lib/Transforms/IPO/AlwaysInliner.cpp +++ lib/Transforms/IPO/AlwaysInliner.cpp @@ -26,8 +26,9 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" -#include "llvm/Transforms/IPO/InlinerPass.h" #include "llvm/Transforms/Utils/Cloning.h" +#include "llvm/Transforms/IPO.h" +#include "llvm/Transforms/IPO/Inliner.h" using namespace llvm; @@ -62,14 +63,14 @@ /// /// Unlike the \c AlwaysInlinerPass, this uses the more heavyweight \c Inliner /// base class to provide several facilities such as array alloca merging. -class AlwaysInlinerLegacyPass : public Inliner { +class AlwaysInlinerLegacyPass : public LegacyInlinerBase { public: - AlwaysInlinerLegacyPass() : Inliner(ID, /*InsertLifetime*/ true) { + AlwaysInlinerLegacyPass() : LegacyInlinerBase(ID, /*InsertLifetime*/ true) { initializeAlwaysInlinerLegacyPassPass(*PassRegistry::getPassRegistry()); } - AlwaysInlinerLegacyPass(bool InsertLifetime) : Inliner(ID, InsertLifetime) { + AlwaysInlinerLegacyPass(bool InsertLifetime) : LegacyInlinerBase(ID, InsertLifetime) { initializeAlwaysInlinerLegacyPassPass(*PassRegistry::getPassRegistry()); } Index: lib/Transforms/IPO/InlineSimple.cpp =================================================================== --- lib/Transforms/IPO/InlineSimple.cpp +++ lib/Transforms/IPO/InlineSimple.cpp @@ -25,7 +25,7 @@ #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" #include "llvm/Transforms/IPO.h" -#include "llvm/Transforms/IPO/InlinerPass.h" +#include "llvm/Transforms/IPO/Inliner.h" using namespace llvm; @@ -38,16 +38,16 @@ /// The common implementation of the inlining logic is shared between this /// inliner pass and the always inliner pass. The two passes use different cost /// analyses to determine when to inline. -class SimpleInliner : public Inliner { +class SimpleInliner : public LegacyInlinerBase { InlineParams Params; public: - SimpleInliner() : Inliner(ID), Params(llvm::getInlineParams()) { + SimpleInliner() : LegacyInlinerBase(ID), Params(llvm::getInlineParams()) { initializeSimpleInlinerPass(*PassRegistry::getPassRegistry()); } - explicit SimpleInliner(InlineParams Params) : Inliner(ID), Params(Params) { + explicit SimpleInliner(InlineParams Params) : LegacyInlinerBase(ID), Params(Params) { initializeSimpleInlinerPass(*PassRegistry::getPassRegistry()); } @@ -101,10 +101,10 @@ bool SimpleInliner::runOnSCC(CallGraphSCC &SCC) { TTIWP = &getAnalysis(); - return Inliner::runOnSCC(SCC); + return LegacyInlinerBase::runOnSCC(SCC); } void SimpleInliner::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); - Inliner::getAnalysisUsage(AU); + LegacyInlinerBase::getAnalysisUsage(AU); } Index: lib/Transforms/IPO/Inliner.cpp =================================================================== --- lib/Transforms/IPO/Inliner.cpp +++ lib/Transforms/IPO/Inliner.cpp @@ -13,6 +13,7 @@ // //===----------------------------------------------------------------------===// +#include "llvm/Transforms/IPO/Inliner.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/AliasAnalysis.h" @@ -26,12 +27,12 @@ #include "llvm/IR/CallSite.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/DiagnosticInfo.h" +#include "llvm/IR/InstIterator.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" -#include "llvm/Transforms/IPO/InlinerPass.h" #include "llvm/Transforms/Utils/Cloning.h" #include "llvm/Transforms/Utils/Local.h" using namespace llvm; @@ -76,15 +77,15 @@ cl::Hidden, cl::desc("Enable inliner stats for imported functions")); } // namespace -Inliner::Inliner(char &ID) : CallGraphSCCPass(ID), InsertLifetime(true) {} +LegacyInlinerBase::LegacyInlinerBase(char &ID) : CallGraphSCCPass(ID), InsertLifetime(true) {} -Inliner::Inliner(char &ID, bool InsertLifetime) +LegacyInlinerBase::LegacyInlinerBase(char &ID, bool InsertLifetime) : CallGraphSCCPass(ID), InsertLifetime(InsertLifetime) {} /// For this class, we declare that we require and preserve the call graph. /// If the derived class implements this method, it should /// always explicitly call the implementation here. -void Inliner::getAnalysisUsage(AnalysisUsage &AU) const { +void LegacyInlinerBase::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequired(); AU.addRequired(); AU.addRequired(); @@ -409,13 +410,13 @@ return false; } -bool Inliner::doInitialization(CallGraph &CG) { +bool LegacyInlinerBase::doInitialization(CallGraph &CG) { if (InlinerFunctionImportStats != InlinerFunctionImportStatsOpts::No) ImportedFunctionsStats.setModuleInfo(CG.getModule()); return false; // No changes to CallGraph. } -bool Inliner::runOnSCC(CallGraphSCC &SCC) { +bool LegacyInlinerBase::runOnSCC(CallGraphSCC &SCC) { if (skipSCC(SCC)) return false; return inlineCalls(SCC); @@ -630,7 +631,7 @@ return Changed; } -bool Inliner::inlineCalls(CallGraphSCC &SCC) { +bool LegacyInlinerBase::inlineCalls(CallGraphSCC &SCC) { CallGraph &CG = getAnalysis().getCallGraph(); ACT = &getAnalysis(); PSI = getAnalysis().getPSI(); @@ -655,7 +656,7 @@ /// Remove now-dead linkonce functions at the end of /// processing to avoid breaking the SCC traversal. -bool Inliner::doFinalization(CallGraph &CG) { +bool LegacyInlinerBase::doFinalization(CallGraph &CG) { if (InlinerFunctionImportStats != InlinerFunctionImportStatsOpts::No) ImportedFunctionsStats.dump(InlinerFunctionImportStats == InlinerFunctionImportStatsOpts::Verbose); @@ -663,7 +664,7 @@ } /// Remove dead functions that are not included in DNR (Do Not Remove) list. -bool Inliner::removeDeadFunctions(CallGraph &CG, bool AlwaysInlineOnly) { +bool LegacyInlinerBase::removeDeadFunctions(CallGraph &CG, bool AlwaysInlineOnly) { SmallVector FunctionsToRemove; SmallVector DeadFunctionsInComdats; SmallDenseMap ComdatEntriesAlive; @@ -765,3 +766,166 @@ } return true; } + +PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC, + CGSCCAnalysisManager &AM, LazyCallGraph &CG, + CGSCCUpdateResult &UR) { + FunctionAnalysisManager &FAM = + AM.getResult(InitialC, CG).getManager(); + const ModuleAnalysisManager &MAM = + AM.getResult(InitialC, CG).getManager(); + bool Changed = false; + + assert(InitialC.size() > 0 && "Cannot handle an empty SCC!"); + Module &M = *InitialC.begin()->getFunction().getParent(); + auto *PSI = MAM.getCachedResult(M); + + std::function GetAssumptionCache = + [&](Function &F) -> AssumptionCache & { + return FAM.getResult(F); + }; + + // Setup the data structure used to plumb customization into the + // `InlineFunction` routine. + // + // FIXME: We should make the `GetAssumptionCache` below a passed by value + // lambda that is converted to a function_ref instead of the address of + // a std::function. + InlineFunctionInfo IFI(/*cg=*/nullptr, &GetAssumptionCache); + + auto GetInlineCost = [&](CallSite CS) { + Function &Callee = *CS.getCalledFunction(); + auto &CalleeTTI = FAM.getResult(Callee); + return llvm::getInlineCost(CS, Params, CalleeTTI, GetAssumptionCache, PSI); + }; + + // We use a worklist of nodes to process so that we can handle if the SCC + // structure changes and some nodes are no longer part of the current SCC. We + // also need to use an updatable pointer for the SCC as a consequence. + SmallVector Nodes; + for (auto &N : InitialC) + Nodes.push_back(&N); + auto *C = &InitialC; + auto *RC = &C->getOuterRefSCC(); + + // We also use a secondary worklist of calls to allow quickly continuing to + // inline through newly inlined call sites where possible. + SmallVector Calls; + + // Track a set vector of inlined callees so that we can augment the caller + // with all of their edges in the call graph before pruning out the ones that + // got simplified away. + SmallSetVector InlinedCallees; + + // Track a set of dead functions to delete once finished with inlining calls. + // We defer deleting these to make it easier to handle the call graph + // updates. + SmallPtrSet DeadFunctions; + + do { + auto &N = *Nodes.pop_back_val(); + if (CG.lookupSCC(N) != C) + continue; + Function &F = N.getFunction(); + if (F.hasFnAttribute(Attribute::OptimizeNone)) + continue; + + // Get the remarks emission analysis for the caller. + auto &ORE = FAM.getResult(F); + + for (Instruction &I : instructions(F)) + if (auto CS = CallSite(&I)) + if (Function *Callee = CS.getCalledFunction()) + if (!Callee->isDeclaration()) + Calls.push_back(CS); + + bool DidInline = false; + while (!Calls.empty()) { + CallSite CS = Calls.pop_back_val(); + Function &Callee = *CS.getCalledFunction(); + + // Check whether we want to inline this callsite. + if (!shouldInline(CS, GetInlineCost, ORE)) + continue; + + if (!InlineFunction(CS, IFI)) + continue; + DidInline = true; + InlinedCallees.insert(&Callee); + + // Add any new callsites to the worklist. + Calls.append(IFI.InlinedCallSites.begin(), IFI.InlinedCallSites.end()); + + // For local functions, check whether this makes the callee trivially + // dead. In that case, we can drop the body of the function eagerly + // which may reduce the number of callers of other functions to one, + // changing inline cost thresholds. + if (Callee.hasLocalLinkage()) { + // To check this we also need to nuke any dead constant uses (perhaps + // made dead by this operation on other functions). + Callee.removeDeadConstantUsers(); + if (Callee.use_empty()) + // Note that after this point, it is an error to do anything other + // than use the callee's address or delete it. + Callee.dropAllReferences(); + } + } + + if (!DidInline) + continue; + Changed = true; + + // Add all the inlined callees' edges to the caller. These are by + // definition trivial edges as we already had a transitive call edge to the + // callee. + for (Function *InlinedCallee : InlinedCallees) { + LazyCallGraph::Node &CalleeN = *CG.lookup(*InlinedCallee); + for (LazyCallGraph::Edge &E : CalleeN) + if (E.isCall()) + RC->insertTrivialCallEdge(N, *E.getNode()); + else + RC->insertTrivialRefEdge(N, *E.getNode()); + } + + // At this point, since we have made changes we have at least removed + // a call instruction. However, in the process we do some incremental + // simplification of the surrounding code. This simplification can + // essentially do all of the same things as a function pass and we can + // re-use the exact same logic for updating the call graph to reflect the + // change.. + C = &updateCGAndAnalysisManagerForFunctionPass(CG, *C, N, AM, UR, + /*DebugLogging*/ true); + RC = &C->getOuterRefSCC(); + + // And remember all of the inlined callees that ended up dead after + // inlining. + for (Function *InlinedCallee : InlinedCallees) + if (InlinedCallee->hasLocalLinkage() && InlinedCallee->use_empty()) + DeadFunctions.insert(InlinedCallee); + InlinedCallees.clear(); + } while (!Nodes.empty()); + + // Now that we've finished inlining all of the calls across this SCC, delete + // all of the trivially dead functions, updating the call graph and the CGSCC + // pass manager in the process. + // + // Note that this walks a pointer set which has non-deterministic order but + // that is OK as all we do is delete things and add pointers to unordered + // sets. + for (Function *DeadF : DeadFunctions) { + // Get the necessary information out of the call graph and nuke the + // function there. + auto &DeadC = *CG.lookupSCC(*CG.lookup(*DeadF)); + auto &DeadRC = DeadC.getOuterRefSCC(); + CG.removeDeadFunction(*DeadF); + + // Mark the relevant parts of the call graph as invalid so we don't visit + // them. + UR.InvalidatedSCCs.insert(&DeadC); + UR.InvalidatedRefSCCs.insert(&DeadRC); + + // And delete the actual function from the module. + M.getFunctionList().erase(DeadF); + } + return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); +} Index: lib/Transforms/Utils/InlineFunction.cpp =================================================================== --- lib/Transforms/Utils/InlineFunction.cpp +++ lib/Transforms/Utils/InlineFunction.cpp @@ -1640,8 +1640,17 @@ } // Update the callgraph if requested. - if (IFI.CG) + if (IFI.CG) { UpdateCallGraphAfterInlining(CS, FirstNewBlock, VMap, IFI); + } else { + // Otherwise just collect the raw call sites that were inlined. + for (BasicBlock &NewBB : + make_range(FirstNewBlock->getIterator(), Caller->end())) + for (Instruction &I : NewBB) + if (auto CS = CallSite(&I)) + IFI.InlinedCallSites.push_back(CS); + } + // Update inlined instructions' line number information. fixupLineNumbers(Caller, FirstNewBlock, TheCall); Index: test/Transforms/Inline/basictest.ll =================================================================== --- test/Transforms/Inline/basictest.ll +++ test/Transforms/Inline/basictest.ll @@ -1,4 +1,5 @@ ; RUN: opt < %s -inline -sroa -S | FileCheck %s +; RUN: opt < %s -passes='cgscc(inline,function(sroa))' -S | FileCheck %s target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128" define i32 @test1f(i32 %i) { Index: test/Transforms/Inline/cgscc-update.ll =================================================================== --- /dev/null +++ test/Transforms/Inline/cgscc-update.ll @@ -0,0 +1,69 @@ +; RUN: opt < %s -aa-pipeline=basic-aa -passes='cgscc(function-attrs,inline)' -S | FileCheck %s +; This test runs the inliner and the function attribute deduction. It ensures +; that when the inliner mutates the call graph it correctly updates the CGSCC +; iteration so that we can compute refined function attributes. In this way it +; is leveraging function attribute computation to observe correct call graph +; updates. + +; Sanity check: this should get annotated as readnone. +; CHECK: Function Attrs: readnone +; CHECK-NEXT: declare void @readnone() +declare void @readnone() readnone + +; The 'test1_' prefixed functions are designed to trigger forming a new direct +; call in the inlined body of the function. After that, we form a new SCC and +; using that can deduce precise function attrs. + +; This function should no longer exist. +; CHECK-NOT: @test1_f() +define internal void @test1_f(void()* %p) { + call void %p() + ret void +} + +; This function should have had 'readnone' deduced for its SCC. +; CHECK: Function Attrs: noinline readnone +; CHECK-NEXT: define void @test1_g() +define void @test1_g() noinline { + call void @test1_f(void()* @test1_h) + ret void +} + +; This function should have had 'readnone' deduced for its SCC. +; CHECK: Function Attrs: noinline readnone +; CHECK-NEXT: define void @test1_h() +define void @test1_h() noinline { + call void @test1_g() + call void @readnone() + ret void +} + + +; The 'test2_' prefixed functions are designed to trigger forming a new direct +; call due to RAUW-ing the returned value of a called function into the caller. +; This too should form a new SCC which can then be reasoned about to compute +; precise function attrs. + +; This function should no longer exist. +; CHECK-NOT: @test2_f() +define internal void()* @test2_f() { + ret void()* @test2_h +} + +; This function should have had 'readnone' deduced for its SCC. +; CHECK: Function Attrs: noinline readnone +; CHECK-NEXT: define void @test2_g() +define void @test2_g() noinline { + %p = call void()* @test2_f() + call void %p() + ret void +} + +; This function should have had 'readnone' deduced for its SCC. +; CHECK: Function Attrs: noinline readnone +; CHECK-NEXT: define void @test2_h() +define void @test2_h() noinline { + call void @test2_g() + call void @readnone() + ret void +} Index: test/Transforms/Inline/nested-inline.ll =================================================================== --- test/Transforms/Inline/nested-inline.ll +++ test/Transforms/Inline/nested-inline.ll @@ -1,4 +1,5 @@ ; RUN: opt < %s -inline -S | FileCheck %s +; RUN: opt < %s -passes='cgscc(inline)' -S | FileCheck %s ; Test that bar and bar2 are both inlined throughout and removed. @A = weak global i32 0 ; [#uses=1] @B = weak global i32 0 ; [#uses=1]