diff --git a/llvm/include/llvm/Analysis/LazyCallGraph.h b/llvm/include/llvm/Analysis/LazyCallGraph.h --- a/llvm/include/llvm/Analysis/LazyCallGraph.h +++ b/llvm/include/llvm/Analysis/LazyCallGraph.h @@ -551,6 +551,7 @@ class RefSCC { friend class LazyCallGraph; friend class LazyCallGraph::Node; + friend class CallGraphUpdater; LazyCallGraph *G; diff --git a/llvm/include/llvm/Transforms/Utils/CallGraphUpdater.h b/llvm/include/llvm/Transforms/Utils/CallGraphUpdater.h --- a/llvm/include/llvm/Transforms/Utils/CallGraphUpdater.h +++ b/llvm/include/llvm/Transforms/Utils/CallGraphUpdater.h @@ -30,6 +30,9 @@ CGSCCAnalysisManager *AM = nullptr; CGSCCUpdateResult *UR = nullptr; + LazyCallGraph::Node &createNode(Function &Fn); + void addNodeToSCC(LazyCallGraph::Node &N, LazyCallGraph::SCC &SCC); + public: CallGraphUpdater() {} CallGraphUpdater(CallGraph &CG) : CG(&CG) {} @@ -49,6 +52,13 @@ /// still needs to be re-analyzed or manually updated. void registerOutlinedFunction(Function &NewFn, LazyCallGraph::SCC *SCC = nullptr); + + /// If a new function was created by outlining, and the new function is only + /// referred to by the original function (i.e.: not called directly), this + /// method can be called to update the call graph for the new function. + void + registerReferredToOutlinedFunction(Function &NewFn, + LazyCallGraph::RefSCC *RefSCC = nullptr); }; } // end namespace llvm diff --git a/llvm/lib/Transforms/Utils/CallGraphUpdater.cpp b/llvm/lib/Transforms/Utils/CallGraphUpdater.cpp --- a/llvm/lib/Transforms/Utils/CallGraphUpdater.cpp +++ b/llvm/lib/Transforms/Utils/CallGraphUpdater.cpp @@ -16,6 +16,23 @@ using namespace llvm; +LazyCallGraph::Node &CallGraphUpdater::createNode(Function &Fn) { + assert(LCG); + assert(!LCG->lookup(Fn) && "node already exists"); + + LazyCallGraph::Node &CGNode = LCG->get(Fn); + LCG->NodeMap[&Fn] = &CGNode; + CGNode.DFSNumber = CGNode.LowLink = -1; + CGNode.populate(); + return CGNode; +} + +void CallGraphUpdater::addNodeToSCC(LazyCallGraph::Node &N, + LazyCallGraph::SCC &SCC) { + SCC.Nodes.push_back(&N); + LCG->SCCMap[&N] = &SCC; +} + void CallGraphUpdater::reanalyzeFunction(Function &Fn) { if (CG) { CallGraphNode *OldCGN = (*CG)[&Fn]; @@ -34,12 +51,27 @@ if (CG) { CG->addToCallGraph(&NewFn); } else if (LCG) { - LazyCallGraph::Node &CGNode = LCG->get(NewFn); - CGNode.DFSNumber = CGNode.LowLink = -1; - CGNode.populate(); - SCC->Nodes.push_back(&CGNode); - LCG->SCCMap[&CGNode] = SCC; - LCG->NodeMap[&NewFn] = &CGNode; + LazyCallGraph::Node &CGNode = createNode(NewFn); + addNodeToSCC(CGNode, *SCC); + } +} + +void CallGraphUpdater::registerReferredToOutlinedFunction( + Function &NewFn, LazyCallGraph::RefSCC *RefSCC) { + if (CG) { + llvm_unreachable("registering referred to functions is not supported " + "with CallGraph"); + } else if (LCG) { + assert(RefSCC && "when using LazyCallGraph RefSCC must not be null"); + + LazyCallGraph::Node &CGNode = createNode(NewFn); + SmallVector Nodes; + Nodes.push_back(&CGNode); + auto *SCC = LCG->createSCC(*RefSCC, Nodes); + addNodeToSCC(CGNode, *SCC); + + RefSCC->SCCIndices[SCC] = RefSCC->SCCIndices.size(); + RefSCC->SCCs.push_back(SCC); } } diff --git a/llvm/unittests/Analysis/CGSCCPassManagerTest.cpp b/llvm/unittests/Analysis/CGSCCPassManagerTest.cpp --- a/llvm/unittests/Analysis/CGSCCPassManagerTest.cpp +++ b/llvm/unittests/Analysis/CGSCCPassManagerTest.cpp @@ -1560,5 +1560,54 @@ MPM.run(*M, MAM); } +TEST_F(CGSCCPassManagerTest, TestInsertionOfNewRefSCC) { + std::unique_ptr M = parseIR("define void @f() {\n" + "entry:\n" + " call void @f()\n" + " ret void\n" + "}\n"); + + CGSCCPassManager CGPM(/*DebugLogging*/ true); + CGPM.addPass(LambdaSCCPassNoPreserve( + [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, + CGSCCUpdateResult &UR) { + CallGraphUpdater CGUpdater(CG, AM, UR); + for (auto &N : C) { + auto &F = N.getFunction(); + if (F.getName() != "f") + continue; + auto *Call = dyn_cast(F.begin()->begin()); + if (!Call || Call->getCalledFunction()->getName() != "f") + continue; + + // Create a new function 'g' and use the CallGraphUpdater to insert + // a RefSCC, SCC, and node for 'g' into the call graph. As a result + // the call graph is composed of two RefSCCs, [(f)] and [(g)]. + auto *G = Function::Create(F.getFunctionType(), F.getLinkage(), + F.getAddressSpace(), "g", F.getParent()); + BasicBlock::Create(F.getParent()->getContext(), "entry", G); + CGUpdater.registerReferredToOutlinedFunction(*G, &C.getOuterRefSCC()); + + // "Demote" the 'f -> f' call egde to a ref edge. + // 1. Erase the call edge from 'f' to 'f'. + Call->eraseFromParent(); + // 2. Insert a ref edge from 'f' to 'f'. + (void)CastInst::CreatePointerCast(&F, + Type::getInt8PtrTy(F.getContext()), + "f.ref", &*F.begin()->begin()); + + ASSERT_NO_FATAL_FAILURE( + updateCGAndAnalysisManagerForCGSCCPass(CG, C, N, AM, UR)) + << "Updating the call graph with a demoted, self-referential " + "call edge 'f -> f', and a newly inserted ref edge 'f -> g', " + "caused a fatal failure"; + } + })); + + ModulePassManager MPM(/*DebugLogging*/ true); + MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); + MPM.run(*M, MAM); +} + #endif } // namespace