diff --git a/llvm/lib/Transforms/IPO/Inliner.cpp b/llvm/lib/Transforms/IPO/Inliner.cpp --- a/llvm/lib/Transforms/IPO/Inliner.cpp +++ b/llvm/lib/Transforms/IPO/Inliner.cpp @@ -876,8 +876,8 @@ // trigger infinite inlining, much like is prevented within the inliner // itself by the InlineHistory above, but spread across CGSCC iterations // and thus hidden from the full inline history. - if (CG.lookupSCC(*CG.lookup(Callee)) == C && - UR.InlinedInternalEdges.count({&N, C})) { + LazyCallGraph::SCC *CalleeSCC = CG.lookupSCC(*CG.lookup(Callee)); + if (CalleeSCC == C && UR.InlinedInternalEdges.count({&N, C})) { LLVM_DEBUG(dbgs() << "Skipping inlining internal SCC edge from a node " "previously split out of this SCC by inlining: " << F.getName() << " -> " << Callee.getName() << "\n"); @@ -935,9 +935,15 @@ if (tryPromoteCall(*ICB)) NewCallee = ICB->getCalledFunction(); } - if (NewCallee) - if (!NewCallee->isDeclaration()) + if (NewCallee) { + if (!NewCallee->isDeclaration()) { Calls->push({ICB, NewHistoryID}); + if (CalleeSCC != C && + CalleeSCC == CG.lookupSCC(CG.get(*NewCallee))) { + ICB->addFnAttr(Attribute::NoInline); + } + } + } } } diff --git a/llvm/test/Transforms/Inline/mut-rec-scc-2.ll b/llvm/test/Transforms/Inline/mut-rec-scc-2.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/mut-rec-scc-2.ll @@ -0,0 +1,19 @@ +; RUN: opt -S -passes='inline' < %s | FileCheck %s + +; Make sure we don't mark calls within the same SCC as original function with noinline. +; CHECK-NOT: noinline + +define void @samescc1() { + call void @samescc2() + ret void +} + +define void @samescc2() { + call void @samescc3() + ret void +} + +define void @samescc3() { + call void @samescc1() + ret void +} diff --git a/llvm/test/Transforms/Inline/mut-rec-scc.ll b/llvm/test/Transforms/Inline/mut-rec-scc.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/mut-rec-scc.ll @@ -0,0 +1,68 @@ +; RUN: opt -S -passes='cgscc(inline,instcombine)' < %s | FileCheck %s + +; We use call to a dummy function to avoid inlining test1 into test2 or vice +; versa, such that we aren't left with a trivial cycle, as trivial cycles are +; special-cased to never be inlined. +; However, InstCombine will eliminate these calls after inlining, and thus +; make the functions eligible for inlining in their callers. +declare void @dummy() readnone nounwind willreturn + +define void @test1() { +; CHECK-LABEL: define void @test1( +; CHECK-NEXT: call void @test2() +; CHECK-NEXT: call void @test2() +; CHECK-NEXT: ret void +; + call void @test2() + call void @test2() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + ret void +} + +define void @test2() { +; CHECK-LABEL: define void @test2( +; CHECK-NEXT: call void @test1() +; CHECK-NEXT: call void @test1() +; CHECK-NEXT: ret void +; + call void @test1() + call void @test1() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + call void @dummy() + ret void +} + +; We should inline the @test2 calls and mark the inlined @test1 calls as noinline +define void @test3() { +; CHECK-LABEL: define void @test3( +; CHECK-NEXT: call void @test1() #[[NOINLINE:[0-9]+]] +; CHECK-NEXT: call void @test1() #[[NOINLINE]] +; CHECK-NEXT: call void @test1() #[[NOINLINE]] +; CHECK-NEXT: call void @test1() #[[NOINLINE]] +; CHECK-NEXT: ret void +; + call void @test2() + call void @test2() + ret void +} + +; CHECK: [[NOINLINE]] = { noinline }